google / material-design-icons

Material Design icons by Google (Material Symbols)
http://google.github.io/material-design-icons/
Apache License 2.0
50.51k stars 9.56k forks source link

The vertical metrics of the Material Symbols variable fonts do not allow them work correctly within Flutter unless altered #1500

Closed timmaffett closed 10 months ago

timmaffett commented 1 year ago

I recently created a package for flutter with proper support for the Material Symbols Icons using the variable font ttf files. This package can be found at https://pub.dev/packages/material_symbols_icons. There is a live example flutter web application demonstrating this here.

When I was developing this package I discovered that the font files found in the variablefonts directory have incorrect vertical metrics. This results is icons that are not vertically centered when using the flutter standard Icon/IconData icon rendering system. Example: exampleOfBadMetrics The icon rendered in red is from the unpatched ttf font with the incorrect vertical metrics and the icon behind (in black) is from a corrected (patched) ttf font. Note how the red icon is not centered within the circle.

All three fonts MaterialSymbolsOutlined[FILL,GRAD,opsz,wght].ttf, MaterialSymbolsRounded[FILL,GRAD,opsz,wght].ttf and MaterialSymbolsSharp[FILL,GRAD,opsz,wght].ttf have these incorrect vertical metrics. (I did not examine the woff2 verisons but I imagine they are also incorrect).

The incorrect metrics look like this: (as reported by fonttools ttx) (The ascent, descent, sTypoAscender, sTypeDescender, usWinAscent and usWinDescent values are incorrect as shown by the table below (marked with a *)

                Outlined, Rounded
                   and Sharp
<hhea>
  ascent            1056   *
  descent            -96   *
   lineGap             0  
<OS_2>
  xAvgCharWidth      960   
  sTypoAscender     1056   *
  sTypeDescender     -96   *
  sTypeLineGap         0
  usWinAscent       1062   *
  usWinDescent        91   *
  sxHeight           960
  sCapHeight         960

The icons in these fonts are created on a 960x960 grid and so the ascender and descenders should uniformly be 960 and 0 respectively. The descender value of -96 (91 for usWinDescent) found in the incorrect metrics are causing the icons to be incorrectly vertically centered when rendered.

The correct fix for this problem would be to specify the correct vertical metrics for each of these fonts when they are being generated.

The existing (non variable) Material Icons fonts found in the fonts directory have proper vertical metrics. (Those fonts are on a 512x512 grid so they have 512 and 0 for their ascender/descender values).

There may be an additional issue of a possible breaking change when this is corrected because existing users of the variable fonts would have most lilkely been having to compensate for the incorrect metrics in order to properly use the fonts. (with css style sheets perhaps ?)

The Material Symbols Icon fonts are properly on a 960x960 square.

              CORRECTED
           Outlined, Rounded
               and Sharp
<hhea>
  ascent         960 
  descent          0
   lineGap         0
<OS_2>
  xAvgCharWidth  960
  sTypoAscender  960
  sTypeDescender   0
  sTypeLineGap     0
  usWinAscent    960
  usWinDescent     0
  sxHeight       960
  sCapHeight     960

My flutter package includes automatic code generation tools which automatically pull the latest fonts and codepoint files from the this repository (google/material-design-icons/variablefonts). For now I must add an additional step of using fonttools ttx to patch the vertical metrics to correct the fonts. The scripts/patch files can be found at [https://github.com/timmaffett/material_symbols_icons/tree/master/rawFontsUnfixed] (https://github.com/timmaffett/material_symbols_icons/tree/master/rawFontsUnfixed) to patch the vertical metrics of each of the three fonts.

It would be wonderful if variable fonts had the correct vertical metrics in the first place! :wink: Thanks!


Google's own reference on properly manual creation of vertical metrics for a font. In the case of these icon fonts things are simplified because we are dealing with a situation where all icons are created on the same 960x960 grid and we do not have descenders like a font with proper letter glyphs would have.

https://github.com/googlefonts/gf-docs/tree/main/VerticalMetrics#vertical-metrics

tphinney commented 1 year ago

Hi Tim! I am the contractor who led the development of Material Symbols, so I am not representing Google policy or anything. But I can explain a few things. I should preface this by saying that as the fonts have been shipping externally for about a year now (and internally even longer, as Google Symbols), actually changing the metrics is… not really an option. They are pretty much locked in now, as anything else that cared about the same data would get shifting icons going from one version to the next.

But for what it’s worth, I can explain why this is all happening, and why I would likely make the same decisions again, even had I known that Flutter would have this problem (which I didn’t until just a few weeks ago).

BTW, I really appreciate the detailed analysis, because seeing somebody else lay out all the numbers again made me think about it one more time, and suddenly realize exactly why Flutter is having this problem, which I hadn’t quite deduced until now. It does not give me a great solution—not even if I had it all to do over from scratch—but I am happy to at least grok the problem fully.

So first off, it is unfortunate that we had several arguably conflicting goals in the development of Material Symbols: 1) Keep the exact same initial icon size and scale etcetera as Material Icons for the Regular style, including the grid alignment at 24 px 2) Beyond that, allow for substantial variation: weight, optical size, grade, and fill. Weight and grade both can influence the outer extent of strokes, meaning that the upper and lower bounds of a glyph can change. This is usually true for icons as it is for ideographic characters. (With alphabetic fonts, often the cap height is unchanged by weight or even grade, but they are a different animal.)

