-
Notifications
You must be signed in to change notification settings - Fork 168
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
[fea syntax] OpenType Variation support? #153
Comments
I've been thinking about this, and it is not yet obvious to me which way to go. At the moment, I lean towards the approach used by fontmake: use current tools to build a full OpenType font, then run a script to merge the master designs into a variable font. This appeals to me because a high level design idea behind the feature file syntax is to not have to specify data which can be derived from the sources, and much of the new variable font structures can be derived from the master designs. Also, the way a variable font is expressed lends itself to this approach: basically, as a single normal OpenType font, with extra tables containing the differences between that and the other master designs ( well, not exactly, but close enough). With current workflows, I don't think we need an extension of feature format for most of the variable font data.. This is actually separate from the variable font issue, but I have noticed that all the major font development tools now have readable source data formats, which would allow fontmake to generate the kern data and design space data directly from the sources. The AFDKO doesn't do this because when it was developed, source font data was proprietary, and the only place human-readable and editable kern data was stored was the feature files. This is no longer the case. I've discussed this with Miguel Sousa and Frank Grießhammer, and to us it looks like a good workflow would be to require the default font to have a fully produced OpenType font, with all the GSUB and non-kern-GPOS feature, and then draw on the master design sources to extract the kern data and all the other delta data. Another advantage of this approach is that it avoids having to re-work the feature files whenever you make blend design space changes. Just in working for the last few weeks with some CFF2 test fonts, I have found myself often changing which master is the default, and playing the design space position of the masters. If any of the metrics had been encoded in in the feature files, this would have been a lot of work. Feature file extensions are then needed only data which is not in the masters. The STAT table will need to be supported, as it currently does not have any external expression. All that said, my experience is that we will still need to come up with feature file syntax for almost everything in order to allow overriding source-derived values. For kern and other metric data, I still like the old MM syntax, where individual values were simply replaced by the list of equivalent values form each master design font, in angle brackets:
However, I think I like Adams' suggestion even more, as it not only lends itself to easy adaption of current workflow, but also avoids the problem having to regenerate all the pos statements whenever the designer makes changes to the master positions in design space; only the master references need to be edited. Let's have a lot more suggestions about alternatives! |
Read, I actually do like the fontmake approach, and I think having "per-master" positioning syntax is sensible. However, we still have the problem of FeatureVariations (rvrn feature and any other feature which changes its implementation at some variation space section): I think we'll need some way to specify these conditions. The "script" and "language" keywords have been a kind of conditions in both FEA and binary OTL forever. With FeatureVariations, we get a new kind of conditions essentially. The ConditionTableFormat1 needs a variation axis id and two values: min and max. Multiple such conditions are expressed in a ConditionSet collectively (which allows to define conditions that kick in only in certain combinations of the axes segments). So at least for this, we'd need some way to specify these conditions. I think in FEA, the axis should be expressed as a tag rather than index. So we need an idea how to express the condition. For example if my general variation source is 2D and has axes "wdth" and "wght" going from.-1 to 1, I might want to apply a different feature set if wdth >= 0.5 and wght >= 0.5. Even though the implementation talks about exchanging the whole FeatureSet, in practice, it'll be more like feature sets for languagesystems "latn dflt", "cyrl dflt" and "cyrl BGR" which would normally have the same dozen features but "cyrl BGR" would also have "locl". FEA has a decent mechanism to control the languagesystem conditions. You set up the general rule using "languagesysten" and then within feature definitions, you can fine-tune it via "script" and "language". I think something like:
The example above would define at the beginning the "varcondsys" entries -- a list of all possible ConditionSets. Any feature definition that does not explicitly use the "varcond" keyword within it would get registered in the FeatureSets for all the ConditionSets. But in case of liga, we would have two behaviors: "varcond dflt" would define the implementation for the default conditions (two lookups are applied) while "varcond wght 0.5 1 wdth 0.5 1;" would define the implementation if wght >= 0.5 and wdth >= 0.5; Ideally, the coordinates in the conditions should be expressed in user coordinate space, not normalized coordinate space, because "regular" font developers would not know much about normalized coordinate space. But that would require much more knowledge of the FEAV compiler (it would need to process "avar" etc.). So I'd settle for normalized space. Overall, the above mechanism is rather similar to how LangSys definitions work in FEA, so it should be possible to reuse some logic. |
Given that it is more common than not these days to separate GSUB and GPOS, perhaps it might be sensible to extend FEA by these keywords:
This, again, would simplify working with includes, especially if the optional conditions section could allow variation master definitions, e.g.
Or perhaps
Inside the new "table GSUB" and "table GPOS" blocks would be the traditional FEA contents. |
With the "table GSUB" and "table GPOS" proposal comes a new idea, for makeotf specifically: a "keep layout tables" option. If my font already has a GSUB table compiled and I only want to compile a GPOS and my FEA only has GPOS stuff, I should be able to do it without destroying the existing GSUB. In other words, I'd love to be able to run makeotf in multiple passes. |
@twardoch the conditions should be defined in the |
Sounds more like a job for fontTools and its feaLib. |
@miguelsousa AFAIK, the Superpolator rules are very simplistic, they only allow for simple substitutions. FeatureVariations can define conditional ligatures, contextual substitutions, positioning etc. Of course it would make sense for a tool to convert from .designspace rules to some FEA code that expresses FeatureVariations. Also: I'm talking here about the FEA syntax. Many workflows use the FEA syntax but don't rely on .designspace. I think building the OTL tables should be possible as a separate step from building the actual font file. The FEA syntax has been implemented by MakeOTF, feaLib and FontForge. I agree the syntax discussion is a bit separate from the tool, but only a bit. In the past, FEA and MakeOTF could be used to completely define sources for GSUB, GPOS, GDEF and BASE. There were higher-level expressions for some of the data, such as group kerning within UFO, but overall, FEA has proven itself to be very popular. |
I've mentioned several ideas in this thread, and I think all those that do not affect the logic of the OTL features for a given instance can be offloaded to a different process, e.g. the creation of the positioning deltas across variation masters. This can be indeed done by providing several separate FEA files which a tool "blends", like Google's fontmake. We don't have to change the FEA syntax for that. But to get reasonable support for FeatureVariations, I don't see how this can be easily without extending FEA. Imagine an Arabic font which in the "wdth" axis only adjusts the width of the kashida stretching glyphs and of stretchable Arabic letters that have the horizontal descenders. In addition to that, the designer might want to gradually switch on stacking ligatures, so glyph sequences that join horizontally if there is ample horizontal space would start joining vertically if there is less space. Such a strategy could be used for other cursive scripts, and even Latin fonts. Similarly, we might want to enable other contextual substitutions when a certain width or weight threshold is exceeded. For that, proper support for the FeatureVariations conditions would be necessary, especially in GSUB. Implementing conditional switching of GPOS adjustments in kern/mark and combining it with the blending of GPOS may indeed be tricky and perhaps out of scope, though the syntax I proposed would still be compatible and allow it in theory. Whether it'll be MakeOTF or feaLib or FontForge that implements the syntax changes first, or ever, is another story. |
FeatureVariations cont'dAlternative approach to the FeatureVariations conditions syntax, inspired a bit with how named lookups and the
In this syntax, we would assume that the for all defined features, if a feature does not have the
Of course, all As with named lookups, I should be able to define
Of course instead of |
The conditions need to, somehow, combine information from two different types of data:
It would be useful to be able to change Maybe there is a way to separate the definition of the condition (with its geometric references) and its application within the feature blocks. A set of named rules, each with any number of conditions, to be defined somewhere in the beginning of the feature text. Then whenever it is needed in the feature text, a rule can be invoked by name. The rule remains active until the end of the block, or until a new rule is called. In fantasy feature text:
|
Perhaps even allow the rules to be nested:
|
This appeals to me. This format also makes it easy to define the default lookups as well as several different rule-based lookups:
When no rules apply, you get the default substitutions. When any rule applies, you do not get the default substitutions, and you do get the substitutions whose rules are satisfied. In the font data, the default lookups are written in a regular GSUB feature, and the rule-based substitutions are defined in ConditionSets in the GSUB FeatureVariations table. If there are no default substitutions, the feature will be written as a regular GSUB feature without any lookups. |
@LettError I like your proposal a lot! |
Additional to this, note that rules can become more complex than a simple boolean equation. If the $-bar strike-through switch happens on a different weight value for condensed width than for extended width, the connecting line is not vertical or horizontal. It may not even be a straight line in the 2-dimensional design space. In that case the rules must define a "staircase" approximation of the "watershed" line, by combining a number of weight & width rules. |
Extending the syntax to include 'and' and 'or' in the if clause would make it possible to define a set of regions in the blend design space to approximate a watershed line that is not orthogonal to all axes. Because of the underlying data structures, I don't think that full boolean logic can be supported: the FeatureVariations table supports only 'or's between sets of 'ands'. For the implementation, each 'and'ed rule would be added to the current ConditionSet, and each 'or' would trigger the start of a new ConditionSet. I don't see a way to support an 'and' between two parenthetical clauses that contain an 'or'. However, the current support is enough to do what Petr is describing, which certainly is useful. |
Any news on this? How can we move this forward? |
I still like the proposal. Let's do it. |
+1 :) |
I just realized — this syntax could actually be extended even more, to support variable positioning:
|
Should we make a PR? What was the consensus? |
I've was just pointed to this thread and admit I haven't kept up with issues related to variable fonts and FEA. But this question occurs to me: How do you extend your example to handle combining marks above either/both of the @tall glyphs? How do you also move any combining mark above the beh? |
I might be missing something in your question, but it’s easy. Use a mark filtering set to ensure you only care about bases and nukta, and any combining marks on the nukta will be repositioned by mkmk. (Assuming that you do any contextual nukta magic before mkmk.) |
Just in case anyone following this issue hasn't seen it, Simon's added a link to a Doodle poll for a meeting to discuss feature file syntax next week here: |
Marks above one/both of the @tall glyphs are likely to collide with the newly raised nukta. At first I was thinking you'd need to raise the nukta further, but more likely you'd want to push the marks up from their default position. So you'll need other contextual rules that recognize such patterns and reposition the marks after mkmk. And the amount of change in mark position depends on exactly what the nukta was (2 dots horizontal? 4 dots? etc) and possibly what the combining marks are. |
Ah, OK, collision detection. I have a solution for that (but not for variable fonts). This is getting off topic so let’s talk about it at the Feature File Format Chat. The quick answer is that I have a shaping engine I can call within my compiler to position arbitrary glyph sequences using the current set of rules, and then check for collisions. |
Regarding the variations part of @simoncozens’ example:
… we need to think about how the syntax will compile under various circumstances. Default, Max and MinIf the default is at 400 we probably want the value to be 0. And if the default is at 700, we’d probably want the value to be 200, in other words interpolating between the explicit values. We need to know what to do if this is different from the default value already in the font. We also need to specify what happens if (in the above example) 200 and 1000 are not the designspace min and max. Since we’re not specifying deltas and tuples directly, we might allow those values to extend (or reduce) to the real min and max. If so, then we’d only need to state 599 and 600 directly, and have the min and max take the values 0 and 200 respectively. Multi-axis fontsIf we add a single new entry for a Width axis such as:
Now, because we are no longer using deltas, we need to specify also what happens at the “corner” of max weight and max width. Otherwise it is unclear whether the corner should take a value of 200 (max wght) or 250 (max wdth). We must “complete the orthogonal grid” in many situations like this, including all intermediates in multi-axis fonts. Otherwise we have to guess which axis takes precedence, work out an arbitrary interpolated or minmaxed value, or some other ugly heuristic. Thus:
Abrupt changes in variation storesA problem in the example is that actual behaviour between 599 and 600 is not as intended: a Axis locations are encoded as Fixed 16.16 (32 bits), thus the resolution is 1/65536. Instead of 599 we should have (600 - 1/65536). A syntax for “x+1/65536” and “x-1/65536” is therefore desirable for this common use case of non-interpolating value changes using variation stores. Some suggestions:
In fact, the compiler needs to be aware that these values become normalized Fixed 2.14 values in variation stores, so these incrementally greater and lesser values have to take that into account, thus compiled to normalized values that are +/- 1/16384. |
Re explicit regions versus locations, see this fontTools discussion: fonttools/fonttools#2207 (However note that Just starts talking about a separate issue, so there are really two very distinct conversations in that issue.) |
Thank you all for the discussions the other day. I think I would like to try and drive things forward in terms of agreeing a syntax. Just to state my assumptions here:
Here's my proposal for varying scalars, then. I don't really care very much about feature replacement - I just don't have a use case for it personally - so I'm happy for someone else to interpret what a feature replacement syntax would look like based on this. A variable scalar replaces a static number value, and is introduced with parentheses. (Parens chosen because they currently have no other semantic value in AFDKO.) Within the parens are any number of masters (using the term in the fontTools.varLib sense of any scalar specified at a given location), separated by whitespace. A master is defined as being a list of pos A B' <0 (wght=200:-100 wght=900:-150 wght=900,wdth=150:-120) 0 0> C' D; |
@simoncozens thanks so much for organizing the meeting and for keeping this discussion moving. I agree that the next big thing to do is to get agreement on syntax. So I will start a PR that updates the Open Type Feature File Specification document and incorporate your latest suggestion for varying scalars as well as @punchcutter's proposal for feature replacement using the language and formatting of the spec. Folks who are interested in this issue can then review and make suggestions there, which I think is probably a more efficient mechanism than trying to hash it out in issue comments. |
An implementation of the proposed variable scalar syntax can be found in the fonttools pull request above. |
Particularly for those of us unfamiliar with fontTools.varLib, could you please elaborate how multi-axis interaction is specified? For example, if I set a Also, is it required to specify default behaviour explicitly, or does the syntax allow for default to be interpolated between min and max? So, in your example, could the default be at 400 thus its value interpolated as -100+((400-200)/(900-200) * (-150- (-100))) = -114.28? |
Maybe my example was unusual in terms of design, but the interpolation mechanism is not varLib specific; it's precisely the same as what is used to get points in glyph outlines from masters and vary them in the designspace. (Literally the same mechanism, in fact...) So: assuming a weight axis min=200,default=200,max=1000 and a width axis min=50,default=100,max=150
If an axis is not specified in a location (e.g. Currently the implementation requires you to specify the default situation explicitly, but I suppose in theory there's nothing in the syntax stopping you from creating min and max cases and have the default interpolated - again, just like designing font masters. |
Thanks for this. It is the fact that default axis locations are specified elsewhere that made your example a bit confusing. (In fact it does make sense if default Also note that, unless all axis locations are fully specified for each value, choosing a different master to be default means all variable positioning rules must be recalculated. If that’s a step too far, I’d like to see the default value explicit even if 0. I would recommend that the “bare value to represent the default position” is required immediately after the initial parenthesis, thus (for the example above):
|
Hi Josh - any news on this? Would it be helpful if I drafted a PR myself? |
@simoncozens we're still working out some details on @punchcutter's feature replacement idea that I want to make sure are incorporated along with your scalar variation proposal. I hope to get that sorted soon but it could be a while still. |
@simoncozens and other interested folks, please see #1350 |
@readroberts @brawer etc.,
Do we have any plan in place to extend the FEA syntax to support OpenType Variation fonts?
This would be a major change of course. Correct me if I'm wrong, there are two major aspects to be revised:
FeatureVariations table
fontTools already supports FeatureVariations so using
.ttx
syntax, one could build anrvrn
feature. But how would be express this in.fea
syntax?ValueRecord variations.
Before 2001, AFDKO did support CFF-based Multiple Master OpenType fonts, so there must have been some syntax to support "per-master metrics data" for metrics, kerning etc. Also, the FEA syntax has some provisions for device-specific adjustments. As we know, GPOS/GDEF/JSTF data in OTVar is inspired by the device adjustments.
General approach
Currently, my understanding is that Google's fontmake uses a method where OT features in each master are compiled using feaLib or AFDKO, and then the fontTools/varLib code is called to add variation data to GPOS.
This generally opens up a legitimate question:
languagesystem
,script
andlanguage
but refer to OTVar.I can think of something like:
In the prolog section of the FEA file, we could have new keywords that could be used to compile the "fvar" table (perhaps), and to "set up" the general rules for how variations work. This would be somehow analogous to how
languagesystem
works:Then, inside any lookup, there could be some syntax that defines the values for the variation. Roughly, it could look like:
With an approach like this, users could be flexible in using the
include()
statements for various masters, i.e. the whole thing could be expressed as:This way, "old-style" FEA files such as for the Source Sans Pro project: ExtraLight kern.fea and ExtraBold kern.fea could be used.
Note: The syntax I'm proposing is just an "idea".
The text was updated successfully, but these errors were encountered: