summaryrefslogtreecommitdiff
path: root/scripts/lib/fontbuild
diff options
context:
space:
mode:
authorAndrej Shadura <andrew.shadura@collabora.co.uk>2019-02-06 13:21:33 +0100
committerAndrej Shadura <andrew.shadura@collabora.co.uk>2019-02-06 13:21:33 +0100
commitba28c24d899c329cb1db6bef162ca32693b08db9 (patch)
tree10ee4a9e796d33d10f30db8082bd4d6c4f5cd924 /scripts/lib/fontbuild
parentdbeb1d1913b5e0b1609af63caa02c2785f832a26 (diff)
New upstream version 0~20170802
Diffstat (limited to 'scripts/lib/fontbuild')
-rw-r--r--scripts/lib/fontbuild/Build.py109
-rw-r--r--scripts/lib/fontbuild/generateGlyph.py116
-rw-r--r--scripts/lib/fontbuild/instanceNames.py4
-rwxr-xr-xscripts/lib/fontbuild/markFeature.py39
-rw-r--r--scripts/lib/fontbuild/mix.py47
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)