Closed Bug 1919906 Opened 3 months ago Closed 2 months ago

Text rendering looks off on maps.apple.com on Windows

Categories

(Core :: Graphics: Text, defect)

Unspecified
Windows
defect

Tracking

()

RESOLVED FIXED
133 Branch
Tracking Status
firefox-esr115 --- wontfix
firefox-esr128 --- wontfix
firefox130 --- wontfix
firefox131 --- wontfix
firefox132 --- wontfix
firefox133 --- wontfix
firefox134 --- fixed

People

(Reporter: bgrins, Assigned: jfkthame, NeedInfo)

References

(Blocks 1 open bug, Regressed 1 open bug, Regression)

Details

(Keywords: regression, webcompat:platform-bug, webcompat:site-report)

Attachments

(5 files)

Attached image edge.jpeg

Same text on Edge

Jonathan, any ideas what's wrong with the glyph layout here? Apple seems interested in fixing this.

Flags: needinfo?(jfkthame)

I can't seem to inspect that text to see how it's being styled, but my guess is this might be a bad interaction between letter-spacing, scaling, and pixel-snapping of glyph positions.

Flags: needinfo?(jfkthame)

The text is rendered on canvas.rt-root (you can use this search query in the inspector to find the element), which has the following rule:

.mk-map-view .rt-root {
  letter-spacing: initial;
}

But removing, or changing this live doesn't seem to fix the issue, but I'm not sure the text get re-rendered without reloading the page.

Also note that the rendering issue doesn't reproduce on Linux.

Based on native method call tracing, I suspect, but I'm not 100% sure that the text is rendered around here:
https://share.firefox.dev/3TDKKL4
I don't know Canvas API enough to spot if something here would possibly render some text in the canvas.

Using the debugger, I think I found where we render the text.

https://beta.maps.apple.com/static/maps-app-web-client/mapkitjs/libs/mapkit.core.1b57c0.js?mkjsVersion=5.78.83
line 1227 (pretty printed)

  • e is the canvas rendering context
          draw: function (e) {
            var t = this._node,
            i = this.params[t.kind];
            this._setTextProperties(e);
            var o = t.parent.annotation.darkColorScheme ? 'dark' : 'light';
            e.fillStyle = i.color[o],
            e.strokeStyle = this.params.haloColor[o],
            e.lineWidth = this.params.haloSize;
            var a = t.size.width / 2,
            n = t.size.height - i.yPadding;
            e.strokeText(t.text, a, n),
            e.fillText(t.text, a, n)
          }
  • t.text = "Transamerica"
  • a = 38.5
  • n = 15
  • e.lineWidth = 2
  • e.font = 600 11px "SFProRegular", "-apple-system", "BlinkMacSystemFont", "Helvetica Neue", Helvetica, Arial, sans-serif
  • e.textAlign = center
  • e.textBaseline = bottom
  • e.miterLimit = 2

Also interesting, in the same source.
Line 1543 (pretty printed)

  • e is the canvas context,
  • t is "Transamerica pyramid"
  • i is "600 11px "SFProRegular", "-apple-system", "BlinkMacSystemFont", "Helvetica Neue", Helvetica, Arial, sans-serif"
              return function (e, t, i) {
                e.font = i;
                var o = t.trim().split(/[\f\n\r\t\v\u0020\u2000-\u200b\u205f\u3000]+/),
                a = [];
                for (; o.length > 0; ) {
                  for (var n = [
                    o.shift()
                  ], s = n.join(' ').length; o.length > 0 && s <= 16; ) n.push(o.shift()),
                  s = n.join(' ').length;
                  s > 16 &&
                  n.length > 1 &&
                  o.unshift(n.pop());
                  const t = n.join(' '),
                  i = e.measureText(t),
                  l = i.width + u.labelOutlineWidth;
                  a.push([t,
                  l,
                  i])
                }
                return a

Same source, line 1135

  • e is "Pyramid"
  • t is "Title"
  • i is 47
  • a is a TextMetrics object
  • l is false
      function l(e, t, i, a, l) {
        o.BaseNode.call(this),
        this.text = e,
        this.kind = t,
        this._renderer = new n(this),
        this.textAscent = a.ascent ||
        0,
        this.textDescent = a.descent ||
        0,
        this.size = new s(i, this._renderer.params[this.kind].height),
        this.keyForFrozenLayer = (l ? 'dark' : 'light') + '-' + t + '#' + this.text
      }

Yeah, the "Transamerica Pyramid" label is drawn using canvas2d.fillText (unlike the other labels around the map), and that doesn't handle spacing well in Arial, which is the font we end up using on Windows.

The result looks a lot better if you remove Arial from the gfx.font_rendering.cleartype_params.force_gdi_classic_for_families pref. (Requires browser restart to see the change, I believe.)

There's a long history behind that setting, and it may be that it's time we got rid of it altogether, but any change to Windows font rendering tends to be controversial. :(

Note that if I render the same text "Transamerica Pyramid" using font: 600 11px Arial in HTML, the spacing is much better. The bad result here is specific to canvas2d text, which apparently doesn't handle the attempt to set the cleartype mode very well.

In the HTML-rendered text, we can see the effect of "force GDI classic" mode, e.g. making the "m" glyph wider. It looks like the canvas2d-rendered text has laid out the glyphs according to the GDI-classic metrics, but the actual rendering is not using this mode and so we get a narrower "m" and bad spacing. (Other glyphs are also affected, but the "m" is perhaps the clearest.)

We don't render the glyphs using "GDI classic" mode in canvas because we explicitly set params.allowGDI to false here. This dates back to bug 1730772.

Removing that line results in much better spacing of the "Transamerica Pyramid" text, but presumably it may regress things that bug 1730772 was aiming to fix.

A quick side note to explain how I found this JS code.
I hacked something to break on any function call whose any of its arguments was equal to Transamerica pyramid.
It paused on many function calls related to the left sidebar, but ultimately paused on the right function calling the canvas API.

Keywords: regression
Regressed by: 1730772

Set release status flags based on info from the regressing bug 1730772

:bas.schouten, since you are the author of the regressor, bug 1730772, could you take a look? Also, could you set the severity field?

For more information, please visit BugBot documentation.

Assignee: nobody → jfkthame
Status: NEW → ASSIGNED
Pushed by jkew@mozilla.com: https://hg.mozilla.org/integration/autoland/rev/42256b64dc32 Ensure fonts used in canvas2d do not apply forced-GDI-classic mode during layout/measurement. r=lsalzman

Backed out for causing bc failures on browser_less_common_selection_manipulations.js.

[task 2024-09-24T20:58:00.287Z] 20:58:00     INFO - TEST-PASS | browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js | Check initial selection - [8,13] deepEqual [8,13] - 
[task 2024-09-24T20:58:00.288Z] 20:58:00     INFO - Test Drag Selection from the first character - Full host
[task 2024-09-24T20:58:00.289Z] 20:58:00     INFO - Selected text is <example.co>
[task 2024-09-24T20:58:00.289Z] 20:58:00     INFO - Buffered messages finished
[task 2024-09-24T20:58:00.291Z] 20:58:00     INFO - TEST-UNEXPECTED-FAIL | browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js | Check initial selection - [0,19] deepEqual [8,18] - 
[task 2024-09-24T20:58:00.291Z] 20:58:00     INFO - Stack trace:
[task 2024-09-24T20:58:00.291Z] 20:58:00     INFO - chrome://mochitests/content/browser/browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js:doTest/<:275
[task 2024-09-24T20:58:00.291Z] 20:58:00     INFO - resource://testing-common/BrowserTestUtils.sys.mjs:withNewTab:121
[task 2024-09-24T20:58:00.292Z] 20:58:00     INFO - chrome://mochitests/content/browser/browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js:doTest:259
[task 2024-09-24T20:58:00.292Z] 20:58:00     INFO - chrome://mochitests/content/browser/browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js:https:250
[task 2024-09-24T20:58:00.292Z] 20:58:00     INFO - chrome://mochikit/content/browser-test.js:handleTask:1145
[task 2024-09-24T20:58:00.292Z] 20:58:00     INFO - chrome://mochikit/content/browser-test.js:_runTaskBasedTest:1217
[task 2024-09-24T20:58:00.292Z] 20:58:00     INFO - chrome://mochikit/content/browser-test.js:Tester_execTest:1358
[task 2024-09-24T20:58:00.292Z] 20:58:00     INFO - chrome://mochikit/content/browser-test.js:nextTest/<:1134
[task 2024-09-24T20:58:00.292Z] 20:58:00     INFO - chrome://mochikit/content/tests/SimpleTest/SimpleTest.js:SimpleTest.waitForFocus/<:1058
[task 2024-09-24T20:58:00.292Z] 20:58:00     INFO - Test Drag Selection from the last character
[task 2024-09-24T20:58:00.293Z] 20:58:00     INFO - Selected text is <example.com/test/some/page.htm>
[task 2024-09-24T20:58:00.294Z] 20:58:00     INFO - TEST-PASS | browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js | Check initial selection - [0,30] deepEqual [0,30] - 
[task 2024-09-24T20:58:00.294Z] 20:58:00     INFO - Test Drag Selection in the middle of the string
[task 2024-09-24T20:58:00.294Z] 20:58:00     INFO - Selected text is <le.co>
[task 2024-09-24T20:58:00.295Z] 20:58:00     INFO - TEST-PASS | browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js | Check initial selection - [13,18] deepEqual [13,18] - 
[task 2024-09-24T20:58:00.296Z] 20:58:00     INFO - Test Double-click on word
[task 2024-09-24T20:58:00.296Z] 20:58:00     INFO - Selected text is </>
[task 2024-09-24T20:58:00.297Z] 20:58:00     INFO - Not taking screenshot here: see the one that was previously logged
[task 2024-09-24T20:58:00.298Z] 20:58:00     INFO - TEST-UNEXPECTED-FAIL | browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js | Check initial selection - [20,24] deepEqual [19,20] - 
[task 2024-09-24T20:58:00.298Z] 20:58:00     INFO - Stack trace:
[task 2024-09-24T20:58:00.298Z] 20:58:00     INFO - chrome://mochitests/content/browser/browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js:doTest/<:275
[task 2024-09-24T20:58:00.298Z] 20:58:00     INFO - resource://testing-common/BrowserTestUtils.sys.mjs:withNewTab:121
[task 2024-09-24T20:58:00.298Z] 20:58:00     INFO - chrome://mochitests/content/browser/browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js:doTest:259
[task 2024-09-24T20:58:00.298Z] 20:58:00     INFO - chrome://mochitests/content/browser/browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js:https:250
[task 2024-09-24T20:58:00.298Z] 20:58:00     INFO - chrome://mochikit/content/browser-test.js:handleTask:1145
[task 2024-09-24T20:58:00.298Z] 20:58:00     INFO - chrome://mochikit/content/browser-test.js:_runTaskBasedTest:1217
[task 2024-09-24T20:58:00.298Z] 20:58:00     INFO - chrome://mochikit/content/browser-test.js:Tester_execTest:1358
[task 2024-09-24T20:58:00.298Z] 20:58:00     INFO - chrome://mochikit/content/browser-test.js:nextTest/<:1134
[task 2024-09-24T20:58:00.298Z] 20:58:00     INFO - chrome://mochikit/content/tests/SimpleTest/SimpleTest.js:SimpleTest.waitForFocus/<:1058
[task 2024-09-24T20:58:00.300Z] 20:58:00     INFO - Click at the right of the text
[task 2024-09-24T20:58:00.300Z] 20:58:00     INFO - Selected text is <https://example.com/test/some/page.htm>
[task 2024-09-24T20:58:00.301Z] 20:58:00     INFO - TEST-PASS | browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js | Check initial selection - [0,38] deepEqual [0,38] -
Flags: needinfo?(jfkthame)

The browser_less_common_selection_manipulations.js failures are happening because the test uses canvas2d.measureText and assumes the resulting metrics will match the text in the URL bar, but because the system font in the URLbar is using GDI Classic mode, and we no longer use those metrics in canvas, the widths don't match.

A workaround for this, I think, is to use an HTML-based (rather than canvas-based) approach to measuring substrings of the URLbar content. I'll push a try run to check... https://treeherder.mozilla.org/jobs?repo=try&revision=fd3691742d870cd51d305f512af0aade142d0d0c

Flags: needinfo?(jfkthame)

Because of the possibility of different rendering modes, it's not guaranteed that
canvas2d.measureText will return exactly the same text metrics as HTML uses.
So instead of relying on canvas to measure substrings of the URLbar content,
measure by putting the text in a <span> and querying its offsetWidth.

Pushed by jkew@mozilla.com: https://hg.mozilla.org/integration/autoland/rev/356a7ea855bc Ensure fonts used in canvas2d do not apply forced-GDI-classic mode during layout/measurement. r=lsalzman https://hg.mozilla.org/integration/autoland/rev/31bd8fe7b5ce Use HTML text measurement in browser_less_common_selection_manipulations.js, as canvas text metrics may not precisely match HTML metrics. r=mak,urlbar-reviewers
Status: ASSIGNED → RESOLVED
Closed: 2 months ago
Resolution: --- → FIXED
Target Milestone: --- → 133 Branch
Regressions: 1922063
Flags: needinfo?(bas)
Regressions: 1923150
Regressions: 1923278

Given the regressions, this doesn't sound like something we want to fast-track shipping.

Jonathan, I think we should probably back this out for now until we get a better handle on the regressions.

Flags: needinfo?(jfkthame)

Possibly, but let's give it a little bit more time: I've just (re-)lando'd bug 1920733, which should resolve the pdf.js-related problems, assuming it sticks.

(leaving ni? in place for now)

Duplicate of this bug: 1769326
Duplicate of this bug: 1757081

Followup on backout plans: per bug 1923150 comment 12, it looks like we're leaving this fix in mozilla-central/Nightly, but we're planning to backout from the beta channel after the next central-to-beta merge (so as not to ship this bug's regressions in 133 beta/release).

[please correct me if that's wrong; I'm just noting the latest plan I could find on this & associated bugs]

See Also: → 1920733

Possible duplicates: bug 1885846, bug 890733, bug 1029327

Blocks: 1885846
See Also: → 890733
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: