Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subset icon font #1201

Open
caperaven opened this issue Aug 3, 2021 · 61 comments
Open

Subset icon font #1201

caperaven opened this issue Aug 3, 2021 · 61 comments

Comments

@caperaven
Copy link

The icon font has all the icons available.
I am looking for a way to customise or create a custom font that only contains the icons I use.
What tooling do you use to create the current icon fonts?

I am kinda new to the font generation thing but would like to get the same behaviour as using the standard font.
That being that the icon name is the identifier of the icon not some ascii code.

The intent of the custom font is for personal use but are there any legal issues I should take note of?

@jfbrennan
Copy link

jfbrennan commented Aug 8, 2021

Need the same because the docs advertise 900+ fonts as weighing "in at only 42KB in its smallest woff2 format" but I'm seeing 109KB and no compression on the response in dev tools.

My code:

src: url('https://fonts.gstatic.com/s/materialicons/v97/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2') format('woff2');

@jfbrennan
Copy link

@caperaven you might want to change the title to better reflect the ask. Perhaps "Customize the icon font set to reduce size"

@jfbrennan
Copy link

jfbrennan commented Aug 8, 2021

Dup of #564, which is 5 years old 😢

@caperaven
Copy link
Author

I am happy to build the font myself, I am just unsure of what the best tooling is to use for this.
Any suggestions?

@rsheeter
Copy link
Collaborator

rsheeter commented Sep 7, 2021

(took the liberty of amending the title)

Figure out the glyph ids for the icons you want to keep (because using text= may retain other icons reachable with the same text, e.g. alarm_on might keep alarm as well) and then subset by glyph id. For example, you could use https://rsheeter.github.io/font101/#hb-shape to get the glyph id and then https://rsheeter.github.io/font101/#pyftsubset to subset the font. This could all be packaged up into a nice cli that takes a list of icon names if you need to do it repeatedly.

Edit: if you plan to deliver to the web you will want to compress the resulting font; https://rsheeter.github.io/font101/#web-serving has some suggestions.

@rsheeter rsheeter changed the title Customize icon font Subset icon font Sep 7, 2021
@tphinney
Copy link
Collaborator

Looks like this was resolved long ago!

@EskelCz
Copy link

EskelCz commented May 10, 2023

@tphinney Actually not really, because it doesn't work with the new Material Symbols. I tried all the options of pyftsubset for an hour, but the ligatures are getting lost in the process. :( Fontsquirrel and fontello doesn't work either.
The font has over 3MB, therefore the subsetting is truly necessary.

@tphinney
Copy link
Collaborator

Probably the ligatures are getting lost because you aren’t including the glyphs that are needed to input the ligatures. Besides the desired symbols you would need a-z, space, underscore, and to include the 'rlig' feature.

@EskelCz
Copy link

EskelCz commented May 12, 2023

@tphinney Ah, I see, that makes sense, thanks.

Tried for another hour and whenever I add U+61-7A (supposedly a-z), the ligatures are there but the font balloons to 2.5MB (from 30kB for just the 30 or so icons I need).

At this point I decided to go with link to fonts.googleapis.com, which download about 40kB for the same font. No idea how, but it works. Still I would prefer self-hosting, if I could manage to make the subset.

@tphinney
Copy link
Collaborator

Note: realized I was forgetting the zero through nine characters, also needed to get at the names of some glyphs.

So, it seems like you are now getting EVERY ligature that can be formed with those glyphs. Which would be nearly all of them. (Except those needing characters you don't need.

Whatever tool you use to subset needs to both:

  1. support maintaining the ligation code
  2. allow you to decide which ligatures you need instead of grabbing all possible ones

I don't know if pyftsubset can do that for you. If not, you need something that can. Unfortunately all this is a bit of a “corner case” as far as font subsetting goes. It is an unusual situation where one wants some but not all of the ligatures that can derive from the available characters.

@EskelCz
Copy link

EskelCz commented May 13, 2023

@tphinney Thanks for the explanation. I haven't found any other tools that can do that programmatically.
I think this calls for a feature of the website. The back-end must have such code already, since it's somehow working when used from the api - it's not serving the entire 3MB font file.
Are feature requests tracked anywhere?

@tphinney
Copy link
Collaborator

Yes. This is the place for external feature requests.
There is also an internal Google database for such things.
I can create an issue there next week. I am traveling at the moment.

@EskelCz
Copy link

EskelCz commented May 25, 2023

@tphinney Sorry to bother again but I just noticed that the API now returns the large file as well. I'd love to use the variable icon font but the size doesn't seem worth it.
image
This is my integration code:

<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@48,100..700,1,0..200" rel="stylesheet" />

Can you please escalate this somehow? Thanks

@EskelCz
Copy link

EskelCz commented Jul 9, 2023

@tphinney Can you please reopen this issue, or should I open a new one about the variable symbol subsetting? I feel like it's a huge waste for such an awesome asset to be basically unusable in production.

@jfbrennan
Copy link

Yes, please reopen. Came back here after 2 years sure I would find Google has indeed solved this by now. Darn.

No app uses 3,000 icons. The smallest configuration I've been able to get yields a 323kb woff2 file (using family=Material+Symbols+Outlined:[email protected]). 323kb is relatively small for 3,000 freaking icons plus filled versions, but that's still far too big for devs who don't like building 10MB React SPAs🤦‍♂️

Ideal solution:
CLI where developers can feed a list of icon names, e.g. home done add expand_more, a style outline, and a list of options, e.g. --fill:0..1, and out pops a new Material Symbols .woff2 variable font with only those icons. I could go from 323kb to like 12kb.

If anyone has ever achieved exactly this, PLEASE reveal your secrets! Like maybe the folks at Google working on Material Symbols who know the tooling/config used to build the full Material Symbols font files. Can ya throw us a bone here?!

@rsheeter
Copy link
Collaborator

rsheeter commented Sep 7, 2023

Reopening because it would be a good feature to have.

If you want to DIY I think what you want to do is roughly:

  1. Identify what glyphs - not codepoints - you want to keep
  2. Subset specifying glyph-ids taken from ^

I'd have to try it to be sure the ligatures would be retained, it's possible you'd end up with just PUA codepoint access. For context, a Google-style icon font typically lets you get the icon glyph by either a PUA or a ligature. If you end up with just PUAs you'd have to rebuild the ligatures.

This could certainly be packaged up into a little tool or exposed by an API endpoint but I believe nobody has sat down and done it so far.

@jfbrennan
Copy link

Thanks @rsheeter, will try to look into all that. It's a totally new space to me.

PUA = "Private Use Area" yeah?
ligature = the icon "strings", e.g. home? Those characters are kind of like internally mapped to a glyph, yeah?

Tool creators do seem to be struggling to provide a solution, but it is nevertheless a need in the web community dzhuang/subset-iconfont#81

@rsheeter
Copy link
Collaborator

rsheeter commented Sep 7, 2023

PUA = "Private Use Area" yeah?

Yes. On https://fonts.google.com/icons?selected=Material+Symbols+Outlined:menu:FILL@0;wght@400;GRAD@0;opsz@24 this is displayed as codepoint. That codepoint maps directly to the glyph via cmap as described in https://rsheeter.github.io/font101/#glyph-ids-and-the-cmap-table.

ligature = the icon "strings", e.g. home? Those characters are kind of like internally mapped to a glyph, yeah?

One extra indirection, a ligature maps a series of glyphs to another glyph so "menu" would resolve to glyphs and then those glyphs would be replaced by the glyph for the menu icon.

https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#lookuptype-4-ligature-substitution-subtable

Tool creators do seem to be struggling to provide a solution

All the parts are available but I appreciate that doesn't mean it's trivial to assemble them.

@rsheeter rsheeter reopened this Sep 7, 2023
@rsheeter
Copy link
Collaborator

rsheeter commented Sep 7, 2023

Apparently I said I would reopen but didn't actually do it

@tphinney
Copy link
Collaborator

tphinney commented Sep 7, 2023

I agree that “good” subsetting of Material Symbols would be lovely and even important functionality for Google to provide. It is… not trivial, and especially not if adding it to Google’s font-serving capabilities.

Another thing that any subsetter needs to do to maintain full functionality is, IF the subset includes the 'FILL' axis, maintain alt glyphs that get swapped at >99% fill. This is needed to ensure that fully-filled glyphs render correctly, especially (1) when multiple axes are at non-default positions; and/or (2) Apple’s rasterizer is being used, as is commonly the case on iPhone and macOS. (Note to Googlers: there are additional wrinkles on this, if we are dealing with the internal Google Symbols version of the font.)

Note that the way these alts are implemented is not supported in all environments (notably not working in Figma and Adobe desktop apps), though they work in all browsers AFAIK.

@rsheeter
Copy link
Collaborator

rsheeter commented Sep 7, 2023

Another thing that any subsetter needs to do to maintain full functionality is, IF the subset includes the 'FILL' axis, maintain alt glyphs that get swapped at >99% fill

If we lose the alt glyphs when subsetting that's a bug in the subsetter that we would fix so I think for planning purposes one could assume that works.

@rsheeter
Copy link
Collaborator

rsheeter commented Sep 7, 2023

https://github.com/rsheeter/subset-gf-icons/blob/main/src/subset_gf_icons/subset_gf_icons.py shows finding the glyph ids which one would then feed to subset, etc. I only had a few minutes to play so that's as far as I got :)

@jfbrennan
Copy link

jfbrennan commented Sep 7, 2023

Google’s font-serving capabilities

A nicety for sure, but I think a CLI is where the biggest impact is. Frontend teams would want to be able to update their project's list of used icon names in a PR and then have CI (or local tooling) spit out the new icon font file, which would get hosted along with all their other assets.

Pardon my frankness, but don't you guys already have these tools available to you?!
Somehow you guys are moving the source SVG files in https://github.com/google/material-design-icons/tree/master/symbols/web through some processor into the final .woff2 file(s) we fetch from fonts.gstatic.com? Can't you just like open-source that tool? I'm sure it's not that simple, but you get the point - something inside Google turns Material Symbols SVGs into a .woff2 file. HAND IT OVER! :)

@jfbrennan
Copy link

^ it was an intern who did it all by hand wasn't it?

@EskelCz
Copy link

EskelCz commented Sep 26, 2023

@tphinney Interesting, you're correct. It's not multi-word icons, it's all icons containing characters a, b or c. All icons without these letters are working. Thanks for the correction.

@rsheeter Any idea why does subset_gf_icons omit these characters?

For completeness, this is my command:

subset_gf_icons ../material-design-icons/variablefont/MaterialSymbolsRounded[FILL,GRAD,opsz,wght].ttf done chat share close manage_accounts check flag more_vert arrow_back_ios magic_button edit pause play_arrow delete forum star minimize send arrow_drop_down person content_paste location_city interests group verified work cake transgender translate factory wc key_off label_important shield info filter_alt sort groups chevron_left chevron_right

@rsheeter
Copy link
Collaborator

Any idea why does subset_gf_icons omit these characters?

My quick hackery assumed zero width space would suffice to break ligatures. I never tested that, it doesn't work. The result was if the sorted unique characters from the icon names you asked to subset activated a ligature it would activate and then some of the icons you wanted to use wouldn't be accessible. Your sample command hits this for the sequence abc.

Should be fixed by rsheeter/subset-gf-icons#2.

@EskelCz
Copy link

EskelCz commented Sep 28, 2023

@rsheeter It works! You're my hero :) Thanks a lot.
Reduced from 3MB to 40KB. Well done.

Example for anyone new here, you can subset the variable Google Symbols font into woff2 like so (replace icon names):

pip install -e .
subset_gf_icons ../material-design-icons/variablefont/MaterialSymbolsRounded[FILL,GRAD,opsz,wght].ttf done chat share close check --flavor woff2

@MarcoTroost
Copy link

@tphinney Can you please reopen this issue, or should I open a new one about the variable symbol subsetting? I feel like it's a huge waste for such an awesome asset to be basically unusable in production.

Bump

@jfbrennan
Copy link

Unless someone beats me to it, I'm going to create a one-pager with complete instructions and examples. I'll link to it here once ready. Thanks a million @rsheeter and @EskelCz

@tphinney
Copy link
Collaborator

tphinney commented Oct 5, 2023

@tphinney Can you please reopen this issue, or should I open a new one about the variable symbol subsetting? I feel like it's a huge waste for such an awesome asset to be basically unusable in production.

Bump

It was reopened a month ago

@MarcoTroost
Copy link

MarcoTroost commented Oct 6, 2023

@jfbrennan & @EskelCz Thank you for exploring possible solutions. Using a python script however to convert material symbols from ttf to woff as part of build seems quite impractical in our pipeline.

I really can't grasp why the Google engineers overlooked the quite obvious flaws in distributing Material Symbols as one extremely large, unusable webfont.

Perhaps they could think of a system like the 'ol Icomoon app, where users can select icons & generate a webfont on the fly?

@EskelCz
Copy link

EskelCz commented Oct 8, 2023

@MarcoTroost I was originally looking for a UI solution too, but actually running a script is much more easily automated so I would say it's in fact a better way to go about it. You can also save the list of your icons with the script.

@tphinney @rsheeter Sorry to bother again but after some time of using the subset I noticed a slight flaw in the rendering/antialiasing of the filled variant. It creates minor gaps between the shapes and once you notice it you can't unsee it. :) It's not there with the original font from fonts.googleapis.com, I just checked. Any ideas what might be happening with precision in the export process?

It looks like this, when zoomed in:
icons

Tried TTF and it's the same thing.

EDIT: Checked glyph coordinates before and after the subsetting, they look the same. Since they are in integers, it also doesn't look like a precision issue.

Before: GlyphCoordinates([(0, 0),(429, 687),(530, 687),(957, 0),(851, 0),(476, 623),(100, 0),(156, 177),(200, 253),(754, 253),(799, 177)])
After: GlyphCoordinates([(0, 0),(429, 687),(530, 687),(957, 0),(851, 0),(476, 623),(100, 0),(156, 177),(200, 253),(754, 253),(799, 177)])

@tphinney
Copy link
Collaborator

tphinney commented Oct 8, 2023

Whether this problem happens or not depends on the font rendering engine in use; Apple’s is the biggest problem in this regard. (Note: I am not saying Apple is doing anything terribly wrong! This is a highly unusual glyph rendering situation. It isn’t like a dynamic Fill axis is something one sees every day in a font.)

The original font avoids this Apple-rendering complication by being coded to substitute a completely filled glyph variant when Fill is >99%. Crazy workaround, but effective. So, the subsetting needs to preserve this, too. Currently this is wired up to the 'rclt' OpenType layout feature.

(Not every glyph needs this, but most of them do. That is, all the ones that have an interior fill, similar to the two shown.)

@rsheeter
Copy link
Collaborator

The subsetter preserves this sort of feature through layout closure. I turned it off to prevent pulling in excess icons so that's a bug in subset-gf-icons.

@rsheeter
Copy link
Collaborator

@tphinney could I impose on you to review rsheeter/subset-gf-icons#3? - looks to me like now if I subset Material Symbols to "star" it additionally retains "star.fill"

@tphinney
Copy link
Collaborator

That sounds like the desired behavior! Rod, can you share an output font with me? You’ve got my email etc. :)

