Skip to content

Commit

Permalink
Merge pull request #1983 from khaledhosny/voltlib-str
Browse files Browse the repository at this point in the history
[voltLib] Support writing back ast as VOLT data
  • Loading branch information
khaledhosny committed Jun 4, 2020
2 parents 26ac716 + 2956772 commit 66a7751
Show file tree
Hide file tree
Showing 3 changed files with 377 additions and 154 deletions.
233 changes: 207 additions & 26 deletions Lib/fontTools/voltLib/ast.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,58 @@
from fontTools.voltLib.error import VoltLibError


class Statement(object):
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 Expression(object):
def __init__(self, location=None):
self.location = location

def build(self, builder):
pass
class Statement(Element):
pass


class Block(Statement):
def __init__(self, location=None):
Statement.__init__(self, location)
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)


class VoltFile(Block):
def __init__(self):
Block.__init__(self, location=None)


class LookupBlock(Block):
def __init__(self, name, location=None):
Block.__init__(self, location)
self.name = name

def build(self, builder):
builder.start_lookup_block(self.location, self.name)
Block.build(self, builder)
builder.end_lookup_block()
def __str__(self):
return '\n' + '\n'.join(str(s) for s in self.statements) + ' END\n'


class GlyphDefinition(Statement):
Expand All @@ -52,6 +64,21 @@ def __init__(self, name, gid, gunicode, gtype, components, location=None):
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):
Expand All @@ -73,6 +100,10 @@ def glyphSet(self, groups=None):
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."""
Expand All @@ -83,6 +114,9 @@ def __init__(self, glyph, location=None):
def glyphSet(self):
return (self.glyph,)

def __str__(self):
return f' GLYPH "{self.glyph}"'


class Enum(Expression):
"""An enum"""
Expand All @@ -103,6 +137,10 @@ def glyphSet(self, groups=None):
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"""
Expand All @@ -121,6 +159,9 @@ def glyphSet(self, groups=None):
'Group "%s" is used but undefined.' % (self.group),
self.location)

def __str__(self):
return f' GROUP "{self.group}"'


class Range(Expression):
"""A glyph range"""
Expand All @@ -133,6 +174,9 @@ def __init__(self, start, end, parser, location=None):
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):
Expand All @@ -141,6 +185,16 @@ def __init__(self, name, tag, langs, location=None):
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):
Expand All @@ -149,6 +203,16 @@ def __init__(self, name, tag, features, location=None):
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):
Expand All @@ -157,6 +221,12 @@ def __init__(self, name, tag, lookups, location=None):
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,
Expand All @@ -174,12 +244,51 @@ def __init__(self, name, process_base, process_marks, mark_glyph_set,
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 is not None:
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
Expand All @@ -203,13 +312,33 @@ def __init__(self, coverage, coverage_to, location=None):
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):
Expand All @@ -218,12 +347,36 @@ def __init__(self, coverages_1, coverages_2, adjust_pair, location=None):
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):
Expand All @@ -232,6 +385,17 @@ def __init__(self, ex_or_in, left=None, right=None, location=None):
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,
Expand All @@ -244,9 +408,26 @@ def __init__(self, name, gid, glyph_name, component, locked,
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}'

0 comments on commit 66a7751

Please sign in to comment.