Open Bug 2020894 Opened 5 months ago Updated 5 months ago

canvas measureText() returns incorrect (inflated) width for emoji at small font sizes

Categories

(Core :: Layout: Text and Fonts, defect, P3)

Firefox 148
defect

Tracking

()

UNCONFIRMED

People

(Reporter: chenglou92, Unassigned)

Details

Attachments

(1 file)

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.3 Safari/605.1.15

Steps to reproduce:

  1. Open repro page and check the Diff column (it's JS, not a static table)

Actual results:

Diff isn't all +0.0

Expected results:

Diff should be all +0.0

CanvasRenderingContext2D.measureText() returns widths for emoji characters that are larger than what the DOM renders. The discrepancy is 2-5px at font sizes below 24px and disappears at >=24px. DOM always renders emoji at exactly the font size (width = fontSize). The issue affects all emoji, all font families, and both <canvas> and OffscreenCanvas.

Safari does not have this issue — canvas and DOM agree on emoji widths at all sizes.

Steps to reproduce

  1. Open the attached emoji-measuretext-repro.html
  2. Observe the table comparing canvas measureText('😀').width vs span.getBoundingClientRect().width

Expected result

Canvas measureText() and DOM getBoundingClientRect() return the same width for the same emoji at the same font size. (This is what Safari does.)

Actual result (Chrome on macOS, DPR=2)

fontSize  canvas  DOM     diff
  10px     13.0    11.0   +2.0
  12px     15.0    12.0   +3.0
  14px     18.0    14.0   +4.0
  15px     19.0    15.0   +4.0
  16px     20.0    16.0   +4.0
  18px     21.0    18.0   +3.0
  20px     22.0    20.0   +2.0
  24px     24.0    24.0   +0.0
  28px     28.0    28.0   +0.0
  32px     32.0    32.0   +0.0

Additional observations

  • Same result with OffscreenCanvas
  • Same result with DPR emulated at 1x (DevTools device mode)
  • Same diff for all 74 emoji tested (basic, ZWJ sequences, flags, skin tones, keycaps)
  • Same diff across all font families (sans-serif, serif, monospace, named fonts)
  • The DOM value always equals the font size (emoji width = fontSize)
  • HarfBuzz and opentype.js (reading the Apple Color Emoji font directly) also give width = fontSize, matching DOM
  • Firefox has the same class of bug but with different diff values (+1 to +5, converges at 28px instead of 24px)
  • Safari canvas matches DOM perfectly at all sizes

Impact

This breaks canvas-based text measurement libraries that need to predict line breaks matching DOM rendering. Text blocks containing emoji get measured as wider than they actually render, causing premature line breaks and incorrect height calculations.

The Bugbug bot thinks this bug should belong to the 'Core::Layout: Text and Fonts' component, and is moving the bug to that component. Please correct in case you think the bot is wrong.

Component: Untriaged → Layout: Text and Fonts
Product: Firefox → Core

Sorry I posted Chrome's discrepancies. The Firefox ones should be:

fontSize canvas DOM diff
10px 13.0 11.5 +1.5
12px 16.0 12.5 +3.5
14px 19.0 14.0 +5.0
15px 20.0 15.0 +5.0
16px 21.0 16.0 +5.0
18px 22.0 18.0 +4.0
20px 23.0 20.0 +3.0
24px 25.0 24.0 +1.0
28px 28.0 28.0 +0.0
32px 32.0 32.0 +0.0

This is somehow related to resolution. I think you are testing this on a high-dpi (retina) screen, where the DOM content is rendered with a devicePixelRatio of 2.0, whereas canvas2d has an effective DPR of 1.0, and this is affecting how the font is scaled, although I'm not sure exactly what's going on.

If you load the same testcase on a low-dpi monitor, or if you zoom out to 50% scale on a retina screen, the discrepancy doesn't occur, because DOM and canvas2d are then using the same DPR.

This is also specific to the particular emoji font: I see this behavior when Apple Color Emoji is being used (by default), but if I include a different emoji font such as Twemoji Mozilla or Segoe UI Emoji in the font-family list, it doesn't happen. I'm guessing this is related to Apple Color Emoji being a bitmap font, whereas the others I tried are scalable outline fonts.

BTW, I don't think it's correct to say that the DOM-rendered emoji width is always equal to the font size. Even with Safari, the results I get are

fontSize  canvas  DOM     diff
  10px     13.0    13.0   +0.0
  12px     16.0    16.0   +0.0
  14px     19.0    19.0   +0.0
  15px     20.0    20.0   +0.0
  16px     21.0    21.0   +0.0
  18px     22.0    22.0   +0.0
  20px     23.0    23.0   +0.0
  24px     25.0    25.0   +0.0
  28px     28.0    28.0   +0.0
  32px     32.0    32.0   +0.0

where we can see that the measured with matches the font size only at 28px and larger. At smaller sizes, the width is larger than font size: the Apple Color Emoji font has non-linear scaling at small sizes.

I'm not convinced this is a valid report... But feels S3-ish, feel free to correct if not.

Severity: -- → S3
Priority: -- → P3

Sorry which part's incorrect? I can rectify. It blocks some user land measurement for text rendering

You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: