remove venv
This commit is contained in:
parent
056387013d
commit
0680c7594e
13999 changed files with 0 additions and 2895688 deletions
|
|
@ -1,475 +0,0 @@
|
|||
"""fontTools.pens.basePen.py -- Tools and base classes to build pen objects.
|
||||
|
||||
The Pen Protocol
|
||||
|
||||
A Pen is a kind of object that standardizes the way how to "draw" outlines:
|
||||
it is a middle man between an outline and a drawing. In other words:
|
||||
it is an abstraction for drawing outlines, making sure that outline objects
|
||||
don't need to know the details about how and where they're being drawn, and
|
||||
that drawings don't need to know the details of how outlines are stored.
|
||||
|
||||
The most basic pattern is this::
|
||||
|
||||
outline.draw(pen) # 'outline' draws itself onto 'pen'
|
||||
|
||||
Pens can be used to render outlines to the screen, but also to construct
|
||||
new outlines. Eg. an outline object can be both a drawable object (it has a
|
||||
draw() method) as well as a pen itself: you *build* an outline using pen
|
||||
methods.
|
||||
|
||||
The AbstractPen class defines the Pen protocol. It implements almost
|
||||
nothing (only no-op closePath() and endPath() methods), but is useful
|
||||
for documentation purposes. Subclassing it basically tells the reader:
|
||||
"this class implements the Pen protocol.". An examples of an AbstractPen
|
||||
subclass is :py:class:`fontTools.pens.transformPen.TransformPen`.
|
||||
|
||||
The BasePen class is a base implementation useful for pens that actually
|
||||
draw (for example a pen renders outlines using a native graphics engine).
|
||||
BasePen contains a lot of base functionality, making it very easy to build
|
||||
a pen that fully conforms to the pen protocol. Note that if you subclass
|
||||
BasePen, you *don't* override moveTo(), lineTo(), etc., but _moveTo(),
|
||||
_lineTo(), etc. See the BasePen doc string for details. Examples of
|
||||
BasePen subclasses are fontTools.pens.boundsPen.BoundsPen and
|
||||
fontTools.pens.cocoaPen.CocoaPen.
|
||||
|
||||
Coordinates are usually expressed as (x, y) tuples, but generally any
|
||||
sequence of length 2 will do.
|
||||
"""
|
||||
|
||||
from typing import Tuple, Dict
|
||||
|
||||
from fontTools.misc.loggingTools import LogMixin
|
||||
from fontTools.misc.transform import DecomposedTransform, Identity
|
||||
|
||||
__all__ = [
|
||||
"AbstractPen",
|
||||
"NullPen",
|
||||
"BasePen",
|
||||
"PenError",
|
||||
"decomposeSuperBezierSegment",
|
||||
"decomposeQuadraticSegment",
|
||||
]
|
||||
|
||||
|
||||
class PenError(Exception):
|
||||
"""Represents an error during penning."""
|
||||
|
||||
|
||||
class OpenContourError(PenError):
|
||||
pass
|
||||
|
||||
|
||||
class AbstractPen:
|
||||
def moveTo(self, pt: Tuple[float, float]) -> None:
|
||||
"""Begin a new sub path, set the current point to 'pt'. You must
|
||||
end each sub path with a call to pen.closePath() or pen.endPath().
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def lineTo(self, pt: Tuple[float, float]) -> None:
|
||||
"""Draw a straight line from the current point to 'pt'."""
|
||||
raise NotImplementedError
|
||||
|
||||
def curveTo(self, *points: Tuple[float, float]) -> None:
|
||||
"""Draw a cubic bezier with an arbitrary number of control points.
|
||||
|
||||
The last point specified is on-curve, all others are off-curve
|
||||
(control) points. If the number of control points is > 2, the
|
||||
segment is split into multiple bezier segments. This works
|
||||
like this:
|
||||
|
||||
Let n be the number of control points (which is the number of
|
||||
arguments to this call minus 1). If n==2, a plain vanilla cubic
|
||||
bezier is drawn. If n==1, we fall back to a quadratic segment and
|
||||
if n==0 we draw a straight line. It gets interesting when n>2:
|
||||
n-1 PostScript-style cubic segments will be drawn as if it were
|
||||
one curve. See decomposeSuperBezierSegment().
|
||||
|
||||
The conversion algorithm used for n>2 is inspired by NURB
|
||||
splines, and is conceptually equivalent to the TrueType "implied
|
||||
points" principle. See also decomposeQuadraticSegment().
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def qCurveTo(self, *points: Tuple[float, float]) -> None:
|
||||
"""Draw a whole string of quadratic curve segments.
|
||||
|
||||
The last point specified is on-curve, all others are off-curve
|
||||
points.
|
||||
|
||||
This method implements TrueType-style curves, breaking up curves
|
||||
using 'implied points': between each two consequtive off-curve points,
|
||||
there is one implied point exactly in the middle between them. See
|
||||
also decomposeQuadraticSegment().
|
||||
|
||||
The last argument (normally the on-curve point) may be None.
|
||||
This is to support contours that have NO on-curve points (a rarely
|
||||
seen feature of TrueType outlines).
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def closePath(self) -> None:
|
||||
"""Close the current sub path. You must call either pen.closePath()
|
||||
or pen.endPath() after each sub path.
|
||||
"""
|
||||
pass
|
||||
|
||||
def endPath(self) -> None:
|
||||
"""End the current sub path, but don't close it. You must call
|
||||
either pen.closePath() or pen.endPath() after each sub path.
|
||||
"""
|
||||
pass
|
||||
|
||||
def addComponent(
|
||||
self,
|
||||
glyphName: str,
|
||||
transformation: Tuple[float, float, float, float, float, float],
|
||||
) -> None:
|
||||
"""Add a sub glyph. The 'transformation' argument must be a 6-tuple
|
||||
containing an affine transformation, or a Transform object from the
|
||||
fontTools.misc.transform module. More precisely: it should be a
|
||||
sequence containing 6 numbers.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def addVarComponent(
|
||||
self,
|
||||
glyphName: str,
|
||||
transformation: DecomposedTransform,
|
||||
location: Dict[str, float],
|
||||
) -> None:
|
||||
"""Add a VarComponent sub glyph. The 'transformation' argument
|
||||
must be a DecomposedTransform from the fontTools.misc.transform module,
|
||||
and the 'location' argument must be a dictionary mapping axis tags
|
||||
to their locations.
|
||||
"""
|
||||
# GlyphSet decomposes for us
|
||||
raise AttributeError
|
||||
|
||||
|
||||
class NullPen(AbstractPen):
|
||||
"""A pen that does nothing."""
|
||||
|
||||
def moveTo(self, pt):
|
||||
pass
|
||||
|
||||
def lineTo(self, pt):
|
||||
pass
|
||||
|
||||
def curveTo(self, *points):
|
||||
pass
|
||||
|
||||
def qCurveTo(self, *points):
|
||||
pass
|
||||
|
||||
def closePath(self):
|
||||
pass
|
||||
|
||||
def endPath(self):
|
||||
pass
|
||||
|
||||
def addComponent(self, glyphName, transformation):
|
||||
pass
|
||||
|
||||
def addVarComponent(self, glyphName, transformation, location):
|
||||
pass
|
||||
|
||||
|
||||
class LoggingPen(LogMixin, AbstractPen):
|
||||
"""A pen with a ``log`` property (see fontTools.misc.loggingTools.LogMixin)"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class MissingComponentError(KeyError):
|
||||
"""Indicates a component pointing to a non-existent glyph in the glyphset."""
|
||||
|
||||
|
||||
class DecomposingPen(LoggingPen):
|
||||
"""Implements a 'addComponent' method that decomposes components
|
||||
(i.e. draws them onto self as simple contours).
|
||||
It can also be used as a mixin class (e.g. see ContourRecordingPen).
|
||||
|
||||
You must override moveTo, lineTo, curveTo and qCurveTo. You may
|
||||
additionally override closePath, endPath and addComponent.
|
||||
|
||||
By default a warning message is logged when a base glyph is missing;
|
||||
set the class variable ``skipMissingComponents`` to False if you want
|
||||
all instances of a sub-class to raise a :class:`MissingComponentError`
|
||||
exception by default.
|
||||
"""
|
||||
|
||||
skipMissingComponents = True
|
||||
# alias error for convenience
|
||||
MissingComponentError = MissingComponentError
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
glyphSet,
|
||||
*args,
|
||||
skipMissingComponents=None,
|
||||
reverseFlipped=False,
|
||||
**kwargs,
|
||||
):
|
||||
"""Takes a 'glyphSet' argument (dict), in which the glyphs that are referenced
|
||||
as components are looked up by their name.
|
||||
|
||||
If the optional 'reverseFlipped' argument is True, components whose transformation
|
||||
matrix has a negative determinant will be decomposed with a reversed path direction
|
||||
to compensate for the flip.
|
||||
|
||||
The optional 'skipMissingComponents' argument can be set to True/False to
|
||||
override the homonymous class attribute for a given pen instance.
|
||||
"""
|
||||
super(DecomposingPen, self).__init__(*args, **kwargs)
|
||||
self.glyphSet = glyphSet
|
||||
self.skipMissingComponents = (
|
||||
self.__class__.skipMissingComponents
|
||||
if skipMissingComponents is None
|
||||
else skipMissingComponents
|
||||
)
|
||||
self.reverseFlipped = reverseFlipped
|
||||
|
||||
def addComponent(self, glyphName, transformation):
|
||||
"""Transform the points of the base glyph and draw it onto self."""
|
||||
from fontTools.pens.transformPen import TransformPen
|
||||
|
||||
try:
|
||||
glyph = self.glyphSet[glyphName]
|
||||
except KeyError:
|
||||
if not self.skipMissingComponents:
|
||||
raise MissingComponentError(glyphName)
|
||||
self.log.warning("glyph '%s' is missing from glyphSet; skipped" % glyphName)
|
||||
else:
|
||||
pen = self
|
||||
if transformation != Identity:
|
||||
pen = TransformPen(pen, transformation)
|
||||
if self.reverseFlipped:
|
||||
# if the transformation has a negative determinant, it will
|
||||
# reverse the contour direction of the component
|
||||
a, b, c, d = transformation[:4]
|
||||
det = a * d - b * c
|
||||
if det < 0:
|
||||
from fontTools.pens.reverseContourPen import ReverseContourPen
|
||||
|
||||
pen = ReverseContourPen(pen)
|
||||
glyph.draw(pen)
|
||||
|
||||
def addVarComponent(self, glyphName, transformation, location):
|
||||
# GlyphSet decomposes for us
|
||||
raise AttributeError
|
||||
|
||||
|
||||
class BasePen(DecomposingPen):
|
||||
"""Base class for drawing pens. You must override _moveTo, _lineTo and
|
||||
_curveToOne. You may additionally override _closePath, _endPath,
|
||||
addComponent, addVarComponent, and/or _qCurveToOne. You should not
|
||||
override any other methods.
|
||||
"""
|
||||
|
||||
def __init__(self, glyphSet=None):
|
||||
super(BasePen, self).__init__(glyphSet)
|
||||
self.__currentPoint = None
|
||||
|
||||
# must override
|
||||
|
||||
def _moveTo(self, pt):
|
||||
raise NotImplementedError
|
||||
|
||||
def _lineTo(self, pt):
|
||||
raise NotImplementedError
|
||||
|
||||
def _curveToOne(self, pt1, pt2, pt3):
|
||||
raise NotImplementedError
|
||||
|
||||
# may override
|
||||
|
||||
def _closePath(self):
|
||||
pass
|
||||
|
||||
def _endPath(self):
|
||||
pass
|
||||
|
||||
def _qCurveToOne(self, pt1, pt2):
|
||||
"""This method implements the basic quadratic curve type. The
|
||||
default implementation delegates the work to the cubic curve
|
||||
function. Optionally override with a native implementation.
|
||||
"""
|
||||
pt0x, pt0y = self.__currentPoint
|
||||
pt1x, pt1y = pt1
|
||||
pt2x, pt2y = pt2
|
||||
mid1x = pt0x + 0.66666666666666667 * (pt1x - pt0x)
|
||||
mid1y = pt0y + 0.66666666666666667 * (pt1y - pt0y)
|
||||
mid2x = pt2x + 0.66666666666666667 * (pt1x - pt2x)
|
||||
mid2y = pt2y + 0.66666666666666667 * (pt1y - pt2y)
|
||||
self._curveToOne((mid1x, mid1y), (mid2x, mid2y), pt2)
|
||||
|
||||
# don't override
|
||||
|
||||
def _getCurrentPoint(self):
|
||||
"""Return the current point. This is not part of the public
|
||||
interface, yet is useful for subclasses.
|
||||
"""
|
||||
return self.__currentPoint
|
||||
|
||||
def closePath(self):
|
||||
self._closePath()
|
||||
self.__currentPoint = None
|
||||
|
||||
def endPath(self):
|
||||
self._endPath()
|
||||
self.__currentPoint = None
|
||||
|
||||
def moveTo(self, pt):
|
||||
self._moveTo(pt)
|
||||
self.__currentPoint = pt
|
||||
|
||||
def lineTo(self, pt):
|
||||
self._lineTo(pt)
|
||||
self.__currentPoint = pt
|
||||
|
||||
def curveTo(self, *points):
|
||||
n = len(points) - 1 # 'n' is the number of control points
|
||||
assert n >= 0
|
||||
if n == 2:
|
||||
# The common case, we have exactly two BCP's, so this is a standard
|
||||
# cubic bezier. Even though decomposeSuperBezierSegment() handles
|
||||
# this case just fine, we special-case it anyway since it's so
|
||||
# common.
|
||||
self._curveToOne(*points)
|
||||
self.__currentPoint = points[-1]
|
||||
elif n > 2:
|
||||
# n is the number of control points; split curve into n-1 cubic
|
||||
# bezier segments. The algorithm used here is inspired by NURB
|
||||
# splines and the TrueType "implied point" principle, and ensures
|
||||
# the smoothest possible connection between two curve segments,
|
||||
# with no disruption in the curvature. It is practical since it
|
||||
# allows one to construct multiple bezier segments with a much
|
||||
# smaller amount of points.
|
||||
_curveToOne = self._curveToOne
|
||||
for pt1, pt2, pt3 in decomposeSuperBezierSegment(points):
|
||||
_curveToOne(pt1, pt2, pt3)
|
||||
self.__currentPoint = pt3
|
||||
elif n == 1:
|
||||
self.qCurveTo(*points)
|
||||
elif n == 0:
|
||||
self.lineTo(points[0])
|
||||
else:
|
||||
raise AssertionError("can't get there from here")
|
||||
|
||||
def qCurveTo(self, *points):
|
||||
n = len(points) - 1 # 'n' is the number of control points
|
||||
assert n >= 0
|
||||
if points[-1] is None:
|
||||
# Special case for TrueType quadratics: it is possible to
|
||||
# define a contour with NO on-curve points. BasePen supports
|
||||
# this by allowing the final argument (the expected on-curve
|
||||
# point) to be None. We simulate the feature by making the implied
|
||||
# on-curve point between the last and the first off-curve points
|
||||
# explicit.
|
||||
x, y = points[-2] # last off-curve point
|
||||
nx, ny = points[0] # first off-curve point
|
||||
impliedStartPoint = (0.5 * (x + nx), 0.5 * (y + ny))
|
||||
self.__currentPoint = impliedStartPoint
|
||||
self._moveTo(impliedStartPoint)
|
||||
points = points[:-1] + (impliedStartPoint,)
|
||||
if n > 0:
|
||||
# Split the string of points into discrete quadratic curve
|
||||
# segments. Between any two consecutive off-curve points
|
||||
# there's an implied on-curve point exactly in the middle.
|
||||
# This is where the segment splits.
|
||||
_qCurveToOne = self._qCurveToOne
|
||||
for pt1, pt2 in decomposeQuadraticSegment(points):
|
||||
_qCurveToOne(pt1, pt2)
|
||||
self.__currentPoint = pt2
|
||||
else:
|
||||
self.lineTo(points[0])
|
||||
|
||||
|
||||
def decomposeSuperBezierSegment(points):
|
||||
"""Split the SuperBezier described by 'points' into a list of regular
|
||||
bezier segments. The 'points' argument must be a sequence with length
|
||||
3 or greater, containing (x, y) coordinates. The last point is the
|
||||
destination on-curve point, the rest of the points are off-curve points.
|
||||
The start point should not be supplied.
|
||||
|
||||
This function returns a list of (pt1, pt2, pt3) tuples, which each
|
||||
specify a regular curveto-style bezier segment.
|
||||
"""
|
||||
n = len(points) - 1
|
||||
assert n > 1
|
||||
bezierSegments = []
|
||||
pt1, pt2, pt3 = points[0], None, None
|
||||
for i in range(2, n + 1):
|
||||
# calculate points in between control points.
|
||||
nDivisions = min(i, 3, n - i + 2)
|
||||
for j in range(1, nDivisions):
|
||||
factor = j / nDivisions
|
||||
temp1 = points[i - 1]
|
||||
temp2 = points[i - 2]
|
||||
temp = (
|
||||
temp2[0] + factor * (temp1[0] - temp2[0]),
|
||||
temp2[1] + factor * (temp1[1] - temp2[1]),
|
||||
)
|
||||
if pt2 is None:
|
||||
pt2 = temp
|
||||
else:
|
||||
pt3 = (0.5 * (pt2[0] + temp[0]), 0.5 * (pt2[1] + temp[1]))
|
||||
bezierSegments.append((pt1, pt2, pt3))
|
||||
pt1, pt2, pt3 = temp, None, None
|
||||
bezierSegments.append((pt1, points[-2], points[-1]))
|
||||
return bezierSegments
|
||||
|
||||
|
||||
def decomposeQuadraticSegment(points):
|
||||
"""Split the quadratic curve segment described by 'points' into a list
|
||||
of "atomic" quadratic segments. The 'points' argument must be a sequence
|
||||
with length 2 or greater, containing (x, y) coordinates. The last point
|
||||
is the destination on-curve point, the rest of the points are off-curve
|
||||
points. The start point should not be supplied.
|
||||
|
||||
This function returns a list of (pt1, pt2) tuples, which each specify a
|
||||
plain quadratic bezier segment.
|
||||
"""
|
||||
n = len(points) - 1
|
||||
assert n > 0
|
||||
quadSegments = []
|
||||
for i in range(n - 1):
|
||||
x, y = points[i]
|
||||
nx, ny = points[i + 1]
|
||||
impliedPt = (0.5 * (x + nx), 0.5 * (y + ny))
|
||||
quadSegments.append((points[i], impliedPt))
|
||||
quadSegments.append((points[-2], points[-1]))
|
||||
return quadSegments
|
||||
|
||||
|
||||
class _TestPen(BasePen):
|
||||
"""Test class that prints PostScript to stdout."""
|
||||
|
||||
def _moveTo(self, pt):
|
||||
print("%s %s moveto" % (pt[0], pt[1]))
|
||||
|
||||
def _lineTo(self, pt):
|
||||
print("%s %s lineto" % (pt[0], pt[1]))
|
||||
|
||||
def _curveToOne(self, bcp1, bcp2, pt):
|
||||
print(
|
||||
"%s %s %s %s %s %s curveto"
|
||||
% (bcp1[0], bcp1[1], bcp2[0], bcp2[1], pt[0], pt[1])
|
||||
)
|
||||
|
||||
def _closePath(self):
|
||||
print("closepath")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pen = _TestPen(None)
|
||||
pen.moveTo((0, 0))
|
||||
pen.lineTo((0, 100))
|
||||
pen.curveTo((50, 75), (60, 50), (50, 25), (0, 0))
|
||||
pen.closePath()
|
||||
|
||||
pen = _TestPen(None)
|
||||
# testing the "no on-curve point" scenario
|
||||
pen.qCurveTo((0, 0), (0, 100), (100, 100), (100, 0), None)
|
||||
pen.closePath()
|
||||
Loading…
Add table
Add a link
Reference in a new issue