That combination means that some styles of the variable font are going to extend further above and below the initial space of the regular. The vertical metrics of the variable font reflect the greatest extent of the glyphs. I was careful for the Typo and HHEA values to add equal amounts both above and below.

But… it appears that Flutter’s vertical centering is based on some upper bound and… the baseline, rather than any of the lower bound values. So increasing both the upper and lower bounds, when the lower bound ends below the baseline afterwards, thus makes Flutter do vertical centering lower.

OK, I understand why they would do that, being focused on alphabetic fonts (rather than, say, ideographic writing systems like Chinese and Japanese). But if we want Material Symbols to generally align and behave like the old Material Icons in grid alignment and sizing, yet also support a much bolder maximum weight and up to two weights extra grade and all that, and not lie about its vertical metrics… we are painted into a corner.

Lying about max/min vertical metrics in ways that understate them is generally unwise, mostly because in some situations it can lead to the tops/bottoms of your font’s glyphs getting clipped at rendering time, which is a Bad Thing.

One font design solution would be if we did all those variations (max weight and max negative grade) without moving any outer points further vertically up or down at the extremes. I did experiment with where to add weight, when I was first prototyping the weight variations for Material Symbols. And honestly the bolder weights sucked badly, especially on more complex icons, if they did not also involve expanding the outer bounds of the icon somewhat. Even with some expansion, more complex shapes need to do less extreme things for inner strokes while the outer strokes carry the full burden of the visual weight gain.

Without going into a super long explanation, you can see some of the same issues being tackled in alphabetic fonts with lowercase, where almost always the x-height is increased in bolder weights.

If we did not care about compatibility with Material Icons in general, we could have made vertical centering work, by changing the alignment of the “Regular” so that in effect it floats above the baseline in the first place. Move everything up by say 120 units (3 px at 24 px sizing), so that even the Bold or Strong designs are still at or above the baseline at all times. Solves the problem, but of course we would lose compatibility with Material Icons, and also have weird vertical positioning for the Regular.

So in short, I didn’t know that we’d break Flutter vertical centering, by doing Material Symbols the way we did. Changing now does not seem like an option, anyway. But that said, even if I had known up front about the Flutter behavior, I don’t see a good route to avoid that problem while achieving our other goals and having good designs.

timmaffett commented 1 year ago

Your explanation makes complete sense as to why the vertical metrics are the way they are. I had gone back and forth thinking about this and looking at this over the last couple weeks because it seemed that there must be a reason.. and now I understand the reason behind it. Thank you for taking the time to explain it!

