[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
:member-order: bysource
:inherited-members:
:members:
:undoc-members:

View File

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

View File

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

View File

@ -1,6 +1,6 @@
#####
eexec
#####
###############################################################
eexec: PostScript charstring encryption and decryption routines
###############################################################
.. automodule:: fontTools.misc.eexec
: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::
:maxdepth: 2

View File

@ -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.
"""Iterate over current and next items in iterable.
>>> 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'))
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

View File

@ -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 **
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!
"""Solve a cubic equation.
>>> 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
Solves *a*x*x*x + b*x*x + c*x + d = 0* where a, b, c and d are real.
Args:
a: coefficient of **
b: coefficient of **
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:

View File

@ -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:

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 *
@ -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"