Web-stories-wp: Font Picker: Final implementation w/ inline font previews

Created on 12 Feb 2020  路  18Comments  路  Source: google/web-stories-wp

As an editor I want to be able to preview fonts in the font picker

Acceptance Criteria

  1. Fonts are previewed in the font picker dropdown
  2. UX: If SVG can be used, use Roboto (16px) as a reference for height. (Can bump to 18 px if 16 is too small)

See #30

Design

Fonts P0 Pea Prometheus Enhancement

Most helpful comment

@pbakaus you don't need to _load them all_, you only need to load the visible ones, and load more as user scrolls and reveals more (that's the wat the catalog at fonts.google.com works) - the subsetted requests are very fast.

All 18 comments

@jauyong Let's make sure this gets triaged tomorrow.

/cc @pbakaus we need to triage it asap.

@spacedmonkey Tagging you here since you made a discovery on potential implementation for font preview at some point, in case you still have it documented somewhere.

Instead of SVGs I recommend we try out creating a single preview stylesheet with all the fonts. I put together this small prototype: https://jsbin.com/bibabex/edit?html,js,output.

What it does is:

  1. Fetches the font-face definition for the minimal text. E.g. using a URL like this: https://fonts.googleapis.com/css2?family=Architects+Daughter&display=swap&text=Architects+Daughter
  2. It extracts and fetches the actual font file (e.g. woff2).
  3. It constructs a "preview" font-face using the fetched font file as a data:base64: URL.

The benefits:

  • Relatively easy to implement
  • Pixel-perfect preview

The negatives:

  • The font declarations, even for minimal number of glyphs, could be bigger than a SVG path. I'd estimate it'd be about 1.5-2K per font family. Some compression is possible here.
  • Loading all fonts (dozens? hundreds?) into memory could kill the memory/CPU. We may need to be do infinite scroll in the font picker to delay font loading.

/cc @swissspidy @pbakaus

@dvoytenko I think we should try this!

One more optimization is to skip the base64 step and concat the binary woff/woff2 files into a single binary that we then split again based on some safe byte delimiter.

I'm fairly concerned about the memory overhead of having hundreds of fonts, albeit tiny, loaded into the editor at the same time, but we should try it out and see how it performs.

That's sounds cool! I will create a draft PR for this one, so we can see how it performs

We can even make the single stylesheet with inlined base64'd woff files in the github action run on a recurring basis. And if we want to be extra crazy, we can put it in CDN rather than keep it locally. Either way, it's a single stylesheet import done conditionally in the client once the user interacts with the font dropdown the first time. And we can display a loader there (or use straight-up code-splitting and use React Suspense).

Actually it would be cool, if gfonts simply provided such a stylesheet - a stylesheet with an @font-face for each font with only the glyphs for naming said font in there.

Another thing: Do we know what to do about illegible fonts? I suppose there are some dingbats in there - or at least some really obscure fonts where you can't read the letters easily?

@ndev1991, @barklund, @pbakaus

I just prototyped it in JS because that's what I know. But this is definitely the server-side-job task. We should just add it to our job that generates fonts.json and produce a "fonts-preview.css" at the same time.

Actually it would be cool, if gfonts simply provided such a stylesheet - a stylesheet with an @font-face for each font with only the glyphs for naming said font in there.

That'd be great, but I couldn't find anything like this. Doesn't mean it doesn't exist, so we should definitely search.

Another thing: Do we know what to do about illegible fonts? I suppose there are some dingbats in there - or at least some really obscure fonts where you can't read the letters easily?

We just should provide an alternative string for display. And we'll have to display it like <span style="normalFont">{fontName}</span><span style="previewFont">{previewText}</span>. Wheres, for most of fonts previewText == fontName and thus only one is needed.

One more optimization is to skip the base64 step and concat the binary woff/woff2 files into a single binary that we then split again based on some safe byte delimiter.

I thought about this some more and I think we should first try base64. We basically have a trade-off between network size and CPU in the main thread. If we put a prepared stylesheet with base64, it will prescanned and ready to go w/o the main thread intervention at all. And we still have tools available to us to optimize:

  1. base64 adds ~30%, and I expect that compression will shave off the same 30%.
  2. We can split "fonts-preview.css" into several files. E.g. "fonts-preview-{group}.css". They will each contain groups of font-faces and our "fonts.json" will include a field "previewGroup". When we know that we want to preview a font, we check that we've loaded an appropriate "font-preview-{group}.css".
  3. Eventually we could switch to per-font-face HTTP URL and provide a service worker that will intercept them. The service worker can internally compress/cache all individual preview font files anyway it likes, including binary chaining. This could still work as "per-group" case as described in (2) and thus we could have a progressive enhancement.

