ast.py 72 KB

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