Merge pull request #2025 from simoncozens/reformat-fealib-otllib
Reformat feaLib and otlLib
This commit is contained in:
commit
74e5945fec
@ -15,23 +15,39 @@ log = logging.getLogger("fontTools.feaLib")
|
||||
def main(args=None):
|
||||
"""Add features from a feature file (.fea) into a OTF font"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Use fontTools to compile OpenType feature files (*.fea).")
|
||||
description="Use fontTools to compile OpenType feature files (*.fea)."
|
||||
)
|
||||
parser.add_argument(
|
||||
"input_fea", metavar="FEATURES", help="Path to the feature file")
|
||||
"input_fea", metavar="FEATURES", help="Path to the feature file"
|
||||
)
|
||||
parser.add_argument(
|
||||
"input_font", metavar="INPUT_FONT", help="Path to the input font")
|
||||
"input_font", metavar="INPUT_FONT", help="Path to the input font"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o", "--output", dest="output_font", metavar="OUTPUT_FONT",
|
||||
help="Path to the output font.")
|
||||
"-o",
|
||||
"--output",
|
||||
dest="output_font",
|
||||
metavar="OUTPUT_FONT",
|
||||
help="Path to the output font.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t", "--tables", metavar="TABLE_TAG", choices=Builder.supportedTables,
|
||||
nargs='+', help="Specify the table(s) to be built.")
|
||||
"-t",
|
||||
"--tables",
|
||||
metavar="TABLE_TAG",
|
||||
choices=Builder.supportedTables,
|
||||
nargs="+",
|
||||
help="Specify the table(s) to be built.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", help="increase the logger verbosity. Multiple -v "
|
||||
"options are allowed.", action="count", default=0)
|
||||
"-v",
|
||||
"--verbose",
|
||||
help="increase the logger verbosity. Multiple -v " "options are allowed.",
|
||||
action="count",
|
||||
default=0,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--traceback", help="show traceback for exceptions.",
|
||||
action="store_true")
|
||||
"--traceback", help="show traceback for exceptions.", action="store_true"
|
||||
)
|
||||
options = parser.parse_args(args)
|
||||
|
||||
levels = ["WARNING", "INFO", "DEBUG"]
|
||||
@ -50,5 +66,5 @@ def main(args=None):
|
||||
font.save(output_font)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -72,7 +72,9 @@ def addOpenTypeFeaturesFromString(font, features, filename=None, tables=None):
|
||||
|
||||
class Builder(object):
|
||||
|
||||
supportedTables = frozenset(Tag(tag) for tag in [
|
||||
supportedTables = frozenset(
|
||||
Tag(tag)
|
||||
for tag in [
|
||||
"BASE",
|
||||
"GDEF",
|
||||
"GPOS",
|
||||
@ -82,7 +84,8 @@ class Builder(object):
|
||||
"hhea",
|
||||
"name",
|
||||
"vhea",
|
||||
])
|
||||
]
|
||||
)
|
||||
|
||||
def __init__(self, font, featurefile):
|
||||
self.font = font
|
||||
@ -170,19 +173,20 @@ class Builder(object):
|
||||
self.build_name()
|
||||
if "OS/2" in tables:
|
||||
self.build_OS_2()
|
||||
for tag in ('GPOS', 'GSUB'):
|
||||
for tag in ("GPOS", "GSUB"):
|
||||
if tag not in tables:
|
||||
continue
|
||||
table = self.makeTable(tag)
|
||||
if (table.ScriptList.ScriptCount > 0 or
|
||||
table.FeatureList.FeatureCount > 0 or
|
||||
table.LookupList.LookupCount > 0):
|
||||
if (
|
||||
table.ScriptList.ScriptCount > 0
|
||||
or table.FeatureList.FeatureCount > 0
|
||||
or table.LookupList.LookupCount > 0
|
||||
):
|
||||
fontTable = self.font[tag] = newTable(tag)
|
||||
fontTable.table = table
|
||||
elif tag in self.font:
|
||||
del self.font[tag]
|
||||
if (any(tag in self.font for tag in ("GPOS", "GSUB")) and
|
||||
"OS/2" in self.font):
|
||||
if any(tag in self.font for tag in ("GPOS", "GSUB")) and "OS/2" in self.font:
|
||||
self.font["OS/2"].usMaxContext = maxCtxFont(self.font)
|
||||
if "GDEF" in tables:
|
||||
gdef = self.buildGDEF()
|
||||
@ -210,16 +214,19 @@ class Builder(object):
|
||||
self.features_.setdefault(key, []).append(lookup)
|
||||
|
||||
def get_lookup_(self, location, builder_class):
|
||||
if (self.cur_lookup_ and
|
||||
type(self.cur_lookup_) == builder_class and
|
||||
self.cur_lookup_.lookupflag == self.lookupflag_ and
|
||||
self.cur_lookup_.markFilterSet ==
|
||||
self.lookupflag_markFilterSet_):
|
||||
if (
|
||||
self.cur_lookup_
|
||||
and type(self.cur_lookup_) == builder_class
|
||||
and self.cur_lookup_.lookupflag == self.lookupflag_
|
||||
and self.cur_lookup_.markFilterSet == self.lookupflag_markFilterSet_
|
||||
):
|
||||
return self.cur_lookup_
|
||||
if self.cur_lookup_name_ and self.cur_lookup_:
|
||||
raise FeatureLibError(
|
||||
"Within a named lookup block, all rules must be of "
|
||||
"the same lookup type and flag", location)
|
||||
"the same lookup type and flag",
|
||||
location,
|
||||
)
|
||||
self.cur_lookup_ = builder_class(self.font, location)
|
||||
self.cur_lookup_.lookupflag = self.lookupflag_
|
||||
self.cur_lookup_.markFilterSet = self.lookupflag_markFilterSet_
|
||||
@ -230,8 +237,7 @@ class Builder(object):
|
||||
if self.cur_feature_name_:
|
||||
# We are starting a lookup rule inside a feature. This includes
|
||||
# lookup rules inside named lookups inside features.
|
||||
self.add_lookup_to_feature_(self.cur_lookup_,
|
||||
self.cur_feature_name_)
|
||||
self.add_lookup_to_feature_(self.cur_lookup_, self.cur_feature_name_)
|
||||
return self.cur_lookup_
|
||||
|
||||
def build_feature_aalt_(self):
|
||||
@ -239,14 +245,16 @@ class Builder(object):
|
||||
return
|
||||
alternates = {g: set(a) for g, a in self.aalt_alternates_.items()}
|
||||
for location, name in self.aalt_features_ + [(None, "aalt")]:
|
||||
feature = [(script, lang, feature, lookups)
|
||||
for (script, lang, feature), lookups
|
||||
in self.features_.items()
|
||||
if feature == name]
|
||||
feature = [
|
||||
(script, lang, feature, lookups)
|
||||
for (script, lang, feature), lookups in self.features_.items()
|
||||
if feature == name
|
||||
]
|
||||
# "aalt" does not have to specify its own lookups, but it might.
|
||||
if not feature and name != "aalt":
|
||||
raise FeatureLibError("Feature %s has not been defined" % name,
|
||||
location)
|
||||
raise FeatureLibError(
|
||||
"Feature %s has not been defined" % name, location
|
||||
)
|
||||
for script, lang, feature, lookups in feature:
|
||||
for lookuplist in lookups:
|
||||
if not isinstance(lookuplist, list):
|
||||
@ -254,19 +262,23 @@ class Builder(object):
|
||||
for lookup in lookuplist:
|
||||
for glyph, alts in lookup.getAlternateGlyphs().items():
|
||||
alternates.setdefault(glyph, set()).update(alts)
|
||||
single = {glyph: list(repl)[0] for glyph, repl in alternates.items()
|
||||
if len(repl) == 1}
|
||||
single = {
|
||||
glyph: list(repl)[0] for glyph, repl in alternates.items() if len(repl) == 1
|
||||
}
|
||||
# TODO: Figure out the glyph alternate ordering used by makeotf.
|
||||
# https://github.com/fonttools/fonttools/issues/836
|
||||
multi = {glyph: sorted(repl, key=self.font.getGlyphID)
|
||||
multi = {
|
||||
glyph: sorted(repl, key=self.font.getGlyphID)
|
||||
for glyph, repl in alternates.items()
|
||||
if len(repl) > 1}
|
||||
if len(repl) > 1
|
||||
}
|
||||
if not single and not multi:
|
||||
return
|
||||
self.features_ = {(script, lang, feature): lookups
|
||||
for (script, lang, feature), lookups
|
||||
in self.features_.items()
|
||||
if feature != "aalt"}
|
||||
self.features_ = {
|
||||
(script, lang, feature): lookups
|
||||
for (script, lang, feature), lookups in self.features_.items()
|
||||
if feature != "aalt"
|
||||
}
|
||||
old_lookups = self.lookups_
|
||||
self.lookups_ = []
|
||||
self.start_feature(self.aalt_location_, "aalt")
|
||||
@ -333,8 +345,12 @@ class Builder(object):
|
||||
params = None
|
||||
if tag == "size":
|
||||
params = otTables.FeatureParamsSize()
|
||||
params.DesignSize, params.SubfamilyID, params.RangeStart, \
|
||||
params.RangeEnd = self.size_parameters_
|
||||
(
|
||||
params.DesignSize,
|
||||
params.SubfamilyID,
|
||||
params.RangeStart,
|
||||
params.RangeEnd,
|
||||
) = self.size_parameters_
|
||||
if tag in self.featureNames_ids_:
|
||||
params.SubfamilyNameID = self.featureNames_ids_[tag]
|
||||
else:
|
||||
@ -352,14 +368,18 @@ class Builder(object):
|
||||
params = otTables.FeatureParamsCharacterVariants()
|
||||
params.Format = 0
|
||||
params.FeatUILabelNameID = self.cv_parameters_ids_.get(
|
||||
(tag, 'FeatUILabelNameID'), 0)
|
||||
(tag, "FeatUILabelNameID"), 0
|
||||
)
|
||||
params.FeatUITooltipTextNameID = self.cv_parameters_ids_.get(
|
||||
(tag, 'FeatUITooltipTextNameID'), 0)
|
||||
(tag, "FeatUITooltipTextNameID"), 0
|
||||
)
|
||||
params.SampleTextNameID = self.cv_parameters_ids_.get(
|
||||
(tag, 'SampleTextNameID'), 0)
|
||||
(tag, "SampleTextNameID"), 0
|
||||
)
|
||||
params.NumNamedParameters = self.cv_num_named_params_.get(tag, 0)
|
||||
params.FirstParamUILabelNameID = self.cv_parameters_ids_.get(
|
||||
(tag, 'ParamUILabelNameID_0'), 0)
|
||||
(tag, "ParamUILabelNameID_0"), 0
|
||||
)
|
||||
params.CharCount = len(self.cv_characters_[tag])
|
||||
params.Character = self.cv_characters_[tag]
|
||||
return params
|
||||
@ -402,10 +422,18 @@ class Builder(object):
|
||||
table.fsType = self.os2_["fstype"]
|
||||
if "panose" in self.os2_:
|
||||
panose = getTableModule("OS/2").Panose()
|
||||
panose.bFamilyType, panose.bSerifStyle, panose.bWeight,\
|
||||
panose.bProportion, panose.bContrast, panose.bStrokeVariation,\
|
||||
panose.bArmStyle, panose.bLetterForm, panose.bMidline, \
|
||||
panose.bXHeight = self.os2_["panose"]
|
||||
(
|
||||
panose.bFamilyType,
|
||||
panose.bSerifStyle,
|
||||
panose.bWeight,
|
||||
panose.bProportion,
|
||||
panose.bContrast,
|
||||
panose.bStrokeVariation,
|
||||
panose.bArmStyle,
|
||||
panose.bLetterForm,
|
||||
panose.bMidline,
|
||||
panose.bXHeight,
|
||||
) = self.os2_["panose"]
|
||||
table.panose = panose
|
||||
if "typoascender" in self.os2_:
|
||||
table.sTypoAscender = self.os2_["typoascender"]
|
||||
@ -441,28 +469,63 @@ class Builder(object):
|
||||
if "upperopsize" in self.os2_:
|
||||
table.usUpperOpticalPointSize = self.os2_["upperopsize"]
|
||||
version = 5
|
||||
|
||||
def checkattr(table, attrs):
|
||||
for attr in attrs:
|
||||
if not hasattr(table, attr):
|
||||
setattr(table, attr, 0)
|
||||
|
||||
table.version = max(version, table.version)
|
||||
# this only happens for unit tests
|
||||
if version >= 1:
|
||||
checkattr(table, ("ulCodePageRange1", "ulCodePageRange2"))
|
||||
if version >= 2:
|
||||
checkattr(table, ("sxHeight", "sCapHeight", "usDefaultChar",
|
||||
"usBreakChar", "usMaxContext"))
|
||||
checkattr(
|
||||
table,
|
||||
(
|
||||
"sxHeight",
|
||||
"sCapHeight",
|
||||
"usDefaultChar",
|
||||
"usBreakChar",
|
||||
"usMaxContext",
|
||||
),
|
||||
)
|
||||
if version >= 5:
|
||||
checkattr(table, ("usLowerOpticalPointSize",
|
||||
"usUpperOpticalPointSize"))
|
||||
checkattr(table, ("usLowerOpticalPointSize", "usUpperOpticalPointSize"))
|
||||
|
||||
def build_codepages_(self, pages):
|
||||
pages2bits = {
|
||||
1252: 0, 1250: 1, 1251: 2, 1253: 3, 1254: 4, 1255: 5, 1256: 6,
|
||||
1257: 7, 1258: 8, 874: 16, 932: 17, 936: 18, 949: 19, 950: 20,
|
||||
1361: 21, 869: 48, 866: 49, 865: 50, 864: 51, 863: 52, 862: 53,
|
||||
861: 54, 860: 55, 857: 56, 855: 57, 852: 58, 775: 59, 737: 60,
|
||||
708: 61, 850: 62, 437: 63,
|
||||
1252: 0,
|
||||
1250: 1,
|
||||
1251: 2,
|
||||
1253: 3,
|
||||
1254: 4,
|
||||
1255: 5,
|
||||
1256: 6,
|
||||
1257: 7,
|
||||
1258: 8,
|
||||
874: 16,
|
||||
932: 17,
|
||||
936: 18,
|
||||
949: 19,
|
||||
950: 20,
|
||||
1361: 21,
|
||||
869: 48,
|
||||
866: 49,
|
||||
865: 50,
|
||||
864: 51,
|
||||
863: 52,
|
||||
862: 53,
|
||||
861: 54,
|
||||
860: 55,
|
||||
857: 56,
|
||||
855: 57,
|
||||
852: 58,
|
||||
775: 59,
|
||||
737: 60,
|
||||
708: 61,
|
||||
850: 62,
|
||||
437: 63,
|
||||
}
|
||||
bits = [pages2bits[p] for p in pages if p in pages2bits]
|
||||
pages = []
|
||||
@ -518,16 +581,22 @@ class Builder(object):
|
||||
def buildGDEF(self):
|
||||
gdef = otTables.GDEF()
|
||||
gdef.GlyphClassDef = self.buildGDEFGlyphClassDef_()
|
||||
gdef.AttachList = \
|
||||
otl.buildAttachList(self.attachPoints_, self.glyphMap)
|
||||
gdef.LigCaretList = \
|
||||
otl.buildLigCaretList(self.ligCaretCoords_, self.ligCaretPoints_,
|
||||
self.glyphMap)
|
||||
gdef.AttachList = otl.buildAttachList(self.attachPoints_, self.glyphMap)
|
||||
gdef.LigCaretList = otl.buildLigCaretList(
|
||||
self.ligCaretCoords_, self.ligCaretPoints_, self.glyphMap
|
||||
)
|
||||
gdef.MarkAttachClassDef = self.buildGDEFMarkAttachClassDef_()
|
||||
gdef.MarkGlyphSetsDef = self.buildGDEFMarkGlyphSetsDef_()
|
||||
gdef.Version = 0x00010002 if gdef.MarkGlyphSetsDef else 0x00010000
|
||||
if any((gdef.GlyphClassDef, gdef.AttachList, gdef.LigCaretList,
|
||||
gdef.MarkAttachClassDef, gdef.MarkGlyphSetsDef)):
|
||||
if any(
|
||||
(
|
||||
gdef.GlyphClassDef,
|
||||
gdef.AttachList,
|
||||
gdef.LigCaretList,
|
||||
gdef.MarkAttachClassDef,
|
||||
gdef.MarkGlyphSetsDef,
|
||||
)
|
||||
):
|
||||
result = newTable("GDEF")
|
||||
result.table = gdef
|
||||
return result
|
||||
@ -562,13 +631,14 @@ class Builder(object):
|
||||
|
||||
def buildGDEFMarkGlyphSetsDef_(self):
|
||||
sets = []
|
||||
for glyphs, id_ in sorted(self.markFilterSets_.items(),
|
||||
key=lambda item: item[1]):
|
||||
for glyphs, id_ in sorted(
|
||||
self.markFilterSets_.items(), key=lambda item: item[1]
|
||||
):
|
||||
sets.append(glyphs)
|
||||
return otl.buildMarkGlyphSetsDef(sets, self.glyphMap)
|
||||
|
||||
def buildLookups_(self, tag):
|
||||
assert tag in ('GPOS', 'GSUB'), tag
|
||||
assert tag in ("GPOS", "GSUB"), tag
|
||||
for lookup in self.lookups_:
|
||||
lookup.lookup_index = None
|
||||
lookups = []
|
||||
@ -606,10 +676,11 @@ class Builder(object):
|
||||
# l.lookup_index will be None when a lookup is not needed
|
||||
# for the table under construction. For example, substitution
|
||||
# rules will have no lookup_index while building GPOS tables.
|
||||
lookup_indices = tuple([l.lookup_index for l in lookups
|
||||
if l.lookup_index is not None])
|
||||
lookup_indices = tuple(
|
||||
[l.lookup_index for l in lookups if l.lookup_index is not None]
|
||||
)
|
||||
|
||||
size_feature = (tag == "GPOS" and feature_tag == "size")
|
||||
size_feature = tag == "GPOS" and feature_tag == "size"
|
||||
if len(lookup_indices) == 0 and not size_feature:
|
||||
continue
|
||||
|
||||
@ -620,14 +691,12 @@ class Builder(object):
|
||||
frec = otTables.FeatureRecord()
|
||||
frec.FeatureTag = feature_tag
|
||||
frec.Feature = otTables.Feature()
|
||||
frec.Feature.FeatureParams = self.buildFeatureParams(
|
||||
feature_tag)
|
||||
frec.Feature.FeatureParams = self.buildFeatureParams(feature_tag)
|
||||
frec.Feature.LookupListIndex = list(lookup_indices)
|
||||
frec.Feature.LookupCount = len(lookup_indices)
|
||||
table.FeatureList.FeatureRecord.append(frec)
|
||||
feature_indices[feature_key] = feature_index
|
||||
scripts.setdefault(script, {}).setdefault(lang, []).append(
|
||||
feature_index)
|
||||
scripts.setdefault(script, {}).setdefault(lang, []).append(feature_index)
|
||||
if self.required_features_.get((script, lang)) == feature_tag:
|
||||
required_feature_indices[(script, lang)] = feature_index
|
||||
|
||||
@ -643,17 +712,16 @@ class Builder(object):
|
||||
langrec.LangSys = otTables.LangSys()
|
||||
langrec.LangSys.LookupOrder = None
|
||||
|
||||
req_feature_index = \
|
||||
required_feature_indices.get((script, lang))
|
||||
req_feature_index = required_feature_indices.get((script, lang))
|
||||
if req_feature_index is None:
|
||||
langrec.LangSys.ReqFeatureIndex = 0xFFFF
|
||||
else:
|
||||
langrec.LangSys.ReqFeatureIndex = req_feature_index
|
||||
|
||||
langrec.LangSys.FeatureIndex = [i for i in feature_indices
|
||||
if i != req_feature_index]
|
||||
langrec.LangSys.FeatureCount = \
|
||||
len(langrec.LangSys.FeatureIndex)
|
||||
langrec.LangSys.FeatureIndex = [
|
||||
i for i in feature_indices if i != req_feature_index
|
||||
]
|
||||
langrec.LangSys.FeatureCount = len(langrec.LangSys.FeatureIndex)
|
||||
|
||||
if lang == "dflt":
|
||||
srec.Script.DefaultLangSys = langrec.LangSys
|
||||
@ -670,24 +738,27 @@ class Builder(object):
|
||||
|
||||
def add_language_system(self, location, script, language):
|
||||
# OpenType Feature File Specification, section 4.b.i
|
||||
if (script == "DFLT" and language == "dflt" and
|
||||
self.default_language_systems_):
|
||||
if script == "DFLT" and language == "dflt" and self.default_language_systems_:
|
||||
raise FeatureLibError(
|
||||
'If "languagesystem DFLT dflt" is present, it must be '
|
||||
'the first of the languagesystem statements', location)
|
||||
"the first of the languagesystem statements",
|
||||
location,
|
||||
)
|
||||
if script == "DFLT":
|
||||
if self.seen_non_DFLT_script_:
|
||||
raise FeatureLibError(
|
||||
'languagesystems using the "DFLT" script tag must '
|
||||
"precede all other languagesystems",
|
||||
location
|
||||
location,
|
||||
)
|
||||
else:
|
||||
self.seen_non_DFLT_script_ = True
|
||||
if (script, language) in self.default_language_systems_:
|
||||
raise FeatureLibError(
|
||||
'"languagesystem %s %s" has already been specified' %
|
||||
(script.strip(), language.strip()), location)
|
||||
'"languagesystem %s %s" has already been specified'
|
||||
% (script.strip(), language.strip()),
|
||||
location,
|
||||
)
|
||||
self.default_language_systems_.add((script, language))
|
||||
|
||||
def get_default_language_systems_(self):
|
||||
@ -699,11 +770,11 @@ class Builder(object):
|
||||
if self.default_language_systems_:
|
||||
return frozenset(self.default_language_systems_)
|
||||
else:
|
||||
return frozenset({('DFLT', 'dflt')})
|
||||
return frozenset({("DFLT", "dflt")})
|
||||
|
||||
def start_feature(self, location, name):
|
||||
self.language_systems = self.get_default_language_systems_()
|
||||
self.script_ = 'DFLT'
|
||||
self.script_ = "DFLT"
|
||||
self.cur_lookup_ = None
|
||||
self.cur_feature_name_ = name
|
||||
self.lookupflag_ = 0
|
||||
@ -722,12 +793,14 @@ class Builder(object):
|
||||
def start_lookup_block(self, location, name):
|
||||
if name in self.named_lookups_:
|
||||
raise FeatureLibError(
|
||||
'Lookup "%s" has already been defined' % name, location)
|
||||
'Lookup "%s" has already been defined' % name, location
|
||||
)
|
||||
if self.cur_feature_name_ == "aalt":
|
||||
raise FeatureLibError(
|
||||
"Lookup blocks cannot be placed inside 'aalt' features; "
|
||||
"move it out, and then refer to it with a lookup statement",
|
||||
location)
|
||||
location,
|
||||
)
|
||||
self.cur_lookup_name_ = name
|
||||
self.named_lookups_[name] = None
|
||||
self.cur_lookup_ = None
|
||||
@ -753,20 +826,24 @@ class Builder(object):
|
||||
self.fontRevision_ = revision
|
||||
|
||||
def set_language(self, location, language, include_default, required):
|
||||
assert(len(language) == 4)
|
||||
if self.cur_feature_name_ in ('aalt', 'size'):
|
||||
assert len(language) == 4
|
||||
if self.cur_feature_name_ in ("aalt", "size"):
|
||||
raise FeatureLibError(
|
||||
"Language statements are not allowed "
|
||||
"within \"feature %s\"" % self.cur_feature_name_, location)
|
||||
'within "feature %s"' % self.cur_feature_name_,
|
||||
location,
|
||||
)
|
||||
if self.cur_feature_name_ is None:
|
||||
raise FeatureLibError(
|
||||
"Language statements are not allowed "
|
||||
"within standalone lookup blocks", location)
|
||||
"within standalone lookup blocks",
|
||||
location,
|
||||
)
|
||||
self.cur_lookup_ = None
|
||||
|
||||
key = (self.script_, language, self.cur_feature_name_)
|
||||
lookups = self.features_.get((key[0], 'dflt', key[2]))
|
||||
if (language == 'dflt' or include_default) and lookups:
|
||||
lookups = self.features_.get((key[0], "dflt", key[2]))
|
||||
if (language == "dflt" or include_default) and lookups:
|
||||
self.features_[key] = lookups[:]
|
||||
else:
|
||||
self.features_[key] = []
|
||||
@ -777,10 +854,14 @@ class Builder(object):
|
||||
if key in self.required_features_:
|
||||
raise FeatureLibError(
|
||||
"Language %s (script %s) has already "
|
||||
"specified feature %s as its required feature" % (
|
||||
language.strip(), self.script_.strip(),
|
||||
self.required_features_[key].strip()),
|
||||
location)
|
||||
"specified feature %s as its required feature"
|
||||
% (
|
||||
language.strip(),
|
||||
self.script_.strip(),
|
||||
self.required_features_[key].strip(),
|
||||
),
|
||||
location,
|
||||
)
|
||||
self.required_features_[key] = self.cur_feature_name_
|
||||
|
||||
def getMarkAttachClass_(self, location, glyphs):
|
||||
@ -796,7 +877,8 @@ class Builder(object):
|
||||
raise FeatureLibError(
|
||||
"Glyph %s already has been assigned "
|
||||
"a MarkAttachmentType at %s" % (glyph, loc),
|
||||
location)
|
||||
location,
|
||||
)
|
||||
self.markAttach_[glyph] = (id_, location)
|
||||
return id_
|
||||
|
||||
@ -823,23 +905,25 @@ class Builder(object):
|
||||
self.lookupflag_ = value
|
||||
|
||||
def set_script(self, location, script):
|
||||
if self.cur_feature_name_ in ('aalt', 'size'):
|
||||
if self.cur_feature_name_ in ("aalt", "size"):
|
||||
raise FeatureLibError(
|
||||
"Script statements are not allowed "
|
||||
"within \"feature %s\"" % self.cur_feature_name_, location)
|
||||
'within "feature %s"' % self.cur_feature_name_,
|
||||
location,
|
||||
)
|
||||
if self.cur_feature_name_ is None:
|
||||
raise FeatureLibError(
|
||||
"Script statements are not allowed "
|
||||
"within standalone lookup blocks", location)
|
||||
if self.language_systems == {(script, 'dflt')}:
|
||||
"Script statements are not allowed " "within standalone lookup blocks",
|
||||
location,
|
||||
)
|
||||
if self.language_systems == {(script, "dflt")}:
|
||||
# Nothing to do.
|
||||
return
|
||||
self.cur_lookup_ = None
|
||||
self.script_ = script
|
||||
self.lookupflag_ = 0
|
||||
self.lookupflag_markFilterSet_ = None
|
||||
self.set_language(location, "dflt",
|
||||
include_default=True, required=False)
|
||||
self.set_language(location, "dflt", include_default=True, required=False)
|
||||
|
||||
def find_lookup_builders_(self, lookups):
|
||||
"""Helper for building chain contextual substitutions
|
||||
@ -850,8 +934,9 @@ class Builder(object):
|
||||
lookup_builders = []
|
||||
for lookuplist in lookups:
|
||||
if lookuplist is not None:
|
||||
lookup_builders.append([self.named_lookups_.get(l.name)
|
||||
for l in lookuplist])
|
||||
lookup_builders.append(
|
||||
[self.named_lookups_.get(l.name) for l in lookuplist]
|
||||
)
|
||||
else:
|
||||
lookup_builders.append(None)
|
||||
return lookup_builders
|
||||
@ -862,17 +947,17 @@ class Builder(object):
|
||||
|
||||
def add_chain_context_pos(self, location, prefix, glyphs, suffix, lookups):
|
||||
lookup = self.get_lookup_(location, ChainContextPosBuilder)
|
||||
lookup.rules.append((prefix, glyphs, suffix,
|
||||
self.find_lookup_builders_(lookups)))
|
||||
lookup.rules.append(
|
||||
(prefix, glyphs, suffix, self.find_lookup_builders_(lookups))
|
||||
)
|
||||
|
||||
def add_chain_context_subst(self, location,
|
||||
prefix, glyphs, suffix, lookups):
|
||||
def add_chain_context_subst(self, location, prefix, glyphs, suffix, lookups):
|
||||
lookup = self.get_lookup_(location, ChainContextSubstBuilder)
|
||||
lookup.rules.append((prefix, glyphs, suffix,
|
||||
self.find_lookup_builders_(lookups)))
|
||||
lookup.rules.append(
|
||||
(prefix, glyphs, suffix, self.find_lookup_builders_(lookups))
|
||||
)
|
||||
|
||||
def add_alternate_subst(self, location,
|
||||
prefix, glyph, suffix, replacement):
|
||||
def add_alternate_subst(self, location, prefix, glyph, suffix, replacement):
|
||||
if self.cur_feature_name_ == "aalt":
|
||||
alts = self.aalt_alternates_.setdefault(glyph, set())
|
||||
alts.update(replacement)
|
||||
@ -885,15 +970,15 @@ class Builder(object):
|
||||
lookup = self.get_lookup_(location, AlternateSubstBuilder)
|
||||
if glyph in lookup.alternates:
|
||||
raise FeatureLibError(
|
||||
'Already defined alternates for glyph "%s"' % glyph,
|
||||
location)
|
||||
'Already defined alternates for glyph "%s"' % glyph, location
|
||||
)
|
||||
lookup.alternates[glyph] = replacement
|
||||
|
||||
def add_feature_reference(self, location, featureName):
|
||||
if self.cur_feature_name_ != "aalt":
|
||||
raise FeatureLibError(
|
||||
'Feature references are only allowed inside "feature aalt"',
|
||||
location)
|
||||
'Feature references are only allowed inside "feature aalt"', location
|
||||
)
|
||||
self.aalt_features_.append((location, featureName))
|
||||
|
||||
def add_featureName(self, tag):
|
||||
@ -919,19 +1004,23 @@ class Builder(object):
|
||||
else:
|
||||
self.base_horiz_axis_ = (bases, scripts)
|
||||
|
||||
def set_size_parameters(self, location, DesignSize, SubfamilyID,
|
||||
RangeStart, RangeEnd):
|
||||
if self.cur_feature_name_ != 'size':
|
||||
def set_size_parameters(
|
||||
self, location, DesignSize, SubfamilyID, RangeStart, RangeEnd
|
||||
):
|
||||
if self.cur_feature_name_ != "size":
|
||||
raise FeatureLibError(
|
||||
"Parameters statements are not allowed "
|
||||
"within \"feature %s\"" % self.cur_feature_name_, location)
|
||||
'within "feature %s"' % self.cur_feature_name_,
|
||||
location,
|
||||
)
|
||||
self.size_parameters_ = [DesignSize, SubfamilyID, RangeStart, RangeEnd]
|
||||
for script, lang in self.language_systems:
|
||||
key = (script, lang, self.cur_feature_name_)
|
||||
self.features_.setdefault(key, [])
|
||||
|
||||
def add_ligature_subst(self, location,
|
||||
prefix, glyphs, suffix, replacement, forceChain):
|
||||
def add_ligature_subst(
|
||||
self, location, prefix, glyphs, suffix, replacement, forceChain
|
||||
):
|
||||
if prefix or suffix or forceChain:
|
||||
chain = self.get_lookup_(location, ChainContextSubstBuilder)
|
||||
lookup = self.get_chained_lookup_(location, LigatureSubstBuilder)
|
||||
@ -947,8 +1036,9 @@ class Builder(object):
|
||||
for g in sorted(itertools.product(*glyphs)):
|
||||
lookup.ligatures[g] = replacement
|
||||
|
||||
def add_multiple_subst(self, location,
|
||||
prefix, glyph, suffix, replacements, forceChain=False):
|
||||
def add_multiple_subst(
|
||||
self, location, prefix, glyph, suffix, replacements, forceChain=False
|
||||
):
|
||||
if prefix or suffix or forceChain:
|
||||
chain = self.get_lookup_(location, ChainContextSubstBuilder)
|
||||
sub = self.get_chained_lookup_(location, MultipleSubstBuilder)
|
||||
@ -959,19 +1049,19 @@ class Builder(object):
|
||||
if glyph in lookup.mapping:
|
||||
if replacements == lookup.mapping[glyph]:
|
||||
log.info(
|
||||
'Removing duplicate multiple substitution from glyph'
|
||||
"Removing duplicate multiple substitution from glyph"
|
||||
' "%s" to %s%s',
|
||||
glyph, replacements,
|
||||
f' at {location}' if location else '',
|
||||
glyph,
|
||||
replacements,
|
||||
f" at {location}" if location else "",
|
||||
)
|
||||
else:
|
||||
raise FeatureLibError(
|
||||
'Already defined substitution for glyph "%s"' % glyph,
|
||||
location)
|
||||
'Already defined substitution for glyph "%s"' % glyph, location
|
||||
)
|
||||
lookup.mapping[glyph] = replacements
|
||||
|
||||
def add_reverse_chain_single_subst(self, location, old_prefix,
|
||||
old_suffix, mapping):
|
||||
def add_reverse_chain_single_subst(self, location, old_prefix, old_suffix, mapping):
|
||||
lookup = self.get_lookup_(location, ReverseChainSingleSubstBuilder)
|
||||
lookup.rules.append((old_prefix, old_suffix, mapping))
|
||||
|
||||
@ -989,15 +1079,18 @@ class Builder(object):
|
||||
if from_glyph in lookup.mapping:
|
||||
if to_glyph == lookup.mapping[from_glyph]:
|
||||
log.info(
|
||||
'Removing duplicate single substitution from glyph'
|
||||
"Removing duplicate single substitution from glyph"
|
||||
' "%s" to "%s" at %s',
|
||||
from_glyph, to_glyph, location,
|
||||
from_glyph,
|
||||
to_glyph,
|
||||
location,
|
||||
)
|
||||
else:
|
||||
raise FeatureLibError(
|
||||
'Already defined rule for replacing glyph "%s" by "%s"' %
|
||||
(from_glyph, lookup.mapping[from_glyph]),
|
||||
location)
|
||||
'Already defined rule for replacing glyph "%s" by "%s"'
|
||||
% (from_glyph, lookup.mapping[from_glyph]),
|
||||
location,
|
||||
)
|
||||
lookup.mapping[from_glyph] = to_glyph
|
||||
|
||||
def add_single_subst_chained_(self, location, prefix, suffix, mapping):
|
||||
@ -1012,9 +1105,11 @@ class Builder(object):
|
||||
def add_cursive_pos(self, location, glyphclass, entryAnchor, exitAnchor):
|
||||
lookup = self.get_lookup_(location, CursivePosBuilder)
|
||||
lookup.add_attachment(
|
||||
location, glyphclass,
|
||||
location,
|
||||
glyphclass,
|
||||
makeOpenTypeAnchor(entryAnchor),
|
||||
makeOpenTypeAnchor(exitAnchor))
|
||||
makeOpenTypeAnchor(exitAnchor),
|
||||
)
|
||||
|
||||
def add_marks_(self, location, lookupBuilder, marks):
|
||||
"""Helper for add_mark_{base,liga,mark}_pos."""
|
||||
@ -1023,15 +1118,15 @@ class Builder(object):
|
||||
for mark in markClassDef.glyphs.glyphSet():
|
||||
if mark not in lookupBuilder.marks:
|
||||
otMarkAnchor = makeOpenTypeAnchor(markClassDef.anchor)
|
||||
lookupBuilder.marks[mark] = (
|
||||
markClass.name, otMarkAnchor)
|
||||
lookupBuilder.marks[mark] = (markClass.name, otMarkAnchor)
|
||||
else:
|
||||
existingMarkClass = lookupBuilder.marks[mark][0]
|
||||
if markClass.name != existingMarkClass:
|
||||
raise FeatureLibError(
|
||||
"Glyph %s cannot be in both @%s and @%s" % (
|
||||
mark, existingMarkClass, markClass.name),
|
||||
location)
|
||||
"Glyph %s cannot be in both @%s and @%s"
|
||||
% (mark, existingMarkClass, markClass.name),
|
||||
location,
|
||||
)
|
||||
|
||||
def add_mark_base_pos(self, location, bases, marks):
|
||||
builder = self.get_lookup_(location, MarkBasePosBuilder)
|
||||
@ -1039,8 +1134,7 @@ class Builder(object):
|
||||
for baseAnchor, markClass in marks:
|
||||
otBaseAnchor = makeOpenTypeAnchor(baseAnchor)
|
||||
for base in bases:
|
||||
builder.bases.setdefault(base, {})[markClass.name] = (
|
||||
otBaseAnchor)
|
||||
builder.bases.setdefault(base, {})[markClass.name] = otBaseAnchor
|
||||
|
||||
def add_mark_lig_pos(self, location, ligatures, components):
|
||||
builder = self.get_lookup_(location, MarkLigPosBuilder)
|
||||
@ -1060,11 +1154,11 @@ class Builder(object):
|
||||
for baseAnchor, markClass in marks:
|
||||
otBaseAnchor = makeOpenTypeAnchor(baseAnchor)
|
||||
for baseMark in baseMarks:
|
||||
builder.baseMarks.setdefault(baseMark, {})[markClass.name] = (
|
||||
otBaseAnchor)
|
||||
builder.baseMarks.setdefault(baseMark, {})[
|
||||
markClass.name
|
||||
] = otBaseAnchor
|
||||
|
||||
def add_class_pair_pos(self, location, glyphclass1, value1,
|
||||
glyphclass2, value2):
|
||||
def add_class_pair_pos(self, location, glyphclass1, value1, glyphclass2, value2):
|
||||
lookup = self.get_lookup_(location, PairPosBuilder)
|
||||
v1 = makeOpenTypeValueRecord(value1, pairPosContext=True)
|
||||
v2 = makeOpenTypeValueRecord(value2, pairPosContext=True)
|
||||
@ -1112,20 +1206,21 @@ class Builder(object):
|
||||
sub.add_pos(location, glyph, otValue)
|
||||
subs.append(sub)
|
||||
assert len(pos) == len(subs), (pos, subs)
|
||||
chain.rules.append(
|
||||
(prefix, [g for g, v in pos], suffix, subs))
|
||||
chain.rules.append((prefix, [g for g, v in pos], suffix, subs))
|
||||
|
||||
def setGlyphClass_(self, location, glyph, glyphClass):
|
||||
oldClass, oldLocation = self.glyphClassDefs_.get(glyph, (None, None))
|
||||
if oldClass and oldClass != glyphClass:
|
||||
raise FeatureLibError(
|
||||
"Glyph %s was assigned to a different class at %s" %
|
||||
(glyph, oldLocation),
|
||||
location)
|
||||
"Glyph %s was assigned to a different class at %s"
|
||||
% (glyph, oldLocation),
|
||||
location,
|
||||
)
|
||||
self.glyphClassDefs_[glyph] = (glyphClass, location)
|
||||
|
||||
def add_glyphClassDef(self, location, baseGlyphs, ligatureGlyphs,
|
||||
markGlyphs, componentGlyphs):
|
||||
def add_glyphClassDef(
|
||||
self, location, baseGlyphs, ligatureGlyphs, markGlyphs, componentGlyphs
|
||||
):
|
||||
for glyph in baseGlyphs:
|
||||
self.setGlyphClass_(location, glyph, 1)
|
||||
for glyph in ligatureGlyphs:
|
||||
@ -1145,8 +1240,7 @@ class Builder(object):
|
||||
if glyph not in self.ligCaretCoords_:
|
||||
self.ligCaretCoords_[glyph] = carets
|
||||
|
||||
def add_name_record(self, location, nameID, platformID, platEncID,
|
||||
langID, string):
|
||||
def add_name_record(self, location, nameID, platformID, platEncID, langID, string):
|
||||
self.names_.append([nameID, platformID, platEncID, langID, string])
|
||||
|
||||
def add_os2_field(self, key, value):
|
||||
@ -1168,8 +1262,7 @@ def makeOpenTypeAnchor(anchor):
|
||||
deviceX = otl.buildDevice(dict(anchor.xDeviceTable))
|
||||
if anchor.yDeviceTable is not None:
|
||||
deviceY = otl.buildDevice(dict(anchor.yDeviceTable))
|
||||
return otl.buildAnchor(anchor.x, anchor.y, anchor.contourpoint,
|
||||
deviceX, deviceY)
|
||||
return otl.buildAnchor(anchor.x, anchor.y, anchor.contourpoint, deviceX, deviceY)
|
||||
|
||||
|
||||
_VALUEREC_ATTRS = {
|
||||
@ -1193,6 +1286,3 @@ def makeOpenTypeValueRecord(v, pairPosContext):
|
||||
vr = {"YAdvance": 0} if v.vertical else {"XAdvance": 0}
|
||||
valRec = otl.buildValue(vr)
|
||||
return valRec
|
||||
|
||||
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
class FeatureLibError(Exception):
|
||||
def __init__(self, message, location):
|
||||
Exception.__init__(self, message)
|
||||
|
@ -77,75 +77,75 @@ class Lexer(object):
|
||||
self.line_start_ = self.pos_
|
||||
return (Lexer.NEWLINE, None, location)
|
||||
if cur_char == "\r":
|
||||
self.pos_ += (2 if next_char == "\n" else 1)
|
||||
self.pos_ += 2 if next_char == "\n" else 1
|
||||
self.line_ += 1
|
||||
self.line_start_ = self.pos_
|
||||
return (Lexer.NEWLINE, None, location)
|
||||
if cur_char == "#":
|
||||
self.scan_until_(Lexer.CHAR_NEWLINE_)
|
||||
return (Lexer.COMMENT, text[start:self.pos_], location)
|
||||
return (Lexer.COMMENT, text[start : self.pos_], location)
|
||||
|
||||
if self.mode_ is Lexer.MODE_FILENAME_:
|
||||
if cur_char != "(":
|
||||
raise FeatureLibError("Expected '(' before file name",
|
||||
location)
|
||||
raise FeatureLibError("Expected '(' before file name", location)
|
||||
self.scan_until_(")")
|
||||
cur_char = text[self.pos_] if self.pos_ < limit else None
|
||||
if cur_char != ")":
|
||||
raise FeatureLibError("Expected ')' after file name",
|
||||
location)
|
||||
raise FeatureLibError("Expected ')' after file name", location)
|
||||
self.pos_ += 1
|
||||
self.mode_ = Lexer.MODE_NORMAL_
|
||||
return (Lexer.FILENAME, text[start + 1:self.pos_ - 1], location)
|
||||
return (Lexer.FILENAME, text[start + 1 : self.pos_ - 1], location)
|
||||
|
||||
if cur_char == "\\" and next_char in Lexer.CHAR_DIGIT_:
|
||||
self.pos_ += 1
|
||||
self.scan_over_(Lexer.CHAR_DIGIT_)
|
||||
return (Lexer.CID, int(text[start + 1:self.pos_], 10), location)
|
||||
return (Lexer.CID, int(text[start + 1 : self.pos_], 10), location)
|
||||
if cur_char == "@":
|
||||
self.pos_ += 1
|
||||
self.scan_over_(Lexer.CHAR_NAME_CONTINUATION_)
|
||||
glyphclass = text[start + 1:self.pos_]
|
||||
glyphclass = text[start + 1 : self.pos_]
|
||||
if len(glyphclass) < 1:
|
||||
raise FeatureLibError("Expected glyph class name", location)
|
||||
if len(glyphclass) > 63:
|
||||
raise FeatureLibError(
|
||||
"Glyph class names must not be longer than 63 characters",
|
||||
location)
|
||||
"Glyph class names must not be longer than 63 characters", location
|
||||
)
|
||||
if not Lexer.RE_GLYPHCLASS.match(glyphclass):
|
||||
raise FeatureLibError(
|
||||
"Glyph class names must consist of letters, digits, "
|
||||
"underscore, period or hyphen", location)
|
||||
"underscore, period or hyphen",
|
||||
location,
|
||||
)
|
||||
return (Lexer.GLYPHCLASS, glyphclass, location)
|
||||
if cur_char in Lexer.CHAR_NAME_START_:
|
||||
self.pos_ += 1
|
||||
self.scan_over_(Lexer.CHAR_NAME_CONTINUATION_)
|
||||
token = text[start:self.pos_]
|
||||
token = text[start : self.pos_]
|
||||
if token == "include":
|
||||
self.mode_ = Lexer.MODE_FILENAME_
|
||||
return (Lexer.NAME, token, location)
|
||||
if cur_char == "0" and next_char in "xX":
|
||||
self.pos_ += 2
|
||||
self.scan_over_(Lexer.CHAR_HEXDIGIT_)
|
||||
return (Lexer.HEXADECIMAL, int(text[start:self.pos_], 16), location)
|
||||
return (Lexer.HEXADECIMAL, int(text[start : self.pos_], 16), location)
|
||||
if cur_char == "0" and next_char in Lexer.CHAR_DIGIT_:
|
||||
self.scan_over_(Lexer.CHAR_DIGIT_)
|
||||
return (Lexer.OCTAL, int(text[start:self.pos_], 8), location)
|
||||
return (Lexer.OCTAL, int(text[start : self.pos_], 8), location)
|
||||
if cur_char in Lexer.CHAR_DIGIT_:
|
||||
self.scan_over_(Lexer.CHAR_DIGIT_)
|
||||
if self.pos_ >= limit or text[self.pos_] != ".":
|
||||
return (Lexer.NUMBER, int(text[start:self.pos_], 10), location)
|
||||
return (Lexer.NUMBER, int(text[start : self.pos_], 10), location)
|
||||
self.scan_over_(".")
|
||||
self.scan_over_(Lexer.CHAR_DIGIT_)
|
||||
return (Lexer.FLOAT, float(text[start:self.pos_]), location)
|
||||
return (Lexer.FLOAT, float(text[start : self.pos_]), location)
|
||||
if cur_char == "-" and next_char in Lexer.CHAR_DIGIT_:
|
||||
self.pos_ += 1
|
||||
self.scan_over_(Lexer.CHAR_DIGIT_)
|
||||
if self.pos_ >= limit or text[self.pos_] != ".":
|
||||
return (Lexer.NUMBER, int(text[start:self.pos_], 10), location)
|
||||
return (Lexer.NUMBER, int(text[start : self.pos_], 10), location)
|
||||
self.scan_over_(".")
|
||||
self.scan_over_(Lexer.CHAR_DIGIT_)
|
||||
return (Lexer.FLOAT, float(text[start:self.pos_]), location)
|
||||
return (Lexer.FLOAT, float(text[start : self.pos_]), location)
|
||||
if cur_char in Lexer.CHAR_SYMBOL_:
|
||||
self.pos_ += 1
|
||||
return (Lexer.SYMBOL, cur_char, location)
|
||||
@ -155,13 +155,11 @@ class Lexer(object):
|
||||
if self.pos_ < self.text_length_ and self.text_[self.pos_] == '"':
|
||||
self.pos_ += 1
|
||||
# strip newlines embedded within a string
|
||||
string = re.sub("[\r\n]", "", text[start + 1:self.pos_ - 1])
|
||||
string = re.sub("[\r\n]", "", text[start + 1 : self.pos_ - 1])
|
||||
return (Lexer.STRING, string, location)
|
||||
else:
|
||||
raise FeatureLibError("Expected '\"' to terminate string",
|
||||
location)
|
||||
raise FeatureLibError("Unexpected character: %r" % cur_char,
|
||||
location)
|
||||
raise FeatureLibError("Expected '\"' to terminate string", location)
|
||||
raise FeatureLibError("Unexpected character: %r" % cur_char, location)
|
||||
|
||||
def scan_over_(self, valid):
|
||||
p = self.pos_
|
||||
@ -180,12 +178,12 @@ class Lexer(object):
|
||||
tag = tag.strip()
|
||||
self.scan_until_(Lexer.CHAR_NEWLINE_)
|
||||
self.scan_over_(Lexer.CHAR_NEWLINE_)
|
||||
regexp = r'}\s*' + tag + r'\s*;'
|
||||
split = re.split(regexp, self.text_[self.pos_:], maxsplit=1)
|
||||
regexp = r"}\s*" + tag + r"\s*;"
|
||||
split = re.split(regexp, self.text_[self.pos_ :], maxsplit=1)
|
||||
if len(split) != 2:
|
||||
raise FeatureLibError(
|
||||
"Expected '} %s;' to terminate anonymous block" % tag,
|
||||
location)
|
||||
"Expected '} %s;' to terminate anonymous block" % tag, location
|
||||
)
|
||||
self.pos_ += len(split[0])
|
||||
return (Lexer.ANONYMOUS_BLOCK, split[0], location)
|
||||
|
||||
@ -237,8 +235,8 @@ class IncludingLexer(object):
|
||||
fname_type, fname_token, fname_location = lexer.next()
|
||||
if fname_type is not Lexer.FILENAME:
|
||||
raise FeatureLibError("Expected file name", fname_location)
|
||||
#semi_type, semi_token, semi_location = lexer.next()
|
||||
#if semi_type is not Lexer.SYMBOL or semi_token != ";":
|
||||
# semi_type, semi_token, semi_location = lexer.next()
|
||||
# if semi_type is not Lexer.SYMBOL or semi_token != ";":
|
||||
# raise FeatureLibError("Expected ';'", semi_location)
|
||||
if os.path.isabs(fname_token):
|
||||
path = fname_token
|
||||
@ -255,8 +253,7 @@ class IncludingLexer(object):
|
||||
curpath = os.getcwd()
|
||||
path = os.path.join(curpath, fname_token)
|
||||
if len(self.lexers_) >= 5:
|
||||
raise FeatureLibError("Too many recursive includes",
|
||||
fname_location)
|
||||
raise FeatureLibError("Too many recursive includes", fname_location)
|
||||
try:
|
||||
self.lexers_.append(self.make_lexer_(path))
|
||||
except FileNotFoundError as err:
|
||||
@ -284,5 +281,6 @@ class IncludingLexer(object):
|
||||
|
||||
class NonIncludingLexer(IncludingLexer):
|
||||
"""Lexer that does not follow `include` statements, emits them as-is."""
|
||||
|
||||
def __next__(self): # Python 3
|
||||
return next(self.lexers_[0])
|
||||
|
@ -1,7 +1,9 @@
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
class FeatureLibLocation(NamedTuple):
|
||||
"""A location in a feature file"""
|
||||
|
||||
file: str
|
||||
line: int
|
||||
column: int
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -88,9 +88,10 @@ def buildLookup(subtables, flags=0, markFilterSet=None):
|
||||
subtables = [st for st in subtables if st is not None]
|
||||
if not subtables:
|
||||
return None
|
||||
assert all(t.LookupType == subtables[0].LookupType for t in subtables), \
|
||||
("all subtables must have the same LookupType; got %s" %
|
||||
repr([t.LookupType for t in subtables]))
|
||||
assert all(t.LookupType == subtables[0].LookupType for t in subtables), (
|
||||
"all subtables must have the same LookupType; got %s"
|
||||
% repr([t.LookupType for t in subtables])
|
||||
)
|
||||
self = ot.Lookup()
|
||||
self.LookupType = subtables[0].LookupType
|
||||
self.LookupFlag = flags
|
||||
@ -101,9 +102,10 @@ def buildLookup(subtables, flags=0, markFilterSet=None):
|
||||
assert isinstance(markFilterSet, int), markFilterSet
|
||||
self.MarkFilteringSet = markFilterSet
|
||||
else:
|
||||
assert (self.LookupFlag & LOOKUP_FLAG_USE_MARK_FILTERING_SET) == 0, \
|
||||
("if markFilterSet is None, flags must not set "
|
||||
"LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x%04x" % flags)
|
||||
assert (self.LookupFlag & LOOKUP_FLAG_USE_MARK_FILTERING_SET) == 0, (
|
||||
"if markFilterSet is None, flags must not set "
|
||||
"LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x%04x" % flags
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
@ -118,13 +120,15 @@ class LookupBuilder(object):
|
||||
self.lookupflag = 0
|
||||
self.markFilterSet = None
|
||||
self.lookup_index = None # assigned when making final tables
|
||||
assert table in ('GPOS', 'GSUB')
|
||||
assert table in ("GPOS", "GSUB")
|
||||
|
||||
def equals(self, other):
|
||||
return (isinstance(other, self.__class__) and
|
||||
self.table == other.table and
|
||||
self.lookupflag == other.lookupflag and
|
||||
self.markFilterSet == other.markFilterSet)
|
||||
return (
|
||||
isinstance(other, self.__class__)
|
||||
and self.table == other.table
|
||||
and self.lookupflag == other.lookupflag
|
||||
and self.markFilterSet == other.markFilterSet
|
||||
)
|
||||
|
||||
def inferGlyphClasses(self):
|
||||
"""Infers glyph glasses for the GDEF table, such as {"cedilla":3}."""
|
||||
@ -197,10 +201,11 @@ class LookupBuilder(object):
|
||||
original source which produced this break, or ``None`` if
|
||||
no location is provided.
|
||||
"""
|
||||
log.warning(OpenTypeLibError(
|
||||
'unsupported "subtable" statement for lookup type',
|
||||
location
|
||||
))
|
||||
log.warning(
|
||||
OpenTypeLibError(
|
||||
'unsupported "subtable" statement for lookup type', location
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class AlternateSubstBuilder(LookupBuilder):
|
||||
@ -225,13 +230,13 @@ class AlternateSubstBuilder(LookupBuilder):
|
||||
`LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
|
||||
flags.
|
||||
"""
|
||||
|
||||
def __init__(self, font, location):
|
||||
LookupBuilder.__init__(self, font, location, 'GSUB', 3)
|
||||
LookupBuilder.__init__(self, font, location, "GSUB", 3)
|
||||
self.alternates = OrderedDict()
|
||||
|
||||
def equals(self, other):
|
||||
return (LookupBuilder.equals(self, other) and
|
||||
self.alternates == other.alternates)
|
||||
return LookupBuilder.equals(self, other) and self.alternates == other.alternates
|
||||
|
||||
def build(self):
|
||||
"""Build the lookup.
|
||||
@ -240,8 +245,9 @@ class AlternateSubstBuilder(LookupBuilder):
|
||||
An ``otTables.Lookup`` object representing the alternate
|
||||
substitution lookup.
|
||||
"""
|
||||
subtables = self.build_subst_subtables(self.alternates,
|
||||
buildAlternateSubstSubtable)
|
||||
subtables = self.build_subst_subtables(
|
||||
self.alternates, buildAlternateSubstSubtable
|
||||
)
|
||||
return self.buildLookup_(subtables)
|
||||
|
||||
def getAlternateGlyphs(self):
|
||||
@ -251,7 +257,7 @@ class AlternateSubstBuilder(LookupBuilder):
|
||||
self.alternates[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
|
||||
|
||||
|
||||
class ChainContextualRuleset():
|
||||
class ChainContextualRuleset:
|
||||
def __init__(self):
|
||||
self.rules = []
|
||||
|
||||
@ -278,7 +284,7 @@ class ChainContextualRuleset():
|
||||
return False
|
||||
|
||||
def format2ClassDefs(self):
|
||||
PREFIX, GLYPHS, SUFFIX = 0,1,2
|
||||
PREFIX, GLYPHS, SUFFIX = 0, 1, 2
|
||||
classDefBuilders = []
|
||||
for ix in [PREFIX, GLYPHS, SUFFIX]:
|
||||
context = []
|
||||
@ -299,10 +305,10 @@ class ChainContextualRuleset():
|
||||
classdefbuilder.add(glyphset)
|
||||
return classdefbuilder
|
||||
|
||||
|
||||
class ChainContextualBuilder(LookupBuilder):
|
||||
def equals(self, other):
|
||||
return (LookupBuilder.equals(self, other) and
|
||||
self.rules == other.rules)
|
||||
return LookupBuilder.equals(self, other) and self.rules == other.rules
|
||||
|
||||
def rulesets(self):
|
||||
# Return a list of ChainContextRuleset objects, taking explicit
|
||||
@ -356,34 +362,41 @@ class ChainContextualBuilder(LookupBuilder):
|
||||
other = "substitution"
|
||||
else:
|
||||
other = "positioning"
|
||||
raise OpenTypeLibError('Missing index of the specified '
|
||||
f'lookup, might be a {other} lookup',
|
||||
self.location)
|
||||
raise OpenTypeLibError(
|
||||
"Missing index of the specified "
|
||||
f"lookup, might be a {other} lookup",
|
||||
self.location,
|
||||
)
|
||||
rec = self.newLookupRecord_(st)
|
||||
rec.SequenceIndex = sequenceIndex
|
||||
rec.LookupListIndex = l.lookup_index
|
||||
return st
|
||||
|
||||
def add_subtable_break(self, location):
|
||||
self.rules.append((self.SUBTABLE_BREAK_, self.SUBTABLE_BREAK_,
|
||||
self.SUBTABLE_BREAK_, [self.SUBTABLE_BREAK_]))
|
||||
self.rules.append(
|
||||
(
|
||||
self.SUBTABLE_BREAK_,
|
||||
self.SUBTABLE_BREAK_,
|
||||
self.SUBTABLE_BREAK_,
|
||||
[self.SUBTABLE_BREAK_],
|
||||
)
|
||||
)
|
||||
|
||||
def newSubtable_(self, chaining=True):
|
||||
subtablename = f"Context{self.subtable_type}"
|
||||
if chaining:
|
||||
subtablename = "Chain"+subtablename
|
||||
st = getattr(ot,subtablename)() # ot.ChainContextPos()/ot.ChainSubst()/etc.
|
||||
subtablename = "Chain" + subtablename
|
||||
st = getattr(ot, subtablename)() # ot.ChainContextPos()/ot.ChainSubst()/etc.
|
||||
setattr(st, f"{self.subtable_type}Count", 0)
|
||||
setattr(st, f"{self.subtable_type}LookupRecord", [])
|
||||
return st
|
||||
|
||||
def attachSubtableWithCount_(self, st,
|
||||
subtable_name, count_name,
|
||||
existing=None,
|
||||
index=None, chaining=False):
|
||||
def attachSubtableWithCount_(
|
||||
self, st, subtable_name, count_name, existing=None, index=None, chaining=False
|
||||
):
|
||||
if chaining:
|
||||
subtable_name = "Chain"+subtable_name
|
||||
count_name = "Chain"+count_name
|
||||
subtable_name = "Chain" + subtable_name
|
||||
count_name = "Chain" + count_name
|
||||
|
||||
if not hasattr(st, count_name):
|
||||
setattr(st, count_name, 0)
|
||||
@ -405,10 +418,13 @@ class ChainContextualBuilder(LookupBuilder):
|
||||
return new_subtable
|
||||
|
||||
def newLookupRecord_(self, st):
|
||||
return self.attachSubtableWithCount_(st,
|
||||
return self.attachSubtableWithCount_(
|
||||
st,
|
||||
f"{self.subtable_type}LookupRecord",
|
||||
f"{self.subtable_type}Count",
|
||||
chaining=False) # Oddly, it isn't ChainSubstLookupRecord
|
||||
chaining=False,
|
||||
) # Oddly, it isn't ChainSubstLookupRecord
|
||||
|
||||
|
||||
class ChainContextPosBuilder(ChainContextualBuilder):
|
||||
"""Builds a Chained Contextual Positioning (GPOS8) lookup.
|
||||
@ -436,8 +452,9 @@ class ChainContextPosBuilder(ChainContextualBuilder):
|
||||
`LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
|
||||
flags.
|
||||
"""
|
||||
|
||||
def __init__(self, font, location):
|
||||
LookupBuilder.__init__(self, font, location, 'GPOS', 8)
|
||||
LookupBuilder.__init__(self, font, location, "GPOS", 8)
|
||||
self.rules = [] # (prefix, input, suffix, lookups)
|
||||
self.subtable_type = "Pos"
|
||||
|
||||
@ -447,8 +464,9 @@ class ChainContextPosBuilder(ChainContextualBuilder):
|
||||
for lookup in lookups[::-1]:
|
||||
if lookup == self.SUBTABLE_BREAK_:
|
||||
return res
|
||||
if isinstance(lookup, SinglePosBuilder) and \
|
||||
all(lookup.can_add(glyph, value) for glyph in glyphs):
|
||||
if isinstance(lookup, SinglePosBuilder) and all(
|
||||
lookup.can_add(glyph, value) for glyph in glyphs
|
||||
):
|
||||
res = lookup
|
||||
return res
|
||||
|
||||
@ -479,8 +497,9 @@ class ChainContextSubstBuilder(ChainContextualBuilder):
|
||||
`LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
|
||||
flags.
|
||||
"""
|
||||
|
||||
def __init__(self, font, location):
|
||||
LookupBuilder.__init__(self, font, location, 'GSUB', 6)
|
||||
LookupBuilder.__init__(self, font, location, "GSUB", 6)
|
||||
self.rules = [] # (prefix, input, suffix, lookups)
|
||||
self.subtable_type = "Subst"
|
||||
|
||||
@ -506,8 +525,9 @@ class ChainContextSubstBuilder(ChainContextualBuilder):
|
||||
if prefix == self.SUBTABLE_BREAK_:
|
||||
return res
|
||||
for sub in rules:
|
||||
if (isinstance(sub, SingleSubstBuilder) and
|
||||
not any(g in glyphs for g in sub.mapping.keys())):
|
||||
if isinstance(sub, SingleSubstBuilder) and not any(
|
||||
g in glyphs for g in sub.mapping.keys()
|
||||
):
|
||||
res = sub
|
||||
return res
|
||||
|
||||
@ -534,13 +554,13 @@ class LigatureSubstBuilder(LookupBuilder):
|
||||
`LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
|
||||
flags.
|
||||
"""
|
||||
|
||||
def __init__(self, font, location):
|
||||
LookupBuilder.__init__(self, font, location, 'GSUB', 4)
|
||||
LookupBuilder.__init__(self, font, location, "GSUB", 4)
|
||||
self.ligatures = OrderedDict() # {('f','f','i'): 'f_f_i'}
|
||||
|
||||
def equals(self, other):
|
||||
return (LookupBuilder.equals(self, other) and
|
||||
self.ligatures == other.ligatures)
|
||||
return LookupBuilder.equals(self, other) and self.ligatures == other.ligatures
|
||||
|
||||
def build(self):
|
||||
"""Build the lookup.
|
||||
@ -549,8 +569,9 @@ class LigatureSubstBuilder(LookupBuilder):
|
||||
An ``otTables.Lookup`` object representing the ligature
|
||||
substitution lookup.
|
||||
"""
|
||||
subtables = self.build_subst_subtables(self.ligatures,
|
||||
buildLigatureSubstSubtable)
|
||||
subtables = self.build_subst_subtables(
|
||||
self.ligatures, buildLigatureSubstSubtable
|
||||
)
|
||||
return self.buildLookup_(subtables)
|
||||
|
||||
def add_subtable_break(self, location):
|
||||
@ -579,17 +600,16 @@ class MultipleSubstBuilder(LookupBuilder):
|
||||
`LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
|
||||
flags.
|
||||
"""
|
||||
|
||||
def __init__(self, font, location):
|
||||
LookupBuilder.__init__(self, font, location, 'GSUB', 2)
|
||||
LookupBuilder.__init__(self, font, location, "GSUB", 2)
|
||||
self.mapping = OrderedDict()
|
||||
|
||||
def equals(self, other):
|
||||
return (LookupBuilder.equals(self, other) and
|
||||
self.mapping == other.mapping)
|
||||
return LookupBuilder.equals(self, other) and self.mapping == other.mapping
|
||||
|
||||
def build(self):
|
||||
subtables = self.build_subst_subtables(self.mapping,
|
||||
buildMultipleSubstSubtable)
|
||||
subtables = self.build_subst_subtables(self.mapping, buildMultipleSubstSubtable)
|
||||
return self.buildLookup_(subtables)
|
||||
|
||||
def add_subtable_break(self, location):
|
||||
@ -612,13 +632,15 @@ class CursivePosBuilder(LookupBuilder):
|
||||
`LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
|
||||
flags.
|
||||
"""
|
||||
|
||||
def __init__(self, font, location):
|
||||
LookupBuilder.__init__(self, font, location, 'GPOS', 3)
|
||||
LookupBuilder.__init__(self, font, location, "GPOS", 3)
|
||||
self.attachments = {}
|
||||
|
||||
def equals(self, other):
|
||||
return (LookupBuilder.equals(self, other) and
|
||||
self.attachments == other.attachments)
|
||||
return (
|
||||
LookupBuilder.equals(self, other) and self.attachments == other.attachments
|
||||
)
|
||||
|
||||
def add_attachment(self, location, glyphs, entryAnchor, exitAnchor):
|
||||
"""Adds attachment information to the cursive positioning lookup.
|
||||
@ -674,15 +696,18 @@ class MarkBasePosBuilder(LookupBuilder):
|
||||
`LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
|
||||
flags.
|
||||
"""
|
||||
|
||||
def __init__(self, font, location):
|
||||
LookupBuilder.__init__(self, font, location, 'GPOS', 4)
|
||||
LookupBuilder.__init__(self, font, location, "GPOS", 4)
|
||||
self.marks = {} # glyphName -> (markClassName, anchor)
|
||||
self.bases = {} # glyphName -> {markClassName: anchor}
|
||||
|
||||
def equals(self, other):
|
||||
return (LookupBuilder.equals(self, other) and
|
||||
self.marks == other.marks and
|
||||
self.bases == other.bases)
|
||||
return (
|
||||
LookupBuilder.equals(self, other)
|
||||
and self.marks == other.marks
|
||||
and self.bases == other.bases
|
||||
)
|
||||
|
||||
def inferGlyphClasses(self):
|
||||
result = {glyph: 1 for glyph in self.bases}
|
||||
@ -697,12 +722,12 @@ class MarkBasePosBuilder(LookupBuilder):
|
||||
positioning lookup.
|
||||
"""
|
||||
markClasses = self.buildMarkClasses_(self.marks)
|
||||
marks = {mark: (markClasses[mc], anchor)
|
||||
for mark, (mc, anchor) in self.marks.items()}
|
||||
marks = {
|
||||
mark: (markClasses[mc], anchor) for mark, (mc, anchor) in self.marks.items()
|
||||
}
|
||||
bases = {}
|
||||
for glyph, anchors in self.bases.items():
|
||||
bases[glyph] = {markClasses[mc]: anchor
|
||||
for (mc, anchor) in anchors.items()}
|
||||
bases[glyph] = {markClasses[mc]: anchor for (mc, anchor) in anchors.items()}
|
||||
subtables = buildMarkBasePos(marks, bases, self.glyphMap)
|
||||
return self.buildLookup_(subtables)
|
||||
|
||||
@ -737,15 +762,18 @@ class MarkLigPosBuilder(LookupBuilder):
|
||||
`LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
|
||||
flags.
|
||||
"""
|
||||
|
||||
def __init__(self, font, location):
|
||||
LookupBuilder.__init__(self, font, location, 'GPOS', 5)
|
||||
LookupBuilder.__init__(self, font, location, "GPOS", 5)
|
||||
self.marks = {} # glyphName -> (markClassName, anchor)
|
||||
self.ligatures = {} # glyphName -> [{markClassName: anchor}, ...]
|
||||
|
||||
def equals(self, other):
|
||||
return (LookupBuilder.equals(self, other) and
|
||||
self.marks == other.marks and
|
||||
self.ligatures == other.ligatures)
|
||||
return (
|
||||
LookupBuilder.equals(self, other)
|
||||
and self.marks == other.marks
|
||||
and self.ligatures == other.ligatures
|
||||
)
|
||||
|
||||
def inferGlyphClasses(self):
|
||||
result = {glyph: 2 for glyph in self.ligatures}
|
||||
@ -760,8 +788,9 @@ class MarkLigPosBuilder(LookupBuilder):
|
||||
positioning lookup.
|
||||
"""
|
||||
markClasses = self.buildMarkClasses_(self.marks)
|
||||
marks = {mark: (markClasses[mc], anchor)
|
||||
for mark, (mc, anchor) in self.marks.items()}
|
||||
marks = {
|
||||
mark: (markClasses[mc], anchor) for mark, (mc, anchor) in self.marks.items()
|
||||
}
|
||||
ligs = {}
|
||||
for lig, components in self.ligatures.items():
|
||||
ligs[lig] = []
|
||||
@ -797,15 +826,18 @@ class MarkMarkPosBuilder(LookupBuilder):
|
||||
`LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
|
||||
flags.
|
||||
"""
|
||||
|
||||
def __init__(self, font, location):
|
||||
LookupBuilder.__init__(self, font, location, 'GPOS', 6)
|
||||
LookupBuilder.__init__(self, font, location, "GPOS", 6)
|
||||
self.marks = {} # glyphName -> (markClassName, anchor)
|
||||
self.baseMarks = {} # glyphName -> {markClassName: anchor}
|
||||
|
||||
def equals(self, other):
|
||||
return (LookupBuilder.equals(self, other) and
|
||||
self.marks == other.marks and
|
||||
self.baseMarks == other.baseMarks)
|
||||
return (
|
||||
LookupBuilder.equals(self, other)
|
||||
and self.marks == other.marks
|
||||
and self.baseMarks == other.baseMarks
|
||||
)
|
||||
|
||||
def inferGlyphClasses(self):
|
||||
result = {glyph: 3 for glyph in self.baseMarks}
|
||||
@ -821,8 +853,9 @@ class MarkMarkPosBuilder(LookupBuilder):
|
||||
"""
|
||||
markClasses = self.buildMarkClasses_(self.marks)
|
||||
markClassList = sorted(markClasses.keys(), key=markClasses.get)
|
||||
marks = {mark: (markClasses[mc], anchor)
|
||||
for mark, (mc, anchor) in self.marks.items()}
|
||||
marks = {
|
||||
mark: (markClasses[mc], anchor) for mark, (mc, anchor) in self.marks.items()
|
||||
}
|
||||
|
||||
st = ot.MarkMarkPos()
|
||||
st.Format = 1
|
||||
@ -864,13 +897,13 @@ class ReverseChainSingleSubstBuilder(LookupBuilder):
|
||||
`LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
|
||||
flags.
|
||||
"""
|
||||
|
||||
def __init__(self, font, location):
|
||||
LookupBuilder.__init__(self, font, location, 'GSUB', 8)
|
||||
LookupBuilder.__init__(self, font, location, "GSUB", 8)
|
||||
self.rules = [] # (prefix, suffix, mapping)
|
||||
|
||||
def equals(self, other):
|
||||
return (LookupBuilder.equals(self, other) and
|
||||
self.rules == other.rules)
|
||||
return LookupBuilder.equals(self, other) and self.rules == other.rules
|
||||
|
||||
def build(self):
|
||||
"""Build the lookup.
|
||||
@ -917,13 +950,13 @@ class SingleSubstBuilder(LookupBuilder):
|
||||
`LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
|
||||
flags.
|
||||
"""
|
||||
|
||||
def __init__(self, font, location):
|
||||
LookupBuilder.__init__(self, font, location, 'GSUB', 1)
|
||||
LookupBuilder.__init__(self, font, location, "GSUB", 1)
|
||||
self.mapping = OrderedDict()
|
||||
|
||||
def equals(self, other):
|
||||
return (LookupBuilder.equals(self, other) and
|
||||
self.mapping == other.mapping)
|
||||
return LookupBuilder.equals(self, other) and self.mapping == other.mapping
|
||||
|
||||
def build(self):
|
||||
"""Build the lookup.
|
||||
@ -932,8 +965,7 @@ class SingleSubstBuilder(LookupBuilder):
|
||||
An ``otTables.Lookup`` object representing the multiple
|
||||
substitution lookup.
|
||||
"""
|
||||
subtables = self.build_subst_subtables(self.mapping,
|
||||
buildSingleSubstSubtable)
|
||||
subtables = self.build_subst_subtables(self.mapping, buildSingleSubstSubtable)
|
||||
return self.buildLookup_(subtables)
|
||||
|
||||
def getAlternateGlyphs(self):
|
||||
@ -953,6 +985,7 @@ class ClassPairPosSubtableBuilder(object):
|
||||
Attributes:
|
||||
builder (PairPosBuilder): A pair positioning lookup builder.
|
||||
"""
|
||||
|
||||
def __init__(self, builder):
|
||||
self.builder_ = builder
|
||||
self.classDef1_, self.classDef2_ = None, None
|
||||
@ -971,11 +1004,13 @@ class ClassPairPosSubtableBuilder(object):
|
||||
value2: An ``otTables.ValueRecord`` object for the right glyph's
|
||||
positioning.
|
||||
"""
|
||||
mergeable = (not self.forceSubtableBreak_ and
|
||||
self.classDef1_ is not None and
|
||||
self.classDef1_.canAdd(gc1) and
|
||||
self.classDef2_ is not None and
|
||||
self.classDef2_.canAdd(gc2))
|
||||
mergeable = (
|
||||
not self.forceSubtableBreak_
|
||||
and self.classDef1_ is not None
|
||||
and self.classDef1_.canAdd(gc1)
|
||||
and self.classDef2_ is not None
|
||||
and self.classDef2_.canAdd(gc2)
|
||||
)
|
||||
if not mergeable:
|
||||
self.flush_()
|
||||
self.classDef1_ = ClassDefBuilder(useClass0=True)
|
||||
@ -997,8 +1032,7 @@ class ClassPairPosSubtableBuilder(object):
|
||||
def flush_(self):
|
||||
if self.classDef1_ is None or self.classDef2_ is None:
|
||||
return
|
||||
st = buildPairPosClassesSubtable(self.values_,
|
||||
self.builder_.glyphMap)
|
||||
st = buildPairPosClassesSubtable(self.values_, self.builder_.glyphMap)
|
||||
if st.Coverage is None:
|
||||
return
|
||||
self.subtables_.append(st)
|
||||
@ -1024,8 +1058,9 @@ class PairPosBuilder(LookupBuilder):
|
||||
`LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
|
||||
flags.
|
||||
"""
|
||||
|
||||
def __init__(self, font, location):
|
||||
LookupBuilder.__init__(self, font, location, 'GPOS', 2)
|
||||
LookupBuilder.__init__(self, font, location, "GPOS", 2)
|
||||
self.pairs = [] # [(gc1, value1, gc2, value2)*]
|
||||
self.glyphPairs = {} # (glyph1, glyph2) --> (value1, value2)
|
||||
self.locations = {} # (gc1, gc2) --> (filepath, line, column)
|
||||
@ -1061,21 +1096,32 @@ class PairPosBuilder(LookupBuilder):
|
||||
# by an 'enum' rule to be overridden by preceding single pairs
|
||||
otherLoc = self.locations[key]
|
||||
log.debug(
|
||||
'Already defined position for pair %s %s at %s; '
|
||||
'choosing the first value',
|
||||
glyph1, glyph2, otherLoc)
|
||||
"Already defined position for pair %s %s at %s; "
|
||||
"choosing the first value",
|
||||
glyph1,
|
||||
glyph2,
|
||||
otherLoc,
|
||||
)
|
||||
else:
|
||||
self.glyphPairs[key] = (value1, value2)
|
||||
self.locations[key] = location
|
||||
|
||||
def add_subtable_break(self, location):
|
||||
self.pairs.append((self.SUBTABLE_BREAK_, self.SUBTABLE_BREAK_,
|
||||
self.SUBTABLE_BREAK_, self.SUBTABLE_BREAK_))
|
||||
self.pairs.append(
|
||||
(
|
||||
self.SUBTABLE_BREAK_,
|
||||
self.SUBTABLE_BREAK_,
|
||||
self.SUBTABLE_BREAK_,
|
||||
self.SUBTABLE_BREAK_,
|
||||
)
|
||||
)
|
||||
|
||||
def equals(self, other):
|
||||
return (LookupBuilder.equals(self, other) and
|
||||
self.glyphPairs == other.glyphPairs and
|
||||
self.pairs == other.pairs)
|
||||
return (
|
||||
LookupBuilder.equals(self, other)
|
||||
and self.glyphPairs == other.glyphPairs
|
||||
and self.pairs == other.pairs
|
||||
)
|
||||
|
||||
def build(self):
|
||||
"""Build the lookup.
|
||||
@ -1103,8 +1149,7 @@ class PairPosBuilder(LookupBuilder):
|
||||
builder.addPair(glyphclass1, value1, glyphclass2, value2)
|
||||
subtables = []
|
||||
if self.glyphPairs:
|
||||
subtables.extend(
|
||||
buildPairPosGlyphs(self.glyphPairs, self.glyphMap))
|
||||
subtables.extend(buildPairPosGlyphs(self.glyphPairs, self.glyphMap))
|
||||
for key in sorted(builders.keys()):
|
||||
subtables.extend(builders[key].subtables())
|
||||
return self.buildLookup_(subtables)
|
||||
@ -1126,8 +1171,9 @@ class SinglePosBuilder(LookupBuilder):
|
||||
`LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
|
||||
flags.
|
||||
"""
|
||||
|
||||
def __init__(self, font, location):
|
||||
LookupBuilder.__init__(self, font, location, 'GPOS', 1)
|
||||
LookupBuilder.__init__(self, font, location, "GPOS", 1)
|
||||
self.locations = {} # glyph -> (filename, line, column)
|
||||
self.mapping = {} # glyph -> ot.ValueRecord
|
||||
|
||||
@ -1146,7 +1192,8 @@ class SinglePosBuilder(LookupBuilder):
|
||||
raise OpenTypeLibError(
|
||||
'Already defined different position for glyph "%s" at %s'
|
||||
% (glyph, otherLoc),
|
||||
location)
|
||||
location,
|
||||
)
|
||||
if otValueRecord:
|
||||
self.mapping[glyph] = otValueRecord
|
||||
self.locations[glyph] = location
|
||||
@ -1157,8 +1204,7 @@ class SinglePosBuilder(LookupBuilder):
|
||||
return curValue is None or curValue == value
|
||||
|
||||
def equals(self, other):
|
||||
return (LookupBuilder.equals(self, other) and
|
||||
self.mapping == other.mapping)
|
||||
return LookupBuilder.equals(self, other) and self.mapping == other.mapping
|
||||
|
||||
def build(self):
|
||||
"""Build the lookup.
|
||||
@ -1330,8 +1376,9 @@ def buildAnchor(x, y, point=None, deviceX=None, deviceY=None):
|
||||
self.AnchorPoint = point
|
||||
self.Format = 2
|
||||
if deviceX is not None or deviceY is not None:
|
||||
assert self.Format == 1, \
|
||||
"Either point, or both of deviceX/deviceY, must be None."
|
||||
assert (
|
||||
self.Format == 1
|
||||
), "Either point, or both of deviceX/deviceY, must be None."
|
||||
self.XDeviceTable = deviceX
|
||||
self.YDeviceTable = deviceY
|
||||
self.Format = 3
|
||||
@ -1469,8 +1516,8 @@ def buildDevice(deltas):
|
||||
self.EndSize = endSize = max(keys)
|
||||
assert 0 <= startSize <= endSize
|
||||
self.DeltaValue = deltaValues = [
|
||||
deltas.get(size, 0)
|
||||
for size in range(startSize, endSize + 1)]
|
||||
deltas.get(size, 0) for size in range(startSize, endSize + 1)
|
||||
]
|
||||
maxDelta = max(deltaValues)
|
||||
minDelta = min(deltaValues)
|
||||
assert minDelta > -129 and maxDelta < 128
|
||||
@ -1760,8 +1807,7 @@ def _getValueFormat(f, values, i):
|
||||
return mask
|
||||
|
||||
|
||||
def buildPairPosClassesSubtable(pairs, glyphMap,
|
||||
valueFormat1=None, valueFormat2=None):
|
||||
def buildPairPosClassesSubtable(pairs, glyphMap, valueFormat1=None, valueFormat2=None):
|
||||
"""Builds a class pair adjustment (GPOS2 format 2) subtable.
|
||||
|
||||
Kerning tables are generally expressed as pair positioning tables using
|
||||
@ -1870,11 +1916,11 @@ def buildPairPosGlyphs(pairs, glyphMap):
|
||||
pos[(glyphA, glyphB)] = (valA, valB)
|
||||
return [
|
||||
buildPairPosGlyphsSubtable(pos, glyphMap, formatA, formatB)
|
||||
for ((formatA, formatB), pos) in sorted(p.items())]
|
||||
for ((formatA, formatB), pos) in sorted(p.items())
|
||||
]
|
||||
|
||||
|
||||
def buildPairPosGlyphsSubtable(pairs, glyphMap,
|
||||
valueFormat1=None, valueFormat2=None):
|
||||
def buildPairPosGlyphsSubtable(pairs, glyphMap, valueFormat1=None, valueFormat2=None):
|
||||
"""Builds a single glyph-based pair adjustment (GPOS2 format 1) subtable.
|
||||
|
||||
This builds a PairPos subtable from a dictionary of glyph pairs and
|
||||
@ -1919,8 +1965,7 @@ def buildPairPosGlyphsSubtable(pairs, glyphMap,
|
||||
ps = ot.PairSet()
|
||||
ps.PairValueRecord = []
|
||||
self.PairSet.append(ps)
|
||||
for glyph2, val1, val2 in \
|
||||
sorted(p[glyph], key=lambda x: glyphMap[x[0]]):
|
||||
for glyph2, val1, val2 in sorted(p[glyph], key=lambda x: glyphMap[x[0]]):
|
||||
pvr = ot.PairValueRecord()
|
||||
pvr.SecondGlyph = glyph2
|
||||
pvr.Value1 = val1 if val1 and val1.getFormat() != 0 else None
|
||||
@ -2092,7 +2137,7 @@ def _makeDeviceTuple(device):
|
||||
device.DeltaFormat,
|
||||
device.StartSize,
|
||||
device.EndSize,
|
||||
() if device.DeltaFormat & 0x8000 else tuple(device.DeltaValue)
|
||||
() if device.DeltaFormat & 0x8000 else tuple(device.DeltaValue),
|
||||
)
|
||||
|
||||
|
||||
@ -2106,6 +2151,7 @@ def _getSinglePosValueSize(valueKey):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def buildValue(value):
|
||||
"""Builds a positioning value record.
|
||||
|
||||
@ -2136,6 +2182,7 @@ def buildValue(value):
|
||||
|
||||
# GDEF
|
||||
|
||||
|
||||
def buildAttachList(attachPoints, glyphMap):
|
||||
"""Builds an AttachList subtable.
|
||||
|
||||
@ -2155,8 +2202,7 @@ def buildAttachList(attachPoints, glyphMap):
|
||||
return None
|
||||
self = ot.AttachList()
|
||||
self.Coverage = buildCoverage(attachPoints.keys(), glyphMap)
|
||||
self.AttachPoint = [buildAttachPoint(attachPoints[g])
|
||||
for g in self.Coverage.glyphs]
|
||||
self.AttachPoint = [buildAttachPoint(attachPoints[g]) for g in self.Coverage.glyphs]
|
||||
self.GlyphCount = len(self.AttachPoint)
|
||||
return self
|
||||
|
||||
@ -2285,6 +2331,7 @@ def buildMarkGlyphSetsDef(markSets, glyphMap):
|
||||
|
||||
class ClassDefBuilder(object):
|
||||
"""Helper for building ClassDef tables."""
|
||||
|
||||
def __init__(self, useClass0):
|
||||
self.classes_ = set()
|
||||
self.glyphs_ = {}
|
||||
@ -2474,7 +2521,7 @@ def _buildAxisRecords(axes, nameTable):
|
||||
axisValRec = ot.AxisValue()
|
||||
axisValRec.AxisIndex = axisRecordIndex
|
||||
axisValRec.Flags = axisVal.get("flags", 0)
|
||||
axisValRec.ValueNameID = _addName(nameTable, axisVal['name'])
|
||||
axisValRec.ValueNameID = _addName(nameTable, axisVal["name"])
|
||||
|
||||
if "value" in axisVal:
|
||||
axisValRec.Value = axisVal["value"]
|
||||
@ -2486,8 +2533,12 @@ def _buildAxisRecords(axes, nameTable):
|
||||
elif "nominalValue" in axisVal:
|
||||
axisValRec.Format = 2
|
||||
axisValRec.NominalValue = axisVal["nominalValue"]
|
||||
axisValRec.RangeMinValue = axisVal.get("rangeMinValue", AXIS_VALUE_NEGATIVE_INFINITY)
|
||||
axisValRec.RangeMaxValue = axisVal.get("rangeMaxValue", AXIS_VALUE_POSITIVE_INFINITY)
|
||||
axisValRec.RangeMinValue = axisVal.get(
|
||||
"rangeMinValue", AXIS_VALUE_NEGATIVE_INFINITY
|
||||
)
|
||||
axisValRec.RangeMaxValue = axisVal.get(
|
||||
"rangeMaxValue", AXIS_VALUE_POSITIVE_INFINITY
|
||||
)
|
||||
else:
|
||||
raise ValueError("Can't determine format for AxisValue")
|
||||
|
||||
@ -2504,7 +2555,7 @@ def _buildAxisValuesFormat4(locations, axes, nameTable):
|
||||
for axisLocationDict in locations:
|
||||
axisValRec = ot.AxisValue()
|
||||
axisValRec.Format = 4
|
||||
axisValRec.ValueNameID = _addName(nameTable, axisLocationDict['name'])
|
||||
axisValRec.ValueNameID = _addName(nameTable, axisLocationDict["name"])
|
||||
axisValRec.Flags = axisLocationDict.get("flags", 0)
|
||||
axisValueRecords = []
|
||||
for tag, value in axisLocationDict["location"].items():
|
||||
|
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
class OpenTypeLibError(Exception):
|
||||
def __init__(self, message, location):
|
||||
Exception.__init__(self, message)
|
||||
|
@ -1,12 +1,11 @@
|
||||
|
||||
__all__ = ['maxCtxFont']
|
||||
__all__ = ["maxCtxFont"]
|
||||
|
||||
|
||||
def maxCtxFont(font):
|
||||
"""Calculate the usMaxContext value for an entire font."""
|
||||
|
||||
maxCtx = 0
|
||||
for tag in ('GSUB', 'GPOS'):
|
||||
for tag in ("GSUB", "GPOS"):
|
||||
if tag not in font:
|
||||
continue
|
||||
table = font[tag].table
|
||||
@ -24,62 +23,59 @@ def maxCtxSubtable(maxCtx, tag, lookupType, st):
|
||||
"""
|
||||
|
||||
# single positioning, single / multiple substitution
|
||||
if (tag == 'GPOS' and lookupType == 1) or (
|
||||
tag == 'GSUB' and lookupType in (1, 2, 3)):
|
||||
if (tag == "GPOS" and lookupType == 1) or (
|
||||
tag == "GSUB" and lookupType in (1, 2, 3)
|
||||
):
|
||||
maxCtx = max(maxCtx, 1)
|
||||
|
||||
# pair positioning
|
||||
elif tag == 'GPOS' and lookupType == 2:
|
||||
elif tag == "GPOS" and lookupType == 2:
|
||||
maxCtx = max(maxCtx, 2)
|
||||
|
||||
# ligatures
|
||||
elif tag == 'GSUB' and lookupType == 4:
|
||||
elif tag == "GSUB" and lookupType == 4:
|
||||
for ligatures in st.ligatures.values():
|
||||
for ligature in ligatures:
|
||||
maxCtx = max(maxCtx, ligature.CompCount)
|
||||
|
||||
# context
|
||||
elif (tag == 'GPOS' and lookupType == 7) or (
|
||||
tag == 'GSUB' and lookupType == 5):
|
||||
maxCtx = maxCtxContextualSubtable(
|
||||
maxCtx, st, 'Pos' if tag == 'GPOS' else 'Sub')
|
||||
elif (tag == "GPOS" and lookupType == 7) or (tag == "GSUB" and lookupType == 5):
|
||||
maxCtx = maxCtxContextualSubtable(maxCtx, st, "Pos" if tag == "GPOS" else "Sub")
|
||||
|
||||
# chained context
|
||||
elif (tag == 'GPOS' and lookupType == 8) or (
|
||||
tag == 'GSUB' and lookupType == 6):
|
||||
elif (tag == "GPOS" and lookupType == 8) or (tag == "GSUB" and lookupType == 6):
|
||||
maxCtx = maxCtxContextualSubtable(
|
||||
maxCtx, st, 'Pos' if tag == 'GPOS' else 'Sub', 'Chain')
|
||||
maxCtx, st, "Pos" if tag == "GPOS" else "Sub", "Chain"
|
||||
)
|
||||
|
||||
# extensions
|
||||
elif (tag == 'GPOS' and lookupType == 9) or (
|
||||
tag == 'GSUB' and lookupType == 7):
|
||||
maxCtx = maxCtxSubtable(
|
||||
maxCtx, tag, st.ExtensionLookupType, st.ExtSubTable)
|
||||
elif (tag == "GPOS" and lookupType == 9) or (tag == "GSUB" and lookupType == 7):
|
||||
maxCtx = maxCtxSubtable(maxCtx, tag, st.ExtensionLookupType, st.ExtSubTable)
|
||||
|
||||
# reverse-chained context
|
||||
elif tag == 'GSUB' and lookupType == 8:
|
||||
maxCtx = maxCtxContextualRule(maxCtx, st, 'Reverse')
|
||||
elif tag == "GSUB" and lookupType == 8:
|
||||
maxCtx = maxCtxContextualRule(maxCtx, st, "Reverse")
|
||||
|
||||
return maxCtx
|
||||
|
||||
|
||||
def maxCtxContextualSubtable(maxCtx, st, ruleType, chain=''):
|
||||
def maxCtxContextualSubtable(maxCtx, st, ruleType, chain=""):
|
||||
"""Calculate usMaxContext based on a contextual feature subtable."""
|
||||
|
||||
if st.Format == 1:
|
||||
for ruleset in getattr(st, '%s%sRuleSet' % (chain, ruleType)):
|
||||
for ruleset in getattr(st, "%s%sRuleSet" % (chain, ruleType)):
|
||||
if ruleset is None:
|
||||
continue
|
||||
for rule in getattr(ruleset, '%s%sRule' % (chain, ruleType)):
|
||||
for rule in getattr(ruleset, "%s%sRule" % (chain, ruleType)):
|
||||
if rule is None:
|
||||
continue
|
||||
maxCtx = maxCtxContextualRule(maxCtx, rule, chain)
|
||||
|
||||
elif st.Format == 2:
|
||||
for ruleset in getattr(st, '%s%sClassSet' % (chain, ruleType)):
|
||||
for ruleset in getattr(st, "%s%sClassSet" % (chain, ruleType)):
|
||||
if ruleset is None:
|
||||
continue
|
||||
for rule in getattr(ruleset, '%s%sClassRule' % (chain, ruleType)):
|
||||
for rule in getattr(ruleset, "%s%sClassRule" % (chain, ruleType)):
|
||||
if rule is None:
|
||||
continue
|
||||
maxCtx = maxCtxContextualRule(maxCtx, rule, chain)
|
||||
@ -95,6 +91,6 @@ def maxCtxContextualRule(maxCtx, st, chain):
|
||||
|
||||
if not chain:
|
||||
return max(maxCtx, st.GlyphCount)
|
||||
elif chain == 'Reverse':
|
||||
elif chain == "Reverse":
|
||||
return max(maxCtx, st.GlyphCount + st.LookAheadGlyphCount)
|
||||
return max(maxCtx, st.InputGlyphCount + st.LookAheadGlyphCount)
|
||||
|
Loading…
x
Reference in New Issue
Block a user