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