You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			453 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			453 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
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 SubstitutionAlternateDefinition(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}"
 |