Add support for text annotations.
This commit is contained in:
parent
f76c0c6e78
commit
5e994f25a2
6 changed files with 539 additions and 52 deletions
|
|
@ -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
12
img/icon-text.svg
Normal 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
24
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
506
src/pdfdraw.js
506
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 = $('<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">×</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
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue