from fontTools.voltLib.error import VoltLibError from typing import NamedTuple class Pos(NamedTuple): adv: int dx: int dy: int adv_adjust_by: dict dx_adjust_by: dict dy_adjust_by: dict def __str__(self): res = ' POS' for attr in ('adv', 'dx', 'dy'): value = getattr(self, attr) if value is not None: res += f' {attr.upper()} {value}' adjust_by = getattr(self, f'{attr}_adjust_by', {}) for size, adjustment in adjust_by.items(): res += f' ADJUST_BY {adjustment} AT {size}' res += ' END_POS' return res class Element(object): def __init__(self, location=None): self.location = location def build(self, builder): pass def __str__(self): raise NotImplementedError class Statement(Element): pass class Expression(Element): pass class VoltFile(Statement): def __init__(self): Statement.__init__(self, location=None) self.statements = [] def build(self, builder): for s in self.statements: s.build(builder) def __str__(self): return '\n' + '\n'.join(str(s) for s in self.statements) + ' END\n' class GlyphDefinition(Statement): def __init__(self, name, gid, gunicode, gtype, components, location=None): Statement.__init__(self, location) self.name = name self.id = gid self.unicode = gunicode self.type = gtype self.components = components def __str__(self): res = f'DEF_GLYPH "{self.name}" ID {self.id}' if self.unicode is not None: if len(self.unicode) > 1: unicodes = ','.join(f'U+{u:04X}' for u in self.unicode) res += f' UNICODEVALUES "{unicodes}"' else: res += f' UNICODE {self.unicode[0]}' if self.type is not None: res += f' TYPE {self.type}' if self.components is not None: res += f' COMPONENTS {self.components}' res += ' END_GLYPH' return res class GroupDefinition(Statement): def __init__(self, name, enum, location=None): Statement.__init__(self, location) self.name = name self.enum = enum self.glyphs_ = None def glyphSet(self, groups=None): if groups is not None and self.name in groups: raise VoltLibError( 'Group "%s" contains itself.' % (self.name), self.location) if self.glyphs_ is None: if groups is None: groups = set({self.name}) else: groups.add(self.name) self.glyphs_ = self.enum.glyphSet(groups) return self.glyphs_ def __str__(self): enum = self.enum and str(self.enum) or '' return f'DEF_GROUP "{self.name}"\n{enum}\nEND_GROUP' class GlyphName(Expression): """A single glyph name, such as cedilla.""" def __init__(self, glyph, location=None): Expression.__init__(self, location) self.glyph = glyph def glyphSet(self): return (self.glyph,) def __str__(self): return f' GLYPH "{self.glyph}"' class Enum(Expression): """An enum""" def __init__(self, enum, location=None): Expression.__init__(self, location) self.enum = enum def __iter__(self): for e in self.glyphSet(): yield e def glyphSet(self, groups=None): glyphs = [] for element in self.enum: if isinstance(element, (GroupName, Enum)): glyphs.extend(element.glyphSet(groups)) else: glyphs.extend(element.glyphSet()) return tuple(glyphs) def __str__(self): enum = ''.join(str(e) for e in self.enum) return f' ENUM{enum} END_ENUM' class GroupName(Expression): """A glyph group""" def __init__(self, group, parser, location=None): Expression.__init__(self, location) self.group = group self.parser_ = parser def glyphSet(self, groups=None): group = self.parser_.resolve_group(self.group) if group is not None: self.glyphs_ = group.glyphSet(groups) return self.glyphs_ else: raise VoltLibError( 'Group "%s" is used but undefined.' % (self.group), self.location) def __str__(self): return f' GROUP "{self.group}"' class Range(Expression): """A glyph range""" def __init__(self, start, end, parser, location=None): Expression.__init__(self, location) self.start = start self.end = end self.parser = parser def glyphSet(self): return tuple(self.parser.glyph_range(self.start, self.end)) def __str__(self): return f' RANGE "{self.start}" TO "{self.end}"' class ScriptDefinition(Statement): def __init__(self, name, tag, langs, location=None): Statement.__init__(self, location) self.name = name self.tag = tag self.langs = langs def __str__(self): res = 'DEF_SCRIPT' if self.name is not None: res += f' NAME "{self.name}"' res += f' TAG "{self.tag}"\n\n' for lang in self.langs: res += f'{lang}' res += 'END_SCRIPT' return res class LangSysDefinition(Statement): def __init__(self, name, tag, features, location=None): Statement.__init__(self, location) self.name = name self.tag = tag self.features = features def __str__(self): res = 'DEF_LANGSYS' if self.name is not None: res += f' NAME "{self.name}"' res += f' TAG "{self.tag}"\n\n' for feature in self.features: res += f'{feature}' res += 'END_LANGSYS\n' return res class FeatureDefinition(Statement): def __init__(self, name, tag, lookups, location=None): Statement.__init__(self, location) self.name = name self.tag = tag self.lookups = lookups def __str__(self): res = f'DEF_FEATURE NAME "{self.name}" TAG "{self.tag}"\n' res += ' ' + ' '.join(f'LOOKUP "{l}"' for l in self.lookups) + '\n' res += 'END_FEATURE\n' return res class LookupDefinition(Statement): def __init__(self, name, process_base, process_marks, mark_glyph_set, direction, reversal, comments, context, sub, pos, location=None): Statement.__init__(self, location) self.name = name self.process_base = process_base self.process_marks = process_marks self.mark_glyph_set = mark_glyph_set self.direction = direction self.reversal = reversal self.comments = comments self.context = context self.sub = sub self.pos = pos def __str__(self): res = f'DEF_LOOKUP "{self.name}"' res += f' {self.process_base and "PROCESS_BASE" or "SKIP_BASE"}' if self.process_marks: res += ' PROCESS_MARKS ' if self.mark_glyph_set: res += f'MARK_GLYPH_SET "{self.mark_glyph_set}"' elif isinstance(self.process_marks, str): res += f'"{self.process_marks}"' else: res += 'ALL' else: res += ' SKIP_MARKS' if self.direction is not None: res += f' DIRECTION {self.direction}' if self.reversal: res += ' REVERSAL' if self.comments is not None: comments = self.comments.replace('\n', r'\n') res += f'\nCOMMENTS "{comments}"' if self.context: res += '\n' + '\n'.join(str(c) for c in self.context) else: res += '\nIN_CONTEXT\nEND_CONTEXT' if self.sub: res += f'\n{self.sub}' if self.pos: res += f'\n{self.pos}' return res class SubstitutionDefinition(Statement): def __init__(self, mapping, location=None): Statement.__init__(self, location) self.mapping = mapping def __str__(self): res = 'AS_SUBSTITUTION\n' for src, dst in self.mapping.items(): src = ''.join(str(s) for s in src) dst = ''.join(str(d) for d in dst) res += f'SUB{src}\nWITH{dst}\nEND_SUB\n' res += 'END_SUBSTITUTION' return res class SubstitutionSingleDefinition(SubstitutionDefinition): pass class SubstitutionMultipleDefinition(SubstitutionDefinition): pass class SubstitutionLigatureDefinition(SubstitutionDefinition): pass class SubstitutionReverseChainingSingleDefinition(SubstitutionDefinition): pass class PositionAttachDefinition(Statement): def __init__(self, coverage, coverage_to, location=None): Statement.__init__(self, location) self.coverage = coverage self.coverage_to = coverage_to def __str__(self): coverage = ''.join(str(c) for c in self.coverage) res = f'AS_POSITION\nATTACH{coverage}\nTO' for coverage, anchor in self.coverage_to: coverage = ''.join(str(c) for c in coverage) res += f'{coverage} AT ANCHOR "{anchor}"' res += '\nEND_ATTACH\nEND_POSITION' return res class PositionAttachCursiveDefinition(Statement): def __init__(self, coverages_exit, coverages_enter, location=None): Statement.__init__(self, location) self.coverages_exit = coverages_exit self.coverages_enter = coverages_enter def __str__(self): res = 'AS_POSITION\nATTACH_CURSIVE' for coverage in self.coverages_exit: coverage = ''.join(str(c) for c in coverage) res += f'\nEXIT {coverage}' for coverage in self.coverages_enter: coverage = ''.join(str(c) for c in coverage) res += f'\nENTER {coverage}' res += '\nEND_ATTACH\nEND_POSITION' return res class PositionAdjustPairDefinition(Statement): def __init__(self, coverages_1, coverages_2, adjust_pair, location=None): Statement.__init__(self, location) self.coverages_1 = coverages_1 self.coverages_2 = coverages_2 self.adjust_pair = adjust_pair def __str__(self): res = 'AS_POSITION\nADJUST_PAIR\n' for coverage in self.coverages_1: coverage = ' '.join(str(c) for c in coverage) res += f' FIRST {coverage}' res += '\n' for coverage in self.coverages_2: coverage = ' '.join(str(c) for c in coverage) res += f' SECOND {coverage}' res += '\n' for (id_1, id_2), (pos_1, pos_2) in self.adjust_pair.items(): res += f' {id_1} {id_2} BY{pos_1}{pos_2}\n' res += '\nEND_ADJUST\nEND_POSITION' return res class PositionAdjustSingleDefinition(Statement): def __init__(self, adjust_single, location=None): Statement.__init__(self, location) self.adjust_single = adjust_single def __str__(self): res = 'AS_POSITION\nADJUST_SINGLE' for coverage, pos in self.adjust_single: coverage = ''.join(str(c) for c in coverage) res += f'{coverage} BY{pos}' res += '\nEND_ADJUST\nEND_POSITION' return res class ContextDefinition(Statement): def __init__(self, ex_or_in, left=None, right=None, location=None): Statement.__init__(self, location) self.ex_or_in = ex_or_in self.left = left if left is not None else [] self.right = right if right is not None else [] def __str__(self): res = self.ex_or_in + '\n' for coverage in self.left: coverage = ''.join(str(c) for c in coverage) res += f' LEFT{coverage}\n' for coverage in self.right: coverage = ''.join(str(c) for c in coverage) res += f' RIGHT{coverage}\n' res += 'END_CONTEXT' return res class AnchorDefinition(Statement): def __init__(self, name, gid, glyph_name, component, locked, pos, location=None): Statement.__init__(self, location) self.name = name self.gid = gid self.glyph_name = glyph_name self.component = component self.locked = locked self.pos = pos def __str__(self): locked = self.locked and ' LOCKED' or '' return (f'DEF_ANCHOR "{self.name}"' f' ON {self.gid}' f' GLYPH {self.glyph_name}' f' COMPONENT {self.component}' f'{locked}' f' AT {self.pos} END_ANCHOR') class SettingDefinition(Statement): def __init__(self, name, value, location=None): Statement.__init__(self, location) self.name = name self.value = value def __str__(self): if self.value is True: return f'{self.name}' if isinstance(self.value, (tuple, list)): value = " ".join(str(v) for v in self.value) return f'{self.name} {value}' return f'{self.name} {self.value}'