summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--scripts/build-v2.py5
-rwxr-xr-xscripts/force_yminmax.py2
-rw-r--r--scripts/lib/fontbuild/Build.py5
-rw-r--r--scripts/lib/fontbuild/anchors.py33
-rw-r--r--scripts/lib/fontbuild/generateGlyph.py42
-rw-r--r--scripts/lib/fontbuild/instanceNames.py27
-rw-r--r--scripts/lib/fontbuild/italics.py5
-rwxr-xr-xscripts/lib/fontbuild/markFeature.py67
-rwxr-xr-xscripts/lib/fontbuild/mkmkFeature.py16
-rwxr-xr-xscripts/run_android_tests.py141
-rwxr-xr-xscripts/touchup_for_android.py168
11 files changed, 433 insertions, 78 deletions
diff --git a/scripts/build-v2.py b/scripts/build-v2.py
index 0c9bfe0..75f9f93 100644
--- a/scripts/build-v2.py
+++ b/scripts/build-v2.py
@@ -49,7 +49,8 @@ def condenseFont(font, scale=.8, stemWidth=185):
m = Transform(xscale, 0, 0, 1, 20, 0)
g.transform(m)
transformFLGlyphMembers(g,m,transformAnchors=False)
- g.width += 40
+ if g.width != 0:
+ g.width += 40
return f
@@ -75,7 +76,7 @@ proj.generateFont(Mix([rg,bd], RPoint(1.125, 1.0)),"%s/Black/Bold/Bk"%FAMILYNAME
proj.generateFont(th.font,"%s/Thin Italic/Italic/Th"%FAMILYNAME, italic=True, stemWidth=80)
proj.generateFont(Mix([th,rg], 0.45),"%s/Light Italic/Italic/Lt"%FAMILYNAME, italic=True, stemWidth=120)
proj.generateFont(Mix([th,rg], RPoint(0.90, 0.92)),"%s/Italic/Italic/Rg"%FAMILYNAME, italic=True, stemWidth=185)
-proj.generateFont(Mix([rg,bd], 0.35),"%s/Medium Italic/Bold Italic/Lt"%FAMILYNAME, italic=True, stemWidth=230)
+proj.generateFont(Mix([rg,bd], 0.35),"%s/Medium Italic/Italic/Lt"%FAMILYNAME, italic=True, stemWidth=230)
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, italic=True, stemWidth=290)
diff --git a/scripts/force_yminmax.py b/scripts/force_yminmax.py
index 36420b1..454414c 100755
--- a/scripts/force_yminmax.py
+++ b/scripts/force_yminmax.py
@@ -1,5 +1,5 @@
#!/usr/bin/python
-"""Post-subset changes for Roboto."""
+"""Post-subset changes for Roboto for Android."""
import sys
diff --git a/scripts/lib/fontbuild/Build.py b/scripts/lib/fontbuild/Build.py
index 5c6ffca..0e1e35d 100644
--- a/scripts/lib/fontbuild/Build.py
+++ b/scripts/lib/fontbuild/Build.py
@@ -29,7 +29,7 @@ class FontProject:
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()
+ #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("#")])
@@ -126,7 +126,8 @@ class FontProject:
elif g.name != ".notdef":
italicizeGlyph(f, g, 10, stemWidth=stemWidth)
removeGlyphOverlap(g)
- g.width += 10
+ if g.width != 0:
+ g.width += 10
if swapSuffixes != None:
for swap in swapSuffixes:
diff --git a/scripts/lib/fontbuild/anchors.py b/scripts/lib/fontbuild/anchors.py
index d82ffa7..fe3e280 100644
--- a/scripts/lib/fontbuild/anchors.py
+++ b/scripts/lib/fontbuild/anchors.py
@@ -13,6 +13,37 @@ def getAnchorByName(g,anchorName):
return a
+def moveMarkAnchors(f, g, anchorName, accentName, dx, dy):
+ if "top"==anchorName:
+ anchors = f[accentName].anchors
+ for anchor in anchors:
+ if "mkmktop_acc" == anchor.name:
+ anchor2 = Anchor()
+ #print anchor.x, dx, anchor.y, dy
+ anchor2.name = "top"
+ anchor2.x = anchor.x + int(dx)
+ anchor2.y = anchor.y + int(dy)
+ g.anchors.append(anchor2)
+
+ elif "bottom"==anchorName:
+ anchors = f[accentName].anchors
+ for anchor in anchors:
+ if "mkmkbottom_acc" == anchor.name:
+ for n in range(len(g.anchors)):
+ if g.anchors[n].name == "bottom":
+ del g.anchors[n]
+ break
+ anchor2 = Anchor()
+ #print anchor.x, dx, anchor.y, dy
+ anchor2.name = "bottom"
+ anchor2.x = anchor.x + int(dx)
+ anchor2.y = anchor.y + int(dy)
+ for anc in anchors:
+ if "top" == anc.name:
+ anchor2.x = anc.x + int(dx)
+ g.anchors.append(anchor2)
+
+
def alignComponentToAnchor(f,glyphName,baseName,accentName,anchorName):
g = getGlyph(glyphName,f)
base = getGlyph(baseName,f)
@@ -26,6 +57,8 @@ def alignComponentToAnchor(f,glyphName,baseName,accentName,anchorName):
offset = (a1.x - a2.x, a1.y - a2.y)
c = getComponentByName(f, g, accentName)
c.offset = offset
+ moveMarkAnchors(f, g, anchorName, accentName, offset.x, offset.y)
+
def alignComponentsToAnchors(f,glyphName,baseName,accentNames):
for a in accentNames:
diff --git a/scripts/lib/fontbuild/generateGlyph.py b/scripts/lib/fontbuild/generateGlyph.py
index 4794fd8..4702631 100644
--- a/scripts/lib/fontbuild/generateGlyph.py
+++ b/scripts/lib/fontbuild/generateGlyph.py
@@ -16,6 +16,43 @@ def parseComposite(composite):
return (glyphName, baseName, accentNames, offset)
+def copyMarkAnchors(f, g, srcname, width):
+ anchors = f[srcname].anchors
+ for anchor in anchors:
+ if "top_dd" == anchor.name:
+ anchor1 = Anchor(anchor)
+ anchor1.x += width
+ g.anchors.append(anchor1)
+ if "bottom_dd" == anchor.name:
+ anchor1 = Anchor(anchor)
+ anchor1.x += width
+ g.anchors.append(anchor1)
+ if "top0315" == anchor.name:
+ anchor1 = Anchor(anchor)
+ anchor1.x += width
+ g.anchors.append(anchor1)
+ if "top" == anchor.name:
+ if g.unicode == None:
+ continue
+ if g.unicode > 0x02B0:
+ continue
+ parenttop_present = 0
+ for anc in g.anchors:
+ if anc.name == "parent_top":
+ parenttop_present = 1
+ if parenttop_present:
+ continue
+ anchor1 = Anchor(anchor)
+ anchor1.name = "parent_top"
+# anchor1.x += width
+ g.anchors.append(anchor1)
+
+ # if "rhotichook" == anchor.name:
+ # anchor1 = Anchor(anchor)
+ # anchor1.x += width
+ # g.anchors.append(anchor1)
+
+
def generateGlyph(f,gname,glyphList={}):
glyphName, baseName, accentNames, offset = parseComposite(gname)
@@ -34,11 +71,12 @@ def generateGlyph(f,gname,glyphList={}):
"anchor not found in glyph '%s'" % (gname, e, baseName))
return
g = f[glyphName]
- if len(accentNames) > 0:
- alignComponentsToAnchors(f, glyphName, baseName, accentNames)
+ 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))
+ if len(accentNames) > 0:
+ alignComponentsToAnchors(f, glyphName, baseName, accentNames)
else:
print ("Existing glyph '%s' found in font, ignoring composition "
"rule '%s'" % (glyphName, gname))
diff --git a/scripts/lib/fontbuild/instanceNames.py b/scripts/lib/fontbuild/instanceNames.py
index 253779c..a05f5e4 100644
--- a/scripts/lib/fontbuild/instanceNames.py
+++ b/scripts/lib/fontbuild/instanceNames.py
@@ -17,7 +17,7 @@ class InstanceNames:
def __init__(self,names):
if type(names) == type(" "):
names = names.split("/")
-
+ #print names
self.longfamily = names[0]
self.longstyle = names[1]
self.shortstyle = names[2]
@@ -84,21 +84,29 @@ class InstanceNames:
flFont.weight = self.weight
flFont.weight_code = self._getWeightCode(self.weight)
flFont.width = self.width
+ if len(self.italic):
+ flFont.italic_angle = -12
fn = flFont.fontnames
fn.clean()
- fn.append(NameRecord(0,1,0,0, "Font data copyright %s %s" %(self.foundry, self.year) ))
- fn.append(NameRecord(0,3,1,1033, "Font data copyright %s %s" %(self.foundry, self.year) ))
+ #fn.append(NameRecord(0,1,0,0, "Font data copyright %s %s" %(self.foundry, self.year) ))
+ #fn.append(NameRecord(0,3,1,1033, "Font data copyright %s %s" %(self.foundry, self.year) ))
+ fn.append(NameRecord(0,1,0,0, "Copyright %s %s Inc. All Rights Reserved." %(self.year, self.foundry) ))
+ fn.append(NameRecord(0,3,1,1033, "Copyright %s %s Inc. All Rights Reserved." %(self.year, self.foundry) ))
fn.append(NameRecord(1,1,0,0, self.longfamily ))
fn.append(NameRecord(1,3,1,1033, self.shortfamily ))
fn.append(NameRecord(2,1,0,0, self.longstyle ))
fn.append(NameRecord(2,3,1,1033, self.longstyle ))
- fn.append(NameRecord(3,1,0,0, "%s:%s:%s" %(self.foundry, self.longfamily, self.year) ))
- fn.append(NameRecord(3,3,1,1033, "%s:%s:%s" %(self.foundry, self.longfamily, self.year) ))
+ #fn.append(NameRecord(3,1,0,0, "%s:%s:%s" %(self.foundry, self.longfamily, self.year) ))
+ #fn.append(NameRecord(3,3,1,1033, "%s:%s:%s" %(self.foundry, self.longfamily, self.year) ))
+ fn.append(NameRecord(3,1,0,0, "%s:%s:%s" %(self.foundry, self.fullname, self.year) ))
+ fn.append(NameRecord(3,3,1,1033, "%s:%s:%s" %(self.foundry, self.fullname, self.year) ))
fn.append(NameRecord(4,1,0,0, self.fullname ))
fn.append(NameRecord(4,3,1,1033, self.fullname ))
- fn.append(NameRecord(5,1,0,0, "Version %s%s; %s" %(self.version, self.build, self.year) ))
- fn.append(NameRecord(5,3,1,1033, "Version %s%s; %s" %(self.version, self.build, self.year) ))
+ #fn.append(NameRecord(5,1,0,0, "Version %s%s; %s" %(self.version, self.build, self.year) ))
+ #fn.append(NameRecord(5,3,1,1033, "Version %s%s; %s" %(self.version, self.build, self.year) ))
+ fn.append(NameRecord(5,1,0,0, "Version %s; %s" %(self.version, self.year) ))
+ fn.append(NameRecord(5,3,1,1033, "Version %s; %s" %(self.version, self.year) ))
fn.append(NameRecord(6,1,0,0, self.postscript ))
fn.append(NameRecord(6,3,1,1033, self.postscript ))
fn.append(NameRecord(7,1,0,0, "%s is a trademark of %s." %(self.longfamily, self.foundry) ))
@@ -131,7 +139,7 @@ class InstanceNames:
return self._getSubstyle(r"Italic|Oblique|Obliq")
def _getWeight(self):
- w = self._getSubstyle(r"Extrabold|Superbold|Super|Fat|Bold|Semibold|Demibold|Medium|Light|Thin")
+ w = self._getSubstyle(r"Extrabold|Superbold|Super|Fat|Black|Bold|Semibold|Demibold|Medium|Light|Thin")
if w == "":
w = "Regular"
return w
@@ -143,6 +151,7 @@ class InstanceNames:
return w
def _getStyleCode(self):
+ #print "shortstyle:", self.shortstyle
styleCode = 0
if self.shortstyle == "Bold":
styleCode = 32
@@ -166,7 +175,7 @@ class InstanceNames:
elif weight == "Semibold":
return 600
elif weight == "Black":
- return 800
+ return 900
elif weight == "Fat":
return 900
diff --git a/scripts/lib/fontbuild/italics.py b/scripts/lib/fontbuild/italics.py
index 86bdc53..77e205a 100644
--- a/scripts/lib/fontbuild/italics.py
+++ b/scripts/lib/fontbuild/italics.py
@@ -6,6 +6,8 @@ import math
from alignpoints import alignCorners
def italicizeGlyph(f, g, angle=10, stemWidth=185):
+ unic = g.unicode #save unicode
+
glyph = f[g.name]
slope = np.tanh(math.pi * angle / 180)
@@ -23,6 +25,9 @@ def italicizeGlyph(f, g, angle=10, stemWidth=185):
transformFLGlyphMembers(f[g.name], m)
+ if unic > 0xFFFF: #restore unicode
+ g.unicode = unic
+
def italicize(glyph, angle=12, stemWidth=180, xoffset=-50):
CURVE_CORRECTION_WEIGHT = .03
diff --git a/scripts/lib/fontbuild/markFeature.py b/scripts/lib/fontbuild/markFeature.py
index e2e36c1..f3ba7c4 100755
--- a/scripts/lib/fontbuild/markFeature.py
+++ b/scripts/lib/fontbuild/markFeature.py
@@ -9,9 +9,12 @@ def GetAliaseName(gname):
return aliases[i][0]
return None
-def CreateAccNameList(font, acc_anchor_name):
+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)
@@ -39,7 +42,7 @@ def CreateGlyphList(font, acc_list, anchor_name):
break
return g_list
-def Create_mark_lookup(accent_g_list, base_g_list, lookupname, acc_class):
+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:
@@ -47,10 +50,11 @@ def Create_mark_lookup(accent_g_list, base_g_list, lookupname, acc_class):
for base in base_g_list:
txt += " pos base " + base[0] + " <anchor " + `base[1]` + " " + `base[2]` + "> mark " + acc_class + ";\n"
- base2 = GetAliaseName(base[0])
- if (None == base2):
- continue
- txt += " pos base " + base2 + " <anchor " + `base[1]` + " " + `base[2]` + "> mark " + acc_class + ";\n"
+ if (lookAliases):
+ base2 = GetAliaseName(base[0])
+ if (None == base2):
+ continue
+ txt += " pos base " + base2 + " <anchor " + `base[1]` + " " + `base[2]` + "> mark " + acc_class + ";\n"
txt += "} " + lookupname + ";\n"
@@ -58,27 +62,40 @@ def Create_mark_lookup(accent_g_list, base_g_list, lookupname, acc_class):
##### 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"
- accent_name_list = []
- accent_mark_list = []
- base_mark_list = []
- anchor_name = "top"
- acc_anchor_name = "_mark" + anchor_name
- 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_mark_lookup(accent_mark_list, base_mark_list, "mark1", "@MC_top")
-
- accent_name_list = []
- accent_mark_list = []
- base_mark_list = []
- anchor_name = "bottom"
- acc_anchor_name = "_mark" + anchor_name
- 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_mark_lookup(accent_mark_list, base_mark_list, "mark2", "@MC_bottom")
+ 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"
diff --git a/scripts/lib/fontbuild/mkmkFeature.py b/scripts/lib/fontbuild/mkmkFeature.py
index a839dae..7a05077 100755
--- a/scripts/lib/fontbuild/mkmkFeature.py
+++ b/scripts/lib/fontbuild/mkmkFeature.py
@@ -29,9 +29,9 @@ def CreateGlyphList(font, acc_list, anchor_name):
break
return g_list
-def Create_mkmk1(accent_g_list, base_g_list, lookupname):
+def Create_mkmk1(accent_g_list, base_g_list, lookupname, acc_class):
txt = "lookup " + lookupname + " {\n"
- acc_class = "@MC_mkmk"
+ #acc_class = "@MC_mkmk"
for acc in accent_g_list:
txt += " markClass " + acc[0] + " <anchor " + `acc[1]` + " " + `acc[2]` + "> " + acc_class +";\n"
@@ -55,7 +55,17 @@ def GenerateFeature_mkmk(font):
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")
+ 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"
diff --git a/scripts/run_android_tests.py b/scripts/run_android_tests.py
index 6b47177..819f065 100755
--- a/scripts/run_android_tests.py
+++ b/scripts/run_android_tests.py
@@ -1,53 +1,152 @@
#!/usr/bin/python
"""Test assumptions that Android relies on."""
+import glob
+import json
import unittest
+from fontTools import ttLib
from nototools import coverage
+from nototools import font_data
+from nototools import render
+from nototools import unicode_data
-import common_tests
-FONTS = common_tests.load_fonts(
- ['out/v2/android/*.ttf'],
- expected_count=18)
+def load_fonts():
+ """Load all fonts built for Android."""
+ all_font_files = glob.glob('out/android/*.ttf')
+ all_fonts = [ttLib.TTFont(font) for font in all_font_files]
+ assert len(all_font_files) == 18
+ return all_font_files, all_fonts
-class TestItalicAngle(common_tests.TestItalicAngle):
- loaded_fonts = FONTS
+class TestVerticalMetrics(unittest.TestCase):
+ """Test the vertical metrics of fonts."""
-class TestMetaInfo(common_tests.TestMetaInfo):
- loaded_fonts = FONTS
+ def setUp(self):
+ _, self.fonts = load_fonts()
+
+ def test_ymin_ymax(self):
+ """Tests yMin and yMax to be equal to what Android expects."""
+ for font in self.fonts:
+ head_table = font['head']
+ self.assertEqual(head_table.yMin, -555)
+ self.assertEqual(head_table.yMax, 2163)
-class TestNames(common_tests.TestNames):
- loaded_fonts = FONTS
- family_name = 'Roboto'
+class TestDigitWidths(unittest.TestCase):
+ """Tests the width of digits."""
+ def setUp(self):
+ _, self.fonts = load_fonts()
+ self.digits = [
+ 'zero', 'one', 'two', 'three', 'four',
+ 'five', 'six', 'seven', 'eight', 'nine']
+
+ def test_digit_widths(self):
+ """Tests all decimal digits to make sure they have the same width."""
+ for font in self.fonts:
+ hmtx_table = font['hmtx']
+ widths = [hmtx_table[digit][0] for digit in self.digits]
+ self.assertEqual(len(set(widths)), 1)
-class TestDigitWidths(common_tests.TestDigitWidths):
- loaded_fonts = FONTS
+class TestCharacterCoverage(unittest.TestCase):
+ """Tests character coverage."""
-class TestCharacterCoverage(common_tests.TestCharacterCoverage):
- loaded_fonts = FONTS
+ def setUp(self):
+ _, self.fonts = load_fonts()
+ self.LEGACY_PUA = frozenset({0xEE01, 0xEE02, 0xF6C3})
def test_lack_of_arrows_and_combining_keycap(self):
- """Tests that arrows and combining keycap are not in Android fonts."""
+ """Tests that arrows and combining keycap are not in the fonts."""
for font in self.fonts:
charset = coverage.character_set(font)
self.assertNotIn(0x20E3, charset) # COMBINING ENCLOSING KEYCAP
self.assertNotIn(0x2191, charset) # UPWARDS ARROW
self.assertNotIn(0x2193, charset) # DOWNWARDS ARROW
+ def test_lack_of_unassigned_chars(self):
+ """Tests that unassigned characters are not in the fonts."""
+ for font in self.fonts:
+ charset = coverage.character_set(font)
+ self.assertNotIn(0x2072, charset)
+ self.assertNotIn(0x2073, charset)
+ self.assertNotIn(0x208F, charset)
-class TestLigatures(common_tests.TestLigatures):
- loaded_fonts = FONTS
+ def test_inclusion_of_sound_recording_copyright(self):
+ """Tests that sound recording copyright symbol is in the fonts."""
+ for font in self.fonts:
+ charset = coverage.character_set(font)
+ self.assertIn(
+ 0x2117, charset, # SOUND RECORDING COPYRIGHT
+ 'U+2117 not found in %s.' % font_data.font_name(font))
+ def test_inclusion_of_legacy_pua(self):
+ """Tests that legacy PUA characters remain in the fonts."""
+ for font in self.fonts:
+ charset = coverage.character_set(font)
+ for char in self.LEGACY_PUA:
+ self.assertIn(char, charset)
-class TestVerticalMetrics(common_tests.TestVerticalMetrics):
- loaded_fonts = FONTS
+ def test_non_inclusion_of_other_pua(self):
+ """Tests that there are not other PUA characters except legacy ones."""
+ for font in self.fonts:
+ charset = coverage.character_set(font)
+ pua_chars = {
+ char for char in charset
+ if 0xE000 <= char <= 0xF8FF or 0xF0000 <= char <= 0x10FFFF}
+ self.assertTrue(pua_chars <= self.LEGACY_PUA)
+
+
+class TestSpacingMarks(unittest.TestCase):
+ """Tests that spacing marks are indeed spacing."""
+
+ def setUp(self):
+ self.font_files, _ = load_fonts()
+ charset = coverage.character_set(self.font_files[0])
+ self.marks_to_test = [char for char in charset
+ if unicode_data.category(char) in ['Lm', 'Sk']]
+ self.advance_cache = {}
+
+ def get_advances(self, text, font):
+ """Get a list of horizontal advances for text rendered in a font."""
+ try:
+ return self.advance_cache[(text, font)]
+ except KeyError:
+ hb_output = render.run_harfbuzz_on_text(text, font, '')
+ hb_output = json.loads(hb_output)
+ advances = [glyph['ax'] for glyph in hb_output]
+ self.advance_cache[(text, font)] = advances
+ return advances
+
+ def test_individual_spacing_marks(self):
+ """Tests that spacing marks are spacing by themselves."""
+ for font in self.font_files:
+ print 'Testing %s for stand-alone spacing marks...' % font
+ for mark in self.marks_to_test:
+ mark = unichr(mark)
+ advances = self.get_advances(mark, font)
+ assert len(advances) == 1
+ self.assertNotEqual(advances[0], 0)
+
+ def test_spacing_marks_in_combination(self):
+ """Tests that spacing marks do not combine with base letters."""
+ for font in self.font_files:
+ print 'Testing %s for spacing marks in combination...' % font
+ for base_letter in (u'A\u00C6BCDEFGHIJKLMNO\u00D8\u01A0PRST'
+ u'U\u01AFVWXYZ'
+ u'a\u00E6bcdefghi\u0131j\u0237klmn'
+ u'o\u00F8\u01A1prs\u017Ftu\u01B0vwxyz'
+ u'\u03D2'):
+ print 'Testing %s combinations' % base_letter
+ for mark in self.marks_to_test:
+ mark = unichr(mark)
+ advances = self.get_advances(base_letter + mark, font)
+ self.assertEqual(len(advances), 2,
+ 'The sequence <%04X, %04X> combines, '
+ 'but it should not' % (ord(base_letter), ord(mark)))
if __name__ == '__main__':
unittest.main()
-
diff --git a/scripts/touchup_for_android.py b/scripts/touchup_for_android.py
index c67d048..a230f27 100755
--- a/scripts/touchup_for_android.py
+++ b/scripts/touchup_for_android.py
@@ -1,29 +1,172 @@
#!/usr/bin/python
"""Post-build changes for Roboto for Android."""
+import collections
+import os
+from os import path
import sys
from fontTools import ttLib
from nototools import font_data
+from nototools import unicode_data
-import temporary_touchups
+
+def drop_lookup(table, lookup_number):
+ """Drop a lookup from an OpenType table by number.
+
+ Actually remove pointers from features to the lookup, which should be less
+ intrusive.
+ """
+ for feature in table.table.FeatureList.FeatureRecord:
+ if lookup_number in feature.Feature.LookupListIndex:
+ feature.Feature.LookupListIndex.remove(lookup_number)
+ feature.Feature.LookupCount -= 1
+
+
+def get_font_name(font):
+ """Gets the name of the font from the name table."""
+ return font_data.get_name_records(font)[4]
+
+
+DIGITS = ['zero', 'one', 'two', 'three', 'four',
+ 'five', 'six', 'seven', 'eight', 'nine']
+
+def fix_digit_widths(font):
+ """Change all digit widths in the font to be the same."""
+ hmtx_table = font['hmtx']
+ widths = [hmtx_table[digit][0] for digit in DIGITS]
+ if len(set(widths)) > 1:
+ width_counter = collections.Counter(widths)
+ most_common_width = width_counter.most_common(1)[0][0]
+ print 'Digit widths were %s.' % repr(widths)
+ print 'Setting all glyph widths to %d.' % most_common_width
+ for digit in DIGITS:
+ assert abs(hmtx_table[digit][0] - most_common_width) <= 1
+ hmtx_table[digit][0] = most_common_width
+
+
+_MAP_SPACING_TO_COMBINING = {
+ 'acute': 'acutecomb',
+ 'breve': 'brevenosp',
+ 'caron': 'uni030C',
+ 'cedilla': 'cedillanosp',
+ 'circumflex': 'circumflexnosp',
+ 'dieresis': 'dieresisnosp',
+ 'dotaccent': 'dotnosp',
+ 'grave': 'gravecomb',
+ 'hungarumlaut': 'acutedblnosp',
+ 'macron': 'macroncomb',
+ 'ogonek': 'ogoneknosp',
+ 'tilde': 'tildecomb',
+ 'ring': 'ringnosp',
+ 'tonos': 'acutecomb',
+ 'uni02F3': 'ringsubnosp',
+}
+
+def fix_ccmp_lookup(font):
+ """Fixes the broken ccmp lookup."""
+ cmap = font_data.get_cmap(font)
+ reverse_cmap = {name: code for (code, name) in cmap.items()}
+
+ # Where we know the bad 'ccmp' is
+ ccmp_lookup = font['GSUB'].table.LookupList.Lookup[2]
+ assert ccmp_lookup.LookupType == 4
+ assert ccmp_lookup.SubTableCount == 1
+ ligatures = ccmp_lookup.SubTable[0].ligatures
+ for first_char, ligtable in ligatures.iteritems():
+ ligatures_to_delete = []
+ for index, ligature in enumerate(ligtable):
+ assert len(ligature.Component) == 1
+ component = ligature.Component[0]
+ if (component.endswith('comb')
+ or component in ['commaaccent',
+ 'commaaccentrotate',
+ 'ringacute']):
+ continue
+ # https://code.google.com/a/google.com/p/roboto/issues/detail?id=54
+ if first_char == 'a' and component == 'uni02BE':
+ ligatures_to_delete.append(index)
+ continue
+ char = reverse_cmap[component]
+ general_category = unicode_data.category(char)
+ if general_category != 'Mn': # not a combining mark
+ ligature.Component[0] = _MAP_SPACING_TO_COMBINING[component]
+ ligatures[first_char] = [
+ ligature for (index, ligature) in enumerate(ligtable)
+ if index not in ligatures_to_delete]
+
+
+def apply_temporary_fixes(font):
+ """Apply some temporary fixes.
+ """
+ # Make sure macStyle is correct
+ # https://code.google.com/a/google.com/p/roboto/issues/detail?id=8
+ font_name = get_font_name(font)
+ bold = ('Bold' in font_name) or ('Black' in font_name)
+ italic = 'Italic' in font_name
+ font['head'].macStyle = (italic << 1) | bold
+
+ # Mark the font free for installation, embedding, etc.
+ # https://code.google.com/a/google.com/p/roboto/issues/detail?id=29
+ os2 = font['OS/2']
+ os2.fsType = 0
+
+ # Set the font vendor to Google
+ # https://code.google.com/a/google.com/p/roboto/issues/detail?id=46
+ os2.achVendID = 'GOOG'
+
+ # Drop the lookup forming the ff ligature
+ # https://code.google.com/a/google.com/p/roboto/issues/detail?id=47
+ drop_lookup(font['GSUB'], 5)
+
+ # Correct the ccmp lookup to use combining marks instead of spacing ones
+ # https://code.google.com/a/google.com/p/roboto/issues/detail?id=48
+ fix_ccmp_lookup(font)
+
+ # Fix the digit widths
+ # https://code.google.com/a/google.com/p/roboto/issues/detail?id=49
+ fix_digit_widths(font)
+
+ # Add cmap for U+2117 SOUND RECORDING COPYRIGHT
+ # https://code.google.com/a/google.com/p/roboto/issues/detail?id=44
+ font_data.add_to_cmap(font, {0x2117: 'published'})
+
+ # Fix version number from buildnumber.txt
+ # https://code.google.com/a/google.com/p/roboto/issues/detail?id=50
+ from datetime import date
+
+ build_number_txt = path.join(
+ path.dirname(__file__), os.pardir, 'res', 'buildnumber.txt')
+ build_number = open(build_number_txt).read().strip()
+
+ version_record = 'Version 2.0%s; %d' % (build_number, date.today().year)
+
+ for record in font['name'].names:
+ if record.nameID == 5:
+ if record.platformID == 1 and record.platEncID == 0: # MacRoman
+ record.string = version_record
+ elif record.platformID == 3 and record.platEncID == 1:
+ # Windows UCS-2
+ record.string = version_record.encode('UTF-16BE')
+ else:
+ assert False
def apply_android_specific_fixes(font):
"""Apply fixes needed for Android."""
- # Remove tab, combining keycap, and the arrows from the cmap table.
- #
- # Arrows are removed to maximize consistency of arrows, since the rest
- # of the arrows come from Noto Symbols.
- #
- # And here are the bugs for the other two issues:
+ # Set ascent, descent, and lineGap values to Android K values
+ hhea = font['hhea']
+ hhea.ascent = 1900
+ hhea.descent = -500
+ hhea.lineGap = 0
+
+ # Remove tab, combining keycap, the arrows, and unassigned characters
+ # from the cmap table
# https://code.google.com/a/google.com/p/roboto/issues/detail?id=51
# https://code.google.com/a/google.com/p/roboto/issues/detail?id=52
+ # https://code.google.com/a/google.com/p/roboto/issues/detail?id=53
font_data.delete_from_cmap(font, [
- 0x0009, # tab
- 0x20E3, # combining keycap
- 0x2191, 0x2193, # vertical arrows
- ])
+ 0x0009, 0x20E3, 0x2072, 0x2073, 0x208F, 0x2191, 0x2193])
# Drop tables not useful on Android
for table in ['LTSH', 'hdmx', 'VDMX', 'gasp']:
@@ -34,8 +177,7 @@ def apply_android_specific_fixes(font):
def correct_font(source_font_name, target_font_name):
"""Corrects metrics and other meta information."""
font = ttLib.TTFont(source_font_name)
- temporary_touchups.apply_temporary_fixes(font)
- temporary_touchups.update_version_and_revision(font)
+ apply_temporary_fixes(font)
apply_android_specific_fixes(font)
font.save(target_font_name)