Skip to content

Commit cd45eb5

Browse files
committed
Get rid of CSS transform on each annotation in the annotation layer
- each annotation has its coordinates/dimensions expressed in percentage, hence it's correctly positioned whatever the scale factor is; - the font sizes are expressed in percentage too and the main font size is scaled thanks a css var (--scale-factor); - the rotation is now applied on the div annotationLayer; - this patch improve the rendering of some strings where the glyph spacing was not correct (it's a Firefox bug); - it helps to simplify the code and it should slightly improve the update of page (on zoom or rotation).
1 parent 89cebcb commit cd45eb5

17 files changed

+113
-136
lines changed

src/core/annotation.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ class Annotation {
432432
this.setAppearance(dict);
433433
this.setBorderAndBackgroundColors(dict.get("MK"));
434434

435+
this._hasOwnCanvas = false;
435436
this._streams = [];
436437
if (this.appearance) {
437438
this._streams.push(this.appearance);
@@ -450,7 +451,6 @@ class Annotation {
450451
modificationDate: this.modificationDate,
451452
rect: this.rectangle,
452453
subtype: params.subtype,
453-
hasOwnCanvas: false,
454454
};
455455

456456
if (params.collectFields) {
@@ -849,7 +849,7 @@ class Annotation {
849849
const data = this.data;
850850
let appearance = this.appearance;
851851
const isUsingOwnCanvas =
852-
data.hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY;
852+
this._hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY;
853853
if (!appearance) {
854854
if (!isUsingOwnCanvas) {
855855
return Promise.resolve(new OperatorList());
@@ -2163,7 +2163,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
21632163
} else if (this.data.radioButton) {
21642164
this._processRadioButton(params);
21652165
} else if (this.data.pushButton) {
2166-
this.data.hasOwnCanvas = true;
2166+
this._hasOwnCanvas = true;
21672167
this._processPushButton(params);
21682168
} else {
21692169
warn("Invalid field flags for button widget annotation");

src/core/xfa/template.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1425,7 +1425,7 @@ class ChoiceList extends XFAObject {
14251425
const field = ui[$getParent]();
14261426
const fontSize = (field.font && field.font.size) || 10;
14271427
const optionStyle = {
1428-
fontSize: `calc(${fontSize}px * var(--zoom-factor))`,
1428+
fontSize: `calc(${fontSize}px * var(--scale-factor))`,
14291429
};
14301430
const children = [];
14311431

src/core/xfa/xhtml.js

+4
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ function mapStyle(styleStr, node, richText) {
182182
);
183183
}
184184

185+
if (richText && style.fontSize) {
186+
style.fontSize = `calc(${style.fontSize} * var(--scale-factor))`;
187+
}
188+
185189
fixTextIndent(style);
186190
return style;
187191
}

src/display/annotation_layer.js

+43-98
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ class AnnotationElement {
199199
const container = document.createElement("section");
200200
let { width, height } = getRectDims(data.rect);
201201

202+
const [pageLLx, pageLLy, pageURx, pageURy] = viewport.viewBox;
203+
const pageWidth = pageURx - pageLLx;
204+
const pageHeight = pageURy - pageLLy;
205+
202206
container.setAttribute("data-annotation-id", data.id);
203207

204208
// Do *not* modify `data.rect`, since that will corrupt the annotation
@@ -210,27 +214,6 @@ class AnnotationElement {
210214
page.view[3] - data.rect[3] + page.view[1],
211215
]);
212216

213-
if (data.hasOwnCanvas) {
214-
const transform = viewport.transform.slice();
215-
const [scaleX, scaleY] = Util.singularValueDecompose2dScale(transform);
216-
width = Math.ceil(width * scaleX);
217-
height = Math.ceil(height * scaleY);
218-
rect[0] *= scaleX;
219-
rect[1] *= scaleY;
220-
// Reset the scale part of the transform matrix (which must be diagonal
221-
// or anti-diagonal) in order to avoid to rescale the canvas.
222-
// The canvas for the annotation is correctly scaled when it is drawn
223-
// (see `beginAnnotation` in canvas.js).
224-
for (let i = 0; i < 4; i++) {
225-
transform[i] = Math.sign(transform[i]);
226-
}
227-
container.style.transform = `matrix(${transform.join(",")})`;
228-
} else {
229-
container.style.transform = `matrix(${viewport.transform.join(",")})`;
230-
}
231-
232-
container.style.transformOrigin = `${-rect[0]}px ${-rect[1]}px`;
233-
234217
if (!ignoreBorder && data.borderStyle.width > 0) {
235218
container.style.borderWidth = `${data.borderStyle.width}px`;
236219
if (data.borderStyle.style !== AnnotationBorderStyleType.UNDERLINE) {
@@ -286,15 +269,11 @@ class AnnotationElement {
286269
}
287270
}
288271

289-
container.style.left = `${rect[0]}px`;
290-
container.style.top = `${rect[1]}px`;
272+
container.style.left = `${(100 * (rect[0] - pageLLx)) / pageWidth}%`;
273+
container.style.top = `${(100 * (rect[1] - pageLLy)) / pageHeight}%`;
274+
container.style.width = `${(100 * width) / pageWidth}%`;
275+
container.style.height = `${(100 * height) / pageHeight}%`;
291276

292-
if (data.hasOwnCanvas) {
293-
container.style.width = container.style.height = "auto";
294-
} else {
295-
container.style.width = `${width}px`;
296-
container.style.height = `${height}px`;
297-
}
298277
return container;
299278
}
300279

@@ -464,7 +443,8 @@ class AnnotationElement {
464443
const popup = popupElement.render();
465444

466445
// Position the popup next to the annotation's container.
467-
popup.style.left = container.style.width;
446+
popup.style.left = "100%";
447+
popup.style.top = "100%";
468448

469449
container.append(popup);
470450
}
@@ -813,8 +793,6 @@ class TextAnnotationElement extends AnnotationElement {
813793
this.container.className = "textAnnotation";
814794

815795
const image = document.createElement("img");
816-
image.style.height = this.container.style.height;
817-
image.style.width = this.container.style.width;
818796
image.src =
819797
this.imageResourcesPath +
820798
"annotation-" +
@@ -918,21 +896,20 @@ class WidgetAnnotationElement extends AnnotationElement {
918896
// it's instead based on the field height.
919897
// If the height is "big" then it could lead to a too big font size
920898
// so in this case use the one we've in the pdf (hence the min).
899+
let computedFontSize;
921900
if (this.data.multiLine) {
922901
const height = Math.abs(this.data.rect[3] - this.data.rect[1]);
923902
const numberOfLines = Math.round(height / (LINE_FACTOR * fontSize)) || 1;
924903
const lineHeight = height / numberOfLines;
925-
style.fontSize = `${Math.min(
904+
computedFontSize = Math.min(
926905
fontSize,
927906
Math.round(lineHeight / LINE_FACTOR)
928-
)}px`;
907+
);
929908
} else {
930909
const height = Math.abs(this.data.rect[3] - this.data.rect[1]);
931-
style.fontSize = `${Math.min(
932-
fontSize,
933-
Math.round(height / LINE_FACTOR)
934-
)}px`;
910+
computedFontSize = Math.min(fontSize, Math.round(height / LINE_FACTOR));
935911
}
912+
style.fontSize = `${computedFontSize}%`;
936913

937914
style.color = Util.makeHexColor(fontColor[0], fontColor[1], fontColor[2]);
938915

@@ -1221,7 +1198,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
12211198
const combWidth = fieldWidth / this.data.maxLen;
12221199

12231200
element.classList.add("comb");
1224-
element.style.letterSpacing = `calc(${combWidth}px - 1ch)`;
1201+
element.style.letterSpacing = `calc(${combWidth}px * var(--scale-factor) - 1ch)`;
12251202
}
12261203
} else {
12271204
element = document.createElement("div");
@@ -1449,10 +1426,6 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
14491426
value: this.data.fieldValue,
14501427
});
14511428

1452-
const fontSize =
1453-
this.data.defaultAppearanceData.fontSize || DEFAULT_FONT_SIZE;
1454-
const fontSizeStyle = `calc(${fontSize}px * var(--zoom-factor))`;
1455-
14561429
const selectElement = document.createElement("select");
14571430
GetElementsByNameSet.add(selectElement);
14581431
selectElement.disabled = this.data.readOnly;
@@ -1483,9 +1456,6 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
14831456
const optionElement = document.createElement("option");
14841457
optionElement.textContent = option.displayValue;
14851458
optionElement.value = option.exportValue;
1486-
if (this.data.combo) {
1487-
optionElement.style.fontSize = fontSizeStyle;
1488-
}
14891459
if (storedData.value.includes(option.exportValue)) {
14901460
optionElement.setAttribute("selected", true);
14911461
addAnEmptyEntry = false;
@@ -1730,9 +1700,12 @@ class PopupAnnotationElement extends AnnotationElement {
17301700
rect[0] + this.data.parentRect[2] - this.data.parentRect[0];
17311701
const popupTop = rect[1];
17321702

1733-
this.container.style.transformOrigin = `${-popupLeft}px ${-popupTop}px`;
1734-
this.container.style.left = `${popupLeft}px`;
1735-
this.container.style.top = `${popupTop}px`;
1703+
const [pageLLx, pageLLy, pageURx, pageURy] = this.viewport.viewBox;
1704+
const pageWidth = pageURx - pageLLx;
1705+
const pageHeight = pageURy - pageLLy;
1706+
1707+
this.container.style.left = `${(100 * (popupLeft - pageLLx)) / pageWidth}%`;
1708+
this.container.style.top = `${(100 * (popupTop - pageLLy)) / pageHeight}%`;
17361709

17371710
this.container.append(popup.render());
17381711
return this.container;
@@ -1942,7 +1915,7 @@ class LineAnnotationElement extends AnnotationElement {
19421915
// trigger the popup, not the entire container.
19431916
const data = this.data;
19441917
const { width, height } = getRectDims(data.rect);
1945-
const svg = this.svgFactory.create(width, height);
1918+
const svg = this.svgFactory.create(width, height, /* noDims = */ true);
19461919

19471920
// PDF coordinates are calculated from a bottom left origin, so transform
19481921
// the line coordinates to a top left origin for the SVG element.
@@ -1987,7 +1960,7 @@ class SquareAnnotationElement extends AnnotationElement {
19871960
// popup, not the entire container.
19881961
const data = this.data;
19891962
const { width, height } = getRectDims(data.rect);
1990-
const svg = this.svgFactory.create(width, height);
1963+
const svg = this.svgFactory.create(width, height, /* noDims = */ true);
19911964

19921965
// The browser draws half of the borders inside the square and half of
19931966
// the borders outside the square by default. This behavior cannot be
@@ -2034,7 +2007,7 @@ class CircleAnnotationElement extends AnnotationElement {
20342007
// popup, not the entire container.
20352008
const data = this.data;
20362009
const { width, height } = getRectDims(data.rect);
2037-
const svg = this.svgFactory.create(width, height);
2010+
const svg = this.svgFactory.create(width, height, /* noDims = */ true);
20382011

20392012
// The browser draws half of the borders inside the circle and half of
20402013
// the borders outside the circle by default. This behavior cannot be
@@ -2084,7 +2057,7 @@ class PolylineAnnotationElement extends AnnotationElement {
20842057
// popup, not the entire container.
20852058
const data = this.data;
20862059
const { width, height } = getRectDims(data.rect);
2087-
const svg = this.svgFactory.create(width, height);
2060+
const svg = this.svgFactory.create(width, height, /* noDims = */ true);
20882061

20892062
// Convert the vertices array to a single points string that the SVG
20902063
// polyline element expects ("x1,y1 x2,y2 ..."). PDF coordinates are
@@ -2172,7 +2145,7 @@ class InkAnnotationElement extends AnnotationElement {
21722145
// trigger for the popup.
21732146
const data = this.data;
21742147
const { width, height } = getRectDims(data.rect);
2175-
const svg = this.svgFactory.create(width, height);
2148+
const svg = this.svgFactory.create(width, height, /* noDims = */ true);
21762149

21772150
for (const inkList of data.inkLists) {
21782151
// Convert the ink list to a single points string that the SVG
@@ -2496,55 +2469,27 @@ class AnnotationLayer {
24962469
* @memberof AnnotationLayer
24972470
*/
24982471
static update(parameters) {
2499-
const { page, viewport, annotations, annotationCanvasMap, div } =
2500-
parameters;
2501-
const transform = viewport.transform;
2502-
const matrix = `matrix(${transform.join(",")})`;
2503-
2504-
let scale, ownMatrix;
2505-
for (const data of annotations) {
2506-
const elements = div.querySelectorAll(
2507-
`[data-annotation-id="${data.id}"]`
2508-
);
2509-
if (elements) {
2510-
for (const element of elements) {
2511-
if (data.hasOwnCanvas) {
2512-
const rect = Util.normalizeRect([
2513-
data.rect[0],
2514-
page.view[3] - data.rect[1] + page.view[1],
2515-
data.rect[2],
2516-
page.view[3] - data.rect[3] + page.view[1],
2517-
]);
2518-
2519-
if (!ownMatrix) {
2520-
// When an annotation has its own canvas, then
2521-
// the scale has been already applied to the canvas,
2522-
// so we musn't scale it twice.
2523-
scale = Math.abs(transform[0] || transform[1]);
2524-
const ownTransform = transform.slice();
2525-
for (let i = 0; i < 4; i++) {
2526-
ownTransform[i] = Math.sign(ownTransform[i]);
2527-
}
2528-
ownMatrix = `matrix(${ownTransform.join(",")})`;
2529-
}
2530-
2531-
const left = rect[0] * scale;
2532-
const top = rect[1] * scale;
2533-
element.style.left = `${left}px`;
2534-
element.style.top = `${top}px`;
2535-
element.style.transformOrigin = `${-left}px ${-top}px`;
2536-
element.style.transform = ownMatrix;
2537-
} else {
2538-
element.style.transform = matrix;
2539-
}
2540-
}
2541-
}
2542-
}
2472+
const { annotationCanvasMap, div } = parameters;
25432473

25442474
this.#setAnnotationCanvasMap(div, annotationCanvasMap);
25452475
div.hidden = false;
25462476
}
25472477

2478+
static setDimensions(div, viewport) {
2479+
const { width, height, rotation } = viewport;
2480+
const { style } = div;
2481+
2482+
if (rotation === 0 || rotation === 180) {
2483+
style.width = `${width}px`;
2484+
style.height = `${height}px`;
2485+
} else {
2486+
style.width = `${height}px`;
2487+
style.height = `${width}px`;
2488+
}
2489+
2490+
div.setAttribute("data-annot-rotation", rotation);
2491+
}
2492+
25482493
static #setAnnotationCanvasMap(div, annotationCanvasMap) {
25492494
if (!annotationCanvasMap) {
25502495
return;

src/display/base_factory.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,18 @@ class BaseSVGFactory {
143143
}
144144
}
145145

146-
create(width, height) {
146+
create(width, height, noDims = true) {
147147
if (width <= 0 || height <= 0) {
148148
throw new Error("Invalid SVG dimensions");
149149
}
150150
const svg = this._createSVG("svg:svg");
151151
svg.setAttribute("version", "1.1");
152-
svg.setAttribute("width", `${width}px`);
153-
svg.setAttribute("height", `${height}px`);
152+
153+
if (!noDims) {
154+
svg.setAttribute("width", `${width}px`);
155+
svg.setAttribute("height", `${height}px`);
156+
}
157+
154158
svg.setAttribute("preserveAspectRatio", "none");
155159
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
156160

src/display/canvas.js

-3
Original file line numberDiff line numberDiff line change
@@ -3021,9 +3021,6 @@ class CanvasGraphics {
30213021
canvasHeight
30223022
);
30233023
const { canvas, context } = this.annotationCanvas;
3024-
const viewportScaleFactorStr = `var(--zoom-factor) * ${PixelsPerInch.PDF_TO_CSS_UNITS}`;
3025-
canvas.style.width = `calc(${width}px * ${viewportScaleFactorStr})`;
3026-
canvas.style.height = `calc(${height}px * ${viewportScaleFactorStr})`;
30273024
this.annotationCanvasMap.set(id, canvas);
30283025
this.annotationCanvas.savedCtx = this.ctx;
30293026
this.ctx = context;

src/display/editor/annotation_editor_layer.js

-9
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import { AnnotationEditorType, Util } from "../../shared/util.js";
2424
import { bindEvents, KeyboardManager } from "./tools.js";
2525
import { FreeTextEditor } from "./freetext.js";
2626
import { InkEditor } from "./ink.js";
27-
import { PixelsPerInch } from "../display_utils.js";
2827

2928
/**
3029
* @typedef {Object} AnnotationEditorLayerOptions
@@ -422,14 +421,6 @@ class AnnotationEditorLayer {
422421
get scaleFactor() {
423422
return this.viewport.scale;
424423
}
425-
426-
/**
427-
* Get the zoom factor.
428-
* @returns {number}
429-
*/
430-
get zoomFactor() {
431-
return this.viewport.scale / PixelsPerInch.PDF_TO_CSS_UNITS;
432-
}
433424
}
434425

435426
export { AnnotationEditorLayer };

0 commit comments

Comments
 (0)