@behdad
Copy link

behdad commented Oct 10, 2023

@EskelCz
Copy link

EskelCz commented Oct 10, 2023

@tphinney Thanks for the explanation!

@tphinney could I impose on you to review rsheeter/subset-gf-icons#3? - looks to me like now if I subset Material Symbols to "star" it additionally retains "star.fill"

I works! Beautiful :)
image

You're a star :) Thanks a lot!

@tphinney
Copy link
Collaborator

Although I am happy to also inspect the font itself, I suspect the proof is in the visual output. If that is fixed, we can reasonably suspect that it is because the fully-filled alternate glyph is being called upon as intended. :)

@yisibl
Copy link

yisibl commented Oct 11, 2023

@rsheeter

About: rsheeter/subset-gf-icons#3

I'm trying to develop a web version and I'm having a little problem. hb-subset is not producing the same results as subset-gf-icons, can you share the full command for hb-subset or pyftsubset?

@yisibl
Copy link

yisibl commented Oct 11, 2023

I read the Python code for subset-gf-icons again, and I think I figured out that for the star icon, I should enter the command:

pyftsubset input.ttf --gids='4261,4995,5012,5013,5014' --output-file="ft.ttf" --verbose --glyph-names

There will be more 'atr', ' 'rtt', etc. in the output Glyph names than in subset-gf-icons, which will be permuted when pyftsubset is subsetted.

