diff --git a/src/display/text_layer.js b/src/display/text_layer.js index 1c0d247779131d..6968b5bc224bcb 100644 --- a/src/display/text_layer.js +++ b/src/display/text_layer.js @@ -83,6 +83,8 @@ class TextLayer { static #canvasContexts = new Map(); + static #minFontSize = null; + static #pendingTextLayers = new Set(); /** @@ -114,6 +116,7 @@ class TextLayer { div: null, properties: null, ctx: null, + minFontSize: null, }; const { pageWidth, pageHeight, pageX, pageY } = viewport.rawDims; this.#transform = [1, 0, 0, -1, -pageX, pageY + pageHeight]; @@ -196,6 +199,7 @@ class TextLayer { div: null, properties: null, ctx: TextLayer.#getCtx(this.#lang), + minFontSize: TextLayer.#getMinFontSize(), }; for (const div of this.#textDivs) { params.properties = this.#textDivProperties.get(div); @@ -242,7 +246,8 @@ class TextLayer { if (this.#disableProcessItems) { return; } - this.#layoutTextParams.ctx ||= TextLayer.#getCtx(this.#lang); + this.#layoutTextParams.ctx ??= TextLayer.#getCtx(this.#lang); + this.#layoutTextParams.minFontSize ??= TextLayer.#getMinFontSize(); const textDivs = this.#textDivs, textContentItemsStr = this.#textContentItemsStr; @@ -326,7 +331,11 @@ class TextLayer { divStyle.left = `${scaleFactorStr}${left.toFixed(2)}px)`; divStyle.top = `${scaleFactorStr}${top.toFixed(2)}px)`; } - divStyle.fontSize = `${scaleFactorStr}${fontHeight.toFixed(2)}px)`; + // We multiply the font size by #getMinFontSize(), and then #layout will + // scale the element by 1/#getMinFontSize(). 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.#getMinFontSize() * fontHeight).toFixed(2)}px)`; divStyle.fontFamily = fontFamily; textDivProperties.fontSize = fontHeight; @@ -386,9 +395,13 @@ class TextLayer { } #layout(params) { - const { div, properties, ctx, prevFontSize, prevFontFamily } = params; + const { div, properties, ctx, prevFontSize, prevFontFamily, minFontSize } = + params; const { style } = div; + let transform = ""; + if (minFontSize > 1) transform = `scale(${1 / minFontSize})`; + if (properties.canvasWidth !== 0 && properties.hasText) { const { fontFamily } = style; const { canvasWidth, fontSize } = properties; @@ -403,7 +416,7 @@ class TextLayer { const { width } = ctx.measureText(div.textContent); if (width > 0) { - transform = `scaleX(${(canvasWidth * this.#scale) / width})`; + transform = `scaleX(${(canvasWidth * this.#scale) / width}) ${transform}`; } } if (properties.angle !== 0) { @@ -452,6 +465,27 @@ class TextLayer { return canvasContext; } + /** + * @returns {number} The minimum font size enforced by the browser + */ + static #getMinFontSize() { + if (this.#minFontSize === null) { + const div = document.createElement("div"); + div.style.opacity = 0; + div.style.lineHeight = 1; + div.style.fontSize = "1px"; + div.innerHTML = "X"; + document.body.append(div); + // In `display:block` elements contain a single line of text, + // the height matched the line height (which, when set to 1, + // matches the actual font size). + this.#minFontSize = div.getBoundingClientRect().height; + div.remove(); + } + + return this.#minFontSize; + } + static #getAscent(fontFamily, lang) { const cachedAscent = this.#ascentCache.get(fontFamily); if (cachedAscent) {