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')) ?> - +