Merge branch 'master' into remove-gvar-zero-workaround
* master: (70 commits)
Bump version: 4.4.0 → 4.4.1.dev0
Release 4.4.0
Update changelog [skip ci]
fontBuilder: allow to build v1 from setupCPAL method
colorLib_test: add tests for buildCPAL v1
colorLib: allow to build CPAL version=1
[CPAL] the absence of a color palette label nameID is 0xFFFF, not 0
colorLib: add type annotations
add tests for buildCORL and buildCPAL
fontBuilder: add setupCOLR and setupCPAL methods
colorLib: add buildCOLR and buildCPAL
ufoLib: don't crash if UFO2 has openTypeHheaCaretOffset=0.0
_g_l_y_f_test: add tests for Glyph.getCoordinates and GlyphComponent.to/fromXML
[glyf] if comp uses anchors compute firstPt-secondPt offset after applying transform
Update NEWS.rst
merger: Convert input checking asserts into proper exceptions
featureVars: Use new exceptions
cff: Use new exceptions
models: Use new exceptions where input is checked
init: Convert input checking asserts into proper exceptions
...
diff --git a/Doc/source/designspaceLib/readme.rst b/Doc/source/designspaceLib/readme.rst
index 69d6af1..d563e85 100644
--- a/Doc/source/designspaceLib/readme.rst
+++ b/Doc/source/designspaceLib/readme.rst
@@ -30,6 +30,10 @@
different objects, as long as they have the same attributes. Reader and
Writer objects can be subclassed as well.
+**Note:** Python attribute names are usually camelCased, the
+corresponding `XML <#document-xml-structure>`_ attributes are usually
+all lowercase.
+
.. example-1:
.. code:: python
diff --git a/Lib/fontTools/__init__.py b/Lib/fontTools/__init__.py
index 0db40fc..089ac7b 100644
--- a/Lib/fontTools/__init__.py
+++ b/Lib/fontTools/__init__.py
@@ -4,6 +4,6 @@
log = logging.getLogger(__name__)
-version = __version__ = "4.2.3.dev0"
+version = __version__ = "4.4.1.dev0"
__all__ = ["version", "log", "configLogger"]
diff --git a/Lib/fontTools/colorLib/__init__.py b/Lib/fontTools/colorLib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Lib/fontTools/colorLib/__init__.py
diff --git a/Lib/fontTools/colorLib/builder.py b/Lib/fontTools/colorLib/builder.py
new file mode 100644
index 0000000..486909e
--- /dev/null
+++ b/Lib/fontTools/colorLib/builder.py
@@ -0,0 +1,147 @@
+import enum
+from typing import Dict, Iterable, List, Optional, Tuple, Union
+from fontTools.ttLib.tables.C_O_L_R_ import LayerRecord, table_C_O_L_R_
+from fontTools.ttLib.tables.C_P_A_L_ import Color, table_C_P_A_L_
+from fontTools.ttLib.tables._n_a_m_e import table__n_a_m_e
+from .errors import ColorLibError
+
+
+def buildCOLR(colorLayers: Dict[str, List[Tuple[str, int]]]) -> table_C_O_L_R_:
+ """Build COLR table from color layers mapping.
+
+ Args:
+ colorLayers: : map of base glyph names to lists of (layer glyph names,
+ palette indices) tuples.
+
+ Return:
+ A new COLRv0 table.
+ """
+ colorLayerLists = {}
+ for baseGlyphName, layers in colorLayers.items():
+ colorLayerLists[baseGlyphName] = [
+ LayerRecord(layerGlyphName, colorID) for layerGlyphName, colorID in layers
+ ]
+
+ colr = table_C_O_L_R_()
+ colr.version = 0
+ colr.ColorLayers = colorLayerLists
+ return colr
+
+
+class ColorPaletteType(enum.IntFlag):
+ USABLE_WITH_LIGHT_BACKGROUND = 0x0001
+ USABLE_WITH_DARK_BACKGROUND = 0x0002
+
+ @classmethod
+ def _missing_(cls, value):
+ # enforce reserved bits
+ if isinstance(value, int) and (value < 0 or value & 0xFFFC != 0):
+ raise ValueError(f"{value} is not a valid {cls.__name__}")
+ return super()._missing_(value)
+
+
+# None, 'abc' or {'en': 'abc', 'de': 'xyz'}
+_OptionalLocalizedString = Union[None, str, Dict[str, str]]
+
+
+def buildPaletteLabels(
+ labels: List[_OptionalLocalizedString], nameTable: table__n_a_m_e
+) -> List[Optional[int]]:
+ return [
+ nameTable.addMultilingualName(l, mac=False)
+ if isinstance(l, dict)
+ else table_C_P_A_L_.NO_NAME_ID
+ if l is None
+ else nameTable.addMultilingualName({"en": l}, mac=False)
+ for l in labels
+ ]
+
+
+def buildCPAL(
+ palettes: List[List[Tuple[float, float, float, float]]],
+ paletteTypes: Optional[List[ColorPaletteType]] = None,
+ paletteLabels: Optional[List[_OptionalLocalizedString]] = None,
+ paletteEntryLabels: Optional[List[_OptionalLocalizedString]] = None,
+ nameTable: Optional[table__n_a_m_e] = None,
+) -> table_C_P_A_L_:
+ """Build CPAL table from list of color palettes.
+
+ Args:
+ palettes: list of lists of colors encoded as tuples of (R, G, B, A) floats
+ in the range [0..1].
+ paletteTypes: optional list of ColorPaletteType, one for each palette.
+ paletteLabels: optional list of palette labels. Each lable can be either:
+ None (no label), a string (for for default English labels), or a
+ localized string (as a dict keyed with BCP47 language codes).
+ paletteEntryLabels: optional list of palette entry labels, one for each
+ palette entry (see paletteLabels).
+ nameTable: optional name table where to store palette and palette entry
+ labels. Required if either paletteLabels or paletteEntryLabels is set.
+
+ Return:
+ A new CPAL v0 or v1 table, if custom palette types or labels are specified.
+ """
+ if len({len(p) for p in palettes}) != 1:
+ raise ColorLibError("color palettes have different lengths")
+
+ if (paletteLabels or paletteEntryLabels) and not nameTable:
+ raise TypeError(
+ "nameTable is required if palette or palette entries have labels"
+ )
+
+ cpal = table_C_P_A_L_()
+ cpal.numPaletteEntries = len(palettes[0])
+
+ cpal.palettes = []
+ for i, palette in enumerate(palettes):
+ colors = []
+ for j, color in enumerate(palette):
+ if not isinstance(color, tuple) or len(color) != 4:
+ raise ColorLibError(
+ f"In palette[{i}][{j}]: expected (R, G, B, A) tuple, got {color!r}"
+ )
+ if any(v > 1 or v < 0 for v in color):
+ raise ColorLibError(
+ f"palette[{i}][{j}] has invalid out-of-range [0..1] color: {color!r}"
+ )
+ # input colors are RGBA, CPAL encodes them as BGRA
+ red, green, blue, alpha = color
+ colors.append(Color(*(round(v * 255) for v in (blue, green, red, alpha))))
+ cpal.palettes.append(colors)
+
+ if any(v is not None for v in (paletteTypes, paletteLabels, paletteEntryLabels)):
+ cpal.version = 1
+
+ if paletteTypes is not None:
+ if len(paletteTypes) != len(palettes):
+ raise ColorLibError(
+ f"Expected {len(palettes)} paletteTypes, got {len(paletteTypes)}"
+ )
+ cpal.paletteTypes = [ColorPaletteType(t).value for t in paletteTypes]
+ else:
+ cpal.paletteTypes = [table_C_P_A_L_.DEFAULT_PALETTE_TYPE] * len(palettes)
+
+ if paletteLabels is not None:
+ if len(paletteLabels) != len(palettes):
+ raise ColorLibError(
+ f"Expected {len(palettes)} paletteLabels, got {len(paletteLabels)}"
+ )
+ cpal.paletteLabels = buildPaletteLabels(paletteLabels, nameTable)
+ else:
+ cpal.paletteLabels = [table_C_P_A_L_.NO_NAME_ID] * len(palettes)
+
+ if paletteEntryLabels is not None:
+ if len(paletteEntryLabels) != cpal.numPaletteEntries:
+ raise ColorLibError(
+ f"Expected {cpal.numPaletteEntries} paletteEntryLabels, "
+ f"got {len(paletteEntryLabels)}"
+ )
+ cpal.paletteEntryLabels = buildPaletteLabels(paletteEntryLabels, nameTable)
+ else:
+ cpal.paletteEntryLabels = [
+ table_C_P_A_L_.NO_NAME_ID
+ ] * cpal.numPaletteEntries
+ else:
+ cpal.version = 0
+
+ return cpal
diff --git a/Lib/fontTools/colorLib/errors.py b/Lib/fontTools/colorLib/errors.py
new file mode 100644
index 0000000..a0bdda1
--- /dev/null
+++ b/Lib/fontTools/colorLib/errors.py
@@ -0,0 +1,3 @@
+
+class ColorLibError(Exception):
+ pass
diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py
index 1836ebb..6e65437 100644
--- a/Lib/fontTools/feaLib/builder.py
+++ b/Lib/fontTools/feaLib/builder.py
@@ -679,15 +679,17 @@
self.cur_lookup_name_ = name
self.named_lookups_[name] = None
self.cur_lookup_ = None
- self.lookupflag_ = 0
- self.lookupflag_markFilterSet_ = None
+ if self.cur_feature_name_ is None:
+ self.lookupflag_ = 0
+ self.lookupflag_markFilterSet_ = None
def end_lookup_block(self):
assert self.cur_lookup_name_ is not None
self.cur_lookup_name_ = None
self.cur_lookup_ = None
- self.lookupflag_ = 0
- self.lookupflag_markFilterSet_ = None
+ if self.cur_feature_name_ is None:
+ self.lookupflag_ = 0
+ self.lookupflag_markFilterSet_ = None
def add_lookup_call(self, lookup_name):
assert lookup_name in self.named_lookups_, lookup_name
@@ -892,9 +894,17 @@
return
lookup = self.get_lookup_(location, MultipleSubstBuilder)
if glyph in lookup.mapping:
- raise FeatureLibError(
- 'Already defined substitution for glyph "%s"' % glyph,
- location)
+ if replacements == lookup.mapping[glyph]:
+ log.info(
+ 'Removing duplicate multiple substitution from glyph'
+ ' "%s" to %s%s',
+ glyph, replacements,
+ ' at {}:{}:{}'.format(*location) if location else '',
+ )
+ else:
+ raise FeatureLibError(
+ 'Already defined substitution for glyph "%s"' % glyph,
+ location)
lookup.mapping[glyph] = replacements
def add_reverse_chain_single_subst(self, location, old_prefix,
diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py
index d8670d6..bda34e2 100644
--- a/Lib/fontTools/feaLib/parser.py
+++ b/Lib/fontTools/feaLib/parser.py
@@ -19,6 +19,14 @@
def __init__(self, featurefile, glyphNames=(), followIncludes=True,
**kwargs):
+ """Initializes a Parser object.
+
+ Note: the `glyphNames` iterable serves a double role to help distinguish
+ glyph names from ranges in the presence of hyphens and to ensure that glyph
+ names referenced in a feature file are actually part of a font's glyph set.
+ If the iterable is left empty, no glyph name in glyph set checking takes
+ place.
+ """
if "glyphMap" in kwargs:
from fontTools.misc.loggingTools import deprecateArgument
deprecateArgument("glyphMap", "use 'glyphNames' (iterable) instead")
@@ -268,6 +276,7 @@
if (accept_glyphname and
self.next_token_type_ in (Lexer.NAME, Lexer.CID)):
glyph = self.expect_glyph_()
+ self.check_glyph_name_in_glyph_set(glyph)
return self.ast.GlyphName(glyph, location=self.cur_token_location_)
if self.next_token_type_ is Lexer.GLYPHCLASS:
self.advance_lexer_()
@@ -292,6 +301,7 @@
location = self.cur_token_location_
if '-' in glyph and glyph not in self.glyphNames_:
start, limit = self.split_glyph_range_(glyph, location)
+ self.check_glyph_name_in_glyph_set(start, limit)
glyphs.add_range(
start, limit,
self.make_glyph_range_(location, start, limit))
@@ -299,10 +309,12 @@
start = glyph
self.expect_symbol_("-")
limit = self.expect_glyph_()
+ self.check_glyph_name_in_glyph_set(start, limit)
glyphs.add_range(
start, limit,
self.make_glyph_range_(location, start, limit))
else:
+ self.check_glyph_name_in_glyph_set(glyph)
glyphs.append(glyph)
elif self.next_token_type_ is Lexer.CID:
glyph = self.expect_glyph_()
@@ -311,11 +323,17 @@
range_start = self.cur_token_
self.expect_symbol_("-")
range_end = self.expect_cid_()
+ self.check_glyph_name_in_glyph_set(
+ f"cid{range_start:05d}",
+ f"cid{range_end:05d}",
+ )
glyphs.add_cid_range(range_start, range_end,
self.make_cid_range_(range_location,
range_start, range_end))
else:
- glyphs.append("cid%05d" % self.cur_token_)
+ glyph_name = f"cid{self.cur_token_:05d}"
+ self.check_glyph_name_in_glyph_set(glyph_name)
+ glyphs.append(glyph_name)
elif self.next_token_type_ is Lexer.GLYPHCLASS:
self.advance_lexer_()
gc = self.glyphclasses_.resolve(self.cur_token_)
@@ -705,7 +723,7 @@
else:
keyword = None
self.expect_symbol_(";")
- if len(new) is 0 and not any(lookups):
+ if len(new) == 0 and not any(lookups):
raise FeatureLibError(
'Expected "by", "from" or explicit lookup references',
self.cur_token_location_)
@@ -1452,12 +1470,21 @@
# Upgrade all single substitutions to multiple substitutions.
if has_single and has_multiple:
- for i, s in enumerate(statements):
+ statements = []
+ for s in block.statements:
if isinstance(s, self.ast.SingleSubstStatement):
- statements[i] = self.ast.MultipleSubstStatement(
- s.prefix, s.glyphs[0].glyphSet()[0], s.suffix,
- [r.glyphSet()[0] for r in s.replacements],
- s.forceChain, location=s.location)
+ glyphs = s.glyphs[0].glyphSet()
+ replacements = s.replacements[0].glyphSet()
+ if len(replacements) == 1:
+ replacements *= len(glyphs)
+ for i, glyph in enumerate(glyphs):
+ statements.append(
+ self.ast.MultipleSubstStatement(
+ s.prefix, glyph, s.suffix, [replacements[i]],
+ s.forceChain, location=s.location))
+ else:
+ statements.append(s)
+ block.statements = statements
def is_cur_keyword_(self, k):
if self.cur_token_type_ is Lexer.NAME:
@@ -1500,6 +1527,21 @@
raise FeatureLibError("Expected a glyph name or CID",
self.cur_token_location_)
+ def check_glyph_name_in_glyph_set(self, *names):
+ """Raises if glyph name (just `start`) or glyph names of a
+ range (`start` and `end`) are not in the glyph set.
+
+ If no glyph set is present, does nothing.
+ """
+ if self.glyphNames_:
+ missing = [name for name in names if name not in self.glyphNames_]
+ if missing:
+ raise FeatureLibError(
+ "The following glyph names are referenced but are missing from the "
+ f"glyph set: {', '.join(missing)}",
+ self.cur_token_location_
+ )
+
def expect_markClass_reference_(self):
name = self.expect_class_name_()
mc = self.glyphclasses_.resolve(name)
diff --git a/Lib/fontTools/fontBuilder.py b/Lib/fontTools/fontBuilder.py
index 29d58fb..a08442a 100644
--- a/Lib/fontTools/fontBuilder.py
+++ b/Lib/fontTools/fontBuilder.py
@@ -602,7 +602,10 @@
varData = buildVarData(list(range(len(regions))), None, optimize=False)
varStore = buildVarStore(varRegionList, [varData])
vstore = VarStoreData(otVarStore=varStore)
- self.font["CFF2"].cff.topDictIndex[0].VarStore = vstore
+ topDict = self.font["CFF2"].cff.topDictIndex[0]
+ topDict.VarStore = vstore
+ for fontDict in topDict.FDArray:
+ fontDict.Private.vstore = vstore
def setupGlyf(self, glyphs, calcGlyphBounds=True):
"""Create the `glyf` table from a dict, that maps glyph names
@@ -765,6 +768,39 @@
self.font, conditionalSubstitutions, featureTag=featureTag
)
+ def setupCOLR(self, colorLayers):
+ """Build new COLR table using color layers dictionary.
+
+ Cf. `fontTools.colorLib.builder.buildCOLR`.
+ """
+ from fontTools.colorLib.builder import buildCOLR
+
+ self.font["COLR"] = buildCOLR(colorLayers)
+
+ def setupCPAL(
+ self,
+ palettes,
+ paletteTypes=None,
+ paletteLabels=None,
+ paletteEntryLabels=None,
+ ):
+ """Build new CPAL table using list of palettes.
+
+ Optionally build CPAL v1 table using paletteTypes, paletteLabels and
+ paletteEntryLabels.
+
+ Cf. `fontTools.colorLib.builder.buildCPAL`.
+ """
+ from fontTools.colorLib.builder import buildCPAL
+
+ self.font["CPAL"] = buildCPAL(
+ palettes,
+ paletteTypes=paletteTypes,
+ paletteLabels=paletteLabels,
+ paletteEntryLabels=paletteEntryLabels,
+ nameTable=self.font.get("name")
+ )
+
def buildCmapSubTable(cmapping, format, platformID, platEncID):
subTable = cmap_classes[format](format)
diff --git a/Lib/fontTools/mtiLib/__init__.py b/Lib/fontTools/mtiLib/__init__.py
index 67e99d7..f0f0a13 100644
--- a/Lib/fontTools/mtiLib/__init__.py
+++ b/Lib/fontTools/mtiLib/__init__.py
@@ -854,7 +854,7 @@
lookup.SubTable = subtables
lookup.SubTableCount = len(lookup.SubTable)
- if lookup.SubTableCount is 0:
+ if lookup.SubTableCount == 0:
# Remove this return when following is fixed:
# https://github.com/fonttools/fonttools/issues/789
return None
diff --git a/Lib/fontTools/pens/ttGlyphPen.py b/Lib/fontTools/pens/ttGlyphPen.py
index f7b1483..0b64cb3 100644
--- a/Lib/fontTools/pens/ttGlyphPen.py
+++ b/Lib/fontTools/pens/ttGlyphPen.py
@@ -1,6 +1,6 @@
from fontTools.misc.py23 import *
from array import array
-from fontTools.misc.fixedTools import MAX_F2DOT14, otRound
+from fontTools.misc.fixedTools import MAX_F2DOT14, otRound, floatToFixedToFloat
from fontTools.pens.basePen import LoggingPen
from fontTools.pens.transformPen import TransformPen
from fontTools.ttLib.tables import ttProgram
@@ -119,7 +119,11 @@
component = GlyphComponent()
component.glyphName = glyphName
component.x, component.y = (otRound(v) for v in transformation[4:])
- transformation = transformation[:4]
+ # quantize floats to F2Dot14 so we get same values as when decompiled
+ # from a binary glyf table
+ transformation = tuple(
+ floatToFixedToFloat(v, 14) for v in transformation[:4]
+ )
if transformation != (1, 0, 0, 1):
if (self.handleOverflowingTransforms and
any(MAX_F2DOT14 < s <= 2 for s in transformation)):
diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py
index 7205c34..fadda34 100644
--- a/Lib/fontTools/subset/__init__.py
+++ b/Lib/fontTools/subset/__init__.py
@@ -546,6 +546,11 @@
if not options.hinting:
# Drop device tables
self.ValueFormat &= ~0x00F0
+ # Downgrade to Format 1 if all ValueRecords are the same
+ if self.Format == 2 and all(v == self.Value[0] for v in self.Value):
+ self.Format = 1
+ self.Value = self.Value[0] if self.ValueFormat != 0 else None
+ del self.ValueCount
return True
@_add_method(otTables.PairPos)
diff --git a/Lib/fontTools/ttLib/tables/C_P_A_L_.py b/Lib/fontTools/ttLib/tables/C_P_A_L_.py
index f041560..a7b4ad2 100644
--- a/Lib/fontTools/ttLib/tables/C_P_A_L_.py
+++ b/Lib/fontTools/ttLib/tables/C_P_A_L_.py
@@ -13,6 +13,9 @@
class table_C_P_A_L_(DefaultTable.DefaultTable):
+ NO_NAME_ID = 0xFFFF
+ DEFAULT_PALETTE_TYPE = 0
+
def __init__(self, tag=None):
DefaultTable.DefaultTable.__init__(self, tag)
self.palettes = []
@@ -45,24 +48,25 @@
offsetToPaletteEntryLabelArray) = (
struct.unpack(">LLL", data[pos:pos+12]))
self.paletteTypes = self._decompileUInt32Array(
- data, offsetToPaletteTypeArray, numPalettes)
+ data, offsetToPaletteTypeArray, numPalettes,
+ default=self.DEFAULT_PALETTE_TYPE)
self.paletteLabels = self._decompileUInt16Array(
- data, offsetToPaletteLabelArray, numPalettes)
+ data, offsetToPaletteLabelArray, numPalettes, default=self.NO_NAME_ID)
self.paletteEntryLabels = self._decompileUInt16Array(
data, offsetToPaletteEntryLabelArray,
- self.numPaletteEntries)
+ self.numPaletteEntries, default=self.NO_NAME_ID)
- def _decompileUInt16Array(self, data, offset, numElements):
+ def _decompileUInt16Array(self, data, offset, numElements, default=0):
if offset == 0:
- return [0] * numElements
+ return [default] * numElements
result = array.array("H", data[offset : offset + 2 * numElements])
if sys.byteorder != "big": result.byteswap()
assert len(result) == numElements, result
return result.tolist()
- def _decompileUInt32Array(self, data, offset, numElements):
+ def _decompileUInt32Array(self, data, offset, numElements, default=0):
if offset == 0:
- return [0] * numElements
+ return [default] * numElements
result = array.array("I", data[offset : offset + 4 * numElements])
if sys.byteorder != "big": result.byteswap()
assert len(result) == numElements, result
@@ -136,7 +140,7 @@
return result
def _compilePaletteLabels(self):
- if self.version == 0 or not any(self.paletteLabels):
+ if self.version == 0 or all(l == self.NO_NAME_ID for l in self.paletteLabels):
return b''
assert len(self.paletteLabels) == len(self.palettes)
result = bytesjoin([struct.pack(">H", label)
@@ -145,7 +149,7 @@
return result
def _compilePaletteEntryLabels(self):
- if self.version == 0 or not any(self.paletteEntryLabels):
+ if self.version == 0 or all(l == self.NO_NAME_ID for l in self.paletteEntryLabels):
return b''
assert len(self.paletteEntryLabels) == self.numPaletteEntries
result = bytesjoin([struct.pack(">H", label)
@@ -165,15 +169,15 @@
writer.newline()
for index, palette in enumerate(self.palettes):
attrs = {"index": index}
- paletteType = paletteTypes.get(index)
- paletteLabel = paletteLabels.get(index)
- if self.version > 0 and paletteLabel is not None:
+ paletteType = paletteTypes.get(index, self.DEFAULT_PALETTE_TYPE)
+ paletteLabel = paletteLabels.get(index, self.NO_NAME_ID)
+ if self.version > 0 and paletteLabel != self.NO_NAME_ID:
attrs["label"] = paletteLabel
- if self.version > 0 and paletteType is not None:
+ if self.version > 0 and paletteType != self.DEFAULT_PALETTE_TYPE:
attrs["type"] = paletteType
writer.begintag("palette", **attrs)
writer.newline()
- if (self.version > 0 and paletteLabel and
+ if (self.version > 0 and paletteLabel != self.NO_NAME_ID and
ttFont and "name" in ttFont):
name = ttFont["name"].getDebugName(paletteLabel)
if name is not None:
@@ -184,11 +188,11 @@
color.toXML(writer, ttFont, cindex)
writer.endtag("palette")
writer.newline()
- if self.version > 0 and any(self.paletteEntryLabels):
+ if self.version > 0 and not all(l == self.NO_NAME_ID for l in self.paletteEntryLabels):
writer.begintag("paletteEntryLabels")
writer.newline()
for index, label in enumerate(self.paletteEntryLabels):
- if label:
+ if label != self.NO_NAME_ID:
writer.simpletag("label", index=index, value=label)
if (self.version > 0 and label and ttFont and "name" in ttFont):
name = ttFont["name"].getDebugName(label)
@@ -200,8 +204,8 @@
def fromXML(self, name, attrs, content, ttFont):
if name == "palette":
- self.paletteLabels.append(int(attrs.get("label", "0")))
- self.paletteTypes.append(int(attrs.get("type", "0")))
+ self.paletteLabels.append(int(attrs.get("label", self.NO_NAME_ID)))
+ self.paletteTypes.append(int(attrs.get("type", self.DEFAULT_PALETTE_TYPE)))
palette = []
for element in content:
if isinstance(element, basestring):
@@ -221,13 +225,13 @@
nameID = safeEval(elementAttr["value"])
colorLabels[labelIndex] = nameID
self.paletteEntryLabels = [
- colorLabels.get(i, 0)
+ colorLabels.get(i, self.NO_NAME_ID)
for i in range(self.numPaletteEntries)]
elif "value" in attrs:
value = safeEval(attrs["value"])
setattr(self, name, value)
if name == "numPaletteEntries":
- self.paletteEntryLabels = [0] * self.numPaletteEntries
+ self.paletteEntryLabels = [self.NO_NAME_ID] * self.numPaletteEntries
class Color(namedtuple("Color", "blue green red alpha")):
diff --git a/Lib/fontTools/ttLib/tables/E_B_L_C_.py b/Lib/fontTools/ttLib/tables/E_B_L_C_.py
index 224042d..f71ae95 100644
--- a/Lib/fontTools/ttLib/tables/E_B_L_C_.py
+++ b/Lib/fontTools/ttLib/tables/E_B_L_C_.py
@@ -482,7 +482,7 @@
dataList = [EblcIndexSubTable.compile(self, ttFont)]
dataList += [struct.pack(dataFormat, offsetValue) for offsetValue in offsetArray]
# Take care of any padding issues. Only occurs in format 3.
- if offsetDataSize * len(dataList) % 4 != 0:
+ if offsetDataSize * len(offsetArray) % 4 != 0:
dataList.append(struct.pack(dataFormat, 0))
return bytesjoin(dataList)
diff --git a/Lib/fontTools/ttLib/tables/_c_m_a_p.py b/Lib/fontTools/ttLib/tables/_c_m_a_p.py
index 2fdc8c3..f7f9266 100644
--- a/Lib/fontTools/ttLib/tables/_c_m_a_p.py
+++ b/Lib/fontTools/ttLib/tables/_c_m_a_p.py
@@ -18,7 +18,7 @@
cmap = {}
glyphOrder = font.getGlyphOrder()
for char,gid in zip(chars,gids):
- if gid is 0:
+ if gid == 0:
continue
try:
name = glyphOrder[gid]
diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
index cc22ad0..a6cd1fc 100644
--- a/Lib/fontTools/ttLib/tables/_g_l_y_f.py
+++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
@@ -122,6 +122,12 @@
ttFont['loca'].set(locations)
if 'maxp' in ttFont:
ttFont['maxp'].numGlyphs = len(self.glyphs)
+ if not data:
+ # As a special case when all glyph in the font are empty, add a zero byte
+ # to the table, so that OTS doesn’t reject it, and to make the table work
+ # on Windows as well.
+ # See https://github.com/khaledhosny/ots/issues/52
+ data = b"\0"
return data
def toXML(self, writer, ttFont, splitGlyphs=False):
@@ -1006,33 +1012,37 @@
coordinates, endPts, flags = g.getCoordinates(glyfTable)
except RecursionError:
raise ttLib.TTLibError("glyph '%s' contains a recursive component reference" % compo.glyphName)
+ coordinates = GlyphCoordinates(coordinates)
if hasattr(compo, "firstPt"):
- # move according to two reference points
+ # component uses two reference points: we apply the transform _before_
+ # computing the offset between the points
+ if hasattr(compo, "transform"):
+ coordinates.transform(compo.transform)
x1,y1 = allCoords[compo.firstPt]
x2,y2 = coordinates[compo.secondPt]
move = x1-x2, y1-y2
- else:
- move = compo.x, compo.y
-
- coordinates = GlyphCoordinates(coordinates)
- if not hasattr(compo, "transform"):
coordinates.translate(move)
else:
- apple_way = compo.flags & SCALED_COMPONENT_OFFSET
- ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET
- assert not (apple_way and ms_way)
- if not (apple_way or ms_way):
- scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT # see top of this file
- else:
- scale_component_offset = apple_way
- if scale_component_offset:
- # the Apple way: first move, then scale (ie. scale the component offset)
+ # component uses XY offsets
+ move = compo.x, compo.y
+ if not hasattr(compo, "transform"):
coordinates.translate(move)
- coordinates.transform(compo.transform)
else:
- # the MS way: first scale, then move
- coordinates.transform(compo.transform)
- coordinates.translate(move)
+ apple_way = compo.flags & SCALED_COMPONENT_OFFSET
+ ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET
+ assert not (apple_way and ms_way)
+ if not (apple_way or ms_way):
+ scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT # see top of this file
+ else:
+ scale_component_offset = apple_way
+ if scale_component_offset:
+ # the Apple way: first move, then scale (ie. scale the component offset)
+ coordinates.translate(move)
+ coordinates.transform(compo.transform)
+ else:
+ # the MS way: first scale, then move
+ coordinates.transform(compo.transform)
+ coordinates.translate(move)
offset = len(allCoords)
allEndPts.extend(e + offset for e in endPts)
allCoords.extend(coordinates)
diff --git a/Lib/fontTools/ttLib/tables/_h_e_a_d.py b/Lib/fontTools/ttLib/tables/_h_e_a_d.py
index 5afc925..154669a 100644
--- a/Lib/fontTools/ttLib/tables/_h_e_a_d.py
+++ b/Lib/fontTools/ttLib/tables/_h_e_a_d.py
@@ -4,7 +4,7 @@
from fontTools.misc.textTools import safeEval, num2binary, binary2num
from fontTools.misc.timeTools import timestampFromString, timestampToString, timestampNow
from fontTools.misc.timeTools import epoch_diff as mac_epoch_diff # For backward compat
-from fontTools.misc.arrayTools import intRect
+from fontTools.misc.arrayTools import intRect, unionRect
from . import DefaultTable
import logging
@@ -34,14 +34,14 @@
class table__h_e_a_d(DefaultTable.DefaultTable):
- dependencies = ['maxp', 'loca', 'CFF ']
+ dependencies = ['maxp', 'loca', 'CFF ', 'CFF2']
def decompile(self, data, ttFont):
dummy, rest = sstruct.unpack2(headFormat, data, self)
if rest:
# this is quite illegal, but there seem to be fonts out there that do this
log.warning("extra bytes at the end of 'head' table")
- assert rest == "\0\0"
+ assert rest == b"\0\0"
# For timestamp fields, ignore the top four bytes. Some fonts have
# bogus values there. Since till 2038 those bytes only can be zero,
@@ -65,6 +65,19 @@
if 'CFF ' in ttFont:
topDict = ttFont['CFF '].cff.topDictIndex[0]
self.xMin, self.yMin, self.xMax, self.yMax = intRect(topDict.FontBBox)
+ elif 'CFF2' in ttFont:
+ topDict = ttFont['CFF2'].cff.topDictIndex[0]
+ charStrings = topDict.CharStrings
+ fontBBox = None
+ for charString in charStrings.values():
+ bounds = charString.calcBounds(charStrings)
+ if bounds is not None:
+ if fontBBox is not None:
+ fontBBox = unionRect(fontBBox, bounds)
+ else:
+ fontBBox = bounds
+ if fontBBox is not None:
+ self.xMin, self.yMin, self.xMax, self.yMax = intRect(fontBBox)
if ttFont.recalcTimestamp:
self.modified = timestampNow()
data = sstruct.pack(headFormat, self)
diff --git a/Lib/fontTools/ttLib/tables/_h_h_e_a.py b/Lib/fontTools/ttLib/tables/_h_h_e_a.py
index 0f5ec51..4d93b28 100644
--- a/Lib/fontTools/ttLib/tables/_h_h_e_a.py
+++ b/Lib/fontTools/ttLib/tables/_h_h_e_a.py
@@ -33,7 +33,7 @@
# Note: Keep in sync with table__v_h_e_a
- dependencies = ['hmtx', 'glyf', 'CFF ']
+ dependencies = ['hmtx', 'glyf', 'CFF ', 'CFF2']
# OpenType spec renamed these, add aliases for compatibility
@property
@@ -52,7 +52,7 @@
sstruct.unpack(hheaFormat, data, self)
def compile(self, ttFont):
- if ttFont.recalcBBoxes and (ttFont.isLoaded('glyf') or ttFont.isLoaded('CFF ')):
+ if ttFont.recalcBBoxes and (ttFont.isLoaded('glyf') or ttFont.isLoaded('CFF ') or ttFont.isLoaded('CFF2')):
self.recalc(ttFont)
self.tableVersion = fi2ve(self.tableVersion)
return sstruct.pack(hheaFormat, self)
@@ -74,8 +74,11 @@
# Calculate those.
g.recalcBounds(glyfTable)
boundsWidthDict[name] = g.xMax - g.xMin
- elif 'CFF ' in ttFont:
- topDict = ttFont['CFF '].cff.topDictIndex[0]
+ elif 'CFF ' in ttFont or 'CFF2' in ttFont:
+ if 'CFF ' in ttFont:
+ topDict = ttFont['CFF '].cff.topDictIndex[0]
+ else:
+ topDict = ttFont['CFF2'].cff.topDictIndex[0]
charStrings = topDict.CharStrings
for name in ttFont.getGlyphOrder():
cs = charStrings[name]
diff --git a/Lib/fontTools/ttLib/tables/_v_h_e_a.py b/Lib/fontTools/ttLib/tables/_v_h_e_a.py
index 2fc4b01..55ba45a 100644
--- a/Lib/fontTools/ttLib/tables/_v_h_e_a.py
+++ b/Lib/fontTools/ttLib/tables/_v_h_e_a.py
@@ -32,13 +32,13 @@
# Note: Keep in sync with table__h_h_e_a
- dependencies = ['vmtx', 'glyf', 'CFF ']
+ dependencies = ['vmtx', 'glyf', 'CFF ', 'CFF2']
def decompile(self, data, ttFont):
sstruct.unpack(vheaFormat, data, self)
def compile(self, ttFont):
- if ttFont.recalcBBoxes and (ttFont.isLoaded('glyf') or ttFont.isLoaded('CFF ')):
+ if ttFont.recalcBBoxes and (ttFont.isLoaded('glyf') or ttFont.isLoaded('CFF ') or ttFont.isLoaded('CFF2')):
self.recalc(ttFont)
self.tableVersion = fi2ve(self.tableVersion)
return sstruct.pack(vheaFormat, self)
@@ -60,8 +60,11 @@
# Calculate those.
g.recalcBounds(glyfTable)
boundsHeightDict[name] = g.yMax - g.yMin
- elif 'CFF ' in ttFont:
- topDict = ttFont['CFF '].cff.topDictIndex[0]
+ elif 'CFF ' in ttFont or 'CFF2' in ttFont:
+ if 'CFF ' in ttFont:
+ topDict = ttFont['CFF '].cff.topDictIndex[0]
+ else:
+ topDict = ttFont['CFF2'].cff.topDictIndex[0]
charStrings = topDict.CharStrings
for name in ttFont.getGlyphOrder():
cs = charStrings[name]
diff --git a/Lib/fontTools/ttLib/tables/otTables.py b/Lib/fontTools/ttLib/tables/otTables.py
index fa00ca0..302c9b2 100644
--- a/Lib/fontTools/ttLib/tables/otTables.py
+++ b/Lib/fontTools/ttLib/tables/otTables.py
@@ -1415,6 +1415,7 @@
oldMarkCoverage.append(glyphName)
oldMarkRecords.append(markRecord)
else:
+ markRecord.Class -= oldClassCount
newMarkCoverage.append(glyphName)
newMarkRecords.append(markRecord)
diff --git a/Lib/fontTools/ufoLib/__init__.py b/Lib/fontTools/ufoLib/__init__.py
index fb09bf6..4b86437 100755
--- a/Lib/fontTools/ufoLib/__init__.py
+++ b/Lib/fontTools/ufoLib/__init__.py
@@ -2146,18 +2146,14 @@
"""
if attr in _ufo2To3FloatToInt:
try:
- v = int(round(value))
+ value = round(value)
except (ValueError, TypeError):
raise UFOLibError("Could not convert value for %s." % attr)
- if v != value:
- value = v
if attr in _ufo2To3NonNegativeInt:
try:
- v = int(abs(value))
+ value = int(abs(value))
except (ValueError, TypeError):
raise UFOLibError("Could not convert value for %s." % attr)
- if v != value:
- value = v
elif attr in _ufo2To3NonNegativeIntOrFloat:
try:
v = float(abs(value))
diff --git a/Lib/fontTools/unicodedata/__init__.py b/Lib/fontTools/unicodedata/__init__.py
index 89528d7..8c2b58f 100644
--- a/Lib/fontTools/unicodedata/__init__.py
+++ b/Lib/fontTools/unicodedata/__init__.py
@@ -190,6 +190,14 @@
# Unicode-9.0 additions
'Adlm', # Adlam
+
+ # Unicode-11.0 additions
+ 'Rohg', # Hanifi Rohingya
+ 'Sogo', # Old Sogdian
+ 'Sogd', # Sogdian
+
+ # Unicode-12.0 additions
+ 'Elym', # Elymaic
}
def script_horizontal_direction(script_code, default=KeyError):
diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py
index 1e8e986..6df39fa 100644
--- a/Lib/fontTools/varLib/__init__.py
+++ b/Lib/fontTools/varLib/__init__.py
@@ -39,13 +39,11 @@
import logging
from copy import deepcopy
from pprint import pformat
+from .errors import VarLibError, VarLibValidationError
log = logging.getLogger("fontTools.varLib")
-class VarLibError(Exception):
- pass
-
#
# Creation routines
#
@@ -81,7 +79,12 @@
coordinates = instance.location
if "en" not in instance.localisedStyleName:
- assert instance.styleName
+ if not instance.styleName:
+ raise VarLibValidationError(
+ f"Instance at location '{coordinates}' must have a default English "
+ "style name ('stylename' attribute on the instance element or a "
+ "stylename element with an 'xml:lang=\"en\"' attribute)."
+ )
localisedStyleName = dict(instance.localisedStyleName)
localisedStyleName["en"] = tounicode(instance.styleName)
else:
@@ -137,20 +140,32 @@
# Current avar requirements. We don't have to enforce
# these on the designer and can deduce some ourselves,
# but for now just enforce them.
- assert axis.minimum == min(keys)
- assert axis.maximum == max(keys)
- assert axis.default in keys
- # No duplicates
- assert len(set(keys)) == len(keys), (
- f"{axis.tag} axis: All axis mapping input='...' "
- "values must be unique, but we found duplicates."
- )
- assert len(set(vals)) == len(vals), (
- f"{axis.tag} axis: All axis mapping output='...' "
- "values must be unique, but we found duplicates."
- )
+ if axis.minimum != min(keys):
+ raise VarLibValidationError(
+ f"Axis '{axis.name}': there must be a mapping for the axis minimum "
+ f"value {axis.minimum} and it must be the lowest input mapping value."
+ )
+ if axis.maximum != max(keys):
+ raise VarLibValidationError(
+ f"Axis '{axis.name}': there must be a mapping for the axis maximum "
+ f"value {axis.maximum} and it must be the highest input mapping value."
+ )
+ if axis.default not in keys:
+ raise VarLibValidationError(
+ f"Axis '{axis.name}': there must be a mapping for the axis default "
+ f"value {axis.default}."
+ )
+ # No duplicate input values (output values can be >= their preceeding value).
+ if len(set(keys)) != len(keys):
+ raise VarLibValidationError(
+ f"Axis '{axis.name}': All axis mapping input='...' values must be "
+ "unique, but we found duplicates."
+ )
# Ascending values
- assert sorted(vals) == vals
+ if sorted(vals) != vals:
+ raise VarLibValidationError(
+ f"Axis '{axis.name}': mapping output values must be in ascending order."
+ )
keys_triple = (axis.minimum, axis.default, axis.maximum)
vals_triple = tuple(axis.map_forward(v) for v in keys_triple)
@@ -214,8 +229,8 @@
def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
-
- assert tolerance >= 0
+ if tolerance < 0:
+ raise ValueError("`tolerance` must be a positive number.")
log.info("Generating gvar")
assert "gvar" not in font
@@ -669,9 +684,11 @@
def _add_CFF2(varFont, model, master_fonts):
- from .cff import (convertCFFtoCFF2, merge_region_fonts)
+ from .cff import merge_region_fonts
glyphOrder = varFont.getGlyphOrder()
- convertCFFtoCFF2(varFont)
+ if "CFF2" not in varFont:
+ from .cff import convertCFFtoCFF2
+ convertCFFtoCFF2(varFont)
ordered_fonts_list = model.reorderMasters(master_fonts, model.reverseMapping)
# re-ordering the master list simplifies building the CFF2 data item lists.
merge_region_fonts(varFont, model, ordered_fonts_list, glyphOrder)
@@ -687,9 +704,10 @@
masters = ds.sources
if not masters:
- raise VarLibError("no sources found in .designspace")
+ raise VarLibValidationError("Designspace must have at least one source.")
instances = ds.instances
+ # TODO: Use fontTools.designspaceLib.tagForAxisName instead.
standard_axis_map = OrderedDict([
('weight', ('wght', {'en': u'Weight'})),
('width', ('wdth', {'en': u'Width'})),
@@ -699,11 +717,15 @@
])
# Setup axes
+ if not ds.axes:
+ raise VarLibValidationError(f"Designspace must have at least one axis.")
+
axes = OrderedDict()
- for axis in ds.axes:
+ for axis_index, axis in enumerate(ds.axes):
axis_name = axis.name
if not axis_name:
- assert axis.tag is not None
+ if not axis.tag:
+ raise VarLibValidationError(f"Axis at index {axis_index} needs a tag.")
axis_name = axis.name = axis.tag
if axis_name in standard_axis_map:
@@ -712,7 +734,8 @@
if not axis.labelNames:
axis.labelNames.update(standard_axis_map[axis_name][1])
else:
- assert axis.tag is not None
+ if not axis.tag:
+ raise VarLibValidationError(f"Axis at index {axis_index} needs a tag.")
if not axis.labelNames:
axis.labelNames["en"] = tounicode(axis_name)
@@ -723,14 +746,28 @@
for obj in masters+instances:
obj_name = obj.name or obj.styleName or ''
loc = obj.location
+ if loc is None:
+ raise VarLibValidationError(
+ f"Source or instance '{obj_name}' has no location."
+ )
for axis_name in loc.keys():
- assert axis_name in axes, "Location axis '%s' unknown for '%s'." % (axis_name, obj_name)
+ if axis_name not in axes:
+ raise VarLibValidationError(
+ f"Location axis '{axis_name}' unknown for '{obj_name}'."
+ )
for axis_name,axis in axes.items():
if axis_name not in loc:
- loc[axis_name] = axis.default
+ # NOTE: `axis.default` is always user-space, but `obj.location` always design-space.
+ loc[axis_name] = axis.map_forward(axis.default)
else:
v = axis.map_backward(loc[axis_name])
- assert axis.minimum <= v <= axis.maximum, "Location for axis '%s' (mapped to %s) out of range for '%s' [%s..%s]" % (axis_name, v, obj_name, axis.minimum, axis.maximum)
+ if not (axis.minimum <= v <= axis.maximum):
+ raise VarLibValidationError(
+ f"Source or instance '{obj_name}' has out-of-range location "
+ f"for axis '{axis_name}': is mapped to {v} but must be in "
+ f"mapped range [{axis.minimum}..{axis.maximum}] (NOTE: all "
+ "values are in user-space)."
+ )
# Normalize master locations
@@ -751,9 +788,15 @@
base_idx = None
for i,m in enumerate(normalized_master_locs):
if all(v == 0 for v in m.values()):
- assert base_idx is None
+ if base_idx is not None:
+ raise VarLibValidationError(
+ "More than one base master found in Designspace."
+ )
base_idx = i
- assert base_idx is not None, "Base master not found; no master at default location?"
+ if base_idx is None:
+ raise VarLibValidationError(
+ "Base master not found; no master at default location?"
+ )
log.info("Index of base master: %s", base_idx)
return _DesignSpaceData(
@@ -870,7 +913,7 @@
_merge_TTHinting(vf, model, master_fonts)
if 'GSUB' not in exclude and ds.rules:
_add_GSUB_feature_variations(vf, ds.axes, ds.internal_axis_supports, ds.rules, ds.rulesProcessingLast)
- if 'CFF2' not in exclude and 'CFF ' in vf:
+ if 'CFF2' not in exclude and ('CFF ' in vf or 'CFF2' in vf):
_add_CFF2(vf, model, master_fonts)
if "post" in vf:
# set 'post' to format 2 to keep the glyph names dropped from CFF2
@@ -910,7 +953,7 @@
elif tp in ("TTF", "OTF", "WOFF", "WOFF2"):
font = TTFont(master_path)
else:
- raise VarLibError("Invalid master path: %r" % master_path)
+ raise VarLibValidationError("Invalid master path: %r" % master_path)
return font
@@ -930,10 +973,10 @@
# If a SourceDescriptor has a layer name, demand that the compiled TTFont
# be supplied by the caller. This spares us from modifying MasterFinder.
if master.layerName and master.font is None:
- raise AttributeError(
- "Designspace source '%s' specified a layer name but lacks the "
- "required TTFont object in the 'font' attribute."
- % (master.name or "<Unknown>")
+ raise VarLibValidationError(
+ f"Designspace source '{master.name or '<Unknown>'}' specified a "
+ "layer name but lacks the required TTFont object in the 'font' "
+ "attribute."
)
return designspace.loadSourceFonts(_open_font, master_finder=master_finder)
diff --git a/Lib/fontTools/varLib/cff.py b/Lib/fontTools/varLib/cff.py
index 967fde7..000e1b3 100644
--- a/Lib/fontTools/varLib/cff.py
+++ b/Lib/fontTools/varLib/cff.py
@@ -1,5 +1,4 @@
from collections import namedtuple
-import os
from fontTools.cffLib import (
maxStackLimit,
TopDictIndex,
@@ -21,6 +20,13 @@
from fontTools.misc.psCharStrings import T2CharString, T2OutlineExtractor
from fontTools.pens.t2CharStringPen import T2CharStringPen, t2c_round
+from .errors import VarLibCFFDictMergeError, VarLibCFFPointTypeMergeError, VarLibMergeError
+
+
+# Backwards compatibility
+MergeDictError = VarLibCFFDictMergeError
+MergeTypeError = VarLibCFFPointTypeMergeError
+
def addCFFVarStore(varFont, varModel, varDataList, masterSupports):
fvarTable = varFont['fvar']
@@ -30,6 +36,11 @@
topDict = varFont['CFF2'].cff.topDictIndex[0]
topDict.VarStore = VarStoreData(otVarStore=varStoreCFFV)
+ if topDict.FDArray[0].vstore is None:
+ fdArray = topDict.FDArray
+ for fontDict in fdArray:
+ if hasattr(fontDict, "Private"):
+ fontDict.Private.vstore = topDict.VarStore
def lib_convertCFFToCFF2(cff, otFont):
@@ -121,16 +132,6 @@
del varFont['CFF ']
-class MergeDictError(TypeError):
- def __init__(self, key, value, values):
- error_msg = ["For the Private Dict key '{}', ".format(key),
- "the default font value list:",
- "\t{}".format(value),
- "had a different number of values than a region font:"]
- error_msg += ["\t{}".format(region_value) for region_value in values]
- error_msg = os.linesep.join(error_msg)
-
-
def conv_to_int(num):
if isinstance(num, float) and num.is_integer():
return int(num)
@@ -214,7 +215,7 @@
try:
values = zip(*values)
except IndexError:
- raise MergeDictError(key, value, values)
+ raise VarLibCFFDictMergeError(key, value, values)
"""
Row 0 contains the first value from each master.
Convert each row from absolute values to relative
@@ -266,6 +267,12 @@
private_dict.rawDict[key] = dataList
+def _cff_or_cff2(font):
+ if "CFF " in font:
+ return font["CFF "]
+ return font["CFF2"]
+
+
def getfd_map(varFont, fonts_list):
""" Since a subset source font may have fewer FontDicts in their
FDArray than the default font, we have to match up the FontDicts in
@@ -278,7 +285,7 @@
default_font = fonts_list[0]
region_fonts = fonts_list[1:]
num_regions = len(region_fonts)
- topDict = default_font['CFF '].cff.topDictIndex[0]
+ topDict = _cff_or_cff2(default_font).cff.topDictIndex[0]
if not hasattr(topDict, 'FDSelect'):
# All glyphs reference only one FontDict.
# Map the FD index for regions to index 0.
@@ -294,7 +301,7 @@
fd_map[fdIndex] = {}
for ri, region_font in enumerate(region_fonts):
region_glyphOrder = region_font.getGlyphOrder()
- region_topDict = region_font['CFF '].cff.topDictIndex[0]
+ region_topDict = _cff_or_cff2(region_font).cff.topDictIndex[0]
if not hasattr(region_topDict, 'FDSelect'):
# All the glyphs share the same FontDict. Pick any glyph.
default_fdIndex = gname_mapping[region_glyphOrder[0]]
@@ -313,7 +320,7 @@
def merge_region_fonts(varFont, model, ordered_fonts_list, glyphOrder):
topDict = varFont['CFF2'].cff.topDictIndex[0]
top_dicts = [topDict] + [
- ttFont['CFF '].cff.topDictIndex[0]
+ _cff_or_cff2(ttFont).cff.topDictIndex[0]
for ttFont in ordered_fonts_list[1:]
]
num_masters = len(model.mapping)
@@ -415,21 +422,6 @@
return cvData
-class MergeTypeError(TypeError):
- def __init__(self, point_type, pt_index, m_index, default_type, glyphName):
- self.error_msg = [
- "In glyph '{gname}' "
- "'{point_type}' at point index {pt_index} in master "
- "index {m_index} differs from the default font point "
- "type '{default_type}'"
- "".format(
- gname=glyphName,
- point_type=point_type, pt_index=pt_index,
- m_index=m_index, default_type=default_type)
- ][0]
- super(MergeTypeError, self).__init__(self.error_msg)
-
-
def makeRoundNumberFunc(tolerance):
if tolerance < 0:
raise ValueError("Rounding tolerance must be positive")
@@ -536,7 +528,7 @@
else:
cmd = self._commands[self.pt_index]
if cmd[0] != point_type:
- raise MergeTypeError(
+ raise VarLibCFFPointTypeMergeError(
point_type,
self.pt_index, len(cmd[1]),
cmd[0], self.glyphName)
@@ -549,7 +541,7 @@
else:
cmd = self._commands[self.pt_index]
if cmd[0] != hint_type:
- raise MergeTypeError(hint_type, self.pt_index, len(cmd[1]),
+ raise VarLibCFFPointTypeMergeError(hint_type, self.pt_index, len(cmd[1]),
cmd[0], self.glyphName)
cmd[1].append(args)
self.pt_index += 1
@@ -565,7 +557,7 @@
else:
cmd = self._commands[self.pt_index]
if cmd[0] != hint_type:
- raise MergeTypeError(hint_type, self.pt_index, len(cmd[1]),
+ raise VarLibCFFPointTypeMergeError(hint_type, self.pt_index, len(cmd[1]),
cmd[0], self.glyphName)
self.pt_index += 1
cmd = self._commands[self.pt_index]
@@ -635,8 +627,8 @@
# second has only args.
if lastOp in ['hintmask', 'cntrmask']:
coord = list(cmd[1])
- assert allEqual(coord), (
- "hintmask values cannot differ between source fonts.")
+ if not allEqual(coord):
+ raise VarLibMergeError("Hintmask values cannot differ between source fonts.")
cmd[1] = [coord[0][0]]
else:
coords = cmd[1]
diff --git a/Lib/fontTools/varLib/errors.py b/Lib/fontTools/varLib/errors.py
new file mode 100644
index 0000000..b73f188
--- /dev/null
+++ b/Lib/fontTools/varLib/errors.py
@@ -0,0 +1,39 @@
+class VarLibError(Exception):
+ """Base exception for the varLib module."""
+
+
+class VarLibValidationError(VarLibError):
+ """Raised when input data is invalid from varLib's point of view."""
+
+
+class VarLibMergeError(VarLibError):
+ """Raised when input data cannot be merged into a variable font."""
+
+
+class VarLibCFFDictMergeError(VarLibMergeError):
+ """Raised when a CFF PrivateDict cannot be merged."""
+
+ def __init__(self, key, value, values):
+ error_msg = (
+ f"For the Private Dict key '{key}', the default font value list:"
+ f"\n\t{value}\nhad a different number of values than a region font:"
+ )
+ for region_value in values:
+ error_msg += f"\n\t{region_value}"
+ self.args = (error_msg,)
+
+
+class VarLibCFFPointTypeMergeError(VarLibMergeError):
+ """Raised when a CFF glyph cannot be merged."""
+
+ def __init__(self, point_type, pt_index, m_index, default_type, glyph_name):
+ error_msg = (
+ f"Glyph '{glyph_name}': '{point_type}' at point index {pt_index} in "
+ f"master index {m_index} differs from the default font point type "
+ f"'{default_type}'"
+ )
+ self.args = (error_msg,)
+
+
+class VariationModelError(VarLibError):
+ """Raised when a variation model is faulty."""
diff --git a/Lib/fontTools/varLib/featureVars.py b/Lib/fontTools/varLib/featureVars.py
index 287a885..dab9a0b 100644
--- a/Lib/fontTools/varLib/featureVars.py
+++ b/Lib/fontTools/varLib/featureVars.py
@@ -10,6 +10,8 @@
from fontTools.otlLib.builder import buildLookup, buildSingleSubstSubtable
from collections import OrderedDict
+from .errors import VarLibValidationError
+
def addFeatureVariations(font, conditionalSubstitutions, featureTag='rvrn'):
"""Add conditional substitutions to a Variable Font.
@@ -312,7 +314,10 @@
for conditionSet, substitutions in conditionalSubstitutions:
conditionTable = []
for axisTag, (minValue, maxValue) in sorted(conditionSet.items()):
- assert minValue < maxValue
+ if minValue > maxValue:
+ raise VarLibValidationError(
+ "A condition set has a minimum value above the maximum value."
+ )
ct = buildConditionTable(axisIndices[axisTag], minValue, maxValue)
conditionTable.append(ct)
diff --git a/Lib/fontTools/varLib/merger.py b/Lib/fontTools/varLib/merger.py
index 2ac64fd..3f04364 100644
--- a/Lib/fontTools/varLib/merger.py
+++ b/Lib/fontTools/varLib/merger.py
@@ -14,6 +14,8 @@
from functools import reduce
from fontTools.otlLib.builder import buildSinglePos
+from .errors import VarLibMergeError
+
class Merger(object):
@@ -60,9 +62,14 @@
return _default
def mergeObjects(self, out, lst, exclude=()):
+ if hasattr(out, "ensureDecompiled"):
+ out.ensureDecompiled()
+ for item in lst:
+ if hasattr(item, "ensureDecompiled"):
+ item.ensureDecompiled()
keys = sorted(vars(out).keys())
- assert all(keys == sorted(vars(v).keys()) for v in lst), \
- (keys, [sorted(vars(v).keys()) for v in lst])
+ if not all(keys == sorted(vars(v).keys()) for v in lst):
+ raise VarLibMergeError((keys, [sorted(vars(v).keys()) for v in lst]))
mergers = self.mergersFor(out)
defaultMerger = mergers.get('*', self.__class__.mergeThings)
try:
@@ -77,7 +84,8 @@
raise
def mergeLists(self, out, lst):
- assert allEqualTo(out, lst, len), (len(out), [len(v) for v in lst])
+ if not allEqualTo(out, lst, len):
+ raise VarLibMergeError((len(out), [len(v) for v in lst]))
for i,(value,values) in enumerate(zip(out, zip(*lst))):
try:
self.mergeThings(value, values)
@@ -87,7 +95,8 @@
def mergeThings(self, out, lst):
try:
- assert allEqualTo(out, lst, type), (out, lst)
+ if not allEqualTo(out, lst, type):
+ raise VarLibMergeError((out, lst))
mergerFunc = self.mergersFor(out).get(None, None)
if mergerFunc is not None:
mergerFunc(self, out, lst)
@@ -96,7 +105,8 @@
elif isinstance(out, list):
self.mergeLists(out, lst)
else:
- assert allEqualTo(out, lst), (out, lst)
+ if not allEqualTo(out, lst):
+ raise VarLibMergeError((out, lst))
except Exception as e:
e.args = e.args + (type(out).__name__,)
raise
@@ -117,7 +127,8 @@
@AligningMerger.merger(ot.GDEF, "GlyphClassDef")
def merge(merger, self, lst):
if self is None:
- assert allNone(lst), (lst)
+ if not allNone(lst):
+ raise VarLibMergeError(lst)
return
lst = [l.classDefs for l in lst]
@@ -129,7 +140,8 @@
allKeys.update(*[l.keys() for l in lst])
for k in allKeys:
allValues = nonNone(l.get(k) for l in lst)
- assert allEqual(allValues), allValues
+ if not allEqual(allValues):
+ raise VarLibMergeError(allValues)
if not allValues:
self[k] = None
else:
@@ -165,7 +177,8 @@
sortKey = font.getReverseGlyphMap().__getitem__
order = sorted(combined, key=sortKey)
# Make sure all input glyphsets were in proper order
- assert all(sorted(vs, key=sortKey) == vs for vs in lst), "glyph orders are not consistent across masters"
+ if not all(sorted(vs, key=sortKey) == vs for vs in lst):
+ raise VarLibMergeError("Glyph order inconsistent across masters.")
del combined
paddedValues = None
@@ -192,7 +205,10 @@
elif self.Format == 2:
return self.Value[self.Coverage.glyphs.index(glyph)]
else:
- assert 0
+ raise VarLibMergeError(
+ "Cannot retrieve effective value for SinglePos lookup, unsupported "
+ f"format {self.Format}."
+ )
return None
def _Lookup_PairPos_get_effective_value_pair(subtables, firstGlyph, secondGlyph):
@@ -214,13 +230,17 @@
klass2 = self.ClassDef2.classDefs.get(secondGlyph, 0)
return self.Class1Record[klass1].Class2Record[klass2]
else:
- assert 0
+ raise VarLibMergeError(
+ "Cannot retrieve effective value pair for PairPos lookup, unsupported "
+ f"format {self.Format}."
+ )
return None
@AligningMerger.merger(ot.SinglePos)
def merge(merger, self, lst):
self.ValueFormat = valueFormat = reduce(int.__or__, [l.ValueFormat for l in lst], 0)
- assert len(lst) == 1 or (valueFormat & ~0xF == 0), valueFormat
+ if not (len(lst) == 1 or (valueFormat & ~0xF == 0)):
+ raise VarLibMergeError(f"SinglePos format {valueFormat} is unsupported.")
# If all have same coverage table and all are format 1,
coverageGlyphs = self.Coverage.glyphs
@@ -506,7 +526,9 @@
elif self.Format == 2:
_PairPosFormat2_merge(self, lst, merger)
else:
- assert False
+ raise VarLibMergeError(
+ f"Cannot merge PairPos lookup, unsupported format {self.Format}."
+ )
del merger.valueFormat1, merger.valueFormat2
@@ -571,7 +593,8 @@
# failures in that case will probably signify mistakes in the
# input masters.
- assert allEqual(allClasses), allClasses
+ if not allEqual(allClasses):
+ raise VarLibMergeError(allClasses)
if not allClasses:
rec = None
else:
@@ -620,19 +643,31 @@
@AligningMerger.merger(ot.MarkBasePos)
def merge(merger, self, lst):
- assert allEqualTo(self.Format, (l.Format for l in lst))
+ if not allEqualTo(self.Format, (l.Format for l in lst)):
+ raise VarLibMergeError(
+ f"MarkBasePos formats inconsistent across masters, "
+ f"expected {self.Format} but got {[l.Format for l in lst]}."
+ )
if self.Format == 1:
_MarkBasePosFormat1_merge(self, lst, merger)
else:
- assert False
+ raise VarLibMergeError(
+ f"Cannot merge MarkBasePos lookup, unsupported format {self.Format}."
+ )
@AligningMerger.merger(ot.MarkMarkPos)
def merge(merger, self, lst):
- assert allEqualTo(self.Format, (l.Format for l in lst))
+ if not allEqualTo(self.Format, (l.Format for l in lst)):
+ raise VarLibMergeError(
+ f"MarkMarkPos formats inconsistent across masters, "
+ f"expected {self.Format} but got {[l.Format for l in lst]}."
+ )
if self.Format == 1:
_MarkBasePosFormat1_merge(self, lst, merger, 'Mark1', 'Mark2')
else:
- assert False
+ raise VarLibMergeError(
+ f"Cannot merge MarkMarkPos lookup, unsupported format {self.Format}."
+ )
def _PairSet_flatten(lst, font):
@@ -761,8 +796,16 @@
if not sts:
continue
if sts[0].__class__.__name__.startswith('Extension'):
- assert allEqual([st.__class__ for st in sts])
- assert allEqual([st.ExtensionLookupType for st in sts])
+ if not allEqual([st.__class__ for st in sts]):
+ raise VarLibMergeError(
+ "Use of extensions inconsistent between masters: "
+ f"{[st.__class__.__name__ for st in sts]}."
+ )
+ if not allEqual([st.ExtensionLookupType for st in sts]):
+ raise VarLibMergeError(
+ "Extension lookup type differs between masters: "
+ f"{[st.ExtensionLookupType for st in sts]}."
+ )
l.LookupType = sts[0].ExtensionLookupType
new_sts = [st.ExtSubTable for st in sts]
del sts[:]
@@ -990,7 +1033,8 @@
masterModel = None
if None in lst:
if allNone(lst):
- assert out is None, (out, lst)
+ if out is not None:
+ raise VarLibMergeError((out, lst))
return
masterModel = self.model
model, lst = masterModel.getSubModel(lst)
@@ -1010,7 +1054,8 @@
@VariationMerger.merger(ot.CaretValue)
def merge(merger, self, lst):
- assert self.Format == 1
+ if self.Format != 1:
+ raise VarLibMergeError(f"CaretValue format {self.Format} unsupported.")
self.Coordinate, DeviceTable = buildVarDevTable(merger.store_builder, [a.Coordinate for a in lst])
if DeviceTable:
self.Format = 3
@@ -1018,7 +1063,8 @@
@VariationMerger.merger(ot.Anchor)
def merge(merger, self, lst):
- assert self.Format == 1
+ if self.Format != 1:
+ raise VarLibMergeError(f"Anchor format {self.Format} unsupported.")
self.XCoordinate, XDeviceTable = buildVarDevTable(merger.store_builder, [a.XCoordinate for a in lst])
self.YCoordinate, YDeviceTable = buildVarDevTable(merger.store_builder, [a.YCoordinate for a in lst])
if XDeviceTable or YDeviceTable:
diff --git a/Lib/fontTools/varLib/models.py b/Lib/fontTools/varLib/models.py
index 2ffe33e..d6837ee 100644
--- a/Lib/fontTools/varLib/models.py
+++ b/Lib/fontTools/varLib/models.py
@@ -5,6 +5,8 @@
'supportScalar',
'VariationModel']
+from .errors import VariationModelError
+
def nonNone(lst):
return [l for l in lst if l is not None]
@@ -43,7 +45,11 @@
0.5
"""
lower, default, upper = triple
- assert lower <= default <= upper, "invalid axis values: %3.3f, %3.3f %3.3f"%(lower, default, upper)
+ if not (lower <= default <= upper):
+ raise ValueError(
+ f"Invalid axis values, must be minimum, default, maximum: "
+ f"{lower:3.3f}, {default:3.3f}, {upper:3.3f}"
+ )
v = max(min(v, upper), lower)
if v == default:
v = 0.
@@ -192,7 +198,7 @@
def __init__(self, locations, axisOrder=None):
if len(set(tuple(sorted(l.items())) for l in locations)) != len(locations):
- raise ValueError("locations must be unique")
+ raise VariationModelError("Locations must be unique.")
self.origLocations = locations
self.axisOrder = axisOrder if axisOrder is not None else []
@@ -220,7 +226,8 @@
@staticmethod
def getMasterLocationsSortKeyFunc(locations, axisOrder=[]):
- assert {} in locations, "Base master not found."
+ if {} not in locations:
+ raise VariationModelError("Base master not found.")
axisPoints = {}
for loc in locations:
if len(loc) != 1:
diff --git a/NEWS.rst b/NEWS.rst
index 1a0698a..6cca080 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -1,3 +1,63 @@
+4.4.0 (released 2020-02-18)
+---------------------------
+
+- [colorLib] Added ``fontTools.colorLib.builder`` module, initially with ``buildCOLR``
+ and ``buildCPAL`` public functions. More color font formats will follow (#1827).
+- [fontBuilder] Added ``setupCOLR`` and ``setupCPAL`` methods (#1826).
+- [ttGlyphPen] Quantize ``GlyphComponent.transform`` floats to ``F2Dot14`` to fix
+ round-trip issue when computing bounding boxes of transformed components (#1830).
+- [glyf] If a component uses reference points (``firstPt`` and ``secondPt``) for
+ alignment (instead of X and Y offsets), compute the effective translation offset
+ *after* having applied any transform (#1831).
+- [glyf] When all glyphs have zero contours, compile ``glyf`` table data as a single
+ null byte in order to pass validation by OTS and Windows (#1829).
+- [feaLib] Parsing feature code now ensures that referenced glyph names are part of
+ the known glyph set, unless a glyph set was not provided.
+- [varLib] When filling in the default axis value for a missing location of a source or
+ instance, correctly map the value forward.
+- [varLib] The avar table can now contain mapping output values that are greater than
+ OR EQUAL to the preceeding value, as the avar specification allows this.
+- [varLib] The errors of the module are now ordered hierarchically below VarLibError.
+ See #1821.
+
+4.3.0 (released 2020-02-03)
+---------------------------
+
+- [EBLC/CBLC] Fixed incorrect padding length calculation for Format 3 IndexSubTable
+ (#1817, #1818).
+- [varLib] Fixed error when merging OTL tables and TTFonts were loaded as ``lazy=True``
+ (#1808, #1809).
+- [varLib] Allow to use master fonts containing ``CFF2`` table when building VF (#1816).
+- [ttLib] Make ``recalcBBoxes`` option work also with ``CFF2`` table (#1816).
+- [feaLib] Don't reset ``lookupflag`` in lookups defined inside feature blocks.
+ They will now inherit the current ``lookupflag`` of the feature. This is what
+ Adobe ``makeotf`` also does in this case (#1815).
+- [feaLib] Fixed bug with mixed single/multiple substitutions. If a single substitution
+ involved a glyph class, we were incorrectly using only the first glyph in the class
+ (#1814).
+
+4.2.5 (released 2020-01-29)
+---------------------------
+
+- [feaLib] Do not fail on duplicate multiple substitutions, only warn (#1811).
+- [subset] Optimize SinglePos subtables to Format 1 if all ValueRecords are the same
+ (#1802).
+
+4.2.4 (released 2020-01-09)
+---------------------------
+
+- [unicodedata] Update RTL_SCRIPTS for Unicode 11 and 12.
+
+4.2.3 (released 2020-01-07)
+---------------------------
+
+- [otTables] Fixed bug when splitting `MarkBasePos` subtables as offsets overflow.
+ The mark class values in the split subtable were not being updated, leading to
+ invalid mark-base attachments (#1797, googlefonts/noto-source#145).
+- [feaLib] Only log a warning instead of error when features contain duplicate
+ substitutions (#1767).
+- [glifLib] Strip XML comments when parsing with lxml (#1784, #1785).
+
4.2.2 (released 2019-12-12)
---------------------------
diff --git a/Tests/colorLib/__init__.py b/Tests/colorLib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tests/colorLib/__init__.py
diff --git a/Tests/colorLib/builder_test.py b/Tests/colorLib/builder_test.py
new file mode 100644
index 0000000..c517187
--- /dev/null
+++ b/Tests/colorLib/builder_test.py
@@ -0,0 +1,187 @@
+from fontTools.ttLib import newTable
+from fontTools.colorLib import builder
+from fontTools.colorLib.errors import ColorLibError
+import pytest
+
+
+def test_buildCOLR_v0():
+ color_layer_lists = {
+ "a": [("a.color0", 0), ("a.color1", 1)],
+ "b": [("b.color1", 1), ("b.color0", 0)],
+ }
+
+ colr = builder.buildCOLR(color_layer_lists)
+
+ assert colr.tableTag == "COLR"
+ assert colr.version == 0
+ assert colr.ColorLayers["a"][0].name == "a.color0"
+ assert colr.ColorLayers["a"][0].colorID == 0
+ assert colr.ColorLayers["a"][1].name == "a.color1"
+ assert colr.ColorLayers["a"][1].colorID == 1
+ assert colr.ColorLayers["b"][0].name == "b.color1"
+ assert colr.ColorLayers["b"][0].colorID == 1
+ assert colr.ColorLayers["b"][1].name == "b.color0"
+ assert colr.ColorLayers["b"][1].colorID == 0
+
+
+def test_buildCPAL_v0():
+ palettes = [
+ [(0.68, 0.20, 0.32, 1.0), (0.45, 0.68, 0.21, 1.0)],
+ [(0.68, 0.20, 0.32, 0.6), (0.45, 0.68, 0.21, 0.6)],
+ [(0.68, 0.20, 0.32, 0.3), (0.45, 0.68, 0.21, 0.3)],
+ ]
+
+ cpal = builder.buildCPAL(palettes)
+
+ assert cpal.tableTag == "CPAL"
+ assert cpal.version == 0
+ assert cpal.numPaletteEntries == 2
+
+ assert len(cpal.palettes) == 3
+ assert [tuple(c) for c in cpal.palettes[0]] == [
+ (82, 51, 173, 255),
+ (54, 173, 115, 255),
+ ]
+ assert [tuple(c) for c in cpal.palettes[1]] == [
+ (82, 51, 173, 153),
+ (54, 173, 115, 153),
+ ]
+ assert [tuple(c) for c in cpal.palettes[2]] == [
+ (82, 51, 173, 76),
+ (54, 173, 115, 76),
+ ]
+
+
+def test_buildCPAL_palettes_different_lengths():
+ with pytest.raises(ColorLibError, match="have different lengths"):
+ builder.buildCPAL([[(1, 1, 1, 1)], [(0, 0, 0, 1), (0.5, 0.5, 0.5, 1)]])
+
+
+def test_buildPaletteLabels():
+ name_table = newTable("name")
+ name_table.names = []
+
+ name_ids = builder.buildPaletteLabels(
+ [None, "hi", {"en": "hello", "de": "hallo"}], name_table
+ )
+
+ assert name_ids == [0xFFFF, 256, 257]
+
+ assert len(name_table.names) == 3
+ assert str(name_table.names[0]) == "hi"
+ assert name_table.names[0].nameID == 256
+
+ assert str(name_table.names[1]) == "hallo"
+ assert name_table.names[1].nameID == 257
+
+ assert str(name_table.names[2]) == "hello"
+ assert name_table.names[2].nameID == 257
+
+
+def test_build_CPAL_v1_types_no_labels():
+ palettes = [
+ [(0.1, 0.2, 0.3, 1.0), (0.4, 0.5, 0.6, 1.0)],
+ [(0.1, 0.2, 0.3, 0.6), (0.4, 0.5, 0.6, 0.6)],
+ [(0.1, 0.2, 0.3, 0.3), (0.4, 0.5, 0.6, 0.3)],
+ ]
+ paletteTypes = [
+ builder.ColorPaletteType.USABLE_WITH_LIGHT_BACKGROUND,
+ builder.ColorPaletteType.USABLE_WITH_DARK_BACKGROUND,
+ builder.ColorPaletteType.USABLE_WITH_LIGHT_BACKGROUND
+ | builder.ColorPaletteType.USABLE_WITH_DARK_BACKGROUND,
+ ]
+
+ cpal = builder.buildCPAL(palettes, paletteTypes=paletteTypes)
+
+ assert cpal.tableTag == "CPAL"
+ assert cpal.version == 1
+ assert cpal.numPaletteEntries == 2
+ assert len(cpal.palettes) == 3
+
+ assert cpal.paletteTypes == paletteTypes
+ assert cpal.paletteLabels == [cpal.NO_NAME_ID] * len(palettes)
+ assert cpal.paletteEntryLabels == [cpal.NO_NAME_ID] * cpal.numPaletteEntries
+
+
+def test_build_CPAL_v1_labels():
+ palettes = [
+ [(0.1, 0.2, 0.3, 1.0), (0.4, 0.5, 0.6, 1.0)],
+ [(0.1, 0.2, 0.3, 0.6), (0.4, 0.5, 0.6, 0.6)],
+ [(0.1, 0.2, 0.3, 0.3), (0.4, 0.5, 0.6, 0.3)],
+ ]
+ paletteLabels = ["First", {"en": "Second", "it": "Seconda"}, None]
+ paletteEntryLabels = ["Foo", "Bar"]
+
+ with pytest.raises(TypeError, match="nameTable is required"):
+ builder.buildCPAL(palettes, paletteLabels=paletteLabels)
+ with pytest.raises(TypeError, match="nameTable is required"):
+ builder.buildCPAL(palettes, paletteEntryLabels=paletteEntryLabels)
+
+ name_table = newTable("name")
+ name_table.names = []
+
+ cpal = builder.buildCPAL(
+ palettes,
+ paletteLabels=paletteLabels,
+ paletteEntryLabels=paletteEntryLabels,
+ nameTable=name_table,
+ )
+
+ assert cpal.tableTag == "CPAL"
+ assert cpal.version == 1
+ assert cpal.numPaletteEntries == 2
+ assert len(cpal.palettes) == 3
+
+ assert cpal.paletteTypes == [cpal.DEFAULT_PALETTE_TYPE] * len(palettes)
+ assert cpal.paletteLabels == [256, 257, cpal.NO_NAME_ID]
+ assert cpal.paletteEntryLabels == [258, 259]
+
+ assert name_table.getDebugName(256) == "First"
+ assert name_table.getDebugName(257) == "Second"
+ assert name_table.getDebugName(258) == "Foo"
+ assert name_table.getDebugName(259) == "Bar"
+
+
+def test_invalid_ColorPaletteType():
+ with pytest.raises(ValueError, match="not a valid ColorPaletteType"):
+ builder.ColorPaletteType(-1)
+ with pytest.raises(ValueError, match="not a valid ColorPaletteType"):
+ builder.ColorPaletteType(4)
+ with pytest.raises(ValueError, match="not a valid ColorPaletteType"):
+ builder.ColorPaletteType("abc")
+
+
+def test_buildCPAL_v1_invalid_args_length():
+ with pytest.raises(ColorLibError, match="Expected 2 paletteTypes, got 1"):
+ builder.buildCPAL([[(0, 0, 0, 0)], [(1, 1, 1, 1)]], paletteTypes=[1])
+
+ with pytest.raises(ColorLibError, match="Expected 2 paletteLabels, got 1"):
+ builder.buildCPAL(
+ [[(0, 0, 0, 0)], [(1, 1, 1, 1)]],
+ paletteLabels=["foo"],
+ nameTable=newTable("name"),
+ )
+
+ with pytest.raises(ColorLibError, match="Expected 1 paletteEntryLabels, got 0"):
+ cpal = builder.buildCPAL(
+ [[(0, 0, 0, 0)], [(1, 1, 1, 1)]],
+ paletteEntryLabels=[],
+ nameTable=newTable("name"),
+ )
+
+
+def test_buildCPAL_invalid_color():
+ with pytest.raises(
+ ColorLibError,
+ match=r"In palette\[0\]\[1\]: expected \(R, G, B, A\) tuple, got \(1, 1, 1\)",
+ ):
+ builder.buildCPAL([[(1, 1, 1, 1), (1, 1, 1)]])
+
+ with pytest.raises(
+ ColorLibError,
+ match=(
+ r"palette\[1\]\[0\] has invalid out-of-range "
+ r"\[0..1\] color: \(1, 1, -1, 2\)"
+ ),
+ ):
+ builder.buildCPAL([[(0, 0, 0, 0)], [(1, 1, -1, 2)]])
diff --git a/Tests/feaLib/builder_test.py b/Tests/feaLib/builder_test.py
index 329b3fd..0400210 100644
--- a/Tests/feaLib/builder_test.py
+++ b/Tests/feaLib/builder_test.py
@@ -41,7 +41,7 @@
a_n_d T_h T_h.swash germandbls ydieresis yacute breve
grave acute dieresis macron circumflex cedilla umlaut ogonek caron
damma hamza sukun kasratan lam_meem_jeem noon.final noon.initial
- by feature lookup sub table
+ by feature lookup sub table uni0327 uni0328 e.fina
""".split()
font = TTFont()
font.setGlyphOrder(glyphs)
@@ -206,9 +206,20 @@
"feature test {"
" sub f_f_i by f f i;"
" sub c_t by c t;"
- " sub f_f_i by f f i;"
+ " sub f_f_i by f_f i;"
"} test;")
+ def test_multipleSubst_multipleIdenticalSubstitutionsForSameGlyph_info(self):
+ logger = logging.getLogger("fontTools.feaLib.builder")
+ with CapturingLogHandler(logger, "INFO") as captor:
+ self.build(
+ "feature test {"
+ " sub f_f_i by f f i;"
+ " sub c_t by c t;"
+ " sub f_f_i by f f i;"
+ "} test;")
+ captor.assertRegex(r"Removing duplicate multiple substitution from glyph \"f_f_i\" to \('f', 'f', 'i'\)")
+
def test_pairPos_redefinition_warning(self):
# https://github.com/fonttools/fonttools/issues/1147
logger = logging.getLogger("fontTools.feaLib.builder")
diff --git a/Tests/feaLib/data/lookupflag.fea b/Tests/feaLib/data/lookupflag.fea
index 651dcd0..ced046b 100644
--- a/Tests/feaLib/data/lookupflag.fea
+++ b/Tests/feaLib/data/lookupflag.fea
@@ -95,3 +95,42 @@
lookup M;
lookup N;
} test;
+
+feature test {
+ lookupflag IgnoreMarks;
+ lookup O {
+ pos one 1;
+ } O;
+ lookup P {
+ pos one 1;
+ } P;
+} test;
+
+feature test {
+ lookup Q {
+ pos one 1;
+ } Q;
+ lookup R {
+ pos one 1;
+ } R;
+} test;
+
+feature test {
+ lookup S {
+ lookupflag IgnoreMarks;
+ pos one 1;
+ } S;
+ lookup T {
+ pos one 1;
+ } T;
+} test;
+
+feature test {
+ lookup U {
+ pos one 1;
+ } U;
+ lookup V {
+ lookupflag IgnoreMarks;
+ pos one 1;
+ } V;
+} test;
diff --git a/Tests/feaLib/data/lookupflag.ttx b/Tests/feaLib/data/lookupflag.ttx
index bb05b9a..52a09b8 100644
--- a/Tests/feaLib/data/lookupflag.ttx
+++ b/Tests/feaLib/data/lookupflag.ttx
@@ -61,7 +61,7 @@
<FeatureRecord index="0">
<FeatureTag value="test"/>
<Feature>
- <!-- LookupCount=14 -->
+ <!-- LookupCount=22 -->
<LookupListIndex index="0" value="0"/>
<LookupListIndex index="1" value="1"/>
<LookupListIndex index="2" value="2"/>
@@ -76,11 +76,19 @@
<LookupListIndex index="11" value="11"/>
<LookupListIndex index="12" value="12"/>
<LookupListIndex index="13" value="13"/>
+ <LookupListIndex index="14" value="14"/>
+ <LookupListIndex index="15" value="15"/>
+ <LookupListIndex index="16" value="16"/>
+ <LookupListIndex index="17" value="17"/>
+ <LookupListIndex index="18" value="18"/>
+ <LookupListIndex index="19" value="19"/>
+ <LookupListIndex index="20" value="20"/>
+ <LookupListIndex index="21" value="21"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
- <!-- LookupCount=14 -->
+ <!-- LookupCount=22 -->
<Lookup index="0">
<LookupType value="1"/>
<LookupFlag value="1"/>
@@ -253,6 +261,102 @@
<Value XAdvance="1"/>
</SinglePos>
</Lookup>
+ <Lookup index="14">
+ <LookupType value="1"/>
+ <LookupFlag value="8"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="15">
+ <LookupType value="1"/>
+ <LookupFlag value="8"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="16">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="17">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="18">
+ <LookupType value="1"/>
+ <LookupFlag value="8"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="19">
+ <LookupType value="1"/>
+ <LookupFlag value="8"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="20">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="21">
+ <LookupType value="1"/>
+ <LookupFlag value="8"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ </Coverage>
+ <ValueFormat value="4"/>
+ <Value XAdvance="1"/>
+ </SinglePos>
+ </Lookup>
</LookupList>
</GPOS>
diff --git a/Tests/feaLib/lexer_test.py b/Tests/feaLib/lexer_test.py
index f8f8e75..fa3f0ea 100644
--- a/Tests/feaLib/lexer_test.py
+++ b/Tests/feaLib/lexer_test.py
@@ -223,7 +223,7 @@
# an in-memory stream, so it will use the current working
# directory to resolve relative include statements
lexer = IncludingLexer(UnicodeIO("include(included.fea);"))
- files = set(loc[0] for _, _, loc in lexer)
+ files = set(os.path.realpath(loc[0]) for _, _, loc in lexer)
expected = os.path.realpath(included.name)
self.assertIn(expected, files)
finally:
diff --git a/Tests/feaLib/parser_test.py b/Tests/feaLib/parser_test.py
index a5b9f69..cb4e689 100644
--- a/Tests/feaLib/parser_test.py
+++ b/Tests/feaLib/parser_test.py
@@ -39,6 +39,14 @@
n.sc o.sc p.sc q.sc r.sc s.sc t.sc u.sc v.sc w.sc x.sc y.sc z.sc
a.swash b.swash x.swash y.swash z.swash
foobar foo.09 foo.1234 foo.9876
+ one two five six acute grave dieresis umlaut cedilla ogonek macron
+ a_f_f_i o_f_f_i f_i f_f_i one.fitted one.oldstyle a.1 a.2 a.3 c_t
+ PRE SUF FIX BACK TRACK LOOK AHEAD ampersand ampersand.1 ampersand.2
+ cid00001 cid00002 cid00003 cid00004 cid00005 cid00006 cid00007
+ cid12345 cid78987 cid00999 cid01000 cid01001 cid00998 cid00995
+ cid00111 cid00222
+ comma endash emdash figuredash damma hamza
+ c_d d.alt n.end s.end f_f
""").split() + ["foo.%d" % i for i in range(1, 200)]
@@ -260,6 +268,12 @@
FeatureLibError, "Font revision numbers must be positive",
self.parse, "table head {FontRevision -17.2;} head;")
+ def test_strict_glyph_name_check(self):
+ self.parse("@bad = [a b ccc];", glyphNames=("a", "b", "ccc"))
+
+ with self.assertRaisesRegex(FeatureLibError, "missing from the glyph set: ccc"):
+ self.parse("@bad = [a b ccc];", glyphNames=("a", "b"))
+
def test_glyphclass(self):
[gc] = self.parse("@dash = [endash emdash figuredash];").statements
self.assertEqual(gc.name, "dash")
@@ -1396,12 +1410,22 @@
" sub f_f by f f;"
" sub f by f;"
" sub f_f_i by f f i;"
+ " sub [a a.sc] by a;"
+ " sub [a a.sc] by [b b.sc];"
"} Look;")
statements = doc.statements[0].statements
for sub in statements:
self.assertIsInstance(sub, ast.MultipleSubstStatement)
self.assertEqual(statements[1].glyph, "f")
self.assertEqual(statements[1].replacement, ["f"])
+ self.assertEqual(statements[3].glyph, "a")
+ self.assertEqual(statements[3].replacement, ["a"])
+ self.assertEqual(statements[4].glyph, "a.sc")
+ self.assertEqual(statements[4].replacement, ["a"])
+ self.assertEqual(statements[5].glyph, "a")
+ self.assertEqual(statements[5].replacement, ["b"])
+ self.assertEqual(statements[6].glyph, "a.sc")
+ self.assertEqual(statements[6].replacement, ["b.sc"])
def test_substitute_from(self): # GSUB LookupType 3
doc = self.parse("feature test {"
diff --git a/Tests/fontBuilder/data/test_var.otf.ttx b/Tests/fontBuilder/data/test_var.otf.ttx
index e00d33c..ccf64dc 100644
--- a/Tests/fontBuilder/data/test_var.otf.ttx
+++ b/Tests/fontBuilder/data/test_var.otf.ttx
@@ -19,10 +19,10 @@
<unitsPerEm value="1000"/>
<created value="Wed Mar 27 00:23:21 2019"/>
<modified value="Wed Mar 27 00:23:21 2019"/>
- <xMin value="0"/>
- <yMin value="0"/>
- <xMax value="0"/>
- <yMax value="0"/>
+ <xMin value="100"/>
+ <yMin value="100"/>
+ <xMax value="600"/>
+ <yMax value="1000"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="3"/>
<fontDirectionHint value="2"/>
@@ -35,10 +35,10 @@
<ascent value="824"/>
<descent value="200"/>
<lineGap value="0"/>
- <advanceWidthMax value="0"/>
+ <advanceWidthMax value="600"/>
<minLeftSideBearing value="0"/>
- <minRightSideBearing value="0"/>
- <xMaxExtent value="0"/>
+ <minRightSideBearing value="200"/>
+ <xMaxExtent value="400"/>
<caretSlopeRise value="1"/>
<caretSlopeRun value="0"/>
<caretOffset value="0"/>
diff --git a/Tests/pens/ttGlyphPen_test.py b/Tests/pens/ttGlyphPen_test.py
index 961738d..f6ad848 100644
--- a/Tests/pens/ttGlyphPen_test.py
+++ b/Tests/pens/ttGlyphPen_test.py
@@ -264,6 +264,33 @@
compositeGlyph.recalcBounds(glyphSet)
self.assertGlyphBoundsEqual(compositeGlyph, (-86, 0, 282, 1))
+ def test_scaled_component_bounds(self):
+ glyphSet = {}
+
+ pen = TTGlyphPen(glyphSet)
+ pen.moveTo((-231, 939))
+ pen.lineTo((-55, 939))
+ pen.lineTo((-55, 745))
+ pen.lineTo((-231, 745))
+ pen.closePath()
+ glyphSet["gravecomb"] = gravecomb = pen.glyph()
+
+ pen = TTGlyphPen(glyphSet)
+ pen.moveTo((-278, 939))
+ pen.lineTo((8, 939))
+ pen.lineTo((8, 745))
+ pen.lineTo((-278, 745))
+ pen.closePath()
+ glyphSet["circumflexcomb"] = circumflexcomb = pen.glyph()
+
+ pen = TTGlyphPen(glyphSet)
+ pen.addComponent("circumflexcomb", (1, 0, 0, 1, 0, 0))
+ pen.addComponent("gravecomb", (0.9, 0, 0, 0.9, 198, 180))
+ glyphSet["uni0302_uni0300"] = uni0302_uni0300 = pen.glyph()
+
+ uni0302_uni0300.recalcBounds(glyphSet)
+ self.assertGlyphBoundsEqual(uni0302_uni0300, (-278, 745, 148, 1025))
+
class _TestGlyph(object):
def __init__(self, glyph):
diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py
index 3d8c27e..14ca313 100644
--- a/Tests/subset/subset_test.py
+++ b/Tests/subset/subset_test.py
@@ -1,5 +1,6 @@
import io
from fontTools.misc.py23 import *
+from fontTools.misc.testTools import getXML
from fontTools import subset
from fontTools.fontBuilder import FontBuilder
from fontTools.ttLib import TTFont, newTable
@@ -11,6 +12,8 @@
import sys
import tempfile
import unittest
+import pathlib
+import pytest
class SubsetTest(unittest.TestCase):
@@ -768,5 +771,106 @@
assert "dollar.rvrn" in font.getGlyphOrder()
+def test_subset_single_pos_format():
+ fb = FontBuilder(unitsPerEm=1000)
+ fb.setupGlyphOrder([".notdef", "a", "b", "c"])
+ fb.setupCharacterMap({ord("a"): "a", ord("b"): "b", ord("c"): "c"})
+ fb.setupNameTable({"familyName": "TestSingePosFormat", "styleName": "Regular"})
+ fb.setupPost()
+ fb.addOpenTypeFeatures("""
+ feature kern {
+ pos a -50;
+ pos b -40;
+ pos c -50;
+ } kern;
+ """)
+
+ buf = io.BytesIO()
+ fb.save(buf)
+ buf.seek(0)
+
+ font = TTFont(buf)
+
+ # The input font has a SinglePos Format 2 subtable where each glyph has
+ # different ValueRecords
+ assert getXML(font["GPOS"].table.LookupList.Lookup[0].toXML, font) == [
+ '<Lookup>',
+ ' <LookupType value="1"/>',
+ ' <LookupFlag value="0"/>',
+ ' <!-- SubTableCount=1 -->',
+ ' <SinglePos index="0" Format="2">',
+ ' <Coverage Format="1">',
+ ' <Glyph value="a"/>',
+ ' <Glyph value="b"/>',
+ ' <Glyph value="c"/>',
+ ' </Coverage>',
+ ' <ValueFormat value="4"/>',
+ ' <!-- ValueCount=3 -->',
+ ' <Value index="0" XAdvance="-50"/>',
+ ' <Value index="1" XAdvance="-40"/>',
+ ' <Value index="2" XAdvance="-50"/>',
+ ' </SinglePos>',
+ '</Lookup>',
+ ]
+
+ options = subset.Options()
+ subsetter = subset.Subsetter(options)
+ subsetter.populate(unicodes=[ord("a"), ord("c")])
+ subsetter.subset(font)
+
+ # All the subsetted glyphs from the original SinglePos Format2 subtable
+ # now have the same ValueRecord, so we use a more compact Format 1 subtable.
+ assert getXML(font["GPOS"].table.LookupList.Lookup[0].toXML, font) == [
+ '<Lookup>',
+ ' <LookupType value="1"/>',
+ ' <LookupFlag value="0"/>',
+ ' <!-- SubTableCount=1 -->',
+ ' <SinglePos index="0" Format="1">',
+ ' <Coverage Format="1">',
+ ' <Glyph value="a"/>',
+ ' <Glyph value="c"/>',
+ ' </Coverage>',
+ ' <ValueFormat value="4"/>',
+ ' <Value XAdvance="-50"/>',
+ ' </SinglePos>',
+ '</Lookup>',
+ ]
+
+
+@pytest.fixture
+def ttf_path(tmp_path):
+ # $(dirname $0)/../ttLib/data
+ ttLib_data = pathlib.Path(__file__).parent.parent / "ttLib" / "data"
+ font = TTFont()
+ font.importXML(ttLib_data / "TestTTF-Regular.ttx")
+ font_path = tmp_path / "TestTTF-Regular.ttf"
+ font.save(font_path)
+ return font_path
+
+
+def test_subset_empty_glyf(tmp_path, ttf_path):
+ subset_path = tmp_path / (ttf_path.name + ".subset")
+ # only keep empty .notdef and space glyph, resulting in an empty glyf table
+ subset.main(
+ [
+ str(ttf_path),
+ "--no-notdef-outline",
+ "--glyph-names",
+ f"--output-file={subset_path}",
+ "--glyphs=.notdef space",
+ ]
+ )
+ subset_font = TTFont(subset_path)
+
+ assert subset_font.getGlyphOrder() == [".notdef", "space"]
+ assert subset_font.reader['glyf'] == b"\x00"
+
+ glyf = subset_font["glyf"]
+ assert all(glyf[g].numberOfContours == 0 for g in subset_font.getGlyphOrder())
+
+ loca = subset_font["loca"]
+ assert all(loc == 0 for loc in loca)
+
+
if __name__ == "__main__":
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/C_B_L_C_test.py b/Tests/ttLib/tables/C_B_L_C_test.py
new file mode 100644
index 0000000..fed25f9
--- /dev/null
+++ b/Tests/ttLib/tables/C_B_L_C_test.py
@@ -0,0 +1,80 @@
+import base64
+import io
+import os
+
+from fontTools.misc.testTools import getXML
+from fontTools.ttLib import TTFont
+
+
+DATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data")
+
+# This is a subset from NotoColorEmoji.ttf which contains an IndexTable format=3
+INDEX_FORMAT_3_TTX = os.path.join(DATA_DIR, "NotoColorEmoji.subset.index_format_3.ttx")
+# The CLBC table was compiled with Harfbuzz' hb-subset and contains the correct padding
+CBLC_INDEX_FORMAT_3 = base64.b64decode(
+ "AAMAAAAAAAEAAAA4AAAALAAAAAIAAAAAZeWIAAAAAAAAAAAAZeWIAAAAAAAAAAAAAAEAA"
+ "21tIAEAAQACAAAAEAADAAMAAAAgAAMAEQAAAAQAAAOmEQ0AAAADABEAABERAAAIUg=="
+)
+
+
+def test_compile_decompile_index_table_format_3():
+ font = TTFont()
+ font.importXML(INDEX_FORMAT_3_TTX)
+ buf = io.BytesIO()
+ font.save(buf)
+ buf.seek(0)
+ font = TTFont(buf)
+
+ assert font.reader["CBLC"] == CBLC_INDEX_FORMAT_3
+
+ assert getXML(font["CBLC"].toXML, font) == [
+ '<header version="3.0"/>',
+ '<strike index="0">',
+ " <bitmapSizeTable>",
+ ' <sbitLineMetrics direction="hori">',
+ ' <ascender value="101"/>',
+ ' <descender value="-27"/>',
+ ' <widthMax value="136"/>',
+ ' <caretSlopeNumerator value="0"/>',
+ ' <caretSlopeDenominator value="0"/>',
+ ' <caretOffset value="0"/>',
+ ' <minOriginSB value="0"/>',
+ ' <minAdvanceSB value="0"/>',
+ ' <maxBeforeBL value="0"/>',
+ ' <minAfterBL value="0"/>',
+ ' <pad1 value="0"/>',
+ ' <pad2 value="0"/>',
+ " </sbitLineMetrics>",
+ ' <sbitLineMetrics direction="vert">',
+ ' <ascender value="101"/>',
+ ' <descender value="-27"/>',
+ ' <widthMax value="136"/>',
+ ' <caretSlopeNumerator value="0"/>',
+ ' <caretSlopeDenominator value="0"/>',
+ ' <caretOffset value="0"/>',
+ ' <minOriginSB value="0"/>',
+ ' <minAdvanceSB value="0"/>',
+ ' <maxBeforeBL value="0"/>',
+ ' <minAfterBL value="0"/>',
+ ' <pad1 value="0"/>',
+ ' <pad2 value="0"/>',
+ " </sbitLineMetrics>",
+ ' <colorRef value="0"/>',
+ ' <startGlyphIndex value="1"/>',
+ ' <endGlyphIndex value="3"/>',
+ ' <ppemX value="109"/>',
+ ' <ppemY value="109"/>',
+ ' <bitDepth value="32"/>',
+ ' <flags value="1"/>',
+ " </bitmapSizeTable>",
+ " <!-- GlyphIds are written but not read. The firstGlyphIndex and",
+ " lastGlyphIndex values will be recalculated by the compiler. -->",
+ ' <eblc_index_sub_table_3 imageFormat="17" firstGlyphIndex="1" lastGlyphIndex="2">',
+ ' <glyphLoc id="1" name="eight"/>',
+ ' <glyphLoc id="2" name="registered"/>',
+ " </eblc_index_sub_table_3>",
+ ' <eblc_index_sub_table_3 imageFormat="17" firstGlyphIndex="3" lastGlyphIndex="3">',
+ ' <glyphLoc id="3" name="uni2049"/>',
+ " </eblc_index_sub_table_3>",
+ "</strike>",
+ ]
diff --git a/Tests/ttLib/tables/C_P_A_L_test.py b/Tests/ttLib/tables/C_P_A_L_test.py
index 6800987..b018a52 100644
--- a/Tests/ttLib/tables/C_P_A_L_test.py
+++ b/Tests/ttLib/tables/C_P_A_L_test.py
@@ -66,9 +66,6 @@
self.assertEqual(cpal.numPaletteEntries, 2)
self.assertEqual(repr(cpal.palettes),
'[[#000000FF, #66CCFFFF], [#000000FF, #800000FF]]')
- self.assertEqual(cpal.paletteLabels, [0, 0])
- self.assertEqual(cpal.paletteTypes, [0, 0])
- self.assertEqual(cpal.paletteEntryLabels, [0, 0])
def test_decompile_v0_sharingColors(self):
cpal = newTable('CPAL')
@@ -80,9 +77,6 @@
'[#223344FF, #99887711, #55555555]',
'[#223344FF, #99887711, #FFFFFFFF]',
'[#223344FF, #99887711, #55555555]'])
- self.assertEqual(cpal.paletteLabels, [0, 0, 0, 0])
- self.assertEqual(cpal.paletteTypes, [0, 0, 0, 0])
- self.assertEqual(cpal.paletteEntryLabels, [0, 0, 0])
def test_decompile_v1_noLabelsNoTypes(self):
cpal = newTable('CPAL')
@@ -92,9 +86,10 @@
self.assertEqual([repr(p) for p in cpal.palettes], [
'[#CAFECAFE, #22110033, #66554477]', # RGBA
'[#59413127, #42424242, #13330037]'])
- self.assertEqual(cpal.paletteLabels, [0, 0])
+ self.assertEqual(cpal.paletteLabels, [cpal.NO_NAME_ID] * len(cpal.palettes))
self.assertEqual(cpal.paletteTypes, [0, 0])
- self.assertEqual(cpal.paletteEntryLabels, [0, 0, 0])
+ self.assertEqual(cpal.paletteEntryLabels,
+ [cpal.NO_NAME_ID] * cpal.numPaletteEntries)
def test_decompile_v1(self):
cpal = newTable('CPAL')
@@ -194,9 +189,6 @@
self.assertEqual(cpal.version, 0)
self.assertEqual(cpal.numPaletteEntries, 2)
self.assertEqual(repr(cpal.palettes), '[[#12345678, #FEDCBA98]]')
- self.assertEqual(cpal.paletteLabels, [0])
- self.assertEqual(cpal.paletteTypes, [0])
- self.assertEqual(cpal.paletteEntryLabels, [0, 0])
def test_fromXML_v1(self):
cpal = newTable('CPAL')
@@ -218,7 +210,8 @@
'[[#12345678, #FEDCBA98, #CAFECAFE]]')
self.assertEqual(cpal.paletteLabels, [259])
self.assertEqual(cpal.paletteTypes, [2])
- self.assertEqual(cpal.paletteEntryLabels, [0, 262, 0])
+ self.assertEqual(cpal.paletteEntryLabels,
+ [cpal.NO_NAME_ID, 262, cpal.NO_NAME_ID])
if __name__ == "__main__":
diff --git a/Tests/ttLib/tables/_g_l_y_f_test.py b/Tests/ttLib/tables/_g_l_y_f_test.py
index 9e2b355..cec15cc 100644
--- a/Tests/ttLib/tables/_g_l_y_f_test.py
+++ b/Tests/ttLib/tables/_g_l_y_f_test.py
@@ -6,9 +6,12 @@
from fontTools.pens.pointPen import PointToSegmentPen
from fontTools.ttLib import TTFont, newTable, TTLibError
from fontTools.ttLib.tables._g_l_y_f import (
+ Glyph,
GlyphCoordinates,
GlyphComponent,
ARGS_ARE_XY_VALUES,
+ SCALED_COMPONENT_OFFSET,
+ UNSCALED_COMPONENT_OFFSET,
WE_HAVE_A_SCALE,
WE_HAVE_A_TWO_BY_TWO,
WE_HAVE_AN_X_AND_Y_SCALE,
@@ -190,7 +193,7 @@
return re.sub(' ttLibVersion=".*"', '', string)
-class glyfTableTest(unittest.TestCase):
+class GlyfTableTest(unittest.TestCase):
def __init__(self, methodName):
unittest.TestCase.__init__(self, methodName)
@@ -338,6 +341,136 @@
glyfTable["glyph00003"].drawPoints(PointToSegmentPen(pen2), glyfTable)
self.assertEqual(pen1.value, pen2.value)
+ def test_compile_empty_table(self):
+ font = TTFont(sfntVersion="\x00\x01\x00\x00")
+ font.importXML(GLYF_TTX)
+ glyfTable = font['glyf']
+ # set all glyphs to zero contours
+ glyfTable.glyphs = {glyphName: Glyph() for glyphName in font.getGlyphOrder()}
+ glyfData = glyfTable.compile(font)
+ self.assertEqual(glyfData, b"\x00")
+ self.assertEqual(list(font["loca"]), [0] * (font["maxp"].numGlyphs+1))
+
+ def test_decompile_empty_table(self):
+ font = TTFont()
+ glyphNames = [".notdef", "space"]
+ font.setGlyphOrder(glyphNames)
+ font["loca"] = newTable("loca")
+ font["loca"].locations = [0] * (len(glyphNames) + 1)
+ font["glyf"] = newTable("glyf")
+ font["glyf"].decompile(b"\x00", font)
+ self.assertEqual(len(font["glyf"]), 2)
+ self.assertEqual(font["glyf"][".notdef"].numberOfContours, 0)
+ self.assertEqual(font["glyf"]["space"].numberOfContours, 0)
+
+
+class GlyphTest:
+
+ def test_getCoordinates(self):
+ glyphSet = {}
+ pen = TTGlyphPen(glyphSet)
+ pen.moveTo((0, 0))
+ pen.lineTo((100, 0))
+ pen.lineTo((100, 100))
+ pen.lineTo((0, 100))
+ pen.closePath()
+ # simple contour glyph
+ glyphSet["a"] = a = pen.glyph()
+
+ assert a.getCoordinates(glyphSet) == (
+ GlyphCoordinates([(0, 0), (100, 0), (100, 100), (0, 100)]),
+ [3],
+ array.array("B", [1, 1, 1, 1]),
+ )
+
+ # composite glyph with only XY offset
+ pen = TTGlyphPen(glyphSet)
+ pen.addComponent("a", (1, 0, 0, 1, 10, 20))
+ glyphSet["b"] = b = pen.glyph()
+
+ assert b.getCoordinates(glyphSet) == (
+ GlyphCoordinates([(10, 20), (110, 20), (110, 120), (10, 120)]),
+ [3],
+ array.array("B", [1, 1, 1, 1]),
+ )
+
+ # composite glyph with a scale (and referencing another composite glyph)
+ pen = TTGlyphPen(glyphSet)
+ pen.addComponent("b", (0.5, 0, 0, 0.5, 0, 0))
+ glyphSet["c"] = c = pen.glyph()
+
+ assert c.getCoordinates(glyphSet) == (
+ GlyphCoordinates([(5, 10), (55, 10), (55, 60), (5, 60)]),
+ [3],
+ array.array("B", [1, 1, 1, 1]),
+ )
+
+ # composite glyph with unscaled offset (MS-style)
+ pen = TTGlyphPen(glyphSet)
+ pen.addComponent("a", (0.5, 0, 0, 0.5, 10, 20))
+ glyphSet["d"] = d = pen.glyph()
+ d.components[0].flags |= UNSCALED_COMPONENT_OFFSET
+
+ assert d.getCoordinates(glyphSet) == (
+ GlyphCoordinates([(10, 20), (60, 20), (60, 70), (10, 70)]),
+ [3],
+ array.array("B", [1, 1, 1, 1]),
+ )
+
+ # composite glyph with a scaled offset (Apple-style)
+ pen = TTGlyphPen(glyphSet)
+ pen.addComponent("a", (0.5, 0, 0, 0.5, 10, 20))
+ glyphSet["e"] = e = pen.glyph()
+ e.components[0].flags |= SCALED_COMPONENT_OFFSET
+
+ assert e.getCoordinates(glyphSet) == (
+ GlyphCoordinates([(5, 10), (55, 10), (55, 60), (5, 60)]),
+ [3],
+ array.array("B", [1, 1, 1, 1]),
+ )
+
+ # composite glyph where the 2nd and 3rd components use anchor points
+ pen = TTGlyphPen(glyphSet)
+ pen.addComponent("a", (1, 0, 0, 1, 0, 0))
+ glyphSet["f"] = f = pen.glyph()
+
+ comp1 = GlyphComponent()
+ comp1.glyphName = "a"
+ # aling the new component's pt 0 to pt 2 of contour points added so far
+ comp1.firstPt = 2
+ comp1.secondPt = 0
+ comp1.flags = 0
+ f.components.append(comp1)
+
+ comp2 = GlyphComponent()
+ comp2.glyphName = "a"
+ # aling the new component's pt 0 to pt 6 of contour points added so far
+ comp2.firstPt = 6
+ comp2.secondPt = 0
+ comp2.transform = [[0.707107, 0.707107], [-0.707107, 0.707107]] # rotate 45 deg
+ comp2.flags = WE_HAVE_A_TWO_BY_TWO
+ f.components.append(comp2)
+
+ coords, end_pts, flags = f.getCoordinates(glyphSet)
+ assert end_pts == [3, 7, 11]
+ assert flags == array.array("B", [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
+ assert list(sum(coords, ())) == pytest.approx(
+ [
+ 0, 0,
+ 100, 0,
+ 100, 100,
+ 0, 100,
+ 100, 100,
+ 200, 100,
+ 200, 200,
+ 100, 200,
+ 200, 200,
+ 270.7107, 270.7107,
+ 200.0, 341.4214,
+ 129.2893, 270.7107,
+ ]
+ )
+
class GlyphComponentTest:
@@ -456,6 +589,29 @@
):
assert value == pytest.approx(expected)
+ def test_toXML_reference_points(self):
+ comp = GlyphComponent()
+ comp.glyphName = "a"
+ comp.flags = 0
+ comp.firstPt = 1
+ comp.secondPt = 2
+
+ assert getXML(comp.toXML) == [
+ '<component glyphName="a" firstPt="1" secondPt="2" flags="0x0"/>'
+ ]
+
+ def test_fromXML_reference_points(self):
+ comp = GlyphComponent()
+ for name, attrs, content in parseXML(
+ ['<component glyphName="a" firstPt="1" secondPt="2" flags="0x0"/>']
+ ):
+ comp.fromXML(name, attrs, content, ttFont=None)
+
+ assert comp.glyphName == "a"
+ assert comp.flags == 0
+ assert (comp.firstPt, comp.secondPt) == (1, 2)
+ assert not hasattr(comp, "transform")
+
if __name__ == "__main__":
import sys
diff --git a/Tests/ttLib/tables/data/NotoColorEmoji.subset.index_format_3.ttx b/Tests/ttLib/tables/data/NotoColorEmoji.subset.index_format_3.ttx
new file mode 100644
index 0000000..6a1728e
--- /dev/null
+++ b/Tests/ttLib/tables/data/NotoColorEmoji.subset.index_format_3.ttx
@@ -0,0 +1,705 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.44">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="eight"/>
+ <GlyphID id="2" name="registered"/>
+ <GlyphID id="3" name="uni2049"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="2.011"/>
+ <checkSumAdjustment value="0x73631d70"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00001011"/>
+ <unitsPerEm value="2048"/>
+ <created value="Wed May 22 20:00:43 2013"/>
+ <modified value="Thu Jan 30 14:31:14 2020"/>
+ <xMin value="0"/>
+ <yMin value="-500"/>
+ <xMax value="2550"/>
+ <yMax value="1900"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="8"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1900"/>
+ <descent value="-500"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="2550"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="0"/>
+ <xMaxExtent value="2550"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="4"/>
+ <maxPoints value="8"/>
+ <maxContours value="2"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="2"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="1"/>
+ <maxFunctionDefs value="1"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="64"/>
+ <maxSizeOfInstructions value="46"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="2550"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000000"/>
+ <ySubscriptXSize value="1331"/>
+ <ySubscriptYSize value="1433"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="286"/>
+ <ySuperscriptXSize value="1331"/>
+ <ySuperscriptYSize value="1433"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="983"/>
+ <yStrikeoutSize value="102"/>
+ <yStrikeoutPosition value="530"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="6"/>
+ <bProportion value="9"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="GOOG"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="56"/>
+ <usLastCharIndex value="8265"/>
+ <sTypoAscender value="1900"/>
+ <sTypoDescender value="-500"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="1900"/>
+ <usWinDescent value="500"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="0"/>
+ <sCapHeight value="1900"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="1"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="2550" lsb="0"/>
+ <mtx name="eight" width="2550" lsb="0"/>
+ <mtx name="registered" width="2550" lsb="0"/>
+ <mtx name="uni2049" width="2550" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_14 platformID="0" platEncID="5">
+ <map uv="0x38" uvs="0xfe0f"/>
+ <map uv="0xae" uvs="0xfe0f"/>
+ <map uv="0x2049" uvs="0xfe0f"/>
+ </cmap_format_14>
+ <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="52" language="0" nGroups="3">
+ <map code="0x38" name="eight"/><!-- DIGIT EIGHT -->
+ <map code="0xae" name="registered"/><!-- REGISTERED SIGN -->
+ <map code="0x2049" name="uni2049"/><!-- EXCLAMATION QUESTION MARK -->
+ </cmap_format_12>
+ </cmap>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright 2013 Google Inc.
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Noto Color Emoji
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ Noto Color Emoji
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Noto Color Emoji
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 2.011;GOOG;noto-emoji:20180424; pistol
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ NotoColorEmoji
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-1244"/>
+ <underlineThickness value="131"/>
+ <isFixedPitch value="1"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <CBDT>
+ <header version="3.0"/>
+ <strikedata index="0">
+ <cbdt_bitmap_format_17 name="registered">
+ <SmallGlyphMetrics>
+ <height value="128"/>
+ <width value="136"/>
+ <BearingX value="0"/>
+ <BearingY value="101"/>
+ <Advance value="136"/>
+ </SmallGlyphMetrics>
+ <rawimagedata>
+ 89504e47 0d0a1a0a 0000000d 49484452
+ 00000088 00000080 08030000 00e737d1
+ 0d000000 ff504c54 454c6971 74747475
+ 75757878 78777777 7b7b7b7e 7e7e6868
+ 68787878 79797977 77777777 77787878
+ 6969697b 7b7b7777 77737373 6f6f6f6c
+ 6c6c6767 67787878 6a6a6a64 64646161
+ 617d7d7d 7a7a7a60 60607575 75797979
+ 6666665e 5e5e7171 716e6e6e 6565655c
+ 5c5c5a5a 5a6c6c6c 58585857 57577373
+ 73555555 53535352 52526e6e 6e505050
+ 4e4e4e6b 6b6b4c4c 4c666666 4b4b4b66
+ 66666565 65494949 65656547 47474646
+ 465b5b5b 4444445a 5a5a4242 42606060
+ 5757573f 3f3f6060 60575757 3d3d3d58
+ 58583a3a 3a5c5c5c 5d5d5d39 39393737
+ 37353535 33333353 53535252 52525252
+ 2f2f2f52 52524f4f 4f2b2b2b 28282825
+ 25252323 23212121 f9766a48 00000050
+ 74524e53 00406180 bfefff20 9fcf508e
+ df30ffff ffffffff 10ffffff ffffffff
+ afffffff ffffffff 70ffffef ffffff80
+ ffffc0ff 8fffefa4 ffdfffff 50ff10ff
+ c162ff80 40ffa7ff d6efffff ffff8fdb
+ 7fffefbf 08ff47de 00000bbe 49444154
+ 7801ecd5 d77eab30 0c067033 dcb0629b
+ 8d11a381 aef77fc2 23b70c93 09745c9d
+ ff5533ac 7e96941f e4bfff7e 8d615ab6
+ 4d9f26d4 b62dd320 7fea603a eed30dae
+ 631ec89f f0fce0e9 81c0f7c8 2f3bfaf3
+ 30181761 144fa250 70360fca 3f925f93
+ 98d34078 9866f915 591af269 4866f23b
+ 312c5a7c 9222cdcb 3bf254c8 e213b592
+ 5f8bc144 5cae100b f63b5186 181c7bb1
+ 529ef221 0af93946 50281097 9bc45028
+ 81f15353 a976c5d0 a2543f32 1f8f1648
+ d6cd2eb5 2c10f5be df0ebf40 2cbaf63f
+ a210a098 0084d1b5 b4112b90 ffcda61c
+ dc0241d6 9c89435e 5cc5c38b 301914c8
+ 3d7c6b4b e9b576c4 2d2bee60 6d7cad29
+ d420bb99 0cf1653b f267c906 d4f62dcf
+ 1878966f 533690cf f9b2299c 2193ec64
+ 31d4968d e624d897 407fc8ea 8fe5807d
+ 11a74653 b60c5964 1787a168 39ed2185
+ 7fbcf75c 1cb240b6 1c0f7276 e788bb59
+ 13b24fce c3611bea 2c0a9b6e 16ef4ca2
+ 6ac9ac9b a592216a 252b1f4d 0cc9b49b
+ 65724f12 4b95c9bb 492ef418 eba308bd
+ 86dcbe27 e6598e5a 32541dc8 06878a21
+ 59eb49b6 fe760c89 b4b94412 05c6f687
+ a544913e 1db4a1cc 814a29e3 794b8544
+ 4e42364b 1c89c4bc b331bea4 87d5c75d
+ 29b5356b 409d36c9 2ea6ba13 cc49527c
+ e9aebd92 affad98f 4ad58fe0 88350379
+ 07b52bcb bb72d7a3 3a25ca7e a4a6ec93
+ 553c75b2 9b72f0af 3b24b65c 21f0bdab
+ fde55392 4eddcb5b 35187a71 cec65e3a
+ 7225ea1f ce0ada17 37a36b86 53a9455d
+ e4705487 d5a8b3fc b6380d85 1c386751
+ 9c4512b5 b0d5ba5f 6edb8f5a d50f822c
+ 2979d33f d0652948 85be9005 7b5974d5
+ 6f38e01c bad741ca 397793a1 54d4af91
+ 4752b193 e59e60a1 f475d001 e70179c0
+ c213f578 22e77822 19ef94f6 eb349144
+ f4b84812 60a9fc75 50e30beb d1a6722e
+ f4e4586f 631094c3 459223d5 3b2db070
+ f2b021e5 db20c417 e63865ce e3d7d5fa
+ 673cba4c 62e23be1 58b97cd4 92e45dfb
+ f609bfed 903d4150 346cd7cc c1774eda
+ 1ddf937b 0d0180e6 e3cbab00 08123dc8
+ db166acf fde59a00 88d7a178 0300d6bd
+ 86003c7f 0c520030 c8ad20fd 29d4a531
+ 0e74a9c5 2406d118 00908ed5 9f01eeb4
+ c4d41ad2 01404526 3640fd31 eb045c88
+ f0a8a607 009be82a 00e8b496 98e41657
+ 6bc83f52 ca43c759 1c8ac2c0 163205c9
+ 258e2524 c7941448 4fa6effc efff5c7b
+ b35c6106 dfb059f6 53733807 fbb3cd8c
+ 00e59814 c16b23c8 601987c1 236d89d7
+ 60db3992 87e00609 bca9dd81 e025b622
+ b95b23bf eef6b70e bf3f372a ba72acc0
+ 76e27d82 05a61ac6 4940f304 5f535537
+ 28bc435a 4474ae0d 897f7bee 9b4898c3
+ fb06152e 5081e653 40033589 b5a53b10
+ 5ac4a56e 99eb8659 f9f32b79 f48e6489
+ 4ba026c1 2363acc0 9681f1b4 2f52b7a0
+ 08f5b18b da61fb9b 9ec2b406 c302c68f
+ f4cd30c6 db29189b 043d91d9 90084e01
+ 2685ab49 f777834c 18b335c2 19a3efe6
+ 196c370d 57dbf0a7 08631d11 c5182932
+ 5d33265d 4d33d63b fdf07aea b8089cfa
+ 73401013 9d6111fa 54adab95 30e5f06e
+ 63ea82e1 798d1d0b a7e68b60 0880c816
+ 037fc755 5babf060 7ba698d6 904681cf
+ c45ab56b 28adb589 27a20745 1098bc74
+ 3d5f2481 a94b5c46 593ba1fe ad5aabb1
+ 31b3b67f 7bbfb729 4e714364 6d6dea7a
+ b06ad8bf 1b6b6798 6a6ba97f ae84ea08
+ 11ec1122 f4c113b7 0b4feb7d 030ca3c1
+ 05e47891 089ee132 b51f030b 6b332cac
+ a010fb0b a4840879 ae832231 3c5bb90d
+ 2f823e5b ce25e605 e7eb8010 d9b7d022
+ 6e197247 c89af302 73c9b93f cd81f319
+ e69af303 11cf3126 674022ce f9aead2d
+ e117b590 c67c060b f97996cd 8f0d26cb
+ 4e2345a0 a65c6d4e 2d74ca32 830bcdb3
+ cccfd759 563891ed 8027d540 1eb37616
+ ac4dfc6f c0891459 b6f67221 448d390c
+ 1f478924 b01bb16f 5b7bd08a 7c59981e
+ 0b350cff 4524f444 84e86c35 17624b79
+ c09b956b 95f033f6 5ae11d22 e7867122
+ d39300f4 d1a18438 04a4082e 3456e4dc
+ a285b874 2bd370f1 22aea467 c70a7e47
+ ff592486 679bd77f 38c230a1 445e5b36
+ 82022bc8 f540de02 5a042b3b fa44ba22
+ 2129e248 0541ba7f eda0f19b 1f10d950
+ 224aa91d e630f445 942a5f3b 94aac76c
+ 797ced17 0e012502 01767630 1c2be2d8
+ 7539bff6 a86092cb 74acc87b c32d91f7
+ fb291590 04b744b0 754b64ff d100c34f
+ 4264f971 2faf7305 4401c927 44d8db53
+ 2217a52a ccb552db ff23b2cf 490f64ab
+ 94c66205 d7e7e507 29575f0d a9942732
+ be8f4202 9724b8c1 49ca149b 2b290f94
+ 48817949 e41057f7 79ec2470 f82ba0c0
+ 994aa7ec 8b6c9d68 0d1b1a2f a2e1ed45
+ 709b8b94 b53bfaad 972fa4cc bf1b8eb0
+ a59810f9 be0b7839 1af08821 3f623597
+ d2570ea1 f04ece45 8bec2ac7 7b4f241c
+ 108920c7 e63b5d35 c6ecb191 1af3d217
+ 31a6fe76 bc42a5c3 a613cddc cb142fc6
+ a4d8dcc3 9b44e397 5baa32e6 ad9fe679
+ 5744e73f d9bb6809 3f07be91 37632a6c
+ d6c6fca2 54f3bcc0 c619e64a 86447650
+ 787b796a 8028d75f 3f242f71 708304ca
+ 672c1679 4e9ddddf 8456c792 eb2010f4
+ 4da77785 2a0e1c98 924b72ce 56708e9b
+ feff7b5e d72e3028 d9fddeee 4a9aeea6
+ 67c0612f 84280f7f 9042cc6a 4184181c
+ 3c8c1041 2f199486 8b53c1d5 06664248
+ cb2bc1db b71d673c 9f5a4e22 c4fa5510
+ 12e2180e 13d23957 53c1e53a d6422441
+ e0e80d67 0ecea91e 64d419a4 58a0cfd2
+ 574b0d79 fb3beb89 2373b72d 53d307de
+ 9b6523c8 d98383f0 e6f4b9bc e9dc9c25
+ efcc4157 f7bfeab6 b15629ae 8b4a1029
+ 8320b194 d5d95fa4 942bae8f 24134214
+ b04d83b4 59c79baf 948e55b2 930f327e
+ 11a48056 1d9840d0 9f7a0d1c f1b874bd
+ 4ab9e83a d1815902 5ad119a4 df6838c3
+ 12092a41 236bafe7 b84c3a28 29675daf
+ f160be79 6d243ba5 c6578fbe 52f5c95f
+ 94525b66 8c713b6b 1b486e17 58e1baf3
+ abc24d29 0ad6ba47 9520535e 26690629
+ ee4ae973 85a26a9b 1381d277 7552ead6
+ ebc21ee2 dc124b5c 5faa411e 1e1c8491
+ 419032e5 a0d149d1 185a69ed 735cefb9
+ d4d255e2 9c069596 765a4f9f 1e89d61c
+ c4e1a2b5 ce99b3c2 ed25ac9f 6038e056
+ 38660b8e 1097d6e8 6cb4be15 4190392f
+ 928641b8 0dadcdb5 42d25950 bea17cb6
+ b552571c 5abd92b0 a5e5fb20 8c0c8211
+ 931ea475 70cc96a8 ae8291f2 405e8c84
+ 5bdabb20 c6ac3e3c 5263dafa b918634a
+ 6695b8dd f9f307b3 f4f97620 3c126362
+ 67742663 eeffda82 0c10a443 4d4fa68d
+ 90e464df 1a50a2b3 2bc4c6f0 40da7184
+ 761bb674 2b7c904f 8f6a1046 06c18869
+ cfc4982f 7b40c261 6d0deb3b 01053d9c
+ 62ee9320 c8865718 358d7873 0ecc5b61
+ 6f7c8eb9 737d105c 7bef7082 6410b46e
+ ad2e4463 5e8088f6 adeae84e 147f78de
+ 98e862db a8999e7a 6f812529 779a8f94
+ febcf6f8 fb704f4b dcb4bd3d 5bdedcf1
+ 1e84c0d6 32f5f172 6bf90e05 9aea5f2b
+ 4976452f 8ae338b1 5f1256b8 feee92ef
+ 501c1f7e 799b04d7 51afd855 725cfb44
+ 7c525f21 838ee7f8 808e704e 7ee20a3a
+ 471bdd2b bc1f9c0f 42670fde 18dcfe6f
+ af5e7694 85a1008e 9f1d2b5e 83585342
+ 4c8cdc02 2975a2f4 4a19bff7 7f96ef94
+ 4174eee0 e8ace647 5c68da93 bf674306
+ b3481cb0 356747ea 1710828c 2e6c059f
+ 0a6d7421 2154789d 1ecd995f a784798a
+ 273c5c1a 235e1ee1 4b6c058c ab68f024
+ 832fafeb 2e1a28ce a0b2be43 4cc372fc
+ 8afb9d29 f07faa36 67228e10 2f60b182
+ 472816e6 acf6eb0c 603616a1 8399e411
+ 520c1662 2a42b999 1c22b468 4c851748
+ 6b267b12 211ec002 018f10d9 9b49eba7
+ 54b0885e ad56a475 1391ac90 d505cc54
+ 68bb4289 709396e0 0f1a16e2 bee4e8cc
+ f8385793 29657606 a99d9b46 1cfd000e
+ 8b71821a 77617232 e00cbec1 3819e4c6
+ 5d34c35d 801b4b6a 77456cc9 40c9103e
+ 154a4506 5be1aed4 b776204d 50eeaeb5
+ 2979a178 15c03b41 c5157991 b6ee5a4e
+ 90861b55 0425a2bf 664a4a46 b6933a63
+ 28c44fa6 6567c988 96a6bf26 12822ab8
+ 19b3ebf5 9aeefbd7 da92aebf 40cbb67f
+ 6defcf5b 063f10a8 35dab9fe 0d516fd7
+ 1fdad6a2 7fc3edd6 4805f023 85a4283e
+ f4ef8943 9da67492 a6f541f4 ef1d628a
+ 64013f95 598a36e6 f9266643 91cde00e
+ 0a4ebddc dc909153 8f17701f 4cc55e6e
+ 4e8b983c f61483fb d136f652 f17c9ae9
+ 59a4b167 35dc5531 a6248dfb 37836b92
+ 31a3807b c39464b0 6bcce9ab 88936976
+ c9e01119 5e51a964 541e4dff 51446f8e
+ 65325255 010f134a 9b9c6dcb a615ceeb
+ 1d126d53 26132b43 78b04caa cd3794cc
+ e0570415 ff346678 2dff2aa6 75d7d9cd
+ c4769dd6 0cfefc79 94ff183d 2a93948d
+ 64820000 00004945 4e44ae42 6082
+ </rawimagedata>
+ </cbdt_bitmap_format_17>
+ <cbdt_bitmap_format_17 name="eight">
+ <SmallGlyphMetrics>
+ <height value="128"/>
+ <width value="136"/>
+ <BearingX value="0"/>
+ <BearingY value="101"/>
+ <Advance value="136"/>
+ </SmallGlyphMetrics>
+ <rawimagedata>
+ 89504e47 0d0a1a0a 0000000d 49484452
+ 00000088 00000080 08030000 00e737d1
+ 0d000000 36504c54 454c6971 40404040
+ 40404040 40404040 40404071 7171c3c3
+ c3dfdfdf efefeff4 f4f4fafa fad0d0d0
+ f8f8f8b6 b6b6e7e7 e7909090 a6a6a6ad
+ 4143d100 00001274 524e5300 0a131e29
+ 3340739e c8dfff84 f266b34d 59e71538
+ bc000003 04494441 547801ed 9ac1b29d
+ 200c860b f02902a2 beffcb76 3a3d73ab
+ a15e4f17 c959946f ed8cbf49 0249cc8f
+ c1603018 0c0683c1 6030b0c7 f910232f
+ 620cde7d 44458874 c460adc5 456e88ce
+ 5ec634a7 25971779 49f3642c 25005097
+ d2b15400 828d8e08 5073f92b 6b028856
+ 3a5a2eb7 e406441b 1da97c4b 02a2858e
+ ad3cb04d ea4a82d0 71c3a21d b1eece2f
+ 920d70ba 8e99cb5b ccaacef1 30e5f216
+ eb045ed3 20a93b37 e61df639 ad7dea44
+ c50899d6 ee752fa6 d49bc4e9 a54cbdbe
+ ece0c47c 155921e8 79662967 1a10bdfb
+ 559944a4 caace71b a0f38b3f 994b0490
+ 9a6f1c34 190527e3 0719414d 2b6fbc30
+ fe266c1f c5a19bb4 822408db cfe085d0
+ 6623447c f10e4e9e ffe2c289 26490374
+ c1bc7e5e 887ce07f 102282f5 d9355659
+ b3839342 4cb2c6c3 fc2fe95b c1ab5dbe
+ 0f075a6f 311522e4 72e27ac4 7b71c467
+ c0a80c48 9c947820 c932c0aa 30aad732
+ e0d02b8c 04519864 9d3971ac 7aa5a2c0
+ 21a2a4a4 e9a654cc 80576d27 f6b59c59
+ b7b9c121 8be7f580 a8dc60b5 37db1a54
+ c7241ea8 e5910a78 fde6b7ad 0fcd55b3
+ 18d60460 5fca372c bba20ea1 847a6b94
+ 5cb1d1e1 7930c9b6 03380b1d d31b1323
+ bcbe8e23 9707f204 78751d6b 79643d74
+ bde3a48e 4f2989bd 8e9c5a83 36a72c94
+ 68cef37c 77e72d8d 2f5a36bb f5ba7e3f
+ 7161331a 1979d8bb 1b85f0bb 300a208b
+ 955dcb24 7db70fd1 5dfe586c b2b8b628
+ e2f3a576 160312cd 625196ce b5fbe0d8
+ 3f11f45b df1570bd cd54e612 cf438767
+ ad2a21b2 f79ded73 77ac3eca 9bc13f75
+ c74d5f88 7c897c66 083116f2 744878a8
+ e642b6e7 ac5113b2 cb6b5ebe 05c8327d
+ f57f09b4 ce24415e cf80c174 7301bcb0
+ 199b3ce2 2d7e1b55 e4e88a26 1f081675
+ d17afcd9 18713ed2 8d2cb42a 2360e99a
+ 6d313292 bed36a7a 5bdf5e22 4746fad3
+ 3c072cfd c6480338 eaf63287 c86f2d93
+ 1ce56d0e d5f126b5 bc49d26c f5bc3c29
+ eed980f0 a9ad0da1 23daefb1 98e9104a
+ ead330af 9aecf6bc 37cc8b46 c3bc762b
+ 6569669b 681e803d e5d291d3 0180375d
+ cd63afd7 15c1ba83 b239242e 708bf502
+ a70ff410 fca7166b f9e2338b b583c160
+ 30180c06 83c160f0 13e55b78 1b5bd5fd
+ 5a000000 0049454e 44ae4260 82
+ </rawimagedata>
+ </cbdt_bitmap_format_17>
+ <cbdt_bitmap_format_17 name="uni2049">
+ <SmallGlyphMetrics>
+ <height value="128"/>
+ <width value="136"/>
+ <BearingX value="0"/>
+ <BearingY value="101"/>
+ <Advance value="136"/>
+ </SmallGlyphMetrics>
+ <rawimagedata>
+ 89504e47 0d0a1a0a 0000000d 49484452
+ 00000088 00000080 08030000 00e737d1
+ 0d000001 3b504c54 454c6971 d73b3bd9
+ 4141da44 44d94141 d94242d5 3737d83f
+ 3fd84040 d94040d9 4141d941 41d53838
+ d13030d8 3f3fee4a 4af74d4d e34545eb
+ 4747ff50 50fe5050 fe4e4eda 4242fe4f
+ 4ffb4747 d94040d3 3434fd4c 4ce14040
+ f34747fc 4b4bd83e 3eeb4242 fb4949fa
+ 4545d639 39d02e2e dc3a3aef 3f3fd333
+ 33f94343 d43737f8 4141f740 40ed3939
+ e13434d3 3434cf2b 2bf63e3e f53c3cd2
+ 3131f439 39d02c2c cc2626d0 2d2df337
+ 37f23535 e83030cf 2b2bf132 32ce2929
+ dd2b2bcb 2222f030 30cb2323 d42525cd
+ 2626ef2d 2dc81d1d c31313cb 2222eb25
+ 25ee2929 c81b1bdb 2121c91f 1fec2525
+ ea2222eb 2424c71a 1ac10c0c c61919c0
+ 0a0ac00a 0ac20c0c c10c0cd1 0b0bda0c
+ 0cc00a0a e10c0cc1 0808be06 06df0909
+ cb0202de 0606d403 03bc0202 dc0303b9
+ 0000b800 00da0101 b80000b9 0000b800
+ 00b70000 fbff4a1d 00000068 74524e53
+ 0040cfff df583080 afbfef8f 10209fff
+ ffffffff ffffffff ffff70ff ffffffff
+ ffffffff 60ffffef ffffffff ffffffbf
+ ffffffff 9fd5ffff ffffffff ffff80ff
+ 8fffffff ef40ffff ffbfffff ffffffff
+ a0ff50df ff8fffff 8fffffff ffffffff
+ ffffdf9f ff50ff8f 9e461cce 00000655
+ 49444154 7801ec96 e9e2e22a 0c476ff7
+ 55812254 dcfabfef ff929320 636b6c71
+ c3d98fdf f0977808 a8fdef1f ff788128
+ 4e6e48b3 7b557956 94553d56 c4555344
+ ed3b1e59 328bcf64 95954e81 1237d9cb
+ 2269324b ba946fb3 2af151af f3d744a0
+ 96710283 c5d5bc46 719985e8 189b9430
+ 7171a9b2 1745b824 7058cc7d 1a827179
+ 0be79d70 2a513891 b96b9d3a 8b8d5c04
+ a78934ed c744dae6 ac31492b 3e833c1f
+ 529a7f48 64156b00 34b81f6c c6120d3c
+ 7953a0e2 56041649 2caf612d 614b6320
+ 6c843509 2f821ebe 7110140c c59a0417
+ b11e5dbf d928fe20 ea691314 d910a848
+ 6b3dfc16 0437933c ac486c3d 36923f07
+ 54a56d48 9102ef47 3f734b7b c63a21ec
+ 4f6ccf29 b2c79934 0f8ba45a b39e702d
+ 92a387ea a949dfe9 291da32e 4a8a670e
+ a79a1161 30d3ab84 e654830b 4d49ae55
+ 94b41baa de1419eb 236d1357 26dbef1a
+ 55d514f8 60a22d82 13153c9c 28944865
+ 8c564a49 b5e5eea5 943640dd 8c9fb1da
+ c506d05c f131266d 70fda888 319d2274
+ c65c4472 e3025272 47af5123 bb79d23b
+ 9b8c833b 77326d18 9106baef ad08771b
+ e5e851ce b45f1b9b e5e34014 87a52c8c
+ 486a8c50 167749e4 c14ce74d 4d84cd21
+ 0a398073 10113c99 e3dea2e0 fced9bcb
+ e75eda34 de26f4c6 224c3f2e b2274c44
+ 0a9cb643 49789da0 73bcd4ac ad8d1198
+ 430d2b82 67138510 29b1f3c5 449db798
+ 2f35436f c3210749 87366617 4224a56f
+ fbbf906d 4df3c298 e62191c6 18e111b1
+ 873ee1e8 ff1ae07d 3d2cf5f2 53cc8888
+ cbae231c 35e95bfb da655040 45d27744
+ 0a27320c 43779c70 1806ef06 5750703c
+ 4d38c2c2 6322c370 3811e0d3 5004a987
+ 1bfc77ef 5322d940 895b6fbf 7818bec2
+ 8ba0493c 4ca9d7c4 8352bd23 f23fc18a
+ bc088a4c 7bfd13f9 65446a10 d94ef87a
+ 46644b78 43a4c5af 2f11a9be 114717ba
+ 11c34010 40b74cee 89a10ca6 630ef9ff
+ 3fac7315 d99b8241 ca8d78a5 993c3951
+ cd0b340b 217cee93 6d8da29a 2752ca4f
+ 96372915 e5e54e4a cdb71eb3 21389d50
+ 5ecea57c f6a70cb6 2e8f0051 a81a7fea
+ 594a4b47 80dc48a9 c77eb494 a378c898
+ 251ba2d0 7cf7970c 9f1a0622 ec0f0f72
+ 4e9190c9 64c22138 6541ae51 34c183e0
+ f0383ce4 113d3df5 a327132b 12205396
+ 3c889a20 337fc7e0 70454343 2e2d6ac6
+ 9f99c984 0721c1ea 6910e698 07330b5c
+ 144567b9 5c9a5910 83934876 a034efcd
+ aca81492 e358042b eb094e97 89907590
+ 68087384 2b1aa72b 4a83ccc3 89394ea5
+ 8ed922f1 c7144098 6313e4b0 614532a4
+ 3f52ec40 2e694808 1cdbedb6 e7c04d51
+ 5a50e110 9cca1c5b e4913220 bb202910
+ f1ede07d 644f8342 4455e060 59fdb814
+ 5baeeb7a d104996f 71cb7060 8b4f2dea
+ ba8aebee f1cd2d73 d4dc5104 594555d5
+ c1d1963b 4a21f716 1f9db77e 921d0cd2
+ 068985a0 597741b3 2b70d0ca 390e712e
+ 06f2e878 b373c89e 068608eb 5cdd777c
+ 1143c74a 0ac25014 865359b1 2f604d6f
+ 2305eb05 f50a27c8 d1d57dff 9759ab4d
+ 120b329b ccf20fed c97c97ad 290639a4
+ 41f622d2 1ebcda4f 11a9cddf 2122dd21
+ a8135986 5412efd2 1d4521bd 881ca395
+ 9cccff43 9ad7ece8 d5cad22a e1b4f331
+ 2805b251 d53618a9 ea471e44 35829c55
+ fb8495fa 9b8bba51 06e4e297 04d9a976
+ fea8531d aa321057 0a442388 aaeecd0a
+ 90118075 8ba905b0 c9850031 045882d4
+ 0026af0e 684c01c8 14e420cb 2337d917
+ 825cdc97 00d94510 00f52a7f e40accde
+ c202d864 43c81842 2e411af2 176227db
+ 91340520 b8f9d904 08c9bb37 99c9abc9
+ 6d24790b 421ac4ba 8a416c10 c9716134
+ 909c5d24 4f45205f 410990fe 1137e643
+ 5eafdc83 129ead9a c8d19b22 90d977cc
+ 49f7d5cf abeb399a fcaae1f1 5e6556a8
+ 7f77f466 95b6c377 d090e9f8 698f0e76
+ 1c048130 8e73da5b 1f8b3b11 4b5b953a
+ 8222efff 043b241b 56d9c334 0e7be377
+ 2bc9ff8b 50869b3c b889a669 9a0b3aa5
+ 7ba455c7 6f18a4ee 332d990d c3bd3f51
+ 179abba8 40f58979 20437c09 aba13c7b
+ 645e3fcc 47abcf61 188e0dfe 7c0a2699
+ 361fafec 91563ba2 19c77138 36031e48
+ c1a30f9b 7955d76f 281ddec5 bc4e0c1e
+ 7564339d 900d49e1 e5a6025e 4f5d69b8
+ ff8c9d0a 16df996a de05aa21 e193ceef
+ c28c87b5 1b1200fc 1dc5c3af 0bcdbf7c
+ c88d6896 02e34388 d1ca0dc9 01d8a560
+ 015ced86 e401c6a5 300278aa 590b5443
+ da420876 3db178b4 d56e682e 0498d783
+ 194270f5 1b920c79 356f86ed 422305d3
+ 9e566d7e e3b4e9c9 26c6187e 9b803f77
+ c1e62302 3b230b11 f98f9b24 377c7b3c
+ d9990d83 74317392 df306cde 45e4fc56
+ af691a86 6f38d2f4 13a054ea b3000000
+ 0049454e 44ae4260 82
+ </rawimagedata>
+ </cbdt_bitmap_format_17>
+ </strikedata>
+ </CBDT>
+
+ <CBLC>
+ <header version="3.0"/>
+ <strike index="0">
+ <bitmapSizeTable>
+ <sbitLineMetrics direction="hori">
+ <ascender value="101"/>
+ <descender value="-27"/>
+ <widthMax value="136"/>
+ <caretSlopeNumerator value="0"/>
+ <caretSlopeDenominator value="0"/>
+ <caretOffset value="0"/>
+ <minOriginSB value="0"/>
+ <minAdvanceSB value="0"/>
+ <maxBeforeBL value="0"/>
+ <minAfterBL value="0"/>
+ <pad1 value="0"/>
+ <pad2 value="0"/>
+ </sbitLineMetrics>
+ <sbitLineMetrics direction="vert">
+ <ascender value="101"/>
+ <descender value="-27"/>
+ <widthMax value="136"/>
+ <caretSlopeNumerator value="0"/>
+ <caretSlopeDenominator value="0"/>
+ <caretOffset value="0"/>
+ <minOriginSB value="0"/>
+ <minAdvanceSB value="0"/>
+ <maxBeforeBL value="0"/>
+ <minAfterBL value="0"/>
+ <pad1 value="0"/>
+ <pad2 value="0"/>
+ </sbitLineMetrics>
+ <colorRef value="0"/>
+ <startGlyphIndex value="1"/>
+ <endGlyphIndex value="3"/>
+ <ppemX value="109"/>
+ <ppemY value="109"/>
+ <bitDepth value="32"/>
+ <flags value="1"/>
+ </bitmapSizeTable>
+ <!-- GlyphIds are written but not read. The firstGlyphIndex and
+ lastGlyphIndex values will be recalculated by the compiler. -->
+ <eblc_index_sub_table_3 imageFormat="17" firstGlyphIndex="1" lastGlyphIndex="2">
+ <glyphLoc id="1" name="eight"/>
+ <glyphLoc id="2" name="registered"/>
+ </eblc_index_sub_table_3>
+ <eblc_index_sub_table_3 imageFormat="17" firstGlyphIndex="3" lastGlyphIndex="3">
+ <glyphLoc id="3" name="uni2049"/>
+ </eblc_index_sub_table_3>
+ </strike>
+ </CBLC>
+
+ <vhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1275"/>
+ <descent value="-1275"/>
+ <lineGap value="0"/>
+ <advanceHeightMax value="2500"/>
+ <minTopSideBearing value="0"/>
+ <minBottomSideBearing value="0"/>
+ <yMaxExtent value="2400"/>
+ <caretSlopeRise value="0"/>
+ <caretSlopeRun value="1"/>
+ <caretOffset value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <reserved4 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfVMetrics value="1"/>
+ </vhea>
+
+ <vmtx>
+ <mtx name=".notdef" height="2500" tsb="0"/>
+ <mtx name="eight" height="2500" tsb="0"/>
+ <mtx name="registered" height="2500" tsb="0"/>
+ <mtx name="uni2049" height="2500" tsb="0"/>
+ </vmtx>
+
+</ttFont>
diff --git a/Tests/ttLib/tables/otTables_test.py b/Tests/ttLib/tables/otTables_test.py
index 4afe2db..bd4daeb 100644
--- a/Tests/ttLib/tables/otTables_test.py
+++ b/Tests/ttLib/tables/otTables_test.py
@@ -605,20 +605,87 @@
ok = otTables.splitMarkBasePos(oldSubTable, newSubTable, overflowRecord=None)
assert ok
- assert oldSubTable.Format == newSubTable.Format
- assert oldSubTable.MarkCoverage.glyphs == [
- "acutecomb", "gravecomb"
+
+ assert getXML(oldSubTable.toXML) == [
+ '<MarkBasePos Format="1">',
+ ' <MarkCoverage Format="1">',
+ ' <Glyph value="acutecomb"/>',
+ ' <Glyph value="gravecomb"/>',
+ ' </MarkCoverage>',
+ ' <BaseCoverage Format="1">',
+ ' <Glyph value="a"/>',
+ ' <Glyph value="c"/>',
+ ' </BaseCoverage>',
+ ' <!-- ClassCount=1 -->',
+ ' <MarkArray>',
+ ' <!-- MarkCount=2 -->',
+ ' <MarkRecord index="0">',
+ ' <Class value="0"/>',
+ ' <MarkAnchor Format="1">',
+ ' <XCoordinate value="0"/>',
+ ' <YCoordinate value="600"/>',
+ ' </MarkAnchor>',
+ ' </MarkRecord>',
+ ' <MarkRecord index="1">',
+ ' <Class value="0"/>',
+ ' <MarkAnchor Format="1">',
+ ' <XCoordinate value="0"/>',
+ ' <YCoordinate value="590"/>',
+ ' </MarkAnchor>',
+ ' </MarkRecord>',
+ ' </MarkArray>',
+ ' <BaseArray>',
+ ' <!-- BaseCount=2 -->',
+ ' <BaseRecord index="0">',
+ ' <BaseAnchor index="0" Format="1">',
+ ' <XCoordinate value="350"/>',
+ ' <YCoordinate value="500"/>',
+ ' </BaseAnchor>',
+ ' </BaseRecord>',
+ ' <BaseRecord index="1">',
+ ' <BaseAnchor index="0" Format="1">',
+ ' <XCoordinate value="300"/>',
+ ' <YCoordinate value="700"/>',
+ ' </BaseAnchor>',
+ ' </BaseRecord>',
+ ' </BaseArray>',
+ '</MarkBasePos>',
]
- assert newSubTable.MarkCoverage.glyphs == ["cedillacomb"]
- assert newSubTable.MarkCoverage.Format == 1
- assert oldSubTable.BaseCoverage.glyphs == newSubTable.BaseCoverage.glyphs
- assert newSubTable.BaseCoverage.Format == 1
- assert oldSubTable.ClassCount == newSubTable.ClassCount == 1
- assert oldSubTable.MarkArray.MarkCount == 2
- assert newSubTable.MarkArray.MarkCount == 1
- assert oldSubTable.BaseArray.BaseCount == newSubTable.BaseArray.BaseCount
- assert newSubTable.BaseArray.BaseRecord[0].BaseAnchor[0] is None
- assert newSubTable.BaseArray.BaseRecord[1].BaseAnchor[0] == buildAnchor(300, 0)
+
+ assert getXML(newSubTable.toXML) == [
+ '<MarkBasePos Format="1">',
+ ' <MarkCoverage Format="1">',
+ ' <Glyph value="cedillacomb"/>',
+ ' </MarkCoverage>',
+ ' <BaseCoverage Format="1">',
+ ' <Glyph value="a"/>',
+ ' <Glyph value="c"/>',
+ ' </BaseCoverage>',
+ ' <!-- ClassCount=1 -->',
+ ' <MarkArray>',
+ ' <!-- MarkCount=1 -->',
+ ' <MarkRecord index="0">',
+ ' <Class value="0"/>',
+ ' <MarkAnchor Format="1">',
+ ' <XCoordinate value="0"/>',
+ ' <YCoordinate value="0"/>',
+ ' </MarkAnchor>',
+ ' </MarkRecord>',
+ ' </MarkArray>',
+ ' <BaseArray>',
+ ' <!-- BaseCount=2 -->',
+ ' <BaseRecord index="0">',
+ ' <BaseAnchor index="0" empty="1"/>',
+ ' </BaseRecord>',
+ ' <BaseRecord index="1">',
+ ' <BaseAnchor index="0" Format="1">',
+ ' <XCoordinate value="300"/>',
+ ' <YCoordinate value="0"/>',
+ ' </BaseAnchor>',
+ ' </BaseRecord>',
+ ' </BaseArray>',
+ '</MarkBasePos>',
+ ]
if __name__ == "__main__":
diff --git a/Tests/varLib/data/TestCFF2Input.designspace b/Tests/varLib/data/TestCFF2Input.designspace
new file mode 100644
index 0000000..ebbf14a
--- /dev/null
+++ b/Tests/varLib/data/TestCFF2Input.designspace
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<designspace format="3">
+ <axes>
+ <axis default="400.0" maximum="900.0" minimum="200.0" name="weight" tag="wght">
+ <map input="200" output="0" /> <!-- ExtraLight -->
+ <map input="300" output="100" /> <!-- Light -->
+ <map input="400" output="368" /> <!-- Regular -->
+ <map input="500" output="486" /> <!-- Medium -->
+ <map input="600" output="600" /> <!-- Semibold -->
+ <map input="700" output="824" /> <!-- Bold -->
+ <map input="900" output="1000" /><!-- Black -->
+ </axis>
+ </axes>
+ <rules>
+ <rule name="named.rule.1">
+ <conditionset>
+ <condition maximum="600" minimum="0" name="weight" />
+ </conditionset>
+ <sub name="dollar" with="dollar.a" />
+ </rule>
+ </rules>
+ <sources>
+ <source filename="master_cff2_input/TestCFF2_ExtraLight.ufo" name="master_0">
+ <lib copy="1" />
+ <location>
+ <dimension name="weight" xvalue="0" />
+ </location>
+ </source>
+ <source filename="master_cff2_input/TestCFF2_Regular.ufo" name="master_1">
+ <glyph mute="1" name="T" />
+ <info copy="1" />
+ <location>
+ <dimension name="weight" xvalue="368" />
+ </location>
+ </source>
+ <source filename="master_cff2_input/TestCFF2_Black.ufo" name="master_2">
+ <location>
+ <dimension name="weight" xvalue="1000" />
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance familyname="Test CFF2 Roman" postscriptfontname="TestCFF2Roman-ExtraLight" stylename="ExtraLight">
+ <location>
+ <dimension name="weight" xvalue="0" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ <instance familyname="Test CFF2 Roman" postscriptfontname="TestCFF2Roman-Light" stylename="Light">
+ <location>
+ <dimension name="weight" xvalue="100" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ <instance familyname="Test CFF2 Roman" postscriptfontname="TestCFF2Roman-Regular" stylename="Regular">
+ <location>
+ <dimension name="weight" xvalue="368" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ <instance familyname="Test CFF2 Roman" postscriptfontname="TestCFF2Roman-Medium" stylename="Medium">
+ <location>
+ <dimension name="weight" xvalue="486" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ <instance familyname="Test CFF2 Roman" postscriptfontname="TestCFF2Roman-Semibold" stylename="Semibold">
+ <location>
+ <dimension name="weight" xvalue="600" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ <instance familyname="Test CFF2 Roman" postscriptfontname="TestCFF2Roman-Bold" stylename="Bold">
+ <location>
+ <dimension name="weight" xvalue="824" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ <instance familyname="Test CFF2 Roman" postscriptfontname="TestCFF2Roman-Black" stylename="Black">
+ <location>
+ <dimension name="weight" xvalue="1000" />
+ </location>
+ <kerning />
+ <info />
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/varLib/data/VarLibLocationTest.designspace b/Tests/varLib/data/VarLibLocationTest.designspace
new file mode 100644
index 0000000..b73e1b5
--- /dev/null
+++ b/Tests/varLib/data/VarLibLocationTest.designspace
@@ -0,0 +1,26 @@
+<?xml version='1.0' encoding='utf-8'?>
+<designspace format="4.0">
+ <axes>
+ <axis default="100" maximum="900" minimum="100" name="weight" tag="wght">
+ <map input="500" output="105"/>
+ <map input="300" output="57"/>
+ <map input="900" output="158"/>
+ <map input="100" output="0"/>
+ </axis>
+ <axis default="50" maximum="100" minimum="0" name="width" tag="wdth" />
+ </axes>
+ <sources>
+ <source filename="A.ufo">
+ <location>
+ <dimension name="weight" xvalue="0" />
+ <dimension name="width" xvalue="50" />
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance filename="C.ufo" familyname="C" stylename="CCC">
+ <location>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/varLib/data/master_cff2_input/TestCFF2_Black.ttx b/Tests/varLib/data/master_cff2_input/TestCFF2_Black.ttx
new file mode 100644
index 0000000..22f8275
--- /dev/null
+++ b/Tests/varLib/data/master_cff2_input/TestCFF2_Black.ttx
@@ -0,0 +1,510 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.2">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="T"/>
+ <GlyphID id="3" name="dollar.a"/>
+ <GlyphID id="4" name="dollar"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.01"/>
+ <checkSumAdjustment value="0x26378952"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Thu Nov 29 14:52:09 2018"/>
+ <modified value="Thu Nov 29 14:52:09 2018"/>
+ <xMin value="0"/>
+ <yMin value="-116"/>
+ <xMax value="600"/>
+ <yMax value="750"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="3"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="984"/>
+ <descent value="-273"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="600"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="0"/>
+ <xMaxExtent value="600"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="5"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x5000"/>
+ <numGlyphs value="5"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="592"/>
+ <usWeightClass value="900"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="11"/>
+ <bWeight value="8"/>
+ <bProportion value="9"/>
+ <bContrast value="3"/>
+ <bStrokeVariation value="4"/>
+ <bArmStyle value="3"/>
+ <bLetterForm value="2"/>
+ <bMidline value="2"/>
+ <bXHeight value="4"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="ADBO"/>
+ <fsSelection value="00000000 00000000"/>
+ <usFirstCharIndex value="36"/>
+ <usLastCharIndex value="84"/>
+ <sTypoAscender value="750"/>
+ <sTypoDescender value="-250"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="984"/>
+ <usWinDescent value="273"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="660"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="1"/>
+ </OS_2>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ 1.010;ADBO;SourceCode_Black
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="5" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Version 1.010;hotconv 1.0.109;makeotfexe 2.5.65596
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ SourceCode_Black
+ </namerecord>
+ <namerecord nameID="17" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Roman Master 2
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.010;ADBO;SourceCode_Black
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.010;hotconv 1.0.109;makeotfexe 2.5.65596
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SourceCode_Black
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Roman Master 2
+ </namerecord>
+ </name>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x54" name="T"/><!-- LATIN CAPITAL LETTER T -->
+ </cmap_format_4>
+ <cmap_format_6 platformID="1" platEncID="0" language="0">
+ <map code="0x24" name="dollar"/>
+ <map code="0x41" name="A"/>
+ <map code="0x54" name="T"/>
+ </cmap_format_6>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x54" name="T"/><!-- LATIN CAPITAL LETTER T -->
+ </cmap_format_4>
+ </cmap>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="1"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ <psName name="dollar.a"/>
+ </extraNames>
+ </post>
+
+ <CFF2>
+ <major value="2"/>
+ <minor value="0"/>
+ <CFFFont name="CFF2Font">
+ <FontMatrix value="0.001 0 0 0.001 0 0"/>
+ <FDArray>
+ <FontDict index="0">
+ <Private>
+ <BlueValues value="-12 0 500 512 580 592 634 646 650 662 696 708"/>
+ <OtherBlues value="-188 -176"/>
+ <FamilyBlues value="-12 0 486 498 574 586 638 650 656 668 712 724"/>
+ <FamilyOtherBlues value="-217 -205"/>
+ <BlueScale value="0.0625"/>
+ <BlueShift value="7"/>
+ <BlueFuzz value="0"/>
+ <StdHW value="134"/>
+ <StdVW value="172"/>
+ </Private>
+ </FontDict>
+ </FDArray>
+ <CharStrings>
+ <CharString name=".notdef">
+ 24 0 rmoveto
+ 552 0 rlineto
+ 0 660 rlineto
+ -552 0 rlineto
+ 0 -660 rlineto
+ 212 104 rmoveto
+ 26 56 rlineto
+ 36 96 rlineto
+ 4 0 rlineto
+ 36 -96 rlineto
+ 26 -56 rlineto
+ -128 0 rlineto
+ -100 68 rmoveto
+ 0 336 rlineto
+ 82 -168 rlineto
+ -82 -168 rlineto
+ 162 252 rmoveto
+ -40 96 rlineto
+ -18 36 rlineto
+ 120 0 rlineto
+ -18 -36 rlineto
+ -40 -96 rlineto
+ -4 0 rlineto
+ 84 -84 rmoveto
+ 82 168 rlineto
+ 0 -336 rlineto
+ -82 168 rlineto
+ </CharString>
+ <CharString name="A">
+ 0 0 rmoveto
+ 176 0 rlineto
+ 73 316 rlineto
+ 14 62 17 78 14 66 rrcurveto
+ 4 0 rlineto
+ 14 -66 19 -78 14 -62 rrcurveto
+ 73 -316 rlineto
+ 182 0 rlineto
+ -196 650 rlineto
+ -208 0 rlineto
+ -196 -650 rlineto
+ 141 138 rmoveto
+ 316 0 rlineto
+ 0 133 rlineto
+ -316 0 rlineto
+ 0 -133 rlineto
+ </CharString>
+ <CharString name="T">
+ 214 0 rmoveto
+ 172 0 rlineto
+ 0 506 rlineto
+ 187 0 rlineto
+ 0 144 rlineto
+ -546 0 rlineto
+ 0 -144 rlineto
+ 187 0 rlineto
+ 0 -506 rlineto
+ </CharString>
+ <CharString name="dollar">
+ -107 260 39 rmoveto
+ -65 0 -28 11 -49 24 rrcurveto
+ 89 -53 rlineto
+ -15 89 rlineto
+ -9 52 -22 18 -43 0 rrcurveto
+ -26 0 -27 -14 -14 -38 rrcurveto
+ 0 -90 71 -54 139 0 rrcurveto
+ 163 0 99 84 0 117 rrcurveto
+ 0 98 -58 68 -142 45 rrcurveto
+ -33 10 rlineto
+ -72 22 -24 24 0 49 rrcurveto
+ 0 61 47 23 67 0 rrcurveto
+ 42 0 27 -4 52 -24 rrcurveto
+ -85 47 rlineto
+ 10 -67 rlineto
+ 11 -75 37 -14 39 0 rrcurveto
+ 26 0 29 15 5 41 rrcurveto
+ -8 88 -76 48 -121 0 rrcurveto
+ -158 0 -85 -80 0 -115 rrcurveto
+ 0 -93 66 -69 121 -39 rrcurveto
+ 32 -11 rlineto
+ 80 -28 23 -19 0 -53 rrcurveto
+ 0 -55 -43 -39 -72 0 rrcurveto
+ 64 275 rmoveto
+ 0 417 rlineto
+ -71 0 rlineto
+ 0 -417 rlineto
+ 71 0 rlineto
+ -79 -429 rmoveto
+ 71 0 rlineto
+ 0 429 rlineto
+ -71 0 rlineto
+ 0 -429 rlineto
+ </CharString>
+ <CharString name="dollar.a">
+ 292 34 rmoveto
+ 163 0 83 80 0 100 rrcurveto
+ 0 182 -302 -4 0 56 rrcurveto
+ 0 21 18 11 36 0 rrcurveto
+ 55 0 39 -16 52 -32 rrcurveto
+ 84 98 rlineto
+ -53 52 -69 36 -97 0 rrcurveto
+ -141 0 -88 -68 0 -104 rrcurveto
+ 0 -188 302 12 0 -68 rrcurveto
+ 0 -20 -19 -10 -37 0 rrcurveto
+ -61 0 -55 20 -72 40 rrcurveto
+ -74 -116 rlineto
+ 65 -54 101 -28 70 0 rrcurveto
+ -19 -150 rmoveto
+ 160 854 rlineto
+ -100 12 rlineto
+ -160 -854 rlineto
+ 100 -12 rlineto
+ </CharString>
+ </CharStrings>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ </GlobalSubrs>
+ </CFF2>
+
+ <BASE>
+ <Version value="0x00010000"/>
+ <HorizAxis>
+ <BaseTagList>
+ <!-- BaseTagCount=2 -->
+ <BaselineTag index="0" value="ideo"/>
+ <BaselineTag index="1" value="romn"/>
+ </BaseTagList>
+ <BaseScriptList>
+ <!-- BaseScriptCount=4 -->
+ <BaseScriptRecord index="0">
+ <BaseScriptTag value="DFLT"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="1">
+ <BaseScriptTag value="cyrl"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="2">
+ <BaseScriptTag value="grek"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="3">
+ <BaseScriptTag value="latn"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ </BaseScriptList>
+ </HorizAxis>
+ </BASE>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="size"/>
+ <Feature>
+ <FeatureParamsSize>
+ <DesignSize value="10.0"/>
+ <SubfamilyID value="0"/>
+ <SubfamilyNameID value="0"/>
+ <RangeStart value="0.0"/>
+ <RangeEnd value="0.0"/>
+ </FeatureParamsSize>
+ <!-- LookupCount=0 -->
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=0 -->
+ </LookupList>
+ </GPOS>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0" Format="1">
+ <Substitution in="dollar" out="dollar.a"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+ <hmtx>
+ <mtx name=".notdef" width="600" lsb="24"/>
+ <mtx name="A" width="600" lsb="0"/>
+ <mtx name="T" width="600" lsb="27"/>
+ <mtx name="dollar" width="560" lsb="51"/>
+ <mtx name="dollar.a" width="600" lsb="56"/>
+ </hmtx>
+
+ <DSIG>
+ <!-- note that the Digital Signature will be invalid after recompilation! -->
+ <tableHeader flag="0x0" numSigs="0" version="1"/>
+ </DSIG>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_cff2_input/TestCFF2_ExtraLight.ttx b/Tests/varLib/data/master_cff2_input/TestCFF2_ExtraLight.ttx
new file mode 100644
index 0000000..e3a35f0
--- /dev/null
+++ b/Tests/varLib/data/master_cff2_input/TestCFF2_ExtraLight.ttx
@@ -0,0 +1,510 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.2">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="T"/>
+ <GlyphID id="3" name="dollar.a"/>
+ <GlyphID id="4" name="dollar"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.01"/>
+ <checkSumAdjustment value="0xeb345d38"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Thu Nov 29 14:52:09 2018"/>
+ <modified value="Thu Nov 29 14:52:09 2018"/>
+ <xMin value="50"/>
+ <yMin value="-115"/>
+ <xMax value="550"/>
+ <yMax value="762"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="3"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="984"/>
+ <descent value="-273"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="600"/>
+ <minLeftSideBearing value="50"/>
+ <minRightSideBearing value="50"/>
+ <xMaxExtent value="550"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="5"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x5000"/>
+ <numGlyphs value="5"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="578"/>
+ <usWeightClass value="200"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="286"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="11"/>
+ <bWeight value="3"/>
+ <bProportion value="9"/>
+ <bContrast value="3"/>
+ <bStrokeVariation value="4"/>
+ <bArmStyle value="3"/>
+ <bLetterForm value="2"/>
+ <bMidline value="2"/>
+ <bXHeight value="4"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="ADBO"/>
+ <fsSelection value="00000000 00000000"/>
+ <usFirstCharIndex value="36"/>
+ <usLastCharIndex value="84"/>
+ <sTypoAscender value="750"/>
+ <sTypoDescender value="-250"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="984"/>
+ <usWinDescent value="273"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="478"/>
+ <sCapHeight value="660"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="1"/>
+ </OS_2>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ 1.010;ADBO;SourceCode_ExtraLight
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="5" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Version 1.010;hotconv 1.0.109;makeotfexe 2.5.65596
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ SourceCode_ExtraLight
+ </namerecord>
+ <namerecord nameID="17" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Roman Master 0
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.010;ADBO;SourceCode_ExtraLight
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.010;hotconv 1.0.109;makeotfexe 2.5.65596
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SourceCode_ExtraLight
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Roman Master 0
+ </namerecord>
+ </name>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x54" name="T"/><!-- LATIN CAPITAL LETTER T -->
+ </cmap_format_4>
+ <cmap_format_6 platformID="1" platEncID="0" language="0">
+ <map code="0x24" name="dollar"/>
+ <map code="0x41" name="A"/>
+ <map code="0x54" name="T"/>
+ </cmap_format_6>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x54" name="T"/><!-- LATIN CAPITAL LETTER T -->
+ </cmap_format_4>
+ </cmap>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="1"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ <psName name="dollar.a"/>
+ </extraNames>
+ </post>
+
+ <CFF2>
+ <major value="2"/>
+ <minor value="0"/>
+ <CFFFont name="CFF2Font">
+ <FontMatrix value="0.001 0 0 0.001 0 0"/>
+ <FDArray>
+ <FontDict index="0">
+ <Private>
+ <BlueValues value="-12 0 478 490 570 582 640 652 660 672 722 734"/>
+ <OtherBlues value="-234 -222"/>
+ <FamilyBlues value="-12 0 486 498 574 586 638 650 656 668 712 724"/>
+ <FamilyOtherBlues value="-217 -205"/>
+ <BlueScale value="0.0625"/>
+ <BlueShift value="7"/>
+ <BlueFuzz value="0"/>
+ <StdHW value="28"/>
+ <StdVW value="34"/>
+ </Private>
+ </FontDict>
+ </FDArray>
+ <CharStrings>
+ <CharString name=".notdef">
+ 84 0 rmoveto
+ 432 0 rlineto
+ 0 660 rlineto
+ -432 0 rlineto
+ 0 -660 rlineto
+ 48 32 rmoveto
+ 102 176 rlineto
+ 64 106 rlineto
+ 4 0 rlineto
+ 62 -106 rlineto
+ 100 -176 rlineto
+ -332 0 rlineto
+ -10 42 rmoveto
+ 0 536 rlineto
+ 154 -270 rlineto
+ -154 -266 rlineto
+ 176 292 rmoveto
+ -56 92 rlineto
+ -94 168 rlineto
+ 302 0 rlineto
+ -94 -168 rlineto
+ -54 -92 rlineto
+ -4 0 rlineto
+ 26 -26 rmoveto
+ 152 270 rlineto
+ 0 -536 rlineto
+ -152 266 rlineto
+ </CharString>
+ <CharString name="A">
+ 50 0 rmoveto
+ 32 0 rlineto
+ 140 396 rlineto
+ 28 80 24 68 24 82 rrcurveto
+ 4 0 rlineto
+ 24 -82 24 -68 28 -80 rrcurveto
+ 138 -396 rlineto
+ 34 0 rlineto
+ -236 660 rlineto
+ -28 0 rlineto
+ -236 -660 rlineto
+ 102 236 rmoveto
+ 293 0 rlineto
+ 0 28 rlineto
+ -293 0 rlineto
+ 0 -28 rlineto
+ </CharString>
+ <CharString name="T">
+ 284 0 rmoveto
+ 32 0 rlineto
+ 0 632 rlineto
+ 234 0 rlineto
+ 0 28 rlineto
+ -500 0 rlineto
+ 0 -28 rlineto
+ 234 0 rlineto
+ 0 -632 rlineto
+ </CharString>
+ <CharString name="dollar">
+ -107 245 7 rmoveto
+ -65 0 -39 15 -46 50 rrcurveto
+ 36 -48 rlineto
+ -28 100 rlineto
+ -4 16 -12 4 -11 0 rrcurveto
+ -14 0 -8 -7 -1 -14 rrcurveto
+ 24 -85 61 -51 107 0 rrcurveto
+ 91 0 90 53 0 111 rrcurveto
+ 0 70 -26 66 -134 57 rrcurveto
+ -19 8 rlineto
+ -93 39 -42 49 0 68 rrcurveto
+ 0 91 60 48 88 0 rrcurveto
+ 56 0 35 -14 44 -50 rrcurveto
+ -38 47 rlineto
+ 28 -100 rlineto
+ 6 -15 10 -5 11 0 rrcurveto
+ 14 0 8 7 1 14 rrcurveto
+ -24 88 -67 48 -84 0 rrcurveto
+ -92 0 -82 -51 0 -108 rrcurveto
+ 0 -80 45 -53 92 -42 rrcurveto
+ 37 -17 rlineto
+ 114 -52 26 -46 0 -65 rrcurveto
+ 0 -92 -65 -54 -90 0 rrcurveto
+ 18 327 rmoveto
+ 0 428 rlineto
+ -22 0 rlineto
+ 0 -428 rlineto
+ 22 0 rlineto
+ -22 -449 rmoveto
+ 22 0 rlineto
+ 0 449 rlineto
+ -22 0 rlineto
+ 0 -449 rlineto
+ </CharString>
+ <CharString name="dollar.a">
+ 311 34 rmoveto
+ 103 0 88 56 0 94 rrcurveto
+ 0 184 -338 -32 0 142 rrcurveto
+ 0 68 57 44 85 0 rrcurveto
+ 76 0 34 -24 44 -38 rrcurveto
+ 20 20 rlineto
+ -41 38 -45 32 -85 0 rrcurveto
+ -99 0 -78 -54 0 -88 rrcurveto
+ 0 -166 338 28 0 -156 rrcurveto
+ 0 -70 -56 -50 -103 0 rrcurveto
+ -85 0 -66 38 -40 34 rrcurveto
+ -18 -22 rlineto
+ 45 -38 73 -40 91 0 rrcurveto
+ -70 -146 rmoveto
+ 158 860 rlineto
+ -30 4 rlineto
+ -158 -860 rlineto
+ 30 -4 rlineto
+ </CharString>
+ </CharStrings>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ </GlobalSubrs>
+ </CFF2>
+
+ <BASE>
+ <Version value="0x00010000"/>
+ <HorizAxis>
+ <BaseTagList>
+ <!-- BaseTagCount=2 -->
+ <BaselineTag index="0" value="ideo"/>
+ <BaselineTag index="1" value="romn"/>
+ </BaseTagList>
+ <BaseScriptList>
+ <!-- BaseScriptCount=4 -->
+ <BaseScriptRecord index="0">
+ <BaseScriptTag value="DFLT"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="1">
+ <BaseScriptTag value="cyrl"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="2">
+ <BaseScriptTag value="grek"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="3">
+ <BaseScriptTag value="latn"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ </BaseScriptList>
+ </HorizAxis>
+ </BASE>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="size"/>
+ <Feature>
+ <FeatureParamsSize>
+ <DesignSize value="10.0"/>
+ <SubfamilyID value="0"/>
+ <SubfamilyNameID value="0"/>
+ <RangeStart value="0.0"/>
+ <RangeEnd value="0.0"/>
+ </FeatureParamsSize>
+ <!-- LookupCount=0 -->
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=0 -->
+ </LookupList>
+ </GPOS>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0" Format="1">
+ <Substitution in="dollar" out="dollar.a"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+ <hmtx>
+ <mtx name=".notdef" width="600" lsb="84"/>
+ <mtx name="A" width="600" lsb="50"/>
+ <mtx name="T" width="600" lsb="50"/>
+ <mtx name="dollar" width="490" lsb="53"/>
+ <mtx name="dollar.a" width="600" lsb="102"/>
+ </hmtx>
+
+ <DSIG>
+ <!-- note that the Digital Signature will be invalid after recompilation! -->
+ <tableHeader flag="0x0" numSigs="0" version="1"/>
+ </DSIG>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_cff2_input/TestCFF2_Regular.ttx b/Tests/varLib/data/master_cff2_input/TestCFF2_Regular.ttx
new file mode 100644
index 0000000..bf0a962
--- /dev/null
+++ b/Tests/varLib/data/master_cff2_input/TestCFF2_Regular.ttx
@@ -0,0 +1,508 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.2">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="T"/>
+ <GlyphID id="3" name="dollar.a"/>
+ <GlyphID id="4" name="dollar"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.01"/>
+ <checkSumAdjustment value="0x60d07155"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Thu Nov 29 14:52:09 2018"/>
+ <modified value="Thu Nov 29 14:52:09 2018"/>
+ <xMin value="31"/>
+ <yMin value="-115"/>
+ <xMax value="569"/>
+ <yMax value="751"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="3"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="984"/>
+ <descent value="-273"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="600"/>
+ <minLeftSideBearing value="31"/>
+ <minRightSideBearing value="31"/>
+ <xMaxExtent value="569"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="5"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x5000"/>
+ <numGlyphs value="5"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="579"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="291"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="11"/>
+ <bWeight value="5"/>
+ <bProportion value="9"/>
+ <bContrast value="3"/>
+ <bStrokeVariation value="4"/>
+ <bArmStyle value="3"/>
+ <bLetterForm value="2"/>
+ <bMidline value="2"/>
+ <bXHeight value="4"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="ADBO"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="36"/>
+ <usLastCharIndex value="84"/>
+ <sTypoAscender value="750"/>
+ <sTypoDescender value="-250"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="984"/>
+ <usWinDescent value="273"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="486"/>
+ <sCapHeight value="660"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="1"/>
+ </OS_2>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ 1.010;ADBO;SourceCodeVariable-Roman
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="5" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Version 1.010;hotconv 1.0.109;makeotfexe 2.5.65596
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ SourceCodeVariable-Roman
+ </namerecord>
+ <namerecord nameID="17" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Roman
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.010;ADBO;SourceCodeVariable-Roman
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Source Code Variable
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.010;hotconv 1.0.109;makeotfexe 2.5.65596
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SourceCodeVariable-Roman
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Roman
+ </namerecord>
+ </name>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x54" name="T"/><!-- LATIN CAPITAL LETTER T -->
+ </cmap_format_4>
+ <cmap_format_6 platformID="1" platEncID="0" language="0">
+ <map code="0x24" name="dollar"/>
+ <map code="0x41" name="A"/>
+ <map code="0x54" name="T"/>
+ </cmap_format_6>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x24" name="dollar"/><!-- DOLLAR SIGN -->
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x54" name="T"/><!-- LATIN CAPITAL LETTER T -->
+ </cmap_format_4>
+ </cmap>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="1"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ <psName name="dollar.a"/>
+ </extraNames>
+ </post>
+
+ <CFF2>
+ <major value="2"/>
+ <minor value="0"/>
+ <CFFFont name="CFF2Font">
+ <FontMatrix value="0.001 0 0 0.001 0 0"/>
+ <FDArray>
+ <FontDict index="0">
+ <Private>
+ <BlueValues value="-12 0 486 498 574 586 638 650 656 668 712 724"/>
+ <OtherBlues value="-217 -205"/>
+ <BlueScale value="0.0625"/>
+ <BlueShift value="7"/>
+ <BlueFuzz value="0"/>
+ <StdHW value="67"/>
+ <StdVW value="85"/>
+ </Private>
+ </FontDict>
+ </FDArray>
+ <CharStrings>
+ <CharString name=".notdef">
+ 62 0 rmoveto
+ 476 0 rlineto
+ 0 660 rlineto
+ -476 0 rlineto
+ 0 -660 rlineto
+ 109 59 rmoveto
+ 73 131 rlineto
+ 54 102 rlineto
+ 4 0 rlineto
+ 52 -102 rlineto
+ 73 -131 rlineto
+ -256 0 rlineto
+ -44 52 rmoveto
+ 0 461 rlineto
+ 127 -232 rlineto
+ -127 -229 rlineto
+ 171 277 rmoveto
+ -50 93 rlineto
+ -66 119 rlineto
+ 234 0 rlineto
+ -65 -119 rlineto
+ -49 -93 rlineto
+ -4 0 rlineto
+ 48 -48 rmoveto
+ 126 232 rlineto
+ 0 -461 rlineto
+ -126 229 rlineto
+ </CharString>
+ <CharString name="A">
+ 31 0 rmoveto
+ 86 0 rlineto
+ 115 366 rlineto
+ 23 73 21 72 21 76 rrcurveto
+ 4 0 rlineto
+ 20 -76 22 -72 23 -73 rrcurveto
+ 113 -366 rlineto
+ 90 0 rlineto
+ -221 656 rlineto
+ -96 0 rlineto
+ -221 -656 rlineto
+ 117 199 rmoveto
+ 301 0 rlineto
+ 0 68 rlineto
+ -301 0 rlineto
+ 0 -68 rlineto
+ </CharString>
+ <CharString name="T">
+ 258 0 rmoveto
+ 84 0 rlineto
+ 0 585 rlineto
+ 217 0 rlineto
+ 0 71 rlineto
+ -518 0 rlineto
+ 0 -71 rlineto
+ 217 0 rlineto
+ 0 -585 rlineto
+ </CharString>
+ <CharString name="dollar">
+ -107 248 35 rmoveto
+ -39 0 -45 5 -46 18 rrcurveto
+ 53 -36 rlineto
+ -17 76 rlineto
+ -12 53 -22 13 -24 0 rrcurveto
+ -22 0 -14 -11 -9 -20 rrcurveto
+ 4 -87 81 -59 107 0 rrcurveto
+ 136 0 82 76 0 107 rrcurveto
+ 0 82 -41 65 -135 47 rrcurveto
+ -38 13 rlineto
+ -71 23 -40 35 0 64 rrcurveto
+ 0 75 57 37 74 0 rrcurveto
+ 30 0 36 -5 42 -17 rrcurveto
+ -52 36 rlineto
+ 17 -76 rlineto
+ 12 -52 25 -14 22 0 rrcurveto
+ 19 0 17 10 8 21 rrcurveto
+ -6 86 -80 60 -101 0 rrcurveto
+ -115 0 -83 -80 0 -102 rrcurveto
+ 0 -100 62 -54 105 -37 rrcurveto
+ 37 -13 rlineto
+ 85 -30 36 -30 0 -63 rrcurveto
+ 0 -74 -53 -42 -82 0 rrcurveto
+ 31 287 rmoveto
+ 0 428 rlineto
+ -40 0 rlineto
+ 0 -428 rlineto
+ 40 0 rlineto
+ -41 -437 rmoveto
+ 40 0 rlineto
+ 0 437 rlineto
+ -40 0 rlineto
+ 0 -437 rlineto
+ </CharString>
+ <CharString name="dollar.a">
+ 304 34 rmoveto
+ 125 0 86 65 0 96 rrcurveto
+ 0 183 -324 -21 0 110 rrcurveto
+ 0 50 42 32 67 0 rrcurveto
+ 68 0 36 -21 47 -36 rrcurveto
+ 44 49 rlineto
+ -46 44 -54 33 -89 0 rrcurveto
+ -115 0 -81 -59 0 -94 rrcurveto
+ 0 -174 324 22 0 -124 rrcurveto
+ 0 -51 -42 -35 -78 0 rrcurveto
+ -76 0 -62 31 -52 37 rrcurveto
+ -39 -58 rlineto
+ 52 -43 84 -36 83 0 rrcurveto
+ -51 -147 rmoveto
+ 159 857 rlineto
+ -56 7 rlineto
+ -159 -858 rlineto
+ 56 -6 rlineto
+ </CharString>
+ </CharStrings>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ </GlobalSubrs>
+ </CFF2>
+
+ <BASE>
+ <Version value="0x00010000"/>
+ <HorizAxis>
+ <BaseTagList>
+ <!-- BaseTagCount=2 -->
+ <BaselineTag index="0" value="ideo"/>
+ <BaselineTag index="1" value="romn"/>
+ </BaseTagList>
+ <BaseScriptList>
+ <!-- BaseScriptCount=4 -->
+ <BaseScriptRecord index="0">
+ <BaseScriptTag value="DFLT"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="1">
+ <BaseScriptTag value="cyrl"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="2">
+ <BaseScriptTag value="grek"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ <BaseScriptRecord index="3">
+ <BaseScriptTag value="latn"/>
+ <BaseScript>
+ <BaseValues>
+ <DefaultIndex value="1"/>
+ <!-- BaseCoordCount=2 -->
+ <BaseCoord index="0" Format="1">
+ <Coordinate value="-170"/>
+ </BaseCoord>
+ <BaseCoord index="1" Format="1">
+ <Coordinate value="0"/>
+ </BaseCoord>
+ </BaseValues>
+ <!-- BaseLangSysCount=0 -->
+ </BaseScript>
+ </BaseScriptRecord>
+ </BaseScriptList>
+ </HorizAxis>
+ </BASE>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="size"/>
+ <Feature>
+ <FeatureParamsSize>
+ <DesignSize value="10.0"/>
+ <SubfamilyID value="0"/>
+ <SubfamilyNameID value="0"/>
+ <RangeStart value="0.0"/>
+ <RangeEnd value="0.0"/>
+ </FeatureParamsSize>
+ <!-- LookupCount=0 -->
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=0 -->
+ </LookupList>
+ </GPOS>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="test"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0" Format="1">
+ <Substitution in="dollar" out="dollar.a"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+ <hmtx>
+ <mtx name=".notdef" width="600" lsb="62"/>
+ <mtx name="A" width="600" lsb="31"/>
+ <mtx name="T" width="600" lsb="41"/>
+ <mtx name="dollar" width="497" lsb="51"/>
+ <mtx name="dollar.a" width="600" lsb="85"/>
+ </hmtx>
+
+ <DSIG>
+ <!-- note that the Digital Signature will be invalid after recompilation! -->
+ <tableHeader flag="0x0" numSigs="0" version="1"/>
+ </DSIG>
+
+</ttFont>
diff --git a/Tests/varLib/models_test.py b/Tests/varLib/models_test.py
index 56f7104..1027f29 100644
--- a/Tests/varLib/models_test.py
+++ b/Tests/varLib/models_test.py
@@ -1,6 +1,6 @@
from fontTools.misc.py23 import *
from fontTools.varLib.models import (
- normalizeLocation, supportScalar, VariationModel)
+ normalizeLocation, supportScalar, VariationModel, VariationModelError)
import pytest
@@ -145,7 +145,7 @@
assert model.deltaWeights == deltaWeights
def test_init_duplicate_locations(self):
- with pytest.raises(ValueError, match="locations must be unique"):
+ with pytest.raises(VariationModelError, match="Locations must be unique."):
VariationModel(
[
{"foo": 0.0, "bar": 0.0},
diff --git a/Tests/varLib/varLib_test.py b/Tests/varLib/varLib_test.py
index e5c2a00..58ac290 100644
--- a/Tests/varLib/varLib_test.py
+++ b/Tests/varLib/varLib_test.py
@@ -1,6 +1,7 @@
from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont, newTable
-from fontTools.varLib import build
+from fontTools.varLib import build, load_designspace
+from fontTools.varLib.errors import VarLibValidationError
from fontTools.varLib.mutator import instantiateVariableFont
from fontTools.varLib import main as varLib_main, load_masters
from fontTools.varLib import set_default_weight_width_slant
@@ -314,6 +315,28 @@
tables = ["fvar", "CFF2"]
self.expect_ttx(varfont, expected_ttx_path, tables)
+ def test_varlib_build_CFF2_from_CFF2(self):
+ ds_path = self.get_test_input('TestCFF2Input.designspace')
+ ttx_dir = self.get_test_input("master_cff2_input")
+ expected_ttx_path = self.get_test_output("BuildTestCFF2.ttx")
+
+ self.temp_dir()
+ for path in self.get_file_list(ttx_dir, '.ttx', 'TestCFF2_'):
+ self.compile_font(path, ".otf", self.tempdir)
+
+ ds = DesignSpaceDocument.fromfile(ds_path)
+ for source in ds.sources:
+ source.path = os.path.join(
+ self.tempdir, os.path.basename(source.filename).replace(".ufo", ".otf")
+ )
+ ds.updatePaths()
+
+ varfont, _, _ = build(ds)
+ varfont = reload_font(varfont)
+
+ tables = ["fvar", "CFF2"]
+ self.expect_ttx(varfont, expected_ttx_path, tables)
+
def test_varlib_build_sparse_CFF2(self):
ds_path = self.get_test_input('TestSparseCFF2VF.designspace')
ttx_dir = self.get_test_input("master_sparse_cff2")
@@ -475,6 +498,27 @@
tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
self.expect_ttx(varfont, expected_ttx_path, tables)
+ def test_varlib_build_lazy_masters(self):
+ # See https://github.com/fonttools/fonttools/issues/1808
+ ds_path = self.get_test_input("SparseMasters.designspace")
+ expected_ttx_path = self.get_test_output("SparseMasters.ttx")
+
+ def _open_font(master_path, master_finder=lambda s: s):
+ font = TTFont()
+ font.importXML(master_path)
+ buf = BytesIO()
+ font.save(buf, reorderTables=False)
+ buf.seek(0)
+ font = TTFont(buf, lazy=True) # reopen in lazy mode, to reproduce #1808
+ return font
+
+ ds = DesignSpaceDocument.fromfile(ds_path)
+ ds.loadSourceFonts(_open_font)
+ varfont, _, _ = build(ds)
+ varfont = reload_font(varfont)
+ tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
+ self.expect_ttx(varfont, expected_ttx_path, tables)
+
def test_varlib_build_sparse_masters_MVAR(self):
import fontTools.varLib.mvar
@@ -682,6 +726,13 @@
("B", "D"): 40,
}
+ def test_designspace_fill_in_location(self):
+ ds_path = self.get_test_input("VarLibLocationTest.designspace")
+ ds = DesignSpaceDocument.fromfile(ds_path)
+ ds_loaded = load_designspace(ds)
+
+ assert ds_loaded.instances[0].location == {"weight": 0, "width": 50}
+
def test_load_masters_layerName_without_required_font():
ds = DesignSpaceDocument()
@@ -691,7 +742,7 @@
ds.addSource(s)
with pytest.raises(
- AttributeError,
+ VarLibValidationError,
match="specified a layer name but lacks the required TTFont object",
):
load_masters(ds)
diff --git a/setup.cfg b/setup.cfg
index 64283d5..63f7a21 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 4.2.3.dev0
+current_version = 4.4.1.dev0
commit = True
tag = False
tag_name = {new_version}
diff --git a/setup.py b/setup.py
index a188d59..6f5f558 100755
--- a/setup.py
+++ b/setup.py
@@ -345,7 +345,7 @@
setup(
name="fonttools",
- version="4.2.3.dev0",
+ version="4.4.1.dev0",
description="Tools to manipulate font files",
author="Just van Rossum",
author_email="just@letterror.com",