diff options
Diffstat (limited to 'scripts')
-rw-r--r-- | scripts/build-v2.py | 5 | ||||
-rwxr-xr-x | scripts/force_yminmax.py | 2 | ||||
-rw-r--r-- | scripts/lib/fontbuild/Build.py | 5 | ||||
-rw-r--r-- | scripts/lib/fontbuild/anchors.py | 33 | ||||
-rw-r--r-- | scripts/lib/fontbuild/generateGlyph.py | 42 | ||||
-rw-r--r-- | scripts/lib/fontbuild/instanceNames.py | 27 | ||||
-rw-r--r-- | scripts/lib/fontbuild/italics.py | 5 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/markFeature.py | 67 | ||||
-rwxr-xr-x | scripts/lib/fontbuild/mkmkFeature.py | 16 | ||||
-rwxr-xr-x | scripts/run_android_tests.py | 141 | ||||
-rwxr-xr-x | scripts/touchup_for_android.py | 168 |
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) |