otConverters.py 72 KB

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