summaryrefslogtreecommitdiff
path: root/scripts/lib
diff options
context:
space:
mode:
authorJames Godfrey-Kittle <jamesgk@google.com>2015-02-10 16:15:36 -0800
committerJames Godfrey-Kittle <jamesgk@google.com>2015-04-16 12:16:29 -0700
commit60d5644c64b0be1af7ecefcf0e644d4e5691a694 (patch)
treea5d3b5baab11f07a1287ab95d9cbd1685508bba3 /scripts/lib
parent44f5079ba84bf30aa4072f877687b188e06ee8ce (diff)
More robust feature support.
Diffstat (limited to 'scripts/lib')
-rw-r--r--scripts/lib/fontbuild/Build.py34
-rwxr-xr-xscripts/lib/fontbuild/features.py179
-rw-r--r--scripts/lib/fontbuild/kerning.py25
-rwxr-xr-xscripts/lib/fontbuild/markFeature.py19
-rwxr-xr-xscripts/lib/fontbuild/mkmkFeature.py18
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