otBase.py 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468
  1. from fontTools.config import OPTIONS
  2. from fontTools.misc.textTools import Tag, bytesjoin
  3. from .DefaultTable import DefaultTable
  4. from enum import IntEnum
  5. import sys
  6. import array
  7. import struct
  8. import logging
  9. from functools import lru_cache
  10. from typing import Iterator, NamedTuple, Optional, Tuple
  11. log = logging.getLogger(__name__)
  12. have_uharfbuzz = False
  13. try:
  14. import uharfbuzz as hb
  15. # repack method added in uharfbuzz >= 0.23; if uharfbuzz *can* be
  16. # imported but repack method is missing, behave as if uharfbuzz
  17. # is not available (fallback to the slower Python implementation)
  18. have_uharfbuzz = callable(getattr(hb, "repack", None))
  19. except ImportError:
  20. pass
  21. USE_HARFBUZZ_REPACKER = OPTIONS[f"{__name__}:USE_HARFBUZZ_REPACKER"]
  22. class OverflowErrorRecord(object):
  23. def __init__(self, overflowTuple):
  24. self.tableType = overflowTuple[0]
  25. self.LookupListIndex = overflowTuple[1]
  26. self.SubTableIndex = overflowTuple[2]
  27. self.itemName = overflowTuple[3]
  28. self.itemIndex = overflowTuple[4]
  29. def __repr__(self):
  30. return str(
  31. (
  32. self.tableType,
  33. "LookupIndex:",
  34. self.LookupListIndex,
  35. "SubTableIndex:",
  36. self.SubTableIndex,
  37. "ItemName:",
  38. self.itemName,
  39. "ItemIndex:",
  40. self.itemIndex,
  41. )
  42. )
  43. class OTLOffsetOverflowError(Exception):
  44. def __init__(self, overflowErrorRecord):
  45. self.value = overflowErrorRecord
  46. def __str__(self):
  47. return repr(self.value)
  48. class RepackerState(IntEnum):
  49. # Repacking control flow is implemnted using a state machine. The state machine table:
  50. #
  51. # State | Packing Success | Packing Failed | Exception Raised |
  52. # ------------+-----------------+----------------+------------------+
  53. # PURE_FT | Return result | PURE_FT | Return failure |
  54. # HB_FT | Return result | HB_FT | FT_FALLBACK |
  55. # FT_FALLBACK | HB_FT | FT_FALLBACK | Return failure |
  56. # Pack only with fontTools, don't allow sharing between extensions.
  57. PURE_FT = 1
  58. # Attempt to pack with harfbuzz (allowing sharing between extensions)
  59. # use fontTools to attempt overflow resolution.
  60. HB_FT = 2
  61. # Fallback if HB/FT packing gets stuck. Pack only with fontTools, don't allow sharing between
  62. # extensions.
  63. FT_FALLBACK = 3
  64. class BaseTTXConverter(DefaultTable):
  65. """Generic base class for TTX table converters. It functions as an
  66. adapter between the TTX (ttLib actually) table model and the model
  67. we use for OpenType tables, which is necessarily subtly different.
  68. """
  69. def decompile(self, data, font):
  70. """Create an object from the binary data. Called automatically on access."""
  71. from . import otTables
  72. reader = OTTableReader(data, tableTag=self.tableTag)
  73. tableClass = getattr(otTables, self.tableTag)
  74. self.table = tableClass()
  75. self.table.decompile(reader, font)
  76. def compile(self, font):
  77. """Compiles the table into binary. Called automatically on save."""
  78. # General outline:
  79. # Create a top-level OTTableWriter for the GPOS/GSUB table.
  80. # Call the compile method for the the table
  81. # for each 'converter' record in the table converter list
  82. # call converter's write method for each item in the value.
  83. # - For simple items, the write method adds a string to the
  84. # writer's self.items list.
  85. # - For Struct/Table/Subtable items, it add first adds new writer to the
  86. # to the writer's self.items, then calls the item's compile method.
  87. # This creates a tree of writers, rooted at the GUSB/GPOS writer, with
  88. # each writer representing a table, and the writer.items list containing
  89. # the child data strings and writers.
  90. # call the getAllData method
  91. # call _doneWriting, which removes duplicates
  92. # call _gatherTables. This traverses the tables, adding unique occurences to a flat list of tables
  93. # Traverse the flat list of tables, calling getDataLength on each to update their position
  94. # Traverse the flat list of tables again, calling getData each get the data in the table, now that
  95. # pos's and offset are known.
  96. # If a lookup subtable overflows an offset, we have to start all over.
  97. overflowRecord = None
  98. # this is 3-state option: default (None) means automatically use hb.repack or
  99. # silently fall back if it fails; True, use it and raise error if not possible
  100. # or it errors out; False, don't use it, even if you can.
  101. use_hb_repack = font.cfg[USE_HARFBUZZ_REPACKER]
  102. if self.tableTag in ("GSUB", "GPOS"):
  103. if use_hb_repack is False:
  104. log.debug(
  105. "hb.repack disabled, compiling '%s' with pure-python serializer",
  106. self.tableTag,
  107. )
  108. elif not have_uharfbuzz:
  109. if use_hb_repack is True:
  110. raise ImportError("No module named 'uharfbuzz'")
  111. else:
  112. assert use_hb_repack is None
  113. log.debug(
  114. "uharfbuzz not found, compiling '%s' with pure-python serializer",
  115. self.tableTag,
  116. )
  117. if (
  118. use_hb_repack in (None, True)
  119. and have_uharfbuzz
  120. and self.tableTag in ("GSUB", "GPOS")
  121. ):
  122. state = RepackerState.HB_FT
  123. else:
  124. state = RepackerState.PURE_FT
  125. hb_first_error_logged = False
  126. lastOverflowRecord = None
  127. while True:
  128. try:
  129. writer = OTTableWriter(tableTag=self.tableTag)
  130. self.table.compile(writer, font)
  131. if state == RepackerState.HB_FT:
  132. return self.tryPackingHarfbuzz(writer, hb_first_error_logged)
  133. elif state == RepackerState.PURE_FT:
  134. return self.tryPackingFontTools(writer)
  135. elif state == RepackerState.FT_FALLBACK:
  136. # Run packing with FontTools only, but don't return the result as it will
  137. # not be optimally packed. Once a successful packing has been found, state is
  138. # changed back to harfbuzz packing to produce the final, optimal, packing.
  139. self.tryPackingFontTools(writer)
  140. log.debug(
  141. "Re-enabling sharing between extensions and switching back to "
  142. "harfbuzz+fontTools packing."
  143. )
  144. state = RepackerState.HB_FT
  145. except OTLOffsetOverflowError as e:
  146. hb_first_error_logged = True
  147. ok = self.tryResolveOverflow(font, e, lastOverflowRecord)
  148. lastOverflowRecord = e.value
  149. if ok:
  150. continue
  151. if state is RepackerState.HB_FT:
  152. log.debug(
  153. "Harfbuzz packing out of resolutions, disabling sharing between extensions and "
  154. "switching to fontTools only packing."
  155. )
  156. state = RepackerState.FT_FALLBACK
  157. else:
  158. raise
  159. def tryPackingHarfbuzz(self, writer, hb_first_error_logged):
  160. try:
  161. log.debug("serializing '%s' with hb.repack", self.tableTag)
  162. return writer.getAllDataUsingHarfbuzz(self.tableTag)
  163. except (ValueError, MemoryError, hb.RepackerError) as e:
  164. # Only log hb repacker errors the first time they occur in
  165. # the offset-overflow resolution loop, they are just noisy.
  166. # Maybe we can revisit this if/when uharfbuzz actually gives
  167. # us more info as to why hb.repack failed...
  168. if not hb_first_error_logged:
  169. error_msg = f"{type(e).__name__}"
  170. if str(e) != "":
  171. error_msg += f": {e}"
  172. log.warning(
  173. "hb.repack failed to serialize '%s', attempting fonttools resolutions "
  174. "; the error message was: %s",
  175. self.tableTag,
  176. error_msg,
  177. )
  178. hb_first_error_logged = True
  179. return writer.getAllData(remove_duplicate=False)
  180. def tryPackingFontTools(self, writer):
  181. return writer.getAllData()
  182. def tryResolveOverflow(self, font, e, lastOverflowRecord):
  183. ok = 0
  184. if lastOverflowRecord == e.value:
  185. # Oh well...
  186. return ok
  187. overflowRecord = e.value
  188. log.info("Attempting to fix OTLOffsetOverflowError %s", e)
  189. if overflowRecord.itemName is None:
  190. from .otTables import fixLookupOverFlows
  191. ok = fixLookupOverFlows(font, overflowRecord)
  192. else:
  193. from .otTables import fixSubTableOverFlows
  194. ok = fixSubTableOverFlows(font, overflowRecord)
  195. if ok:
  196. return ok
  197. # Try upgrading lookup to Extension and hope
  198. # that cross-lookup sharing not happening would
  199. # fix overflow...
  200. from .otTables import fixLookupOverFlows
  201. return fixLookupOverFlows(font, overflowRecord)
  202. def toXML(self, writer, font):
  203. self.table.toXML2(writer, font)
  204. def fromXML(self, name, attrs, content, font):
  205. from . import otTables
  206. if not hasattr(self, "table"):
  207. tableClass = getattr(otTables, self.tableTag)
  208. self.table = tableClass()
  209. self.table.fromXML(name, attrs, content, font)
  210. self.table.populateDefaults()
  211. def ensureDecompiled(self, recurse=True):
  212. self.table.ensureDecompiled(recurse=recurse)
  213. # https://github.com/fonttools/fonttools/pull/2285#issuecomment-834652928
  214. assert len(struct.pack("i", 0)) == 4
  215. assert array.array("i").itemsize == 4, "Oops, file a bug against fonttools."
  216. class OTTableReader(object):
  217. """Helper class to retrieve data from an OpenType table."""
  218. __slots__ = ("data", "offset", "pos", "localState", "tableTag")
  219. def __init__(self, data, localState=None, offset=0, tableTag=None):
  220. self.data = data
  221. self.offset = offset
  222. self.pos = offset
  223. self.localState = localState
  224. self.tableTag = tableTag
  225. def advance(self, count):
  226. self.pos += count
  227. def seek(self, pos):
  228. self.pos = pos
  229. def copy(self):
  230. other = self.__class__(self.data, self.localState, self.offset, self.tableTag)
  231. other.pos = self.pos
  232. return other
  233. def getSubReader(self, offset):
  234. offset = self.offset + offset
  235. return self.__class__(self.data, self.localState, offset, self.tableTag)
  236. def readValue(self, typecode, staticSize):
  237. pos = self.pos
  238. newpos = pos + staticSize
  239. (value,) = struct.unpack(f">{typecode}", self.data[pos:newpos])
  240. self.pos = newpos
  241. return value
  242. def readArray(self, typecode, staticSize, count):
  243. pos = self.pos
  244. newpos = pos + count * staticSize
  245. value = array.array(typecode, self.data[pos:newpos])
  246. if sys.byteorder != "big":
  247. value.byteswap()
  248. self.pos = newpos
  249. return value.tolist()
  250. def readInt8(self):
  251. return self.readValue("b", staticSize=1)
  252. def readInt8Array(self, count):
  253. return self.readArray("b", staticSize=1, count=count)
  254. def readShort(self):
  255. return self.readValue("h", staticSize=2)
  256. def readShortArray(self, count):
  257. return self.readArray("h", staticSize=2, count=count)
  258. def readLong(self):
  259. return self.readValue("i", staticSize=4)
  260. def readLongArray(self, count):
  261. return self.readArray("i", staticSize=4, count=count)
  262. def readUInt8(self):
  263. return self.readValue("B", staticSize=1)
  264. def readUInt8Array(self, count):
  265. return self.readArray("B", staticSize=1, count=count)
  266. def readUShort(self):
  267. return self.readValue("H", staticSize=2)
  268. def readUShortArray(self, count):
  269. return self.readArray("H", staticSize=2, count=count)
  270. def readULong(self):
  271. return self.readValue("I", staticSize=4)
  272. def readULongArray(self, count):
  273. return self.readArray("I", staticSize=4, count=count)
  274. def readUInt24(self):
  275. pos = self.pos
  276. newpos = pos + 3
  277. (value,) = struct.unpack(">l", b"\0" + self.data[pos:newpos])
  278. self.pos = newpos
  279. return value
  280. def readUInt24Array(self, count):
  281. return [self.readUInt24() for _ in range(count)]
  282. def readTag(self):
  283. pos = self.pos
  284. newpos = pos + 4
  285. value = Tag(self.data[pos:newpos])
  286. assert len(value) == 4, value
  287. self.pos = newpos
  288. return value
  289. def readData(self, count):
  290. pos = self.pos
  291. newpos = pos + count
  292. value = self.data[pos:newpos]
  293. self.pos = newpos
  294. return value
  295. def __setitem__(self, name, value):
  296. state = self.localState.copy() if self.localState else dict()
  297. state[name] = value
  298. self.localState = state
  299. def __getitem__(self, name):
  300. return self.localState and self.localState[name]
  301. def __contains__(self, name):
  302. return self.localState and name in self.localState
  303. class OffsetToWriter(object):
  304. def __init__(self, subWriter, offsetSize):
  305. self.subWriter = subWriter
  306. self.offsetSize = offsetSize
  307. def __eq__(self, other):
  308. if type(self) != type(other):
  309. return NotImplemented
  310. return self.subWriter == other.subWriter and self.offsetSize == other.offsetSize
  311. def __hash__(self):
  312. # only works after self._doneWriting() has been called
  313. return hash((self.subWriter, self.offsetSize))
  314. class OTTableWriter(object):
  315. """Helper class to gather and assemble data for OpenType tables."""
  316. def __init__(self, localState=None, tableTag=None):
  317. self.items = []
  318. self.pos = None
  319. self.localState = localState
  320. self.tableTag = tableTag
  321. self.parent = None
  322. def __setitem__(self, name, value):
  323. state = self.localState.copy() if self.localState else dict()
  324. state[name] = value
  325. self.localState = state
  326. def __getitem__(self, name):
  327. return self.localState[name]
  328. def __delitem__(self, name):
  329. del self.localState[name]
  330. # assembler interface
  331. def getDataLength(self):
  332. """Return the length of this table in bytes, without subtables."""
  333. l = 0
  334. for item in self.items:
  335. if hasattr(item, "getCountData"):
  336. l += item.size
  337. elif hasattr(item, "subWriter"):
  338. l += item.offsetSize
  339. else:
  340. l = l + len(item)
  341. return l
  342. def getData(self):
  343. """Assemble the data for this writer/table, without subtables."""
  344. items = list(self.items) # make a shallow copy
  345. pos = self.pos
  346. numItems = len(items)
  347. for i in range(numItems):
  348. item = items[i]
  349. if hasattr(item, "subWriter"):
  350. if item.offsetSize == 4:
  351. items[i] = packULong(item.subWriter.pos - pos)
  352. elif item.offsetSize == 2:
  353. try:
  354. items[i] = packUShort(item.subWriter.pos - pos)
  355. except struct.error:
  356. # provide data to fix overflow problem.
  357. overflowErrorRecord = self.getOverflowErrorRecord(
  358. item.subWriter
  359. )
  360. raise OTLOffsetOverflowError(overflowErrorRecord)
  361. elif item.offsetSize == 3:
  362. items[i] = packUInt24(item.subWriter.pos - pos)
  363. else:
  364. raise ValueError(item.offsetSize)
  365. return bytesjoin(items)
  366. def getDataForHarfbuzz(self):
  367. """Assemble the data for this writer/table with all offset field set to 0"""
  368. items = list(self.items)
  369. packFuncs = {2: packUShort, 3: packUInt24, 4: packULong}
  370. for i, item in enumerate(items):
  371. if hasattr(item, "subWriter"):
  372. # Offset value is not needed in harfbuzz repacker, so setting offset to 0 to avoid overflow here
  373. if item.offsetSize in packFuncs:
  374. items[i] = packFuncs[item.offsetSize](0)
  375. else:
  376. raise ValueError(item.offsetSize)
  377. return bytesjoin(items)
  378. def __hash__(self):
  379. # only works after self._doneWriting() has been called
  380. return hash(self.items)
  381. def __ne__(self, other):
  382. result = self.__eq__(other)
  383. return result if result is NotImplemented else not result
  384. def __eq__(self, other):
  385. if type(self) != type(other):
  386. return NotImplemented
  387. return self.items == other.items
  388. def _doneWriting(self, internedTables, shareExtension=False):
  389. # Convert CountData references to data string items
  390. # collapse duplicate table references to a unique entry
  391. # "tables" are OTTableWriter objects.
  392. # For Extension Lookup types, we can
  393. # eliminate duplicates only within the tree under the Extension Lookup,
  394. # as offsets may exceed 64K even between Extension LookupTable subtables.
  395. isExtension = hasattr(self, "Extension")
  396. # Certain versions of Uniscribe reject the font if the GSUB/GPOS top-level
  397. # arrays (ScriptList, FeatureList, LookupList) point to the same, possibly
  398. # empty, array. So, we don't share those.
  399. # See: https://github.com/fonttools/fonttools/issues/518
  400. dontShare = hasattr(self, "DontShare")
  401. if isExtension and not shareExtension:
  402. internedTables = {}
  403. items = self.items
  404. for i in range(len(items)):
  405. item = items[i]
  406. if hasattr(item, "getCountData"):
  407. items[i] = item.getCountData()
  408. elif hasattr(item, "subWriter"):
  409. item.subWriter._doneWriting(
  410. internedTables, shareExtension=shareExtension
  411. )
  412. # At this point, all subwriters are hashable based on their items.
  413. # (See hash and comparison magic methods above.) So the ``setdefault``
  414. # call here will return the first writer object we've seen with
  415. # equal content, or store it in the dictionary if it's not been
  416. # seen yet. We therefore replace the subwriter object with an equivalent
  417. # object, which deduplicates the tree.
  418. if not dontShare:
  419. items[i].subWriter = internedTables.setdefault(
  420. item.subWriter, item.subWriter
  421. )
  422. self.items = tuple(items)
  423. def _gatherTables(self, tables, extTables, done):
  424. # Convert table references in self.items tree to a flat
  425. # list of tables in depth-first traversal order.
  426. # "tables" are OTTableWriter objects.
  427. # We do the traversal in reverse order at each level, in order to
  428. # resolve duplicate references to be the last reference in the list of tables.
  429. # For extension lookups, duplicate references can be merged only within the
  430. # writer tree under the extension lookup.
  431. done[id(self)] = True
  432. numItems = len(self.items)
  433. iRange = list(range(numItems))
  434. iRange.reverse()
  435. isExtension = hasattr(self, "Extension")
  436. selfTables = tables
  437. if isExtension:
  438. assert (
  439. extTables is not None
  440. ), "Program or XML editing error. Extension subtables cannot contain extensions subtables"
  441. tables, extTables, done = extTables, None, {}
  442. # add Coverage table if it is sorted last.
  443. sortCoverageLast = False
  444. if hasattr(self, "sortCoverageLast"):
  445. # Find coverage table
  446. for i in range(numItems):
  447. item = self.items[i]
  448. if (
  449. hasattr(item, "subWriter")
  450. and getattr(item.subWriter, "name", None) == "Coverage"
  451. ):
  452. sortCoverageLast = True
  453. break
  454. if id(item.subWriter) not in done:
  455. item.subWriter._gatherTables(tables, extTables, done)
  456. else:
  457. # We're a new parent of item
  458. pass
  459. for i in iRange:
  460. item = self.items[i]
  461. if not hasattr(item, "subWriter"):
  462. continue
  463. if (
  464. sortCoverageLast
  465. and (i == 1)
  466. and getattr(item.subWriter, "name", None) == "Coverage"
  467. ):
  468. # we've already 'gathered' it above
  469. continue
  470. if id(item.subWriter) not in done:
  471. item.subWriter._gatherTables(tables, extTables, done)
  472. else:
  473. # Item is already written out by other parent
  474. pass
  475. selfTables.append(self)
  476. def _gatherGraphForHarfbuzz(self, tables, obj_list, done, objidx, virtual_edges):
  477. real_links = []
  478. virtual_links = []
  479. item_idx = objidx
  480. # Merge virtual_links from parent
  481. for idx in virtual_edges:
  482. virtual_links.append((0, 0, idx))
  483. sortCoverageLast = False
  484. coverage_idx = 0
  485. if hasattr(self, "sortCoverageLast"):
  486. # Find coverage table
  487. for i, item in enumerate(self.items):
  488. if getattr(item, "name", None) == "Coverage":
  489. sortCoverageLast = True
  490. if id(item) not in done:
  491. coverage_idx = item_idx = item._gatherGraphForHarfbuzz(
  492. tables, obj_list, done, item_idx, virtual_edges
  493. )
  494. else:
  495. coverage_idx = done[id(item)]
  496. virtual_edges.append(coverage_idx)
  497. break
  498. child_idx = 0
  499. offset_pos = 0
  500. for i, item in enumerate(self.items):
  501. if hasattr(item, "subWriter"):
  502. pos = offset_pos
  503. elif hasattr(item, "getCountData"):
  504. offset_pos += item.size
  505. continue
  506. else:
  507. offset_pos = offset_pos + len(item)
  508. continue
  509. if id(item.subWriter) not in done:
  510. child_idx = item_idx = item.subWriter._gatherGraphForHarfbuzz(
  511. tables, obj_list, done, item_idx, virtual_edges
  512. )
  513. else:
  514. child_idx = done[id(item.subWriter)]
  515. real_edge = (pos, item.offsetSize, child_idx)
  516. real_links.append(real_edge)
  517. offset_pos += item.offsetSize
  518. tables.append(self)
  519. obj_list.append((real_links, virtual_links))
  520. item_idx += 1
  521. done[id(self)] = item_idx
  522. if sortCoverageLast:
  523. virtual_edges.pop()
  524. return item_idx
  525. def getAllDataUsingHarfbuzz(self, tableTag):
  526. """The Whole table is represented as a Graph.
  527. Assemble graph data and call Harfbuzz repacker to pack the table.
  528. Harfbuzz repacker is faster and retain as much sub-table sharing as possible, see also:
  529. https://github.com/harfbuzz/harfbuzz/blob/main/docs/repacker.md
  530. The input format for hb.repack() method is explained here:
  531. https://github.com/harfbuzz/uharfbuzz/blob/main/src/uharfbuzz/_harfbuzz.pyx#L1149
  532. """
  533. internedTables = {}
  534. self._doneWriting(internedTables, shareExtension=True)
  535. tables = []
  536. obj_list = []
  537. done = {}
  538. objidx = 0
  539. virtual_edges = []
  540. self._gatherGraphForHarfbuzz(tables, obj_list, done, objidx, virtual_edges)
  541. # Gather all data in two passes: the absolute positions of all
  542. # subtable are needed before the actual data can be assembled.
  543. pos = 0
  544. for table in tables:
  545. table.pos = pos
  546. pos = pos + table.getDataLength()
  547. data = []
  548. for table in tables:
  549. tableData = table.getDataForHarfbuzz()
  550. data.append(tableData)
  551. if hasattr(hb, "repack_with_tag"):
  552. return hb.repack_with_tag(str(tableTag), data, obj_list)
  553. else:
  554. return hb.repack(data, obj_list)
  555. def getAllData(self, remove_duplicate=True):
  556. """Assemble all data, including all subtables."""
  557. if remove_duplicate:
  558. internedTables = {}
  559. self._doneWriting(internedTables)
  560. tables = []
  561. extTables = []
  562. done = {}
  563. self._gatherTables(tables, extTables, done)
  564. tables.reverse()
  565. extTables.reverse()
  566. # Gather all data in two passes: the absolute positions of all
  567. # subtable are needed before the actual data can be assembled.
  568. pos = 0
  569. for table in tables:
  570. table.pos = pos
  571. pos = pos + table.getDataLength()
  572. for table in extTables:
  573. table.pos = pos
  574. pos = pos + table.getDataLength()
  575. data = []
  576. for table in tables:
  577. tableData = table.getData()
  578. data.append(tableData)
  579. for table in extTables:
  580. tableData = table.getData()
  581. data.append(tableData)
  582. return bytesjoin(data)
  583. # interface for gathering data, as used by table.compile()
  584. def getSubWriter(self):
  585. subwriter = self.__class__(self.localState, self.tableTag)
  586. subwriter.parent = (
  587. self # because some subtables have idential values, we discard
  588. )
  589. # the duplicates under the getAllData method. Hence some
  590. # subtable writers can have more than one parent writer.
  591. # But we just care about first one right now.
  592. return subwriter
  593. def writeValue(self, typecode, value):
  594. self.items.append(struct.pack(f">{typecode}", value))
  595. def writeArray(self, typecode, values):
  596. a = array.array(typecode, values)
  597. if sys.byteorder != "big":
  598. a.byteswap()
  599. self.items.append(a.tobytes())
  600. def writeInt8(self, value):
  601. assert -128 <= value < 128, value
  602. self.items.append(struct.pack(">b", value))
  603. def writeInt8Array(self, values):
  604. self.writeArray("b", values)
  605. def writeShort(self, value):
  606. assert -32768 <= value < 32768, value
  607. self.items.append(struct.pack(">h", value))
  608. def writeShortArray(self, values):
  609. self.writeArray("h", values)
  610. def writeLong(self, value):
  611. self.items.append(struct.pack(">i", value))
  612. def writeLongArray(self, values):
  613. self.writeArray("i", values)
  614. def writeUInt8(self, value):
  615. assert 0 <= value < 256, value
  616. self.items.append(struct.pack(">B", value))
  617. def writeUInt8Array(self, values):
  618. self.writeArray("B", values)
  619. def writeUShort(self, value):
  620. assert 0 <= value < 0x10000, value
  621. self.items.append(struct.pack(">H", value))
  622. def writeUShortArray(self, values):
  623. self.writeArray("H", values)
  624. def writeULong(self, value):
  625. self.items.append(struct.pack(">I", value))
  626. def writeULongArray(self, values):
  627. self.writeArray("I", values)
  628. def writeUInt24(self, value):
  629. assert 0 <= value < 0x1000000, value
  630. b = struct.pack(">L", value)
  631. self.items.append(b[1:])
  632. def writeUInt24Array(self, values):
  633. for value in values:
  634. self.writeUInt24(value)
  635. def writeTag(self, tag):
  636. tag = Tag(tag).tobytes()
  637. assert len(tag) == 4, tag
  638. self.items.append(tag)
  639. def writeSubTable(self, subWriter, offsetSize):
  640. self.items.append(OffsetToWriter(subWriter, offsetSize))
  641. def writeCountReference(self, table, name, size=2, value=None):
  642. ref = CountReference(table, name, size=size, value=value)
  643. self.items.append(ref)
  644. return ref
  645. def writeStruct(self, format, values):
  646. data = struct.pack(*(format,) + values)
  647. self.items.append(data)
  648. def writeData(self, data):
  649. self.items.append(data)
  650. def getOverflowErrorRecord(self, item):
  651. LookupListIndex = SubTableIndex = itemName = itemIndex = None
  652. if self.name == "LookupList":
  653. LookupListIndex = item.repeatIndex
  654. elif self.name == "Lookup":
  655. LookupListIndex = self.repeatIndex
  656. SubTableIndex = item.repeatIndex
  657. else:
  658. itemName = getattr(item, "name", "<none>")
  659. if hasattr(item, "repeatIndex"):
  660. itemIndex = item.repeatIndex
  661. if self.name == "SubTable":
  662. LookupListIndex = self.parent.repeatIndex
  663. SubTableIndex = self.repeatIndex
  664. elif self.name == "ExtSubTable":
  665. LookupListIndex = self.parent.parent.repeatIndex
  666. SubTableIndex = self.parent.repeatIndex
  667. else: # who knows how far below the SubTable level we are! Climb back up to the nearest subtable.
  668. itemName = ".".join([self.name, itemName])
  669. p1 = self.parent
  670. while p1 and p1.name not in ["ExtSubTable", "SubTable"]:
  671. itemName = ".".join([p1.name, itemName])
  672. p1 = p1.parent
  673. if p1:
  674. if p1.name == "ExtSubTable":
  675. LookupListIndex = p1.parent.parent.repeatIndex
  676. SubTableIndex = p1.parent.repeatIndex
  677. else:
  678. LookupListIndex = p1.parent.repeatIndex
  679. SubTableIndex = p1.repeatIndex
  680. return OverflowErrorRecord(
  681. (self.tableTag, LookupListIndex, SubTableIndex, itemName, itemIndex)
  682. )
  683. class CountReference(object):
  684. """A reference to a Count value, not a count of references."""
  685. def __init__(self, table, name, size=None, value=None):
  686. self.table = table
  687. self.name = name
  688. self.size = size
  689. if value is not None:
  690. self.setValue(value)
  691. def setValue(self, value):
  692. table = self.table
  693. name = self.name
  694. if table[name] is None:
  695. table[name] = value
  696. else:
  697. assert table[name] == value, (name, table[name], value)
  698. def getValue(self):
  699. return self.table[self.name]
  700. def getCountData(self):
  701. v = self.table[self.name]
  702. if v is None:
  703. v = 0
  704. return {1: packUInt8, 2: packUShort, 4: packULong}[self.size](v)
  705. def packUInt8(value):
  706. return struct.pack(">B", value)
  707. def packUShort(value):
  708. return struct.pack(">H", value)
  709. def packULong(value):
  710. assert 0 <= value < 0x100000000, value
  711. return struct.pack(">I", value)
  712. def packUInt24(value):
  713. assert 0 <= value < 0x1000000, value
  714. return struct.pack(">I", value)[1:]
  715. class BaseTable(object):
  716. """Generic base class for all OpenType (sub)tables."""
  717. def __getattr__(self, attr):
  718. reader = self.__dict__.get("reader")
  719. if reader:
  720. del self.reader
  721. font = self.font
  722. del self.font
  723. self.decompile(reader, font)
  724. return getattr(self, attr)
  725. raise AttributeError(attr)
  726. def ensureDecompiled(self, recurse=False):
  727. reader = self.__dict__.get("reader")
  728. if reader:
  729. del self.reader
  730. font = self.font
  731. del self.font
  732. self.decompile(reader, font)
  733. if recurse:
  734. for subtable in self.iterSubTables():
  735. subtable.value.ensureDecompiled(recurse)
  736. def __getstate__(self):
  737. # before copying/pickling 'lazy' objects, make a shallow copy of OTTableReader
  738. # https://github.com/fonttools/fonttools/issues/2965
  739. if "reader" in self.__dict__:
  740. state = self.__dict__.copy()
  741. state["reader"] = self.__dict__["reader"].copy()
  742. return state
  743. return self.__dict__
  744. @classmethod
  745. def getRecordSize(cls, reader):
  746. totalSize = 0
  747. for conv in cls.converters:
  748. size = conv.getRecordSize(reader)
  749. if size is NotImplemented:
  750. return NotImplemented
  751. countValue = 1
  752. if conv.repeat:
  753. if conv.repeat in reader:
  754. countValue = reader[conv.repeat] + conv.aux
  755. else:
  756. return NotImplemented
  757. totalSize += size * countValue
  758. return totalSize
  759. def getConverters(self):
  760. return self.converters
  761. def getConverterByName(self, name):
  762. return self.convertersByName[name]
  763. def populateDefaults(self, propagator=None):
  764. for conv in self.getConverters():
  765. if conv.repeat:
  766. if not hasattr(self, conv.name):
  767. setattr(self, conv.name, [])
  768. countValue = len(getattr(self, conv.name)) - conv.aux
  769. try:
  770. count_conv = self.getConverterByName(conv.repeat)
  771. setattr(self, conv.repeat, countValue)
  772. except KeyError:
  773. # conv.repeat is a propagated count
  774. if propagator and conv.repeat in propagator:
  775. propagator[conv.repeat].setValue(countValue)
  776. else:
  777. if conv.aux and not eval(conv.aux, None, self.__dict__):
  778. continue
  779. if hasattr(self, conv.name):
  780. continue # Warn if it should NOT be present?!
  781. if hasattr(conv, "writeNullOffset"):
  782. setattr(self, conv.name, None) # Warn?
  783. # elif not conv.isCount:
  784. # # Warn?
  785. # pass
  786. if hasattr(conv, "DEFAULT"):
  787. # OptionalValue converters (e.g. VarIndex)
  788. setattr(self, conv.name, conv.DEFAULT)
  789. def decompile(self, reader, font):
  790. self.readFormat(reader)
  791. table = {}
  792. self.__rawTable = table # for debugging
  793. for conv in self.getConverters():
  794. if conv.name == "SubTable":
  795. conv = conv.getConverter(reader.tableTag, table["LookupType"])
  796. if conv.name == "ExtSubTable":
  797. conv = conv.getConverter(reader.tableTag, table["ExtensionLookupType"])
  798. if conv.name == "FeatureParams":
  799. conv = conv.getConverter(reader["FeatureTag"])
  800. if conv.name == "SubStruct":
  801. conv = conv.getConverter(reader.tableTag, table["MorphType"])
  802. try:
  803. if conv.repeat:
  804. if isinstance(conv.repeat, int):
  805. countValue = conv.repeat
  806. elif conv.repeat in table:
  807. countValue = table[conv.repeat]
  808. else:
  809. # conv.repeat is a propagated count
  810. countValue = reader[conv.repeat]
  811. countValue += conv.aux
  812. table[conv.name] = conv.readArray(reader, font, table, countValue)
  813. else:
  814. if conv.aux and not eval(conv.aux, None, table):
  815. continue
  816. table[conv.name] = conv.read(reader, font, table)
  817. if conv.isPropagated:
  818. reader[conv.name] = table[conv.name]
  819. except Exception as e:
  820. name = conv.name
  821. e.args = e.args + (name,)
  822. raise
  823. if hasattr(self, "postRead"):
  824. self.postRead(table, font)
  825. else:
  826. self.__dict__.update(table)
  827. del self.__rawTable # succeeded, get rid of debugging info
  828. def compile(self, writer, font):
  829. self.ensureDecompiled()
  830. # TODO Following hack to be removed by rewriting how FormatSwitching tables
  831. # are handled.
  832. # https://github.com/fonttools/fonttools/pull/2238#issuecomment-805192631
  833. if hasattr(self, "preWrite"):
  834. deleteFormat = not hasattr(self, "Format")
  835. table = self.preWrite(font)
  836. deleteFormat = deleteFormat and hasattr(self, "Format")
  837. else:
  838. deleteFormat = False
  839. table = self.__dict__.copy()
  840. # some count references may have been initialized in a custom preWrite; we set
  841. # these in the writer's state beforehand (instead of sequentially) so they will
  842. # be propagated to all nested subtables even if the count appears in the current
  843. # table only *after* the offset to the subtable that it is counting.
  844. for conv in self.getConverters():
  845. if conv.isCount and conv.isPropagated:
  846. value = table.get(conv.name)
  847. if isinstance(value, CountReference):
  848. writer[conv.name] = value
  849. if hasattr(self, "sortCoverageLast"):
  850. writer.sortCoverageLast = 1
  851. if hasattr(self, "DontShare"):
  852. writer.DontShare = True
  853. if hasattr(self.__class__, "LookupType"):
  854. writer["LookupType"].setValue(self.__class__.LookupType)
  855. self.writeFormat(writer)
  856. for conv in self.getConverters():
  857. value = table.get(
  858. conv.name
  859. ) # TODO Handle defaults instead of defaulting to None!
  860. if conv.repeat:
  861. if value is None:
  862. value = []
  863. countValue = len(value) - conv.aux
  864. if isinstance(conv.repeat, int):
  865. assert len(value) == conv.repeat, "expected %d values, got %d" % (
  866. conv.repeat,
  867. len(value),
  868. )
  869. elif conv.repeat in table:
  870. CountReference(table, conv.repeat, value=countValue)
  871. else:
  872. # conv.repeat is a propagated count
  873. writer[conv.repeat].setValue(countValue)
  874. try:
  875. conv.writeArray(writer, font, table, value)
  876. except Exception as e:
  877. e.args = e.args + (conv.name + "[]",)
  878. raise
  879. elif conv.isCount:
  880. # Special-case Count values.
  881. # Assumption: a Count field will *always* precede
  882. # the actual array(s).
  883. # We need a default value, as it may be set later by a nested
  884. # table. We will later store it here.
  885. # We add a reference: by the time the data is assembled
  886. # the Count value will be filled in.
  887. # We ignore the current count value since it will be recomputed,
  888. # unless it's a CountReference that was already initialized in a custom preWrite.
  889. if isinstance(value, CountReference):
  890. ref = value
  891. ref.size = conv.staticSize
  892. writer.writeData(ref)
  893. table[conv.name] = ref.getValue()
  894. else:
  895. ref = writer.writeCountReference(table, conv.name, conv.staticSize)
  896. table[conv.name] = None
  897. if conv.isPropagated:
  898. writer[conv.name] = ref
  899. elif conv.isLookupType:
  900. # We make sure that subtables have the same lookup type,
  901. # and that the type is the same as the one set on the
  902. # Lookup object, if any is set.
  903. if conv.name not in table:
  904. table[conv.name] = None
  905. ref = writer.writeCountReference(
  906. table, conv.name, conv.staticSize, table[conv.name]
  907. )
  908. writer["LookupType"] = ref
  909. else:
  910. if conv.aux and not eval(conv.aux, None, table):
  911. continue
  912. try:
  913. conv.write(writer, font, table, value)
  914. except Exception as e:
  915. name = value.__class__.__name__ if value is not None else conv.name
  916. e.args = e.args + (name,)
  917. raise
  918. if conv.isPropagated:
  919. writer[conv.name] = value
  920. if deleteFormat:
  921. del self.Format
  922. def readFormat(self, reader):
  923. pass
  924. def writeFormat(self, writer):
  925. pass
  926. def toXML(self, xmlWriter, font, attrs=None, name=None):
  927. tableName = name if name else self.__class__.__name__
  928. if attrs is None:
  929. attrs = []
  930. if hasattr(self, "Format"):
  931. attrs = attrs + [("Format", self.Format)]
  932. xmlWriter.begintag(tableName, attrs)
  933. xmlWriter.newline()
  934. self.toXML2(xmlWriter, font)
  935. xmlWriter.endtag(tableName)
  936. xmlWriter.newline()
  937. def toXML2(self, xmlWriter, font):
  938. # Simpler variant of toXML, *only* for the top level tables (like GPOS, GSUB).
  939. # This is because in TTX our parent writes our main tag, and in otBase.py we
  940. # do it ourselves. I think I'm getting schizophrenic...
  941. for conv in self.getConverters():
  942. if conv.repeat:
  943. value = getattr(self, conv.name, [])
  944. for i in range(len(value)):
  945. item = value[i]
  946. conv.xmlWrite(xmlWriter, font, item, conv.name, [("index", i)])
  947. else:
  948. if conv.aux and not eval(conv.aux, None, vars(self)):
  949. continue
  950. value = getattr(
  951. self, conv.name, None
  952. ) # TODO Handle defaults instead of defaulting to None!
  953. conv.xmlWrite(xmlWriter, font, value, conv.name, [])
  954. def fromXML(self, name, attrs, content, font):
  955. try:
  956. conv = self.getConverterByName(name)
  957. except KeyError:
  958. raise # XXX on KeyError, raise nice error
  959. value = conv.xmlRead(attrs, content, font)
  960. if conv.repeat:
  961. seq = getattr(self, conv.name, None)
  962. if seq is None:
  963. seq = []
  964. setattr(self, conv.name, seq)
  965. seq.append(value)
  966. else:
  967. setattr(self, conv.name, value)
  968. def __ne__(self, other):
  969. result = self.__eq__(other)
  970. return result if result is NotImplemented else not result
  971. def __eq__(self, other):
  972. if type(self) != type(other):
  973. return NotImplemented
  974. self.ensureDecompiled()
  975. other.ensureDecompiled()
  976. return self.__dict__ == other.__dict__
  977. class SubTableEntry(NamedTuple):
  978. """See BaseTable.iterSubTables()"""
  979. name: str
  980. value: "BaseTable"
  981. index: Optional[int] = None # index into given array, None for single values
  982. def iterSubTables(self) -> Iterator[SubTableEntry]:
  983. """Yield (name, value, index) namedtuples for all subtables of current table.
  984. A sub-table is an instance of BaseTable (or subclass thereof) that is a child
  985. of self, the current parent table.
  986. The tuples also contain the attribute name (str) of the of parent table to get
  987. a subtable, and optionally, for lists of subtables (i.e. attributes associated
  988. with a converter that has a 'repeat'), an index into the list containing the
  989. given subtable value.
  990. This method can be useful to traverse trees of otTables.
  991. """
  992. for conv in self.getConverters():
  993. name = conv.name
  994. value = getattr(self, name, None)
  995. if value is None:
  996. continue
  997. if isinstance(value, BaseTable):
  998. yield self.SubTableEntry(name, value)
  999. elif isinstance(value, list):
  1000. yield from (
  1001. self.SubTableEntry(name, v, index=i)
  1002. for i, v in enumerate(value)
  1003. if isinstance(v, BaseTable)
  1004. )
  1005. # instance (not @class)method for consistency with FormatSwitchingBaseTable
  1006. def getVariableAttrs(self):
  1007. return getVariableAttrs(self.__class__)
  1008. class FormatSwitchingBaseTable(BaseTable):
  1009. """Minor specialization of BaseTable, for tables that have multiple
  1010. formats, eg. CoverageFormat1 vs. CoverageFormat2."""
  1011. @classmethod
  1012. def getRecordSize(cls, reader):
  1013. return NotImplemented
  1014. def getConverters(self):
  1015. try:
  1016. fmt = self.Format
  1017. except AttributeError:
  1018. # some FormatSwitchingBaseTables (e.g. Coverage) no longer have 'Format'
  1019. # attribute after fully decompiled, only gain one in preWrite before being
  1020. # recompiled. In the decompiled state, these hand-coded classes defined in
  1021. # otTables.py lose their format-specific nature and gain more high-level
  1022. # attributes that are not tied to converters.
  1023. return []
  1024. return self.converters.get(self.Format, [])
  1025. def getConverterByName(self, name):
  1026. return self.convertersByName[self.Format][name]
  1027. def readFormat(self, reader):
  1028. self.Format = reader.readUShort()
  1029. def writeFormat(self, writer):
  1030. writer.writeUShort(self.Format)
  1031. def toXML(self, xmlWriter, font, attrs=None, name=None):
  1032. BaseTable.toXML(self, xmlWriter, font, attrs, name)
  1033. def getVariableAttrs(self):
  1034. return getVariableAttrs(self.__class__, self.Format)
  1035. class UInt8FormatSwitchingBaseTable(FormatSwitchingBaseTable):
  1036. def readFormat(self, reader):
  1037. self.Format = reader.readUInt8()
  1038. def writeFormat(self, writer):
  1039. writer.writeUInt8(self.Format)
  1040. formatSwitchingBaseTables = {
  1041. "uint16": FormatSwitchingBaseTable,
  1042. "uint8": UInt8FormatSwitchingBaseTable,
  1043. }
  1044. def getFormatSwitchingBaseTableClass(formatType):
  1045. try:
  1046. return formatSwitchingBaseTables[formatType]
  1047. except KeyError:
  1048. raise TypeError(f"Unsupported format type: {formatType!r}")
  1049. # memoize since these are parsed from otData.py, thus stay constant
  1050. @lru_cache()
  1051. def getVariableAttrs(cls: BaseTable, fmt: Optional[int] = None) -> Tuple[str]:
  1052. """Return sequence of variable table field names (can be empty).
  1053. Attributes are deemed "variable" when their otData.py's description contain
  1054. 'VarIndexBase + {offset}', e.g. COLRv1 PaintVar* tables.
  1055. """
  1056. if not issubclass(cls, BaseTable):
  1057. raise TypeError(cls)
  1058. if issubclass(cls, FormatSwitchingBaseTable):
  1059. if fmt is None:
  1060. raise TypeError(f"'fmt' is required for format-switching {cls.__name__}")
  1061. converters = cls.convertersByName[fmt]
  1062. else:
  1063. converters = cls.convertersByName
  1064. # assume if no 'VarIndexBase' field is present, table has no variable fields
  1065. if "VarIndexBase" not in converters:
  1066. return ()
  1067. varAttrs = {}
  1068. for name, conv in converters.items():
  1069. offset = conv.getVarIndexOffset()
  1070. if offset is not None:
  1071. varAttrs[name] = offset
  1072. return tuple(sorted(varAttrs, key=varAttrs.__getitem__))
  1073. #
  1074. # Support for ValueRecords
  1075. #
  1076. # This data type is so different from all other OpenType data types that
  1077. # it requires quite a bit of code for itself. It even has special support
  1078. # in OTTableReader and OTTableWriter...
  1079. #
  1080. valueRecordFormat = [
  1081. # Mask Name isDevice signed
  1082. (0x0001, "XPlacement", 0, 1),
  1083. (0x0002, "YPlacement", 0, 1),
  1084. (0x0004, "XAdvance", 0, 1),
  1085. (0x0008, "YAdvance", 0, 1),
  1086. (0x0010, "XPlaDevice", 1, 0),
  1087. (0x0020, "YPlaDevice", 1, 0),
  1088. (0x0040, "XAdvDevice", 1, 0),
  1089. (0x0080, "YAdvDevice", 1, 0),
  1090. # reserved:
  1091. (0x0100, "Reserved1", 0, 0),
  1092. (0x0200, "Reserved2", 0, 0),
  1093. (0x0400, "Reserved3", 0, 0),
  1094. (0x0800, "Reserved4", 0, 0),
  1095. (0x1000, "Reserved5", 0, 0),
  1096. (0x2000, "Reserved6", 0, 0),
  1097. (0x4000, "Reserved7", 0, 0),
  1098. (0x8000, "Reserved8", 0, 0),
  1099. ]
  1100. def _buildDict():
  1101. d = {}
  1102. for mask, name, isDevice, signed in valueRecordFormat:
  1103. d[name] = mask, isDevice, signed
  1104. return d
  1105. valueRecordFormatDict = _buildDict()
  1106. class ValueRecordFactory(object):
  1107. """Given a format code, this object convert ValueRecords."""
  1108. def __init__(self, valueFormat):
  1109. format = []
  1110. for mask, name, isDevice, signed in valueRecordFormat:
  1111. if valueFormat & mask:
  1112. format.append((name, isDevice, signed))
  1113. self.format = format
  1114. def __len__(self):
  1115. return len(self.format)
  1116. def readValueRecord(self, reader, font):
  1117. format = self.format
  1118. if not format:
  1119. return None
  1120. valueRecord = ValueRecord()
  1121. for name, isDevice, signed in format:
  1122. if signed:
  1123. value = reader.readShort()
  1124. else:
  1125. value = reader.readUShort()
  1126. if isDevice:
  1127. if value:
  1128. from . import otTables
  1129. subReader = reader.getSubReader(value)
  1130. value = getattr(otTables, name)()
  1131. value.decompile(subReader, font)
  1132. else:
  1133. value = None
  1134. setattr(valueRecord, name, value)
  1135. return valueRecord
  1136. def writeValueRecord(self, writer, font, valueRecord):
  1137. for name, isDevice, signed in self.format:
  1138. value = getattr(valueRecord, name, 0)
  1139. if isDevice:
  1140. if value:
  1141. subWriter = writer.getSubWriter()
  1142. writer.writeSubTable(subWriter, offsetSize=2)
  1143. value.compile(subWriter, font)
  1144. else:
  1145. writer.writeUShort(0)
  1146. elif signed:
  1147. writer.writeShort(value)
  1148. else:
  1149. writer.writeUShort(value)
  1150. class ValueRecord(object):
  1151. # see ValueRecordFactory
  1152. def __init__(self, valueFormat=None, src=None):
  1153. if valueFormat is not None:
  1154. for mask, name, isDevice, signed in valueRecordFormat:
  1155. if valueFormat & mask:
  1156. setattr(self, name, None if isDevice else 0)
  1157. if src is not None:
  1158. for key, val in src.__dict__.items():
  1159. if not hasattr(self, key):
  1160. continue
  1161. setattr(self, key, val)
  1162. elif src is not None:
  1163. self.__dict__ = src.__dict__.copy()
  1164. def getFormat(self):
  1165. format = 0
  1166. for name in self.__dict__.keys():
  1167. format = format | valueRecordFormatDict[name][0]
  1168. return format
  1169. def getEffectiveFormat(self):
  1170. format = 0
  1171. for name, value in self.__dict__.items():
  1172. if value:
  1173. format = format | valueRecordFormatDict[name][0]
  1174. return format
  1175. def toXML(self, xmlWriter, font, valueName, attrs=None):
  1176. if attrs is None:
  1177. simpleItems = []
  1178. else:
  1179. simpleItems = list(attrs)
  1180. for mask, name, isDevice, format in valueRecordFormat[:4]: # "simple" values
  1181. if hasattr(self, name):
  1182. simpleItems.append((name, getattr(self, name)))
  1183. deviceItems = []
  1184. for mask, name, isDevice, format in valueRecordFormat[4:8]: # device records
  1185. if hasattr(self, name):
  1186. device = getattr(self, name)
  1187. if device is not None:
  1188. deviceItems.append((name, device))
  1189. if deviceItems:
  1190. xmlWriter.begintag(valueName, simpleItems)
  1191. xmlWriter.newline()
  1192. for name, deviceRecord in deviceItems:
  1193. if deviceRecord is not None:
  1194. deviceRecord.toXML(xmlWriter, font, name=name)
  1195. xmlWriter.endtag(valueName)
  1196. xmlWriter.newline()
  1197. else:
  1198. xmlWriter.simpletag(valueName, simpleItems)
  1199. xmlWriter.newline()
  1200. def fromXML(self, name, attrs, content, font):
  1201. from . import otTables
  1202. for k, v in attrs.items():
  1203. setattr(self, k, int(v))
  1204. for element in content:
  1205. if not isinstance(element, tuple):
  1206. continue
  1207. name, attrs, content = element
  1208. value = getattr(otTables, name)()
  1209. for elem2 in content:
  1210. if not isinstance(elem2, tuple):
  1211. continue
  1212. name2, attrs2, content2 = elem2
  1213. value.fromXML(name2, attrs2, content2, font)
  1214. setattr(self, name, value)
  1215. def __ne__(self, other):
  1216. result = self.__eq__(other)
  1217. return result if result is NotImplemented else not result
  1218. def __eq__(self, other):
  1219. if type(self) != type(other):
  1220. return NotImplemented
  1221. return self.__dict__ == other.__dict__