Merge pull request #804 from anthrotype/t2pen-round
[t2CharStringPen] add "roundTolerance" option
This commit is contained in:
commit
5479090856
@ -9,15 +9,6 @@ from fontTools.misc.psCharStrings import T2CharString
|
||||
from fontTools.pens.basePen import BasePen
|
||||
|
||||
|
||||
def roundInt(v):
|
||||
return int(round(v))
|
||||
|
||||
|
||||
def roundIntPoint(point):
|
||||
x, y = point
|
||||
return roundInt(x), roundInt(y)
|
||||
|
||||
|
||||
class RelativeCoordinatePen(BasePen):
|
||||
|
||||
def __init__(self, glyphSet):
|
||||
@ -75,20 +66,45 @@ class RelativeCoordinatePen(BasePen):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def makeRoundFunc(tolerance):
|
||||
if tolerance < 0:
|
||||
raise ValueError("Rounding tolerance must be positive")
|
||||
|
||||
def _round(number):
|
||||
if tolerance == 0:
|
||||
return number # no-op
|
||||
rounded = round(number)
|
||||
# return rounded integer if the tolerance >= 0.5, or if the absolute
|
||||
# difference between the original float and the rounded integer is
|
||||
# within the tolerance
|
||||
if tolerance >= .5 or abs(rounded - number) <= tolerance:
|
||||
return rounded
|
||||
else:
|
||||
# else return the value un-rounded
|
||||
return number
|
||||
|
||||
def roundPoint(point):
|
||||
x, y = point
|
||||
return _round(x), _round(y)
|
||||
|
||||
return roundPoint
|
||||
|
||||
|
||||
class T2CharStringPen(RelativeCoordinatePen):
|
||||
|
||||
def __init__(self, width, glyphSet):
|
||||
def __init__(self, width, glyphSet, roundTolerance=0.5):
|
||||
RelativeCoordinatePen.__init__(self, glyphSet)
|
||||
self.roundPoint = makeRoundFunc(roundTolerance)
|
||||
self._heldMove = None
|
||||
self._program = []
|
||||
if width is not None:
|
||||
self._program.append(roundInt(width))
|
||||
self._program.append(round(width))
|
||||
|
||||
def _moveTo(self, pt):
|
||||
RelativeCoordinatePen._moveTo(self, roundIntPoint(pt))
|
||||
RelativeCoordinatePen._moveTo(self, self.roundPoint(pt))
|
||||
|
||||
def _relativeMoveTo(self, pt):
|
||||
pt = roundIntPoint(pt)
|
||||
pt = self.roundPoint(pt)
|
||||
x, y = pt
|
||||
self._heldMove = [x, y, "rmoveto"]
|
||||
|
||||
@ -98,22 +114,25 @@ class T2CharStringPen(RelativeCoordinatePen):
|
||||
self._heldMove = None
|
||||
|
||||
def _lineTo(self, pt):
|
||||
RelativeCoordinatePen._lineTo(self, roundIntPoint(pt))
|
||||
RelativeCoordinatePen._lineTo(self, self.roundPoint(pt))
|
||||
|
||||
def _relativeLineTo(self, pt):
|
||||
self._storeHeldMove()
|
||||
pt = roundIntPoint(pt)
|
||||
pt = self.roundPoint(pt)
|
||||
x, y = pt
|
||||
self._program.extend([x, y, "rlineto"])
|
||||
|
||||
def _curveToOne(self, pt1, pt2, pt3):
|
||||
RelativeCoordinatePen._curveToOne(self, roundIntPoint(pt1), roundIntPoint(pt2), roundIntPoint(pt3))
|
||||
RelativeCoordinatePen._curveToOne(self,
|
||||
self.roundPoint(pt1),
|
||||
self.roundPoint(pt2),
|
||||
self.roundPoint(pt3))
|
||||
|
||||
def _relativeCurveToOne(self, pt1, pt2, pt3):
|
||||
self._storeHeldMove()
|
||||
pt1 = roundIntPoint(pt1)
|
||||
pt2 = roundIntPoint(pt2)
|
||||
pt3 = roundIntPoint(pt3)
|
||||
pt1 = self.roundPoint(pt1)
|
||||
pt2 = self.roundPoint(pt2)
|
||||
pt3 = self.roundPoint(pt3)
|
||||
x1, y1 = pt1
|
||||
x2, y2 = pt2
|
||||
x3, y3 = pt3
|
||||
@ -127,5 +146,6 @@ class T2CharStringPen(RelativeCoordinatePen):
|
||||
|
||||
def getCharString(self, private=None, globalSubrs=None):
|
||||
program = self._program + ["endchar"]
|
||||
charString = T2CharString(program=program, private=private, globalSubrs=globalSubrs)
|
||||
charString = T2CharString(
|
||||
program=program, private=private, globalSubrs=globalSubrs)
|
||||
return charString
|
||||
|
125
Lib/fontTools/pens/t2CharStringPen_test.py
Normal file
125
Lib/fontTools/pens/t2CharStringPen_test.py
Normal file
@ -0,0 +1,125 @@
|
||||
from __future__ import print_function, division, absolute_import
|
||||
from fontTools.misc.py23 import *
|
||||
from fontTools.pens.t2CharStringPen import T2CharStringPen
|
||||
import unittest
|
||||
|
||||
|
||||
class T2CharStringPenTest(unittest.TestCase):
|
||||
|
||||
def __init__(self, methodName):
|
||||
unittest.TestCase.__init__(self, methodName)
|
||||
# Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
|
||||
# and fires deprecation warnings if a program uses the old name.
|
||||
if not hasattr(self, "assertRaisesRegex"):
|
||||
self.assertRaisesRegex = self.assertRaisesRegexp
|
||||
|
||||
def assertAlmostEqualProgram(self, expected, actual):
|
||||
self.assertEqual(len(expected), len(actual))
|
||||
for i1, i2 in zip(expected, actual):
|
||||
if isinstance(i1, basestring):
|
||||
self.assertIsInstance(i2, basestring)
|
||||
self.assertEqual(i1, i2)
|
||||
else:
|
||||
self.assertAlmostEqual(i1, i2)
|
||||
|
||||
def test_draw_lines(self):
|
||||
pen = T2CharStringPen(100, {})
|
||||
pen.moveTo((0, 0))
|
||||
pen.lineTo((10, 0))
|
||||
pen.lineTo((10, 10))
|
||||
pen.lineTo((0, 10))
|
||||
pen.closePath() # no-op
|
||||
charstring = pen.getCharString(None, None)
|
||||
|
||||
self.assertEqual(
|
||||
[100,
|
||||
0, 0, 'rmoveto',
|
||||
10, 0, 'rlineto',
|
||||
0, 10, 'rlineto',
|
||||
-10, 0, 'rlineto',
|
||||
'endchar'],
|
||||
charstring.program)
|
||||
|
||||
def test_draw_curves(self):
|
||||
pen = T2CharStringPen(100, {})
|
||||
pen.moveTo((0, 0))
|
||||
pen.curveTo((10, 0), (20, 10), (20, 20))
|
||||
pen.curveTo((20, 30), (10, 40), (0, 40))
|
||||
pen.endPath() # no-op
|
||||
charstring = pen.getCharString(None, None)
|
||||
|
||||
self.assertEqual(
|
||||
[100,
|
||||
0, 0, 'rmoveto',
|
||||
10, 0, 10, 10, 0, 10, 'rrcurveto',
|
||||
0, 10, -10, 10, -10, 0, 'rrcurveto',
|
||||
'endchar'],
|
||||
charstring.program)
|
||||
|
||||
def test_default_width(self):
|
||||
pen = T2CharStringPen(None, {})
|
||||
charstring = pen.getCharString(None, None)
|
||||
self.assertEqual(['endchar'], charstring.program)
|
||||
|
||||
def test_no_round(self):
|
||||
pen = T2CharStringPen(100.1, {}, roundTolerance=0.0)
|
||||
pen.moveTo((0, 0))
|
||||
pen.curveTo((10.1, 0.1), (19.9, 9.9), (20.49, 20.49))
|
||||
pen.curveTo((20.49, 30.49), (9.9, 39.9), (0.1, 40.1))
|
||||
pen.closePath()
|
||||
charstring = pen.getCharString(None, None)
|
||||
|
||||
self.assertAlmostEqualProgram(
|
||||
[100, # we always round the advance width
|
||||
0, 0, 'rmoveto',
|
||||
10.1, 0.1, 9.8, 9.8, 0.59, 10.59, 'rrcurveto',
|
||||
0, 10, -10.59, 9.41, -9.8, 0.2, 'rrcurveto',
|
||||
'endchar'],
|
||||
charstring.program)
|
||||
|
||||
def test_round_all(self):
|
||||
pen = T2CharStringPen(100.1, {}, roundTolerance=0.5)
|
||||
pen.moveTo((0, 0))
|
||||
pen.curveTo((10.1, 0.1), (19.9, 9.9), (20.49, 20.49))
|
||||
pen.curveTo((20.49, 30.49), (9.9, 39.9), (0.1, 40.1))
|
||||
pen.closePath()
|
||||
charstring = pen.getCharString(None, None)
|
||||
|
||||
self.assertEqual(
|
||||
[100,
|
||||
0, 0, 'rmoveto',
|
||||
10, 0, 10, 10, 0, 10, 'rrcurveto',
|
||||
0, 10, -10, 10, -10, 0, 'rrcurveto',
|
||||
'endchar'],
|
||||
charstring.program)
|
||||
|
||||
def test_round_some(self):
|
||||
pen = T2CharStringPen(100, {}, roundTolerance=0.2)
|
||||
pen.moveTo((0, 0))
|
||||
# the following two are rounded as within the tolerance
|
||||
pen.lineTo((10.1, 0.1))
|
||||
pen.lineTo((19.9, 9.9))
|
||||
# this one is not rounded as it exceeds the tolerance
|
||||
pen.lineTo((20.49, 20.49))
|
||||
pen.closePath()
|
||||
charstring = pen.getCharString(None, None)
|
||||
|
||||
self.assertAlmostEqualProgram(
|
||||
[100,
|
||||
0, 0, 'rmoveto',
|
||||
10, 0, 'rlineto',
|
||||
10, 10, 'rlineto',
|
||||
0.49, 10.49, 'rlineto',
|
||||
'endchar'],
|
||||
charstring.program)
|
||||
|
||||
def test_invalid_tolerance(self):
|
||||
self.assertRaisesRegex(
|
||||
ValueError,
|
||||
"Rounding tolerance must be positive",
|
||||
T2CharStringPen, None, {}, roundTolerance=-0.1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.exit(unittest.main())
|
Loading…
x
Reference in New Issue
Block a user