Skip to content

Commit ad15532

Browse files
authored
Merge pull request #15179 from calixteman/editor_cp
[Editor] Use serialized data when copying/pasting
2 parents 642676a + 3c17dbb commit ad15532

File tree

6 files changed

+203
-99
lines changed

6 files changed

+203
-99
lines changed

src/display/editor/annotation_editor_layer.js

+15
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,21 @@ class AnnotationEditorLayer {
408408
return null;
409409
}
410410

411+
/**
412+
* Create a new editor
413+
* @param {Object} data
414+
* @returns {AnnotationEditor}
415+
*/
416+
deserialize(data) {
417+
switch (data.annotationType) {
418+
case AnnotationEditorType.FREETEXT:
419+
return FreeTextEditor.deserialize(data, this);
420+
case AnnotationEditorType.INK:
421+
return InkEditor.deserialize(data, this);
422+
}
423+
return null;
424+
}
425+
411426
/**
412427
* Create and add a new editor.
413428
* @param {MouseEvent} event

src/display/editor/editor.js

+48-12
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,26 @@ class AnnotationEditor {
290290
}
291291
}
292292

293+
getRectInCurrentCoords(rect, pageHeight) {
294+
const [x1, y1, x2, y2] = rect;
295+
296+
const width = x2 - x1;
297+
const height = y2 - y1;
298+
299+
switch (this.rotation) {
300+
case 0:
301+
return [x1, pageHeight - y2, width, height];
302+
case 90:
303+
return [x1, pageHeight - y1, height, width];
304+
case 180:
305+
return [x2, pageHeight - y1, width, height];
306+
case 270:
307+
return [x2, pageHeight - y2, height, width];
308+
default:
309+
throw new Error("Invalid rotation");
310+
}
311+
}
312+
293313
/**
294314
* Executed once this editor has been rendered.
295315
*/
@@ -336,18 +356,6 @@ class AnnotationEditor {
336356
return false;
337357
}
338358

339-
/**
340-
* Copy the elements of an editor in order to be able to build
341-
* a new one from these data.
342-
* It's used on ctrl+c action.
343-
*
344-
* To implement in subclasses.
345-
* @returns {AnnotationEditor}
346-
*/
347-
copy() {
348-
unreachable("An editor must be copyable");
349-
}
350-
351359
/**
352360
* Check if this editor needs to be rebuilt or not.
353361
* @returns {boolean}
@@ -378,6 +386,34 @@ class AnnotationEditor {
378386
unreachable("An editor must be serializable");
379387
}
380388

389+
/**
390+
* Deserialize the editor.
391+
* The result of the deserialization is a new editor.
392+
*
393+
* @param {Object} data
394+
* @param {AnnotationEditorLayer} parent
395+
* @returns {AnnotationEditor}
396+
*/
397+
static deserialize(data, parent) {
398+
const editor = new this.prototype.constructor({
399+
parent,
400+
id: parent.getNextId(),
401+
});
402+
editor.rotation = data.rotation;
403+
404+
const [pageWidth, pageHeight] = parent.pageDimensions;
405+
const [x, y, width, height] = editor.getRectInCurrentCoords(
406+
data.rect,
407+
pageHeight
408+
);
409+
editor.x = x / pageWidth;
410+
editor.y = y / pageHeight;
411+
editor.width = width / pageWidth;
412+
editor.height = height / pageHeight;
413+
414+
return editor;
415+
}
416+
381417
/**
382418
* Remove this editor.
383419
* It's used on ctrl+backspace action.

src/display/editor/freetext.js

+19-20
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@
1313
* limitations under the License.
1414
*/
1515

16+
// eslint-disable-next-line max-len
17+
/** @typedef {import("./annotation_editor_layer.js").AnnotationEditorLayer} AnnotationEditorLayer */
18+
1619
import {
1720
AnnotationEditorParamsType,
1821
AnnotationEditorType,
1922
assert,
2023
LINE_FACTOR,
24+
Util,
2125
} from "../../shared/util.js";
2226
import { AnnotationEditor } from "./editor.js";
2327
import { bindEvents } from "./tools.js";
@@ -77,26 +81,6 @@ class FreeTextEditor extends AnnotationEditor {
7781
);
7882
}
7983

