OpenType GX
an exploratory proposal
Behdad Esfahbod
behdad@google.com
February 2016
https://goo.gl/0N3zLy
Table of Contents
Introduction
Let's revive and revise TrueType GX font variations, and integrate it with OpenType. In common terms this enables run-time interpolation between font masters, also known as dynamic interpolation. There are two main benefits to doing this: 1. font file size savings, specially on mobile devices, and 2. making the entire gamut of weight, width, etc available to users, not just a series of interpolated instances.
Technology for run-time interpolation was designed in the 90s independently by Adobe and Apple. Adobe's technology enabled run-time interpolation of Type 1 fonts and was called Multiple Master (MM from here on). Apple's technology applied to TrueType and was called TrueType GX font variations (GXVar from here on). Both technologies live on to this day in one or two implementations, but never became popular. In this proposal, we will propose integrating the core variation tables from GXVar into OpenType, and extend existing OpenType tables (GSUB, GPOS, etc) to integrate with glyph variations.
Both MM and GXVar implement variations by just repositioning points (on-curve and control) of an outline, while keeping the outline structure (curve types & number of points) intact. This is nice, because these can be generated from a series of interpolation-compatible font masters that satisfy such requirements. For background on how GXVar and MM work, see Appendix A—Background.
Architecture
We are going to follow the same architectural model as GXVar. To that extent, we will integrate some tables from GXVar directly for some aspects, extend existing OpenType tables for other aspects, and diverge from GXVar tables for some more aspects yet. In the future, we might redesign some of the tables that are imported verbatim from GXVar to address their shortcomings.
In particular, in the Proposal section we will discuss how to implement variations to the following aspects of a font:
Font variation metadata,
Glyph shape variation,
Font metrics variation,
Positioning variation,
Conditional substitutions and positioning,
Glyph metrics variation,
Hinting data variation.
An important design decision of the proposal is that the variation-enabled OpenType font extension will be non-invasive, the same way that the GXVar is. That is, to a client that does not know about variations, the font will perform as a valid (even if bulky) OpenType font representing the base face of the variation font. This significantly simplifies the backward-compatibility of this extension, specially in the face of web fonts.
Another important design decision of the proposal is that it should be possible to create a variation-enabled OpenType font from input masters and configurations that font designers already use in their workflow. An embodiment of this decision is to be able to consume MutatorMath input data (designspace as well as master UFOs) for example, while not limiting other workflows (Glyphs app, FontLab, etc).
Practical considerations
The following practical considerations help understand the proposal better:
Overlap removal
Normally, overlaps in glyph contours are removed during font production. This, however, disturbs interpolation-compatibility of the masters as well as resulting in inferior interpolation results in cases that it does not break interpolation-compatibility. The good news is that there is not a technical requirement to remove overlaps. TrueType outlines are filled with a nonzero-winding rule which works just fine with overlapping (and self-intersecting) contours. CFF fonts have an unspecified fill rule. Applying the same nonzero-winding rule to them makes them work with overlapping contours as well, so specifying that will be a requirement of this proposal.
Cubic-to-quadratic Bézier conversion
Most designers design font masters in an interpolation-compatible way using cubic Bézier curves. TrueType fonts as well as GXVar, however, use quadratic Bézier curves. While adding cubic curves to TrueType ‘glyf’ table is a worthwhile exercise by itself, it is not a requirement of this proposal. Instead, we have implemented (cu2qu) an Open Source cubic-to-quadratic curve converter in Python that operates on multiple interpolation-compatible cubic outlines and produces interpolation-compatible quadratic outlines. This can be used in implementations that might initially only support variations in TrueType-flavored fonts but not CFF-flavored ones.
CFF glyph shape point numbers
While the CFF specification does not explicitly define indices for points in a glyph outline, there is a straightforward way to define those: load glyph into a series of moveto, lineto, and curveto operations, ignoring any flexing, then assign point indices to the point operands of those operations starting from zero. We will use this numbering to extend GXVar glyph shape variations to CFF-flavored OpenType fonts.
Extrapolation
The GXVar model does not support extrapolation across axes beyond the outermost masters provided. If such extrapolation is desirable, the builder process can perform the extrapolation at font build time and include those as extra input masters.
Proposal
In this section we discuss concrete proposals to add variations to different aspects of OpenType fonts:
Font variation metadata
For specification of the variation axes and pre-defined instances, we propose copying the ‘fvar’ and ‘avar’ tables from GXVar into OpenType.
The ‘fvar’ table describes variation axes and their default values as well as support range (eg. “weight” axis defaults to 1.0 and is supported from 0.5 to 2.0), as well as a set of named instances, which are pre-defined interpolated font instances in the interpolation design space. The named instances refer to ‘name’ table entries for the face name (“Bold Semi-Condensed”, etc).
The ‘avar’ table enables for a non-linear mapping from user axis coordinates to the internal normalized axis values. This enables interpolation curves like the Luc(as) de Groot’s interpolation curves, or the Impallari formula, transparently from the user. The mapping is decided by the font build process and is specified as a piece-wise linear function (like everything else in this proposal). Under this model, any continuous curve can be specified to any desired fidelity by using a more or less granular piecewise-linear approximation.
In the GXVar model, each variation axis is specified by a four-letter tag (much like the sfnt and GSUB/GPOS tags). It also specifies the default value for each interpolation axis, which is the value associated with the base font.
The GXVar model also includes the following four predefined axes:
Tag | Name | Default | Description |
'wght' | Weight | 1.0 | Used for making a font range from light to black. |
'wdth' | Width | 1.0 | Used for fonts ranging from narrow to wide. |
'slnt' | Slant | 0.0 | Specifies the angle (in degrees) of italic slant. |
'opsz' | Optical Size | 12.0 | Specifies the optical point size. |
(Table courtesy of Apple)
The meaning for the slant and optical size axis values is well-defined. For weight and width, we propose defining the value in the following way:
The observed OS/2 tableusWeightClassof an interpolated instance is the usWeightClass of the base font multiplied by the ‘wght’ axis value of the requested instance. That is, if the base font has usWeightClass of 400 (Normal / Regular), and Light (300) face is needed, the ‘wght’ axis value of the requested instance should be set to 300/400 = 0.75.For the width axis, similarly, the observed OS/2 tableusWidthClassof the interpolated instance is the usWidthClass of the base font multiplied by the ‘wdth’ axis value of the requested instance.
We suggest making the following adjustments to those four pre-defined axes:
‘wght’: Change from a default:1.0 model to one matching CSS / OS/2 usWeightClass model. Clarify that default axis value of the font must match the usWeightClass value in OS/2 table.
Note: If supporting both the old default-1.0 model and the new CSS-valued model is needed, we can defined the observed OS/2 usWeightClass of an instance font to be the usWeightClass of the original font, times the ‘wght’ axis value of the instance, divided by the default value of the ‘wght’ axis as defined in the ‘fvar’ table. This is, for example, what I have implemented in fontconfig.‘wdth’: Change from a default:1.0 model to one matching OS/2 usWidthClass model. Clarify that default axis value of the font must match the usWidthClass value in OS/2 table.
Note: Something based on the default axis value can be done here as well, similar to the ‘wght’ axis.‘slnt’: Change from a “angle-in-degrees” model to an “skew-coefficient” model. That is, a slnt:0.1 will conceptually skew the font 10% to the right. This is a better model than angles since for linear interpolation (which is what we have), applying, eg, twice the value of a change, makes it skew twice, not rotate twice. Clarify that default axis value of the font must be consistent with italicsAngle in post table. (that is, be the same, if the angle-based value is kept, and mathematically correspond, if the skew definition is adopted).
‘size’: Clarify that the default axis value (and min/max?) in the font must be consistent with usLowerOpticalPointSize and usUpperOpticalPointSize in OS/2 table if those fields are present. Define what it means to be consistent with them.
Note that because the ‘avar’ mechanism for non-linear mapping of user axis values to normalized axis values exists, these proposed redefinitions of the axes do not expose any practical limitations on what can be achieved by the format in terms of the functional behavior.
Glyph shape variation
For glyph shape variation, ie. the main font variation data, we propose copying the ‘gvar’ table from GXVar. Note that the Apple spec does not document some important aspects of how ‘gvar’ is implemented in Apple products and fonts, namely the use of IUP-like interpolation for untouched points. IUP here refers to the “Interpolate Untouched Points” TrueType hinting instruction. This needs to be clarified before integrating the ‘gvar’ table into OpenType.
The Apple model only applies to TrueType-flavored outlines (ie. the ‘glyf’ table). OpenType also includes CFF-flavored outlines (‘CFF ’ table) and it is desirable that the OpenType variation font model can support both. Since the ‘gvar’ table is non-invasive, ie. it encodes deltas for glyph outline coordinate data from the base font, it can equally apply to CFF outlines, as long as the points in a CFF glyph outline are properly numbered, for example, as described in CFF glyph shape point numbers. In such a world, glyph loading can be summarized as a succession of the following steps:
Load base glyph shape from ‘glyf’ or ‘CFF ’ table,
Apply ‘gvar’ deltas for requested variation,
Apply hinting or auto-hinting if desired.
Font metrics variation
Font variations need a way to specify variations in font-wide metrics, like ascent, descent, line-gap, caret slant, underline / strikethrough position, superscript / subscript position and size, etc.
GXVar addresses this need by using the ‘fmtx’ table. The current version of the ‘fmtx’ table specifies these variations by way of appointing certain points within a (dummy) glyph index in the font. While this is a smart solution, it requires adding a new glyph to the variation font, which is rather invasive to the font as a whole, as other tables need to be adjusted to include data for this new glyph. While this is achievable, in our opinion, a solution that directly encodes the variations of various font metrics without requiring a new glyph is preferable. Moreover, the current version of the table leaves out variations of metrics like underline / strikethrough position, superscript / subscript position and size, and possibly other metrics.
Instead, a new version of the ‘fmtx’ table can be defined that includes variation data for the following tables directly: ‘hhea’, ‘vhea’, and ‘OS/2’. The old version will still be supported. Note that of all the values in the ‘head’ table, the xMin, yMin, xMax, yMax values can be interpolated but are hardly used. The ‘head’ table unitsPerEm value on the other hand, while can be interpolated, is too fundamental to the definition of the font, and as such we propose no variation to be supported: all masters must have the same unitsPerEm. This requirement might be reconsidered.
Positioning variation
For complex positioning variation, we consider two technologies:
Apple’s AAT technology handles positioning via ‘kerx’, ‘ankr’, ‘lcar’, and other tables. Some of these tables already include support for variations, and others can be extended to do so. At any rate, these are not officially part of OpenType and out of the scope of this proposal.
The OpenType solution to glyph positioning lies in the ‘GPOS’ and to a small extent ‘GDEF’ table. In the rest of this section we explore possible solutions to this problem.
To extend GPOS / GDEF for variations is straightforward: everywhere where an X / Y value is specified, we need to also specify variations to that value. Initially we were considering extending the ValueFormat, AnchorFormat, and CaretValue tables. However, in discussion with others, the idea of extending the DeviceTable was brought up. We have since refocused on this idea for the following reasons:
For compatibility with existing AnchorFormat and CaretValue tables: extending the DeviceTable format to include variations means that AnchorFormat and CaretValue tables do not need a new format; this is backwards-compatible with existing implementations as is desired.
For ValueFormat-based values, this obviates the need to allocate four bits out of the (scarce) remaining four free bits of the ValueFormat bit enumeration.
As such, we propose an extension to the OpenType Device table. Whereas currently the Device table is defined as:
Device table
Type | Name | Description |
uint16 | StartSize | Smallest size to correct-in ppem |
uint16 | EndSize | Largest size to correct-in ppem |
uint16 | DeltaFormat | Format of DeltaValue array data: 1, 2, or 3 |
uint16 | DeltaValue[ ] | Array of compressed data |
(Table courtesy of Microsoft)
We note that only DeltaFormat values of 1, 2, and 3 are currently defined. We propose a structure, VariationDevice, that can be differentiated based on its “DeltaFormat”, to encode GPOS/GDEF value variations. This new struct, as well as structs it depends on are provided as C++ structs below, with code to compute the delta, given a list of instance coordinates numbered based on the axis indices from ‘fvar’ table:
struct VariationAxis
{
USHORT axisIndex;
F2DOT14 startCoord;
F2DOT14 peakCoord;
F2DOT14 endCoord;
inline float evaluate (int *coords, unsigned coord_len) const
{
int coord = axisIndex < coord_len ? coords[axisIndex] : 0;
/* TODO: Make sure this matches desired ‘gvar’ behavior. */
if (coord == 0) return 0;
assert(!(startCoord < 0 && endCoord > 0);
if (coord < startCoord || coord > endCoord) return 0.;
if (coord == peakCoord) return 1.;
/* Interpolate */
if (coord < peakCoord)
return float (coord - startCoord) / (peakCoord - startCoord);
else
return float (endCoord - coord) / (endCoord - peakCoord);
}
};
struct VariationTuple
{
USHORT numAxes;
OffsetTo<VariationAxis> axis[numAxes]; /* Sorted by axisIndex. */
inline float evaluate (int *coords, unsigned coord_len) const
{
float v = 1.;
for (unsigned int i = 0; i < numAxes; i++)
v *= (this+axes[i]).evaluate(coords, coord_len);
return v;
}
};
struct VariationMap
{
USHORT numTuples;
OffsetTo<VariationTuple> tuples[numTuples];
inline float evaluate (unsigned i, int *coords, unsigned coord_len) const
{
return (this+tuples[i]).evaluate(coords, coord_len);
}
};
struct VariationDevice
{
OffsetTo<VariationMap> variationMap;
USHORT deltaCount;
USHORT deltaFormat; /* Format=0x10 */
SHORT deltaValue[deltaCount];
inline float get_delta (int *coords, unsigned coord_count) const
{
float v = 0;
const VariationMap &map = this+variationMap;
unsigned int count = MIN((unsigned int) deltaCount, map.numTuples);
for (unsigned int i = 0; i < count; i++)
v += deltaValue[i] * map.evaluate(i, coords, coord_count);
return v;
}
};
I have to be first to agree that this sounds rather heavy. But I believe it’s better than it looks like at first glance. The following principles guided this design:
Random-access and extremely fast to apply; in particular, there’s no bit-tweaking decoding involved,
Follows the structure and spirit of the GSUB/GPOS tables (offsets and fixed-size values), as opposed to more convoluted byte-optimized design of glyf/gvar tables; indeed, the code to compute a delta for these tables is already included above, is about twenty lines of code.
Let us now take a look at how many bytes we have spend on average, per value that needs variation:
In the above scheme, a VariationTuple represents a master, and a VariationMap represents a set of masters that have deltas (aka variations) for the value at hand. As such, the number of distinct VariationMap structs in a GPOS table would be limited to (2^n - 1) where n is the number of masters. In reality, I doubt that most fonts will have more than a few different VariationMap structs. Since we refer to VariationMap structs by offset, multiple values that need variations can refer to the same VariationMap object, offset-limit permitting. As such, we can discount the amortized overhead of the VariationMap objects as negligible.
To encode variations for each value, two bytes are used by the offset to Device table. Six bytes are consumed by the VariationDevice table’s fixed members, and from there on, two bytes per each master used for this value’s variations (other than the base master which does not need any variations encoded). That is, for a two-master font, 10 bytes are used to encode variations for each value. That’s definitely a lot. But this becomes much better as we add more masters. For a six-master font, the cost to encode variations of each value is 18 bytes, which is a mere three bytes per master per value. In general, my gut feeling is that the combined GPOS table will be smaller than the sum of the sizes of the GPOS tables for all masters. The only exception is the class-based kerning, because the matrix there is extremely compact and low-overhead. In fonts that class-based kerning subtables dominate the GPOS bytes, up to 50% growth compared to sum of master GPOS tables can be expected, but that is the worst case.
Conditional substitutions and positioning
Apple has their own technology for glyph substitutions, namely, the ‘morx’ table. Variations support for that table remains as an exercise for Apple. In the meanwhile, we consider such support in the GSUB table, which is the OpenType Layout answer to the same need.
While GPOS/GDEF value variations support bulk of the need for complex layout in variation fonts, it is also desirable to be able to include conditional substitutions. For example, some ligatures might be better turned off in parts of the design space (eg. bolder weights). We propose implementing this as a generic addition to GSUB and GPOS tables in the following way:
We are going to enable substituting individual lookups with alternate versions (or nothing) based on variation coordinates. To that end, we first need a new LookupFlag bit:
LookupFlag bit enumeration
Type | Name | Description |
0x0001 | RightToLeft | ... |
0x0002 | IgnoreBaseGlyphs | If set, skips over base glyphs |
0x0004 | IgnoreLigatures | If set, skips over ligatures |
0x0008 | IgnoreMarks | If set, skips over all combining marks |
0x0010 | UseMarkFilteringSet | If set, indicates that the lookup table structure is followed by a MarkFilteringSet field. The layout engine skips over all mark glyphs not in the mark filtering set indicated. |
0x0020 | UseVariationAlternates | If set, indicates that the lookup table structure is followed by LongOffset to a VariationAlternates field. The layout engine substitutes the alternate lookup if font variations are in use. |
0x00C0 | Reserved | For future use (Set to zero) |
0xFF00 | MarkAttachmentType | If not zero, skips over all marks of attachment type different from specified. |
The new VariationAlternates struct and its supporting structures:
struct VariationGridAxis
{
USHORT axisIndex;
USHORT numAxisCoords;
F2DOT14 axisCoords[numAxisCoords]; /* Sorted by coordinate */
inline int locate (int *coords, unsigned int coord_len) const
{
int coord = axisIndex < coord_len ? coords[axisIndex] : 0;
unsigned int i = 0;
while (i + 1 < numAxisCoords && coord < axisCoords[i + 1])
i++;
return axisCoords[i] <= coord ? i : -1;
}
inline unsigned int get_len (void) const { return axisCoords.len - 1; }
};
struct VariationGrid
{
USHORT numAxes;
OffsetTo<VariationGridAxis> axis[numAxes]; /* Sorted by axisIndex. */
inline int locate (int *coords, unsigned int coord_len) const
{
int location = 0;
for (unsigned int i = 0; i < numAxes; i++)
{
const VariationGridAxis &axis = (this+axes[i]);
int axisLoc = axis.locate (coords, coord_len);
if (axisLoc == -1)
return -1;
location = location * (axis.get_len () - 1) + axisLoc; /* XXX Check for overflow?. */
}
return location;
}
};
struct VariationAlternates
{
OffsetTo<VariationGrid> grid;
USHORT numLookupIndex;
USHORT lookupIndex[numLookupIndex];
inline unsigned int get_lookup_index (int *coords, unsigned int coord_len) const
{
int location = (this+grid).locate (coords, coord_len);
If (location == -1)
return 0xFFFE; /* Special value 2. */
return location < numLookupIndex ? lookupIndex[location] : 0xFFFF; /* Special value 1. */
}
};
A VariationGrid structure defines a hyper-grid on a select number of design-space axes, that need to use an alternate lookup instead of the current lookup. For example, imagine we want to substitute lookup 10 with lookup 11 if ‘wght’ > 0.5. Then our VariationGridAxis for ‘wght’ axis needs to encode two numbers in axisCoords: 0.5, and 1.0 (encoded in F2DOT14). What these means sit that we want to override what lookup is applied if 0.5 <= ‘wght’ <= 1.0 (need to figure out the equality conditions). Whereas if we wanted to use one alternate for 0.5 <= ‘wght’ < 0.75 and another if 0.75 <= ‘wght’ <= 1.0 then we’d encode the three numbers 0.5, 0.75, and 1.0 in VariationGridAxis’s axisCoord for this axis.
When the hyper-grid is specified, each location in the grid is addressed using an index, by enumerating cells starting at zero and with axes ranked by the order of their appearance. Eg, if there is only one axis with three coordinate values specified, the hyper-grid is a simple vector of two cells, which are addressed 0 and 1. If there are two such axes, then the hypergrid has 4 cells, numbered 0 to 3 in this order: (0,0), (0,1), (1,0), (1,1), as one expects. Then, the alternate lookup indices for each cell are encoded in the lookupIndex of the VariationAlternates struct.
For instance coordinates outside the grid, a special value of 0xFFFE is returned by VariationAlternates::get_lookup_index(). This value has a special meaning that means “apply current lookup”. It can be used in the grid table itself as well. A special value of 0xFFFF (Not_Found), on the other hand, means “don’t apply any lookup”. If a cell contains the number of the current lookup itself, it must be interpreted like 0xFFFE: that is, apply the current lookup immediately. Any other value must be a valid lookup index and will cause the lookup at that index in the table’s LookupList to be processed instead of current lookup. Note that the new lookup itself might have VariationAlternates which would need to be processed.
Old proposal follows:
Extend each and every (possibly except for the Extension types) GSUB/GPOS lookup type, to add a new subtable type for variation-conditional processing.
TODO After discussing with Martin on Feb 9, we like to change the proposal in this section.
Note that while the more intuitive way to adapt GSUB/GPOS for variation-conditional processing might be to include a new lookup-type that encodes an enabling condition and refers to a regular lookup subtable(-list) for processing, or to extend the Extension lookup type with a new subtable format, we propose a new subtable type for each lookup type instead, for good reason: with subtables of a lookup, it is possible to reason / prove certain properties of mixing subtables. For example, two SingleSubstitute subtables A and B appearing in that order are equivalent to the union of A and B’s mappings, with A’s mappings taking precedence over B’s for glyphs that appear in both mappings. Similar reasoning can be done for other lookup types, whereas in general, no such reasoning is possible for separate lookups. (TODO Flesh this out / make sure this is comprehensible.)
We propose expanding each lookup type with the following new sub-format:
FormatF subtable
|
|
|
|
|
|
|
|
|
|
|
|
where Condition points to a data-structure for specifying parts of the design space, TODO to be specified here.
Glyph metrics variation
The default glyph metrics (horizontal advance and left-side bearing as well as vertical advance and top-side bearing) variations are already encoded in the ‘gvar’ table by way of four phantom points, the same way that such variations are handled in TrueType hinting. This, however, makes loading the variation in advance widths contingent on loading the glyph variation data from ‘gvar’. While this is not a big deal in general, it might prove to slow down OpenType shaping process significantly. As such, we should explore encoding variation data for horizontal advance (‘hmtx’), vertical advance (‘vmtx’), and vertical origin (‘VORG’ for CFF fonts) in a more optimized variation table(s).
Hinting data variation
When considering hinting, we have a few distinct situations to consider: TrueType or CFF, hinting, static auto-hinting, or dynamic auto-hinting:
TrueType outlines:
Hinting: Variation support for TrueType hinting is included in the GXVar model. In this model, the TrueType bytecode for all masters shall be the same, and the data included in the ‘cvt ‘ table will vary. Such variation is encoded in the ‘cvar’ table.
Static auto-hinting: Tools like ttfautohint can be extended to process a set of masters together, and to generate family-width bytecode, with variations defined by way of ‘cvt ‘ and ‘cvar’ tables.
Dynamic auto-hinting: For run-time auto-hinting, only glyph shapes matter, and auto-hinters like the FreeType autohinter will continue to work with no modification as long as glyph loading routines are updated to load variation glyphs.
CFF outlines:
Hinting: CFF hints are embedded in glyph charstrings. Encoding variations to those will be cumbersome but not impossible, if determined to be needed. This probably was supported in the Multiple Master format by way of using the blend operator as input to the hinting operators.
Static auto-hinting: Almost all hinted CFF fonts are auto-hinted at build time using the AFDKO autohinter these days. It is possible, if cumbersome, to auto-hint multiple masters at the same time and encode hinting variation data in an auxiliary table to be used at run-time for proper hinting.
Dynamic auto-hinting: An alternative solution to CFF (and possibly TrueType) hinting will involve integrating the AFDKO auto-hinter into the rasterizer, and only providing variations for CFF auto-hinting parameters (bluezones, etc). This is a larger change that is beyond the scope of the current proposal.
Note that in theory, nothing stops a client to extend itself to apply TrueType-style hinting to CFF data or CFF-style hinting to TrueType data. But any such extension is beyond the scope of this proposal.
Implementation status
Support for ‘fvar’, ‘avar’, and ‘gvar’ for TrueType-flavored fonts has existed in Apple’s OS X forever (what about iOS?). It has also been implemented in FreeType, though, the implementation did not support IUP-like interpolation before, but that was fixed in May 2015 after a long group detective work. Note that you need a very recent version of FreeType for all these to work correctly (late-April 2016 is good.)
Support for the ‘fvar’, ‘avar’, and ‘gvar’ tables was added to the FontTools library in early 2015. This is a sample script to create a GXVar font using FontTools, though a more complicated master configuration needs more work (see below).
Support for GSUB/GPOS/GDEF extensions is being worked on in FontTools and HarfBuzz in parallel. This should allow us to test our model as well as build tools to combine GSUB/GPOS of multiple source masters into a variation-extended set of tables.
TODO
Supported to some extent: skia, chrome, fontconfig.
To do:
CFF-flavored freetype
To build:
Instantiator, aka mutator
fontmake + mutatormath
GSUB/GPOS/GDEF integrator
coming up with avar mapping from master weights, etc, based on font statistics
File size savings
TODO
Deployment
Just named instances
Exposed variation knobs
Responsive design (justification, …)
CSS
Possibly reuse font-feature-settings. Consider fluid scaling with viewport units, CSS calc() and element/container queries that can take advantage of a variable font.
Further discussion
Implementation in tiers
This proposal can be implemented in tiers. The following is one such model, presented here just for the sake of discussion:
Tier 0: Support for ‘fvar’, ‘avar’, ‘gvar’ in the context of TrueType-flavored fonts,
Tier 1: All of Tier 0, as well as extensions to GSUB/GPOS/GDEF.
Tier 2: All of Tier 1, as well as support for applying ‘gvar’ to CFF-flavored fonts,
Support for hinting variations is left out of the tiering above. Other aspects (font metrics, accelerated glyph metrics, etc) are also left out of the tiering above.
Anisotropic interpolation
TODO:
Description of anisotropic interpolation.
Find out if this was possible in GXVar.
Make a case why it would be a good thing to have.
Appendix A—Background
Interpolation has become common in recent years, thanks to tools like Superpolator / MutatorMath, as well as modern font editors like Glyphs and RoboFont. At its core, interpolation is simple and trivial: a designer provides multiple manually-designed masters that are "interpolation-compatible", and several instances can then be generated by software using simple linear interpolation.
All systems allow for arbitrary axes of interpolation. Predefined axes include weight, width, optical size, but to the math underneath, these mean nothing. They are just independent axes.
Multiple Master
Multiple Master is a Type 1 font technology. It was included in early drafts of OpenType (1.25) but removed from the final version. In its documented version, MM only supports cube combinations, that is, there is a master at every corner, and only corners, of the design space. This limitation was lifted in later version of Multiple Master, but never documented as far as we can see.
The way variations were implemented in the version of Type 2 charstrings that was aimed for the OpenType-with-MM version was through a blend operator. Quoting from “The Type 2 Charstring Format”, Adobe Developer Support, Technical Note #5177, 5 May 1998:
For example, for a multiple master font with three master designs, and the individual masters need the equivalent of
100 30 rmoveto
120 50 rmoveto
95 30 rmoveto
this would be encoded as:
100 30 20 –5 20 0 2 blend rmoveto
Those numbers are basically:
100 30 (120-100) (95-100) (50-30) (30-3) 2 blend rmoveto
Basically the blend operator here is told to blend two numbers. The run-time environment should already know how many masters are involved and what the mixing factor for each is. The blend then pops the six operators, blends them, and pushes the resulting two values on the stack again. This means that the MM approach to variations is invasive. That is, to a font consumer that does not know about MM, the font is unusable.
The MM draft in OpenType included support for variations of Value values in GPOS. We considered reviving this for our proposal but decided to not do so.
TrueType GX font variations
TrueType GX font variations (GXVar from here on) takes a rather different approach to the problem: The base font is encoded as is (using TrueType ‘glyf’ table, etc), and the variations are encoded in new tables (‘fvar’, ‘avar’, ‘gvar’, etc). This is non-invasive in that to a consumer system that does not know about glyph variations, the base font still works.
Another nice property of the GXVar model is that there is no notion of masters whatsoever. The masters are distilled into variation functions that are encoded in the font, but the variation functions for different glyphs can be different. What this means in real life is that not all glyphs need to be present in all masters. Moreover, arbitrary numbers and configurations of masters are supported. What the GXVar model does imply however, is how masters mix. GXVar supports, essentially, a piecewise linear mixture of masters. We take a deeper look into that.
At its core, the interpolated fonts problem comes that to interpolating single numerical values. These values can be and X or Y coordinate of a point in a glyph outline, or can be a kerning value, or font height, etc. But they are all the same problem: the value is different in different masters, and we need to encode how the value varies over the design space. Since GXVar encodes the base font completely, the variations only need to encode the differences of the value compared to the base, also known as deltas.
Lets look at the simplest example. We have a Regular and a Bold master, there is the origin, which represents the Regular master, and then the Bold master at coordinate weight=+1.0. We made up the +1.0 number; the actual number is insignificant for the purpose of this discussion. Then, to evaluate… TODO finish this section based on / by linking to writings from Nathan Willis.
Acknowledgements
Behdad Esfahbod wrote the initial version of this document and maintains the text.
Multiple people started talking about dynamic font variations since ATypI 2014 in Barcelona. The ideas presented in this document are the results of author(s)’s discussions with the following people, via email or face-to-face meetings, at ATypI 2014 & 2015, the OpenType Layout ad-hoc Working Group’s meeting in Mountain View in October 2015, as well as other meetings (omissions are my fault):
Adam Twardoch, Benjamin Wagner, Bob Jung, Christian Robertson, Christopher Chapman, Cosimo Lupo, Dave Crossland, Dave Lemon, Dave Opstad, David Kuettel, Denis Jacquerye, Doug Felt, Eric Muller, Erik van Blokland, Frederik Berlaen, Georg Seifert, Greg Hitchcock, James Godfrey-kittle, John Hudson, John Jenkins, Jonathan Kew, Jungshik Shin, Just van Rossum, Karsten Luecke, Ken Lunde, Kenji Baheux, Martin Hosken, Luc(as) de Groot, Mike Reed, Nathan Willis, Ned Holbrook, Peter Constable, Peter Lofting, Raph Levien, Roderick Sheeter, Roozbeh Pournader, Sairus Patel, Sascha Brawer, Sergey Malkin, Tom Rickner, Werner Lemberg.
The author was also informed by the following writings:
A Multiple Master based method for scaling glyphs without changing the stroke characteristics, Tim Ahrens
Variable Fonts for Responsive Design, Nick Sherman
Tutorials tagged “multiple master”, Glyphs app
Interpolation Theory, Luc(as) de Groot
as well as the various TrueType GX font variation tables from Apple TrueType Reference Manual.
References
TODO Move all external link here.
Specifications
Source code repositories
Revision history
2016-03-13 Rewrote Conditional substitutions and positioning section with new proposal.
2016-03-10 Fleshed out positioning variation with proposed Device table extension.
2016-03-04 Rewrote proposed “Font variation metadata” predefined axis behavior changes.
2016-02-05 Initial semi-public revision.