MPEGGroup / OpenFontFormat

Official MPEG repository to discuss issues on Open Font Format (ISO/IEC 14496-22)
31 stars 6 forks source link

Variable substitution: No mechanism for interdependent axes #54

Open skef opened 1 year ago

skef commented 1 year ago

More complete document with background: conditions.pdf (This version of the PDF was updated August 8th).

Consider the archetypal case of substitution: A variable font has two glyphs for the dollar sign, one with two vertical strokes and another with just one stroke. The designer wants to switch between the glyphs when they judge that the strokes are too thick to leave room for each other. In the usual examples this decision is tied to the wgwt axis.

However, the thickness of a stroke is not necessarily just a function of one axis. Indeed, the opsz axis, which is registered and already used in some variable fonts, also changes the thickness of strokes, with strokes getting somewhat thinner as the axis increases. If a designer's decision about when to substitute is based on thickness, their preference across both axes might look something like this:

cond_fig5ink

Unfortunately this is impossible to express directly with the currently available mechanisms. The best you can do is a stepwise approximation, perhaps something like this:

cond_fig6ink

Therefore, even for what isn't all that good an approximation, we now have eight regions plus a default just for one glyph across two axes. If a different glyph needs similar treatment along a different line on the same combination of axes, we can use the same regimentation but will then wind up with 16 regions plus a default. And so on.

Proposal

There there are likely many novel means of addressing the interdependent axis substitution problem, but it seems easiest to choose the least novel. That is, rather than having to invent a bunch of new machinery it seems preferable to use something already available. And in this case the easiest option is to use a normal interpolated value, just like those already used for point locations in glyph outline data or kerning values in GPOS.

Accordingly, suppose that we were to add Condition Table Format 2: "Condition Value":

uint16    format                 Format = 2
int16     default                Value at default location
uint16    deltaSetOuterIndex     
uint16    deltaSetInnerIndex

The way this condition works is what you would expect: It evaluates to true when the value is positive and false when it is 0 or negative. The font engineer making use of the condition value is then responsible for placing the "zero line" where the designer needs it.

(See document above for solution to the sample problem.)

behdad commented 1 year ago

Unfortunately this is impossible to express directly with the currently available mechanisms.

One way I thought about this is to synthesize a the sloped line as a new axis with avar2, and use it... But that's very complicated to do a simple thing.

I'm very much in favor of your proposal.

Lorp commented 1 year ago

avar2 was my thought too. You’d create a "stem width" axis (XOPQ if you want to align it with the Type Network spec), control it via avar2, and code GSUB substitutions according to it rather than to user axes. I’m not sure there’s a less complicated way to do it. Why do you call it complicated, Behdad?

behdad commented 1 year ago

Why do you call it complicated, Behdad?

I feel like you'd need a new axis for any substitution then. I don't think even the standard parametric axes would do it.

I find stef's proposal much simpler.

Lorp commented 1 year ago

Apologies for not reading the PDF properly. It’s a brilliant idea that avoids adding new axes (which would affect numerous other tables and leads to UI annoyances even if "hidden").

skef commented 1 year ago

A correspondent asked for some example files to make all of this a bit more concrete. In making those I realized I had the opsz axis backwards (relative to its documented use) in the examples, so I reversed the direction using the labels. These files use the new direction

  1. cond_fonts.zip
  2. CondVariable-Roman.otf.zip

The first contains the source files used to build the font, and the second is just the font alone.

The font has two codepoints: a and $. $ switches between two glyphs using the 8 region workaround from the example. a contains a dot with a center that moves vertically according to the condition value described in the PDF, except that all magnitudes are divided by 20. If you display both glyphs in a tool that lets you adjust VF axes, you'll see that the substitution happens close to when the dot crosses the baseline (e.g. y = 0). The "close to" is due to the approximation inherent in the workaround.

skef commented 1 year ago

Based on discussions over at #53, I think we may also want a flag in this new subtable indicating that the value should be boolean-negated. (That is, treated as false when it would normally be treated as true, and vice-versa.)