Merge pull request #1821 from fonttools/varLib-wrap-exceptions
[varLib] Use proper exceptions for signalling input source errors
This commit is contained in:
commit
4be6aaf71c
@ -39,13 +39,11 @@ import os.path
|
||||
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 @@ def _add_fvar(font, axes, instances):
|
||||
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 @@ def _add_avar(font, axes):
|
||||
# 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_stat(font, axes):
|
||||
|
||||
|
||||
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
|
||||
@ -703,9 +718,10 @@ def load_designspace(designspace):
|
||||
|
||||
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'})),
|
||||
@ -715,11 +731,15 @@ def load_designspace(designspace):
|
||||
])
|
||||
|
||||
# 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:
|
||||
@ -728,7 +748,8 @@ def load_designspace(designspace):
|
||||
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)
|
||||
|
||||
@ -739,15 +760,28 @@ def load_designspace(designspace):
|
||||
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:
|
||||
# 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
|
||||
|
||||
@ -768,9 +802,15 @@ def load_designspace(designspace):
|
||||
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(
|
||||
@ -927,7 +967,7 @@ def _open_font(path, master_finder=lambda s: s):
|
||||
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
|
||||
|
||||
|
||||
@ -947,10 +987,10 @@ def load_masters(designspace, master_finder=lambda s: s):
|
||||
# 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)
|
||||
|
@ -1,5 +1,4 @@
|
||||
from collections import namedtuple
|
||||
import os
|
||||
from fontTools.cffLib import (
|
||||
maxStackLimit,
|
||||
TopDictIndex,
|
||||
@ -21,6 +20,13 @@ from fontTools.varLib.models import allEqual
|
||||
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']
|
||||
@ -126,16 +132,6 @@ def convertCFFtoCFF2(varFont):
|
||||
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)
|
||||
@ -219,7 +215,7 @@ def merge_PrivateDicts(top_dicts, vsindex_dict, var_model, fd_map):
|
||||
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
|
||||
@ -426,21 +422,6 @@ def merge_charstrings(glyphOrder, num_masters, top_dicts, masterModel):
|
||||
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")
|
||||
@ -547,7 +528,7 @@ class CFF2CharStringMergePen(T2CharStringPen):
|
||||
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)
|
||||
@ -560,7 +541,7 @@ class CFF2CharStringMergePen(T2CharStringPen):
|
||||
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
|
||||
@ -576,7 +557,7 @@ class CFF2CharStringMergePen(T2CharStringPen):
|
||||
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]
|
||||
@ -646,8 +627,8 @@ class CFF2CharStringMergePen(T2CharStringPen):
|
||||
# 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]
|
||||
|
39
Lib/fontTools/varLib/errors.py
Normal file
39
Lib/fontTools/varLib/errors.py
Normal file
@ -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."""
|
@ -10,6 +10,8 @@ from fontTools.ttLib.tables import otTables as ot
|
||||
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 @@ def addFeatureVariationsRaw(font, conditionalSubstitutions, featureTag='rvrn'):
|
||||
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)
|
||||
|
||||
|
@ -14,6 +14,8 @@ from fontTools.varLib.varStore import VarStoreInstancer
|
||||
from functools import reduce
|
||||
from fontTools.otlLib.builder import buildSinglePos
|
||||
|
||||
from .errors import VarLibMergeError
|
||||
|
||||
|
||||
class Merger(object):
|
||||
|
||||
@ -66,8 +68,8 @@ class Merger(object):
|
||||
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:
|
||||
@ -82,7 +84,8 @@ class Merger(object):
|
||||
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)
|
||||
@ -92,7 +95,8 @@ class Merger(object):
|
||||
|
||||
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)
|
||||
@ -101,7 +105,8 @@ class Merger(object):
|
||||
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
|
||||
@ -122,7 +127,8 @@ class AligningMerger(Merger):
|
||||
@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]
|
||||
@ -134,7 +140,8 @@ def merge(merger, self, lst):
|
||||
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:
|
||||
@ -170,7 +177,8 @@ def _merge_GlyphOrders(font, lst, values_lst=None, default=None):
|
||||
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
|
||||
@ -197,7 +205,10 @@ def _Lookup_SinglePos_get_effective_value(subtables, glyph):
|
||||
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):
|
||||
@ -219,13 +230,17 @@ def _Lookup_PairPos_get_effective_value_pair(subtables, firstGlyph, secondGlyph)
|
||||
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
|
||||
@ -511,7 +526,9 @@ def merge(merger, self, lst):
|
||||
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
|
||||
|
||||
@ -576,7 +593,8 @@ def _MarkBasePosFormat1_merge(self, lst, merger, Mark='Mark', Base='Base'):
|
||||
# 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:
|
||||
@ -625,19 +643,31 @@ def _MarkBasePosFormat1_merge(self, lst, merger, Mark='Mark', Base='Base'):
|
||||
|
||||
@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):
|
||||
@ -766,8 +796,16 @@ def merge(merger, self, lst):
|
||||
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[:]
|
||||
@ -995,7 +1033,8 @@ class VariationMerger(AligningMerger):
|
||||
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)
|
||||
@ -1015,7 +1054,8 @@ def buildVarDevTable(store_builder, master_values):
|
||||
|
||||
@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
|
||||
@ -1023,7 +1063,8 @@ def merge(merger, self, lst):
|
||||
|
||||
@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:
|
||||
|
@ -5,6 +5,8 @@ __all__ = ['nonNone', 'allNone', 'allEqual', 'allEqualTo', 'subList',
|
||||
'supportScalar',
|
||||
'VariationModel']
|
||||
|
||||
from .errors import VariationModelError
|
||||
|
||||
|
||||
def nonNone(lst):
|
||||
return [l for l in lst if l is not None]
|
||||
@ -43,7 +45,11 @@ def normalizeValue(v, triple):
|
||||
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 @@ class VariationModel(object):
|
||||
|
||||
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 @@ class VariationModel(object):
|
||||
|
||||
@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:
|
||||
|
4
NEWS.rst
4
NEWS.rst
@ -2,6 +2,10 @@
|
||||
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)
|
||||
---------------------------
|
||||
|
@ -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 @@ class VariationModelTest(object):
|
||||
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},
|
||||
|
@ -1,6 +1,7 @@
|
||||
from fontTools.misc.py23 import *
|
||||
from fontTools.ttLib import TTFont, newTable
|
||||
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
|
||||
@ -744,7 +745,7 @@ def test_load_masters_layerName_without_required_font():
|
||||
ds.addSource(s)
|
||||
|
||||
with pytest.raises(
|
||||
AttributeError,
|
||||
VarLibValidationError,
|
||||
match="specified a layer name but lacks the required TTFont object",
|
||||
):
|
||||
load_masters(ds)
|
||||
|
Loading…
x
Reference in New Issue
Block a user