[docs] fontTools.misc.* part 1 (#1956)

* Document misc.arrayTools
* Document misc.bezierTools
* Document cliTools
* Document eexec
This commit is contained in:
Simon Cozens 2020-05-19 09:51:17 +01:00 committed by GitHub
parent db26cf804e
commit 76902b7129
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 512 additions and 142 deletions

View File

@ -1,8 +1,9 @@
########## #############################################
arrayTools arrayTools: Various array and rectangle tools
########## #############################################
.. automodule:: fontTools.misc.arrayTools .. automodule:: fontTools.misc.arrayTools
:member-order: bysource
:inherited-members: :inherited-members:
:members: :members:
:undoc-members: :undoc-members:

View File

@ -1,6 +1,6 @@
########### ####################################################
bezierTools bezierTools: Routines for working with Bezier curves
########### ####################################################
.. automodule:: fontTools.misc.bezierTools .. automodule:: fontTools.misc.bezierTools
:inherited-members: :inherited-members:

View File

@ -1,6 +1,6 @@
######## ###################################################################
cliTools cliTools: Utilities for command-line interfaces and console scripts
######## ###################################################################
.. automodule:: fontTools.misc.cliTools .. automodule:: fontTools.misc.cliTools
:inherited-members: :inherited-members:

View File

@ -1,6 +1,6 @@
##### ###############################################################
eexec eexec: PostScript charstring encryption and decryption routines
##### ###############################################################
.. automodule:: fontTools.misc.eexec .. automodule:: fontTools.misc.eexec
:inherited-members: :inherited-members:

View File

@ -1,6 +1,9 @@
#### ##########################################################
misc misc: Miscellaneous libraries helpful for font engineering
#### ##########################################################
This is a collection of packages, most of which are used as internal support
utilities by fontTools, but some of which may be more generally useful.
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2

View File

@ -1,8 +1,6 @@
# """Routines for calculating bounding boxes, point in rectangle calculations and
# Various array and rectangle tools, but mostly rectangles, hence the so on.
# name of this module (not). """
#
from fontTools.misc.py23 import * from fontTools.misc.py23 import *
from fontTools.misc.fixedTools import otRound from fontTools.misc.fixedTools import otRound
@ -11,8 +9,13 @@ import math
import operator import operator
def calcBounds(array): def calcBounds(array):
"""Return the bounding rectangle of a 2D points array as a tuple: """Calculate the bounding rectangle of a 2D points array.
(xMin, yMin, xMax, yMax)
Args:
array: A sequence of 2D tuples.
Returns:
A four-item tuple representing the bounding rectangle ``(xMin, yMin, xMax, yMax)``.
""" """
if len(array) == 0: if len(array) == 0:
return 0, 0, 0, 0 return 0, 0, 0, 0
@ -21,29 +24,64 @@ def calcBounds(array):
return min(xs), min(ys), max(xs), max(ys) return min(xs), min(ys), max(xs), max(ys)
def calcIntBounds(array, round=otRound): def calcIntBounds(array, round=otRound):
"""Return the integer bounding rectangle of a 2D points array as a """Calculate the integer bounding rectangle of a 2D points array.
tuple: (xMin, yMin, xMax, yMax)
Values are rounded to closest integer towards +Infinity using otRound Values are rounded to closest integer towards ``+Infinity`` using the
function by default, unless an optional 'round' function is passed. :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)) return tuple(round(v) for v in calcBounds(array))
def updateBounds(bounds, p, min=min, max=max): 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 (x, y) = p
xMin, yMin, xMax, yMax = bounds xMin, yMin, xMax, yMax = bounds
return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y) return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y)
def pointInRect(p, rect): 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 (x, y) = p
xMin, yMin, xMax, yMax = rect xMin, yMin, xMax, yMax = rect
return (xMin <= x <= xMax) and (yMin <= y <= yMax) return (xMin <= x <= xMax) and (yMin <= y <= yMax)
def pointsInRect(array, rect): def pointsInRect(array, rect):
"""Find out which points or array are inside rect. """Determine which points are inside a bounding rectangle.
Returns an array with a boolean for each point.
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: if len(array) < 1:
return [] return []
@ -51,41 +89,105 @@ def pointsInRect(array, rect):
return [(xMin <= x <= xMax) and (yMin <= y <= yMax) for x, y in array] return [(xMin <= x <= xMax) and (yMin <= y <= yMax) for x, y in array]
def vectorLength(vector): 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 x, y = vector
return math.sqrt(x**2 + y**2) return math.sqrt(x**2 + y**2)
def asInt16(array): 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] return [int(math.floor(i+0.5)) for i in array]
def normRect(rect): 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 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 (xMin, yMin, xMax, yMax) = rect
return min(xMin, xMax), min(yMin, yMax), max(xMin, xMax), max(yMin, yMax) return min(xMin, xMax), min(yMin, yMax), max(xMin, xMax), max(yMin, yMax)
def scaleRect(rect, x, y): 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 (xMin, yMin, xMax, yMax) = rect
return xMin * x, yMin * y, xMax * x, yMax * y return xMin * x, yMin * y, xMax * x, yMax * y
def offsetRect(rect, dx, dy): 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 (xMin, yMin, xMax, yMax) = rect
return xMin+dx, yMin+dy, xMax+dx, yMax+dy return xMin+dx, yMin+dy, xMax+dx, yMax+dy
def insetRect(rect, dx, 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 (xMin, yMin, xMax, yMax) = rect
return xMin+dx, yMin+dy, xMax-dx, yMax-dy return xMin+dx, yMin+dy, xMax-dx, yMax-dy
def sectRect(rect1, rect2): def sectRect(rect1, rect2):
"""Return a boolean and a rectangle. If the input rectangles intersect, return """Test for rectangle-rectangle intersection.
True and the intersecting rectangle. Return False and (0, 0, 0, 0) if the input
rectangles don't intersect. 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 (xMin1, yMin1, xMax1, yMax1) = rect1
(xMin2, yMin2, xMax2, yMax2) = rect2 (xMin2, yMin2, xMax2, yMax2) = rect2
@ -96,9 +198,16 @@ def sectRect(rect1, rect2):
return True, (xMin, yMin, xMax, yMax) return True, (xMin, yMin, xMax, yMax)
def unionRect(rect1, rect2): def unionRect(rect1, rect2):
"""Return the smallest rectangle in which both input rectangles are fully """Determine union of bounding rectangles.
enclosed. In other words, return the total bounding rectangle of both input
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 (xMin1, yMin1, xMax1, yMax1) = rect1
(xMin2, yMin2, xMax2, yMax2) = rect2 (xMin2, yMin2, xMax2, yMax2) = rect2
@ -106,16 +215,32 @@ def unionRect(rect1, rect2):
max(xMax1, xMax2), max(yMax1, yMax2)) max(xMax1, xMax2), max(yMax1, yMax2))
return (xMin, yMin, xMax, yMax) return (xMin, yMin, xMax, yMax)
def rectCenter(rect0): def rectCenter(rect):
"""Return the center of the rectangle as an (x, y) coordinate.""" """Determine rectangle center.
(xMin, yMin, xMax, yMax) = rect0
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 return (xMin+xMax)/2, (yMin+yMax)/2
def intRect(rect1): def intRect(rect):
"""Return the rectangle, rounded off to integer values, but guaranteeing that """Round a rectangle to integer values.
the resulting rectangle is NOT smaller than the original.
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)) xMin = int(math.floor(xMin))
yMin = int(math.floor(yMin)) yMin = int(math.floor(yMin))
xMax = int(math.ceil(xMax)) xMax = int(math.ceil(xMax))
@ -124,9 +249,18 @@ def intRect(rect1):
class Vector(object): 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): 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) self.values = values if keep else list(values)
def __getitem__(self, index): def __getitem__(self, index):
@ -191,6 +325,7 @@ class Vector(object):
def __round__(self): def __round__(self):
return Vector(self._unaryOp(round), keep=True) return Vector(self._unaryOp(round), keep=True)
def toInt(self): def toInt(self):
"""Synonym for ``round``."""
return self.__round__() return self.__round__()
def __eq__(self, other): def __eq__(self, other):
@ -208,6 +343,8 @@ class Vector(object):
def __abs__(self): def __abs__(self):
return math.sqrt(sum([x*x for x in self.values])) return math.sqrt(sum([x*x for x in self.values]))
def dot(self, other): def dot(self, other):
"""Performs vector dot product, returning sum of
``a[0] * b[0], a[1] * b[1], ...``"""
a = self.values a = self.values
b = other.values if type(other) == Vector else b b = other.values if type(other) == Vector else b
assert len(a) == len(b) assert len(a) == len(b)
@ -215,29 +352,37 @@ class Vector(object):
def pairwise(iterable, reverse=False): def pairwise(iterable, reverse=False):
"""Iterate over current and next items in iterable, optionally in """Iterate over current and next items in iterable.
reverse order.
>>> tuple(pairwise([])) Args:
() iterable: An iterable
>>> tuple(pairwise([], reverse=True)) reverse: If true, iterate in reverse order.
()
>>> tuple(pairwise([0])) Returns:
((0, 0),) A iterable yielding two elements per iteration.
>>> tuple(pairwise([0], reverse=True))
((0, 0),) Example:
>>> tuple(pairwise([0, 1]))
((0, 1), (1, 0)) >>> tuple(pairwise([]))
>>> tuple(pairwise([0, 1], reverse=True)) ()
((1, 0), (0, 1)) >>> tuple(pairwise([], reverse=True))
>>> tuple(pairwise([0, 1, 2])) ()
((0, 1), (1, 2), (2, 0)) >>> tuple(pairwise([0]))
>>> tuple(pairwise([0, 1, 2], reverse=True)) ((0, 0),)
((2, 1), (1, 0), (0, 2)) >>> tuple(pairwise([0], reverse=True))
>>> tuple(pairwise(['a', 'b', 'c', 'd'])) ((0, 0),)
(('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'a')) >>> tuple(pairwise([0, 1]))
>>> tuple(pairwise(['a', 'b', 'c', 'd'], reverse=True)) ((0, 1), (1, 0))
(('d', 'c'), ('c', 'b'), ('b', 'a'), ('a', 'd')) >>> 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: if not iterable:
return return

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- 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 from fontTools.misc.arrayTools import calcBounds
@ -29,7 +29,19 @@ __all__ = [
def calcCubicArcLength(pt1, pt2, pt3, pt4, tolerance=0.005): 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) 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) return _calcCubicArcLengthCRecurse(mult, *one) + _calcCubicArcLengthCRecurse(mult, *two)
def calcCubicArcLengthC(pt1, pt2, pt3, pt4, tolerance=0.005): 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 mult = 1. + 1.5 * tolerance # The 1.5 is a empirical hack; no math
return _calcCubicArcLengthCRecurse(mult, pt1, pt2, pt3, pt4) return _calcCubicArcLengthCRecurse(mult, pt1, pt2, pt3, pt4)
@ -69,8 +89,17 @@ def _intSecAtan(x):
def calcQuadraticArcLength(pt1, pt2, pt3): def calcQuadraticArcLength(pt1, pt2, pt3):
"""Return the arc length for a qudratic bezier segment. """Calculates the arc length for a quadratic Bezier segment.
pt1 and pt3 are the "anchor" points, pt2 is the "handle".
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 >>> calcQuadraticArcLength((0, 0), (0, 0), (0, 0)) # empty segment
0.0 0.0
@ -95,9 +124,16 @@ def calcQuadraticArcLength(pt1, pt2, pt3):
def calcQuadraticArcLengthC(pt1, pt2, pt3): def calcQuadraticArcLengthC(pt1, pt2, pt3):
"""Return the arc length for a qudratic bezier segment using complex points. """Calculates the arc length for a quadratic Bezier segment.
pt1 and pt3 are the "anchor" points, pt2 is the "handle"."""
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. # Analytical solution to the length of a quadratic bezier.
# I'll explain how I arrived at this later. # I'll explain how I arrived at this later.
d0 = pt2 - pt1 d0 = pt2 - pt1
@ -120,15 +156,36 @@ def calcQuadraticArcLengthC(pt1, pt2, pt3):
def approximateQuadraticArcLength(pt1, pt2, pt3): def approximateQuadraticArcLength(pt1, pt2, pt3):
# Approximate length of quadratic Bezier curve using Gauss-Legendre quadrature """Calculates the arc length for a quadratic Bezier segment.
# with n=3 points.
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)) return approximateQuadraticArcLengthC(complex(*pt1), complex(*pt2), complex(*pt3))
def approximateQuadraticArcLengthC(pt1, pt2, pt3): def approximateQuadraticArcLengthC(pt1, pt2, pt3):
# Approximate length of quadratic Bezier curve using Gauss-Legendre quadrature """Calculates the arc length for a quadratic Bezier segment.
# with n=3 points for complex points.
# 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 # This, essentially, approximates the length-of-derivative function
# to be integrated with the best-matching fifth-degree polynomial # to be integrated with the best-matching fifth-degree polynomial
# approximation of it. # approximation of it.
@ -145,8 +202,17 @@ def approximateQuadraticArcLengthC(pt1, pt2, pt3):
def calcQuadraticBounds(pt1, pt2, pt3): def calcQuadraticBounds(pt1, pt2, pt3):
"""Return the bounding rectangle for a qudratic bezier segment. """Calculates the bounding rectangle for a quadratic Bezier segment.
pt1 and pt3 are the "anchor" points, pt2 is the "handle".
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)) >>> calcQuadraticBounds((0, 0), (50, 100), (100, 0))
(0, 0, 100, 50.0) (0, 0, 100, 50.0)
@ -166,8 +232,18 @@ def calcQuadraticBounds(pt1, pt2, pt3):
def approximateCubicArcLength(pt1, pt2, pt3, pt4): def approximateCubicArcLength(pt1, pt2, pt3, pt4):
"""Return the approximate arc length for a cubic bezier segment. """Approximates the arc length for a cubic Bezier segment.
pt1 and pt4 are the "anchor" points, pt2 and pt3 are the "handles".
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)) >>> approximateCubicArcLength((0, 0), (25, 100), (75, 100), (100, 0))
190.04332968932817 190.04332968932817
@ -180,18 +256,18 @@ def approximateCubicArcLength(pt1, pt2, pt3, pt4):
>>> approximateCubicArcLength((0, 0), (50, 0), (100, -50), (-50, 0)) # cusp >>> approximateCubicArcLength((0, 0), (50, 0), (100, -50), (-50, 0)) # cusp
154.80848416537057 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)) return approximateCubicArcLengthC(complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4))
def approximateCubicArcLengthC(pt1, pt2, pt3, pt4): def approximateCubicArcLengthC(pt1, pt2, pt3, pt4):
"""Return the approximate arc length for a cubic bezier segment of complex points. """Approximates the arc length for a cubic Bezier segment.
pt1 and pt4 are the "anchor" points, pt2 and pt3 are the "handles"."""
# Approximate length of cubic Bezier curve using Gauss-Lobatto quadrature Args:
# with n=5 points for complex points. pt1,pt2,pt3,pt4: Control points of the Bezier as complex numbers.
#
Returns:
Arc length value.
"""
# This, essentially, approximates the length-of-derivative function # This, essentially, approximates the length-of-derivative function
# to be integrated with the best-matching seventh-degree polynomial # to be integrated with the best-matching seventh-degree polynomial
# approximation of it. # approximation of it.
@ -210,8 +286,15 @@ def approximateCubicArcLengthC(pt1, pt2, pt3, pt4):
def calcCubicBounds(pt1, pt2, pt3, pt4): def calcCubicBounds(pt1, pt2, pt3, pt4):
"""Return the bounding rectangle for a cubic bezier segment. """Calculates the bounding rectangle for a quadratic Bezier segment.
pt1 and pt4 are the "anchor" points, pt2 and pt3 are the "handles".
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)) >>> calcCubicBounds((0, 0), (25, 100), (75, 100), (100, 0))
(0, 0, 100, 75.0) (0, 0, 100, 75.0)
@ -235,11 +318,22 @@ def calcCubicBounds(pt1, pt2, pt3, pt4):
def splitLine(pt1, pt2, where, isHorizontal): def splitLine(pt1, pt2, where, isHorizontal):
"""Split the line between pt1 and pt2 at position 'where', which """Split a line at a given coordinate.
is an x coordinate if isHorizontal is False, a y coordinate if
isHorizontal is True. Return a list of two line segments if the Args:
line was successfully split, or a list containing the original pt1: Start point of line as 2D tuple.
line. 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)) >>> printSegments(splitLine((0, 0), (100, 100), 50, True))
((0, 0), (50, 50)) ((0, 0), (50, 50))
@ -281,9 +375,21 @@ def splitLine(pt1, pt2, where, isHorizontal):
def splitQuadratic(pt1, pt2, pt3, where, isHorizontal): def splitQuadratic(pt1, pt2, pt3, where, isHorizontal):
"""Split the quadratic curve between pt1, pt2 and pt3 at position 'where', """Split a quadratic Bezier curve at a given coordinate.
which is an x coordinate if isHorizontal is False, a y coordinate if
isHorizontal is True. Return a list of curve segments. 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)) >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 150, False))
((0, 0), (50, 100), (100, 0)) ((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): def splitCubic(pt1, pt2, pt3, pt4, where, isHorizontal):
"""Split the cubic curve between pt1, pt2, pt3 and pt4 at position 'where', """Split a cubic Bezier curve at a given coordinate.
which is an x coordinate if isHorizontal is False, a y coordinate if
isHorizontal is True. Return a list of curve segments. 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)) >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 150, False))
((0, 0), (25, 100), (75, 100), (100, 0)) ((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): def splitQuadraticAtT(pt1, pt2, pt3, *ts):
"""Split the quadratic curve between pt1, pt2 and pt3 at one or more """Split a quadratic Bezier curve at one or more values of t.
values of t. Return a list of curve segments.
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)) >>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5))
((0, 0), (25, 50), (50, 50)) ((0, 0), (25, 50), (50, 50))
@ -353,8 +479,16 @@ def splitQuadraticAtT(pt1, pt2, pt3, *ts):
def splitCubicAtT(pt1, pt2, pt3, pt4, *ts): def splitCubicAtT(pt1, pt2, pt3, pt4, *ts):
"""Split the cubic curve between pt1, pt2, pt3 and pt4 at one or more """Split a cubic Bezier curve at one or more values of t.
values of t. Return a list of curve segments.
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)) >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5))
((0, 0), (12.5, 50), (31.25, 75), (50, 75)) ((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, def solveQuadratic(a, b, c,
sqrt=sqrt): sqrt=sqrt):
"""Solve a quadratic equation where a, b and c are real. """Solve a quadratic equation.
a*x*x + b*x + c = 0
This function returns a list of roots. Note that the returned list Solves *a*x*x + b*x + c = 0* where a, b and c are real.
is neither guaranteed to be sorted nor to contain unique values!
Args:
a: coefficient of **
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(a) < epsilon:
if abs(b) < epsilon: if abs(b) < epsilon:
@ -462,25 +604,36 @@ def solveQuadratic(a, b, c,
def solveCubic(a, b, c, d): def solveCubic(a, b, c, d):
"""Solve a cubic equation where a, b, c and d are real. """Solve a cubic equation.
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) Solves *a*x*x*x + b*x*x + c*x + d = 0* where a, b, c and d are real.
[-3.0, -0.0, 2.0]
>>> solveCubic(-10.0, -9.0, 48.0, -29.0) Args:
[-2.9, 1.0, 1.0] a: coefficient of **
>>> solveCubic(-9.875, -9.0, 47.625, -28.75) b: coefficient of **
[-2.911392, 1.0, 1.0] c: coefficient of *x*
>>> solveCubic(1.0, -4.5, 6.75, -3.375) d: constant term
[1.5, 1.5, 1.5]
>>> solveCubic(-12.0, 18.0, -9.0, 1.50023651123) Returns:
[0.5, 0.5, 0.5] A list of roots. Note that the returned list is neither guaranteed to
>>> solveCubic( be sorted nor to contain unique values!
... 9.0, 0.0, 0.0, -7.62939453125e-05
... ) == [-0.0, -0.0, -0.0] Examples::
True
>>> 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: # adapted from:

View File

@ -8,6 +8,28 @@ numberAddedRE = re.compile(r"#\d+$")
def makeOutputFileName(input, outputDir=None, extension=None, overWrite=False): 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) dirName, fileName = os.path.split(input)
fileName, ext = os.path.splitext(fileName) fileName, ext = os.path.splitext(fileName)
if outputDir: if outputDir:

View File

@ -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 * from fontTools.misc.py23 import *
@ -19,12 +29,24 @@ def _encryptChar(plain, R):
def decrypt(cipherstring, R): def decrypt(cipherstring, R):
r""" r"""
>>> testStr = b"\0\0asdadads asds\265" Decrypts a string using the Type 1 encryption algorithm.
>>> decryptedStr, R = decrypt(testStr, 12321)
>>> decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1' Args:
True cipherstring: String of ciphertext.
>>> R == 36142 R: Initial key.
True
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 = [] plainList = []
for cipher in cipherstring: for cipher in cipherstring:
@ -35,6 +57,30 @@ def decrypt(cipherstring, R):
def encrypt(plainstring, R): def encrypt(plainstring, R):
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' >>> testStr = b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
>>> encryptedStr, R = encrypt(testStr, 12321) >>> encryptedStr, R = encrypt(testStr, 12321)
>>> encryptedStr == b"\0\0asdadads asds\265" >>> encryptedStr == b"\0\0asdadads asds\265"