otConverters.py 73 KB

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