80-
/** @inheritdoc */
81-
copy() {
82-
const [width, height] = this.parent.viewportBaseDimensions;
83-
const editor = new FreeTextEditor({
84-
parent: this.parent,
85-
id: this.parent.getNextId(),
86-
x: this.x * width,
87-
y: this.y * height,
88-
});
89-
90-
editor.width = this.width;
91-
editor.height = this.height;
92-
editor.#color = this.#color;
93-
editor.#fontSize = this.#fontSize;
94-
editor.#content = this.#content;
95-
editor.#contentHTML = this.#contentHTML;
96-
97-
return editor;
98-
}
99-
10084
static updateDefaultParams(type, value) {
10185
switch (type) {
10286
case AnnotationEditorParamsType.FREETEXT_SIZE:
@@ -370,6 +354,21 @@ class FreeTextEditor extends AnnotationEditor {
370354
return this.div;
371355
}
372356

357+
/** @inheritdoc */
358+
static deserialize(data, parent) {
359+
const editor = super.deserialize(data, parent);
360+
361+
editor.#fontSize = data.fontSize;
362+
editor.#color = Util.makeHexColor(...data.color);
363+
editor.#content = data.value;
364+
editor.#contentHTML = data.value
365+
.split("\n")
366+
.map(line => `<div>${line}</div>`)
367+
.join("");
368+
369+
return editor;
370+
}
371+
373372
/** @inheritdoc */
374373
serialize() {
375374
if (this.isEmpty()) {

src/display/editor/ink.js

+78-46
Original file line numberDiff line numberDiff line change
@@ -76,34 +76,6 @@ class InkEditor extends AnnotationEditor {
7676
this.#boundCanvasMousedown = this.canvasMousedown.bind(this);
7777
}
7878

79-
/** @inheritdoc */
80-
copy() {
81-
const editor = new InkEditor({
82-
parent: this.parent,
83-
id: this.parent.getNextId(),
84-
});
85-
86-
editor.x = this.x;
87-
editor.y = this.y;
88-
editor.width = this.width;
89-
editor.height = this.height;
90-
editor.color = this.color;
91-
editor.thickness = this.thickness;
92-
editor.paths = this.paths.slice();
93-
editor.bezierPath2D = this.bezierPath2D.slice();
94-
editor.scaleFactor = this.scaleFactor;
95-
editor.translationX = this.translationX;
96-
editor.translationY = this.translationY;
97-
editor.#aspectRatio = this.#aspectRatio;
98-
editor.#baseWidth = this.#baseWidth;
99-
editor.#baseHeight = this.#baseHeight;
100-
editor.#disableEditing = this.#disableEditing;
101-
editor.#realWidth = this.#realWidth;
102-
editor.#realHeight = this.#realHeight;
103-
104-
return editor;
105-
}
106-
10779
static updateDefaultParams(type, value) {
10880
switch (type) {
10981
case AnnotationEditorParamsType.INK_THICKNESS:
@@ -351,7 +323,7 @@ class InkEditor extends AnnotationEditor {
351323
const xy = [x, y];
352324
bezier = [[xy, xy.slice(), xy.slice(), xy]];
353325
}
354-
const path2D = this.#buildPath2D(bezier);
326+
const path2D = InkEditor.#buildPath2D(bezier);
355327
this.currentPath.length = 0;
356328

357329
const cmd = () => {
@@ -543,17 +515,18 @@ class InkEditor extends AnnotationEditor {
543515

544516
if (this.width) {
545517
// This editor was created in using copy (ctrl+c).
546-
this.#isCanvasInitialized = true;
547518
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions;
548519
this.setAt(
549520
baseX * parentWidth,
550521
baseY * parentHeight,
551522
this.width * parentWidth,
552523
this.height * parentHeight
553524
);
554-
this.setDims(this.width * parentWidth, this.height * parentHeight);
525+
this.#isCanvasInitialized = true;
555526
this.#setCanvasDims();
527+
this.setDims(this.width * parentWidth, this.height * parentHeight);
556528
this.#redraw();
529+
this.#setMinDims();
557530
this.div.classList.add("disabled");
558531
} else {
559532
this.div.classList.add("editing");
@@ -570,8 +543,8 @@ class InkEditor extends AnnotationEditor {
570543
return;
571544
}
572545
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions;
573-
this.canvas.width = this.width * parentWidth;
574-
this.canvas.height = this.height * parentHeight;
546+
this.canvas.width = Math.ceil(this.width * parentWidth);
547+
this.canvas.height = Math.ceil(this.height * parentHeight);
575548
this.#updateTransform();
576549
}
577550

@@ -610,10 +583,7 @@ class InkEditor extends AnnotationEditor {
610583
this.height = height / parentHeight;
611584

612585
if (this.#disableEditing) {
613-
const padding = this.#getPadding();
614-
const scaleFactorW = (width - padding) / this.#baseWidth;
615-
const scaleFactorH = (height - padding) / this.#baseHeight;
616-
this.scaleFactor = Math.min(scaleFactorW, scaleFactorH);
586+
this.#setScaleFactor(width, height);
617587
}
618588

619589
this.#setCanvasDims();
@@ -622,6 +592,13 @@ class InkEditor extends AnnotationEditor {
622592
this.canvas.style.visibility = "visible";
623593
}
624594

595+
#setScaleFactor(width, height) {
596+
const padding = this.#getPadding();
597+
const scaleFactorW = (width - padding) / this.#baseWidth;
598+
const scaleFactorH = (height - padding) / this.#baseHeight;
599+
this.scaleFactor = Math.min(scaleFactorW, scaleFactorH);
600+
}
601+
625602
/**
626603
* Update the canvas transform.
627604
*/
@@ -642,7 +619,7 @@ class InkEditor extends AnnotationEditor {
642619
* @param {Arra<Array<number>} bezier
643620
* @returns {Path2D}
644621
*/
645-
#buildPath2D(bezier) {
622+
static #buildPath2D(bezier) {
646623
const path2D = new Path2D();
647624
for (let i = 0, ii = bezier.length; i < ii; i++) {
648625
const [first, control1, control2, second] = bezier[i];
@@ -859,14 +836,7 @@ class InkEditor extends AnnotationEditor {
859836
this.height = height / parentHeight;
860837

861838
this.#aspectRatio = width / height;
862-
const { style } = this.div;
863-
if (this.#aspectRatio >= 1) {
864-
style.minHeight = `${RESIZER_SIZE}px`;
865-
style.minWidth = `${Math.round(this.#aspectRatio * RESIZER_SIZE)}px`;
866-
} else {
867-
style.minWidth = `${RESIZER_SIZE}px`;
868-
style.minHeight = `${Math.round(RESIZER_SIZE / this.#aspectRatio)}px`;
869-
}
839+
this.#setMinDims();
870840

871841
const prevTranslationX = this.translationX;
872842
const prevTranslationY = this.translationY;
@@ -886,6 +856,68 @@ class InkEditor extends AnnotationEditor {
886856
);
887857
}
888858

859+
#setMinDims() {
860+
const { style } = this.div;
861+
if (this.#aspectRatio >= 1) {
862+
style.minHeight = `${RESIZER_SIZE}px`;
863+
style.minWidth = `${Math.round(this.#aspectRatio * RESIZER_SIZE)}px`;
864+
} else {
865+
style.minWidth = `${RESIZER_SIZE}px`;
866+
style.minHeight = `${Math.round(RESIZER_SIZE / this.#aspectRatio)}px`;
867+
}
868+
}
869+
870+
/** @inheritdoc */
871+
static deserialize(data, parent) {
872+
const editor = super.deserialize(data, parent);
873+
874+
editor.thickness = data.thickness;
875+
editor.color = Util.makeHexColor(...data.color);
876+
877+
const [pageWidth, pageHeight] = parent.pageDimensions;
878+
const width = editor.width * pageWidth;
879+
const height = editor.height * pageHeight;
880+
const scaleFactor = parent.scaleFactor;
881+
const padding = data.thickness / 2;
882+
883+
editor.#aspectRatio = width / height;
884+
editor.#disableEditing = true;
885+
editor.#realWidth = Math.round(width);
886+
editor.#realHeight = Math.round(height);
887+
888+
for (const { bezier } of data.paths) {
889+
const path = [];
890+
editor.paths.push(path);
891+
let p0 = scaleFactor * (bezier[0] - padding);
892+
let p1 = scaleFactor * (height - bezier[1] - padding);
893+
for (let i = 2, ii = bezier.length; i < ii; i += 6) {
894+
const p10 = scaleFactor * (bezier[i] - padding);
895+
const p11 = scaleFactor * (height - bezier[i + 1] - padding);
896+
const p20 = scaleFactor * (bezier[i + 2] - padding);
897+
const p21 = scaleFactor * (height - bezier[i + 3] - padding);
898+
const p30 = scaleFactor * (bezier[i + 4] - padding);
899+
const p31 = scaleFactor * (height - bezier[i + 5] - padding);
900+
path.push([
901+
[p0, p1],
902+
[p10, p11],
903+
[p20, p21],
904+
[p30, p31],
905+
]);
906+
p0 = p30;
907+
p1 = p31;
908+
}
909+
const path2D = this.#buildPath2D(path);
910+
editor.bezierPath2D.push(path2D);
911+
}
912+
913+
const bbox = editor.#getBbox();
914+
editor.#baseWidth = bbox[2] - bbox[0];
915+
editor.#baseHeight = bbox[3] - bbox[1];
916+
editor.#setScaleFactor(width, height);
917+
918+
return editor;
919+
}
920+
889921
/** @inheritdoc */
890922
serialize() {
891923
if (this.isEmpty()) {

0 commit comments

Comments
 (0)