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

Compile variable features #635

Merged
merged 17 commits into from
Jan 11, 2024
Merged

Compile variable features #635

merged 17 commits into from
Jan 11, 2024

Conversation

simoncozens
Copy link
Contributor

@simoncozens simoncozens commented Jul 19, 2022

This is a work in progress. The idea is this: when we are compiling a variable font from designspace, most of the time the feature files are exactly the same; the only thing which differs is the "implicit" rules created from anchors and kerning. Currently we build each font independently, and this means we compile binary features N times (where N=number of masters), and then merge them at the end. When there are overflows, compiling binary features N times is slow. Instead, this PR holds off the compilation of GSUB/GPOS tables to the end, and uses "variable" feature writers which describe how the kerning and anchors vary across the designspace. i.e. instead of:

### MutatorMathTest-LightCondensed ###
lookup kern_ltr {
    lookupflag IgnoreMarks;
    pos A J -10;
} kern_ltr;

### MutatorMathTest-BoldCondensed ###
lookup kern_ltr {
    lookupflag IgnoreMarks;
    pos A J -20;
} kern_ltr;

and compiles the features twice, it produces

lookup kern_ltr {
    lookupflag IgnoreMarks;
    pos A J (wght=0:-10 wght=1000:-20);
} kern_ltr;

and compiles the features once.

It's a lot faster.

  • Curs feature writer is untested.
  • There's some problem with enumerated kerns.
  • I'm not sure I trust it yet.

Lib/ufo2ft/__init__.py Outdated Show resolved Hide resolved
@madig
Copy link
Collaborator

madig commented Aug 2, 2022

So to trigger this, a font project must make sure to have the same exact feature file text in every source, meaning that you have to use variable feature syntax if there are differences in some parts of the Designspace?

@@ -92,6 +95,10 @@ def __init__(self, ufo, ttFont=None, glyphSet=None, **kwargs):

glyphOrder = ttFont.getGlyphOrder()
if glyphSet is not None:
if set(glyphOrder) != set(glyphSet.keys()):
print("Glyph order incompatible")
Copy link
Collaborator

@madig madig Aug 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be a logger.error. Also, the glyph set is being tested, not the order.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just before an assert so it doesn't matter much if we print or do logger.error, an AssertionError should never trigger and is not something client can expect to recover from. It's just the error message for the hard crash, it could be moved to the assert optional string parameter, if you want.

@simoncozens
Copy link
Contributor Author

Yes, it doesn’t attempt to do a text/AST-based merge of the feature files. At least not at this stage - maybe that can come later. I’m fairly sure that this will cover a good number of font projects, and those that it doesn’t get cover will get the old style merge.

if compiler is not None:
# The result is cached in the compiler instance, so if another
# writer requests one it is not compiled again.
if hasattr(compiler, "_gsub"):
return compiler._gsub

glyphOrder = compiler.ttFont.getGlyphOrder()
fvar = compiler.ttFont.get("fvar")
if fvar:
feafile = copy.deepcopy(feafile)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the deepcopy?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed, why?

@madig
Copy link
Collaborator

madig commented Aug 3, 2022

In one of our fonts, this seems to drop some kerning pairs but not others (can't check if this is just the kern splitting because the font doesn't compile without this PR) 🤔 weird, drilling some more.

Also, the Rules writer is not hooked up and justr dropping it into the default feature writer list doesn't seem to do anything.

self._conditionsets.append(conditionset)
else:
cs_name = "ConditionSet%i" % self._conditionsets.index(conditionset)
# XXX
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

@madig
Copy link
Collaborator

madig commented Aug 3, 2022

So, I get rules to work when I graft the GSUB <FeatureVariations> from a previous ufo2ft and adapt the lookup numbers. This means that the rules writer should do whatever varLib is doing I guess.

Notes:

  1. The writer outputs values in design space. There seems to be no official spec for this so I'll need to check feaLib
  2. The writer outputs integer locations, where Designspaces can have floating point ones.

@simoncozens
Copy link
Contributor Author

I disabled the rules writer because varLib also handles designspace rules during the merge and I was ending up with them being emitted twice.

@madig
Copy link
Collaborator

madig commented Aug 3, 2022

Weird. For my test font, no GSUB variations are emitted without the rules writer.

@madig
Copy link
Collaborator

madig commented Aug 3, 2022

Now we do approximately what varLib is doing with rules, though compared to a reference, the numbers and lookups are off in some places. Maybe I use overlayFeatureVariations wrong. At least with inspection by eye, stuff seems to work as expected.

There still remains a difference in kern behavior I need to investigate.

@madig
Copy link
Collaborator

madig commented Aug 3, 2022

Also todo: The test should include disjoint kerning pairs for different scripts, to ensure we handle disjoint kerning in different masters correctly (i.e. Armenian only in one, Greek only in the other).

@madig
Copy link
Collaborator

madig commented Aug 8, 2022

I disabled the rules writer because varLib also handles designspace rules during the merge and I was ending up with them being emitted twice.

Oh, I know what's going on. My test sources happen to trigger the compile-features-once-but-later code path, so varLib adds feature variations and then we overwrite them when running the feature writers. This also seems to affect kerning.

