[Editor] Don't use the editor parent which can be null.

An annotation editor layer can be destroyed when it's invisible, hence some
annotations can have a null parent but when printing/saving or when changing
font size, color, ... of all added annotations (when selected with ctrl+a) we
still need to have some parent properties especially the page dimensions, global
scale factor and global rotation angle.
This patch aims to remove all the references to the parent in the editor instances
except in some cases where an editor should obviously have one.
It fixes #15780.
This commit is contained in:
Calixte Denizet 2022-12-05 12:25:06 +01:00
parent feb6f5951c
commit b93bf9f654
9 changed files with 270 additions and 126 deletions

View File

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

View File

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

View File

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

View File

@ -165,7 +165,7 @@ class InkEditor extends AnnotationEditor {
*/
#updateThickness(thickness) {
const savedThickness = this.thickness;
this.parent.addCommands({
this.addCommands({
cmd: () => {
this.thickness = thickness;
this.#fitToContent();
@ -187,7 +187,7 @@ class InkEditor extends AnnotationEditor {
*/
#updateColor(color) {
const savedColor = this.color;
this.parent.addCommands({
this.addCommands({
cmd: () => {
this.color = color;
this.#redraw();
@ -210,7 +210,7 @@ class InkEditor extends AnnotationEditor {
#updateOpacity(opacity) {
opacity /= 100;
const savedOpacity = this.opacity;
this.parent.addCommands({
this.addCommands({
cmd: () => {
this.opacity = opacity;
this.#redraw();
@ -268,6 +268,27 @@ class InkEditor extends AnnotationEditor {
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 */
enableEditMode() {
if (this.#disableEditing || this.canvas === null) {
@ -311,14 +332,17 @@ class InkEditor extends AnnotationEditor {
}
#getInitialBBox() {
const { width, height, rotation } = this.parent.viewport;
switch (rotation) {
const {
parentRotation,
parentDimensions: [width, height],
} = this;
switch (parentRotation) {
case 90:
return [0, width, width, height];
return [0, height, height, width];
case 180:
return [width, height, width, height];
case 270:
return [height, 0, width, height];
return [width, 0, height, width];
default:
return [0, 0, width, height];
}
@ -328,12 +352,12 @@ class InkEditor extends AnnotationEditor {
* Set line styles.
*/
#setStroke() {
this.ctx.lineWidth =
(this.thickness * this.parent.scaleFactor) / this.scaleFactor;
this.ctx.lineCap = "round";
this.ctx.lineJoin = "round";
this.ctx.miterLimit = 10;
this.ctx.strokeStyle = `${this.color}${opacityToHex(this.opacity)}`;
const { ctx, color, opacity, thickness, parentScale, scaleFactor } = this;
ctx.lineWidth = (thickness * parentScale) / scaleFactor;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.miterLimit = 10;
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
// move it to the right position in the DOM.
this.parent.moveEditorInDOM(this);
// After the div has been moved in the DOM, the focus may have been stolen
// by document.body, hence we just keep it here.
this.div.focus();
if (this.pageIndex === this._uiManager.currentPageIndex) {
// After the div has been moved in the DOM, the focus may have been stolen
// by document.body, hence we just keep it here.
this.div.focus();
}
}
/** @inheritdoc */
@ -581,7 +607,7 @@ class InkEditor extends AnnotationEditor {
this.#boundCanvasPointermove
);
this.parent.addToAnnotationStorage(this);
this.addToAnnotationStorage();
}
/**
@ -649,7 +675,7 @@ class InkEditor extends AnnotationEditor {
if (this.width) {
// This editor was created in using copy (ctrl+c).
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions;
const [parentWidth, parentHeight] = this.parentDimensions;
this.setAt(
baseX * parentWidth,
baseY * parentHeight,
@ -676,7 +702,7 @@ class InkEditor extends AnnotationEditor {
if (!this.#isCanvasInitialized) {
return;
}
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions;
const [parentWidth, parentHeight] = this.parentDimensions;
this.canvas.width = Math.ceil(this.width * parentWidth);
this.canvas.height = Math.ceil(this.height * parentHeight);
this.#updateTransform();
@ -712,7 +738,7 @@ class InkEditor extends AnnotationEditor {
this.setDims(width, height);
}
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions;
const [parentWidth, parentHeight] = this.parentDimensions;
this.width = width / parentWidth;
this.height = height / parentHeight;
@ -940,7 +966,7 @@ class InkEditor extends AnnotationEditor {
*/
#getPadding() {
return this.#disableEditing
? Math.ceil(this.thickness * this.parent.scaleFactor)
? Math.ceil(this.thickness * this.parentScale)
: 0;
}
@ -967,7 +993,7 @@ class InkEditor extends AnnotationEditor {
const width = Math.ceil(padding + this.#baseWidth * 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.height = height / parentHeight;
@ -1005,17 +1031,17 @@ class InkEditor extends AnnotationEditor {
}
/** @inheritdoc */
static deserialize(data, parent) {
const editor = super.deserialize(data, parent);
static deserialize(data, parent, uiManager) {
const editor = super.deserialize(data, parent, uiManager);
editor.thickness = data.thickness;
editor.color = Util.makeHexColor(...data.color);
editor.opacity = data.opacity;
const [pageWidth, pageHeight] = parent.pageDimensions;
const [pageWidth, pageHeight] = editor.pageDimensions;
const width = editor.width * pageWidth;
const height = editor.height * pageHeight;
const scaleFactor = parent.scaleFactor;
const scaleFactor = editor.parentScale;
const padding = data.thickness / 2;
editor.#aspectRatio = width / height;
@ -1058,10 +1084,6 @@ class InkEditor extends AnnotationEditor {
/** @inheritdoc */
serialize() {
if (this._serialized !== undefined) {
return this._serialized;
}
if (this.isEmpty()) {
return null;
}
@ -1078,12 +1100,12 @@ class InkEditor extends AnnotationEditor {
thickness: this.thickness,
opacity: this.opacity,
paths: this.#serializePaths(
this.scaleFactor / this.parent.scaleFactor,
this.scaleFactor / this.parentScale,
this.translationX,
this.translationY,
height
),
pageIndex: this.parent.pageIndex,
pageIndex: this.pageIndex,
rect,
rotation: this.rotation,
};

View File

@ -25,7 +25,7 @@ import {
Util,
warn,
} from "../../shared/util.js";
import { getColorValues, getRGB } from "../display_utils.js";
import { getColorValues, getRGB, PixelsPerInch } from "../display_utils.js";
function bindEvents(obj, element, names) {
for (const name of names) {
@ -350,12 +350,16 @@ class AnnotationEditorUIManager {
#allLayers = new Map();
#annotationStorage = null;
#commandManager = new CommandManager();
#currentPageIndex = 0;
#editorTypes = null;
#editorsToRescale = new Set();
#eventBus = null;
#idManager = new IdManager();
@ -378,6 +382,10 @@ class AnnotationEditorUIManager {
#boundOnPageChanging = this.onPageChanging.bind(this);
#boundOnScaleChanging = this.onScaleChanging.bind(this);
#boundOnRotationChanging = this.onRotationChanging.bind(this);
#previousStates = {
isEditing: false,
isEmpty: true,
@ -413,22 +421,32 @@ class AnnotationEditorUIManager {
[["Escape", "mac+Escape"], AnnotationEditorUIManager.prototype.unselectAll],
]);
constructor(container, eventBus) {
constructor(container, eventBus, annotationStorage) {
this.#container = container;
this.#eventBus = eventBus;
this.#eventBus._on("editingaction", this.#boundOnEditingAction);
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() {
this.#removeKeyboardManager();
this.#eventBus._off("editingaction", this.#boundOnEditingAction);
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()) {
layer.destroy();
}
this.#allLayers.clear();
this.#allEditors.clear();
this.#editorsToRescale.clear();
this.#activeEditor = null;
this.#selectedEditors.clear();
this.#commandManager.destroy();
@ -442,6 +460,41 @@ class AnnotationEditorUIManager {
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() {
// 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.
@ -646,6 +699,14 @@ class AnnotationEditorUIManager {
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.
* @param {AnnotationEditorLayer} layer
@ -783,6 +844,7 @@ class AnnotationEditorUIManager {
removeEditor(editor) {
this.#allEditors.delete(editor.id);
this.unselect(editor);
this.#annotationStorage?.remove(editor.id);
}
/**

View File

@ -599,6 +599,49 @@ describe("Editor", () => {
[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 {PDFPageProxy} pdfPage
* @property {IL10n} l10n
* @property {AnnotationStorage} [annotationStorage] - Storage for annotation
* @property {TextAccessibilityManager} [accessibilityManager]
* data in forms.
*/
/**
@ -126,7 +124,6 @@ class DefaultAnnotationEditorLayerFactory {
pdfPage,
accessibilityManager = null,
l10n,
annotationStorage = null,
}) {
return new AnnotationEditorLayerBuilder({
uiManager,
@ -134,7 +131,6 @@ class DefaultAnnotationEditorLayerFactory {
pdfPage,
accessibilityManager,
l10n,
annotationStorage,
});
}
}

View File

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

View File

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