1*e1fe3e4aSElliott Hughesfrom fontTools.misc.fixedTools import ( 2*e1fe3e4aSElliott Hughes fixedToFloat as fi2fl, 3*e1fe3e4aSElliott Hughes floatToFixed as fl2fi, 4*e1fe3e4aSElliott Hughes floatToFixedToStr as fl2str, 5*e1fe3e4aSElliott Hughes strToFixedToFloat as str2fl, 6*e1fe3e4aSElliott Hughes ensureVersionIsLong as fi2ve, 7*e1fe3e4aSElliott Hughes versionToFixed as ve2fi, 8*e1fe3e4aSElliott Hughes) 9*e1fe3e4aSElliott Hughesfrom fontTools.misc.roundTools import nearestMultipleShortestRepr, otRound 10*e1fe3e4aSElliott Hughesfrom fontTools.misc.textTools import bytesjoin, tobytes, tostr, pad, safeEval 11*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import getSearchRange 12*e1fe3e4aSElliott Hughesfrom .otBase import ( 13*e1fe3e4aSElliott Hughes CountReference, 14*e1fe3e4aSElliott Hughes FormatSwitchingBaseTable, 15*e1fe3e4aSElliott Hughes OTTableReader, 16*e1fe3e4aSElliott Hughes OTTableWriter, 17*e1fe3e4aSElliott Hughes ValueRecordFactory, 18*e1fe3e4aSElliott Hughes) 19*e1fe3e4aSElliott Hughesfrom .otTables import ( 20*e1fe3e4aSElliott Hughes lookupTypes, 21*e1fe3e4aSElliott Hughes AATStateTable, 22*e1fe3e4aSElliott Hughes AATState, 23*e1fe3e4aSElliott Hughes AATAction, 24*e1fe3e4aSElliott Hughes ContextualMorphAction, 25*e1fe3e4aSElliott Hughes LigatureMorphAction, 26*e1fe3e4aSElliott Hughes InsertionMorphAction, 27*e1fe3e4aSElliott Hughes MorxSubtable, 28*e1fe3e4aSElliott Hughes ExtendMode as _ExtendMode, 29*e1fe3e4aSElliott Hughes CompositeMode as _CompositeMode, 30*e1fe3e4aSElliott Hughes NO_VARIATION_INDEX, 31*e1fe3e4aSElliott Hughes) 32*e1fe3e4aSElliott Hughesfrom itertools import zip_longest 33*e1fe3e4aSElliott Hughesfrom functools import partial 34*e1fe3e4aSElliott Hughesimport re 35*e1fe3e4aSElliott Hughesimport struct 36*e1fe3e4aSElliott Hughesfrom typing import Optional 37*e1fe3e4aSElliott Hughesimport logging 38*e1fe3e4aSElliott Hughes 39*e1fe3e4aSElliott Hughes 40*e1fe3e4aSElliott Hugheslog = logging.getLogger(__name__) 41*e1fe3e4aSElliott Hughesistuple = lambda t: isinstance(t, tuple) 42*e1fe3e4aSElliott Hughes 43*e1fe3e4aSElliott Hughes 44*e1fe3e4aSElliott Hughesdef buildConverters(tableSpec, tableNamespace): 45*e1fe3e4aSElliott Hughes """Given a table spec from otData.py, build a converter object for each 46*e1fe3e4aSElliott Hughes field of the table. This is called for each table in otData.py, and 47*e1fe3e4aSElliott Hughes the results are assigned to the corresponding class in otTables.py.""" 48*e1fe3e4aSElliott Hughes converters = [] 49*e1fe3e4aSElliott Hughes convertersByName = {} 50*e1fe3e4aSElliott Hughes for tp, name, repeat, aux, descr in tableSpec: 51*e1fe3e4aSElliott Hughes tableName = name 52*e1fe3e4aSElliott Hughes if name.startswith("ValueFormat"): 53*e1fe3e4aSElliott Hughes assert tp == "uint16" 54*e1fe3e4aSElliott Hughes converterClass = ValueFormat 55*e1fe3e4aSElliott Hughes elif name.endswith("Count") or name in ("StructLength", "MorphType"): 56*e1fe3e4aSElliott Hughes converterClass = { 57*e1fe3e4aSElliott Hughes "uint8": ComputedUInt8, 58*e1fe3e4aSElliott Hughes "uint16": ComputedUShort, 59*e1fe3e4aSElliott Hughes "uint32": ComputedULong, 60*e1fe3e4aSElliott Hughes }[tp] 61*e1fe3e4aSElliott Hughes elif name == "SubTable": 62*e1fe3e4aSElliott Hughes converterClass = SubTable 63*e1fe3e4aSElliott Hughes elif name == "ExtSubTable": 64*e1fe3e4aSElliott Hughes converterClass = ExtSubTable 65*e1fe3e4aSElliott Hughes elif name == "SubStruct": 66*e1fe3e4aSElliott Hughes converterClass = SubStruct 67*e1fe3e4aSElliott Hughes elif name == "FeatureParams": 68*e1fe3e4aSElliott Hughes converterClass = FeatureParams 69*e1fe3e4aSElliott Hughes elif name in ("CIDGlyphMapping", "GlyphCIDMapping"): 70*e1fe3e4aSElliott Hughes converterClass = StructWithLength 71*e1fe3e4aSElliott Hughes else: 72*e1fe3e4aSElliott Hughes if not tp in converterMapping and "(" not in tp: 73*e1fe3e4aSElliott Hughes tableName = tp 74*e1fe3e4aSElliott Hughes converterClass = Struct 75*e1fe3e4aSElliott Hughes else: 76*e1fe3e4aSElliott Hughes converterClass = eval(tp, tableNamespace, converterMapping) 77*e1fe3e4aSElliott Hughes 78*e1fe3e4aSElliott Hughes conv = converterClass(name, repeat, aux, description=descr) 79*e1fe3e4aSElliott Hughes 80*e1fe3e4aSElliott Hughes if conv.tableClass: 81*e1fe3e4aSElliott Hughes # A "template" such as OffsetTo(AType) knowss the table class already 82*e1fe3e4aSElliott Hughes tableClass = conv.tableClass 83*e1fe3e4aSElliott Hughes elif tp in ("MortChain", "MortSubtable", "MorxChain"): 84*e1fe3e4aSElliott Hughes tableClass = tableNamespace.get(tp) 85*e1fe3e4aSElliott Hughes else: 86*e1fe3e4aSElliott Hughes tableClass = tableNamespace.get(tableName) 87*e1fe3e4aSElliott Hughes 88*e1fe3e4aSElliott Hughes if not conv.tableClass: 89*e1fe3e4aSElliott Hughes conv.tableClass = tableClass 90*e1fe3e4aSElliott Hughes 91*e1fe3e4aSElliott Hughes if name in ["SubTable", "ExtSubTable", "SubStruct"]: 92*e1fe3e4aSElliott Hughes conv.lookupTypes = tableNamespace["lookupTypes"] 93*e1fe3e4aSElliott Hughes # also create reverse mapping 94*e1fe3e4aSElliott Hughes for t in conv.lookupTypes.values(): 95*e1fe3e4aSElliott Hughes for cls in t.values(): 96*e1fe3e4aSElliott Hughes convertersByName[cls.__name__] = Table(name, repeat, aux, cls) 97*e1fe3e4aSElliott Hughes if name == "FeatureParams": 98*e1fe3e4aSElliott Hughes conv.featureParamTypes = tableNamespace["featureParamTypes"] 99*e1fe3e4aSElliott Hughes conv.defaultFeatureParams = tableNamespace["FeatureParams"] 100*e1fe3e4aSElliott Hughes for cls in conv.featureParamTypes.values(): 101*e1fe3e4aSElliott Hughes convertersByName[cls.__name__] = Table(name, repeat, aux, cls) 102*e1fe3e4aSElliott Hughes converters.append(conv) 103*e1fe3e4aSElliott Hughes assert name not in convertersByName, name 104*e1fe3e4aSElliott Hughes convertersByName[name] = conv 105*e1fe3e4aSElliott Hughes return converters, convertersByName 106*e1fe3e4aSElliott Hughes 107*e1fe3e4aSElliott Hughes 108*e1fe3e4aSElliott Hughesclass _MissingItem(tuple): 109*e1fe3e4aSElliott Hughes __slots__ = () 110*e1fe3e4aSElliott Hughes 111*e1fe3e4aSElliott Hughes 112*e1fe3e4aSElliott Hughestry: 113*e1fe3e4aSElliott Hughes from collections import UserList 114*e1fe3e4aSElliott Hughesexcept ImportError: 115*e1fe3e4aSElliott Hughes from UserList import UserList 116*e1fe3e4aSElliott Hughes 117*e1fe3e4aSElliott Hughes 118*e1fe3e4aSElliott Hughesclass _LazyList(UserList): 119*e1fe3e4aSElliott Hughes def __getslice__(self, i, j): 120*e1fe3e4aSElliott Hughes return self.__getitem__(slice(i, j)) 121*e1fe3e4aSElliott Hughes 122*e1fe3e4aSElliott Hughes def __getitem__(self, k): 123*e1fe3e4aSElliott Hughes if isinstance(k, slice): 124*e1fe3e4aSElliott Hughes indices = range(*k.indices(len(self))) 125*e1fe3e4aSElliott Hughes return [self[i] for i in indices] 126*e1fe3e4aSElliott Hughes item = self.data[k] 127*e1fe3e4aSElliott Hughes if isinstance(item, _MissingItem): 128*e1fe3e4aSElliott Hughes self.reader.seek(self.pos + item[0] * self.recordSize) 129*e1fe3e4aSElliott Hughes item = self.conv.read(self.reader, self.font, {}) 130*e1fe3e4aSElliott Hughes self.data[k] = item 131*e1fe3e4aSElliott Hughes return item 132*e1fe3e4aSElliott Hughes 133*e1fe3e4aSElliott Hughes def __add__(self, other): 134*e1fe3e4aSElliott Hughes if isinstance(other, _LazyList): 135*e1fe3e4aSElliott Hughes other = list(other) 136*e1fe3e4aSElliott Hughes elif isinstance(other, list): 137*e1fe3e4aSElliott Hughes pass 138*e1fe3e4aSElliott Hughes else: 139*e1fe3e4aSElliott Hughes return NotImplemented 140*e1fe3e4aSElliott Hughes return list(self) + other 141*e1fe3e4aSElliott Hughes 142*e1fe3e4aSElliott Hughes def __radd__(self, other): 143*e1fe3e4aSElliott Hughes if not isinstance(other, list): 144*e1fe3e4aSElliott Hughes return NotImplemented 145*e1fe3e4aSElliott Hughes return other + list(self) 146*e1fe3e4aSElliott Hughes 147*e1fe3e4aSElliott Hughes 148*e1fe3e4aSElliott Hughesclass BaseConverter(object): 149*e1fe3e4aSElliott Hughes """Base class for converter objects. Apart from the constructor, this 150*e1fe3e4aSElliott Hughes is an abstract class.""" 151*e1fe3e4aSElliott Hughes 152*e1fe3e4aSElliott Hughes def __init__(self, name, repeat, aux, tableClass=None, *, description=""): 153*e1fe3e4aSElliott Hughes self.name = name 154*e1fe3e4aSElliott Hughes self.repeat = repeat 155*e1fe3e4aSElliott Hughes self.aux = aux 156*e1fe3e4aSElliott Hughes self.tableClass = tableClass 157*e1fe3e4aSElliott Hughes self.isCount = name.endswith("Count") or name in [ 158*e1fe3e4aSElliott Hughes "DesignAxisRecordSize", 159*e1fe3e4aSElliott Hughes "ValueRecordSize", 160*e1fe3e4aSElliott Hughes ] 161*e1fe3e4aSElliott Hughes self.isLookupType = name.endswith("LookupType") or name == "MorphType" 162*e1fe3e4aSElliott Hughes self.isPropagated = name in [ 163*e1fe3e4aSElliott Hughes "ClassCount", 164*e1fe3e4aSElliott Hughes "Class2Count", 165*e1fe3e4aSElliott Hughes "FeatureTag", 166*e1fe3e4aSElliott Hughes "SettingsCount", 167*e1fe3e4aSElliott Hughes "VarRegionCount", 168*e1fe3e4aSElliott Hughes "MappingCount", 169*e1fe3e4aSElliott Hughes "RegionAxisCount", 170*e1fe3e4aSElliott Hughes "DesignAxisCount", 171*e1fe3e4aSElliott Hughes "DesignAxisRecordSize", 172*e1fe3e4aSElliott Hughes "AxisValueCount", 173*e1fe3e4aSElliott Hughes "ValueRecordSize", 174*e1fe3e4aSElliott Hughes "AxisCount", 175*e1fe3e4aSElliott Hughes "BaseGlyphRecordCount", 176*e1fe3e4aSElliott Hughes "LayerRecordCount", 177*e1fe3e4aSElliott Hughes ] 178*e1fe3e4aSElliott Hughes self.description = description 179*e1fe3e4aSElliott Hughes 180*e1fe3e4aSElliott Hughes def readArray(self, reader, font, tableDict, count): 181*e1fe3e4aSElliott Hughes """Read an array of values from the reader.""" 182*e1fe3e4aSElliott Hughes lazy = font.lazy and count > 8 183*e1fe3e4aSElliott Hughes if lazy: 184*e1fe3e4aSElliott Hughes recordSize = self.getRecordSize(reader) 185*e1fe3e4aSElliott Hughes if recordSize is NotImplemented: 186*e1fe3e4aSElliott Hughes lazy = False 187*e1fe3e4aSElliott Hughes if not lazy: 188*e1fe3e4aSElliott Hughes l = [] 189*e1fe3e4aSElliott Hughes for i in range(count): 190*e1fe3e4aSElliott Hughes l.append(self.read(reader, font, tableDict)) 191*e1fe3e4aSElliott Hughes return l 192*e1fe3e4aSElliott Hughes else: 193*e1fe3e4aSElliott Hughes l = _LazyList() 194*e1fe3e4aSElliott Hughes l.reader = reader.copy() 195*e1fe3e4aSElliott Hughes l.pos = l.reader.pos 196*e1fe3e4aSElliott Hughes l.font = font 197*e1fe3e4aSElliott Hughes l.conv = self 198*e1fe3e4aSElliott Hughes l.recordSize = recordSize 199*e1fe3e4aSElliott Hughes l.extend(_MissingItem([i]) for i in range(count)) 200*e1fe3e4aSElliott Hughes reader.advance(count * recordSize) 201*e1fe3e4aSElliott Hughes return l 202*e1fe3e4aSElliott Hughes 203*e1fe3e4aSElliott Hughes def getRecordSize(self, reader): 204*e1fe3e4aSElliott Hughes if hasattr(self, "staticSize"): 205*e1fe3e4aSElliott Hughes return self.staticSize 206*e1fe3e4aSElliott Hughes return NotImplemented 207*e1fe3e4aSElliott Hughes 208*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 209*e1fe3e4aSElliott Hughes """Read a value from the reader.""" 210*e1fe3e4aSElliott Hughes raise NotImplementedError(self) 211*e1fe3e4aSElliott Hughes 212*e1fe3e4aSElliott Hughes def writeArray(self, writer, font, tableDict, values): 213*e1fe3e4aSElliott Hughes try: 214*e1fe3e4aSElliott Hughes for i, value in enumerate(values): 215*e1fe3e4aSElliott Hughes self.write(writer, font, tableDict, value, i) 216*e1fe3e4aSElliott Hughes except Exception as e: 217*e1fe3e4aSElliott Hughes e.args = e.args + (i,) 218*e1fe3e4aSElliott Hughes raise 219*e1fe3e4aSElliott Hughes 220*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 221*e1fe3e4aSElliott Hughes """Write a value to the writer.""" 222*e1fe3e4aSElliott Hughes raise NotImplementedError(self) 223*e1fe3e4aSElliott Hughes 224*e1fe3e4aSElliott Hughes def xmlRead(self, attrs, content, font): 225*e1fe3e4aSElliott Hughes """Read a value from XML.""" 226*e1fe3e4aSElliott Hughes raise NotImplementedError(self) 227*e1fe3e4aSElliott Hughes 228*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 229*e1fe3e4aSElliott Hughes """Write a value to XML.""" 230*e1fe3e4aSElliott Hughes raise NotImplementedError(self) 231*e1fe3e4aSElliott Hughes 232*e1fe3e4aSElliott Hughes varIndexBasePlusOffsetRE = re.compile(r"VarIndexBase\s*\+\s*(\d+)") 233*e1fe3e4aSElliott Hughes 234*e1fe3e4aSElliott Hughes def getVarIndexOffset(self) -> Optional[int]: 235*e1fe3e4aSElliott Hughes """If description has `VarIndexBase + {offset}`, return the offset else None.""" 236*e1fe3e4aSElliott Hughes m = self.varIndexBasePlusOffsetRE.search(self.description) 237*e1fe3e4aSElliott Hughes if not m: 238*e1fe3e4aSElliott Hughes return None 239*e1fe3e4aSElliott Hughes return int(m.group(1)) 240*e1fe3e4aSElliott Hughes 241*e1fe3e4aSElliott Hughes 242*e1fe3e4aSElliott Hughesclass SimpleValue(BaseConverter): 243*e1fe3e4aSElliott Hughes @staticmethod 244*e1fe3e4aSElliott Hughes def toString(value): 245*e1fe3e4aSElliott Hughes return value 246*e1fe3e4aSElliott Hughes 247*e1fe3e4aSElliott Hughes @staticmethod 248*e1fe3e4aSElliott Hughes def fromString(value): 249*e1fe3e4aSElliott Hughes return value 250*e1fe3e4aSElliott Hughes 251*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 252*e1fe3e4aSElliott Hughes xmlWriter.simpletag(name, attrs + [("value", self.toString(value))]) 253*e1fe3e4aSElliott Hughes xmlWriter.newline() 254*e1fe3e4aSElliott Hughes 255*e1fe3e4aSElliott Hughes def xmlRead(self, attrs, content, font): 256*e1fe3e4aSElliott Hughes return self.fromString(attrs["value"]) 257*e1fe3e4aSElliott Hughes 258*e1fe3e4aSElliott Hughes 259*e1fe3e4aSElliott Hughesclass OptionalValue(SimpleValue): 260*e1fe3e4aSElliott Hughes DEFAULT = None 261*e1fe3e4aSElliott Hughes 262*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 263*e1fe3e4aSElliott Hughes if value != self.DEFAULT: 264*e1fe3e4aSElliott Hughes attrs.append(("value", self.toString(value))) 265*e1fe3e4aSElliott Hughes xmlWriter.simpletag(name, attrs) 266*e1fe3e4aSElliott Hughes xmlWriter.newline() 267*e1fe3e4aSElliott Hughes 268*e1fe3e4aSElliott Hughes def xmlRead(self, attrs, content, font): 269*e1fe3e4aSElliott Hughes if "value" in attrs: 270*e1fe3e4aSElliott Hughes return self.fromString(attrs["value"]) 271*e1fe3e4aSElliott Hughes return self.DEFAULT 272*e1fe3e4aSElliott Hughes 273*e1fe3e4aSElliott Hughes 274*e1fe3e4aSElliott Hughesclass IntValue(SimpleValue): 275*e1fe3e4aSElliott Hughes @staticmethod 276*e1fe3e4aSElliott Hughes def fromString(value): 277*e1fe3e4aSElliott Hughes return int(value, 0) 278*e1fe3e4aSElliott Hughes 279*e1fe3e4aSElliott Hughes 280*e1fe3e4aSElliott Hughesclass Long(IntValue): 281*e1fe3e4aSElliott Hughes staticSize = 4 282*e1fe3e4aSElliott Hughes 283*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 284*e1fe3e4aSElliott Hughes return reader.readLong() 285*e1fe3e4aSElliott Hughes 286*e1fe3e4aSElliott Hughes def readArray(self, reader, font, tableDict, count): 287*e1fe3e4aSElliott Hughes return reader.readLongArray(count) 288*e1fe3e4aSElliott Hughes 289*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 290*e1fe3e4aSElliott Hughes writer.writeLong(value) 291*e1fe3e4aSElliott Hughes 292*e1fe3e4aSElliott Hughes def writeArray(self, writer, font, tableDict, values): 293*e1fe3e4aSElliott Hughes writer.writeLongArray(values) 294*e1fe3e4aSElliott Hughes 295*e1fe3e4aSElliott Hughes 296*e1fe3e4aSElliott Hughesclass ULong(IntValue): 297*e1fe3e4aSElliott Hughes staticSize = 4 298*e1fe3e4aSElliott Hughes 299*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 300*e1fe3e4aSElliott Hughes return reader.readULong() 301*e1fe3e4aSElliott Hughes 302*e1fe3e4aSElliott Hughes def readArray(self, reader, font, tableDict, count): 303*e1fe3e4aSElliott Hughes return reader.readULongArray(count) 304*e1fe3e4aSElliott Hughes 305*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 306*e1fe3e4aSElliott Hughes writer.writeULong(value) 307*e1fe3e4aSElliott Hughes 308*e1fe3e4aSElliott Hughes def writeArray(self, writer, font, tableDict, values): 309*e1fe3e4aSElliott Hughes writer.writeULongArray(values) 310*e1fe3e4aSElliott Hughes 311*e1fe3e4aSElliott Hughes 312*e1fe3e4aSElliott Hughesclass Flags32(ULong): 313*e1fe3e4aSElliott Hughes @staticmethod 314*e1fe3e4aSElliott Hughes def toString(value): 315*e1fe3e4aSElliott Hughes return "0x%08X" % value 316*e1fe3e4aSElliott Hughes 317*e1fe3e4aSElliott Hughes 318*e1fe3e4aSElliott Hughesclass VarIndex(OptionalValue, ULong): 319*e1fe3e4aSElliott Hughes DEFAULT = NO_VARIATION_INDEX 320*e1fe3e4aSElliott Hughes 321*e1fe3e4aSElliott Hughes 322*e1fe3e4aSElliott Hughesclass Short(IntValue): 323*e1fe3e4aSElliott Hughes staticSize = 2 324*e1fe3e4aSElliott Hughes 325*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 326*e1fe3e4aSElliott Hughes return reader.readShort() 327*e1fe3e4aSElliott Hughes 328*e1fe3e4aSElliott Hughes def readArray(self, reader, font, tableDict, count): 329*e1fe3e4aSElliott Hughes return reader.readShortArray(count) 330*e1fe3e4aSElliott Hughes 331*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 332*e1fe3e4aSElliott Hughes writer.writeShort(value) 333*e1fe3e4aSElliott Hughes 334*e1fe3e4aSElliott Hughes def writeArray(self, writer, font, tableDict, values): 335*e1fe3e4aSElliott Hughes writer.writeShortArray(values) 336*e1fe3e4aSElliott Hughes 337*e1fe3e4aSElliott Hughes 338*e1fe3e4aSElliott Hughesclass UShort(IntValue): 339*e1fe3e4aSElliott Hughes staticSize = 2 340*e1fe3e4aSElliott Hughes 341*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 342*e1fe3e4aSElliott Hughes return reader.readUShort() 343*e1fe3e4aSElliott Hughes 344*e1fe3e4aSElliott Hughes def readArray(self, reader, font, tableDict, count): 345*e1fe3e4aSElliott Hughes return reader.readUShortArray(count) 346*e1fe3e4aSElliott Hughes 347*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 348*e1fe3e4aSElliott Hughes writer.writeUShort(value) 349*e1fe3e4aSElliott Hughes 350*e1fe3e4aSElliott Hughes def writeArray(self, writer, font, tableDict, values): 351*e1fe3e4aSElliott Hughes writer.writeUShortArray(values) 352*e1fe3e4aSElliott Hughes 353*e1fe3e4aSElliott Hughes 354*e1fe3e4aSElliott Hughesclass Int8(IntValue): 355*e1fe3e4aSElliott Hughes staticSize = 1 356*e1fe3e4aSElliott Hughes 357*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 358*e1fe3e4aSElliott Hughes return reader.readInt8() 359*e1fe3e4aSElliott Hughes 360*e1fe3e4aSElliott Hughes def readArray(self, reader, font, tableDict, count): 361*e1fe3e4aSElliott Hughes return reader.readInt8Array(count) 362*e1fe3e4aSElliott Hughes 363*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 364*e1fe3e4aSElliott Hughes writer.writeInt8(value) 365*e1fe3e4aSElliott Hughes 366*e1fe3e4aSElliott Hughes def writeArray(self, writer, font, tableDict, values): 367*e1fe3e4aSElliott Hughes writer.writeInt8Array(values) 368*e1fe3e4aSElliott Hughes 369*e1fe3e4aSElliott Hughes 370*e1fe3e4aSElliott Hughesclass UInt8(IntValue): 371*e1fe3e4aSElliott Hughes staticSize = 1 372*e1fe3e4aSElliott Hughes 373*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 374*e1fe3e4aSElliott Hughes return reader.readUInt8() 375*e1fe3e4aSElliott Hughes 376*e1fe3e4aSElliott Hughes def readArray(self, reader, font, tableDict, count): 377*e1fe3e4aSElliott Hughes return reader.readUInt8Array(count) 378*e1fe3e4aSElliott Hughes 379*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 380*e1fe3e4aSElliott Hughes writer.writeUInt8(value) 381*e1fe3e4aSElliott Hughes 382*e1fe3e4aSElliott Hughes def writeArray(self, writer, font, tableDict, values): 383*e1fe3e4aSElliott Hughes writer.writeUInt8Array(values) 384*e1fe3e4aSElliott Hughes 385*e1fe3e4aSElliott Hughes 386*e1fe3e4aSElliott Hughesclass UInt24(IntValue): 387*e1fe3e4aSElliott Hughes staticSize = 3 388*e1fe3e4aSElliott Hughes 389*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 390*e1fe3e4aSElliott Hughes return reader.readUInt24() 391*e1fe3e4aSElliott Hughes 392*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 393*e1fe3e4aSElliott Hughes writer.writeUInt24(value) 394*e1fe3e4aSElliott Hughes 395*e1fe3e4aSElliott Hughes 396*e1fe3e4aSElliott Hughesclass ComputedInt(IntValue): 397*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 398*e1fe3e4aSElliott Hughes if value is not None: 399*e1fe3e4aSElliott Hughes xmlWriter.comment("%s=%s" % (name, value)) 400*e1fe3e4aSElliott Hughes xmlWriter.newline() 401*e1fe3e4aSElliott Hughes 402*e1fe3e4aSElliott Hughes 403*e1fe3e4aSElliott Hughesclass ComputedUInt8(ComputedInt, UInt8): 404*e1fe3e4aSElliott Hughes pass 405*e1fe3e4aSElliott Hughes 406*e1fe3e4aSElliott Hughes 407*e1fe3e4aSElliott Hughesclass ComputedUShort(ComputedInt, UShort): 408*e1fe3e4aSElliott Hughes pass 409*e1fe3e4aSElliott Hughes 410*e1fe3e4aSElliott Hughes 411*e1fe3e4aSElliott Hughesclass ComputedULong(ComputedInt, ULong): 412*e1fe3e4aSElliott Hughes pass 413*e1fe3e4aSElliott Hughes 414*e1fe3e4aSElliott Hughes 415*e1fe3e4aSElliott Hughesclass Tag(SimpleValue): 416*e1fe3e4aSElliott Hughes staticSize = 4 417*e1fe3e4aSElliott Hughes 418*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 419*e1fe3e4aSElliott Hughes return reader.readTag() 420*e1fe3e4aSElliott Hughes 421*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 422*e1fe3e4aSElliott Hughes writer.writeTag(value) 423*e1fe3e4aSElliott Hughes 424*e1fe3e4aSElliott Hughes 425*e1fe3e4aSElliott Hughesclass GlyphID(SimpleValue): 426*e1fe3e4aSElliott Hughes staticSize = 2 427*e1fe3e4aSElliott Hughes typecode = "H" 428*e1fe3e4aSElliott Hughes 429*e1fe3e4aSElliott Hughes def readArray(self, reader, font, tableDict, count): 430*e1fe3e4aSElliott Hughes return font.getGlyphNameMany( 431*e1fe3e4aSElliott Hughes reader.readArray(self.typecode, self.staticSize, count) 432*e1fe3e4aSElliott Hughes ) 433*e1fe3e4aSElliott Hughes 434*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 435*e1fe3e4aSElliott Hughes return font.getGlyphName(reader.readValue(self.typecode, self.staticSize)) 436*e1fe3e4aSElliott Hughes 437*e1fe3e4aSElliott Hughes def writeArray(self, writer, font, tableDict, values): 438*e1fe3e4aSElliott Hughes writer.writeArray(self.typecode, font.getGlyphIDMany(values)) 439*e1fe3e4aSElliott Hughes 440*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 441*e1fe3e4aSElliott Hughes writer.writeValue(self.typecode, font.getGlyphID(value)) 442*e1fe3e4aSElliott Hughes 443*e1fe3e4aSElliott Hughes 444*e1fe3e4aSElliott Hughesclass GlyphID32(GlyphID): 445*e1fe3e4aSElliott Hughes staticSize = 4 446*e1fe3e4aSElliott Hughes typecode = "L" 447*e1fe3e4aSElliott Hughes 448*e1fe3e4aSElliott Hughes 449*e1fe3e4aSElliott Hughesclass NameID(UShort): 450*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 451*e1fe3e4aSElliott Hughes xmlWriter.simpletag(name, attrs + [("value", value)]) 452*e1fe3e4aSElliott Hughes if font and value: 453*e1fe3e4aSElliott Hughes nameTable = font.get("name") 454*e1fe3e4aSElliott Hughes if nameTable: 455*e1fe3e4aSElliott Hughes name = nameTable.getDebugName(value) 456*e1fe3e4aSElliott Hughes xmlWriter.write(" ") 457*e1fe3e4aSElliott Hughes if name: 458*e1fe3e4aSElliott Hughes xmlWriter.comment(name) 459*e1fe3e4aSElliott Hughes else: 460*e1fe3e4aSElliott Hughes xmlWriter.comment("missing from name table") 461*e1fe3e4aSElliott Hughes log.warning("name id %d missing from name table" % value) 462*e1fe3e4aSElliott Hughes xmlWriter.newline() 463*e1fe3e4aSElliott Hughes 464*e1fe3e4aSElliott Hughes 465*e1fe3e4aSElliott Hughesclass STATFlags(UShort): 466*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 467*e1fe3e4aSElliott Hughes xmlWriter.simpletag(name, attrs + [("value", value)]) 468*e1fe3e4aSElliott Hughes flags = [] 469*e1fe3e4aSElliott Hughes if value & 0x01: 470*e1fe3e4aSElliott Hughes flags.append("OlderSiblingFontAttribute") 471*e1fe3e4aSElliott Hughes if value & 0x02: 472*e1fe3e4aSElliott Hughes flags.append("ElidableAxisValueName") 473*e1fe3e4aSElliott Hughes if flags: 474*e1fe3e4aSElliott Hughes xmlWriter.write(" ") 475*e1fe3e4aSElliott Hughes xmlWriter.comment(" ".join(flags)) 476*e1fe3e4aSElliott Hughes xmlWriter.newline() 477*e1fe3e4aSElliott Hughes 478*e1fe3e4aSElliott Hughes 479*e1fe3e4aSElliott Hughesclass FloatValue(SimpleValue): 480*e1fe3e4aSElliott Hughes @staticmethod 481*e1fe3e4aSElliott Hughes def fromString(value): 482*e1fe3e4aSElliott Hughes return float(value) 483*e1fe3e4aSElliott Hughes 484*e1fe3e4aSElliott Hughes 485*e1fe3e4aSElliott Hughesclass DeciPoints(FloatValue): 486*e1fe3e4aSElliott Hughes staticSize = 2 487*e1fe3e4aSElliott Hughes 488*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 489*e1fe3e4aSElliott Hughes return reader.readUShort() / 10 490*e1fe3e4aSElliott Hughes 491*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 492*e1fe3e4aSElliott Hughes writer.writeUShort(round(value * 10)) 493*e1fe3e4aSElliott Hughes 494*e1fe3e4aSElliott Hughes 495*e1fe3e4aSElliott Hughesclass BaseFixedValue(FloatValue): 496*e1fe3e4aSElliott Hughes staticSize = NotImplemented 497*e1fe3e4aSElliott Hughes precisionBits = NotImplemented 498*e1fe3e4aSElliott Hughes readerMethod = NotImplemented 499*e1fe3e4aSElliott Hughes writerMethod = NotImplemented 500*e1fe3e4aSElliott Hughes 501*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 502*e1fe3e4aSElliott Hughes return self.fromInt(getattr(reader, self.readerMethod)()) 503*e1fe3e4aSElliott Hughes 504*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 505*e1fe3e4aSElliott Hughes getattr(writer, self.writerMethod)(self.toInt(value)) 506*e1fe3e4aSElliott Hughes 507*e1fe3e4aSElliott Hughes @classmethod 508*e1fe3e4aSElliott Hughes def fromInt(cls, value): 509*e1fe3e4aSElliott Hughes return fi2fl(value, cls.precisionBits) 510*e1fe3e4aSElliott Hughes 511*e1fe3e4aSElliott Hughes @classmethod 512*e1fe3e4aSElliott Hughes def toInt(cls, value): 513*e1fe3e4aSElliott Hughes return fl2fi(value, cls.precisionBits) 514*e1fe3e4aSElliott Hughes 515*e1fe3e4aSElliott Hughes @classmethod 516*e1fe3e4aSElliott Hughes def fromString(cls, value): 517*e1fe3e4aSElliott Hughes return str2fl(value, cls.precisionBits) 518*e1fe3e4aSElliott Hughes 519*e1fe3e4aSElliott Hughes @classmethod 520*e1fe3e4aSElliott Hughes def toString(cls, value): 521*e1fe3e4aSElliott Hughes return fl2str(value, cls.precisionBits) 522*e1fe3e4aSElliott Hughes 523*e1fe3e4aSElliott Hughes 524*e1fe3e4aSElliott Hughesclass Fixed(BaseFixedValue): 525*e1fe3e4aSElliott Hughes staticSize = 4 526*e1fe3e4aSElliott Hughes precisionBits = 16 527*e1fe3e4aSElliott Hughes readerMethod = "readLong" 528*e1fe3e4aSElliott Hughes writerMethod = "writeLong" 529*e1fe3e4aSElliott Hughes 530*e1fe3e4aSElliott Hughes 531*e1fe3e4aSElliott Hughesclass F2Dot14(BaseFixedValue): 532*e1fe3e4aSElliott Hughes staticSize = 2 533*e1fe3e4aSElliott Hughes precisionBits = 14 534*e1fe3e4aSElliott Hughes readerMethod = "readShort" 535*e1fe3e4aSElliott Hughes writerMethod = "writeShort" 536*e1fe3e4aSElliott Hughes 537*e1fe3e4aSElliott Hughes 538*e1fe3e4aSElliott Hughesclass Angle(F2Dot14): 539*e1fe3e4aSElliott Hughes # angles are specified in degrees, and encoded as F2Dot14 fractions of half 540*e1fe3e4aSElliott Hughes # circle: e.g. 1.0 => 180, -0.5 => -90, -2.0 => -360, etc. 541*e1fe3e4aSElliott Hughes bias = 0.0 542*e1fe3e4aSElliott Hughes factor = 1.0 / (1 << 14) * 180 # 0.010986328125 543*e1fe3e4aSElliott Hughes 544*e1fe3e4aSElliott Hughes @classmethod 545*e1fe3e4aSElliott Hughes def fromInt(cls, value): 546*e1fe3e4aSElliott Hughes return (super().fromInt(value) + cls.bias) * 180 547*e1fe3e4aSElliott Hughes 548*e1fe3e4aSElliott Hughes @classmethod 549*e1fe3e4aSElliott Hughes def toInt(cls, value): 550*e1fe3e4aSElliott Hughes return super().toInt((value / 180) - cls.bias) 551*e1fe3e4aSElliott Hughes 552*e1fe3e4aSElliott Hughes @classmethod 553*e1fe3e4aSElliott Hughes def fromString(cls, value): 554*e1fe3e4aSElliott Hughes # quantize to nearest multiples of minimum fixed-precision angle 555*e1fe3e4aSElliott Hughes return otRound(float(value) / cls.factor) * cls.factor 556*e1fe3e4aSElliott Hughes 557*e1fe3e4aSElliott Hughes @classmethod 558*e1fe3e4aSElliott Hughes def toString(cls, value): 559*e1fe3e4aSElliott Hughes return nearestMultipleShortestRepr(value, cls.factor) 560*e1fe3e4aSElliott Hughes 561*e1fe3e4aSElliott Hughes 562*e1fe3e4aSElliott Hughesclass BiasedAngle(Angle): 563*e1fe3e4aSElliott Hughes # A bias of 1.0 is used in the representation of start and end angles 564*e1fe3e4aSElliott Hughes # of COLRv1 PaintSweepGradients to allow for encoding +360deg 565*e1fe3e4aSElliott Hughes bias = 1.0 566*e1fe3e4aSElliott Hughes 567*e1fe3e4aSElliott Hughes 568*e1fe3e4aSElliott Hughesclass Version(SimpleValue): 569*e1fe3e4aSElliott Hughes staticSize = 4 570*e1fe3e4aSElliott Hughes 571*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 572*e1fe3e4aSElliott Hughes value = reader.readLong() 573*e1fe3e4aSElliott Hughes return value 574*e1fe3e4aSElliott Hughes 575*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 576*e1fe3e4aSElliott Hughes value = fi2ve(value) 577*e1fe3e4aSElliott Hughes writer.writeLong(value) 578*e1fe3e4aSElliott Hughes 579*e1fe3e4aSElliott Hughes @staticmethod 580*e1fe3e4aSElliott Hughes def fromString(value): 581*e1fe3e4aSElliott Hughes return ve2fi(value) 582*e1fe3e4aSElliott Hughes 583*e1fe3e4aSElliott Hughes @staticmethod 584*e1fe3e4aSElliott Hughes def toString(value): 585*e1fe3e4aSElliott Hughes return "0x%08x" % value 586*e1fe3e4aSElliott Hughes 587*e1fe3e4aSElliott Hughes @staticmethod 588*e1fe3e4aSElliott Hughes def fromFloat(v): 589*e1fe3e4aSElliott Hughes return fl2fi(v, 16) 590*e1fe3e4aSElliott Hughes 591*e1fe3e4aSElliott Hughes 592*e1fe3e4aSElliott Hughesclass Char64(SimpleValue): 593*e1fe3e4aSElliott Hughes """An ASCII string with up to 64 characters. 594*e1fe3e4aSElliott Hughes 595*e1fe3e4aSElliott Hughes Unused character positions are filled with 0x00 bytes. 596*e1fe3e4aSElliott Hughes Used in Apple AAT fonts in the `gcid` table. 597*e1fe3e4aSElliott Hughes """ 598*e1fe3e4aSElliott Hughes 599*e1fe3e4aSElliott Hughes staticSize = 64 600*e1fe3e4aSElliott Hughes 601*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 602*e1fe3e4aSElliott Hughes data = reader.readData(self.staticSize) 603*e1fe3e4aSElliott Hughes zeroPos = data.find(b"\0") 604*e1fe3e4aSElliott Hughes if zeroPos >= 0: 605*e1fe3e4aSElliott Hughes data = data[:zeroPos] 606*e1fe3e4aSElliott Hughes s = tostr(data, encoding="ascii", errors="replace") 607*e1fe3e4aSElliott Hughes if s != tostr(data, encoding="ascii", errors="ignore"): 608*e1fe3e4aSElliott Hughes log.warning('replaced non-ASCII characters in "%s"' % s) 609*e1fe3e4aSElliott Hughes return s 610*e1fe3e4aSElliott Hughes 611*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 612*e1fe3e4aSElliott Hughes data = tobytes(value, encoding="ascii", errors="replace") 613*e1fe3e4aSElliott Hughes if data != tobytes(value, encoding="ascii", errors="ignore"): 614*e1fe3e4aSElliott Hughes log.warning('replacing non-ASCII characters in "%s"' % value) 615*e1fe3e4aSElliott Hughes if len(data) > self.staticSize: 616*e1fe3e4aSElliott Hughes log.warning( 617*e1fe3e4aSElliott Hughes 'truncating overlong "%s" to %d bytes' % (value, self.staticSize) 618*e1fe3e4aSElliott Hughes ) 619*e1fe3e4aSElliott Hughes data = (data + b"\0" * self.staticSize)[: self.staticSize] 620*e1fe3e4aSElliott Hughes writer.writeData(data) 621*e1fe3e4aSElliott Hughes 622*e1fe3e4aSElliott Hughes 623*e1fe3e4aSElliott Hughesclass Struct(BaseConverter): 624*e1fe3e4aSElliott Hughes def getRecordSize(self, reader): 625*e1fe3e4aSElliott Hughes return self.tableClass and self.tableClass.getRecordSize(reader) 626*e1fe3e4aSElliott Hughes 627*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 628*e1fe3e4aSElliott Hughes table = self.tableClass() 629*e1fe3e4aSElliott Hughes table.decompile(reader, font) 630*e1fe3e4aSElliott Hughes return table 631*e1fe3e4aSElliott Hughes 632*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 633*e1fe3e4aSElliott Hughes value.compile(writer, font) 634*e1fe3e4aSElliott Hughes 635*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 636*e1fe3e4aSElliott Hughes if value is None: 637*e1fe3e4aSElliott Hughes if attrs: 638*e1fe3e4aSElliott Hughes # If there are attributes (probably index), then 639*e1fe3e4aSElliott Hughes # don't drop this even if it's NULL. It will mess 640*e1fe3e4aSElliott Hughes # up the array indices of the containing element. 641*e1fe3e4aSElliott Hughes xmlWriter.simpletag(name, attrs + [("empty", 1)]) 642*e1fe3e4aSElliott Hughes xmlWriter.newline() 643*e1fe3e4aSElliott Hughes else: 644*e1fe3e4aSElliott Hughes pass # NULL table, ignore 645*e1fe3e4aSElliott Hughes else: 646*e1fe3e4aSElliott Hughes value.toXML(xmlWriter, font, attrs, name=name) 647*e1fe3e4aSElliott Hughes 648*e1fe3e4aSElliott Hughes def xmlRead(self, attrs, content, font): 649*e1fe3e4aSElliott Hughes if "empty" in attrs and safeEval(attrs["empty"]): 650*e1fe3e4aSElliott Hughes return None 651*e1fe3e4aSElliott Hughes table = self.tableClass() 652*e1fe3e4aSElliott Hughes Format = attrs.get("Format") 653*e1fe3e4aSElliott Hughes if Format is not None: 654*e1fe3e4aSElliott Hughes table.Format = int(Format) 655*e1fe3e4aSElliott Hughes 656*e1fe3e4aSElliott Hughes noPostRead = not hasattr(table, "postRead") 657*e1fe3e4aSElliott Hughes if noPostRead: 658*e1fe3e4aSElliott Hughes # TODO Cache table.hasPropagated. 659*e1fe3e4aSElliott Hughes cleanPropagation = False 660*e1fe3e4aSElliott Hughes for conv in table.getConverters(): 661*e1fe3e4aSElliott Hughes if conv.isPropagated: 662*e1fe3e4aSElliott Hughes cleanPropagation = True 663*e1fe3e4aSElliott Hughes if not hasattr(font, "_propagator"): 664*e1fe3e4aSElliott Hughes font._propagator = {} 665*e1fe3e4aSElliott Hughes propagator = font._propagator 666*e1fe3e4aSElliott Hughes assert conv.name not in propagator, (conv.name, propagator) 667*e1fe3e4aSElliott Hughes setattr(table, conv.name, None) 668*e1fe3e4aSElliott Hughes propagator[conv.name] = CountReference(table.__dict__, conv.name) 669*e1fe3e4aSElliott Hughes 670*e1fe3e4aSElliott Hughes for element in content: 671*e1fe3e4aSElliott Hughes if isinstance(element, tuple): 672*e1fe3e4aSElliott Hughes name, attrs, content = element 673*e1fe3e4aSElliott Hughes table.fromXML(name, attrs, content, font) 674*e1fe3e4aSElliott Hughes else: 675*e1fe3e4aSElliott Hughes pass 676*e1fe3e4aSElliott Hughes 677*e1fe3e4aSElliott Hughes table.populateDefaults(propagator=getattr(font, "_propagator", None)) 678*e1fe3e4aSElliott Hughes 679*e1fe3e4aSElliott Hughes if noPostRead: 680*e1fe3e4aSElliott Hughes if cleanPropagation: 681*e1fe3e4aSElliott Hughes for conv in table.getConverters(): 682*e1fe3e4aSElliott Hughes if conv.isPropagated: 683*e1fe3e4aSElliott Hughes propagator = font._propagator 684*e1fe3e4aSElliott Hughes del propagator[conv.name] 685*e1fe3e4aSElliott Hughes if not propagator: 686*e1fe3e4aSElliott Hughes del font._propagator 687*e1fe3e4aSElliott Hughes 688*e1fe3e4aSElliott Hughes return table 689*e1fe3e4aSElliott Hughes 690*e1fe3e4aSElliott Hughes def __repr__(self): 691*e1fe3e4aSElliott Hughes return "Struct of " + repr(self.tableClass) 692*e1fe3e4aSElliott Hughes 693*e1fe3e4aSElliott Hughes 694*e1fe3e4aSElliott Hughesclass StructWithLength(Struct): 695*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 696*e1fe3e4aSElliott Hughes pos = reader.pos 697*e1fe3e4aSElliott Hughes table = self.tableClass() 698*e1fe3e4aSElliott Hughes table.decompile(reader, font) 699*e1fe3e4aSElliott Hughes reader.seek(pos + table.StructLength) 700*e1fe3e4aSElliott Hughes return table 701*e1fe3e4aSElliott Hughes 702*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 703*e1fe3e4aSElliott Hughes for convIndex, conv in enumerate(value.getConverters()): 704*e1fe3e4aSElliott Hughes if conv.name == "StructLength": 705*e1fe3e4aSElliott Hughes break 706*e1fe3e4aSElliott Hughes lengthIndex = len(writer.items) + convIndex 707*e1fe3e4aSElliott Hughes if isinstance(value, FormatSwitchingBaseTable): 708*e1fe3e4aSElliott Hughes lengthIndex += 1 # implicit Format field 709*e1fe3e4aSElliott Hughes deadbeef = {1: 0xDE, 2: 0xDEAD, 4: 0xDEADBEEF}[conv.staticSize] 710*e1fe3e4aSElliott Hughes 711*e1fe3e4aSElliott Hughes before = writer.getDataLength() 712*e1fe3e4aSElliott Hughes value.StructLength = deadbeef 713*e1fe3e4aSElliott Hughes value.compile(writer, font) 714*e1fe3e4aSElliott Hughes length = writer.getDataLength() - before 715*e1fe3e4aSElliott Hughes lengthWriter = writer.getSubWriter() 716*e1fe3e4aSElliott Hughes conv.write(lengthWriter, font, tableDict, length) 717*e1fe3e4aSElliott Hughes assert writer.items[lengthIndex] == b"\xde\xad\xbe\xef"[: conv.staticSize] 718*e1fe3e4aSElliott Hughes writer.items[lengthIndex] = lengthWriter.getAllData() 719*e1fe3e4aSElliott Hughes 720*e1fe3e4aSElliott Hughes 721*e1fe3e4aSElliott Hughesclass Table(Struct): 722*e1fe3e4aSElliott Hughes staticSize = 2 723*e1fe3e4aSElliott Hughes 724*e1fe3e4aSElliott Hughes def readOffset(self, reader): 725*e1fe3e4aSElliott Hughes return reader.readUShort() 726*e1fe3e4aSElliott Hughes 727*e1fe3e4aSElliott Hughes def writeNullOffset(self, writer): 728*e1fe3e4aSElliott Hughes writer.writeUShort(0) 729*e1fe3e4aSElliott Hughes 730*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 731*e1fe3e4aSElliott Hughes offset = self.readOffset(reader) 732*e1fe3e4aSElliott Hughes if offset == 0: 733*e1fe3e4aSElliott Hughes return None 734*e1fe3e4aSElliott Hughes table = self.tableClass() 735*e1fe3e4aSElliott Hughes reader = reader.getSubReader(offset) 736*e1fe3e4aSElliott Hughes if font.lazy: 737*e1fe3e4aSElliott Hughes table.reader = reader 738*e1fe3e4aSElliott Hughes table.font = font 739*e1fe3e4aSElliott Hughes else: 740*e1fe3e4aSElliott Hughes table.decompile(reader, font) 741*e1fe3e4aSElliott Hughes return table 742*e1fe3e4aSElliott Hughes 743*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 744*e1fe3e4aSElliott Hughes if value is None: 745*e1fe3e4aSElliott Hughes self.writeNullOffset(writer) 746*e1fe3e4aSElliott Hughes else: 747*e1fe3e4aSElliott Hughes subWriter = writer.getSubWriter() 748*e1fe3e4aSElliott Hughes subWriter.name = self.name 749*e1fe3e4aSElliott Hughes if repeatIndex is not None: 750*e1fe3e4aSElliott Hughes subWriter.repeatIndex = repeatIndex 751*e1fe3e4aSElliott Hughes writer.writeSubTable(subWriter, offsetSize=self.staticSize) 752*e1fe3e4aSElliott Hughes value.compile(subWriter, font) 753*e1fe3e4aSElliott Hughes 754*e1fe3e4aSElliott Hughes 755*e1fe3e4aSElliott Hughesclass LTable(Table): 756*e1fe3e4aSElliott Hughes staticSize = 4 757*e1fe3e4aSElliott Hughes 758*e1fe3e4aSElliott Hughes def readOffset(self, reader): 759*e1fe3e4aSElliott Hughes return reader.readULong() 760*e1fe3e4aSElliott Hughes 761*e1fe3e4aSElliott Hughes def writeNullOffset(self, writer): 762*e1fe3e4aSElliott Hughes writer.writeULong(0) 763*e1fe3e4aSElliott Hughes 764*e1fe3e4aSElliott Hughes 765*e1fe3e4aSElliott Hughes# Table pointed to by a 24-bit, 3-byte long offset 766*e1fe3e4aSElliott Hughesclass Table24(Table): 767*e1fe3e4aSElliott Hughes staticSize = 3 768*e1fe3e4aSElliott Hughes 769*e1fe3e4aSElliott Hughes def readOffset(self, reader): 770*e1fe3e4aSElliott Hughes return reader.readUInt24() 771*e1fe3e4aSElliott Hughes 772*e1fe3e4aSElliott Hughes def writeNullOffset(self, writer): 773*e1fe3e4aSElliott Hughes writer.writeUInt24(0) 774*e1fe3e4aSElliott Hughes 775*e1fe3e4aSElliott Hughes 776*e1fe3e4aSElliott Hughes# TODO Clean / merge the SubTable and SubStruct 777*e1fe3e4aSElliott Hughes 778*e1fe3e4aSElliott Hughes 779*e1fe3e4aSElliott Hughesclass SubStruct(Struct): 780*e1fe3e4aSElliott Hughes def getConverter(self, tableType, lookupType): 781*e1fe3e4aSElliott Hughes tableClass = self.lookupTypes[tableType][lookupType] 782*e1fe3e4aSElliott Hughes return self.__class__(self.name, self.repeat, self.aux, tableClass) 783*e1fe3e4aSElliott Hughes 784*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 785*e1fe3e4aSElliott Hughes super(SubStruct, self).xmlWrite(xmlWriter, font, value, None, attrs) 786*e1fe3e4aSElliott Hughes 787*e1fe3e4aSElliott Hughes 788*e1fe3e4aSElliott Hughesclass SubTable(Table): 789*e1fe3e4aSElliott Hughes def getConverter(self, tableType, lookupType): 790*e1fe3e4aSElliott Hughes tableClass = self.lookupTypes[tableType][lookupType] 791*e1fe3e4aSElliott Hughes return self.__class__(self.name, self.repeat, self.aux, tableClass) 792*e1fe3e4aSElliott Hughes 793*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 794*e1fe3e4aSElliott Hughes super(SubTable, self).xmlWrite(xmlWriter, font, value, None, attrs) 795*e1fe3e4aSElliott Hughes 796*e1fe3e4aSElliott Hughes 797*e1fe3e4aSElliott Hughesclass ExtSubTable(LTable, SubTable): 798*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 799*e1fe3e4aSElliott Hughes writer.Extension = True # actually, mere presence of the field flags it as an Ext Subtable writer. 800*e1fe3e4aSElliott Hughes Table.write(self, writer, font, tableDict, value, repeatIndex) 801*e1fe3e4aSElliott Hughes 802*e1fe3e4aSElliott Hughes 803*e1fe3e4aSElliott Hughesclass FeatureParams(Table): 804*e1fe3e4aSElliott Hughes def getConverter(self, featureTag): 805*e1fe3e4aSElliott Hughes tableClass = self.featureParamTypes.get(featureTag, self.defaultFeatureParams) 806*e1fe3e4aSElliott Hughes return self.__class__(self.name, self.repeat, self.aux, tableClass) 807*e1fe3e4aSElliott Hughes 808*e1fe3e4aSElliott Hughes 809*e1fe3e4aSElliott Hughesclass ValueFormat(IntValue): 810*e1fe3e4aSElliott Hughes staticSize = 2 811*e1fe3e4aSElliott Hughes 812*e1fe3e4aSElliott Hughes def __init__(self, name, repeat, aux, tableClass=None, *, description=""): 813*e1fe3e4aSElliott Hughes BaseConverter.__init__( 814*e1fe3e4aSElliott Hughes self, name, repeat, aux, tableClass, description=description 815*e1fe3e4aSElliott Hughes ) 816*e1fe3e4aSElliott Hughes self.which = "ValueFormat" + ("2" if name[-1] == "2" else "1") 817*e1fe3e4aSElliott Hughes 818*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 819*e1fe3e4aSElliott Hughes format = reader.readUShort() 820*e1fe3e4aSElliott Hughes reader[self.which] = ValueRecordFactory(format) 821*e1fe3e4aSElliott Hughes return format 822*e1fe3e4aSElliott Hughes 823*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, format, repeatIndex=None): 824*e1fe3e4aSElliott Hughes writer.writeUShort(format) 825*e1fe3e4aSElliott Hughes writer[self.which] = ValueRecordFactory(format) 826*e1fe3e4aSElliott Hughes 827*e1fe3e4aSElliott Hughes 828*e1fe3e4aSElliott Hughesclass ValueRecord(ValueFormat): 829*e1fe3e4aSElliott Hughes def getRecordSize(self, reader): 830*e1fe3e4aSElliott Hughes return 2 * len(reader[self.which]) 831*e1fe3e4aSElliott Hughes 832*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 833*e1fe3e4aSElliott Hughes return reader[self.which].readValueRecord(reader, font) 834*e1fe3e4aSElliott Hughes 835*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 836*e1fe3e4aSElliott Hughes writer[self.which].writeValueRecord(writer, font, value) 837*e1fe3e4aSElliott Hughes 838*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 839*e1fe3e4aSElliott Hughes if value is None: 840*e1fe3e4aSElliott Hughes pass # NULL table, ignore 841*e1fe3e4aSElliott Hughes else: 842*e1fe3e4aSElliott Hughes value.toXML(xmlWriter, font, self.name, attrs) 843*e1fe3e4aSElliott Hughes 844*e1fe3e4aSElliott Hughes def xmlRead(self, attrs, content, font): 845*e1fe3e4aSElliott Hughes from .otBase import ValueRecord 846*e1fe3e4aSElliott Hughes 847*e1fe3e4aSElliott Hughes value = ValueRecord() 848*e1fe3e4aSElliott Hughes value.fromXML(None, attrs, content, font) 849*e1fe3e4aSElliott Hughes return value 850*e1fe3e4aSElliott Hughes 851*e1fe3e4aSElliott Hughes 852*e1fe3e4aSElliott Hughesclass AATLookup(BaseConverter): 853*e1fe3e4aSElliott Hughes BIN_SEARCH_HEADER_SIZE = 10 854*e1fe3e4aSElliott Hughes 855*e1fe3e4aSElliott Hughes def __init__(self, name, repeat, aux, tableClass, *, description=""): 856*e1fe3e4aSElliott Hughes BaseConverter.__init__( 857*e1fe3e4aSElliott Hughes self, name, repeat, aux, tableClass, description=description 858*e1fe3e4aSElliott Hughes ) 859*e1fe3e4aSElliott Hughes if issubclass(self.tableClass, SimpleValue): 860*e1fe3e4aSElliott Hughes self.converter = self.tableClass(name="Value", repeat=None, aux=None) 861*e1fe3e4aSElliott Hughes else: 862*e1fe3e4aSElliott Hughes self.converter = Table( 863*e1fe3e4aSElliott Hughes name="Value", repeat=None, aux=None, tableClass=self.tableClass 864*e1fe3e4aSElliott Hughes ) 865*e1fe3e4aSElliott Hughes 866*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 867*e1fe3e4aSElliott Hughes format = reader.readUShort() 868*e1fe3e4aSElliott Hughes if format == 0: 869*e1fe3e4aSElliott Hughes return self.readFormat0(reader, font) 870*e1fe3e4aSElliott Hughes elif format == 2: 871*e1fe3e4aSElliott Hughes return self.readFormat2(reader, font) 872*e1fe3e4aSElliott Hughes elif format == 4: 873*e1fe3e4aSElliott Hughes return self.readFormat4(reader, font) 874*e1fe3e4aSElliott Hughes elif format == 6: 875*e1fe3e4aSElliott Hughes return self.readFormat6(reader, font) 876*e1fe3e4aSElliott Hughes elif format == 8: 877*e1fe3e4aSElliott Hughes return self.readFormat8(reader, font) 878*e1fe3e4aSElliott Hughes else: 879*e1fe3e4aSElliott Hughes assert False, "unsupported lookup format: %d" % format 880*e1fe3e4aSElliott Hughes 881*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 882*e1fe3e4aSElliott Hughes values = list( 883*e1fe3e4aSElliott Hughes sorted([(font.getGlyphID(glyph), val) for glyph, val in value.items()]) 884*e1fe3e4aSElliott Hughes ) 885*e1fe3e4aSElliott Hughes # TODO: Also implement format 4. 886*e1fe3e4aSElliott Hughes formats = list( 887*e1fe3e4aSElliott Hughes sorted( 888*e1fe3e4aSElliott Hughes filter( 889*e1fe3e4aSElliott Hughes None, 890*e1fe3e4aSElliott Hughes [ 891*e1fe3e4aSElliott Hughes self.buildFormat0(writer, font, values), 892*e1fe3e4aSElliott Hughes self.buildFormat2(writer, font, values), 893*e1fe3e4aSElliott Hughes self.buildFormat6(writer, font, values), 894*e1fe3e4aSElliott Hughes self.buildFormat8(writer, font, values), 895*e1fe3e4aSElliott Hughes ], 896*e1fe3e4aSElliott Hughes ) 897*e1fe3e4aSElliott Hughes ) 898*e1fe3e4aSElliott Hughes ) 899*e1fe3e4aSElliott Hughes # We use the format ID as secondary sort key to make the output 900*e1fe3e4aSElliott Hughes # deterministic when multiple formats have same encoded size. 901*e1fe3e4aSElliott Hughes dataSize, lookupFormat, writeMethod = formats[0] 902*e1fe3e4aSElliott Hughes pos = writer.getDataLength() 903*e1fe3e4aSElliott Hughes writeMethod() 904*e1fe3e4aSElliott Hughes actualSize = writer.getDataLength() - pos 905*e1fe3e4aSElliott Hughes assert ( 906*e1fe3e4aSElliott Hughes actualSize == dataSize 907*e1fe3e4aSElliott Hughes ), "AATLookup format %d claimed to write %d bytes, but wrote %d" % ( 908*e1fe3e4aSElliott Hughes lookupFormat, 909*e1fe3e4aSElliott Hughes dataSize, 910*e1fe3e4aSElliott Hughes actualSize, 911*e1fe3e4aSElliott Hughes ) 912*e1fe3e4aSElliott Hughes 913*e1fe3e4aSElliott Hughes @staticmethod 914*e1fe3e4aSElliott Hughes def writeBinSearchHeader(writer, numUnits, unitSize): 915*e1fe3e4aSElliott Hughes writer.writeUShort(unitSize) 916*e1fe3e4aSElliott Hughes writer.writeUShort(numUnits) 917*e1fe3e4aSElliott Hughes searchRange, entrySelector, rangeShift = getSearchRange( 918*e1fe3e4aSElliott Hughes n=numUnits, itemSize=unitSize 919*e1fe3e4aSElliott Hughes ) 920*e1fe3e4aSElliott Hughes writer.writeUShort(searchRange) 921*e1fe3e4aSElliott Hughes writer.writeUShort(entrySelector) 922*e1fe3e4aSElliott Hughes writer.writeUShort(rangeShift) 923*e1fe3e4aSElliott Hughes 924*e1fe3e4aSElliott Hughes def buildFormat0(self, writer, font, values): 925*e1fe3e4aSElliott Hughes numGlyphs = len(font.getGlyphOrder()) 926*e1fe3e4aSElliott Hughes if len(values) != numGlyphs: 927*e1fe3e4aSElliott Hughes return None 928*e1fe3e4aSElliott Hughes valueSize = self.converter.staticSize 929*e1fe3e4aSElliott Hughes return ( 930*e1fe3e4aSElliott Hughes 2 + numGlyphs * valueSize, 931*e1fe3e4aSElliott Hughes 0, 932*e1fe3e4aSElliott Hughes lambda: self.writeFormat0(writer, font, values), 933*e1fe3e4aSElliott Hughes ) 934*e1fe3e4aSElliott Hughes 935*e1fe3e4aSElliott Hughes def writeFormat0(self, writer, font, values): 936*e1fe3e4aSElliott Hughes writer.writeUShort(0) 937*e1fe3e4aSElliott Hughes for glyphID_, value in values: 938*e1fe3e4aSElliott Hughes self.converter.write( 939*e1fe3e4aSElliott Hughes writer, font, tableDict=None, value=value, repeatIndex=None 940*e1fe3e4aSElliott Hughes ) 941*e1fe3e4aSElliott Hughes 942*e1fe3e4aSElliott Hughes def buildFormat2(self, writer, font, values): 943*e1fe3e4aSElliott Hughes segStart, segValue = values[0] 944*e1fe3e4aSElliott Hughes segEnd = segStart 945*e1fe3e4aSElliott Hughes segments = [] 946*e1fe3e4aSElliott Hughes for glyphID, curValue in values[1:]: 947*e1fe3e4aSElliott Hughes if glyphID != segEnd + 1 or curValue != segValue: 948*e1fe3e4aSElliott Hughes segments.append((segStart, segEnd, segValue)) 949*e1fe3e4aSElliott Hughes segStart = segEnd = glyphID 950*e1fe3e4aSElliott Hughes segValue = curValue 951*e1fe3e4aSElliott Hughes else: 952*e1fe3e4aSElliott Hughes segEnd = glyphID 953*e1fe3e4aSElliott Hughes segments.append((segStart, segEnd, segValue)) 954*e1fe3e4aSElliott Hughes valueSize = self.converter.staticSize 955*e1fe3e4aSElliott Hughes numUnits, unitSize = len(segments) + 1, valueSize + 4 956*e1fe3e4aSElliott Hughes return ( 957*e1fe3e4aSElliott Hughes 2 + self.BIN_SEARCH_HEADER_SIZE + numUnits * unitSize, 958*e1fe3e4aSElliott Hughes 2, 959*e1fe3e4aSElliott Hughes lambda: self.writeFormat2(writer, font, segments), 960*e1fe3e4aSElliott Hughes ) 961*e1fe3e4aSElliott Hughes 962*e1fe3e4aSElliott Hughes def writeFormat2(self, writer, font, segments): 963*e1fe3e4aSElliott Hughes writer.writeUShort(2) 964*e1fe3e4aSElliott Hughes valueSize = self.converter.staticSize 965*e1fe3e4aSElliott Hughes numUnits, unitSize = len(segments), valueSize + 4 966*e1fe3e4aSElliott Hughes self.writeBinSearchHeader(writer, numUnits, unitSize) 967*e1fe3e4aSElliott Hughes for firstGlyph, lastGlyph, value in segments: 968*e1fe3e4aSElliott Hughes writer.writeUShort(lastGlyph) 969*e1fe3e4aSElliott Hughes writer.writeUShort(firstGlyph) 970*e1fe3e4aSElliott Hughes self.converter.write( 971*e1fe3e4aSElliott Hughes writer, font, tableDict=None, value=value, repeatIndex=None 972*e1fe3e4aSElliott Hughes ) 973*e1fe3e4aSElliott Hughes writer.writeUShort(0xFFFF) 974*e1fe3e4aSElliott Hughes writer.writeUShort(0xFFFF) 975*e1fe3e4aSElliott Hughes writer.writeData(b"\x00" * valueSize) 976*e1fe3e4aSElliott Hughes 977*e1fe3e4aSElliott Hughes def buildFormat6(self, writer, font, values): 978*e1fe3e4aSElliott Hughes valueSize = self.converter.staticSize 979*e1fe3e4aSElliott Hughes numUnits, unitSize = len(values), valueSize + 2 980*e1fe3e4aSElliott Hughes return ( 981*e1fe3e4aSElliott Hughes 2 + self.BIN_SEARCH_HEADER_SIZE + (numUnits + 1) * unitSize, 982*e1fe3e4aSElliott Hughes 6, 983*e1fe3e4aSElliott Hughes lambda: self.writeFormat6(writer, font, values), 984*e1fe3e4aSElliott Hughes ) 985*e1fe3e4aSElliott Hughes 986*e1fe3e4aSElliott Hughes def writeFormat6(self, writer, font, values): 987*e1fe3e4aSElliott Hughes writer.writeUShort(6) 988*e1fe3e4aSElliott Hughes valueSize = self.converter.staticSize 989*e1fe3e4aSElliott Hughes numUnits, unitSize = len(values), valueSize + 2 990*e1fe3e4aSElliott Hughes self.writeBinSearchHeader(writer, numUnits, unitSize) 991*e1fe3e4aSElliott Hughes for glyphID, value in values: 992*e1fe3e4aSElliott Hughes writer.writeUShort(glyphID) 993*e1fe3e4aSElliott Hughes self.converter.write( 994*e1fe3e4aSElliott Hughes writer, font, tableDict=None, value=value, repeatIndex=None 995*e1fe3e4aSElliott Hughes ) 996*e1fe3e4aSElliott Hughes writer.writeUShort(0xFFFF) 997*e1fe3e4aSElliott Hughes writer.writeData(b"\x00" * valueSize) 998*e1fe3e4aSElliott Hughes 999*e1fe3e4aSElliott Hughes def buildFormat8(self, writer, font, values): 1000*e1fe3e4aSElliott Hughes minGlyphID, maxGlyphID = values[0][0], values[-1][0] 1001*e1fe3e4aSElliott Hughes if len(values) != maxGlyphID - minGlyphID + 1: 1002*e1fe3e4aSElliott Hughes return None 1003*e1fe3e4aSElliott Hughes valueSize = self.converter.staticSize 1004*e1fe3e4aSElliott Hughes return ( 1005*e1fe3e4aSElliott Hughes 6 + len(values) * valueSize, 1006*e1fe3e4aSElliott Hughes 8, 1007*e1fe3e4aSElliott Hughes lambda: self.writeFormat8(writer, font, values), 1008*e1fe3e4aSElliott Hughes ) 1009*e1fe3e4aSElliott Hughes 1010*e1fe3e4aSElliott Hughes def writeFormat8(self, writer, font, values): 1011*e1fe3e4aSElliott Hughes firstGlyphID = values[0][0] 1012*e1fe3e4aSElliott Hughes writer.writeUShort(8) 1013*e1fe3e4aSElliott Hughes writer.writeUShort(firstGlyphID) 1014*e1fe3e4aSElliott Hughes writer.writeUShort(len(values)) 1015*e1fe3e4aSElliott Hughes for _, value in values: 1016*e1fe3e4aSElliott Hughes self.converter.write( 1017*e1fe3e4aSElliott Hughes writer, font, tableDict=None, value=value, repeatIndex=None 1018*e1fe3e4aSElliott Hughes ) 1019*e1fe3e4aSElliott Hughes 1020*e1fe3e4aSElliott Hughes def readFormat0(self, reader, font): 1021*e1fe3e4aSElliott Hughes numGlyphs = len(font.getGlyphOrder()) 1022*e1fe3e4aSElliott Hughes data = self.converter.readArray(reader, font, tableDict=None, count=numGlyphs) 1023*e1fe3e4aSElliott Hughes return {font.getGlyphName(k): value for k, value in enumerate(data)} 1024*e1fe3e4aSElliott Hughes 1025*e1fe3e4aSElliott Hughes def readFormat2(self, reader, font): 1026*e1fe3e4aSElliott Hughes mapping = {} 1027*e1fe3e4aSElliott Hughes pos = reader.pos - 2 # start of table is at UShort for format 1028*e1fe3e4aSElliott Hughes unitSize, numUnits = reader.readUShort(), reader.readUShort() 1029*e1fe3e4aSElliott Hughes assert unitSize >= 4 + self.converter.staticSize, unitSize 1030*e1fe3e4aSElliott Hughes for i in range(numUnits): 1031*e1fe3e4aSElliott Hughes reader.seek(pos + i * unitSize + 12) 1032*e1fe3e4aSElliott Hughes last = reader.readUShort() 1033*e1fe3e4aSElliott Hughes first = reader.readUShort() 1034*e1fe3e4aSElliott Hughes value = self.converter.read(reader, font, tableDict=None) 1035*e1fe3e4aSElliott Hughes if last != 0xFFFF: 1036*e1fe3e4aSElliott Hughes for k in range(first, last + 1): 1037*e1fe3e4aSElliott Hughes mapping[font.getGlyphName(k)] = value 1038*e1fe3e4aSElliott Hughes return mapping 1039*e1fe3e4aSElliott Hughes 1040*e1fe3e4aSElliott Hughes def readFormat4(self, reader, font): 1041*e1fe3e4aSElliott Hughes mapping = {} 1042*e1fe3e4aSElliott Hughes pos = reader.pos - 2 # start of table is at UShort for format 1043*e1fe3e4aSElliott Hughes unitSize = reader.readUShort() 1044*e1fe3e4aSElliott Hughes assert unitSize >= 6, unitSize 1045*e1fe3e4aSElliott Hughes for i in range(reader.readUShort()): 1046*e1fe3e4aSElliott Hughes reader.seek(pos + i * unitSize + 12) 1047*e1fe3e4aSElliott Hughes last = reader.readUShort() 1048*e1fe3e4aSElliott Hughes first = reader.readUShort() 1049*e1fe3e4aSElliott Hughes offset = reader.readUShort() 1050*e1fe3e4aSElliott Hughes if last != 0xFFFF: 1051*e1fe3e4aSElliott Hughes dataReader = reader.getSubReader(0) # relative to current position 1052*e1fe3e4aSElliott Hughes dataReader.seek(pos + offset) # relative to start of table 1053*e1fe3e4aSElliott Hughes data = self.converter.readArray( 1054*e1fe3e4aSElliott Hughes dataReader, font, tableDict=None, count=last - first + 1 1055*e1fe3e4aSElliott Hughes ) 1056*e1fe3e4aSElliott Hughes for k, v in enumerate(data): 1057*e1fe3e4aSElliott Hughes mapping[font.getGlyphName(first + k)] = v 1058*e1fe3e4aSElliott Hughes return mapping 1059*e1fe3e4aSElliott Hughes 1060*e1fe3e4aSElliott Hughes def readFormat6(self, reader, font): 1061*e1fe3e4aSElliott Hughes mapping = {} 1062*e1fe3e4aSElliott Hughes pos = reader.pos - 2 # start of table is at UShort for format 1063*e1fe3e4aSElliott Hughes unitSize = reader.readUShort() 1064*e1fe3e4aSElliott Hughes assert unitSize >= 2 + self.converter.staticSize, unitSize 1065*e1fe3e4aSElliott Hughes for i in range(reader.readUShort()): 1066*e1fe3e4aSElliott Hughes reader.seek(pos + i * unitSize + 12) 1067*e1fe3e4aSElliott Hughes glyphID = reader.readUShort() 1068*e1fe3e4aSElliott Hughes value = self.converter.read(reader, font, tableDict=None) 1069*e1fe3e4aSElliott Hughes if glyphID != 0xFFFF: 1070*e1fe3e4aSElliott Hughes mapping[font.getGlyphName(glyphID)] = value 1071*e1fe3e4aSElliott Hughes return mapping 1072*e1fe3e4aSElliott Hughes 1073*e1fe3e4aSElliott Hughes def readFormat8(self, reader, font): 1074*e1fe3e4aSElliott Hughes first = reader.readUShort() 1075*e1fe3e4aSElliott Hughes count = reader.readUShort() 1076*e1fe3e4aSElliott Hughes data = self.converter.readArray(reader, font, tableDict=None, count=count) 1077*e1fe3e4aSElliott Hughes return {font.getGlyphName(first + k): value for (k, value) in enumerate(data)} 1078*e1fe3e4aSElliott Hughes 1079*e1fe3e4aSElliott Hughes def xmlRead(self, attrs, content, font): 1080*e1fe3e4aSElliott Hughes value = {} 1081*e1fe3e4aSElliott Hughes for element in content: 1082*e1fe3e4aSElliott Hughes if isinstance(element, tuple): 1083*e1fe3e4aSElliott Hughes name, a, eltContent = element 1084*e1fe3e4aSElliott Hughes if name == "Lookup": 1085*e1fe3e4aSElliott Hughes value[a["glyph"]] = self.converter.xmlRead(a, eltContent, font) 1086*e1fe3e4aSElliott Hughes return value 1087*e1fe3e4aSElliott Hughes 1088*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 1089*e1fe3e4aSElliott Hughes xmlWriter.begintag(name, attrs) 1090*e1fe3e4aSElliott Hughes xmlWriter.newline() 1091*e1fe3e4aSElliott Hughes for glyph, value in sorted(value.items()): 1092*e1fe3e4aSElliott Hughes self.converter.xmlWrite( 1093*e1fe3e4aSElliott Hughes xmlWriter, font, value=value, name="Lookup", attrs=[("glyph", glyph)] 1094*e1fe3e4aSElliott Hughes ) 1095*e1fe3e4aSElliott Hughes xmlWriter.endtag(name) 1096*e1fe3e4aSElliott Hughes xmlWriter.newline() 1097*e1fe3e4aSElliott Hughes 1098*e1fe3e4aSElliott Hughes 1099*e1fe3e4aSElliott Hughes# The AAT 'ankr' table has an unusual structure: An offset to an AATLookup 1100*e1fe3e4aSElliott Hughes# followed by an offset to a glyph data table. Other than usual, the 1101*e1fe3e4aSElliott Hughes# offsets in the AATLookup are not relative to the beginning of 1102*e1fe3e4aSElliott Hughes# the beginning of the 'ankr' table, but relative to the glyph data table. 1103*e1fe3e4aSElliott Hughes# So, to find the anchor data for a glyph, one needs to add the offset 1104*e1fe3e4aSElliott Hughes# to the data table to the offset found in the AATLookup, and then use 1105*e1fe3e4aSElliott Hughes# the sum of these two offsets to find the actual data. 1106*e1fe3e4aSElliott Hughesclass AATLookupWithDataOffset(BaseConverter): 1107*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 1108*e1fe3e4aSElliott Hughes lookupOffset = reader.readULong() 1109*e1fe3e4aSElliott Hughes dataOffset = reader.readULong() 1110*e1fe3e4aSElliott Hughes lookupReader = reader.getSubReader(lookupOffset) 1111*e1fe3e4aSElliott Hughes lookup = AATLookup("DataOffsets", None, None, UShort) 1112*e1fe3e4aSElliott Hughes offsets = lookup.read(lookupReader, font, tableDict) 1113*e1fe3e4aSElliott Hughes result = {} 1114*e1fe3e4aSElliott Hughes for glyph, offset in offsets.items(): 1115*e1fe3e4aSElliott Hughes dataReader = reader.getSubReader(offset + dataOffset) 1116*e1fe3e4aSElliott Hughes item = self.tableClass() 1117*e1fe3e4aSElliott Hughes item.decompile(dataReader, font) 1118*e1fe3e4aSElliott Hughes result[glyph] = item 1119*e1fe3e4aSElliott Hughes return result 1120*e1fe3e4aSElliott Hughes 1121*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 1122*e1fe3e4aSElliott Hughes # We do not work with OTTableWriter sub-writers because 1123*e1fe3e4aSElliott Hughes # the offsets in our AATLookup are relative to our data 1124*e1fe3e4aSElliott Hughes # table, for which we need to provide an offset value itself. 1125*e1fe3e4aSElliott Hughes # It might have been possible to somehow make a kludge for 1126*e1fe3e4aSElliott Hughes # performing this indirect offset computation directly inside 1127*e1fe3e4aSElliott Hughes # OTTableWriter. But this would have made the internal logic 1128*e1fe3e4aSElliott Hughes # of OTTableWriter even more complex than it already is, 1129*e1fe3e4aSElliott Hughes # so we decided to roll our own offset computation for the 1130*e1fe3e4aSElliott Hughes # contents of the AATLookup and associated data table. 1131*e1fe3e4aSElliott Hughes offsetByGlyph, offsetByData, dataLen = {}, {}, 0 1132*e1fe3e4aSElliott Hughes compiledData = [] 1133*e1fe3e4aSElliott Hughes for glyph in sorted(value, key=font.getGlyphID): 1134*e1fe3e4aSElliott Hughes subWriter = OTTableWriter() 1135*e1fe3e4aSElliott Hughes value[glyph].compile(subWriter, font) 1136*e1fe3e4aSElliott Hughes data = subWriter.getAllData() 1137*e1fe3e4aSElliott Hughes offset = offsetByData.get(data, None) 1138*e1fe3e4aSElliott Hughes if offset == None: 1139*e1fe3e4aSElliott Hughes offset = dataLen 1140*e1fe3e4aSElliott Hughes dataLen = dataLen + len(data) 1141*e1fe3e4aSElliott Hughes offsetByData[data] = offset 1142*e1fe3e4aSElliott Hughes compiledData.append(data) 1143*e1fe3e4aSElliott Hughes offsetByGlyph[glyph] = offset 1144*e1fe3e4aSElliott Hughes # For calculating the offsets to our AATLookup and data table, 1145*e1fe3e4aSElliott Hughes # we can use the regular OTTableWriter infrastructure. 1146*e1fe3e4aSElliott Hughes lookupWriter = writer.getSubWriter() 1147*e1fe3e4aSElliott Hughes lookup = AATLookup("DataOffsets", None, None, UShort) 1148*e1fe3e4aSElliott Hughes lookup.write(lookupWriter, font, tableDict, offsetByGlyph, None) 1149*e1fe3e4aSElliott Hughes 1150*e1fe3e4aSElliott Hughes dataWriter = writer.getSubWriter() 1151*e1fe3e4aSElliott Hughes writer.writeSubTable(lookupWriter, offsetSize=4) 1152*e1fe3e4aSElliott Hughes writer.writeSubTable(dataWriter, offsetSize=4) 1153*e1fe3e4aSElliott Hughes for d in compiledData: 1154*e1fe3e4aSElliott Hughes dataWriter.writeData(d) 1155*e1fe3e4aSElliott Hughes 1156*e1fe3e4aSElliott Hughes def xmlRead(self, attrs, content, font): 1157*e1fe3e4aSElliott Hughes lookup = AATLookup("DataOffsets", None, None, self.tableClass) 1158*e1fe3e4aSElliott Hughes return lookup.xmlRead(attrs, content, font) 1159*e1fe3e4aSElliott Hughes 1160*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 1161*e1fe3e4aSElliott Hughes lookup = AATLookup("DataOffsets", None, None, self.tableClass) 1162*e1fe3e4aSElliott Hughes lookup.xmlWrite(xmlWriter, font, value, name, attrs) 1163*e1fe3e4aSElliott Hughes 1164*e1fe3e4aSElliott Hughes 1165*e1fe3e4aSElliott Hughesclass MorxSubtableConverter(BaseConverter): 1166*e1fe3e4aSElliott Hughes _PROCESSING_ORDERS = { 1167*e1fe3e4aSElliott Hughes # bits 30 and 28 of morx.CoverageFlags; see morx spec 1168*e1fe3e4aSElliott Hughes (False, False): "LayoutOrder", 1169*e1fe3e4aSElliott Hughes (True, False): "ReversedLayoutOrder", 1170*e1fe3e4aSElliott Hughes (False, True): "LogicalOrder", 1171*e1fe3e4aSElliott Hughes (True, True): "ReversedLogicalOrder", 1172*e1fe3e4aSElliott Hughes } 1173*e1fe3e4aSElliott Hughes 1174*e1fe3e4aSElliott Hughes _PROCESSING_ORDERS_REVERSED = {val: key for key, val in _PROCESSING_ORDERS.items()} 1175*e1fe3e4aSElliott Hughes 1176*e1fe3e4aSElliott Hughes def __init__(self, name, repeat, aux, tableClass=None, *, description=""): 1177*e1fe3e4aSElliott Hughes BaseConverter.__init__( 1178*e1fe3e4aSElliott Hughes self, name, repeat, aux, tableClass, description=description 1179*e1fe3e4aSElliott Hughes ) 1180*e1fe3e4aSElliott Hughes 1181*e1fe3e4aSElliott Hughes def _setTextDirectionFromCoverageFlags(self, flags, subtable): 1182*e1fe3e4aSElliott Hughes if (flags & 0x20) != 0: 1183*e1fe3e4aSElliott Hughes subtable.TextDirection = "Any" 1184*e1fe3e4aSElliott Hughes elif (flags & 0x80) != 0: 1185*e1fe3e4aSElliott Hughes subtable.TextDirection = "Vertical" 1186*e1fe3e4aSElliott Hughes else: 1187*e1fe3e4aSElliott Hughes subtable.TextDirection = "Horizontal" 1188*e1fe3e4aSElliott Hughes 1189*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 1190*e1fe3e4aSElliott Hughes pos = reader.pos 1191*e1fe3e4aSElliott Hughes m = MorxSubtable() 1192*e1fe3e4aSElliott Hughes m.StructLength = reader.readULong() 1193*e1fe3e4aSElliott Hughes flags = reader.readUInt8() 1194*e1fe3e4aSElliott Hughes orderKey = ((flags & 0x40) != 0, (flags & 0x10) != 0) 1195*e1fe3e4aSElliott Hughes m.ProcessingOrder = self._PROCESSING_ORDERS[orderKey] 1196*e1fe3e4aSElliott Hughes self._setTextDirectionFromCoverageFlags(flags, m) 1197*e1fe3e4aSElliott Hughes m.Reserved = reader.readUShort() 1198*e1fe3e4aSElliott Hughes m.Reserved |= (flags & 0xF) << 16 1199*e1fe3e4aSElliott Hughes m.MorphType = reader.readUInt8() 1200*e1fe3e4aSElliott Hughes m.SubFeatureFlags = reader.readULong() 1201*e1fe3e4aSElliott Hughes tableClass = lookupTypes["morx"].get(m.MorphType) 1202*e1fe3e4aSElliott Hughes if tableClass is None: 1203*e1fe3e4aSElliott Hughes assert False, "unsupported 'morx' lookup type %s" % m.MorphType 1204*e1fe3e4aSElliott Hughes # To decode AAT ligatures, we need to know the subtable size. 1205*e1fe3e4aSElliott Hughes # The easiest way to pass this along is to create a new reader 1206*e1fe3e4aSElliott Hughes # that works on just the subtable as its data. 1207*e1fe3e4aSElliott Hughes headerLength = reader.pos - pos 1208*e1fe3e4aSElliott Hughes data = reader.data[reader.pos : reader.pos + m.StructLength - headerLength] 1209*e1fe3e4aSElliott Hughes assert len(data) == m.StructLength - headerLength 1210*e1fe3e4aSElliott Hughes subReader = OTTableReader(data=data, tableTag=reader.tableTag) 1211*e1fe3e4aSElliott Hughes m.SubStruct = tableClass() 1212*e1fe3e4aSElliott Hughes m.SubStruct.decompile(subReader, font) 1213*e1fe3e4aSElliott Hughes reader.seek(pos + m.StructLength) 1214*e1fe3e4aSElliott Hughes return m 1215*e1fe3e4aSElliott Hughes 1216*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 1217*e1fe3e4aSElliott Hughes xmlWriter.begintag(name, attrs) 1218*e1fe3e4aSElliott Hughes xmlWriter.newline() 1219*e1fe3e4aSElliott Hughes xmlWriter.comment("StructLength=%d" % value.StructLength) 1220*e1fe3e4aSElliott Hughes xmlWriter.newline() 1221*e1fe3e4aSElliott Hughes xmlWriter.simpletag("TextDirection", value=value.TextDirection) 1222*e1fe3e4aSElliott Hughes xmlWriter.newline() 1223*e1fe3e4aSElliott Hughes xmlWriter.simpletag("ProcessingOrder", value=value.ProcessingOrder) 1224*e1fe3e4aSElliott Hughes xmlWriter.newline() 1225*e1fe3e4aSElliott Hughes if value.Reserved != 0: 1226*e1fe3e4aSElliott Hughes xmlWriter.simpletag("Reserved", value="0x%04x" % value.Reserved) 1227*e1fe3e4aSElliott Hughes xmlWriter.newline() 1228*e1fe3e4aSElliott Hughes xmlWriter.comment("MorphType=%d" % value.MorphType) 1229*e1fe3e4aSElliott Hughes xmlWriter.newline() 1230*e1fe3e4aSElliott Hughes xmlWriter.simpletag("SubFeatureFlags", value="0x%08x" % value.SubFeatureFlags) 1231*e1fe3e4aSElliott Hughes xmlWriter.newline() 1232*e1fe3e4aSElliott Hughes value.SubStruct.toXML(xmlWriter, font) 1233*e1fe3e4aSElliott Hughes xmlWriter.endtag(name) 1234*e1fe3e4aSElliott Hughes xmlWriter.newline() 1235*e1fe3e4aSElliott Hughes 1236*e1fe3e4aSElliott Hughes def xmlRead(self, attrs, content, font): 1237*e1fe3e4aSElliott Hughes m = MorxSubtable() 1238*e1fe3e4aSElliott Hughes covFlags = 0 1239*e1fe3e4aSElliott Hughes m.Reserved = 0 1240*e1fe3e4aSElliott Hughes for eltName, eltAttrs, eltContent in filter(istuple, content): 1241*e1fe3e4aSElliott Hughes if eltName == "CoverageFlags": 1242*e1fe3e4aSElliott Hughes # Only in XML from old versions of fonttools. 1243*e1fe3e4aSElliott Hughes covFlags = safeEval(eltAttrs["value"]) 1244*e1fe3e4aSElliott Hughes orderKey = ((covFlags & 0x40) != 0, (covFlags & 0x10) != 0) 1245*e1fe3e4aSElliott Hughes m.ProcessingOrder = self._PROCESSING_ORDERS[orderKey] 1246*e1fe3e4aSElliott Hughes self._setTextDirectionFromCoverageFlags(covFlags, m) 1247*e1fe3e4aSElliott Hughes elif eltName == "ProcessingOrder": 1248*e1fe3e4aSElliott Hughes m.ProcessingOrder = eltAttrs["value"] 1249*e1fe3e4aSElliott Hughes assert m.ProcessingOrder in self._PROCESSING_ORDERS_REVERSED, ( 1250*e1fe3e4aSElliott Hughes "unknown ProcessingOrder: %s" % m.ProcessingOrder 1251*e1fe3e4aSElliott Hughes ) 1252*e1fe3e4aSElliott Hughes elif eltName == "TextDirection": 1253*e1fe3e4aSElliott Hughes m.TextDirection = eltAttrs["value"] 1254*e1fe3e4aSElliott Hughes assert m.TextDirection in {"Horizontal", "Vertical", "Any"}, ( 1255*e1fe3e4aSElliott Hughes "unknown TextDirection %s" % m.TextDirection 1256*e1fe3e4aSElliott Hughes ) 1257*e1fe3e4aSElliott Hughes elif eltName == "Reserved": 1258*e1fe3e4aSElliott Hughes m.Reserved = safeEval(eltAttrs["value"]) 1259*e1fe3e4aSElliott Hughes elif eltName == "SubFeatureFlags": 1260*e1fe3e4aSElliott Hughes m.SubFeatureFlags = safeEval(eltAttrs["value"]) 1261*e1fe3e4aSElliott Hughes elif eltName.endswith("Morph"): 1262*e1fe3e4aSElliott Hughes m.fromXML(eltName, eltAttrs, eltContent, font) 1263*e1fe3e4aSElliott Hughes else: 1264*e1fe3e4aSElliott Hughes assert False, eltName 1265*e1fe3e4aSElliott Hughes m.Reserved = (covFlags & 0xF) << 16 | m.Reserved 1266*e1fe3e4aSElliott Hughes return m 1267*e1fe3e4aSElliott Hughes 1268*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 1269*e1fe3e4aSElliott Hughes covFlags = (value.Reserved & 0x000F0000) >> 16 1270*e1fe3e4aSElliott Hughes reverseOrder, logicalOrder = self._PROCESSING_ORDERS_REVERSED[ 1271*e1fe3e4aSElliott Hughes value.ProcessingOrder 1272*e1fe3e4aSElliott Hughes ] 1273*e1fe3e4aSElliott Hughes covFlags |= 0x80 if value.TextDirection == "Vertical" else 0 1274*e1fe3e4aSElliott Hughes covFlags |= 0x40 if reverseOrder else 0 1275*e1fe3e4aSElliott Hughes covFlags |= 0x20 if value.TextDirection == "Any" else 0 1276*e1fe3e4aSElliott Hughes covFlags |= 0x10 if logicalOrder else 0 1277*e1fe3e4aSElliott Hughes value.CoverageFlags = covFlags 1278*e1fe3e4aSElliott Hughes lengthIndex = len(writer.items) 1279*e1fe3e4aSElliott Hughes before = writer.getDataLength() 1280*e1fe3e4aSElliott Hughes value.StructLength = 0xDEADBEEF 1281*e1fe3e4aSElliott Hughes # The high nibble of value.Reserved is actuallly encoded 1282*e1fe3e4aSElliott Hughes # into coverageFlags, so we need to clear it here. 1283*e1fe3e4aSElliott Hughes origReserved = value.Reserved # including high nibble 1284*e1fe3e4aSElliott Hughes value.Reserved = value.Reserved & 0xFFFF # without high nibble 1285*e1fe3e4aSElliott Hughes value.compile(writer, font) 1286*e1fe3e4aSElliott Hughes value.Reserved = origReserved # restore original value 1287*e1fe3e4aSElliott Hughes assert writer.items[lengthIndex] == b"\xde\xad\xbe\xef" 1288*e1fe3e4aSElliott Hughes length = writer.getDataLength() - before 1289*e1fe3e4aSElliott Hughes writer.items[lengthIndex] = struct.pack(">L", length) 1290*e1fe3e4aSElliott Hughes 1291*e1fe3e4aSElliott Hughes 1292*e1fe3e4aSElliott Hughes# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html#ExtendedStateHeader 1293*e1fe3e4aSElliott Hughes# TODO: Untangle the implementation of the various lookup-specific formats. 1294*e1fe3e4aSElliott Hughesclass STXHeader(BaseConverter): 1295*e1fe3e4aSElliott Hughes def __init__(self, name, repeat, aux, tableClass, *, description=""): 1296*e1fe3e4aSElliott Hughes BaseConverter.__init__( 1297*e1fe3e4aSElliott Hughes self, name, repeat, aux, tableClass, description=description 1298*e1fe3e4aSElliott Hughes ) 1299*e1fe3e4aSElliott Hughes assert issubclass(self.tableClass, AATAction) 1300*e1fe3e4aSElliott Hughes self.classLookup = AATLookup("GlyphClasses", None, None, UShort) 1301*e1fe3e4aSElliott Hughes if issubclass(self.tableClass, ContextualMorphAction): 1302*e1fe3e4aSElliott Hughes self.perGlyphLookup = AATLookup("PerGlyphLookup", None, None, GlyphID) 1303*e1fe3e4aSElliott Hughes else: 1304*e1fe3e4aSElliott Hughes self.perGlyphLookup = None 1305*e1fe3e4aSElliott Hughes 1306*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 1307*e1fe3e4aSElliott Hughes table = AATStateTable() 1308*e1fe3e4aSElliott Hughes pos = reader.pos 1309*e1fe3e4aSElliott Hughes classTableReader = reader.getSubReader(0) 1310*e1fe3e4aSElliott Hughes stateArrayReader = reader.getSubReader(0) 1311*e1fe3e4aSElliott Hughes entryTableReader = reader.getSubReader(0) 1312*e1fe3e4aSElliott Hughes actionReader = None 1313*e1fe3e4aSElliott Hughes ligaturesReader = None 1314*e1fe3e4aSElliott Hughes table.GlyphClassCount = reader.readULong() 1315*e1fe3e4aSElliott Hughes classTableReader.seek(pos + reader.readULong()) 1316*e1fe3e4aSElliott Hughes stateArrayReader.seek(pos + reader.readULong()) 1317*e1fe3e4aSElliott Hughes entryTableReader.seek(pos + reader.readULong()) 1318*e1fe3e4aSElliott Hughes if self.perGlyphLookup is not None: 1319*e1fe3e4aSElliott Hughes perGlyphTableReader = reader.getSubReader(0) 1320*e1fe3e4aSElliott Hughes perGlyphTableReader.seek(pos + reader.readULong()) 1321*e1fe3e4aSElliott Hughes if issubclass(self.tableClass, LigatureMorphAction): 1322*e1fe3e4aSElliott Hughes actionReader = reader.getSubReader(0) 1323*e1fe3e4aSElliott Hughes actionReader.seek(pos + reader.readULong()) 1324*e1fe3e4aSElliott Hughes ligComponentReader = reader.getSubReader(0) 1325*e1fe3e4aSElliott Hughes ligComponentReader.seek(pos + reader.readULong()) 1326*e1fe3e4aSElliott Hughes ligaturesReader = reader.getSubReader(0) 1327*e1fe3e4aSElliott Hughes ligaturesReader.seek(pos + reader.readULong()) 1328*e1fe3e4aSElliott Hughes numLigComponents = (ligaturesReader.pos - ligComponentReader.pos) // 2 1329*e1fe3e4aSElliott Hughes assert numLigComponents >= 0 1330*e1fe3e4aSElliott Hughes table.LigComponents = ligComponentReader.readUShortArray(numLigComponents) 1331*e1fe3e4aSElliott Hughes table.Ligatures = self._readLigatures(ligaturesReader, font) 1332*e1fe3e4aSElliott Hughes elif issubclass(self.tableClass, InsertionMorphAction): 1333*e1fe3e4aSElliott Hughes actionReader = reader.getSubReader(0) 1334*e1fe3e4aSElliott Hughes actionReader.seek(pos + reader.readULong()) 1335*e1fe3e4aSElliott Hughes table.GlyphClasses = self.classLookup.read(classTableReader, font, tableDict) 1336*e1fe3e4aSElliott Hughes numStates = int( 1337*e1fe3e4aSElliott Hughes (entryTableReader.pos - stateArrayReader.pos) / (table.GlyphClassCount * 2) 1338*e1fe3e4aSElliott Hughes ) 1339*e1fe3e4aSElliott Hughes for stateIndex in range(numStates): 1340*e1fe3e4aSElliott Hughes state = AATState() 1341*e1fe3e4aSElliott Hughes table.States.append(state) 1342*e1fe3e4aSElliott Hughes for glyphClass in range(table.GlyphClassCount): 1343*e1fe3e4aSElliott Hughes entryIndex = stateArrayReader.readUShort() 1344*e1fe3e4aSElliott Hughes state.Transitions[glyphClass] = self._readTransition( 1345*e1fe3e4aSElliott Hughes entryTableReader, entryIndex, font, actionReader 1346*e1fe3e4aSElliott Hughes ) 1347*e1fe3e4aSElliott Hughes if self.perGlyphLookup is not None: 1348*e1fe3e4aSElliott Hughes table.PerGlyphLookups = self._readPerGlyphLookups( 1349*e1fe3e4aSElliott Hughes table, perGlyphTableReader, font 1350*e1fe3e4aSElliott Hughes ) 1351*e1fe3e4aSElliott Hughes return table 1352*e1fe3e4aSElliott Hughes 1353*e1fe3e4aSElliott Hughes def _readTransition(self, reader, entryIndex, font, actionReader): 1354*e1fe3e4aSElliott Hughes transition = self.tableClass() 1355*e1fe3e4aSElliott Hughes entryReader = reader.getSubReader( 1356*e1fe3e4aSElliott Hughes reader.pos + entryIndex * transition.staticSize 1357*e1fe3e4aSElliott Hughes ) 1358*e1fe3e4aSElliott Hughes transition.decompile(entryReader, font, actionReader) 1359*e1fe3e4aSElliott Hughes return transition 1360*e1fe3e4aSElliott Hughes 1361*e1fe3e4aSElliott Hughes def _readLigatures(self, reader, font): 1362*e1fe3e4aSElliott Hughes limit = len(reader.data) 1363*e1fe3e4aSElliott Hughes numLigatureGlyphs = (limit - reader.pos) // 2 1364*e1fe3e4aSElliott Hughes return font.getGlyphNameMany(reader.readUShortArray(numLigatureGlyphs)) 1365*e1fe3e4aSElliott Hughes 1366*e1fe3e4aSElliott Hughes def _countPerGlyphLookups(self, table): 1367*e1fe3e4aSElliott Hughes # Somewhat annoyingly, the morx table does not encode 1368*e1fe3e4aSElliott Hughes # the size of the per-glyph table. So we need to find 1369*e1fe3e4aSElliott Hughes # the maximum value that MorphActions use as index 1370*e1fe3e4aSElliott Hughes # into this table. 1371*e1fe3e4aSElliott Hughes numLookups = 0 1372*e1fe3e4aSElliott Hughes for state in table.States: 1373*e1fe3e4aSElliott Hughes for t in state.Transitions.values(): 1374*e1fe3e4aSElliott Hughes if isinstance(t, ContextualMorphAction): 1375*e1fe3e4aSElliott Hughes if t.MarkIndex != 0xFFFF: 1376*e1fe3e4aSElliott Hughes numLookups = max(numLookups, t.MarkIndex + 1) 1377*e1fe3e4aSElliott Hughes if t.CurrentIndex != 0xFFFF: 1378*e1fe3e4aSElliott Hughes numLookups = max(numLookups, t.CurrentIndex + 1) 1379*e1fe3e4aSElliott Hughes return numLookups 1380*e1fe3e4aSElliott Hughes 1381*e1fe3e4aSElliott Hughes def _readPerGlyphLookups(self, table, reader, font): 1382*e1fe3e4aSElliott Hughes pos = reader.pos 1383*e1fe3e4aSElliott Hughes lookups = [] 1384*e1fe3e4aSElliott Hughes for _ in range(self._countPerGlyphLookups(table)): 1385*e1fe3e4aSElliott Hughes lookupReader = reader.getSubReader(0) 1386*e1fe3e4aSElliott Hughes lookupReader.seek(pos + reader.readULong()) 1387*e1fe3e4aSElliott Hughes lookups.append(self.perGlyphLookup.read(lookupReader, font, {})) 1388*e1fe3e4aSElliott Hughes return lookups 1389*e1fe3e4aSElliott Hughes 1390*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 1391*e1fe3e4aSElliott Hughes glyphClassWriter = OTTableWriter() 1392*e1fe3e4aSElliott Hughes self.classLookup.write( 1393*e1fe3e4aSElliott Hughes glyphClassWriter, font, tableDict, value.GlyphClasses, repeatIndex=None 1394*e1fe3e4aSElliott Hughes ) 1395*e1fe3e4aSElliott Hughes glyphClassData = pad(glyphClassWriter.getAllData(), 2) 1396*e1fe3e4aSElliott Hughes glyphClassCount = max(value.GlyphClasses.values()) + 1 1397*e1fe3e4aSElliott Hughes glyphClassTableOffset = 16 # size of STXHeader 1398*e1fe3e4aSElliott Hughes if self.perGlyphLookup is not None: 1399*e1fe3e4aSElliott Hughes glyphClassTableOffset += 4 1400*e1fe3e4aSElliott Hughes 1401*e1fe3e4aSElliott Hughes glyphClassTableOffset += self.tableClass.actionHeaderSize 1402*e1fe3e4aSElliott Hughes actionData, actionIndex = self.tableClass.compileActions(font, value.States) 1403*e1fe3e4aSElliott Hughes stateArrayData, entryTableData = self._compileStates( 1404*e1fe3e4aSElliott Hughes font, value.States, glyphClassCount, actionIndex 1405*e1fe3e4aSElliott Hughes ) 1406*e1fe3e4aSElliott Hughes stateArrayOffset = glyphClassTableOffset + len(glyphClassData) 1407*e1fe3e4aSElliott Hughes entryTableOffset = stateArrayOffset + len(stateArrayData) 1408*e1fe3e4aSElliott Hughes perGlyphOffset = entryTableOffset + len(entryTableData) 1409*e1fe3e4aSElliott Hughes perGlyphData = pad(self._compilePerGlyphLookups(value, font), 4) 1410*e1fe3e4aSElliott Hughes if actionData is not None: 1411*e1fe3e4aSElliott Hughes actionOffset = entryTableOffset + len(entryTableData) 1412*e1fe3e4aSElliott Hughes else: 1413*e1fe3e4aSElliott Hughes actionOffset = None 1414*e1fe3e4aSElliott Hughes 1415*e1fe3e4aSElliott Hughes ligaturesOffset, ligComponentsOffset = None, None 1416*e1fe3e4aSElliott Hughes ligComponentsData = self._compileLigComponents(value, font) 1417*e1fe3e4aSElliott Hughes ligaturesData = self._compileLigatures(value, font) 1418*e1fe3e4aSElliott Hughes if ligComponentsData is not None: 1419*e1fe3e4aSElliott Hughes assert len(perGlyphData) == 0 1420*e1fe3e4aSElliott Hughes ligComponentsOffset = actionOffset + len(actionData) 1421*e1fe3e4aSElliott Hughes ligaturesOffset = ligComponentsOffset + len(ligComponentsData) 1422*e1fe3e4aSElliott Hughes 1423*e1fe3e4aSElliott Hughes writer.writeULong(glyphClassCount) 1424*e1fe3e4aSElliott Hughes writer.writeULong(glyphClassTableOffset) 1425*e1fe3e4aSElliott Hughes writer.writeULong(stateArrayOffset) 1426*e1fe3e4aSElliott Hughes writer.writeULong(entryTableOffset) 1427*e1fe3e4aSElliott Hughes if self.perGlyphLookup is not None: 1428*e1fe3e4aSElliott Hughes writer.writeULong(perGlyphOffset) 1429*e1fe3e4aSElliott Hughes if actionOffset is not None: 1430*e1fe3e4aSElliott Hughes writer.writeULong(actionOffset) 1431*e1fe3e4aSElliott Hughes if ligComponentsOffset is not None: 1432*e1fe3e4aSElliott Hughes writer.writeULong(ligComponentsOffset) 1433*e1fe3e4aSElliott Hughes writer.writeULong(ligaturesOffset) 1434*e1fe3e4aSElliott Hughes writer.writeData(glyphClassData) 1435*e1fe3e4aSElliott Hughes writer.writeData(stateArrayData) 1436*e1fe3e4aSElliott Hughes writer.writeData(entryTableData) 1437*e1fe3e4aSElliott Hughes writer.writeData(perGlyphData) 1438*e1fe3e4aSElliott Hughes if actionData is not None: 1439*e1fe3e4aSElliott Hughes writer.writeData(actionData) 1440*e1fe3e4aSElliott Hughes if ligComponentsData is not None: 1441*e1fe3e4aSElliott Hughes writer.writeData(ligComponentsData) 1442*e1fe3e4aSElliott Hughes if ligaturesData is not None: 1443*e1fe3e4aSElliott Hughes writer.writeData(ligaturesData) 1444*e1fe3e4aSElliott Hughes 1445*e1fe3e4aSElliott Hughes def _compileStates(self, font, states, glyphClassCount, actionIndex): 1446*e1fe3e4aSElliott Hughes stateArrayWriter = OTTableWriter() 1447*e1fe3e4aSElliott Hughes entries, entryIDs = [], {} 1448*e1fe3e4aSElliott Hughes for state in states: 1449*e1fe3e4aSElliott Hughes for glyphClass in range(glyphClassCount): 1450*e1fe3e4aSElliott Hughes transition = state.Transitions[glyphClass] 1451*e1fe3e4aSElliott Hughes entryWriter = OTTableWriter() 1452*e1fe3e4aSElliott Hughes transition.compile(entryWriter, font, actionIndex) 1453*e1fe3e4aSElliott Hughes entryData = entryWriter.getAllData() 1454*e1fe3e4aSElliott Hughes assert ( 1455*e1fe3e4aSElliott Hughes len(entryData) == transition.staticSize 1456*e1fe3e4aSElliott Hughes ), "%s has staticSize %d, " "but actually wrote %d bytes" % ( 1457*e1fe3e4aSElliott Hughes repr(transition), 1458*e1fe3e4aSElliott Hughes transition.staticSize, 1459*e1fe3e4aSElliott Hughes len(entryData), 1460*e1fe3e4aSElliott Hughes ) 1461*e1fe3e4aSElliott Hughes entryIndex = entryIDs.get(entryData) 1462*e1fe3e4aSElliott Hughes if entryIndex is None: 1463*e1fe3e4aSElliott Hughes entryIndex = len(entries) 1464*e1fe3e4aSElliott Hughes entryIDs[entryData] = entryIndex 1465*e1fe3e4aSElliott Hughes entries.append(entryData) 1466*e1fe3e4aSElliott Hughes stateArrayWriter.writeUShort(entryIndex) 1467*e1fe3e4aSElliott Hughes stateArrayData = pad(stateArrayWriter.getAllData(), 4) 1468*e1fe3e4aSElliott Hughes entryTableData = pad(bytesjoin(entries), 4) 1469*e1fe3e4aSElliott Hughes return stateArrayData, entryTableData 1470*e1fe3e4aSElliott Hughes 1471*e1fe3e4aSElliott Hughes def _compilePerGlyphLookups(self, table, font): 1472*e1fe3e4aSElliott Hughes if self.perGlyphLookup is None: 1473*e1fe3e4aSElliott Hughes return b"" 1474*e1fe3e4aSElliott Hughes numLookups = self._countPerGlyphLookups(table) 1475*e1fe3e4aSElliott Hughes assert len(table.PerGlyphLookups) == numLookups, ( 1476*e1fe3e4aSElliott Hughes "len(AATStateTable.PerGlyphLookups) is %d, " 1477*e1fe3e4aSElliott Hughes "but the actions inside the table refer to %d" 1478*e1fe3e4aSElliott Hughes % (len(table.PerGlyphLookups), numLookups) 1479*e1fe3e4aSElliott Hughes ) 1480*e1fe3e4aSElliott Hughes writer = OTTableWriter() 1481*e1fe3e4aSElliott Hughes for lookup in table.PerGlyphLookups: 1482*e1fe3e4aSElliott Hughes lookupWriter = writer.getSubWriter() 1483*e1fe3e4aSElliott Hughes self.perGlyphLookup.write(lookupWriter, font, {}, lookup, None) 1484*e1fe3e4aSElliott Hughes writer.writeSubTable(lookupWriter, offsetSize=4) 1485*e1fe3e4aSElliott Hughes return writer.getAllData() 1486*e1fe3e4aSElliott Hughes 1487*e1fe3e4aSElliott Hughes def _compileLigComponents(self, table, font): 1488*e1fe3e4aSElliott Hughes if not hasattr(table, "LigComponents"): 1489*e1fe3e4aSElliott Hughes return None 1490*e1fe3e4aSElliott Hughes writer = OTTableWriter() 1491*e1fe3e4aSElliott Hughes for component in table.LigComponents: 1492*e1fe3e4aSElliott Hughes writer.writeUShort(component) 1493*e1fe3e4aSElliott Hughes return writer.getAllData() 1494*e1fe3e4aSElliott Hughes 1495*e1fe3e4aSElliott Hughes def _compileLigatures(self, table, font): 1496*e1fe3e4aSElliott Hughes if not hasattr(table, "Ligatures"): 1497*e1fe3e4aSElliott Hughes return None 1498*e1fe3e4aSElliott Hughes writer = OTTableWriter() 1499*e1fe3e4aSElliott Hughes for glyphName in table.Ligatures: 1500*e1fe3e4aSElliott Hughes writer.writeUShort(font.getGlyphID(glyphName)) 1501*e1fe3e4aSElliott Hughes return writer.getAllData() 1502*e1fe3e4aSElliott Hughes 1503*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 1504*e1fe3e4aSElliott Hughes xmlWriter.begintag(name, attrs) 1505*e1fe3e4aSElliott Hughes xmlWriter.newline() 1506*e1fe3e4aSElliott Hughes xmlWriter.comment("GlyphClassCount=%s" % value.GlyphClassCount) 1507*e1fe3e4aSElliott Hughes xmlWriter.newline() 1508*e1fe3e4aSElliott Hughes for g, klass in sorted(value.GlyphClasses.items()): 1509*e1fe3e4aSElliott Hughes xmlWriter.simpletag("GlyphClass", glyph=g, value=klass) 1510*e1fe3e4aSElliott Hughes xmlWriter.newline() 1511*e1fe3e4aSElliott Hughes for stateIndex, state in enumerate(value.States): 1512*e1fe3e4aSElliott Hughes xmlWriter.begintag("State", index=stateIndex) 1513*e1fe3e4aSElliott Hughes xmlWriter.newline() 1514*e1fe3e4aSElliott Hughes for glyphClass, trans in sorted(state.Transitions.items()): 1515*e1fe3e4aSElliott Hughes trans.toXML( 1516*e1fe3e4aSElliott Hughes xmlWriter, 1517*e1fe3e4aSElliott Hughes font=font, 1518*e1fe3e4aSElliott Hughes attrs={"onGlyphClass": glyphClass}, 1519*e1fe3e4aSElliott Hughes name="Transition", 1520*e1fe3e4aSElliott Hughes ) 1521*e1fe3e4aSElliott Hughes xmlWriter.endtag("State") 1522*e1fe3e4aSElliott Hughes xmlWriter.newline() 1523*e1fe3e4aSElliott Hughes for i, lookup in enumerate(value.PerGlyphLookups): 1524*e1fe3e4aSElliott Hughes xmlWriter.begintag("PerGlyphLookup", index=i) 1525*e1fe3e4aSElliott Hughes xmlWriter.newline() 1526*e1fe3e4aSElliott Hughes for glyph, val in sorted(lookup.items()): 1527*e1fe3e4aSElliott Hughes xmlWriter.simpletag("Lookup", glyph=glyph, value=val) 1528*e1fe3e4aSElliott Hughes xmlWriter.newline() 1529*e1fe3e4aSElliott Hughes xmlWriter.endtag("PerGlyphLookup") 1530*e1fe3e4aSElliott Hughes xmlWriter.newline() 1531*e1fe3e4aSElliott Hughes if hasattr(value, "LigComponents"): 1532*e1fe3e4aSElliott Hughes xmlWriter.begintag("LigComponents") 1533*e1fe3e4aSElliott Hughes xmlWriter.newline() 1534*e1fe3e4aSElliott Hughes for i, val in enumerate(getattr(value, "LigComponents")): 1535*e1fe3e4aSElliott Hughes xmlWriter.simpletag("LigComponent", index=i, value=val) 1536*e1fe3e4aSElliott Hughes xmlWriter.newline() 1537*e1fe3e4aSElliott Hughes xmlWriter.endtag("LigComponents") 1538*e1fe3e4aSElliott Hughes xmlWriter.newline() 1539*e1fe3e4aSElliott Hughes self._xmlWriteLigatures(xmlWriter, font, value, name, attrs) 1540*e1fe3e4aSElliott Hughes xmlWriter.endtag(name) 1541*e1fe3e4aSElliott Hughes xmlWriter.newline() 1542*e1fe3e4aSElliott Hughes 1543*e1fe3e4aSElliott Hughes def _xmlWriteLigatures(self, xmlWriter, font, value, name, attrs): 1544*e1fe3e4aSElliott Hughes if not hasattr(value, "Ligatures"): 1545*e1fe3e4aSElliott Hughes return 1546*e1fe3e4aSElliott Hughes xmlWriter.begintag("Ligatures") 1547*e1fe3e4aSElliott Hughes xmlWriter.newline() 1548*e1fe3e4aSElliott Hughes for i, g in enumerate(getattr(value, "Ligatures")): 1549*e1fe3e4aSElliott Hughes xmlWriter.simpletag("Ligature", index=i, glyph=g) 1550*e1fe3e4aSElliott Hughes xmlWriter.newline() 1551*e1fe3e4aSElliott Hughes xmlWriter.endtag("Ligatures") 1552*e1fe3e4aSElliott Hughes xmlWriter.newline() 1553*e1fe3e4aSElliott Hughes 1554*e1fe3e4aSElliott Hughes def xmlRead(self, attrs, content, font): 1555*e1fe3e4aSElliott Hughes table = AATStateTable() 1556*e1fe3e4aSElliott Hughes for eltName, eltAttrs, eltContent in filter(istuple, content): 1557*e1fe3e4aSElliott Hughes if eltName == "GlyphClass": 1558*e1fe3e4aSElliott Hughes glyph = eltAttrs["glyph"] 1559*e1fe3e4aSElliott Hughes value = eltAttrs["value"] 1560*e1fe3e4aSElliott Hughes table.GlyphClasses[glyph] = safeEval(value) 1561*e1fe3e4aSElliott Hughes elif eltName == "State": 1562*e1fe3e4aSElliott Hughes state = self._xmlReadState(eltAttrs, eltContent, font) 1563*e1fe3e4aSElliott Hughes table.States.append(state) 1564*e1fe3e4aSElliott Hughes elif eltName == "PerGlyphLookup": 1565*e1fe3e4aSElliott Hughes lookup = self.perGlyphLookup.xmlRead(eltAttrs, eltContent, font) 1566*e1fe3e4aSElliott Hughes table.PerGlyphLookups.append(lookup) 1567*e1fe3e4aSElliott Hughes elif eltName == "LigComponents": 1568*e1fe3e4aSElliott Hughes table.LigComponents = self._xmlReadLigComponents( 1569*e1fe3e4aSElliott Hughes eltAttrs, eltContent, font 1570*e1fe3e4aSElliott Hughes ) 1571*e1fe3e4aSElliott Hughes elif eltName == "Ligatures": 1572*e1fe3e4aSElliott Hughes table.Ligatures = self._xmlReadLigatures(eltAttrs, eltContent, font) 1573*e1fe3e4aSElliott Hughes table.GlyphClassCount = max(table.GlyphClasses.values()) + 1 1574*e1fe3e4aSElliott Hughes return table 1575*e1fe3e4aSElliott Hughes 1576*e1fe3e4aSElliott Hughes def _xmlReadState(self, attrs, content, font): 1577*e1fe3e4aSElliott Hughes state = AATState() 1578*e1fe3e4aSElliott Hughes for eltName, eltAttrs, eltContent in filter(istuple, content): 1579*e1fe3e4aSElliott Hughes if eltName == "Transition": 1580*e1fe3e4aSElliott Hughes glyphClass = safeEval(eltAttrs["onGlyphClass"]) 1581*e1fe3e4aSElliott Hughes transition = self.tableClass() 1582*e1fe3e4aSElliott Hughes transition.fromXML(eltName, eltAttrs, eltContent, font) 1583*e1fe3e4aSElliott Hughes state.Transitions[glyphClass] = transition 1584*e1fe3e4aSElliott Hughes return state 1585*e1fe3e4aSElliott Hughes 1586*e1fe3e4aSElliott Hughes def _xmlReadLigComponents(self, attrs, content, font): 1587*e1fe3e4aSElliott Hughes ligComponents = [] 1588*e1fe3e4aSElliott Hughes for eltName, eltAttrs, _eltContent in filter(istuple, content): 1589*e1fe3e4aSElliott Hughes if eltName == "LigComponent": 1590*e1fe3e4aSElliott Hughes ligComponents.append(safeEval(eltAttrs["value"])) 1591*e1fe3e4aSElliott Hughes return ligComponents 1592*e1fe3e4aSElliott Hughes 1593*e1fe3e4aSElliott Hughes def _xmlReadLigatures(self, attrs, content, font): 1594*e1fe3e4aSElliott Hughes ligs = [] 1595*e1fe3e4aSElliott Hughes for eltName, eltAttrs, _eltContent in filter(istuple, content): 1596*e1fe3e4aSElliott Hughes if eltName == "Ligature": 1597*e1fe3e4aSElliott Hughes ligs.append(eltAttrs["glyph"]) 1598*e1fe3e4aSElliott Hughes return ligs 1599*e1fe3e4aSElliott Hughes 1600*e1fe3e4aSElliott Hughes 1601*e1fe3e4aSElliott Hughesclass CIDGlyphMap(BaseConverter): 1602*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 1603*e1fe3e4aSElliott Hughes numCIDs = reader.readUShort() 1604*e1fe3e4aSElliott Hughes result = {} 1605*e1fe3e4aSElliott Hughes for cid, glyphID in enumerate(reader.readUShortArray(numCIDs)): 1606*e1fe3e4aSElliott Hughes if glyphID != 0xFFFF: 1607*e1fe3e4aSElliott Hughes result[cid] = font.getGlyphName(glyphID) 1608*e1fe3e4aSElliott Hughes return result 1609*e1fe3e4aSElliott Hughes 1610*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 1611*e1fe3e4aSElliott Hughes items = {cid: font.getGlyphID(glyph) for cid, glyph in value.items()} 1612*e1fe3e4aSElliott Hughes count = max(items) + 1 if items else 0 1613*e1fe3e4aSElliott Hughes writer.writeUShort(count) 1614*e1fe3e4aSElliott Hughes for cid in range(count): 1615*e1fe3e4aSElliott Hughes writer.writeUShort(items.get(cid, 0xFFFF)) 1616*e1fe3e4aSElliott Hughes 1617*e1fe3e4aSElliott Hughes def xmlRead(self, attrs, content, font): 1618*e1fe3e4aSElliott Hughes result = {} 1619*e1fe3e4aSElliott Hughes for eName, eAttrs, _eContent in filter(istuple, content): 1620*e1fe3e4aSElliott Hughes if eName == "CID": 1621*e1fe3e4aSElliott Hughes result[safeEval(eAttrs["cid"])] = eAttrs["glyph"].strip() 1622*e1fe3e4aSElliott Hughes return result 1623*e1fe3e4aSElliott Hughes 1624*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 1625*e1fe3e4aSElliott Hughes xmlWriter.begintag(name, attrs) 1626*e1fe3e4aSElliott Hughes xmlWriter.newline() 1627*e1fe3e4aSElliott Hughes for cid, glyph in sorted(value.items()): 1628*e1fe3e4aSElliott Hughes if glyph is not None and glyph != 0xFFFF: 1629*e1fe3e4aSElliott Hughes xmlWriter.simpletag("CID", cid=cid, glyph=glyph) 1630*e1fe3e4aSElliott Hughes xmlWriter.newline() 1631*e1fe3e4aSElliott Hughes xmlWriter.endtag(name) 1632*e1fe3e4aSElliott Hughes xmlWriter.newline() 1633*e1fe3e4aSElliott Hughes 1634*e1fe3e4aSElliott Hughes 1635*e1fe3e4aSElliott Hughesclass GlyphCIDMap(BaseConverter): 1636*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 1637*e1fe3e4aSElliott Hughes glyphOrder = font.getGlyphOrder() 1638*e1fe3e4aSElliott Hughes count = reader.readUShort() 1639*e1fe3e4aSElliott Hughes cids = reader.readUShortArray(count) 1640*e1fe3e4aSElliott Hughes if count > len(glyphOrder): 1641*e1fe3e4aSElliott Hughes log.warning( 1642*e1fe3e4aSElliott Hughes "GlyphCIDMap has %d elements, " 1643*e1fe3e4aSElliott Hughes "but the font has only %d glyphs; " 1644*e1fe3e4aSElliott Hughes "ignoring the rest" % (count, len(glyphOrder)) 1645*e1fe3e4aSElliott Hughes ) 1646*e1fe3e4aSElliott Hughes result = {} 1647*e1fe3e4aSElliott Hughes for glyphID in range(min(len(cids), len(glyphOrder))): 1648*e1fe3e4aSElliott Hughes cid = cids[glyphID] 1649*e1fe3e4aSElliott Hughes if cid != 0xFFFF: 1650*e1fe3e4aSElliott Hughes result[glyphOrder[glyphID]] = cid 1651*e1fe3e4aSElliott Hughes return result 1652*e1fe3e4aSElliott Hughes 1653*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 1654*e1fe3e4aSElliott Hughes items = { 1655*e1fe3e4aSElliott Hughes font.getGlyphID(g): cid 1656*e1fe3e4aSElliott Hughes for g, cid in value.items() 1657*e1fe3e4aSElliott Hughes if cid is not None and cid != 0xFFFF 1658*e1fe3e4aSElliott Hughes } 1659*e1fe3e4aSElliott Hughes count = max(items) + 1 if items else 0 1660*e1fe3e4aSElliott Hughes writer.writeUShort(count) 1661*e1fe3e4aSElliott Hughes for glyphID in range(count): 1662*e1fe3e4aSElliott Hughes writer.writeUShort(items.get(glyphID, 0xFFFF)) 1663*e1fe3e4aSElliott Hughes 1664*e1fe3e4aSElliott Hughes def xmlRead(self, attrs, content, font): 1665*e1fe3e4aSElliott Hughes result = {} 1666*e1fe3e4aSElliott Hughes for eName, eAttrs, _eContent in filter(istuple, content): 1667*e1fe3e4aSElliott Hughes if eName == "CID": 1668*e1fe3e4aSElliott Hughes result[eAttrs["glyph"]] = safeEval(eAttrs["value"]) 1669*e1fe3e4aSElliott Hughes return result 1670*e1fe3e4aSElliott Hughes 1671*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 1672*e1fe3e4aSElliott Hughes xmlWriter.begintag(name, attrs) 1673*e1fe3e4aSElliott Hughes xmlWriter.newline() 1674*e1fe3e4aSElliott Hughes for glyph, cid in sorted(value.items()): 1675*e1fe3e4aSElliott Hughes if cid is not None and cid != 0xFFFF: 1676*e1fe3e4aSElliott Hughes xmlWriter.simpletag("CID", glyph=glyph, value=cid) 1677*e1fe3e4aSElliott Hughes xmlWriter.newline() 1678*e1fe3e4aSElliott Hughes xmlWriter.endtag(name) 1679*e1fe3e4aSElliott Hughes xmlWriter.newline() 1680*e1fe3e4aSElliott Hughes 1681*e1fe3e4aSElliott Hughes 1682*e1fe3e4aSElliott Hughesclass DeltaValue(BaseConverter): 1683*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 1684*e1fe3e4aSElliott Hughes StartSize = tableDict["StartSize"] 1685*e1fe3e4aSElliott Hughes EndSize = tableDict["EndSize"] 1686*e1fe3e4aSElliott Hughes DeltaFormat = tableDict["DeltaFormat"] 1687*e1fe3e4aSElliott Hughes assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat" 1688*e1fe3e4aSElliott Hughes nItems = EndSize - StartSize + 1 1689*e1fe3e4aSElliott Hughes nBits = 1 << DeltaFormat 1690*e1fe3e4aSElliott Hughes minusOffset = 1 << nBits 1691*e1fe3e4aSElliott Hughes mask = (1 << nBits) - 1 1692*e1fe3e4aSElliott Hughes signMask = 1 << (nBits - 1) 1693*e1fe3e4aSElliott Hughes 1694*e1fe3e4aSElliott Hughes DeltaValue = [] 1695*e1fe3e4aSElliott Hughes tmp, shift = 0, 0 1696*e1fe3e4aSElliott Hughes for i in range(nItems): 1697*e1fe3e4aSElliott Hughes if shift == 0: 1698*e1fe3e4aSElliott Hughes tmp, shift = reader.readUShort(), 16 1699*e1fe3e4aSElliott Hughes shift = shift - nBits 1700*e1fe3e4aSElliott Hughes value = (tmp >> shift) & mask 1701*e1fe3e4aSElliott Hughes if value & signMask: 1702*e1fe3e4aSElliott Hughes value = value - minusOffset 1703*e1fe3e4aSElliott Hughes DeltaValue.append(value) 1704*e1fe3e4aSElliott Hughes return DeltaValue 1705*e1fe3e4aSElliott Hughes 1706*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 1707*e1fe3e4aSElliott Hughes StartSize = tableDict["StartSize"] 1708*e1fe3e4aSElliott Hughes EndSize = tableDict["EndSize"] 1709*e1fe3e4aSElliott Hughes DeltaFormat = tableDict["DeltaFormat"] 1710*e1fe3e4aSElliott Hughes DeltaValue = value 1711*e1fe3e4aSElliott Hughes assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat" 1712*e1fe3e4aSElliott Hughes nItems = EndSize - StartSize + 1 1713*e1fe3e4aSElliott Hughes nBits = 1 << DeltaFormat 1714*e1fe3e4aSElliott Hughes assert len(DeltaValue) == nItems 1715*e1fe3e4aSElliott Hughes mask = (1 << nBits) - 1 1716*e1fe3e4aSElliott Hughes 1717*e1fe3e4aSElliott Hughes tmp, shift = 0, 16 1718*e1fe3e4aSElliott Hughes for value in DeltaValue: 1719*e1fe3e4aSElliott Hughes shift = shift - nBits 1720*e1fe3e4aSElliott Hughes tmp = tmp | ((value & mask) << shift) 1721*e1fe3e4aSElliott Hughes if shift == 0: 1722*e1fe3e4aSElliott Hughes writer.writeUShort(tmp) 1723*e1fe3e4aSElliott Hughes tmp, shift = 0, 16 1724*e1fe3e4aSElliott Hughes if shift != 16: 1725*e1fe3e4aSElliott Hughes writer.writeUShort(tmp) 1726*e1fe3e4aSElliott Hughes 1727*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 1728*e1fe3e4aSElliott Hughes xmlWriter.simpletag(name, attrs + [("value", value)]) 1729*e1fe3e4aSElliott Hughes xmlWriter.newline() 1730*e1fe3e4aSElliott Hughes 1731*e1fe3e4aSElliott Hughes def xmlRead(self, attrs, content, font): 1732*e1fe3e4aSElliott Hughes return safeEval(attrs["value"]) 1733*e1fe3e4aSElliott Hughes 1734*e1fe3e4aSElliott Hughes 1735*e1fe3e4aSElliott Hughesclass VarIdxMapValue(BaseConverter): 1736*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 1737*e1fe3e4aSElliott Hughes fmt = tableDict["EntryFormat"] 1738*e1fe3e4aSElliott Hughes nItems = tableDict["MappingCount"] 1739*e1fe3e4aSElliott Hughes 1740*e1fe3e4aSElliott Hughes innerBits = 1 + (fmt & 0x000F) 1741*e1fe3e4aSElliott Hughes innerMask = (1 << innerBits) - 1 1742*e1fe3e4aSElliott Hughes outerMask = 0xFFFFFFFF - innerMask 1743*e1fe3e4aSElliott Hughes outerShift = 16 - innerBits 1744*e1fe3e4aSElliott Hughes 1745*e1fe3e4aSElliott Hughes entrySize = 1 + ((fmt & 0x0030) >> 4) 1746*e1fe3e4aSElliott Hughes readArray = { 1747*e1fe3e4aSElliott Hughes 1: reader.readUInt8Array, 1748*e1fe3e4aSElliott Hughes 2: reader.readUShortArray, 1749*e1fe3e4aSElliott Hughes 3: reader.readUInt24Array, 1750*e1fe3e4aSElliott Hughes 4: reader.readULongArray, 1751*e1fe3e4aSElliott Hughes }[entrySize] 1752*e1fe3e4aSElliott Hughes 1753*e1fe3e4aSElliott Hughes return [ 1754*e1fe3e4aSElliott Hughes (((raw & outerMask) << outerShift) | (raw & innerMask)) 1755*e1fe3e4aSElliott Hughes for raw in readArray(nItems) 1756*e1fe3e4aSElliott Hughes ] 1757*e1fe3e4aSElliott Hughes 1758*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, value, repeatIndex=None): 1759*e1fe3e4aSElliott Hughes fmt = tableDict["EntryFormat"] 1760*e1fe3e4aSElliott Hughes mapping = value 1761*e1fe3e4aSElliott Hughes writer["MappingCount"].setValue(len(mapping)) 1762*e1fe3e4aSElliott Hughes 1763*e1fe3e4aSElliott Hughes innerBits = 1 + (fmt & 0x000F) 1764*e1fe3e4aSElliott Hughes innerMask = (1 << innerBits) - 1 1765*e1fe3e4aSElliott Hughes outerShift = 16 - innerBits 1766*e1fe3e4aSElliott Hughes 1767*e1fe3e4aSElliott Hughes entrySize = 1 + ((fmt & 0x0030) >> 4) 1768*e1fe3e4aSElliott Hughes writeArray = { 1769*e1fe3e4aSElliott Hughes 1: writer.writeUInt8Array, 1770*e1fe3e4aSElliott Hughes 2: writer.writeUShortArray, 1771*e1fe3e4aSElliott Hughes 3: writer.writeUInt24Array, 1772*e1fe3e4aSElliott Hughes 4: writer.writeULongArray, 1773*e1fe3e4aSElliott Hughes }[entrySize] 1774*e1fe3e4aSElliott Hughes 1775*e1fe3e4aSElliott Hughes writeArray( 1776*e1fe3e4aSElliott Hughes [ 1777*e1fe3e4aSElliott Hughes (((idx & 0xFFFF0000) >> outerShift) | (idx & innerMask)) 1778*e1fe3e4aSElliott Hughes for idx in mapping 1779*e1fe3e4aSElliott Hughes ] 1780*e1fe3e4aSElliott Hughes ) 1781*e1fe3e4aSElliott Hughes 1782*e1fe3e4aSElliott Hughes 1783*e1fe3e4aSElliott Hughesclass VarDataValue(BaseConverter): 1784*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 1785*e1fe3e4aSElliott Hughes values = [] 1786*e1fe3e4aSElliott Hughes 1787*e1fe3e4aSElliott Hughes regionCount = tableDict["VarRegionCount"] 1788*e1fe3e4aSElliott Hughes wordCount = tableDict["NumShorts"] 1789*e1fe3e4aSElliott Hughes 1790*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/2279 1791*e1fe3e4aSElliott Hughes longWords = bool(wordCount & 0x8000) 1792*e1fe3e4aSElliott Hughes wordCount = wordCount & 0x7FFF 1793*e1fe3e4aSElliott Hughes 1794*e1fe3e4aSElliott Hughes if longWords: 1795*e1fe3e4aSElliott Hughes readBigArray, readSmallArray = reader.readLongArray, reader.readShortArray 1796*e1fe3e4aSElliott Hughes else: 1797*e1fe3e4aSElliott Hughes readBigArray, readSmallArray = reader.readShortArray, reader.readInt8Array 1798*e1fe3e4aSElliott Hughes 1799*e1fe3e4aSElliott Hughes n1, n2 = min(regionCount, wordCount), max(regionCount, wordCount) 1800*e1fe3e4aSElliott Hughes values.extend(readBigArray(n1)) 1801*e1fe3e4aSElliott Hughes values.extend(readSmallArray(n2 - n1)) 1802*e1fe3e4aSElliott Hughes if n2 > regionCount: # Padding 1803*e1fe3e4aSElliott Hughes del values[regionCount:] 1804*e1fe3e4aSElliott Hughes 1805*e1fe3e4aSElliott Hughes return values 1806*e1fe3e4aSElliott Hughes 1807*e1fe3e4aSElliott Hughes def write(self, writer, font, tableDict, values, repeatIndex=None): 1808*e1fe3e4aSElliott Hughes regionCount = tableDict["VarRegionCount"] 1809*e1fe3e4aSElliott Hughes wordCount = tableDict["NumShorts"] 1810*e1fe3e4aSElliott Hughes 1811*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/2279 1812*e1fe3e4aSElliott Hughes longWords = bool(wordCount & 0x8000) 1813*e1fe3e4aSElliott Hughes wordCount = wordCount & 0x7FFF 1814*e1fe3e4aSElliott Hughes 1815*e1fe3e4aSElliott Hughes (writeBigArray, writeSmallArray) = { 1816*e1fe3e4aSElliott Hughes False: (writer.writeShortArray, writer.writeInt8Array), 1817*e1fe3e4aSElliott Hughes True: (writer.writeLongArray, writer.writeShortArray), 1818*e1fe3e4aSElliott Hughes }[longWords] 1819*e1fe3e4aSElliott Hughes 1820*e1fe3e4aSElliott Hughes n1, n2 = min(regionCount, wordCount), max(regionCount, wordCount) 1821*e1fe3e4aSElliott Hughes writeBigArray(values[:n1]) 1822*e1fe3e4aSElliott Hughes writeSmallArray(values[n1:regionCount]) 1823*e1fe3e4aSElliott Hughes if n2 > regionCount: # Padding 1824*e1fe3e4aSElliott Hughes writer.writeSmallArray([0] * (n2 - regionCount)) 1825*e1fe3e4aSElliott Hughes 1826*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 1827*e1fe3e4aSElliott Hughes xmlWriter.simpletag(name, attrs + [("value", value)]) 1828*e1fe3e4aSElliott Hughes xmlWriter.newline() 1829*e1fe3e4aSElliott Hughes 1830*e1fe3e4aSElliott Hughes def xmlRead(self, attrs, content, font): 1831*e1fe3e4aSElliott Hughes return safeEval(attrs["value"]) 1832*e1fe3e4aSElliott Hughes 1833*e1fe3e4aSElliott Hughes 1834*e1fe3e4aSElliott Hughesclass LookupFlag(UShort): 1835*e1fe3e4aSElliott Hughes def xmlWrite(self, xmlWriter, font, value, name, attrs): 1836*e1fe3e4aSElliott Hughes xmlWriter.simpletag(name, attrs + [("value", value)]) 1837*e1fe3e4aSElliott Hughes flags = [] 1838*e1fe3e4aSElliott Hughes if value & 0x01: 1839*e1fe3e4aSElliott Hughes flags.append("rightToLeft") 1840*e1fe3e4aSElliott Hughes if value & 0x02: 1841*e1fe3e4aSElliott Hughes flags.append("ignoreBaseGlyphs") 1842*e1fe3e4aSElliott Hughes if value & 0x04: 1843*e1fe3e4aSElliott Hughes flags.append("ignoreLigatures") 1844*e1fe3e4aSElliott Hughes if value & 0x08: 1845*e1fe3e4aSElliott Hughes flags.append("ignoreMarks") 1846*e1fe3e4aSElliott Hughes if value & 0x10: 1847*e1fe3e4aSElliott Hughes flags.append("useMarkFilteringSet") 1848*e1fe3e4aSElliott Hughes if value & 0xFF00: 1849*e1fe3e4aSElliott Hughes flags.append("markAttachmentType[%i]" % (value >> 8)) 1850*e1fe3e4aSElliott Hughes if flags: 1851*e1fe3e4aSElliott Hughes xmlWriter.comment(" ".join(flags)) 1852*e1fe3e4aSElliott Hughes xmlWriter.newline() 1853*e1fe3e4aSElliott Hughes 1854*e1fe3e4aSElliott Hughes 1855*e1fe3e4aSElliott Hughesclass _UInt8Enum(UInt8): 1856*e1fe3e4aSElliott Hughes enumClass = NotImplemented 1857*e1fe3e4aSElliott Hughes 1858*e1fe3e4aSElliott Hughes def read(self, reader, font, tableDict): 1859*e1fe3e4aSElliott Hughes return self.enumClass(super().read(reader, font, tableDict)) 1860*e1fe3e4aSElliott Hughes 1861*e1fe3e4aSElliott Hughes @classmethod 1862*e1fe3e4aSElliott Hughes def fromString(cls, value): 1863*e1fe3e4aSElliott Hughes return getattr(cls.enumClass, value.upper()) 1864*e1fe3e4aSElliott Hughes 1865*e1fe3e4aSElliott Hughes @classmethod 1866*e1fe3e4aSElliott Hughes def toString(cls, value): 1867*e1fe3e4aSElliott Hughes return cls.enumClass(value).name.lower() 1868*e1fe3e4aSElliott Hughes 1869*e1fe3e4aSElliott Hughes 1870*e1fe3e4aSElliott Hughesclass ExtendMode(_UInt8Enum): 1871*e1fe3e4aSElliott Hughes enumClass = _ExtendMode 1872*e1fe3e4aSElliott Hughes 1873*e1fe3e4aSElliott Hughes 1874*e1fe3e4aSElliott Hughesclass CompositeMode(_UInt8Enum): 1875*e1fe3e4aSElliott Hughes enumClass = _CompositeMode 1876*e1fe3e4aSElliott Hughes 1877*e1fe3e4aSElliott Hughes 1878*e1fe3e4aSElliott HughesconverterMapping = { 1879*e1fe3e4aSElliott Hughes # type class 1880*e1fe3e4aSElliott Hughes "int8": Int8, 1881*e1fe3e4aSElliott Hughes "int16": Short, 1882*e1fe3e4aSElliott Hughes "uint8": UInt8, 1883*e1fe3e4aSElliott Hughes "uint16": UShort, 1884*e1fe3e4aSElliott Hughes "uint24": UInt24, 1885*e1fe3e4aSElliott Hughes "uint32": ULong, 1886*e1fe3e4aSElliott Hughes "char64": Char64, 1887*e1fe3e4aSElliott Hughes "Flags32": Flags32, 1888*e1fe3e4aSElliott Hughes "VarIndex": VarIndex, 1889*e1fe3e4aSElliott Hughes "Version": Version, 1890*e1fe3e4aSElliott Hughes "Tag": Tag, 1891*e1fe3e4aSElliott Hughes "GlyphID": GlyphID, 1892*e1fe3e4aSElliott Hughes "GlyphID32": GlyphID32, 1893*e1fe3e4aSElliott Hughes "NameID": NameID, 1894*e1fe3e4aSElliott Hughes "DeciPoints": DeciPoints, 1895*e1fe3e4aSElliott Hughes "Fixed": Fixed, 1896*e1fe3e4aSElliott Hughes "F2Dot14": F2Dot14, 1897*e1fe3e4aSElliott Hughes "Angle": Angle, 1898*e1fe3e4aSElliott Hughes "BiasedAngle": BiasedAngle, 1899*e1fe3e4aSElliott Hughes "struct": Struct, 1900*e1fe3e4aSElliott Hughes "Offset": Table, 1901*e1fe3e4aSElliott Hughes "LOffset": LTable, 1902*e1fe3e4aSElliott Hughes "Offset24": Table24, 1903*e1fe3e4aSElliott Hughes "ValueRecord": ValueRecord, 1904*e1fe3e4aSElliott Hughes "DeltaValue": DeltaValue, 1905*e1fe3e4aSElliott Hughes "VarIdxMapValue": VarIdxMapValue, 1906*e1fe3e4aSElliott Hughes "VarDataValue": VarDataValue, 1907*e1fe3e4aSElliott Hughes "LookupFlag": LookupFlag, 1908*e1fe3e4aSElliott Hughes "ExtendMode": ExtendMode, 1909*e1fe3e4aSElliott Hughes "CompositeMode": CompositeMode, 1910*e1fe3e4aSElliott Hughes "STATFlags": STATFlags, 1911*e1fe3e4aSElliott Hughes # AAT 1912*e1fe3e4aSElliott Hughes "CIDGlyphMap": CIDGlyphMap, 1913*e1fe3e4aSElliott Hughes "GlyphCIDMap": GlyphCIDMap, 1914*e1fe3e4aSElliott Hughes "MortChain": StructWithLength, 1915*e1fe3e4aSElliott Hughes "MortSubtable": StructWithLength, 1916*e1fe3e4aSElliott Hughes "MorxChain": StructWithLength, 1917*e1fe3e4aSElliott Hughes "MorxSubtable": MorxSubtableConverter, 1918*e1fe3e4aSElliott Hughes # "Template" types 1919*e1fe3e4aSElliott Hughes "AATLookup": lambda C: partial(AATLookup, tableClass=C), 1920*e1fe3e4aSElliott Hughes "AATLookupWithDataOffset": lambda C: partial(AATLookupWithDataOffset, tableClass=C), 1921*e1fe3e4aSElliott Hughes "STXHeader": lambda C: partial(STXHeader, tableClass=C), 1922*e1fe3e4aSElliott Hughes "OffsetTo": lambda C: partial(Table, tableClass=C), 1923*e1fe3e4aSElliott Hughes "LOffsetTo": lambda C: partial(LTable, tableClass=C), 1924*e1fe3e4aSElliott Hughes "LOffset24To": lambda C: partial(Table24, tableClass=C), 1925*e1fe3e4aSElliott Hughes} 1926