summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md50
-rw-r--r--res/roboto.cfg1
-rw-r--r--scripts/build-v2.py13
-rw-r--r--scripts/lib/fontbuild/Build.py118
-rw-r--r--scripts/lib/fontbuild/alignpoints.py31
-rw-r--r--scripts/lib/fontbuild/curveFitPen.py107
-rw-r--r--scripts/lib/fontbuild/instanceNames.py2
-rw-r--r--scripts/lib/fontbuild/italics.py59
-rw-r--r--scripts/lib/fontbuild/kerning.py112
-rwxr-xr-xscripts/lib/fontbuild/markFeature.py137
-rwxr-xr-xscripts/lib/fontbuild/mkmkFeature.py87
-rwxr-xr-xscripts/run_android_tests.py20
-rwxr-xr-xscripts/run_general_tests.py37
-rwxr-xr-xscripts/run_web_tests.py4
-rwxr-xr-xscripts/touchup_for_android.py14
-rwxr-xr-xscripts/touchup_for_web.py6
-rw-r--r--src/v2/README.md6
-rw-r--r--src/v2/Roboto_Bold.ufo/fontinfo.plist4
-rw-r--r--src/v2/Roboto_Bold.vfbbin415462 -> 415462 bytes
-rw-r--r--src/v2/Roboto_Regular.ufo/fontinfo.plist4
-rw-r--r--src/v2/Roboto_Regular.vfbbin471237 -> 471237 bytes
-rw-r--r--src/v2/Roboto_Thin.ufo/fontinfo.plist4
-rw-r--r--src/v2/Roboto_Thin.vfbbin405061 -> 405061 bytes
-rw-r--r--third_party/fontcrunch/Makefile4
-rw-r--r--third_party/fontcrunch/quadopt.cc44
25 files changed, 330 insertions, 534 deletions
diff --git a/README.md b/README.md
index f4a36cd..08681f6 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,8 @@ cd $HOME/roboto-src
```bash
git clone https://github.com/google/roboto.git
git clone https://github.com/behdad/fonttools.git
+git clone https://github.com/googlei18n/cu2qu.git
+git clone https://github.com/jamesgk/ufo2ft.git
git clone https://github.com/robofab-developers/robofab.git
git clone https://github.com/typesupply/feaTools.git
git clone https://github.com/typemytype/booleanOperations.git
@@ -39,11 +41,7 @@ via:
sudo apt-get install cython
```
-##### For OTF generation:
-
-```bash
-git clone https://github.com/typesupply/ufo2fdk.git
-```
+##### For OTF/TTF generation:
To build the FDK yourself:
@@ -59,12 +57,6 @@ If you're not building the FDK yourself, download the pre-built version
[here](http://www.adobe.com/devnet/opentype/afdko.html) and unzip it into the
current directory.
-##### For TTF generation, on Ubuntu:
-
-```bash
-sudo apt-get install fontforge python-fontforge
-```
-
##### For post-production:
```bash
@@ -85,16 +77,17 @@ You can install the necessary modules at the sytem level:
```bash
cd fonttools
sudo python setup.py install
+cd ../cu2qu
+sudo python setup.py install
+cd ../ufo2ft
+sudo python setup.py install
cd ../robofab
sudo python setup.py install
cd ../feaTools
sudo python setup.py install
cd ../Cython-0.22
sudo python setup.py install
-cd ../booleanOperations/cppWrapper
-sudo python setup.py build_ext --inplace
-cp pyClipper.so ../Lib/booleanOperations
-cd ..
+cd ../booleanOperations
sudo python setup.py install
cd ..
```
@@ -103,6 +96,8 @@ Or set `$PYTHONPATH` locally before running `make`:
```bash
PYTHONPATH="$PYTHONPATH:$HOME/roboto-src/fonttools/Lib"
+PYTHONPATH="$PYTHONPATH:$HOME/roboto-src/cu2qu/Lib"
+PYTHONPATH="$PYTHONPATH:$HOME/roboto-src/ufo2ft/Lib"
PYTHONPATH="$PYTHONPATH:$HOME/roboto-src/robofab/Lib"
PYTHONPATH="$PYTHONPATH:$HOME/roboto-src/feaTools/Lib"
PYTHONPATH="$PYTHONPATH:$HOME/roboto-src/booleanOperations/Lib"
@@ -110,18 +105,6 @@ PYTHONPATH="$PYTHONPATH:$HOME/roboto-src/booleanOperations/Lib"
##### For OTF generation:
-```bash
-cd ufo2fdk
-sudo python setup.py install
-cd ..
-```
-
-Or:
-
-```bash
-PYTHONPATH="$PYTHONPATH:$HOME/roboto-src/ufo2fdk/Lib"
-```
-
If building the FDK yourself, follow the instructions in `afdko/FDK/FDK Build Notes.txt`:
```bash
@@ -204,10 +187,11 @@ The Roboto build toolchain depends on:
glyph overlap removal.
- (requires Cython to install: http://cython.org/)
-## OTF Generation
+## OTF/TTF Generation
OTF generation depends on:
-- ufo2fdk (https://github.com/typesupply/ufo2fdk)
+- ufo2ft (https://github.com/jamesgk/ufo2ft)
+- cu2qu (https://github.com/googlei18n/cu2qu)
- Open-source portions of the AFDKO
(https://github.com/adobe-type-tools/afdko/releases)
@@ -216,14 +200,6 @@ the variety which includes closed-source tools
(http://www.adobe.com/devnet/opentype/afdko.html), though these closed-source
portions are not used to build Roboto.
-## TTF Generation
-TTF generation depends on:
-
-- FontForge (https://github.com/fontforge/fontforge)
-
-Whose Python interface should be available on Ubuntu by default via `apt-get
-install fontforge python-fontforge`.
-
## Post-Production
Post-production scripts (most of the code outside of the `fontbuild` directory,
e.g. for testing output) depend on:
diff --git a/res/roboto.cfg b/res/roboto.cfg
index 205cd46..4531bd2 100644
--- a/res/roboto.cfg
+++ b/res/roboto.cfg
@@ -36,6 +36,7 @@ decompose: integral product florin Tbar tbar Hbar hbar Eng eng
uogonek Uogonek.smcp Aogonek.smcp Eogonek.smcp uni0524 uni0525 uni0526
uni0527 uni052E uni052F Hdesc hdesc uni2C69 uni2C6A uni2C6B uni2C6C Ndesc ndesc
uni0498.smcp uni04A2.smcp uni04AA.smcp uni04B6.smcp nbspace uni202F uni205F erev uni1AB5
+ uni2050
predecompose: uni04B4 uni04B5 dcroat uni040F uni045F uni0490 uni0491 OE
oe Oslash oslash uni04A6 uni04A7 uni0492 uni0493 uni04BC uni04BD gamma Ohorn
diff --git a/scripts/build-v2.py b/scripts/build-v2.py
index 58842d6..6026939 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)
@@ -102,7 +101,7 @@ proj.generateFont(Mix([rg, bd], 0.35), "%s/Medium/Regular/Lt"%FAMILYNAME)
proj.generateFont(Mix([rg, bd], RPoint(0.73, 0.73)),
"%s/Bold/Bold/Rg"%FAMILYNAME)
proj.generateFont(Mix([rg, bd], RPoint(1.125, 1.0)),
- "%s/Black/Bold/Bk"%FAMILYNAME)
+ "%s/Black/Regular/Bk"%FAMILYNAME)
proj.generateFont(th.font, "%s/Thin Italic/Italic/Th"%FAMILYNAME,
italic=True, stemWidth=80)
@@ -116,9 +115,13 @@ proj.generateFont(Mix([rg, bd], RPoint(0.73, 0.73)),
"%s/Bold Italic/Bold Italic/Rg"%FAMILYNAME,
italic=True, stemWidth=290)
proj.generateFont(Mix([rg, bd], RPoint(1.125, 1.0)),
- "%s/Black Italic/Bold Italic/Bk"%FAMILYNAME,
+ "%s/Black Italic/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 d1e870a..a4e5bac 100644
--- a/scripts/lib/fontbuild/Build.py
+++ b/scripts/lib/fontbuild/Build.py
@@ -13,23 +13,24 @@
# limitations under the License.
+import ConfigParser
+import os
+import sys
+
from booleanOperations import BooleanOperationManager
+from cu2qu.rf import fonts_to_quadratic
+from fontTools.misc.transform import Transform
from robofab.world import OpenFont
-from fontbuild.mix import Mix,Master,narrowFLGlyph
+from ufo2ft import compileOTF, compileTTF
+
+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.convertCurves import glyphCurvesToQuadratic
+from fontbuild.markFeature import RobotoFeatureCompiler, RobotoKernWriter
from fontbuild.mitreGlyph import mitreGlyph
-from fontbuild.generateGlyph import generateGlyph
-from fontTools.misc.transform import Transform
-from fontbuild.kerning import makeKernFeature
-from fontbuild.features import readFeatureFile, writeFeatureFile
-from fontbuild.markFeature import GenerateFeature_mark
-from fontbuild.mkmkFeature import GenerateFeature_mkmk
-from fontbuild.decomposeGlyph import decomposeGlyph
-import ConfigParser
-import os
-import sys
+from fontbuild.mix import Mix,Master,narrowFLGlyph
class FontProject:
@@ -66,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):
@@ -120,18 +121,8 @@ class FontProject:
if g.name == "uniFFFD":
continue
-
- # if i < 24:
- # continue
- # if i > 86:
- # for i,g in enumerate(fl.font.glyphs):
- # fl.UpdateGlyph(i)
- # # break
- # assert False
-
- # print g.name
- # if self.thinfont != None:
- # narrowFLGlyph(g,self.thinfont.getGlyph(g.name),factor=narrowAmmount)
+
+ removeGlyphOverlap(g)
if g.name in self.lessItalic:
italicizeGlyph(f, g, 9, stemWidth=stemWidth)
@@ -163,33 +154,49 @@ 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)
+
+ def generateTTFs(self):
+ """Build TTF for each font generated since last call to generateTTFs."""
- if self.buildTTF:
- log(">> Generating TTF file")
- import fontforge
- otFont = fontforge.open(otfName)
- otFont.generate(self.generateOutputPath(f, "ttf"))
+ fonts = [OpenFont(ufo) for ufo in self.generatedFonts]
+ self.generatedFonts = []
+
+ log(">> Converting curves to quadratic")
+ # using a slightly higher max error (e.g. 0.0025 em), dots will have
+ # 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)
+ else:
+ for font in fonts:
+ fonts_to_quadratic([font], max_err_em=max_err, dump_stats=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)
def transformGlyphMembers(g, m):
@@ -274,30 +281,13 @@ 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,
+ kernWriter=RobotoKernWriter)
+ otf.save(destFile)
diff --git a/scripts/lib/fontbuild/alignpoints.py b/scripts/lib/fontbuild/alignpoints.py
index 1133716..e3bb539 100644
--- a/scripts/lib/fontbuild/alignpoints.py
+++ b/scripts/lib/fontbuild/alignpoints.py
@@ -13,9 +13,11 @@
# limitations under the License.
+import math
+
import numpy as np
from numpy.linalg import lstsq
-import math
+
def alignCorners(glyph, va, subsegments):
out = va.copy()
@@ -28,7 +30,7 @@ def alignCorners(glyph, va, subsegments):
# if seg.type == "line":
# subIndex = subsegmentIndex(i,j,subsegments)
# out[subIndex] = alignPoints(va[subIndex])
-
+
for i,c in enumerate(subsegments):
segmentCount = len(glyph.contours[i].segments)
n = len(c)
@@ -66,7 +68,7 @@ def alignCorners(glyph, va, subsegments):
def subsegmentIndex(contourIndex, segmentIndex, subsegments):
# This whole thing is so dumb. Need a better data model for subsegments
-
+
contourOffset = 0
for i,c in enumerate(subsegments):
if i == contourIndex:
@@ -77,10 +79,11 @@ def subsegmentIndex(contourIndex, segmentIndex, subsegments):
startIndex = subsegments[contourIndex][segmentIndex-1][0]
segmentCount = subsegments[contourIndex][segmentIndex][1]
endIndex = (startIndex + segmentCount + 1) % (n)
-
+
indices = np.array([(startIndex + i) % (n) + contourOffset for i in range(segmentCount + 1)])
return indices
+
def alignPoints(pts, start=None, end=None):
if start == None or end == None:
start, end = fitLine(pts)
@@ -89,6 +92,7 @@ def alignPoints(pts, start=None, end=None):
out[i] = nearestPoint(start, end, p)
return out
+
def findCorner(pp, nn):
if len(pp) < 4 or len(nn) < 4:
assert 0, "line too short to fit"
@@ -96,34 +100,36 @@ def findCorner(pp, nn):
nStart,nEnd = fitLine(nn)
prev = pEnd - pStart
next = nEnd - nStart
- # print int(np.arctan2(prev[1],prev[0]) / math.pi * 180),
+ # print int(np.arctan2(prev[1],prev[0]) / math.pi * 180),
# print int(np.arctan2(next[1],next[0]) / math.pi * 180)
# if lines are parallel, return simple average of end and start points
- if np.dot(prev / np.linalg.norm(prev),
+ if np.dot(prev / np.linalg.norm(prev),
next / np.linalg.norm(next)) > .999999:
# print "parallel lines", np.arctan2(prev[1],prev[0]), np.arctan2(next[1],next[0])
# print prev, next
assert 0, "parallel lines"
return lineIntersect(pStart, pEnd, nStart, nEnd)
+
def lineIntersect((x1,y1),(x2,y2),(x3,y3),(x4,y4)):
x12 = x1 - x2
x34 = x3 - x4
y12 = y1 - y2
y34 = y3 - y4
-
+
det = x12 * y34 - y12 * x34
if det == 0:
print "parallel!"
-
+
a = x1 * y2 - y1 * x2
b = x3 * y4 - y3 * x4
-
+
x = (a * x34 - b * x12) / det
y = (a * y34 - b * y12) / det
-
+
return (x,y)
+
def fitLineLSQ(pts):
"returns a line fit with least squares. Fails for vertical lines"
n = len(pts)
@@ -133,6 +139,7 @@ def fitLineLSQ(pts):
line = lstsq(a,pts[:,1])[0]
return line
+
def fitLine(pts):
"""returns a start vector and direction vector
Assumes points segments that already form a somewhat smooth line
@@ -147,7 +154,8 @@ def fitLine(pts):
direction = np.mean(a[1:-1], axis=0)
start = np.mean(pts[1:-1], axis=0)
return start, start+direction
-
+
+
def nearestPoint(a,b,c):
"nearest point to point c on line a_b"
magnitude = np.linalg.norm(b-a)
@@ -155,6 +163,7 @@ def nearestPoint(a,b,c):
raise Exception, "Line segment cannot be 0 length"
return (b-a) * np.dot((c-a) / magnitude, (b-a) / magnitude) + a
+
# pts = np.array([[1,1],[2,2],[3,3],[4,4]])
# pts2 = np.array([[1,0],[2,0],[3,0],[4,0]])
# print alignPoints(pts2, start = pts[0], end = pts[0]+pts[0])
diff --git a/scripts/lib/fontbuild/curveFitPen.py b/scripts/lib/fontbuild/curveFitPen.py
index 7c232c0..f7c0cae 100644
--- a/scripts/lib/fontbuild/curveFitPen.py
+++ b/scripts/lib/fontbuild/curveFitPen.py
@@ -17,25 +17,24 @@
__all__ = ["SubsegmentPen","SubsegmentsToCurvesPen", "segmentGlyph", "fitGlyph"]
+
from fontTools.pens.basePen import BasePen
-from fontTools.misc import bezierTools
-from robofab.pens.pointPen import AbstractPointPen
-from robofab.pens.adapterPens import PointToSegmentPen, GuessSmoothPointPen
import numpy as np
-from numpy.linalg import norm
from numpy import array as v
-from random import random
-
+from numpy.linalg import norm
+from robofab.pens.adapterPens import GuessSmoothPointPen
from robofab.pens.pointPen import BasePointToSegmentPen
+
+
class SubsegmentsToCurvesPointPen(BasePointToSegmentPen):
def __init__(self, glyph, subsegmentGlyph, subsegments):
BasePointToSegmentPen.__init__(self)
self.glyph = glyph
self.subPen = SubsegmentsToCurvesPen(None, glyph.getPen(), subsegmentGlyph, subsegments)
-
+
def setMatchTangents(self, b):
self.subPen.matchTangents = b
-
+
def _flushContour(self, segments):
#
# adapted from robofab.pens.adapterPens.rfUFOPointPen
@@ -56,13 +55,13 @@ class SubsegmentsToCurvesPointPen(BasePointToSegmentPen):
self.subPen.setLastSmooth(True)
if segmentType == 'line':
del segments[-1]
-
+
self.subPen.moveTo(movePt)
-
+
# do the rest of the segments
for segmentType, points in segments:
isSmooth = True in [smooth for pt, smooth, name, kwargs in points]
- pp = [pt for pt, smooth, name, kwargs in points]
+ pp = [pt for pt, smooth, name, kwargs in points]
if segmentType == "line":
assert len(pp) == 1
if isSmooth:
@@ -73,17 +72,18 @@ class SubsegmentsToCurvesPointPen(BasePointToSegmentPen):
assert len(pp) == 3
if isSmooth:
self.subPen.smoothCurveTo(*pp)
- else:
+ else:
self.subPen.curveTo(*pp)
elif segmentType == "qcurve":
assert 0, "qcurve not supported"
else:
assert 0, "illegal segmentType: %s" % segmentType
self.subPen.closePath()
-
+
def addComponent(self, glyphName, transform):
self.subPen.addComponent(glyphName, transform)
+
class SubsegmentsToCurvesPen(BasePen):
def __init__(self, glyphSet, otherPen, subsegmentGlyph, subsegments):
BasePen.__init__(self, None)
@@ -95,10 +95,10 @@ class SubsegmentsToCurvesPen(BasePen):
self.lastPoint = (0,0)
self.lastSmooth = False
self.nextSmooth = False
-
+
def setLastSmooth(self, b):
self.lastSmooth = b
-
+
def _moveTo(self, (x, y)):
self.contourIndex += 1
self.segmentIndex = 0
@@ -106,7 +106,7 @@ class SubsegmentsToCurvesPen(BasePen):
p = self.ssglyph.contours[self.contourIndex][0].points[0]
self.otherPen.moveTo((p.x, p.y))
self.lastPoint = (x,y)
-
+
def _lineTo(self, (x, y)):
self.segmentIndex += 1
index = self.subsegments[self.contourIndex][self.segmentIndex][0]
@@ -114,39 +114,39 @@ class SubsegmentsToCurvesPen(BasePen):
self.otherPen.lineTo((p.x, p.y))
self.lastPoint = (x,y)
self.lastSmooth = False
-
+
def smoothLineTo(self, (x, y)):
self.lineTo((x,y))
self.lastSmooth = True
-
+
def smoothCurveTo(self, (x1, y1), (x2, y2), (x3, y3)):
self.nextSmooth = True
self.curveTo((x1, y1), (x2, y2), (x3, y3))
self.nextSmooth = False
self.lastSmooth = True
-
- def _curveToOne(self, (x1, y1), (x2, y2), (x3, y3)):
+
+ def _curveToOne(self, (x1, y1), (x2, y2), (x3, y3)):
self.segmentIndex += 1
c = self.ssglyph.contours[self.contourIndex]
n = len(c)
startIndex = (self.subsegments[self.contourIndex][self.segmentIndex-1][0])
segmentCount = (self.subsegments[self.contourIndex][self.segmentIndex][1])
endIndex = (startIndex + segmentCount + 1) % (n)
-
+
indices = [(startIndex + i) % (n) for i in range(segmentCount + 1)]
points = np.array([(c[i].points[0].x, c[i].points[0].y) for i in indices])
prevPoint = (c[(startIndex - 1)].points[0].x, c[(startIndex - 1)].points[0].y)
nextPoint = (c[(endIndex) % n].points[0].x, c[(endIndex) % n].points[0].y)
prevTangent = prevPoint - points[0]
nextTangent = nextPoint - points[-1]
-
+
tangent1 = points[1] - points[0]
tangent3 = points[-2] - points[-1]
prevTangent /= np.linalg.norm(prevTangent)
nextTangent /= np.linalg.norm(nextTangent)
tangent1 /= np.linalg.norm(tangent1)
tangent3 /= np.linalg.norm(tangent3)
-
+
tangent1, junk = self.smoothTangents(tangent1, prevTangent, self.lastSmooth)
tangent3, junk = self.smoothTangents(tangent3, nextTangent, self.nextSmooth)
if self.matchTangents == True:
@@ -162,7 +162,7 @@ class SubsegmentsToCurvesPen(BasePen):
self.otherPen.curveTo((cp[1,0], cp[1,1]), (cp[2,0], cp[2,1]), (cp[3,0], cp[3,1]))
self.lastPoint = (x3, y3)
self.lastSmooth = False
-
+
def smoothTangents(self,t1,t2,forceSmooth = False):
if forceSmooth or (abs(t1.dot(t2)) > .95 and norm(t1-t2) > 1):
# print t1,t2,
@@ -170,15 +170,13 @@ class SubsegmentsToCurvesPen(BasePen):
t2 = -t1
# print t1,t2
return t1 / norm(t1), t2 / norm(t2)
-
-
+
def _closePath(self):
self.otherPen.closePath()
-
+
def _endPath(self):
self.otherPen.endPath()
-
-
+
def addComponent(self, glyphName, transformation):
self.otherPen.addComponent(glyphName, transformation)
@@ -189,10 +187,10 @@ class SubsegmentPointPen(BasePointToSegmentPen):
self.glyph = glyph
self.resolution = resolution
self.subPen = SubsegmentPen(None, glyph.getPen())
-
+
def getSubsegments(self):
return self.subPen.subsegments[:]
-
+
def _flushContour(self, segments):
#
# adapted from robofab.pens.adapterPens.rfUFOPointPen
@@ -210,9 +208,9 @@ class SubsegmentPointPen(BasePointToSegmentPen):
movePt, smooth, name, kwargs = points[-1]
if segmentType == 'line':
del segments[-1]
-
+
self.subPen.moveTo(movePt)
-
+
# do the rest of the segments
for segmentType, points in segments:
points = [pt for pt, smooth, name, kwargs in points]
@@ -227,12 +225,13 @@ class SubsegmentPointPen(BasePointToSegmentPen):
else:
assert 0, "illegal segmentType: %s" % segmentType
self.subPen.closePath()
-
+
def addComponent(self, glyphName, transform):
self.subPen.addComponent(glyphName, transform)
+
class SubsegmentPen(BasePen):
-
+
def __init__(self, glyphSet, otherPen, resolution=25):
BasePen.__init__(self,glyphSet)
self.resolution = resolution
@@ -240,7 +239,7 @@ class SubsegmentPen(BasePen):
self.subsegments = []
self.startContour = (0,0)
self.contourIndex = -1
-
+
def _moveTo(self, (x, y)):
self.contourIndex += 1
self.segmentIndex = 0
@@ -250,7 +249,7 @@ class SubsegmentPen(BasePen):
self.startContour = (x,y)
self.lastPoint = (x,y)
self.otherPen.moveTo((x,y))
-
+
def _lineTo(self, (x, y)):
count = self.stepsForSegment((x,y),self.lastPoint)
if count < 1:
@@ -262,7 +261,7 @@ class SubsegmentPen(BasePen):
y1 = self.lastPoint[1] + (y - self.lastPoint[1]) * i/float(count)
self.otherPen.lineTo((x1,y1))
self.lastPoint = (x,y)
-
+
def _curveToOne(self, (x1, y1), (x2, y2), (x3, y3)):
count = self.stepsForSegment((x3,y3),self.lastPoint)
if count < 2:
@@ -277,46 +276,53 @@ class SubsegmentPen(BasePen):
for i in range(count):
self.otherPen.lineTo((x[i],y[i]))
self.lastPoint = (x3,y3)
-
+
def _closePath(self):
if not (self.lastPoint[0] == self.startContour[0] and self.lastPoint[1] == self.startContour[1]):
self._lineTo(self.startContour)
+
+ # round values used by otherPen (a RoboFab SegmentToPointPen) to decide
+ # whether to delete duplicate points at start and end of contour
+ #TODO(jamesgk) figure out why we have to do this hack, then remove it
+ c = self.otherPen.contour
+ for i in [0, -1]:
+ c[i] = [[round(n, 5) for n in c[i][0]]] + list(c[i][1:])
+
self.otherPen.closePath()
-
+
def _endPath(self):
self.otherPen.endPath()
-
+
def addComponent(self, glyphName, transformation):
self.otherPen.addComponent(glyphName, transformation)
-
+
def stepsForSegment(self, p1, p2):
dist = np.linalg.norm(v(p1) - v(p2))
out = int(dist / self.resolution)
return out
-
+
def renderCurve(self,p,count):
curvePoints = []
t = 1.0 / float(count)
temp = t * t
-
+
f = p[0]
fd = 3 * (p[1] - p[0]) * t
fdd_per_2 = 3 * (p[0] - 2 * p[1] + p[2]) * temp
fddd_per_2 = 3 * (3 * (p[1] - p[2]) + p[3] - p[0]) * temp * t
-
+
fddd = fddd_per_2 + fddd_per_2
fdd = fdd_per_2 + fdd_per_2
fddd_per_6 = fddd_per_2 * (1.0 / 3)
-
+
for i in range(count):
f = f + fd + fdd_per_2 + fddd_per_6
fd = fd + fdd + fddd_per_2
fdd = fdd + fddd
fdd_per_2 = fdd_per_2 + fddd_per_2
curvePoints.append(f)
-
- return curvePoints
+ return curvePoints
def fitBezierSimple(pts):
@@ -363,14 +369,14 @@ def fitBezier(pts,tangent0=None,tangent3=None):
pout = pts.copy()
pout[:,0] -= (T[:,0] * pts[0,0]) + (T[:,3] * pts[-1,0])
pout[:,1] -= (T[:,0] * pts[0,1]) + (T[:,3] * pts[-1,1])
-
+
TT = np.zeros((n*2,4))
for i in range(n):
for j in range(2):
TT[i*2,j*2] = T[i,j+1]
TT[i*2+1,j*2+1] = T[i,j+1]
pout = pout.reshape((n*2,1),order="C")
-
+
if tangent0 != None and tangent3 != None:
tangentConstraintsT = np.array([
[tangent0[1], -tangent0[0], 0, 0],
@@ -414,4 +420,3 @@ if __name__ == '__main__':
[1,1]
])
print np.array(p.renderCurve(pts,10)) * 10
-
diff --git a/scripts/lib/fontbuild/instanceNames.py b/scripts/lib/fontbuild/instanceNames.py
index 281eb03..890b5a6 100644
--- a/scripts/lib/fontbuild/instanceNames.py
+++ b/scripts/lib/fontbuild/instanceNames.py
@@ -57,7 +57,7 @@ class InstanceNames:
f.info.versionMajor = version
f.info.versionMinor = versionMinor
f.info.year = self.year
- f.info.copyright = "Font data copyright %s %s" %(self.foundry, self.year)
+ #f.info.copyright = "Font data copyright %s %s" %(self.foundry, self.year)
f.info.trademark = "%s is a trademark of %s." %(self.longfamily, self.foundry)
f.info.openTypeNameDesigner = "Christian Robertson"
diff --git a/scripts/lib/fontbuild/italics.py b/scripts/lib/fontbuild/italics.py
index c889bd5..51d1f96 100644
--- a/scripts/lib/fontbuild/italics.py
+++ b/scripts/lib/fontbuild/italics.py
@@ -13,12 +13,18 @@
# limitations under the License.
+import math
+
from fontTools.misc.transform import Transform
-from robofab.world import RFont
-from time import clock
import numpy as np
-import math
-from alignpoints import alignCorners
+from numpy.linalg import norm
+from scipy.sparse.linalg import cg
+from scipy.ndimage.filters import gaussian_filter1d as gaussian
+from scipy.cluster.vq import vq, whiten
+
+from fontbuild.alignpoints import alignCorners
+from fontbuild.curveFitPen import fitGlyph, segmentGlyph
+
def italicizeGlyph(f, g, angle=10, stemWidth=185):
unic = g.unicode #save unicode
@@ -33,11 +39,11 @@ def italicizeGlyph(f, g, angle=10, stemWidth=185):
m = Transform(1, 0, slope, 1, 0, 0)
xoffset, junk = m.transformPoint((0, MEAN_YCENTER))
m = Transform(.97, 0, slope, 1, xoffset, 0)
-
+
if len(glyph) > 0:
g2 = italicize(f[g.name], angle, xoffset=xoffset, stemWidth=stemWidth)
f.insertGlyph(g2, g.name)
-
+
transformFLGlyphMembers(f[g.name], m)
if unic > 0xFFFF: #restore unicode
@@ -47,18 +53,20 @@ def italicizeGlyph(f, g, angle=10, stemWidth=185):
def italicize(glyph, angle=12, stemWidth=180, xoffset=-50):
CURVE_CORRECTION_WEIGHT = .03
CORNER_WEIGHT = 10
+
+ # decompose the glyph into smaller segments
ga, subsegments = segmentGlyph(glyph,25)
va, e = glyphToMesh(ga)
n = len(va)
grad = mapEdges(lambda a,(p,n): normalize(p-a), va, e)
cornerWeights = mapEdges(lambda a,(p,n): normalize(p-a).dot(normalize(a-n)), grad, e)[:,0].reshape((-1,1))
smooth = np.ones((n,1)) * CURVE_CORRECTION_WEIGHT
-
+
controlPoints = findControlPointsInMesh(glyph, va, subsegments)
smooth[controlPoints > 0] = 1
smooth[cornerWeights < .6] = CORNER_WEIGHT
# smooth[cornerWeights >= .9999] = 1
-
+
out = va.copy()
hascurves = False
for c in glyph.contours:
@@ -73,20 +81,25 @@ def italicize(glyph, angle=12, stemWidth=180, xoffset=-50):
# out = copyMeshDetails(va, out, e, 6)
else:
outCorrected = out
+
+ # create a transform for italicizing
normals = edgeNormals(out, e)
center = va + normals * stemWidth * .4
if stemWidth > 130:
center[:, 0] = va[:, 0] * .7 + center[:,0] * .3
centerSkew = skewMesh(center.dot(np.array([[.97,0],[0,1]])), angle * .9)
+
+ # apply the transform
out = outCorrected + (centerSkew - center)
out[:,1] = outCorrected[:,1]
-
+
+ # make some corrections
smooth = np.ones((n,1)) * .1
out = alignCorners(glyph, out, subsegments)
out = copyMeshDetails(skewMesh(va, angle), out, e, 7, smooth=smooth)
# grad = mapEdges(lambda a,(p,n): normalize(p-a), skewMesh(outCorrected, angle*.9), e)
# out = recompose(out, grad, e, smooth=smooth)
-
+
out = skewMesh(out, angle * .1)
out[:,0] += xoffset
# out[:,1] = outCorrected[:,1]
@@ -95,6 +108,8 @@ def italicize(glyph, angle=12, stemWidth=180, xoffset=-50):
# gOut.width *= .97
# gOut.width += 10
# return gOut
+
+ # recompose the glyph into original segments
return fitGlyph(glyph, gOut, subsegments)
@@ -111,13 +126,7 @@ def transformFLGlyphMembers(g, m, transformAnchors = True):
a.x = aa[0]
# a.x,a.y = (aa[0] - p[0], aa[1] - p[1])
# a.x = a.x - m[4]
-
-from curveFitPen import fitGlyph,segmentGlyph
-from numpy.linalg import norm
-from scipy.sparse.linalg import cg
-from scipy.ndimage.filters import gaussian_filter1d as gaussian
-from scipy.cluster.vq import vq, kmeans2, whiten
def glyphToMesh(g):
points = []
@@ -132,6 +141,7 @@ def glyphToMesh(g):
offset += len(c)
return np.array(points), edges
+
def meshToGlyph(points, g):
g1 = g.copy()
j = 0
@@ -144,6 +154,7 @@ def meshToGlyph(points, g):
j += 1
return g1
+
def quantizeGradient(grad, book=None):
if book == None:
book = np.array([(1,0),(0,1),(0,-1),(-1,0)])
@@ -153,6 +164,7 @@ def quantizeGradient(grad, book=None):
out[i] = normalize(v)
return out
+
def findControlPointsInMesh(glyph, va, subsegments):
controlPointIndices = np.zeros((len(va),1))
index = 0
@@ -166,7 +178,6 @@ def findControlPointsInMesh(glyph, va, subsegments):
return controlPointIndices
-
def recompose(v, grad, e, smooth=1, P=None, distance=None):
n = len(v)
if distance == None:
@@ -183,6 +194,7 @@ def recompose(v, grad, e, smooth=1, P=None, distance=None):
out[:,i] = cg(P, f[:,i])[0]
return out
+
def mP(v,e):
n = len(v)
M = np.zeros((n,n))
@@ -193,18 +205,21 @@ def mP(v,e):
M[i,i] = 2
return M
+
def normalize(v):
n = np.linalg.norm(v)
if n == 0:
return v
return v/n
+
def mapEdges(func,v,e,*args):
b = v.copy()
for i, edges in e.iteritems():
b[i] = func(v[i], [v[j] for j in edges], *args)
return b
+
def getNormal(a,b,c):
"Assumes TT winding direction"
p = np.roll(normalize(b - a), 1)
@@ -214,19 +229,23 @@ def getNormal(a,b,c):
# print p, n, normalize((p + n) * .5)
return normalize((p + n) * .5)
+
def edgeNormals(v,e):
"Assumes a mesh where each vertex has exactly least two edges"
return mapEdges(lambda a,(p,n) : getNormal(a,p,n),v,e)
+
def rangePrevNext(count):
c = np.arange(count,dtype=int)
r = np.vstack((c, np.roll(c, 1), np.roll(c, -1)))
return r.T
+
def skewMesh(v,angle):
slope = np.tanh([math.pi * angle / 180])
return v.dot(np.array([[1,0],[slope,1]]))
+
def labelConnected(e):
label = 0
labels = np.zeros((len(e),1))
@@ -236,6 +255,7 @@ def labelConnected(e):
label += 1
return labels
+
def copyGradDetails(a,b,e,scale=15):
n = len(a)
labels = labelConnected(e)
@@ -245,6 +265,7 @@ def copyGradDetails(a,b,e,scale=15):
out[mask,:] = gaussian(b[mask,:], scale, mode="wrap", axis=0) + a[mask,:] - gaussian(a[mask,:], scale, mode="wrap", axis=0)
return out
+
def copyMeshDetails(va,vb,e,scale=5,smooth=.01):
gradA = mapEdges(lambda a,(p,n): normalize(p-a), va, e)
gradB = mapEdges(lambda a,(p,n): normalize(p-a), vb, e)
@@ -253,8 +274,6 @@ def copyMeshDetails(va,vb,e,scale=5,smooth=.01):
return recompose(vb, grad, e, smooth=smooth)
-
-
def condenseGlyph(glyph, scale=.8, stemWidth=185):
ga, subsegments = segmentGlyph(glyph, 25)
va, e = glyphToMesh(ga)
@@ -273,7 +292,7 @@ def condenseGlyph(glyph, scale=.8, stemWidth=185):
# cornerWeights = mapEdges(lambda a,(p,n): normalize(p-a).dot(normalize(a-n)), grad, e)[:,0].reshape((-1,1))
# smooth = np.ones((n,1)) * .1
# smooth[cornerWeights < .6] = 10
- #
+ #
# grad2 = quantizeGradient(grad).astype(float)
# grad2 = copyGradDetails(grad, grad2, e, scale=10)
# grad2 = mapEdges(lambda a,e: normalize(a), grad2, e)
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..395e537 100755
--- a/scripts/lib/fontbuild/markFeature.py
+++ b/scripts/lib/fontbuild/markFeature.py
@@ -13,105 +13,38 @@
# 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] + " <anchor " + `int(acc[1])` + " " + `int(acc[2])` + "> " + acc_class +";\n"
-
- for base in base_g_list:
- txt += " pos base " + base[0] + " <anchor " + `int(base[1])` + " " + `int(base[2])` + "> mark " + acc_class + ";\n"
- if (lookAliases):
- base2 = GetAliaseName(base[0])
- if (None == base2):
- continue
- txt += " pos base " + base2 + " <anchor " + `int(base[1])` + " " + `int(base[2])` + "> 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"]]
+
+
+class RobotoKernWriter(KernFeatureWriter):
+ leftFeaClassRe = r"@_(.+)_L$"
+ rightFeaClassRe = r"@_(.+)_R$"
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] + " <anchor " + `int(acc[1])` + " " + `int(acc[2])` + "> " + acc_class +";\n"
-
- for base in base_g_list:
- txt += " pos mark " + base[0] + " <anchor " + `int(base[1])` + " " + `int(base[2])` + "> 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)
diff --git a/scripts/run_android_tests.py b/scripts/run_android_tests.py
index 3751670..f6a1f46 100755
--- a/scripts/run_android_tests.py
+++ b/scripts/run_android_tests.py
@@ -19,12 +19,32 @@
import unittest
from nototools.unittests import font_tests
+import run_general_tests
+
FONTS = font_tests.load_fonts(
['out/android/*.ttf'],
expected_count=18)
+class TestMetaInfo(run_general_tests.TestMetaInfo):
+ """Bugs:
+ https://github.com/google/roboto/issues/142
+ """
+
+ loaded_fonts = FONTS
+ mark_heavier_as_bold = True
+
+
+class TestNames(run_general_tests.TestNames):
+ """Bugs:
+ https://github.com/google/roboto/issues/37
+ """
+
+ loaded_fonts = FONTS
+ mark_heavier_as_bold = True
+
+
class TestVerticalMetrics(font_tests.TestVerticalMetrics):
loaded_fonts = FONTS
test_glyphs_ymin_ymax = None
diff --git a/scripts/run_general_tests.py b/scripts/run_general_tests.py
index ddfae17..494651d 100755
--- a/scripts/run_general_tests.py
+++ b/scripts/run_general_tests.py
@@ -24,7 +24,7 @@ from nototools.unittests import font_tests
import roboto_data
FONTS = font_tests.load_fonts(
- ['hinted/*.ttf'],
+ ['out/RobotoTTF/*.ttf', 'out/RobotoCondensedTTF/*.ttf'],
expected_count=18)
UFOS = font_tests.load_fonts(
@@ -44,12 +44,13 @@ class TestItalicAngle(font_tests.TestItalicAngle):
class TestMetaInfo(font_tests.TestMetaInfo):
"""Bugs:
+ https://github.com/google/roboto/issues/142
https://code.google.com/a/google.com/p/roboto/issues/detail?id=8
https://code.google.com/a/google.com/p/roboto/issues/detail?id=29
"""
loaded_fonts = FONTS
- mark_heavier_as_bold = True
+ mark_heavier_as_bold = False
test_us_weight = None
#expected_version = '2.' + roboto_data.get_build_number()
@@ -60,6 +61,20 @@ class TestMetaInfo(font_tests.TestMetaInfo):
expected_os2_achVendID = 'GOOG'
+class TestNames(font_tests.TestNames):
+ """Bugs:
+ https://github.com/google/roboto/issues/37
+ """
+
+ loaded_fonts = FONTS
+ family_name = 'Roboto'
+ mark_heavier_as_bold = False
+ expected_copyright = 'Copyright 2011 Google Inc. All Rights Reserved.'
+
+ def expected_unique_id(self, family, style):
+ return 'Google:%s:2015' % family
+
+
class TestDigitWidths(font_tests.TestDigitWidths):
loaded_fonts = FONTS
@@ -96,8 +111,22 @@ class TestVerticalMetrics(font_tests.TestVerticalMetrics):
class TestGlyphAreas(font_tests.TestGlyphAreas):
- loaded_fonts = UFOS
- masters = UFO_MASTERS
+ master_weights_to_test = ['Thin', 'Bold']
+ instance_weights_to_test = ['Thin', 'Regular', 'Bold']
+ exclude = ['Condensed', 'Italic']
+
+ master_glyph_sets = [
+ f.replace('_', '-') for f in UFO_MASTERS[0]], UFO_MASTERS[1]
+ instance_glyph_sets = FONTS[0], [f.getGlyphSet() for f in FONTS[1]]
+
+ master_glyphs_to_test = UFO_MASTERS[1][0].keys()
+ instance_glyphs_to_test = FONTS[1][0].getGlyphOrder()
+
+ #TODO maybe fix masters so that whitelisting isn't necessary
+ whitelist = [
+ 'uni0488', # offset 20 units b/w masters, interpolated points are off
+ 'uni2050' # has flipped component, so contour is backwards in master
+ ]
if __name__ == '__main__':
diff --git a/scripts/run_web_tests.py b/scripts/run_web_tests.py
index 4f051bf..5d3fd34 100755
--- a/scripts/run_web_tests.py
+++ b/scripts/run_web_tests.py
@@ -53,8 +53,8 @@ class TestNames(font_tests.TestNames):
mark_heavier_as_bold = True
expected_copyright = 'Copyright 2011 Google Inc. All Rights Reserved.'
- def expected_unique_id(self, full_name):
- return full_name
+ def expected_unique_id(self, family, style):
+ return family + ' ' + style
class TestDigitWidths(font_tests.TestDigitWidths):
diff --git a/scripts/touchup_for_android.py b/scripts/touchup_for_android.py
index 187a432..e13adea 100755
--- a/scripts/touchup_for_android.py
+++ b/scripts/touchup_for_android.py
@@ -56,7 +56,7 @@ def apply_android_specific_fixes(font):
hhea.lineGap = 0
# Remove combining keycap and the arrows from the cmap table:
- # https://code.google.com/a/google.com/p/roboto/issues/detail?id=52
+ # https://github.com/google/roboto/issues/99
font_data.delete_from_cmap(font, [
0x20E3, # COMBINING ENCLOSING KEYCAP
0x2191, # UPWARDS ARROW
@@ -68,6 +68,18 @@ def apply_android_specific_fixes(font):
if table in font:
del font[table]
+ # Set bold bits for Black (macStyle bit 0, fsSelection bit 5, subfamily)
+ name_records = font_data.get_name_records(font)
+ family_name = name_records[1]
+ subfam_name = name_records[2]
+ if family_name.endswith('Black'):
+ font['head'].macStyle |= (1 << 0)
+ font['OS/2'].fsSelection |= (1 << 5)
+ font['OS/2'].fsSelection &= ~(1 << 6)
+ new_subfam_name = (
+ ('Bold ' + subfam_name) if subfam_name != 'Regular' else 'Bold')
+ font_data.set_name_record(font, 2, new_subfam_name)
+
def correct_font(source_font_name, target_font_name):
"""Corrects metrics and other meta information."""
diff --git a/scripts/touchup_for_web.py b/scripts/touchup_for_web.py
index 3e29e58..7879d9e 100755
--- a/scripts/touchup_for_web.py
+++ b/scripts/touchup_for_web.py
@@ -47,12 +47,6 @@ def apply_web_specific_fixes(font, family_name):
family_name += ' Condensed'
full_name = family_name + ' ' + subfamily_name
- # macStyle
- bold = subfamily_name.startswith(('Bold', 'Black'))
- italic = subfamily_name.endswith('Italic')
- macStyle = (italic << 1) | bold
- font['head'].macStyle = macStyle
-
# Family, subfamily names
font_data.set_name_record(font, 16, family_name)
style_map = ['Regular', 'Bold', 'Italic', 'Bold Italic']
diff --git a/src/v2/README.md b/src/v2/README.md
index b2362d6..892d3b3 100644
--- a/src/v2/README.md
+++ b/src/v2/README.md
@@ -18,8 +18,12 @@ wine [path-to-vfb2ufo]/exe/vfb2ufo.exe [roboto]/src/v2/Roboto_Bold.vfb
The converter should work both ways, so it is possible to convert altered UFOs
back into VFBs which can be opened in FontLab.
-**Note:** There is currently an issue when converting via vfb2ufo, in which
+### Notes
+There is currently an issue when converting via vfb2ufo, in which
some anchors are dropped from glyphs. For now it is also necessary to run the
script `get_dropped_anchors.py` through FontLab after updating the VFBs, in
order to extract these dropped anchors into an external resource which is then
re-incorporated into the masters during the build.
+
+vfb2ufo may output UFOs with bogus timestamps. Verify the openTypeNameCreated
+field in the output `fontinfo.plist` files.
diff --git a/src/v2/Roboto_Bold.ufo/fontinfo.plist b/src/v2/Roboto_Bold.ufo/fontinfo.plist
index 8e4af9c..c0a6068 100644
--- a/src/v2/Roboto_Bold.ufo/fontinfo.plist
+++ b/src/v2/Roboto_Bold.ufo/fontinfo.plist
@@ -7,7 +7,7 @@
<key>capHeight</key>
<integer>1456</integer>
<key>copyright</key>
- <string>Copyright 2014 Google Inc. All Rights Reserved.</string>
+ <string>Copyright 2011 Google Inc. All Rights Reserved.</string>
<key>descender</key>
<integer>-555</integer>
<key>familyName</key>
@@ -15,7 +15,7 @@
<key>italicAngle</key>
<integer>0</integer>
<key>openTypeHeadCreated</key>
- <string>2074/09/13 05:29:33</string>
+ <string>2008/09/12 12:29:34</string>
<key>openTypeHeadFlags</key>
<array>
</array>
diff --git a/src/v2/Roboto_Bold.vfb b/src/v2/Roboto_Bold.vfb
index 7c54196..5f81153 100644
--- a/src/v2/Roboto_Bold.vfb
+++ b/src/v2/Roboto_Bold.vfb
Binary files differ
diff --git a/src/v2/Roboto_Regular.ufo/fontinfo.plist b/src/v2/Roboto_Regular.ufo/fontinfo.plist
index 82b4505..8f7dd23 100644
--- a/src/v2/Roboto_Regular.ufo/fontinfo.plist
+++ b/src/v2/Roboto_Regular.ufo/fontinfo.plist
@@ -7,7 +7,7 @@
<key>capHeight</key>
<integer>1456</integer>
<key>copyright</key>
- <string>Copyright 2014 Google Inc. All Rights Reserved.</string>
+ <string>Copyright 2011 Google Inc. All Rights Reserved.</string>
<key>descender</key>
<integer>-555</integer>
<key>familyName</key>
@@ -15,7 +15,7 @@
<key>italicAngle</key>
<integer>0</integer>
<key>openTypeHeadCreated</key>
- <string>2074/09/13 05:29:33</string>
+ <string>2008/09/12 12:29:34</string>
<key>openTypeHeadFlags</key>
<array>
</array>
diff --git a/src/v2/Roboto_Regular.vfb b/src/v2/Roboto_Regular.vfb
index 252e7cb..8c9d12b 100644
--- a/src/v2/Roboto_Regular.vfb
+++ b/src/v2/Roboto_Regular.vfb
Binary files differ
diff --git a/src/v2/Roboto_Thin.ufo/fontinfo.plist b/src/v2/Roboto_Thin.ufo/fontinfo.plist
index c779e7e..e1ba393 100644
--- a/src/v2/Roboto_Thin.ufo/fontinfo.plist
+++ b/src/v2/Roboto_Thin.ufo/fontinfo.plist
@@ -7,7 +7,7 @@
<key>capHeight</key>
<integer>1456</integer>
<key>copyright</key>
- <string>Copyright 2014 Google Inc. All Rights Reserved.</string>
+ <string>Copyright 2011 Google Inc. All Rights Reserved.</string>
<key>descender</key>
<integer>-555</integer>
<key>familyName</key>
@@ -15,7 +15,7 @@
<key>italicAngle</key>
<integer>0</integer>
<key>openTypeHeadCreated</key>
- <string>2074/09/13 05:29:33</string>
+ <string>2008/09/12 12:29:34</string>
<key>openTypeHeadFlags</key>
<array>
</array>
diff --git a/src/v2/Roboto_Thin.vfb b/src/v2/Roboto_Thin.vfb
index d3cf141..ee8c2f6 100644
--- a/src/v2/Roboto_Thin.vfb
+++ b/src/v2/Roboto_Thin.vfb
Binary files differ
diff --git a/third_party/fontcrunch/Makefile b/third_party/fontcrunch/Makefile
index 2090d39..3a073ec 100644
--- a/third_party/fontcrunch/Makefile
+++ b/third_party/fontcrunch/Makefile
@@ -20,7 +20,7 @@ OPT = $(patsubst %.bez, %.bezopt, $(SRC))
dummy: $(OPT)
quadopt: quadopt.cc
- $(CXX) $< -std=c++0x -O3 -o $@
+ $(CXX) $< -std=c++0x -O3 -Werror -Wall -o $@
%.bezopt: %.bez quadopt
./quadopt $< $@
@@ -29,4 +29,4 @@ clean:
rm -f quadopt
find . -name '*.bez' -delete
find . -name '*.bezopt' -delete
- rmdir ?? \ No newline at end of file
+ rmdir ??
diff --git a/third_party/fontcrunch/quadopt.cc b/third_party/fontcrunch/quadopt.cc
index 59f3523..a76da84 100644
--- a/third_party/fontcrunch/quadopt.cc
+++ b/third_party/fontcrunch/quadopt.cc
@@ -119,8 +119,8 @@ class ArclenFunctor {
public:
ArclenFunctor(const Quad& q)
: dx0(2 * (q.p[1].x - q.p[0].x))
- , dx1(2 * (q.p[2].x - q.p[1].x))
, dy0(2 * (q.p[1].y - q.p[0].y))
+ , dx1(2 * (q.p[2].x - q.p[1].x))
, dy1(2 * (q.p[2].y - q.p[1].y)) { }
void operator()(double dydx[1], double t, const double y[1]) {
Point p(deriv(t));
@@ -224,7 +224,6 @@ Point Thetas::dir(double s) const {
// L1 angle norm, 2, L2 angle norm, 0.05
// L1 distance norm, 200
-double penalty = 1;
double dist_factor = .005;
double angle_factor = 5;
@@ -272,7 +271,9 @@ private:
// of angle mismatch
double measureQuad(const Thetas& curve, double s0, double s1, const Quad& q) {
ArclenFunctor derivs(q);
- double ss = (s1 - s0) / q.arclen();
+ double ss = 0;
+ if (q.arclen() != 0)
+ ss = (s1 - s0) / q.arclen();
MeasureFunctor err(curve, s0, ss, derivs, q);
const int n = 10;
double dt = 1./n;
@@ -293,13 +294,13 @@ struct Break {
};
struct Statelet {
- void combine(const Statelet* prev, double score, Quad q);
+ void combine(const Statelet* prev, double score, Quad q, double penalty);
const Statelet* prev;
double score;
Quad q;
};
-void Statelet::combine(const Statelet* newprev, double newscore, Quad newq) {
+void Statelet::combine(const Statelet* newprev, double newscore, Quad newq, double penalty) {
prev = newprev;
double pmul = 2;
if (newq.isLine()) {
@@ -313,18 +314,18 @@ void Statelet::combine(const Statelet* newprev, double newscore, Quad newq) {
}
struct State {
- void combine(const State* prev, double score, Quad q);
+ void combine(const State* prev, double score, Quad q, double penalty);
vector<Statelet> sts;
bool init;
};
-void State::combine(const State* prev, double score, Quad q) {
+void State::combine(const State* prev, double score, Quad q, double penalty) {
const Statelet* prevsl = prev->sts.empty() ? 0 : &prev->sts[0];
if (prevsl == 0 && !prev->init) {
return;
}
Statelet sl;
- sl.combine(prevsl, score, q);
+ sl.combine(prevsl, score, q, penalty);
if (sts.empty()) {
sts.push_back(sl);
} else {
@@ -379,7 +380,7 @@ void findBreaks(vector<Break>* breaks, const Thetas& curve) {
bool intersect(Point* result, Point p0, Point dir0, Point p1, Point dir1) {
double det = dir0.x * dir1.y - dir0.y * dir1.x;
- if (std::abs(det) < 1e-6) return false;
+ if (std::abs(det) < 1e-6 || std::isnan(det)) return false;
det = 1 / det;
double a = p0.y * dir0.x - p0.x * dir0.y;
double b = p1.y * dir1.x - p1.x * dir1.y;
@@ -389,47 +390,47 @@ bool intersect(Point* result, Point p0, Point dir0, Point p1, Point dir1) {
}
void tryQuad(const State* prev, State* st, const Thetas& curve,
- const Break& bk0, const Break& bk1, const Quad& q) {
+ const Break& bk0, const Break& bk1, const Quad& q, double penalty) {
double score = measureQuad(curve, bk0.s, bk1.s, q);
- st->combine(prev, score, q);
+ st->combine(prev, score, q, penalty);
}
void tryLineQuad(const State* prev, State* st, const Thetas& curve,
- const Break& bk0, const Break& bk1) {
+ const Break& bk0, const Break& bk1, double penalty) {
if (isInt(bk0.xy.x) && isInt(bk0.xy.y)) {
Quad line(bk0.xy, lerp(0.5, bk0.xy, bk1.xy), bk1.xy);
- tryQuad(prev, st, curve, bk0, bk1, line);
+ tryQuad(prev, st, curve, bk0, bk1, line, penalty);
}
Point pmid;
if (intersect(&pmid, bk0.xy, bk0.dir, bk1.xy, bk1.dir)) {
Quad q(bk0.xy, round(pmid), bk1.xy);
if (okForHalf(prev, q)) {
- tryQuad(prev, st, curve, bk0, bk1, q);
+ tryQuad(prev, st, curve, bk0, bk1, q, penalty);
}
}
}
-vector<Quad> optimize(const Thetas& curve) {
+vector<Quad> optimize(const Thetas& curve, double penalty=1) {
vector<Break> breaks;
findBreaks(&breaks, curve);
int n = breaks.size() - 1;
vector<State> states;
states.resize(n + 1);
states[0].init = true;
- tryLineQuad(&states[0], &states[n], curve, breaks[0], breaks[n]);
+ tryLineQuad(&states[0], &states[n], curve, breaks[0], breaks[n], penalty);
if (states[n].sts[0].score <= 3 * penalty) {
goto done;
}
for (int i = 1; i < n; i++) {
- tryLineQuad(&states[0], &states[i], curve, breaks[0], breaks[i]);
- tryLineQuad(&states[i], &states[n], curve, breaks[i], breaks[n]);
+ tryLineQuad(&states[0], &states[i], curve, breaks[0], breaks[i], penalty);
+ tryLineQuad(&states[i], &states[n], curve, breaks[i], breaks[n], penalty);
}
if (states[n].sts[0].score <= 4 * penalty) {
goto done;
}
for (int i = 1; i <= n; i++) {
for (int j = i - 1; j >= 0; j--) {
- tryLineQuad(&states[j], &states[i], curve, breaks[j], breaks[i]);
+ tryLineQuad(&states[j], &states[i], curve, breaks[j], breaks[i], penalty);
}
}
done:
@@ -447,9 +448,8 @@ void readBzs(vector<Quad>* result, std::istream& is) {
result->push_back(Quad(Point(x0, y0), Point(x1, y1), Point(x2, y2)));
}
// Round the endpoints, they must be on integers
- (*result)[0].p[0] = round((*result)[0].p[0]);
- Quad* lastq = &(*result)[(*result).size()];
- lastq->p[2] = round(lastq->p[2]);
+ result->front().p[0] = round(result->front().p[0]);
+ result->back().p[2] = round(result->back().p[2]);
}
int main(int argc, char** argv) {