pyftsubset output

Closing glyph list over 'GSUB': 6 glyphs before
Glyph names: ['.notdef', 'a', 'r', 's', 'star', 't']
Glyph IDs:   [0, 4261, 4995, 5012, 5013, 5014]
Closed glyph list over 'GSUB': 12 glyphs after
Glyph names: ['.notdef', 'a', 'atr', 'r', 'rtt', 's', 'star', 'star.fill', 'stars', 'stars.fill', 'start', 't']
Glyph IDs:   [0, 209, 210, 2213, 2344, 2630, 4261, 4262, 4995, 5012, 5013, 5014]

subset-gf-icons output

Closing glyph list over 'glyf': 7 glyphs before
Glyph names: ['.notdef', 'a', 'r', 's', 'star', 'star.fill', 't']
Glyph IDs:   [0, 4261, 4262, 4995, 5012, 5013, 5014]
Closed glyph list over 'glyf': 7 glyphs after
Glyph names: ['.notdef', 'a', 'r', 's', 'star', 'star.fill', 't']
Glyph IDs:   [0, 4261, 4262, 4995, 5012, 5013, 5014]

@behdad @anthrotype
For hb-subset and pyftsubset do we have parameters to do the same as subset-gf-icons? Or does the subset generated by subset-gf-icons need to be consistent with them?