But I'd really just start with base64 + compression first. And see how that behaves.

However, what this points to, is that we need to change how we render our font previews. The most important optimization that's given us by browser: even if the font-face is declared, the font binary itself is not loaded until a content using it is display. What it means for us is that we definitely would want our font preview list to either render previews with display:none or not render them at all and leave blanks. When the user scrolls to a group of previews (or close to them) we would flip display to non-none or render them. I.e. this is a form of virtual scrolling. This will gives us the biggest improvement for CPU and memory. And if we later implement (2) and/or (3) it will even be even better.

You can use聽https://developers.google.com/fonts/docs/getting_started#optimizing_your_font_requests聽to get fonts subsetted to only the characters you need, and聽https://developers.google.com/fonts/docs/developer_api聽to get a list of the public library :)

You can use聽https://developers.google.com/fonts/docs/getting_started#optimizing_your_font_requests聽to get fonts subsetted to only the characters you need, and聽https://developers.google.com/fonts/docs/developer_api聽to get a list of the public library :)

thanks @davelab6! These are very helpful, and we're already using both :) Our concrete questions is more around how to render a preview of all fonts in a way that isn't super slow to load or crashes the browser...we have a few options:

  1. convert font name previews to actual images (to not have to load fonts)
  2. load all multi hundred subsetted fonts (might crash browser?? IDK)
  3. convert subsetted fonts to svgs and use those (similar to 1)

Any thoughts on that?

@pbakaus you don't need to _load them all_, you only need to load the visible ones, and load more as user scrolls and reveals more (that's the wat the catalog at fonts.google.com works) - the subsetted requests are very fast.

@tomasdev that's great to hear, thanks for the confirmation that that strategy works! OK, we'll focus on a virtual scroller then, which will dynamically load the current set of fonts when they're visible.

Ok. It all points to this:

  1. We have several good ways to get minimal menu fonts efficiently.
  2. As the first priority we need to focus on virtualizing the font picker to ensure we only ask for menu fonts as we need them.

Ok. It all points to this:

  1. We have several good ways to get minimal menu fonts efficiently.
  2. As the first priority we need to focus on virtualizing the font picker to ensure we only ask for menu fonts as we need them.

@dvoytenko when we meaning the menu fonts, visible fonts on dropdown? If we have 8 font families available once, we need to call maybe 16 fonts above 4 and bottom 4 additionally to make it smooth?

Yes. Apparently Google Fonts call this feature "menu fonts". You can load menu fonts like this:

https://fonts.googleapis.com/css?family=Roboto%7COpen+Sans&subset=menu

It's automatic and precached. So it should be pretty optimal. Though if we don't like the performance, we still have the same option of stitching it via base64.

There is features on fontpicker like popular fonts and suggested ones that used recently. Any ideas how we can fetch popular fonts? And how we can solve the suggested ones that used recently. Do we need to have them on db or just fonts that used for that story?

I looked into generating SVG previews of all the fonts. It wasn't something easy to do in the browser ( on the fly ) as I recall. Which left two options.

  1. Add a build step in the plugin build, that generates all the svgs and store ( cache ) them in the plugin. Then when a font is displayed, lazily load them in. This would mean generating, storing and keeping up to date 1k worth of svg files with the plugin.
  2. Lazily fetch fonts as they rendered in preview. Google fonts as the ability to load only characters a subset of characters. So only the character that appear in the name of the font. In my tests, only downloading a subset of characters, really made the font file much smaller, sub 2k. But there is always overhead of dns lookups and cache miss etc.

Those are the options I went down with font preview. If it is not clear, i think option 2 is better idea of the 2. Makes it easier to follow for custom font support later.

I would be very interested to know what the google docs team does.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dvoytenko picture dvoytenko  路  3Comments

injainja picture injainja  路  4Comments

obetomuniz picture obetomuniz  路  3Comments

jauyong picture jauyong  路  4Comments

Maverick283 picture Maverick283  路  3Comments