From eb2b7c2c8613c6f796b9b50977fb1d40e2067343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Mon, 8 Dec 2025 15:57:07 +0100 Subject: [PATCH] Move text layer scaling logic to CSS This commit moves all the logic to scale up&down ``s in the text layer, introduced in #18283, to CSS. The motivation for this change is that #18283 is still not enough for all cases. That PR fixed the problem in Chrome&Firefox desktop, which allow users to set an actual minimum font size in the browser settings. However, other browsers (e.g. the Chrome-based WebView on Android) have more complex logic and they scale up small text rather than simply applying a minimum. A workaround for that behavior is probably out of scope for PDF.js itself as it only affects not officially supported platforms. However, having access to the actual expected font height (through `--font-height`) allows embedders of PDF.js to implement a workaround by themselves. --- src/display/text_layer.js | 24 ++++++------------------ test/unit/text_layer_spec.js | 11 ++++++----- web/text_layer_builder.css | 16 ++++++++++++++++ 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/display/text_layer.js b/src/display/text_layer.js index 724924dc6..4ddb6b18b 100644 --- a/src/display/text_layer.js +++ b/src/display/text_layer.js @@ -128,6 +128,7 @@ class TextLayer { this.#pageHeight = pageHeight; TextLayer.#ensureMinFontSizeComputed(); + container.style.setProperty("--min-font-size", TextLayer.#minFontSize); setLayerDimensions(container, viewport); @@ -342,7 +343,6 @@ class TextLayer { top = tx[5] - fontAscent * Math.cos(angle); } - const scaleFactorStr = "calc(var(--total-scale-factor) *"; const divStyle = textDiv.style; // Setting the style properties individually, rather than all at once, // should be OK since the `textDiv` isn't appended to the document yet. @@ -351,14 +351,10 @@ class TextLayer { divStyle.top = `${((100 * top) / this.#pageHeight).toFixed(2)}%`; } else { // We're in a marked content span, hence we can't use percents. - divStyle.left = `${scaleFactorStr}${left.toFixed(2)}px)`; - divStyle.top = `${scaleFactorStr}${top.toFixed(2)}px)`; + divStyle.left = `calc(var(--total-scale-factor) * ${left.toFixed(2)}px)`; + divStyle.top = `calc(var(--total-scale-factor) * ${top.toFixed(2)}px)`; } - // We multiply the font size by #minFontSize, and then #layout will - // scale the element by 1/#minFontSize. This allows us to effectively - // ignore the minimum font size enforced by the browser, so that the text - // layer s can always match the size of the text in the canvas. - divStyle.fontSize = `${scaleFactorStr}${(TextLayer.#minFontSize * fontHeight).toFixed(2)}px)`; + divStyle.setProperty("--font-height", `${fontHeight.toFixed(2)}px`); divStyle.fontFamily = fontFamily; textDivProperties.fontSize = fontHeight; @@ -421,11 +417,6 @@ class TextLayer { const { div, properties, ctx } = params; const { style } = div; - let transform = ""; - if (TextLayer.#minFontSize > 1) { - transform = `scale(${1 / TextLayer.#minFontSize})`; - } - if (properties.canvasWidth !== 0 && properties.hasText) { const { fontFamily } = style; const { canvasWidth, fontSize } = properties; @@ -435,14 +426,11 @@ class TextLayer { const { width } = ctx.measureText(div.textContent); if (width > 0) { - transform = `scaleX(${(canvasWidth * this.#scale) / width}) ${transform}`; + style.setProperty("--scale-x", (canvasWidth * this.#scale) / width); } } if (properties.angle !== 0) { - transform = `rotate(${properties.angle}deg) ${transform}`; - } - if (transform.length > 0) { - style.transform = transform; + style.setProperty("--rotate", `${properties.angle}deg`); } } diff --git a/test/unit/text_layer_spec.js b/test/unit/text_layer_spec.js index 644e74245..ee097f807 100644 --- a/test/unit/text_layer_spec.js +++ b/test/unit/text_layer_spec.js @@ -101,11 +101,12 @@ describe("textLayer", function () { const getTransform = container => { const transform = []; - for (const span of container.childNodes) { - const t = span.style.transform; - expect(t).toMatch(/^scaleX\([\d.]+\)$/); - - transform.push(t); + for (const { style } of container.childNodes) { + transform.push({ + fontHeight: style.getPropertyValue("--font-height"), + scaleX: style.getPropertyValue("--scale-x"), + rotate: style.getPropertyValue("--rotate"), + }); } return transform; }; diff --git a/web/text_layer_builder.css b/web/text_layer_builder.css index 8dbac995c..698450ad5 100644 --- a/web/text_layer_builder.css +++ b/web/text_layer_builder.css @@ -38,9 +38,25 @@ transform-origin: 0% 0%; } + /* We multiply the font size by --min-font-size, and then scale the text + * elements by 1/--min-font-size. This allows us to effectively ignore the + * minimum font size enforced by the browser, so that the text layer s + * can always match the size of the text in the canvas. */ + --min-font-size: 1; + --text-scale-factor: calc(var(--total-scale-factor) * var(--min-font-size)); + --min-font-size-inv: calc(1 / var(--min-font-size)); + > :not(.markedContent), .markedContent span:not(.markedContent) { z-index: 1; + + --font-height: 0; /* set by text_layer.js */ + font-size: calc(var(--text-scale-factor) * var(--font-height)); + + --scale-x: 1; + --rotate: 0deg; + transform: rotate(var(--rotate)) scaleX(var(--scale-x)) + scale(var(--min-font-size-inv)); } /* Only necessary in Google Chrome, see issue 14205, and most unfortunately