1*e1fe3e4aSElliott Hughes# coding: utf-8 2*e1fe3e4aSElliott Hughes"""fontTools.ttLib.tables.otTables -- A collection of classes representing the various 3*e1fe3e4aSElliott HughesOpenType subtables. 4*e1fe3e4aSElliott Hughes 5*e1fe3e4aSElliott HughesMost are constructed upon import from data in otData.py, all are populated with 6*e1fe3e4aSElliott Hughesconverter objects from otConverters.py. 7*e1fe3e4aSElliott Hughes""" 8*e1fe3e4aSElliott Hughesimport copy 9*e1fe3e4aSElliott Hughesfrom enum import IntEnum 10*e1fe3e4aSElliott Hughesfrom functools import reduce 11*e1fe3e4aSElliott Hughesfrom math import radians 12*e1fe3e4aSElliott Hughesimport itertools 13*e1fe3e4aSElliott Hughesfrom collections import defaultdict, namedtuple 14*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables.otTraverse import dfs_base_table 15*e1fe3e4aSElliott Hughesfrom fontTools.misc.arrayTools import quantizeRect 16*e1fe3e4aSElliott Hughesfrom fontTools.misc.roundTools import otRound 17*e1fe3e4aSElliott Hughesfrom fontTools.misc.transform import Transform, Identity 18*e1fe3e4aSElliott Hughesfrom fontTools.misc.textTools import bytesjoin, pad, safeEval 19*e1fe3e4aSElliott Hughesfrom fontTools.pens.boundsPen import ControlBoundsPen 20*e1fe3e4aSElliott Hughesfrom fontTools.pens.transformPen import TransformPen 21*e1fe3e4aSElliott Hughesfrom .otBase import ( 22*e1fe3e4aSElliott Hughes BaseTable, 23*e1fe3e4aSElliott Hughes FormatSwitchingBaseTable, 24*e1fe3e4aSElliott Hughes ValueRecord, 25*e1fe3e4aSElliott Hughes CountReference, 26*e1fe3e4aSElliott Hughes getFormatSwitchingBaseTableClass, 27*e1fe3e4aSElliott Hughes) 28*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.lookupDebugInfo import LookupDebugInfo, LOOKUP_DEBUG_INFO_KEY 29*e1fe3e4aSElliott Hughesimport logging 30*e1fe3e4aSElliott Hughesimport struct 31*e1fe3e4aSElliott Hughesfrom typing import TYPE_CHECKING, Iterator, List, Optional, Set 32*e1fe3e4aSElliott Hughes 33*e1fe3e4aSElliott Hughesif TYPE_CHECKING: 34*e1fe3e4aSElliott Hughes from fontTools.ttLib.ttGlyphSet import _TTGlyphSet 35*e1fe3e4aSElliott Hughes 36*e1fe3e4aSElliott Hughes 37*e1fe3e4aSElliott Hugheslog = logging.getLogger(__name__) 38*e1fe3e4aSElliott Hughes 39*e1fe3e4aSElliott Hughes 40*e1fe3e4aSElliott Hughesclass AATStateTable(object): 41*e1fe3e4aSElliott Hughes def __init__(self): 42*e1fe3e4aSElliott Hughes self.GlyphClasses = {} # GlyphID --> GlyphClass 43*e1fe3e4aSElliott Hughes self.States = [] # List of AATState, indexed by state number 44*e1fe3e4aSElliott Hughes self.PerGlyphLookups = [] # [{GlyphID:GlyphID}, ...] 45*e1fe3e4aSElliott Hughes 46*e1fe3e4aSElliott Hughes 47*e1fe3e4aSElliott Hughesclass AATState(object): 48*e1fe3e4aSElliott Hughes def __init__(self): 49*e1fe3e4aSElliott Hughes self.Transitions = {} # GlyphClass --> AATAction 50*e1fe3e4aSElliott Hughes 51*e1fe3e4aSElliott Hughes 52*e1fe3e4aSElliott Hughesclass AATAction(object): 53*e1fe3e4aSElliott Hughes _FLAGS = None 54*e1fe3e4aSElliott Hughes 55*e1fe3e4aSElliott Hughes @staticmethod 56*e1fe3e4aSElliott Hughes def compileActions(font, states): 57*e1fe3e4aSElliott Hughes return (None, None) 58*e1fe3e4aSElliott Hughes 59*e1fe3e4aSElliott Hughes def _writeFlagsToXML(self, xmlWriter): 60*e1fe3e4aSElliott Hughes flags = [f for f in self._FLAGS if self.__dict__[f]] 61*e1fe3e4aSElliott Hughes if flags: 62*e1fe3e4aSElliott Hughes xmlWriter.simpletag("Flags", value=",".join(flags)) 63*e1fe3e4aSElliott Hughes xmlWriter.newline() 64*e1fe3e4aSElliott Hughes if self.ReservedFlags != 0: 65*e1fe3e4aSElliott Hughes xmlWriter.simpletag("ReservedFlags", value="0x%04X" % self.ReservedFlags) 66*e1fe3e4aSElliott Hughes xmlWriter.newline() 67*e1fe3e4aSElliott Hughes 68*e1fe3e4aSElliott Hughes def _setFlag(self, flag): 69*e1fe3e4aSElliott Hughes assert flag in self._FLAGS, "unsupported flag %s" % flag 70*e1fe3e4aSElliott Hughes self.__dict__[flag] = True 71*e1fe3e4aSElliott Hughes 72*e1fe3e4aSElliott Hughes 73*e1fe3e4aSElliott Hughesclass RearrangementMorphAction(AATAction): 74*e1fe3e4aSElliott Hughes staticSize = 4 75*e1fe3e4aSElliott Hughes actionHeaderSize = 0 76*e1fe3e4aSElliott Hughes _FLAGS = ["MarkFirst", "DontAdvance", "MarkLast"] 77*e1fe3e4aSElliott Hughes 78*e1fe3e4aSElliott Hughes _VERBS = { 79*e1fe3e4aSElliott Hughes 0: "no change", 80*e1fe3e4aSElliott Hughes 1: "Ax ⇒ xA", 81*e1fe3e4aSElliott Hughes 2: "xD ⇒ Dx", 82*e1fe3e4aSElliott Hughes 3: "AxD ⇒ DxA", 83*e1fe3e4aSElliott Hughes 4: "ABx ⇒ xAB", 84*e1fe3e4aSElliott Hughes 5: "ABx ⇒ xBA", 85*e1fe3e4aSElliott Hughes 6: "xCD ⇒ CDx", 86*e1fe3e4aSElliott Hughes 7: "xCD ⇒ DCx", 87*e1fe3e4aSElliott Hughes 8: "AxCD ⇒ CDxA", 88*e1fe3e4aSElliott Hughes 9: "AxCD ⇒ DCxA", 89*e1fe3e4aSElliott Hughes 10: "ABxD ⇒ DxAB", 90*e1fe3e4aSElliott Hughes 11: "ABxD ⇒ DxBA", 91*e1fe3e4aSElliott Hughes 12: "ABxCD ⇒ CDxAB", 92*e1fe3e4aSElliott Hughes 13: "ABxCD ⇒ CDxBA", 93*e1fe3e4aSElliott Hughes 14: "ABxCD ⇒ DCxAB", 94*e1fe3e4aSElliott Hughes 15: "ABxCD ⇒ DCxBA", 95*e1fe3e4aSElliott Hughes } 96*e1fe3e4aSElliott Hughes 97*e1fe3e4aSElliott Hughes def __init__(self): 98*e1fe3e4aSElliott Hughes self.NewState = 0 99*e1fe3e4aSElliott Hughes self.Verb = 0 100*e1fe3e4aSElliott Hughes self.MarkFirst = False 101*e1fe3e4aSElliott Hughes self.DontAdvance = False 102*e1fe3e4aSElliott Hughes self.MarkLast = False 103*e1fe3e4aSElliott Hughes self.ReservedFlags = 0 104*e1fe3e4aSElliott Hughes 105*e1fe3e4aSElliott Hughes def compile(self, writer, font, actionIndex): 106*e1fe3e4aSElliott Hughes assert actionIndex is None 107*e1fe3e4aSElliott Hughes writer.writeUShort(self.NewState) 108*e1fe3e4aSElliott Hughes assert self.Verb >= 0 and self.Verb <= 15, self.Verb 109*e1fe3e4aSElliott Hughes flags = self.Verb | self.ReservedFlags 110*e1fe3e4aSElliott Hughes if self.MarkFirst: 111*e1fe3e4aSElliott Hughes flags |= 0x8000 112*e1fe3e4aSElliott Hughes if self.DontAdvance: 113*e1fe3e4aSElliott Hughes flags |= 0x4000 114*e1fe3e4aSElliott Hughes if self.MarkLast: 115*e1fe3e4aSElliott Hughes flags |= 0x2000 116*e1fe3e4aSElliott Hughes writer.writeUShort(flags) 117*e1fe3e4aSElliott Hughes 118*e1fe3e4aSElliott Hughes def decompile(self, reader, font, actionReader): 119*e1fe3e4aSElliott Hughes assert actionReader is None 120*e1fe3e4aSElliott Hughes self.NewState = reader.readUShort() 121*e1fe3e4aSElliott Hughes flags = reader.readUShort() 122*e1fe3e4aSElliott Hughes self.Verb = flags & 0xF 123*e1fe3e4aSElliott Hughes self.MarkFirst = bool(flags & 0x8000) 124*e1fe3e4aSElliott Hughes self.DontAdvance = bool(flags & 0x4000) 125*e1fe3e4aSElliott Hughes self.MarkLast = bool(flags & 0x2000) 126*e1fe3e4aSElliott Hughes self.ReservedFlags = flags & 0x1FF0 127*e1fe3e4aSElliott Hughes 128*e1fe3e4aSElliott Hughes def toXML(self, xmlWriter, font, attrs, name): 129*e1fe3e4aSElliott Hughes xmlWriter.begintag(name, **attrs) 130*e1fe3e4aSElliott Hughes xmlWriter.newline() 131*e1fe3e4aSElliott Hughes xmlWriter.simpletag("NewState", value=self.NewState) 132*e1fe3e4aSElliott Hughes xmlWriter.newline() 133*e1fe3e4aSElliott Hughes self._writeFlagsToXML(xmlWriter) 134*e1fe3e4aSElliott Hughes xmlWriter.simpletag("Verb", value=self.Verb) 135*e1fe3e4aSElliott Hughes verbComment = self._VERBS.get(self.Verb) 136*e1fe3e4aSElliott Hughes if verbComment is not None: 137*e1fe3e4aSElliott Hughes xmlWriter.comment(verbComment) 138*e1fe3e4aSElliott Hughes xmlWriter.newline() 139*e1fe3e4aSElliott Hughes xmlWriter.endtag(name) 140*e1fe3e4aSElliott Hughes xmlWriter.newline() 141*e1fe3e4aSElliott Hughes 142*e1fe3e4aSElliott Hughes def fromXML(self, name, attrs, content, font): 143*e1fe3e4aSElliott Hughes self.NewState = self.Verb = self.ReservedFlags = 0 144*e1fe3e4aSElliott Hughes self.MarkFirst = self.DontAdvance = self.MarkLast = False 145*e1fe3e4aSElliott Hughes content = [t for t in content if isinstance(t, tuple)] 146*e1fe3e4aSElliott Hughes for eltName, eltAttrs, eltContent in content: 147*e1fe3e4aSElliott Hughes if eltName == "NewState": 148*e1fe3e4aSElliott Hughes self.NewState = safeEval(eltAttrs["value"]) 149*e1fe3e4aSElliott Hughes elif eltName == "Verb": 150*e1fe3e4aSElliott Hughes self.Verb = safeEval(eltAttrs["value"]) 151*e1fe3e4aSElliott Hughes elif eltName == "ReservedFlags": 152*e1fe3e4aSElliott Hughes self.ReservedFlags = safeEval(eltAttrs["value"]) 153*e1fe3e4aSElliott Hughes elif eltName == "Flags": 154*e1fe3e4aSElliott Hughes for flag in eltAttrs["value"].split(","): 155*e1fe3e4aSElliott Hughes self._setFlag(flag.strip()) 156*e1fe3e4aSElliott Hughes 157*e1fe3e4aSElliott Hughes 158*e1fe3e4aSElliott Hughesclass ContextualMorphAction(AATAction): 159*e1fe3e4aSElliott Hughes staticSize = 8 160*e1fe3e4aSElliott Hughes actionHeaderSize = 0 161*e1fe3e4aSElliott Hughes _FLAGS = ["SetMark", "DontAdvance"] 162*e1fe3e4aSElliott Hughes 163*e1fe3e4aSElliott Hughes def __init__(self): 164*e1fe3e4aSElliott Hughes self.NewState = 0 165*e1fe3e4aSElliott Hughes self.SetMark, self.DontAdvance = False, False 166*e1fe3e4aSElliott Hughes self.ReservedFlags = 0 167*e1fe3e4aSElliott Hughes self.MarkIndex, self.CurrentIndex = 0xFFFF, 0xFFFF 168*e1fe3e4aSElliott Hughes 169*e1fe3e4aSElliott Hughes def compile(self, writer, font, actionIndex): 170*e1fe3e4aSElliott Hughes assert actionIndex is None 171*e1fe3e4aSElliott Hughes writer.writeUShort(self.NewState) 172*e1fe3e4aSElliott Hughes flags = self.ReservedFlags 173*e1fe3e4aSElliott Hughes if self.SetMark: 174*e1fe3e4aSElliott Hughes flags |= 0x8000 175*e1fe3e4aSElliott Hughes if self.DontAdvance: 176*e1fe3e4aSElliott Hughes flags |= 0x4000 177*e1fe3e4aSElliott Hughes writer.writeUShort(flags) 178*e1fe3e4aSElliott Hughes writer.writeUShort(self.MarkIndex) 179*e1fe3e4aSElliott Hughes writer.writeUShort(self.CurrentIndex) 180*e1fe3e4aSElliott Hughes 181*e1fe3e4aSElliott Hughes def decompile(self, reader, font, actionReader): 182*e1fe3e4aSElliott Hughes assert actionReader is None 183*e1fe3e4aSElliott Hughes self.NewState = reader.readUShort() 184*e1fe3e4aSElliott Hughes flags = reader.readUShort() 185*e1fe3e4aSElliott Hughes self.SetMark = bool(flags & 0x8000) 186*e1fe3e4aSElliott Hughes self.DontAdvance = bool(flags & 0x4000) 187*e1fe3e4aSElliott Hughes self.ReservedFlags = flags & 0x3FFF 188*e1fe3e4aSElliott Hughes self.MarkIndex = reader.readUShort() 189*e1fe3e4aSElliott Hughes self.CurrentIndex = reader.readUShort() 190*e1fe3e4aSElliott Hughes 191*e1fe3e4aSElliott Hughes def toXML(self, xmlWriter, font, attrs, name): 192*e1fe3e4aSElliott Hughes xmlWriter.begintag(name, **attrs) 193*e1fe3e4aSElliott Hughes xmlWriter.newline() 194*e1fe3e4aSElliott Hughes xmlWriter.simpletag("NewState", value=self.NewState) 195*e1fe3e4aSElliott Hughes xmlWriter.newline() 196*e1fe3e4aSElliott Hughes self._writeFlagsToXML(xmlWriter) 197*e1fe3e4aSElliott Hughes xmlWriter.simpletag("MarkIndex", value=self.MarkIndex) 198*e1fe3e4aSElliott Hughes xmlWriter.newline() 199*e1fe3e4aSElliott Hughes xmlWriter.simpletag("CurrentIndex", value=self.CurrentIndex) 200*e1fe3e4aSElliott Hughes xmlWriter.newline() 201*e1fe3e4aSElliott Hughes xmlWriter.endtag(name) 202*e1fe3e4aSElliott Hughes xmlWriter.newline() 203*e1fe3e4aSElliott Hughes 204*e1fe3e4aSElliott Hughes def fromXML(self, name, attrs, content, font): 205*e1fe3e4aSElliott Hughes self.NewState = self.ReservedFlags = 0 206*e1fe3e4aSElliott Hughes self.SetMark = self.DontAdvance = False 207*e1fe3e4aSElliott Hughes self.MarkIndex, self.CurrentIndex = 0xFFFF, 0xFFFF 208*e1fe3e4aSElliott Hughes content = [t for t in content if isinstance(t, tuple)] 209*e1fe3e4aSElliott Hughes for eltName, eltAttrs, eltContent in content: 210*e1fe3e4aSElliott Hughes if eltName == "NewState": 211*e1fe3e4aSElliott Hughes self.NewState = safeEval(eltAttrs["value"]) 212*e1fe3e4aSElliott Hughes elif eltName == "Flags": 213*e1fe3e4aSElliott Hughes for flag in eltAttrs["value"].split(","): 214*e1fe3e4aSElliott Hughes self._setFlag(flag.strip()) 215*e1fe3e4aSElliott Hughes elif eltName == "ReservedFlags": 216*e1fe3e4aSElliott Hughes self.ReservedFlags = safeEval(eltAttrs["value"]) 217*e1fe3e4aSElliott Hughes elif eltName == "MarkIndex": 218*e1fe3e4aSElliott Hughes self.MarkIndex = safeEval(eltAttrs["value"]) 219*e1fe3e4aSElliott Hughes elif eltName == "CurrentIndex": 220*e1fe3e4aSElliott Hughes self.CurrentIndex = safeEval(eltAttrs["value"]) 221*e1fe3e4aSElliott Hughes 222*e1fe3e4aSElliott Hughes 223*e1fe3e4aSElliott Hughesclass LigAction(object): 224*e1fe3e4aSElliott Hughes def __init__(self): 225*e1fe3e4aSElliott Hughes self.Store = False 226*e1fe3e4aSElliott Hughes # GlyphIndexDelta is a (possibly negative) delta that gets 227*e1fe3e4aSElliott Hughes # added to the glyph ID at the top of the AAT runtime 228*e1fe3e4aSElliott Hughes # execution stack. It is *not* a byte offset into the 229*e1fe3e4aSElliott Hughes # morx table. The result of the addition, which is performed 230*e1fe3e4aSElliott Hughes # at run time by the shaping engine, is an index into 231*e1fe3e4aSElliott Hughes # the ligature components table. See 'morx' specification. 232*e1fe3e4aSElliott Hughes # In the AAT specification, this field is called Offset; 233*e1fe3e4aSElliott Hughes # but its meaning is quite different from other offsets 234*e1fe3e4aSElliott Hughes # in either AAT or OpenType, so we use a different name. 235*e1fe3e4aSElliott Hughes self.GlyphIndexDelta = 0 236*e1fe3e4aSElliott Hughes 237*e1fe3e4aSElliott Hughes 238*e1fe3e4aSElliott Hughesclass LigatureMorphAction(AATAction): 239*e1fe3e4aSElliott Hughes staticSize = 6 240*e1fe3e4aSElliott Hughes 241*e1fe3e4aSElliott Hughes # 4 bytes for each of {action,ligComponents,ligatures}Offset 242*e1fe3e4aSElliott Hughes actionHeaderSize = 12 243*e1fe3e4aSElliott Hughes 244*e1fe3e4aSElliott Hughes _FLAGS = ["SetComponent", "DontAdvance"] 245*e1fe3e4aSElliott Hughes 246*e1fe3e4aSElliott Hughes def __init__(self): 247*e1fe3e4aSElliott Hughes self.NewState = 0 248*e1fe3e4aSElliott Hughes self.SetComponent, self.DontAdvance = False, False 249*e1fe3e4aSElliott Hughes self.ReservedFlags = 0 250*e1fe3e4aSElliott Hughes self.Actions = [] 251*e1fe3e4aSElliott Hughes 252*e1fe3e4aSElliott Hughes def compile(self, writer, font, actionIndex): 253*e1fe3e4aSElliott Hughes assert actionIndex is not None 254*e1fe3e4aSElliott Hughes writer.writeUShort(self.NewState) 255*e1fe3e4aSElliott Hughes flags = self.ReservedFlags 256*e1fe3e4aSElliott Hughes if self.SetComponent: 257*e1fe3e4aSElliott Hughes flags |= 0x8000 258*e1fe3e4aSElliott Hughes if self.DontAdvance: 259*e1fe3e4aSElliott Hughes flags |= 0x4000 260*e1fe3e4aSElliott Hughes if len(self.Actions) > 0: 261*e1fe3e4aSElliott Hughes flags |= 0x2000 262*e1fe3e4aSElliott Hughes writer.writeUShort(flags) 263*e1fe3e4aSElliott Hughes if len(self.Actions) > 0: 264*e1fe3e4aSElliott Hughes actions = self.compileLigActions() 265*e1fe3e4aSElliott Hughes writer.writeUShort(actionIndex[actions]) 266*e1fe3e4aSElliott Hughes else: 267*e1fe3e4aSElliott Hughes writer.writeUShort(0) 268*e1fe3e4aSElliott Hughes 269*e1fe3e4aSElliott Hughes def decompile(self, reader, font, actionReader): 270*e1fe3e4aSElliott Hughes assert actionReader is not None 271*e1fe3e4aSElliott Hughes self.NewState = reader.readUShort() 272*e1fe3e4aSElliott Hughes flags = reader.readUShort() 273*e1fe3e4aSElliott Hughes self.SetComponent = bool(flags & 0x8000) 274*e1fe3e4aSElliott Hughes self.DontAdvance = bool(flags & 0x4000) 275*e1fe3e4aSElliott Hughes performAction = bool(flags & 0x2000) 276*e1fe3e4aSElliott Hughes # As of 2017-09-12, the 'morx' specification says that 277*e1fe3e4aSElliott Hughes # the reserved bitmask in ligature subtables is 0x3FFF. 278*e1fe3e4aSElliott Hughes # However, the specification also defines a flag 0x2000, 279*e1fe3e4aSElliott Hughes # so the reserved value should actually be 0x1FFF. 280*e1fe3e4aSElliott Hughes # TODO: Report this specification bug to Apple. 281*e1fe3e4aSElliott Hughes self.ReservedFlags = flags & 0x1FFF 282*e1fe3e4aSElliott Hughes actionIndex = reader.readUShort() 283*e1fe3e4aSElliott Hughes if performAction: 284*e1fe3e4aSElliott Hughes self.Actions = self._decompileLigActions(actionReader, actionIndex) 285*e1fe3e4aSElliott Hughes else: 286*e1fe3e4aSElliott Hughes self.Actions = [] 287*e1fe3e4aSElliott Hughes 288*e1fe3e4aSElliott Hughes @staticmethod 289*e1fe3e4aSElliott Hughes def compileActions(font, states): 290*e1fe3e4aSElliott Hughes result, actions, actionIndex = b"", set(), {} 291*e1fe3e4aSElliott Hughes for state in states: 292*e1fe3e4aSElliott Hughes for _glyphClass, trans in state.Transitions.items(): 293*e1fe3e4aSElliott Hughes actions.add(trans.compileLigActions()) 294*e1fe3e4aSElliott Hughes # Sort the compiled actions in decreasing order of 295*e1fe3e4aSElliott Hughes # length, so that the longer sequence come before the 296*e1fe3e4aSElliott Hughes # shorter ones. For each compiled action ABCD, its 297*e1fe3e4aSElliott Hughes # suffixes BCD, CD, and D do not be encoded separately 298*e1fe3e4aSElliott Hughes # (in case they occur); instead, we can just store an 299*e1fe3e4aSElliott Hughes # index that points into the middle of the longer 300*e1fe3e4aSElliott Hughes # sequence. Every compiled AAT ligature sequence is 301*e1fe3e4aSElliott Hughes # terminated with an end-of-sequence flag, which can 302*e1fe3e4aSElliott Hughes # only be set on the last element of the sequence. 303*e1fe3e4aSElliott Hughes # Therefore, it is sufficient to consider just the 304*e1fe3e4aSElliott Hughes # suffixes. 305*e1fe3e4aSElliott Hughes for a in sorted(actions, key=lambda x: (-len(x), x)): 306*e1fe3e4aSElliott Hughes if a not in actionIndex: 307*e1fe3e4aSElliott Hughes for i in range(0, len(a), 4): 308*e1fe3e4aSElliott Hughes suffix = a[i:] 309*e1fe3e4aSElliott Hughes suffixIndex = (len(result) + i) // 4 310*e1fe3e4aSElliott Hughes actionIndex.setdefault(suffix, suffixIndex) 311*e1fe3e4aSElliott Hughes result += a 312*e1fe3e4aSElliott Hughes result = pad(result, 4) 313*e1fe3e4aSElliott Hughes return (result, actionIndex) 314*e1fe3e4aSElliott Hughes 315*e1fe3e4aSElliott Hughes def compileLigActions(self): 316*e1fe3e4aSElliott Hughes result = [] 317*e1fe3e4aSElliott Hughes for i, action in enumerate(self.Actions): 318*e1fe3e4aSElliott Hughes last = i == len(self.Actions) - 1 319*e1fe3e4aSElliott Hughes value = action.GlyphIndexDelta & 0x3FFFFFFF 320*e1fe3e4aSElliott Hughes value |= 0x80000000 if last else 0 321*e1fe3e4aSElliott Hughes value |= 0x40000000 if action.Store else 0 322*e1fe3e4aSElliott Hughes result.append(struct.pack(">L", value)) 323*e1fe3e4aSElliott Hughes return bytesjoin(result) 324*e1fe3e4aSElliott Hughes 325*e1fe3e4aSElliott Hughes def _decompileLigActions(self, actionReader, actionIndex): 326*e1fe3e4aSElliott Hughes actions = [] 327*e1fe3e4aSElliott Hughes last = False 328*e1fe3e4aSElliott Hughes reader = actionReader.getSubReader(actionReader.pos + actionIndex * 4) 329*e1fe3e4aSElliott Hughes while not last: 330*e1fe3e4aSElliott Hughes value = reader.readULong() 331*e1fe3e4aSElliott Hughes last = bool(value & 0x80000000) 332*e1fe3e4aSElliott Hughes action = LigAction() 333*e1fe3e4aSElliott Hughes actions.append(action) 334*e1fe3e4aSElliott Hughes action.Store = bool(value & 0x40000000) 335*e1fe3e4aSElliott Hughes delta = value & 0x3FFFFFFF 336*e1fe3e4aSElliott Hughes if delta >= 0x20000000: # sign-extend 30-bit value 337*e1fe3e4aSElliott Hughes delta = -0x40000000 + delta 338*e1fe3e4aSElliott Hughes action.GlyphIndexDelta = delta 339*e1fe3e4aSElliott Hughes return actions 340*e1fe3e4aSElliott Hughes 341*e1fe3e4aSElliott Hughes def fromXML(self, name, attrs, content, font): 342*e1fe3e4aSElliott Hughes self.NewState = self.ReservedFlags = 0 343*e1fe3e4aSElliott Hughes self.SetComponent = self.DontAdvance = False 344*e1fe3e4aSElliott Hughes self.ReservedFlags = 0 345*e1fe3e4aSElliott Hughes self.Actions = [] 346*e1fe3e4aSElliott Hughes content = [t for t in content if isinstance(t, tuple)] 347*e1fe3e4aSElliott Hughes for eltName, eltAttrs, eltContent in content: 348*e1fe3e4aSElliott Hughes if eltName == "NewState": 349*e1fe3e4aSElliott Hughes self.NewState = safeEval(eltAttrs["value"]) 350*e1fe3e4aSElliott Hughes elif eltName == "Flags": 351*e1fe3e4aSElliott Hughes for flag in eltAttrs["value"].split(","): 352*e1fe3e4aSElliott Hughes self._setFlag(flag.strip()) 353*e1fe3e4aSElliott Hughes elif eltName == "ReservedFlags": 354*e1fe3e4aSElliott Hughes self.ReservedFlags = safeEval(eltAttrs["value"]) 355*e1fe3e4aSElliott Hughes elif eltName == "Action": 356*e1fe3e4aSElliott Hughes action = LigAction() 357*e1fe3e4aSElliott Hughes flags = eltAttrs.get("Flags", "").split(",") 358*e1fe3e4aSElliott Hughes flags = [f.strip() for f in flags] 359*e1fe3e4aSElliott Hughes action.Store = "Store" in flags 360*e1fe3e4aSElliott Hughes action.GlyphIndexDelta = safeEval(eltAttrs["GlyphIndexDelta"]) 361*e1fe3e4aSElliott Hughes self.Actions.append(action) 362*e1fe3e4aSElliott Hughes 363*e1fe3e4aSElliott Hughes def toXML(self, xmlWriter, font, attrs, name): 364*e1fe3e4aSElliott Hughes xmlWriter.begintag(name, **attrs) 365*e1fe3e4aSElliott Hughes xmlWriter.newline() 366*e1fe3e4aSElliott Hughes xmlWriter.simpletag("NewState", value=self.NewState) 367*e1fe3e4aSElliott Hughes xmlWriter.newline() 368*e1fe3e4aSElliott Hughes self._writeFlagsToXML(xmlWriter) 369*e1fe3e4aSElliott Hughes for action in self.Actions: 370*e1fe3e4aSElliott Hughes attribs = [("GlyphIndexDelta", action.GlyphIndexDelta)] 371*e1fe3e4aSElliott Hughes if action.Store: 372*e1fe3e4aSElliott Hughes attribs.append(("Flags", "Store")) 373*e1fe3e4aSElliott Hughes xmlWriter.simpletag("Action", attribs) 374*e1fe3e4aSElliott Hughes xmlWriter.newline() 375*e1fe3e4aSElliott Hughes xmlWriter.endtag(name) 376*e1fe3e4aSElliott Hughes xmlWriter.newline() 377*e1fe3e4aSElliott Hughes 378*e1fe3e4aSElliott Hughes 379*e1fe3e4aSElliott Hughesclass InsertionMorphAction(AATAction): 380*e1fe3e4aSElliott Hughes staticSize = 8 381*e1fe3e4aSElliott Hughes actionHeaderSize = 4 # 4 bytes for actionOffset 382*e1fe3e4aSElliott Hughes _FLAGS = [ 383*e1fe3e4aSElliott Hughes "SetMark", 384*e1fe3e4aSElliott Hughes "DontAdvance", 385*e1fe3e4aSElliott Hughes "CurrentIsKashidaLike", 386*e1fe3e4aSElliott Hughes "MarkedIsKashidaLike", 387*e1fe3e4aSElliott Hughes "CurrentInsertBefore", 388*e1fe3e4aSElliott Hughes "MarkedInsertBefore", 389*e1fe3e4aSElliott Hughes ] 390*e1fe3e4aSElliott Hughes 391*e1fe3e4aSElliott Hughes def __init__(self): 392*e1fe3e4aSElliott Hughes self.NewState = 0 393*e1fe3e4aSElliott Hughes for flag in self._FLAGS: 394*e1fe3e4aSElliott Hughes setattr(self, flag, False) 395*e1fe3e4aSElliott Hughes self.ReservedFlags = 0 396*e1fe3e4aSElliott Hughes self.CurrentInsertionAction, self.MarkedInsertionAction = [], [] 397*e1fe3e4aSElliott Hughes 398*e1fe3e4aSElliott Hughes def compile(self, writer, font, actionIndex): 399*e1fe3e4aSElliott Hughes assert actionIndex is not None 400*e1fe3e4aSElliott Hughes writer.writeUShort(self.NewState) 401*e1fe3e4aSElliott Hughes flags = self.ReservedFlags 402*e1fe3e4aSElliott Hughes if self.SetMark: 403*e1fe3e4aSElliott Hughes flags |= 0x8000 404*e1fe3e4aSElliott Hughes if self.DontAdvance: 405*e1fe3e4aSElliott Hughes flags |= 0x4000 406*e1fe3e4aSElliott Hughes if self.CurrentIsKashidaLike: 407*e1fe3e4aSElliott Hughes flags |= 0x2000 408*e1fe3e4aSElliott Hughes if self.MarkedIsKashidaLike: 409*e1fe3e4aSElliott Hughes flags |= 0x1000 410*e1fe3e4aSElliott Hughes if self.CurrentInsertBefore: 411*e1fe3e4aSElliott Hughes flags |= 0x0800 412*e1fe3e4aSElliott Hughes if self.MarkedInsertBefore: 413*e1fe3e4aSElliott Hughes flags |= 0x0400 414*e1fe3e4aSElliott Hughes flags |= len(self.CurrentInsertionAction) << 5 415*e1fe3e4aSElliott Hughes flags |= len(self.MarkedInsertionAction) 416*e1fe3e4aSElliott Hughes writer.writeUShort(flags) 417*e1fe3e4aSElliott Hughes if len(self.CurrentInsertionAction) > 0: 418*e1fe3e4aSElliott Hughes currentIndex = actionIndex[tuple(self.CurrentInsertionAction)] 419*e1fe3e4aSElliott Hughes else: 420*e1fe3e4aSElliott Hughes currentIndex = 0xFFFF 421*e1fe3e4aSElliott Hughes writer.writeUShort(currentIndex) 422*e1fe3e4aSElliott Hughes if len(self.MarkedInsertionAction) > 0: 423*e1fe3e4aSElliott Hughes markedIndex = actionIndex[tuple(self.MarkedInsertionAction)] 424*e1fe3e4aSElliott Hughes else: 425*e1fe3e4aSElliott Hughes markedIndex = 0xFFFF 426*e1fe3e4aSElliott Hughes writer.writeUShort(markedIndex) 427*e1fe3e4aSElliott Hughes 428*e1fe3e4aSElliott Hughes def decompile(self, reader, font, actionReader): 429*e1fe3e4aSElliott Hughes assert actionReader is not None 430*e1fe3e4aSElliott Hughes self.NewState = reader.readUShort() 431*e1fe3e4aSElliott Hughes flags = reader.readUShort() 432*e1fe3e4aSElliott Hughes self.SetMark = bool(flags & 0x8000) 433*e1fe3e4aSElliott Hughes self.DontAdvance = bool(flags & 0x4000) 434*e1fe3e4aSElliott Hughes self.CurrentIsKashidaLike = bool(flags & 0x2000) 435*e1fe3e4aSElliott Hughes self.MarkedIsKashidaLike = bool(flags & 0x1000) 436*e1fe3e4aSElliott Hughes self.CurrentInsertBefore = bool(flags & 0x0800) 437*e1fe3e4aSElliott Hughes self.MarkedInsertBefore = bool(flags & 0x0400) 438*e1fe3e4aSElliott Hughes self.CurrentInsertionAction = self._decompileInsertionAction( 439*e1fe3e4aSElliott Hughes actionReader, font, index=reader.readUShort(), count=((flags & 0x03E0) >> 5) 440*e1fe3e4aSElliott Hughes ) 441*e1fe3e4aSElliott Hughes self.MarkedInsertionAction = self._decompileInsertionAction( 442*e1fe3e4aSElliott Hughes actionReader, font, index=reader.readUShort(), count=(flags & 0x001F) 443*e1fe3e4aSElliott Hughes ) 444*e1fe3e4aSElliott Hughes 445*e1fe3e4aSElliott Hughes def _decompileInsertionAction(self, actionReader, font, index, count): 446*e1fe3e4aSElliott Hughes if index == 0xFFFF or count == 0: 447*e1fe3e4aSElliott Hughes return [] 448*e1fe3e4aSElliott Hughes reader = actionReader.getSubReader(actionReader.pos + index * 2) 449*e1fe3e4aSElliott Hughes return font.getGlyphNameMany(reader.readUShortArray(count)) 450*e1fe3e4aSElliott Hughes 451*e1fe3e4aSElliott Hughes def toXML(self, xmlWriter, font, attrs, name): 452*e1fe3e4aSElliott Hughes xmlWriter.begintag(name, **attrs) 453*e1fe3e4aSElliott Hughes xmlWriter.newline() 454*e1fe3e4aSElliott Hughes xmlWriter.simpletag("NewState", value=self.NewState) 455*e1fe3e4aSElliott Hughes xmlWriter.newline() 456*e1fe3e4aSElliott Hughes self._writeFlagsToXML(xmlWriter) 457*e1fe3e4aSElliott Hughes for g in self.CurrentInsertionAction: 458*e1fe3e4aSElliott Hughes xmlWriter.simpletag("CurrentInsertionAction", glyph=g) 459*e1fe3e4aSElliott Hughes xmlWriter.newline() 460*e1fe3e4aSElliott Hughes for g in self.MarkedInsertionAction: 461*e1fe3e4aSElliott Hughes xmlWriter.simpletag("MarkedInsertionAction", glyph=g) 462*e1fe3e4aSElliott Hughes xmlWriter.newline() 463*e1fe3e4aSElliott Hughes xmlWriter.endtag(name) 464*e1fe3e4aSElliott Hughes xmlWriter.newline() 465*e1fe3e4aSElliott Hughes 466*e1fe3e4aSElliott Hughes def fromXML(self, name, attrs, content, font): 467*e1fe3e4aSElliott Hughes self.__init__() 468*e1fe3e4aSElliott Hughes content = [t for t in content if isinstance(t, tuple)] 469*e1fe3e4aSElliott Hughes for eltName, eltAttrs, eltContent in content: 470*e1fe3e4aSElliott Hughes if eltName == "NewState": 471*e1fe3e4aSElliott Hughes self.NewState = safeEval(eltAttrs["value"]) 472*e1fe3e4aSElliott Hughes elif eltName == "Flags": 473*e1fe3e4aSElliott Hughes for flag in eltAttrs["value"].split(","): 474*e1fe3e4aSElliott Hughes self._setFlag(flag.strip()) 475*e1fe3e4aSElliott Hughes elif eltName == "CurrentInsertionAction": 476*e1fe3e4aSElliott Hughes self.CurrentInsertionAction.append(eltAttrs["glyph"]) 477*e1fe3e4aSElliott Hughes elif eltName == "MarkedInsertionAction": 478*e1fe3e4aSElliott Hughes self.MarkedInsertionAction.append(eltAttrs["glyph"]) 479*e1fe3e4aSElliott Hughes else: 480*e1fe3e4aSElliott Hughes assert False, eltName 481*e1fe3e4aSElliott Hughes 482*e1fe3e4aSElliott Hughes @staticmethod 483*e1fe3e4aSElliott Hughes def compileActions(font, states): 484*e1fe3e4aSElliott Hughes actions, actionIndex, result = set(), {}, b"" 485*e1fe3e4aSElliott Hughes for state in states: 486*e1fe3e4aSElliott Hughes for _glyphClass, trans in state.Transitions.items(): 487*e1fe3e4aSElliott Hughes if trans.CurrentInsertionAction is not None: 488*e1fe3e4aSElliott Hughes actions.add(tuple(trans.CurrentInsertionAction)) 489*e1fe3e4aSElliott Hughes if trans.MarkedInsertionAction is not None: 490*e1fe3e4aSElliott Hughes actions.add(tuple(trans.MarkedInsertionAction)) 491*e1fe3e4aSElliott Hughes # Sort the compiled actions in decreasing order of 492*e1fe3e4aSElliott Hughes # length, so that the longer sequence come before the 493*e1fe3e4aSElliott Hughes # shorter ones. 494*e1fe3e4aSElliott Hughes for action in sorted(actions, key=lambda x: (-len(x), x)): 495*e1fe3e4aSElliott Hughes # We insert all sub-sequences of the action glyph sequence 496*e1fe3e4aSElliott Hughes # into actionIndex. For example, if one action triggers on 497*e1fe3e4aSElliott Hughes # glyph sequence [A, B, C, D, E] and another action triggers 498*e1fe3e4aSElliott Hughes # on [C, D], we return result=[A, B, C, D, E] (as list of 499*e1fe3e4aSElliott Hughes # encoded glyph IDs), and actionIndex={('A','B','C','D','E'): 0, 500*e1fe3e4aSElliott Hughes # ('C','D'): 2}. 501*e1fe3e4aSElliott Hughes if action in actionIndex: 502*e1fe3e4aSElliott Hughes continue 503*e1fe3e4aSElliott Hughes for start in range(0, len(action)): 504*e1fe3e4aSElliott Hughes startIndex = (len(result) // 2) + start 505*e1fe3e4aSElliott Hughes for limit in range(start, len(action)): 506*e1fe3e4aSElliott Hughes glyphs = action[start : limit + 1] 507*e1fe3e4aSElliott Hughes actionIndex.setdefault(glyphs, startIndex) 508*e1fe3e4aSElliott Hughes for glyph in action: 509*e1fe3e4aSElliott Hughes glyphID = font.getGlyphID(glyph) 510*e1fe3e4aSElliott Hughes result += struct.pack(">H", glyphID) 511*e1fe3e4aSElliott Hughes return result, actionIndex 512*e1fe3e4aSElliott Hughes 513*e1fe3e4aSElliott Hughes 514*e1fe3e4aSElliott Hughesclass FeatureParams(BaseTable): 515*e1fe3e4aSElliott Hughes def compile(self, writer, font): 516*e1fe3e4aSElliott Hughes assert ( 517*e1fe3e4aSElliott Hughes featureParamTypes.get(writer["FeatureTag"]) == self.__class__ 518*e1fe3e4aSElliott Hughes ), "Wrong FeatureParams type for feature '%s': %s" % ( 519*e1fe3e4aSElliott Hughes writer["FeatureTag"], 520*e1fe3e4aSElliott Hughes self.__class__.__name__, 521*e1fe3e4aSElliott Hughes ) 522*e1fe3e4aSElliott Hughes BaseTable.compile(self, writer, font) 523*e1fe3e4aSElliott Hughes 524*e1fe3e4aSElliott Hughes def toXML(self, xmlWriter, font, attrs=None, name=None): 525*e1fe3e4aSElliott Hughes BaseTable.toXML(self, xmlWriter, font, attrs, name=self.__class__.__name__) 526*e1fe3e4aSElliott Hughes 527*e1fe3e4aSElliott Hughes 528*e1fe3e4aSElliott Hughesclass FeatureParamsSize(FeatureParams): 529*e1fe3e4aSElliott Hughes pass 530*e1fe3e4aSElliott Hughes 531*e1fe3e4aSElliott Hughes 532*e1fe3e4aSElliott Hughesclass FeatureParamsStylisticSet(FeatureParams): 533*e1fe3e4aSElliott Hughes pass 534*e1fe3e4aSElliott Hughes 535*e1fe3e4aSElliott Hughes 536*e1fe3e4aSElliott Hughesclass FeatureParamsCharacterVariants(FeatureParams): 537*e1fe3e4aSElliott Hughes pass 538*e1fe3e4aSElliott Hughes 539*e1fe3e4aSElliott Hughes 540*e1fe3e4aSElliott Hughesclass Coverage(FormatSwitchingBaseTable): 541*e1fe3e4aSElliott Hughes # manual implementation to get rid of glyphID dependencies 542*e1fe3e4aSElliott Hughes 543*e1fe3e4aSElliott Hughes def populateDefaults(self, propagator=None): 544*e1fe3e4aSElliott Hughes if not hasattr(self, "glyphs"): 545*e1fe3e4aSElliott Hughes self.glyphs = [] 546*e1fe3e4aSElliott Hughes 547*e1fe3e4aSElliott Hughes def postRead(self, rawTable, font): 548*e1fe3e4aSElliott Hughes if self.Format == 1: 549*e1fe3e4aSElliott Hughes self.glyphs = rawTable["GlyphArray"] 550*e1fe3e4aSElliott Hughes elif self.Format == 2: 551*e1fe3e4aSElliott Hughes glyphs = self.glyphs = [] 552*e1fe3e4aSElliott Hughes ranges = rawTable["RangeRecord"] 553*e1fe3e4aSElliott Hughes # Some SIL fonts have coverage entries that don't have sorted 554*e1fe3e4aSElliott Hughes # StartCoverageIndex. If it is so, fixup and warn. We undo 555*e1fe3e4aSElliott Hughes # this when writing font out. 556*e1fe3e4aSElliott Hughes sorted_ranges = sorted(ranges, key=lambda a: a.StartCoverageIndex) 557*e1fe3e4aSElliott Hughes if ranges != sorted_ranges: 558*e1fe3e4aSElliott Hughes log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.") 559*e1fe3e4aSElliott Hughes ranges = sorted_ranges 560*e1fe3e4aSElliott Hughes del sorted_ranges 561*e1fe3e4aSElliott Hughes for r in ranges: 562*e1fe3e4aSElliott Hughes start = r.Start 563*e1fe3e4aSElliott Hughes end = r.End 564*e1fe3e4aSElliott Hughes startID = font.getGlyphID(start) 565*e1fe3e4aSElliott Hughes endID = font.getGlyphID(end) + 1 566*e1fe3e4aSElliott Hughes glyphs.extend(font.getGlyphNameMany(range(startID, endID))) 567*e1fe3e4aSElliott Hughes else: 568*e1fe3e4aSElliott Hughes self.glyphs = [] 569*e1fe3e4aSElliott Hughes log.warning("Unknown Coverage format: %s", self.Format) 570*e1fe3e4aSElliott Hughes del self.Format # Don't need this anymore 571*e1fe3e4aSElliott Hughes 572*e1fe3e4aSElliott Hughes def preWrite(self, font): 573*e1fe3e4aSElliott Hughes glyphs = getattr(self, "glyphs", None) 574*e1fe3e4aSElliott Hughes if glyphs is None: 575*e1fe3e4aSElliott Hughes glyphs = self.glyphs = [] 576*e1fe3e4aSElliott Hughes format = 1 577*e1fe3e4aSElliott Hughes rawTable = {"GlyphArray": glyphs} 578*e1fe3e4aSElliott Hughes if glyphs: 579*e1fe3e4aSElliott Hughes # find out whether Format 2 is more compact or not 580*e1fe3e4aSElliott Hughes glyphIDs = font.getGlyphIDMany(glyphs) 581*e1fe3e4aSElliott Hughes brokenOrder = sorted(glyphIDs) != glyphIDs 582*e1fe3e4aSElliott Hughes 583*e1fe3e4aSElliott Hughes last = glyphIDs[0] 584*e1fe3e4aSElliott Hughes ranges = [[last]] 585*e1fe3e4aSElliott Hughes for glyphID in glyphIDs[1:]: 586*e1fe3e4aSElliott Hughes if glyphID != last + 1: 587*e1fe3e4aSElliott Hughes ranges[-1].append(last) 588*e1fe3e4aSElliott Hughes ranges.append([glyphID]) 589*e1fe3e4aSElliott Hughes last = glyphID 590*e1fe3e4aSElliott Hughes ranges[-1].append(last) 591*e1fe3e4aSElliott Hughes 592*e1fe3e4aSElliott Hughes if brokenOrder or len(ranges) * 3 < len(glyphs): # 3 words vs. 1 word 593*e1fe3e4aSElliott Hughes # Format 2 is more compact 594*e1fe3e4aSElliott Hughes index = 0 595*e1fe3e4aSElliott Hughes for i in range(len(ranges)): 596*e1fe3e4aSElliott Hughes start, end = ranges[i] 597*e1fe3e4aSElliott Hughes r = RangeRecord() 598*e1fe3e4aSElliott Hughes r.StartID = start 599*e1fe3e4aSElliott Hughes r.Start = font.getGlyphName(start) 600*e1fe3e4aSElliott Hughes r.End = font.getGlyphName(end) 601*e1fe3e4aSElliott Hughes r.StartCoverageIndex = index 602*e1fe3e4aSElliott Hughes ranges[i] = r 603*e1fe3e4aSElliott Hughes index = index + end - start + 1 604*e1fe3e4aSElliott Hughes if brokenOrder: 605*e1fe3e4aSElliott Hughes log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.") 606*e1fe3e4aSElliott Hughes ranges.sort(key=lambda a: a.StartID) 607*e1fe3e4aSElliott Hughes for r in ranges: 608*e1fe3e4aSElliott Hughes del r.StartID 609*e1fe3e4aSElliott Hughes format = 2 610*e1fe3e4aSElliott Hughes rawTable = {"RangeRecord": ranges} 611*e1fe3e4aSElliott Hughes # else: 612*e1fe3e4aSElliott Hughes # fallthrough; Format 1 is more compact 613*e1fe3e4aSElliott Hughes self.Format = format 614*e1fe3e4aSElliott Hughes return rawTable 615*e1fe3e4aSElliott Hughes 616*e1fe3e4aSElliott Hughes def toXML2(self, xmlWriter, font): 617*e1fe3e4aSElliott Hughes for glyphName in getattr(self, "glyphs", []): 618*e1fe3e4aSElliott Hughes xmlWriter.simpletag("Glyph", value=glyphName) 619*e1fe3e4aSElliott Hughes xmlWriter.newline() 620*e1fe3e4aSElliott Hughes 621*e1fe3e4aSElliott Hughes def fromXML(self, name, attrs, content, font): 622*e1fe3e4aSElliott Hughes glyphs = getattr(self, "glyphs", None) 623*e1fe3e4aSElliott Hughes if glyphs is None: 624*e1fe3e4aSElliott Hughes glyphs = [] 625*e1fe3e4aSElliott Hughes self.glyphs = glyphs 626*e1fe3e4aSElliott Hughes glyphs.append(attrs["value"]) 627*e1fe3e4aSElliott Hughes 628*e1fe3e4aSElliott Hughes 629*e1fe3e4aSElliott Hughes# The special 0xFFFFFFFF delta-set index is used to indicate that there 630*e1fe3e4aSElliott Hughes# is no variation data in the ItemVariationStore for a given variable field 631*e1fe3e4aSElliott HughesNO_VARIATION_INDEX = 0xFFFFFFFF 632*e1fe3e4aSElliott Hughes 633*e1fe3e4aSElliott Hughes 634*e1fe3e4aSElliott Hughesclass DeltaSetIndexMap(getFormatSwitchingBaseTableClass("uint8")): 635*e1fe3e4aSElliott Hughes def populateDefaults(self, propagator=None): 636*e1fe3e4aSElliott Hughes if not hasattr(self, "mapping"): 637*e1fe3e4aSElliott Hughes self.mapping = [] 638*e1fe3e4aSElliott Hughes 639*e1fe3e4aSElliott Hughes def postRead(self, rawTable, font): 640*e1fe3e4aSElliott Hughes assert (rawTable["EntryFormat"] & 0xFFC0) == 0 641*e1fe3e4aSElliott Hughes self.mapping = rawTable["mapping"] 642*e1fe3e4aSElliott Hughes 643*e1fe3e4aSElliott Hughes @staticmethod 644*e1fe3e4aSElliott Hughes def getEntryFormat(mapping): 645*e1fe3e4aSElliott Hughes ored = 0 646*e1fe3e4aSElliott Hughes for idx in mapping: 647*e1fe3e4aSElliott Hughes ored |= idx 648*e1fe3e4aSElliott Hughes 649*e1fe3e4aSElliott Hughes inner = ored & 0xFFFF 650*e1fe3e4aSElliott Hughes innerBits = 0 651*e1fe3e4aSElliott Hughes while inner: 652*e1fe3e4aSElliott Hughes innerBits += 1 653*e1fe3e4aSElliott Hughes inner >>= 1 654*e1fe3e4aSElliott Hughes innerBits = max(innerBits, 1) 655*e1fe3e4aSElliott Hughes assert innerBits <= 16 656*e1fe3e4aSElliott Hughes 657*e1fe3e4aSElliott Hughes ored = (ored >> (16 - innerBits)) | (ored & ((1 << innerBits) - 1)) 658*e1fe3e4aSElliott Hughes if ored <= 0x000000FF: 659*e1fe3e4aSElliott Hughes entrySize = 1 660*e1fe3e4aSElliott Hughes elif ored <= 0x0000FFFF: 661*e1fe3e4aSElliott Hughes entrySize = 2 662*e1fe3e4aSElliott Hughes elif ored <= 0x00FFFFFF: 663*e1fe3e4aSElliott Hughes entrySize = 3 664*e1fe3e4aSElliott Hughes else: 665*e1fe3e4aSElliott Hughes entrySize = 4 666*e1fe3e4aSElliott Hughes 667*e1fe3e4aSElliott Hughes return ((entrySize - 1) << 4) | (innerBits - 1) 668*e1fe3e4aSElliott Hughes 669*e1fe3e4aSElliott Hughes def preWrite(self, font): 670*e1fe3e4aSElliott Hughes mapping = getattr(self, "mapping", None) 671*e1fe3e4aSElliott Hughes if mapping is None: 672*e1fe3e4aSElliott Hughes mapping = self.mapping = [] 673*e1fe3e4aSElliott Hughes self.Format = 1 if len(mapping) > 0xFFFF else 0 674*e1fe3e4aSElliott Hughes rawTable = self.__dict__.copy() 675*e1fe3e4aSElliott Hughes rawTable["MappingCount"] = len(mapping) 676*e1fe3e4aSElliott Hughes rawTable["EntryFormat"] = self.getEntryFormat(mapping) 677*e1fe3e4aSElliott Hughes return rawTable 678*e1fe3e4aSElliott Hughes 679*e1fe3e4aSElliott Hughes def toXML2(self, xmlWriter, font): 680*e1fe3e4aSElliott Hughes # Make xml dump less verbose, by omitting no-op entries like: 681*e1fe3e4aSElliott Hughes # <Map index="..." outer="65535" inner="65535"/> 682*e1fe3e4aSElliott Hughes xmlWriter.comment("Omitted values default to 0xFFFF/0xFFFF (no variations)") 683*e1fe3e4aSElliott Hughes xmlWriter.newline() 684*e1fe3e4aSElliott Hughes for i, value in enumerate(getattr(self, "mapping", [])): 685*e1fe3e4aSElliott Hughes attrs = [("index", i)] 686*e1fe3e4aSElliott Hughes if value != NO_VARIATION_INDEX: 687*e1fe3e4aSElliott Hughes attrs.extend( 688*e1fe3e4aSElliott Hughes [ 689*e1fe3e4aSElliott Hughes ("outer", value >> 16), 690*e1fe3e4aSElliott Hughes ("inner", value & 0xFFFF), 691*e1fe3e4aSElliott Hughes ] 692*e1fe3e4aSElliott Hughes ) 693*e1fe3e4aSElliott Hughes xmlWriter.simpletag("Map", attrs) 694*e1fe3e4aSElliott Hughes xmlWriter.newline() 695*e1fe3e4aSElliott Hughes 696*e1fe3e4aSElliott Hughes def fromXML(self, name, attrs, content, font): 697*e1fe3e4aSElliott Hughes mapping = getattr(self, "mapping", None) 698*e1fe3e4aSElliott Hughes if mapping is None: 699*e1fe3e4aSElliott Hughes self.mapping = mapping = [] 700*e1fe3e4aSElliott Hughes index = safeEval(attrs["index"]) 701*e1fe3e4aSElliott Hughes outer = safeEval(attrs.get("outer", "0xFFFF")) 702*e1fe3e4aSElliott Hughes inner = safeEval(attrs.get("inner", "0xFFFF")) 703*e1fe3e4aSElliott Hughes assert inner <= 0xFFFF 704*e1fe3e4aSElliott Hughes mapping.insert(index, (outer << 16) | inner) 705*e1fe3e4aSElliott Hughes 706*e1fe3e4aSElliott Hughes 707*e1fe3e4aSElliott Hughesclass VarIdxMap(BaseTable): 708*e1fe3e4aSElliott Hughes def populateDefaults(self, propagator=None): 709*e1fe3e4aSElliott Hughes if not hasattr(self, "mapping"): 710*e1fe3e4aSElliott Hughes self.mapping = {} 711*e1fe3e4aSElliott Hughes 712*e1fe3e4aSElliott Hughes def postRead(self, rawTable, font): 713*e1fe3e4aSElliott Hughes assert (rawTable["EntryFormat"] & 0xFFC0) == 0 714*e1fe3e4aSElliott Hughes glyphOrder = font.getGlyphOrder() 715*e1fe3e4aSElliott Hughes mapList = rawTable["mapping"] 716*e1fe3e4aSElliott Hughes mapList.extend([mapList[-1]] * (len(glyphOrder) - len(mapList))) 717*e1fe3e4aSElliott Hughes self.mapping = dict(zip(glyphOrder, mapList)) 718*e1fe3e4aSElliott Hughes 719*e1fe3e4aSElliott Hughes def preWrite(self, font): 720*e1fe3e4aSElliott Hughes mapping = getattr(self, "mapping", None) 721*e1fe3e4aSElliott Hughes if mapping is None: 722*e1fe3e4aSElliott Hughes mapping = self.mapping = {} 723*e1fe3e4aSElliott Hughes 724*e1fe3e4aSElliott Hughes glyphOrder = font.getGlyphOrder() 725*e1fe3e4aSElliott Hughes mapping = [mapping[g] for g in glyphOrder] 726*e1fe3e4aSElliott Hughes while len(mapping) > 1 and mapping[-2] == mapping[-1]: 727*e1fe3e4aSElliott Hughes del mapping[-1] 728*e1fe3e4aSElliott Hughes 729*e1fe3e4aSElliott Hughes rawTable = {"mapping": mapping} 730*e1fe3e4aSElliott Hughes rawTable["MappingCount"] = len(mapping) 731*e1fe3e4aSElliott Hughes rawTable["EntryFormat"] = DeltaSetIndexMap.getEntryFormat(mapping) 732*e1fe3e4aSElliott Hughes return rawTable 733*e1fe3e4aSElliott Hughes 734*e1fe3e4aSElliott Hughes def toXML2(self, xmlWriter, font): 735*e1fe3e4aSElliott Hughes for glyph, value in sorted(getattr(self, "mapping", {}).items()): 736*e1fe3e4aSElliott Hughes attrs = ( 737*e1fe3e4aSElliott Hughes ("glyph", glyph), 738*e1fe3e4aSElliott Hughes ("outer", value >> 16), 739*e1fe3e4aSElliott Hughes ("inner", value & 0xFFFF), 740*e1fe3e4aSElliott Hughes ) 741*e1fe3e4aSElliott Hughes xmlWriter.simpletag("Map", attrs) 742*e1fe3e4aSElliott Hughes xmlWriter.newline() 743*e1fe3e4aSElliott Hughes 744*e1fe3e4aSElliott Hughes def fromXML(self, name, attrs, content, font): 745*e1fe3e4aSElliott Hughes mapping = getattr(self, "mapping", None) 746*e1fe3e4aSElliott Hughes if mapping is None: 747*e1fe3e4aSElliott Hughes mapping = {} 748*e1fe3e4aSElliott Hughes self.mapping = mapping 749*e1fe3e4aSElliott Hughes try: 750*e1fe3e4aSElliott Hughes glyph = attrs["glyph"] 751*e1fe3e4aSElliott Hughes except: # https://github.com/fonttools/fonttools/commit/21cbab8ce9ded3356fef3745122da64dcaf314e9#commitcomment-27649836 752*e1fe3e4aSElliott Hughes glyph = font.getGlyphOrder()[attrs["index"]] 753*e1fe3e4aSElliott Hughes outer = safeEval(attrs["outer"]) 754*e1fe3e4aSElliott Hughes inner = safeEval(attrs["inner"]) 755*e1fe3e4aSElliott Hughes assert inner <= 0xFFFF 756*e1fe3e4aSElliott Hughes mapping[glyph] = (outer << 16) | inner 757*e1fe3e4aSElliott Hughes 758*e1fe3e4aSElliott Hughes 759*e1fe3e4aSElliott Hughesclass VarRegionList(BaseTable): 760*e1fe3e4aSElliott Hughes def preWrite(self, font): 761*e1fe3e4aSElliott Hughes # The OT spec says VarStore.VarRegionList.RegionAxisCount should always 762*e1fe3e4aSElliott Hughes # be equal to the fvar.axisCount, and OTS < v8.0.0 enforces this rule 763*e1fe3e4aSElliott Hughes # even when the VarRegionList is empty. We can't treat RegionAxisCount 764*e1fe3e4aSElliott Hughes # like a normal propagated count (== len(Region[i].VarRegionAxis)), 765*e1fe3e4aSElliott Hughes # otherwise it would default to 0 if VarRegionList is empty. 766*e1fe3e4aSElliott Hughes # Thus, we force it to always be equal to fvar.axisCount. 767*e1fe3e4aSElliott Hughes # https://github.com/khaledhosny/ots/pull/192 768*e1fe3e4aSElliott Hughes fvarTable = font.get("fvar") 769*e1fe3e4aSElliott Hughes if fvarTable: 770*e1fe3e4aSElliott Hughes self.RegionAxisCount = len(fvarTable.axes) 771*e1fe3e4aSElliott Hughes return { 772*e1fe3e4aSElliott Hughes **self.__dict__, 773*e1fe3e4aSElliott Hughes "RegionAxisCount": CountReference(self.__dict__, "RegionAxisCount"), 774*e1fe3e4aSElliott Hughes } 775*e1fe3e4aSElliott Hughes 776*e1fe3e4aSElliott Hughes 777*e1fe3e4aSElliott Hughesclass SingleSubst(FormatSwitchingBaseTable): 778*e1fe3e4aSElliott Hughes def populateDefaults(self, propagator=None): 779*e1fe3e4aSElliott Hughes if not hasattr(self, "mapping"): 780*e1fe3e4aSElliott Hughes self.mapping = {} 781*e1fe3e4aSElliott Hughes 782*e1fe3e4aSElliott Hughes def postRead(self, rawTable, font): 783*e1fe3e4aSElliott Hughes mapping = {} 784*e1fe3e4aSElliott Hughes input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 785*e1fe3e4aSElliott Hughes if self.Format == 1: 786*e1fe3e4aSElliott Hughes delta = rawTable["DeltaGlyphID"] 787*e1fe3e4aSElliott Hughes inputGIDS = font.getGlyphIDMany(input) 788*e1fe3e4aSElliott Hughes outGIDS = [(glyphID + delta) % 65536 for glyphID in inputGIDS] 789*e1fe3e4aSElliott Hughes outNames = font.getGlyphNameMany(outGIDS) 790*e1fe3e4aSElliott Hughes for inp, out in zip(input, outNames): 791*e1fe3e4aSElliott Hughes mapping[inp] = out 792*e1fe3e4aSElliott Hughes elif self.Format == 2: 793*e1fe3e4aSElliott Hughes assert ( 794*e1fe3e4aSElliott Hughes len(input) == rawTable["GlyphCount"] 795*e1fe3e4aSElliott Hughes ), "invalid SingleSubstFormat2 table" 796*e1fe3e4aSElliott Hughes subst = rawTable["Substitute"] 797*e1fe3e4aSElliott Hughes for inp, sub in zip(input, subst): 798*e1fe3e4aSElliott Hughes mapping[inp] = sub 799*e1fe3e4aSElliott Hughes else: 800*e1fe3e4aSElliott Hughes assert 0, "unknown format: %s" % self.Format 801*e1fe3e4aSElliott Hughes self.mapping = mapping 802*e1fe3e4aSElliott Hughes del self.Format # Don't need this anymore 803*e1fe3e4aSElliott Hughes 804*e1fe3e4aSElliott Hughes def preWrite(self, font): 805*e1fe3e4aSElliott Hughes mapping = getattr(self, "mapping", None) 806*e1fe3e4aSElliott Hughes if mapping is None: 807*e1fe3e4aSElliott Hughes mapping = self.mapping = {} 808*e1fe3e4aSElliott Hughes items = list(mapping.items()) 809*e1fe3e4aSElliott Hughes getGlyphID = font.getGlyphID 810*e1fe3e4aSElliott Hughes gidItems = [(getGlyphID(a), getGlyphID(b)) for a, b in items] 811*e1fe3e4aSElliott Hughes sortableItems = sorted(zip(gidItems, items)) 812*e1fe3e4aSElliott Hughes 813*e1fe3e4aSElliott Hughes # figure out format 814*e1fe3e4aSElliott Hughes format = 2 815*e1fe3e4aSElliott Hughes delta = None 816*e1fe3e4aSElliott Hughes for inID, outID in gidItems: 817*e1fe3e4aSElliott Hughes if delta is None: 818*e1fe3e4aSElliott Hughes delta = (outID - inID) % 65536 819*e1fe3e4aSElliott Hughes 820*e1fe3e4aSElliott Hughes if (inID + delta) % 65536 != outID: 821*e1fe3e4aSElliott Hughes break 822*e1fe3e4aSElliott Hughes else: 823*e1fe3e4aSElliott Hughes if delta is None: 824*e1fe3e4aSElliott Hughes # the mapping is empty, better use format 2 825*e1fe3e4aSElliott Hughes format = 2 826*e1fe3e4aSElliott Hughes else: 827*e1fe3e4aSElliott Hughes format = 1 828*e1fe3e4aSElliott Hughes 829*e1fe3e4aSElliott Hughes rawTable = {} 830*e1fe3e4aSElliott Hughes self.Format = format 831*e1fe3e4aSElliott Hughes cov = Coverage() 832*e1fe3e4aSElliott Hughes input = [item[1][0] for item in sortableItems] 833*e1fe3e4aSElliott Hughes subst = [item[1][1] for item in sortableItems] 834*e1fe3e4aSElliott Hughes cov.glyphs = input 835*e1fe3e4aSElliott Hughes rawTable["Coverage"] = cov 836*e1fe3e4aSElliott Hughes if format == 1: 837*e1fe3e4aSElliott Hughes assert delta is not None 838*e1fe3e4aSElliott Hughes rawTable["DeltaGlyphID"] = delta 839*e1fe3e4aSElliott Hughes else: 840*e1fe3e4aSElliott Hughes rawTable["Substitute"] = subst 841*e1fe3e4aSElliott Hughes return rawTable 842*e1fe3e4aSElliott Hughes 843*e1fe3e4aSElliott Hughes def toXML2(self, xmlWriter, font): 844*e1fe3e4aSElliott Hughes items = sorted(self.mapping.items()) 845*e1fe3e4aSElliott Hughes for inGlyph, outGlyph in items: 846*e1fe3e4aSElliott Hughes xmlWriter.simpletag("Substitution", [("in", inGlyph), ("out", outGlyph)]) 847*e1fe3e4aSElliott Hughes xmlWriter.newline() 848*e1fe3e4aSElliott Hughes 849*e1fe3e4aSElliott Hughes def fromXML(self, name, attrs, content, font): 850*e1fe3e4aSElliott Hughes mapping = getattr(self, "mapping", None) 851*e1fe3e4aSElliott Hughes if mapping is None: 852*e1fe3e4aSElliott Hughes mapping = {} 853*e1fe3e4aSElliott Hughes self.mapping = mapping 854*e1fe3e4aSElliott Hughes mapping[attrs["in"]] = attrs["out"] 855*e1fe3e4aSElliott Hughes 856*e1fe3e4aSElliott Hughes 857*e1fe3e4aSElliott Hughesclass MultipleSubst(FormatSwitchingBaseTable): 858*e1fe3e4aSElliott Hughes def populateDefaults(self, propagator=None): 859*e1fe3e4aSElliott Hughes if not hasattr(self, "mapping"): 860*e1fe3e4aSElliott Hughes self.mapping = {} 861*e1fe3e4aSElliott Hughes 862*e1fe3e4aSElliott Hughes def postRead(self, rawTable, font): 863*e1fe3e4aSElliott Hughes mapping = {} 864*e1fe3e4aSElliott Hughes if self.Format == 1: 865*e1fe3e4aSElliott Hughes glyphs = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 866*e1fe3e4aSElliott Hughes subst = [s.Substitute for s in rawTable["Sequence"]] 867*e1fe3e4aSElliott Hughes mapping = dict(zip(glyphs, subst)) 868*e1fe3e4aSElliott Hughes else: 869*e1fe3e4aSElliott Hughes assert 0, "unknown format: %s" % self.Format 870*e1fe3e4aSElliott Hughes self.mapping = mapping 871*e1fe3e4aSElliott Hughes del self.Format # Don't need this anymore 872*e1fe3e4aSElliott Hughes 873*e1fe3e4aSElliott Hughes def preWrite(self, font): 874*e1fe3e4aSElliott Hughes mapping = getattr(self, "mapping", None) 875*e1fe3e4aSElliott Hughes if mapping is None: 876*e1fe3e4aSElliott Hughes mapping = self.mapping = {} 877*e1fe3e4aSElliott Hughes cov = Coverage() 878*e1fe3e4aSElliott Hughes cov.glyphs = sorted(list(mapping.keys()), key=font.getGlyphID) 879*e1fe3e4aSElliott Hughes self.Format = 1 880*e1fe3e4aSElliott Hughes rawTable = { 881*e1fe3e4aSElliott Hughes "Coverage": cov, 882*e1fe3e4aSElliott Hughes "Sequence": [self.makeSequence_(mapping[glyph]) for glyph in cov.glyphs], 883*e1fe3e4aSElliott Hughes } 884*e1fe3e4aSElliott Hughes return rawTable 885*e1fe3e4aSElliott Hughes 886*e1fe3e4aSElliott Hughes def toXML2(self, xmlWriter, font): 887*e1fe3e4aSElliott Hughes items = sorted(self.mapping.items()) 888*e1fe3e4aSElliott Hughes for inGlyph, outGlyphs in items: 889*e1fe3e4aSElliott Hughes out = ",".join(outGlyphs) 890*e1fe3e4aSElliott Hughes xmlWriter.simpletag("Substitution", [("in", inGlyph), ("out", out)]) 891*e1fe3e4aSElliott Hughes xmlWriter.newline() 892*e1fe3e4aSElliott Hughes 893*e1fe3e4aSElliott Hughes def fromXML(self, name, attrs, content, font): 894*e1fe3e4aSElliott Hughes mapping = getattr(self, "mapping", None) 895*e1fe3e4aSElliott Hughes if mapping is None: 896*e1fe3e4aSElliott Hughes mapping = {} 897*e1fe3e4aSElliott Hughes self.mapping = mapping 898*e1fe3e4aSElliott Hughes 899*e1fe3e4aSElliott Hughes # TTX v3.0 and earlier. 900*e1fe3e4aSElliott Hughes if name == "Coverage": 901*e1fe3e4aSElliott Hughes self.old_coverage_ = [] 902*e1fe3e4aSElliott Hughes for element in content: 903*e1fe3e4aSElliott Hughes if not isinstance(element, tuple): 904*e1fe3e4aSElliott Hughes continue 905*e1fe3e4aSElliott Hughes element_name, element_attrs, _ = element 906*e1fe3e4aSElliott Hughes if element_name == "Glyph": 907*e1fe3e4aSElliott Hughes self.old_coverage_.append(element_attrs["value"]) 908*e1fe3e4aSElliott Hughes return 909*e1fe3e4aSElliott Hughes if name == "Sequence": 910*e1fe3e4aSElliott Hughes index = int(attrs.get("index", len(mapping))) 911*e1fe3e4aSElliott Hughes glyph = self.old_coverage_[index] 912*e1fe3e4aSElliott Hughes glyph_mapping = mapping[glyph] = [] 913*e1fe3e4aSElliott Hughes for element in content: 914*e1fe3e4aSElliott Hughes if not isinstance(element, tuple): 915*e1fe3e4aSElliott Hughes continue 916*e1fe3e4aSElliott Hughes element_name, element_attrs, _ = element 917*e1fe3e4aSElliott Hughes if element_name == "Substitute": 918*e1fe3e4aSElliott Hughes glyph_mapping.append(element_attrs["value"]) 919*e1fe3e4aSElliott Hughes return 920*e1fe3e4aSElliott Hughes 921*e1fe3e4aSElliott Hughes # TTX v3.1 and later. 922*e1fe3e4aSElliott Hughes outGlyphs = attrs["out"].split(",") if attrs["out"] else [] 923*e1fe3e4aSElliott Hughes mapping[attrs["in"]] = [g.strip() for g in outGlyphs] 924*e1fe3e4aSElliott Hughes 925*e1fe3e4aSElliott Hughes @staticmethod 926*e1fe3e4aSElliott Hughes def makeSequence_(g): 927*e1fe3e4aSElliott Hughes seq = Sequence() 928*e1fe3e4aSElliott Hughes seq.Substitute = g 929*e1fe3e4aSElliott Hughes return seq 930*e1fe3e4aSElliott Hughes 931*e1fe3e4aSElliott Hughes 932*e1fe3e4aSElliott Hughesclass ClassDef(FormatSwitchingBaseTable): 933*e1fe3e4aSElliott Hughes def populateDefaults(self, propagator=None): 934*e1fe3e4aSElliott Hughes if not hasattr(self, "classDefs"): 935*e1fe3e4aSElliott Hughes self.classDefs = {} 936*e1fe3e4aSElliott Hughes 937*e1fe3e4aSElliott Hughes def postRead(self, rawTable, font): 938*e1fe3e4aSElliott Hughes classDefs = {} 939*e1fe3e4aSElliott Hughes 940*e1fe3e4aSElliott Hughes if self.Format == 1: 941*e1fe3e4aSElliott Hughes start = rawTable["StartGlyph"] 942*e1fe3e4aSElliott Hughes classList = rawTable["ClassValueArray"] 943*e1fe3e4aSElliott Hughes startID = font.getGlyphID(start) 944*e1fe3e4aSElliott Hughes endID = startID + len(classList) 945*e1fe3e4aSElliott Hughes glyphNames = font.getGlyphNameMany(range(startID, endID)) 946*e1fe3e4aSElliott Hughes for glyphName, cls in zip(glyphNames, classList): 947*e1fe3e4aSElliott Hughes if cls: 948*e1fe3e4aSElliott Hughes classDefs[glyphName] = cls 949*e1fe3e4aSElliott Hughes 950*e1fe3e4aSElliott Hughes elif self.Format == 2: 951*e1fe3e4aSElliott Hughes records = rawTable["ClassRangeRecord"] 952*e1fe3e4aSElliott Hughes for rec in records: 953*e1fe3e4aSElliott Hughes cls = rec.Class 954*e1fe3e4aSElliott Hughes if not cls: 955*e1fe3e4aSElliott Hughes continue 956*e1fe3e4aSElliott Hughes start = rec.Start 957*e1fe3e4aSElliott Hughes end = rec.End 958*e1fe3e4aSElliott Hughes startID = font.getGlyphID(start) 959*e1fe3e4aSElliott Hughes endID = font.getGlyphID(end) + 1 960*e1fe3e4aSElliott Hughes glyphNames = font.getGlyphNameMany(range(startID, endID)) 961*e1fe3e4aSElliott Hughes for glyphName in glyphNames: 962*e1fe3e4aSElliott Hughes classDefs[glyphName] = cls 963*e1fe3e4aSElliott Hughes else: 964*e1fe3e4aSElliott Hughes log.warning("Unknown ClassDef format: %s", self.Format) 965*e1fe3e4aSElliott Hughes self.classDefs = classDefs 966*e1fe3e4aSElliott Hughes del self.Format # Don't need this anymore 967*e1fe3e4aSElliott Hughes 968*e1fe3e4aSElliott Hughes def _getClassRanges(self, font): 969*e1fe3e4aSElliott Hughes classDefs = getattr(self, "classDefs", None) 970*e1fe3e4aSElliott Hughes if classDefs is None: 971*e1fe3e4aSElliott Hughes self.classDefs = {} 972*e1fe3e4aSElliott Hughes return 973*e1fe3e4aSElliott Hughes getGlyphID = font.getGlyphID 974*e1fe3e4aSElliott Hughes items = [] 975*e1fe3e4aSElliott Hughes for glyphName, cls in classDefs.items(): 976*e1fe3e4aSElliott Hughes if not cls: 977*e1fe3e4aSElliott Hughes continue 978*e1fe3e4aSElliott Hughes items.append((getGlyphID(glyphName), glyphName, cls)) 979*e1fe3e4aSElliott Hughes if items: 980*e1fe3e4aSElliott Hughes items.sort() 981*e1fe3e4aSElliott Hughes last, lastName, lastCls = items[0] 982*e1fe3e4aSElliott Hughes ranges = [[lastCls, last, lastName]] 983*e1fe3e4aSElliott Hughes for glyphID, glyphName, cls in items[1:]: 984*e1fe3e4aSElliott Hughes if glyphID != last + 1 or cls != lastCls: 985*e1fe3e4aSElliott Hughes ranges[-1].extend([last, lastName]) 986*e1fe3e4aSElliott Hughes ranges.append([cls, glyphID, glyphName]) 987*e1fe3e4aSElliott Hughes last = glyphID 988*e1fe3e4aSElliott Hughes lastName = glyphName 989*e1fe3e4aSElliott Hughes lastCls = cls 990*e1fe3e4aSElliott Hughes ranges[-1].extend([last, lastName]) 991*e1fe3e4aSElliott Hughes return ranges 992*e1fe3e4aSElliott Hughes 993*e1fe3e4aSElliott Hughes def preWrite(self, font): 994*e1fe3e4aSElliott Hughes format = 2 995*e1fe3e4aSElliott Hughes rawTable = {"ClassRangeRecord": []} 996*e1fe3e4aSElliott Hughes ranges = self._getClassRanges(font) 997*e1fe3e4aSElliott Hughes if ranges: 998*e1fe3e4aSElliott Hughes startGlyph = ranges[0][1] 999*e1fe3e4aSElliott Hughes endGlyph = ranges[-1][3] 1000*e1fe3e4aSElliott Hughes glyphCount = endGlyph - startGlyph + 1 1001*e1fe3e4aSElliott Hughes if len(ranges) * 3 < glyphCount + 1: 1002*e1fe3e4aSElliott Hughes # Format 2 is more compact 1003*e1fe3e4aSElliott Hughes for i in range(len(ranges)): 1004*e1fe3e4aSElliott Hughes cls, start, startName, end, endName = ranges[i] 1005*e1fe3e4aSElliott Hughes rec = ClassRangeRecord() 1006*e1fe3e4aSElliott Hughes rec.Start = startName 1007*e1fe3e4aSElliott Hughes rec.End = endName 1008*e1fe3e4aSElliott Hughes rec.Class = cls 1009*e1fe3e4aSElliott Hughes ranges[i] = rec 1010*e1fe3e4aSElliott Hughes format = 2 1011*e1fe3e4aSElliott Hughes rawTable = {"ClassRangeRecord": ranges} 1012*e1fe3e4aSElliott Hughes else: 1013*e1fe3e4aSElliott Hughes # Format 1 is more compact 1014*e1fe3e4aSElliott Hughes startGlyphName = ranges[0][2] 1015*e1fe3e4aSElliott Hughes classes = [0] * glyphCount 1016*e1fe3e4aSElliott Hughes for cls, start, startName, end, endName in ranges: 1017*e1fe3e4aSElliott Hughes for g in range(start - startGlyph, end - startGlyph + 1): 1018*e1fe3e4aSElliott Hughes classes[g] = cls 1019*e1fe3e4aSElliott Hughes format = 1 1020*e1fe3e4aSElliott Hughes rawTable = {"StartGlyph": startGlyphName, "ClassValueArray": classes} 1021*e1fe3e4aSElliott Hughes self.Format = format 1022*e1fe3e4aSElliott Hughes return rawTable 1023*e1fe3e4aSElliott Hughes 1024*e1fe3e4aSElliott Hughes def toXML2(self, xmlWriter, font): 1025*e1fe3e4aSElliott Hughes items = sorted(self.classDefs.items()) 1026*e1fe3e4aSElliott Hughes for glyphName, cls in items: 1027*e1fe3e4aSElliott Hughes xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)]) 1028*e1fe3e4aSElliott Hughes xmlWriter.newline() 1029*e1fe3e4aSElliott Hughes 1030*e1fe3e4aSElliott Hughes def fromXML(self, name, attrs, content, font): 1031*e1fe3e4aSElliott Hughes classDefs = getattr(self, "classDefs", None) 1032*e1fe3e4aSElliott Hughes if classDefs is None: 1033*e1fe3e4aSElliott Hughes classDefs = {} 1034*e1fe3e4aSElliott Hughes self.classDefs = classDefs 1035*e1fe3e4aSElliott Hughes classDefs[attrs["glyph"]] = int(attrs["class"]) 1036*e1fe3e4aSElliott Hughes 1037*e1fe3e4aSElliott Hughes 1038*e1fe3e4aSElliott Hughesclass AlternateSubst(FormatSwitchingBaseTable): 1039*e1fe3e4aSElliott Hughes def populateDefaults(self, propagator=None): 1040*e1fe3e4aSElliott Hughes if not hasattr(self, "alternates"): 1041*e1fe3e4aSElliott Hughes self.alternates = {} 1042*e1fe3e4aSElliott Hughes 1043*e1fe3e4aSElliott Hughes def postRead(self, rawTable, font): 1044*e1fe3e4aSElliott Hughes alternates = {} 1045*e1fe3e4aSElliott Hughes if self.Format == 1: 1046*e1fe3e4aSElliott Hughes input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 1047*e1fe3e4aSElliott Hughes alts = rawTable["AlternateSet"] 1048*e1fe3e4aSElliott Hughes assert len(input) == len(alts) 1049*e1fe3e4aSElliott Hughes for inp, alt in zip(input, alts): 1050*e1fe3e4aSElliott Hughes alternates[inp] = alt.Alternate 1051*e1fe3e4aSElliott Hughes else: 1052*e1fe3e4aSElliott Hughes assert 0, "unknown format: %s" % self.Format 1053*e1fe3e4aSElliott Hughes self.alternates = alternates 1054*e1fe3e4aSElliott Hughes del self.Format # Don't need this anymore 1055*e1fe3e4aSElliott Hughes 1056*e1fe3e4aSElliott Hughes def preWrite(self, font): 1057*e1fe3e4aSElliott Hughes self.Format = 1 1058*e1fe3e4aSElliott Hughes alternates = getattr(self, "alternates", None) 1059*e1fe3e4aSElliott Hughes if alternates is None: 1060*e1fe3e4aSElliott Hughes alternates = self.alternates = {} 1061*e1fe3e4aSElliott Hughes items = list(alternates.items()) 1062*e1fe3e4aSElliott Hughes for i in range(len(items)): 1063*e1fe3e4aSElliott Hughes glyphName, set = items[i] 1064*e1fe3e4aSElliott Hughes items[i] = font.getGlyphID(glyphName), glyphName, set 1065*e1fe3e4aSElliott Hughes items.sort() 1066*e1fe3e4aSElliott Hughes cov = Coverage() 1067*e1fe3e4aSElliott Hughes cov.glyphs = [item[1] for item in items] 1068*e1fe3e4aSElliott Hughes alternates = [] 1069*e1fe3e4aSElliott Hughes setList = [item[-1] for item in items] 1070*e1fe3e4aSElliott Hughes for set in setList: 1071*e1fe3e4aSElliott Hughes alts = AlternateSet() 1072*e1fe3e4aSElliott Hughes alts.Alternate = set 1073*e1fe3e4aSElliott Hughes alternates.append(alts) 1074*e1fe3e4aSElliott Hughes # a special case to deal with the fact that several hundred Adobe Japan1-5 1075*e1fe3e4aSElliott Hughes # CJK fonts will overflow an offset if the coverage table isn't pushed to the end. 1076*e1fe3e4aSElliott Hughes # Also useful in that when splitting a sub-table because of an offset overflow 1077*e1fe3e4aSElliott Hughes # I don't need to calculate the change in the subtable offset due to the change in the coverage table size. 1078*e1fe3e4aSElliott Hughes # Allows packing more rules in subtable. 1079*e1fe3e4aSElliott Hughes self.sortCoverageLast = 1 1080*e1fe3e4aSElliott Hughes return {"Coverage": cov, "AlternateSet": alternates} 1081*e1fe3e4aSElliott Hughes 1082*e1fe3e4aSElliott Hughes def toXML2(self, xmlWriter, font): 1083*e1fe3e4aSElliott Hughes items = sorted(self.alternates.items()) 1084*e1fe3e4aSElliott Hughes for glyphName, alternates in items: 1085*e1fe3e4aSElliott Hughes xmlWriter.begintag("AlternateSet", glyph=glyphName) 1086*e1fe3e4aSElliott Hughes xmlWriter.newline() 1087*e1fe3e4aSElliott Hughes for alt in alternates: 1088*e1fe3e4aSElliott Hughes xmlWriter.simpletag("Alternate", glyph=alt) 1089*e1fe3e4aSElliott Hughes xmlWriter.newline() 1090*e1fe3e4aSElliott Hughes xmlWriter.endtag("AlternateSet") 1091*e1fe3e4aSElliott Hughes xmlWriter.newline() 1092*e1fe3e4aSElliott Hughes 1093*e1fe3e4aSElliott Hughes def fromXML(self, name, attrs, content, font): 1094*e1fe3e4aSElliott Hughes alternates = getattr(self, "alternates", None) 1095*e1fe3e4aSElliott Hughes if alternates is None: 1096*e1fe3e4aSElliott Hughes alternates = {} 1097*e1fe3e4aSElliott Hughes self.alternates = alternates 1098*e1fe3e4aSElliott Hughes glyphName = attrs["glyph"] 1099*e1fe3e4aSElliott Hughes set = [] 1100*e1fe3e4aSElliott Hughes alternates[glyphName] = set 1101*e1fe3e4aSElliott Hughes for element in content: 1102*e1fe3e4aSElliott Hughes if not isinstance(element, tuple): 1103*e1fe3e4aSElliott Hughes continue 1104*e1fe3e4aSElliott Hughes name, attrs, content = element 1105*e1fe3e4aSElliott Hughes set.append(attrs["glyph"]) 1106*e1fe3e4aSElliott Hughes 1107*e1fe3e4aSElliott Hughes 1108*e1fe3e4aSElliott Hughesclass LigatureSubst(FormatSwitchingBaseTable): 1109*e1fe3e4aSElliott Hughes def populateDefaults(self, propagator=None): 1110*e1fe3e4aSElliott Hughes if not hasattr(self, "ligatures"): 1111*e1fe3e4aSElliott Hughes self.ligatures = {} 1112*e1fe3e4aSElliott Hughes 1113*e1fe3e4aSElliott Hughes def postRead(self, rawTable, font): 1114*e1fe3e4aSElliott Hughes ligatures = {} 1115*e1fe3e4aSElliott Hughes if self.Format == 1: 1116*e1fe3e4aSElliott Hughes input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) 1117*e1fe3e4aSElliott Hughes ligSets = rawTable["LigatureSet"] 1118*e1fe3e4aSElliott Hughes assert len(input) == len(ligSets) 1119*e1fe3e4aSElliott Hughes for i in range(len(input)): 1120*e1fe3e4aSElliott Hughes ligatures[input[i]] = ligSets[i].Ligature 1121*e1fe3e4aSElliott Hughes else: 1122*e1fe3e4aSElliott Hughes assert 0, "unknown format: %s" % self.Format 1123*e1fe3e4aSElliott Hughes self.ligatures = ligatures 1124*e1fe3e4aSElliott Hughes del self.Format # Don't need this anymore 1125*e1fe3e4aSElliott Hughes 1126*e1fe3e4aSElliott Hughes @staticmethod 1127*e1fe3e4aSElliott Hughes def _getLigatureSortKey(components): 1128*e1fe3e4aSElliott Hughes # Computes a key for ordering ligatures in a GSUB Type-4 lookup. 1129*e1fe3e4aSElliott Hughes 1130*e1fe3e4aSElliott Hughes # When building the OpenType lookup, we need to make sure that 1131*e1fe3e4aSElliott Hughes # the longest sequence of components is listed first, so we 1132*e1fe3e4aSElliott Hughes # use the negative length as the key for sorting. 1133*e1fe3e4aSElliott Hughes # Note, we no longer need to worry about deterministic order because the 1134*e1fe3e4aSElliott Hughes # ligature mapping `dict` remembers the insertion order, and this in 1135*e1fe3e4aSElliott Hughes # turn depends on the order in which the ligatures are written in the FEA. 1136*e1fe3e4aSElliott Hughes # Since python sort algorithm is stable, the ligatures of equal length 1137*e1fe3e4aSElliott Hughes # will keep the relative order in which they appear in the feature file. 1138*e1fe3e4aSElliott Hughes # For example, given the following ligatures (all starting with 'f' and 1139*e1fe3e4aSElliott Hughes # thus belonging to the same LigatureSet): 1140*e1fe3e4aSElliott Hughes # 1141*e1fe3e4aSElliott Hughes # feature liga { 1142*e1fe3e4aSElliott Hughes # sub f i by f_i; 1143*e1fe3e4aSElliott Hughes # sub f f f by f_f_f; 1144*e1fe3e4aSElliott Hughes # sub f f by f_f; 1145*e1fe3e4aSElliott Hughes # sub f f i by f_f_i; 1146*e1fe3e4aSElliott Hughes # } liga; 1147*e1fe3e4aSElliott Hughes # 1148*e1fe3e4aSElliott Hughes # this should sort to: f_f_f, f_f_i, f_i, f_f 1149*e1fe3e4aSElliott Hughes # This is also what fea-rs does, see: 1150*e1fe3e4aSElliott Hughes # https://github.com/adobe-type-tools/afdko/issues/1727 1151*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/3428 1152*e1fe3e4aSElliott Hughes # https://github.com/googlefonts/fontc/pull/680 1153*e1fe3e4aSElliott Hughes return -len(components) 1154*e1fe3e4aSElliott Hughes 1155*e1fe3e4aSElliott Hughes def preWrite(self, font): 1156*e1fe3e4aSElliott Hughes self.Format = 1 1157*e1fe3e4aSElliott Hughes ligatures = getattr(self, "ligatures", None) 1158*e1fe3e4aSElliott Hughes if ligatures is None: 1159*e1fe3e4aSElliott Hughes ligatures = self.ligatures = {} 1160*e1fe3e4aSElliott Hughes 1161*e1fe3e4aSElliott Hughes if ligatures and isinstance(next(iter(ligatures)), tuple): 1162*e1fe3e4aSElliott Hughes # New high-level API in v3.1 and later. Note that we just support compiling this 1163*e1fe3e4aSElliott Hughes # for now. We don't load to this API, and don't do XML with it. 1164*e1fe3e4aSElliott Hughes 1165*e1fe3e4aSElliott Hughes # ligatures is map from components-sequence to lig-glyph 1166*e1fe3e4aSElliott Hughes newLigatures = dict() 1167*e1fe3e4aSElliott Hughes for comps in sorted(ligatures.keys(), key=self._getLigatureSortKey): 1168*e1fe3e4aSElliott Hughes ligature = Ligature() 1169*e1fe3e4aSElliott Hughes ligature.Component = comps[1:] 1170*e1fe3e4aSElliott Hughes ligature.CompCount = len(comps) 1171*e1fe3e4aSElliott Hughes ligature.LigGlyph = ligatures[comps] 1172*e1fe3e4aSElliott Hughes newLigatures.setdefault(comps[0], []).append(ligature) 1173*e1fe3e4aSElliott Hughes ligatures = newLigatures 1174*e1fe3e4aSElliott Hughes 1175*e1fe3e4aSElliott Hughes items = list(ligatures.items()) 1176*e1fe3e4aSElliott Hughes for i in range(len(items)): 1177*e1fe3e4aSElliott Hughes glyphName, set = items[i] 1178*e1fe3e4aSElliott Hughes items[i] = font.getGlyphID(glyphName), glyphName, set 1179*e1fe3e4aSElliott Hughes items.sort() 1180*e1fe3e4aSElliott Hughes cov = Coverage() 1181*e1fe3e4aSElliott Hughes cov.glyphs = [item[1] for item in items] 1182*e1fe3e4aSElliott Hughes 1183*e1fe3e4aSElliott Hughes ligSets = [] 1184*e1fe3e4aSElliott Hughes setList = [item[-1] for item in items] 1185*e1fe3e4aSElliott Hughes for set in setList: 1186*e1fe3e4aSElliott Hughes ligSet = LigatureSet() 1187*e1fe3e4aSElliott Hughes ligs = ligSet.Ligature = [] 1188*e1fe3e4aSElliott Hughes for lig in set: 1189*e1fe3e4aSElliott Hughes ligs.append(lig) 1190*e1fe3e4aSElliott Hughes ligSets.append(ligSet) 1191*e1fe3e4aSElliott Hughes # Useful in that when splitting a sub-table because of an offset overflow 1192*e1fe3e4aSElliott Hughes # I don't need to calculate the change in subtabl offset due to the coverage table size. 1193*e1fe3e4aSElliott Hughes # Allows packing more rules in subtable. 1194*e1fe3e4aSElliott Hughes self.sortCoverageLast = 1 1195*e1fe3e4aSElliott Hughes return {"Coverage": cov, "LigatureSet": ligSets} 1196*e1fe3e4aSElliott Hughes 1197*e1fe3e4aSElliott Hughes def toXML2(self, xmlWriter, font): 1198*e1fe3e4aSElliott Hughes items = sorted(self.ligatures.items()) 1199*e1fe3e4aSElliott Hughes for glyphName, ligSets in items: 1200*e1fe3e4aSElliott Hughes xmlWriter.begintag("LigatureSet", glyph=glyphName) 1201*e1fe3e4aSElliott Hughes xmlWriter.newline() 1202*e1fe3e4aSElliott Hughes for lig in ligSets: 1203*e1fe3e4aSElliott Hughes xmlWriter.simpletag( 1204*e1fe3e4aSElliott Hughes "Ligature", glyph=lig.LigGlyph, components=",".join(lig.Component) 1205*e1fe3e4aSElliott Hughes ) 1206*e1fe3e4aSElliott Hughes xmlWriter.newline() 1207*e1fe3e4aSElliott Hughes xmlWriter.endtag("LigatureSet") 1208*e1fe3e4aSElliott Hughes xmlWriter.newline() 1209*e1fe3e4aSElliott Hughes 1210*e1fe3e4aSElliott Hughes def fromXML(self, name, attrs, content, font): 1211*e1fe3e4aSElliott Hughes ligatures = getattr(self, "ligatures", None) 1212*e1fe3e4aSElliott Hughes if ligatures is None: 1213*e1fe3e4aSElliott Hughes ligatures = {} 1214*e1fe3e4aSElliott Hughes self.ligatures = ligatures 1215*e1fe3e4aSElliott Hughes glyphName = attrs["glyph"] 1216*e1fe3e4aSElliott Hughes ligs = [] 1217*e1fe3e4aSElliott Hughes ligatures[glyphName] = ligs 1218*e1fe3e4aSElliott Hughes for element in content: 1219*e1fe3e4aSElliott Hughes if not isinstance(element, tuple): 1220*e1fe3e4aSElliott Hughes continue 1221*e1fe3e4aSElliott Hughes name, attrs, content = element 1222*e1fe3e4aSElliott Hughes lig = Ligature() 1223*e1fe3e4aSElliott Hughes lig.LigGlyph = attrs["glyph"] 1224*e1fe3e4aSElliott Hughes components = attrs["components"] 1225*e1fe3e4aSElliott Hughes lig.Component = components.split(",") if components else [] 1226*e1fe3e4aSElliott Hughes lig.CompCount = len(lig.Component) 1227*e1fe3e4aSElliott Hughes ligs.append(lig) 1228*e1fe3e4aSElliott Hughes 1229*e1fe3e4aSElliott Hughes 1230*e1fe3e4aSElliott Hughesclass COLR(BaseTable): 1231*e1fe3e4aSElliott Hughes def decompile(self, reader, font): 1232*e1fe3e4aSElliott Hughes # COLRv0 is exceptional in that LayerRecordCount appears *after* the 1233*e1fe3e4aSElliott Hughes # LayerRecordArray it counts, but the parser logic expects Count fields 1234*e1fe3e4aSElliott Hughes # to always precede the arrays. Here we work around this by parsing the 1235*e1fe3e4aSElliott Hughes # LayerRecordCount before the rest of the table, and storing it in 1236*e1fe3e4aSElliott Hughes # the reader's local state. 1237*e1fe3e4aSElliott Hughes subReader = reader.getSubReader(offset=0) 1238*e1fe3e4aSElliott Hughes for conv in self.getConverters(): 1239*e1fe3e4aSElliott Hughes if conv.name != "LayerRecordCount": 1240*e1fe3e4aSElliott Hughes subReader.advance(conv.staticSize) 1241*e1fe3e4aSElliott Hughes continue 1242*e1fe3e4aSElliott Hughes reader[conv.name] = conv.read(subReader, font, tableDict={}) 1243*e1fe3e4aSElliott Hughes break 1244*e1fe3e4aSElliott Hughes else: 1245*e1fe3e4aSElliott Hughes raise AssertionError("LayerRecordCount converter not found") 1246*e1fe3e4aSElliott Hughes return BaseTable.decompile(self, reader, font) 1247*e1fe3e4aSElliott Hughes 1248*e1fe3e4aSElliott Hughes def preWrite(self, font): 1249*e1fe3e4aSElliott Hughes # The writer similarly assumes Count values precede the things counted, 1250*e1fe3e4aSElliott Hughes # thus here we pre-initialize a CountReference; the actual count value 1251*e1fe3e4aSElliott Hughes # will be set to the lenght of the array by the time this is assembled. 1252*e1fe3e4aSElliott Hughes self.LayerRecordCount = None 1253*e1fe3e4aSElliott Hughes return { 1254*e1fe3e4aSElliott Hughes **self.__dict__, 1255*e1fe3e4aSElliott Hughes "LayerRecordCount": CountReference(self.__dict__, "LayerRecordCount"), 1256*e1fe3e4aSElliott Hughes } 1257*e1fe3e4aSElliott Hughes 1258*e1fe3e4aSElliott Hughes def computeClipBoxes(self, glyphSet: "_TTGlyphSet", quantization: int = 1): 1259*e1fe3e4aSElliott Hughes if self.Version == 0: 1260*e1fe3e4aSElliott Hughes return 1261*e1fe3e4aSElliott Hughes 1262*e1fe3e4aSElliott Hughes clips = {} 1263*e1fe3e4aSElliott Hughes for rec in self.BaseGlyphList.BaseGlyphPaintRecord: 1264*e1fe3e4aSElliott Hughes try: 1265*e1fe3e4aSElliott Hughes clipBox = rec.Paint.computeClipBox(self, glyphSet, quantization) 1266*e1fe3e4aSElliott Hughes except Exception as e: 1267*e1fe3e4aSElliott Hughes from fontTools.ttLib import TTLibError 1268*e1fe3e4aSElliott Hughes 1269*e1fe3e4aSElliott Hughes raise TTLibError( 1270*e1fe3e4aSElliott Hughes f"Failed to compute COLR ClipBox for {rec.BaseGlyph!r}" 1271*e1fe3e4aSElliott Hughes ) from e 1272*e1fe3e4aSElliott Hughes 1273*e1fe3e4aSElliott Hughes if clipBox is not None: 1274*e1fe3e4aSElliott Hughes clips[rec.BaseGlyph] = clipBox 1275*e1fe3e4aSElliott Hughes 1276*e1fe3e4aSElliott Hughes hasClipList = hasattr(self, "ClipList") and self.ClipList is not None 1277*e1fe3e4aSElliott Hughes if not clips: 1278*e1fe3e4aSElliott Hughes if hasClipList: 1279*e1fe3e4aSElliott Hughes self.ClipList = None 1280*e1fe3e4aSElliott Hughes else: 1281*e1fe3e4aSElliott Hughes if not hasClipList: 1282*e1fe3e4aSElliott Hughes self.ClipList = ClipList() 1283*e1fe3e4aSElliott Hughes self.ClipList.Format = 1 1284*e1fe3e4aSElliott Hughes self.ClipList.clips = clips 1285*e1fe3e4aSElliott Hughes 1286*e1fe3e4aSElliott Hughes 1287*e1fe3e4aSElliott Hughesclass LookupList(BaseTable): 1288*e1fe3e4aSElliott Hughes @property 1289*e1fe3e4aSElliott Hughes def table(self): 1290*e1fe3e4aSElliott Hughes for l in self.Lookup: 1291*e1fe3e4aSElliott Hughes for st in l.SubTable: 1292*e1fe3e4aSElliott Hughes if type(st).__name__.endswith("Subst"): 1293*e1fe3e4aSElliott Hughes return "GSUB" 1294*e1fe3e4aSElliott Hughes if type(st).__name__.endswith("Pos"): 1295*e1fe3e4aSElliott Hughes return "GPOS" 1296*e1fe3e4aSElliott Hughes raise ValueError 1297*e1fe3e4aSElliott Hughes 1298*e1fe3e4aSElliott Hughes def toXML2(self, xmlWriter, font): 1299*e1fe3e4aSElliott Hughes if ( 1300*e1fe3e4aSElliott Hughes not font 1301*e1fe3e4aSElliott Hughes or "Debg" not in font 1302*e1fe3e4aSElliott Hughes or LOOKUP_DEBUG_INFO_KEY not in font["Debg"].data 1303*e1fe3e4aSElliott Hughes ): 1304*e1fe3e4aSElliott Hughes return super().toXML2(xmlWriter, font) 1305*e1fe3e4aSElliott Hughes debugData = font["Debg"].data[LOOKUP_DEBUG_INFO_KEY][self.table] 1306*e1fe3e4aSElliott Hughes for conv in self.getConverters(): 1307*e1fe3e4aSElliott Hughes if conv.repeat: 1308*e1fe3e4aSElliott Hughes value = getattr(self, conv.name, []) 1309*e1fe3e4aSElliott Hughes for lookupIndex, item in enumerate(value): 1310*e1fe3e4aSElliott Hughes if str(lookupIndex) in debugData: 1311*e1fe3e4aSElliott Hughes info = LookupDebugInfo(*debugData[str(lookupIndex)]) 1312*e1fe3e4aSElliott Hughes tag = info.location 1313*e1fe3e4aSElliott Hughes if info.name: 1314*e1fe3e4aSElliott Hughes tag = f"{info.name}: {tag}" 1315*e1fe3e4aSElliott Hughes if info.feature: 1316*e1fe3e4aSElliott Hughes script, language, feature = info.feature 1317*e1fe3e4aSElliott Hughes tag = f"{tag} in {feature} ({script}/{language})" 1318*e1fe3e4aSElliott Hughes xmlWriter.comment(tag) 1319*e1fe3e4aSElliott Hughes xmlWriter.newline() 1320*e1fe3e4aSElliott Hughes 1321*e1fe3e4aSElliott Hughes conv.xmlWrite( 1322*e1fe3e4aSElliott Hughes xmlWriter, font, item, conv.name, [("index", lookupIndex)] 1323*e1fe3e4aSElliott Hughes ) 1324*e1fe3e4aSElliott Hughes else: 1325*e1fe3e4aSElliott Hughes if conv.aux and not eval(conv.aux, None, vars(self)): 1326*e1fe3e4aSElliott Hughes continue 1327*e1fe3e4aSElliott Hughes value = getattr( 1328*e1fe3e4aSElliott Hughes self, conv.name, None 1329*e1fe3e4aSElliott Hughes ) # TODO Handle defaults instead of defaulting to None! 1330*e1fe3e4aSElliott Hughes conv.xmlWrite(xmlWriter, font, value, conv.name, []) 1331*e1fe3e4aSElliott Hughes 1332*e1fe3e4aSElliott Hughes 1333*e1fe3e4aSElliott Hughesclass BaseGlyphRecordArray(BaseTable): 1334*e1fe3e4aSElliott Hughes def preWrite(self, font): 1335*e1fe3e4aSElliott Hughes self.BaseGlyphRecord = sorted( 1336*e1fe3e4aSElliott Hughes self.BaseGlyphRecord, key=lambda rec: font.getGlyphID(rec.BaseGlyph) 1337*e1fe3e4aSElliott Hughes ) 1338*e1fe3e4aSElliott Hughes return self.__dict__.copy() 1339*e1fe3e4aSElliott Hughes 1340*e1fe3e4aSElliott Hughes 1341*e1fe3e4aSElliott Hughesclass BaseGlyphList(BaseTable): 1342*e1fe3e4aSElliott Hughes def preWrite(self, font): 1343*e1fe3e4aSElliott Hughes self.BaseGlyphPaintRecord = sorted( 1344*e1fe3e4aSElliott Hughes self.BaseGlyphPaintRecord, key=lambda rec: font.getGlyphID(rec.BaseGlyph) 1345*e1fe3e4aSElliott Hughes ) 1346*e1fe3e4aSElliott Hughes return self.__dict__.copy() 1347*e1fe3e4aSElliott Hughes 1348*e1fe3e4aSElliott Hughes 1349*e1fe3e4aSElliott Hughesclass ClipBoxFormat(IntEnum): 1350*e1fe3e4aSElliott Hughes Static = 1 1351*e1fe3e4aSElliott Hughes Variable = 2 1352*e1fe3e4aSElliott Hughes 1353*e1fe3e4aSElliott Hughes def is_variable(self): 1354*e1fe3e4aSElliott Hughes return self is self.Variable 1355*e1fe3e4aSElliott Hughes 1356*e1fe3e4aSElliott Hughes def as_variable(self): 1357*e1fe3e4aSElliott Hughes return self.Variable 1358*e1fe3e4aSElliott Hughes 1359*e1fe3e4aSElliott Hughes 1360*e1fe3e4aSElliott Hughesclass ClipBox(getFormatSwitchingBaseTableClass("uint8")): 1361*e1fe3e4aSElliott Hughes formatEnum = ClipBoxFormat 1362*e1fe3e4aSElliott Hughes 1363*e1fe3e4aSElliott Hughes def as_tuple(self): 1364*e1fe3e4aSElliott Hughes return tuple(getattr(self, conv.name) for conv in self.getConverters()) 1365*e1fe3e4aSElliott Hughes 1366*e1fe3e4aSElliott Hughes def __repr__(self): 1367*e1fe3e4aSElliott Hughes return f"{self.__class__.__name__}{self.as_tuple()}" 1368*e1fe3e4aSElliott Hughes 1369*e1fe3e4aSElliott Hughes 1370*e1fe3e4aSElliott Hughesclass ClipList(getFormatSwitchingBaseTableClass("uint8")): 1371*e1fe3e4aSElliott Hughes def populateDefaults(self, propagator=None): 1372*e1fe3e4aSElliott Hughes if not hasattr(self, "clips"): 1373*e1fe3e4aSElliott Hughes self.clips = {} 1374*e1fe3e4aSElliott Hughes 1375*e1fe3e4aSElliott Hughes def postRead(self, rawTable, font): 1376*e1fe3e4aSElliott Hughes clips = {} 1377*e1fe3e4aSElliott Hughes glyphOrder = font.getGlyphOrder() 1378*e1fe3e4aSElliott Hughes for i, rec in enumerate(rawTable["ClipRecord"]): 1379*e1fe3e4aSElliott Hughes if rec.StartGlyphID > rec.EndGlyphID: 1380*e1fe3e4aSElliott Hughes log.warning( 1381*e1fe3e4aSElliott Hughes "invalid ClipRecord[%i].StartGlyphID (%i) > " 1382*e1fe3e4aSElliott Hughes "EndGlyphID (%i); skipped", 1383*e1fe3e4aSElliott Hughes i, 1384*e1fe3e4aSElliott Hughes rec.StartGlyphID, 1385*e1fe3e4aSElliott Hughes rec.EndGlyphID, 1386*e1fe3e4aSElliott Hughes ) 1387*e1fe3e4aSElliott Hughes continue 1388*e1fe3e4aSElliott Hughes redefinedGlyphs = [] 1389*e1fe3e4aSElliott Hughes missingGlyphs = [] 1390*e1fe3e4aSElliott Hughes for glyphID in range(rec.StartGlyphID, rec.EndGlyphID + 1): 1391*e1fe3e4aSElliott Hughes try: 1392*e1fe3e4aSElliott Hughes glyph = glyphOrder[glyphID] 1393*e1fe3e4aSElliott Hughes except IndexError: 1394*e1fe3e4aSElliott Hughes missingGlyphs.append(glyphID) 1395*e1fe3e4aSElliott Hughes continue 1396*e1fe3e4aSElliott Hughes if glyph not in clips: 1397*e1fe3e4aSElliott Hughes clips[glyph] = copy.copy(rec.ClipBox) 1398*e1fe3e4aSElliott Hughes else: 1399*e1fe3e4aSElliott Hughes redefinedGlyphs.append(glyphID) 1400*e1fe3e4aSElliott Hughes if redefinedGlyphs: 1401*e1fe3e4aSElliott Hughes log.warning( 1402*e1fe3e4aSElliott Hughes "ClipRecord[%i] overlaps previous records; " 1403*e1fe3e4aSElliott Hughes "ignoring redefined clip boxes for the " 1404*e1fe3e4aSElliott Hughes "following glyph ID range: [%i-%i]", 1405*e1fe3e4aSElliott Hughes i, 1406*e1fe3e4aSElliott Hughes min(redefinedGlyphs), 1407*e1fe3e4aSElliott Hughes max(redefinedGlyphs), 1408*e1fe3e4aSElliott Hughes ) 1409*e1fe3e4aSElliott Hughes if missingGlyphs: 1410*e1fe3e4aSElliott Hughes log.warning( 1411*e1fe3e4aSElliott Hughes "ClipRecord[%i] range references missing " "glyph IDs: [%i-%i]", 1412*e1fe3e4aSElliott Hughes i, 1413*e1fe3e4aSElliott Hughes min(missingGlyphs), 1414*e1fe3e4aSElliott Hughes max(missingGlyphs), 1415*e1fe3e4aSElliott Hughes ) 1416*e1fe3e4aSElliott Hughes self.clips = clips 1417*e1fe3e4aSElliott Hughes 1418*e1fe3e4aSElliott Hughes def groups(self): 1419*e1fe3e4aSElliott Hughes glyphsByClip = defaultdict(list) 1420*e1fe3e4aSElliott Hughes uniqueClips = {} 1421*e1fe3e4aSElliott Hughes for glyphName, clipBox in self.clips.items(): 1422*e1fe3e4aSElliott Hughes key = clipBox.as_tuple() 1423*e1fe3e4aSElliott Hughes glyphsByClip[key].append(glyphName) 1424*e1fe3e4aSElliott Hughes if key not in uniqueClips: 1425*e1fe3e4aSElliott Hughes uniqueClips[key] = clipBox 1426*e1fe3e4aSElliott Hughes return { 1427*e1fe3e4aSElliott Hughes frozenset(glyphs): uniqueClips[key] for key, glyphs in glyphsByClip.items() 1428*e1fe3e4aSElliott Hughes } 1429*e1fe3e4aSElliott Hughes 1430*e1fe3e4aSElliott Hughes def preWrite(self, font): 1431*e1fe3e4aSElliott Hughes if not hasattr(self, "clips"): 1432*e1fe3e4aSElliott Hughes self.clips = {} 1433*e1fe3e4aSElliott Hughes clipBoxRanges = {} 1434*e1fe3e4aSElliott Hughes glyphMap = font.getReverseGlyphMap() 1435*e1fe3e4aSElliott Hughes for glyphs, clipBox in self.groups().items(): 1436*e1fe3e4aSElliott Hughes glyphIDs = sorted( 1437*e1fe3e4aSElliott Hughes glyphMap[glyphName] for glyphName in glyphs if glyphName in glyphMap 1438*e1fe3e4aSElliott Hughes ) 1439*e1fe3e4aSElliott Hughes if not glyphIDs: 1440*e1fe3e4aSElliott Hughes continue 1441*e1fe3e4aSElliott Hughes last = glyphIDs[0] 1442*e1fe3e4aSElliott Hughes ranges = [[last]] 1443*e1fe3e4aSElliott Hughes for glyphID in glyphIDs[1:]: 1444*e1fe3e4aSElliott Hughes if glyphID != last + 1: 1445*e1fe3e4aSElliott Hughes ranges[-1].append(last) 1446*e1fe3e4aSElliott Hughes ranges.append([glyphID]) 1447*e1fe3e4aSElliott Hughes last = glyphID 1448*e1fe3e4aSElliott Hughes ranges[-1].append(last) 1449*e1fe3e4aSElliott Hughes for start, end in ranges: 1450*e1fe3e4aSElliott Hughes assert (start, end) not in clipBoxRanges 1451*e1fe3e4aSElliott Hughes clipBoxRanges[(start, end)] = clipBox 1452*e1fe3e4aSElliott Hughes 1453*e1fe3e4aSElliott Hughes clipRecords = [] 1454*e1fe3e4aSElliott Hughes for (start, end), clipBox in sorted(clipBoxRanges.items()): 1455*e1fe3e4aSElliott Hughes record = ClipRecord() 1456*e1fe3e4aSElliott Hughes record.StartGlyphID = start 1457*e1fe3e4aSElliott Hughes record.EndGlyphID = end 1458*e1fe3e4aSElliott Hughes record.ClipBox = clipBox 1459*e1fe3e4aSElliott Hughes clipRecords.append(record) 1460*e1fe3e4aSElliott Hughes rawTable = { 1461*e1fe3e4aSElliott Hughes "ClipCount": len(clipRecords), 1462*e1fe3e4aSElliott Hughes "ClipRecord": clipRecords, 1463*e1fe3e4aSElliott Hughes } 1464*e1fe3e4aSElliott Hughes return rawTable 1465*e1fe3e4aSElliott Hughes 1466*e1fe3e4aSElliott Hughes def toXML(self, xmlWriter, font, attrs=None, name=None): 1467*e1fe3e4aSElliott Hughes tableName = name if name else self.__class__.__name__ 1468*e1fe3e4aSElliott Hughes if attrs is None: 1469*e1fe3e4aSElliott Hughes attrs = [] 1470*e1fe3e4aSElliott Hughes if hasattr(self, "Format"): 1471*e1fe3e4aSElliott Hughes attrs.append(("Format", self.Format)) 1472*e1fe3e4aSElliott Hughes xmlWriter.begintag(tableName, attrs) 1473*e1fe3e4aSElliott Hughes xmlWriter.newline() 1474*e1fe3e4aSElliott Hughes # sort clips alphabetically to ensure deterministic XML dump 1475*e1fe3e4aSElliott Hughes for glyphs, clipBox in sorted( 1476*e1fe3e4aSElliott Hughes self.groups().items(), key=lambda item: min(item[0]) 1477*e1fe3e4aSElliott Hughes ): 1478*e1fe3e4aSElliott Hughes xmlWriter.begintag("Clip") 1479*e1fe3e4aSElliott Hughes xmlWriter.newline() 1480*e1fe3e4aSElliott Hughes for glyphName in sorted(glyphs): 1481*e1fe3e4aSElliott Hughes xmlWriter.simpletag("Glyph", value=glyphName) 1482*e1fe3e4aSElliott Hughes xmlWriter.newline() 1483*e1fe3e4aSElliott Hughes xmlWriter.begintag("ClipBox", [("Format", clipBox.Format)]) 1484*e1fe3e4aSElliott Hughes xmlWriter.newline() 1485*e1fe3e4aSElliott Hughes clipBox.toXML2(xmlWriter, font) 1486*e1fe3e4aSElliott Hughes xmlWriter.endtag("ClipBox") 1487*e1fe3e4aSElliott Hughes xmlWriter.newline() 1488*e1fe3e4aSElliott Hughes xmlWriter.endtag("Clip") 1489*e1fe3e4aSElliott Hughes xmlWriter.newline() 1490*e1fe3e4aSElliott Hughes xmlWriter.endtag(tableName) 1491*e1fe3e4aSElliott Hughes xmlWriter.newline() 1492*e1fe3e4aSElliott Hughes 1493*e1fe3e4aSElliott Hughes def fromXML(self, name, attrs, content, font): 1494*e1fe3e4aSElliott Hughes clips = getattr(self, "clips", None) 1495*e1fe3e4aSElliott Hughes if clips is None: 1496*e1fe3e4aSElliott Hughes self.clips = clips = {} 1497*e1fe3e4aSElliott Hughes assert name == "Clip" 1498*e1fe3e4aSElliott Hughes glyphs = [] 1499*e1fe3e4aSElliott Hughes clipBox = None 1500*e1fe3e4aSElliott Hughes for elem in content: 1501*e1fe3e4aSElliott Hughes if not isinstance(elem, tuple): 1502*e1fe3e4aSElliott Hughes continue 1503*e1fe3e4aSElliott Hughes name, attrs, content = elem 1504*e1fe3e4aSElliott Hughes if name == "Glyph": 1505*e1fe3e4aSElliott Hughes glyphs.append(attrs["value"]) 1506*e1fe3e4aSElliott Hughes elif name == "ClipBox": 1507*e1fe3e4aSElliott Hughes clipBox = ClipBox() 1508*e1fe3e4aSElliott Hughes clipBox.Format = safeEval(attrs["Format"]) 1509*e1fe3e4aSElliott Hughes for elem in content: 1510*e1fe3e4aSElliott Hughes if not isinstance(elem, tuple): 1511*e1fe3e4aSElliott Hughes continue 1512*e1fe3e4aSElliott Hughes name, attrs, content = elem 1513*e1fe3e4aSElliott Hughes clipBox.fromXML(name, attrs, content, font) 1514*e1fe3e4aSElliott Hughes if clipBox: 1515*e1fe3e4aSElliott Hughes for glyphName in glyphs: 1516*e1fe3e4aSElliott Hughes clips[glyphName] = clipBox 1517*e1fe3e4aSElliott Hughes 1518*e1fe3e4aSElliott Hughes 1519*e1fe3e4aSElliott Hughesclass ExtendMode(IntEnum): 1520*e1fe3e4aSElliott Hughes PAD = 0 1521*e1fe3e4aSElliott Hughes REPEAT = 1 1522*e1fe3e4aSElliott Hughes REFLECT = 2 1523*e1fe3e4aSElliott Hughes 1524*e1fe3e4aSElliott Hughes 1525*e1fe3e4aSElliott Hughes# Porter-Duff modes for COLRv1 PaintComposite: 1526*e1fe3e4aSElliott Hughes# https://github.com/googlefonts/colr-gradients-spec/tree/off_sub_1#compositemode-enumeration 1527*e1fe3e4aSElliott Hughesclass CompositeMode(IntEnum): 1528*e1fe3e4aSElliott Hughes CLEAR = 0 1529*e1fe3e4aSElliott Hughes SRC = 1 1530*e1fe3e4aSElliott Hughes DEST = 2 1531*e1fe3e4aSElliott Hughes SRC_OVER = 3 1532*e1fe3e4aSElliott Hughes DEST_OVER = 4 1533*e1fe3e4aSElliott Hughes SRC_IN = 5 1534*e1fe3e4aSElliott Hughes DEST_IN = 6 1535*e1fe3e4aSElliott Hughes SRC_OUT = 7 1536*e1fe3e4aSElliott Hughes DEST_OUT = 8 1537*e1fe3e4aSElliott Hughes SRC_ATOP = 9 1538*e1fe3e4aSElliott Hughes DEST_ATOP = 10 1539*e1fe3e4aSElliott Hughes XOR = 11 1540*e1fe3e4aSElliott Hughes PLUS = 12 1541*e1fe3e4aSElliott Hughes SCREEN = 13 1542*e1fe3e4aSElliott Hughes OVERLAY = 14 1543*e1fe3e4aSElliott Hughes DARKEN = 15 1544*e1fe3e4aSElliott Hughes LIGHTEN = 16 1545*e1fe3e4aSElliott Hughes COLOR_DODGE = 17 1546*e1fe3e4aSElliott Hughes COLOR_BURN = 18 1547*e1fe3e4aSElliott Hughes HARD_LIGHT = 19 1548*e1fe3e4aSElliott Hughes SOFT_LIGHT = 20 1549*e1fe3e4aSElliott Hughes DIFFERENCE = 21 1550*e1fe3e4aSElliott Hughes EXCLUSION = 22 1551*e1fe3e4aSElliott Hughes MULTIPLY = 23 1552*e1fe3e4aSElliott Hughes HSL_HUE = 24 1553*e1fe3e4aSElliott Hughes HSL_SATURATION = 25 1554*e1fe3e4aSElliott Hughes HSL_COLOR = 26 1555*e1fe3e4aSElliott Hughes HSL_LUMINOSITY = 27 1556*e1fe3e4aSElliott Hughes 1557*e1fe3e4aSElliott Hughes 1558*e1fe3e4aSElliott Hughesclass PaintFormat(IntEnum): 1559*e1fe3e4aSElliott Hughes PaintColrLayers = 1 1560*e1fe3e4aSElliott Hughes PaintSolid = 2 1561*e1fe3e4aSElliott Hughes PaintVarSolid = 3 1562*e1fe3e4aSElliott Hughes PaintLinearGradient = 4 1563*e1fe3e4aSElliott Hughes PaintVarLinearGradient = 5 1564*e1fe3e4aSElliott Hughes PaintRadialGradient = 6 1565*e1fe3e4aSElliott Hughes PaintVarRadialGradient = 7 1566*e1fe3e4aSElliott Hughes PaintSweepGradient = 8 1567*e1fe3e4aSElliott Hughes PaintVarSweepGradient = 9 1568*e1fe3e4aSElliott Hughes PaintGlyph = 10 1569*e1fe3e4aSElliott Hughes PaintColrGlyph = 11 1570*e1fe3e4aSElliott Hughes PaintTransform = 12 1571*e1fe3e4aSElliott Hughes PaintVarTransform = 13 1572*e1fe3e4aSElliott Hughes PaintTranslate = 14 1573*e1fe3e4aSElliott Hughes PaintVarTranslate = 15 1574*e1fe3e4aSElliott Hughes PaintScale = 16 1575*e1fe3e4aSElliott Hughes PaintVarScale = 17 1576*e1fe3e4aSElliott Hughes PaintScaleAroundCenter = 18 1577*e1fe3e4aSElliott Hughes PaintVarScaleAroundCenter = 19 1578*e1fe3e4aSElliott Hughes PaintScaleUniform = 20 1579*e1fe3e4aSElliott Hughes PaintVarScaleUniform = 21 1580*e1fe3e4aSElliott Hughes PaintScaleUniformAroundCenter = 22 1581*e1fe3e4aSElliott Hughes PaintVarScaleUniformAroundCenter = 23 1582*e1fe3e4aSElliott Hughes PaintRotate = 24 1583*e1fe3e4aSElliott Hughes PaintVarRotate = 25 1584*e1fe3e4aSElliott Hughes PaintRotateAroundCenter = 26 1585*e1fe3e4aSElliott Hughes PaintVarRotateAroundCenter = 27 1586*e1fe3e4aSElliott Hughes PaintSkew = 28 1587*e1fe3e4aSElliott Hughes PaintVarSkew = 29 1588*e1fe3e4aSElliott Hughes PaintSkewAroundCenter = 30 1589*e1fe3e4aSElliott Hughes PaintVarSkewAroundCenter = 31 1590*e1fe3e4aSElliott Hughes PaintComposite = 32 1591*e1fe3e4aSElliott Hughes 1592*e1fe3e4aSElliott Hughes def is_variable(self): 1593*e1fe3e4aSElliott Hughes return self.name.startswith("PaintVar") 1594*e1fe3e4aSElliott Hughes 1595*e1fe3e4aSElliott Hughes def as_variable(self): 1596*e1fe3e4aSElliott Hughes if self.is_variable(): 1597*e1fe3e4aSElliott Hughes return self 1598*e1fe3e4aSElliott Hughes try: 1599*e1fe3e4aSElliott Hughes return PaintFormat.__members__[f"PaintVar{self.name[5:]}"] 1600*e1fe3e4aSElliott Hughes except KeyError: 1601*e1fe3e4aSElliott Hughes return None 1602*e1fe3e4aSElliott Hughes 1603*e1fe3e4aSElliott Hughes 1604*e1fe3e4aSElliott Hughesclass Paint(getFormatSwitchingBaseTableClass("uint8")): 1605*e1fe3e4aSElliott Hughes formatEnum = PaintFormat 1606*e1fe3e4aSElliott Hughes 1607*e1fe3e4aSElliott Hughes def getFormatName(self): 1608*e1fe3e4aSElliott Hughes try: 1609*e1fe3e4aSElliott Hughes return self.formatEnum(self.Format).name 1610*e1fe3e4aSElliott Hughes except ValueError: 1611*e1fe3e4aSElliott Hughes raise NotImplementedError(f"Unknown Paint format: {self.Format}") 1612*e1fe3e4aSElliott Hughes 1613*e1fe3e4aSElliott Hughes def toXML(self, xmlWriter, font, attrs=None, name=None): 1614*e1fe3e4aSElliott Hughes tableName = name if name else self.__class__.__name__ 1615*e1fe3e4aSElliott Hughes if attrs is None: 1616*e1fe3e4aSElliott Hughes attrs = [] 1617*e1fe3e4aSElliott Hughes attrs.append(("Format", self.Format)) 1618*e1fe3e4aSElliott Hughes xmlWriter.begintag(tableName, attrs) 1619*e1fe3e4aSElliott Hughes xmlWriter.comment(self.getFormatName()) 1620*e1fe3e4aSElliott Hughes xmlWriter.newline() 1621*e1fe3e4aSElliott Hughes self.toXML2(xmlWriter, font) 1622*e1fe3e4aSElliott Hughes xmlWriter.endtag(tableName) 1623*e1fe3e4aSElliott Hughes xmlWriter.newline() 1624*e1fe3e4aSElliott Hughes 1625*e1fe3e4aSElliott Hughes def iterPaintSubTables(self, colr: COLR) -> Iterator[BaseTable.SubTableEntry]: 1626*e1fe3e4aSElliott Hughes if self.Format == PaintFormat.PaintColrLayers: 1627*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/2438: don't die when no LayerList exists 1628*e1fe3e4aSElliott Hughes layers = [] 1629*e1fe3e4aSElliott Hughes if colr.LayerList is not None: 1630*e1fe3e4aSElliott Hughes layers = colr.LayerList.Paint 1631*e1fe3e4aSElliott Hughes yield from ( 1632*e1fe3e4aSElliott Hughes BaseTable.SubTableEntry(name="Layers", value=v, index=i) 1633*e1fe3e4aSElliott Hughes for i, v in enumerate( 1634*e1fe3e4aSElliott Hughes layers[self.FirstLayerIndex : self.FirstLayerIndex + self.NumLayers] 1635*e1fe3e4aSElliott Hughes ) 1636*e1fe3e4aSElliott Hughes ) 1637*e1fe3e4aSElliott Hughes return 1638*e1fe3e4aSElliott Hughes 1639*e1fe3e4aSElliott Hughes if self.Format == PaintFormat.PaintColrGlyph: 1640*e1fe3e4aSElliott Hughes for record in colr.BaseGlyphList.BaseGlyphPaintRecord: 1641*e1fe3e4aSElliott Hughes if record.BaseGlyph == self.Glyph: 1642*e1fe3e4aSElliott Hughes yield BaseTable.SubTableEntry(name="BaseGlyph", value=record.Paint) 1643*e1fe3e4aSElliott Hughes return 1644*e1fe3e4aSElliott Hughes else: 1645*e1fe3e4aSElliott Hughes raise KeyError(f"{self.Glyph!r} not in colr.BaseGlyphList") 1646*e1fe3e4aSElliott Hughes 1647*e1fe3e4aSElliott Hughes for conv in self.getConverters(): 1648*e1fe3e4aSElliott Hughes if conv.tableClass is not None and issubclass(conv.tableClass, type(self)): 1649*e1fe3e4aSElliott Hughes value = getattr(self, conv.name) 1650*e1fe3e4aSElliott Hughes yield BaseTable.SubTableEntry(name=conv.name, value=value) 1651*e1fe3e4aSElliott Hughes 1652*e1fe3e4aSElliott Hughes def getChildren(self, colr) -> List["Paint"]: 1653*e1fe3e4aSElliott Hughes # this is kept for backward compatibility (e.g. it's used by the subsetter) 1654*e1fe3e4aSElliott Hughes return [p.value for p in self.iterPaintSubTables(colr)] 1655*e1fe3e4aSElliott Hughes 1656*e1fe3e4aSElliott Hughes def traverse(self, colr: COLR, callback): 1657*e1fe3e4aSElliott Hughes """Depth-first traversal of graph rooted at self, callback on each node.""" 1658*e1fe3e4aSElliott Hughes if not callable(callback): 1659*e1fe3e4aSElliott Hughes raise TypeError("callback must be callable") 1660*e1fe3e4aSElliott Hughes 1661*e1fe3e4aSElliott Hughes for path in dfs_base_table( 1662*e1fe3e4aSElliott Hughes self, iter_subtables_fn=lambda paint: paint.iterPaintSubTables(colr) 1663*e1fe3e4aSElliott Hughes ): 1664*e1fe3e4aSElliott Hughes paint = path[-1].value 1665*e1fe3e4aSElliott Hughes callback(paint) 1666*e1fe3e4aSElliott Hughes 1667*e1fe3e4aSElliott Hughes def getTransform(self) -> Transform: 1668*e1fe3e4aSElliott Hughes if self.Format == PaintFormat.PaintTransform: 1669*e1fe3e4aSElliott Hughes t = self.Transform 1670*e1fe3e4aSElliott Hughes return Transform(t.xx, t.yx, t.xy, t.yy, t.dx, t.dy) 1671*e1fe3e4aSElliott Hughes elif self.Format == PaintFormat.PaintTranslate: 1672*e1fe3e4aSElliott Hughes return Identity.translate(self.dx, self.dy) 1673*e1fe3e4aSElliott Hughes elif self.Format == PaintFormat.PaintScale: 1674*e1fe3e4aSElliott Hughes return Identity.scale(self.scaleX, self.scaleY) 1675*e1fe3e4aSElliott Hughes elif self.Format == PaintFormat.PaintScaleAroundCenter: 1676*e1fe3e4aSElliott Hughes return ( 1677*e1fe3e4aSElliott Hughes Identity.translate(self.centerX, self.centerY) 1678*e1fe3e4aSElliott Hughes .scale(self.scaleX, self.scaleY) 1679*e1fe3e4aSElliott Hughes .translate(-self.centerX, -self.centerY) 1680*e1fe3e4aSElliott Hughes ) 1681*e1fe3e4aSElliott Hughes elif self.Format == PaintFormat.PaintScaleUniform: 1682*e1fe3e4aSElliott Hughes return Identity.scale(self.scale) 1683*e1fe3e4aSElliott Hughes elif self.Format == PaintFormat.PaintScaleUniformAroundCenter: 1684*e1fe3e4aSElliott Hughes return ( 1685*e1fe3e4aSElliott Hughes Identity.translate(self.centerX, self.centerY) 1686*e1fe3e4aSElliott Hughes .scale(self.scale) 1687*e1fe3e4aSElliott Hughes .translate(-self.centerX, -self.centerY) 1688*e1fe3e4aSElliott Hughes ) 1689*e1fe3e4aSElliott Hughes elif self.Format == PaintFormat.PaintRotate: 1690*e1fe3e4aSElliott Hughes return Identity.rotate(radians(self.angle)) 1691*e1fe3e4aSElliott Hughes elif self.Format == PaintFormat.PaintRotateAroundCenter: 1692*e1fe3e4aSElliott Hughes return ( 1693*e1fe3e4aSElliott Hughes Identity.translate(self.centerX, self.centerY) 1694*e1fe3e4aSElliott Hughes .rotate(radians(self.angle)) 1695*e1fe3e4aSElliott Hughes .translate(-self.centerX, -self.centerY) 1696*e1fe3e4aSElliott Hughes ) 1697*e1fe3e4aSElliott Hughes elif self.Format == PaintFormat.PaintSkew: 1698*e1fe3e4aSElliott Hughes return Identity.skew(radians(-self.xSkewAngle), radians(self.ySkewAngle)) 1699*e1fe3e4aSElliott Hughes elif self.Format == PaintFormat.PaintSkewAroundCenter: 1700*e1fe3e4aSElliott Hughes return ( 1701*e1fe3e4aSElliott Hughes Identity.translate(self.centerX, self.centerY) 1702*e1fe3e4aSElliott Hughes .skew(radians(-self.xSkewAngle), radians(self.ySkewAngle)) 1703*e1fe3e4aSElliott Hughes .translate(-self.centerX, -self.centerY) 1704*e1fe3e4aSElliott Hughes ) 1705*e1fe3e4aSElliott Hughes if PaintFormat(self.Format).is_variable(): 1706*e1fe3e4aSElliott Hughes raise NotImplementedError(f"Variable Paints not supported: {self.Format}") 1707*e1fe3e4aSElliott Hughes 1708*e1fe3e4aSElliott Hughes return Identity 1709*e1fe3e4aSElliott Hughes 1710*e1fe3e4aSElliott Hughes def computeClipBox( 1711*e1fe3e4aSElliott Hughes self, colr: COLR, glyphSet: "_TTGlyphSet", quantization: int = 1 1712*e1fe3e4aSElliott Hughes ) -> Optional[ClipBox]: 1713*e1fe3e4aSElliott Hughes pen = ControlBoundsPen(glyphSet) 1714*e1fe3e4aSElliott Hughes for path in dfs_base_table( 1715*e1fe3e4aSElliott Hughes self, iter_subtables_fn=lambda paint: paint.iterPaintSubTables(colr) 1716*e1fe3e4aSElliott Hughes ): 1717*e1fe3e4aSElliott Hughes paint = path[-1].value 1718*e1fe3e4aSElliott Hughes if paint.Format == PaintFormat.PaintGlyph: 1719*e1fe3e4aSElliott Hughes transformation = reduce( 1720*e1fe3e4aSElliott Hughes Transform.transform, 1721*e1fe3e4aSElliott Hughes (st.value.getTransform() for st in path), 1722*e1fe3e4aSElliott Hughes Identity, 1723*e1fe3e4aSElliott Hughes ) 1724*e1fe3e4aSElliott Hughes glyphSet[paint.Glyph].draw(TransformPen(pen, transformation)) 1725*e1fe3e4aSElliott Hughes 1726*e1fe3e4aSElliott Hughes if pen.bounds is None: 1727*e1fe3e4aSElliott Hughes return None 1728*e1fe3e4aSElliott Hughes 1729*e1fe3e4aSElliott Hughes cb = ClipBox() 1730*e1fe3e4aSElliott Hughes cb.Format = int(ClipBoxFormat.Static) 1731*e1fe3e4aSElliott Hughes cb.xMin, cb.yMin, cb.xMax, cb.yMax = quantizeRect(pen.bounds, quantization) 1732*e1fe3e4aSElliott Hughes return cb 1733*e1fe3e4aSElliott Hughes 1734*e1fe3e4aSElliott Hughes 1735*e1fe3e4aSElliott Hughes# For each subtable format there is a class. However, we don't really distinguish 1736*e1fe3e4aSElliott Hughes# between "field name" and "format name": often these are the same. Yet there's 1737*e1fe3e4aSElliott Hughes# a whole bunch of fields with different names. The following dict is a mapping 1738*e1fe3e4aSElliott Hughes# from "format name" to "field name". _buildClasses() uses this to create a 1739*e1fe3e4aSElliott Hughes# subclass for each alternate field name. 1740*e1fe3e4aSElliott Hughes# 1741*e1fe3e4aSElliott Hughes_equivalents = { 1742*e1fe3e4aSElliott Hughes "MarkArray": ("Mark1Array",), 1743*e1fe3e4aSElliott Hughes "LangSys": ("DefaultLangSys",), 1744*e1fe3e4aSElliott Hughes "Coverage": ( 1745*e1fe3e4aSElliott Hughes "MarkCoverage", 1746*e1fe3e4aSElliott Hughes "BaseCoverage", 1747*e1fe3e4aSElliott Hughes "LigatureCoverage", 1748*e1fe3e4aSElliott Hughes "Mark1Coverage", 1749*e1fe3e4aSElliott Hughes "Mark2Coverage", 1750*e1fe3e4aSElliott Hughes "BacktrackCoverage", 1751*e1fe3e4aSElliott Hughes "InputCoverage", 1752*e1fe3e4aSElliott Hughes "LookAheadCoverage", 1753*e1fe3e4aSElliott Hughes "VertGlyphCoverage", 1754*e1fe3e4aSElliott Hughes "HorizGlyphCoverage", 1755*e1fe3e4aSElliott Hughes "TopAccentCoverage", 1756*e1fe3e4aSElliott Hughes "ExtendedShapeCoverage", 1757*e1fe3e4aSElliott Hughes "MathKernCoverage", 1758*e1fe3e4aSElliott Hughes ), 1759*e1fe3e4aSElliott Hughes "ClassDef": ( 1760*e1fe3e4aSElliott Hughes "ClassDef1", 1761*e1fe3e4aSElliott Hughes "ClassDef2", 1762*e1fe3e4aSElliott Hughes "BacktrackClassDef", 1763*e1fe3e4aSElliott Hughes "InputClassDef", 1764*e1fe3e4aSElliott Hughes "LookAheadClassDef", 1765*e1fe3e4aSElliott Hughes "GlyphClassDef", 1766*e1fe3e4aSElliott Hughes "MarkAttachClassDef", 1767*e1fe3e4aSElliott Hughes ), 1768*e1fe3e4aSElliott Hughes "Anchor": ( 1769*e1fe3e4aSElliott Hughes "EntryAnchor", 1770*e1fe3e4aSElliott Hughes "ExitAnchor", 1771*e1fe3e4aSElliott Hughes "BaseAnchor", 1772*e1fe3e4aSElliott Hughes "LigatureAnchor", 1773*e1fe3e4aSElliott Hughes "Mark2Anchor", 1774*e1fe3e4aSElliott Hughes "MarkAnchor", 1775*e1fe3e4aSElliott Hughes ), 1776*e1fe3e4aSElliott Hughes "Device": ( 1777*e1fe3e4aSElliott Hughes "XPlaDevice", 1778*e1fe3e4aSElliott Hughes "YPlaDevice", 1779*e1fe3e4aSElliott Hughes "XAdvDevice", 1780*e1fe3e4aSElliott Hughes "YAdvDevice", 1781*e1fe3e4aSElliott Hughes "XDeviceTable", 1782*e1fe3e4aSElliott Hughes "YDeviceTable", 1783*e1fe3e4aSElliott Hughes "DeviceTable", 1784*e1fe3e4aSElliott Hughes ), 1785*e1fe3e4aSElliott Hughes "Axis": ( 1786*e1fe3e4aSElliott Hughes "HorizAxis", 1787*e1fe3e4aSElliott Hughes "VertAxis", 1788*e1fe3e4aSElliott Hughes ), 1789*e1fe3e4aSElliott Hughes "MinMax": ("DefaultMinMax",), 1790*e1fe3e4aSElliott Hughes "BaseCoord": ( 1791*e1fe3e4aSElliott Hughes "MinCoord", 1792*e1fe3e4aSElliott Hughes "MaxCoord", 1793*e1fe3e4aSElliott Hughes ), 1794*e1fe3e4aSElliott Hughes "JstfLangSys": ("DefJstfLangSys",), 1795*e1fe3e4aSElliott Hughes "JstfGSUBModList": ( 1796*e1fe3e4aSElliott Hughes "ShrinkageEnableGSUB", 1797*e1fe3e4aSElliott Hughes "ShrinkageDisableGSUB", 1798*e1fe3e4aSElliott Hughes "ExtensionEnableGSUB", 1799*e1fe3e4aSElliott Hughes "ExtensionDisableGSUB", 1800*e1fe3e4aSElliott Hughes ), 1801*e1fe3e4aSElliott Hughes "JstfGPOSModList": ( 1802*e1fe3e4aSElliott Hughes "ShrinkageEnableGPOS", 1803*e1fe3e4aSElliott Hughes "ShrinkageDisableGPOS", 1804*e1fe3e4aSElliott Hughes "ExtensionEnableGPOS", 1805*e1fe3e4aSElliott Hughes "ExtensionDisableGPOS", 1806*e1fe3e4aSElliott Hughes ), 1807*e1fe3e4aSElliott Hughes "JstfMax": ( 1808*e1fe3e4aSElliott Hughes "ShrinkageJstfMax", 1809*e1fe3e4aSElliott Hughes "ExtensionJstfMax", 1810*e1fe3e4aSElliott Hughes ), 1811*e1fe3e4aSElliott Hughes "MathKern": ( 1812*e1fe3e4aSElliott Hughes "TopRightMathKern", 1813*e1fe3e4aSElliott Hughes "TopLeftMathKern", 1814*e1fe3e4aSElliott Hughes "BottomRightMathKern", 1815*e1fe3e4aSElliott Hughes "BottomLeftMathKern", 1816*e1fe3e4aSElliott Hughes ), 1817*e1fe3e4aSElliott Hughes "MathGlyphConstruction": ("VertGlyphConstruction", "HorizGlyphConstruction"), 1818*e1fe3e4aSElliott Hughes} 1819*e1fe3e4aSElliott Hughes 1820*e1fe3e4aSElliott Hughes# 1821*e1fe3e4aSElliott Hughes# OverFlow logic, to automatically create ExtensionLookups 1822*e1fe3e4aSElliott Hughes# XXX This should probably move to otBase.py 1823*e1fe3e4aSElliott Hughes# 1824*e1fe3e4aSElliott Hughes 1825*e1fe3e4aSElliott Hughes 1826*e1fe3e4aSElliott Hughesdef fixLookupOverFlows(ttf, overflowRecord): 1827*e1fe3e4aSElliott Hughes """Either the offset from the LookupList to a lookup overflowed, or 1828*e1fe3e4aSElliott Hughes an offset from a lookup to a subtable overflowed. 1829*e1fe3e4aSElliott Hughes The table layout is: 1830*e1fe3e4aSElliott Hughes GPSO/GUSB 1831*e1fe3e4aSElliott Hughes Script List 1832*e1fe3e4aSElliott Hughes Feature List 1833*e1fe3e4aSElliott Hughes LookUpList 1834*e1fe3e4aSElliott Hughes Lookup[0] and contents 1835*e1fe3e4aSElliott Hughes SubTable offset list 1836*e1fe3e4aSElliott Hughes SubTable[0] and contents 1837*e1fe3e4aSElliott Hughes ... 1838*e1fe3e4aSElliott Hughes SubTable[n] and contents 1839*e1fe3e4aSElliott Hughes ... 1840*e1fe3e4aSElliott Hughes Lookup[n] and contents 1841*e1fe3e4aSElliott Hughes SubTable offset list 1842*e1fe3e4aSElliott Hughes SubTable[0] and contents 1843*e1fe3e4aSElliott Hughes ... 1844*e1fe3e4aSElliott Hughes SubTable[n] and contents 1845*e1fe3e4aSElliott Hughes If the offset to a lookup overflowed (SubTableIndex is None) 1846*e1fe3e4aSElliott Hughes we must promote the *previous* lookup to an Extension type. 1847*e1fe3e4aSElliott Hughes If the offset from a lookup to subtable overflowed, then we must promote it 1848*e1fe3e4aSElliott Hughes to an Extension Lookup type. 1849*e1fe3e4aSElliott Hughes """ 1850*e1fe3e4aSElliott Hughes ok = 0 1851*e1fe3e4aSElliott Hughes lookupIndex = overflowRecord.LookupListIndex 1852*e1fe3e4aSElliott Hughes if overflowRecord.SubTableIndex is None: 1853*e1fe3e4aSElliott Hughes lookupIndex = lookupIndex - 1 1854*e1fe3e4aSElliott Hughes if lookupIndex < 0: 1855*e1fe3e4aSElliott Hughes return ok 1856*e1fe3e4aSElliott Hughes if overflowRecord.tableType == "GSUB": 1857*e1fe3e4aSElliott Hughes extType = 7 1858*e1fe3e4aSElliott Hughes elif overflowRecord.tableType == "GPOS": 1859*e1fe3e4aSElliott Hughes extType = 9 1860*e1fe3e4aSElliott Hughes 1861*e1fe3e4aSElliott Hughes lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup 1862*e1fe3e4aSElliott Hughes lookup = lookups[lookupIndex] 1863*e1fe3e4aSElliott Hughes # If the previous lookup is an extType, look further back. Very unlikely, but possible. 1864*e1fe3e4aSElliott Hughes while lookup.SubTable[0].__class__.LookupType == extType: 1865*e1fe3e4aSElliott Hughes lookupIndex = lookupIndex - 1 1866*e1fe3e4aSElliott Hughes if lookupIndex < 0: 1867*e1fe3e4aSElliott Hughes return ok 1868*e1fe3e4aSElliott Hughes lookup = lookups[lookupIndex] 1869*e1fe3e4aSElliott Hughes 1870*e1fe3e4aSElliott Hughes for lookupIndex in range(lookupIndex, len(lookups)): 1871*e1fe3e4aSElliott Hughes lookup = lookups[lookupIndex] 1872*e1fe3e4aSElliott Hughes if lookup.LookupType != extType: 1873*e1fe3e4aSElliott Hughes lookup.LookupType = extType 1874*e1fe3e4aSElliott Hughes for si in range(len(lookup.SubTable)): 1875*e1fe3e4aSElliott Hughes subTable = lookup.SubTable[si] 1876*e1fe3e4aSElliott Hughes extSubTableClass = lookupTypes[overflowRecord.tableType][extType] 1877*e1fe3e4aSElliott Hughes extSubTable = extSubTableClass() 1878*e1fe3e4aSElliott Hughes extSubTable.Format = 1 1879*e1fe3e4aSElliott Hughes extSubTable.ExtSubTable = subTable 1880*e1fe3e4aSElliott Hughes lookup.SubTable[si] = extSubTable 1881*e1fe3e4aSElliott Hughes ok = 1 1882*e1fe3e4aSElliott Hughes return ok 1883*e1fe3e4aSElliott Hughes 1884*e1fe3e4aSElliott Hughes 1885*e1fe3e4aSElliott Hughesdef splitMultipleSubst(oldSubTable, newSubTable, overflowRecord): 1886*e1fe3e4aSElliott Hughes ok = 1 1887*e1fe3e4aSElliott Hughes oldMapping = sorted(oldSubTable.mapping.items()) 1888*e1fe3e4aSElliott Hughes oldLen = len(oldMapping) 1889*e1fe3e4aSElliott Hughes 1890*e1fe3e4aSElliott Hughes if overflowRecord.itemName in ["Coverage", "RangeRecord"]: 1891*e1fe3e4aSElliott Hughes # Coverage table is written last. Overflow is to or within the 1892*e1fe3e4aSElliott Hughes # the coverage table. We will just cut the subtable in half. 1893*e1fe3e4aSElliott Hughes newLen = oldLen // 2 1894*e1fe3e4aSElliott Hughes 1895*e1fe3e4aSElliott Hughes elif overflowRecord.itemName == "Sequence": 1896*e1fe3e4aSElliott Hughes # We just need to back up by two items from the overflowed 1897*e1fe3e4aSElliott Hughes # Sequence index to make sure the offset to the Coverage table 1898*e1fe3e4aSElliott Hughes # doesn't overflow. 1899*e1fe3e4aSElliott Hughes newLen = overflowRecord.itemIndex - 1 1900*e1fe3e4aSElliott Hughes 1901*e1fe3e4aSElliott Hughes newSubTable.mapping = {} 1902*e1fe3e4aSElliott Hughes for i in range(newLen, oldLen): 1903*e1fe3e4aSElliott Hughes item = oldMapping[i] 1904*e1fe3e4aSElliott Hughes key = item[0] 1905*e1fe3e4aSElliott Hughes newSubTable.mapping[key] = item[1] 1906*e1fe3e4aSElliott Hughes del oldSubTable.mapping[key] 1907*e1fe3e4aSElliott Hughes 1908*e1fe3e4aSElliott Hughes return ok 1909*e1fe3e4aSElliott Hughes 1910*e1fe3e4aSElliott Hughes 1911*e1fe3e4aSElliott Hughesdef splitAlternateSubst(oldSubTable, newSubTable, overflowRecord): 1912*e1fe3e4aSElliott Hughes ok = 1 1913*e1fe3e4aSElliott Hughes if hasattr(oldSubTable, "sortCoverageLast"): 1914*e1fe3e4aSElliott Hughes newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast 1915*e1fe3e4aSElliott Hughes 1916*e1fe3e4aSElliott Hughes oldAlts = sorted(oldSubTable.alternates.items()) 1917*e1fe3e4aSElliott Hughes oldLen = len(oldAlts) 1918*e1fe3e4aSElliott Hughes 1919*e1fe3e4aSElliott Hughes if overflowRecord.itemName in ["Coverage", "RangeRecord"]: 1920*e1fe3e4aSElliott Hughes # Coverage table is written last. overflow is to or within the 1921*e1fe3e4aSElliott Hughes # the coverage table. We will just cut the subtable in half. 1922*e1fe3e4aSElliott Hughes newLen = oldLen // 2 1923*e1fe3e4aSElliott Hughes 1924*e1fe3e4aSElliott Hughes elif overflowRecord.itemName == "AlternateSet": 1925*e1fe3e4aSElliott Hughes # We just need to back up by two items 1926*e1fe3e4aSElliott Hughes # from the overflowed AlternateSet index to make sure the offset 1927*e1fe3e4aSElliott Hughes # to the Coverage table doesn't overflow. 1928*e1fe3e4aSElliott Hughes newLen = overflowRecord.itemIndex - 1 1929*e1fe3e4aSElliott Hughes 1930*e1fe3e4aSElliott Hughes newSubTable.alternates = {} 1931*e1fe3e4aSElliott Hughes for i in range(newLen, oldLen): 1932*e1fe3e4aSElliott Hughes item = oldAlts[i] 1933*e1fe3e4aSElliott Hughes key = item[0] 1934*e1fe3e4aSElliott Hughes newSubTable.alternates[key] = item[1] 1935*e1fe3e4aSElliott Hughes del oldSubTable.alternates[key] 1936*e1fe3e4aSElliott Hughes 1937*e1fe3e4aSElliott Hughes return ok 1938*e1fe3e4aSElliott Hughes 1939*e1fe3e4aSElliott Hughes 1940*e1fe3e4aSElliott Hughesdef splitLigatureSubst(oldSubTable, newSubTable, overflowRecord): 1941*e1fe3e4aSElliott Hughes ok = 1 1942*e1fe3e4aSElliott Hughes oldLigs = sorted(oldSubTable.ligatures.items()) 1943*e1fe3e4aSElliott Hughes oldLen = len(oldLigs) 1944*e1fe3e4aSElliott Hughes 1945*e1fe3e4aSElliott Hughes if overflowRecord.itemName in ["Coverage", "RangeRecord"]: 1946*e1fe3e4aSElliott Hughes # Coverage table is written last. overflow is to or within the 1947*e1fe3e4aSElliott Hughes # the coverage table. We will just cut the subtable in half. 1948*e1fe3e4aSElliott Hughes newLen = oldLen // 2 1949*e1fe3e4aSElliott Hughes 1950*e1fe3e4aSElliott Hughes elif overflowRecord.itemName == "LigatureSet": 1951*e1fe3e4aSElliott Hughes # We just need to back up by two items 1952*e1fe3e4aSElliott Hughes # from the overflowed AlternateSet index to make sure the offset 1953*e1fe3e4aSElliott Hughes # to the Coverage table doesn't overflow. 1954*e1fe3e4aSElliott Hughes newLen = overflowRecord.itemIndex - 1 1955*e1fe3e4aSElliott Hughes 1956*e1fe3e4aSElliott Hughes newSubTable.ligatures = {} 1957*e1fe3e4aSElliott Hughes for i in range(newLen, oldLen): 1958*e1fe3e4aSElliott Hughes item = oldLigs[i] 1959*e1fe3e4aSElliott Hughes key = item[0] 1960*e1fe3e4aSElliott Hughes newSubTable.ligatures[key] = item[1] 1961*e1fe3e4aSElliott Hughes del oldSubTable.ligatures[key] 1962*e1fe3e4aSElliott Hughes 1963*e1fe3e4aSElliott Hughes return ok 1964*e1fe3e4aSElliott Hughes 1965*e1fe3e4aSElliott Hughes 1966*e1fe3e4aSElliott Hughesdef splitPairPos(oldSubTable, newSubTable, overflowRecord): 1967*e1fe3e4aSElliott Hughes st = oldSubTable 1968*e1fe3e4aSElliott Hughes ok = False 1969*e1fe3e4aSElliott Hughes newSubTable.Format = oldSubTable.Format 1970*e1fe3e4aSElliott Hughes if oldSubTable.Format == 1 and len(oldSubTable.PairSet) > 1: 1971*e1fe3e4aSElliott Hughes for name in "ValueFormat1", "ValueFormat2": 1972*e1fe3e4aSElliott Hughes setattr(newSubTable, name, getattr(oldSubTable, name)) 1973*e1fe3e4aSElliott Hughes 1974*e1fe3e4aSElliott Hughes # Move top half of coverage to new subtable 1975*e1fe3e4aSElliott Hughes 1976*e1fe3e4aSElliott Hughes newSubTable.Coverage = oldSubTable.Coverage.__class__() 1977*e1fe3e4aSElliott Hughes 1978*e1fe3e4aSElliott Hughes coverage = oldSubTable.Coverage.glyphs 1979*e1fe3e4aSElliott Hughes records = oldSubTable.PairSet 1980*e1fe3e4aSElliott Hughes 1981*e1fe3e4aSElliott Hughes oldCount = len(oldSubTable.PairSet) // 2 1982*e1fe3e4aSElliott Hughes 1983*e1fe3e4aSElliott Hughes oldSubTable.Coverage.glyphs = coverage[:oldCount] 1984*e1fe3e4aSElliott Hughes oldSubTable.PairSet = records[:oldCount] 1985*e1fe3e4aSElliott Hughes 1986*e1fe3e4aSElliott Hughes newSubTable.Coverage.glyphs = coverage[oldCount:] 1987*e1fe3e4aSElliott Hughes newSubTable.PairSet = records[oldCount:] 1988*e1fe3e4aSElliott Hughes 1989*e1fe3e4aSElliott Hughes oldSubTable.PairSetCount = len(oldSubTable.PairSet) 1990*e1fe3e4aSElliott Hughes newSubTable.PairSetCount = len(newSubTable.PairSet) 1991*e1fe3e4aSElliott Hughes 1992*e1fe3e4aSElliott Hughes ok = True 1993*e1fe3e4aSElliott Hughes 1994*e1fe3e4aSElliott Hughes elif oldSubTable.Format == 2 and len(oldSubTable.Class1Record) > 1: 1995*e1fe3e4aSElliott Hughes if not hasattr(oldSubTable, "Class2Count"): 1996*e1fe3e4aSElliott Hughes oldSubTable.Class2Count = len(oldSubTable.Class1Record[0].Class2Record) 1997*e1fe3e4aSElliott Hughes for name in "Class2Count", "ClassDef2", "ValueFormat1", "ValueFormat2": 1998*e1fe3e4aSElliott Hughes setattr(newSubTable, name, getattr(oldSubTable, name)) 1999*e1fe3e4aSElliott Hughes 2000*e1fe3e4aSElliott Hughes # The two subtables will still have the same ClassDef2 and the table 2001*e1fe3e4aSElliott Hughes # sharing will still cause the sharing to overflow. As such, disable 2002*e1fe3e4aSElliott Hughes # sharing on the one that is serialized second (that's oldSubTable). 2003*e1fe3e4aSElliott Hughes oldSubTable.DontShare = True 2004*e1fe3e4aSElliott Hughes 2005*e1fe3e4aSElliott Hughes # Move top half of class numbers to new subtable 2006*e1fe3e4aSElliott Hughes 2007*e1fe3e4aSElliott Hughes newSubTable.Coverage = oldSubTable.Coverage.__class__() 2008*e1fe3e4aSElliott Hughes newSubTable.ClassDef1 = oldSubTable.ClassDef1.__class__() 2009*e1fe3e4aSElliott Hughes 2010*e1fe3e4aSElliott Hughes coverage = oldSubTable.Coverage.glyphs 2011*e1fe3e4aSElliott Hughes classDefs = oldSubTable.ClassDef1.classDefs 2012*e1fe3e4aSElliott Hughes records = oldSubTable.Class1Record 2013*e1fe3e4aSElliott Hughes 2014*e1fe3e4aSElliott Hughes oldCount = len(oldSubTable.Class1Record) // 2 2015*e1fe3e4aSElliott Hughes newGlyphs = set(k for k, v in classDefs.items() if v >= oldCount) 2016*e1fe3e4aSElliott Hughes 2017*e1fe3e4aSElliott Hughes oldSubTable.Coverage.glyphs = [g for g in coverage if g not in newGlyphs] 2018*e1fe3e4aSElliott Hughes oldSubTable.ClassDef1.classDefs = { 2019*e1fe3e4aSElliott Hughes k: v for k, v in classDefs.items() if v < oldCount 2020*e1fe3e4aSElliott Hughes } 2021*e1fe3e4aSElliott Hughes oldSubTable.Class1Record = records[:oldCount] 2022*e1fe3e4aSElliott Hughes 2023*e1fe3e4aSElliott Hughes newSubTable.Coverage.glyphs = [g for g in coverage if g in newGlyphs] 2024*e1fe3e4aSElliott Hughes newSubTable.ClassDef1.classDefs = { 2025*e1fe3e4aSElliott Hughes k: (v - oldCount) for k, v in classDefs.items() if v > oldCount 2026*e1fe3e4aSElliott Hughes } 2027*e1fe3e4aSElliott Hughes newSubTable.Class1Record = records[oldCount:] 2028*e1fe3e4aSElliott Hughes 2029*e1fe3e4aSElliott Hughes oldSubTable.Class1Count = len(oldSubTable.Class1Record) 2030*e1fe3e4aSElliott Hughes newSubTable.Class1Count = len(newSubTable.Class1Record) 2031*e1fe3e4aSElliott Hughes 2032*e1fe3e4aSElliott Hughes ok = True 2033*e1fe3e4aSElliott Hughes 2034*e1fe3e4aSElliott Hughes return ok 2035*e1fe3e4aSElliott Hughes 2036*e1fe3e4aSElliott Hughes 2037*e1fe3e4aSElliott Hughesdef splitMarkBasePos(oldSubTable, newSubTable, overflowRecord): 2038*e1fe3e4aSElliott Hughes # split half of the mark classes to the new subtable 2039*e1fe3e4aSElliott Hughes classCount = oldSubTable.ClassCount 2040*e1fe3e4aSElliott Hughes if classCount < 2: 2041*e1fe3e4aSElliott Hughes # oh well, not much left to split... 2042*e1fe3e4aSElliott Hughes return False 2043*e1fe3e4aSElliott Hughes 2044*e1fe3e4aSElliott Hughes oldClassCount = classCount // 2 2045*e1fe3e4aSElliott Hughes newClassCount = classCount - oldClassCount 2046*e1fe3e4aSElliott Hughes 2047*e1fe3e4aSElliott Hughes oldMarkCoverage, oldMarkRecords = [], [] 2048*e1fe3e4aSElliott Hughes newMarkCoverage, newMarkRecords = [], [] 2049*e1fe3e4aSElliott Hughes for glyphName, markRecord in zip( 2050*e1fe3e4aSElliott Hughes oldSubTable.MarkCoverage.glyphs, oldSubTable.MarkArray.MarkRecord 2051*e1fe3e4aSElliott Hughes ): 2052*e1fe3e4aSElliott Hughes if markRecord.Class < oldClassCount: 2053*e1fe3e4aSElliott Hughes oldMarkCoverage.append(glyphName) 2054*e1fe3e4aSElliott Hughes oldMarkRecords.append(markRecord) 2055*e1fe3e4aSElliott Hughes else: 2056*e1fe3e4aSElliott Hughes markRecord.Class -= oldClassCount 2057*e1fe3e4aSElliott Hughes newMarkCoverage.append(glyphName) 2058*e1fe3e4aSElliott Hughes newMarkRecords.append(markRecord) 2059*e1fe3e4aSElliott Hughes 2060*e1fe3e4aSElliott Hughes oldBaseRecords, newBaseRecords = [], [] 2061*e1fe3e4aSElliott Hughes for rec in oldSubTable.BaseArray.BaseRecord: 2062*e1fe3e4aSElliott Hughes oldBaseRecord, newBaseRecord = rec.__class__(), rec.__class__() 2063*e1fe3e4aSElliott Hughes oldBaseRecord.BaseAnchor = rec.BaseAnchor[:oldClassCount] 2064*e1fe3e4aSElliott Hughes newBaseRecord.BaseAnchor = rec.BaseAnchor[oldClassCount:] 2065*e1fe3e4aSElliott Hughes oldBaseRecords.append(oldBaseRecord) 2066*e1fe3e4aSElliott Hughes newBaseRecords.append(newBaseRecord) 2067*e1fe3e4aSElliott Hughes 2068*e1fe3e4aSElliott Hughes newSubTable.Format = oldSubTable.Format 2069*e1fe3e4aSElliott Hughes 2070*e1fe3e4aSElliott Hughes oldSubTable.MarkCoverage.glyphs = oldMarkCoverage 2071*e1fe3e4aSElliott Hughes newSubTable.MarkCoverage = oldSubTable.MarkCoverage.__class__() 2072*e1fe3e4aSElliott Hughes newSubTable.MarkCoverage.glyphs = newMarkCoverage 2073*e1fe3e4aSElliott Hughes 2074*e1fe3e4aSElliott Hughes # share the same BaseCoverage in both halves 2075*e1fe3e4aSElliott Hughes newSubTable.BaseCoverage = oldSubTable.BaseCoverage 2076*e1fe3e4aSElliott Hughes 2077*e1fe3e4aSElliott Hughes oldSubTable.ClassCount = oldClassCount 2078*e1fe3e4aSElliott Hughes newSubTable.ClassCount = newClassCount 2079*e1fe3e4aSElliott Hughes 2080*e1fe3e4aSElliott Hughes oldSubTable.MarkArray.MarkRecord = oldMarkRecords 2081*e1fe3e4aSElliott Hughes newSubTable.MarkArray = oldSubTable.MarkArray.__class__() 2082*e1fe3e4aSElliott Hughes newSubTable.MarkArray.MarkRecord = newMarkRecords 2083*e1fe3e4aSElliott Hughes 2084*e1fe3e4aSElliott Hughes oldSubTable.MarkArray.MarkCount = len(oldMarkRecords) 2085*e1fe3e4aSElliott Hughes newSubTable.MarkArray.MarkCount = len(newMarkRecords) 2086*e1fe3e4aSElliott Hughes 2087*e1fe3e4aSElliott Hughes oldSubTable.BaseArray.BaseRecord = oldBaseRecords 2088*e1fe3e4aSElliott Hughes newSubTable.BaseArray = oldSubTable.BaseArray.__class__() 2089*e1fe3e4aSElliott Hughes newSubTable.BaseArray.BaseRecord = newBaseRecords 2090*e1fe3e4aSElliott Hughes 2091*e1fe3e4aSElliott Hughes oldSubTable.BaseArray.BaseCount = len(oldBaseRecords) 2092*e1fe3e4aSElliott Hughes newSubTable.BaseArray.BaseCount = len(newBaseRecords) 2093*e1fe3e4aSElliott Hughes 2094*e1fe3e4aSElliott Hughes return True 2095*e1fe3e4aSElliott Hughes 2096*e1fe3e4aSElliott Hughes 2097*e1fe3e4aSElliott HughessplitTable = { 2098*e1fe3e4aSElliott Hughes "GSUB": { 2099*e1fe3e4aSElliott Hughes # 1: splitSingleSubst, 2100*e1fe3e4aSElliott Hughes 2: splitMultipleSubst, 2101*e1fe3e4aSElliott Hughes 3: splitAlternateSubst, 2102*e1fe3e4aSElliott Hughes 4: splitLigatureSubst, 2103*e1fe3e4aSElliott Hughes # 5: splitContextSubst, 2104*e1fe3e4aSElliott Hughes # 6: splitChainContextSubst, 2105*e1fe3e4aSElliott Hughes # 7: splitExtensionSubst, 2106*e1fe3e4aSElliott Hughes # 8: splitReverseChainSingleSubst, 2107*e1fe3e4aSElliott Hughes }, 2108*e1fe3e4aSElliott Hughes "GPOS": { 2109*e1fe3e4aSElliott Hughes # 1: splitSinglePos, 2110*e1fe3e4aSElliott Hughes 2: splitPairPos, 2111*e1fe3e4aSElliott Hughes # 3: splitCursivePos, 2112*e1fe3e4aSElliott Hughes 4: splitMarkBasePos, 2113*e1fe3e4aSElliott Hughes # 5: splitMarkLigPos, 2114*e1fe3e4aSElliott Hughes # 6: splitMarkMarkPos, 2115*e1fe3e4aSElliott Hughes # 7: splitContextPos, 2116*e1fe3e4aSElliott Hughes # 8: splitChainContextPos, 2117*e1fe3e4aSElliott Hughes # 9: splitExtensionPos, 2118*e1fe3e4aSElliott Hughes }, 2119*e1fe3e4aSElliott Hughes} 2120*e1fe3e4aSElliott Hughes 2121*e1fe3e4aSElliott Hughes 2122*e1fe3e4aSElliott Hughesdef fixSubTableOverFlows(ttf, overflowRecord): 2123*e1fe3e4aSElliott Hughes """ 2124*e1fe3e4aSElliott Hughes An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts. 2125*e1fe3e4aSElliott Hughes """ 2126*e1fe3e4aSElliott Hughes table = ttf[overflowRecord.tableType].table 2127*e1fe3e4aSElliott Hughes lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex] 2128*e1fe3e4aSElliott Hughes subIndex = overflowRecord.SubTableIndex 2129*e1fe3e4aSElliott Hughes subtable = lookup.SubTable[subIndex] 2130*e1fe3e4aSElliott Hughes 2131*e1fe3e4aSElliott Hughes # First, try not sharing anything for this subtable... 2132*e1fe3e4aSElliott Hughes if not hasattr(subtable, "DontShare"): 2133*e1fe3e4aSElliott Hughes subtable.DontShare = True 2134*e1fe3e4aSElliott Hughes return True 2135*e1fe3e4aSElliott Hughes 2136*e1fe3e4aSElliott Hughes if hasattr(subtable, "ExtSubTable"): 2137*e1fe3e4aSElliott Hughes # We split the subtable of the Extension table, and add a new Extension table 2138*e1fe3e4aSElliott Hughes # to contain the new subtable. 2139*e1fe3e4aSElliott Hughes 2140*e1fe3e4aSElliott Hughes subTableType = subtable.ExtSubTable.__class__.LookupType 2141*e1fe3e4aSElliott Hughes extSubTable = subtable 2142*e1fe3e4aSElliott Hughes subtable = extSubTable.ExtSubTable 2143*e1fe3e4aSElliott Hughes newExtSubTableClass = lookupTypes[overflowRecord.tableType][ 2144*e1fe3e4aSElliott Hughes extSubTable.__class__.LookupType 2145*e1fe3e4aSElliott Hughes ] 2146*e1fe3e4aSElliott Hughes newExtSubTable = newExtSubTableClass() 2147*e1fe3e4aSElliott Hughes newExtSubTable.Format = extSubTable.Format 2148*e1fe3e4aSElliott Hughes toInsert = newExtSubTable 2149*e1fe3e4aSElliott Hughes 2150*e1fe3e4aSElliott Hughes newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] 2151*e1fe3e4aSElliott Hughes newSubTable = newSubTableClass() 2152*e1fe3e4aSElliott Hughes newExtSubTable.ExtSubTable = newSubTable 2153*e1fe3e4aSElliott Hughes else: 2154*e1fe3e4aSElliott Hughes subTableType = subtable.__class__.LookupType 2155*e1fe3e4aSElliott Hughes newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] 2156*e1fe3e4aSElliott Hughes newSubTable = newSubTableClass() 2157*e1fe3e4aSElliott Hughes toInsert = newSubTable 2158*e1fe3e4aSElliott Hughes 2159*e1fe3e4aSElliott Hughes if hasattr(lookup, "SubTableCount"): # may not be defined yet. 2160*e1fe3e4aSElliott Hughes lookup.SubTableCount = lookup.SubTableCount + 1 2161*e1fe3e4aSElliott Hughes 2162*e1fe3e4aSElliott Hughes try: 2163*e1fe3e4aSElliott Hughes splitFunc = splitTable[overflowRecord.tableType][subTableType] 2164*e1fe3e4aSElliott Hughes except KeyError: 2165*e1fe3e4aSElliott Hughes log.error( 2166*e1fe3e4aSElliott Hughes "Don't know how to split %s lookup type %s", 2167*e1fe3e4aSElliott Hughes overflowRecord.tableType, 2168*e1fe3e4aSElliott Hughes subTableType, 2169*e1fe3e4aSElliott Hughes ) 2170*e1fe3e4aSElliott Hughes return False 2171*e1fe3e4aSElliott Hughes 2172*e1fe3e4aSElliott Hughes ok = splitFunc(subtable, newSubTable, overflowRecord) 2173*e1fe3e4aSElliott Hughes if ok: 2174*e1fe3e4aSElliott Hughes lookup.SubTable.insert(subIndex + 1, toInsert) 2175*e1fe3e4aSElliott Hughes return ok 2176*e1fe3e4aSElliott Hughes 2177*e1fe3e4aSElliott Hughes 2178*e1fe3e4aSElliott Hughes# End of OverFlow logic 2179*e1fe3e4aSElliott Hughes 2180*e1fe3e4aSElliott Hughes 2181*e1fe3e4aSElliott Hughesdef _buildClasses(): 2182*e1fe3e4aSElliott Hughes import re 2183*e1fe3e4aSElliott Hughes from .otData import otData 2184*e1fe3e4aSElliott Hughes 2185*e1fe3e4aSElliott Hughes formatPat = re.compile(r"([A-Za-z0-9]+)Format(\d+)$") 2186*e1fe3e4aSElliott Hughes namespace = globals() 2187*e1fe3e4aSElliott Hughes 2188*e1fe3e4aSElliott Hughes # populate module with classes 2189*e1fe3e4aSElliott Hughes for name, table in otData: 2190*e1fe3e4aSElliott Hughes baseClass = BaseTable 2191*e1fe3e4aSElliott Hughes m = formatPat.match(name) 2192*e1fe3e4aSElliott Hughes if m: 2193*e1fe3e4aSElliott Hughes # XxxFormatN subtable, we only add the "base" table 2194*e1fe3e4aSElliott Hughes name = m.group(1) 2195*e1fe3e4aSElliott Hughes # the first row of a format-switching otData table describes the Format; 2196*e1fe3e4aSElliott Hughes # the first column defines the type of the Format field. 2197*e1fe3e4aSElliott Hughes # Currently this can be either 'uint16' or 'uint8'. 2198*e1fe3e4aSElliott Hughes formatType = table[0][0] 2199*e1fe3e4aSElliott Hughes baseClass = getFormatSwitchingBaseTableClass(formatType) 2200*e1fe3e4aSElliott Hughes if name not in namespace: 2201*e1fe3e4aSElliott Hughes # the class doesn't exist yet, so the base implementation is used. 2202*e1fe3e4aSElliott Hughes cls = type(name, (baseClass,), {}) 2203*e1fe3e4aSElliott Hughes if name in ("GSUB", "GPOS"): 2204*e1fe3e4aSElliott Hughes cls.DontShare = True 2205*e1fe3e4aSElliott Hughes namespace[name] = cls 2206*e1fe3e4aSElliott Hughes 2207*e1fe3e4aSElliott Hughes # link Var{Table} <-> {Table} (e.g. ColorStop <-> VarColorStop, etc.) 2208*e1fe3e4aSElliott Hughes for name, _ in otData: 2209*e1fe3e4aSElliott Hughes if name.startswith("Var") and len(name) > 3 and name[3:] in namespace: 2210*e1fe3e4aSElliott Hughes varType = namespace[name] 2211*e1fe3e4aSElliott Hughes noVarType = namespace[name[3:]] 2212*e1fe3e4aSElliott Hughes varType.NoVarType = noVarType 2213*e1fe3e4aSElliott Hughes noVarType.VarType = varType 2214*e1fe3e4aSElliott Hughes 2215*e1fe3e4aSElliott Hughes for base, alts in _equivalents.items(): 2216*e1fe3e4aSElliott Hughes base = namespace[base] 2217*e1fe3e4aSElliott Hughes for alt in alts: 2218*e1fe3e4aSElliott Hughes namespace[alt] = base 2219*e1fe3e4aSElliott Hughes 2220*e1fe3e4aSElliott Hughes global lookupTypes 2221*e1fe3e4aSElliott Hughes lookupTypes = { 2222*e1fe3e4aSElliott Hughes "GSUB": { 2223*e1fe3e4aSElliott Hughes 1: SingleSubst, 2224*e1fe3e4aSElliott Hughes 2: MultipleSubst, 2225*e1fe3e4aSElliott Hughes 3: AlternateSubst, 2226*e1fe3e4aSElliott Hughes 4: LigatureSubst, 2227*e1fe3e4aSElliott Hughes 5: ContextSubst, 2228*e1fe3e4aSElliott Hughes 6: ChainContextSubst, 2229*e1fe3e4aSElliott Hughes 7: ExtensionSubst, 2230*e1fe3e4aSElliott Hughes 8: ReverseChainSingleSubst, 2231*e1fe3e4aSElliott Hughes }, 2232*e1fe3e4aSElliott Hughes "GPOS": { 2233*e1fe3e4aSElliott Hughes 1: SinglePos, 2234*e1fe3e4aSElliott Hughes 2: PairPos, 2235*e1fe3e4aSElliott Hughes 3: CursivePos, 2236*e1fe3e4aSElliott Hughes 4: MarkBasePos, 2237*e1fe3e4aSElliott Hughes 5: MarkLigPos, 2238*e1fe3e4aSElliott Hughes 6: MarkMarkPos, 2239*e1fe3e4aSElliott Hughes 7: ContextPos, 2240*e1fe3e4aSElliott Hughes 8: ChainContextPos, 2241*e1fe3e4aSElliott Hughes 9: ExtensionPos, 2242*e1fe3e4aSElliott Hughes }, 2243*e1fe3e4aSElliott Hughes "mort": { 2244*e1fe3e4aSElliott Hughes 4: NoncontextualMorph, 2245*e1fe3e4aSElliott Hughes }, 2246*e1fe3e4aSElliott Hughes "morx": { 2247*e1fe3e4aSElliott Hughes 0: RearrangementMorph, 2248*e1fe3e4aSElliott Hughes 1: ContextualMorph, 2249*e1fe3e4aSElliott Hughes 2: LigatureMorph, 2250*e1fe3e4aSElliott Hughes # 3: Reserved, 2251*e1fe3e4aSElliott Hughes 4: NoncontextualMorph, 2252*e1fe3e4aSElliott Hughes 5: InsertionMorph, 2253*e1fe3e4aSElliott Hughes }, 2254*e1fe3e4aSElliott Hughes } 2255*e1fe3e4aSElliott Hughes lookupTypes["JSTF"] = lookupTypes["GPOS"] # JSTF contains GPOS 2256*e1fe3e4aSElliott Hughes for lookupEnum in lookupTypes.values(): 2257*e1fe3e4aSElliott Hughes for enum, cls in lookupEnum.items(): 2258*e1fe3e4aSElliott Hughes cls.LookupType = enum 2259*e1fe3e4aSElliott Hughes 2260*e1fe3e4aSElliott Hughes global featureParamTypes 2261*e1fe3e4aSElliott Hughes featureParamTypes = { 2262*e1fe3e4aSElliott Hughes "size": FeatureParamsSize, 2263*e1fe3e4aSElliott Hughes } 2264*e1fe3e4aSElliott Hughes for i in range(1, 20 + 1): 2265*e1fe3e4aSElliott Hughes featureParamTypes["ss%02d" % i] = FeatureParamsStylisticSet 2266*e1fe3e4aSElliott Hughes for i in range(1, 99 + 1): 2267*e1fe3e4aSElliott Hughes featureParamTypes["cv%02d" % i] = FeatureParamsCharacterVariants 2268*e1fe3e4aSElliott Hughes 2269*e1fe3e4aSElliott Hughes # add converters to classes 2270*e1fe3e4aSElliott Hughes from .otConverters import buildConverters 2271*e1fe3e4aSElliott Hughes 2272*e1fe3e4aSElliott Hughes for name, table in otData: 2273*e1fe3e4aSElliott Hughes m = formatPat.match(name) 2274*e1fe3e4aSElliott Hughes if m: 2275*e1fe3e4aSElliott Hughes # XxxFormatN subtable, add converter to "base" table 2276*e1fe3e4aSElliott Hughes name, format = m.groups() 2277*e1fe3e4aSElliott Hughes format = int(format) 2278*e1fe3e4aSElliott Hughes cls = namespace[name] 2279*e1fe3e4aSElliott Hughes if not hasattr(cls, "converters"): 2280*e1fe3e4aSElliott Hughes cls.converters = {} 2281*e1fe3e4aSElliott Hughes cls.convertersByName = {} 2282*e1fe3e4aSElliott Hughes converters, convertersByName = buildConverters(table[1:], namespace) 2283*e1fe3e4aSElliott Hughes cls.converters[format] = converters 2284*e1fe3e4aSElliott Hughes cls.convertersByName[format] = convertersByName 2285*e1fe3e4aSElliott Hughes # XXX Add staticSize? 2286*e1fe3e4aSElliott Hughes else: 2287*e1fe3e4aSElliott Hughes cls = namespace[name] 2288*e1fe3e4aSElliott Hughes cls.converters, cls.convertersByName = buildConverters(table, namespace) 2289*e1fe3e4aSElliott Hughes # XXX Add staticSize? 2290*e1fe3e4aSElliott Hughes 2291*e1fe3e4aSElliott Hughes 2292*e1fe3e4aSElliott Hughes_buildClasses() 2293*e1fe3e4aSElliott Hughes 2294*e1fe3e4aSElliott Hughes 2295*e1fe3e4aSElliott Hughesdef _getGlyphsFromCoverageTable(coverage): 2296*e1fe3e4aSElliott Hughes if coverage is None: 2297*e1fe3e4aSElliott Hughes # empty coverage table 2298*e1fe3e4aSElliott Hughes return [] 2299*e1fe3e4aSElliott Hughes else: 2300*e1fe3e4aSElliott Hughes return coverage.glyphs 2301