-
Notifications
You must be signed in to change notification settings - Fork 449
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
[varLib] Add Feature Variations #1240
Conversation
Thank you Just!
the way currently varLib does the coordinate normalization is with a private class
yes, a I'll complete my review soon, and also have a look at the test you provided. |
@justvanrossum I just added the avar-modified normalization with 081ca13, PTAL. |
I'm not sure if going through avar is actually desirable here. Because avar mapping tends to be done later, to satisfy other requirements (I saw several examples during TYPO Labs), yet the point at which a glyph would switch from one form to the next should not change when such avar modifications are made. In the light of that it may be desirable to use normalized coordinates all the way. |
Curious about @LettError's opinion about this. |
I see this API at the level of a designspace document. The master locations there are also in user coordinates, that will be normalized without going through avar. |
I believe this is not about being desirable or not. FeatureVariation tables contain normalized coordinates, and by that I conclude that the avar should be used (when present) for converting from user-scale coordinates (i.e. axes location in fvar) to normalized coordinates (-1 to 1). In https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#featurevariations-table
Also, in Chapter "Coordinate Scales and Normalization", https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview, there is a mention of "if avar table is present..." (step 4), followed by
Where did you get the impression that "avar mapping tends to be done later, to satisfy other requirements"? |
Actually, the master (and instances) locations in the designspace currently are not in user coordinates ("user" coordinates being those encoded in the fvar, with which the user interacts with). They are in internal "design" coordinates. the terminology is very confusing, and my head is spinning right now... 😕 |
oh, perhaps you mean the min/max values in the FeatureVariation tables should be specified using the internal design-space coordinates -- just like the master locations are, e.g. for weight, it could be the thickness of the stem, etc. --, rather than the user coordinates (e.g. 1-1000 scale for wght registered axis)? @behdad am I correct? |
Yes, the terminology is confusing and we should clarify them. Here's what I think we're looking at:
I'm assuming master and instance locations will predate the avar map. This means that substitution glyphs will be drawn before the avar map can be determined, which would make it necessary to store those coordinates in |
Thanks Erik for clarifying the terms. So the way Just originally wrote this, the addFeatureVariations function was accepting the "raw" or designspace's internal coordinates, whereas I understood it to use "user" coordinates, thus requiring to perform the avar mapping when converting them to "normalized" coordinates. @justvanrossum I'm going to revert my last commit |
This reverts commit 081ca13. #1240 (comment)
this is what tripped the whole misunderstanding
The way the designspace spec defines the "rule" element seems to fit very nicely with the way this new For example, given this rules definition, with locations expressed in raw internal coordinates (which may or may not be different from the user coordinates when axis maps are present): <rules>
<rule name="named.rule.1">
<condition minimum="155" maximum="190" name="weight" />
<condition minimum="50" maximum="100" name="width" />
<sub name="dollar" byname="dollar.alt"/>
</rule>
</rules> the compiler (varLib.build) would call the function like this: addFeatureVariations(
ttFont,
[ # list of rules
( # each rule is a tuple containing a Region (list) and a Substitution (dict)
# regions contain a list of Spaces, dict mapping axis tags to min/max values
[{"wght": (155, 190)}, # equivalent to "condition" element in designspace document
{"wdth": (50, 100)}],
{"dollar": "dollar.alt"} # same as the "sub" element inside the "rule" element
)
]
) I think we can implement it like that for now, since we already have it the designspace spec, and deal with the further complications (extend FEA syntax) later. |
Sorry for messing up the terminology. I indeed meant raw design coordinates. |
That would be fantastic. Perhaps my little test case can just become a designspace-based test. |
and rename getPermutations to iterAllCombinations. It's not really permutations we are after here, but more combinations of indexes sorted by decreasing length, from more specific to less
@justvanrossum one thing I still don't understand is why do you have to "explode" all the combinations of rules. Can't we simply assume that the user writes such list of rules in the logical order in which they are meant to be applied, and we add one FeatureVariationRecord for each rule? |
in an inline comment you explain
but I still don't get it. Well, it's ok -- I'll sleep over it. |
Suppose a rule
For the area in which rules But then in n-dimensional space. |
Makes sense now, thanks Erik! |
Yeah, it took me quite a while myself to see that the format is really quite dumb, and that we need all this to have a decent level of expressiveness. Erik's explanation is spot on. You need three rules for that example case. |
Note that feature variations can potentially be used with features other than rvrn. Also note that in Erik's example, you don't necessarily need three lookups. The effect can be achieved in two lookups: one that does substitution for a, and one that does substitution for b. You would still need an extra feature table, though: I don't know of a less-dumb way. |
Hm yes, but I'm not sure how to specify conditions in the context of a feature that has been defined in a fea file. Especially since there is no .fea syntax for feature variations yet. At the same time, to achieve such effects, I think they could always be built on top of |
The implementation actually does that as far as I can see. It's indeed the feature records that need "exploding". Replace the word "lookup" in Erik's comment with "feature record" and it matches the actual situation I think. |
@justvanrossum I edited the example. |
(Btw. where I wrote "dumb" I meant "low level". It was certainly not meant as a judgment of the subtable format.) |
After writing #1243, I realized that "raw design coordinates" is a coordinate system we don't have access to here, as it's warped by the I think the input of |
Why?... 🤯 |
FYI: if this gets in, with some support for it in the designspace file (though, as noted above, that may need to change), it'd satisfy #1176. |
oh... Is that because the addFeatureVariations function only takes the variable TTFont, thus does not have access to the designspace document itself where the axis map elements are defined (mapping user to raw, in Erik's terminology)? That's also probably why, e.g., |
Yeah, I wrote I think I will simply remove the normalization step from featureVars.py. |
…ven in normalized coordinates
Thinking about this once more:
While this is true, it is impossible to combine such behavior with rvrn-based conditional substitutions, due to the first-match-only behavior of the FeatureVariations subtable. The exploding trickery that featureVars.py uses to implement more complex behavior can only work for one feature. We can manipulate the lookups for that one feature in arbitrary ways, but we can't do that for multiple features simultaneously. So I'm really not sure how feasible non-rvrn feature variations will be as a general principle. |
I'm still trying to wrap my head around why this won't work (jetlag isn't helping). Do you have an example? A FeatureTableSubstitution table can list alternate feature records for multiple features, so a given condition can modify rvrn at the same time as other features. If you have ConditionSets with identical conditions, they could be collapsed. If you have one ConditionSet that matches a superset or overlapping set of another, then it can be decomposed into non-overlapping conditions. It would seem that any condition is computable, though it may take some pre-processing to save a more complex set of conditions in the file than specified in the parameters to addFeatureVariations(). |
Correction: there is no replacement of feature records. Feature records exist in the FeatureList table, and associate a feature tag with a default feature table. The FeatureTableSubstitution table provides an alternate feature table to replace the default table when certain conditions are matched. But Rob's point is correct: the FeatureTableSubstitution table can substitute the default feature tables for several features with alternate feature tables. |
@PeterCon and @robmck-ms: sorry, you're absolutely right, I mixed things up. There can of course be multiple features affected by one condition, as the FeatureTableSubstitutionRecord field is an array. |
Following up from #1243, I think what's missing for merging this PR to master is:
Also, note that addFeatureVariations expects normalized coordinates whereas the designspace rule elements' conditions use non-normalized, internal designspace coordinates, so the caller needs to take responsibility of doing the normalization. |
Can we merge and mark it absolutely experimental / subject-to-change/move? I can then try hooking it up to varLib and make any changes needed as I go. Thanks. |
@behdad sgtm, thank you! |
The three type of coordinates make sense. The type cycle is "designer -> machine -> user", so if we replace "designer" with "raw" and "machine" with "normalized", we're there. I think there had been a lot of confusion about this because the OT spec only talks about the two latter steps while font tool developers also need to take the first one into account. |
This module contains a function that adds conditional substitutions to a variable font. It's pretty much an afterburner on an otherwise ready variable font.
Brief example:
Currently, the minimum and maximum values for a region are specified in
userraw design coordinates, and are therefore normalized without going throughavar
. That makes most sense to me from a designers perspective. I can imagine to add a flag that would allow those values to be specified in normalized coordinates.I have a test script based on existing test data within the fonttools test suite, that needs to be rewritten into proper tests. I may need help with that.
addFeatureVariationsTest.py.zip