diff options
author | Andrej Shadura <andrew.shadura@collabora.co.uk> | 2019-02-06 13:21:33 +0100 |
---|---|---|
committer | Andrej Shadura <andrew.shadura@collabora.co.uk> | 2019-02-06 13:21:33 +0100 |
commit | ba28c24d899c329cb1db6bef162ca32693b08db9 (patch) | |
tree | 10ee4a9e796d33d10f30db8082bd4d6c4f5cd924 /scripts/lib/fontbuild | |
parent | dbeb1d1913b5e0b1609af63caa02c2785f832a26 (diff) |
New upstream version 0~20170802
Diffstat (limited to 'scripts/lib/fontbuild')
-rw-r--r-- | scripts/lib/fontbuild/Build.py | 109 | ||||
-rw-r--r-- | scripts/lib/fontbuild/generateGlyph.py | 116 | ||||
-rw-r--r-- | scripts/lib/fontbuild/instanceNames.py | 4 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/markFeature.py | 39 | ||||
-rw-r--r-- | scripts/lib/fontbuild/mix.py | 47 |
5 files changed, 133 insertions, 182 deletions
diff --git a/scripts/lib/fontbuild/Build.py b/scripts/lib/fontbuild/Build.py index a4e5bac..03defa6 100644 --- a/scripts/lib/fontbuild/Build.py +++ b/scripts/lib/fontbuild/Build.py @@ -18,7 +18,7 @@ import os import sys from booleanOperations import BooleanOperationManager -from cu2qu.rf import fonts_to_quadratic +from cu2qu.ufo import fonts_to_quadratic from fontTools.misc.transform import Transform from robofab.world import OpenFont from ufo2ft import compileOTF, compileTTF @@ -34,23 +34,23 @@ from fontbuild.mix import Mix,Master,narrowFLGlyph class FontProject: - - def __init__(self, basefont, basedir, configfile, thinfont = None): + + def __init__(self, basefont, basedir, configfile): self.basefont = basefont - self.thinfont = thinfont self.basedir = basedir self.config = ConfigParser.RawConfigParser() - self.configfile = self.basedir+"/"+configfile + self.configfile = os.path.join(self.basedir, configfile) self.config.read(self.configfile) - - diacriticList = open(self.basedir + "/" + self.config.get("res","diacriticfile")).readlines() - self.diacriticList = [line.strip() for line in diacriticList if not line.startswith("#")] - self.ot_classes = open(self.basedir + "/" + self.config.get("res","ot_classesfile")).read() - self.ot_kerningclasses = open(self.basedir + "/" + self.config.get("res","ot_kerningclassesfile")).read() - #self.ot_features = open(self.basedir + "/" + self.config.get("res","ot_featuresfile")).read() - adobeGlyphList = open(self.basedir + "/" + self.config.get("res", "agl_glyphlistfile")).readlines() - self.adobeGlyphList = dict([line.split(";") for line in adobeGlyphList if not line.startswith("#")]) - + + self.diacriticList = [ + line.strip() for line in self.openResource("diacriticfile") + if not line.startswith("#")] + self.adobeGlyphList = dict( + line.split(";") for line in self.openResource("agl_glyphlistfile") + if not line.startswith("#")) + self.glyphOrder = self.openResource("glyphorder") + self.thinGlyphOrder = self.openResource("glyphorder_thin") + # map exceptional glyph names in Roboto to names in the AGL roboNames = ( ('Obar', 'Ocenteredtilde'), ('obar', 'obarred'), @@ -64,30 +64,16 @@ class FontProject: self.lessItalic = self.config.get("glyphs","lessitalic").split() self.deleteList = self.config.get("glyphs","delete").split() self.noItalic = self.config.get("glyphs","noitalic").split() - self.buildnumber = self.loadBuildNumber() - + self.buildOTF = False self.compatible = False self.generatedFonts = [] - - - def loadBuildNumber(self): - versionFile = open(self.basedir + "/" + self.config.get("main","buildnumberfile"), "r+") - buildnumber = int(versionFile.read().strip()) - buildnumber = "%05d" %(int(buildnumber) + 1) - print "BuildNumber: %s" %(buildnumber) - versionFile.close() - return buildnumber - - def incrementBuildNumber(self): - if len(self.buildnumber) > 0: - versionFile = open(self.basedir + "/" + self.config.get("main","buildnumberfile"), "r+") - versionFile.seek(0) - versionFile.write(self.buildnumber) - versionFile.truncate() - versionFile.close() - else: - raise Exception("Empty build number") + + def openResource(self, name): + with open(os.path.join( + self.basedir, self.config.get("res", name))) as resourceFile: + resource = resourceFile.read() + return resource.splitlines() def generateOutputPath(self, font, ext): family = font.info.familyName.replace(" ", "") @@ -96,9 +82,9 @@ class FontProject: if not os.path.exists(path): os.makedirs(path) return os.path.join(path, "%s-%s.%s" % (family, style, ext)) - - def generateFont(self, mix, names, italic=False, swapSuffixes=None, stemWidth=185, kern=True): - + + def generateFont(self, mix, names, italic=False, swapSuffixes=None, stemWidth=185): + n = names.split("/") log("---------------------\n%s %s\n----------------------" %(n[0],n[1])) log(">> Mixing masters") @@ -118,7 +104,7 @@ class FontProject: for g in f: i += 1 if i % 10 == 0: print g.name - + if g.name == "uniFFFD": continue @@ -128,11 +114,12 @@ class FontProject: italicizeGlyph(f, g, 9, stemWidth=stemWidth) elif False == (g.name in self.noItalic): italicizeGlyph(f, g, 10, stemWidth=stemWidth) - #elif g.name != ".notdef": - # italicizeGlyph(g, 10, stemWidth=stemWidth) if g.width != 0: g.width += 10 + # set the oblique flag in fsSelection + f.info.openTypeOS2Selection.append(9) + if swapSuffixes != None: for swap in swapSuffixes: swapList = [g.name for g in f if g.name.endswith(swap)] @@ -146,7 +133,7 @@ class FontProject: log(">> Generating glyphs") generateGlyphs(f, self.diacriticList, self.adobeGlyphList) log(">> Copying features") - readFeatureFile(f, self.ot_classes + self.basefont.features.text) + readFeatureFile(f, self.basefont.features.text) log(">> Decomposing") for gname in self.decompose: if f.has_key(gname): @@ -158,10 +145,6 @@ class FontProject: cleanCurves(f) deleteGlyphs(f, self.deleteList) - if kern: - log(">> Generating kern classes") - readFeatureFile(f, self.ot_kerningclasses) - log(">> Generating font files") ufoName = self.generateOutputPath(f, "ufo") f.save(ufoName) @@ -171,7 +154,9 @@ class FontProject: log(">> Generating OTF file") newFont = OpenFont(ufoName) otfName = self.generateOutputPath(f, "otf") - saveOTF(newFont, otfName) + saveOTF( + newFont, otfName, + self.thinGlyphOrder if "Thin" in otfName else self.glyphOrder) def generateTTFs(self): """Build TTF for each font generated since last call to generateTTFs.""" @@ -184,19 +169,19 @@ class FontProject: # fewer control points and look noticeably different max_err = 0.002 if self.compatible: - fonts_to_quadratic(fonts, max_err_em=max_err, dump_stats=True) + fonts_to_quadratic(fonts, max_err_em=max_err, dump_stats=True, reverse_direction=True) else: for font in fonts: - fonts_to_quadratic([font], max_err_em=max_err, dump_stats=True) + fonts_to_quadratic([font], max_err_em=max_err, dump_stats=True, reverse_direction=True) log(">> Generating TTF files") for font in fonts: ttfName = self.generateOutputPath(font, "ttf") log(os.path.basename(ttfName)) - for glyph in font: - for contour in glyph: - contour.reverseContour() - saveOTF(font, ttfName, truetype=True) + saveOTF( + font, ttfName, + self.thinGlyphOrder if "Thin" in ttfName else self.glyphOrder, + truetype=True) def transformGlyphMembers(g, m): @@ -219,6 +204,7 @@ def transformGlyphMembers(g, m): s.Transform(m) #c.scale = s + def swapContours(f,gName1,gName2): try: g1 = f[gName1] @@ -248,7 +234,7 @@ def log(msg): def generateGlyphs(f, glyphNames, glyphList={}): log(">> Generating diacritics") glyphnames = [gname for gname in glyphNames if not gname.startswith("#") and gname != ""] - + for glyphName in glyphNames: generateGlyph(f, glyphName, glyphList) @@ -260,7 +246,7 @@ def cleanCurves(f): # log(">> Mitring sharp corners") # for g in f: # mitreGlyph(g, 3., .7) - + # log(">> Converting curves to quadratic") # for g in f: # glyphCurvesToQuadratic(g) @@ -281,13 +267,16 @@ def removeGlyphOverlap(glyph): manager.union(contours, glyph.getPointPen()) -def saveOTF(font, destFile, truetype=False): +def saveOTF(font, destFile, glyphOrder, truetype=False): """Save a RoboFab font as an OTF binary using ufo2fdk.""" if truetype: - compiler = compileTTF + otf = compileTTF(font, featureCompilerClass=RobotoFeatureCompiler, + kernWriter=RobotoKernWriter, glyphOrder=glyphOrder, + convertCubics=False, + useProductionNames=False) else: - compiler = compileOTF - otf = compiler(font, featureCompilerClass=RobotoFeatureCompiler, - kernWriter=RobotoKernWriter) + otf = compileOTF(font, featureCompilerClass=RobotoFeatureCompiler, + kernWriter=RobotoKernWriter, glyphOrder=glyphOrder, + useProductionNames=False) otf.save(destFile) diff --git a/scripts/lib/fontbuild/generateGlyph.py b/scripts/lib/fontbuild/generateGlyph.py index 5787a37..465f940 100644 --- a/scripts/lib/fontbuild/generateGlyph.py +++ b/scripts/lib/fontbuild/generateGlyph.py @@ -14,91 +14,53 @@ import re -from anchors import alignComponentsToAnchors from string import find +from anchors import alignComponentsToAnchors, getAnchorByName + + def parseComposite(composite): c = composite.split("=") d = c[1].split("/") glyphName = d[0] if len(d) == 1: - offset = [0,0] + offset = [0, 0] else: offset = [int(i) for i in d[1].split(",")] accentString = c[0] accents = accentString.split("+") baseName = accents.pop(0) - accentNames = [i.split(":") for i in accents ] + accentNames = [i.split(":") for i in accents] return (glyphName, baseName, accentNames, offset) def copyMarkAnchors(f, g, srcname, width): - unicode_range = range(0x0030, 0x02B0) + range(0x1E00, 0x1EFF) - anchors = f[srcname].anchors - for anchor in anchors: - if "top_dd" == anchor.name: - g.appendAnchor(anchor.name, (anchor.x + width, anchor.y)) - if "bottom_dd" == anchor.name: + for anchor in f[srcname].anchors: + if anchor.name in ("top_dd", "bottom_dd", "top0315"): g.appendAnchor(anchor.name, (anchor.x + width, anchor.y)) - if "top0315" == anchor.name: - g.appendAnchor(anchor.name, (anchor.x + width, anchor.y)) - if "top" == anchor.name: - if g.unicode == None: - if not g.name.endswith(('.ccmp', '.smcp', '.NAV')): - continue - if False == (g.unicode in unicode_range): - if not g.name.endswith(('.ccmp', '.smcp', '.NAV')): - continue - #if g.unicode > 0x02B0: - # continue - parenttop_present = 0 - for anc in g.anchors: - if anc.name == "parent_top": - parenttop_present = 1 - if 0 == parenttop_present: - g.appendAnchor("parent_top", anchor.position) - - if "bottom" == anchor.name: - if g.unicode == None: - if -1 == find(g.name, ".smcp"): - continue - if False == (g.unicode in unicode_range): - if -1 == find(g.name, ".smcp"): - continue - #if g.unicode > 0x02B0: - # continue - bottom_present = 0 - for anc in g.anchors: - if anc.name == "bottom": - bottom_present = 1 - if 0 == bottom_present: - g.appendAnchor("bottom", anchor.position) - - -# g.appendAnchor("top", anchor.position) - - # if "rhotichook" == anchor.name: - # g.appendAnchor(anchor.name, (anchor.x + width, anchor.y)) - - #print g.anchors - for anchor in g.anchors: - if "top" == anchor.name: - #print g.name, g.anchors - return - - anchor_parent_top = None - for anchor in g.anchors: - if "parent_top" == anchor.name: - anchor_parent_top = anchor - break + if ("top" == anchor.name and + not any(a.name == "parent_top" for a in g.anchors)): + g.appendAnchor("parent_top", anchor.position) + if ("bottom" == anchor.name and + not any(a.name == "bottom" for a in g.anchors)): + g.appendAnchor("bottom", anchor.position) + + if any(a.name == "top" for a in g.anchors): + return + + anchor_parent_top = getAnchorByName(g, "parent_top") if anchor_parent_top is not None: g.appendAnchor("top", anchor_parent_top.position) def generateGlyph(f,gname,glyphList={}): glyphName, baseName, accentNames, offset = parseComposite(gname) + if f.has_key(glyphName): + print('Existing glyph "%s" found in font, ignoring composition rule ' + '"%s"' % (glyphName, gname)) + return if baseName.find("_") != -1: g = f.newGlyph(glyphName) @@ -107,25 +69,21 @@ def generateGlyph(f,gname,glyphList={}): g.width += f[componentName].width setUnicodeValue(g, glyphList) - else: - if not f.has_key(glyphName): - try: - f.compileGlyph(glyphName, baseName, accentNames) - except KeyError as e: - print ("KeyError raised for composition rule '%s', likely %s " - "anchor not found in glyph '%s'" % (gname, e, baseName)) - return - g = f[glyphName] - setUnicodeValue(g, glyphList) - copyMarkAnchors(f, g, baseName, offset[1] + offset[0]) - if offset[0] != 0 or offset[1] != 0: - g.width += offset[1] + offset[0] - g.move((offset[0], 0), anchors=False) - if len(accentNames) > 0: - alignComponentsToAnchors(f, glyphName, baseName, accentNames) - else: - print ("Existing glyph '%s' found in font, ignoring composition " - "rule '%s'" % (glyphName, gname)) + else: + try: + f.compileGlyph(glyphName, baseName, accentNames) + except KeyError as e: + print('KeyError raised for composition rule "%s", likely "%s" ' + 'anchor not found in glyph "%s"' % (gname, e, baseName)) + return + g = f[glyphName] + setUnicodeValue(g, glyphList) + copyMarkAnchors(f, g, baseName, offset[1] + offset[0]) + if len(accentNames) > 0: + alignComponentsToAnchors(f, glyphName, baseName, accentNames) + if offset[0] != 0 or offset[1] != 0: + g.width += offset[1] + offset[0] + g.move((offset[0], 0), anchors=False) def setUnicodeValue(glyph, glyphList): diff --git a/scripts/lib/fontbuild/instanceNames.py b/scripts/lib/fontbuild/instanceNames.py index 890b5a6..dd7cba1 100644 --- a/scripts/lib/fontbuild/instanceNames.py +++ b/scripts/lib/fontbuild/instanceNames.py @@ -66,8 +66,8 @@ class InstanceNames: f.info.openTypeNameManufacturerURL = self.foundry + ".com" f.info.openTypeNameLicense = self.license f.info.openTypeNameLicenseURL = self.licenseURL - f.info.openTypeNameVersion = "%i.%i" %(version,versionMinor) - f.info.openTypeNameUniqueID = "%s:%s:%s" %(self.foundry, self.longfamily, self.year) + f.info.openTypeNameVersion = "Version %i.%i" %(version,versionMinor) + f.info.openTypeNameUniqueID = "%s:%s:%s" %(self.foundry, self.fullname, self.year) # f.info.openTypeNameDescription = "" # f.info.openTypeNameCompatibleFullName = "" # f.info.openTypeNameSampleText = "" diff --git a/scripts/lib/fontbuild/markFeature.py b/scripts/lib/fontbuild/markFeature.py index 395e537..9d091bb 100755 --- a/scripts/lib/fontbuild/markFeature.py +++ b/scripts/lib/fontbuild/markFeature.py @@ -23,26 +23,31 @@ class RobotoFeatureCompiler(FeatureOTFCompiler): 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]]
+ ["top", "_marktop"],
+ ["bottom", "_markbottom"],
+ ["top_dd", "_marktop_dd"],
+ ["bottom_dd", "_markbottom_dd"],
+ ["rhotichook", "_markrhotichook"],
+ ["top0315", "_marktop0315"],
+ ["parent_top", "_markparent_top"],
+ ["parenthesses.w1", "_markparenthesses.w1"],
+ ["parenthesses.w2", "_markparenthesses.w2"],
+ ["parenthesses.w3", "_markparenthesses.w3"]]
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"]]
+ ["mkmkbottom_acc", "_markbottom"],
+
+ # By providing a pair with accent anchor _bottom and no base anchor,
+ # we designate all glyphs with _bottom as accents (so that they will
+ # be used as base glyphs for mkmk features) without generating any
+ # positioning rules actually using this anchor (which is instead
+ # used to generate composite glyphs). This is all for consistency
+ # with older roboto versions.
+ ["", "_bottom"],
+ ]
+
+ self.ligaAnchorPairs = []
class RobotoKernWriter(KernFeatureWriter):
diff --git a/scripts/lib/fontbuild/mix.py b/scripts/lib/fontbuild/mix.py index 519b50d..c958701 100644 --- a/scripts/lib/fontbuild/mix.py +++ b/scripts/lib/fontbuild/mix.py @@ -218,13 +218,12 @@ class FGlyph: class Master: - def __init__(self, font=None, v=0, kernlist=None, overlay=None, - anchorPath=None): + def __init__(self, font=None, v=0, kernlist=None, overlay=None): if isinstance(font, FFont): self.font = None self.ffont = font elif isinstance(font,str): - self.openFont(font,overlay, anchorPath) + self.openFont(font,overlay) elif isinstance(font,Mix): self.font = font else: @@ -243,7 +242,7 @@ class Master: and not k[0] == ""] #TODO implement class based kerning / external kerning file - def openFont(self, path, overlayPath=None, anchorPath=None): + def openFont(self, path, overlayPath=None): self.font = OpenFont(path) for g in self.font: size = len(g) @@ -257,16 +256,6 @@ class Master: for overlayGlyph in overlayFont: font.insertGlyph(overlayGlyph) - # work around a bug with vfb2ufo in which anchors are dropped from - # glyphs containing components and no contours. "anchorPath" should - # point to the output of src/v2/get_dropped_anchors.py - if anchorPath: - anchorData = json.load(open(anchorPath)) - for glyphName, anchors in anchorData.items(): - glyph = self.font[glyphName] - for name, (x, y) in anchors.items(): - glyph.appendAnchor(str(name), (x, y)) - self.ffont = FFont(self.font) @@ -345,14 +334,24 @@ def interpolate(a,b,v,e=0): qe = (b-a)*v*v*v + a #cubic easing le = a+(b-a)*v # linear easing return le + (qe-le) * e - + def interpolateKerns(kA, kB, v): - kerns = {} - for pair in kA.keys(): - matchedKern = kB.get(pair) - # if matchedkern == None: - # matchedkern = Kern(kA) - # matchedkern.value = 0 - if matchedKern != None: - kerns[pair] = interpolate(kA[pair], matchedKern, v.x) - return kerns + # to yield correct kerning for Roboto output, we must emulate the behavior + # of old versions of this code; namely, take the kerning values of the first + # master instead of actually interpolating. + # old code: + # https://github.com/google/roboto/blob/7f083ac31241cc86d019ea6227fa508b9fcf39a6/scripts/lib/fontbuild/mix.py + # bug: + # https://github.com/google/roboto/issues/213 + + #kerns = {} + #for pair, val in kA.items(): + # kerns[pair] = interpolate(val, kB.get(pair, 0), v.x) + #for pair, val in kB.items(): + # lerped_val = interpolate(val, kA.get(pair, 0), 1 - v.x) + # if pair in kerns: + # assert abs(kerns[pair] - lerped_val) < 1e-6 + # else: + # kerns[pair] = lerped_val + #return kerns + return dict(kA) |