@rsheeter
Copy link
Collaborator

hb-subset is not producing the same results as subset-gf-icons, can you share the full command for hb-subset or pyftsubset?

There isn't an exact equivalent, you'd have to make a new entrypoint that does what subset-gf-icons does. I'm not sure off-hand if the closure function you need is readily accessible; if not that might require a change to HB.

@yisibl
Copy link

yisibl commented Oct 12, 2023

@rsheeter I found the right way to go and should have used the --no-layout-closure flag. See harfbuzz/harfbuzz#4192

pyftsubset input.ttf --output-file="ft.ttf" --gids="4261,4995,5012,5013,5014" --verbose --no-layout-closure
hb-subset input.ttf -o hb.ttf --gids='4261,4995,5012,5013,5014' --no-layout-closure

The difference between the current hb-subset and pyftsubset is that star.fill is not added.

@yisibl
Copy link

yisibl commented Oct 12, 2023

The original font avoids this Apple-rendering complication by being coded to substitute a completely filled glyph variant when Fill is >99%.

@tphinney Magic, do you mean that when the variable axis FILL = 99% , theoretically there shouldn't be a blank space in the middle of the icon?

Is it just that Apple's font rendering engine doesn't handle this extreme case very well?

My tests have found blanks in Chrome and Firefox on the Mac as well.

image

@tphinney
Copy link
Collaborator

Yes, that is about it, although technically the code sure looks like it says the alt glyph kicks in at GREATER THAN 0.99, so for example at 0.991 or higher fill, you should get a swap to the fully-filled version of the icon.

That is exactly the reason, that Apple’s rendering engine does not handle this well. But honestly the whole thing of having two filled glyph areas that are supposedly seamlessly abut each other… this is really NOT a normal font rendering expectation/situation at all. We are doing something pretty darn weird, and I really don’t blame Apple that their rendering does not deal with it well. That’s why I came up with the alternate glyph swap at >99% fill as a workaround. (At least, I think it was my idea. Most of the clever technical ideas on this were Vassil Kateliev’s, though!)

@yisibl
Copy link

yisibl commented Oct 12, 2023

@tphinney Thanks!

I found some rlig implementations, and I'm curious, which one does material-design-icons use?https://github.com/TypeNetwork/salon-variablefonts#feature-variations--alternate-layers

@tphinney
Copy link
Collaborator

Sorry I didn’t notice that last comment earlier.

We use 'rlig' two different ways —

  1. we are using it for normal ligation behaviors, no weirdness involved, unlike what is described there
  2. we also use it for what you would expect one to use 'rvrn' for, except that because the details of 'rvrn' are dumb, we can’t use it. Our particular build path uses UFO as described at https://github.com/TypeNetwork/salon-variablefonts#feature-variations--alternate-layers

@0smr
Copy link

0smr commented Jun 20, 2024

Probably the ligatures are getting lost because you aren’t including the glyphs that are needed to input the ligatures. Besides the desired symbols you would need a-z, space, underscore, and to include the 'rlig' feature.

@tphinney Hi, I just want to say thank you, and I'm wondering if this is mentioned anywhere. If it's not, I'm not sure if it's a good idea, but I'm hoping to write it down somewhere in case someone encounters this issue, since most people may not read the whole thread.
I spent three days figuring this out, and simply including a-z and underscore fixed the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

11 participants