diff options
-rw-r--r-- | scripts/common_tests.py | 62 | ||||
-rw-r--r-- | scripts/glyph_area_pen.py | 77 | ||||
-rwxr-xr-x | scripts/run_general_tests.py | 18 |
3 files changed, 155 insertions, 2 deletions
diff --git a/scripts/common_tests.py b/scripts/common_tests.py index f5a7793..54dff86 100644 --- a/scripts/common_tests.py +++ b/scripts/common_tests.py @@ -27,6 +27,7 @@ import freetype import layout import roboto_data +from glyph_area_pen import GlyphAreaPen def get_rendered_char_height(font_filename, font_size, char, target='mono'): @@ -42,15 +43,19 @@ def get_rendered_char_height(font_filename, font_size, char, target='mono'): return face.glyph.bitmap.rows -def load_fonts(patterns, expected_count=None): +def load_fonts(patterns, expected_count=None, font_class=None): """Load all fonts specified in the patterns. Also assert that the number of the fonts found is exactly the same as expected_count.""" + + if font_class is None: + font_class = ttLib.TTFont + all_font_files = [] for pattern in patterns: all_font_files += glob.glob(pattern) - all_fonts = [ttLib.TTFont(font) for font in all_font_files] + all_fonts = [font_class(font) for font in all_font_files] if expected_count: assert len(all_font_files) == expected_count return all_font_files, all_fonts @@ -327,3 +332,56 @@ class TestVerticalMetrics(FontTest): self.assertEqual(hhea_table.ascent, 1900) self.assertEqual(hhea_table.lineGap, 0) + +class TestGlyphAreas(unittest.TestCase): + """Tests that glyph areas between weights have the right ratios.""" + + def setUp(self): + """Determine which glyphs are intentionally unchanged.""" + + self.unchanged = set() + pen = self.pen = GlyphAreaPen() + thin, bold = self.getFonts(self.masters[1], "Roboto", "Thin", "Bold") + for glyph in thin: + glyph.draw(pen) + thin_area = pen.unload() + bold[glyph.name].draw(pen) + bold_area = pen.unload() + if thin_area == bold_area: + if thin_area: + self.unchanged.add(glyph.name) + else: + assert thin_area and bold_area + + def getFonts(self, fonts, family, *weights): + """Extract fonts of certain family and weights from given font list.""" + + fonts = dict((f.info.styleName, f) for f in fonts + if f.info.familyName == family) + return [fonts[w] for w in weights] + + def test_output(self): + """Test that only empty or intentionally unchanged glyphs are unchanged. + """ + + pen = self.pen + thin, regular, bold = self.getFonts( + self.loaded_fonts[1], "Roboto", "Thin", "Regular", "Bold") + regular_areas = {} + for glyph in regular: + glyph.draw(pen) + regular_areas[glyph.name] = pen.unload() + + for other in [thin, bold]: + for name, regular_area in regular_areas.iteritems(): + other[name].draw(pen) + other_area = pen.unload() + if not regular_area: # glyph probably contains only components + self.assertFalse(other_area) + continue + unchanged = regular_area == other_area + if unchanged: + msg = name + " has not changed, but should have." + else: + msg = name + " has changed, but should not have." + self.assertEqual(unchanged, name in self.unchanged, msg) diff --git a/scripts/glyph_area_pen.py b/scripts/glyph_area_pen.py new file mode 100644 index 0000000..1251b6a --- /dev/null +++ b/scripts/glyph_area_pen.py @@ -0,0 +1,77 @@ +# 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 fontTools.pens.basePen import AbstractPen + + +class GlyphAreaPen(AbstractPen): + """Pen used for calculating the area of the contours in a glyph.""" + + def __init__(self): + self.area = 0 + + def unload(self): + """Return and then reset the calculated area.""" + + area = self.area + self.area = 0 + return area + + def moveTo(self, pt): + """Remember the first point in this contour, in case it's closed. Also + set the initial value for p0 in this contour, which will always refer to + the most recent point. + """ + + self.first = pt + self.p0 = pt + + def lineTo(self, pt): + """Add the signed area beneath the line from the latest point to this + one. Signed areas cancel each other based on the horizontal direction of + the line. + """ + + (x0, y0), (x1, y1) = self.p0, pt + self.area += (x1 - x0) * (y1 + y0) / 2.0 + self.p0 = pt + + def curveTo(self, *points): + """Add the signed area of this cubic curve. + https://github.com/Pomax/bezierinfo/issues/44 + """ + + p1, p2, p3 = points + x0, y0 = self.p0 + x1, y1 = p1[0] - x0, p1[1] - y0 + x2, y2 = p2[0] - x0, p2[1] - y0 + x3, y3 = p3[0] - x0, p3[1] - y0 + self.area += ( + x1 * ( - y2 - y3) + + x2 * (y1 - 2 * y3) + + x3 * (y1 + 2 * y2 ) + ) * 3.0 / 20 + self.lineTo(p3) + + def closePath(self): + """Add the area beneath this contour's closing line.""" + self.lineTo(self.first) + + def endPath(self): + pass + + def addComponent(self, glyphName, transformation): + """Don't count components towards the area, for now.""" + pass diff --git a/scripts/run_general_tests.py b/scripts/run_general_tests.py index 68ca269..297c37c 100755 --- a/scripts/run_general_tests.py +++ b/scripts/run_general_tests.py @@ -18,12 +18,24 @@ import unittest +from robofab.world import OpenFont + import common_tests FONTS = common_tests.load_fonts( ['hinted/*.ttf'], expected_count=18) +UFOS = common_tests.load_fonts( + ['out/RobotoUFO/*.ufo', 'out/RobotoCondensedUFO/*.ufo'], + expected_count=18, + font_class=OpenFont) + +UFO_MASTERS = common_tests.load_fonts( + ['src/v2/*.ufo'], + expected_count=3, + font_class=OpenFont) + class TestItalicAngle(common_tests.TestItalicAngle): loaded_fonts = FONTS @@ -55,6 +67,12 @@ class TestVerticalMetrics(common_tests.TestVerticalMetrics): test_ymin_ymax = None test_hhea_table_metrics = None + +class TestGlyphAreas(common_tests.TestGlyphAreas): + loaded_fonts = UFOS + masters = UFO_MASTERS + + if __name__ == '__main__': unittest.main() |