Skip to content

Commit

Permalink
Overrride the minimum font size when rendering the text layer
Browse files Browse the repository at this point in the history
Browsers have an accessibility option that allows user to enforce
a minimum font size for all text rendered in the page, regardless
of what the font-size CSS property says. For example, it can be
found in Firefox under `font.minimum-size.x-western`.

When rendering the <span>s in the text layer, this causes the
text layer to not be aligned anymore with the underlying canvas.
While normally accessibility features should not be worked around,
in this case it is *not* improving accessibility:
- the text is transparent, so making it bigger doesn't make it more
  readable
- the selection UX for users with that accessibility option enabled
  is worse than for other users (it's basically unusable).

While there is tecnically no way to ignore that minimum font size,
this commit does it by multiplying all the `font-size`s in the text
layer by minFontSize, and then scaling all the `<span>`s down by
1/minFontSize.
  • Loading branch information
nicolo-ribaudo committed Jun 18, 2024
1 parent 95a7de9 commit e2d9297
Showing 1 changed file with 38 additions and 4 deletions.
42 changes: 38 additions & 4 deletions src/display/text_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ class TextLayer {

static #canvasContexts = new Map();

static #minFontSize = null;

static #pendingTextLayers = new Set();

/**
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 <span>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;
Expand Down Expand Up @@ -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})`;

Check failure on line 403 in src/display/text_layer.js

View workflow job for this annotation

GitHub Actions / Lint (lts/*)

Expected { after 'if' condition

if (properties.canvasWidth !== 0 && properties.hasText) {
const { fontFamily } = style;
const { canvasWidth, fontSize } = properties;
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit e2d9297

Please sign in to comment.