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

use instantiator when decomposing "sparse" composite glyphs #826

Merged
merged 28 commits into from
Mar 25, 2024

Conversation

anthrotype
Copy link
Member

googlefonts/glyphsLib#954

So.. In Glyphs.app there's a nifty feature which I'll call "sparse" composite glyph for lack of better name, whereby you can have a composite glyph that defines additional or fewer masters (sources, layers, whatever you call it) than some of its component glyphs.

Eg. imagine a font with 1 weight axis and 3 masters, Regular (default), Medium and Bold; the "Agrave" composite glyph references "A" and "gravecomb" as components; "A" and "gravecomb" are only defined for Regular and Bold, but not Medium; in the Medium master the "Agrave" glyph needed some adjustments to the component offsets (or maybe the advance width).
Or, another example, you could have the "Agrave" (composite) and "gravecomb" (component) only be defined for Regular and Bold masters, while "A" (component) is also defined in the Medium master (e.g. contours needed some adjustments), so you have a composite glyph "Agrave" defined in 2 masters using a component defined in 3 masters.

This usually works fine for TrueType fonts, however problems arise when you need to decompose the composite glyph (for CFF fonts, or for TTF when contours and components are mixed, or components have different 2x2 transforms across masters). If you decompose these sparse composite glyphs without taking into account the fact that they have incomplete or additional master definitions, you either end up with a decomposed simple glyph that doesn't look exactly as the composite did, or the compilation fails because some components appear to be missing in some masters...

In order to fix this, it is necessary to interpolate the glyphs for the missing locations while or just before decomposing the sparse composite glyph, which is why I added the new instantiator module originally in fontmake (#825).

More precisely, in the first case (composite with more masters than its components), we need to interpolate the component glyphs on-the-fly at the missing locations while decomposing the composite glyph.
For the second case (composite with fewer masters than its component[s]), we first traverse all the components across all masters and collect the set of locations in which they may be defined; then we interpolate our composite glyph at the new locations that it doesn't already define, and finally we proceed with decomposing it.

Right now, fontmake requires the font developer to manually ensure that all the composite glyphs and all their components are "aligned", i.e. are exactly defined for the same set of masters.

I hope you'll find this useful, as this is taking me more than I had anticipated. Initially I started with fixing it in glyphsLib, then of course I got tangled up in its complexity and decided I would rather fix in ufo2ft so this would work regardless of input source format.

Have a nice weekend!

@anthrotype anthrotype force-pushed the interpolatable-filters branch from 388cb9f to 7fbaedc Compare March 18, 2024 11:39
@anthrotype anthrotype force-pushed the interpolatable-filters branch 4 times, most recently from b506ee3 to 6fec373 Compare March 19, 2024 14:14
    These filters take multiple glyphSets as input and zip through the glyphs with the same name, processing them as a group instead of one glyph at a time.
this will be used in filters that decompose composite glyphs, to make sure the latter appears the same after decomposition in situations where the composite glyph is defined at fewer source locations than some of its component glyphs.
@anthrotype anthrotype force-pushed the interpolatable-filters branch 2 times, most recently from 38ad741 to d499a31 Compare March 20, 2024 13:36
also note the preProcess_test.py: previously if a glyph was marked non-export (e.g. _o.numero), we were decomposing it to contours as a whole alonside all of its nested components (e.g. 'o'), but that's unnecessary; now we only strictly decompose the components whose base glyph is marked as non-export, without recursing into their nested components, as there may still be a chance that after decomposing only those, the parent glyph can remain as a pure composite.
… in addition to bool

this will simplify the OTFCompiler a bit
…ault TTF masters

For TrueType only, when the glyphSet for a non default master contains composite glyphs that point to missing component base glyphs, we create and add empty glyphs so that the composite glyph is not dropped from the master TTF as invalid (i.e. pointing to nowhere); varLib will ignore those empty glyphs when building gvar variations, so the additional glyphs will not add extra masters. The glyph metrics (HVAR, VVAR) similarly will not have additional masters for the empty glyphs (we use a sentinel value understood by varLib 'skip me').
The composite glyph, on the other hand, will still be considered when building variations.

One may use this technique to adjust the placement of components in a composite glyph, and/or its advance width/height, but only for some intermediate locations without requiring to define intermediate masters for the component glyphs themselves.
Factored out some shared code from TTFInterpolatablePreProcessor into a new BaseInterpolatablePreProcessor, and defined a new OTFInterpolatablePreProcessor.

The interpolatable pre-processors take an optional Instantiator instance, and pass that on to interpolatable filters so they can generate glyph instances on the fly.
They also attempt to convert a list of non-interpolatable filters to an equivalent interpolatable filter (e.g. DecomposeTransformedComponentsFilters => DecomposeTransformedComponentsIFilter) so that client code (fontmake) that sets up filters doesn't need to change.
Thanks to our new shiny interpolatable DecomposeComponentsIFilter, our test variable CFF2 font looks as it should have been from day one!
The test font contained a composite glyph 'edotabove' which used 'e' as one of its component, and the latter's glyph in turn defined one extra master at the middle (in a sparse layer); since in CFF fonts we have to decompose all composites, before we would simply ignore that 'e' had this intermediate master and the decomposed 'edotabove' would have one fewer set of deltas and thus look incorrectly and unlike 'e'. Now it is decomposed correctly, with the intermediate master 'bubbling up' from the 'e' component to the 'edotabove' composite glyph.
…sing

At some point we decided to skip missing components when decomposing composites and just issue a logging warning. But that was not a good idea, as it hides other font bugs (or even code bugs), and it is no longer needed because we now support sparse composite glyphs. We may reintroduce the skip behavior as an option if need be.
and use design coordinates for source locations as well as interpolated layers, to aid debugging
@anthrotype anthrotype force-pushed the interpolatable-filters branch from d499a31 to b2dbba4 Compare March 20, 2024 17:39
this will exercise the interpolatable FlattenComponentsIFilter
@anthrotype anthrotype force-pushed the interpolatable-filters branch from b2dbba4 to c921318 Compare March 20, 2024 17:39
@anthrotype anthrotype merged commit 4d9aca9 into main Mar 25, 2024
9 checks passed
@anthrotype anthrotype deleted the interpolatable-filters branch March 25, 2024 17:01
anthrotype added a commit to googlefonts/glyphsLib that referenced this pull request Mar 25, 2024
ufo2ft can now interpolate components or composite glyphs as needed at build time while decomposing composites, as well as add empty component placeholders when keeping the glyphs as composites (as of googlefonts/ufo2ft#826).
Therefore glyphsLib does not need any more to add intermediate layers to the component base glyphs when these are defined at fewer master locations than the composite glyphs they are referenced from.
schriftgestalt pushed a commit to googlefonts/glyphsLib that referenced this pull request Oct 23, 2024
ufo2ft can now interpolate components or composite glyphs as needed at build time while decomposing composites, as well as add empty component placeholders when keeping the glyphs as composites (as of googlefonts/ufo2ft#826).
Therefore glyphsLib does not need any more to add intermediate layers to the component base glyphs when these are defined at fewer master locations than the composite glyphs they are referenced from.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant