GoogleForCreators / web-stories-wp

Web Stories for WordPress
https://wp.stories.google
Apache License 2.0
759 stars 179 forks source link

Font Picker: Final implementation w/ inline font previews #309

Closed jauyong closed 4 years ago

jauyong commented 4 years ago

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

dvoytenko commented 4 years ago

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

dvoytenko commented 4 years ago

/cc @pbakaus we need to triage it asap.

miina commented 4 years ago

@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.

dvoytenko commented 4 years ago

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:

The negatives:

/cc @swissspidy @pbakaus

pbakaus commented 4 years ago

@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.

ndev1991 commented 4 years ago

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

barklund commented 4 years ago

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?

dvoytenko commented 4 years ago

@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.

davelab6 commented 4 years ago

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 :)

pbakaus commented 4 years ago

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?

tomasdev commented 4 years ago

@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.

pbakaus commented 4 years ago

@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.

dvoytenko commented 4 years ago

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.
ndev1991 commented 4 years ago

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?

dvoytenko commented 4 years ago

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.

ndev1991 commented 4 years ago

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?

spacedmonkey commented 4 years ago

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.

dvoytenko commented 4 years ago

Google Docs and Google Fonts essentially use https://fonts.googleapis.com/css?family=Roboto%7COpen+Sans&subset=menu.