Merge pull request #15782 from calixteman/15780

[api-minor][Editor] Don't use the editor parent which can be null.
This commit is contained in:
calixteman 2022-12-08 14:27:42 +01:00 committed by GitHub
commit fe3df4dcb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 270 additions and 126 deletions

View File

@ -17,8 +17,6 @@
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
/** @typedef {import("./tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */ /** @typedef {import("./tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
/** @typedef {import("../annotation_storage.js").AnnotationStorage} AnnotationStorage */
// eslint-disable-next-line max-len
/** @typedef {import("../../web/text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */ /** @typedef {import("../../web/text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
/** @typedef {import("../../web/interfaces").IL10n} IL10n */ /** @typedef {import("../../web/interfaces").IL10n} IL10n */
@ -33,7 +31,6 @@ import { InkEditor } from "./ink.js";
* @property {HTMLDivElement} div * @property {HTMLDivElement} div
* @property {AnnotationEditorUIManager} uiManager * @property {AnnotationEditorUIManager} uiManager
* @property {boolean} enabled * @property {boolean} enabled
* @property {AnnotationStorage} annotationStorage
* @property {TextAccessibilityManager} [accessibilityManager] * @property {TextAccessibilityManager} [accessibilityManager]
* @property {number} pageIndex * @property {number} pageIndex
* @property {IL10n} l10n * @property {IL10n} l10n
@ -73,7 +70,6 @@ class AnnotationEditorLayer {
options.uiManager.registerEditorTypes([FreeTextEditor, InkEditor]); options.uiManager.registerEditorTypes([FreeTextEditor, InkEditor]);
this.#uiManager = options.uiManager; this.#uiManager = options.uiManager;
this.annotationStorage = options.annotationStorage;
this.pageIndex = options.pageIndex; this.pageIndex = options.pageIndex;
this.div = options.div; this.div = options.div;
this.#accessibilityManager = options.accessibilityManager; this.#accessibilityManager = options.accessibilityManager;
@ -213,7 +209,6 @@ class AnnotationEditorLayer {
this.#uiManager.removeEditor(editor); this.#uiManager.removeEditor(editor);
this.detach(editor); this.detach(editor);
this.annotationStorage.remove(editor.id);
editor.div.style.display = "none"; editor.div.style.display = "none";
setTimeout(() => { setTimeout(() => {
// When the div is removed from DOM the focus can move on the // When the div is removed from DOM the focus can move on the
@ -244,7 +239,6 @@ class AnnotationEditorLayer {
} }
this.attach(editor); this.attach(editor);
editor.pageIndex = this.pageIndex;
editor.parent?.detach(editor); editor.parent?.detach(editor);
editor.setParent(this); editor.setParent(this);
if (editor.div && editor.isAttachedToDOM) { if (editor.div && editor.isAttachedToDOM) {
@ -270,7 +264,7 @@ class AnnotationEditorLayer {
this.moveEditorInDOM(editor); this.moveEditorInDOM(editor);
editor.onceAdded(); editor.onceAdded();
this.addToAnnotationStorage(editor); this.#uiManager.addToAnnotationStorage(editor);
} }
moveEditorInDOM(editor) { moveEditorInDOM(editor) {
@ -282,16 +276,6 @@ class AnnotationEditorLayer {
); );
} }
/**
* Add an editor in the annotation storage.
* @param {AnnotationEditor} editor
*/
addToAnnotationStorage(editor) {
if (!editor.isEmpty() && !this.annotationStorage.has(editor.id)) {
this.annotationStorage.setValue(editor.id, editor);
}
}
/** /**
* Add or rebuild depending if it has been removed or not. * Add or rebuild depending if it has been removed or not.
* @param {AnnotationEditor} editor * @param {AnnotationEditor} editor
@ -365,9 +349,9 @@ class AnnotationEditorLayer {
deserialize(data) { deserialize(data) {
switch (data.annotationType) { switch (data.annotationType) {
case AnnotationEditorType.FREETEXT: case AnnotationEditorType.FREETEXT:
return FreeTextEditor.deserialize(data, this); return FreeTextEditor.deserialize(data, this, this.#uiManager);
case AnnotationEditorType.INK: case AnnotationEditorType.INK:
return InkEditor.deserialize(data, this); return InkEditor.deserialize(data, this, this.#uiManager);
} }
return null; return null;
} }
@ -384,6 +368,7 @@ class AnnotationEditorLayer {
id, id,
x: event.offsetX, x: event.offsetX,
y: event.offsetY, y: event.offsetY,
uiManager: this.#uiManager,
}); });
if (editor) { if (editor) {
this.add(editor); this.add(editor);
@ -520,8 +505,8 @@ class AnnotationEditorLayer {
for (const editor of this.#editors.values()) { for (const editor of this.#editors.values()) {
this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv); this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv);
editor.isAttachedToDOM = false;
editor.setParent(null); editor.setParent(null);
editor.isAttachedToDOM = false;
editor.div.remove(); editor.div.remove();
} }
this.div = null; this.div = null;
@ -571,14 +556,6 @@ class AnnotationEditorLayer {
this.updateMode(); this.updateMode();
} }
/**
* Get the scale factor from the viewport.
* @returns {number}
*/
get scaleFactor() {
return this.viewport.scale;
}
/** /**
* Get page dimensions. * Get page dimensions.
* @returns {Object} dimensions. * @returns {Object} dimensions.
@ -591,11 +568,6 @@ class AnnotationEditorLayer {
return [width, height]; return [width, height];
} }
get viewportBaseDimensions() {
const { width, height, rotation } = this.viewport;
return rotation % 180 === 0 ? [width, height] : [height, width];
}
/** /**
* Set the dimensions of the main div. * Set the dimensions of the main div.
*/ */

View File

@ -15,12 +15,15 @@
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
/** @typedef {import("./annotation_editor_layer.js").AnnotationEditorLayer} AnnotationEditorLayer */ /** @typedef {import("./annotation_editor_layer.js").AnnotationEditorLayer} AnnotationEditorLayer */
// eslint-disable-next-line max-len
/** @typedef {import("./tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
import { bindEvents, ColorManager } from "./tools.js"; import { bindEvents, ColorManager } from "./tools.js";
import { FeatureTest, shadow, unreachable } from "../../shared/util.js"; import { FeatureTest, shadow, unreachable } from "../../shared/util.js";
/** /**
* @typedef {Object} AnnotationEditorParameters * @typedef {Object} AnnotationEditorParameters
* @property {AnnotationEditorUIManager} uiManager - the global manager
* @property {AnnotationEditorLayer} parent - the layer containing this editor * @property {AnnotationEditorLayer} parent - the layer containing this editor
* @property {string} id - editor id * @property {string} id - editor id
* @property {number} x - x-coordinate * @property {number} x - x-coordinate
@ -41,6 +44,8 @@ class AnnotationEditor {
#isInEditMode = false; #isInEditMode = false;
_uiManager = null;
#zIndex = AnnotationEditor._zIndex++; #zIndex = AnnotationEditor._zIndex++;
static _colorManager = new ColorManager(); static _colorManager = new ColorManager();
@ -61,15 +66,15 @@ class AnnotationEditor {
this.pageIndex = parameters.parent.pageIndex; this.pageIndex = parameters.parent.pageIndex;
this.name = parameters.name; this.name = parameters.name;
this.div = null; this.div = null;
this._uiManager = parameters.uiManager;
const [width, height] = this.parent.viewportBaseDimensions; this.rotation = this.parent.viewport.rotation;
this.pageDimensions = this.parent.pageDimensions;
const [width, height] = this.parentDimensions;
this.x = parameters.x / width; this.x = parameters.x / width;
this.y = parameters.y / height; this.y = parameters.y / height;
this.rotation = this.parent.viewport.rotation;
this.isAttachedToDOM = false; this.isAttachedToDOM = false;
this._serialized = undefined;
} }
static get _defaultLineColor() { static get _defaultLineColor() {
@ -80,9 +85,16 @@ class AnnotationEditor {
); );
} }
setParent(parent) { /**
this._serialized = !parent ? this.serialize() : undefined; * Add some commands into the CommandManager (undo/redo stuff).
this.parent = parent; * @param {Object} params
*/
addCommands(params) {
this._uiManager.addCommands(params);
}
get currentLayer() {
return this._uiManager.currentLayer;
} }
/** /**
@ -99,6 +111,14 @@ class AnnotationEditor {
this.div.style.zIndex = this.#zIndex; this.div.style.zIndex = this.#zIndex;
} }
setParent(parent) {
if (parent !== null) {
this.pageIndex = parent.pageIndex;
this.pageDimensions = parent.pageDimensions;
}
this.parent = parent;
}
/** /**
* onfocus callback. * onfocus callback.
*/ */
@ -130,7 +150,7 @@ class AnnotationEditor {
event.preventDefault(); event.preventDefault();
if (!this.parent.isMultipleSelection) { if (!this.parent?.isMultipleSelection) {
this.commitOrRemove(); this.commitOrRemove();
} }
} }
@ -147,7 +167,11 @@ class AnnotationEditor {
* Commit the data contained in this editor. * Commit the data contained in this editor.
*/ */
commit() { commit() {
this.parent.addToAnnotationStorage(this); this.addToAnnotationStorage();
}
addToAnnotationStorage() {
this._uiManager.addToAnnotationStorage(this);
} }
/** /**
@ -170,7 +194,7 @@ class AnnotationEditor {
* @param {number} ty - y-translation in screen coordinates. * @param {number} ty - y-translation in screen coordinates.
*/ */
setAt(x, y, tx, ty) { setAt(x, y, tx, ty) {
const [width, height] = this.parent.viewportBaseDimensions; const [width, height] = this.parentDimensions;
[tx, ty] = this.screenToPageTranslation(tx, ty); [tx, ty] = this.screenToPageTranslation(tx, ty);
this.x = (x + tx) / width; this.x = (x + tx) / width;
@ -186,7 +210,7 @@ class AnnotationEditor {
* @param {number} y - y-translation in screen coordinates. * @param {number} y - y-translation in screen coordinates.
*/ */
translate(x, y) { translate(x, y) {
const [width, height] = this.parent.viewportBaseDimensions; const [width, height] = this.parentDimensions;
[x, y] = this.screenToPageTranslation(x, y); [x, y] = this.screenToPageTranslation(x, y);
this.x += x / width; this.x += x / width;
@ -202,8 +226,7 @@ class AnnotationEditor {
* @param {number} y * @param {number} y
*/ */
screenToPageTranslation(x, y) { screenToPageTranslation(x, y) {
const { rotation } = this.parent.viewport; switch (this.parentRotation) {
switch (rotation) {
case 90: case 90:
return [y, -x]; return [y, -x];
case 180: case 180:
@ -215,13 +238,27 @@ class AnnotationEditor {
} }
} }
get parentScale() {
return this._uiManager.viewParameters.realScale;
}
get parentRotation() {
return this._uiManager.viewParameters.rotation;
}
get parentDimensions() {
const { realScale } = this._uiManager.viewParameters;
const [pageWidth, pageHeight] = this.pageDimensions;
return [pageWidth * realScale, pageHeight * realScale];
}
/** /**
* Set the dimensions of this editor. * Set the dimensions of this editor.
* @param {number} width * @param {number} width
* @param {number} height * @param {number} height
*/ */
setDims(width, height) { setDims(width, height) {
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; const [parentWidth, parentHeight] = this.parentDimensions;
this.div.style.width = `${(100 * width) / parentWidth}%`; this.div.style.width = `${(100 * width) / parentWidth}%`;
this.div.style.height = `${(100 * height) / parentHeight}%`; this.div.style.height = `${(100 * height) / parentHeight}%`;
} }
@ -235,7 +272,7 @@ class AnnotationEditor {
return; return;
} }
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; const [parentWidth, parentHeight] = this.parentDimensions;
if (!widthPercent) { if (!widthPercent) {
style.width = `${(100 * parseFloat(width)) / parentWidth}%`; style.width = `${(100 * parseFloat(width)) / parentWidth}%`;
} }
@ -302,10 +339,10 @@ class AnnotationEditor {
} }
getRect(tx, ty) { getRect(tx, ty) {
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; const scale = this.parentScale;
const [pageWidth, pageHeight] = this.parent.pageDimensions; const [pageWidth, pageHeight] = this.pageDimensions;
const shiftX = (pageWidth * tx) / parentWidth; const shiftX = tx / scale;
const shiftY = (pageHeight * ty) / parentHeight; const shiftY = ty / scale;
const x = this.x * pageWidth; const x = this.x * pageWidth;
const y = this.y * pageHeight; const y = this.y * pageHeight;
const width = this.width * pageWidth; const width = this.width * pageWidth;
@ -443,16 +480,18 @@ class AnnotationEditor {
* *
* @param {Object} data * @param {Object} data
* @param {AnnotationEditorLayer} parent * @param {AnnotationEditorLayer} parent
* @param {AnnotationEditorUIManager} uiManager
* @returns {AnnotationEditor} * @returns {AnnotationEditor}
*/ */
static deserialize(data, parent) { static deserialize(data, parent, uiManager) {
const editor = new this.prototype.constructor({ const editor = new this.prototype.constructor({
parent, parent,
id: parent.getNextId(), id: parent.getNextId(),
uiManager,
}); });
editor.rotation = data.rotation; editor.rotation = data.rotation;
const [pageWidth, pageHeight] = parent.pageDimensions; const [pageWidth, pageHeight] = editor.pageDimensions;
const [x, y, width, height] = editor.getRectInCurrentCoords( const [x, y, width, height] = editor.getRectInCurrentCoords(
data.rect, data.rect,
pageHeight pageHeight

View File

@ -153,12 +153,12 @@ class FreeTextEditor extends AnnotationEditor {
#updateFontSize(fontSize) { #updateFontSize(fontSize) {
const setFontsize = size => { const setFontsize = size => {
this.editorDiv.style.fontSize = `calc(${size}px * var(--scale-factor))`; this.editorDiv.style.fontSize = `calc(${size}px * var(--scale-factor))`;
this.translate(0, -(size - this.#fontSize) * this.parent.scaleFactor); this.translate(0, -(size - this.#fontSize) * this.parentScale);
this.#fontSize = size; this.#fontSize = size;
this.#setEditorDimensions(); this.#setEditorDimensions();
}; };
const savedFontsize = this.#fontSize; const savedFontsize = this.#fontSize;
this.parent.addCommands({ this.addCommands({
cmd: () => { cmd: () => {
setFontsize(fontSize); setFontsize(fontSize);
}, },
@ -178,14 +178,12 @@ class FreeTextEditor extends AnnotationEditor {
*/ */
#updateColor(color) { #updateColor(color) {
const savedColor = this.#color; const savedColor = this.#color;
this.parent.addCommands({ this.addCommands({
cmd: () => { cmd: () => {
this.#color = color; this.#color = this.editorDiv.style.color = color;
this.editorDiv.style.color = color;
}, },
undo: () => { undo: () => {
this.#color = savedColor; this.#color = this.editorDiv.style.color = savedColor;
this.editorDiv.style.color = savedColor;
}, },
mustExec: true, mustExec: true,
type: AnnotationEditorParamsType.FREETEXT_COLOR, type: AnnotationEditorParamsType.FREETEXT_COLOR,
@ -197,10 +195,10 @@ class FreeTextEditor extends AnnotationEditor {
/** @inheritdoc */ /** @inheritdoc */
getInitialTranslation() { getInitialTranslation() {
// The start of the base line is where the user clicked. // The start of the base line is where the user clicked.
const scale = this.parentScale;
return [ return [
-FreeTextEditor._internalPadding * this.parent.scaleFactor, -FreeTextEditor._internalPadding * scale,
-(FreeTextEditor._internalPadding + this.#fontSize) * -(FreeTextEditor._internalPadding + this.#fontSize) * scale,
this.parent.scaleFactor,
]; ];
} }
@ -254,9 +252,11 @@ class FreeTextEditor extends AnnotationEditor {
this.editorDiv.removeEventListener("blur", this.#boundEditorDivBlur); this.editorDiv.removeEventListener("blur", this.#boundEditorDivBlur);
this.editorDiv.removeEventListener("input", this.#boundEditorDivInput); this.editorDiv.removeEventListener("input", this.#boundEditorDivInput);
// On Chrome, the focus is given to <body> when contentEditable is set to if (this.pageIndex === this._uiManager.currentPageIndex) {
// false, hence we focus the div. // On Chrome, the focus is given to <body> when contentEditable is set to
this.div.focus(); // false, hence we focus the div.
this.div.focus();
}
// In case the blur callback hasn't been called. // In case the blur callback hasn't been called.
this.isEditing = false; this.isEditing = false;
@ -311,8 +311,22 @@ class FreeTextEditor extends AnnotationEditor {
} }
#setEditorDimensions() { #setEditorDimensions() {
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; const [parentWidth, parentHeight] = this.parentDimensions;
const rect = this.div.getBoundingClientRect();
let rect;
if (this.isAttachedToDOM) {
rect = this.div.getBoundingClientRect();
} else {
// This editor isn't on screen but we need to get its dimensions, so
// we just insert it in the DOM, get its bounding box and then remove it.
const { currentLayer, div } = this;
const savedDisplay = div.style.display;
div.style.display = "hidden";
currentLayer.div.append(this.div);
rect = div.getBoundingClientRect();
div.remove();
div.style.display = savedDisplay;
}
this.width = rect.width / parentWidth; this.width = rect.width / parentWidth;
this.height = rect.height / parentHeight; this.height = rect.height / parentHeight;
@ -323,6 +337,10 @@ class FreeTextEditor extends AnnotationEditor {
* @returns {undefined} * @returns {undefined}
*/ */
commit() { commit() {
if (!this.isInEditMode()) {
return;
}
super.commit(); super.commit();
if (!this.#hasAlreadyBeenCommitted) { if (!this.#hasAlreadyBeenCommitted) {
// This editor has something and it's the first time // This editor has something and it's the first time
@ -435,7 +453,7 @@ class FreeTextEditor extends AnnotationEditor {
if (this.width) { if (this.width) {
// This editor was created in using copy (ctrl+c). // This editor was created in using copy (ctrl+c).
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; const [parentWidth, parentHeight] = this.parentDimensions;
this.setAt( this.setAt(
baseX * parentWidth, baseX * parentWidth,
baseY * parentHeight, baseY * parentHeight,
@ -466,8 +484,8 @@ class FreeTextEditor extends AnnotationEditor {
} }
/** @inheritdoc */ /** @inheritdoc */
static deserialize(data, parent) { static deserialize(data, parent, uiManager) {
const editor = super.deserialize(data, parent); const editor = super.deserialize(data, parent, uiManager);
editor.#fontSize = data.fontSize; editor.#fontSize = data.fontSize;
editor.#color = Util.makeHexColor(...data.color); editor.#color = Util.makeHexColor(...data.color);
@ -478,19 +496,17 @@ class FreeTextEditor extends AnnotationEditor {
/** @inheritdoc */ /** @inheritdoc */
serialize() { serialize() {
if (this._serialized !== undefined) {
return this._serialized;
}
if (this.isEmpty()) { if (this.isEmpty()) {
return null; return null;
} }
const padding = FreeTextEditor._internalPadding * this.parent.scaleFactor; const padding = FreeTextEditor._internalPadding * this.parentScale;
const rect = this.getRect(padding, padding); const rect = this.getRect(padding, padding);
const color = AnnotationEditor._colorManager.convert( const color = AnnotationEditor._colorManager.convert(
getComputedStyle(this.editorDiv).color this.isAttachedToDOM
? getComputedStyle(this.editorDiv).color
: this.#color
); );
return { return {
@ -498,7 +514,7 @@ class FreeTextEditor extends AnnotationEditor {
color, color,
fontSize: this.#fontSize, fontSize: this.#fontSize,
value: this.#content, value: this.#content,
pageIndex: this.parent.pageIndex, pageIndex: this.pageIndex,
rect, rect,
rotation: this.rotation, rotation: this.rotation,
}; };

View File

@ -165,7 +165,7 @@ class InkEditor extends AnnotationEditor {
*/ */
#updateThickness(thickness) { #updateThickness(thickness) {
const savedThickness = this.thickness; const savedThickness = this.thickness;
this.parent.addCommands({ this.addCommands({
cmd: () => { cmd: () => {
this.thickness = thickness; this.thickness = thickness;
this.#fitToContent(); this.#fitToContent();
@ -187,7 +187,7 @@ class InkEditor extends AnnotationEditor {
*/ */
#updateColor(color) { #updateColor(color) {
const savedColor = this.color; const savedColor = this.color;
this.parent.addCommands({ this.addCommands({
cmd: () => { cmd: () => {
this.color = color; this.color = color;
this.#redraw(); this.#redraw();
@ -210,7 +210,7 @@ class InkEditor extends AnnotationEditor {
#updateOpacity(opacity) { #updateOpacity(opacity) {
opacity /= 100; opacity /= 100;
const savedOpacity = this.opacity; const savedOpacity = this.opacity;
this.parent.addCommands({ this.addCommands({
cmd: () => { cmd: () => {
this.opacity = opacity; this.opacity = opacity;
this.#redraw(); this.#redraw();
@ -268,6 +268,27 @@ class InkEditor extends AnnotationEditor {
super.remove(); super.remove();
} }
setParent(parent) {
if (!this.parent && parent) {
// We've a parent hence the rescale will be handled thanks to the
// ResizeObserver.
this._uiManager.removeShouldRescale(this);
} else if (this.parent && parent === null) {
// The editor is removed from the DOM, hence we handle the rescale thanks
// to the onScaleChanging callback.
// This way, it'll be saved/printed correctly.
this._uiManager.addShouldRescale(this);
}
super.setParent(parent);
}
onScaleChanging() {
const [parentWidth, parentHeight] = this.parentDimensions;
const width = this.width * parentWidth;
const height = this.height * parentHeight;
this.setDimensions(width, height);
}
/** @inheritdoc */ /** @inheritdoc */
enableEditMode() { enableEditMode() {
if (this.#disableEditing || this.canvas === null) { if (this.#disableEditing || this.canvas === null) {
@ -311,14 +332,17 @@ class InkEditor extends AnnotationEditor {
} }
#getInitialBBox() { #getInitialBBox() {
const { width, height, rotation } = this.parent.viewport; const {
switch (rotation) { parentRotation,
parentDimensions: [width, height],
} = this;
switch (parentRotation) {
case 90: case 90:
return [0, width, width, height]; return [0, height, height, width];
case 180: case 180:
return [width, height, width, height]; return [width, height, width, height];
case 270: case 270:
return [height, 0, width, height]; return [width, 0, height, width];
default: default:
return [0, 0, width, height]; return [0, 0, width, height];
} }
@ -328,12 +352,12 @@ class InkEditor extends AnnotationEditor {
* Set line styles. * Set line styles.
*/ */
#setStroke() { #setStroke() {
this.ctx.lineWidth = const { ctx, color, opacity, thickness, parentScale, scaleFactor } = this;
(this.thickness * this.parent.scaleFactor) / this.scaleFactor; ctx.lineWidth = (thickness * parentScale) / scaleFactor;
this.ctx.lineCap = "round"; ctx.lineCap = "round";
this.ctx.lineJoin = "round"; ctx.lineJoin = "round";
this.ctx.miterLimit = 10; ctx.miterLimit = 10;
this.ctx.strokeStyle = `${this.color}${opacityToHex(this.opacity)}`; ctx.strokeStyle = `${color}${opacityToHex(opacity)}`;
} }
/** /**
@ -445,7 +469,7 @@ class InkEditor extends AnnotationEditor {
} }
}; };
this.parent.addCommands({ cmd, undo, mustExec: true }); this.addCommands({ cmd, undo, mustExec: true });
} }
/** /**
@ -493,9 +517,11 @@ class InkEditor extends AnnotationEditor {
// When commiting, the position of this editor is changed, hence we must // When commiting, the position of this editor is changed, hence we must
// move it to the right position in the DOM. // move it to the right position in the DOM.
this.parent.moveEditorInDOM(this); this.parent.moveEditorInDOM(this);
// After the div has been moved in the DOM, the focus may have been stolen if (this.pageIndex === this._uiManager.currentPageIndex) {
// by document.body, hence we just keep it here. // After the div has been moved in the DOM, the focus may have been stolen
this.div.focus(); // by document.body, hence we just keep it here.
this.div.focus();
}
} }
/** @inheritdoc */ /** @inheritdoc */
@ -581,7 +607,7 @@ class InkEditor extends AnnotationEditor {
this.#boundCanvasPointermove this.#boundCanvasPointermove
); );
this.parent.addToAnnotationStorage(this); this.addToAnnotationStorage();
} }
/** /**
@ -649,7 +675,7 @@ class InkEditor extends AnnotationEditor {
if (this.width) { if (this.width) {
// This editor was created in using copy (ctrl+c). // This editor was created in using copy (ctrl+c).
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; const [parentWidth, parentHeight] = this.parentDimensions;
this.setAt( this.setAt(
baseX * parentWidth, baseX * parentWidth,
baseY * parentHeight, baseY * parentHeight,
@ -676,7 +702,7 @@ class InkEditor extends AnnotationEditor {
if (!this.#isCanvasInitialized) { if (!this.#isCanvasInitialized) {
return; return;
} }
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; const [parentWidth, parentHeight] = this.parentDimensions;
this.canvas.width = Math.ceil(this.width * parentWidth); this.canvas.width = Math.ceil(this.width * parentWidth);
this.canvas.height = Math.ceil(this.height * parentHeight); this.canvas.height = Math.ceil(this.height * parentHeight);
this.#updateTransform(); this.#updateTransform();
@ -712,7 +738,7 @@ class InkEditor extends AnnotationEditor {
this.setDims(width, height); this.setDims(width, height);
} }
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; const [parentWidth, parentHeight] = this.parentDimensions;
this.width = width / parentWidth; this.width = width / parentWidth;
this.height = height / parentHeight; this.height = height / parentHeight;
@ -940,7 +966,7 @@ class InkEditor extends AnnotationEditor {
*/ */
#getPadding() { #getPadding() {
return this.#disableEditing return this.#disableEditing
? Math.ceil(this.thickness * this.parent.scaleFactor) ? Math.ceil(this.thickness * this.parentScale)
: 0; : 0;
} }
@ -967,7 +993,7 @@ class InkEditor extends AnnotationEditor {
const width = Math.ceil(padding + this.#baseWidth * this.scaleFactor); const width = Math.ceil(padding + this.#baseWidth * this.scaleFactor);
const height = Math.ceil(padding + this.#baseHeight * this.scaleFactor); const height = Math.ceil(padding + this.#baseHeight * this.scaleFactor);
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; const [parentWidth, parentHeight] = this.parentDimensions;
this.width = width / parentWidth; this.width = width / parentWidth;
this.height = height / parentHeight; this.height = height / parentHeight;
@ -1005,17 +1031,17 @@ class InkEditor extends AnnotationEditor {
} }
/** @inheritdoc */ /** @inheritdoc */
static deserialize(data, parent) { static deserialize(data, parent, uiManager) {
const editor = super.deserialize(data, parent); const editor = super.deserialize(data, parent, uiManager);
editor.thickness = data.thickness; editor.thickness = data.thickness;
editor.color = Util.makeHexColor(...data.color); editor.color = Util.makeHexColor(...data.color);
editor.opacity = data.opacity; editor.opacity = data.opacity;
const [pageWidth, pageHeight] = parent.pageDimensions; const [pageWidth, pageHeight] = editor.pageDimensions;
const width = editor.width * pageWidth; const width = editor.width * pageWidth;
const height = editor.height * pageHeight; const height = editor.height * pageHeight;
const scaleFactor = parent.scaleFactor; const scaleFactor = editor.parentScale;
const padding = data.thickness / 2; const padding = data.thickness / 2;
editor.#aspectRatio = width / height; editor.#aspectRatio = width / height;
@ -1058,10 +1084,6 @@ class InkEditor extends AnnotationEditor {
/** @inheritdoc */ /** @inheritdoc */
serialize() { serialize() {
if (this._serialized !== undefined) {
return this._serialized;
}
if (this.isEmpty()) { if (this.isEmpty()) {
return null; return null;
} }
@ -1078,12 +1100,12 @@ class InkEditor extends AnnotationEditor {
thickness: this.thickness, thickness: this.thickness,
opacity: this.opacity, opacity: this.opacity,
paths: this.#serializePaths( paths: this.#serializePaths(
this.scaleFactor / this.parent.scaleFactor, this.scaleFactor / this.parentScale,
this.translationX, this.translationX,
this.translationY, this.translationY,
height height
), ),
pageIndex: this.parent.pageIndex, pageIndex: this.pageIndex,
rect, rect,
rotation: this.rotation, rotation: this.rotation,
}; };

View File

@ -25,7 +25,7 @@ import {
Util, Util,
warn, warn,
} from "../../shared/util.js"; } from "../../shared/util.js";
import { getColorValues, getRGB } from "../display_utils.js"; import { getColorValues, getRGB, PixelsPerInch } from "../display_utils.js";
function bindEvents(obj, element, names) { function bindEvents(obj, element, names) {
for (const name of names) { for (const name of names) {
@ -350,12 +350,16 @@ class AnnotationEditorUIManager {
#allLayers = new Map(); #allLayers = new Map();
#annotationStorage = null;
#commandManager = new CommandManager(); #commandManager = new CommandManager();
#currentPageIndex = 0; #currentPageIndex = 0;
#editorTypes = null; #editorTypes = null;
#editorsToRescale = new Set();
#eventBus = null; #eventBus = null;
#idManager = new IdManager(); #idManager = new IdManager();
@ -378,6 +382,10 @@ class AnnotationEditorUIManager {
#boundOnPageChanging = this.onPageChanging.bind(this); #boundOnPageChanging = this.onPageChanging.bind(this);
#boundOnScaleChanging = this.onScaleChanging.bind(this);
#boundOnRotationChanging = this.onRotationChanging.bind(this);
#previousStates = { #previousStates = {
isEditing: false, isEditing: false,
isEmpty: true, isEmpty: true,
@ -413,22 +421,32 @@ class AnnotationEditorUIManager {
[["Escape", "mac+Escape"], AnnotationEditorUIManager.prototype.unselectAll], [["Escape", "mac+Escape"], AnnotationEditorUIManager.prototype.unselectAll],
]); ]);
constructor(container, eventBus) { constructor(container, eventBus, annotationStorage) {
this.#container = container; this.#container = container;
this.#eventBus = eventBus; this.#eventBus = eventBus;
this.#eventBus._on("editingaction", this.#boundOnEditingAction); this.#eventBus._on("editingaction", this.#boundOnEditingAction);
this.#eventBus._on("pagechanging", this.#boundOnPageChanging); this.#eventBus._on("pagechanging", this.#boundOnPageChanging);
this.#eventBus._on("scalechanging", this.#boundOnScaleChanging);
this.#eventBus._on("rotationchanging", this.#boundOnRotationChanging);
this.#annotationStorage = annotationStorage;
this.viewParameters = {
realScale: PixelsPerInch.PDF_TO_CSS_UNITS,
rotation: 0,
};
} }
destroy() { destroy() {
this.#removeKeyboardManager(); this.#removeKeyboardManager();
this.#eventBus._off("editingaction", this.#boundOnEditingAction); this.#eventBus._off("editingaction", this.#boundOnEditingAction);
this.#eventBus._off("pagechanging", this.#boundOnPageChanging); this.#eventBus._off("pagechanging", this.#boundOnPageChanging);
this.#eventBus._off("scalechanging", this.#boundOnScaleChanging);
this.#eventBus._off("rotationchanging", this.#boundOnRotationChanging);
for (const layer of this.#allLayers.values()) { for (const layer of this.#allLayers.values()) {
layer.destroy(); layer.destroy();
} }
this.#allLayers.clear(); this.#allLayers.clear();
this.#allEditors.clear(); this.#allEditors.clear();
this.#editorsToRescale.clear();
this.#activeEditor = null; this.#activeEditor = null;
this.#selectedEditors.clear(); this.#selectedEditors.clear();
this.#commandManager.destroy(); this.#commandManager.destroy();
@ -442,6 +460,41 @@ class AnnotationEditorUIManager {
this.#container.focus(); this.#container.focus();
} }
addShouldRescale(editor) {
this.#editorsToRescale.add(editor);
}
removeShouldRescale(editor) {
this.#editorsToRescale.delete(editor);
}
onScaleChanging({ scale }) {
this.commitOrRemove();
this.viewParameters.realScale = scale * PixelsPerInch.PDF_TO_CSS_UNITS;
for (const editor of this.#editorsToRescale) {
editor.onScaleChanging();
}
}
onRotationChanging({ pagesRotation }) {
this.commitOrRemove();
this.viewParameters.rotation = pagesRotation;
}
/**
* Add an editor in the annotation storage.
* @param {AnnotationEditor} editor
*/
addToAnnotationStorage(editor) {
if (
!editor.isEmpty() &&
this.#annotationStorage &&
!this.#annotationStorage.has(editor.id)
) {
this.#annotationStorage.setValue(editor.id, editor);
}
}
#addKeyboardManager() { #addKeyboardManager() {
// The keyboard events are caught at the container level in order to be able // The keyboard events are caught at the container level in order to be able
// to execute some callbacks even if the current page doesn't have focus. // to execute some callbacks even if the current page doesn't have focus.
@ -646,6 +699,14 @@ class AnnotationEditorUIManager {
return this.#idManager.getId(); return this.#idManager.getId();
} }
get currentLayer() {
return this.#allLayers.get(this.#currentPageIndex);
}
get currentPageIndex() {
return this.#currentPageIndex;
}
/** /**
* Add a new layer for a page which will contains the editors. * Add a new layer for a page which will contains the editors.
* @param {AnnotationEditorLayer} layer * @param {AnnotationEditorLayer} layer
@ -783,6 +844,7 @@ class AnnotationEditorUIManager {
removeEditor(editor) { removeEditor(editor) {
this.#allEditors.delete(editor.id); this.#allEditors.delete(editor.id);
this.unselect(editor); this.unselect(editor);
this.#annotationStorage?.remove(editor.id);
} }
/** /**

View File

@ -599,6 +599,49 @@ describe("Editor", () => {
[0, 0, 0], [0, 0, 0],
[0, 0, 0], [0, 0, 0],
]); ]);
// Increase the font size for all the annotations.
// Select all.
await page.keyboard.down("Control");
await page.keyboard.press("a");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
page.evaluate(() => {
window.PDFViewerApplication.eventBus.dispatch(
"switchannotationeditorparams",
{
source: null,
type: /* AnnotationEditorParamsType.FREETEXT_SIZE */ 1,
value: 13,
}
);
});
await page.waitForTimeout(10);
expect(await serialize("fontSize"))
.withContext(`In ${browserName}`)
.toEqual([13, 13]);
// Change the colors for all the annotations.
page.evaluate(() => {
window.PDFViewerApplication.eventBus.dispatch(
"switchannotationeditorparams",
{
source: null,
type: /* AnnotationEditorParamsType.FREETEXT_COLOR */ 2,
value: "#FF0000",
}
);
});
await page.waitForTimeout(10);
expect(await serialize("color"))
.withContext(`In ${browserName}`)
.toEqual([
[255, 0, 0],
[255, 0, 0],
]);
}) })
); );
}); });

View File

@ -111,9 +111,7 @@ class DefaultAnnotationEditorLayerFactory {
* @property {HTMLDivElement} pageDiv * @property {HTMLDivElement} pageDiv
* @property {PDFPageProxy} pdfPage * @property {PDFPageProxy} pdfPage
* @property {IL10n} l10n * @property {IL10n} l10n
* @property {AnnotationStorage} [annotationStorage] - Storage for annotation
* @property {TextAccessibilityManager} [accessibilityManager] * @property {TextAccessibilityManager} [accessibilityManager]
* data in forms.
*/ */
/** /**
@ -126,7 +124,6 @@ class DefaultAnnotationEditorLayerFactory {
pdfPage, pdfPage,
accessibilityManager = null, accessibilityManager = null,
l10n, l10n,
annotationStorage = null,
}) { }) {
return new AnnotationEditorLayerBuilder({ return new AnnotationEditorLayerBuilder({
uiManager, uiManager,
@ -134,7 +131,6 @@ class DefaultAnnotationEditorLayerFactory {
pdfPage, pdfPage,
accessibilityManager, accessibilityManager,
l10n, l10n,
annotationStorage,
}); });
} }
} }

View File

@ -237,9 +237,7 @@ class IPDFAnnotationEditorLayerFactory {
* @property {HTMLDivElement} pageDiv * @property {HTMLDivElement} pageDiv
* @property {PDFPageProxy} pdfPage * @property {PDFPageProxy} pdfPage
* @property {IL10n} l10n * @property {IL10n} l10n
* @property {AnnotationStorage} [annotationStorage] - Storage for annotation
* @property {TextAccessibilityManager} [accessibilityManager] * @property {TextAccessibilityManager} [accessibilityManager]
* data in forms.
*/ */
/** /**
@ -251,7 +249,6 @@ class IPDFAnnotationEditorLayerFactory {
pageDiv, pageDiv,
pdfPage, pdfPage,
l10n, l10n,
annotationStorage = null,
accessibilityManager, accessibilityManager,
}) {} }) {}
} }

View File

@ -730,7 +730,8 @@ class PDFViewer {
} else if (isValidAnnotationEditorMode(mode)) { } else if (isValidAnnotationEditorMode(mode)) {
this.#annotationEditorUIManager = new AnnotationEditorUIManager( this.#annotationEditorUIManager = new AnnotationEditorUIManager(
this.container, this.container,
this.eventBus this.eventBus,
this.pdfDocument?.annotationStorage
); );
if (mode !== AnnotationEditorType.NONE) { if (mode !== AnnotationEditorType.NONE) {
this.#annotationEditorUIManager.updateMode(mode); this.#annotationEditorUIManager.updateMode(mode);
@ -1741,9 +1742,7 @@ class PDFViewer {
* @property {HTMLDivElement} pageDiv * @property {HTMLDivElement} pageDiv
* @property {PDFPageProxy} pdfPage * @property {PDFPageProxy} pdfPage
* @property {IL10n} l10n * @property {IL10n} l10n
* @property {AnnotationStorage} [annotationStorage] - Storage for annotation
* @property {TextAccessibilityManager} [accessibilityManager] * @property {TextAccessibilityManager} [accessibilityManager]
* data in forms.
*/ */
/** /**
@ -1756,13 +1755,11 @@ class PDFViewer {
pdfPage, pdfPage,
accessibilityManager = null, accessibilityManager = null,
l10n, l10n,
annotationStorage = this.pdfDocument?.annotationStorage,
}) { }) {
return new AnnotationEditorLayerBuilder({ return new AnnotationEditorLayerBuilder({
uiManager, uiManager,
pageDiv, pageDiv,
pdfPage, pdfPage,
annotationStorage,
accessibilityManager, accessibilityManager,
l10n, l10n,
}); });