diff --git a/src/display/text_layer.js b/src/display/text_layer.js index ef67ae5af33f4..264b4fb2245c8 100644 --- a/src/display/text_layer.js +++ b/src/display/text_layer.js @@ -69,6 +69,8 @@ class TextLayer { #pageWidth = 0; + #pixelRatio = OutputScale.pixelRatio; + #reader = null; #rootContainer = null; @@ -122,7 +124,7 @@ class TextLayer { this.#imagesHandler = images; - this.#scale = viewport.scale * OutputScale.pixelRatio; + this.#scale = viewport.scale * this.#pixelRatio; this.#rotation = viewport.rotation; this.#layoutTextParams = { div: null, @@ -229,6 +231,7 @@ class TextLayer { if (scale !== this.#scale) { onBefore?.(); this.#scale = scale; + this.#pixelRatio = OutputScale.pixelRatio; const params = { div: null, properties: null, @@ -424,17 +427,26 @@ class TextLayer { #layout(params) { const { div, properties, ctx } = params; const { style } = div; + const { canvasWidth, fontSize } = properties; - if (properties.canvasWidth !== 0 && properties.hasText) { + if (canvasWidth !== 0 && fontSize !== 0 && properties.hasText) { const { fontFamily } = style; - const { canvasWidth, fontSize } = properties; - - TextLayer.#ensureCtxFont(ctx, fontSize * this.#scale, fontFamily); + // Firefox quantizes canvas font sizes after dividing by the device-pixel + // ratio. Measure at a quantized size, then rescale the width. + const pixelRatio = this.#pixelRatio; + const measuredSize = + TextLayer.#quantizeFontSize((fontSize * this.#scale) / pixelRatio) * + pixelRatio; + + TextLayer.#ensureCtxFont(ctx, measuredSize, fontFamily); // Only measure the width for multi-char text divs, see `appendText`. const { width } = ctx.measureText(div.textContent); if (width > 0) { - style.setProperty("--scale-x", (canvasWidth * this.#scale) / width); + style.setProperty( + "--scale-x", + (canvasWidth * measuredSize) / (width * fontSize) + ); } } if (properties.angle !== 0) { @@ -489,6 +501,14 @@ class TextLayer { return ctx; } + // Match Firefox's 7-bit `QuantizeFontSize` implementation: + // https://searchfox.org/firefox-main/rev/04b29f9c2d2dbf5639c3f45ea812bb4c21dc81c6/dom/canvas/CanvasRenderingContext2D.cpp#4205-4215 + static #quantizeFontSize(size) { + size = Math.fround(size); + const d = Math.fround(size * ((1 << 17) + 1)); + return Math.fround(d - Math.fround(d - size)); + } + static #ensureCtxFont(ctx, size, family) { const cached = this.#canvasCtxFonts.get(ctx); if (size === cached.size && family === cached.family) { diff --git a/test/text_layer_test.css b/test/text_layer_test.css index df0d5859bf1ae..bd499da23d99a 100644 --- a/test/text_layer_test.css +++ b/test/text_layer_test.css @@ -26,7 +26,9 @@ position: absolute; white-space: pre; transform-origin: 0% 0%; - border: solid 1px rgb(255 0 0 / 0.5); + + /* Gecko may drop transformed 1px borders during rasterization. */ + border: solid 2px rgb(255 0 0 / 0.5); background-color: rgb(255 255 32 / 0.1); box-sizing: border-box; }