Add support for text annotations.

This commit is contained in:
Joachim Bauch 2021-12-10 16:54:42 +01:00
parent f76c0c6e78
commit 5e994f25a2
No known key found for this signature in database
GPG key ID: 77C1D22D53E15F02
6 changed files with 539 additions and 52 deletions

View file

@ -80,6 +80,11 @@
mask-image: url("../img/icon-line.svg");
}
.textMode::before {
-webkit-mask-image: url("../img/icon-text.svg");
mask-image: url("../img/icon-text.svg");
}
/*
// Provided by viewer.css
.toolbarButton.download::before {
@ -252,3 +257,39 @@
/* stylelint-disable-next-line max-line-length */
box-shadow: 0 1px 0 hsla(0, 0%, 100%, 0.05) inset, 0 0 1px hsla(0, 0%, 100%, 0.15) inset, 0 1px 0 hsla(0, 0%, 100%, 0.05);
}
.textareaContainer {
position: absolute !important;
border: none;
width: 300px;
max-width: 300px;
}
.textareaContainer textarea {
height: 200px;
max-height: 200px;
width: calc(100% - 2em);
max-width: calc(100% - 2em);
margin: 1em;
box-shadow: none;
}
.textareaContainer .deleteButton {
width: 20%;
height: 25px;
float: right;
margin-bottom: 5px;
font-size: 18px;
}
.textareaContainer .author {
display: inline-block;
margin-top: 12px;
margin-left: 12px;
padding: 0.5em;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border-radius: 4px;
}

12
img/icon-text.svg Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
<g>
<path fill="#FFFFFF" d="M4.565,2.609h22.89l0.217,6.879h-0.969c-0.475-2.162-1.078-3.568-1.809-4.221
c-0.732-0.651-2.27-0.978-4.615-0.978h-2.254v20.715c0,1.56,0.244,2.526,0.732,2.898c0.486,0.373,1.555,0.619,3.201,0.738v0.75
h-11.8v-0.75c1.713-0.133,2.78-0.414,3.202-0.848c0.421-0.431,0.632-1.52,0.632-3.264V4.289h-2.272
c-2.24,0-3.766,0.323-4.575,0.968c-0.811,0.646-1.42,2.056-1.829,4.231H4.328L4.565,2.609z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 908 B

24
package-lock.json generated
View file

@ -10,6 +10,8 @@
"dependencies": {
"@jaames/iro": "5.5.2",
"jquery": "3.6.0",
"jquery-ui": "1.13.0",
"lodash": "4.17.21",
"paper": "0.12.15",
"pdfjs-dist": "2.12.313",
"pdfjs-dist-viewer-min": "2.12.313",
@ -2104,6 +2106,14 @@
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw=="
},
"node_modules/jquery-ui": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.13.0.tgz",
"integrity": "sha512-Osf7ECXNTYHtKBkn9xzbIf9kifNrBhfywFEKxOeB/OVctVmLlouV9mfc2qXCp6uyO4Pn72PXKOnj09qXetopCw==",
"dependencies": {
"jquery": ">=1.8.0 <4.0.0"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -2235,8 +2245,7 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.merge": {
"version": "4.6.2",
@ -5670,6 +5679,14 @@
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw=="
},
"jquery-ui": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.13.0.tgz",
"integrity": "sha512-Osf7ECXNTYHtKBkn9xzbIf9kifNrBhfywFEKxOeB/OVctVmLlouV9mfc2qXCp6uyO4Pn72PXKOnj09qXetopCw==",
"requires": {
"jquery": ">=1.8.0 <4.0.0"
}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -5777,8 +5794,7 @@
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash.merge": {
"version": "4.6.2",

View file

@ -14,6 +14,8 @@
"dependencies": {
"@jaames/iro": "5.5.2",
"jquery": "3.6.0",
"jquery-ui": "1.13.0",
"lodash": "4.17.21",
"paper": "0.12.15",
"pdfjs-dist": "2.12.313",
"pdfjs-dist-viewer-min": "2.12.313",

View file

@ -21,6 +21,8 @@
*/
import $ from 'jquery';
import 'jquery-ui/ui/widgets/draggable';
import { each, throttle } from 'lodash';
import io from 'socket.io-client';
import iro from '@jaames/iro';
import paper from 'paper';
@ -145,7 +147,7 @@ BaseDrawer.prototype.onMouseLeave = function(page_annotator, event) {};
BaseDrawer.prototype.onMouseMove = function(page_annotator, event) {};
BaseDrawer.prototype.onClick = function(page_annotator, event) {};
BaseDrawer.prototype.onKeyUp = function(page_annotator, event) {};
BaseDrawer.prototype.onItemMoved = function(page_annotator, name, item, event) {};
BaseDrawer.prototype.onItemMoved = function(page_annotator, item, event) {};
var NullDrawer = function() {
BaseDrawer.apply(this, arguments);
@ -341,23 +343,31 @@ SelectDrawer.prototype._select = function(item) {
this._unselect();
this.selected_item = item;
this.selected_item.shadowColor = "black";
this.selected_item.shadowBlur = 10;
var color = item.strokeColor.toCSS(true);
if (item.strokeColor.hasAlpha() && color.length <= 7) {
color += Math.round(item.strokeColor.alpha * 255).toString(16);
if (this.selected_item.onFocus) {
var result = this.selected_item.onFocus();
if (result) {
this.selected_item = result;
}
} else {
this.selected_item.shadowColor = "black";
this.selected_item.shadowBlur = 10;
var color = item.strokeColor.toCSS(true);
if (item.strokeColor.hasAlpha() && color.length <= 7) {
color += Math.round(item.strokeColor.alpha * 255).toString(16);
}
var settings = {
'color': color,
'strokeWidth': item.strokeWidth,
};
this.prev_settings = this.annotator.updateSettings(settings);
}
var settings = {
'color': color,
'strokeWidth': item.strokeWidth,
};
this.prev_settings = this.annotator.updateSettings(settings);
};
SelectDrawer.prototype._unselect = function() {
var reset;
if (this.prev_settings) {
if (this.selected_item) {
if (this.selected_item && !this.selected_item.onBlur) {
reset = {
'strokeColor': this.selected_item.strokeColor,
'strokeWidth': this.selected_item.strokeWidth,
@ -371,12 +381,16 @@ SelectDrawer.prototype._unselect = function() {
return;
}
if (reset) {
this.selected_item.strokeColor = reset.strokeColor;
this.selected_item.strokeWidth = reset.strokeWidth;
if (this.selected_item.onBlur) {
this.selected_item.onBlur();
} else {
if (reset) {
this.selected_item.strokeColor = reset.strokeColor;
this.selected_item.strokeWidth = reset.strokeWidth;
}
this.selected_item.shadowColor = null;
this.selected_item.shadowBlur = 0;
}
this.selected_item.shadowColor = null;
this.selected_item.shadowBlur = 0;
this.selected_item = null;
};
@ -418,24 +432,39 @@ SelectDrawer.prototype.destroy = function() {
this._unselect();
};
SelectDrawer.prototype.onMouseDown = function(page_annotator, event) {
var hit = page_annotator.scope.project.hitTest(event.point);
SelectDrawer.prototype._hitItem = function(page_annotator, point) {
var hit = page_annotator.scope.project.hitTest(point);
if (!hit) {
this._unselect();
return;
// Check if user clicked on a text annotation.
each(page_annotator.textAnnotations, function(ta) {
var elem = ta.textareaContainer;
if (!elem.is(':visible')) {
return;
}
var rect = elem[0].getBoundingClientRect();
if (rect.left <= point.x && rect.right >= point.x &&
rect.top <= point.y && rect.bottom >= point.y) {
hit = {
"item": ta,
};
}
});
if (!hit) {
this._unselect();
return;
}
}
this._select(hit.item);
};
SelectDrawer.prototype.onClick = function(page_annotator, event) {
var hit = page_annotator.scope.project.hitTest(event.point);
if (!hit) {
this._unselect();
return;
}
SelectDrawer.prototype.onMouseDown = function(page_annotator, event) {
this._hitItem(page_annotator, event.point);
};
this._select(hit.item);
SelectDrawer.prototype.onClick = function(page_annotator, event) {
this._hitItem(page_annotator, event.point);
};
SelectDrawer.prototype.onKeyUp = function(page_annotator, event) {
@ -443,30 +472,35 @@ SelectDrawer.prototype.onKeyUp = function(page_annotator, event) {
return;
}
if (this.selected_item.ignoreKeyUp && this.selected_item.ignoreKeyUp()) {
return;
}
switch (event.keyCode) {
case 8: // Backspace
// Fallthrough
case 46: // Delete key
this.annotator.deleteItem(page_annotator, this.selected_item);
this.selected_item.remove();
var item = this.selected_item;
this.annotator.deleteItem(page_annotator, item);
this._unselect();
item.remove();
break;
}
};
SelectDrawer.prototype.onItemMoved = function(page_annotator, name, item, event) {
SelectDrawer.prototype.onItemMoved = function(page_annotator, item, event) {
event.stopPropagation();
item.position.x += event.delta.x;
item.position.y += event.delta.y;
this.addPendingItem(page_annotator, name, item);
this.addPendingItem(page_annotator, item);
};
SelectDrawer.prototype.addPendingItem = function(page_annotator, name, item) {
if (this.pending_items.hasOwnProperty(name)) {
this.pending_items[name][0] = page_annotator;
this.pending_items[name][1] = item;
SelectDrawer.prototype.addPendingItem = function(page_annotator, item) {
if (this.pending_items.hasOwnProperty(item.name)) {
this.pending_items[item.name][0] = page_annotator;
this.pending_items[item.name][1] = item;
} else {
this.pending_items[name] = [page_annotator, item, Date.now()];
this.pending_items[item.name] = [page_annotator, item, Date.now()];
}
};
@ -482,7 +516,25 @@ SelectDrawer.prototype.updateSettings = function(page_annotator) {
this.selected_item.strokeColor = this.annotator.color;
this.selected_item.strokeWidth = this.annotator.strokeWidth;
this.addPendingItem(page_annotator, this.selected_item.name, this.selected_item);
this.addPendingItem(page_annotator, this.selected_item);
};
var TextDrawer = function(annotator) {
BaseDrawer.apply(this, arguments);
};
TextDrawer.prototype = Object.create(BaseDrawer.prototype);
TextDrawer.prototype.onClick = function(page_annotator, event) {
var author = this.annotator.displayname;
var textarea = page_annotator.createTextArea({
'x': event.point.x,
'y': event.point.y,
'author': author,
});
textarea.sendData();
textarea.onFocus();
textarea.textarea.trigger('focus');
this.annotator.setDrawMode("select");
};
var Cursor = function(annotator, userid, radius) {
@ -522,6 +574,7 @@ Cursor.prototype.draw = function(page_annotator, x, y, color, text) {
if (!this.circle) {
page_annotator.activate();
this.circle = new paper.Path.Circle(center, this.radius);
this.circle.name = getObjectId();
}
if (!this.page_annotator) {
this.page_annotator = page_annotator;
@ -564,12 +617,256 @@ Cursor.prototype.update = function() {
this.label.css('top', y - (this.radius));
};
var TextArea = function(page_annotator, text, color, author) {
this.page_annotator = page_annotator;
this.content = text;
this.color = color;
this.author = author;
this.modified = Math.round((new Date()).getTime() / 1000);
this.name = getObjectId();
this.textareaContainer = $('<div class="textareaContainer form-group shadow-textarea"></div>');
this.textareaContainer.draggable({
drag: function(event, ui) {
this.textareaContainerPos = [ui.position.left, ui.position.top];
this.update();
this.sendData();
}.bind(this),
stop: function(event, ui) {
this.textareaContainerPos = [ui.position.left, ui.position.top];
this.update();
this.sendData();
}.bind(this),
});
this.textareaContainer.draggable('disable');
this.textareaContainer.on('click', function(event) {
this.page_annotator.annotator.drawer.onClick(this.page_annotator, {
'point': {
'x': event.originalEvent.clientX,
'y': event.originalEvent.clientY,
},
});
}.bind(this));
this.textareaContainer.on('mouseup', function(event) {
this.page_annotator.annotator.drawer.onMouseUp(this.page_annotator, {
'point': {
'x': event.originalEvent.clientX,
'y': event.originalEvent.clientY,
},
});
}.bind(this));
this.page_annotator.container.append(this.textareaContainer);
this.circle = null;
this.line = null;
this.textarea = $('<textarea class="form-control" disabled="disabled"></textarea>');
this.authorLabel = $('<span class="author"</span>');
this.closeButton = $('<button type="button" disabled="disabled" class="deleteButton btn btn-danger form-control close" aria-label="Close"> <span aria-hidden="true">&times;</span></button>');
this.closeButton.on('click', function() {
this.textareaContainer.hide();
this.update();
}.bind(this));
this.textarea.on('input', function(e) {
if (this.content === this.textarea.val()) {
return;
}
this.content = this.textarea.val();
this.author = this.page_annotator.annotator.displayname;
this.modified = Math.round((new Date()).getTime() / 1000);
this.update();
this.sendData();
}.bind(this));
this.textarea.on('focus', function(e) {
this.onFocus();
}.bind(this));
this.textareaContainer.append(this.authorLabel, this.closeButton, this.textarea);
this.textareaContainer.css("background-color", this.color);
this.textarea.css("font-size", this.page_annotator.annotator.fontSize+"px");
this._sendData = throttle(function() {
this.page_annotator.annotator.sendItem(this.page_annotator, this);
}.bind(this), 250);
};
TextArea.prototype.exportJSON = function() {
var data = {
"type": "text-annotation",
"anchor": [
this.circle.position.x,
this.circle.position.y,
],
"author": this.author,
"color": this.color,
"content": this.content,
"modified": this.modified,
"pos": [
this.textareaContainerPos[0] / this.page_annotator.scale,
this.textareaContainerPos[1] / this.page_annotator.scale,
],
};
return JSON.stringify(data);
};
TextArea.prototype.sendData = function() {
this._sendData();
};
TextArea.prototype.draw = function(x, y) {
this.x = x;
this.y = y;
if (!this.circle) {
this.drawCircle();
}
if (!this.textareaContainerPos) {
this.textareaContainerPos = [x, y];
}
this.update();
};
TextArea.prototype.drawCircle = function() {
var center = new paper.Point(this.x, this.y);
var circle = new paper.Path.Circle(center, 10);
circle.name = this.name+"pointer";
circle.set({
fillColor: this.color,
shadowColor: this.color,
shadowBlur: 2,
position: center
});
circle.onClick = function(e) {
this.textareaContainer.show();
this.textarea.trigger('blur');
this.update();
}.bind(this);
circle.onFocus = function() {
this.onFocus();
return this;
}.bind(this);
circle.onBlur = function() {
this.onBlur();
return this;
}.bind(this);
circle.onMouseDrag = function(event) {
event.stopPropagation();
this.textarea.trigger('blur');
this.circle.position.x += event.delta.x;
this.circle.position.y += event.delta.y;
this.update();
this.sendData();
}.bind(this);
circle.onMouseUp = function(event) {
this.update();
this.sendData();
}.bind(this);
this.circle = circle;
};
TextArea.prototype.update = function(page_annotator) {
this.authorLabel.text(this.author || 'Anonymous');
this.authorLabel.css('background-color', this.color);
this.textareaContainer.css('left', this.textareaContainerPos[0]*this.page_annotator.scale);
this.textareaContainer.css('top', this.textareaContainerPos[1]*this.page_annotator.scale);
this.textarea.css("font-size", this.page_annotator.annotator.fontSize+"px");
this.textarea.val(this.content);
if (this.line) {
this.line.remove();
this.line = null;
}
if (this.textareaContainer.is(':visible')) {
var from = new paper.Point(
this.circle.position.x,
this.circle.position.y);
var to = new paper.Point(
this.textareaContainerPos[0],
this.textareaContainerPos[1]);
this.line = new paper.Path.Line(from, to);
this.line.strokeColor = this.color;
this.line.strokeWidth = 2;
if (!$('textarea', this.textareaContainer).prop("disabled")) {
this.line.shadowColor = "black";
this.line.shadowBlur = 5;
}
this.line.onFocus = function() {
this.onFocus();
return this;
}.bind(this);
this.line.onBlur = function() {
this.onBlur();
return this;
}.bind(this);
}
};
TextArea.prototype.remove = function() {
this.circle.remove();
if (this.line) {
this.line.remove();
}
this.textareaContainer.remove();
delete this.page_annotator.textAnnotations[this.name];
};
TextArea.prototype.ignoreKeyUp = function() {
if (this.textarea.is(':focus')) {
// The textarea currently is focused, ignore keypresses in drawer,
// otherwise the element gets deleted when the user presses "delete".
return true;
}
return false;
};
TextArea.prototype.onFocus = function() {
if (this._selecting) {
return;
}
this._selecting = true;
$('textarea', this.textareaContainer).prop("disabled", false);
this.closeButton.prop("disabled", false);
this.textareaContainer.draggable("enable");
this.textareaContainer.css("box-shadow", "0px 0px 15px 5px"+this.color);
this.circle.shadowColor = "black";
this.circle.shadowBlur = 10;
if (this.line) {
this.line.shadowColor = "black";
this.line.shadowBlur = 5;
}
this._selecting = false;
};
TextArea.prototype.onBlur = function() {
if (this._unselecting) {
return;
}
this._unselecting = true;
$('textarea', this.textareaContainer).prop("disabled", true);
this.closeButton.prop("disabled", true);
this.textareaContainer.draggable("disable");
this.textareaContainer.css("box-shadow", "none");
this.circle.shadowColor = null;
this.circle.shadowBlur = 0;
if (this.line) {
this.line.shadowColor = null;
this.line.shadowBlur = 0;
}
this.textarea.trigger('blur');
this._unselecting = false;
};
TextArea.prototype.onResized = function(scale) {
this.page_annotator.container.append(this.textareaContainer);
this.draw(this.x, this.y);
};
var PageAnnotator = function(annotator, pagenum, container, page) {
this.annotator = annotator;
this.pagenum = pagenum;
this.container = container;
this.page = page;
this.pending_activation = {};
this.textAnnotations = {};
this.canvas = document.createElement("canvas");
this.canvas.style.position = "absolute";
this.canvas.style.left = 0;
@ -645,6 +942,28 @@ var PageAnnotator = function(annotator, pagenum, container, page) {
}.bind(this));
};
PageAnnotator.prototype.exportSVG = function() {
// Hide circle and line for text annotations so they don't show up in exported PDFs.
each(this.textAnnotations, function(ta) {
ta.circle.remove();
if (ta.line) {
ta.line.remove();
}
}.bind(this));
var svg = this.scope.project.exportSVG({
asString: true
});
each(this.textAnnotations, function(ta) {
this.scope.project.activeLayer.addChild(ta.circle);
if (ta.line) {
this.scope.project.activeLayer.addChild(ta.line);
}
}.bind(this));
return svg;
};
PageAnnotator.prototype.activate = function() {
PageAnnotator.prototype.__active = this;
for (var drawerName in this.pending_activation) {
@ -689,6 +1008,9 @@ PageAnnotator.prototype.update = function(scale) {
(width + (this.pagewidth * (1 - scale))) / 2,
(height + (this.pageheight * (1 - scale))) / 2);
this.view.zoom = scale;
each(this.textAnnotations, function(ta) {
ta.onResized();
});
this.view._needsUpdate = true;
this.view.requestUpdate();
};
@ -696,7 +1018,7 @@ PageAnnotator.prototype.update = function(scale) {
PageAnnotator.prototype.createPath = function(options) {
var path = new paper.Path(options || {});
path.onMouseDrag = function(event) {
this.annotator.drawer.onItemMoved(this, path.name, path, event);
this.annotator.drawer.onItemMoved(this, path, event);
}.bind(this);
return path;
};
@ -705,7 +1027,7 @@ PageAnnotator.prototype.createRectangle = function(options) {
var rect = new paper.Path.Rectangle(options || {});
rect.onMouseDrag = function(event) {
if (rect.ready) {
this.annotator.drawer.onItemMoved(this, rect.name, rect, event);
this.annotator.drawer.onItemMoved(this, rect, event);
}
}.bind(this);
return rect;
@ -723,12 +1045,34 @@ PageAnnotator.prototype.createEllipse = function(options) {
ellipse.name = options.name;
ellipse.onMouseDrag = function(event) {
if (ellipse.ready) {
this.annotator.drawer.onItemMoved(this, ellipse.name, ellipse, event);
this.annotator.drawer.onItemMoved(this, ellipse, event);
}
}.bind(this);
return ellipse;
};
PageAnnotator.prototype.createTextArea = function(options) {
var text = options.text || "";
var color = options.color || this.annotator.color;
var author = options.author || "";
var textarea = new TextArea(this, text, color, author);
if (options.name) {
textarea.name = options.name;
}
if (options.modified) {
textarea.modified = options.modified;
}
this.textAnnotations[textarea.name] = textarea;
textarea.draw(options.x, options.y);
if (options.anchor && textarea.circle) {
textarea.circle.position = {
x: options.anchor[0],
y: options.anchor[1],
};
}
return textarea;
};
PageAnnotator.prototype.drawItem = function(name, data) {
this.activate();
var path = this.scope.project.getItem({"name": name});
@ -747,6 +1091,36 @@ PageAnnotator.prototype.drawItem = function(name, data) {
path.name = name;
};
PageAnnotator.prototype.drawTextAnnotation = function(userid, name, data) {
this.activate();
var textarea;
if (this.textAnnotations.hasOwnProperty(name)) {
textarea = this.textAnnotations[name];
textarea.author = data.author;
textarea.modified = data.modified;
textarea.color = data.color;
textarea.content = data.content;
textarea.circle.position = {
x: Math.max(0, data.anchor[0]),
y: Math.max(0, data.anchor[1]),
};
textarea.draw(data.pos[0], data.pos[1]);
} else {
textarea = this.createTextArea({
'x': data.pos[0],
'y': data.pos[1],
'text': data.content,
'color': data.color,
'name': name,
'anchor': data.anchor,
'author': data.author,
'modified': data.modified,
});
}
textarea.update();
return textarea;
};
PageAnnotator.prototype.deleteItem = function(name) {
this.activate();
var path = this.scope.project.getItem({"name": name});
@ -793,6 +1167,7 @@ function Annotator(socketurl, id, userid, displayname, token) {
strokeWidth = parseStrokeWidth(strokeWidth);
}
this.strokeWidth = strokeWidth;
this.fontSize = 12;
this.pageCount = -1;
this.currentPage = null;
this.users = {};
@ -1229,6 +1604,9 @@ Annotator.prototype.setDrawMode = function(mode) {
case "line":
this.drawer = new LineDrawer(this);
break;
case "text":
this.drawer = new TextDrawer(this);
break;
case null:
break;
default:
@ -1285,7 +1663,17 @@ Annotator.prototype.processCursorMessage = function(userid, message) {
};
Annotator.prototype.processItemMessage = function(userid, message) {
if (typeof message.data === "string") {
message.data = JSON.parse(message.data);
}
this.getPage(message.page).then(function(page_annotator) {
switch (message.data.type) {
case "text-annotation":
page_annotator.drawTextAnnotation(userid, message.name, message.data);
return;
}
page_annotator.drawItem(message.name, message.data);
});
};
@ -1331,12 +1719,11 @@ Annotator.prototype.exportSVG = function() {
if (result.length < this.pageCount) {
var page = pages[result.length + 1];
if (page) {
var svg = page.scope.project.exportSVG({
asString: true
});
var svg = page.exportSVG();
result.push(svg);
}
}
return resolve(result);
}.bind(this);
@ -1346,9 +1733,7 @@ Annotator.prototype.exportSVG = function() {
pages[pagenum] = page_annotator;
var svg = null;
if (page_annotator) {
svg = page_annotator.scope.project.exportSVG({
asString: true
});
svg = page_annotator.exportSVG();
}
result[pagenum - 1] = svg;
remaining -= 1;
@ -1360,6 +1745,32 @@ Annotator.prototype.exportSVG = function() {
}.bind(this));
};
Annotator.prototype.exportTextAnnotations = function() {
var list = [];
each(this.annotators, function(page_annotator) {
each(page_annotator.textAnnotations, function(annotation) {
var scaleX = page_annotator.pagewidth / (annotation.circle.project.view.bounds.width * CSS_UNITS);
var scaleY = page_annotator.pageheight / (annotation.circle.project.view.bounds.height * CSS_UNITS);
var color = annotation.color;
if (color.length === 9 && color[0] === '#') {
// PDF annotations don't support transparency.
color = color.substr(0, 7);
}
var taObj = {
"page": page_annotator.pagenum - 1,
"x": annotation.circle.position.x * scaleX,
"y": annotation.circle.position.y * scaleY,
"author": annotation.author,
"modified": annotation.modified,
"text": annotation.content,
"color": color,
};
list.push(taObj);
});
});
return list;
};
Annotator.prototype.downloadPdf = function() {
this.exportSVG().then(function(svg) {
if (!svg.length) {
@ -1371,6 +1782,7 @@ Annotator.prototype.downloadPdf = function() {
var data = {
"svg": svg,
"token": this.token,
"text": this.exportTextAnnotations(),
};
// Download code from https://stackoverflow.com/a/23797348

View file

@ -250,7 +250,11 @@ See https://github.com/adobe-type-tools/cmap-resources
<?php p($l->t('Line')) ?>
</span>
</button>
<button id="textAnnotation" class="modeButton toolbarButton textMode hiddenMediumView" title="<?php p($l->t('Text')) ?>" tabindex="34" data-l10n-id="text" data-mode="text">
<span data-l10n-id="text">
<?php p($l->t('Text')) ?>
</span>
</button>
</div>
</div> <!-- drawMenuToolbar -->