diff --git a/src/core/annotation.js b/src/core/annotation.js index 3e9d0d5773927d..c76d658788a372 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -1523,6 +1523,19 @@ class WidgetAnnotation extends Annotation { return !!(this.data.fieldFlags & flag); } + static _getRotationMatrix(rotation, width, height) { + switch (rotation) { + case 90: + return [0, 1, -1, 0, width, 0]; + case 180: + return [-1, 0, 0, -1, width, height]; + case 270: + return [0, -1, 1, 0, 0, height]; + default: + throw new Error("Invalid rotation"); + } + } + getRotationMatrix(annotationStorage) { const storageEntry = annotationStorage ? annotationStorage.get(this.data.id) @@ -1539,16 +1552,7 @@ class WidgetAnnotation extends Annotation { const width = this.data.rect[2] - this.data.rect[0]; const height = this.data.rect[3] - this.data.rect[1]; - switch (rotation) { - case 90: - return [0, 1, -1, 0, width, 0]; - case 180: - return [-1, 0, 0, -1, width, height]; - case 270: - return [0, -1, 1, 0, 0, height]; - default: - throw new Error("Invalid rotation"); - } + return WidgetAnnotation._getRotationMatrix(rotation, width, height); } getBorderAndBackgroundAppearances(annotationStorage) { @@ -3199,7 +3203,7 @@ class FreeTextAnnotation extends MarkupAnnotation { } static createNewDict(annotation, xref, { apRef, ap }) { - const { color, fontSize, rect, user, value } = annotation; + const { color, fontSize, rect, rotation, user, value } = annotation; const freetext = new Dict(xref); freetext.set("Type", Name.get("Annot")); freetext.set("Subtype", Name.get("FreeText")); @@ -3210,7 +3214,7 @@ class FreeTextAnnotation extends MarkupAnnotation { freetext.set("Contents", value); freetext.set("F", 4); freetext.set("Border", [0, 0, 0]); - freetext.set("Rotate", 0); + freetext.set("Rotate", rotation); if (user) { freetext.set("T", stringToUTF8String(user)); @@ -3230,7 +3234,7 @@ class FreeTextAnnotation extends MarkupAnnotation { static async createNewAppearanceStream(annotation, xref, params) { const { baseFontRef, evaluator, task } = params; - const { color, fontSize, rect, value } = annotation; + const { color, fontSize, rect, rotation, value } = annotation; const resources = new Dict(xref); const font = new Dict(xref); @@ -3258,8 +3262,12 @@ class FreeTextAnnotation extends MarkupAnnotation { ); const [x1, y1, x2, y2] = rect; - const w = x2 - x1; - const h = y2 - y1; + let w = x2 - x1; + let h = y2 - y1; + + if (rotation % 180 !== 0) { + [w, h] = [h, w]; + } const lines = value.split("\n"); const scale = fontSize / 1000; @@ -3315,6 +3323,11 @@ class FreeTextAnnotation extends MarkupAnnotation { appearanceStreamDict.set("Length", appearance.length); appearanceStreamDict.set("Resources", resources); + if (rotation) { + const matrix = WidgetAnnotation._getRotationMatrix(rotation, w, h); + appearanceStreamDict.set("Matrix", matrix); + } + const ap = new StringStream(appearance); ap.dict = appearanceStreamDict; @@ -3683,18 +3696,19 @@ class InkAnnotation extends MarkupAnnotation { } static createNewDict(annotation, xref, { apRef, ap }) { + const { paths, rect, rotation } = annotation; const ink = new Dict(xref); ink.set("Type", Name.get("Annot")); ink.set("Subtype", Name.get("Ink")); ink.set("CreationDate", `D:${getModificationDate()}`); - ink.set("Rect", annotation.rect); + ink.set("Rect", rect); ink.set( "InkList", - annotation.paths.map(p => p.points) + paths.map(p => p.points) ); ink.set("F", 4); ink.set("Border", [0, 0, 0]); - ink.set("Rotate", 0); + ink.set("Rotate", rotation); const n = new Dict(xref); ink.set("AP", n); @@ -3709,16 +3723,21 @@ class InkAnnotation extends MarkupAnnotation { } static async createNewAppearanceStream(annotation, xref, params) { - const [x1, y1, x2, y2] = annotation.rect; - const w = x2 - x1; - const h = y2 - y1; + const { color, rect, rotation, paths, thickness } = annotation; + const [x1, y1, x2, y2] = rect; + let w = x2 - x1; + let h = y2 - y1; + + if (rotation % 180 !== 0) { + [w, h] = [h, w]; + } const appearanceBuffer = [ - `${annotation.thickness} w`, - `${getPdfColor(annotation.color, /* isFill */ false)}`, + `${thickness} w`, + `${getPdfColor(color, /* isFill */ false)}`, ]; const buffer = []; - for (const { bezier } of annotation.paths) { + for (const { bezier } of paths) { buffer.length = 0; buffer.push( `${numberToString(bezier[0])} ${numberToString(bezier[1])} m` @@ -3742,6 +3761,11 @@ class InkAnnotation extends MarkupAnnotation { appearanceStreamDict.set("BBox", [0, 0, w, h]); appearanceStreamDict.set("Length", appearance.length); + if (rotation) { + const matrix = WidgetAnnotation._getRotationMatrix(rotation, w, h); + appearanceStreamDict.set("Matrix", matrix); + } + const ap = new StringStream(appearance); ap.dict = appearanceStreamDict; diff --git a/src/core/document.js b/src/core/document.js index 069d1f3611ff44..c89078d90dfe25 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -314,7 +314,6 @@ class Page { ...newData.annotations ); - this.xref.resetNewRef(); return objects; } diff --git a/src/display/annotation_storage.js b/src/display/annotation_storage.js index f3a4098ba2a87e..b4a82fdf97bf98 100644 --- a/src/display/annotation_storage.js +++ b/src/display/annotation_storage.js @@ -145,6 +145,7 @@ class AnnotationStorage { const val = value instanceof AnnotationEditor ? value.serialize() : value; clone.set(key, val); } + return clone; } diff --git a/src/display/editor/annotation_editor_layer.js b/src/display/editor/annotation_editor_layer.js index 98e0038e25d5a7..c5a4f8b931aaa3 100644 --- a/src/display/editor/annotation_editor_layer.js +++ b/src/display/editor/annotation_editor_layer.js @@ -20,8 +20,8 @@ /** @typedef {import("../annotation_storage.js").AnnotationStorage} AnnotationStorage */ /** @typedef {import("../../web/interfaces").IL10n} IL10n */ -import { AnnotationEditorType, Util } from "../../shared/util.js"; import { bindEvents, KeyboardManager } from "./tools.js"; +import { AnnotationEditorType } from "../../shared/util.js"; import { FreeTextEditor } from "./freetext.js"; import { InkEditor } from "./ink.js"; @@ -106,6 +106,7 @@ class AnnotationEditorLayer { } else { this.div.removeEventListener("mouseover", this.#boundMouseover); } + this.setActiveEditor(null); } /** @@ -273,6 +274,11 @@ class AnnotationEditorLayer { if (editor.parent === this) { return; } + + if (this.#uiManager.isActive(editor)) { + editor.parent.setActiveEditor(null); + } + this.attach(editor); editor.pageIndex = this.pageIndex; editor.parent.detach(editor); @@ -419,10 +425,10 @@ class AnnotationEditorLayer { this.#changeParent(editor); const rect = this.div.getBoundingClientRect(); - editor.setAt( - event.clientX - rect.x - editor.mouseX, - event.clientY - rect.y - editor.mouseY - ); + const endX = event.clientX - rect.x; + const endY = event.clientY - rect.y; + + editor.translate(endX - editor.startX, endY - editor.startY); } /** @@ -463,11 +469,9 @@ class AnnotationEditorLayer { */ render(parameters) { this.viewport = parameters.viewport; - this.inverseViewportTransform = Util.inverseTransform( - this.viewport.transform - ); bindEvents(this, this.div, ["dragover", "drop", "keydown"]); this.div.addEventListener("click", this.#boundClick); + this.setDimensions(); } /** @@ -475,17 +479,9 @@ class AnnotationEditorLayer { * @param {Object} parameters */ update(parameters) { - const transform = Util.transform( - parameters.viewport.transform, - this.inverseViewportTransform - ); + this.setActiveEditor(null); this.viewport = parameters.viewport; - this.inverseViewportTransform = Util.inverseTransform( - this.viewport.transform - ); - for (const editor of this.#editors.values()) { - editor.transform(transform); - } + this.setDimensions(); } /** @@ -495,6 +491,38 @@ class AnnotationEditorLayer { get scaleFactor() { return this.viewport.scale; } + + /** + * Get page dimensions. + * @returns {Object} dimensions. + */ + get pageDimensions() { + const [pageLLx, pageLLy, pageURx, pageURy] = this.viewport.viewBox; + const width = pageURx - pageLLx; + const height = pageURy - pageLLy; + + 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. + */ + setDimensions() { + const { width, height, rotation } = this.viewport; + + const flipOrientation = rotation % 180 !== 0, + widthStr = Math.floor(width) + "px", + heightStr = Math.floor(height) + "px"; + + this.div.style.width = flipOrientation ? heightStr : widthStr; + this.div.style.height = flipOrientation ? widthStr : heightStr; + this.div.setAttribute("data-annotation-rotation", rotation); + } } export { AnnotationEditorLayer }; diff --git a/src/display/editor/editor.js b/src/display/editor/editor.js index 6de6c7cedc999e..82886c6c33d87e 100644 --- a/src/display/editor/editor.js +++ b/src/display/editor/editor.js @@ -16,8 +16,8 @@ // eslint-disable-next-line max-len /** @typedef {import("./annotation_editor_layer.js").AnnotationEditorLayer} AnnotationEditorLayer */ -import { unreachable, Util } from "../../shared/util.js"; import { bindEvents } from "./tools.js"; +import { unreachable } from "../../shared/util.js"; /** * @typedef {Object} AnnotationEditorParameters @@ -47,8 +47,11 @@ class AnnotationEditor { this.pageIndex = parameters.parent.pageIndex; this.name = parameters.name; this.div = null; - this.x = Math.round(parameters.x); - this.y = Math.round(parameters.y); + + const [width, height] = this.parent.viewportBaseDimensions; + this.x = parameters.x / width; + this.y = parameters.y / height; + this.rotation = this.parent.viewport.rotation; this.isAttachedToDOM = false; } @@ -107,21 +110,14 @@ class AnnotationEditor { } } - /** - * Get the pointer coordinates in order to correctly translate the - * div in case of drag-and-drop. - * @param {MouseEvent} event - */ - mousedown(event) { - this.mouseX = event.offsetX; - this.mouseY = event.offsetY; - } - /** * We use drag-and-drop in order to move an editor on a page. * @param {DragEvent} event */ dragstart(event) { + const rect = this.parent.div.getBoundingClientRect(); + this.startX = event.clientX - rect.x; + this.startY = event.clientY - rect.y; event.dataTransfer.setData("text/plain", this.id); event.dataTransfer.effectAllowed = "move"; } @@ -131,21 +127,51 @@ class AnnotationEditor { * @param {number} x * @param {number} y */ - setAt(x, y) { - this.x = Math.round(x); - this.y = Math.round(y); + setAt(x, y, tx, ty) { + const [width, height] = this.parent.viewportBaseDimensions; + [tx, ty] = this.screenToPageTranslation(tx, ty); + + this.x = (x + tx) / width; + this.y = (y + ty) / height; - this.div.style.left = `${this.x}px`; - this.div.style.top = `${this.y}px`; + this.div.style.left = `${100 * this.x}%`; + this.div.style.top = `${100 * this.y}%`; } /** * Translate the editor position within its parent. * @param {number} x * @param {number} y + * @param {boolean} isScreen. */ translate(x, y) { - this.setAt(this.x + x, this.y + y); + const [width, height] = this.parent.viewportBaseDimensions; + [x, y] = this.screenToPageTranslation(x, y); + + this.x += x / width; + this.y += y / height; + + this.div.style.left = `${100 * this.x}%`; + this.div.style.top = `${100 * this.y}%`; + } + + /** + * Convert a screen translation into a page one. + * @param {number} x + * @param {number} y + */ + screenToPageTranslation(x, y) { + const { rotation } = this.parent.viewport; + switch (rotation) { + case 90: + return [y, -x]; + case 180: + return [-x, -y]; + case 270: + return [-y, x]; + default: + return [x, y]; + } } /** @@ -154,8 +180,9 @@ class AnnotationEditor { * @param {number} height */ setDims(width, height) { - this.div.style.width = `${width}px`; - this.div.style.height = `${height}px`; + const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; + this.div.style.width = `${(100 * width) / parentWidth}%`; + this.div.style.height = `${(100 * height) / parentHeight}%`; } /** @@ -172,54 +199,68 @@ class AnnotationEditor { */ render() { this.div = document.createElement("div"); + this.div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360); this.div.className = this.name; this.div.setAttribute("id", this.id); this.div.tabIndex = 100; const [tx, ty] = this.getInitialTranslation(); - this.x = Math.round(this.x + tx); - this.y = Math.round(this.y + ty); - - this.div.style.left = `${this.x}px`; - this.div.style.top = `${this.y}px`; + this.translate(tx, ty); - bindEvents(this, this.div, [ - "dragstart", - "focusin", - "focusout", - "mousedown", - ]); + bindEvents(this, this.div, ["dragstart", "focusin", "focusout"]); return this.div; } + 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 x = this.x * pageWidth; + const y = this.y * pageHeight; + const width = this.width * pageWidth; + const height = this.height * pageHeight; + + switch (this.rotation) { + case 0: + return [ + x + shiftX, + pageHeight - y - shiftY - height, + x + shiftX + width, + pageHeight - y - shiftY, + ]; + case 90: + return [ + x + shiftY, + pageHeight - y + shiftX, + x + shiftY + height, + pageHeight - y + shiftX + width, + ]; + case 180: + return [ + x - shiftX - width, + pageHeight - y + shiftY, + x - shiftX, + pageHeight - y + shiftY + height, + ]; + case 270: + return [ + x - shiftY - height, + pageHeight - y - shiftX - width, + x - shiftY, + pageHeight - y - shiftX, + ]; + default: + throw new Error("Invalid rotation"); + } + } + /** * Executed once this editor has been rendered. */ onceAdded() {} - /** - * Apply the current transform (zoom) to this editor. - * @param {Array} transform - */ - transform(transform) { - const { style } = this.div; - const width = parseFloat(style.width); - const height = parseFloat(style.height); - - const [x1, y1] = Util.applyTransform([this.x, this.y], transform); - - if (!Number.isNaN(width)) { - const [x2] = Util.applyTransform([this.x + width, 0], transform); - this.div.style.width = `${x2 - x1}px`; - } - if (!Number.isNaN(height)) { - const [, y2] = Util.applyTransform([0, this.y + height], transform); - this.div.style.height = `${y2 - y1}px`; - } - this.setAt(x1, y1); - } - /** * Check if the editor contains something. * @returns {boolean} diff --git a/src/display/editor/freetext.js b/src/display/editor/freetext.js index d657f2e3dd9174..3094c238d1950e 100644 --- a/src/display/editor/freetext.js +++ b/src/display/editor/freetext.js @@ -17,7 +17,6 @@ import { AnnotationEditorType, assert, LINE_FACTOR, - Util, } from "../../shared/util.js"; import { AnnotationEditor } from "./editor.js"; import { bindEvents } from "./tools.js"; @@ -72,11 +71,12 @@ class FreeTextEditor extends AnnotationEditor { /** @inheritdoc */ copy() { + const [width, height] = this.parent.viewportBaseDimensions; const editor = new FreeTextEditor({ parent: this.parent, id: this.parent.getNextId(), - x: this.x, - y: this.y, + x: this.x * width, + y: this.y * height, }); editor.width = this.width; @@ -180,9 +180,10 @@ class FreeTextEditor extends AnnotationEditor { this.#contentHTML = this.editorDiv.innerHTML; this.#content = this.#extractText().trimEnd(); + const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; const style = getComputedStyle(this.div); - this.width = parseFloat(style.width); - this.height = parseFloat(style.height); + this.width = parseFloat(style.width) / parentWidth; + this.height = parseFloat(style.height) / parentHeight; } /** @inheritdoc */ @@ -205,6 +206,12 @@ class FreeTextEditor extends AnnotationEditor { return this.div; } + let baseX, baseY; + if (this.width) { + baseX = this.x; + baseY = this.y; + } + super.render(); this.editorDiv = document.createElement("div"); this.editorDiv.tabIndex = 0; @@ -232,7 +239,13 @@ class FreeTextEditor extends AnnotationEditor { if (this.width) { // This editor was created in using copy (ctrl+c). - this.setAt(this.x + this.width, this.y + this.height); + const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; + this.setAt( + baseX * parentWidth, + baseY * parentHeight, + this.width * parentWidth, + this.height * parentHeight + ); // eslint-disable-next-line no-unsanitized/property this.editorDiv.innerHTML = this.#contentHTML; } @@ -242,24 +255,17 @@ class FreeTextEditor extends AnnotationEditor { /** @inheritdoc */ serialize() { - const rect = this.editorDiv.getBoundingClientRect(); const padding = FreeTextEditor._internalPadding * this.parent.scaleFactor; - const [x1, y1] = Util.applyTransform( - [this.x + padding, this.y + padding + rect.height], - this.parent.inverseViewportTransform - ); + const rect = this.getRect(padding, padding); - const [x2, y2] = Util.applyTransform( - [this.x + padding + rect.width, this.y + padding], - this.parent.inverseViewportTransform - ); return { annotationType: AnnotationEditorType.FREETEXT, color: [0, 0, 0], fontSize: this.#fontSize, value: this.#content, pageIndex: this.parent.pageIndex, - rect: [x1, y1, x2, y2], + rect, + rotation: this.rotation, }; } } diff --git a/src/display/editor/ink.js b/src/display/editor/ink.js index e9bc81232e6a3c..b5de8996adc3c1 100644 --- a/src/display/editor/ink.js +++ b/src/display/editor/ink.js @@ -39,6 +39,10 @@ class InkEditor extends AnnotationEditor { #observer = null; + #realWidth = 0; + + #realHeight = 0; + constructor(params) { super({ ...params, name: "inkEditor" }); this.color = params.color || "CanvasText"; @@ -79,6 +83,8 @@ class InkEditor extends AnnotationEditor { editor.#baseWidth = this.#baseWidth; editor.#baseHeight = this.#baseHeight; editor.#disableEditing = this.#disableEditing; + editor.#realWidth = this.#realWidth; + editor.#realHeight = this.#realHeight; return editor; } @@ -135,7 +141,7 @@ class InkEditor extends AnnotationEditor { /** @inheritdoc */ disableEditMode() { - if (!this.isInEditMode()) { + if (!this.isInEditMode() || this.canvas === null) { return; } @@ -159,6 +165,19 @@ class InkEditor extends AnnotationEditor { return this.paths.length === 0; } + #getInitialBBox() { + const { width, height, rotation } = this.parent.viewport; + switch (rotation) { + case 90: + return [0, width, width, height]; + case 180: + return [width, height, width, height]; + case 270: + return [height, 0, width, height]; + } + return [0, 0, width, height]; + } + /** * Set line styles. */ @@ -257,9 +276,10 @@ class InkEditor extends AnnotationEditor { return; } + const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; const { ctx, height, width } = this; ctx.setTransform(1, 0, 0, 1, 0, 0); - ctx.clearRect(0, 0, width, height); + ctx.clearRect(0, 0, width * parentWidth, height * parentHeight); this.#updateTransform(); for (const path of this.bezierPath2D) { ctx.stroke(path); @@ -390,15 +410,30 @@ class InkEditor extends AnnotationEditor { return this.div; } - super.render(); - this.#createCanvas(); + let baseX, baseY; + if (this.width) { + baseX = this.x; + baseY = this.y; + } + super.render(); this.div.classList.add("editing"); + const [x, y, w, h] = this.#getInitialBBox(); + this.setAt(x, y, 0, 0); + this.setDims(w, h); + + this.#createCanvas(); if (this.width) { // This editor was created in using copy (ctrl+c). - this.setAt(this.x + this.width, this.y + this.height); - this.setDims(this.width, this.height); + const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; + this.setAt( + baseX * parentWidth, + baseY * parentHeight, + this.width * parentWidth, + this.height * parentHeight + ); + this.setDims(this.width * parentWidth, this.height * parentHeight); this.#setCanvasDims(); this.#redraw(); this.div.classList.add("disabled"); @@ -410,8 +445,9 @@ class InkEditor extends AnnotationEditor { } #setCanvasDims() { - this.canvas.width = this.width; - this.canvas.height = this.height; + const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; + this.canvas.width = this.width * parentWidth; + this.canvas.height = this.height * parentHeight; this.#updateTransform(); } @@ -423,19 +459,28 @@ class InkEditor extends AnnotationEditor { * @returns */ setDimensions(width, height) { - if (this.width === width && this.height === height) { + const roundedWidth = Math.round(width); + const roundedHeight = Math.round(height); + if ( + this.#realWidth === roundedWidth && + this.#realHeight === roundedHeight + ) { return; } + this.#realWidth = roundedWidth; + this.#realHeight = roundedHeight; + this.canvas.style.visibility = "hidden"; if (this.#aspectRatio) { height = Math.ceil(width / this.#aspectRatio); - this.div.style.height = `${height}px`; + this.setDims(width, height); } - this.width = width; - this.height = height; + const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; + this.width = width / parentWidth; + this.height = height / parentHeight; if (this.#disableEditing) { const padding = this.#getPadding(); @@ -682,8 +727,9 @@ class InkEditor extends AnnotationEditor { const width = Math.ceil(padding + this.#baseWidth * this.scaleFactor); const height = Math.ceil(padding + this.#baseHeight * this.scaleFactor); - this.width = width; - this.height = height; + const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions; + this.width = width / parentWidth; + this.height = height / parentHeight; this.#aspectRatio = width / height; @@ -704,16 +750,9 @@ class InkEditor extends AnnotationEditor { /** @inheritdoc */ serialize() { - const rect = this.div.getBoundingClientRect(); - const [x1, y1] = Util.applyTransform( - [this.x, this.y + rect.height], - this.parent.inverseViewportTransform - ); - - const [x2, y2] = Util.applyTransform( - [this.x + rect.width, this.y], - this.parent.inverseViewportTransform - ); + const rect = this.getRect(0, 0); + const height = + this.rotation % 180 === 0 ? rect[3] - rect[1] : rect[2] - rect[0]; return { annotationType: AnnotationEditorType.INK, @@ -723,10 +762,11 @@ class InkEditor extends AnnotationEditor { this.scaleFactor / this.parent.scaleFactor, this.translationX, this.translationY, - y2 - y1 + height ), pageIndex: this.parent.pageIndex, - rect: [x1, y1, x2, y2], + rect, + rotation: this.rotation, }; } } diff --git a/test/test_manifest.json b/test/test_manifest.json index 5cb901d6bd4da3..0645e57df22eba 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -6639,6 +6639,7 @@ "value": "Hello World", "pageIndex": 0, "rect": [67.5, 543, 119, 556.5], + "rotation": 0, "orderIndex": 0 }, "pdfjs_internal_editor_1": { @@ -6663,8 +6664,164 @@ }], "pageIndex": 0, "rect": [71.5, 534.5, 115, 562], + "rotation": 0, "orderIndex": 1 } } + }, + { + "id": "tracemonkey-editor-rotation", + "file": "pdfs/tracemonkey.pdf", + "md5": "9a192d8b1a7dc652a19835f6f08098bd", + "rounds": 1, + "lastPage": 1, + "type": "eq", + "print": true, + "annotationStorage": { + "pdfjs_internal_editor_3": { + "annotationType": 3, + "color": [0, 0, 0], + "fontSize": 10, + "value": "Hello", + "pageIndex": 0, + "rect": [ + 96.34615384615385, 554.8461538461538, 119.12498076923077, + 568.3461538461538 + ], + "rotation": 0 + }, + "pdfjs_internal_editor_4": { + "annotationType": 3, + "color": [0, 0, 0], + "fontSize": 10, + "value": "PDF", + "pageIndex": 0, + "rect": [ + 125.57692307692308, 533.5384615384615, 139.07692307692307, + 553.5384807692308 + ], + "rotation": 90 + }, + "pdfjs_internal_editor_5": { + "annotationType": 3, + "color": [0, 0, 0], + "fontSize": 10, + "value": ".js", + "pageIndex": 0, + "rect": [ + 105.9615576923077, 535.0769230769231, 115.96153846153847, + 548.5769230769231 + ], + "rotation": 180 + }, + "pdfjs_internal_editor_6": { + "annotationType": 3, + "color": [0, 0, 0], + "fontSize": 10, + "value": "world!", + "pageIndex": 0, + "rect": [ + 77.84615384615385, 531.682673076923, 91.34615384615385, + 558.3461538461538 + ], + "rotation": 270 + }, + "pdfjs_internal_editor_21": { + "annotationType": 15, + "color": [0, 0, 0], + "thickness": 1, + "paths": [ + { + "bezier": [ + 0.5, 15.653846153846189, 0.5, 10.612792605955292, + 2.221156193659856, 5.960961418318131, 2.221156193659856, + 0.7371591421274406 + ], + "points": [ + 0.5, 15.653846153846189, 2.221156193659856, 0.7371591421274406 + ] + } + ], + "pageIndex": 0, + "rect": [ + 416.53846153846155, 561.8076923076923, 419.41346388596753, + 577.9615384615385 + ], + "rotation": 0 + }, + "pdfjs_internal_editor_23": { + "annotationType": 15, + "color": [0, 0, 0], + "thickness": 1, + "paths": [ + { + "bezier": [ + 0.5, 18.538461538461547, 0.5, 12.869221974582576, + 3.9307267310416893, 5.207607308237302, 1.6538461538461537, + 0.653846153846164 + ], + "points": [ + 0.5, 18.538461538461547, 2.434116685812059, 4.572198481030599, + 1.9307532933714027, 9.17784944259592, 1.6538461538461537, + 0.653846153846164 + ] + } + ], + "pageIndex": 0, + "rect": [ + 390.00000000000006, 543.4615384615386, 409.0384615384616, + 546.9134638859676 + ], + "rotation": 90 + }, + "pdfjs_internal_editor_25": { + "annotationType": 15, + "color": [0, 0, 0], + "thickness": 1, + "paths": [ + { + "bezier": [ + 0.5, 24.307692307692264, 0.5, 16.218230266280443, + 1.6442331167367787, 8.976323168614734, 1.6442331167367787, + 0.8509134145882982 + ], + "points": [ + 0.5, 24.307692307692264, 1.6442331167367787, 0.8509134145882982 + ] + } + ], + "pageIndex": 0, + "rect": [ + 422.7788438063401, 515.7692307692307, 425.07692307692304, + 540.576923076923 + ], + "rotation": 180 + }, + "pdfjs_internal_editor_27": { + "annotationType": 15, + "color": [0, 0, 0], + "thickness": 1, + "paths": [ + { + "bezier": [ + 0.5, 32.96153846153845, 4.262222952239026, 32.96153846153845, + 2.8076923076923075, 4.355429108316972, 2.8076923076923075, + 0.6538461538461462 + ], + "points": [ + 0.5, 32.96153846153845, 2.9761779872739975, 7.263528385840449, + 3.0646797609357885, 18.195785915618856, 2.8076923076923075, + 0.6538461538461462 + ] + } + ], + "pageIndex": 0, + "rect": [ + 425.6538461538462, 553.7403822678785, 459.11538461538464, + 557.7692307692307 + ], + "rotation": 270 + } + } } ] diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js index 78ff722f076ac4..76e24b3c0edd9b 100644 --- a/test/unit/annotation_spec.js +++ b/test/unit/annotation_spec.js @@ -4027,6 +4027,7 @@ describe("annotation", function () { { annotationType: AnnotationEditorType.FREETEXT, rect: [12, 34, 56, 78], + rotation: 0, fontSize: 10, color: [0, 0, 0], value: "Hello PDF.js World!", @@ -4078,6 +4079,7 @@ describe("annotation", function () { { annotationType: AnnotationEditorType.FREETEXT, rect: [12, 34, 56, 78], + rotation: 0, fontSize: 10, color: [0, 0, 0], value: "A", @@ -4181,6 +4183,7 @@ describe("annotation", function () { { annotationType: AnnotationEditorType.INK, rect: [12, 34, 56, 78], + rotation: 0, thickness: 1, color: [0, 0, 0], paths: [ @@ -4239,6 +4242,7 @@ describe("annotation", function () { { annotationType: AnnotationEditorType.INK, rect: [12, 34, 56, 78], + rotation: 0, thickness: 1, color: [0, 0, 0], paths: [ diff --git a/web/annotation_editor_layer_builder.css b/web/annotation_editor_layer_builder.css index fe4b9b1553f1ab..eaf697a91e42d9 100644 --- a/web/annotation_editor_layer_builder.css +++ b/web/annotation_editor_layer_builder.css @@ -20,14 +20,23 @@ --freetext-padding: 2px; } +[data-editor-rotation="90"] { + transform: rotate(90deg); +} +[data-editor-rotation="180"] { + transform: rotate(180deg); +} +[data-editor-rotation="270"] { + transform: rotate(270deg); +} + .annotationEditorLayer { background: transparent; - width: 100%; - height: 100%; position: absolute; top: 0; left: 0; font-size: calc(100px * var(--scale-factor)); + transform-origin: 0 0; } .annotationEditorLayer .freeTextEditor { @@ -38,6 +47,7 @@ resize: none; width: auto; height: auto; + transform-origin: 0 0; } .annotationEditorLayer .freeTextEditor .internal { @@ -88,6 +98,7 @@ outline: var(--hover-outline); } +.annotationEditorLayer .inkEditor.disabled:focus, .annotationEditorLayer .inkEditor.disabled:focus { resize: horizontal; } @@ -100,6 +111,7 @@ width: 100%; height: 100%; z-index: 1; + transform-origin: 0 0; } .annotationEditorLayer .background { diff --git a/web/annotation_editor_layer_builder.js b/web/annotation_editor_layer_builder.js index 7c785dd0e3fbb3..412941ce89c889 100644 --- a/web/annotation_editor_layer_builder.js +++ b/web/annotation_editor_layer_builder.js @@ -66,8 +66,9 @@ class AnnotationEditorLayerBuilder { return; } + const clonedViewport = viewport.clone({ dontFlip: true }); if (this.div) { - this.annotationEditorLayer.update({ viewport: viewport.clone() }); + this.annotationEditorLayer.update({ viewport: clonedViewport }); this.show(); return; } @@ -86,7 +87,7 @@ class AnnotationEditorLayerBuilder { }); const parameters = { - viewport: viewport.clone(), + viewport: clonedViewport, div: this.div, annotations: null, intent,