From 3849063d3621ced13987d66157748b530f5d77da Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Sun, 5 Mar 2023 13:57:27 +0100 Subject: [PATCH] [Annotation] Don't rotate an annotation when it has the NoRotate flag --- src/core/annotation.js | 15 +++++ src/display/annotation_layer.js | 64 +++++++++++---------- test/annotation_layer_builder_overrides.css | 2 +- test/driver.js | 4 ++ test/test_manifest.json | 27 +++++++++ web/annotation_layer_builder.css | 29 ++++++++-- 6 files changed, 105 insertions(+), 36 deletions(-) diff --git a/src/core/annotation.js b/src/core/annotation.js index 0993bfd6b..331c43c9a 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -486,6 +486,7 @@ class Annotation { rect: this.rectangle, subtype: params.subtype, hasOwnCanvas: false, + noRotate: !!(this.flags & AnnotationFlag.NOROTATE), }; if (params.collectFields) { @@ -3415,6 +3416,7 @@ class SignatureWidgetAnnotation extends WidgetAnnotation { // non-serializable and will thus cause errors when sending annotations // to the main-thread (issue 10347). this.data.fieldValue = null; + this.data.hasOwnCanvas = this.data.noRotate; } getFieldObject() { @@ -3433,6 +3435,10 @@ class TextAnnotation extends MarkupAnnotation { super(params); + // No rotation for Text (see 12.5.6.4). + this.data.noRotate = true; + this.data.hasOwnCanvas = this.data.noRotate; + const { dict } = params; this.data.annotationType = AnnotationType.TEXT; @@ -3551,6 +3557,8 @@ class FreeTextAnnotation extends MarkupAnnotation { constructor(params) { super(params); + this.data.hasOwnCanvas = this.data.noRotate; + const { xref } = params; this.data.annotationType = AnnotationType.FREETEXT; this.setDefaultAppearance(params); @@ -3731,6 +3739,7 @@ class LineAnnotation extends MarkupAnnotation { const { dict, xref } = params; this.data.annotationType = AnnotationType.LINE; + this.data.hasOwnCanvas = this.data.noRotate; const lineCoordinates = dict.getArray("L"); this.data.lineCoordinates = Util.normalizeRect(lineCoordinates); @@ -3795,6 +3804,7 @@ class SquareAnnotation extends MarkupAnnotation { const { dict, xref } = params; this.data.annotationType = AnnotationType.SQUARE; + this.data.hasOwnCanvas = this.data.noRotate; if (!this.appearance) { // The default stroke color is black. @@ -3906,6 +3916,7 @@ class PolylineAnnotation extends MarkupAnnotation { const { dict, xref } = params; this.data.annotationType = AnnotationType.POLYLINE; + this.data.hasOwnCanvas = this.data.noRotate; this.data.vertices = []; if (!(this instanceof PolygonAnnotation)) { @@ -3990,6 +4001,8 @@ class InkAnnotation extends MarkupAnnotation { constructor(params) { super(params); + this.data.hasOwnCanvas = this.data.noRotate; + const { dict, xref } = params; this.data.annotationType = AnnotationType.INK; this.data.inkLists = []; @@ -4325,6 +4338,7 @@ class StampAnnotation extends MarkupAnnotation { super(params); this.data.annotationType = AnnotationType.STAMP; + this.data.hasOwnCanvas = this.data.noRotate; } } @@ -4336,6 +4350,7 @@ class FileAttachmentAnnotation extends MarkupAnnotation { const file = new FileSpec(dict.get("FS"), xref); this.data.annotationType = AnnotationType.FILEATTACHMENT; + this.data.hasOwnCanvas = this.data.noRotate; this.data.file = file.serializable; const name = dict.get("Name"); diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index e80d50f5d..a70c4455b 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -199,6 +199,10 @@ class AnnotationElement { const container = document.createElement("section"); container.setAttribute("data-annotation-id", data.id); + if (data.noRotate) { + container.classList.add("norotate"); + } + const { pageWidth, pageHeight, pageX, pageY } = viewport.rawDims; const { width, height } = getRectDims(data.rect); @@ -454,7 +458,7 @@ class AnnotationElement { // If no trigger element is specified, create it. if (!trigger) { trigger = document.createElement("div"); - trigger.className = "popupTriggerArea"; + trigger.classList.add("popupTriggerArea"); container.append(trigger); } @@ -493,7 +497,7 @@ class AnnotationElement { } for (const quadrilateral of this.quadrilaterals) { - quadrilateral.className = className; + quadrilateral.classList.add(className); } return this.quadrilaterals; } @@ -622,7 +626,7 @@ class LinkAnnotationElement extends AnnotationElement { ); } - this.container.className = "linkAnnotation"; + this.container.classList.add("linkAnnotation"); if (isBound) { this.container.append(link); } @@ -857,7 +861,7 @@ class TextAnnotationElement extends AnnotationElement { } render() { - this.container.className = "textAnnotation"; + this.container.classList.add("textAnnotation"); const image = document.createElement("img"); image.src = @@ -1029,7 +1033,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { const storage = this.annotationStorage; const id = this.data.id; - this.container.className = "textWidgetAnnotation"; + this.container.classList.add("textWidgetAnnotation"); let element = null; if (this.renderForms) { @@ -1357,7 +1361,7 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement { storage.setValue(id, { value }); } - this.container.className = "buttonWidgetAnnotation checkBox"; + this.container.classList.add("buttonWidgetAnnotation", "checkBox"); const element = document.createElement("input"); GetElementsByNameSet.add(element); @@ -1431,7 +1435,7 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement { } render() { - this.container.className = "buttonWidgetAnnotation radioButton"; + this.container.classList.add("buttonWidgetAnnotation", "radioButton"); const storage = this.annotationStorage; const data = this.data; const id = data.id; @@ -1525,7 +1529,7 @@ class PushButtonWidgetAnnotationElement extends LinkAnnotationElement { // equal to that of a link annotation, but may have more functionality, such // as performing actions on form fields (resetting, submitting, et cetera). const container = super.render(); - container.className = "buttonWidgetAnnotation pushButton"; + container.classList.add("buttonWidgetAnnotation", "pushButton"); if (this.data.alternativeText) { container.title = this.data.alternativeText; @@ -1550,7 +1554,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { } render() { - this.container.className = "choiceWidgetAnnotation"; + this.container.classList.add("choiceWidgetAnnotation"); const storage = this.annotationStorage; const id = this.data.id; @@ -1810,7 +1814,7 @@ class PopupAnnotationElement extends AnnotationElement { } render() { - this.container.className = "popupAnnotation"; + this.container.classList.add("popupAnnotation"); const parentElements = this.layer.querySelectorAll( `[data-annotation-id="${this.data.parentId}"]` @@ -1870,7 +1874,7 @@ class PopupElement { const BACKGROUND_ENLIGHT = 0.7; const wrapper = document.createElement("div"); - wrapper.className = "popupWrapper"; + wrapper.classList.add("popupWrapper"); // For Popup annotations we hide the entire section because it contains // only the popup. However, for Text annotations without a separate Popup @@ -1880,7 +1884,7 @@ class PopupElement { this.hideElement.hidden = true; const popup = document.createElement("div"); - popup.className = "popup"; + popup.classList.add("popup"); const color = this.color; if (color) { @@ -1902,7 +1906,7 @@ class PopupElement { const dateObject = PDFDateString.toDateObject(this.modificationDate); if (dateObject) { const modificationDate = document.createElement("span"); - modificationDate.className = "popupDate"; + modificationDate.classList.add("popupDate"); modificationDate.textContent = "{{date}}, {{time}}"; modificationDate.dataset.l10nId = "annotation_date_string"; modificationDate.dataset.l10nArgs = JSON.stringify({ @@ -1921,7 +1925,7 @@ class PopupElement { intent: "richText", div: popup, }); - popup.lastChild.className = "richText popupContent"; + popup.lastChild.classList.add("richText", "popupContent"); } else { const contents = this._formatContents(this.contentsObj); popup.append(contents); @@ -1953,7 +1957,7 @@ class PopupElement { */ _formatContents({ str, dir }) { const p = document.createElement("p"); - p.className = "popupContent"; + p.classList.add("popupContent"); p.dir = dir; const lines = str.split(/(?:\r\n?|\n)/); for (let i = 0, ii = lines.length; i < ii; ++i) { @@ -2030,11 +2034,11 @@ class FreeTextAnnotationElement extends AnnotationElement { } render() { - this.container.className = "freeTextAnnotation"; + this.container.classList.add("freeTextAnnotation"); if (this.textContent) { const content = document.createElement("div"); - content.className = "annotationTextContent"; + content.classList.add("annotationTextContent"); content.setAttribute("role", "comment"); for (const line of this.textContent) { const lineSpan = document.createElement("span"); @@ -2063,7 +2067,7 @@ class LineAnnotationElement extends AnnotationElement { } render() { - this.container.className = "lineAnnotation"; + this.container.classList.add("lineAnnotation"); // Create an invisible line with the same starting and ending coordinates // that acts as the trigger for the popup. Only the line itself should @@ -2112,7 +2116,7 @@ class SquareAnnotationElement extends AnnotationElement { } render() { - this.container.className = "squareAnnotation"; + this.container.classList.add("squareAnnotation"); // Create an invisible square with the same rectangle that acts as the // trigger for the popup. Only the square itself should trigger the @@ -2163,7 +2167,7 @@ class CircleAnnotationElement extends AnnotationElement { } render() { - this.container.className = "circleAnnotation"; + this.container.classList.add("circleAnnotation"); // Create an invisible circle with the same ellipse that acts as the // trigger for the popup. Only the circle itself should trigger the @@ -2217,7 +2221,7 @@ class PolylineAnnotationElement extends AnnotationElement { } render() { - this.container.className = this.containerClassName; + this.container.classList.add(this.containerClassName); // Create an invisible polyline with the same points that acts as the // trigger for the popup. Only the polyline itself should trigger the @@ -2283,7 +2287,7 @@ class CaretAnnotationElement extends AnnotationElement { } render() { - this.container.className = "caretAnnotation"; + this.container.classList.add("caretAnnotation"); if (!this.data.hasPopup) { this._createPopup(null, this.data); @@ -2310,7 +2314,7 @@ class InkAnnotationElement extends AnnotationElement { } render() { - this.container.className = this.containerClassName; + this.container.classList.add(this.containerClassName); // Create an invisible polyline with the same points that acts as the // trigger for the popup. @@ -2379,7 +2383,7 @@ class HighlightAnnotationElement extends AnnotationElement { return this._renderQuadrilaterals("highlightAnnotation"); } - this.container.className = "highlightAnnotation"; + this.container.classList.add("highlightAnnotation"); return this.container; } } @@ -2408,7 +2412,7 @@ class UnderlineAnnotationElement extends AnnotationElement { return this._renderQuadrilaterals("underlineAnnotation"); } - this.container.className = "underlineAnnotation"; + this.container.classList.add("underlineAnnotation"); return this.container; } } @@ -2437,7 +2441,7 @@ class SquigglyAnnotationElement extends AnnotationElement { return this._renderQuadrilaterals("squigglyAnnotation"); } - this.container.className = "squigglyAnnotation"; + this.container.classList.add("squigglyAnnotation"); return this.container; } } @@ -2466,7 +2470,7 @@ class StrikeOutAnnotationElement extends AnnotationElement { return this._renderQuadrilaterals("strikeoutAnnotation"); } - this.container.className = "strikeoutAnnotation"; + this.container.classList.add("strikeoutAnnotation"); return this.container; } } @@ -2483,7 +2487,7 @@ class StampAnnotationElement extends AnnotationElement { } render() { - this.container.className = "stampAnnotation"; + this.container.classList.add("stampAnnotation"); if (!this.data.hasPopup) { this._createPopup(null, this.data); @@ -2508,7 +2512,7 @@ class FileAttachmentAnnotationElement extends AnnotationElement { } render() { - this.container.className = "fileAttachmentAnnotation"; + this.container.classList.add("fileAttachmentAnnotation"); let trigger; if (this.data.hasAppearance) { @@ -2524,7 +2528,7 @@ class FileAttachmentAnnotationElement extends AnnotationElement { /paperclip/i.test(this.data.name) ? "paperclip" : "pushpin" }.svg`; } - trigger.className = "popupTriggerArea"; + trigger.classList.add("popupTriggerArea"); trigger.addEventListener("dblclick", this._download.bind(this)); if ( diff --git a/test/annotation_layer_builder_overrides.css b/test/annotation_layer_builder_overrides.css index dfd683afb..d28e84628 100644 --- a/test/annotation_layer_builder_overrides.css +++ b/test/annotation_layer_builder_overrides.css @@ -19,7 +19,7 @@ position: absolute; } -.annotationLayer .buttonWidgetAnnotation.pushButton > img { +.annotationLayer .wasCanvas { width: 100%; height: 100%; position: absolute; diff --git a/test/driver.js b/test/driver.js index dff07ed6e..e722ca7bb 100644 --- a/test/driver.js +++ b/test/driver.js @@ -135,6 +135,7 @@ async function convertCanvasesToImages(annotationCanvasMap, outputScale) { new Promise(resolve => { canvas.toBlob(blob => { const image = document.createElement("img"); + image.classList.add("wasCanvas"); image.onload = function () { image.style.width = Math.floor(image.width / outputScale) + "px"; resolve(); @@ -625,6 +626,9 @@ class Driver { let viewport = page.getViewport({ scale: PixelsPerInch.PDF_TO_CSS_UNITS, }); + if (task.rotation) { + viewport = viewport.clone({ rotation: task.rotation }); + } // Restrict the test from creating a canvas that is too big. const MAX_CANVAS_PIXEL_DIMENSION = 4096; const largestDimension = Math.max(viewport.width, viewport.height); diff --git a/test/test_manifest.json b/test/test_manifest.json index 3d874f371..7cdb058a5 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -7394,5 +7394,32 @@ "rounds": 1, "link": true, "type": "eq" + }, + { + "id": "annotation-link-text-popup-rotated-90", + "file": "pdfs/annotation-link-text-popup.pdf", + "md5": "4bbf56e81d47232de5f305124ab0ba27", + "rounds": 1, + "type": "eq", + "annotations": true, + "rotation": 90 + }, + { + "id": "annotation-link-text-popup-rotated-180", + "file": "pdfs/annotation-link-text-popup.pdf", + "md5": "4bbf56e81d47232de5f305124ab0ba27", + "rounds": 1, + "type": "eq", + "annotations": true, + "rotation": 180 + }, + { + "id": "annotation-link-text-popup-rotated-270", + "file": "pdfs/annotation-link-text-popup.pdf", + "md5": "4bbf56e81d47232de5f305124ab0ba27", + "rounds": 1, + "type": "eq", + "annotations": true, + "rotation": 270 } ] diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css index ba1bb1dfe..6a6d95b44 100644 --- a/web/annotation_layer_builder.css +++ b/web/annotation_layer_builder.css @@ -53,6 +53,22 @@ z-index: 3; } +.annotationLayer[data-main-rotation="90"] .norotate { + transform: rotate(270deg) translateX(-100%); +} +.annotationLayer[data-main-rotation="180"] .norotate { + transform: rotate(180deg) translate(-100%, -100%); +} +.annotationLayer[data-main-rotation="270"] .norotate { + transform: rotate(90deg) translateY(-100%); +} + +.annotationLayer canvas { + position: absolute; + width: 100%; + height: 100%; +} + .annotationLayer section { position: absolute; text-align: initial; @@ -75,11 +91,6 @@ height: 100%; } -.annotationLayer .buttonWidgetAnnotation.pushButton > canvas { - width: 100%; - height: 100%; -} - .annotationLayer .linkAnnotation > a:hover, .annotationLayer .buttonWidgetAnnotation.pushButton > a:hover { opacity: 0.2; @@ -92,6 +103,8 @@ cursor: pointer; width: 100%; height: 100%; + top: 0; + left: 0; } .annotationLayer .textWidgetAnnotation input, @@ -237,6 +250,10 @@ width: 100%; } +.annotationLayer .fileAttachmentAnnotation .popupTriggerArea { + position: absolute; +} + .annotationLayer .popupWrapper { position: absolute; font-size: calc(9px * var(--scale-factor)); @@ -306,6 +323,8 @@ position: absolute; width: 100%; height: 100%; + top: 0; + left: 0; } .annotationLayer .annotationTextContent {