merging fixes & changes from delft-sprint-2012
git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@611 4cde692c-a291-49d1-8350-778aa11640f8
diff --git a/Lib/fontTools/misc/arrayTools.py b/Lib/fontTools/misc/arrayTools.py
index 2a35f5f..3f39e7e 100644
--- a/Lib/fontTools/misc/arrayTools.py
+++ b/Lib/fontTools/misc/arrayTools.py
@@ -3,157 +3,157 @@
# name of this module (not).
#
-import numpy
+
+import math
def calcBounds(array):
- """Return the bounding rectangle of a 2D points array as a tuple:
- (xMin, yMin, xMax, yMax)
- """
- if len(array) == 0:
- return 0, 0, 0, 0
- xMin, yMin = numpy.minimum.reduce(array)
- xMax, yMax = numpy.maximum.reduce(array)
- return xMin, yMin, xMax, yMax
+ """Return the bounding rectangle of a 2D points array as a tuple:
+ (xMin, yMin, xMax, yMax)
+ """
+ if len(array) == 0:
+ return 0, 0, 0, 0
+ xs = [x for x, y in array]
+ ys = [y for x, y in array]
+ return min(xs), min(ys), max(xs), max(ys)
def updateBounds(bounds, (x, y), min=min, max=max):
- """Return the bounding recangle of rectangle bounds and point (x, y)."""
- xMin, yMin, xMax, yMax = bounds
- return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y)
+ """Return the bounding recangle of rectangle bounds and point (x, y)."""
+ xMin, yMin, xMax, yMax = bounds
+ return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y)
def pointInRect((x, y), rect):
- """Return True when point (x, y) is inside rect."""
- xMin, yMin, xMax, yMax = rect
- return (xMin <= x <= xMax) and (yMin <= y <= yMax)
+ """Return True when point (x, y) is inside rect."""
+ xMin, yMin, xMax, yMax = rect
+ return (xMin <= x <= xMax) and (yMin <= y <= yMax)
def pointsInRect(array, rect):
- """Find out which points or array are inside rect.
- Returns an array with a boolean for each point.
- """
- if len(array) < 1:
- return []
- lefttop = rect[:2]
- rightbottom = rect[2:]
- condition = numpy.logical_and(
- numpy.greater_equal(array, lefttop),
- numpy.less_equal(array, rightbottom))
- return numpy.logical_and.reduce(condition, -1)
+ """Find out which points or array are inside rect.
+ Returns an array with a boolean for each point.
+ """
+ if len(array) < 1:
+ return []
+ xMin, yMin, xMax, yMax = rect
+ return [(xMin <= x <= xMax) and (yMin <= y <= yMax) for x, y in array]
def vectorLength(vector):
- """Return the length of the given vector."""
- return numpy.sqrt(vector[0]**2 + vector[1]**2)
+ """Return the length of the given vector."""
+ x, y = vector
+ return math.sqrt(x**2 + y**2)
def asInt16(array):
- """Round and cast to 16 bit integer."""
- return numpy.floor(array + 0.5).astype(numpy.int16)
-
+ """Round and cast to 16 bit integer."""
+ return [int(math.floor(i+0.5)) for i in array]
+
def normRect((xMin, yMin, xMax, yMax)):
- """Normalize the rectangle so that the following holds:
- xMin <= xMax and yMin <= yMax
- """
- return min(xMin, xMax), min(yMin, yMax), max(xMin, xMax), max(yMin, yMax)
+ """Normalize the rectangle so that the following holds:
+ xMin <= xMax and yMin <= yMax
+ """
+ return min(xMin, xMax), min(yMin, yMax), max(xMin, xMax), max(yMin, yMax)
def scaleRect((xMin, yMin, xMax, yMax), x, y):
- """Scale the rectangle by x, y."""
- return xMin * x, yMin * y, xMax * x, yMax * y
+ """Scale the rectangle by x, y."""
+ return xMin * x, yMin * y, xMax * x, yMax * y
def offsetRect((xMin, yMin, xMax, yMax), dx, dy):
- """Offset the rectangle by dx, dy."""
- return xMin+dx, yMin+dy, xMax+dx, yMax+dy
+ """Offset the rectangle by dx, dy."""
+ return xMin+dx, yMin+dy, xMax+dx, yMax+dy
def insetRect((xMin, yMin, xMax, yMax), dx, dy):
- """Inset the rectangle by dx, dy on all sides."""
- return xMin+dx, yMin+dy, xMax-dx, yMax-dy
+ """Inset the rectangle by dx, dy on all sides."""
+ return xMin+dx, yMin+dy, xMax-dx, yMax-dy
def sectRect((xMin1, yMin1, xMax1, yMax1), (xMin2, yMin2, xMax2, yMax2)):
- """Return a boolean and a rectangle. If the input rectangles intersect, return
- True and the intersecting rectangle. Return False and (0, 0, 0, 0) if the input
- rectangles don't intersect.
- """
- xMin, yMin, xMax, yMax = (max(xMin1, xMin2), max(yMin1, yMin2),
- min(xMax1, xMax2), min(yMax1, yMax2))
- if xMin >= xMax or yMin >= yMax:
- return 0, (0, 0, 0, 0)
- return 1, (xMin, yMin, xMax, yMax)
+ """Return a boolean and a rectangle. If the input rectangles intersect, return
+ True and the intersecting rectangle. Return False and (0, 0, 0, 0) if the input
+ rectangles don't intersect.
+ """
+ xMin, yMin, xMax, yMax = (max(xMin1, xMin2), max(yMin1, yMin2),
+ min(xMax1, xMax2), min(yMax1, yMax2))
+ if xMin >= xMax or yMin >= yMax:
+ return 0, (0, 0, 0, 0)
+ return 1, (xMin, yMin, xMax, yMax)
def unionRect((xMin1, yMin1, xMax1, yMax1), (xMin2, yMin2, xMax2, yMax2)):
- """Return the smallest rectangle in which both input rectangles are fully
- enclosed. In other words, return the total bounding rectangle of both input
- rectangles.
- """
- xMin, yMin, xMax, yMax = (min(xMin1, xMin2), min(yMin1, yMin2),
- max(xMax1, xMax2), max(yMax1, yMax2))
- return (xMin, yMin, xMax, yMax)
+ """Return the smallest rectangle in which both input rectangles are fully
+ enclosed. In other words, return the total bounding rectangle of both input
+ rectangles.
+ """
+ xMin, yMin, xMax, yMax = (min(xMin1, xMin2), min(yMin1, yMin2),
+ max(xMax1, xMax2), max(yMax1, yMax2))
+ return (xMin, yMin, xMax, yMax)
def rectCenter((xMin, yMin, xMax, yMax)):
- """Return the center of the rectangle as an (x, y) coordinate."""
- return (xMin+xMax)/2, (yMin+yMax)/2
+ """Return the center of the rectangle as an (x, y) coordinate."""
+ return (xMin+xMax)/2, (yMin+yMax)/2
def intRect((xMin, yMin, xMax, yMax)):
- """Return the rectangle, rounded off to integer values, but guaranteeing that
- the resulting rectangle is NOT smaller than the original.
- """
- import math
- xMin = int(math.floor(xMin))
- yMin = int(math.floor(yMin))
- xMax = int(math.ceil(xMax))
- yMax = int(math.ceil(yMax))
- return (xMin, yMin, xMax, yMax)
+ """Return the rectangle, rounded off to integer values, but guaranteeing that
+ the resulting rectangle is NOT smaller than the original.
+ """
+ import math
+ xMin = int(math.floor(xMin))
+ yMin = int(math.floor(yMin))
+ xMax = int(math.ceil(xMax))
+ yMax = int(math.ceil(yMax))
+ return (xMin, yMin, xMax, yMax)
def _test():
- """
- >>> import math
- >>> calcBounds([(0, 40), (0, 100), (50, 50), (80, 10)])
- (0, 10, 80, 100)
- >>> updateBounds((0, 0, 0, 0), (100, 100))
- (0, 0, 100, 100)
- >>> pointInRect((50, 50), (0, 0, 100, 100))
- True
- >>> pointInRect((0, 0), (0, 0, 100, 100))
- True
- >>> pointInRect((100, 100), (0, 0, 100, 100))
- True
- >>> not pointInRect((101, 100), (0, 0, 100, 100))
- True
- >>> list(pointsInRect([(50, 50), (0, 0), (100, 100), (101, 100)], (0, 0, 100, 100)))
- [True, True, True, False]
- >>> vectorLength((3, 4))
- 5.0
- >>> vectorLength((1, 1)) == math.sqrt(2)
- True
- >>> list(asInt16(numpy.array([0, 0.1, 0.5, 0.9])))
- [0, 0, 1, 1]
- >>> normRect((0, 10, 100, 200))
- (0, 10, 100, 200)
- >>> normRect((100, 200, 0, 10))
- (0, 10, 100, 200)
- >>> scaleRect((10, 20, 50, 150), 1.5, 2)
- (15.0, 40, 75.0, 300)
- >>> offsetRect((10, 20, 30, 40), 5, 6)
- (15, 26, 35, 46)
- >>> insetRect((10, 20, 50, 60), 5, 10)
- (15, 30, 45, 50)
- >>> insetRect((10, 20, 50, 60), -5, -10)
- (5, 10, 55, 70)
- >>> intersects, rect = sectRect((0, 10, 20, 30), (0, 40, 20, 50))
- >>> not intersects
- True
- >>> intersects, rect = sectRect((0, 10, 20, 30), (5, 20, 35, 50))
- >>> intersects
- 1
- >>> rect
- (5, 20, 20, 30)
- >>> unionRect((0, 10, 20, 30), (0, 40, 20, 50))
- (0, 10, 20, 50)
- >>> rectCenter((0, 0, 100, 200))
- (50, 100)
- >>> rectCenter((0, 0, 100, 199.0))
- (50, 99.5)
- >>> intRect((0.9, 2.9, 3.1, 4.1))
- (0, 2, 4, 5)
- """
+ """
+ >>> import math
+ >>> calcBounds([])
+ (0, 0, 0, 0)
+ >>> calcBounds([(0, 40), (0, 100), (50, 50), (80, 10)])
+ (0, 10, 80, 100)
+ >>> updateBounds((0, 0, 0, 0), (100, 100))
+ (0, 0, 100, 100)
+ >>> pointInRect((50, 50), (0, 0, 100, 100))
+ True
+ >>> pointInRect((0, 0), (0, 0, 100, 100))
+ True
+ >>> pointInRect((100, 100), (0, 0, 100, 100))
+ True
+ >>> not pointInRect((101, 100), (0, 0, 100, 100))
+ True
+ >>> list(pointsInRect([(50, 50), (0, 0), (100, 100), (101, 100)], (0, 0, 100, 100)))
+ [True, True, True, False]
+ >>> vectorLength((3, 4))
+ 5.0
+ >>> vectorLength((1, 1)) == math.sqrt(2)
+ True
+ >>> list(asInt16([0, 0.1, 0.5, 0.9]))
+ [0, 0, 1, 1]
+ >>> normRect((0, 10, 100, 200))
+ (0, 10, 100, 200)
+ >>> normRect((100, 200, 0, 10))
+ (0, 10, 100, 200)
+ >>> scaleRect((10, 20, 50, 150), 1.5, 2)
+ (15.0, 40, 75.0, 300)
+ >>> offsetRect((10, 20, 30, 40), 5, 6)
+ (15, 26, 35, 46)
+ >>> insetRect((10, 20, 50, 60), 5, 10)
+ (15, 30, 45, 50)
+ >>> insetRect((10, 20, 50, 60), -5, -10)
+ (5, 10, 55, 70)
+ >>> intersects, rect = sectRect((0, 10, 20, 30), (0, 40, 20, 50))
+ >>> not intersects
+ True
+ >>> intersects, rect = sectRect((0, 10, 20, 30), (5, 20, 35, 50))
+ >>> intersects
+ 1
+ >>> rect
+ (5, 20, 20, 30)
+ >>> unionRect((0, 10, 20, 30), (0, 40, 20, 50))
+ (0, 10, 20, 50)
+ >>> rectCenter((0, 0, 100, 200))
+ (50, 100)
+ >>> rectCenter((0, 0, 100, 199.0))
+ (50, 99.5)
+ >>> intRect((0.9, 2.9, 3.1, 4.1))
+ (0, 2, 4, 5)
+ """
if __name__ == "__main__":
- import doctest
- doctest.testmod()
+ import doctest
+ doctest.testmod()
diff --git a/Lib/fontTools/misc/bezierTools.py b/Lib/fontTools/misc/bezierTools.py
index 9643beb..4c897d5 100644
--- a/Lib/fontTools/misc/bezierTools.py
+++ b/Lib/fontTools/misc/bezierTools.py
@@ -1,228 +1,251 @@
-"""fontTools.misc.bezierTools.py -- tools for working with bezier path segments."""
+"""fontTools.misc.bezierTools.py -- tools for working with bezier path segments.
+"""
__all__ = [
- "calcQuadraticBounds",
- "calcCubicBounds",
- "splitLine",
- "splitQuadratic",
- "splitCubic",
- "splitQuadraticAtT",
- "splitCubicAtT",
- "solveQuadratic",
- "solveCubic",
+ "calcQuadraticBounds",
+ "calcCubicBounds",
+ "splitLine",
+ "splitQuadratic",
+ "splitCubic",
+ "splitQuadraticAtT",
+ "splitCubicAtT",
+ "solveQuadratic",
+ "solveCubic",
]
from fontTools.misc.arrayTools import calcBounds
-import numpy
epsilon = 1e-12
def calcQuadraticBounds(pt1, pt2, pt3):
- """Return the bounding rectangle for a qudratic bezier segment.
- pt1 and pt3 are the "anchor" points, pt2 is the "handle".
+ """Return the bounding rectangle for a qudratic bezier segment.
+ pt1 and pt3 are the "anchor" points, pt2 is the "handle".
- >>> calcQuadraticBounds((0, 0), (50, 100), (100, 0))
- (0.0, 0.0, 100.0, 50.0)
- >>> calcQuadraticBounds((0, 0), (100, 0), (100, 100))
- (0.0, 0.0, 100.0, 100.0)
- """
- a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
- # calc first derivative
- ax, ay = a * 2
- bx, by = b
- roots = []
- if ax != 0:
- roots.append(-bx/ax)
- if ay != 0:
- roots.append(-by/ay)
- points = [a*t*t + b*t + c for t in roots if 0 <= t < 1] + [pt1, pt3]
- return calcBounds(points)
+ >>> calcQuadraticBounds((0, 0), (50, 100), (100, 0))
+ (0, 0, 100, 50.0)
+ >>> calcQuadraticBounds((0, 0), (100, 0), (100, 100))
+ (0.0, 0.0, 100, 100)
+ """
+ (ax, ay), (bx, by), (cx, cy) = calcQuadraticParameters(pt1, pt2, pt3)
+ ax2 = ax*2.0
+ ay2 = ay*2.0
+ roots = []
+ if ax2 != 0:
+ roots.append(-bx/ax2)
+ if ay2 != 0:
+ roots.append(-by/ay2)
+ points = [(ax*t*t + bx*t + cx, ay*t*t + by*t + cy) for t in roots if 0 <= t < 1] + [pt1, pt3]
+ return calcBounds(points)
def calcCubicBounds(pt1, pt2, pt3, pt4):
- """Return the bounding rectangle for a cubic bezier segment.
- pt1 and pt4 are the "anchor" points, pt2 and pt3 are the "handles".
+ """Return the bounding rectangle for a cubic bezier segment.
+ pt1 and pt4 are the "anchor" points, pt2 and pt3 are the "handles".
- >>> calcCubicBounds((0, 0), (25, 100), (75, 100), (100, 0))
- (0.0, 0.0, 100.0, 75.0)
- >>> calcCubicBounds((0, 0), (50, 0), (100, 50), (100, 100))
- (0.0, 0.0, 100.0, 100.0)
- >>> calcCubicBounds((50, 0), (0, 100), (100, 100), (50, 0))
- (35.5662432703, 0.0, 64.4337567297, 75.0)
- """
- a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
- # calc first derivative
- ax, ay = a * 3.0
- bx, by = b * 2.0
- cx, cy = c
- xRoots = [t for t in solveQuadratic(ax, bx, cx) if 0 <= t < 1]
- yRoots = [t for t in solveQuadratic(ay, by, cy) if 0 <= t < 1]
- roots = xRoots + yRoots
-
- points = [(a*t*t*t + b*t*t + c * t + d) for t in roots] + [pt1, pt4]
- return calcBounds(points)
+ >>> calcCubicBounds((0, 0), (25, 100), (75, 100), (100, 0))
+ (0, 0, 100, 75.0)
+ >>> calcCubicBounds((0, 0), (50, 0), (100, 50), (100, 100))
+ (0.0, 0.0, 100, 100)
+ >>> print "%f %f %f %f" % calcCubicBounds((50, 0), (0, 100), (100, 100), (50, 0))
+ 35.566243 0.000000 64.433757 75.000000
+ """
+ (ax, ay), (bx, by), (cx, cy), (dx, dy) = calcCubicParameters(pt1, pt2, pt3, pt4)
+ # calc first derivative
+ ax3 = ax * 3.0
+ ay3 = ay * 3.0
+ bx2 = bx * 2.0
+ by2 = by * 2.0
+ xRoots = [t for t in solveQuadratic(ax3, bx2, cx) if 0 <= t < 1]
+ yRoots = [t for t in solveQuadratic(ay3, by2, cy) if 0 <= t < 1]
+ roots = xRoots + yRoots
+
+ points = [(ax*t*t*t + bx*t*t + cx * t + dx, ay*t*t*t + by*t*t + cy * t + dy) for t in roots] + [pt1, pt4]
+ return calcBounds(points)
def splitLine(pt1, pt2, where, isHorizontal):
- """Split the line between pt1 and pt2 at position 'where', which
- is an x coordinate if isHorizontal is False, a y coordinate if
- isHorizontal is True. Return a list of two line segments if the
- line was successfully split, or a list containing the original
- line.
+ """Split the line between pt1 and pt2 at position 'where', which
+ is an x coordinate if isHorizontal is False, a y coordinate if
+ isHorizontal is True. Return a list of two line segments if the
+ line was successfully split, or a list containing the original
+ line.
- >>> printSegments(splitLine((0, 0), (100, 100), 50, True))
- ((0, 0), (50.0, 50.0))
- ((50.0, 50.0), (100, 100))
- >>> printSegments(splitLine((0, 0), (100, 100), 100, True))
- ((0, 0), (100, 100))
- >>> printSegments(splitLine((0, 0), (100, 100), 0, True))
- ((0, 0), (0.0, 0.0))
- ((0.0, 0.0), (100, 100))
- >>> printSegments(splitLine((0, 0), (100, 100), 0, False))
- ((0, 0), (0.0, 0.0))
- ((0.0, 0.0), (100, 100))
- """
- pt1, pt2 = numpy.array((pt1, pt2))
- a = (pt2 - pt1)
- b = pt1
- ax = a[isHorizontal]
- if ax == 0:
- return [(pt1, pt2)]
- t = float(where - b[isHorizontal]) / ax
- if 0 <= t < 1:
- midPt = a * t + b
- return [(pt1, midPt), (midPt, pt2)]
- else:
- return [(pt1, pt2)]
+ >>> printSegments(splitLine((0, 0), (100, 100), 50, True))
+ ((0, 0), (50.0, 50.0))
+ ((50.0, 50.0), (100, 100))
+ >>> printSegments(splitLine((0, 0), (100, 100), 100, True))
+ ((0, 0), (100, 100))
+ >>> printSegments(splitLine((0, 0), (100, 100), 0, True))
+ ((0, 0), (0.0, 0.0))
+ ((0.0, 0.0), (100, 100))
+ >>> printSegments(splitLine((0, 0), (100, 100), 0, False))
+ ((0, 0), (0.0, 0.0))
+ ((0.0, 0.0), (100, 100))
+ """
+ pt1x, pt1y = pt1
+ pt2x, pt2y = pt2
+
+ ax = (pt2x - pt1x)
+ ay = (pt2y - pt1y)
+
+ bx = pt1x
+ by = pt1y
+
+ ax1 = (ax, ay)[isHorizontal]
+
+ if ax == 0:
+ return [(pt1, pt2)]
+
+ t = float(where - (bx, by)[isHorizontal]) / ax
+ if 0 <= t < 1:
+ midPt = ax * t + bx, ay * t + by
+ return [(pt1, midPt), (midPt, pt2)]
+ else:
+ return [(pt1, pt2)]
def splitQuadratic(pt1, pt2, pt3, where, isHorizontal):
- """Split the quadratic curve between pt1, pt2 and pt3 at position 'where',
- which is an x coordinate if isHorizontal is False, a y coordinate if
- isHorizontal is True. Return a list of curve segments.
+ """Split the quadratic curve between pt1, pt2 and pt3 at position 'where',
+ which is an x coordinate if isHorizontal is False, a y coordinate if
+ isHorizontal is True. Return a list of curve segments.
- >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 150, False))
- ((0, 0), (50, 100), (100, 0))
- >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 50, False))
- ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
- ((50.0, 50.0), (75.0, 50.0), (100.0, 0.0))
- >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 25, False))
- ((0.0, 0.0), (12.5, 25.0), (25.0, 37.5))
- ((25.0, 37.5), (62.5, 75.0), (100.0, 0.0))
- >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 25, True))
- ((0.0, 0.0), (7.32233047034, 14.6446609407), (14.6446609407, 25.0))
- ((14.6446609407, 25.0), (50.0, 75.0), (85.3553390593, 25.0))
- ((85.3553390593, 25.0), (92.6776695297, 14.6446609407), (100.0, -7.1054273576e-15))
- >>> # XXX I'm not at all sure if the following behavior is desirable:
- >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 50, True))
- ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
- ((50.0, 50.0), (50.0, 50.0), (50.0, 50.0))
- ((50.0, 50.0), (75.0, 50.0), (100.0, 0.0))
- """
- a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
- solutions = solveQuadratic(a[isHorizontal], b[isHorizontal],
- c[isHorizontal] - where)
- solutions = [t for t in solutions if 0 <= t < 1]
- solutions.sort()
- if not solutions:
- return [(pt1, pt2, pt3)]
- return _splitQuadraticAtT(a, b, c, *solutions)
+ >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 150, False))
+ ((0, 0), (50, 100), (100, 0))
+ >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 50, False))
+ ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
+ ((50.0, 50.0), (75.0, 50.0), (100.0, 0.0))
+ >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 25, False))
+ ((0.0, 0.0), (12.5, 25.0), (25.0, 37.5))
+ ((25.0, 37.5), (62.5, 75.0), (100.0, 0.0))
+ >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 25, True))
+ ((0.0, 0.0), (7.32233047034, 14.6446609407), (14.6446609407, 25.0))
+ ((14.6446609407, 25.0), (50.0, 75.0), (85.3553390593, 25.0))
+ ((85.3553390593, 25.0), (92.6776695297, 14.6446609407), (100.0, -7.1054273576e-15))
+ >>> # XXX I'm not at all sure if the following behavior is desirable:
+ >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 50, True))
+ ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
+ ((50.0, 50.0), (50.0, 50.0), (50.0, 50.0))
+ ((50.0, 50.0), (75.0, 50.0), (100.0, 0.0))
+ """
+ a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
+ solutions = solveQuadratic(a[isHorizontal], b[isHorizontal],
+ c[isHorizontal] - where)
+ solutions = [t for t in solutions if 0 <= t < 1]
+ solutions.sort()
+ if not solutions:
+ return [(pt1, pt2, pt3)]
+ return _splitQuadraticAtT(a, b, c, *solutions)
def splitCubic(pt1, pt2, pt3, pt4, where, isHorizontal):
- """Split the cubic curve between pt1, pt2, pt3 and pt4 at position 'where',
- which is an x coordinate if isHorizontal is False, a y coordinate if
- isHorizontal is True. Return a list of curve segments.
+ """Split the cubic curve between pt1, pt2, pt3 and pt4 at position 'where',
+ which is an x coordinate if isHorizontal is False, a y coordinate if
+ isHorizontal is True. Return a list of curve segments.
- >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 150, False))
- ((0, 0), (25, 100), (75, 100), (100, 0))
- >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 50, False))
- ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0))
- ((50.0, 75.0), (68.75, 75.0), (87.5, 50.0), (100.0, 0.0))
- >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 25, True))
- ((0.0, 0.0), (2.2937927384, 9.17517095361), (4.79804488188, 17.5085042869), (7.47413641001, 25.0))
- ((7.47413641001, 25.0), (31.2886200204, 91.6666666667), (68.7113799796, 91.6666666667), (92.52586359, 25.0))
- ((92.52586359, 25.0), (95.2019551181, 17.5085042869), (97.7062072616, 9.17517095361), (100.0, 1.7763568394e-15))
- """
- a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
- solutions = solveCubic(a[isHorizontal], b[isHorizontal], c[isHorizontal],
- d[isHorizontal] - where)
- solutions = [t for t in solutions if 0 <= t < 1]
- solutions.sort()
- if not solutions:
- return [(pt1, pt2, pt3, pt4)]
- return _splitCubicAtT(a, b, c, d, *solutions)
+ >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 150, False))
+ ((0, 0), (25, 100), (75, 100), (100, 0))
+ >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 50, False))
+ ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0))
+ ((50.0, 75.0), (68.75, 75.0), (87.5, 50.0), (100.0, 0.0))
+ >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 25, True))
+ ((0.0, 0.0), (2.2937927384, 9.17517095361), (4.79804488188, 17.5085042869), (7.47413641001, 25.0))
+ ((7.47413641001, 25.0), (31.2886200204, 91.6666666667), (68.7113799796, 91.6666666667), (92.52586359, 25.0))
+ ((92.52586359, 25.0), (95.2019551181, 17.5085042869), (97.7062072616, 9.17517095361), (100.0, 1.7763568394e-15))
+ """
+ a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
+ solutions = solveCubic(a[isHorizontal], b[isHorizontal], c[isHorizontal],
+ d[isHorizontal] - where)
+ solutions = [t for t in solutions if 0 <= t < 1]
+ solutions.sort()
+ if not solutions:
+ return [(pt1, pt2, pt3, pt4)]
+ return _splitCubicAtT(a, b, c, d, *solutions)
def splitQuadraticAtT(pt1, pt2, pt3, *ts):
- """Split the quadratic curve between pt1, pt2 and pt3 at one or more
- values of t. Return a list of curve segments.
+ """Split the quadratic curve between pt1, pt2 and pt3 at one or more
+ values of t. Return a list of curve segments.
- >>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5))
- ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
- ((50.0, 50.0), (75.0, 50.0), (100.0, 0.0))
- >>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5, 0.75))
- ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
- ((50.0, 50.0), (62.5, 50.0), (75.0, 37.5))
- ((75.0, 37.5), (87.5, 25.0), (100.0, 0.0))
- """
- a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
- return _splitQuadraticAtT(a, b, c, *ts)
+ >>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5))
+ ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
+ ((50.0, 50.0), (75.0, 50.0), (100.0, 0.0))
+ >>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5, 0.75))
+ ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
+ ((50.0, 50.0), (62.5, 50.0), (75.0, 37.5))
+ ((75.0, 37.5), (87.5, 25.0), (100.0, 0.0))
+ """
+ a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
+ return _splitQuadraticAtT(a, b, c, *ts)
def splitCubicAtT(pt1, pt2, pt3, pt4, *ts):
- """Split the cubic curve between pt1, pt2, pt3 and pt4 at one or more
- values of t. Return a list of curve segments.
+ """Split the cubic curve between pt1, pt2, pt3 and pt4 at one or more
+ values of t. Return a list of curve segments.
- >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5))
- ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0))
- ((50.0, 75.0), (68.75, 75.0), (87.5, 50.0), (100.0, 0.0))
- >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5, 0.75))
- ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0))
- ((50.0, 75.0), (59.375, 75.0), (68.75, 68.75), (77.34375, 56.25))
- ((77.34375, 56.25), (85.9375, 43.75), (93.75, 25.0), (100.0, 0.0))
- """
- a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
- return _splitCubicAtT(a, b, c, d, *ts)
+ >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5))
+ ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0))
+ ((50.0, 75.0), (68.75, 75.0), (87.5, 50.0), (100.0, 0.0))
+ >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5, 0.75))
+ ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0))
+ ((50.0, 75.0), (59.375, 75.0), (68.75, 68.75), (77.34375, 56.25))
+ ((77.34375, 56.25), (85.9375, 43.75), (93.75, 25.0), (100.0, 0.0))
+ """
+ a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
+ return _splitCubicAtT(a, b, c, d, *ts)
def _splitQuadraticAtT(a, b, c, *ts):
- ts = list(ts)
- segments = []
- ts.insert(0, 0.0)
- ts.append(1.0)
- for i in range(len(ts) - 1):
- t1 = ts[i]
- t2 = ts[i+1]
- delta = (t2 - t1)
- # calc new a, b and c
- a1 = a * delta**2
- b1 = (2*a*t1 + b) * delta
- c1 = a*t1**2 + b*t1 + c
- pt1, pt2, pt3 = calcQuadraticPoints(a1, b1, c1)
- segments.append((pt1, pt2, pt3))
- return segments
+ ts = list(ts)
+ segments = []
+ ts.insert(0, 0.0)
+ ts.append(1.0)
+ ax, ay = a
+ bx, by = b
+ cx, cy = c
+ for i in range(len(ts) - 1):
+ t1 = ts[i]
+ t2 = ts[i+1]
+ delta = (t2 - t1)
+ # calc new a, b and c
+ a1x = ax * delta**2
+ a1y = ay * delta**2
+ b1x = (2*ax*t1 + bx) * delta
+ b1y = (2*ay*t1 + by) * delta
+ c1x = ax*t1**2 + bx*t1 + cx
+ c1y = ay*t1**2 + by*t1 + cy
+
+ pt1, pt2, pt3 = calcQuadraticPoints((a1x, a1y), (b1x, b1y), (c1x, c1y))
+ segments.append((pt1, pt2, pt3))
+ return segments
def _splitCubicAtT(a, b, c, d, *ts):
- ts = list(ts)
- ts.insert(0, 0.0)
- ts.append(1.0)
- segments = []
- for i in range(len(ts) - 1):
- t1 = ts[i]
- t2 = ts[i+1]
- delta = (t2 - t1)
- # calc new a, b, c and d
- a1 = a * delta**3
- b1 = (3*a*t1 + b) * delta**2
- c1 = (2*b*t1 + c + 3*a*t1**2) * delta
- d1 = a*t1**3 + b*t1**2 + c*t1 + d
- pt1, pt2, pt3, pt4 = calcCubicPoints(a1, b1, c1, d1)
- segments.append((pt1, pt2, pt3, pt4))
- return segments
+ ts = list(ts)
+ ts.insert(0, 0.0)
+ ts.append(1.0)
+ segments = []
+ ax, ay = a
+ bx, by = b
+ cx, cy = c
+ dx, dy = d
+ for i in range(len(ts) - 1):
+ t1 = ts[i]
+ t2 = ts[i+1]
+ delta = (t2 - t1)
+ # calc new a, b, c and d
+ a1x = ax * delta**3
+ a1y = ay * delta**3
+ b1x = (3*ax*t1 + bx) * delta**2
+ b1y = (3*ay*t1 + by) * delta**2
+ c1x = (2*bx*t1 + cx + 3*ax*t1**2) * delta
+ c1y = (2*by*t1 + cy + 3*ay*t1**2) * delta
+ d1x = ax*t1**3 + bx*t1**2 + cx*t1 + dx
+ d1y = ay*t1**3 + by*t1**2 + cy*t1 + dy
+ pt1, pt2, pt3, pt4 = calcCubicPoints((a1x, a1y), (b1x, b1y), (c1x, c1y), (d1x, d1y))
+ segments.append((pt1, pt2, pt3, pt4))
+ return segments
#
@@ -233,74 +256,74 @@
def solveQuadratic(a, b, c,
- sqrt=sqrt):
- """Solve a quadratic equation where a, b and c are real.
- a*x*x + b*x + c = 0
- This function returns a list of roots. Note that the returned list
- is neither guaranteed to be sorted nor to contain unique values!
- """
- if abs(a) < epsilon:
- if abs(b) < epsilon:
- # We have a non-equation; therefore, we have no valid solution
- roots = []
- else:
- # We have a linear equation with 1 root.
- roots = [-c/b]
- else:
- # We have a true quadratic equation. Apply the quadratic formula to find two roots.
- DD = b*b - 4.0*a*c
- if DD >= 0.0:
- rDD = sqrt(DD)
- roots = [(-b+rDD)/2.0/a, (-b-rDD)/2.0/a]
- else:
- # complex roots, ignore
- roots = []
- return roots
+ sqrt=sqrt):
+ """Solve a quadratic equation where a, b and c are real.
+ a*x*x + b*x + c = 0
+ This function returns a list of roots. Note that the returned list
+ is neither guaranteed to be sorted nor to contain unique values!
+ """
+ if abs(a) < epsilon:
+ if abs(b) < epsilon:
+ # We have a non-equation; therefore, we have no valid solution
+ roots = []
+ else:
+ # We have a linear equation with 1 root.
+ roots = [-c/b]
+ else:
+ # We have a true quadratic equation. Apply the quadratic formula to find two roots.
+ DD = b*b - 4.0*a*c
+ if DD >= 0.0:
+ rDD = sqrt(DD)
+ roots = [(-b+rDD)/2.0/a, (-b-rDD)/2.0/a]
+ else:
+ # complex roots, ignore
+ roots = []
+ return roots
def solveCubic(a, b, c, d,
- abs=abs, pow=pow, sqrt=sqrt, cos=cos, acos=acos, pi=pi):
- """Solve a cubic equation where a, b, c and d are real.
- a*x*x*x + b*x*x + c*x + d = 0
- This function returns a list of roots. Note that the returned list
- is neither guaranteed to be sorted nor to contain unique values!
- """
- #
- # adapted from:
- # CUBIC.C - Solve a cubic polynomial
- # public domain by Ross Cottrell
- # found at: http://www.strangecreations.com/library/snippets/Cubic.C
- #
- if abs(a) < epsilon:
- # don't just test for zero; for very small values of 'a' solveCubic()
- # returns unreliable results, so we fall back to quad.
- return solveQuadratic(b, c, d)
- a = float(a)
- a1 = b/a
- a2 = c/a
- a3 = d/a
-
- Q = (a1*a1 - 3.0*a2)/9.0
- R = (2.0*a1*a1*a1 - 9.0*a1*a2 + 27.0*a3)/54.0
- R2_Q3 = R*R - Q*Q*Q
+ abs=abs, pow=pow, sqrt=sqrt, cos=cos, acos=acos, pi=pi):
+ """Solve a cubic equation where a, b, c and d are real.
+ a*x*x*x + b*x*x + c*x + d = 0
+ This function returns a list of roots. Note that the returned list
+ is neither guaranteed to be sorted nor to contain unique values!
+ """
+ #
+ # adapted from:
+ # CUBIC.C - Solve a cubic polynomial
+ # public domain by Ross Cottrell
+ # found at: http://www.strangecreations.com/library/snippets/Cubic.C
+ #
+ if abs(a) < epsilon:
+ # don't just test for zero; for very small values of 'a' solveCubic()
+ # returns unreliable results, so we fall back to quad.
+ return solveQuadratic(b, c, d)
+ a = float(a)
+ a1 = b/a
+ a2 = c/a
+ a3 = d/a
+
+ Q = (a1*a1 - 3.0*a2)/9.0
+ R = (2.0*a1*a1*a1 - 9.0*a1*a2 + 27.0*a3)/54.0
+ R2_Q3 = R*R - Q*Q*Q
- if R2_Q3 < 0:
- theta = acos(R/sqrt(Q*Q*Q))
- rQ2 = -2.0*sqrt(Q)
- x0 = rQ2*cos(theta/3.0) - a1/3.0
- x1 = rQ2*cos((theta+2.0*pi)/3.0) - a1/3.0
- x2 = rQ2*cos((theta+4.0*pi)/3.0) - a1/3.0
- return [x0, x1, x2]
- else:
- if Q == 0 and R == 0:
- x = 0
- else:
- x = pow(sqrt(R2_Q3)+abs(R), 1/3.0)
- x = x + Q/x
- if R >= 0.0:
- x = -x
- x = x - a1/3.0
- return [x]
+ if R2_Q3 < 0:
+ theta = acos(R/sqrt(Q*Q*Q))
+ rQ2 = -2.0*sqrt(Q)
+ x0 = rQ2*cos(theta/3.0) - a1/3.0
+ x1 = rQ2*cos((theta+2.0*pi)/3.0) - a1/3.0
+ x2 = rQ2*cos((theta+4.0*pi)/3.0) - a1/3.0
+ return [x0, x1, x2]
+ else:
+ if Q == 0 and R == 0:
+ x = 0
+ else:
+ x = pow(sqrt(R2_Q3)+abs(R), 1/3.0)
+ x = x + Q/x
+ if R >= 0.0:
+ x = -x
+ x = x - a1/3.0
+ return [x]
#
@@ -308,58 +331,79 @@
#
def calcQuadraticParameters(pt1, pt2, pt3):
- pt1, pt2, pt3 = numpy.array((pt1, pt2, pt3))
- c = pt1
- b = (pt2 - c) * 2.0
- a = pt3 - c - b
- return a, b, c
+ x2, y2 = pt2
+ x3, y3 = pt3
+ cx, cy = pt1
+ bx = (x2 - cx) * 2.0
+ by = (y2 - cy) * 2.0
+ ax = x3 - cx - bx
+ ay = y3 - cy - by
+ return (ax, ay), (bx, by), (cx, cy)
def calcCubicParameters(pt1, pt2, pt3, pt4):
- pt1, pt2, pt3, pt4 = numpy.array((pt1, pt2, pt3, pt4))
- d = pt1
- c = (pt2 - d) * 3.0
- b = (pt3 - pt2) * 3.0 - c
- a = pt4 - d - c - b
- return a, b, c, d
+ x2, y2 = pt2
+ x3, y3 = pt3
+ x4, y4 = pt4
+ dx, dy = pt1
+ cx = (x2 -dx) * 3.0
+ cy = (y2 -dy) * 3.0
+ bx = (x3 - x2) * 3.0 - cx
+ by = (y3 - y2) * 3.0 - cy
+ ax = x4 - dx - cx - bx
+ ay = y4 - dy - cy - by
+ return (ax, ay), (bx, by), (cx, cy), (dx, dy)
def calcQuadraticPoints(a, b, c):
- pt1 = c
- pt2 = (b * 0.5) + c
- pt3 = a + b + c
- return pt1, pt2, pt3
+ ax, ay = a
+ bx, by = b
+ cx, cy = c
+ x1 = cx
+ y1 = cy
+ x2 = (bx * 0.5) + cx
+ y2 = (by * 0.5) + cy
+ x3 = ax + bx + cx
+ y3 = ay + by + cy
+ return (x1, y1), (x2, y2), (x3, y3)
def calcCubicPoints(a, b, c, d):
- pt1 = d
- pt2 = (c / 3.0) + d
- pt3 = (b + c) / 3.0 + pt2
- pt4 = a + d + c + b
- return pt1, pt2, pt3, pt4
+ ax, ay = a
+ bx, by = b
+ cx, cy = c
+ dx, dy = d
+ x1 = dx
+ y1 = dy
+ x2 = (cx / 3.0) + dx
+ y2 = (cy / 3.0) + dy
+ x3 = (bx + cx) / 3.0 + x2
+ y3 = (by + cy) / 3.0 + y2
+ x4 = ax + dx + cx + bx
+ y4 = ay + dy + cy + by
+ return (x1, y1), (x2, y2), (x3, y3), (x4, y4)
def _segmentrepr(obj):
- """
- >>> _segmentrepr([1, [2, 3], [], [[2, [3, 4], numpy.array([0.1, 2.2])]]])
- '(1, (2, 3), (), ((2, (3, 4), (0.1, 2.2))))'
- """
- try:
- it = iter(obj)
- except TypeError:
- return str(obj)
- else:
- return "(%s)" % ", ".join([_segmentrepr(x) for x in it])
+ """
+ >>> _segmentrepr([1, [2, 3], [], [[2, [3, 4], [0.1, 2.2]]]])
+ '(1, (2, 3), (), ((2, (3, 4), (0.1, 2.2))))'
+ """
+ try:
+ it = iter(obj)
+ except TypeError:
+ return str(obj)
+ else:
+ return "(%s)" % ", ".join([_segmentrepr(x) for x in it])
def printSegments(segments):
- """Helper for the doctests, displaying each segment in a list of
- segments on a single line as a tuple.
- """
- for segment in segments:
- print _segmentrepr(segment)
-
+ """Helper for the doctests, displaying each segment in a list of
+ segments on a single line as a tuple.
+ """
+ for segment in segments:
+ print _segmentrepr(segment)
if __name__ == "__main__":
- import doctest
- doctest.testmod()
+ import doctest
+ doctest.testmod()
diff --git a/Lib/fontTools/misc/psLib.py b/Lib/fontTools/misc/psLib.py
index 848f71d..3c7bd37 100644
--- a/Lib/fontTools/misc/psLib.py
+++ b/Lib/fontTools/misc/psLib.py
@@ -343,9 +343,9 @@
if __name__ == "__main__":
- import macfs
- fss, ok = macfs.StandardGetFile("LWFN")
- if ok:
+ import EasyDialogs
+ path = EasyDialogs.AskFileForOpen()
+ if path:
from fontTools import t1Lib
- data, kind = t1Lib.read(fss.as_pathname())
+ data, kind = t1Lib.read(path)
font = suckfont(data)
diff --git a/Lib/fontTools/misc/textTools.py b/Lib/fontTools/misc/textTools.py
index 4c6a650..2fd5d0b 100644
--- a/Lib/fontTools/misc/textTools.py
+++ b/Lib/fontTools/misc/textTools.py
@@ -5,7 +5,7 @@
def safeEval(data, eval=eval):
- """A safe replacement for eval."""
+ """A (kindof) safe replacement for eval."""
return eval(data, {"__builtins__":{}}, {})