aboutsummaryrefslogtreecommitdiff
path: root/Lib
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
parentadd91cddc62bc5fc56859a6bdcc353e6bc3b50f8 (diff)
downloadfonttools-48ad6aad4d82d71b250cdcbfd0849852df53df41.tar.gz
Upgrade fonttools to 4.10.2
Test: None Change-Id: Id3822c9b749cbea541cf013f9404d8548292e3f4
Diffstat (limited to 'Lib')
-rw-r--r--Lib/fontTools/__init__.py2
-rw-r--r--Lib/fontTools/feaLib/ast.py40
-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
-rw-r--r--Lib/fontTools/ttLib/sfnt.py50
-rw-r--r--Lib/fontTools/ttLib/tables/_n_a_m_e.py7
-rw-r--r--Lib/fonttools.egg-info/PKG-INFO49
9 files changed, 590 insertions, 182 deletions
diff --git a/Lib/fontTools/__init__.py b/Lib/fontTools/__init__.py
index 11cc487d..32cd174b 100644
--- a/Lib/fontTools/__init__.py
+++ b/Lib/fontTools/__init__.py
@@ -4,6 +4,6 @@ from fontTools.misc.loggingTools import configLogger
log = logging.getLogger(__name__)
-version = __version__ = "4.10.0"
+version = __version__ = "4.10.2"
__all__ = ["version", "log", "configLogger"]
diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py
index a305e67f..8d5f97a8 100644
--- a/Lib/fontTools/feaLib/ast.py
+++ b/Lib/fontTools/feaLib/ast.py
@@ -617,16 +617,24 @@ class ChainContextPosStatement(Statement):
``prefix``, ``glyphs``, and ``suffix`` should be lists of
`glyph-containing objects`_ .
- ``lookups`` should be a list of lists containing :class:`LookupBlock`
- statements. The length of the outer list should equal to the length of
- ``glyphs``; the inner lists can be of variable length. Where there is no
- chaining lookup at the given glyph position, the entry in ``lookups``
- should be ``None``."""
+ ``lookups`` should be a list of elements representing what lookups
+ to apply at each glyph position. Each element should be a
+ :class:`LookupBlock` to apply a single chaining lookup at the given
+ position, a list of :class:`LookupBlock`\ s to apply multiple
+ lookups, or ``None`` to apply no lookup. The length of the outer
+ list should equal the length of ``glyphs``; the inner lists can be
+ of variable length."""
def __init__(self, prefix, glyphs, suffix, lookups, location=None):
Statement.__init__(self, location)
self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
- self.lookups = lookups
+ self.lookups = list(lookups)
+ for i, lookup in enumerate(lookups):
+ if lookup:
+ try:
+ (_ for _ in lookup)
+ except TypeError:
+ self.lookups[i] = [lookup]
def build(self, builder):
"""Calls the builder's ``add_chain_context_pos`` callback."""
@@ -662,14 +670,24 @@ class ChainContextSubstStatement(Statement):
``prefix``, ``glyphs``, and ``suffix`` should be lists of
`glyph-containing objects`_ .
- ``lookups`` should be a list of :class:`LookupBlock` statements, with
- length equal to the length of ``glyphs``. Where there is no chaining
- lookup at the given glyph position, the entry in ``lookups`` should be
- ``None``."""
+ ``lookups`` should be a list of elements representing what lookups
+ to apply at each glyph position. Each element should be a
+ :class:`LookupBlock` to apply a single chaining lookup at the given
+ position, a list of :class:`LookupBlock`\ s to apply multiple
+ lookups, or ``None`` to apply no lookup. The length of the outer
+ list should equal the length of ``glyphs``; the inner lists can be
+ of variable length."""
+
def __init__(self, prefix, glyphs, suffix, lookups, location=None):
Statement.__init__(self, location)
self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
- self.lookups = lookups
+ self.lookups = list(lookups)
+ for i, lookup in enumerate(lookups):
+ if lookup:
+ try:
+ (_ for _ in lookup)
+ except TypeError:
+ self.lookups[i] = [lookup]
def build(self, builder):
"""Calls the builder's ``add_chain_context_subst`` callback."""
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"
diff --git a/Lib/fontTools/ttLib/sfnt.py b/Lib/fontTools/ttLib/sfnt.py
index 9c45305d..2f3b6698 100644
--- a/Lib/fontTools/ttLib/sfnt.py
+++ b/Lib/fontTools/ttLib/sfnt.py
@@ -12,7 +12,9 @@ classes, since whenever to number of tables changes or whenever
a table's length chages you need to rewrite the whole file anyway.
"""
-from fontTools.misc.py23 import *
+from io import BytesIO
+from types import SimpleNamespace
+from fontTools.misc.py23 import Tag
from fontTools.misc import sstruct
from fontTools.ttLib import TTLibError
import struct
@@ -122,29 +124,29 @@ class SFNTReader(object):
def close(self):
self.file.close()
- def __deepcopy__(self, memo):
- """Overrides the default deepcopy of SFNTReader object, to make it work
- in the case when TTFont is loaded with lazy=True, and thus reader holds a
- reference to a file object which is not pickleable.
- We work around it by manually copying the data into a in-memory stream.
- """
- from copy import deepcopy
-
- cls = self.__class__
- obj = cls.__new__(cls)
- for k, v in self.__dict__.items():
- if k == "file":
- pos = v.tell()
- v.seek(0)
- buf = BytesIO(v.read())
- v.seek(pos)
- buf.seek(pos)
- if hasattr(v, "name"):
- buf.name = v.name
- obj.file = buf
- else:
- obj.__dict__[k] = deepcopy(v, memo)
- return obj
+ # We define custom __getstate__ and __setstate__ to make SFNTReader pickle-able
+ # and deepcopy-able. When a TTFont is loaded as lazy=True, SFNTReader holds a
+ # reference to an external file object which is not pickleable. So in __getstate__
+ # we store the file name and current position, and in __setstate__ we reopen the
+ # same named file after unpickling.
+
+ def __getstate__(self):
+ if isinstance(self.file, BytesIO):
+ # BytesIO is already pickleable, return the state unmodified
+ return self.__dict__
+
+ # remove unpickleable file attribute, and only store its name and pos
+ state = self.__dict__.copy()
+ del state["file"]
+ state["_filename"] = self.file.name
+ state["_filepos"] = self.file.tell()
+ return state
+
+ def __setstate__(self, state):
+ if "file" not in state:
+ self.file = open(state.pop("_filename"), "rb")
+ self.file.seek(state.pop("_filepos"))
+ self.__dict__.update(state)
# default compression level for WOFF 1.0 tables and metadata
diff --git a/Lib/fontTools/ttLib/tables/_n_a_m_e.py b/Lib/fontTools/ttLib/tables/_n_a_m_e.py
index ec5d07ee..3f756c8f 100644
--- a/Lib/fontTools/ttLib/tables/_n_a_m_e.py
+++ b/Lib/fontTools/ttLib/tables/_n_a_m_e.py
@@ -222,8 +222,11 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
# Collect matching name IDs
matchingNames = dict()
for name in self.names:
- key = (name.string, name.platformID,
- name.platEncID, name.langID)
+ try:
+ key = (name.toUnicode(), name.platformID,
+ name.platEncID, name.langID)
+ except UnicodeDecodeError:
+ continue
if key in reqNameSet:
nameSet = matchingNames.setdefault(name.nameID, set())
nameSet.add(key)
diff --git a/Lib/fonttools.egg-info/PKG-INFO b/Lib/fonttools.egg-info/PKG-INFO
index 3ca17207..96babb71 100644
--- a/Lib/fonttools.egg-info/PKG-INFO
+++ b/Lib/fonttools.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: fonttools
-Version: 4.10.0
+Version: 4.10.2
Summary: Tools to manipulate font files
Home-page: http://github.com/fonttools/fonttools
Author: Just van Rossum
@@ -211,15 +211,15 @@ Description: |Travis Build Status| |Appveyor Build status| |Coverage Status| |Py
Olivier Berten, Samyak Bhuta, Erik van Blokland, Petr van Blokland,
Jelle Bosma, Sascha Brawer, Tom Byrer, Frédéric Coiffier, Vincent
- Connare, Dave Crossland, Simon Daniels, Peter Dekkers, Behdad Esfahbod,
- Behnam Esfahbod, Hannes Famira, Sam Fishman, Matt Fontaine, Yannis
- Haralambous, Greg Hitchcock, Jeremie Hornus, Khaled Hosny, John Hudson,
- Denis Moyogo Jacquerye, Jack Jansen, Tom Kacvinsky, Jens Kutilek,
- Antoine Leca, Werner Lemberg, Tal Leming, Peter Lofting, Cosimo Lupo,
- Masaya Nakamura, Dave Opstad, Laurence Penney, Roozbeh Pournader, Garret
- Rieger, Read Roberts, Guido van Rossum, Just van Rossum, Andreas Seidel,
- Georg Seifert, Chris Simpkins, Miguel Sousa, Adam Twardoch, Adrien Tétar, Vitaly Volkov,
- Paul Wise.
+ Connare, David Corbett, Dave Crossland, Simon Daniels, Peter Dekkers,
+ Behdad Esfahbod, Behnam Esfahbod, Hannes Famira, Sam Fishman, Matt
+ Fontaine, Yannis Haralambous, Greg Hitchcock, Jeremie Hornus, Khaled
+ Hosny, John Hudson, Denis Moyogo Jacquerye, Jack Jansen, Tom Kacvinsky,
+ Jens Kutilek, Antoine Leca, Werner Lemberg, Tal Leming, Peter Lofting,
+ Cosimo Lupo, Masaya Nakamura, Dave Opstad, Laurence Penney, Roozbeh
+ Pournader, Garret Rieger, Read Roberts, Guido van Rossum, Just van
+ Rossum, Andreas Seidel, Georg Seifert, Chris Simpkins, Miguel Sousa,
+ Adam Twardoch, Adrien Tétar, Vitaly Volkov, Paul Wise.
Copyrights
~~~~~~~~~~
@@ -253,6 +253,25 @@ Description: |Travis Build Status| |Appveyor Build status| |Coverage Status| |Py
Changelog
~~~~~~~~~
+ 4.10.2 (released 2020-05-20)
+ ----------------------------
+
+ - [sfnt] Fixed ``NameError: SimpleNamespace`` while reading TTC header. The regression
+ was introduced with 4.10.1 after removing ``py23`` star import.
+
+ 4.10.1 (released 2020-05-19)
+ ----------------------------
+
+ - [sfnt] Make ``SFNTReader`` pickleable even when TTFont is loaded with lazy=True
+ option and thus keeps a reference to an external file (#1962, #1967).
+ - [feaLib.ast] Restore backward compatibility (broken in 4.10 with #1905) for
+ ``ChainContextPosStatement`` and ``ChainContextSubstStatement`` classes.
+ Make them accept either list of lookups or list of lists of lookups (#1961).
+ - [docs] Document some modules in ``fontTools.misc`` package: ``arrayTools``,
+ ``bezierTools`` ``cliTools`` and ``eexec`` (#1956).
+ - [ttLib._n_a_m_e] Fixed ``findMultilingualName()`` when name record's ``string`` is
+ encoded as bytes sequence (#1963).
+
4.10.0 (released 2020-05-15)
----------------------------
@@ -1945,13 +1964,13 @@ Classifier: Topic :: Text Processing :: Fonts
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
Requires-Python: >=3.6
-Provides-Extra: interpolatable
+Provides-Extra: unicode
+Provides-Extra: all
Provides-Extra: ufo
+Provides-Extra: interpolatable
Provides-Extra: graphite
-Provides-Extra: lxml
-Provides-Extra: unicode
Provides-Extra: type1
+Provides-Extra: symfont
+Provides-Extra: lxml
Provides-Extra: plot
Provides-Extra: woff
-Provides-Extra: symfont
-Provides-Extra: all