I did look into the cause within the flutter engine and it not exactly straightforward, as font rendering is done within the skia library on most platforms. For now the patching of the font's vertical metrics for flutter seems to be the best solution for creating flutter compatible versions.

You mention "even had I known that Flutter would have this problem (which I didn’t until just a few weeks ago)."

If I may ask what brought the issue with Flutter to your attention?

Do you think that creating (additional) flutter specific versions of ttf fonts would be an option ? Perhaps I could add a new issue such as 'Create flutter specific versions of the variable fonts' ?

As flutter is a google project closely tied to material design having versions that work with flutter unpatched seems like at least a possibility.

Thanks again!

tphinney commented 1 year ago

Somebody filed a very similar bug, internally, which brought the issue to my attention.

I am not convinced that having two versions of the fonts around, that are incompatible with each other, would be a great solution.

I wonder if Flutter has the same issue with vertical centering of ideographic (Chinese and/or Japanese) text?

timmaffett commented 1 year ago

I do not know about flutter having issues with Chinese/Japanese text, but I do know Flutter is heavily used throughout the region and I have not seen issues about it.

For now my material_symbols_icons package works flawlessly with the existing variable fonts (with patched metrics). This is all completely hidden from the users of the package as it only occurs when the package is build/updated.

One thing I did discover when I was in the process of solving this problem was that if I took the fonts into High-Logic FontCreator app and had it RE-compute the metrics the fonts would work correctly.
I thought possibliy these values may be interesting to you.

The metrics it would create were:

                Outlined, Rounded        FontCreator Recomputed
                   and Sharp           Metrics (that worked in flutter)
<hhea>
  ascent            1056   *                   1087            
  descent            -96   *                   -107
   lineGap             0                          0
<OS_2>
  xAvgCharWidth      960                        960
  sTypoAscender     1056   *                    850
  sTypeDescender     -96   *                   -110
  sTypeLineGap         0                        192
  usWinAscent       1062   *                   1087
  usWinDescent        91   *                    107
  sxHeight           960                        960
  sCapHeight         960                        960

Thanks again!

timmaffett commented 1 year ago

I changed the name of this issue from The variable fonts in the variablefonts directory have incorrect vertical metrics in the hhea and os_2 tables to The vertical metrics of the Material Symbols variable fonts do not allow them work correctly within Flutter unless altered to more correctly address the issue at hand.

tphinney commented 1 year ago

That is interesting. The only vertical metrics that are changing enough to cause that much shift are the sTypo set. (Note: "sType" is a typo for "sTypo" in a couple of the metrics, there.) I would imagine that sTypoAscender is the main thing in play.

This could be easily checked if you were so inclined. If you could, from your current “good” results, perhaps try setting both sTypoLineGap and sTypoDescender to zero and verify that doesn’t disrupt your “good” alignment? If it does mess things up, change sTypoDescender back to -110 as your first adjustment, see if that fixes it.

That said, although interesting, I don’t know that we can do anything near-term with the information. I strongly suspect your current metrics, that sTypoAscender setting in particular against other upper bound measurements, will yield some different first-line positioning between different apps, with just standard (not vertically centered) text in a text box. I should think that would be considered unacceptable. :(

timmaffett commented 10 months ago

With the fixing of https://github.com/flutter/flutter/issues/138592 with https://github.com/flutter/flutter/pull/138937 then this issue (the same issue) should be fixed as well.
I also confirmed that with #138937 in place the https://pub.dev/packages/material_symbols_icons Symbols icons all render correctly centered without modifying the font metrics of the symbols fonts. (and that the altered-metrics symbols fonts continue to render correctly as well with #138937 in place).

Going forward it will now be possible to use the material symbols icons fonts unaltered, (but those versions would obviously continue to render incorrectly on previous flutter versions.) The https://pub.dev/packages/material_symbols_icons package will render correctly on all previous and new flutter versions.

Closing this issue as fixed by https://github.com/flutter/flutter/pull/138937