diff options
author | James Godfrey-Kittle <jamesgk@google.com> | 2015-02-10 16:15:36 -0800 |
---|---|---|
committer | James Godfrey-Kittle <jamesgk@google.com> | 2015-04-16 12:16:29 -0700 |
commit | 60d5644c64b0be1af7ecefcf0e644d4e5691a694 (patch) | |
tree | a5d3b5baab11f07a1287ab95d9cbd1685508bba3 /scripts/lib | |
parent | 44f5079ba84bf30aa4072f877687b188e06ee8ce (diff) |
More robust feature support.
Diffstat (limited to 'scripts/lib')
-rw-r--r-- | scripts/lib/fontbuild/Build.py | 34 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/features.py | 179 | ||||
-rw-r--r-- | scripts/lib/fontbuild/kerning.py | 25 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/markFeature.py | 19 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/mkmkFeature.py | 18 |
5 files changed, 128 insertions, 147 deletions
diff --git a/scripts/lib/fontbuild/Build.py b/scripts/lib/fontbuild/Build.py index 630d84e..a785c10 100644 --- a/scripts/lib/fontbuild/Build.py +++ b/scripts/lib/fontbuild/Build.py @@ -6,8 +6,7 @@ from fontbuild.convertCurves import glyphCurvesToQuadratic from fontbuild.mitreGlyph import mitreGlyph from fontbuild.generateGlyph import generateGlyph from fontTools.misc.transform import Transform -from fontbuild.kerning import generateFLKernClassesFromOTString -from fontbuild.features import CreateFeaFile, validateFeatureFile +from fontbuild.features import * from fontbuild.markFeature import GenerateFeature_mark from fontbuild.mkmkFeature import GenerateFeature_mkmk from fontbuild.decomposeGlyph import decomposeGlyph @@ -131,9 +130,8 @@ class FontProject: log(">> Generating glyphs") generateGlyphs(f, self.diacriticList) log(">> Copying features") - f.ot_classes = self.ot_classes - copyFeatures(self.basefont, f) - validateFeatureFile(f) + readGlyphClasses(f, self.ot_classes) + readFeatureFile(f, self.basefont.features.text) log(">> Decomposing") for gname in self.decompose: if f.has_key(gname): @@ -144,24 +142,16 @@ class FontProject: cleanCurves(f) deleteGlyphs(f, self.deleteList) - #TODO(jamesgk) create a system for generating RFont features and classes - #if kern: - # log(">> Generating kern classes") - # generateFLKernClassesFromOTString(f, self.ot_kerningclasses) - # kern = f.MakeKernFeature() - # kern_exist = False - # for fea_id in range (len(f.features)): - # if "kern" == f.features[fea_id].tag: - # f.features[fea_id] = kern - # kern_exist = True - # if False == kern_exist: - # f.features.append(kern) + if kern: + log(">> Generating kern classes") + readGlyphClasses(f, self.ot_kerningclasses, update=False) directoryName = n[0].replace(" ", "") fontName = "%s-%s" % (f.info.familyName.replace(" ", ""), f.info.styleName.replace(" ", "")) log(">> Generating font files") + generateFeatureFile(f) directoryPath = "%s/%s/%sUFO"%(self.basedir,self.builddir,directoryName) if not os.path.exists(directoryPath): os.makedirs(directoryPath) @@ -184,8 +174,8 @@ class FontProject: log(">> Generating FEA files") GenerateFeature_mark(f) GenerateFeature_mkmk(f) - feaName = "%s/%s.fea"%(directoryPath,f.font_name) - CreateFeaFile(f, feaName) + feaName = "%s/%s.fea"%(directoryPath,fontName) + writeFeatureFile(f, feaName) def transformGlyphMembers(g, m): @@ -229,12 +219,6 @@ def swapGlyphs(f,gName1,gName2): def log(msg): print msg -# def addOTFeatures(f): -# f.ot_classes = ot_classes - -def copyFeatures(f1, f2): - f2.features.text = f2.features.text - def generateGlyphs(f, glyphNames): log(">> Generating diacritics") diff --git a/scripts/lib/fontbuild/features.py b/scripts/lib/fontbuild/features.py index 6be3bad..3c4c1d0 100755 --- a/scripts/lib/fontbuild/features.py +++ b/scripts/lib/fontbuild/features.py @@ -1,75 +1,110 @@ +"""Functions for parsing and validating RoboFab RFont feature files."""
+
+
import re
-className = re.compile(r"@[\w\.]+")
-classVal = re.compile(r"\[(\s*(?:[\w\.-]+\s*)+)\]")
-classDef = re.compile(
- r"(%s)\s*?=\s*?%s;" % (className.pattern, classVal.pattern))
-featureDef = re.compile(
- r"(feature (?P<tag>[A-Za-z]{4}) \{.*?\} (?P=tag);\n)", re.DOTALL)
-comment = re.compile(r"\s*#.*")
-
-
-def validateFeatureFile(font):
- """Remove invalid features and glyph/class references from an RFont."""
-
- classes, text = validateGlyphClasses(font, font.features.text)
- for feature, name in featureDef.findall(text):
- remove = False
- for reference in className.findall(feature):
- if reference not in classes:
- print ("Undefined glyph class %s referenced in feature "
- "definition %s (removed)." % (reference, name))
- remove = True
- for references in classVal.findall(feature):
- for reference in references.split():
- if "-" not in reference and not font.has_key(reference):
- print ("Undefined glyph %s referenced in feature "
- "definition %s (removed)." % (reference, name))
- remove = True
- if remove:
- text = text.replace(feature, "")
- font.features.text = text
-
-
-def validateGlyphClasses(font, text):
- """Parse glyph classes from feature text, removing invalid references."""
-
- classes = set()
- validLines = []
- for line in [l for l in re.split(r"[\r\n]+", text) if not comment.match(l)]:
- match = classDef.match(line)
- if match:
- name, references = match.groups()
- classes.add(name)
-
- validRefs = []
- for reference in references.split():
- if reference.startswith("@") and reference not in classes:
- print ("Undefined glyph class %s referenced in glyph class "
- "definition %s (removed)." % (reference, name))
- elif "-" not in reference and not font.has_key(reference):
- print ("Undefined glyph %s referenced in glyph class "
- "definition %s (removed)." % (reference, name))
- else:
- validRefs.append(reference)
-
- line = "%s = [%s];" % (name, " ".join(validRefs))
- validLines.append(line)
-
- return classes, "\n".join(validLines)
-
-
-def CreateFeaFile(font, path):
- fea_text = font.ot_classes
- for cls in font.classes:
- text = "@" + cls + "];\n"
- text = string.replace(text, ":", "= [")
- text = string.replace(text, "\'", "")
- fea_text += text
- for fea in font.features:
- fea_text += fea.value
- fea_text = string.replace(fea_text, "\r\n", "\n")
- fout = open(path, "w")
- fout.write(fea_text)
- fout.close()
\ No newline at end of file +# 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 = re.compile(r"@%s{,29}" % _glyphNameChars)
+_classValToken = (
+ r"(?:%s|%s(?:\s*-\s*%s)?)" % (_className.pattern, _glyphName, _glyphName))
+_classVal = re.compile(
+ r"\[(\s*%s(?:\s+%s)*)\s*\]" % (_classValToken, _classValToken))
+_classDef = re.compile(
+ r"(%s)\s*=\s*%s;" % (_className.pattern, _classVal.pattern))
+_featureDef = re.compile(
+ r"(feature\s+(?P<tag>[A-Za-z]{4})\s+\{.*?\}\s+(?P=tag)\s*;\s*?\n)",
+ re.DOTALL)
+_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)
+ lines = [l for l in re.split(r"[\r\n]+", text) if not _comment.match(l)]
+ text = "\n".join(lines)
+
+ errorMsg = "feature definition %s (definition removed)"
+ if not hasattr(font.features, "tags"):
+ font.features.tags = []
+ font.features.values = {}
+ for value, tag in _featureDef.findall(text):
+ valid = True
+ for reference in _className.findall(value):
+ valid = valid and _isValidReference(errorMsg % tag, reference, font)
+ for referenceList in _classVal.findall(value):
+ for ref in referenceList.split():
+ valid = valid and _isValidReference(errorMsg % tag, ref, font)
+ if valid:
+ font.features.tags.append(tag)
+ font.features.values[tag] = value
+
+
+def readGlyphClasses(font, text, update=True):
+ """Incorporate valid glyph classes from feature text into font."""
+
+ lines = [l for l in re.split(r"[\r\n]+", text) if not _comment.match(l)]
+ text = "\n".join(lines)
+
+ 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)
+ validRefs = []
+ for reference in value.split():
+ if _isValidReference(errorMsg % name, reference, font):
+ validRefs.append(reference)
+ value = " ".join(validRefs)
+ font.classNames.append(name)
+ font.classVals[name] = value
+
+ if not hasattr(font, "languageSystems"):
+ font.languageSystems = []
+ for system in _systemDef.findall(text):
+ if system not in font.languageSystems:
+ font.languageSystems.append(system)
+
+
+def _isValidReference(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])
+ features = "\n".join([font.features.values[t] for t in font.features.tags])
+ font.features.text = "\n\n".join([classes, systems, features])
+
+
+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()
diff --git a/scripts/lib/fontbuild/kerning.py b/scripts/lib/fontbuild/kerning.py deleted file mode 100644 index def733b..0000000 --- a/scripts/lib/fontbuild/kerning.py +++ /dev/null @@ -1,25 +0,0 @@ -import re - -def markKernClassesLR(f): - for i in range(len(f.classes)): - classname = f.classes[i].split(':', 1).pop(0).strip() - if classname.startswith('_'): - l = 0 - r = 0 - if classname.endswith('_L'): - l = 1 - elif classname.endswith('_R'): - r = 1 - elif classname.endswith('_LR'): - l = 1 - r = 1 - f.SetClassFlags(i, l, r) - fl.UpdateFont() - -def generateFLKernClassesFromOTString(f,classString): - classString.replace("\r","\n") - rx = re.compile(r"@(_[\w]+)\s*=\s*\[\s*(\w+?)\s+(.*?)\]\s*;") - classes = ["%s : %s' %s" %(m[0],m[1],m[2]) for m in rx.findall(classString)] - f.classes = classes - markKernClassesLR(f) - diff --git a/scripts/lib/fontbuild/markFeature.py b/scripts/lib/fontbuild/markFeature.py index 4eb2b32..c966085 100755 --- a/scripts/lib/fontbuild/markFeature.py +++ b/scripts/lib/fontbuild/markFeature.py @@ -8,7 +8,7 @@ def GetAliaseName(gname): def CreateAccNameList(font, acc_anchor_name):
lst = []
- for g in font.glyphs:
+ for g in font:
for anchor in g.anchors:
if acc_anchor_name == anchor.name:
lst.append(g.name)
@@ -16,7 +16,7 @@ def CreateAccNameList(font, acc_anchor_name): def CreateAccGlyphList(font, acc_list, acc_anchor_name):
g_list = []
- for g in font.glyphs:
+ for g in font:
if g.name in acc_list:
for anchor in g.anchors:
if acc_anchor_name == anchor.name:
@@ -27,7 +27,7 @@ def CreateAccGlyphList(font, acc_list, acc_anchor_name): def CreateGlyphList(font, acc_list, anchor_name):
g_list = []
- for g in font.glyphs:
+ for g in font:
if g.name in acc_list:
continue
for anchor in g.anchors:
@@ -77,15 +77,8 @@ def GenerateFeature_mark(font): base_mark_list = CreateGlyphList(font, accent_name_list, anchor_name)
text += Create_mark_lookup(accent_mark_list, base_mark_list, "mark2", "@MC_bottom")
-
text += "} mark;\n"
- mark = Feature("mark", text)
-
- not_exist = True
- for n in range(len(font.features)):
- if ('mark' == font.features[n].tag):
- font.features[n] = mark
- not_exist = False
- if (not_exist):
- font.features.append(mark)
+ if "mark" not in font.features.tags:
+ font.features.tags.append("mark")
+ font.features.values["mark"] = text
diff --git a/scripts/lib/fontbuild/mkmkFeature.py b/scripts/lib/fontbuild/mkmkFeature.py index cfc722e..e2195f5 100755 --- a/scripts/lib/fontbuild/mkmkFeature.py +++ b/scripts/lib/fontbuild/mkmkFeature.py @@ -1,6 +1,6 @@ def CreateAccNameList(font, acc_anchor_name):
lst = []
- for g in font.glyphs:
+ for g in font:
for anchor in g.anchors:
if acc_anchor_name == anchor.name:
lst.append(g.name)
@@ -8,7 +8,7 @@ def CreateAccNameList(font, acc_anchor_name): def CreateAccGlyphList(font, acc_list, acc_anchor_name):
g_list = []
- for g in font.glyphs:
+ for g in font:
if g.name in acc_list:
for anchor in g.anchors:
if acc_anchor_name == anchor.name:
@@ -19,7 +19,7 @@ def CreateAccGlyphList(font, acc_list, acc_anchor_name): def CreateGlyphList(font, acc_list, anchor_name):
g_list = []
- for g in font.glyphs:
+ for g in font:
for anchor in g.anchors:
if anchor_name == anchor.name:
g_list.append([g.name, anchor.x, anchor.y])
@@ -55,13 +55,7 @@ def GenerateFeature_mkmk(font): text += Create_mkmk1(accent_mark_list, base_mark_list, "mkmk1")
text += "} mkmk;\n"
- mkmk = Feature("mkmk", text)
- not_exist = True
- for n in range(len(font.features)):
- if ('mkmk' == font.features[n].tag):
- font.features[n] = mkmk
- not_exist = False
-
- if (not_exist):
- font.features.append(mkmk)
+ if "mkmk" not in font.features.tags:
+ font.features.tags.append("mkmk")
+ font.features.values["mkmk"] = text
|