googlefonts/oxidize

Font editor perspectives

justvanrossum opened this issue ยท 11 comments

I just spoke to @chrissimpkins, and shared some loose thoughts about "faster font compilation" in the context of a font editor, and he suggested that I post here, as you are talking about use cases.

(I follow this repo only superficially, as the details of Rust table implementation strategies are beyond my expertise.)

Premise: A font editor would benefit from showing the user correctly shaped live previews.

FontGoggles attempts to do this as quickly as possible when previewing UFOs, using the following strategy:

  • It scrapes all unicode and anchor information from the .glif files as fast as is possible with Python
  • That information and the kerning and .fea data available is presented to ufo2ft to write the final .fea data
  • feaLib is used to compile that into GSUB/GPOS/GDEF
  • A "sparse" font (a TTF with the bare minimum of tables, ie. no outlines) is presented to HarfBuzz, which can then shape the user-entered string correctly
  • It watches for changes in individual .glif files in the UFO, and updates its internal cmap and anchor data incrementally, so it won't have to re-scrape ALL unicode + anchor data when a single glyph changes.

While this is a lot faster than compiling a full font with fontmake, there are still two bottlenecks:

  • The scraping of unicode + anchor data from .glif files when opening a UFO
  • The compilation of .fea into binary tables when opening a UFO, and when updating according to changes in the UFO

So: specialized (Rust-based?) tools for those two tasks would be incredibly useful.

For a font editor with in-memory data, things will look a little different:

  • cmap and anchor data can be made quickly available
  • some GSUB features may be generated on the fly, using glyph name conventions
  • kerning data will be live

With the currently available tools, we have to go via .fea to get data that HarfBuzz can use. This gives us a double bottle-neck:

  • Writing .fea data from live data
  • Compiling binary tables from .fea

My final thought here is that a superfast shaper isn't a requirement, but a shaper that can more efficiently work from live data (for example kerning and anchors) is what's really needed.

Using Rust-based tools will surely make many things much faster, but to me there is possibly more to gain with a clever (I know I'm hand-waving) system that can shape more directly from font/glyph source data, looking only at the data that is relevant for the string being displayed, without having to go via TWO intermediate formats (.fea and OTL tables).

The other thing that could be beneficial for a font editor is incremental compilation to a full font: when I edit a single glyph of a single master of a 50,000-glyph variable CJK font, it shouldn't require recompilation of the whole thing.

cmyr commented

So this is basically the original motivation that set me down on this path, and remains one of the major goals. I have other dreams (like a step-through debugger for shaping, where it can show each rule that is applied, and its source) but I am definitely keeping this case in mind.

Incremental compilation is also definitely something nice to have; ideally that can be a layer on top of the general compilation tools.

If providing fast interactive shaping is a goal, then good Rust โŸท Other Languages interop should be part of the goals, too.

Rust is awesome for specific low level fastness, but not so much for high level orchestrating of complex processes, where people with lower CompSci skills still want to (and deserve to be able to) customize.

madig commented

(@cmyr regarding a step-by-step non-interactive shaper, see http://corvelsoftware.co.uk/crowbar/)

One other topic when it comes to font editors is efficient interoperability, since they might be written in different languages. e.g. do I need to write a file to disk to feed to the compiler, or can I provide the input data over pipes, or use a C API?

do I need to write a file to disk to feed to the compiler

At time of writing, yes.

can I provide the input data over pipes, or use a C API?

Not yet, but I don't see why not. Wrt pipes, what would you expect to pipe? - a standalone .glyphs file makes sense but UFO+designspace doesn't seem to lend itself as well to piping. Or perhaps you had some other input in mind?

Or file an issue against https://github.com/googlefonts/fontmake-rs explaining what you'd like to do?

adding my 2 cents:

There are two use cases:

  • Plain exporting the full font to get a finished .otf/ttf.
  • partially building some structures for different purposes. e.g. text preview, validating fea code.

The two most expensive operations I encounter in Glyphs:

  • writing .fea code (manually written and automatically enraged (kern, mark โ€ฆ) and parsing it in again.
  • building and compacting GPOS/GSUB tables

The current state of external compilers (like fontc or fontmake) adds the parsing and translation of the source file.

One solution to cut down most of the above could be to expose the internal representation (by an API/callbacks) that the editor app could build up data (instead of the source parser). So the editor tells the compiler: "Here are some outlines", "Here is a list of kerning pairs", "Here is another kerning pair", "Here is some fea-code" โ€ฆ

That could be done by a direct c style API or by pipes (potentially slower? More error prone?)

We are in the works for feaKit: it will get an API that lets me give it some fea code (a lookup or a feature) one at a time or, more importantly lets me write the GPOS lookups directly into the internal representation. That has two big advantages. It is much faster and, if done right lets me incrementally update things (e.g. a changed kerning pair).

And one important benefit: it can give much more precise errors. Because the context were that data came from is much clearer.

One solution to cut down most of the above could be to expose the internal representation

An in memory translation to what fontc reads .glyphs files into (here) should be pretty quick, no need to touch disk or parse a source file.

feaKit [...] lets me write the GPOS lookups directly into the internal representation

Ha, us too. fea-rs is learning this trick so fontc can avoid generating strings to parse.

An in memory translation to what fontc reads .glyphs files into (here)

IIUC, Georg was referring to the fontc IR (fontir/src/ir.rs) proper, not the glyph-reader/font.rs that we parse the plist into. So basically the font editor would do the job of glyphs2fontir themselves, skipping not just the plist parsing but the glyphs=>IR itself and somehow produce IR directly to feed fontc

IIUC, Georg was referring to the fontc IR (fontir/src/ir.rs) proper, not the glyph-reader/font.rs that we parse the plist into

That was my understanding also but I think that bypasses things we do want to happen.

We should be able to find a level where it makes sense to hand over the data.