diff --git a/src/core/annotation.js b/src/core/annotation.js index ef61f78efada35..bd9dc692abfafa 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -432,6 +432,7 @@ class Annotation { this.setAppearance(dict); this.setBorderAndBackgroundColors(dict.get("MK")); + this._hasOwnCanvas = false; this._streams = []; if (this.appearance) { this._streams.push(this.appearance); @@ -450,7 +451,6 @@ class Annotation { modificationDate: this.modificationDate, rect: this.rectangle, subtype: params.subtype, - hasOwnCanvas: false, }; if (params.collectFields) { @@ -849,7 +849,7 @@ class Annotation { const data = this.data; let appearance = this.appearance; const isUsingOwnCanvas = - data.hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY; + this._hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY; if (!appearance) { if (!isUsingOwnCanvas) { return Promise.resolve(new OperatorList()); @@ -2163,7 +2163,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { } else if (this.data.radioButton) { this._processRadioButton(params); } else if (this.data.pushButton) { - this.data.hasOwnCanvas = true; + this._hasOwnCanvas = true; this._processPushButton(params); } else { warn("Invalid field flags for button widget annotation"); diff --git a/src/core/xfa/template.js b/src/core/xfa/template.js index bd4e1b343d8801..49e5f2698e95a2 100644 --- a/src/core/xfa/template.js +++ b/src/core/xfa/template.js @@ -1425,7 +1425,7 @@ class ChoiceList extends XFAObject { const field = ui[$getParent](); const fontSize = (field.font && field.font.size) || 10; const optionStyle = { - fontSize: `calc(${fontSize}px * var(--zoom-factor))`, + fontSize: `calc(${fontSize}px * var(--scale-factor))`, }; const children = []; diff --git a/src/core/xfa/xhtml.js b/src/core/xfa/xhtml.js index bf5aceaef681ce..e39c88d0e79297 100644 --- a/src/core/xfa/xhtml.js +++ b/src/core/xfa/xhtml.js @@ -182,6 +182,10 @@ function mapStyle(styleStr, node, richText) { ); } + if (richText && style.fontSize) { + style.fontSize = `calc(${style.fontSize} * var(--scale-factor))`; + } + fixTextIndent(style); return style; } diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index a6f3eb23a2b683..62939eed617922 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -199,6 +199,10 @@ class AnnotationElement { const container = document.createElement("section"); let { width, height } = getRectDims(data.rect); + const [pageLLx, pageLLy, pageURx, pageURy] = viewport.viewBox; + const pageWidth = pageURx - pageLLx; + const pageHeight = pageURy - pageLLy; + container.setAttribute("data-annotation-id", data.id); // Do *not* modify `data.rect`, since that will corrupt the annotation @@ -210,27 +214,6 @@ class AnnotationElement { page.view[3] - data.rect[3] + page.view[1], ]); - if (data.hasOwnCanvas) { - const transform = viewport.transform.slice(); - const [scaleX, scaleY] = Util.singularValueDecompose2dScale(transform); - width = Math.ceil(width * scaleX); - height = Math.ceil(height * scaleY); - rect[0] *= scaleX; - rect[1] *= scaleY; - // Reset the scale part of the transform matrix (which must be diagonal - // or anti-diagonal) in order to avoid to rescale the canvas. - // The canvas for the annotation is correctly scaled when it is drawn - // (see `beginAnnotation` in canvas.js). - for (let i = 0; i < 4; i++) { - transform[i] = Math.sign(transform[i]); - } - container.style.transform = `matrix(${transform.join(",")})`; - } else { - container.style.transform = `matrix(${viewport.transform.join(",")})`; - } - - container.style.transformOrigin = `${-rect[0]}px ${-rect[1]}px`; - if (!ignoreBorder && data.borderStyle.width > 0) { container.style.borderWidth = `${data.borderStyle.width}px`; if (data.borderStyle.style !== AnnotationBorderStyleType.UNDERLINE) { @@ -286,15 +269,11 @@ class AnnotationElement { } } - container.style.left = `${rect[0]}px`; - container.style.top = `${rect[1]}px`; + container.style.left = `${(100 * (rect[0] - pageLLx)) / pageWidth}%`; + container.style.top = `${(100 * (rect[1] - pageLLy)) / pageHeight}%`; + container.style.width = `${(100 * width) / pageWidth}%`; + container.style.height = `${(100 * height) / pageHeight}%`; - if (data.hasOwnCanvas) { - container.style.width = container.style.height = "auto"; - } else { - container.style.width = `${width}px`; - container.style.height = `${height}px`; - } return container; } @@ -918,21 +897,20 @@ class WidgetAnnotationElement extends AnnotationElement { // it's instead based on the field height. // If the height is "big" then it could lead to a too big font size // so in this case use the one we've in the pdf (hence the min). + let computedFontSize; if (this.data.multiLine) { const height = Math.abs(this.data.rect[3] - this.data.rect[1]); const numberOfLines = Math.round(height / (LINE_FACTOR * fontSize)) || 1; const lineHeight = height / numberOfLines; - style.fontSize = `${Math.min( + computedFontSize = Math.min( fontSize, Math.round(lineHeight / LINE_FACTOR) - )}px`; + ); } else { const height = Math.abs(this.data.rect[3] - this.data.rect[1]); - style.fontSize = `${Math.min( - fontSize, - Math.round(height / LINE_FACTOR) - )}px`; + computedFontSize = Math.min(fontSize, Math.round(height / LINE_FACTOR)); } + style.fontSize = `${computedFontSize}%`; style.color = Util.makeHexColor(fontColor[0], fontColor[1], fontColor[2]); @@ -1221,7 +1199,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { const combWidth = fieldWidth / this.data.maxLen; element.classList.add("comb"); - element.style.letterSpacing = `calc(${combWidth}px - 1ch)`; + element.style.letterSpacing = `calc(${combWidth}px * var(--scale-factor) - 1ch)`; } } else { element = document.createElement("div"); @@ -1449,10 +1427,6 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { value: this.data.fieldValue, }); - const fontSize = - this.data.defaultAppearanceData.fontSize || DEFAULT_FONT_SIZE; - const fontSizeStyle = `calc(${fontSize}px * var(--zoom-factor))`; - const selectElement = document.createElement("select"); GetElementsByNameSet.add(selectElement); selectElement.disabled = this.data.readOnly; @@ -1483,9 +1457,6 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { const optionElement = document.createElement("option"); optionElement.textContent = option.displayValue; optionElement.value = option.exportValue; - if (this.data.combo) { - optionElement.style.fontSize = fontSizeStyle; - } if (storedData.value.includes(option.exportValue)) { optionElement.setAttribute("selected", true); addAnEmptyEntry = false; @@ -1730,9 +1701,12 @@ class PopupAnnotationElement extends AnnotationElement { rect[0] + this.data.parentRect[2] - this.data.parentRect[0]; const popupTop = rect[1]; - this.container.style.transformOrigin = `${-popupLeft}px ${-popupTop}px`; - this.container.style.left = `${popupLeft}px`; - this.container.style.top = `${popupTop}px`; + const [pageLLx, pageLLy, pageURx, pageURy] = this.viewport.viewBox; + const pageWidth = pageURx - pageLLx; + const pageHeight = pageURy - pageLLy; + + this.container.style.left = `${(100 * (popupLeft - pageLLx)) / pageWidth}%`; + this.container.style.top = `${(100 * (popupTop - pageLLy)) / pageHeight}%`; this.container.append(popup.render()); return this.container; @@ -2496,50 +2470,7 @@ class AnnotationLayer { * @memberof AnnotationLayer */ static update(parameters) { - const { page, viewport, annotations, annotationCanvasMap, div } = - parameters; - const transform = viewport.transform; - const matrix = `matrix(${transform.join(",")})`; - - let scale, ownMatrix; - for (const data of annotations) { - const elements = div.querySelectorAll( - `[data-annotation-id="${data.id}"]` - ); - if (elements) { - for (const element of elements) { - if (data.hasOwnCanvas) { - const rect = Util.normalizeRect([ - data.rect[0], - page.view[3] - data.rect[1] + page.view[1], - data.rect[2], - page.view[3] - data.rect[3] + page.view[1], - ]); - - if (!ownMatrix) { - // When an annotation has its own canvas, then - // the scale has been already applied to the canvas, - // so we musn't scale it twice. - scale = Math.abs(transform[0] || transform[1]); - const ownTransform = transform.slice(); - for (let i = 0; i < 4; i++) { - ownTransform[i] = Math.sign(ownTransform[i]); - } - ownMatrix = `matrix(${ownTransform.join(",")})`; - } - - const left = rect[0] * scale; - const top = rect[1] * scale; - element.style.left = `${left}px`; - element.style.top = `${top}px`; - element.style.transformOrigin = `${-left}px ${-top}px`; - element.style.transform = ownMatrix; - } else { - element.style.transform = matrix; - } - } - } - } + const { annotationCanvasMap, div } = parameters; this.#setAnnotationCanvasMap(div, annotationCanvasMap); div.hidden = false; diff --git a/src/display/canvas.js b/src/display/canvas.js index cd3950cb893b37..98a6eec1fa9ecd 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -3021,9 +3021,6 @@ class CanvasGraphics { canvasHeight ); const { canvas, context } = this.annotationCanvas; - const viewportScaleFactorStr = `var(--zoom-factor) * ${PixelsPerInch.PDF_TO_CSS_UNITS}`; - canvas.style.width = `calc(${width}px * ${viewportScaleFactorStr})`; - canvas.style.height = `calc(${height}px * ${viewportScaleFactorStr})`; this.annotationCanvasMap.set(id, canvas); this.annotationCanvas.savedCtx = this.ctx; this.ctx = context; diff --git a/src/display/editor/freetext.js b/src/display/editor/freetext.js index 14cb1739058aba..b0042f8ad389ea 100644 --- a/src/display/editor/freetext.js +++ b/src/display/editor/freetext.js @@ -21,7 +21,6 @@ import { } from "../../shared/util.js"; import { AnnotationEditor } from "./editor.js"; import { bindEvents } from "./tools.js"; -import { PixelsPerInch } from "../display_utils.js"; /** * Basic text editor in order to create a FreeTex annotation. @@ -92,9 +91,9 @@ class FreeTextEditor extends AnnotationEditor { getInitialTranslation() { // The start of the base line is where the user clicked. return [ - -FreeTextEditor._internalPadding * this.parent.zoomFactor, + -FreeTextEditor._internalPadding * this.parent.scaleFactor, -(FreeTextEditor._internalPadding + this.#fontSize) * - this.parent.zoomFactor, + this.parent.scaleFactor, ]; } @@ -208,7 +207,7 @@ class FreeTextEditor extends AnnotationEditor { this.editorDiv.contentEditable = true; const { style } = this.editorDiv; - style.fontSize = `calc(${this.#fontSize}px * var(--zoom-factor))`; + style.fontSize = `${this.#fontSize}%`; style.color = this.#color; this.div.append(this.editorDiv); @@ -235,7 +234,7 @@ class FreeTextEditor extends AnnotationEditor { /** @inheritdoc */ serialize() { const rect = this.editorDiv.getBoundingClientRect(); - const padding = FreeTextEditor._internalPadding * this.parent.zoomFactor; + const padding = FreeTextEditor._internalPadding * this.parent.scaleFactor; const [x1, y1] = Util.applyTransform( [this.x + padding, this.y + padding + rect.height], this.parent.inverseViewportTransform @@ -248,7 +247,7 @@ class FreeTextEditor extends AnnotationEditor { return { annotationType: AnnotationEditorType.FREETEXT, color: [0, 0, 0], - fontSize: this.#fontSize / PixelsPerInch.PDF_TO_CSS_UNITS, + fontSize: this.#fontSize, value: this.#content, pageIndex: this.parent.pageIndex, rect: [x1, y1, x2, y2], diff --git a/web/annotation_editor_layer_builder.css b/web/annotation_editor_layer_builder.css index 7fe9b43d632db9..06fa1d3a5b8320 100644 --- a/web/annotation_editor_layer_builder.css +++ b/web/annotation_editor_layer_builder.css @@ -27,13 +27,14 @@ position: absolute; top: 0; left: 0; + font-size: calc(100px * var(--scale-factor)); } .annotationEditorLayer .freeTextEditor { position: absolute; background: transparent; border-radius: 3px; - padding: calc(var(--freetext-padding) * var(--zoom-factor)); + padding: calc(var(--freetext-padding) * var(--scale-factor)); resize: none; width: auto; height: auto; diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css index 17f796188a57d2..da3e63918f60dd 100644 --- a/web/annotation_layer_builder.css +++ b/web/annotation_layer_builder.css @@ -27,9 +27,29 @@ } } +.rotate90 { + transform: rotate(90deg) translateY(-100%); +} +.rotate180 { + transform: rotate(180deg) translate(-100%, -100%); +} +.rotate270 { + transform: rotate(270deg) translateX(-100%); +} + +.annotationLayer { + position: absolute; + top: 0; + left: 0; + font-size: calc(100px * var(--scale-factor)); + pointer-events: none; + transform-origin: 0 0; +} + .annotationLayer section { position: absolute; text-align: initial; + pointer-events: auto; } .annotationLayer .linkAnnotation > a, @@ -43,9 +63,8 @@ } .annotationLayer .buttonWidgetAnnotation.pushButton > canvas { - position: relative; - top: 0; - left: 0; + width: 100%; + height: 100%; z-index: -1; } @@ -200,15 +219,11 @@ margin-left: 5px; cursor: pointer; font: message-box; - font-size: 9px; + font-size: 9%; white-space: normal; word-wrap: break-word; } -.annotationLayer .popup > * { - font-size: 9px; -} - .annotationLayer .popup h1 { display: inline-block; } @@ -226,6 +241,7 @@ .annotationLayer .richText > * { white-space: pre-wrap; + font-size: calc(9px * var(--scale-factor)); } .annotationLayer .highlightAnnotation, diff --git a/web/annotation_layer_builder.js b/web/annotation_layer_builder.js index aa1d9ac4ba53e1..9ec5d38fa2e5d7 100644 --- a/web/annotation_layer_builder.js +++ b/web/annotation_layer_builder.js @@ -129,6 +129,27 @@ class AnnotationLayerBuilder { AnnotationLayer.render(parameters); this.l10n.translate(this.div); } + + const { width, height, rotation } = viewport; + const { style } = this.div; + + if (rotation === 0 || rotation === 180) { + style.width = `${width}px`; + style.height = `${height}px`; + } else { + style.width = `${height}px`; + style.height = `${width}px`; + } + + const { classList } = this.div; + for (const angle of [90, 180, 270]) { + const rotate = `rotate${angle}`; + if (angle === rotation) { + classList.add(rotate); + } else { + classList.remove(rotate); + } + } } cancel() { diff --git a/web/app_options.js b/web/app_options.js index d75277c568254f..1b08174da93ac1 100644 --- a/web/app_options.js +++ b/web/app_options.js @@ -61,7 +61,7 @@ const OptionKind = { const defaultOptions = { annotationEditorEnabled: { /** @type {boolean} */ - value: typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING"), + value: true, // typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING"), kind: OptionKind.VIEWER + OptionKind.PREFERENCE, }, annotationMode: { diff --git a/web/base_viewer.js b/web/base_viewer.js index d4a34ce1dbb0ba..83b717855387e7 100644 --- a/web/base_viewer.js +++ b/web/base_viewer.js @@ -1048,7 +1048,10 @@ class BaseViewer { return; } - docStyle.setProperty("--zoom-factor", newScale); + docStyle.setProperty( + "--scale-factor", + newScale * PixelsPerInch.PDF_TO_CSS_UNITS + ); const updateArgs = { scale: newScale }; for (const pageView of this._pages) { diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 1bf37459588c8a..adcee9c049f29e 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -377,7 +377,7 @@ class PDFPageView { }); if (this._isStandalone) { - docStyle.setProperty("--zoom-factor", this.scale); + docStyle.setProperty("--scale-factor", this.viewport.scale); } if (this.svg) { diff --git a/web/pdf_viewer.css b/web/pdf_viewer.css index 98ddc44e19b676..c489106a90f94d 100644 --- a/web/pdf_viewer.css +++ b/web/pdf_viewer.css @@ -23,7 +23,7 @@ --page-margin: 1px auto -8px; --page-border: 9px solid transparent; --spreadHorizontalWrapped-margin-LR: -3.5px; - --zoom-factor: 1; + --scale-factor: 1; } @media screen and (forced-colors: active) {