ast.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. from fontTools.voltLib.error import VoltLibError
  2. from typing import NamedTuple
  3. class Pos(NamedTuple):
  4. adv: int
  5. dx: int
  6. dy: int
  7. adv_adjust_by: dict
  8. dx_adjust_by: dict
  9. dy_adjust_by: dict
  10. def __str__(self):
  11. res = " POS"
  12. for attr in ("adv", "dx", "dy"):
  13. value = getattr(self, attr)
  14. if value is not None:
  15. res += f" {attr.upper()} {value}"
  16. adjust_by = getattr(self, f"{attr}_adjust_by", {})
  17. for size, adjustment in adjust_by.items():
  18. res += f" ADJUST_BY {adjustment} AT {size}"
  19. res += " END_POS"
  20. return res
  21. class Element(object):
  22. def __init__(self, location=None):
  23. self.location = location
  24. def build(self, builder):
  25. pass
  26. def __str__(self):
  27. raise NotImplementedError
  28. class Statement(Element):
  29. pass
  30. class Expression(Element):
  31. pass
  32. class VoltFile(Statement):
  33. def __init__(self):
  34. Statement.__init__(self, location=None)
  35. self.statements = []
  36. def build(self, builder):
  37. for s in self.statements:
  38. s.build(builder)
  39. def __str__(self):
  40. return "\n" + "\n".join(str(s) for s in self.statements) + " END\n"
  41. class GlyphDefinition(Statement):
  42. def __init__(self, name, gid, gunicode, gtype, components, location=None):
  43. Statement.__init__(self, location)
  44. self.name = name
  45. self.id = gid
  46. self.unicode = gunicode
  47. self.type = gtype
  48. self.components = components
  49. def __str__(self):
  50. res = f'DEF_GLYPH "{self.name}" ID {self.id}'
  51. if self.unicode is not None:
  52. if len(self.unicode) > 1:
  53. unicodes = ",".join(f"U+{u:04X}" for u in self.unicode)
  54. res += f' UNICODEVALUES "{unicodes}"'
  55. else:
  56. res += f" UNICODE {self.unicode[0]}"
  57. if self.type is not None:
  58. res += f" TYPE {self.type}"
  59. if self.components is not None:
  60. res += f" COMPONENTS {self.components}"
  61. res += " END_GLYPH"
  62. return res
  63. class GroupDefinition(Statement):
  64. def __init__(self, name, enum, location=None):
  65. Statement.__init__(self, location)
  66. self.name = name
  67. self.enum = enum
  68. self.glyphs_ = None
  69. def glyphSet(self, groups=None):
  70. if groups is not None and self.name in groups:
  71. raise VoltLibError(
  72. 'Group "%s" contains itself.' % (self.name), self.location
  73. )
  74. if self.glyphs_ is None:
  75. if groups is None:
  76. groups = set({self.name})
  77. else:
  78. groups.add(self.name)
  79. self.glyphs_ = self.enum.glyphSet(groups)
  80. return self.glyphs_
  81. def __str__(self):
  82. enum = self.enum and str(self.enum) or ""
  83. return f'DEF_GROUP "{self.name}"\n{enum}\nEND_GROUP'
  84. class GlyphName(Expression):
  85. """A single glyph name, such as cedilla."""
  86. def __init__(self, glyph, location=None):
  87. Expression.__init__(self, location)
  88. self.glyph = glyph
  89. def glyphSet(self):
  90. return (self.glyph,)
  91. def __str__(self):
  92. return f' GLYPH "{self.glyph}"'
  93. class Enum(Expression):
  94. """An enum"""
  95. def __init__(self, enum, location=None):
  96. Expression.__init__(self, location)
  97. self.enum = enum
  98. def __iter__(self):
  99. for e in self.glyphSet():
  100. yield e
  101. def glyphSet(self, groups=None):
  102. glyphs = []
  103. for element in self.enum:
  104. if isinstance(element, (GroupName, Enum)):
  105. glyphs.extend(element.glyphSet(groups))
  106. else:
  107. glyphs.extend(element.glyphSet())
  108. return tuple(glyphs)
  109. def __str__(self):
  110. enum = "".join(str(e) for e in self.enum)
  111. return f" ENUM{enum} END_ENUM"
  112. class GroupName(Expression):
  113. """A glyph group"""
  114. def __init__(self, group, parser, location=None):
  115. Expression.__init__(self, location)
  116. self.group = group
  117. self.parser_ = parser
  118. def glyphSet(self, groups=None):
  119. group = self.parser_.resolve_group(self.group)
  120. if group is not None:
  121. self.glyphs_ = group.glyphSet(groups)
  122. return self.glyphs_
  123. else:
  124. raise VoltLibError(
  125. 'Group "%s" is used but undefined.' % (self.group), self.location
  126. )
  127. def __str__(self):
  128. return f' GROUP "{self.group}"'
  129. class Range(Expression):
  130. """A glyph range"""
  131. def __init__(self, start, end, parser, location=None):
  132. Expression.__init__(self, location)
  133. self.start = start
  134. self.end = end
  135. self.parser = parser
  136. def glyphSet(self):
  137. return tuple(self.parser.glyph_range(self.start, self.end))
  138. def __str__(self):
  139. return f' RANGE "{self.start}" TO "{self.end}"'
  140. class ScriptDefinition(Statement):
  141. def __init__(self, name, tag, langs, location=None):
  142. Statement.__init__(self, location)
  143. self.name = name
  144. self.tag = tag
  145. self.langs = langs
  146. def __str__(self):
  147. res = "DEF_SCRIPT"
  148. if self.name is not None:
  149. res += f' NAME "{self.name}"'
  150. res += f' TAG "{self.tag}"\n\n'
  151. for lang in self.langs:
  152. res += f"{lang}"
  153. res += "END_SCRIPT"
  154. return res
  155. class LangSysDefinition(Statement):
  156. def __init__(self, name, tag, features, location=None):
  157. Statement.__init__(self, location)
  158. self.name = name
  159. self.tag = tag
  160. self.features = features
  161. def __str__(self):
  162. res = "DEF_LANGSYS"
  163. if self.name is not None:
  164. res += f' NAME "{self.name}"'
  165. res += f' TAG "{self.tag}"\n\n'
  166. for feature in self.features:
  167. res += f"{feature}"
  168. res += "END_LANGSYS\n"
  169. return res
  170. class FeatureDefinition(Statement):
  171. def __init__(self, name, tag, lookups, location=None):
  172. Statement.__init__(self, location)
  173. self.name = name
  174. self.tag = tag
  175. self.lookups = lookups
  176. def __str__(self):
  177. res = f'DEF_FEATURE NAME "{self.name}" TAG "{self.tag}"\n'
  178. res += " " + " ".join(f'LOOKUP "{l}"' for l in self.lookups) + "\n"
  179. res += "END_FEATURE\n"
  180. return res
  181. class LookupDefinition(Statement):
  182. def __init__(
  183. self,
  184. name,
  185. process_base,
  186. process_marks,
  187. mark_glyph_set,
  188. direction,
  189. reversal,
  190. comments,
  191. context,
  192. sub,
  193. pos,
  194. location=None,
  195. ):
  196. Statement.__init__(self, location)
  197. self.name = name
  198. self.process_base = process_base
  199. self.process_marks = process_marks
  200. self.mark_glyph_set = mark_glyph_set
  201. self.direction = direction
  202. self.reversal = reversal
  203. self.comments = comments
  204. self.context = context
  205. self.sub = sub
  206. self.pos = pos
  207. def __str__(self):
  208. res = f'DEF_LOOKUP "{self.name}"'
  209. res += f' {self.process_base and "PROCESS_BASE" or "SKIP_BASE"}'
  210. if self.process_marks:
  211. res += " PROCESS_MARKS "
  212. if self.mark_glyph_set:
  213. res += f'MARK_GLYPH_SET "{self.mark_glyph_set}"'
  214. elif isinstance(self.process_marks, str):
  215. res += f'"{self.process_marks}"'
  216. else:
  217. res += "ALL"
  218. else:
  219. res += " SKIP_MARKS"
  220. if self.direction is not None:
  221. res += f" DIRECTION {self.direction}"
  222. if self.reversal:
  223. res += " REVERSAL"
  224. if self.comments is not None:
  225. comments = self.comments.replace("\n", r"\n")
  226. res += f'\nCOMMENTS "{comments}"'
  227. if self.context:
  228. res += "\n" + "\n".join(str(c) for c in self.context)
  229. else:
  230. res += "\nIN_CONTEXT\nEND_CONTEXT"
  231. if self.sub:
  232. res += f"\n{self.sub}"
  233. if self.pos:
  234. res += f"\n{self.pos}"
  235. return res
  236. class SubstitutionDefinition(Statement):
  237. def __init__(self, mapping, location=None):
  238. Statement.__init__(self, location)
  239. self.mapping = mapping
  240. def __str__(self):
  241. res = "AS_SUBSTITUTION\n"
  242. for src, dst in self.mapping.items():
  243. src = "".join(str(s) for s in src)
  244. dst = "".join(str(d) for d in dst)
  245. res += f"SUB{src}\nWITH{dst}\nEND_SUB\n"
  246. res += "END_SUBSTITUTION"
  247. return res
  248. class SubstitutionSingleDefinition(SubstitutionDefinition):
  249. pass
  250. class SubstitutionMultipleDefinition(SubstitutionDefinition):
  251. pass
  252. class SubstitutionLigatureDefinition(SubstitutionDefinition):
  253. pass
  254. class SubstitutionReverseChainingSingleDefinition(SubstitutionDefinition):
  255. pass
  256. class PositionAttachDefinition(Statement):
  257. def __init__(self, coverage, coverage_to, location=None):
  258. Statement.__init__(self, location)
  259. self.coverage = coverage
  260. self.coverage_to = coverage_to
  261. def __str__(self):
  262. coverage = "".join(str(c) for c in self.coverage)
  263. res = f"AS_POSITION\nATTACH{coverage}\nTO"
  264. for coverage, anchor in self.coverage_to:
  265. coverage = "".join(str(c) for c in coverage)
  266. res += f'{coverage} AT ANCHOR "{anchor}"'
  267. res += "\nEND_ATTACH\nEND_POSITION"
  268. return res
  269. class PositionAttachCursiveDefinition(Statement):
  270. def __init__(self, coverages_exit, coverages_enter, location=None):
  271. Statement.__init__(self, location)
  272. self.coverages_exit = coverages_exit
  273. self.coverages_enter = coverages_enter
  274. def __str__(self):
  275. res = "AS_POSITION\nATTACH_CURSIVE"
  276. for coverage in self.coverages_exit:
  277. coverage = "".join(str(c) for c in coverage)
  278. res += f"\nEXIT {coverage}"
  279. for coverage in self.coverages_enter:
  280. coverage = "".join(str(c) for c in coverage)
  281. res += f"\nENTER {coverage}"
  282. res += "\nEND_ATTACH\nEND_POSITION"
  283. return res
  284. class PositionAdjustPairDefinition(Statement):
  285. def __init__(self, coverages_1, coverages_2, adjust_pair, location=None):
  286. Statement.__init__(self, location)
  287. self.coverages_1 = coverages_1
  288. self.coverages_2 = coverages_2
  289. self.adjust_pair = adjust_pair
  290. def __str__(self):
  291. res = "AS_POSITION\nADJUST_PAIR\n"
  292. for coverage in self.coverages_1:
  293. coverage = " ".join(str(c) for c in coverage)
  294. res += f" FIRST {coverage}"
  295. res += "\n"
  296. for coverage in self.coverages_2:
  297. coverage = " ".join(str(c) for c in coverage)
  298. res += f" SECOND {coverage}"
  299. res += "\n"
  300. for (id_1, id_2), (pos_1, pos_2) in self.adjust_pair.items():
  301. res += f" {id_1} {id_2} BY{pos_1}{pos_2}\n"
  302. res += "\nEND_ADJUST\nEND_POSITION"
  303. return res
  304. class PositionAdjustSingleDefinition(Statement):
  305. def __init__(self, adjust_single, location=None):
  306. Statement.__init__(self, location)
  307. self.adjust_single = adjust_single
  308. def __str__(self):
  309. res = "AS_POSITION\nADJUST_SINGLE"
  310. for coverage, pos in self.adjust_single:
  311. coverage = "".join(str(c) for c in coverage)
  312. res += f"{coverage} BY{pos}"
  313. res += "\nEND_ADJUST\nEND_POSITION"
  314. return res
  315. class ContextDefinition(Statement):
  316. def __init__(self, ex_or_in, left=None, right=None, location=None):
  317. Statement.__init__(self, location)
  318. self.ex_or_in = ex_or_in
  319. self.left = left if left is not None else []
  320. self.right = right if right is not None else []
  321. def __str__(self):
  322. res = self.ex_or_in + "\n"
  323. for coverage in self.left:
  324. coverage = "".join(str(c) for c in coverage)
  325. res += f" LEFT{coverage}\n"
  326. for coverage in self.right:
  327. coverage = "".join(str(c) for c in coverage)
  328. res += f" RIGHT{coverage}\n"
  329. res += "END_CONTEXT"
  330. return res
  331. class AnchorDefinition(Statement):
  332. def __init__(self, name, gid, glyph_name, component, locked, pos, location=None):
  333. Statement.__init__(self, location)
  334. self.name = name
  335. self.gid = gid
  336. self.glyph_name = glyph_name
  337. self.component = component
  338. self.locked = locked
  339. self.pos = pos
  340. def __str__(self):
  341. locked = self.locked and " LOCKED" or ""
  342. return (
  343. f'DEF_ANCHOR "{self.name}"'
  344. f" ON {self.gid}"
  345. f" GLYPH {self.glyph_name}"
  346. f" COMPONENT {self.component}"
  347. f"{locked}"
  348. f" AT {self.pos} END_ANCHOR"
  349. )
  350. class SettingDefinition(Statement):
  351. def __init__(self, name, value, location=None):
  352. Statement.__init__(self, location)
  353. self.name = name
  354. self.value = value
  355. def __str__(self):
  356. if self.value is True:
  357. return f"{self.name}"
  358. if isinstance(self.value, (tuple, list)):
  359. value = " ".join(str(v) for v in self.value)
  360. return f"{self.name} {value}"
  361. return f"{self.name} {self.value}"