otTables.py 80 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273
  1. # coding: utf-8
  2. """fontTools.ttLib.tables.otTables -- A collection of classes representing the various
  3. OpenType subtables.
  4. Most are constructed upon import from data in otData.py, all are populated with
  5. converter objects from otConverters.py.
  6. """
  7. import copy
  8. from enum import IntEnum
  9. from functools import reduce
  10. from math import radians
  11. import itertools
  12. from collections import defaultdict, namedtuple
  13. from fontTools.ttLib.tables.otTraverse import dfs_base_table
  14. from fontTools.misc.arrayTools import quantizeRect
  15. from fontTools.misc.roundTools import otRound
  16. from fontTools.misc.transform import Transform, Identity
  17. from fontTools.misc.textTools import bytesjoin, pad, safeEval
  18. from fontTools.pens.boundsPen import ControlBoundsPen
  19. from fontTools.pens.transformPen import TransformPen
  20. from .otBase import (
  21. BaseTable,
  22. FormatSwitchingBaseTable,
  23. ValueRecord,
  24. CountReference,
  25. getFormatSwitchingBaseTableClass,
  26. )
  27. from fontTools.feaLib.lookupDebugInfo import LookupDebugInfo, LOOKUP_DEBUG_INFO_KEY
  28. import logging
  29. import struct
  30. from typing import TYPE_CHECKING, Iterator, List, Optional, Set
  31. if TYPE_CHECKING:
  32. from fontTools.ttLib.ttGlyphSet import _TTGlyphSet
  33. log = logging.getLogger(__name__)
  34. class AATStateTable(object):
  35. def __init__(self):
  36. self.GlyphClasses = {} # GlyphID --> GlyphClass
  37. self.States = [] # List of AATState, indexed by state number
  38. self.PerGlyphLookups = [] # [{GlyphID:GlyphID}, ...]
  39. class AATState(object):
  40. def __init__(self):
  41. self.Transitions = {} # GlyphClass --> AATAction
  42. class AATAction(object):
  43. _FLAGS = None
  44. @staticmethod
  45. def compileActions(font, states):
  46. return (None, None)
  47. def _writeFlagsToXML(self, xmlWriter):
  48. flags = [f for f in self._FLAGS if self.__dict__[f]]
  49. if flags:
  50. xmlWriter.simpletag("Flags", value=",".join(flags))
  51. xmlWriter.newline()
  52. if self.ReservedFlags != 0:
  53. xmlWriter.simpletag("ReservedFlags", value="0x%04X" % self.ReservedFlags)
  54. xmlWriter.newline()
  55. def _setFlag(self, flag):
  56. assert flag in self._FLAGS, "unsupported flag %s" % flag
  57. self.__dict__[flag] = True
  58. class RearrangementMorphAction(AATAction):
  59. staticSize = 4
  60. actionHeaderSize = 0
  61. _FLAGS = ["MarkFirst", "DontAdvance", "MarkLast"]
  62. _VERBS = {
  63. 0: "no change",
  64. 1: "Ax ⇒ xA",
  65. 2: "xD ⇒ Dx",
  66. 3: "AxD ⇒ DxA",
  67. 4: "ABx ⇒ xAB",
  68. 5: "ABx ⇒ xBA",
  69. 6: "xCD ⇒ CDx",
  70. 7: "xCD ⇒ DCx",
  71. 8: "AxCD ⇒ CDxA",
  72. 9: "AxCD ⇒ DCxA",
  73. 10: "ABxD ⇒ DxAB",
  74. 11: "ABxD ⇒ DxBA",
  75. 12: "ABxCD ⇒ CDxAB",
  76. 13: "ABxCD ⇒ CDxBA",
  77. 14: "ABxCD ⇒ DCxAB",
  78. 15: "ABxCD ⇒ DCxBA",
  79. }
  80. def __init__(self):
  81. self.NewState = 0
  82. self.Verb = 0
  83. self.MarkFirst = False
  84. self.DontAdvance = False
  85. self.MarkLast = False
  86. self.ReservedFlags = 0
  87. def compile(self, writer, font, actionIndex):
  88. assert actionIndex is None
  89. writer.writeUShort(self.NewState)
  90. assert self.Verb >= 0 and self.Verb <= 15, self.Verb
  91. flags = self.Verb | self.ReservedFlags
  92. if self.MarkFirst:
  93. flags |= 0x8000
  94. if self.DontAdvance:
  95. flags |= 0x4000
  96. if self.MarkLast:
  97. flags |= 0x2000
  98. writer.writeUShort(flags)
  99. def decompile(self, reader, font, actionReader):
  100. assert actionReader is None
  101. self.NewState = reader.readUShort()
  102. flags = reader.readUShort()
  103. self.Verb = flags & 0xF
  104. self.MarkFirst = bool(flags & 0x8000)
  105. self.DontAdvance = bool(flags & 0x4000)
  106. self.MarkLast = bool(flags & 0x2000)
  107. self.ReservedFlags = flags & 0x1FF0
  108. def toXML(self, xmlWriter, font, attrs, name):
  109. xmlWriter.begintag(name, **attrs)
  110. xmlWriter.newline()
  111. xmlWriter.simpletag("NewState", value=self.NewState)
  112. xmlWriter.newline()
  113. self._writeFlagsToXML(xmlWriter)
  114. xmlWriter.simpletag("Verb", value=self.Verb)
  115. verbComment = self._VERBS.get(self.Verb)
  116. if verbComment is not None:
  117. xmlWriter.comment(verbComment)
  118. xmlWriter.newline()
  119. xmlWriter.endtag(name)
  120. xmlWriter.newline()
  121. def fromXML(self, name, attrs, content, font):
  122. self.NewState = self.Verb = self.ReservedFlags = 0
  123. self.MarkFirst = self.DontAdvance = self.MarkLast = False
  124. content = [t for t in content if isinstance(t, tuple)]
  125. for eltName, eltAttrs, eltContent in content:
  126. if eltName == "NewState":
  127. self.NewState = safeEval(eltAttrs["value"])
  128. elif eltName == "Verb":
  129. self.Verb = safeEval(eltAttrs["value"])
  130. elif eltName == "ReservedFlags":
  131. self.ReservedFlags = safeEval(eltAttrs["value"])
  132. elif eltName == "Flags":
  133. for flag in eltAttrs["value"].split(","):
  134. self._setFlag(flag.strip())
  135. class ContextualMorphAction(AATAction):
  136. staticSize = 8
  137. actionHeaderSize = 0
  138. _FLAGS = ["SetMark", "DontAdvance"]
  139. def __init__(self):
  140. self.NewState = 0
  141. self.SetMark, self.DontAdvance = False, False
  142. self.ReservedFlags = 0
  143. self.MarkIndex, self.CurrentIndex = 0xFFFF, 0xFFFF
  144. def compile(self, writer, font, actionIndex):
  145. assert actionIndex is None
  146. writer.writeUShort(self.NewState)
  147. flags = self.ReservedFlags
  148. if self.SetMark:
  149. flags |= 0x8000
  150. if self.DontAdvance:
  151. flags |= 0x4000
  152. writer.writeUShort(flags)
  153. writer.writeUShort(self.MarkIndex)
  154. writer.writeUShort(self.CurrentIndex)
  155. def decompile(self, reader, font, actionReader):
  156. assert actionReader is None
  157. self.NewState = reader.readUShort()
  158. flags = reader.readUShort()
  159. self.SetMark = bool(flags & 0x8000)
  160. self.DontAdvance = bool(flags & 0x4000)
  161. self.ReservedFlags = flags & 0x3FFF
  162. self.MarkIndex = reader.readUShort()
  163. self.CurrentIndex = reader.readUShort()
  164. def toXML(self, xmlWriter, font, attrs, name):
  165. xmlWriter.begintag(name, **attrs)
  166. xmlWriter.newline()
  167. xmlWriter.simpletag("NewState", value=self.NewState)
  168. xmlWriter.newline()
  169. self._writeFlagsToXML(xmlWriter)
  170. xmlWriter.simpletag("MarkIndex", value=self.MarkIndex)
  171. xmlWriter.newline()
  172. xmlWriter.simpletag("CurrentIndex", value=self.CurrentIndex)
  173. xmlWriter.newline()
  174. xmlWriter.endtag(name)
  175. xmlWriter.newline()
  176. def fromXML(self, name, attrs, content, font):
  177. self.NewState = self.ReservedFlags = 0
  178. self.SetMark = self.DontAdvance = False
  179. self.MarkIndex, self.CurrentIndex = 0xFFFF, 0xFFFF
  180. content = [t for t in content if isinstance(t, tuple)]
  181. for eltName, eltAttrs, eltContent in content:
  182. if eltName == "NewState":
  183. self.NewState = safeEval(eltAttrs["value"])
  184. elif eltName == "Flags":
  185. for flag in eltAttrs["value"].split(","):
  186. self._setFlag(flag.strip())
  187. elif eltName == "ReservedFlags":
  188. self.ReservedFlags = safeEval(eltAttrs["value"])
  189. elif eltName == "MarkIndex":
  190. self.MarkIndex = safeEval(eltAttrs["value"])
  191. elif eltName == "CurrentIndex":
  192. self.CurrentIndex = safeEval(eltAttrs["value"])
  193. class LigAction(object):
  194. def __init__(self):
  195. self.Store = False
  196. # GlyphIndexDelta is a (possibly negative) delta that gets
  197. # added to the glyph ID at the top of the AAT runtime
  198. # execution stack. It is *not* a byte offset into the
  199. # morx table. The result of the addition, which is performed
  200. # at run time by the shaping engine, is an index into
  201. # the ligature components table. See 'morx' specification.
  202. # In the AAT specification, this field is called Offset;
  203. # but its meaning is quite different from other offsets
  204. # in either AAT or OpenType, so we use a different name.
  205. self.GlyphIndexDelta = 0
  206. class LigatureMorphAction(AATAction):
  207. staticSize = 6
  208. # 4 bytes for each of {action,ligComponents,ligatures}Offset
  209. actionHeaderSize = 12
  210. _FLAGS = ["SetComponent", "DontAdvance"]
  211. def __init__(self):
  212. self.NewState = 0
  213. self.SetComponent, self.DontAdvance = False, False
  214. self.ReservedFlags = 0
  215. self.Actions = []
  216. def compile(self, writer, font, actionIndex):
  217. assert actionIndex is not None
  218. writer.writeUShort(self.NewState)
  219. flags = self.ReservedFlags
  220. if self.SetComponent:
  221. flags |= 0x8000
  222. if self.DontAdvance:
  223. flags |= 0x4000
  224. if len(self.Actions) > 0:
  225. flags |= 0x2000
  226. writer.writeUShort(flags)
  227. if len(self.Actions) > 0:
  228. actions = self.compileLigActions()
  229. writer.writeUShort(actionIndex[actions])
  230. else:
  231. writer.writeUShort(0)
  232. def decompile(self, reader, font, actionReader):
  233. assert actionReader is not None
  234. self.NewState = reader.readUShort()
  235. flags = reader.readUShort()
  236. self.SetComponent = bool(flags & 0x8000)
  237. self.DontAdvance = bool(flags & 0x4000)
  238. performAction = bool(flags & 0x2000)
  239. # As of 2017-09-12, the 'morx' specification says that
  240. # the reserved bitmask in ligature subtables is 0x3FFF.
  241. # However, the specification also defines a flag 0x2000,
  242. # so the reserved value should actually be 0x1FFF.
  243. # TODO: Report this specification bug to Apple.
  244. self.ReservedFlags = flags & 0x1FFF
  245. actionIndex = reader.readUShort()
  246. if performAction:
  247. self.Actions = self._decompileLigActions(actionReader, actionIndex)
  248. else:
  249. self.Actions = []
  250. @staticmethod
  251. def compileActions(font, states):
  252. result, actions, actionIndex = b"", set(), {}
  253. for state in states:
  254. for _glyphClass, trans in state.Transitions.items():
  255. actions.add(trans.compileLigActions())
  256. # Sort the compiled actions in decreasing order of
  257. # length, so that the longer sequence come before the
  258. # shorter ones. For each compiled action ABCD, its
  259. # suffixes BCD, CD, and D do not be encoded separately
  260. # (in case they occur); instead, we can just store an
  261. # index that points into the middle of the longer
  262. # sequence. Every compiled AAT ligature sequence is
  263. # terminated with an end-of-sequence flag, which can
  264. # only be set on the last element of the sequence.
  265. # Therefore, it is sufficient to consider just the
  266. # suffixes.
  267. for a in sorted(actions, key=lambda x: (-len(x), x)):
  268. if a not in actionIndex:
  269. for i in range(0, len(a), 4):
  270. suffix = a[i:]
  271. suffixIndex = (len(result) + i) // 4
  272. actionIndex.setdefault(suffix, suffixIndex)
  273. result += a
  274. result = pad(result, 4)
  275. return (result, actionIndex)
  276. def compileLigActions(self):
  277. result = []
  278. for i, action in enumerate(self.Actions):
  279. last = i == len(self.Actions) - 1
  280. value = action.GlyphIndexDelta & 0x3FFFFFFF
  281. value |= 0x80000000 if last else 0
  282. value |= 0x40000000 if action.Store else 0
  283. result.append(struct.pack(">L", value))
  284. return bytesjoin(result)
  285. def _decompileLigActions(self, actionReader, actionIndex):
  286. actions = []
  287. last = False
  288. reader = actionReader.getSubReader(actionReader.pos + actionIndex * 4)
  289. while not last:
  290. value = reader.readULong()
  291. last = bool(value & 0x80000000)
  292. action = LigAction()
  293. actions.append(action)
  294. action.Store = bool(value & 0x40000000)
  295. delta = value & 0x3FFFFFFF
  296. if delta >= 0x20000000: # sign-extend 30-bit value
  297. delta = -0x40000000 + delta
  298. action.GlyphIndexDelta = delta
  299. return actions
  300. def fromXML(self, name, attrs, content, font):
  301. self.NewState = self.ReservedFlags = 0
  302. self.SetComponent = self.DontAdvance = False
  303. self.ReservedFlags = 0
  304. self.Actions = []
  305. content = [t for t in content if isinstance(t, tuple)]
  306. for eltName, eltAttrs, eltContent in content:
  307. if eltName == "NewState":
  308. self.NewState = safeEval(eltAttrs["value"])
  309. elif eltName == "Flags":
  310. for flag in eltAttrs["value"].split(","):
  311. self._setFlag(flag.strip())
  312. elif eltName == "ReservedFlags":
  313. self.ReservedFlags = safeEval(eltAttrs["value"])
  314. elif eltName == "Action":
  315. action = LigAction()
  316. flags = eltAttrs.get("Flags", "").split(",")
  317. flags = [f.strip() for f in flags]
  318. action.Store = "Store" in flags
  319. action.GlyphIndexDelta = safeEval(eltAttrs["GlyphIndexDelta"])
  320. self.Actions.append(action)
  321. def toXML(self, xmlWriter, font, attrs, name):
  322. xmlWriter.begintag(name, **attrs)
  323. xmlWriter.newline()
  324. xmlWriter.simpletag("NewState", value=self.NewState)
  325. xmlWriter.newline()
  326. self._writeFlagsToXML(xmlWriter)
  327. for action in self.Actions:
  328. attribs = [("GlyphIndexDelta", action.GlyphIndexDelta)]
  329. if action.Store:
  330. attribs.append(("Flags", "Store"))
  331. xmlWriter.simpletag("Action", attribs)
  332. xmlWriter.newline()
  333. xmlWriter.endtag(name)
  334. xmlWriter.newline()
  335. class InsertionMorphAction(AATAction):
  336. staticSize = 8
  337. actionHeaderSize = 4 # 4 bytes for actionOffset
  338. _FLAGS = [
  339. "SetMark",
  340. "DontAdvance",
  341. "CurrentIsKashidaLike",
  342. "MarkedIsKashidaLike",
  343. "CurrentInsertBefore",
  344. "MarkedInsertBefore",
  345. ]
  346. def __init__(self):
  347. self.NewState = 0
  348. for flag in self._FLAGS:
  349. setattr(self, flag, False)
  350. self.ReservedFlags = 0
  351. self.CurrentInsertionAction, self.MarkedInsertionAction = [], []
  352. def compile(self, writer, font, actionIndex):
  353. assert actionIndex is not None
  354. writer.writeUShort(self.NewState)
  355. flags = self.ReservedFlags
  356. if self.SetMark:
  357. flags |= 0x8000
  358. if self.DontAdvance:
  359. flags |= 0x4000
  360. if self.CurrentIsKashidaLike:
  361. flags |= 0x2000
  362. if self.MarkedIsKashidaLike:
  363. flags |= 0x1000
  364. if self.CurrentInsertBefore:
  365. flags |= 0x0800
  366. if self.MarkedInsertBefore:
  367. flags |= 0x0400
  368. flags |= len(self.CurrentInsertionAction) << 5
  369. flags |= len(self.MarkedInsertionAction)
  370. writer.writeUShort(flags)
  371. if len(self.CurrentInsertionAction) > 0:
  372. currentIndex = actionIndex[tuple(self.CurrentInsertionAction)]
  373. else:
  374. currentIndex = 0xFFFF
  375. writer.writeUShort(currentIndex)
  376. if len(self.MarkedInsertionAction) > 0:
  377. markedIndex = actionIndex[tuple(self.MarkedInsertionAction)]
  378. else:
  379. markedIndex = 0xFFFF
  380. writer.writeUShort(markedIndex)
  381. def decompile(self, reader, font, actionReader):
  382. assert actionReader is not None
  383. self.NewState = reader.readUShort()
  384. flags = reader.readUShort()
  385. self.SetMark = bool(flags & 0x8000)
  386. self.DontAdvance = bool(flags & 0x4000)
  387. self.CurrentIsKashidaLike = bool(flags & 0x2000)
  388. self.MarkedIsKashidaLike = bool(flags & 0x1000)
  389. self.CurrentInsertBefore = bool(flags & 0x0800)
  390. self.MarkedInsertBefore = bool(flags & 0x0400)
  391. self.CurrentInsertionAction = self._decompileInsertionAction(
  392. actionReader, font, index=reader.readUShort(), count=((flags & 0x03E0) >> 5)
  393. )
  394. self.MarkedInsertionAction = self._decompileInsertionAction(
  395. actionReader, font, index=reader.readUShort(), count=(flags & 0x001F)
  396. )
  397. def _decompileInsertionAction(self, actionReader, font, index, count):
  398. if index == 0xFFFF or count == 0:
  399. return []
  400. reader = actionReader.getSubReader(actionReader.pos + index * 2)
  401. return font.getGlyphNameMany(reader.readUShortArray(count))
  402. def toXML(self, xmlWriter, font, attrs, name):
  403. xmlWriter.begintag(name, **attrs)
  404. xmlWriter.newline()
  405. xmlWriter.simpletag("NewState", value=self.NewState)
  406. xmlWriter.newline()
  407. self._writeFlagsToXML(xmlWriter)
  408. for g in self.CurrentInsertionAction:
  409. xmlWriter.simpletag("CurrentInsertionAction", glyph=g)
  410. xmlWriter.newline()
  411. for g in self.MarkedInsertionAction:
  412. xmlWriter.simpletag("MarkedInsertionAction", glyph=g)
  413. xmlWriter.newline()
  414. xmlWriter.endtag(name)
  415. xmlWriter.newline()
  416. def fromXML(self, name, attrs, content, font):
  417. self.__init__()
  418. content = [t for t in content if isinstance(t, tuple)]
  419. for eltName, eltAttrs, eltContent in content:
  420. if eltName == "NewState":
  421. self.NewState = safeEval(eltAttrs["value"])
  422. elif eltName == "Flags":
  423. for flag in eltAttrs["value"].split(","):
  424. self._setFlag(flag.strip())
  425. elif eltName == "CurrentInsertionAction":
  426. self.CurrentInsertionAction.append(eltAttrs["glyph"])
  427. elif eltName == "MarkedInsertionAction":
  428. self.MarkedInsertionAction.append(eltAttrs["glyph"])
  429. else:
  430. assert False, eltName
  431. @staticmethod
  432. def compileActions(font, states):
  433. actions, actionIndex, result = set(), {}, b""
  434. for state in states:
  435. for _glyphClass, trans in state.Transitions.items():
  436. if trans.CurrentInsertionAction is not None:
  437. actions.add(tuple(trans.CurrentInsertionAction))
  438. if trans.MarkedInsertionAction is not None:
  439. actions.add(tuple(trans.MarkedInsertionAction))
  440. # Sort the compiled actions in decreasing order of
  441. # length, so that the longer sequence come before the
  442. # shorter ones.
  443. for action in sorted(actions, key=lambda x: (-len(x), x)):
  444. # We insert all sub-sequences of the action glyph sequence
  445. # into actionIndex. For example, if one action triggers on
  446. # glyph sequence [A, B, C, D, E] and another action triggers
  447. # on [C, D], we return result=[A, B, C, D, E] (as list of
  448. # encoded glyph IDs), and actionIndex={('A','B','C','D','E'): 0,
  449. # ('C','D'): 2}.
  450. if action in actionIndex:
  451. continue
  452. for start in range(0, len(action)):
  453. startIndex = (len(result) // 2) + start
  454. for limit in range(start, len(action)):
  455. glyphs = action[start : limit + 1]
  456. actionIndex.setdefault(glyphs, startIndex)
  457. for glyph in action:
  458. glyphID = font.getGlyphID(glyph)
  459. result += struct.pack(">H", glyphID)
  460. return result, actionIndex
  461. class FeatureParams(BaseTable):
  462. def compile(self, writer, font):
  463. assert (
  464. featureParamTypes.get(writer["FeatureTag"]) == self.__class__
  465. ), "Wrong FeatureParams type for feature '%s': %s" % (
  466. writer["FeatureTag"],
  467. self.__class__.__name__,
  468. )
  469. BaseTable.compile(self, writer, font)
  470. def toXML(self, xmlWriter, font, attrs=None, name=None):
  471. BaseTable.toXML(self, xmlWriter, font, attrs, name=self.__class__.__name__)
  472. class FeatureParamsSize(FeatureParams):
  473. pass
  474. class FeatureParamsStylisticSet(FeatureParams):
  475. pass
  476. class FeatureParamsCharacterVariants(FeatureParams):
  477. pass
  478. class Coverage(FormatSwitchingBaseTable):
  479. # manual implementation to get rid of glyphID dependencies
  480. def populateDefaults(self, propagator=None):
  481. if not hasattr(self, "glyphs"):
  482. self.glyphs = []
  483. def postRead(self, rawTable, font):
  484. if self.Format == 1:
  485. self.glyphs = rawTable["GlyphArray"]
  486. elif self.Format == 2:
  487. glyphs = self.glyphs = []
  488. ranges = rawTable["RangeRecord"]
  489. # Some SIL fonts have coverage entries that don't have sorted
  490. # StartCoverageIndex. If it is so, fixup and warn. We undo
  491. # this when writing font out.
  492. sorted_ranges = sorted(ranges, key=lambda a: a.StartCoverageIndex)
  493. if ranges != sorted_ranges:
  494. log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.")
  495. ranges = sorted_ranges
  496. del sorted_ranges
  497. for r in ranges:
  498. start = r.Start
  499. end = r.End
  500. startID = font.getGlyphID(start)
  501. endID = font.getGlyphID(end) + 1
  502. glyphs.extend(font.getGlyphNameMany(range(startID, endID)))
  503. else:
  504. self.glyphs = []
  505. log.warning("Unknown Coverage format: %s", self.Format)
  506. del self.Format # Don't need this anymore
  507. def preWrite(self, font):
  508. glyphs = getattr(self, "glyphs", None)
  509. if glyphs is None:
  510. glyphs = self.glyphs = []
  511. format = 1
  512. rawTable = {"GlyphArray": glyphs}
  513. if glyphs:
  514. # find out whether Format 2 is more compact or not
  515. glyphIDs = font.getGlyphIDMany(glyphs)
  516. brokenOrder = sorted(glyphIDs) != glyphIDs
  517. last = glyphIDs[0]
  518. ranges = [[last]]
  519. for glyphID in glyphIDs[1:]:
  520. if glyphID != last + 1:
  521. ranges[-1].append(last)
  522. ranges.append([glyphID])
  523. last = glyphID
  524. ranges[-1].append(last)
  525. if brokenOrder or len(ranges) * 3 < len(glyphs): # 3 words vs. 1 word
  526. # Format 2 is more compact
  527. index = 0
  528. for i in range(len(ranges)):
  529. start, end = ranges[i]
  530. r = RangeRecord()
  531. r.StartID = start
  532. r.Start = font.getGlyphName(start)
  533. r.End = font.getGlyphName(end)
  534. r.StartCoverageIndex = index
  535. ranges[i] = r
  536. index = index + end - start + 1
  537. if brokenOrder:
  538. log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.")
  539. ranges.sort(key=lambda a: a.StartID)
  540. for r in ranges:
  541. del r.StartID
  542. format = 2
  543. rawTable = {"RangeRecord": ranges}
  544. # else:
  545. # fallthrough; Format 1 is more compact
  546. self.Format = format
  547. return rawTable
  548. def toXML2(self, xmlWriter, font):
  549. for glyphName in getattr(self, "glyphs", []):
  550. xmlWriter.simpletag("Glyph", value=glyphName)
  551. xmlWriter.newline()
  552. def fromXML(self, name, attrs, content, font):
  553. glyphs = getattr(self, "glyphs", None)
  554. if glyphs is None:
  555. glyphs = []
  556. self.glyphs = glyphs
  557. glyphs.append(attrs["value"])
  558. # The special 0xFFFFFFFF delta-set index is used to indicate that there
  559. # is no variation data in the ItemVariationStore for a given variable field
  560. NO_VARIATION_INDEX = 0xFFFFFFFF
  561. class DeltaSetIndexMap(getFormatSwitchingBaseTableClass("uint8")):
  562. def populateDefaults(self, propagator=None):
  563. if not hasattr(self, "mapping"):
  564. self.mapping = []
  565. def postRead(self, rawTable, font):
  566. assert (rawTable["EntryFormat"] & 0xFFC0) == 0
  567. self.mapping = rawTable["mapping"]
  568. @staticmethod
  569. def getEntryFormat(mapping):
  570. ored = 0
  571. for idx in mapping:
  572. ored |= idx
  573. inner = ored & 0xFFFF
  574. innerBits = 0
  575. while inner:
  576. innerBits += 1
  577. inner >>= 1
  578. innerBits = max(innerBits, 1)
  579. assert innerBits <= 16
  580. ored = (ored >> (16 - innerBits)) | (ored & ((1 << innerBits) - 1))
  581. if ored <= 0x000000FF:
  582. entrySize = 1
  583. elif ored <= 0x0000FFFF:
  584. entrySize = 2
  585. elif ored <= 0x00FFFFFF:
  586. entrySize = 3
  587. else:
  588. entrySize = 4
  589. return ((entrySize - 1) << 4) | (innerBits - 1)
  590. def preWrite(self, font):
  591. mapping = getattr(self, "mapping", None)
  592. if mapping is None:
  593. mapping = self.mapping = []
  594. self.Format = 1 if len(mapping) > 0xFFFF else 0
  595. rawTable = self.__dict__.copy()
  596. rawTable["MappingCount"] = len(mapping)
  597. rawTable["EntryFormat"] = self.getEntryFormat(mapping)
  598. return rawTable
  599. def toXML2(self, xmlWriter, font):
  600. # Make xml dump less verbose, by omitting no-op entries like:
  601. # <Map index="..." outer="65535" inner="65535"/>
  602. xmlWriter.comment("Omitted values default to 0xFFFF/0xFFFF (no variations)")
  603. xmlWriter.newline()
  604. for i, value in enumerate(getattr(self, "mapping", [])):
  605. attrs = [("index", i)]
  606. if value != NO_VARIATION_INDEX:
  607. attrs.extend(
  608. [
  609. ("outer", value >> 16),
  610. ("inner", value & 0xFFFF),
  611. ]
  612. )
  613. xmlWriter.simpletag("Map", attrs)
  614. xmlWriter.newline()
  615. def fromXML(self, name, attrs, content, font):
  616. mapping = getattr(self, "mapping", None)
  617. if mapping is None:
  618. self.mapping = mapping = []
  619. index = safeEval(attrs["index"])
  620. outer = safeEval(attrs.get("outer", "0xFFFF"))
  621. inner = safeEval(attrs.get("inner", "0xFFFF"))
  622. assert inner <= 0xFFFF
  623. mapping.insert(index, (outer << 16) | inner)
  624. class VarIdxMap(BaseTable):
  625. def populateDefaults(self, propagator=None):
  626. if not hasattr(self, "mapping"):
  627. self.mapping = {}
  628. def postRead(self, rawTable, font):
  629. assert (rawTable["EntryFormat"] & 0xFFC0) == 0
  630. glyphOrder = font.getGlyphOrder()
  631. mapList = rawTable["mapping"]
  632. mapList.extend([mapList[-1]] * (len(glyphOrder) - len(mapList)))
  633. self.mapping = dict(zip(glyphOrder, mapList))
  634. def preWrite(self, font):
  635. mapping = getattr(self, "mapping", None)
  636. if mapping is None:
  637. mapping = self.mapping = {}
  638. glyphOrder = font.getGlyphOrder()
  639. mapping = [mapping[g] for g in glyphOrder]
  640. while len(mapping) > 1 and mapping[-2] == mapping[-1]:
  641. del mapping[-1]
  642. rawTable = {"mapping": mapping}
  643. rawTable["MappingCount"] = len(mapping)
  644. rawTable["EntryFormat"] = DeltaSetIndexMap.getEntryFormat(mapping)
  645. return rawTable
  646. def toXML2(self, xmlWriter, font):
  647. for glyph, value in sorted(getattr(self, "mapping", {}).items()):
  648. attrs = (
  649. ("glyph", glyph),
  650. ("outer", value >> 16),
  651. ("inner", value & 0xFFFF),
  652. )
  653. xmlWriter.simpletag("Map", attrs)
  654. xmlWriter.newline()
  655. def fromXML(self, name, attrs, content, font):
  656. mapping = getattr(self, "mapping", None)
  657. if mapping is None:
  658. mapping = {}
  659. self.mapping = mapping
  660. try:
  661. glyph = attrs["glyph"]
  662. except: # https://github.com/fonttools/fonttools/commit/21cbab8ce9ded3356fef3745122da64dcaf314e9#commitcomment-27649836
  663. glyph = font.getGlyphOrder()[attrs["index"]]
  664. outer = safeEval(attrs["outer"])
  665. inner = safeEval(attrs["inner"])
  666. assert inner <= 0xFFFF
  667. mapping[glyph] = (outer << 16) | inner
  668. class VarRegionList(BaseTable):
  669. def preWrite(self, font):
  670. # The OT spec says VarStore.VarRegionList.RegionAxisCount should always
  671. # be equal to the fvar.axisCount, and OTS < v8.0.0 enforces this rule
  672. # even when the VarRegionList is empty. We can't treat RegionAxisCount
  673. # like a normal propagated count (== len(Region[i].VarRegionAxis)),
  674. # otherwise it would default to 0 if VarRegionList is empty.
  675. # Thus, we force it to always be equal to fvar.axisCount.
  676. # https://github.com/khaledhosny/ots/pull/192
  677. fvarTable = font.get("fvar")
  678. if fvarTable:
  679. self.RegionAxisCount = len(fvarTable.axes)
  680. return {
  681. **self.__dict__,
  682. "RegionAxisCount": CountReference(self.__dict__, "RegionAxisCount"),
  683. }
  684. class SingleSubst(FormatSwitchingBaseTable):
  685. def populateDefaults(self, propagator=None):
  686. if not hasattr(self, "mapping"):
  687. self.mapping = {}
  688. def postRead(self, rawTable, font):
  689. mapping = {}
  690. input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
  691. if self.Format == 1:
  692. delta = rawTable["DeltaGlyphID"]
  693. inputGIDS = font.getGlyphIDMany(input)
  694. outGIDS = [(glyphID + delta) % 65536 for glyphID in inputGIDS]
  695. outNames = font.getGlyphNameMany(outGIDS)
  696. for inp, out in zip(input, outNames):
  697. mapping[inp] = out
  698. elif self.Format == 2:
  699. assert (
  700. len(input) == rawTable["GlyphCount"]
  701. ), "invalid SingleSubstFormat2 table"
  702. subst = rawTable["Substitute"]
  703. for inp, sub in zip(input, subst):
  704. mapping[inp] = sub
  705. else:
  706. assert 0, "unknown format: %s" % self.Format
  707. self.mapping = mapping
  708. del self.Format # Don't need this anymore
  709. def preWrite(self, font):
  710. mapping = getattr(self, "mapping", None)
  711. if mapping is None:
  712. mapping = self.mapping = {}
  713. items = list(mapping.items())
  714. getGlyphID = font.getGlyphID
  715. gidItems = [(getGlyphID(a), getGlyphID(b)) for a, b in items]
  716. sortableItems = sorted(zip(gidItems, items))
  717. # figure out format
  718. format = 2
  719. delta = None
  720. for inID, outID in gidItems:
  721. if delta is None:
  722. delta = (outID - inID) % 65536
  723. if (inID + delta) % 65536 != outID:
  724. break
  725. else:
  726. if delta is None:
  727. # the mapping is empty, better use format 2
  728. format = 2
  729. else:
  730. format = 1
  731. rawTable = {}
  732. self.Format = format
  733. cov = Coverage()
  734. input = [item[1][0] for item in sortableItems]
  735. subst = [item[1][1] for item in sortableItems]
  736. cov.glyphs = input
  737. rawTable["Coverage"] = cov
  738. if format == 1:
  739. assert delta is not None
  740. rawTable["DeltaGlyphID"] = delta
  741. else:
  742. rawTable["Substitute"] = subst
  743. return rawTable
  744. def toXML2(self, xmlWriter, font):
  745. items = sorted(self.mapping.items())
  746. for inGlyph, outGlyph in items:
  747. xmlWriter.simpletag("Substitution", [("in", inGlyph), ("out", outGlyph)])
  748. xmlWriter.newline()
  749. def fromXML(self, name, attrs, content, font):
  750. mapping = getattr(self, "mapping", None)
  751. if mapping is None:
  752. mapping = {}
  753. self.mapping = mapping
  754. mapping[attrs["in"]] = attrs["out"]
  755. class MultipleSubst(FormatSwitchingBaseTable):
  756. def populateDefaults(self, propagator=None):
  757. if not hasattr(self, "mapping"):
  758. self.mapping = {}
  759. def postRead(self, rawTable, font):
  760. mapping = {}
  761. if self.Format == 1:
  762. glyphs = _getGlyphsFromCoverageTable(rawTable["Coverage"])
  763. subst = [s.Substitute for s in rawTable["Sequence"]]
  764. mapping = dict(zip(glyphs, subst))
  765. else:
  766. assert 0, "unknown format: %s" % self.Format
  767. self.mapping = mapping
  768. del self.Format # Don't need this anymore
  769. def preWrite(self, font):
  770. mapping = getattr(self, "mapping", None)
  771. if mapping is None:
  772. mapping = self.mapping = {}
  773. cov = Coverage()
  774. cov.glyphs = sorted(list(mapping.keys()), key=font.getGlyphID)
  775. self.Format = 1
  776. rawTable = {
  777. "Coverage": cov,
  778. "Sequence": [self.makeSequence_(mapping[glyph]) for glyph in cov.glyphs],
  779. }
  780. return rawTable
  781. def toXML2(self, xmlWriter, font):
  782. items = sorted(self.mapping.items())
  783. for inGlyph, outGlyphs in items:
  784. out = ",".join(outGlyphs)
  785. xmlWriter.simpletag("Substitution", [("in", inGlyph), ("out", out)])
  786. xmlWriter.newline()
  787. def fromXML(self, name, attrs, content, font):
  788. mapping = getattr(self, "mapping", None)
  789. if mapping is None:
  790. mapping = {}
  791. self.mapping = mapping
  792. # TTX v3.0 and earlier.
  793. if name == "Coverage":
  794. self.old_coverage_ = []
  795. for element in content:
  796. if not isinstance(element, tuple):
  797. continue
  798. element_name, element_attrs, _ = element
  799. if element_name == "Glyph":
  800. self.old_coverage_.append(element_attrs["value"])
  801. return
  802. if name == "Sequence":
  803. index = int(attrs.get("index", len(mapping)))
  804. glyph = self.old_coverage_[index]
  805. glyph_mapping = mapping[glyph] = []
  806. for element in content:
  807. if not isinstance(element, tuple):
  808. continue
  809. element_name, element_attrs, _ = element
  810. if element_name == "Substitute":
  811. glyph_mapping.append(element_attrs["value"])
  812. return
  813. # TTX v3.1 and later.
  814. outGlyphs = attrs["out"].split(",") if attrs["out"] else []
  815. mapping[attrs["in"]] = [g.strip() for g in outGlyphs]
  816. @staticmethod
  817. def makeSequence_(g):
  818. seq = Sequence()
  819. seq.Substitute = g
  820. return seq
  821. class ClassDef(FormatSwitchingBaseTable):
  822. def populateDefaults(self, propagator=None):
  823. if not hasattr(self, "classDefs"):
  824. self.classDefs = {}
  825. def postRead(self, rawTable, font):
  826. classDefs = {}
  827. if self.Format == 1:
  828. start = rawTable["StartGlyph"]
  829. classList = rawTable["ClassValueArray"]
  830. startID = font.getGlyphID(start)
  831. endID = startID + len(classList)
  832. glyphNames = font.getGlyphNameMany(range(startID, endID))
  833. for glyphName, cls in zip(glyphNames, classList):
  834. if cls:
  835. classDefs[glyphName] = cls
  836. elif self.Format == 2:
  837. records = rawTable["ClassRangeRecord"]
  838. for rec in records:
  839. cls = rec.Class
  840. if not cls:
  841. continue
  842. start = rec.Start
  843. end = rec.End
  844. startID = font.getGlyphID(start)
  845. endID = font.getGlyphID(end) + 1
  846. glyphNames = font.getGlyphNameMany(range(startID, endID))
  847. for glyphName in glyphNames:
  848. classDefs[glyphName] = cls
  849. else:
  850. log.warning("Unknown ClassDef format: %s", self.Format)
  851. self.classDefs = classDefs
  852. del self.Format # Don't need this anymore
  853. def _getClassRanges(self, font):
  854. classDefs = getattr(self, "classDefs", None)
  855. if classDefs is None:
  856. self.classDefs = {}
  857. return
  858. getGlyphID = font.getGlyphID
  859. items = []
  860. for glyphName, cls in classDefs.items():
  861. if not cls:
  862. continue
  863. items.append((getGlyphID(glyphName), glyphName, cls))
  864. if items:
  865. items.sort()
  866. last, lastName, lastCls = items[0]
  867. ranges = [[lastCls, last, lastName]]
  868. for glyphID, glyphName, cls in items[1:]:
  869. if glyphID != last + 1 or cls != lastCls:
  870. ranges[-1].extend([last, lastName])
  871. ranges.append([cls, glyphID, glyphName])
  872. last = glyphID
  873. lastName = glyphName
  874. lastCls = cls
  875. ranges[-1].extend([last, lastName])
  876. return ranges
  877. def preWrite(self, font):
  878. format = 2
  879. rawTable = {"ClassRangeRecord": []}
  880. ranges = self._getClassRanges(font)
  881. if ranges:
  882. startGlyph = ranges[0][1]
  883. endGlyph = ranges[-1][3]
  884. glyphCount = endGlyph - startGlyph + 1
  885. if len(ranges) * 3 < glyphCount + 1:
  886. # Format 2 is more compact
  887. for i in range(len(ranges)):
  888. cls, start, startName, end, endName = ranges[i]
  889. rec = ClassRangeRecord()
  890. rec.Start = startName
  891. rec.End = endName
  892. rec.Class = cls
  893. ranges[i] = rec
  894. format = 2
  895. rawTable = {"ClassRangeRecord": ranges}
  896. else:
  897. # Format 1 is more compact
  898. startGlyphName = ranges[0][2]
  899. classes = [0] * glyphCount
  900. for cls, start, startName, end, endName in ranges:
  901. for g in range(start - startGlyph, end - startGlyph + 1):
  902. classes[g] = cls
  903. format = 1
  904. rawTable = {"StartGlyph": startGlyphName, "ClassValueArray": classes}
  905. self.Format = format
  906. return rawTable
  907. def toXML2(self, xmlWriter, font):
  908. items = sorted(self.classDefs.items())
  909. for glyphName, cls in items:
  910. xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)])
  911. xmlWriter.newline()
  912. def fromXML(self, name, attrs, content, font):
  913. classDefs = getattr(self, "classDefs", None)
  914. if classDefs is None:
  915. classDefs = {}
  916. self.classDefs = classDefs
  917. classDefs[attrs["glyph"]] = int(attrs["class"])
  918. class AlternateSubst(FormatSwitchingBaseTable):
  919. def populateDefaults(self, propagator=None):
  920. if not hasattr(self, "alternates"):
  921. self.alternates = {}
  922. def postRead(self, rawTable, font):
  923. alternates = {}
  924. if self.Format == 1:
  925. input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
  926. alts = rawTable["AlternateSet"]
  927. assert len(input) == len(alts)
  928. for inp, alt in zip(input, alts):
  929. alternates[inp] = alt.Alternate
  930. else:
  931. assert 0, "unknown format: %s" % self.Format
  932. self.alternates = alternates
  933. del self.Format # Don't need this anymore
  934. def preWrite(self, font):
  935. self.Format = 1
  936. alternates = getattr(self, "alternates", None)
  937. if alternates is None:
  938. alternates = self.alternates = {}
  939. items = list(alternates.items())
  940. for i in range(len(items)):
  941. glyphName, set = items[i]
  942. items[i] = font.getGlyphID(glyphName), glyphName, set
  943. items.sort()
  944. cov = Coverage()
  945. cov.glyphs = [item[1] for item in items]
  946. alternates = []
  947. setList = [item[-1] for item in items]
  948. for set in setList:
  949. alts = AlternateSet()
  950. alts.Alternate = set
  951. alternates.append(alts)
  952. # a special case to deal with the fact that several hundred Adobe Japan1-5
  953. # CJK fonts will overflow an offset if the coverage table isn't pushed to the end.
  954. # Also useful in that when splitting a sub-table because of an offset overflow
  955. # I don't need to calculate the change in the subtable offset due to the change in the coverage table size.
  956. # Allows packing more rules in subtable.
  957. self.sortCoverageLast = 1
  958. return {"Coverage": cov, "AlternateSet": alternates}
  959. def toXML2(self, xmlWriter, font):
  960. items = sorted(self.alternates.items())
  961. for glyphName, alternates in items:
  962. xmlWriter.begintag("AlternateSet", glyph=glyphName)
  963. xmlWriter.newline()
  964. for alt in alternates:
  965. xmlWriter.simpletag("Alternate", glyph=alt)
  966. xmlWriter.newline()
  967. xmlWriter.endtag("AlternateSet")
  968. xmlWriter.newline()
  969. def fromXML(self, name, attrs, content, font):
  970. alternates = getattr(self, "alternates", None)
  971. if alternates is None:
  972. alternates = {}
  973. self.alternates = alternates
  974. glyphName = attrs["glyph"]
  975. set = []
  976. alternates[glyphName] = set
  977. for element in content:
  978. if not isinstance(element, tuple):
  979. continue
  980. name, attrs, content = element
  981. set.append(attrs["glyph"])
  982. class LigatureSubst(FormatSwitchingBaseTable):
  983. def populateDefaults(self, propagator=None):
  984. if not hasattr(self, "ligatures"):
  985. self.ligatures = {}
  986. def postRead(self, rawTable, font):
  987. ligatures = {}
  988. if self.Format == 1:
  989. input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
  990. ligSets = rawTable["LigatureSet"]
  991. assert len(input) == len(ligSets)
  992. for i in range(len(input)):
  993. ligatures[input[i]] = ligSets[i].Ligature
  994. else:
  995. assert 0, "unknown format: %s" % self.Format
  996. self.ligatures = ligatures
  997. del self.Format # Don't need this anymore
  998. def preWrite(self, font):
  999. self.Format = 1
  1000. ligatures = getattr(self, "ligatures", None)
  1001. if ligatures is None:
  1002. ligatures = self.ligatures = {}
  1003. if ligatures and isinstance(next(iter(ligatures)), tuple):
  1004. # New high-level API in v3.1 and later. Note that we just support compiling this
  1005. # for now. We don't load to this API, and don't do XML with it.
  1006. # ligatures is map from components-sequence to lig-glyph
  1007. newLigatures = dict()
  1008. for comps, lig in sorted(
  1009. ligatures.items(), key=lambda item: (-len(item[0]), item[0])
  1010. ):
  1011. ligature = Ligature()
  1012. ligature.Component = comps[1:]
  1013. ligature.CompCount = len(comps)
  1014. ligature.LigGlyph = lig
  1015. newLigatures.setdefault(comps[0], []).append(ligature)
  1016. ligatures = newLigatures
  1017. items = list(ligatures.items())
  1018. for i in range(len(items)):
  1019. glyphName, set = items[i]
  1020. items[i] = font.getGlyphID(glyphName), glyphName, set
  1021. items.sort()
  1022. cov = Coverage()
  1023. cov.glyphs = [item[1] for item in items]
  1024. ligSets = []
  1025. setList = [item[-1] for item in items]
  1026. for set in setList:
  1027. ligSet = LigatureSet()
  1028. ligs = ligSet.Ligature = []
  1029. for lig in set:
  1030. ligs.append(lig)
  1031. ligSets.append(ligSet)
  1032. # Useful in that when splitting a sub-table because of an offset overflow
  1033. # I don't need to calculate the change in subtabl offset due to the coverage table size.
  1034. # Allows packing more rules in subtable.
  1035. self.sortCoverageLast = 1
  1036. return {"Coverage": cov, "LigatureSet": ligSets}
  1037. def toXML2(self, xmlWriter, font):
  1038. items = sorted(self.ligatures.items())
  1039. for glyphName, ligSets in items:
  1040. xmlWriter.begintag("LigatureSet", glyph=glyphName)
  1041. xmlWriter.newline()
  1042. for lig in ligSets:
  1043. xmlWriter.simpletag(
  1044. "Ligature", glyph=lig.LigGlyph, components=",".join(lig.Component)
  1045. )
  1046. xmlWriter.newline()
  1047. xmlWriter.endtag("LigatureSet")
  1048. xmlWriter.newline()
  1049. def fromXML(self, name, attrs, content, font):
  1050. ligatures = getattr(self, "ligatures", None)
  1051. if ligatures is None:
  1052. ligatures = {}
  1053. self.ligatures = ligatures
  1054. glyphName = attrs["glyph"]
  1055. ligs = []
  1056. ligatures[glyphName] = ligs
  1057. for element in content:
  1058. if not isinstance(element, tuple):
  1059. continue
  1060. name, attrs, content = element
  1061. lig = Ligature()
  1062. lig.LigGlyph = attrs["glyph"]
  1063. components = attrs["components"]
  1064. lig.Component = components.split(",") if components else []
  1065. lig.CompCount = len(lig.Component)
  1066. ligs.append(lig)
  1067. class COLR(BaseTable):
  1068. def decompile(self, reader, font):
  1069. # COLRv0 is exceptional in that LayerRecordCount appears *after* the
  1070. # LayerRecordArray it counts, but the parser logic expects Count fields
  1071. # to always precede the arrays. Here we work around this by parsing the
  1072. # LayerRecordCount before the rest of the table, and storing it in
  1073. # the reader's local state.
  1074. subReader = reader.getSubReader(offset=0)
  1075. for conv in self.getConverters():
  1076. if conv.name != "LayerRecordCount":
  1077. subReader.advance(conv.staticSize)
  1078. continue
  1079. reader[conv.name] = conv.read(subReader, font, tableDict={})
  1080. break
  1081. else:
  1082. raise AssertionError("LayerRecordCount converter not found")
  1083. return BaseTable.decompile(self, reader, font)
  1084. def preWrite(self, font):
  1085. # The writer similarly assumes Count values precede the things counted,
  1086. # thus here we pre-initialize a CountReference; the actual count value
  1087. # will be set to the lenght of the array by the time this is assembled.
  1088. self.LayerRecordCount = None
  1089. return {
  1090. **self.__dict__,
  1091. "LayerRecordCount": CountReference(self.__dict__, "LayerRecordCount"),
  1092. }
  1093. def computeClipBoxes(self, glyphSet: "_TTGlyphSet", quantization: int = 1):
  1094. if self.Version == 0:
  1095. return
  1096. clips = {}
  1097. for rec in self.BaseGlyphList.BaseGlyphPaintRecord:
  1098. try:
  1099. clipBox = rec.Paint.computeClipBox(self, glyphSet, quantization)
  1100. except Exception as e:
  1101. from fontTools.ttLib import TTLibError
  1102. raise TTLibError(
  1103. f"Failed to compute COLR ClipBox for {rec.BaseGlyph!r}"
  1104. ) from e
  1105. if clipBox is not None:
  1106. clips[rec.BaseGlyph] = clipBox
  1107. hasClipList = hasattr(self, "ClipList") and self.ClipList is not None
  1108. if not clips:
  1109. if hasClipList:
  1110. self.ClipList = None
  1111. else:
  1112. if not hasClipList:
  1113. self.ClipList = ClipList()
  1114. self.ClipList.Format = 1
  1115. self.ClipList.clips = clips
  1116. class LookupList(BaseTable):
  1117. @property
  1118. def table(self):
  1119. for l in self.Lookup:
  1120. for st in l.SubTable:
  1121. if type(st).__name__.endswith("Subst"):
  1122. return "GSUB"
  1123. if type(st).__name__.endswith("Pos"):
  1124. return "GPOS"
  1125. raise ValueError
  1126. def toXML2(self, xmlWriter, font):
  1127. if (
  1128. not font
  1129. or "Debg" not in font
  1130. or LOOKUP_DEBUG_INFO_KEY not in font["Debg"].data
  1131. ):
  1132. return super().toXML2(xmlWriter, font)
  1133. debugData = font["Debg"].data[LOOKUP_DEBUG_INFO_KEY][self.table]
  1134. for conv in self.getConverters():
  1135. if conv.repeat:
  1136. value = getattr(self, conv.name, [])
  1137. for lookupIndex, item in enumerate(value):
  1138. if str(lookupIndex) in debugData:
  1139. info = LookupDebugInfo(*debugData[str(lookupIndex)])
  1140. tag = info.location
  1141. if info.name:
  1142. tag = f"{info.name}: {tag}"
  1143. if info.feature:
  1144. script, language, feature = info.feature
  1145. tag = f"{tag} in {feature} ({script}/{language})"
  1146. xmlWriter.comment(tag)
  1147. xmlWriter.newline()
  1148. conv.xmlWrite(
  1149. xmlWriter, font, item, conv.name, [("index", lookupIndex)]
  1150. )
  1151. else:
  1152. if conv.aux and not eval(conv.aux, None, vars(self)):
  1153. continue
  1154. value = getattr(
  1155. self, conv.name, None
  1156. ) # TODO Handle defaults instead of defaulting to None!
  1157. conv.xmlWrite(xmlWriter, font, value, conv.name, [])
  1158. class BaseGlyphRecordArray(BaseTable):
  1159. def preWrite(self, font):
  1160. self.BaseGlyphRecord = sorted(
  1161. self.BaseGlyphRecord, key=lambda rec: font.getGlyphID(rec.BaseGlyph)
  1162. )
  1163. return self.__dict__.copy()
  1164. class BaseGlyphList(BaseTable):
  1165. def preWrite(self, font):
  1166. self.BaseGlyphPaintRecord = sorted(
  1167. self.BaseGlyphPaintRecord, key=lambda rec: font.getGlyphID(rec.BaseGlyph)
  1168. )
  1169. return self.__dict__.copy()
  1170. class ClipBoxFormat(IntEnum):
  1171. Static = 1
  1172. Variable = 2
  1173. def is_variable(self):
  1174. return self is self.Variable
  1175. def as_variable(self):
  1176. return self.Variable
  1177. class ClipBox(getFormatSwitchingBaseTableClass("uint8")):
  1178. formatEnum = ClipBoxFormat
  1179. def as_tuple(self):
  1180. return tuple(getattr(self, conv.name) for conv in self.getConverters())
  1181. def __repr__(self):
  1182. return f"{self.__class__.__name__}{self.as_tuple()}"
  1183. class ClipList(getFormatSwitchingBaseTableClass("uint8")):
  1184. def populateDefaults(self, propagator=None):
  1185. if not hasattr(self, "clips"):
  1186. self.clips = {}
  1187. def postRead(self, rawTable, font):
  1188. clips = {}
  1189. glyphOrder = font.getGlyphOrder()
  1190. for i, rec in enumerate(rawTable["ClipRecord"]):
  1191. if rec.StartGlyphID > rec.EndGlyphID:
  1192. log.warning(
  1193. "invalid ClipRecord[%i].StartGlyphID (%i) > "
  1194. "EndGlyphID (%i); skipped",
  1195. i,
  1196. rec.StartGlyphID,
  1197. rec.EndGlyphID,
  1198. )
  1199. continue
  1200. redefinedGlyphs = []
  1201. missingGlyphs = []
  1202. for glyphID in range(rec.StartGlyphID, rec.EndGlyphID + 1):
  1203. try:
  1204. glyph = glyphOrder[glyphID]
  1205. except IndexError:
  1206. missingGlyphs.append(glyphID)
  1207. continue
  1208. if glyph not in clips:
  1209. clips[glyph] = copy.copy(rec.ClipBox)
  1210. else:
  1211. redefinedGlyphs.append(glyphID)
  1212. if redefinedGlyphs:
  1213. log.warning(
  1214. "ClipRecord[%i] overlaps previous records; "
  1215. "ignoring redefined clip boxes for the "
  1216. "following glyph ID range: [%i-%i]",
  1217. i,
  1218. min(redefinedGlyphs),
  1219. max(redefinedGlyphs),
  1220. )
  1221. if missingGlyphs:
  1222. log.warning(
  1223. "ClipRecord[%i] range references missing " "glyph IDs: [%i-%i]",
  1224. i,
  1225. min(missingGlyphs),
  1226. max(missingGlyphs),
  1227. )
  1228. self.clips = clips
  1229. def groups(self):
  1230. glyphsByClip = defaultdict(list)
  1231. uniqueClips = {}
  1232. for glyphName, clipBox in self.clips.items():
  1233. key = clipBox.as_tuple()
  1234. glyphsByClip[key].append(glyphName)
  1235. if key not in uniqueClips:
  1236. uniqueClips[key] = clipBox
  1237. return {
  1238. frozenset(glyphs): uniqueClips[key] for key, glyphs in glyphsByClip.items()
  1239. }
  1240. def preWrite(self, font):
  1241. if not hasattr(self, "clips"):
  1242. self.clips = {}
  1243. clipBoxRanges = {}
  1244. glyphMap = font.getReverseGlyphMap()
  1245. for glyphs, clipBox in self.groups().items():
  1246. glyphIDs = sorted(
  1247. glyphMap[glyphName] for glyphName in glyphs if glyphName in glyphMap
  1248. )
  1249. if not glyphIDs:
  1250. continue
  1251. last = glyphIDs[0]
  1252. ranges = [[last]]
  1253. for glyphID in glyphIDs[1:]:
  1254. if glyphID != last + 1:
  1255. ranges[-1].append(last)
  1256. ranges.append([glyphID])
  1257. last = glyphID
  1258. ranges[-1].append(last)
  1259. for start, end in ranges:
  1260. assert (start, end) not in clipBoxRanges
  1261. clipBoxRanges[(start, end)] = clipBox
  1262. clipRecords = []
  1263. for (start, end), clipBox in sorted(clipBoxRanges.items()):
  1264. record = ClipRecord()
  1265. record.StartGlyphID = start
  1266. record.EndGlyphID = end
  1267. record.ClipBox = clipBox
  1268. clipRecords.append(record)
  1269. rawTable = {
  1270. "ClipCount": len(clipRecords),
  1271. "ClipRecord": clipRecords,
  1272. }
  1273. return rawTable
  1274. def toXML(self, xmlWriter, font, attrs=None, name=None):
  1275. tableName = name if name else self.__class__.__name__
  1276. if attrs is None:
  1277. attrs = []
  1278. if hasattr(self, "Format"):
  1279. attrs.append(("Format", self.Format))
  1280. xmlWriter.begintag(tableName, attrs)
  1281. xmlWriter.newline()
  1282. # sort clips alphabetically to ensure deterministic XML dump
  1283. for glyphs, clipBox in sorted(
  1284. self.groups().items(), key=lambda item: min(item[0])
  1285. ):
  1286. xmlWriter.begintag("Clip")
  1287. xmlWriter.newline()
  1288. for glyphName in sorted(glyphs):
  1289. xmlWriter.simpletag("Glyph", value=glyphName)
  1290. xmlWriter.newline()
  1291. xmlWriter.begintag("ClipBox", [("Format", clipBox.Format)])
  1292. xmlWriter.newline()
  1293. clipBox.toXML2(xmlWriter, font)
  1294. xmlWriter.endtag("ClipBox")
  1295. xmlWriter.newline()
  1296. xmlWriter.endtag("Clip")
  1297. xmlWriter.newline()
  1298. xmlWriter.endtag(tableName)
  1299. xmlWriter.newline()
  1300. def fromXML(self, name, attrs, content, font):
  1301. clips = getattr(self, "clips", None)
  1302. if clips is None:
  1303. self.clips = clips = {}
  1304. assert name == "Clip"
  1305. glyphs = []
  1306. clipBox = None
  1307. for elem in content:
  1308. if not isinstance(elem, tuple):
  1309. continue
  1310. name, attrs, content = elem
  1311. if name == "Glyph":
  1312. glyphs.append(attrs["value"])
  1313. elif name == "ClipBox":
  1314. clipBox = ClipBox()
  1315. clipBox.Format = safeEval(attrs["Format"])
  1316. for elem in content:
  1317. if not isinstance(elem, tuple):
  1318. continue
  1319. name, attrs, content = elem
  1320. clipBox.fromXML(name, attrs, content, font)
  1321. if clipBox:
  1322. for glyphName in glyphs:
  1323. clips[glyphName] = clipBox
  1324. class ExtendMode(IntEnum):
  1325. PAD = 0
  1326. REPEAT = 1
  1327. REFLECT = 2
  1328. # Porter-Duff modes for COLRv1 PaintComposite:
  1329. # https://github.com/googlefonts/colr-gradients-spec/tree/off_sub_1#compositemode-enumeration
  1330. class CompositeMode(IntEnum):
  1331. CLEAR = 0
  1332. SRC = 1
  1333. DEST = 2
  1334. SRC_OVER = 3
  1335. DEST_OVER = 4
  1336. SRC_IN = 5
  1337. DEST_IN = 6
  1338. SRC_OUT = 7
  1339. DEST_OUT = 8
  1340. SRC_ATOP = 9
  1341. DEST_ATOP = 10
  1342. XOR = 11
  1343. PLUS = 12
  1344. SCREEN = 13
  1345. OVERLAY = 14
  1346. DARKEN = 15
  1347. LIGHTEN = 16
  1348. COLOR_DODGE = 17
  1349. COLOR_BURN = 18
  1350. HARD_LIGHT = 19
  1351. SOFT_LIGHT = 20
  1352. DIFFERENCE = 21
  1353. EXCLUSION = 22
  1354. MULTIPLY = 23
  1355. HSL_HUE = 24
  1356. HSL_SATURATION = 25
  1357. HSL_COLOR = 26
  1358. HSL_LUMINOSITY = 27
  1359. class PaintFormat(IntEnum):
  1360. PaintColrLayers = 1
  1361. PaintSolid = 2
  1362. PaintVarSolid = 3
  1363. PaintLinearGradient = 4
  1364. PaintVarLinearGradient = 5
  1365. PaintRadialGradient = 6
  1366. PaintVarRadialGradient = 7
  1367. PaintSweepGradient = 8
  1368. PaintVarSweepGradient = 9
  1369. PaintGlyph = 10
  1370. PaintColrGlyph = 11
  1371. PaintTransform = 12
  1372. PaintVarTransform = 13
  1373. PaintTranslate = 14
  1374. PaintVarTranslate = 15
  1375. PaintScale = 16
  1376. PaintVarScale = 17
  1377. PaintScaleAroundCenter = 18
  1378. PaintVarScaleAroundCenter = 19
  1379. PaintScaleUniform = 20
  1380. PaintVarScaleUniform = 21
  1381. PaintScaleUniformAroundCenter = 22
  1382. PaintVarScaleUniformAroundCenter = 23
  1383. PaintRotate = 24
  1384. PaintVarRotate = 25
  1385. PaintRotateAroundCenter = 26
  1386. PaintVarRotateAroundCenter = 27
  1387. PaintSkew = 28
  1388. PaintVarSkew = 29
  1389. PaintSkewAroundCenter = 30
  1390. PaintVarSkewAroundCenter = 31
  1391. PaintComposite = 32
  1392. def is_variable(self):
  1393. return self.name.startswith("PaintVar")
  1394. def as_variable(self):
  1395. if self.is_variable():
  1396. return self
  1397. try:
  1398. return PaintFormat.__members__[f"PaintVar{self.name[5:]}"]
  1399. except KeyError:
  1400. return None
  1401. class Paint(getFormatSwitchingBaseTableClass("uint8")):
  1402. formatEnum = PaintFormat
  1403. def getFormatName(self):
  1404. try:
  1405. return self.formatEnum(self.Format).name
  1406. except ValueError:
  1407. raise NotImplementedError(f"Unknown Paint format: {self.Format}")
  1408. def toXML(self, xmlWriter, font, attrs=None, name=None):
  1409. tableName = name if name else self.__class__.__name__
  1410. if attrs is None:
  1411. attrs = []
  1412. attrs.append(("Format", self.Format))
  1413. xmlWriter.begintag(tableName, attrs)
  1414. xmlWriter.comment(self.getFormatName())
  1415. xmlWriter.newline()
  1416. self.toXML2(xmlWriter, font)
  1417. xmlWriter.endtag(tableName)
  1418. xmlWriter.newline()
  1419. def iterPaintSubTables(self, colr: COLR) -> Iterator[BaseTable.SubTableEntry]:
  1420. if self.Format == PaintFormat.PaintColrLayers:
  1421. # https://github.com/fonttools/fonttools/issues/2438: don't die when no LayerList exists
  1422. layers = []
  1423. if colr.LayerList is not None:
  1424. layers = colr.LayerList.Paint
  1425. yield from (
  1426. BaseTable.SubTableEntry(name="Layers", value=v, index=i)
  1427. for i, v in enumerate(
  1428. layers[self.FirstLayerIndex : self.FirstLayerIndex + self.NumLayers]
  1429. )
  1430. )
  1431. return
  1432. if self.Format == PaintFormat.PaintColrGlyph:
  1433. for record in colr.BaseGlyphList.BaseGlyphPaintRecord:
  1434. if record.BaseGlyph == self.Glyph:
  1435. yield BaseTable.SubTableEntry(name="BaseGlyph", value=record.Paint)
  1436. return
  1437. else:
  1438. raise KeyError(f"{self.Glyph!r} not in colr.BaseGlyphList")
  1439. for conv in self.getConverters():
  1440. if conv.tableClass is not None and issubclass(conv.tableClass, type(self)):
  1441. value = getattr(self, conv.name)
  1442. yield BaseTable.SubTableEntry(name=conv.name, value=value)
  1443. def getChildren(self, colr) -> List["Paint"]:
  1444. # this is kept for backward compatibility (e.g. it's used by the subsetter)
  1445. return [p.value for p in self.iterPaintSubTables(colr)]
  1446. def traverse(self, colr: COLR, callback):
  1447. """Depth-first traversal of graph rooted at self, callback on each node."""
  1448. if not callable(callback):
  1449. raise TypeError("callback must be callable")
  1450. for path in dfs_base_table(
  1451. self, iter_subtables_fn=lambda paint: paint.iterPaintSubTables(colr)
  1452. ):
  1453. paint = path[-1].value
  1454. callback(paint)
  1455. def getTransform(self) -> Transform:
  1456. if self.Format == PaintFormat.PaintTransform:
  1457. t = self.Transform
  1458. return Transform(t.xx, t.yx, t.xy, t.yy, t.dx, t.dy)
  1459. elif self.Format == PaintFormat.PaintTranslate:
  1460. return Identity.translate(self.dx, self.dy)
  1461. elif self.Format == PaintFormat.PaintScale:
  1462. return Identity.scale(self.scaleX, self.scaleY)
  1463. elif self.Format == PaintFormat.PaintScaleAroundCenter:
  1464. return (
  1465. Identity.translate(self.centerX, self.centerY)
  1466. .scale(self.scaleX, self.scaleY)
  1467. .translate(-self.centerX, -self.centerY)
  1468. )
  1469. elif self.Format == PaintFormat.PaintScaleUniform:
  1470. return Identity.scale(self.scale)
  1471. elif self.Format == PaintFormat.PaintScaleUniformAroundCenter:
  1472. return (
  1473. Identity.translate(self.centerX, self.centerY)
  1474. .scale(self.scale)
  1475. .translate(-self.centerX, -self.centerY)
  1476. )
  1477. elif self.Format == PaintFormat.PaintRotate:
  1478. return Identity.rotate(radians(self.angle))
  1479. elif self.Format == PaintFormat.PaintRotateAroundCenter:
  1480. return (
  1481. Identity.translate(self.centerX, self.centerY)
  1482. .rotate(radians(self.angle))
  1483. .translate(-self.centerX, -self.centerY)
  1484. )
  1485. elif self.Format == PaintFormat.PaintSkew:
  1486. return Identity.skew(radians(-self.xSkewAngle), radians(self.ySkewAngle))
  1487. elif self.Format == PaintFormat.PaintSkewAroundCenter:
  1488. return (
  1489. Identity.translate(self.centerX, self.centerY)
  1490. .skew(radians(-self.xSkewAngle), radians(self.ySkewAngle))
  1491. .translate(-self.centerX, -self.centerY)
  1492. )
  1493. if PaintFormat(self.Format).is_variable():
  1494. raise NotImplementedError(f"Variable Paints not supported: {self.Format}")
  1495. return Identity
  1496. def computeClipBox(
  1497. self, colr: COLR, glyphSet: "_TTGlyphSet", quantization: int = 1
  1498. ) -> Optional[ClipBox]:
  1499. pen = ControlBoundsPen(glyphSet)
  1500. for path in dfs_base_table(
  1501. self, iter_subtables_fn=lambda paint: paint.iterPaintSubTables(colr)
  1502. ):
  1503. paint = path[-1].value
  1504. if paint.Format == PaintFormat.PaintGlyph:
  1505. transformation = reduce(
  1506. Transform.transform,
  1507. (st.value.getTransform() for st in path),
  1508. Identity,
  1509. )
  1510. glyphSet[paint.Glyph].draw(TransformPen(pen, transformation))
  1511. if pen.bounds is None:
  1512. return None
  1513. cb = ClipBox()
  1514. cb.Format = int(ClipBoxFormat.Static)
  1515. cb.xMin, cb.yMin, cb.xMax, cb.yMax = quantizeRect(pen.bounds, quantization)
  1516. return cb
  1517. # For each subtable format there is a class. However, we don't really distinguish
  1518. # between "field name" and "format name": often these are the same. Yet there's
  1519. # a whole bunch of fields with different names. The following dict is a mapping
  1520. # from "format name" to "field name". _buildClasses() uses this to create a
  1521. # subclass for each alternate field name.
  1522. #
  1523. _equivalents = {
  1524. "MarkArray": ("Mark1Array",),
  1525. "LangSys": ("DefaultLangSys",),
  1526. "Coverage": (
  1527. "MarkCoverage",
  1528. "BaseCoverage",
  1529. "LigatureCoverage",
  1530. "Mark1Coverage",
  1531. "Mark2Coverage",
  1532. "BacktrackCoverage",
  1533. "InputCoverage",
  1534. "LookAheadCoverage",
  1535. "VertGlyphCoverage",
  1536. "HorizGlyphCoverage",
  1537. "TopAccentCoverage",
  1538. "ExtendedShapeCoverage",
  1539. "MathKernCoverage",
  1540. ),
  1541. "ClassDef": (
  1542. "ClassDef1",
  1543. "ClassDef2",
  1544. "BacktrackClassDef",
  1545. "InputClassDef",
  1546. "LookAheadClassDef",
  1547. "GlyphClassDef",
  1548. "MarkAttachClassDef",
  1549. ),
  1550. "Anchor": (
  1551. "EntryAnchor",
  1552. "ExitAnchor",
  1553. "BaseAnchor",
  1554. "LigatureAnchor",
  1555. "Mark2Anchor",
  1556. "MarkAnchor",
  1557. ),
  1558. "Device": (
  1559. "XPlaDevice",
  1560. "YPlaDevice",
  1561. "XAdvDevice",
  1562. "YAdvDevice",
  1563. "XDeviceTable",
  1564. "YDeviceTable",
  1565. "DeviceTable",
  1566. ),
  1567. "Axis": (
  1568. "HorizAxis",
  1569. "VertAxis",
  1570. ),
  1571. "MinMax": ("DefaultMinMax",),
  1572. "BaseCoord": (
  1573. "MinCoord",
  1574. "MaxCoord",
  1575. ),
  1576. "JstfLangSys": ("DefJstfLangSys",),
  1577. "JstfGSUBModList": (
  1578. "ShrinkageEnableGSUB",
  1579. "ShrinkageDisableGSUB",
  1580. "ExtensionEnableGSUB",
  1581. "ExtensionDisableGSUB",
  1582. ),
  1583. "JstfGPOSModList": (
  1584. "ShrinkageEnableGPOS",
  1585. "ShrinkageDisableGPOS",
  1586. "ExtensionEnableGPOS",
  1587. "ExtensionDisableGPOS",
  1588. ),
  1589. "JstfMax": (
  1590. "ShrinkageJstfMax",
  1591. "ExtensionJstfMax",
  1592. ),
  1593. "MathKern": (
  1594. "TopRightMathKern",
  1595. "TopLeftMathKern",
  1596. "BottomRightMathKern",
  1597. "BottomLeftMathKern",
  1598. ),
  1599. "MathGlyphConstruction": ("VertGlyphConstruction", "HorizGlyphConstruction"),
  1600. }
  1601. #
  1602. # OverFlow logic, to automatically create ExtensionLookups
  1603. # XXX This should probably move to otBase.py
  1604. #
  1605. def fixLookupOverFlows(ttf, overflowRecord):
  1606. """Either the offset from the LookupList to a lookup overflowed, or
  1607. an offset from a lookup to a subtable overflowed.
  1608. The table layout is:
  1609. GPSO/GUSB
  1610. Script List
  1611. Feature List
  1612. LookUpList
  1613. Lookup[0] and contents
  1614. SubTable offset list
  1615. SubTable[0] and contents
  1616. ...
  1617. SubTable[n] and contents
  1618. ...
  1619. Lookup[n] and contents
  1620. SubTable offset list
  1621. SubTable[0] and contents
  1622. ...
  1623. SubTable[n] and contents
  1624. If the offset to a lookup overflowed (SubTableIndex is None)
  1625. we must promote the *previous* lookup to an Extension type.
  1626. If the offset from a lookup to subtable overflowed, then we must promote it
  1627. to an Extension Lookup type.
  1628. """
  1629. ok = 0
  1630. lookupIndex = overflowRecord.LookupListIndex
  1631. if overflowRecord.SubTableIndex is None:
  1632. lookupIndex = lookupIndex - 1
  1633. if lookupIndex < 0:
  1634. return ok
  1635. if overflowRecord.tableType == "GSUB":
  1636. extType = 7
  1637. elif overflowRecord.tableType == "GPOS":
  1638. extType = 9
  1639. lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup
  1640. lookup = lookups[lookupIndex]
  1641. # If the previous lookup is an extType, look further back. Very unlikely, but possible.
  1642. while lookup.SubTable[0].__class__.LookupType == extType:
  1643. lookupIndex = lookupIndex - 1
  1644. if lookupIndex < 0:
  1645. return ok
  1646. lookup = lookups[lookupIndex]
  1647. for lookupIndex in range(lookupIndex, len(lookups)):
  1648. lookup = lookups[lookupIndex]
  1649. if lookup.LookupType != extType:
  1650. lookup.LookupType = extType
  1651. for si in range(len(lookup.SubTable)):
  1652. subTable = lookup.SubTable[si]
  1653. extSubTableClass = lookupTypes[overflowRecord.tableType][extType]
  1654. extSubTable = extSubTableClass()
  1655. extSubTable.Format = 1
  1656. extSubTable.ExtSubTable = subTable
  1657. lookup.SubTable[si] = extSubTable
  1658. ok = 1
  1659. return ok
  1660. def splitMultipleSubst(oldSubTable, newSubTable, overflowRecord):
  1661. ok = 1
  1662. oldMapping = sorted(oldSubTable.mapping.items())
  1663. oldLen = len(oldMapping)
  1664. if overflowRecord.itemName in ["Coverage", "RangeRecord"]:
  1665. # Coverage table is written last. Overflow is to or within the
  1666. # the coverage table. We will just cut the subtable in half.
  1667. newLen = oldLen // 2
  1668. elif overflowRecord.itemName == "Sequence":
  1669. # We just need to back up by two items from the overflowed
  1670. # Sequence index to make sure the offset to the Coverage table
  1671. # doesn't overflow.
  1672. newLen = overflowRecord.itemIndex - 1
  1673. newSubTable.mapping = {}
  1674. for i in range(newLen, oldLen):
  1675. item = oldMapping[i]
  1676. key = item[0]
  1677. newSubTable.mapping[key] = item[1]
  1678. del oldSubTable.mapping[key]
  1679. return ok
  1680. def splitAlternateSubst(oldSubTable, newSubTable, overflowRecord):
  1681. ok = 1
  1682. if hasattr(oldSubTable, "sortCoverageLast"):
  1683. newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast
  1684. oldAlts = sorted(oldSubTable.alternates.items())
  1685. oldLen = len(oldAlts)
  1686. if overflowRecord.itemName in ["Coverage", "RangeRecord"]:
  1687. # Coverage table is written last. overflow is to or within the
  1688. # the coverage table. We will just cut the subtable in half.
  1689. newLen = oldLen // 2
  1690. elif overflowRecord.itemName == "AlternateSet":
  1691. # We just need to back up by two items
  1692. # from the overflowed AlternateSet index to make sure the offset
  1693. # to the Coverage table doesn't overflow.
  1694. newLen = overflowRecord.itemIndex - 1
  1695. newSubTable.alternates = {}
  1696. for i in range(newLen, oldLen):
  1697. item = oldAlts[i]
  1698. key = item[0]
  1699. newSubTable.alternates[key] = item[1]
  1700. del oldSubTable.alternates[key]
  1701. return ok
  1702. def splitLigatureSubst(oldSubTable, newSubTable, overflowRecord):
  1703. ok = 1
  1704. oldLigs = sorted(oldSubTable.ligatures.items())
  1705. oldLen = len(oldLigs)
  1706. if overflowRecord.itemName in ["Coverage", "RangeRecord"]:
  1707. # Coverage table is written last. overflow is to or within the
  1708. # the coverage table. We will just cut the subtable in half.
  1709. newLen = oldLen // 2
  1710. elif overflowRecord.itemName == "LigatureSet":
  1711. # We just need to back up by two items
  1712. # from the overflowed AlternateSet index to make sure the offset
  1713. # to the Coverage table doesn't overflow.
  1714. newLen = overflowRecord.itemIndex - 1
  1715. newSubTable.ligatures = {}
  1716. for i in range(newLen, oldLen):
  1717. item = oldLigs[i]
  1718. key = item[0]
  1719. newSubTable.ligatures[key] = item[1]
  1720. del oldSubTable.ligatures[key]
  1721. return ok
  1722. def splitPairPos(oldSubTable, newSubTable, overflowRecord):
  1723. st = oldSubTable
  1724. ok = False
  1725. newSubTable.Format = oldSubTable.Format
  1726. if oldSubTable.Format == 1 and len(oldSubTable.PairSet) > 1:
  1727. for name in "ValueFormat1", "ValueFormat2":
  1728. setattr(newSubTable, name, getattr(oldSubTable, name))
  1729. # Move top half of coverage to new subtable
  1730. newSubTable.Coverage = oldSubTable.Coverage.__class__()
  1731. coverage = oldSubTable.Coverage.glyphs
  1732. records = oldSubTable.PairSet
  1733. oldCount = len(oldSubTable.PairSet) // 2
  1734. oldSubTable.Coverage.glyphs = coverage[:oldCount]
  1735. oldSubTable.PairSet = records[:oldCount]
  1736. newSubTable.Coverage.glyphs = coverage[oldCount:]
  1737. newSubTable.PairSet = records[oldCount:]
  1738. oldSubTable.PairSetCount = len(oldSubTable.PairSet)
  1739. newSubTable.PairSetCount = len(newSubTable.PairSet)
  1740. ok = True
  1741. elif oldSubTable.Format == 2 and len(oldSubTable.Class1Record) > 1:
  1742. if not hasattr(oldSubTable, "Class2Count"):
  1743. oldSubTable.Class2Count = len(oldSubTable.Class1Record[0].Class2Record)
  1744. for name in "Class2Count", "ClassDef2", "ValueFormat1", "ValueFormat2":
  1745. setattr(newSubTable, name, getattr(oldSubTable, name))
  1746. # The two subtables will still have the same ClassDef2 and the table
  1747. # sharing will still cause the sharing to overflow. As such, disable
  1748. # sharing on the one that is serialized second (that's oldSubTable).
  1749. oldSubTable.DontShare = True
  1750. # Move top half of class numbers to new subtable
  1751. newSubTable.Coverage = oldSubTable.Coverage.__class__()
  1752. newSubTable.ClassDef1 = oldSubTable.ClassDef1.__class__()
  1753. coverage = oldSubTable.Coverage.glyphs
  1754. classDefs = oldSubTable.ClassDef1.classDefs
  1755. records = oldSubTable.Class1Record
  1756. oldCount = len(oldSubTable.Class1Record) // 2
  1757. newGlyphs = set(k for k, v in classDefs.items() if v >= oldCount)
  1758. oldSubTable.Coverage.glyphs = [g for g in coverage if g not in newGlyphs]
  1759. oldSubTable.ClassDef1.classDefs = {
  1760. k: v for k, v in classDefs.items() if v < oldCount
  1761. }
  1762. oldSubTable.Class1Record = records[:oldCount]
  1763. newSubTable.Coverage.glyphs = [g for g in coverage if g in newGlyphs]
  1764. newSubTable.ClassDef1.classDefs = {
  1765. k: (v - oldCount) for k, v in classDefs.items() if v > oldCount
  1766. }
  1767. newSubTable.Class1Record = records[oldCount:]
  1768. oldSubTable.Class1Count = len(oldSubTable.Class1Record)
  1769. newSubTable.Class1Count = len(newSubTable.Class1Record)
  1770. ok = True
  1771. return ok
  1772. def splitMarkBasePos(oldSubTable, newSubTable, overflowRecord):
  1773. # split half of the mark classes to the new subtable
  1774. classCount = oldSubTable.ClassCount
  1775. if classCount < 2:
  1776. # oh well, not much left to split...
  1777. return False
  1778. oldClassCount = classCount // 2
  1779. newClassCount = classCount - oldClassCount
  1780. oldMarkCoverage, oldMarkRecords = [], []
  1781. newMarkCoverage, newMarkRecords = [], []
  1782. for glyphName, markRecord in zip(
  1783. oldSubTable.MarkCoverage.glyphs, oldSubTable.MarkArray.MarkRecord
  1784. ):
  1785. if markRecord.Class < oldClassCount:
  1786. oldMarkCoverage.append(glyphName)
  1787. oldMarkRecords.append(markRecord)
  1788. else:
  1789. markRecord.Class -= oldClassCount
  1790. newMarkCoverage.append(glyphName)
  1791. newMarkRecords.append(markRecord)
  1792. oldBaseRecords, newBaseRecords = [], []
  1793. for rec in oldSubTable.BaseArray.BaseRecord:
  1794. oldBaseRecord, newBaseRecord = rec.__class__(), rec.__class__()
  1795. oldBaseRecord.BaseAnchor = rec.BaseAnchor[:oldClassCount]
  1796. newBaseRecord.BaseAnchor = rec.BaseAnchor[oldClassCount:]
  1797. oldBaseRecords.append(oldBaseRecord)
  1798. newBaseRecords.append(newBaseRecord)
  1799. newSubTable.Format = oldSubTable.Format
  1800. oldSubTable.MarkCoverage.glyphs = oldMarkCoverage
  1801. newSubTable.MarkCoverage = oldSubTable.MarkCoverage.__class__()
  1802. newSubTable.MarkCoverage.glyphs = newMarkCoverage
  1803. # share the same BaseCoverage in both halves
  1804. newSubTable.BaseCoverage = oldSubTable.BaseCoverage
  1805. oldSubTable.ClassCount = oldClassCount
  1806. newSubTable.ClassCount = newClassCount
  1807. oldSubTable.MarkArray.MarkRecord = oldMarkRecords
  1808. newSubTable.MarkArray = oldSubTable.MarkArray.__class__()
  1809. newSubTable.MarkArray.MarkRecord = newMarkRecords
  1810. oldSubTable.MarkArray.MarkCount = len(oldMarkRecords)
  1811. newSubTable.MarkArray.MarkCount = len(newMarkRecords)
  1812. oldSubTable.BaseArray.BaseRecord = oldBaseRecords
  1813. newSubTable.BaseArray = oldSubTable.BaseArray.__class__()
  1814. newSubTable.BaseArray.BaseRecord = newBaseRecords
  1815. oldSubTable.BaseArray.BaseCount = len(oldBaseRecords)
  1816. newSubTable.BaseArray.BaseCount = len(newBaseRecords)
  1817. return True
  1818. splitTable = {
  1819. "GSUB": {
  1820. # 1: splitSingleSubst,
  1821. 2: splitMultipleSubst,
  1822. 3: splitAlternateSubst,
  1823. 4: splitLigatureSubst,
  1824. # 5: splitContextSubst,
  1825. # 6: splitChainContextSubst,
  1826. # 7: splitExtensionSubst,
  1827. # 8: splitReverseChainSingleSubst,
  1828. },
  1829. "GPOS": {
  1830. # 1: splitSinglePos,
  1831. 2: splitPairPos,
  1832. # 3: splitCursivePos,
  1833. 4: splitMarkBasePos,
  1834. # 5: splitMarkLigPos,
  1835. # 6: splitMarkMarkPos,
  1836. # 7: splitContextPos,
  1837. # 8: splitChainContextPos,
  1838. # 9: splitExtensionPos,
  1839. },
  1840. }
  1841. def fixSubTableOverFlows(ttf, overflowRecord):
  1842. """
  1843. An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts.
  1844. """
  1845. table = ttf[overflowRecord.tableType].table
  1846. lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex]
  1847. subIndex = overflowRecord.SubTableIndex
  1848. subtable = lookup.SubTable[subIndex]
  1849. # First, try not sharing anything for this subtable...
  1850. if not hasattr(subtable, "DontShare"):
  1851. subtable.DontShare = True
  1852. return True
  1853. if hasattr(subtable, "ExtSubTable"):
  1854. # We split the subtable of the Extension table, and add a new Extension table
  1855. # to contain the new subtable.
  1856. subTableType = subtable.ExtSubTable.__class__.LookupType
  1857. extSubTable = subtable
  1858. subtable = extSubTable.ExtSubTable
  1859. newExtSubTableClass = lookupTypes[overflowRecord.tableType][
  1860. extSubTable.__class__.LookupType
  1861. ]
  1862. newExtSubTable = newExtSubTableClass()
  1863. newExtSubTable.Format = extSubTable.Format
  1864. toInsert = newExtSubTable
  1865. newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
  1866. newSubTable = newSubTableClass()
  1867. newExtSubTable.ExtSubTable = newSubTable
  1868. else:
  1869. subTableType = subtable.__class__.LookupType
  1870. newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
  1871. newSubTable = newSubTableClass()
  1872. toInsert = newSubTable
  1873. if hasattr(lookup, "SubTableCount"): # may not be defined yet.
  1874. lookup.SubTableCount = lookup.SubTableCount + 1
  1875. try:
  1876. splitFunc = splitTable[overflowRecord.tableType][subTableType]
  1877. except KeyError:
  1878. log.error(
  1879. "Don't know how to split %s lookup type %s",
  1880. overflowRecord.tableType,
  1881. subTableType,
  1882. )
  1883. return False
  1884. ok = splitFunc(subtable, newSubTable, overflowRecord)
  1885. if ok:
  1886. lookup.SubTable.insert(subIndex + 1, toInsert)
  1887. return ok
  1888. # End of OverFlow logic
  1889. def _buildClasses():
  1890. import re
  1891. from .otData import otData
  1892. formatPat = re.compile(r"([A-Za-z0-9]+)Format(\d+)$")
  1893. namespace = globals()
  1894. # populate module with classes
  1895. for name, table in otData:
  1896. baseClass = BaseTable
  1897. m = formatPat.match(name)
  1898. if m:
  1899. # XxxFormatN subtable, we only add the "base" table
  1900. name = m.group(1)
  1901. # the first row of a format-switching otData table describes the Format;
  1902. # the first column defines the type of the Format field.
  1903. # Currently this can be either 'uint16' or 'uint8'.
  1904. formatType = table[0][0]
  1905. baseClass = getFormatSwitchingBaseTableClass(formatType)
  1906. if name not in namespace:
  1907. # the class doesn't exist yet, so the base implementation is used.
  1908. cls = type(name, (baseClass,), {})
  1909. if name in ("GSUB", "GPOS"):
  1910. cls.DontShare = True
  1911. namespace[name] = cls
  1912. # link Var{Table} <-> {Table} (e.g. ColorStop <-> VarColorStop, etc.)
  1913. for name, _ in otData:
  1914. if name.startswith("Var") and len(name) > 3 and name[3:] in namespace:
  1915. varType = namespace[name]
  1916. noVarType = namespace[name[3:]]
  1917. varType.NoVarType = noVarType
  1918. noVarType.VarType = varType
  1919. for base, alts in _equivalents.items():
  1920. base = namespace[base]
  1921. for alt in alts:
  1922. namespace[alt] = base
  1923. global lookupTypes
  1924. lookupTypes = {
  1925. "GSUB": {
  1926. 1: SingleSubst,
  1927. 2: MultipleSubst,
  1928. 3: AlternateSubst,
  1929. 4: LigatureSubst,
  1930. 5: ContextSubst,
  1931. 6: ChainContextSubst,
  1932. 7: ExtensionSubst,
  1933. 8: ReverseChainSingleSubst,
  1934. },
  1935. "GPOS": {
  1936. 1: SinglePos,
  1937. 2: PairPos,
  1938. 3: CursivePos,
  1939. 4: MarkBasePos,
  1940. 5: MarkLigPos,
  1941. 6: MarkMarkPos,
  1942. 7: ContextPos,
  1943. 8: ChainContextPos,
  1944. 9: ExtensionPos,
  1945. },
  1946. "mort": {
  1947. 4: NoncontextualMorph,
  1948. },
  1949. "morx": {
  1950. 0: RearrangementMorph,
  1951. 1: ContextualMorph,
  1952. 2: LigatureMorph,
  1953. # 3: Reserved,
  1954. 4: NoncontextualMorph,
  1955. 5: InsertionMorph,
  1956. },
  1957. }
  1958. lookupTypes["JSTF"] = lookupTypes["GPOS"] # JSTF contains GPOS
  1959. for lookupEnum in lookupTypes.values():
  1960. for enum, cls in lookupEnum.items():
  1961. cls.LookupType = enum
  1962. global featureParamTypes
  1963. featureParamTypes = {
  1964. "size": FeatureParamsSize,
  1965. }
  1966. for i in range(1, 20 + 1):
  1967. featureParamTypes["ss%02d" % i] = FeatureParamsStylisticSet
  1968. for i in range(1, 99 + 1):
  1969. featureParamTypes["cv%02d" % i] = FeatureParamsCharacterVariants
  1970. # add converters to classes
  1971. from .otConverters import buildConverters
  1972. for name, table in otData:
  1973. m = formatPat.match(name)
  1974. if m:
  1975. # XxxFormatN subtable, add converter to "base" table
  1976. name, format = m.groups()
  1977. format = int(format)
  1978. cls = namespace[name]
  1979. if not hasattr(cls, "converters"):
  1980. cls.converters = {}
  1981. cls.convertersByName = {}
  1982. converters, convertersByName = buildConverters(table[1:], namespace)
  1983. cls.converters[format] = converters
  1984. cls.convertersByName[format] = convertersByName
  1985. # XXX Add staticSize?
  1986. else:
  1987. cls = namespace[name]
  1988. cls.converters, cls.convertersByName = buildConverters(table, namespace)
  1989. # XXX Add staticSize?
  1990. _buildClasses()
  1991. def _getGlyphsFromCoverageTable(coverage):
  1992. if coverage is None:
  1993. # empty coverage table
  1994. return []
  1995. else:
  1996. return coverage.glyphs