This commit is contained in:
Behdad Esfahbod 2023-03-07 11:19:22 -07:00
parent 6389385813
commit 6ba1302ce4
4 changed files with 9686 additions and 5246 deletions

View File

@ -16,6 +16,7 @@ log = logging.getLogger(__name__)
from .otBase import BaseTTXConverter
class table__a_v_a_r(BaseTTXConverter):
"""Axis Variations Table

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,10 @@ from collections import defaultdict, namedtuple
from fontTools.misc.roundTools import otRound
from fontTools.misc.textTools import bytesjoin, pad, safeEval
from .otBase import (
BaseTable, FormatSwitchingBaseTable, ValueRecord, CountReference,
BaseTable,
FormatSwitchingBaseTable,
ValueRecord,
CountReference,
getFormatSwitchingBaseTableClass,
)
from fontTools.feaLib.lookupDebugInfo import LookupDebugInfo, LOOKUP_DEBUG_INFO_KEY
@ -48,9 +51,7 @@ class AATAction(object):
xmlWriter.simpletag("Flags", value=",".join(flags))
xmlWriter.newline()
if self.ReservedFlags != 0:
xmlWriter.simpletag(
"ReservedFlags",
value='0x%04X' % self.ReservedFlags)
xmlWriter.simpletag("ReservedFlags", value="0x%04X" % self.ReservedFlags)
xmlWriter.newline()
def _setFlag(self, flag):
@ -95,9 +96,12 @@ class RearrangementMorphAction(AATAction):
writer.writeUShort(self.NewState)
assert self.Verb >= 0 and self.Verb <= 15, self.Verb
flags = self.Verb | self.ReservedFlags
if self.MarkFirst: flags |= 0x8000
if self.DontAdvance: flags |= 0x4000
if self.MarkLast: flags |= 0x2000
if self.MarkFirst:
flags |= 0x8000
if self.DontAdvance:
flags |= 0x4000
if self.MarkLast:
flags |= 0x2000
writer.writeUShort(flags)
def decompile(self, reader, font, actionReader):
@ -155,8 +159,10 @@ class ContextualMorphAction(AATAction):
assert actionIndex is None
writer.writeUShort(self.NewState)
flags = self.ReservedFlags
if self.SetMark: flags |= 0x8000
if self.DontAdvance: flags |= 0x4000
if self.SetMark:
flags |= 0x8000
if self.DontAdvance:
flags |= 0x4000
writer.writeUShort(flags)
writer.writeUShort(self.MarkIndex)
writer.writeUShort(self.CurrentIndex)
@ -179,8 +185,7 @@ class ContextualMorphAction(AATAction):
self._writeFlagsToXML(xmlWriter)
xmlWriter.simpletag("MarkIndex", value=self.MarkIndex)
xmlWriter.newline()
xmlWriter.simpletag("CurrentIndex",
value=self.CurrentIndex)
xmlWriter.simpletag("CurrentIndex", value=self.CurrentIndex)
xmlWriter.newline()
xmlWriter.endtag(name)
xmlWriter.newline()
@ -237,9 +242,12 @@ class LigatureMorphAction(AATAction):
assert actionIndex is not None
writer.writeUShort(self.NewState)
flags = self.ReservedFlags
if self.SetComponent: flags |= 0x8000
if self.DontAdvance: flags |= 0x4000
if len(self.Actions) > 0: flags |= 0x2000
if self.SetComponent:
flags |= 0x8000
if self.DontAdvance:
flags |= 0x4000
if len(self.Actions) > 0:
flags |= 0x2000
writer.writeUShort(flags)
if len(self.Actions) > 0:
actions = self.compileLigActions()
@ -262,8 +270,7 @@ class LigatureMorphAction(AATAction):
self.ReservedFlags = flags & 0x1FFF
actionIndex = reader.readUShort()
if performAction:
self.Actions = self._decompileLigActions(
actionReader, actionIndex)
self.Actions = self._decompileLigActions(actionReader, actionIndex)
else:
self.Actions = []
@ -289,8 +296,7 @@ class LigatureMorphAction(AATAction):
for i in range(0, len(a), 4):
suffix = a[i:]
suffixIndex = (len(result) + i) // 4
actionIndex.setdefault(
suffix, suffixIndex)
actionIndex.setdefault(suffix, suffixIndex)
result += a
result = pad(result, 4)
return (result, actionIndex)
@ -298,7 +304,7 @@ class LigatureMorphAction(AATAction):
def compileLigActions(self):
result = []
for i, action in enumerate(self.Actions):
last = (i == len(self.Actions) - 1)
last = i == len(self.Actions) - 1
value = action.GlyphIndexDelta & 0x3FFFFFFF
value |= 0x80000000 if last else 0
value |= 0x40000000 if action.Store else 0
@ -308,8 +314,7 @@ class LigatureMorphAction(AATAction):
def _decompileLigActions(self, actionReader, actionIndex):
actions = []
last = False
reader = actionReader.getSubReader(
actionReader.pos + actionIndex * 4)
reader = actionReader.getSubReader(actionReader.pos + actionIndex * 4)
while not last:
value = reader.readULong()
last = bool(value & 0x80000000)
@ -341,8 +346,7 @@ class LigatureMorphAction(AATAction):
flags = eltAttrs.get("Flags", "").split(",")
flags = [f.strip() for f in flags]
action.Store = "Store" in flags
action.GlyphIndexDelta = safeEval(
eltAttrs["GlyphIndexDelta"])
action.GlyphIndexDelta = safeEval(eltAttrs["GlyphIndexDelta"])
self.Actions.append(action)
def toXML(self, xmlWriter, font, attrs, name):
@ -364,9 +368,14 @@ class LigatureMorphAction(AATAction):
class InsertionMorphAction(AATAction):
staticSize = 8
actionHeaderSize = 4 # 4 bytes for actionOffset
_FLAGS = ["SetMark", "DontAdvance",
"CurrentIsKashidaLike", "MarkedIsKashidaLike",
"CurrentInsertBefore", "MarkedInsertBefore"]
_FLAGS = [
"SetMark",
"DontAdvance",
"CurrentIsKashidaLike",
"MarkedIsKashidaLike",
"CurrentInsertBefore",
"MarkedInsertBefore",
]
def __init__(self):
self.NewState = 0
@ -379,24 +388,28 @@ class InsertionMorphAction(AATAction):
assert actionIndex is not None
writer.writeUShort(self.NewState)
flags = self.ReservedFlags
if self.SetMark: flags |= 0x8000
if self.DontAdvance: flags |= 0x4000
if self.CurrentIsKashidaLike: flags |= 0x2000
if self.MarkedIsKashidaLike: flags |= 0x1000
if self.CurrentInsertBefore: flags |= 0x0800
if self.MarkedInsertBefore: flags |= 0x0400
if self.SetMark:
flags |= 0x8000
if self.DontAdvance:
flags |= 0x4000
if self.CurrentIsKashidaLike:
flags |= 0x2000
if self.MarkedIsKashidaLike:
flags |= 0x1000
if self.CurrentInsertBefore:
flags |= 0x0800
if self.MarkedInsertBefore:
flags |= 0x0400
flags |= len(self.CurrentInsertionAction) << 5
flags |= len(self.MarkedInsertionAction)
writer.writeUShort(flags)
if len(self.CurrentInsertionAction) > 0:
currentIndex = actionIndex[
tuple(self.CurrentInsertionAction)]
currentIndex = actionIndex[tuple(self.CurrentInsertionAction)]
else:
currentIndex = 0xFFFF
writer.writeUShort(currentIndex)
if len(self.MarkedInsertionAction) > 0:
markedIndex = actionIndex[
tuple(self.MarkedInsertionAction)]
markedIndex = actionIndex[tuple(self.MarkedInsertionAction)]
else:
markedIndex = 0xFFFF
writer.writeUShort(markedIndex)
@ -412,19 +425,16 @@ class InsertionMorphAction(AATAction):
self.CurrentInsertBefore = bool(flags & 0x0800)
self.MarkedInsertBefore = bool(flags & 0x0400)
self.CurrentInsertionAction = self._decompileInsertionAction(
actionReader, font,
index=reader.readUShort(),
count=((flags & 0x03E0) >> 5))
actionReader, font, index=reader.readUShort(), count=((flags & 0x03E0) >> 5)
)
self.MarkedInsertionAction = self._decompileInsertionAction(
actionReader, font,
index=reader.readUShort(),
count=(flags & 0x001F))
actionReader, font, index=reader.readUShort(), count=(flags & 0x001F)
)
def _decompileInsertionAction(self, actionReader, font, index, count):
if index == 0xFFFF or count == 0:
return []
reader = actionReader.getSubReader(
actionReader.pos + index * 2)
reader = actionReader.getSubReader(actionReader.pos + index * 2)
return font.getGlyphNameMany(reader.readUShortArray(count))
def toXML(self, xmlWriter, font, attrs, name):
@ -452,11 +462,9 @@ class InsertionMorphAction(AATAction):
for flag in eltAttrs["value"].split(","):
self._setFlag(flag.strip())
elif eltName == "CurrentInsertionAction":
self.CurrentInsertionAction.append(
eltAttrs["glyph"])
self.CurrentInsertionAction.append(eltAttrs["glyph"])
elif eltName == "MarkedInsertionAction":
self.MarkedInsertionAction.append(
eltAttrs["glyph"])
self.MarkedInsertionAction.append(eltAttrs["glyph"])
else:
assert False, eltName
@ -493,29 +501,37 @@ class InsertionMorphAction(AATAction):
class FeatureParams(BaseTable):
def compile(self, writer, font):
assert featureParamTypes.get(writer['FeatureTag']) == self.__class__, "Wrong FeatureParams type for feature '%s': %s" % (writer['FeatureTag'], self.__class__.__name__)
assert (
featureParamTypes.get(writer["FeatureTag"]) == self.__class__
), "Wrong FeatureParams type for feature '%s': %s" % (
writer["FeatureTag"],
self.__class__.__name__,
)
BaseTable.compile(self, writer, font)
def toXML(self, xmlWriter, font, attrs=None, name=None):
BaseTable.toXML(self, xmlWriter, font, attrs, name=self.__class__.__name__)
class FeatureParamsSize(FeatureParams):
pass
class FeatureParamsStylisticSet(FeatureParams):
pass
class FeatureParamsCharacterVariants(FeatureParams):
pass
class Coverage(FormatSwitchingBaseTable):
# manual implementation to get rid of glyphID dependencies
def populateDefaults(self, propagator=None):
if not hasattr(self, 'glyphs'):
if not hasattr(self, "glyphs"):
self.glyphs = []
def postRead(self, rawTable, font):
@ -606,14 +622,13 @@ NO_VARIATION_INDEX = 0xFFFFFFFF
class DeltaSetIndexMap(getFormatSwitchingBaseTableClass("uint8")):
def populateDefaults(self, propagator=None):
if not hasattr(self, 'mapping'):
if not hasattr(self, "mapping"):
self.mapping = []
def postRead(self, rawTable, font):
assert (rawTable['EntryFormat'] & 0xFFC0) == 0
self.mapping = rawTable['mapping']
assert (rawTable["EntryFormat"] & 0xFFC0) == 0
self.mapping = rawTable["mapping"]
@staticmethod
def getEntryFormat(mapping):
@ -647,24 +662,24 @@ class DeltaSetIndexMap(getFormatSwitchingBaseTableClass("uint8")):
mapping = self.mapping = []
self.Format = 1 if len(mapping) > 0xFFFF else 0
rawTable = self.__dict__.copy()
rawTable['MappingCount'] = len(mapping)
rawTable['EntryFormat'] = self.getEntryFormat(mapping)
rawTable["MappingCount"] = len(mapping)
rawTable["EntryFormat"] = self.getEntryFormat(mapping)
return rawTable
def toXML2(self, xmlWriter, font):
# Make xml dump less verbose, by omitting no-op entries like:
# <Map index="..." outer="65535" inner="65535"/>
xmlWriter.comment(
"Omitted values default to 0xFFFF/0xFFFF (no variations)"
)
xmlWriter.comment("Omitted values default to 0xFFFF/0xFFFF (no variations)")
xmlWriter.newline()
for i, value in enumerate(getattr(self, "mapping", [])):
attrs = [('index', i)]
attrs = [("index", i)]
if value != NO_VARIATION_INDEX:
attrs.extend([
('outer', value >> 16),
('inner', value & 0xFFFF),
])
attrs.extend(
[
("outer", value >> 16),
("inner", value & 0xFFFF),
]
)
xmlWriter.simpletag("Map", attrs)
xmlWriter.newline()
@ -672,23 +687,22 @@ class DeltaSetIndexMap(getFormatSwitchingBaseTableClass("uint8")):
mapping = getattr(self, "mapping", None)
if mapping is None:
self.mapping = mapping = []
index = safeEval(attrs['index'])
outer = safeEval(attrs.get('outer', '0xFFFF'))
inner = safeEval(attrs.get('inner', '0xFFFF'))
index = safeEval(attrs["index"])
outer = safeEval(attrs.get("outer", "0xFFFF"))
inner = safeEval(attrs.get("inner", "0xFFFF"))
assert inner <= 0xFFFF
mapping.insert(index, (outer << 16) | inner)
class VarIdxMap(BaseTable):
def populateDefaults(self, propagator=None):
if not hasattr(self, 'mapping'):
if not hasattr(self, "mapping"):
self.mapping = {}
def postRead(self, rawTable, font):
assert (rawTable['EntryFormat'] & 0xFFC0) == 0
assert (rawTable["EntryFormat"] & 0xFFC0) == 0
glyphOrder = font.getGlyphOrder()
mapList = rawTable['mapping']
mapList = rawTable["mapping"]
mapList.extend([mapList[-1]] * (len(glyphOrder) - len(mapList)))
self.mapping = dict(zip(glyphOrder, mapList))
@ -703,17 +717,17 @@ class VarIdxMap(BaseTable):
while len(mapping) > 1 and mapping[-2] == mapping[-1]:
del mapping[-1]
rawTable = {'mapping': mapping}
rawTable['MappingCount'] = len(mapping)
rawTable['EntryFormat'] = DeltaSetIndexMap.getEntryFormat(mapping)
rawTable = {"mapping": mapping}
rawTable["MappingCount"] = len(mapping)
rawTable["EntryFormat"] = DeltaSetIndexMap.getEntryFormat(mapping)
return rawTable
def toXML2(self, xmlWriter, font):
for glyph, value in sorted(getattr(self, "mapping", {}).items()):
attrs = (
('glyph', glyph),
('outer', value >> 16),
('inner', value & 0xFFFF),
("glyph", glyph),
("outer", value >> 16),
("inner", value & 0xFFFF),
)
xmlWriter.simpletag("Map", attrs)
xmlWriter.newline()
@ -724,17 +738,16 @@ class VarIdxMap(BaseTable):
mapping = {}
self.mapping = mapping
try:
glyph = attrs['glyph']
glyph = attrs["glyph"]
except: # https://github.com/fonttools/fonttools/commit/21cbab8ce9ded3356fef3745122da64dcaf314e9#commitcomment-27649836
glyph = font.getGlyphOrder()[attrs['index']]
outer = safeEval(attrs['outer'])
inner = safeEval(attrs['inner'])
glyph = font.getGlyphOrder()[attrs["index"]]
outer = safeEval(attrs["outer"])
inner = safeEval(attrs["inner"])
assert inner <= 0xFFFF
mapping[glyph] = (outer << 16) | inner
class VarRegionList(BaseTable):
def preWrite(self, font):
# The OT spec says VarStore.VarRegionList.RegionAxisCount should always
# be equal to the fvar.axisCount, and OTS < v8.0.0 enforces this rule
@ -748,14 +761,13 @@ class VarRegionList(BaseTable):
self.RegionAxisCount = len(fvarTable.axes)
return {
**self.__dict__,
"RegionAxisCount": CountReference(self.__dict__, "RegionAxisCount")
"RegionAxisCount": CountReference(self.__dict__, "RegionAxisCount"),
}
class SingleSubst(FormatSwitchingBaseTable):
def populateDefaults(self, propagator=None):
if not hasattr(self, 'mapping'):
if not hasattr(self, "mapping"):
self.mapping = {}
def postRead(self, rawTable, font):
@ -769,8 +781,9 @@ class SingleSubst(FormatSwitchingBaseTable):
for inp, out in zip(input, outNames):
mapping[inp] = out
elif self.Format == 2:
assert len(input) == rawTable["GlyphCount"], \
"invalid SingleSubstFormat2 table"
assert (
len(input) == rawTable["GlyphCount"]
), "invalid SingleSubstFormat2 table"
subst = rawTable["Substitute"]
for inp, sub in zip(input, subst):
mapping[inp] = sub
@ -821,8 +834,7 @@ class SingleSubst(FormatSwitchingBaseTable):
def toXML2(self, xmlWriter, font):
items = sorted(self.mapping.items())
for inGlyph, outGlyph in items:
xmlWriter.simpletag("Substitution",
[("in", inGlyph), ("out", outGlyph)])
xmlWriter.simpletag("Substitution", [("in", inGlyph), ("out", outGlyph)])
xmlWriter.newline()
def fromXML(self, name, attrs, content, font):
@ -834,9 +846,8 @@ class SingleSubst(FormatSwitchingBaseTable):
class MultipleSubst(FormatSwitchingBaseTable):
def populateDefaults(self, propagator=None):
if not hasattr(self, 'mapping'):
if not hasattr(self, "mapping"):
self.mapping = {}
def postRead(self, rawTable, font):
@ -859,8 +870,7 @@ class MultipleSubst(FormatSwitchingBaseTable):
self.Format = 1
rawTable = {
"Coverage": cov,
"Sequence": [self.makeSequence_(mapping[glyph])
for glyph in cov.glyphs],
"Sequence": [self.makeSequence_(mapping[glyph]) for glyph in cov.glyphs],
}
return rawTable
@ -868,8 +878,7 @@ class MultipleSubst(FormatSwitchingBaseTable):
items = sorted(self.mapping.items())
for inGlyph, outGlyphs in items:
out = ",".join(outGlyphs)
xmlWriter.simpletag("Substitution",
[("in", inGlyph), ("out", out)])
xmlWriter.simpletag("Substitution", [("in", inGlyph), ("out", out)])
xmlWriter.newline()
def fromXML(self, name, attrs, content, font):
@ -912,9 +921,8 @@ class MultipleSubst(FormatSwitchingBaseTable):
class ClassDef(FormatSwitchingBaseTable):
def populateDefaults(self, propagator=None):
if not hasattr(self, 'classDefs'):
if not hasattr(self, "classDefs"):
self.classDefs = {}
def postRead(self, rawTable, font):
@ -1019,9 +1027,8 @@ class ClassDef(FormatSwitchingBaseTable):
class AlternateSubst(FormatSwitchingBaseTable):
def populateDefaults(self, propagator=None):
if not hasattr(self, 'alternates'):
if not hasattr(self, "alternates"):
self.alternates = {}
def postRead(self, rawTable, font):
@ -1090,9 +1097,8 @@ class AlternateSubst(FormatSwitchingBaseTable):
class LigatureSubst(FormatSwitchingBaseTable):
def populateDefaults(self, propagator=None):
if not hasattr(self, 'ligatures'):
if not hasattr(self, "ligatures"):
self.ligatures = {}
def postRead(self, rawTable, font):
@ -1120,7 +1126,9 @@ class LigatureSubst(FormatSwitchingBaseTable):
# ligatures is map from components-sequence to lig-glyph
newLigatures = dict()
for comps,lig in sorted(ligatures.items(), key=lambda item: (-len(item[0]), item[0])):
for comps, lig in sorted(
ligatures.items(), key=lambda item: (-len(item[0]), item[0])
):
ligature = Ligature()
ligature.Component = comps[1:]
ligature.CompCount = len(comps)
@ -1156,8 +1164,9 @@ class LigatureSubst(FormatSwitchingBaseTable):
xmlWriter.begintag("LigatureSet", glyph=glyphName)
xmlWriter.newline()
for lig in ligSets:
xmlWriter.simpletag("Ligature", glyph=lig.LigGlyph,
components=",".join(lig.Component))
xmlWriter.simpletag(
"Ligature", glyph=lig.LigGlyph, components=",".join(lig.Component)
)
xmlWriter.newline()
xmlWriter.endtag("LigatureSet")
xmlWriter.newline()
@ -1183,7 +1192,6 @@ class LigatureSubst(FormatSwitchingBaseTable):
class COLR(BaseTable):
def decompile(self, reader, font):
# COLRv0 is exceptional in that LayerRecordCount appears *after* the
# LayerRecordArray it counts, but the parser logic expects Count fields
@ -1208,7 +1216,7 @@ class COLR(BaseTable):
self.LayerRecordCount = None
return {
**self.__dict__,
"LayerRecordCount": CountReference(self.__dict__, "LayerRecordCount")
"LayerRecordCount": CountReference(self.__dict__, "LayerRecordCount"),
}
@ -1224,7 +1232,11 @@ class LookupList(BaseTable):
raise ValueError
def toXML2(self, xmlWriter, font):
if not font or "Debg" not in font or LOOKUP_DEBUG_INFO_KEY not in font["Debg"].data:
if (
not font
or "Debg" not in font
or LOOKUP_DEBUG_INFO_KEY not in font["Debg"].data
):
return super().toXML2(xmlWriter, font)
debugData = font["Debg"].data[LOOKUP_DEBUG_INFO_KEY][self.table]
for conv in self.getConverters():
@ -1235,37 +1247,37 @@ class LookupList(BaseTable):
info = LookupDebugInfo(*debugData[str(lookupIndex)])
tag = info.location
if info.name:
tag = f'{info.name}: {tag}'
tag = f"{info.name}: {tag}"
if info.feature:
script, language, feature = info.feature
tag = f'{tag} in {feature} ({script}/{language})'
tag = f"{tag} in {feature} ({script}/{language})"
xmlWriter.comment(tag)
xmlWriter.newline()
conv.xmlWrite(xmlWriter, font, item, conv.name,
[("index", lookupIndex)])
conv.xmlWrite(
xmlWriter, font, item, conv.name, [("index", lookupIndex)]
)
else:
if conv.aux and not eval(conv.aux, None, vars(self)):
continue
value = getattr(self, conv.name, None) # TODO Handle defaults instead of defaulting to None!
value = getattr(
self, conv.name, None
) # TODO Handle defaults instead of defaulting to None!
conv.xmlWrite(xmlWriter, font, value, conv.name, [])
class BaseGlyphRecordArray(BaseTable):
class BaseGlyphRecordArray(BaseTable):
def preWrite(self, font):
self.BaseGlyphRecord = sorted(
self.BaseGlyphRecord,
key=lambda rec: font.getGlyphID(rec.BaseGlyph)
self.BaseGlyphRecord, key=lambda rec: font.getGlyphID(rec.BaseGlyph)
)
return self.__dict__.copy()
class BaseGlyphList(BaseTable):
def preWrite(self, font):
self.BaseGlyphPaintRecord = sorted(
self.BaseGlyphPaintRecord,
key=lambda rec: font.getGlyphID(rec.BaseGlyph)
self.BaseGlyphPaintRecord, key=lambda rec: font.getGlyphID(rec.BaseGlyph)
)
return self.__dict__.copy()
@ -1292,7 +1304,6 @@ class ClipBox(getFormatSwitchingBaseTableClass("uint8")):
class ClipList(getFormatSwitchingBaseTableClass("uint8")):
def populateDefaults(self, propagator=None):
if not hasattr(self, "clips"):
self.clips = {}
@ -1333,8 +1344,7 @@ class ClipList(getFormatSwitchingBaseTableClass("uint8")):
)
if missingGlyphs:
log.warning(
"ClipRecord[%i] range references missing "
"glyph IDs: [%i-%i]",
"ClipRecord[%i] range references missing " "glyph IDs: [%i-%i]",
i,
min(missingGlyphs),
max(missingGlyphs),
@ -1350,8 +1360,7 @@ class ClipList(getFormatSwitchingBaseTableClass("uint8")):
if key not in uniqueClips:
uniqueClips[key] = clipBox
return {
frozenset(glyphs): uniqueClips[key]
for key, glyphs in glyphsByClip.items()
frozenset(glyphs): uniqueClips[key] for key, glyphs in glyphsByClip.items()
}
def preWrite(self, font):
@ -1361,8 +1370,7 @@ class ClipList(getFormatSwitchingBaseTableClass("uint8")):
glyphMap = font.getReverseGlyphMap()
for glyphs, clipBox in self.groups().items():
glyphIDs = sorted(
glyphMap[glyphName] for glyphName in glyphs
if glyphName in glyphMap
glyphMap[glyphName] for glyphName in glyphs if glyphName in glyphMap
)
if not glyphIDs:
continue
@ -1486,7 +1494,7 @@ class CompositeMode(IntEnum):
class PaintFormat(IntEnum):
PaintColrLayers = 1
PaintSolid = 2
PaintVarSolid = 3,
PaintVarSolid = (3,)
PaintLinearGradient = 4
PaintVarLinearGradient = 5
PaintRadialGradient = 6
@ -1556,9 +1564,7 @@ class Paint(getFormatSwitchingBaseTableClass("uint8")):
layers = []
if colr.LayerList is not None:
layers = colr.LayerList.Paint
return layers[
self.FirstLayerIndex : self.FirstLayerIndex + self.NumLayers
]
return layers[self.FirstLayerIndex : self.FirstLayerIndex + self.NumLayers]
if self.Format == PaintFormat.PaintColrGlyph:
for record in colr.BaseGlyphList.BaseGlyphPaintRecord:
@ -1596,30 +1602,82 @@ class Paint(getFormatSwitchingBaseTableClass("uint8")):
# subclass for each alternate field name.
#
_equivalents = {
'MarkArray': ("Mark1Array",),
'LangSys': ('DefaultLangSys',),
'Coverage': ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage',
'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage',
'LookAheadCoverage', 'VertGlyphCoverage', 'HorizGlyphCoverage',
'TopAccentCoverage', 'ExtendedShapeCoverage', 'MathKernCoverage'),
'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef',
'LookAheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'),
'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor',
'Mark2Anchor', 'MarkAnchor'),
'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice',
'XDeviceTable', 'YDeviceTable', 'DeviceTable'),
'Axis': ('HorizAxis', 'VertAxis',),
'MinMax': ('DefaultMinMax',),
'BaseCoord': ('MinCoord', 'MaxCoord',),
'JstfLangSys': ('DefJstfLangSys',),
'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB',
'ExtensionDisableGSUB',),
'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS',
'ExtensionDisableGPOS',),
'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',),
'MathKern': ('TopRightMathKern', 'TopLeftMathKern', 'BottomRightMathKern',
'BottomLeftMathKern'),
'MathGlyphConstruction': ('VertGlyphConstruction', 'HorizGlyphConstruction'),
"MarkArray": ("Mark1Array",),
"LangSys": ("DefaultLangSys",),
"Coverage": (
"MarkCoverage",
"BaseCoverage",
"LigatureCoverage",
"Mark1Coverage",
"Mark2Coverage",
"BacktrackCoverage",
"InputCoverage",
"LookAheadCoverage",
"VertGlyphCoverage",
"HorizGlyphCoverage",
"TopAccentCoverage",
"ExtendedShapeCoverage",
"MathKernCoverage",
),
"ClassDef": (
"ClassDef1",
"ClassDef2",
"BacktrackClassDef",
"InputClassDef",
"LookAheadClassDef",
"GlyphClassDef",
"MarkAttachClassDef",
),
"Anchor": (
"EntryAnchor",
"ExitAnchor",
"BaseAnchor",
"LigatureAnchor",
"Mark2Anchor",
"MarkAnchor",
),
"Device": (
"XPlaDevice",
"YPlaDevice",
"XAdvDevice",
"YAdvDevice",
"XDeviceTable",
"YDeviceTable",
"DeviceTable",
),
"Axis": (
"HorizAxis",
"VertAxis",
),
"MinMax": ("DefaultMinMax",),
"BaseCoord": (
"MinCoord",
"MaxCoord",
),
"JstfLangSys": ("DefJstfLangSys",),
"JstfGSUBModList": (
"ShrinkageEnableGSUB",
"ShrinkageDisableGSUB",
"ExtensionEnableGSUB",
"ExtensionDisableGSUB",
),
"JstfGPOSModList": (
"ShrinkageEnableGPOS",
"ShrinkageDisableGPOS",
"ExtensionEnableGPOS",
"ExtensionDisableGPOS",
),
"JstfMax": (
"ShrinkageJstfMax",
"ExtensionJstfMax",
),
"MathKern": (
"TopRightMathKern",
"TopLeftMathKern",
"BottomRightMathKern",
"BottomLeftMathKern",
),
"MathGlyphConstruction": ("VertGlyphConstruction", "HorizGlyphConstruction"),
}
#
@ -1627,6 +1685,7 @@ _equivalents = {
# XXX This should probably move to otBase.py
#
def fixLookupOverFlows(ttf, overflowRecord):
"""Either the offset from the LookupList to a lookup overflowed, or
an offset from a lookup to a subtable overflowed.
@ -1653,13 +1712,13 @@ def fixLookupOverFlows(ttf, overflowRecord):
"""
ok = 0
lookupIndex = overflowRecord.LookupListIndex
if (overflowRecord.SubTableIndex is None):
if overflowRecord.SubTableIndex is None:
lookupIndex = lookupIndex - 1
if lookupIndex < 0:
return ok
if overflowRecord.tableType == 'GSUB':
if overflowRecord.tableType == "GSUB":
extType = 7
elif overflowRecord.tableType == 'GPOS':
elif overflowRecord.tableType == "GPOS":
extType = 9
lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup
@ -1685,17 +1744,18 @@ def fixLookupOverFlows(ttf, overflowRecord):
ok = 1
return ok
def splitMultipleSubst(oldSubTable, newSubTable, overflowRecord):
ok = 1
oldMapping = sorted(oldSubTable.mapping.items())
oldLen = len(oldMapping)
if overflowRecord.itemName in ['Coverage', 'RangeRecord']:
if overflowRecord.itemName in ["Coverage", "RangeRecord"]:
# Coverage table is written last. Overflow is to or within the
# the coverage table. We will just cut the subtable in half.
newLen = oldLen // 2
elif overflowRecord.itemName == 'Sequence':
elif overflowRecord.itemName == "Sequence":
# We just need to back up by two items from the overflowed
# Sequence index to make sure the offset to the Coverage table
# doesn't overflow.
@ -1710,20 +1770,21 @@ def splitMultipleSubst(oldSubTable, newSubTable, overflowRecord):
return ok
def splitAlternateSubst(oldSubTable, newSubTable, overflowRecord):
ok = 1
if hasattr(oldSubTable, 'sortCoverageLast'):
if hasattr(oldSubTable, "sortCoverageLast"):
newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast
oldAlts = sorted(oldSubTable.alternates.items())
oldLen = len(oldAlts)
if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
if overflowRecord.itemName in ["Coverage", "RangeRecord"]:
# Coverage table is written last. overflow is to or within the
# the coverage table. We will just cut the subtable in half.
newLen = oldLen // 2
elif overflowRecord.itemName == 'AlternateSet':
elif overflowRecord.itemName == "AlternateSet":
# We just need to back up by two items
# from the overflowed AlternateSet index to make sure the offset
# to the Coverage table doesn't overflow.
@ -1744,12 +1805,12 @@ def splitLigatureSubst(oldSubTable, newSubTable, overflowRecord):
oldLigs = sorted(oldSubTable.ligatures.items())
oldLen = len(oldLigs)
if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
if overflowRecord.itemName in ["Coverage", "RangeRecord"]:
# Coverage table is written last. overflow is to or within the
# the coverage table. We will just cut the subtable in half.
newLen = oldLen // 2
elif overflowRecord.itemName == 'LigatureSet':
elif overflowRecord.itemName == "LigatureSet":
# We just need to back up by two items
# from the overflowed AlternateSet index to make sure the offset
# to the Coverage table doesn't overflow.
@ -1770,7 +1831,7 @@ def splitPairPos(oldSubTable, newSubTable, overflowRecord):
ok = False
newSubTable.Format = oldSubTable.Format
if oldSubTable.Format == 1 and len(oldSubTable.PairSet) > 1:
for name in 'ValueFormat1', 'ValueFormat2':
for name in "ValueFormat1", "ValueFormat2":
setattr(newSubTable, name, getattr(oldSubTable, name))
# Move top half of coverage to new subtable
@ -1794,9 +1855,9 @@ def splitPairPos(oldSubTable, newSubTable, overflowRecord):
ok = True
elif oldSubTable.Format == 2 and len(oldSubTable.Class1Record) > 1:
if not hasattr(oldSubTable, 'Class2Count'):
if not hasattr(oldSubTable, "Class2Count"):
oldSubTable.Class2Count = len(oldSubTable.Class1Record[0].Class2Record)
for name in 'Class2Count', 'ClassDef2', 'ValueFormat1', 'ValueFormat2':
for name in "Class2Count", "ClassDef2", "ValueFormat1", "ValueFormat2":
setattr(newSubTable, name, getattr(oldSubTable, name))
# The two subtables will still have the same ClassDef2 and the table
@ -1817,11 +1878,15 @@ def splitPairPos(oldSubTable, newSubTable, overflowRecord):
newGlyphs = set(k for k, v in classDefs.items() if v >= oldCount)
oldSubTable.Coverage.glyphs = [g for g in coverage if g not in newGlyphs]
oldSubTable.ClassDef1.classDefs = {k:v for k,v in classDefs.items() if v < oldCount}
oldSubTable.ClassDef1.classDefs = {
k: v for k, v in classDefs.items() if v < oldCount
}
oldSubTable.Class1Record = records[:oldCount]
newSubTable.Coverage.glyphs = [g for g in coverage if g in newGlyphs]
newSubTable.ClassDef1.classDefs = {k:(v-oldCount) for k,v in classDefs.items() if v > oldCount}
newSubTable.ClassDef1.classDefs = {
k: (v - oldCount) for k, v in classDefs.items() if v > oldCount
}
newSubTable.Class1Record = records[oldCount:]
oldSubTable.Class1Count = len(oldSubTable.Class1Record)
@ -1845,8 +1910,7 @@ def splitMarkBasePos(oldSubTable, newSubTable, overflowRecord):
oldMarkCoverage, oldMarkRecords = [], []
newMarkCoverage, newMarkRecords = [], []
for glyphName, markRecord in zip(
oldSubTable.MarkCoverage.glyphs,
oldSubTable.MarkArray.MarkRecord
oldSubTable.MarkCoverage.glyphs, oldSubTable.MarkArray.MarkRecord
):
if markRecord.Class < oldClassCount:
oldMarkCoverage.append(glyphName)
@ -1893,7 +1957,8 @@ def splitMarkBasePos(oldSubTable, newSubTable, overflowRecord):
return True
splitTable = { 'GSUB': {
splitTable = {
"GSUB": {
# 1: splitSingleSubst,
2: splitMultipleSubst,
3: splitAlternateSubst,
@ -1903,7 +1968,7 @@ splitTable = { 'GSUB': {
# 7: splitExtensionSubst,
# 8: splitReverseChainSingleSubst,
},
'GPOS': {
"GPOS": {
# 1: splitSinglePos,
2: splitPairPos,
# 3: splitCursivePos,
@ -1913,9 +1978,9 @@ splitTable = { 'GSUB': {
# 7: splitContextPos,
# 8: splitChainContextPos,
# 9: splitExtensionPos,
},
}
}
def fixSubTableOverFlows(ttf, overflowRecord):
"""
@ -1931,14 +1996,16 @@ def fixSubTableOverFlows(ttf, overflowRecord):
subtable.DontShare = True
return True
if hasattr(subtable, 'ExtSubTable'):
if hasattr(subtable, "ExtSubTable"):
# We split the subtable of the Extension table, and add a new Extension table
# to contain the new subtable.
subTableType = subtable.ExtSubTable.__class__.LookupType
extSubTable = subtable
subtable = extSubTable.ExtSubTable
newExtSubTableClass = lookupTypes[overflowRecord.tableType][extSubTable.__class__.LookupType]
newExtSubTableClass = lookupTypes[overflowRecord.tableType][
extSubTable.__class__.LookupType
]
newExtSubTable = newExtSubTableClass()
newExtSubTable.Format = extSubTable.Format
toInsert = newExtSubTable
@ -1952,7 +2019,7 @@ def fixSubTableOverFlows(ttf, overflowRecord):
newSubTable = newSubTableClass()
toInsert = newSubTable
if hasattr(lookup, 'SubTableCount'): # may not be defined yet.
if hasattr(lookup, "SubTableCount"): # may not be defined yet.
lookup.SubTableCount = lookup.SubTableCount + 1
try:
@ -1970,6 +2037,7 @@ def fixSubTableOverFlows(ttf, overflowRecord):
lookup.SubTable.insert(subIndex + 1, toInsert)
return ok
# End of OverFlow logic
@ -1995,7 +2063,7 @@ def _buildClasses():
if name not in namespace:
# the class doesn't exist yet, so the base implementation is used.
cls = type(name, (baseClass,), {})
if name in ('GSUB', 'GPOS'):
if name in ("GSUB", "GPOS"):
cls.DontShare = True
namespace[name] = cls
@ -2014,7 +2082,7 @@ def _buildClasses():
global lookupTypes
lookupTypes = {
'GSUB': {
"GSUB": {
1: SingleSubst,
2: MultipleSubst,
3: AlternateSubst,
@ -2024,7 +2092,7 @@ def _buildClasses():
7: ExtensionSubst,
8: ReverseChainSingleSubst,
},
'GPOS': {
"GPOS": {
1: SinglePos,
2: PairPos,
3: CursivePos,
@ -2035,10 +2103,10 @@ def _buildClasses():
8: ChainContextPos,
9: ExtensionPos,
},
'mort': {
"mort": {
4: NoncontextualMorph,
},
'morx': {
"morx": {
0: RearrangementMorph,
1: ContextualMorph,
2: LigatureMorph,
@ -2047,22 +2115,23 @@ def _buildClasses():
5: InsertionMorph,
},
}
lookupTypes['JSTF'] = lookupTypes['GPOS'] # JSTF contains GPOS
lookupTypes["JSTF"] = lookupTypes["GPOS"] # JSTF contains GPOS
for lookupEnum in lookupTypes.values():
for enum, cls in lookupEnum.items():
cls.LookupType = enum
global featureParamTypes
featureParamTypes = {
'size': FeatureParamsSize,
"size": FeatureParamsSize,
}
for i in range(1, 20 + 1):
featureParamTypes['ss%02d' % i] = FeatureParamsStylisticSet
featureParamTypes["ss%02d" % i] = FeatureParamsStylisticSet
for i in range(1, 99 + 1):
featureParamTypes['cv%02d' % i] = FeatureParamsCharacterVariants
featureParamTypes["cv%02d" % i] = FeatureParamsCharacterVariants
# add converters to classes
from .otConverters import buildConverters
for name, table in otData:
m = formatPat.match(name)
if m: