canvas measureText() returns incorrect (inflated) width for emoji at small font sizes
Categories
(Core :: Layout: Text and Fonts, defect, P3)
Tracking
()
People
(Reporter: chenglou92, Unassigned)
Details
Attachments
(1 file)
|
1.20 KB,
text/html
|
Details |
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:
- 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
- Open the attached
emoji-measuretext-repro.html - Observe the table comparing canvas
measureText('😀').widthvsspan.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.
Comment 1•5 months ago
|
||
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.
| Reporter | ||
Comment 2•5 months ago
|
||
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
Comment 3•5 months ago
|
||
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.
Comment 4•5 months ago
|
||
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.
Comment 5•5 months ago
|
||
I'm not convinced this is a valid report... But feels S3-ish, feel free to correct if not.
| Reporter | ||
Comment 6•5 months ago
|
||
Sorry which part's incorrect? I can rectify. It blocks some user land measurement for text rendering
Description
•