diff --git a/css/pdfdraw.css b/css/pdfdraw.css
index dd9245a..28802e4 100644
--- a/css/pdfdraw.css
+++ b/css/pdfdraw.css
@@ -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;
+}
diff --git a/img/icon-text.svg b/img/icon-text.svg
new file mode 100644
index 0000000..889f1ee
--- /dev/null
+++ b/img/icon-text.svg
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index ff7d2f4..616c7ff 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index 26fbed8..afc1fdd 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/pdfdraw.js b/src/pdfdraw.js
index e5e7442..cd57a46 100644
--- a/src/pdfdraw.js
+++ b/src/pdfdraw.js
@@ -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 = $('
');
+ 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 = $('');
+ this.authorLabel = $('');
+ this.closeButton = $('');
+ 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
diff --git a/templates/viewer.php b/templates/viewer.php
index 95d1444..0ca7011 100644
--- a/templates/viewer.php
+++ b/templates/viewer.php
@@ -250,7 +250,11 @@ See https://github.com/adobe-type-tools/cmap-resources
t('Line')) ?>
-
+