1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
"""Functions for parsing and validating RoboFab RFont feature files."""
import re
# feature file syntax rules from:
# http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html
_glyphNameChars = r"[A-Za-z_][\w.]"
_glyphName = r"%s{,30}" % _glyphNameChars
_className = r"@%s{,29}" % _glyphNameChars
_classValToken = (
r"(?:%s|%s(?:\s*-\s*%s)?)" % (_className, _glyphName, _glyphName))
_classVal = r"\[\s*(%s(?:\s+%s)*)\s*\]" % (_classValToken, _classValToken)
_classDef = re.compile(r"(%s)\s*=\s*%s\s*;" % (_className, _classVal))
_featureDef = re.compile(
r"(feature\s+(?P<tag>[A-Za-z]{4})\s+\{.*?\}\s+(?P=tag)\s*;)",
re.DOTALL)
_subRuleToken = r"(?:%s|%s)'?" % (_glyphName, _className)
_subRuleTokenList = (
r"\[?\s*(%s(?:\s+%s)*)\s*\]?" % (_subRuleToken, _subRuleToken))
_subRule = re.compile(
r"(\s*)sub(?:stitute)?\s+%s\s+by\s+%s\s*;" %
(_subRuleTokenList, _subRuleTokenList))
_systemDef = re.compile(r"languagesystem\s+([A-Za-z]+)\s+([A-Za-z]+)\s*;")
_comment = re.compile(r"\s*#.*")
def readFeatureFile(font, text):
"""Incorporate valid definitions from feature text into font."""
readGlyphClasses(font, text)
text = "\n".join([l for l in text.splitlines() if not _comment.match(l)])
# filter out substitution rules with invalid references
errorMsg = "feature definition %s (substitution rule removed)"
if not hasattr(font.features, "tags"):
font.features.tags = []
font.features.values = {}
for value, tag in _featureDef.findall(text):
lines = value.splitlines()
for i in range(len(lines)):
match = _subRule.match(lines[i])
if not match:
continue
indentation, subbed, sub = match.groups()
refs = subbed.split() + sub.split()
invalid = None
for ref in refs:
if ref[-1] == "'":
ref = ref[:-1]
if not invalid and not _isValidRef(errorMsg % tag, ref, font):
invalid = ref
if invalid:
lines[i] = ("%s; # substitution rule removed for invalid "
"reference %s" % (indentation, invalid))
font.features.tags.append(tag)
font.features.values[tag] = "\n".join(lines)
def readGlyphClasses(font, text, update=True):
"""Incorporate valid glyph classes from feature text into font."""
text = "\n".join([l for l in text.splitlines() if not _comment.match(l)])
# filter out invalid references from glyph class definitions
errorMsg = "glyph class definition %s (reference removed)"
if not hasattr(font, "classNames"):
font.classNames = []
font.classVals = {}
for name, value in _classDef.findall(text):
if name in font.classNames:
if not update:
continue
font.classNames.remove(name)
refs = value.split()
refs = [r for r in refs if _isValidRef(errorMsg % name, r, font)]
font.classNames.append(name)
font.classVals[name] = " ".join(refs)
if not hasattr(font, "languageSystems"):
font.languageSystems = []
for system in _systemDef.findall(text):
if system not in font.languageSystems:
font.languageSystems.append(system)
def _isValidRef(referencer, ref, font):
"""Check if a reference is valid for a font."""
if ref.startswith("@"):
if not font.classVals.has_key(ref):
print "Undefined class %s referenced in %s." % (ref, referencer)
return False
else:
for r in ref.split("-"):
if r and not font.has_key(r):
print "Undefined glyph %s referenced in %s." % (r, referencer)
return False
return True
def generateFeatureFile(font):
"""Populate a font's feature file text from its classes and features."""
classes = "\n".join(
["%s = [%s];" % (n, font.classVals[n]) for n in font.classNames])
systems = "\n".join(
["languagesystem %s %s;" % (s[0], s[1]) for s in font.languageSystems])
fea = "\n\n".join([font.features.values[t] for t in font.features.tags])
font.features.text = "\n\n".join([classes, systems, fea])
def writeFeatureFile(font, path):
"""Write the font's features to an external file."""
generateFeatureFile(font)
fout = open(path, "w")
fout.write(font.features.text)
fout.close()
|