xref: /aosp_15_r20/external/fonttools/Lib/fontTools/voltLib/ast.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1from fontTools.voltLib.error import VoltLibError
2from typing import NamedTuple
3
4
5class 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
26class 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
37class Statement(Element):
38    pass
39
40
41class Expression(Element):
42    pass
43
44
45class 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
58class 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
83class 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
108class 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
122class 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
147class 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
169class 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
185class 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
203class 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
221class 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
235class 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
293class 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
308class SubstitutionSingleDefinition(SubstitutionDefinition):
309    pass
310
311
312class SubstitutionMultipleDefinition(SubstitutionDefinition):
313    pass
314
315
316class SubstitutionLigatureDefinition(SubstitutionDefinition):
317    pass
318
319
320class SubstitutionReverseChainingSingleDefinition(SubstitutionDefinition):
321    pass
322
323
324class 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
340class 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
358class 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
381class 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
395class 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
414class 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
436class 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