aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/misc
diff options
context:
space:
mode:
authorHaibo Huang <hhb@google.com>2020-05-20 04:45:51 -0700
committerHaibo Huang <hhb@google.com>2020-05-20 04:45:51 -0700
commit48ad6aad4d82d71b250cdcbfd0849852df53df41 (patch)
tree2f12c5c144ad7862d457f97d320226c0f47c622d /Lib/fontTools/misc
parentadd91cddc62bc5fc56859a6bdcc353e6bc3b50f8 (diff)
downloadfonttools-48ad6aad4d82d71b250cdcbfd0849852df53df41.tar.gz
Upgrade fonttools to 4.10.2
Test: None Change-Id: Id3822c9b749cbea541cf013f9404d8548292e3f4
Diffstat (limited to 'Lib/fontTools/misc')
-rw-r--r--Lib/fontTools/misc/arrayTools.py261
-rw-r--r--Lib/fontTools/misc/bezierTools.py279
-rw-r--r--Lib/fontTools/misc/cliTools.py22
-rw-r--r--Lib/fontTools/misc/eexec.py62
4 files changed, 495 insertions, 129 deletions
diff --git a/Lib/fontTools/misc/arrayTools.py b/Lib/fontTools/misc/arrayTools.py
index f929676c..81b2418d 100644
--- a/Lib/fontTools/misc/arrayTools.py
+++ b/Lib/fontTools/misc/arrayTools.py
@@ -1,8 +1,6 @@
-#
-# Various array and rectangle tools, but mostly rectangles, hence the
-# name of this module (not).
-#
-
+"""Routines for calculating bounding boxes, point in rectangle calculations and
+so on.
+"""
from fontTools.misc.py23 import *
from fontTools.misc.fixedTools import otRound
@@ -11,8 +9,13 @@ import math
import operator
def calcBounds(array):
- """Return the bounding rectangle of a 2D points array as a tuple:
- (xMin, yMin, xMax, yMax)
+ """Calculate the bounding rectangle of a 2D points array.
+
+ Args:
+ array: A sequence of 2D tuples.
+
+ Returns:
+ A four-item tuple representing the bounding rectangle ``(xMin, yMin, xMax, yMax)``.
"""
if len(array) == 0:
return 0, 0, 0, 0
@@ -21,29 +24,64 @@ def calcBounds(array):
return min(xs), min(ys), max(xs), max(ys)
def calcIntBounds(array, round=otRound):
- """Return the integer bounding rectangle of a 2D points array as a
- tuple: (xMin, yMin, xMax, yMax)
- Values are rounded to closest integer towards +Infinity using otRound
- function by default, unless an optional 'round' function is passed.
+ """Calculate the integer bounding rectangle of a 2D points array.
+
+ Values are rounded to closest integer towards ``+Infinity`` using the
+ :func:`fontTools.misc.fixedTools.otRound` function by default, unless
+ an optional ``round`` function is passed.
+
+ Args:
+ array: A sequence of 2D tuples.
+ round: A rounding function of type ``f(x: float) -> int``.
+
+ Returns:
+ A four-item tuple of integers representing the bounding rectangle:
+ ``(xMin, yMin, xMax, yMax)``.
"""
return tuple(round(v) for v in calcBounds(array))
def updateBounds(bounds, p, min=min, max=max):
- """Return the bounding recangle of rectangle bounds and point (x, y)."""
+ """Add a point to a bounding rectangle.
+
+ Args:
+ bounds: A bounding rectangle expressed as a tuple
+ ``(xMin, yMin, xMax, yMax)``.
+ p: A 2D tuple representing a point.
+ min,max: functions to compute the minimum and maximum.
+
+ Returns:
+ The updated bounding rectangle ``(xMin, yMin, xMax, yMax)``.
+ """
(x, y) = p
xMin, yMin, xMax, yMax = bounds
return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y)
def pointInRect(p, rect):
- """Return True when point (x, y) is inside rect."""
+ """Test if a point is inside a bounding rectangle.
+
+ Args:
+ p: A 2D tuple representing a point.
+ rect: A bounding rectangle expressed as a tuple
+ ``(xMin, yMin, xMax, yMax)``.
+
+ Returns:
+ ``True`` if the point is inside the rectangle, ``False`` otherwise.
+ """
(x, y) = p
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.
+ """Determine which points are inside a bounding rectangle.
+
+ Args:
+ array: A sequence of 2D tuples.
+ rect: A bounding rectangle expressed as a tuple
+ ``(xMin, yMin, xMax, yMax)``.
+
+ Returns:
+ A list containing the points inside the rectangle.
"""
if len(array) < 1:
return []
@@ -51,41 +89,105 @@ def pointsInRect(array, rect):
return [(xMin <= x <= xMax) and (yMin <= y <= yMax) for x, y in array]
def vectorLength(vector):
- """Return the length of the given vector."""
+ """Calculate the length of the given vector.
+
+ Args:
+ vector: A 2D tuple.
+
+ Returns:
+ The Euclidean length of the vector.
+ """
x, y = vector
return math.sqrt(x**2 + y**2)
def asInt16(array):
- """Round and cast to 16 bit integer."""
+ """Round a list of floats to 16-bit signed integers.
+
+ Args:
+ array: List of float values.
+
+ Returns:
+ A list of rounded integers.
+ """
return [int(math.floor(i+0.5)) for i in array]
def normRect(rect):
- """Normalize the rectangle so that the following holds:
+ """Normalize a bounding box rectangle.
+
+ This function "turns the rectangle the right way up", so that the following
+ holds::
+
xMin <= xMax and yMin <= yMax
+
+ Args:
+ rect: A bounding rectangle expressed as a tuple
+ ``(xMin, yMin, xMax, yMax)``.
+
+ Returns:
+ A normalized bounding rectangle.
"""
(xMin, yMin, xMax, yMax) = rect
return min(xMin, xMax), min(yMin, yMax), max(xMin, xMax), max(yMin, yMax)
def scaleRect(rect, x, y):
- """Scale the rectangle by x, y."""
+ """Scale a bounding box rectangle.
+
+ Args:
+ rect: A bounding rectangle expressed as a tuple
+ ``(xMin, yMin, xMax, yMax)``.
+ x: Factor to scale the rectangle along the X axis.
+ Y: Factor to scale the rectangle along the Y axis.
+
+ Returns:
+ A scaled bounding rectangle.
+ """
(xMin, yMin, xMax, yMax) = rect
return xMin * x, yMin * y, xMax * x, yMax * y
def offsetRect(rect, dx, dy):
- """Offset the rectangle by dx, dy."""
+ """Offset a bounding box rectangle.
+
+ Args:
+ rect: A bounding rectangle expressed as a tuple
+ ``(xMin, yMin, xMax, yMax)``.
+ dx: Amount to offset the rectangle along the X axis.
+ dY: Amount to offset the rectangle along the Y axis.
+
+ Returns:
+ An offset bounding rectangle.
+ """
(xMin, yMin, xMax, yMax) = rect
return xMin+dx, yMin+dy, xMax+dx, yMax+dy
def insetRect(rect, dx, dy):
- """Inset the rectangle by dx, dy on all sides."""
+ """Inset a bounding box rectangle on all sides.
+
+ Args:
+ rect: A bounding rectangle expressed as a tuple
+ ``(xMin, yMin, xMax, yMax)``.
+ dx: Amount to inset the rectangle along the X axis.
+ dY: Amount to inset the rectangle along the Y axis.
+
+ Returns:
+ An inset bounding rectangle.
+ """
(xMin, yMin, xMax, yMax) = rect
return xMin+dx, yMin+dy, xMax-dx, yMax-dy
def sectRect(rect1, rect2):
- """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.
+ """Test for rectangle-rectangle intersection.
+
+ Args:
+ rect1: First bounding rectangle, expressed as tuples
+ ``(xMin, yMin, xMax, yMax)``.
+ rect2: Second bounding rectangle.
+
+ Returns:
+ A boolean and a rectangle.
+ If the input rectangles intersect, returns ``True`` and the intersecting
+ rectangle. Returns ``False`` and ``(0, 0, 0, 0)`` if the input
+ rectangles don't intersect.
"""
(xMin1, yMin1, xMax1, yMax1) = rect1
(xMin2, yMin2, xMax2, yMax2) = rect2
@@ -96,9 +198,16 @@ def sectRect(rect1, rect2):
return True, (xMin, yMin, xMax, yMax)
def unionRect(rect1, rect2):
- """Return the smallest rectangle in which both input rectangles are fully
- enclosed. In other words, return the total bounding rectangle of both input
- rectangles.
+ """Determine union of bounding rectangles.
+
+ Args:
+ rect1: First bounding rectangle, expressed as tuples
+ ``(xMin, yMin, xMax, yMax)``.
+ rect2: Second bounding rectangle.
+
+ Returns:
+ The smallest rectangle in which both input rectangles are fully
+ enclosed.
"""
(xMin1, yMin1, xMax1, yMax1) = rect1
(xMin2, yMin2, xMax2, yMax2) = rect2
@@ -106,16 +215,32 @@ def unionRect(rect1, rect2):
max(xMax1, xMax2), max(yMax1, yMax2))
return (xMin, yMin, xMax, yMax)
-def rectCenter(rect0):
- """Return the center of the rectangle as an (x, y) coordinate."""
- (xMin, yMin, xMax, yMax) = rect0
+def rectCenter(rect):
+ """Determine rectangle center.
+
+ Args:
+ rect: Bounding rectangle, expressed as tuples
+ ``(xMin, yMin, xMax, yMax)``.
+
+ Returns:
+ A 2D tuple representing the point at the center of the rectangle.
+ """
+ (xMin, yMin, xMax, yMax) = rect
return (xMin+xMax)/2, (yMin+yMax)/2
-def intRect(rect1):
- """Return the rectangle, rounded off to integer values, but guaranteeing that
- the resulting rectangle is NOT smaller than the original.
+def intRect(rect):
+ """Round a rectangle to integer values.
+
+ Guarantees that the resulting rectangle is NOT smaller than the original.
+
+ Args:
+ rect: Bounding rectangle, expressed as tuples
+ ``(xMin, yMin, xMax, yMax)``.
+
+ Returns:
+ A rounded bounding rectangle.
"""
- (xMin, yMin, xMax, yMax) = rect1
+ (xMin, yMin, xMax, yMax) = rect
xMin = int(math.floor(xMin))
yMin = int(math.floor(yMin))
xMax = int(math.ceil(xMax))
@@ -124,9 +249,18 @@ def intRect(rect1):
class Vector(object):
- """A math-like vector."""
+ """A math-like vector.
+
+ Represents an n-dimensional numeric vector. ``Vector`` objects support
+ vector addition and subtraction, scalar multiplication and division,
+ negation, rounding, and comparison tests.
+
+ Attributes:
+ values: Sequence of values stored in the vector.
+ """
def __init__(self, values, keep=False):
+ """Initialize a vector. If ``keep`` is true, values will be copied."""
self.values = values if keep else list(values)
def __getitem__(self, index):
@@ -191,6 +325,7 @@ class Vector(object):
def __round__(self):
return Vector(self._unaryOp(round), keep=True)
def toInt(self):
+ """Synonym for ``round``."""
return self.__round__()
def __eq__(self, other):
@@ -208,6 +343,8 @@ class Vector(object):
def __abs__(self):
return math.sqrt(sum([x*x for x in self.values]))
def dot(self, other):
+ """Performs vector dot product, returning sum of
+ ``a[0] * b[0], a[1] * b[1], ...``"""
a = self.values
b = other.values if type(other) == Vector else b
assert len(a) == len(b)
@@ -215,29 +352,37 @@ class Vector(object):
def pairwise(iterable, reverse=False):
- """Iterate over current and next items in iterable, optionally in
- reverse order.
-
- >>> tuple(pairwise([]))
- ()
- >>> tuple(pairwise([], reverse=True))
- ()
- >>> tuple(pairwise([0]))
- ((0, 0),)
- >>> tuple(pairwise([0], reverse=True))
- ((0, 0),)
- >>> tuple(pairwise([0, 1]))
- ((0, 1), (1, 0))
- >>> tuple(pairwise([0, 1], reverse=True))
- ((1, 0), (0, 1))
- >>> tuple(pairwise([0, 1, 2]))
- ((0, 1), (1, 2), (2, 0))
- >>> tuple(pairwise([0, 1, 2], reverse=True))
- ((2, 1), (1, 0), (0, 2))
- >>> tuple(pairwise(['a', 'b', 'c', 'd']))
- (('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'a'))
- >>> tuple(pairwise(['a', 'b', 'c', 'd'], reverse=True))
- (('d', 'c'), ('c', 'b'), ('b', 'a'), ('a', 'd'))
+ """Iterate over current and next items in iterable.
+
+ Args:
+ iterable: An iterable
+ reverse: If true, iterate in reverse order.
+
+ Returns:
+ A iterable yielding two elements per iteration.
+
+ Example:
+
+ >>> tuple(pairwise([]))
+ ()
+ >>> tuple(pairwise([], reverse=True))
+ ()
+ >>> tuple(pairwise([0]))
+ ((0, 0),)
+ >>> tuple(pairwise([0], reverse=True))
+ ((0, 0),)
+ >>> tuple(pairwise([0, 1]))
+ ((0, 1), (1, 0))
+ >>> tuple(pairwise([0, 1], reverse=True))
+ ((1, 0), (0, 1))
+ >>> tuple(pairwise([0, 1, 2]))
+ ((0, 1), (1, 2), (2, 0))
+ >>> tuple(pairwise([0, 1, 2], reverse=True))
+ ((2, 1), (1, 0), (0, 2))
+ >>> tuple(pairwise(['a', 'b', 'c', 'd']))
+ (('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'a'))
+ >>> tuple(pairwise(['a', 'b', 'c', 'd'], reverse=True))
+ (('d', 'c'), ('c', 'b'), ('b', 'a'), ('a', 'd'))
"""
if not iterable:
return
diff --git a/Lib/fontTools/misc/bezierTools.py b/Lib/fontTools/misc/bezierTools.py
index bd436fe2..bc3bea2e 100644
--- a/Lib/fontTools/misc/bezierTools.py
+++ b/Lib/fontTools/misc/bezierTools.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-"""fontTools.misc.bezierTools.py -- tools for working with bezier path segments.
+"""fontTools.misc.bezierTools.py -- tools for working with Bezier path segments.
"""
from fontTools.misc.arrayTools import calcBounds
@@ -29,7 +29,19 @@ __all__ = [
def calcCubicArcLength(pt1, pt2, pt3, pt4, tolerance=0.005):
- """Return the arc length for a cubic bezier segment."""
+ """Calculates the arc length for a cubic Bezier segment.
+
+ Whereas :func:`approximateCubicArcLength` approximates the length, this
+ function calculates it by "measuring", recursively dividing the curve
+ until the divided segments are shorter than ``tolerance``.
+
+ Args:
+ pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
+ tolerance: Controls the precision of the calcuation.
+
+ Returns:
+ Arc length value.
+ """
return calcCubicArcLengthC(complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4), tolerance)
@@ -49,7 +61,15 @@ def _calcCubicArcLengthCRecurse(mult, p0, p1, p2, p3):
return _calcCubicArcLengthCRecurse(mult, *one) + _calcCubicArcLengthCRecurse(mult, *two)
def calcCubicArcLengthC(pt1, pt2, pt3, pt4, tolerance=0.005):
- """Return the arc length for a cubic bezier segment using complex points."""
+ """Calculates the arc length for a cubic Bezier segment.
+
+ Args:
+ pt1,pt2,pt3,pt4: Control points of the Bezier as complex numbers.
+ tolerance: Controls the precision of the calcuation.
+
+ Returns:
+ Arc length value.
+ """
mult = 1. + 1.5 * tolerance # The 1.5 is a empirical hack; no math
return _calcCubicArcLengthCRecurse(mult, pt1, pt2, pt3, pt4)
@@ -69,8 +89,17 @@ def _intSecAtan(x):
def calcQuadraticArcLength(pt1, pt2, pt3):
- """Return the arc length for a qudratic bezier segment.
- pt1 and pt3 are the "anchor" points, pt2 is the "handle".
+ """Calculates the arc length for a quadratic Bezier segment.
+
+ Args:
+ pt1: Start point of the Bezier as 2D tuple.
+ pt2: Handle point of the Bezier as 2D tuple.
+ pt3: End point of the Bezier as 2D tuple.
+
+ Returns:
+ Arc length value.
+
+ Example::
>>> calcQuadraticArcLength((0, 0), (0, 0), (0, 0)) # empty segment
0.0
@@ -95,9 +124,16 @@ def calcQuadraticArcLength(pt1, pt2, pt3):
def calcQuadraticArcLengthC(pt1, pt2, pt3):
- """Return the arc length for a qudratic bezier segment using complex points.
- pt1 and pt3 are the "anchor" points, pt2 is the "handle"."""
+ """Calculates the arc length for a quadratic Bezier segment.
+ Args:
+ pt1: Start point of the Bezier as a complex number.
+ pt2: Handle point of the Bezier as a complex number.
+ pt3: End point of the Bezier as a complex number.
+
+ Returns:
+ Arc length value.
+ """
# Analytical solution to the length of a quadratic bezier.
# I'll explain how I arrived at this later.
d0 = pt2 - pt1
@@ -120,15 +156,36 @@ def calcQuadraticArcLengthC(pt1, pt2, pt3):
def approximateQuadraticArcLength(pt1, pt2, pt3):
- # Approximate length of quadratic Bezier curve using Gauss-Legendre quadrature
- # with n=3 points.
+ """Calculates the arc length for a quadratic Bezier segment.
+
+ Uses Gauss-Legendre quadrature for a branch-free approximation.
+ See :func:`calcQuadraticArcLength` for a slower but more accurate result.
+
+ Args:
+ pt1: Start point of the Bezier as 2D tuple.
+ pt2: Handle point of the Bezier as 2D tuple.
+ pt3: End point of the Bezier as 2D tuple.
+
+ Returns:
+ Approximate arc length value.
+ """
return approximateQuadraticArcLengthC(complex(*pt1), complex(*pt2), complex(*pt3))
def approximateQuadraticArcLengthC(pt1, pt2, pt3):
- # Approximate length of quadratic Bezier curve using Gauss-Legendre quadrature
- # with n=3 points for complex points.
- #
+ """Calculates the arc length for a quadratic Bezier segment.
+
+ Uses Gauss-Legendre quadrature for a branch-free approximation.
+ See :func:`calcQuadraticArcLength` for a slower but more accurate result.
+
+ Args:
+ pt1: Start point of the Bezier as a complex number.
+ pt2: Handle point of the Bezier as a complex number.
+ pt3: End point of the Bezier as a complex number.
+
+ Returns:
+ Approximate arc length value.
+ """
# This, essentially, approximates the length-of-derivative function
# to be integrated with the best-matching fifth-degree polynomial
# approximation of it.
@@ -145,8 +202,17 @@ def approximateQuadraticArcLengthC(pt1, pt2, pt3):
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".
+ """Calculates the bounding rectangle for a quadratic Bezier segment.
+
+ Args:
+ pt1: Start point of the Bezier as a 2D tuple.
+ pt2: Handle point of the Bezier as a 2D tuple.
+ pt3: End point of the Bezier as a 2D tuple.
+
+ Returns:
+ A four-item tuple representing the bounding rectangle ``(xMin, yMin, xMax, yMax)``.
+
+ Example::
>>> calcQuadraticBounds((0, 0), (50, 100), (100, 0))
(0, 0, 100, 50.0)
@@ -166,8 +232,18 @@ def calcQuadraticBounds(pt1, pt2, pt3):
def approximateCubicArcLength(pt1, pt2, pt3, pt4):
- """Return the approximate arc length for a cubic bezier segment.
- pt1 and pt4 are the "anchor" points, pt2 and pt3 are the "handles".
+ """Approximates the arc length for a cubic Bezier segment.
+
+ Uses Gauss-Lobatto quadrature with n=5 points to approximate arc length.
+ See :func:`calcCubicArcLength` for a slower but more accurate result.
+
+ Args:
+ pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
+
+ Returns:
+ Arc length value.
+
+ Example::
>>> approximateCubicArcLength((0, 0), (25, 100), (75, 100), (100, 0))
190.04332968932817
@@ -180,18 +256,18 @@ def approximateCubicArcLength(pt1, pt2, pt3, pt4):
>>> approximateCubicArcLength((0, 0), (50, 0), (100, -50), (-50, 0)) # cusp
154.80848416537057
"""
- # Approximate length of cubic Bezier curve using Gauss-Lobatto quadrature
- # with n=5 points.
return approximateCubicArcLengthC(complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4))
def approximateCubicArcLengthC(pt1, pt2, pt3, pt4):
- """Return the approximate arc length for a cubic bezier segment of complex points.
- pt1 and pt4 are the "anchor" points, pt2 and pt3 are the "handles"."""
+ """Approximates the arc length for a cubic Bezier segment.
- # Approximate length of cubic Bezier curve using Gauss-Lobatto quadrature
- # with n=5 points for complex points.
- #
+ Args:
+ pt1,pt2,pt3,pt4: Control points of the Bezier as complex numbers.
+
+ Returns:
+ Arc length value.
+ """
# This, essentially, approximates the length-of-derivative function
# to be integrated with the best-matching seventh-degree polynomial
# approximation of it.
@@ -210,8 +286,15 @@ def approximateCubicArcLengthC(pt1, pt2, pt3, pt4):
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".
+ """Calculates the bounding rectangle for a quadratic Bezier segment.
+
+ Args:
+ pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
+
+ Returns:
+ A four-item tuple representing the bounding rectangle ``(xMin, yMin, xMax, yMax)``.
+
+ Example::
>>> calcCubicBounds((0, 0), (25, 100), (75, 100), (100, 0))
(0, 0, 100, 75.0)
@@ -235,11 +318,22 @@ def calcCubicBounds(pt1, pt2, pt3, pt4):
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 a line at a given coordinate.
+
+ Args:
+ pt1: Start point of line as 2D tuple.
+ pt2: End point of line as 2D tuple.
+ where: Position at which to split the line.
+ isHorizontal: Direction of the ray splitting the line. If true,
+ ``where`` is interpreted as a Y coordinate; if false, then
+ ``where`` is interpreted as an X coordinate.
+
+ Returns:
+ A list of two line segments (each line segment being two 2D tuples)
+ if the line was successfully split, or a list containing the original
+ line.
+
+ Example::
>>> printSegments(splitLine((0, 0), (100, 100), 50, True))
((0, 0), (50, 50))
@@ -281,9 +375,21 @@ def splitLine(pt1, pt2, where, isHorizontal):
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 a quadratic Bezier curve at a given coordinate.
+
+ Args:
+ pt1,pt2,pt3: Control points of the Bezier as 2D tuples.
+ where: Position at which to split the curve.
+ isHorizontal: Direction of the ray splitting the curve. If true,
+ ``where`` is interpreted as a Y coordinate; if false, then
+ ``where`` is interpreted as an X coordinate.
+
+ Returns:
+ A list of two curve segments (each curve segment being three 2D tuples)
+ if the curve was successfully split, or a list containing the original
+ curve.
+
+ Example::
>>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 150, False))
((0, 0), (50, 100), (100, 0))
@@ -313,9 +419,21 @@ def splitQuadratic(pt1, pt2, pt3, where, isHorizontal):
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 a cubic Bezier curve at a given coordinate.
+
+ Args:
+ pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
+ where: Position at which to split the curve.
+ isHorizontal: Direction of the ray splitting the curve. If true,
+ ``where`` is interpreted as a Y coordinate; if false, then
+ ``where`` is interpreted as an X coordinate.
+
+ Returns:
+ A list of two curve segments (each curve segment being four 2D tuples)
+ if the curve was successfully split, or a list containing the original
+ curve.
+
+ Example::
>>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 150, False))
((0, 0), (25, 100), (75, 100), (100, 0))
@@ -337,8 +455,16 @@ def splitCubic(pt1, pt2, pt3, pt4, where, isHorizontal):
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 a quadratic Bezier curve at one or more values of t.
+
+ Args:
+ pt1,pt2,pt3: Control points of the Bezier as 2D tuples.
+ *ts: Positions at which to split the curve.
+
+ Returns:
+ A list of curve segments (each curve segment being three 2D tuples).
+
+ Examples::
>>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5))
((0, 0), (25, 50), (50, 50))
@@ -353,8 +479,16 @@ def splitQuadraticAtT(pt1, pt2, pt3, *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 a cubic Bezier curve at one or more values of t.
+
+ Args:
+ pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
+ *ts: Positions at which to split the curve.
+
+ Returns:
+ A list of curve segments (each curve segment being four 2D tuples).
+
+ Examples::
>>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5))
((0, 0), (12.5, 50), (31.25, 75), (50, 75))
@@ -437,10 +571,18 @@ from math import sqrt, acos, cos, pi
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!
+ """Solve a quadratic equation.
+
+ Solves *a*x*x + b*x + c = 0* where a, b and c are real.
+
+ Args:
+ a: coefficient of *x²*
+ b: coefficient of *x*
+ c: constant term
+
+ 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:
@@ -462,25 +604,36 @@ def solveQuadratic(a, b, c,
def solveCubic(a, b, c, d):
- """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!
-
- >>> solveCubic(1, 1, -6, 0)
- [-3.0, -0.0, 2.0]
- >>> solveCubic(-10.0, -9.0, 48.0, -29.0)
- [-2.9, 1.0, 1.0]
- >>> solveCubic(-9.875, -9.0, 47.625, -28.75)
- [-2.911392, 1.0, 1.0]
- >>> solveCubic(1.0, -4.5, 6.75, -3.375)
- [1.5, 1.5, 1.5]
- >>> solveCubic(-12.0, 18.0, -9.0, 1.50023651123)
- [0.5, 0.5, 0.5]
- >>> solveCubic(
- ... 9.0, 0.0, 0.0, -7.62939453125e-05
- ... ) == [-0.0, -0.0, -0.0]
- True
+ """Solve a cubic equation.
+
+ Solves *a*x*x*x + b*x*x + c*x + d = 0* where a, b, c and d are real.
+
+ Args:
+ a: coefficient of *x³*
+ b: coefficient of *x²*
+ c: coefficient of *x*
+ d: constant term
+
+ Returns:
+ A list of roots. Note that the returned list is neither guaranteed to
+ be sorted nor to contain unique values!
+
+ Examples::
+
+ >>> solveCubic(1, 1, -6, 0)
+ [-3.0, -0.0, 2.0]
+ >>> solveCubic(-10.0, -9.0, 48.0, -29.0)
+ [-2.9, 1.0, 1.0]
+ >>> solveCubic(-9.875, -9.0, 47.625, -28.75)
+ [-2.911392, 1.0, 1.0]
+ >>> solveCubic(1.0, -4.5, 6.75, -3.375)
+ [1.5, 1.5, 1.5]
+ >>> solveCubic(-12.0, 18.0, -9.0, 1.50023651123)
+ [0.5, 0.5, 0.5]
+ >>> solveCubic(
+ ... 9.0, 0.0, 0.0, -7.62939453125e-05
+ ... ) == [-0.0, -0.0, -0.0]
+ True
"""
#
# adapted from:
diff --git a/Lib/fontTools/misc/cliTools.py b/Lib/fontTools/misc/cliTools.py
index 48913c14..4e5353b9 100644
--- a/Lib/fontTools/misc/cliTools.py
+++ b/Lib/fontTools/misc/cliTools.py
@@ -8,6 +8,28 @@ numberAddedRE = re.compile(r"#\d+$")
def makeOutputFileName(input, outputDir=None, extension=None, overWrite=False):
+ """Generates a suitable file name for writing output.
+
+ Often tools will want to take a file, do some kind of transformation to it,
+ and write it out again. This function determines an appropriate name for the
+ output file, through one or more of the following steps:
+
+ - changing the output directory
+ - replacing the file extension
+ - suffixing the filename with a number (``#1``, ``#2``, etc.) to avoid
+ overwriting an existing file.
+
+ Args:
+ input: Name of input file.
+ outputDir: Optionally, a new directory to write the file into.
+ extension: Optionally, a replacement for the current file extension.
+ overWrite: Overwriting an existing file is permitted if true; if false
+ and the proposed filename exists, a new name will be generated by
+ adding an appropriate number suffix.
+
+ Returns:
+ str: Suitable output filename
+ """
dirName, fileName = os.path.split(input)
fileName, ext = os.path.splitext(fileName)
if outputDir:
diff --git a/Lib/fontTools/misc/eexec.py b/Lib/fontTools/misc/eexec.py
index 19ec6b08..36719a1c 100644
--- a/Lib/fontTools/misc/eexec.py
+++ b/Lib/fontTools/misc/eexec.py
@@ -1,5 +1,15 @@
-"""fontTools.misc.eexec.py -- Module implementing the eexec and
-charstring encryption algorithm as used by PostScript Type 1 fonts.
+"""
+PostScript Type 1 fonts make use of two types of encryption: charstring
+encryption and ``eexec`` encryption. Charstring encryption is used for
+the charstrings themselves, while ``eexec`` is used to encrypt larger
+sections of the font program, such as the ``Private`` and ``CharStrings``
+dictionaries. Despite the different names, the algorithm is the same,
+although ``eexec`` encryption uses a fixed initial key R=55665.
+
+The algorithm uses cipher feedback, meaning that the ciphertext is used
+to modify the key. Because of this, the routines in this module return
+the new key at the end of the operation.
+
"""
from fontTools.misc.py23 import *
@@ -19,12 +29,24 @@ def _encryptChar(plain, R):
def decrypt(cipherstring, R):
r"""
- >>> testStr = b"\0\0asdadads asds\265"
- >>> decryptedStr, R = decrypt(testStr, 12321)
- >>> decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
- True
- >>> R == 36142
- True
+ Decrypts a string using the Type 1 encryption algorithm.
+
+ Args:
+ cipherstring: String of ciphertext.
+ R: Initial key.
+
+ Returns:
+ decryptedStr: Plaintext string.
+ R: Output key for subsequent decryptions.
+
+ Examples::
+
+ >>> testStr = b"\0\0asdadads asds\265"
+ >>> decryptedStr, R = decrypt(testStr, 12321)
+ >>> decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
+ True
+ >>> R == 36142
+ True
"""
plainList = []
for cipher in cipherstring:
@@ -35,6 +57,30 @@ def decrypt(cipherstring, R):
def encrypt(plainstring, R):
r"""
+ Encrypts a string using the Type 1 encryption algorithm.
+
+ Note that the algorithm as described in the Type 1 specification requires the
+ plaintext to be prefixed with a number of random bytes. (For ``eexec`` the
+ number of random bytes is set to 4.) This routine does *not* add the random
+ prefix to its input.
+
+ Args:
+ plainstring: String of plaintext.
+ R: Initial key.
+
+ Returns:
+ cipherstring: Ciphertext string.
+ R: Output key for subsequent encryptions.
+
+ Examples::
+
+ >>> testStr = b"\0\0asdadads asds\265"
+ >>> decryptedStr, R = decrypt(testStr, 12321)
+ >>> decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
+ True
+ >>> R == 36142
+ True
+
>>> testStr = b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
>>> encryptedStr, R = encrypt(testStr, 12321)
>>> encryptedStr == b"\0\0asdadads asds\265"