This table encodes variable-composite glyphs in a way that can be combined
with any of glyf/gvar or CFF2. Since the primary purpose of
variable-composites is to make font files (especially the CJK fonts) smaller,
several new data-structures are introduced to achieve more compression compared
to an equivalent non-variable-composite font.
The following foundational data-structures are used in this able:
-
uint32varis a variable-length encoding ofuint32values, which uses 1 to 5 bytes depending on the magnitude of the value being encoded. It borrows ideas from the UTF-8 encoding, but is more efficient because it does not have some of the random-access properties of UTF-8 encoding. -
TupleValuesis borrowed from theTupleVariationStorePacked Deltas with minor modification to allow storing 32-bit values. -
CFF2IndexOfis simply a CFF2-styleIndexstructure containing data of a particular type. CFF2Indexis defined here. The purpose ofCFF2IndexOfis to efficiently store a list of variable-sized data, for example glyph records. -
MultiItemVariationStore: This is a new data-structure. It is a hybrid betweenItemVariationStore, andTupleVariationStore, borrowing ideas from both and improving upon them for more efficient storage of variations of tuples of numbers.
To be added to The OpenType Font File Data Types
A uint32var is a variable-length encoding of a uint32. If the number fits in
7 bits, then it is encoded as is. Otherwise, it encodes the number of
subsequent bytes as top bits of the first byte. The subsequent bytes use the
full 8 bits for storage.
TODO: Add table of encoding bytes.
Here is Python code to read and write uint32var values:
def read_uint32var(data, i):
"""Read a variable-length uint32 number from data starting at index i.
Return the number and the next index.
"""
b0 = data[i]
if b0 < 0x80:
return b0, i + 1
elif b0 < 0xC0:
return (b0 - 0x80) << 8 | data[i + 1], i + 2
elif b0 < 0xE0:
return (b0 - 0xC0) << 16 | data[i + 1] << 8 | data[i + 2], i + 3
elif b0 < 0xF0:
return (b0 - 0xE0) << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[
i + 3
], i + 4
else:
return (b0 - 0xF0) << 32 | data[i + 1] << 24 | data[i + 2] << 16 | data[
i + 3
] << 8 | data[i + 4], i + 5def write_uint32var(v):
"""Write a variable-length uint32 number.
Return the data.
"""
if v < 0x80:
return struct.pack(">B", v)
elif v < 0x4000:
return struct.pack(">H", (v | 0x8000))
elif v < 0x200000:
return struct.pack(">L", (v | 0xC00000))[1:]
elif v < 0x10000000:
return struct.pack(">L", (v | 0xE0000000))
else:
return struct.pack(">B", 0xF0) + struct.pack(">L", v)TupleValues is similar to the TupleVariationStore Packed
Deltas
with a minor modification: if the top two bits of the control byte
(DELTAS_ARE_ZERO and DELTAS_ARE_WORDS) are both set, then the following
values are 32-bit. That difference should be incorporated in the Packed Deltas
section of TupleVariationStore, and is backwards-compatible because the two
top bits can currently never be set at the same time.
TupleValues can be used in two different ways:
- When the number of values to be decoded is known in advance, decoding stops when the needed number of values are decoded.
- When the number of values to be decoded is not known in advance, but the number of encoded bytes is known, then values are decoded until the bytes are depleted.
CFF2IndexOf is simply a CFF2-style Index structure containing data of a particular type.
CFF2 Index is defined
here.
The purpose of CFF2IndexOf is to efficiently store a list of variable-sized
data, for example glyph records, or other data.
Note that the count field of CFF2 Index structure is uint32, unlike the
count field of CFF Index structure.
To be added to OpenType Font Variations Common Table Formats.
A MultiItemVariationStore is a new data-structure. It is a hybrid between
ItemVariationStore, and TupleVariationStore, borrowing ideas from both and
improving upon them for more efficient storage of variations of tuples of
numbers.
Like ItemVariationStore, entries are addressed using a 32-bit VarIdx,
with the top 16 bits called "outer" index, and lower 16 bits called the "inner"
index.
Whereas the ItemVariationStore stores deltas for a single scalar value for
each VarIdx, the MultiItemVariationStore stores deltas for a tuple for each
VarIdx. Compared to ItemVariationStore, the MultiItemVariationStore uses a
sparse encoding of the active axes for each region, which is more efficient
in fonts with high number of axes.
Compared to TupleVariationStore, the MultiItemVariationStore is optimized
for smaller tuples and allows tuple-sharing, which is important for its
efficiency over the TupleVariationStore. It also does not have some of the
limitations that TupleVariationStore has, like the total size of an entry
being limited to 64kb.
The following structures form the MultiItemVariationStore. Its processing is
fairly similar to that of the ItemVariationStore, except that the deltas
encoded for each entry consist of multiple numbers per region. The
TupleValues for each entry is as such the concatenation of the tuple deltas
for each region.
struct MultiItemVariationStore
{
uint16 format; // Set to 1
Offset32To<SparseVariationRegionList> variationRegionListOffset;
uint16 itemVariationDataCount;
Offset32To<MultiItemVariationData> itemVariationDataOffsets[itemVariationDataCount];
};struct SparseVariationRegionList
{
uint16 regionCount;
Offset32To<SparseVariationRegion> variationRegionOffsets[regionCount];
}struct SparseVariationRegion
{
uint16 regionAxisCount;
SparseRegionAxisCoordinates regionAxes[regionAxisCount];
};struct SparseRegionAxisCoordinates
{
uint16 axisIndex;
F2DOT14 startCoord;
F2DOT14 peakCoord;
F2DOT14 endCoord;
};struct MultiItemVariationData
{
uint8 Format; // 1
uint16 regionIndexCount;
uint16 regionIndexes[regionIndexCount];
CFF2IndexOf<TupleValues> deltaSets;
};The deltaSets in a MultiItemVariationData table store the delta-set for a
single tuple, addressed by the "inner" index of the VarIdx, whereas a
MultiItemVariationData table itself represents the data for all values
sharing the same "outer" index.
The TupleValues for each entry are the concatenation of the tuple deltas for
each region. The length of the tuple is calculate by dividing the number of
entries in the TupleValues structure by the number of regions.
A Variable Composite record is a concatenation of Variable Component records. Variable Component records have varying sizes.
struct VarCompositeGlyph
{
VarComponent components[];
};When decoding a VarCompositeGlyph, the decoder stops when the bytes for the
glyph are depleted.
A Variable Component record encodes one component's glyph index, variations location, and transformation in a variable-sized and efficient manner.
| type | name | notes |
|---|---|---|
| uint32var | flags |
See below. |
| GlyphID16 or GlyphID24 | gid |
This is a GlyphID16 if GID_IS_24BIT bit of flags is clear, else GlyphID24. |
| uint32var | conditionIndex |
Optional, only present if HAVE_CONDITION bit of flags is set. |
| uint32var | axisIndicesIndex |
Optional, only present if HAVE_AXES bit of flags is set. |
| TupleValues | axisValues |
Optional, only present if HAVE_AXES bit of flags is set. The axis value for each axis. |
| uint32var | axisValuesVarIndex |
Optional, only present if AXIS_VALUES_HAVE_VARIATION bit of flags is set. |
| uint32var | transformVarIndex |
Optional, only present if TRANSFORM_HAS_VARIATION bit of flags is set. |
| FWORD | TranslateX |
Optional, only present if HAVE_TRANSLATE_X bit of flags is set. |
| FWORD | TranslateY |
Optional, only present if HAVE_TRANSLATE_Y bit of flags is set. |
| F4DOT12 | Rotation |
Optional, only present if HAVE_ROTATION bit of flags is set. Counter-clockwise. |
| F6DOT10 | ScaleX |
Optional, only present if HAVE_SCALE_X bit of flags is set. |
| F6DOT10 | ScaleY |
Optional, only present if HAVE_SCALE_Y bit of flags is set. |
| F4DOT12 | SkewX |
Optional, only present if HAVE_SKEW_X bit of flags is set. Counter-clockwise. |
| F4DOT12 | SkewY |
Optional, only present if HAVE_SKEW_Y bit of flags is set. Counter-clockwise. |
| FWORD | TCenterX |
Optional, only present if HAVE_TCENTER_X bit of flags is set. |
| FWORD | TCenterY |
Optional, only present if HAVE_TCENTER_Y bit of flags is set. |
| uint32var[] | reserved |
Optional, process and discard one uint32var per each set bit in RESERVED_MASK. |
| bit number | name |
|---|---|
| 0 | RESET_UNSPECIFIED_AXES |
| 1 | HAVE_AXES |
| 2 | AXIS_VALUES_HAVE_VARIATION |
| 3 | TRANSFORM_HAS_VARIATION |
| 4 | HAVE_TRANSLATE_X |
| 5 | HAVE_TRANSLATE_Y |
| 6 | HAVE_ROTATION |
| 7 | HAVE_CONDITION |
| 8 | HAVE_SCALE_X |
| 9 | HAVE_SCALE_Y |
| 10 | HAVE_TCENTER_X |
| 11 | HAVE_TCENTER_Y |
| 12 | GID_IS_24BIT |
| 13 | HAVE_SKEW_X |
| 14 | HAVE_SKEW_Y |
| 15-31 | RESERVED_MASK. Set to 0 |
The flags are arranged in the order of likely use, to minimize the storage
bytes of the flags field.
The transformation data consists of individual optional fields, which can be used to construct a transformation matrix.
Transformation fields:
| name | default value |
|---|---|
| TranslateX | 0 |
| TranslateY | 0 |
| Rotation | 0 |
| ScaleX | 1 |
| ScaleY | ScaleX |
| SkewX | 0 |
| SkewY | 0 |
| TCenterX | 0 |
| TCenterY | 0 |
The TCenterX and TCenterY values represent the “center of transformation”.
The rotation and skew parameters are in angles as multiples of Pi.
Two new types, F4DOT12 and F6DOT10 need to be added to the Data
Types
section of the specification.
Details of how to build a transformation matrix, as Python code:
# Using fontTools.misc.transform.Transform
t = Transform() # Identity
t = t.translate(TranslateX + TCenterX, TranslateY + TCenterY)
t = t.rotate(Rotation * math.pi)
t = t.scale(ScaleX, ScaleY)
t = t.skew(-SkewX * math.pi, SkewY * math.pi)
t = t.translate(-TCenterX, -TCenterY)The top-level VARC table header is as follows:
struct VARC
{
uint16 majorVersion; // 1
uint16 minorVersion; // 0
Offset32To<Coverage> coverage;
Offset32To<MultiItemVariationStore> varStore;
Offset32To<ConditionList> conditionList;
Offset32To<CFF2IndexOf<TupleValues>> axisIndicesList;
Offset32To<CFF2IndexOf<VarCompositeGlyph>> glyphRecords;
};struct ConditionList
{
Array32Of<Offset32To<Condition>>; // Array of offsets from the beginning of the ConditionList table
}The coverage table enumerates all glyphs that have Variable-Composite records
in this table. The records are encoded in glyphRecords by coverage index as
usual.
The varStore stores the variations of axis-values and transform values as
referenced in the Variable Component records.
The axisIndicesList encodes a list of axis-indices tuples, and is shared by
the glyphs which address it using an index.
The component glyphs to be loaded use the coordinate values specified (with any variations applied if present). The outlines from all components are concatenated to form the outline for the main glyph, before any rasterization.
For each parsed component, if HAVE_CONDITION flag is set, then the component
is loaded but not used (eg. not displayed) unless the referred Condition
(using conditionIndex) in the top-level conditionList evaluates to true.
For Condition types that require a variation-store, the MultiItemVariationStore
varStore shall be used to get deltas for an outer/inner variation numbers,
with a tuple as appropriate.
For any unspecified axis, the value used depends on flag
RESET_UNSPECIFIED_AXES. If the flag is set, then the normalized values from
font's current variation settings is used.
If the flag is clear the axis values from current glyph being processed
(which itself might recursively come from the font or its own parent glyphs)
are used. For example, if the font variations have wght=.25 (normalized),
and current glyph being processed is using wght=.5 because it was referenced
from another VarComposite glyph itself, when referring to a component that does
not specify the wght axis, if the flag bit is set, then the value of
wght=.25 (from font) will be used. If the flag bit is clear, wght=.5 (from
current glyph) will be used.
The component location and transform can vary. These variations are stored in
the MultiItemVariationStore data-structure. The variations for location are
referred to by the axisValuesVarIndex member of a component if any, and
variations for transform are referred to by the transformVarIndex if any. For
transform variations, only those fields specified and as such encoded as per
the flags have variations.
The component then is recursively loaded from the VARC table, if it is
present in this table; otherwise, falls back to glyf/gvar, CFF2, or any
other mechanism to get raw outlines. An exception to the recursive rule is
that if a component refers to the glyphID of the current glyph, instead of
recursing (which would result in infinite recursion), the glyph outline for the
component is loaded directly instead, as if the glyphID had no VARC entry.
Note: While it is the (undocumented?) behavior of the glyf table that
glyphs loaded are shifted to align their LSB to that specified in the hmtx
table, much like regular Composite glyphs, this does not apply to component
glyphs being loaded as part of a variable-composite glyph.
Note: A static (non-variable) font that uses the VARC table, would not
have fvar / avar tables but would have the gvar table in a
TrueType-flavored font, or CFF2 variations in a CFF-flavored font. This is
because the components themselves store their variables in the classic way. The
exception to this situation is a font with VARC table that does NOT vary the
component locations and transforms, and does not encode any location for the
components either. In practice, such a font would just use the VARC table
in the manner of classic components in a glyf table.