diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..2d4cce0ba
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,15 @@
+sudo: false
+language: python
+python:
+ - "3.4"
+branches:
+ only:
+ - master
+
+install:
+ - python setup.py install
+ - pip install git+https://github.com/behdad/fonttools
+ - pip install git+https://github.com/unified-font-object/ufoLib
+
+script:
+ - python setup.py test
diff --git a/Lib/cu2qu/__init__.py b/Lib/cu2qu/__init__.py
index 77d92dd8d..9c4de981e 100644
--- a/Lib/cu2qu/__init__.py
+++ b/Lib/cu2qu/__init__.py
@@ -234,4 +234,4 @@ def curves_to_quadratic(curves, max_errors):
for c, s, max_err in zip(curves, splines, max_errors):
if s is None:
raise ApproxNotFoundError(c)
- return [[(s.real,s.imag) for s in spline] for spline in splines], None
+ return [[(s.real,s.imag) for s in spline] for spline in splines]
diff --git a/Lib/cu2qu/pens.py b/Lib/cu2qu/pens.py
index 63a49c7de..625630378 100644
--- a/Lib/cu2qu/pens.py
+++ b/Lib/cu2qu/pens.py
@@ -72,7 +72,7 @@ class Cu2QuPen(AbstractPen):
def _curve_to_quadratic(self, pt1, pt2, pt3):
curve = (self.current_pt, pt1, pt2, pt3)
- quadratic, _ = curve_to_quadratic(curve, self.max_err)
+ quadratic = curve_to_quadratic(curve, self.max_err)
if self.stats is not None:
n = str(len(quadratic))
self.stats[n] = self.stats.get(n, 0) + 1
@@ -144,7 +144,7 @@ class Cu2QuPointPen(BasePointToSegmentPen):
on_curve, smooth, name, kwargs = sub_points[-1]
bcp1, bcp2 = sub_points[0][0], sub_points[1][0]
cubic = [prev_on_curve, bcp1, bcp2, on_curve]
- quad, _ = curve_to_quadratic(cubic, self.max_err)
+ quad = curve_to_quadratic(cubic, self.max_err)
if self.stats is not None:
n = str(len(quad))
self.stats[n] = self.stats.get(n, 0) + 1
diff --git a/Lib/cu2qu/test/cu2qu_test.py b/Lib/cu2qu/test/cu2qu_test.py
index 3ef96c308..a9a51465c 100644
--- a/Lib/cu2qu/test/cu2qu_test.py
+++ b/Lib/cu2qu/test/cu2qu_test.py
@@ -16,6 +16,7 @@
from __future__ import print_function, division, absolute_import
import collections
+import math
import unittest
import random
@@ -34,12 +35,18 @@ class CurveToQuadraticTest(unittest.TestCase):
random.seed(1)
curves = [generate_curve() for i in range(1000)]
- cls.single_splines, cls.single_errors = zip(*[
- curve_to_quadratic(c, MAX_ERR) for c in curves])
+ cls.single_splines = [
+ curve_to_quadratic(c, MAX_ERR) for c in curves]
+ cls.single_errors = [
+ cls.curve_spline_dist(c, s)
+ for c, s in zip(curves, cls.single_splines)]
- cls.compat_splines, cls.compat_errors = zip(*[
- curves_to_quadratic(curves[i:i + 3], [MAX_ERR] * 3)
- for i in range(0, 300, 3)])
+ curve_groups = [curves[i:i + 3] for i in range(0, 300, 3)]
+ cls.compat_splines = [
+ curves_to_quadratic(c, [MAX_ERR] * 3) for c in curve_groups]
+ cls.compat_errors = [
+ [cls.curve_spline_dist(c, s) for c, s in zip(curve_group, splines)]
+ for curve_group, splines in zip(curve_groups, cls.compat_splines)]
cls.results = []
@@ -60,13 +67,13 @@ class CurveToQuadraticTest(unittest.TestCase):
"""
expected = {
- 3: 5,
- 4: 31,
- 5: 74,
- 6: 228,
- 7: 416,
- 8: 242,
- 9: 4}
+ 3: 6,
+ 4: 26,
+ 5: 82,
+ 6: 232,
+ 7: 360,
+ 8: 266,
+ 9: 28}
results = collections.defaultdict(int)
for spline in self.single_splines:
@@ -79,10 +86,10 @@ class CurveToQuadraticTest(unittest.TestCase):
"""Test that conversion results are unchanged for multiple curves."""
expected = {
- 6: 3,
- 7: 34,
- 8: 62,
- 9: 1}
+ 6: 11,
+ 7: 35,
+ 8: 49,
+ 9: 5}
results = collections.defaultdict(int)
for splines in self.compat_splines:
@@ -113,6 +120,60 @@ class CurveToQuadraticTest(unittest.TestCase):
self.assertLessEqual(error, MAX_ERR)
self.results.append(('compatible errors', results))
+ @classmethod
+ def curve_spline_dist(cls, bezier, spline, total_steps=20):
+ """Max distance between a bezier and quadratic spline at sampled points."""
+
+ error = 0
+ n = len(spline) - 2
+ steps = total_steps // n
+ for i in range(0, n - 1):
+ p1 = spline[0] if i == 0 else p3
+ p2 = spline[i + 1]
+ if i < n - 1:
+ p3 = cls.lerp(spline[i + 1], spline[i + 2], 0.5)
+ else:
+ p3 = spline[n + 2]
+ segment = p1, p2, p3
+ for j in range(steps):
+ error = max(error, cls.dist(
+ cls.cubic_bezier_at(bezier, (j / steps + i) / n),
+ cls.quadratic_bezier_at(segment, j / steps)))
+ return error
+
+ @classmethod
+ def lerp(cls, p1, p2, t):
+ (x1, y1), (x2, y2) = p1, p2
+ return x1 + (x2 - x1) * t, y1 + (y2 - y1) * t
+
+ @classmethod
+ def dist(cls, p1, p2):
+ (x1, y1), (x2, y2) = p1, p2
+ return math.hypot(x1 - x2, y1 - y2)
+
+ @classmethod
+ def quadratic_bezier_at(cls, b, t):
+ (x1, y1), (x2, y2), (x3, y3) = b
+ _t = 1 - t
+ t2 = t * t
+ _t2 = _t * _t
+ _2_t_t = 2 * t * _t
+ return (_t2 * x1 + _2_t_t * x2 + t2 * x3,
+ _t2 * y1 + _2_t_t * y2 + t2 * y3)
+
+ @classmethod
+ def cubic_bezier_at(cls, b, t):
+ (x1, y1), (x2, y2), (x3, y3), (x4, y4) = b
+ _t = 1 - t
+ t2 = t * t
+ _t2 = _t * _t
+ t3 = t * t2
+ _t3 = _t * _t2
+ _3_t2_t = 3 * t2 * _t
+ _3_t_t2 = 3 * t * _t2
+ return (_t3 * x1 + _3_t_t2 * x2 + _3_t2_t * x3 + t3 * x4,
+ _t3 * y1 + _3_t_t2 * y2 + _3_t2_t * y3 + t3 * y4)
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/cu2qu/test/data/quadratic/A_.glif b/Lib/cu2qu/test/data/quadratic/A_.glif
index f70bfa6c3..673eb514e 100644
--- a/Lib/cu2qu/test/data/quadratic/A_.glif
+++ b/Lib/cu2qu/test/data/quadratic/A_.glif
@@ -6,8 +6,8 @@
-
-
+
+
@@ -22,12 +22,12 @@
-
+
-
-
+
+
@@ -37,27 +37,27 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
+
-
-
+
+
@@ -71,22 +71,4 @@
-
-
- org.robofab.fontlab.guides
-
- hguides
-
-
- angle
- 0
- position
- -180
-
-
-
- org.robofab.fontlab.mark
- 80
-
-
-
\ No newline at end of file
+
diff --git a/Lib/cu2qu/test/data/quadratic/E_acute.glif b/Lib/cu2qu/test/data/quadratic/E_acute.glif
index b0a5ec486..e5bb4fd5b 100644
--- a/Lib/cu2qu/test/data/quadratic/E_acute.glif
+++ b/Lib/cu2qu/test/data/quadratic/E_acute.glif
@@ -6,15 +6,15 @@
-
-
+
+
-
-
+
+
@@ -30,7 +30,8 @@
-
+
+
@@ -38,26 +39,27 @@
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
\ No newline at end of file
+
diff --git a/Lib/cu2qu/test/data/quadratic/a.glif b/Lib/cu2qu/test/data/quadratic/a.glif
index 141bc75fb..dc776e117 100644
--- a/Lib/cu2qu/test/data/quadratic/a.glif
+++ b/Lib/cu2qu/test/data/quadratic/a.glif
@@ -6,8 +6,8 @@
-
-
+
+
@@ -17,17 +17,17 @@
-
+
-
-
+
+
-
-
+
+
@@ -37,17 +37,17 @@
-
-
+
+
-
-
+
+
-
-
+
+
@@ -57,8 +57,8 @@
-
-
+
+
@@ -90,4 +90,4 @@
-
\ No newline at end of file
+
diff --git a/Lib/cu2qu/ufo.py b/Lib/cu2qu/ufo.py
index a671db826..2d3543ecd 100644
--- a/Lib/cu2qu/ufo.py
+++ b/Lib/cu2qu/ufo.py
@@ -126,7 +126,7 @@ def _segments_to_quadratic(segments, max_err, stats):
assert all(s[0] == 'curve' for s in segments), 'Non-cubic given to convert'
- new_points, _ = curves_to_quadratic([s[1] for s in segments], max_err)
+ new_points = curves_to_quadratic([s[1] for s in segments], max_err)
n = len(new_points[0])
assert all(len(s) == n for s in new_points[1:]), 'Converted incompatibly'