You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
286 lines
9.3 KiB
286 lines
9.3 KiB
import os
|
|
|
|
from fontTools.misc.loggingTools import CapturingLogHandler
|
|
from fontTools.cu2qu.ufo import (
|
|
fonts_to_quadratic,
|
|
font_to_quadratic,
|
|
glyphs_to_quadratic,
|
|
glyph_to_quadratic,
|
|
logger,
|
|
CURVE_TYPE_LIB_KEY,
|
|
)
|
|
from fontTools.cu2qu.errors import (
|
|
IncompatibleSegmentNumberError,
|
|
IncompatibleSegmentTypesError,
|
|
IncompatibleFontsError,
|
|
)
|
|
|
|
import pytest
|
|
|
|
|
|
ufoLib2 = pytest.importorskip("ufoLib2")
|
|
|
|
DATADIR = os.path.join(os.path.dirname(__file__), 'data')
|
|
|
|
TEST_UFOS = [
|
|
os.path.join(DATADIR, "RobotoSubset-Regular.ufo"),
|
|
os.path.join(DATADIR, "RobotoSubset-Bold.ufo"),
|
|
]
|
|
|
|
|
|
@pytest.fixture
|
|
def fonts():
|
|
return [ufoLib2.Font.open(ufo) for ufo in TEST_UFOS]
|
|
|
|
|
|
class FontsToQuadraticTest(object):
|
|
|
|
def test_modified(self, fonts):
|
|
modified = fonts_to_quadratic(fonts)
|
|
assert modified
|
|
|
|
def test_stats(self, fonts):
|
|
stats = {}
|
|
fonts_to_quadratic(fonts, stats=stats)
|
|
assert stats == {'1': 1, '2': 79, '3': 130, '4': 2}
|
|
|
|
def test_dump_stats(self, fonts):
|
|
with CapturingLogHandler(logger, "INFO") as captor:
|
|
fonts_to_quadratic(fonts, dump_stats=True)
|
|
assert captor.assertRegex("New spline lengths:")
|
|
|
|
def test_remember_curve_type(self, fonts):
|
|
fonts_to_quadratic(fonts, remember_curve_type=True)
|
|
assert fonts[0].lib[CURVE_TYPE_LIB_KEY] == "quadratic"
|
|
with CapturingLogHandler(logger, "INFO") as captor:
|
|
fonts_to_quadratic(fonts, remember_curve_type=True)
|
|
assert captor.assertRegex("already converted")
|
|
|
|
def test_no_remember_curve_type(self, fonts):
|
|
assert CURVE_TYPE_LIB_KEY not in fonts[0].lib
|
|
fonts_to_quadratic(fonts, remember_curve_type=False)
|
|
assert CURVE_TYPE_LIB_KEY not in fonts[0].lib
|
|
|
|
def test_different_glyphsets(self, fonts):
|
|
del fonts[0]['a']
|
|
assert 'a' not in fonts[0]
|
|
assert 'a' in fonts[1]
|
|
assert fonts_to_quadratic(fonts)
|
|
|
|
def test_max_err_em_float(self, fonts):
|
|
stats = {}
|
|
fonts_to_quadratic(fonts, max_err_em=0.002, stats=stats)
|
|
assert stats == {'1': 5, '2': 193, '3': 14}
|
|
|
|
def test_max_err_em_list(self, fonts):
|
|
stats = {}
|
|
fonts_to_quadratic(fonts, max_err_em=[0.002, 0.002], stats=stats)
|
|
assert stats == {'1': 5, '2': 193, '3': 14}
|
|
|
|
def test_max_err_float(self, fonts):
|
|
stats = {}
|
|
fonts_to_quadratic(fonts, max_err=4.096, stats=stats)
|
|
assert stats == {'1': 5, '2': 193, '3': 14}
|
|
|
|
def test_max_err_list(self, fonts):
|
|
stats = {}
|
|
fonts_to_quadratic(fonts, max_err=[4.096, 4.096], stats=stats)
|
|
assert stats == {'1': 5, '2': 193, '3': 14}
|
|
|
|
def test_both_max_err_and_max_err_em(self, fonts):
|
|
with pytest.raises(TypeError, match="Only one .* can be specified"):
|
|
fonts_to_quadratic(fonts, max_err=1.000, max_err_em=0.001)
|
|
|
|
def test_single_font(self, fonts):
|
|
assert font_to_quadratic(fonts[0], max_err_em=0.002,
|
|
reverse_direction=True)
|
|
|
|
|
|
class GlyphsToQuadraticTest(object):
|
|
|
|
@pytest.mark.parametrize(
|
|
["glyph", "expected"],
|
|
[('A', False), # contains no curves, it is not modified
|
|
('a', True)],
|
|
ids=['lines-only', 'has-curves']
|
|
)
|
|
def test_modified(self, fonts, glyph, expected):
|
|
glyphs = [f[glyph] for f in fonts]
|
|
assert glyphs_to_quadratic(glyphs) == expected
|
|
|
|
def test_stats(self, fonts):
|
|
stats = {}
|
|
glyphs_to_quadratic([f['a'] for f in fonts], stats=stats)
|
|
assert stats == {'2': 1, '3': 7, '4': 3, '5': 1}
|
|
|
|
def test_max_err_float(self, fonts):
|
|
glyphs = [f['a'] for f in fonts]
|
|
stats = {}
|
|
glyphs_to_quadratic(glyphs, max_err=4.096, stats=stats)
|
|
assert stats == {'2': 11, '3': 1}
|
|
|
|
def test_max_err_list(self, fonts):
|
|
glyphs = [f['a'] for f in fonts]
|
|
stats = {}
|
|
glyphs_to_quadratic(glyphs, max_err=[4.096, 4.096], stats=stats)
|
|
assert stats == {'2': 11, '3': 1}
|
|
|
|
def test_reverse_direction(self, fonts):
|
|
glyphs = [f['A'] for f in fonts]
|
|
assert glyphs_to_quadratic(glyphs, reverse_direction=True)
|
|
|
|
def test_single_glyph(self, fonts):
|
|
assert glyph_to_quadratic(fonts[0]['a'], max_err=4.096,
|
|
reverse_direction=True)
|
|
|
|
@pytest.mark.parametrize(
|
|
["outlines", "exception", "message"],
|
|
[
|
|
[
|
|
[
|
|
[
|
|
('moveTo', ((0, 0),)),
|
|
('curveTo', ((1, 1), (2, 2), (3, 3))),
|
|
('curveTo', ((4, 4), (5, 5), (6, 6))),
|
|
('closePath', ()),
|
|
],
|
|
[
|
|
('moveTo', ((7, 7),)),
|
|
('curveTo', ((8, 8), (9, 9), (10, 10))),
|
|
('closePath', ()),
|
|
]
|
|
],
|
|
IncompatibleSegmentNumberError,
|
|
"have different number of segments",
|
|
],
|
|
[
|
|
[
|
|
|
|
[
|
|
('moveTo', ((0, 0),)),
|
|
('curveTo', ((1, 1), (2, 2), (3, 3))),
|
|
('closePath', ()),
|
|
],
|
|
[
|
|
('moveTo', ((4, 4),)),
|
|
('lineTo', ((5, 5),)),
|
|
('closePath', ()),
|
|
],
|
|
],
|
|
IncompatibleSegmentTypesError,
|
|
"have incompatible segment types",
|
|
],
|
|
],
|
|
ids=[
|
|
"unequal-length",
|
|
"different-segment-types",
|
|
]
|
|
)
|
|
def test_incompatible_glyphs(self, outlines, exception, message):
|
|
glyphs = []
|
|
for i, outline in enumerate(outlines):
|
|
glyph = ufoLib2.objects.Glyph("glyph%d" % i)
|
|
pen = glyph.getPen()
|
|
for operator, args in outline:
|
|
getattr(pen, operator)(*args)
|
|
glyphs.append(glyph)
|
|
with pytest.raises(exception) as excinfo:
|
|
glyphs_to_quadratic(glyphs)
|
|
assert excinfo.match(message)
|
|
|
|
def test_incompatible_fonts(self):
|
|
font1 = ufoLib2.Font()
|
|
font1.info.unitsPerEm = 1000
|
|
glyph1 = font1.newGlyph("a")
|
|
pen1 = glyph1.getPen()
|
|
for operator, args in [("moveTo", ((0, 0),)),
|
|
("lineTo", ((1, 1),)),
|
|
("endPath", ())]:
|
|
getattr(pen1, operator)(*args)
|
|
|
|
font2 = ufoLib2.Font()
|
|
font2.info.unitsPerEm = 1000
|
|
glyph2 = font2.newGlyph("a")
|
|
pen2 = glyph2.getPen()
|
|
for operator, args in [("moveTo", ((0, 0),)),
|
|
("curveTo", ((1, 1), (2, 2), (3, 3))),
|
|
("endPath", ())]:
|
|
getattr(pen2, operator)(*args)
|
|
|
|
with pytest.raises(IncompatibleFontsError) as excinfo:
|
|
fonts_to_quadratic([font1, font2])
|
|
assert excinfo.match("fonts contains incompatible glyphs: 'a'")
|
|
|
|
assert hasattr(excinfo.value, "glyph_errors")
|
|
error = excinfo.value.glyph_errors['a']
|
|
assert isinstance(error, IncompatibleSegmentTypesError)
|
|
assert error.segments == {1: ["line", "curve"]}
|
|
|
|
def test_already_quadratic(self):
|
|
glyph = ufoLib2.objects.Glyph()
|
|
pen = glyph.getPen()
|
|
pen.moveTo((0, 0))
|
|
pen.qCurveTo((1, 1), (2, 2))
|
|
pen.closePath()
|
|
assert not glyph_to_quadratic(glyph)
|
|
|
|
def test_open_paths(self):
|
|
glyph = ufoLib2.objects.Glyph()
|
|
pen = glyph.getPen()
|
|
pen.moveTo((0, 0))
|
|
pen.lineTo((1, 1))
|
|
pen.curveTo((2, 2), (3, 3), (4, 4))
|
|
pen.endPath()
|
|
assert glyph_to_quadratic(glyph)
|
|
# open contour is still open
|
|
assert glyph[-1][0].segmentType == "move"
|
|
|
|
def test_ignore_components(self):
|
|
glyph = ufoLib2.objects.Glyph()
|
|
pen = glyph.getPen()
|
|
pen.addComponent('a', (1, 0, 0, 1, 0, 0))
|
|
pen.moveTo((0, 0))
|
|
pen.curveTo((1, 1), (2, 2), (3, 3))
|
|
pen.closePath()
|
|
assert glyph_to_quadratic(glyph)
|
|
assert len(glyph.components) == 1
|
|
|
|
def test_overlapping_start_end_points(self):
|
|
# https://github.com/googlefonts/fontmake/issues/572
|
|
glyph1 = ufoLib2.objects.Glyph()
|
|
pen = glyph1.getPointPen()
|
|
pen.beginPath()
|
|
pen.addPoint((0, 651), segmentType="line")
|
|
pen.addPoint((0, 101), segmentType="line")
|
|
pen.addPoint((0, 101), segmentType="line")
|
|
pen.addPoint((0, 651), segmentType="line")
|
|
pen.endPath()
|
|
|
|
glyph2 = ufoLib2.objects.Glyph()
|
|
pen = glyph2.getPointPen()
|
|
pen.beginPath()
|
|
pen.addPoint((1, 651), segmentType="line")
|
|
pen.addPoint((2, 101), segmentType="line")
|
|
pen.addPoint((3, 101), segmentType="line")
|
|
pen.addPoint((4, 651), segmentType="line")
|
|
pen.endPath()
|
|
|
|
glyphs = [glyph1, glyph2]
|
|
|
|
assert glyphs_to_quadratic(glyphs, reverse_direction=True)
|
|
|
|
assert [[(p.x, p.y) for p in glyph[0]] for glyph in glyphs] == [
|
|
[
|
|
(0, 651),
|
|
(0, 651),
|
|
(0, 101),
|
|
(0, 101),
|
|
],
|
|
[
|
|
(1, 651),
|
|
(4, 651),
|
|
(3, 101),
|
|
(2, 101)
|
|
],
|
|
]
|