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:
-
uint32var
is a variable-length encoding ofuint32
values, 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. -
TupleValues
is borrowed from theTupleVariationStore
Packed Deltas with minor modification to allow storing 32-bit values. -
CFF2IndexOf
is simply a CFF2-styleIndex
structure containing data of a particular type. CFF2Index
is defined here. The purpose ofCFF2IndexOf
is 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 + 5
def 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.