ast.py 72 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143
  1. import weakref
  2. from fontTools.feaLib.error import FeatureLibError
  3. from fontTools.feaLib.location import FeatureLibLocation
  4. from fontTools.misc.encodingTools import getEncoding
  5. from fontTools.misc.textTools import byteord, tobytes
  6. from collections import OrderedDict
  7. import itertools
  8. SHIFT = " " * 4
  9. __all__ = [
  10. "Element",
  11. "FeatureFile",
  12. "Comment",
  13. "GlyphName",
  14. "GlyphClass",
  15. "GlyphClassName",
  16. "MarkClassName",
  17. "AnonymousBlock",
  18. "Block",
  19. "FeatureBlock",
  20. "NestedBlock",
  21. "LookupBlock",
  22. "GlyphClassDefinition",
  23. "GlyphClassDefStatement",
  24. "MarkClass",
  25. "MarkClassDefinition",
  26. "AlternateSubstStatement",
  27. "Anchor",
  28. "AnchorDefinition",
  29. "AttachStatement",
  30. "AxisValueLocationStatement",
  31. "BaseAxis",
  32. "CVParametersNameStatement",
  33. "ChainContextPosStatement",
  34. "ChainContextSubstStatement",
  35. "CharacterStatement",
  36. "ConditionsetStatement",
  37. "CursivePosStatement",
  38. "ElidedFallbackName",
  39. "ElidedFallbackNameID",
  40. "Expression",
  41. "FeatureNameStatement",
  42. "FeatureReferenceStatement",
  43. "FontRevisionStatement",
  44. "HheaField",
  45. "IgnorePosStatement",
  46. "IgnoreSubstStatement",
  47. "IncludeStatement",
  48. "LanguageStatement",
  49. "LanguageSystemStatement",
  50. "LigatureCaretByIndexStatement",
  51. "LigatureCaretByPosStatement",
  52. "LigatureSubstStatement",
  53. "LookupFlagStatement",
  54. "LookupReferenceStatement",
  55. "MarkBasePosStatement",
  56. "MarkLigPosStatement",
  57. "MarkMarkPosStatement",
  58. "MultipleSubstStatement",
  59. "NameRecord",
  60. "OS2Field",
  61. "PairPosStatement",
  62. "ReverseChainSingleSubstStatement",
  63. "ScriptStatement",
  64. "SinglePosStatement",
  65. "SingleSubstStatement",
  66. "SizeParameters",
  67. "Statement",
  68. "STATAxisValueStatement",
  69. "STATDesignAxisStatement",
  70. "STATNameStatement",
  71. "SubtableStatement",
  72. "TableBlock",
  73. "ValueRecord",
  74. "ValueRecordDefinition",
  75. "VheaField",
  76. ]
  77. def deviceToString(device):
  78. if device is None:
  79. return "<device NULL>"
  80. else:
  81. return "<device %s>" % ", ".join("%d %d" % t for t in device)
  82. fea_keywords = set(
  83. [
  84. "anchor",
  85. "anchordef",
  86. "anon",
  87. "anonymous",
  88. "by",
  89. "contour",
  90. "cursive",
  91. "device",
  92. "enum",
  93. "enumerate",
  94. "excludedflt",
  95. "exclude_dflt",
  96. "feature",
  97. "from",
  98. "ignore",
  99. "ignorebaseglyphs",
  100. "ignoreligatures",
  101. "ignoremarks",
  102. "include",
  103. "includedflt",
  104. "include_dflt",
  105. "language",
  106. "languagesystem",
  107. "lookup",
  108. "lookupflag",
  109. "mark",
  110. "markattachmenttype",
  111. "markclass",
  112. "nameid",
  113. "null",
  114. "parameters",
  115. "pos",
  116. "position",
  117. "required",
  118. "righttoleft",
  119. "reversesub",
  120. "rsub",
  121. "script",
  122. "sub",
  123. "substitute",
  124. "subtable",
  125. "table",
  126. "usemarkfilteringset",
  127. "useextension",
  128. "valuerecorddef",
  129. "base",
  130. "gdef",
  131. "head",
  132. "hhea",
  133. "name",
  134. "vhea",
  135. "vmtx",
  136. ]
  137. )
  138. def asFea(g):
  139. if hasattr(g, "asFea"):
  140. return g.asFea()
  141. elif isinstance(g, tuple) and len(g) == 2:
  142. return asFea(g[0]) + " - " + asFea(g[1]) # a range
  143. elif g.lower() in fea_keywords:
  144. return "\\" + g
  145. else:
  146. return g
  147. class Element(object):
  148. """A base class representing "something" in a feature file."""
  149. def __init__(self, location=None):
  150. #: location of this element as a `FeatureLibLocation` object.
  151. if location and not isinstance(location, FeatureLibLocation):
  152. location = FeatureLibLocation(*location)
  153. self.location = location
  154. def build(self, builder):
  155. pass
  156. def asFea(self, indent=""):
  157. """Returns this element as a string of feature code. For block-type
  158. elements (such as :class:`FeatureBlock`), the `indent` string is
  159. added to the start of each line in the output."""
  160. raise NotImplementedError
  161. def __str__(self):
  162. return self.asFea()
  163. class Statement(Element):
  164. pass
  165. class Expression(Element):
  166. pass
  167. class Comment(Element):
  168. """A comment in a feature file."""
  169. def __init__(self, text, location=None):
  170. super(Comment, self).__init__(location)
  171. #: Text of the comment
  172. self.text = text
  173. def asFea(self, indent=""):
  174. return self.text
  175. class NullGlyph(Expression):
  176. """The NULL glyph, used in glyph deletion substitutions."""
  177. def __init__(self, location=None):
  178. Expression.__init__(self, location)
  179. #: The name itself as a string
  180. def glyphSet(self):
  181. """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
  182. return ()
  183. def asFea(self, indent=""):
  184. return "NULL"
  185. class GlyphName(Expression):
  186. """A single glyph name, such as ``cedilla``."""
  187. def __init__(self, glyph, location=None):
  188. Expression.__init__(self, location)
  189. #: The name itself as a string
  190. self.glyph = glyph
  191. def glyphSet(self):
  192. """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
  193. return (self.glyph,)
  194. def asFea(self, indent=""):
  195. return asFea(self.glyph)
  196. class GlyphClass(Expression):
  197. """A glyph class, such as ``[acute cedilla grave]``."""
  198. def __init__(self, glyphs=None, location=None):
  199. Expression.__init__(self, location)
  200. #: The list of glyphs in this class, as :class:`GlyphName` objects.
  201. self.glyphs = glyphs if glyphs is not None else []
  202. self.original = []
  203. self.curr = 0
  204. def glyphSet(self):
  205. """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
  206. return tuple(self.glyphs)
  207. def asFea(self, indent=""):
  208. if len(self.original):
  209. if self.curr < len(self.glyphs):
  210. self.original.extend(self.glyphs[self.curr :])
  211. self.curr = len(self.glyphs)
  212. return "[" + " ".join(map(asFea, self.original)) + "]"
  213. else:
  214. return "[" + " ".join(map(asFea, self.glyphs)) + "]"
  215. def extend(self, glyphs):
  216. """Add a list of :class:`GlyphName` objects to the class."""
  217. self.glyphs.extend(glyphs)
  218. def append(self, glyph):
  219. """Add a single :class:`GlyphName` object to the class."""
  220. self.glyphs.append(glyph)
  221. def add_range(self, start, end, glyphs):
  222. """Add a range (e.g. ``A-Z``) to the class. ``start`` and ``end``
  223. are either :class:`GlyphName` objects or strings representing the
  224. start and end glyphs in the class, and ``glyphs`` is the full list of
  225. :class:`GlyphName` objects in the range."""
  226. if self.curr < len(self.glyphs):
  227. self.original.extend(self.glyphs[self.curr :])
  228. self.original.append((start, end))
  229. self.glyphs.extend(glyphs)
  230. self.curr = len(self.glyphs)
  231. def add_cid_range(self, start, end, glyphs):
  232. """Add a range to the class by glyph ID. ``start`` and ``end`` are the
  233. initial and final IDs, and ``glyphs`` is the full list of
  234. :class:`GlyphName` objects in the range."""
  235. if self.curr < len(self.glyphs):
  236. self.original.extend(self.glyphs[self.curr :])
  237. self.original.append(("\\{}".format(start), "\\{}".format(end)))
  238. self.glyphs.extend(glyphs)
  239. self.curr = len(self.glyphs)
  240. def add_class(self, gc):
  241. """Add glyphs from the given :class:`GlyphClassName` object to the
  242. class."""
  243. if self.curr < len(self.glyphs):
  244. self.original.extend(self.glyphs[self.curr :])
  245. self.original.append(gc)
  246. self.glyphs.extend(gc.glyphSet())
  247. self.curr = len(self.glyphs)
  248. class GlyphClassName(Expression):
  249. """A glyph class name, such as ``@FRENCH_MARKS``. This must be instantiated
  250. with a :class:`GlyphClassDefinition` object."""
  251. def __init__(self, glyphclass, location=None):
  252. Expression.__init__(self, location)
  253. assert isinstance(glyphclass, GlyphClassDefinition)
  254. self.glyphclass = glyphclass
  255. def glyphSet(self):
  256. """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
  257. return tuple(self.glyphclass.glyphSet())
  258. def asFea(self, indent=""):
  259. return "@" + self.glyphclass.name
  260. class MarkClassName(Expression):
  261. """A mark class name, such as ``@FRENCH_MARKS`` defined with ``markClass``.
  262. This must be instantiated with a :class:`MarkClass` object."""
  263. def __init__(self, markClass, location=None):
  264. Expression.__init__(self, location)
  265. assert isinstance(markClass, MarkClass)
  266. self.markClass = markClass
  267. def glyphSet(self):
  268. """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
  269. return self.markClass.glyphSet()
  270. def asFea(self, indent=""):
  271. return "@" + self.markClass.name
  272. class AnonymousBlock(Statement):
  273. """An anonymous data block."""
  274. def __init__(self, tag, content, location=None):
  275. Statement.__init__(self, location)
  276. self.tag = tag #: string containing the block's "tag"
  277. self.content = content #: block data as string
  278. def asFea(self, indent=""):
  279. res = "anon {} {{\n".format(self.tag)
  280. res += self.content
  281. res += "}} {};\n\n".format(self.tag)
  282. return res
  283. class Block(Statement):
  284. """A block of statements: feature, lookup, etc."""
  285. def __init__(self, location=None):
  286. Statement.__init__(self, location)
  287. self.statements = [] #: Statements contained in the block
  288. def build(self, builder):
  289. """When handed a 'builder' object of comparable interface to
  290. :class:`fontTools.feaLib.builder`, walks the statements in this
  291. block, calling the builder callbacks."""
  292. for s in self.statements:
  293. s.build(builder)
  294. def asFea(self, indent=""):
  295. indent += SHIFT
  296. return (
  297. indent
  298. + ("\n" + indent).join([s.asFea(indent=indent) for s in self.statements])
  299. + "\n"
  300. )
  301. class FeatureFile(Block):
  302. """The top-level element of the syntax tree, containing the whole feature
  303. file in its ``statements`` attribute."""
  304. def __init__(self):
  305. Block.__init__(self, location=None)
  306. self.markClasses = {} # name --> ast.MarkClass
  307. def asFea(self, indent=""):
  308. return "\n".join(s.asFea(indent=indent) for s in self.statements)
  309. class FeatureBlock(Block):
  310. """A named feature block."""
  311. def __init__(self, name, use_extension=False, location=None):
  312. Block.__init__(self, location)
  313. self.name, self.use_extension = name, use_extension
  314. def build(self, builder):
  315. """Call the ``start_feature`` callback on the builder object, visit
  316. all the statements in this feature, and then call ``end_feature``."""
  317. builder.start_feature(self.location, self.name, self.use_extension)
  318. # language exclude_dflt statements modify builder.features_
  319. # limit them to this block with temporary builder.features_
  320. features = builder.features_
  321. builder.features_ = {}
  322. Block.build(self, builder)
  323. for key, value in builder.features_.items():
  324. features.setdefault(key, []).extend(value)
  325. builder.features_ = features
  326. builder.end_feature()
  327. def asFea(self, indent=""):
  328. res = indent + "feature %s " % self.name.strip()
  329. if self.use_extension:
  330. res += "useExtension "
  331. res += "{\n"
  332. res += Block.asFea(self, indent=indent)
  333. res += indent + "} %s;\n" % self.name.strip()
  334. return res
  335. class NestedBlock(Block):
  336. """A block inside another block, for example when found inside a
  337. ``cvParameters`` block."""
  338. def __init__(self, tag, block_name, location=None):
  339. Block.__init__(self, location)
  340. self.tag = tag
  341. self.block_name = block_name
  342. def build(self, builder):
  343. Block.build(self, builder)
  344. if self.block_name == "ParamUILabelNameID":
  345. builder.add_to_cv_num_named_params(self.tag)
  346. def asFea(self, indent=""):
  347. res = "{}{} {{\n".format(indent, self.block_name)
  348. res += Block.asFea(self, indent=indent)
  349. res += "{}}};\n".format(indent)
  350. return res
  351. class LookupBlock(Block):
  352. """A named lookup, containing ``statements``."""
  353. def __init__(self, name, use_extension=False, location=None):
  354. Block.__init__(self, location)
  355. self.name, self.use_extension = name, use_extension
  356. def build(self, builder):
  357. builder.start_lookup_block(self.location, self.name, self.use_extension)
  358. Block.build(self, builder)
  359. builder.end_lookup_block()
  360. def asFea(self, indent=""):
  361. res = "lookup {} ".format(self.name)
  362. if self.use_extension:
  363. res += "useExtension "
  364. res += "{\n"
  365. res += Block.asFea(self, indent=indent)
  366. res += "{}}} {};\n".format(indent, self.name)
  367. return res
  368. class TableBlock(Block):
  369. """A ``table ... { }`` block."""
  370. def __init__(self, name, location=None):
  371. Block.__init__(self, location)
  372. self.name = name
  373. def asFea(self, indent=""):
  374. res = "table {} {{\n".format(self.name.strip())
  375. res += super(TableBlock, self).asFea(indent=indent)
  376. res += "}} {};\n".format(self.name.strip())
  377. return res
  378. class GlyphClassDefinition(Statement):
  379. """Example: ``@UPPERCASE = [A-Z];``."""
  380. def __init__(self, name, glyphs, location=None):
  381. Statement.__init__(self, location)
  382. self.name = name #: class name as a string, without initial ``@``
  383. self.glyphs = glyphs #: a :class:`GlyphClass` object
  384. def glyphSet(self):
  385. """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
  386. return tuple(self.glyphs.glyphSet())
  387. def asFea(self, indent=""):
  388. return "@" + self.name + " = " + self.glyphs.asFea() + ";"
  389. class GlyphClassDefStatement(Statement):
  390. """Example: ``GlyphClassDef @UPPERCASE, [B], [C], [D];``. The parameters
  391. must be either :class:`GlyphClass` or :class:`GlyphClassName` objects, or
  392. ``None``."""
  393. def __init__(
  394. self, baseGlyphs, markGlyphs, ligatureGlyphs, componentGlyphs, location=None
  395. ):
  396. Statement.__init__(self, location)
  397. self.baseGlyphs, self.markGlyphs = (baseGlyphs, markGlyphs)
  398. self.ligatureGlyphs = ligatureGlyphs
  399. self.componentGlyphs = componentGlyphs
  400. def build(self, builder):
  401. """Calls the builder's ``add_glyphClassDef`` callback."""
  402. base = self.baseGlyphs.glyphSet() if self.baseGlyphs else tuple()
  403. liga = self.ligatureGlyphs.glyphSet() if self.ligatureGlyphs else tuple()
  404. mark = self.markGlyphs.glyphSet() if self.markGlyphs else tuple()
  405. comp = self.componentGlyphs.glyphSet() if self.componentGlyphs else tuple()
  406. builder.add_glyphClassDef(self.location, base, liga, mark, comp)
  407. def asFea(self, indent=""):
  408. return "GlyphClassDef {}, {}, {}, {};".format(
  409. self.baseGlyphs.asFea() if self.baseGlyphs else "",
  410. self.ligatureGlyphs.asFea() if self.ligatureGlyphs else "",
  411. self.markGlyphs.asFea() if self.markGlyphs else "",
  412. self.componentGlyphs.asFea() if self.componentGlyphs else "",
  413. )
  414. class MarkClass(object):
  415. """One `or more` ``markClass`` statements for the same mark class.
  416. While glyph classes can be defined only once, the feature file format
  417. allows expanding mark classes with multiple definitions, each using
  418. different glyphs and anchors. The following are two ``MarkClassDefinitions``
  419. for the same ``MarkClass``::
  420. markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS;
  421. markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS;
  422. The ``MarkClass`` object is therefore just a container for a list of
  423. :class:`MarkClassDefinition` statements.
  424. """
  425. def __init__(self, name):
  426. self.name = name
  427. self.definitions = []
  428. self.glyphs = OrderedDict() # glyph --> ast.MarkClassDefinitions
  429. def addDefinition(self, definition):
  430. """Add a :class:`MarkClassDefinition` statement to this mark class."""
  431. assert isinstance(definition, MarkClassDefinition)
  432. self.definitions.append(weakref.proxy(definition))
  433. for glyph in definition.glyphSet():
  434. if glyph in self.glyphs:
  435. otherLoc = self.glyphs[glyph].location
  436. if otherLoc is None:
  437. end = ""
  438. else:
  439. end = f" at {otherLoc}"
  440. raise FeatureLibError(
  441. "Glyph %s already defined%s" % (glyph, end), definition.location
  442. )
  443. self.glyphs[glyph] = definition
  444. def glyphSet(self):
  445. """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
  446. return tuple(self.glyphs.keys())
  447. def asFea(self, indent=""):
  448. res = "\n".join(d.asFea() for d in self.definitions)
  449. return res
  450. class MarkClassDefinition(Statement):
  451. """A single ``markClass`` statement. The ``markClass`` should be a
  452. :class:`MarkClass` object, the ``anchor`` an :class:`Anchor` object,
  453. and the ``glyphs`` parameter should be a `glyph-containing object`_ .
  454. Example:
  455. .. code:: python
  456. mc = MarkClass("FRENCH_ACCENTS")
  457. mc.addDefinition( MarkClassDefinition(mc, Anchor(350, 800),
  458. GlyphClass([ GlyphName("acute"), GlyphName("grave") ])
  459. ) )
  460. mc.addDefinition( MarkClassDefinition(mc, Anchor(350, -200),
  461. GlyphClass([ GlyphName("cedilla") ])
  462. ) )
  463. mc.asFea()
  464. # markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS;
  465. # markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS;
  466. """
  467. def __init__(self, markClass, anchor, glyphs, location=None):
  468. Statement.__init__(self, location)
  469. assert isinstance(markClass, MarkClass)
  470. assert isinstance(anchor, Anchor) and isinstance(glyphs, Expression)
  471. self.markClass, self.anchor, self.glyphs = markClass, anchor, glyphs
  472. def glyphSet(self):
  473. """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
  474. return self.glyphs.glyphSet()
  475. def asFea(self, indent=""):
  476. return "markClass {} {} @{};".format(
  477. self.glyphs.asFea(), self.anchor.asFea(), self.markClass.name
  478. )
  479. class AlternateSubstStatement(Statement):
  480. """A ``sub ... from ...`` statement.
  481. ``glyph`` and ``replacement`` should be `glyph-containing objects`_.
  482. ``prefix`` and ``suffix`` should be lists of `glyph-containing objects`_."""
  483. def __init__(self, prefix, glyph, suffix, replacement, location=None):
  484. Statement.__init__(self, location)
  485. self.prefix, self.glyph, self.suffix = (prefix, glyph, suffix)
  486. self.replacement = replacement
  487. def build(self, builder):
  488. """Calls the builder's ``add_alternate_subst`` callback."""
  489. glyph = self.glyph.glyphSet()
  490. assert len(glyph) == 1, glyph
  491. glyph = list(glyph)[0]
  492. prefix = [p.glyphSet() for p in self.prefix]
  493. suffix = [s.glyphSet() for s in self.suffix]
  494. replacement = self.replacement.glyphSet()
  495. builder.add_alternate_subst(self.location, prefix, glyph, suffix, replacement)
  496. def asFea(self, indent=""):
  497. res = "sub "
  498. if len(self.prefix) or len(self.suffix):
  499. if len(self.prefix):
  500. res += " ".join(map(asFea, self.prefix)) + " "
  501. res += asFea(self.glyph) + "'" # even though we really only use 1
  502. if len(self.suffix):
  503. res += " " + " ".join(map(asFea, self.suffix))
  504. else:
  505. res += asFea(self.glyph)
  506. res += " from "
  507. res += asFea(self.replacement)
  508. res += ";"
  509. return res
  510. class Anchor(Expression):
  511. """An ``Anchor`` element, used inside a ``pos`` rule.
  512. If a ``name`` is given, this will be used in preference to the coordinates.
  513. Other values should be integer.
  514. """
  515. def __init__(
  516. self,
  517. x,
  518. y,
  519. name=None,
  520. contourpoint=None,
  521. xDeviceTable=None,
  522. yDeviceTable=None,
  523. location=None,
  524. ):
  525. Expression.__init__(self, location)
  526. self.name = name
  527. self.x, self.y, self.contourpoint = x, y, contourpoint
  528. self.xDeviceTable, self.yDeviceTable = xDeviceTable, yDeviceTable
  529. def asFea(self, indent=""):
  530. if self.name is not None:
  531. return "<anchor {}>".format(self.name)
  532. res = "<anchor {} {}".format(self.x, self.y)
  533. if self.contourpoint:
  534. res += " contourpoint {}".format(self.contourpoint)
  535. if self.xDeviceTable or self.yDeviceTable:
  536. res += " "
  537. res += deviceToString(self.xDeviceTable)
  538. res += " "
  539. res += deviceToString(self.yDeviceTable)
  540. res += ">"
  541. return res
  542. class AnchorDefinition(Statement):
  543. """A named anchor definition. (2.e.viii). ``name`` should be a string."""
  544. def __init__(self, name, x, y, contourpoint=None, location=None):
  545. Statement.__init__(self, location)
  546. self.name, self.x, self.y, self.contourpoint = name, x, y, contourpoint
  547. def asFea(self, indent=""):
  548. res = "anchorDef {} {}".format(self.x, self.y)
  549. if self.contourpoint:
  550. res += " contourpoint {}".format(self.contourpoint)
  551. res += " {};".format(self.name)
  552. return res
  553. class AttachStatement(Statement):
  554. """A ``GDEF`` table ``Attach`` statement."""
  555. def __init__(self, glyphs, contourPoints, location=None):
  556. Statement.__init__(self, location)
  557. self.glyphs = glyphs #: A `glyph-containing object`_
  558. self.contourPoints = contourPoints #: A list of integer contour points
  559. def build(self, builder):
  560. """Calls the builder's ``add_attach_points`` callback."""
  561. glyphs = self.glyphs.glyphSet()
  562. builder.add_attach_points(self.location, glyphs, self.contourPoints)
  563. def asFea(self, indent=""):
  564. return "Attach {} {};".format(
  565. self.glyphs.asFea(), " ".join(str(c) for c in self.contourPoints)
  566. )
  567. class ChainContextPosStatement(Statement):
  568. r"""A chained contextual positioning statement.
  569. ``prefix``, ``glyphs``, and ``suffix`` should be lists of
  570. `glyph-containing objects`_ .
  571. ``lookups`` should be a list of elements representing what lookups
  572. to apply at each glyph position. Each element should be a
  573. :class:`LookupBlock` to apply a single chaining lookup at the given
  574. position, a list of :class:`LookupBlock`\ s to apply multiple
  575. lookups, or ``None`` to apply no lookup. The length of the outer
  576. list should equal the length of ``glyphs``; the inner lists can be
  577. of variable length."""
  578. def __init__(self, prefix, glyphs, suffix, lookups, location=None):
  579. Statement.__init__(self, location)
  580. self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
  581. self.lookups = list(lookups)
  582. for i, lookup in enumerate(lookups):
  583. if lookup:
  584. try:
  585. iter(lookup)
  586. except TypeError:
  587. self.lookups[i] = [lookup]
  588. def build(self, builder):
  589. """Calls the builder's ``add_chain_context_pos`` callback."""
  590. prefix = [p.glyphSet() for p in self.prefix]
  591. glyphs = [g.glyphSet() for g in self.glyphs]
  592. suffix = [s.glyphSet() for s in self.suffix]
  593. builder.add_chain_context_pos(
  594. self.location, prefix, glyphs, suffix, self.lookups
  595. )
  596. def asFea(self, indent=""):
  597. res = "pos "
  598. if (
  599. len(self.prefix)
  600. or len(self.suffix)
  601. or any([x is not None for x in self.lookups])
  602. ):
  603. if len(self.prefix):
  604. res += " ".join(g.asFea() for g in self.prefix) + " "
  605. for i, g in enumerate(self.glyphs):
  606. res += g.asFea() + "'"
  607. if self.lookups[i]:
  608. for lu in self.lookups[i]:
  609. res += " lookup " + lu.name
  610. if i < len(self.glyphs) - 1:
  611. res += " "
  612. if len(self.suffix):
  613. res += " " + " ".join(map(asFea, self.suffix))
  614. else:
  615. res += " ".join(map(asFea, self.glyphs))
  616. res += ";"
  617. return res
  618. class ChainContextSubstStatement(Statement):
  619. r"""A chained contextual substitution statement.
  620. ``prefix``, ``glyphs``, and ``suffix`` should be lists of
  621. `glyph-containing objects`_ .
  622. ``lookups`` should be a list of elements representing what lookups
  623. to apply at each glyph position. Each element should be a
  624. :class:`LookupBlock` to apply a single chaining lookup at the given
  625. position, a list of :class:`LookupBlock`\ s to apply multiple
  626. lookups, or ``None`` to apply no lookup. The length of the outer
  627. list should equal the length of ``glyphs``; the inner lists can be
  628. of variable length."""
  629. def __init__(self, prefix, glyphs, suffix, lookups, location=None):
  630. Statement.__init__(self, location)
  631. self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
  632. self.lookups = list(lookups)
  633. for i, lookup in enumerate(lookups):
  634. if lookup:
  635. try:
  636. iter(lookup)
  637. except TypeError:
  638. self.lookups[i] = [lookup]
  639. def build(self, builder):
  640. """Calls the builder's ``add_chain_context_subst`` callback."""
  641. prefix = [p.glyphSet() for p in self.prefix]
  642. glyphs = [g.glyphSet() for g in self.glyphs]
  643. suffix = [s.glyphSet() for s in self.suffix]
  644. builder.add_chain_context_subst(
  645. self.location, prefix, glyphs, suffix, self.lookups
  646. )
  647. def asFea(self, indent=""):
  648. res = "sub "
  649. if (
  650. len(self.prefix)
  651. or len(self.suffix)
  652. or any([x is not None for x in self.lookups])
  653. ):
  654. if len(self.prefix):
  655. res += " ".join(g.asFea() for g in self.prefix) + " "
  656. for i, g in enumerate(self.glyphs):
  657. res += g.asFea() + "'"
  658. if self.lookups[i]:
  659. for lu in self.lookups[i]:
  660. res += " lookup " + lu.name
  661. if i < len(self.glyphs) - 1:
  662. res += " "
  663. if len(self.suffix):
  664. res += " " + " ".join(map(asFea, self.suffix))
  665. else:
  666. res += " ".join(map(asFea, self.glyphs))
  667. res += ";"
  668. return res
  669. class CursivePosStatement(Statement):
  670. """A cursive positioning statement. Entry and exit anchors can either
  671. be :class:`Anchor` objects or ``None``."""
  672. def __init__(self, glyphclass, entryAnchor, exitAnchor, location=None):
  673. Statement.__init__(self, location)
  674. self.glyphclass = glyphclass
  675. self.entryAnchor, self.exitAnchor = entryAnchor, exitAnchor
  676. def build(self, builder):
  677. """Calls the builder object's ``add_cursive_pos`` callback."""
  678. builder.add_cursive_pos(
  679. self.location, self.glyphclass.glyphSet(), self.entryAnchor, self.exitAnchor
  680. )
  681. def asFea(self, indent=""):
  682. entry = self.entryAnchor.asFea() if self.entryAnchor else "<anchor NULL>"
  683. exit = self.exitAnchor.asFea() if self.exitAnchor else "<anchor NULL>"
  684. return "pos cursive {} {} {};".format(self.glyphclass.asFea(), entry, exit)
  685. class FeatureReferenceStatement(Statement):
  686. """Example: ``feature salt;``"""
  687. def __init__(self, featureName, location=None):
  688. Statement.__init__(self, location)
  689. self.location, self.featureName = (location, featureName)
  690. def build(self, builder):
  691. """Calls the builder object's ``add_feature_reference`` callback."""
  692. builder.add_feature_reference(self.location, self.featureName)
  693. def asFea(self, indent=""):
  694. return "feature {};".format(self.featureName)
  695. class IgnorePosStatement(Statement):
  696. """An ``ignore pos`` statement, containing `one or more` contexts to ignore.
  697. ``chainContexts`` should be a list of ``(prefix, glyphs, suffix)`` tuples,
  698. with each of ``prefix``, ``glyphs`` and ``suffix`` being
  699. `glyph-containing objects`_ ."""
  700. def __init__(self, chainContexts, location=None):
  701. Statement.__init__(self, location)
  702. self.chainContexts = chainContexts
  703. def build(self, builder):
  704. """Calls the builder object's ``add_chain_context_pos`` callback on each
  705. rule context."""
  706. for prefix, glyphs, suffix in self.chainContexts:
  707. prefix = [p.glyphSet() for p in prefix]
  708. glyphs = [g.glyphSet() for g in glyphs]
  709. suffix = [s.glyphSet() for s in suffix]
  710. builder.add_chain_context_pos(self.location, prefix, glyphs, suffix, [])
  711. def asFea(self, indent=""):
  712. contexts = []
  713. for prefix, glyphs, suffix in self.chainContexts:
  714. res = ""
  715. if len(prefix) or len(suffix):
  716. if len(prefix):
  717. res += " ".join(map(asFea, prefix)) + " "
  718. res += " ".join(g.asFea() + "'" for g in glyphs)
  719. if len(suffix):
  720. res += " " + " ".join(map(asFea, suffix))
  721. else:
  722. res += " ".join(map(asFea, glyphs))
  723. contexts.append(res)
  724. return "ignore pos " + ", ".join(contexts) + ";"
  725. class IgnoreSubstStatement(Statement):
  726. """An ``ignore sub`` statement, containing `one or more` contexts to ignore.
  727. ``chainContexts`` should be a list of ``(prefix, glyphs, suffix)`` tuples,
  728. with each of ``prefix``, ``glyphs`` and ``suffix`` being
  729. `glyph-containing objects`_ ."""
  730. def __init__(self, chainContexts, location=None):
  731. Statement.__init__(self, location)
  732. self.chainContexts = chainContexts
  733. def build(self, builder):
  734. """Calls the builder object's ``add_chain_context_subst`` callback on
  735. each rule context."""
  736. for prefix, glyphs, suffix in self.chainContexts:
  737. prefix = [p.glyphSet() for p in prefix]
  738. glyphs = [g.glyphSet() for g in glyphs]
  739. suffix = [s.glyphSet() for s in suffix]
  740. builder.add_chain_context_subst(self.location, prefix, glyphs, suffix, [])
  741. def asFea(self, indent=""):
  742. contexts = []
  743. for prefix, glyphs, suffix in self.chainContexts:
  744. res = ""
  745. if len(prefix):
  746. res += " ".join(map(asFea, prefix)) + " "
  747. res += " ".join(g.asFea() + "'" for g in glyphs)
  748. if len(suffix):
  749. res += " " + " ".join(map(asFea, suffix))
  750. contexts.append(res)
  751. return "ignore sub " + ", ".join(contexts) + ";"
  752. class IncludeStatement(Statement):
  753. """An ``include()`` statement."""
  754. def __init__(self, filename, location=None):
  755. super(IncludeStatement, self).__init__(location)
  756. self.filename = filename #: String containing name of file to include
  757. def build(self):
  758. # TODO: consider lazy-loading the including parser/lexer?
  759. raise FeatureLibError(
  760. "Building an include statement is not implemented yet. "
  761. "Instead, use Parser(..., followIncludes=True) for building.",
  762. self.location,
  763. )
  764. def asFea(self, indent=""):
  765. return indent + "include(%s);" % self.filename
  766. class LanguageStatement(Statement):
  767. """A ``language`` statement within a feature."""
  768. def __init__(self, language, include_default=True, required=False, location=None):
  769. Statement.__init__(self, location)
  770. assert len(language) == 4
  771. self.language = language #: A four-character language tag
  772. self.include_default = include_default #: If false, "exclude_dflt"
  773. self.required = required
  774. def build(self, builder):
  775. """Call the builder object's ``set_language`` callback."""
  776. builder.set_language(
  777. location=self.location,
  778. language=self.language,
  779. include_default=self.include_default,
  780. required=self.required,
  781. )
  782. def asFea(self, indent=""):
  783. res = "language {}".format(self.language.strip())
  784. if not self.include_default:
  785. res += " exclude_dflt"
  786. if self.required:
  787. res += " required"
  788. res += ";"
  789. return res
  790. class LanguageSystemStatement(Statement):
  791. """A top-level ``languagesystem`` statement."""
  792. def __init__(self, script, language, location=None):
  793. Statement.__init__(self, location)
  794. self.script, self.language = (script, language)
  795. def build(self, builder):
  796. """Calls the builder object's ``add_language_system`` callback."""
  797. builder.add_language_system(self.location, self.script, self.language)
  798. def asFea(self, indent=""):
  799. return "languagesystem {} {};".format(self.script, self.language.strip())
  800. class FontRevisionStatement(Statement):
  801. """A ``head`` table ``FontRevision`` statement. ``revision`` should be a
  802. number, and will be formatted to three significant decimal places."""
  803. def __init__(self, revision, location=None):
  804. Statement.__init__(self, location)
  805. self.revision = revision
  806. def build(self, builder):
  807. builder.set_font_revision(self.location, self.revision)
  808. def asFea(self, indent=""):
  809. return "FontRevision {:.3f};".format(self.revision)
  810. class LigatureCaretByIndexStatement(Statement):
  811. """A ``GDEF`` table ``LigatureCaretByIndex`` statement. ``glyphs`` should be
  812. a `glyph-containing object`_, and ``carets`` should be a list of integers."""
  813. def __init__(self, glyphs, carets, location=None):
  814. Statement.__init__(self, location)
  815. self.glyphs, self.carets = (glyphs, carets)
  816. def build(self, builder):
  817. """Calls the builder object's ``add_ligatureCaretByIndex_`` callback."""
  818. glyphs = self.glyphs.glyphSet()
  819. builder.add_ligatureCaretByIndex_(self.location, glyphs, set(self.carets))
  820. def asFea(self, indent=""):
  821. return "LigatureCaretByIndex {} {};".format(
  822. self.glyphs.asFea(), " ".join(str(x) for x in self.carets)
  823. )
  824. class LigatureCaretByPosStatement(Statement):
  825. """A ``GDEF`` table ``LigatureCaretByPos`` statement. ``glyphs`` should be
  826. a `glyph-containing object`_, and ``carets`` should be a list of integers."""
  827. def __init__(self, glyphs, carets, location=None):
  828. Statement.__init__(self, location)
  829. self.glyphs, self.carets = (glyphs, carets)
  830. def build(self, builder):
  831. """Calls the builder object's ``add_ligatureCaretByPos_`` callback."""
  832. glyphs = self.glyphs.glyphSet()
  833. builder.add_ligatureCaretByPos_(self.location, glyphs, set(self.carets))
  834. def asFea(self, indent=""):
  835. return "LigatureCaretByPos {} {};".format(
  836. self.glyphs.asFea(), " ".join(str(x) for x in self.carets)
  837. )
  838. class LigatureSubstStatement(Statement):
  839. """A chained contextual substitution statement.
  840. ``prefix``, ``glyphs``, and ``suffix`` should be lists of
  841. `glyph-containing objects`_; ``replacement`` should be a single
  842. `glyph-containing object`_.
  843. If ``forceChain`` is True, this is expressed as a chaining rule
  844. (e.g. ``sub f' i' by f_i``) even when no context is given."""
  845. def __init__(self, prefix, glyphs, suffix, replacement, forceChain, location=None):
  846. Statement.__init__(self, location)
  847. self.prefix, self.glyphs, self.suffix = (prefix, glyphs, suffix)
  848. self.replacement, self.forceChain = replacement, forceChain
  849. def build(self, builder):
  850. prefix = [p.glyphSet() for p in self.prefix]
  851. glyphs = [g.glyphSet() for g in self.glyphs]
  852. suffix = [s.glyphSet() for s in self.suffix]
  853. builder.add_ligature_subst(
  854. self.location, prefix, glyphs, suffix, self.replacement, self.forceChain
  855. )
  856. def asFea(self, indent=""):
  857. res = "sub "
  858. if len(self.prefix) or len(self.suffix) or self.forceChain:
  859. if len(self.prefix):
  860. res += " ".join(g.asFea() for g in self.prefix) + " "
  861. res += " ".join(g.asFea() + "'" for g in self.glyphs)
  862. if len(self.suffix):
  863. res += " " + " ".join(g.asFea() for g in self.suffix)
  864. else:
  865. res += " ".join(g.asFea() for g in self.glyphs)
  866. res += " by "
  867. res += asFea(self.replacement)
  868. res += ";"
  869. return res
  870. class LookupFlagStatement(Statement):
  871. """A ``lookupflag`` statement. The ``value`` should be an integer value
  872. representing the flags in use, but not including the ``markAttachment``
  873. class and ``markFilteringSet`` values, which must be specified as
  874. glyph-containing objects."""
  875. def __init__(
  876. self, value=0, markAttachment=None, markFilteringSet=None, location=None
  877. ):
  878. Statement.__init__(self, location)
  879. self.value = value
  880. self.markAttachment = markAttachment
  881. self.markFilteringSet = markFilteringSet
  882. def build(self, builder):
  883. """Calls the builder object's ``set_lookup_flag`` callback."""
  884. markAttach = None
  885. if self.markAttachment is not None:
  886. markAttach = self.markAttachment.glyphSet()
  887. markFilter = None
  888. if self.markFilteringSet is not None:
  889. markFilter = self.markFilteringSet.glyphSet()
  890. builder.set_lookup_flag(self.location, self.value, markAttach, markFilter)
  891. def asFea(self, indent=""):
  892. res = []
  893. flags = ["RightToLeft", "IgnoreBaseGlyphs", "IgnoreLigatures", "IgnoreMarks"]
  894. curr = 1
  895. for i in range(len(flags)):
  896. if self.value & curr != 0:
  897. res.append(flags[i])
  898. curr = curr << 1
  899. if self.markAttachment is not None:
  900. res.append("MarkAttachmentType {}".format(self.markAttachment.asFea()))
  901. if self.markFilteringSet is not None:
  902. res.append("UseMarkFilteringSet {}".format(self.markFilteringSet.asFea()))
  903. if not res:
  904. res = ["0"]
  905. return "lookupflag {};".format(" ".join(res))
  906. class LookupReferenceStatement(Statement):
  907. """Represents a ``lookup ...;`` statement to include a lookup in a feature.
  908. The ``lookup`` should be a :class:`LookupBlock` object."""
  909. def __init__(self, lookup, location=None):
  910. Statement.__init__(self, location)
  911. self.location, self.lookup = (location, lookup)
  912. def build(self, builder):
  913. """Calls the builder object's ``add_lookup_call`` callback."""
  914. builder.add_lookup_call(self.lookup.name)
  915. def asFea(self, indent=""):
  916. return "lookup {};".format(self.lookup.name)
  917. class MarkBasePosStatement(Statement):
  918. """A mark-to-base positioning rule. The ``base`` should be a
  919. `glyph-containing object`_. The ``marks`` should be a list of
  920. (:class:`Anchor`, :class:`MarkClass`) tuples."""
  921. def __init__(self, base, marks, location=None):
  922. Statement.__init__(self, location)
  923. self.base, self.marks = base, marks
  924. def build(self, builder):
  925. """Calls the builder object's ``add_mark_base_pos`` callback."""
  926. builder.add_mark_base_pos(self.location, self.base.glyphSet(), self.marks)
  927. def asFea(self, indent=""):
  928. res = "pos base {}".format(self.base.asFea())
  929. for a, m in self.marks:
  930. res += "\n" + indent + SHIFT + "{} mark @{}".format(a.asFea(), m.name)
  931. res += ";"
  932. return res
  933. class MarkLigPosStatement(Statement):
  934. """A mark-to-ligature positioning rule. The ``ligatures`` must be a
  935. `glyph-containing object`_. The ``marks`` should be a list of lists: each
  936. element in the top-level list represents a component glyph, and is made
  937. up of a list of (:class:`Anchor`, :class:`MarkClass`) tuples representing
  938. mark attachment points for that position.
  939. Example::
  940. m1 = MarkClass("TOP_MARKS")
  941. m2 = MarkClass("BOTTOM_MARKS")
  942. # ... add definitions to mark classes...
  943. glyph = GlyphName("lam_meem_jeem")
  944. marks = [
  945. [ (Anchor(625,1800), m1) ], # Attachments on 1st component (lam)
  946. [ (Anchor(376,-378), m2) ], # Attachments on 2nd component (meem)
  947. [ ] # No attachments on the jeem
  948. ]
  949. mlp = MarkLigPosStatement(glyph, marks)
  950. mlp.asFea()
  951. # pos ligature lam_meem_jeem <anchor 625 1800> mark @TOP_MARKS
  952. # ligComponent <anchor 376 -378> mark @BOTTOM_MARKS;
  953. """
  954. def __init__(self, ligatures, marks, location=None):
  955. Statement.__init__(self, location)
  956. self.ligatures, self.marks = ligatures, marks
  957. def build(self, builder):
  958. """Calls the builder object's ``add_mark_lig_pos`` callback."""
  959. builder.add_mark_lig_pos(self.location, self.ligatures.glyphSet(), self.marks)
  960. def asFea(self, indent=""):
  961. res = "pos ligature {}".format(self.ligatures.asFea())
  962. ligs = []
  963. for l in self.marks:
  964. temp = ""
  965. if l is None or not len(l):
  966. temp = "\n" + indent + SHIFT * 2 + "<anchor NULL>"
  967. else:
  968. for a, m in l:
  969. temp += (
  970. "\n"
  971. + indent
  972. + SHIFT * 2
  973. + "{} mark @{}".format(a.asFea(), m.name)
  974. )
  975. ligs.append(temp)
  976. res += ("\n" + indent + SHIFT + "ligComponent").join(ligs)
  977. res += ";"
  978. return res
  979. class MarkMarkPosStatement(Statement):
  980. """A mark-to-mark positioning rule. The ``baseMarks`` must be a
  981. `glyph-containing object`_. The ``marks`` should be a list of
  982. (:class:`Anchor`, :class:`MarkClass`) tuples."""
  983. def __init__(self, baseMarks, marks, location=None):
  984. Statement.__init__(self, location)
  985. self.baseMarks, self.marks = baseMarks, marks
  986. def build(self, builder):
  987. """Calls the builder object's ``add_mark_mark_pos`` callback."""
  988. builder.add_mark_mark_pos(self.location, self.baseMarks.glyphSet(), self.marks)
  989. def asFea(self, indent=""):
  990. res = "pos mark {}".format(self.baseMarks.asFea())
  991. for a, m in self.marks:
  992. res += "\n" + indent + SHIFT + "{} mark @{}".format(a.asFea(), m.name)
  993. res += ";"
  994. return res
  995. class MultipleSubstStatement(Statement):
  996. """A multiple substitution statement.
  997. Args:
  998. prefix: a list of `glyph-containing objects`_.
  999. glyph: a single glyph-containing object.
  1000. suffix: a list of glyph-containing objects.
  1001. replacement: a list of glyph-containing objects.
  1002. forceChain: If true, the statement is expressed as a chaining rule
  1003. (e.g. ``sub f' i' by f_i``) even when no context is given.
  1004. """
  1005. def __init__(
  1006. self, prefix, glyph, suffix, replacement, forceChain=False, location=None
  1007. ):
  1008. Statement.__init__(self, location)
  1009. self.prefix, self.glyph, self.suffix = prefix, glyph, suffix
  1010. self.replacement = replacement
  1011. self.forceChain = forceChain
  1012. def build(self, builder):
  1013. """Calls the builder object's ``add_multiple_subst`` callback."""
  1014. prefix = [p.glyphSet() for p in self.prefix]
  1015. suffix = [s.glyphSet() for s in self.suffix]
  1016. if hasattr(self.glyph, "glyphSet"):
  1017. originals = self.glyph.glyphSet()
  1018. else:
  1019. originals = [self.glyph]
  1020. count = len(originals)
  1021. replaces = []
  1022. for r in self.replacement:
  1023. if hasattr(r, "glyphSet"):
  1024. replace = r.glyphSet()
  1025. else:
  1026. replace = [r]
  1027. if len(replace) == 1 and len(replace) != count:
  1028. replace = replace * count
  1029. replaces.append(replace)
  1030. replaces = list(zip(*replaces))
  1031. seen_originals = set()
  1032. for i, original in enumerate(originals):
  1033. if original not in seen_originals:
  1034. seen_originals.add(original)
  1035. builder.add_multiple_subst(
  1036. self.location,
  1037. prefix,
  1038. original,
  1039. suffix,
  1040. replaces and replaces[i] or (),
  1041. self.forceChain,
  1042. )
  1043. def asFea(self, indent=""):
  1044. res = "sub "
  1045. if len(self.prefix) or len(self.suffix) or self.forceChain:
  1046. if len(self.prefix):
  1047. res += " ".join(map(asFea, self.prefix)) + " "
  1048. res += asFea(self.glyph) + "'"
  1049. if len(self.suffix):
  1050. res += " " + " ".join(map(asFea, self.suffix))
  1051. else:
  1052. res += asFea(self.glyph)
  1053. replacement = self.replacement or [NullGlyph()]
  1054. res += " by "
  1055. res += " ".join(map(asFea, replacement))
  1056. res += ";"
  1057. return res
  1058. class PairPosStatement(Statement):
  1059. """A pair positioning statement.
  1060. ``glyphs1`` and ``glyphs2`` should be `glyph-containing objects`_.
  1061. ``valuerecord1`` should be a :class:`ValueRecord` object;
  1062. ``valuerecord2`` should be either a :class:`ValueRecord` object or ``None``.
  1063. If ``enumerated`` is true, then this is expressed as an
  1064. `enumerated pair <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#6.b.ii>`_.
  1065. """
  1066. def __init__(
  1067. self,
  1068. glyphs1,
  1069. valuerecord1,
  1070. glyphs2,
  1071. valuerecord2,
  1072. enumerated=False,
  1073. location=None,
  1074. ):
  1075. Statement.__init__(self, location)
  1076. self.enumerated = enumerated
  1077. self.glyphs1, self.valuerecord1 = glyphs1, valuerecord1
  1078. self.glyphs2, self.valuerecord2 = glyphs2, valuerecord2
  1079. def build(self, builder):
  1080. """Calls a callback on the builder object:
  1081. * If the rule is enumerated, calls ``add_specific_pair_pos`` on each
  1082. combination of first and second glyphs.
  1083. * If the glyphs are both single :class:`GlyphName` objects, calls
  1084. ``add_specific_pair_pos``.
  1085. * Else, calls ``add_class_pair_pos``.
  1086. """
  1087. if self.enumerated:
  1088. g = [self.glyphs1.glyphSet(), self.glyphs2.glyphSet()]
  1089. seen_pair = False
  1090. for glyph1, glyph2 in itertools.product(*g):
  1091. seen_pair = True
  1092. builder.add_specific_pair_pos(
  1093. self.location, glyph1, self.valuerecord1, glyph2, self.valuerecord2
  1094. )
  1095. if not seen_pair:
  1096. raise FeatureLibError(
  1097. "Empty glyph class in positioning rule", self.location
  1098. )
  1099. return
  1100. is_specific = isinstance(self.glyphs1, GlyphName) and isinstance(
  1101. self.glyphs2, GlyphName
  1102. )
  1103. if is_specific:
  1104. builder.add_specific_pair_pos(
  1105. self.location,
  1106. self.glyphs1.glyph,
  1107. self.valuerecord1,
  1108. self.glyphs2.glyph,
  1109. self.valuerecord2,
  1110. )
  1111. else:
  1112. builder.add_class_pair_pos(
  1113. self.location,
  1114. self.glyphs1.glyphSet(),
  1115. self.valuerecord1,
  1116. self.glyphs2.glyphSet(),
  1117. self.valuerecord2,
  1118. )
  1119. def asFea(self, indent=""):
  1120. res = "enum " if self.enumerated else ""
  1121. if self.valuerecord2:
  1122. res += "pos {} {} {} {};".format(
  1123. self.glyphs1.asFea(),
  1124. self.valuerecord1.asFea(),
  1125. self.glyphs2.asFea(),
  1126. self.valuerecord2.asFea(),
  1127. )
  1128. else:
  1129. res += "pos {} {} {};".format(
  1130. self.glyphs1.asFea(), self.glyphs2.asFea(), self.valuerecord1.asFea()
  1131. )
  1132. return res
  1133. class ReverseChainSingleSubstStatement(Statement):
  1134. """A reverse chaining substitution statement. You don't see those every day.
  1135. Note the unusual argument order: ``suffix`` comes `before` ``glyphs``.
  1136. ``old_prefix``, ``old_suffix``, ``glyphs`` and ``replacements`` should be
  1137. lists of `glyph-containing objects`_. ``glyphs`` and ``replacements`` should
  1138. be one-item lists.
  1139. """
  1140. def __init__(self, old_prefix, old_suffix, glyphs, replacements, location=None):
  1141. Statement.__init__(self, location)
  1142. self.old_prefix, self.old_suffix = old_prefix, old_suffix
  1143. self.glyphs = glyphs
  1144. self.replacements = replacements
  1145. def build(self, builder):
  1146. prefix = [p.glyphSet() for p in self.old_prefix]
  1147. suffix = [s.glyphSet() for s in self.old_suffix]
  1148. originals = self.glyphs[0].glyphSet()
  1149. replaces = self.replacements[0].glyphSet()
  1150. if len(replaces) == 1:
  1151. replaces = replaces * len(originals)
  1152. builder.add_reverse_chain_single_subst(
  1153. self.location, prefix, suffix, dict(zip(originals, replaces))
  1154. )
  1155. def asFea(self, indent=""):
  1156. res = "rsub "
  1157. if len(self.old_prefix) or len(self.old_suffix):
  1158. if len(self.old_prefix):
  1159. res += " ".join(asFea(g) for g in self.old_prefix) + " "
  1160. res += " ".join(asFea(g) + "'" for g in self.glyphs)
  1161. if len(self.old_suffix):
  1162. res += " " + " ".join(asFea(g) for g in self.old_suffix)
  1163. else:
  1164. res += " ".join(map(asFea, self.glyphs))
  1165. res += " by {};".format(" ".join(asFea(g) for g in self.replacements))
  1166. return res
  1167. class SingleSubstStatement(Statement):
  1168. """A single substitution statement.
  1169. Note the unusual argument order: ``prefix`` and suffix come `after`
  1170. the replacement ``glyphs``. ``prefix``, ``suffix``, ``glyphs`` and
  1171. ``replace`` should be lists of `glyph-containing objects`_. ``glyphs`` and
  1172. ``replace`` should be one-item lists.
  1173. """
  1174. def __init__(self, glyphs, replace, prefix, suffix, forceChain, location=None):
  1175. Statement.__init__(self, location)
  1176. self.prefix, self.suffix = prefix, suffix
  1177. self.forceChain = forceChain
  1178. self.glyphs = glyphs
  1179. self.replacements = replace
  1180. def build(self, builder):
  1181. """Calls the builder object's ``add_single_subst`` callback."""
  1182. prefix = [p.glyphSet() for p in self.prefix]
  1183. suffix = [s.glyphSet() for s in self.suffix]
  1184. originals = self.glyphs[0].glyphSet()
  1185. replaces = self.replacements[0].glyphSet()
  1186. if len(replaces) == 1:
  1187. replaces = replaces * len(originals)
  1188. builder.add_single_subst(
  1189. self.location,
  1190. prefix,
  1191. suffix,
  1192. OrderedDict(zip(originals, replaces)),
  1193. self.forceChain,
  1194. )
  1195. def asFea(self, indent=""):
  1196. res = "sub "
  1197. if len(self.prefix) or len(self.suffix) or self.forceChain:
  1198. if len(self.prefix):
  1199. res += " ".join(asFea(g) for g in self.prefix) + " "
  1200. res += " ".join(asFea(g) + "'" for g in self.glyphs)
  1201. if len(self.suffix):
  1202. res += " " + " ".join(asFea(g) for g in self.suffix)
  1203. else:
  1204. res += " ".join(asFea(g) for g in self.glyphs)
  1205. res += " by {};".format(" ".join(asFea(g) for g in self.replacements))
  1206. return res
  1207. class ScriptStatement(Statement):
  1208. """A ``script`` statement."""
  1209. def __init__(self, script, location=None):
  1210. Statement.__init__(self, location)
  1211. self.script = script #: the script code
  1212. def build(self, builder):
  1213. """Calls the builder's ``set_script`` callback."""
  1214. builder.set_script(self.location, self.script)
  1215. def asFea(self, indent=""):
  1216. return "script {};".format(self.script.strip())
  1217. class SinglePosStatement(Statement):
  1218. """A single position statement. ``prefix`` and ``suffix`` should be
  1219. lists of `glyph-containing objects`_.
  1220. ``pos`` should be a one-element list containing a (`glyph-containing object`_,
  1221. :class:`ValueRecord`) tuple."""
  1222. def __init__(self, pos, prefix, suffix, forceChain, location=None):
  1223. Statement.__init__(self, location)
  1224. self.pos, self.prefix, self.suffix = pos, prefix, suffix
  1225. self.forceChain = forceChain
  1226. def build(self, builder):
  1227. """Calls the builder object's ``add_single_pos`` callback."""
  1228. prefix = [p.glyphSet() for p in self.prefix]
  1229. suffix = [s.glyphSet() for s in self.suffix]
  1230. pos = [(g.glyphSet(), value) for g, value in self.pos]
  1231. builder.add_single_pos(self.location, prefix, suffix, pos, self.forceChain)
  1232. def asFea(self, indent=""):
  1233. res = "pos "
  1234. if len(self.prefix) or len(self.suffix) or self.forceChain:
  1235. if len(self.prefix):
  1236. res += " ".join(map(asFea, self.prefix)) + " "
  1237. res += " ".join(
  1238. [
  1239. asFea(x[0])
  1240. + "'"
  1241. + ((" " + x[1].asFea()) if x[1] is not None else "")
  1242. for x in self.pos
  1243. ]
  1244. )
  1245. if len(self.suffix):
  1246. res += " " + " ".join(map(asFea, self.suffix))
  1247. else:
  1248. res += " ".join(
  1249. [
  1250. asFea(x[0]) + " " + (x[1].asFea() if x[1] is not None else "")
  1251. for x in self.pos
  1252. ]
  1253. )
  1254. res += ";"
  1255. return res
  1256. class SubtableStatement(Statement):
  1257. """Represents a subtable break."""
  1258. def __init__(self, location=None):
  1259. Statement.__init__(self, location)
  1260. def build(self, builder):
  1261. """Calls the builder objects's ``add_subtable_break`` callback."""
  1262. builder.add_subtable_break(self.location)
  1263. def asFea(self, indent=""):
  1264. return "subtable;"
  1265. class ValueRecord(Expression):
  1266. """Represents a value record."""
  1267. def __init__(
  1268. self,
  1269. xPlacement=None,
  1270. yPlacement=None,
  1271. xAdvance=None,
  1272. yAdvance=None,
  1273. xPlaDevice=None,
  1274. yPlaDevice=None,
  1275. xAdvDevice=None,
  1276. yAdvDevice=None,
  1277. vertical=False,
  1278. location=None,
  1279. ):
  1280. Expression.__init__(self, location)
  1281. self.xPlacement, self.yPlacement = (xPlacement, yPlacement)
  1282. self.xAdvance, self.yAdvance = (xAdvance, yAdvance)
  1283. self.xPlaDevice, self.yPlaDevice = (xPlaDevice, yPlaDevice)
  1284. self.xAdvDevice, self.yAdvDevice = (xAdvDevice, yAdvDevice)
  1285. self.vertical = vertical
  1286. def __eq__(self, other):
  1287. return (
  1288. self.xPlacement == other.xPlacement
  1289. and self.yPlacement == other.yPlacement
  1290. and self.xAdvance == other.xAdvance
  1291. and self.yAdvance == other.yAdvance
  1292. and self.xPlaDevice == other.xPlaDevice
  1293. and self.xAdvDevice == other.xAdvDevice
  1294. )
  1295. def __ne__(self, other):
  1296. return not self.__eq__(other)
  1297. def __hash__(self):
  1298. return (
  1299. hash(self.xPlacement)
  1300. ^ hash(self.yPlacement)
  1301. ^ hash(self.xAdvance)
  1302. ^ hash(self.yAdvance)
  1303. ^ hash(self.xPlaDevice)
  1304. ^ hash(self.yPlaDevice)
  1305. ^ hash(self.xAdvDevice)
  1306. ^ hash(self.yAdvDevice)
  1307. )
  1308. def asFea(self, indent=""):
  1309. if not self:
  1310. return "<NULL>"
  1311. x, y = self.xPlacement, self.yPlacement
  1312. xAdvance, yAdvance = self.xAdvance, self.yAdvance
  1313. xPlaDevice, yPlaDevice = self.xPlaDevice, self.yPlaDevice
  1314. xAdvDevice, yAdvDevice = self.xAdvDevice, self.yAdvDevice
  1315. vertical = self.vertical
  1316. # Try format A, if possible.
  1317. if x is None and y is None:
  1318. if xAdvance is None and vertical:
  1319. return str(yAdvance)
  1320. elif yAdvance is None and not vertical:
  1321. return str(xAdvance)
  1322. # Make any remaining None value 0 to avoid generating invalid records.
  1323. x = x or 0
  1324. y = y or 0
  1325. xAdvance = xAdvance or 0
  1326. yAdvance = yAdvance or 0
  1327. # Try format B, if possible.
  1328. if (
  1329. xPlaDevice is None
  1330. and yPlaDevice is None
  1331. and xAdvDevice is None
  1332. and yAdvDevice is None
  1333. ):
  1334. return "<%s %s %s %s>" % (x, y, xAdvance, yAdvance)
  1335. # Last resort is format C.
  1336. return "<%s %s %s %s %s %s %s %s>" % (
  1337. x,
  1338. y,
  1339. xAdvance,
  1340. yAdvance,
  1341. deviceToString(xPlaDevice),
  1342. deviceToString(yPlaDevice),
  1343. deviceToString(xAdvDevice),
  1344. deviceToString(yAdvDevice),
  1345. )
  1346. def __bool__(self):
  1347. return any(
  1348. getattr(self, v) is not None
  1349. for v in [
  1350. "xPlacement",
  1351. "yPlacement",
  1352. "xAdvance",
  1353. "yAdvance",
  1354. "xPlaDevice",
  1355. "yPlaDevice",
  1356. "xAdvDevice",
  1357. "yAdvDevice",
  1358. ]
  1359. )
  1360. __nonzero__ = __bool__
  1361. class ValueRecordDefinition(Statement):
  1362. """Represents a named value record definition."""
  1363. def __init__(self, name, value, location=None):
  1364. Statement.__init__(self, location)
  1365. self.name = name #: Value record name as string
  1366. self.value = value #: :class:`ValueRecord` object
  1367. def asFea(self, indent=""):
  1368. return "valueRecordDef {} {};".format(self.value.asFea(), self.name)
  1369. def simplify_name_attributes(pid, eid, lid):
  1370. if pid == 3 and eid == 1 and lid == 1033:
  1371. return ""
  1372. elif pid == 1 and eid == 0 and lid == 0:
  1373. return "1"
  1374. else:
  1375. return "{} {} {}".format(pid, eid, lid)
  1376. class NameRecord(Statement):
  1377. """Represents a name record. (`Section 9.e. <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#9.e>`_)"""
  1378. def __init__(self, nameID, platformID, platEncID, langID, string, location=None):
  1379. Statement.__init__(self, location)
  1380. self.nameID = nameID #: Name ID as integer (e.g. 9 for designer's name)
  1381. self.platformID = platformID #: Platform ID as integer
  1382. self.platEncID = platEncID #: Platform encoding ID as integer
  1383. self.langID = langID #: Language ID as integer
  1384. self.string = string #: Name record value
  1385. def build(self, builder):
  1386. """Calls the builder object's ``add_name_record`` callback."""
  1387. builder.add_name_record(
  1388. self.location,
  1389. self.nameID,
  1390. self.platformID,
  1391. self.platEncID,
  1392. self.langID,
  1393. self.string,
  1394. )
  1395. def asFea(self, indent=""):
  1396. def escape(c, escape_pattern):
  1397. # Also escape U+0022 QUOTATION MARK and U+005C REVERSE SOLIDUS
  1398. if c >= 0x20 and c <= 0x7E and c not in (0x22, 0x5C):
  1399. return chr(c)
  1400. else:
  1401. return escape_pattern % c
  1402. encoding = getEncoding(self.platformID, self.platEncID, self.langID)
  1403. if encoding is None:
  1404. raise FeatureLibError("Unsupported encoding", self.location)
  1405. s = tobytes(self.string, encoding=encoding)
  1406. if encoding == "utf_16_be":
  1407. escaped_string = "".join(
  1408. [
  1409. escape(byteord(s[i]) * 256 + byteord(s[i + 1]), r"\%04x")
  1410. for i in range(0, len(s), 2)
  1411. ]
  1412. )
  1413. else:
  1414. escaped_string = "".join([escape(byteord(b), r"\%02x") for b in s])
  1415. plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
  1416. if plat != "":
  1417. plat += " "
  1418. return 'nameid {} {}"{}";'.format(self.nameID, plat, escaped_string)
  1419. class FeatureNameStatement(NameRecord):
  1420. """Represents a ``sizemenuname`` or ``name`` statement."""
  1421. def build(self, builder):
  1422. """Calls the builder object's ``add_featureName`` callback."""
  1423. NameRecord.build(self, builder)
  1424. builder.add_featureName(self.nameID)
  1425. def asFea(self, indent=""):
  1426. if self.nameID == "size":
  1427. tag = "sizemenuname"
  1428. else:
  1429. tag = "name"
  1430. plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
  1431. if plat != "":
  1432. plat += " "
  1433. return '{} {}"{}";'.format(tag, plat, self.string)
  1434. class STATNameStatement(NameRecord):
  1435. """Represents a STAT table ``name`` statement."""
  1436. def asFea(self, indent=""):
  1437. plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
  1438. if plat != "":
  1439. plat += " "
  1440. return 'name {}"{}";'.format(plat, self.string)
  1441. class SizeParameters(Statement):
  1442. """A ``parameters`` statement."""
  1443. def __init__(self, DesignSize, SubfamilyID, RangeStart, RangeEnd, location=None):
  1444. Statement.__init__(self, location)
  1445. self.DesignSize = DesignSize
  1446. self.SubfamilyID = SubfamilyID
  1447. self.RangeStart = RangeStart
  1448. self.RangeEnd = RangeEnd
  1449. def build(self, builder):
  1450. """Calls the builder object's ``set_size_parameters`` callback."""
  1451. builder.set_size_parameters(
  1452. self.location,
  1453. self.DesignSize,
  1454. self.SubfamilyID,
  1455. self.RangeStart,
  1456. self.RangeEnd,
  1457. )
  1458. def asFea(self, indent=""):
  1459. res = "parameters {:.1f} {}".format(self.DesignSize, self.SubfamilyID)
  1460. if self.RangeStart != 0 or self.RangeEnd != 0:
  1461. res += " {} {}".format(int(self.RangeStart * 10), int(self.RangeEnd * 10))
  1462. return res + ";"
  1463. class CVParametersNameStatement(NameRecord):
  1464. """Represent a name statement inside a ``cvParameters`` block."""
  1465. def __init__(
  1466. self, nameID, platformID, platEncID, langID, string, block_name, location=None
  1467. ):
  1468. NameRecord.__init__(
  1469. self, nameID, platformID, platEncID, langID, string, location=location
  1470. )
  1471. self.block_name = block_name
  1472. def build(self, builder):
  1473. """Calls the builder object's ``add_cv_parameter`` callback."""
  1474. item = ""
  1475. if self.block_name == "ParamUILabelNameID":
  1476. item = "_{}".format(builder.cv_num_named_params_.get(self.nameID, 0))
  1477. builder.add_cv_parameter(self.nameID)
  1478. self.nameID = (self.nameID, self.block_name + item)
  1479. NameRecord.build(self, builder)
  1480. def asFea(self, indent=""):
  1481. plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
  1482. if plat != "":
  1483. plat += " "
  1484. return 'name {}"{}";'.format(plat, self.string)
  1485. class CharacterStatement(Statement):
  1486. """
  1487. Statement used in cvParameters blocks of Character Variant features (cvXX).
  1488. The Unicode value may be written with either decimal or hexadecimal
  1489. notation. The value must be preceded by '0x' if it is a hexadecimal value.
  1490. The largest Unicode value allowed is 0xFFFFFF.
  1491. """
  1492. def __init__(self, character, tag, location=None):
  1493. Statement.__init__(self, location)
  1494. self.character = character
  1495. self.tag = tag
  1496. def build(self, builder):
  1497. """Calls the builder object's ``add_cv_character`` callback."""
  1498. builder.add_cv_character(self.character, self.tag)
  1499. def asFea(self, indent=""):
  1500. return "Character {:#x};".format(self.character)
  1501. class BaseAxis(Statement):
  1502. """An axis definition, being either a ``VertAxis.BaseTagList/BaseScriptList``
  1503. pair or a ``HorizAxis.BaseTagList/BaseScriptList`` pair."""
  1504. def __init__(self, bases, scripts, vertical, minmax=None, location=None):
  1505. Statement.__init__(self, location)
  1506. self.bases = bases #: A list of baseline tag names as strings
  1507. self.scripts = scripts #: A list of script record tuplets (script tag, default baseline tag, base coordinate)
  1508. self.vertical = vertical #: Boolean; VertAxis if True, HorizAxis if False
  1509. self.minmax = [] #: A set of minmax record
  1510. def build(self, builder):
  1511. """Calls the builder object's ``set_base_axis`` callback."""
  1512. builder.set_base_axis(self.bases, self.scripts, self.vertical, self.minmax)
  1513. def asFea(self, indent=""):
  1514. direction = "Vert" if self.vertical else "Horiz"
  1515. scripts = [
  1516. "{} {} {}".format(a[0], a[1], " ".join(map(str, a[2])))
  1517. for a in self.scripts
  1518. ]
  1519. minmaxes = [
  1520. "\n{}Axis.MinMax {} {} {}, {};".format(direction, a[0], a[1], a[2], a[3])
  1521. for a in self.minmax
  1522. ]
  1523. return "{}Axis.BaseTagList {};\n{}{}Axis.BaseScriptList {};".format(
  1524. direction, " ".join(self.bases), indent, direction, ", ".join(scripts)
  1525. ) + "\n".join(minmaxes)
  1526. class OS2Field(Statement):
  1527. """An entry in the ``OS/2`` table. Most ``values`` should be numbers or
  1528. strings, apart from when the key is ``UnicodeRange``, ``CodePageRange``
  1529. or ``Panose``, in which case it should be an array of integers."""
  1530. def __init__(self, key, value, location=None):
  1531. Statement.__init__(self, location)
  1532. self.key = key
  1533. self.value = value
  1534. def build(self, builder):
  1535. """Calls the builder object's ``add_os2_field`` callback."""
  1536. builder.add_os2_field(self.key, self.value)
  1537. def asFea(self, indent=""):
  1538. def intarr2str(x):
  1539. return " ".join(map(str, x))
  1540. numbers = (
  1541. "FSType",
  1542. "TypoAscender",
  1543. "TypoDescender",
  1544. "TypoLineGap",
  1545. "winAscent",
  1546. "winDescent",
  1547. "XHeight",
  1548. "CapHeight",
  1549. "WeightClass",
  1550. "WidthClass",
  1551. "LowerOpSize",
  1552. "UpperOpSize",
  1553. )
  1554. ranges = ("UnicodeRange", "CodePageRange")
  1555. keywords = dict([(x.lower(), [x, str]) for x in numbers])
  1556. keywords.update([(x.lower(), [x, intarr2str]) for x in ranges])
  1557. keywords["panose"] = ["Panose", intarr2str]
  1558. keywords["vendor"] = ["Vendor", lambda y: '"{}"'.format(y)]
  1559. if self.key in keywords:
  1560. return "{} {};".format(
  1561. keywords[self.key][0], keywords[self.key][1](self.value)
  1562. )
  1563. return "" # should raise exception
  1564. class HheaField(Statement):
  1565. """An entry in the ``hhea`` table."""
  1566. def __init__(self, key, value, location=None):
  1567. Statement.__init__(self, location)
  1568. self.key = key
  1569. self.value = value
  1570. def build(self, builder):
  1571. """Calls the builder object's ``add_hhea_field`` callback."""
  1572. builder.add_hhea_field(self.key, self.value)
  1573. def asFea(self, indent=""):
  1574. fields = ("CaretOffset", "Ascender", "Descender", "LineGap")
  1575. keywords = dict([(x.lower(), x) for x in fields])
  1576. return "{} {};".format(keywords[self.key], self.value)
  1577. class VheaField(Statement):
  1578. """An entry in the ``vhea`` table."""
  1579. def __init__(self, key, value, location=None):
  1580. Statement.__init__(self, location)
  1581. self.key = key
  1582. self.value = value
  1583. def build(self, builder):
  1584. """Calls the builder object's ``add_vhea_field`` callback."""
  1585. builder.add_vhea_field(self.key, self.value)
  1586. def asFea(self, indent=""):
  1587. fields = ("VertTypoAscender", "VertTypoDescender", "VertTypoLineGap")
  1588. keywords = dict([(x.lower(), x) for x in fields])
  1589. return "{} {};".format(keywords[self.key], self.value)
  1590. class STATDesignAxisStatement(Statement):
  1591. """A STAT table Design Axis
  1592. Args:
  1593. tag (str): a 4 letter axis tag
  1594. axisOrder (int): an int
  1595. names (list): a list of :class:`STATNameStatement` objects
  1596. """
  1597. def __init__(self, tag, axisOrder, names, location=None):
  1598. Statement.__init__(self, location)
  1599. self.tag = tag
  1600. self.axisOrder = axisOrder
  1601. self.names = names
  1602. self.location = location
  1603. def build(self, builder):
  1604. builder.addDesignAxis(self, self.location)
  1605. def asFea(self, indent=""):
  1606. indent += SHIFT
  1607. res = f"DesignAxis {self.tag} {self.axisOrder} {{ \n"
  1608. res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
  1609. res += "};"
  1610. return res
  1611. class ElidedFallbackName(Statement):
  1612. """STAT table ElidedFallbackName
  1613. Args:
  1614. names: a list of :class:`STATNameStatement` objects
  1615. """
  1616. def __init__(self, names, location=None):
  1617. Statement.__init__(self, location)
  1618. self.names = names
  1619. self.location = location
  1620. def build(self, builder):
  1621. builder.setElidedFallbackName(self.names, self.location)
  1622. def asFea(self, indent=""):
  1623. indent += SHIFT
  1624. res = "ElidedFallbackName { \n"
  1625. res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
  1626. res += "};"
  1627. return res
  1628. class ElidedFallbackNameID(Statement):
  1629. """STAT table ElidedFallbackNameID
  1630. Args:
  1631. value: an int pointing to an existing name table name ID
  1632. """
  1633. def __init__(self, value, location=None):
  1634. Statement.__init__(self, location)
  1635. self.value = value
  1636. self.location = location
  1637. def build(self, builder):
  1638. builder.setElidedFallbackName(self.value, self.location)
  1639. def asFea(self, indent=""):
  1640. return f"ElidedFallbackNameID {self.value};"
  1641. class STATAxisValueStatement(Statement):
  1642. """A STAT table Axis Value Record
  1643. Args:
  1644. names (list): a list of :class:`STATNameStatement` objects
  1645. locations (list): a list of :class:`AxisValueLocationStatement` objects
  1646. flags (int): an int
  1647. """
  1648. def __init__(self, names, locations, flags, location=None):
  1649. Statement.__init__(self, location)
  1650. self.names = names
  1651. self.locations = locations
  1652. self.flags = flags
  1653. def build(self, builder):
  1654. builder.addAxisValueRecord(self, self.location)
  1655. def asFea(self, indent=""):
  1656. res = "AxisValue {\n"
  1657. for location in self.locations:
  1658. res += location.asFea()
  1659. for nameRecord in self.names:
  1660. res += nameRecord.asFea()
  1661. res += "\n"
  1662. if self.flags:
  1663. flags = ["OlderSiblingFontAttribute", "ElidableAxisValueName"]
  1664. flagStrings = []
  1665. curr = 1
  1666. for i in range(len(flags)):
  1667. if self.flags & curr != 0:
  1668. flagStrings.append(flags[i])
  1669. curr = curr << 1
  1670. res += f"flag {' '.join(flagStrings)};\n"
  1671. res += "};"
  1672. return res
  1673. class AxisValueLocationStatement(Statement):
  1674. """
  1675. A STAT table Axis Value Location
  1676. Args:
  1677. tag (str): a 4 letter axis tag
  1678. values (list): a list of ints and/or floats
  1679. """
  1680. def __init__(self, tag, values, location=None):
  1681. Statement.__init__(self, location)
  1682. self.tag = tag
  1683. self.values = values
  1684. def asFea(self, res=""):
  1685. res += f"location {self.tag} "
  1686. res += f"{' '.join(str(i) for i in self.values)};\n"
  1687. return res
  1688. class ConditionsetStatement(Statement):
  1689. """
  1690. A variable layout conditionset
  1691. Args:
  1692. name (str): the name of this conditionset
  1693. conditions (dict): a dictionary mapping axis tags to a
  1694. tuple of (min,max) userspace coordinates.
  1695. """
  1696. def __init__(self, name, conditions, location=None):
  1697. Statement.__init__(self, location)
  1698. self.name = name
  1699. self.conditions = conditions
  1700. def build(self, builder):
  1701. builder.add_conditionset(self.location, self.name, self.conditions)
  1702. def asFea(self, res="", indent=""):
  1703. res += indent + f"conditionset {self.name} " + "{\n"
  1704. for tag, (minvalue, maxvalue) in self.conditions.items():
  1705. res += indent + SHIFT + f"{tag} {minvalue} {maxvalue};\n"
  1706. res += indent + "}" + f" {self.name};\n"
  1707. return res
  1708. class VariationBlock(Block):
  1709. """A variation feature block, applicable in a given set of conditions."""
  1710. def __init__(self, name, conditionset, use_extension=False, location=None):
  1711. Block.__init__(self, location)
  1712. self.name, self.conditionset, self.use_extension = (
  1713. name,
  1714. conditionset,
  1715. use_extension,
  1716. )
  1717. def build(self, builder):
  1718. """Call the ``start_feature`` callback on the builder object, visit
  1719. all the statements in this feature, and then call ``end_feature``."""
  1720. builder.start_feature(self.location, self.name, self.use_extension)
  1721. if (
  1722. self.conditionset != "NULL"
  1723. and self.conditionset not in builder.conditionsets_
  1724. ):
  1725. raise FeatureLibError(
  1726. f"variation block used undefined conditionset {self.conditionset}",
  1727. self.location,
  1728. )
  1729. # language exclude_dflt statements modify builder.features_
  1730. # limit them to this block with temporary builder.features_
  1731. features = builder.features_
  1732. builder.features_ = {}
  1733. Block.build(self, builder)
  1734. for key, value in builder.features_.items():
  1735. items = builder.feature_variations_.setdefault(key, {}).setdefault(
  1736. self.conditionset, []
  1737. )
  1738. items.extend(value)
  1739. if key not in features:
  1740. features[key] = [] # Ensure we make a feature record
  1741. builder.features_ = features
  1742. builder.end_feature()
  1743. def asFea(self, indent=""):
  1744. res = indent + "variation %s " % self.name.strip()
  1745. res += self.conditionset + " "
  1746. if self.use_extension:
  1747. res += "useExtension "
  1748. res += "{\n"
  1749. res += Block.asFea(self, indent=indent)
  1750. res += indent + "} %s;\n" % self.name.strip()
  1751. return res