otConverters.py 68 KB

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