From e8c9488af391731a94e9c47b63e926f3d7e14c6e Mon Sep 17 00:00:00 2001 From: James Godfrey-Kittle Date: Tue, 3 Nov 2015 13:28:11 -0800 Subject: Generate TTFs with ufo2ft --- scripts/build-v2.py | 9 ++- scripts/lib/fontbuild/Build.py | 74 ++++++++------------ scripts/lib/fontbuild/kerning.py | 112 ----------------------------- scripts/lib/fontbuild/markFeature.py | 132 ++++++++--------------------------- scripts/lib/fontbuild/mkmkFeature.py | 87 ----------------------- 5 files changed, 66 insertions(+), 348 deletions(-) delete mode 100644 scripts/lib/fontbuild/kerning.py delete mode 100755 scripts/lib/fontbuild/mkmkFeature.py diff --git a/scripts/build-v2.py b/scripts/build-v2.py index 58842d6..0ef86e9 100644 --- a/scripts/build-v2.py +++ b/scripts/build-v2.py @@ -91,8 +91,7 @@ proj = FontProject(rg.font, BASEDIR, "res/roboto.cfg", th.ffont) FAMILYNAME = "Roboto" proj.buildOTF = True -#proj.autohintOTF = True -proj.buildTTF = True +#proj.compatible = True proj.generateFont(th.font, "%s/Thin/Regular/Th"%FAMILYNAME) proj.generateFont(Mix([th, rg], 0.45), "%s/Light/Regular/Lt"%FAMILYNAME) @@ -119,6 +118,10 @@ proj.generateFont(Mix([rg, bd], RPoint(1.125, 1.0)), "%s/Black Italic/Bold Italic/Bk"%FAMILYNAME, italic=True, stemWidth=290) +# unfortunately some condensed forms (*.cn) of glyphs are not compatible with +# their original forms, so we can't convert all fonts together compatibly +proj.generateTTFs() + thcn1 = Master(condenseFont(th.font, .84, 40)) cn1 = Master(rg.ffont.addDiff(thcn1.ffont, th.ffont)) bdcn1 = Master(bd.ffont.addDiff(thcn1.ffont, th.ffont)) @@ -143,4 +146,4 @@ proj.generateFont(Mix([cn1, bdcn1], RPoint(0.75, 0.75)), "%s Condensed/Bold Italic/Bold Italic/Rg"%FAMILYNAME, italic=True, swapSuffixes=[".cn"], stemWidth=240) -sys.exit(0) +proj.generateTTFs() diff --git a/scripts/lib/fontbuild/Build.py b/scripts/lib/fontbuild/Build.py index f29c727..34a672e 100644 --- a/scripts/lib/fontbuild/Build.py +++ b/scripts/lib/fontbuild/Build.py @@ -18,20 +18,19 @@ import os import sys from booleanOperations import BooleanOperationManager +from convert_curves import fonts_to_quadratic from fontTools.misc.transform import Transform from robofab.world import OpenFont +from ufo2ft import compileOTF, compileTTF -from fontbuild.convertCurves import glyphCurvesToQuadratic from fontbuild.decomposeGlyph import decomposeGlyph from fontbuild.features import readFeatureFile, writeFeatureFile from fontbuild.generateGlyph import generateGlyph from fontbuild.instanceNames import setNamesRF from fontbuild.italics import italicizeGlyph -from fontbuild.kerning import makeKernFeature -from fontbuild.markFeature import GenerateFeature_mark +from fontbuild.markFeature import RobotoFeatureCompiler from fontbuild.mitreGlyph import mitreGlyph from fontbuild.mix import Mix,Master,narrowFLGlyph -from fontbuild.mkmkFeature import GenerateFeature_mkmk class FontProject: @@ -68,8 +67,8 @@ class FontProject: self.buildnumber = self.loadBuildNumber() self.buildOTF = False - self.autohintOTF = False - self.buildTTF = False + self.compatible = False + self.generatedFonts = [] def loadBuildNumber(self): @@ -155,33 +154,38 @@ class FontProject: setNamesRF(f, n, foundry=self.config.get('main', 'foundry'), version=self.config.get('main', 'version')) - cleanCurves(f) + if not self.compatible: + cleanCurves(f) deleteGlyphs(f, self.deleteList) if kern: log(">> Generating kern classes") readFeatureFile(f, self.ot_kerningclasses) - makeKernFeature(f, self.ot_kerningclasses) log(">> Generating font files") - GenerateFeature_mark(f) - GenerateFeature_mkmk(f) ufoName = self.generateOutputPath(f, "ufo") f.save(ufoName) + self.generatedFonts.append(ufoName) if self.buildOTF: log(">> Generating OTF file") newFont = OpenFont(ufoName) otfName = self.generateOutputPath(f, "otf") - builtSuccessfully = saveOTF(newFont, otfName, autohint=self.autohintOTF) - if not builtSuccessfully: - sys.exit(1) + saveOTF(newFont, otfName) - if self.buildTTF: - log(">> Generating TTF file") - import fontforge - otFont = fontforge.open(otfName) - otFont.generate(self.generateOutputPath(f, "ttf")) + def generateTTFs(self): + """Build TTF for each font generated since last call to generateTTFs.""" + + fonts = [OpenFont(ufo) for ufo in self.generatedFonts] + log(">> Converting curves to quadratic") + fonts_to_quadratic(fonts, self.compatible) + + log(">> Generating TTF files") + for font in fonts: + ttfName = self.generateOutputPath(font, "ttf") + log(os.path.basename(ttfName)) + saveOTF(font, ttfName, truetype=True) + self.generatedFonts = [] def transformGlyphMembers(g, m): @@ -266,30 +270,12 @@ def removeGlyphOverlap(glyph): manager.union(contours, glyph.getPointPen()) -def saveOTF(font, destFile, autohint=False): - """Save a RoboFab font as an OTF binary using ufo2fdk. - - Returns True on success, False otherwise. - """ - - from ufo2fdk import OTFCompiler - - # glyphs with multiple unicode values must be split up, due to FontTool's - # use of a name -> UV dictionary during cmap compilation - for glyph in font: - if len(glyph.unicodes) > 1: - newUV = glyph.unicodes.pop() - newGlyph = font.newGlyph("uni%04X" % newUV) - newGlyph.appendComponent(glyph.name) - newGlyph.unicode = newUV - newGlyph.width = glyph.width - - compiler = OTFCompiler() - reports = compiler.compile(font, destFile, autohint=autohint) - if autohint: - print reports["autohint"] - print reports["makeotf"] +def saveOTF(font, destFile, truetype=False): + """Save a RoboFab font as an OTF binary using ufo2fdk.""" - successMsg = ("makeotfexe [NOTE] Wrote new font file '%s'." % - os.path.basename(destFile)) - return successMsg in reports["makeotf"] + if truetype: + compiler = compileTTF + else: + compiler = compileOTF + otf = compiler(font, featureCompilerClass=RobotoFeatureCompiler) + otf.save(destFile) diff --git a/scripts/lib/fontbuild/kerning.py b/scripts/lib/fontbuild/kerning.py deleted file mode 100644 index c93e303..0000000 --- a/scripts/lib/fontbuild/kerning.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright 2015 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from feaTools import parser -from feaTools.writers.baseWriter import AbstractFeatureWriter - - -class KernFeatureWriter(AbstractFeatureWriter): - """Generates a kerning feature based on glyph class definitions. - - Uses the kerning rules contained in an RFont's kerning attribute, as well as - glyph classes from parsed OTF text. Class-based rules are set based on the - existing rules for their key glyphs. - """ - - def __init__(self, font): - self.kerning = font.kerning - self.leftClasses = [] - self.rightClasses = [] - self.classSizes = {} - - def write(self, linesep="\n"): - """Write kern feature.""" - - # maintain collections of different rule types - leftClassKerning, rightClassKerning, classPairKerning = {}, {}, {} - for leftName, leftContents in self.leftClasses: - leftKey = leftContents[0] - - # collect rules with two classes - for rightName, rightContents in self.rightClasses: - rightKey = rightContents[0] - pair = leftKey, rightKey - kerningVal = self.kerning[pair] - if kerningVal is None: - continue - classPairKerning[leftName, rightName] = kerningVal - self.kerning.remove(pair) - - # collect rules with left class and right glyph - for pair, kerningVal in self.kerning.getLeft(leftKey): - leftClassKerning[leftName, pair[1]] = kerningVal - self.kerning.remove(pair) - - # collect rules with left glyph and right class - for rightName, rightContents in self.rightClasses: - rightKey = rightContents[0] - for pair, kerningVal in self.kerning.getRight(rightKey): - rightClassKerning[pair[0], rightName] = kerningVal - self.kerning.remove(pair) - - # write the feature - self.ruleCount = 0 - lines = ["feature kern {"] - lines.append(self._writeKerning(self.kerning, linesep)) - lines.append(self._writeKerning(leftClassKerning, linesep, True)) - lines.append(self._writeKerning(rightClassKerning, linesep, True)) - lines.append(self._writeKerning(classPairKerning, linesep)) - lines.append("} kern;") - return linesep.join(lines) - - def _writeKerning(self, kerning, linesep, enum=False): - """Write kerning rules for a mapping of pairs to values.""" - - lines = [] - enum = "enum " if enum else "" - pairs = kerning.items() - pairs.sort() - for (left, right), val in pairs: - if enum: - rulesAdded = (self.classSizes.get(left, 1) * - self.classSizes.get(right, 1)) - else: - rulesAdded = 1 - self.ruleCount += rulesAdded - if self.ruleCount > 1024: - lines.append(" subtable;") - self.ruleCount = rulesAdded - lines.append(" %spos %s %s %d;" % (enum, left, right, val)) - return linesep.join(lines) - - def classDefinition(self, name, contents): - """Store a class definition as either a left- or right-hand class.""" - - if not name.startswith("@_"): - return - info = (name, contents) - if name.endswith("_L"): - self.leftClasses.append(info) - elif name.endswith("_R"): - self.rightClasses.append(info) - self.classSizes[name] = len(contents) - - -def makeKernFeature(font, text): - """Add a kern feature to the font, using a KernFeatureWriter.""" - - writer = KernFeatureWriter(font) - parser.parseFeatures(writer, text) - font.features.text += writer.write() diff --git a/scripts/lib/fontbuild/markFeature.py b/scripts/lib/fontbuild/markFeature.py index b617ef6..945cef0 100755 --- a/scripts/lib/fontbuild/markFeature.py +++ b/scripts/lib/fontbuild/markFeature.py @@ -13,105 +13,33 @@ # limitations under the License. -from fontbuild.features import updateFeature - - -aliases = [["uni0430", "a"], ["uni0435", "e"], ["uni0440", "p"], ["uni0441", "c"], ["uni0445", "x"], ["uni0455", "s"], ["uni0456", "i"], ["uni0471", "psi"]] - -def GetAliaseName(gname): - for i in range (len(aliases)): - if (gname == aliases[i][1]): - return aliases[i][0] - return None - -def CreateAccNameList(font, acc_anchor_name, bCombAccentOnly = True): - #combrange = range(0x0300,0x0370) + range(0x1AB0,0x1ABF) + range(0x1DC0,0x1DE0) - lst = [] - for g in font: - if bCombAccentOnly and g.width != 0: #((g.unicode < 0x0300) or (g.unicode > 0x362)): - continue - for anchor in g.anchors: - if acc_anchor_name == anchor.name: - lst.append(g.name) - return lst - -def CreateAccGlyphList(font, acc_list, acc_anchor_name): - g_list = [] - for g in font: - if g.name in acc_list: - for anchor in g.anchors: - if acc_anchor_name == anchor.name: - g_list.append([g.name, anchor.x, anchor.y]) - break - return g_list - - -def CreateGlyphList(font, acc_list, anchor_name): - g_list = [] - for g in font: - if g.name in acc_list: - continue - for anchor in g.anchors: - if anchor_name == anchor.name: - g_list.append([g.name, anchor.x, anchor.y]) - break - return g_list - -def Create_mark_lookup(accent_g_list, base_g_list, lookupname, acc_class, lookAliases = True): - txt = "lookup " + lookupname + " {\n" - - for acc in accent_g_list: - txt += " markClass " + acc[0] + " " + acc_class +";\n" - - for base in base_g_list: - txt += " pos base " + base[0] + " mark " + acc_class + ";\n" - if (lookAliases): - base2 = GetAliaseName(base[0]) - if (None == base2): - continue - txt += " pos base " + base2 + " mark " + acc_class + ";\n" - - txt += "} " + lookupname + ";\n" - - return txt - -##### main ############## -def GenerateFeature_mark(font): - - combination_anchor_list = [ - ["top", "_marktop", True, True], - ["bottom", "_markbottom", True, True], - ["top_dd", "_marktop_dd", True, False], - ["bottom_dd", "_markbottom_dd", True, False], - ["rhotichook", "_markrhotichook", False, False], - ["top0315", "_marktop0315", False, False], - ["parent_top", "_markparent_top", False, False], - ["parenthesses.w1", "_markparenthesses.w1", False, False], - ["parenthesses.w2", "_markparenthesses.w2", False, False], - ["parenthesses.w3", "_markparenthesses.w3", False, False] - ] - - text = "feature mark {\n" - - for n in range(len(combination_anchor_list)): - - accent_name_list = [] - accent_mark_list = [] - base_mark_list = [] - - anchors_pair = combination_anchor_list[n] - anchor_name = anchors_pair[0] - acc_anchor_name = anchors_pair[1] - comb_accent_only = anchors_pair[2] - expand_to_composits = anchors_pair[3] - lookupname = "mark"+`n+1` - classname = "@MC_" + anchor_name - - accent_name_list = CreateAccNameList(font, acc_anchor_name, comb_accent_only) - accent_mark_list = CreateAccGlyphList(font, accent_name_list, acc_anchor_name) - base_mark_list = CreateGlyphList(font, accent_name_list, anchor_name) - text += Create_mark_lookup(accent_mark_list, base_mark_list, lookupname, classname, expand_to_composits) - - text += "} mark;\n" - - updateFeature(font, "mark", text) +from ufo2ft.kernFeatureWriter import KernFeatureWriter +from ufo2ft.makeotfParts import FeatureOTFCompiler + + +class RobotoFeatureCompiler(FeatureOTFCompiler): + def precompile(self): + self.overwriteFeatures = True + + def setupAnchorPairs(self): + self.anchorPairs = [ + ["top", "_marktop", True, True], + ["bottom", "_markbottom", True, True], + ["top_dd", "_marktop_dd", True, False], + ["bottom_dd", "_markbottom_dd", True, False], + ["rhotichook", "_markrhotichook", False, False], + ["top0315", "_marktop0315", False, False], + ["parent_top", "_markparent_top", False, False], + ["parenthesses.w1", "_markparenthesses.w1", False, False], + ["parenthesses.w2", "_markparenthesses.w2", False, False], + ["parenthesses.w3", "_markparenthesses.w3", False, False]] + + self.mkmkAnchorPairs = [ + ["mkmktop", "_marktop"], + ["mkmkbottom_acc", "_markbottom"]] + + def setupAliases(self): + self.aliases = [ + ["a", "uni0430"], ["e", "uni0435"], ["p", "uni0440"], + ["c", "uni0441"], ["x", "uni0445"], ["s", "uni0455"], + ["i", "uni0456"], ["psi", "uni0471"]] diff --git a/scripts/lib/fontbuild/mkmkFeature.py b/scripts/lib/fontbuild/mkmkFeature.py deleted file mode 100755 index 16f9313..0000000 --- a/scripts/lib/fontbuild/mkmkFeature.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2015 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from fontbuild.features import updateFeature - - -def CreateAccNameList(font, acc_anchor_name): - lst = [] - for g in font: - for anchor in g.anchors: - if acc_anchor_name == anchor.name: - lst.append(g.name) - return lst - -def CreateAccGlyphList(font, acc_list, acc_anchor_name): - g_list = [] - for g in font: - if g.name in acc_list: - for anchor in g.anchors: - if acc_anchor_name == anchor.name: - g_list.append([g.name, anchor.x, anchor.y]) - break - return g_list - - -def CreateGlyphList(font, acc_list, anchor_name): - g_list = [] - for g in font: - for anchor in g.anchors: - if anchor_name == anchor.name: - g_list.append([g.name, anchor.x, anchor.y]) - break - return g_list - -def Create_mkmk1(accent_g_list, base_g_list, lookupname, acc_class): - txt = "lookup " + lookupname + " {\n" - #acc_class = "@MC_mkmk" - for acc in accent_g_list: - txt += " markClass " + acc[0] + " " + acc_class +";\n" - - for base in base_g_list: - txt += " pos mark " + base[0] + " mark " + acc_class + ";\n" - - txt += "} " + lookupname + ";\n" - - return txt - - -##### main ############## -def GenerateFeature_mkmk(font): - text = "feature mkmk {\n" - - accent_name_list = [] - accent_mark_list = [] - base_mark_list = [] - anchor_name = "mkmktop" - acc_anchor_name = "_marktop" - accent_name_list = CreateAccNameList(font, acc_anchor_name) - accent_mark_list = CreateAccGlyphList(font, accent_name_list, acc_anchor_name) - base_mark_list = CreateGlyphList(font, accent_name_list, anchor_name) - text += Create_mkmk1(accent_mark_list, base_mark_list, "mkmk1", "@MC_mkmk_top") - - accent_name_list = [] - accent_mark_list = [] - base_mark_list = [] - anchor_name = "mkmkbottom_acc" - acc_anchor_name = "_markbottom" - accent_name_list = CreateAccNameList(font, acc_anchor_name) - accent_mark_list = CreateAccGlyphList(font, accent_name_list, acc_anchor_name) - base_mark_list = CreateGlyphList(font, accent_name_list, anchor_name) - text += Create_mkmk1(accent_mark_list, base_mark_list, "mkmk2", "@MC_mkmk_bottom") - - text += "} mkmk;\n" - - updateFeature(font, "mkmk", text) -- cgit v1.2.3