@madig
Copy link
Collaborator

madig commented Aug 8, 2022

Next, my test source has e.g. an r vs d class-based kern that gets lost when compiling features later for VFs. 🤔

@simoncozens
Copy link
Contributor Author

You're using non-public test sources, which makes it difficult for other people to repro... I'll see what I can find.

@simoncozens
Copy link
Contributor Author

Note to self: _featuresCompatible will return false if there are any ligature caret positions, which glyphsLib writes into the feature file (with per-master-file variations). Need to think how to handle these.

@madig
Copy link
Collaborator

madig commented Aug 17, 2022

Woops, yeah. I'll be back at work next week and can see if I can make a reproducer. For now, maybe just compile the font with identical features with 2.28.0 and then with this branch and compare kerning.

I thought I turned ligature writing off? Should be in https://github.com/googlefonts/glyphsLib/releases/tag/v6.0.7

@anthrotype
Copy link
Member

yeah, the GdefFeatureWriter in ufo2ft should handle both the GlyphClassDefs (using public.openTypeCategories in the ufo lib) and the LigatureCarets (from the glyph anchors named "caret_", "vcaret_")

@madig
Copy link
Collaborator

madig commented Aug 23, 2022

So, I tried to come up with a minimal reproducer, but so far, no luck. What did seem to work was to make a union of all kerning values over all sources, putting in missing ones as zero-kerns. The specific pair I was looking at was actually "missing" such a zero-kern in one corner of the design space. Maybe that's what's missing?

@madig
Copy link
Collaborator

madig commented Aug 23, 2022

varLib allows "sparse kerning", i.e. if Light and bold have a value but Regular doesn't, the value at Regular is interpolated rather than set to zero. So, filling in zero for missing kern pairs seems like the wrong thing to do.

@madig
Copy link
Collaborator

madig commented Aug 23, 2022

In my testing with a proprietary font, it seems the kerning is there, but it is not used, as if the shaper was stopping earlier. Subsetting the font to contain just the two glyphs I want to look at makes the kern work as expected. Will try to reproduce with an OSS font.

@madig
Copy link
Collaborator

madig commented Aug 23, 2022

Found an open font that repros: Roboto Serif with "py".

@simoncozens
Copy link
Contributor Author

Here is a very minimal reproducer:
RobotoSerif-Mini.glyphs.gz

@madig
Copy link
Collaborator

madig commented Aug 24, 2022

(The last commit should be using sets instead of lists for members in side(1|2)Groups but I don't want to anger the determinism gods)

@simoncozens
Copy link
Contributor Author

simoncozens commented Aug 25, 2022

With your patch and fonttools/fonttools#2776, I think this is good now.

It has a slight change of behaviour compared to the past in respect of "empty" kern pairs, but I think this is for the better. If an intermediate master previously did not define a kern value for a pair which had kerning in other masters, we would assume that value was zero. For example, if you had Regular=-100, Bold=-200 and no kern defined in the SemiBold, you would get a kerning value of zero. Presumably designers would have realised that they didn't mean that, and put an explicit kern in there.

But now we can do better than that: if there is no explicit kern for an intermediate master, it will be interpolated. This should make people's kerning processes a lot faster...

@simoncozens
Copy link
Contributor Author

Ship it! :-)

* Using this version of otRound allows us to reuse a lot more code
* A couple of variable scalar / designspace utility functions
* Allow compileGSUB to know about fvar tables
This is the same as a FeatureCompiler but it knows its designspace and
it also loads the RulesFeatureWriter if there are any designspace rules
Add test for different feature writers

Fix up test expectations

This should now test the variable features
anthrotype added a commit to googlefonts/glyphsLib that referenced this pull request Jan 11, 2024
in googlefonts/ufo2ft#635 that symbol is no longer exported, better to import from its actual location
from ufo2ft.constants import INDIC_SCRIPTS, USE_SCRIPTS
from ufo2ft.featureWriters import BaseFeatureWriter, ast
from ufo2ft.util import (
classifyGlyphs,
quantize,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@simoncozens I had to change glyphsLib's markFeatureWriter to import quantize from ufo2ft.util otherwise we get ImportError with this PR:
googlefonts/glyphsLib@ae08b26

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh I had forgotten that you already have a PR for that, sorry: googlefonts/glyphsLib#957

ok so we shall merge that one once we release this

this can be useful to debugging, comparing build time of new vs old build pipeline, or if one wants to ensure no binary changes occur when updating the compiler
@anthrotype
Copy link
Member

anthrotype commented Jan 11, 2024

@simoncozens hope you don't mind if I added a variableFeatures=True compile option that we can use from fontmake to force disable this (52ca46a), just in case

See googlefonts/fontmake#1064

@anthrotype anthrotype merged commit 657cb64 into main Jan 11, 2024
9 checks passed
@anthrotype anthrotype deleted the compile-variable-features branch January 11, 2024 15:13
@anthrotype anthrotype restored the compile-variable-features branch January 11, 2024 15:13
@khaledhosny khaledhosny deleted the compile-variable-features branch January 13, 2024 18:37
anthrotype added a commit to googlefonts/fontmake that referenced this pull request Jan 18, 2024
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.

5 participants