aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/pens/pointPen.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/fontTools/pens/pointPen.py')
-rw-r--r--Lib/fontTools/pens/pointPen.py942
1 files changed, 487 insertions, 455 deletions
diff --git a/Lib/fontTools/pens/pointPen.py b/Lib/fontTools/pens/pointPen.py
index 4c3148bf..eb1ebc20 100644
--- a/Lib/fontTools/pens/pointPen.py
+++ b/Lib/fontTools/pens/pointPen.py
@@ -13,481 +13,513 @@ For instance, whether or not a point is smooth, and its name.
"""
import math
-from typing import Any, Optional, Tuple
+from typing import Any, Optional, Tuple, Dict
from fontTools.pens.basePen import AbstractPen, PenError
+from fontTools.misc.transform import DecomposedTransform
__all__ = [
- "AbstractPointPen",
- "BasePointToSegmentPen",
- "PointToSegmentPen",
- "SegmentToPointPen",
- "GuessSmoothPointPen",
- "ReverseContourPointPen",
+ "AbstractPointPen",
+ "BasePointToSegmentPen",
+ "PointToSegmentPen",
+ "SegmentToPointPen",
+ "GuessSmoothPointPen",
+ "ReverseContourPointPen",
]
class AbstractPointPen:
- """Baseclass for all PointPens."""
-
- def beginPath(self, identifier: Optional[str] = None, **kwargs: Any) -> None:
- """Start a new sub path."""
- raise NotImplementedError
-
- def endPath(self) -> None:
- """End the current sub path."""
- raise NotImplementedError
-
- def addPoint(
- self,
- pt: Tuple[float, float],
- segmentType: Optional[str] = None,
- smooth: bool = False,
- name: Optional[str] = None,
- identifier: Optional[str] = None,
- **kwargs: Any
- ) -> None:
- """Add a point to the current sub path."""
- raise NotImplementedError
-
- def addComponent(
- self,
- baseGlyphName: str,
- transformation: Tuple[float, float, float, float, float, float],
- identifier: Optional[str] = None,
- **kwargs: Any
- ) -> None:
- """Add a sub glyph."""
- raise NotImplementedError
+ """Baseclass for all PointPens."""
+
+ def beginPath(self, identifier: Optional[str] = None, **kwargs: Any) -> None:
+ """Start a new sub path."""
+ raise NotImplementedError
+
+ def endPath(self) -> None:
+ """End the current sub path."""
+ raise NotImplementedError
+
+ def addPoint(
+ self,
+ pt: Tuple[float, float],
+ segmentType: Optional[str] = None,
+ smooth: bool = False,
+ name: Optional[str] = None,
+ identifier: Optional[str] = None,
+ **kwargs: Any,
+ ) -> None:
+ """Add a point to the current sub path."""
+ raise NotImplementedError
+
+ def addComponent(
+ self,
+ baseGlyphName: str,
+ transformation: Tuple[float, float, float, float, float, float],
+ identifier: Optional[str] = None,
+ **kwargs: Any,
+ ) -> None:
+ """Add a sub glyph."""
+ raise NotImplementedError
+
+ def addVarComponent(
+ self,
+ glyphName: str,
+ transformation: DecomposedTransform,
+ location: Dict[str, float],
+ identifier: Optional[str] = None,
+ **kwargs: Any,
+ ) -> 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.
+ """
+ # ttGlyphSet decomposes for us
+ raise AttributeError
class BasePointToSegmentPen(AbstractPointPen):
- """
- Base class for retrieving the outline in a segment-oriented
- way. The PointPen protocol is simple yet also a little tricky,
- so when you need an outline presented as segments but you have
- as points, do use this base implementation as it properly takes
- care of all the edge cases.
- """
-
- def __init__(self):
- self.currentPath = None
-
- def beginPath(self, identifier=None, **kwargs):
- if self.currentPath is not None:
- raise PenError("Path already begun.")
- self.currentPath = []
-
- def _flushContour(self, segments):
- """Override this method.
-
- It will be called for each non-empty sub path with a list
- of segments: the 'segments' argument.
-
- The segments list contains tuples of length 2:
- (segmentType, points)
-
- segmentType is one of "move", "line", "curve" or "qcurve".
- "move" may only occur as the first segment, and it signifies
- an OPEN path. A CLOSED path does NOT start with a "move", in
- fact it will not contain a "move" at ALL.
-
- The 'points' field in the 2-tuple is a list of point info
- tuples. The list has 1 or more items, a point tuple has
- four items:
- (point, smooth, name, kwargs)
- 'point' is an (x, y) coordinate pair.
-
- For a closed path, the initial moveTo point is defined as
- the last point of the last segment.
-
- The 'points' list of "move" and "line" segments always contains
- exactly one point tuple.
- """
- raise NotImplementedError
-
- def endPath(self):
- if self.currentPath is None:
- raise PenError("Path not begun.")
- points = self.currentPath
- self.currentPath = None
- if not points:
- return
- if len(points) == 1:
- # Not much more we can do than output a single move segment.
- pt, segmentType, smooth, name, kwargs = points[0]
- segments = [("move", [(pt, smooth, name, kwargs)])]
- self._flushContour(segments)
- return
- segments = []
- if points[0][1] == "move":
- # It's an open contour, insert a "move" segment for the first
- # point and remove that first point from the point list.
- pt, segmentType, smooth, name, kwargs = points[0]
- segments.append(("move", [(pt, smooth, name, kwargs)]))
- points.pop(0)
- else:
- # It's a closed contour. Locate the first on-curve point, and
- # rotate the point list so that it _ends_ with an on-curve
- # point.
- firstOnCurve = None
- for i in range(len(points)):
- segmentType = points[i][1]
- if segmentType is not None:
- firstOnCurve = i
- break
- if firstOnCurve is None:
- # Special case for quadratics: a contour with no on-curve
- # points. Add a "None" point. (See also the Pen protocol's
- # qCurveTo() method and fontTools.pens.basePen.py.)
- points.append((None, "qcurve", None, None, None))
- else:
- points = points[firstOnCurve+1:] + points[:firstOnCurve+1]
-
- currentSegment = []
- for pt, segmentType, smooth, name, kwargs in points:
- currentSegment.append((pt, smooth, name, kwargs))
- if segmentType is None:
- continue
- segments.append((segmentType, currentSegment))
- currentSegment = []
-
- self._flushContour(segments)
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None,
- identifier=None, **kwargs):
- if self.currentPath is None:
- raise PenError("Path not begun")
- self.currentPath.append((pt, segmentType, smooth, name, kwargs))
+ """
+ Base class for retrieving the outline in a segment-oriented
+ way. The PointPen protocol is simple yet also a little tricky,
+ so when you need an outline presented as segments but you have
+ as points, do use this base implementation as it properly takes
+ care of all the edge cases.
+ """
+
+ def __init__(self):
+ self.currentPath = None
+
+ def beginPath(self, identifier=None, **kwargs):
+ if self.currentPath is not None:
+ raise PenError("Path already begun.")
+ self.currentPath = []
+
+ def _flushContour(self, segments):
+ """Override this method.
+
+ It will be called for each non-empty sub path with a list
+ of segments: the 'segments' argument.
+
+ The segments list contains tuples of length 2:
+ (segmentType, points)
+
+ segmentType is one of "move", "line", "curve" or "qcurve".
+ "move" may only occur as the first segment, and it signifies
+ an OPEN path. A CLOSED path does NOT start with a "move", in
+ fact it will not contain a "move" at ALL.
+
+ The 'points' field in the 2-tuple is a list of point info
+ tuples. The list has 1 or more items, a point tuple has
+ four items:
+ (point, smooth, name, kwargs)
+ 'point' is an (x, y) coordinate pair.
+
+ For a closed path, the initial moveTo point is defined as
+ the last point of the last segment.
+
+ The 'points' list of "move" and "line" segments always contains
+ exactly one point tuple.
+ """
+ raise NotImplementedError
+
+ def endPath(self):
+ if self.currentPath is None:
+ raise PenError("Path not begun.")
+ points = self.currentPath
+ self.currentPath = None
+ if not points:
+ return
+ if len(points) == 1:
+ # Not much more we can do than output a single move segment.
+ pt, segmentType, smooth, name, kwargs = points[0]
+ segments = [("move", [(pt, smooth, name, kwargs)])]
+ self._flushContour(segments)
+ return
+ segments = []
+ if points[0][1] == "move":
+ # It's an open contour, insert a "move" segment for the first
+ # point and remove that first point from the point list.
+ pt, segmentType, smooth, name, kwargs = points[0]
+ segments.append(("move", [(pt, smooth, name, kwargs)]))
+ points.pop(0)
+ else:
+ # It's a closed contour. Locate the first on-curve point, and
+ # rotate the point list so that it _ends_ with an on-curve
+ # point.
+ firstOnCurve = None
+ for i in range(len(points)):
+ segmentType = points[i][1]
+ if segmentType is not None:
+ firstOnCurve = i
+ break
+ if firstOnCurve is None:
+ # Special case for quadratics: a contour with no on-curve
+ # points. Add a "None" point. (See also the Pen protocol's
+ # qCurveTo() method and fontTools.pens.basePen.py.)
+ points.append((None, "qcurve", None, None, None))
+ else:
+ points = points[firstOnCurve + 1 :] + points[: firstOnCurve + 1]
+
+ currentSegment = []
+ for pt, segmentType, smooth, name, kwargs in points:
+ currentSegment.append((pt, smooth, name, kwargs))
+ if segmentType is None:
+ continue
+ segments.append((segmentType, currentSegment))
+ currentSegment = []
+
+ self._flushContour(segments)
+
+ def addPoint(
+ self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs
+ ):
+ if self.currentPath is None:
+ raise PenError("Path not begun")
+ self.currentPath.append((pt, segmentType, smooth, name, kwargs))
class PointToSegmentPen(BasePointToSegmentPen):
- """
- Adapter class that converts the PointPen protocol to the
- (Segment)Pen protocol.
-
- NOTE: The segment pen does not support and will drop point names, identifiers
- and kwargs.
- """
-
- def __init__(self, segmentPen, outputImpliedClosingLine=False):
- BasePointToSegmentPen.__init__(self)
- self.pen = segmentPen
- self.outputImpliedClosingLine = outputImpliedClosingLine
-
- def _flushContour(self, segments):
- if not segments:
- raise PenError("Must have at least one segment.")
- pen = self.pen
- if segments[0][0] == "move":
- # It's an open path.
- closed = False
- points = segments[0][1]
- if len(points) != 1:
- raise PenError(f"Illegal move segment point count: {len(points)}")
- movePt, _, _ , _ = points[0]
- del segments[0]
- else:
- # It's a closed path, do a moveTo to the last
- # point of the last segment.
- closed = True
- segmentType, points = segments[-1]
- movePt, _, _ , _ = points[-1]
- if movePt is None:
- # quad special case: a contour with no on-curve points contains
- # one "qcurve" segment that ends with a point that's None. We
- # must not output a moveTo() in that case.
- pass
- else:
- pen.moveTo(movePt)
- outputImpliedClosingLine = self.outputImpliedClosingLine
- nSegments = len(segments)
- lastPt = movePt
- for i in range(nSegments):
- segmentType, points = segments[i]
- points = [pt for pt, _, _ , _ in points]
- if segmentType == "line":
- if len(points) != 1:
- raise PenError(f"Illegal line segment point count: {len(points)}")
- pt = points[0]
- # For closed contours, a 'lineTo' is always implied from the last oncurve
- # point to the starting point, thus we can omit it when the last and
- # starting point don't overlap.
- # However, when the last oncurve point is a "line" segment and has same
- # coordinates as the starting point of a closed contour, we need to output
- # the closing 'lineTo' explicitly (regardless of the value of the
- # 'outputImpliedClosingLine' option) in order to disambiguate this case from
- # the implied closing 'lineTo', otherwise the duplicate point would be lost.
- # See https://github.com/googlefonts/fontmake/issues/572.
- if (
- i + 1 != nSegments
- or outputImpliedClosingLine
- or not closed
- or pt == lastPt
- ):
- pen.lineTo(pt)
- lastPt = pt
- elif segmentType == "curve":
- pen.curveTo(*points)
- lastPt = points[-1]
- elif segmentType == "qcurve":
- pen.qCurveTo(*points)
- lastPt = points[-1]
- else:
- raise PenError(f"Illegal segmentType: {segmentType}")
- if closed:
- pen.closePath()
- else:
- pen.endPath()
-
- def addComponent(self, glyphName, transform, identifier=None, **kwargs):
- del identifier # unused
- del kwargs # unused
- self.pen.addComponent(glyphName, transform)
+ """
+ Adapter class that converts the PointPen protocol to the
+ (Segment)Pen protocol.
+
+ NOTE: The segment pen does not support and will drop point names, identifiers
+ and kwargs.
+ """
+
+ def __init__(self, segmentPen, outputImpliedClosingLine=False):
+ BasePointToSegmentPen.__init__(self)
+ self.pen = segmentPen
+ self.outputImpliedClosingLine = outputImpliedClosingLine
+
+ def _flushContour(self, segments):
+ if not segments:
+ raise PenError("Must have at least one segment.")
+ pen = self.pen
+ if segments[0][0] == "move":
+ # It's an open path.
+ closed = False
+ points = segments[0][1]
+ if len(points) != 1:
+ raise PenError(f"Illegal move segment point count: {len(points)}")
+ movePt, _, _, _ = points[0]
+ del segments[0]
+ else:
+ # It's a closed path, do a moveTo to the last
+ # point of the last segment.
+ closed = True
+ segmentType, points = segments[-1]
+ movePt, _, _, _ = points[-1]
+ if movePt is None:
+ # quad special case: a contour with no on-curve points contains
+ # one "qcurve" segment that ends with a point that's None. We
+ # must not output a moveTo() in that case.
+ pass
+ else:
+ pen.moveTo(movePt)
+ outputImpliedClosingLine = self.outputImpliedClosingLine
+ nSegments = len(segments)
+ lastPt = movePt
+ for i in range(nSegments):
+ segmentType, points = segments[i]
+ points = [pt for pt, _, _, _ in points]
+ if segmentType == "line":
+ if len(points) != 1:
+ raise PenError(f"Illegal line segment point count: {len(points)}")
+ pt = points[0]
+ # For closed contours, a 'lineTo' is always implied from the last oncurve
+ # point to the starting point, thus we can omit it when the last and
+ # starting point don't overlap.
+ # However, when the last oncurve point is a "line" segment and has same
+ # coordinates as the starting point of a closed contour, we need to output
+ # the closing 'lineTo' explicitly (regardless of the value of the
+ # 'outputImpliedClosingLine' option) in order to disambiguate this case from
+ # the implied closing 'lineTo', otherwise the duplicate point would be lost.
+ # See https://github.com/googlefonts/fontmake/issues/572.
+ if (
+ i + 1 != nSegments
+ or outputImpliedClosingLine
+ or not closed
+ or pt == lastPt
+ ):
+ pen.lineTo(pt)
+ lastPt = pt
+ elif segmentType == "curve":
+ pen.curveTo(*points)
+ lastPt = points[-1]
+ elif segmentType == "qcurve":
+ pen.qCurveTo(*points)
+ lastPt = points[-1]
+ else:
+ raise PenError(f"Illegal segmentType: {segmentType}")
+ if closed:
+ pen.closePath()
+ else:
+ pen.endPath()
+
+ def addComponent(self, glyphName, transform, identifier=None, **kwargs):
+ del identifier # unused
+ del kwargs # unused
+ self.pen.addComponent(glyphName, transform)
class SegmentToPointPen(AbstractPen):
- """
- Adapter class that converts the (Segment)Pen protocol to the
- PointPen protocol.
- """
-
- def __init__(self, pointPen, guessSmooth=True):
- if guessSmooth:
- self.pen = GuessSmoothPointPen(pointPen)
- else:
- self.pen = pointPen
- self.contour = None
-
- def _flushContour(self):
- pen = self.pen
- pen.beginPath()
- for pt, segmentType in self.contour:
- pen.addPoint(pt, segmentType=segmentType)
- pen.endPath()
-
- def moveTo(self, pt):
- self.contour = []
- self.contour.append((pt, "move"))
-
- def lineTo(self, pt):
- if self.contour is None:
- raise PenError("Contour missing required initial moveTo")
- self.contour.append((pt, "line"))
-
- def curveTo(self, *pts):
- if not pts:
- raise TypeError("Must pass in at least one point")
- if self.contour is None:
- raise PenError("Contour missing required initial moveTo")
- for pt in pts[:-1]:
- self.contour.append((pt, None))
- self.contour.append((pts[-1], "curve"))
-
- def qCurveTo(self, *pts):
- if not pts:
- raise TypeError("Must pass in at least one point")
- if pts[-1] is None:
- self.contour = []
- else:
- if self.contour is None:
- raise PenError("Contour missing required initial moveTo")
- for pt in pts[:-1]:
- self.contour.append((pt, None))
- if pts[-1] is not None:
- self.contour.append((pts[-1], "qcurve"))
-
- def closePath(self):
- if self.contour is None:
- raise PenError("Contour missing required initial moveTo")
- if len(self.contour) > 1 and self.contour[0][0] == self.contour[-1][0]:
- self.contour[0] = self.contour[-1]
- del self.contour[-1]
- else:
- # There's an implied line at the end, replace "move" with "line"
- # for the first point
- pt, tp = self.contour[0]
- if tp == "move":
- self.contour[0] = pt, "line"
- self._flushContour()
- self.contour = None
-
- def endPath(self):
- if self.contour is None:
- raise PenError("Contour missing required initial moveTo")
- self._flushContour()
- self.contour = None
-
- def addComponent(self, glyphName, transform):
- if self.contour is not None:
- raise PenError("Components must be added before or after contours")
- self.pen.addComponent(glyphName, transform)
+ """
+ Adapter class that converts the (Segment)Pen protocol to the
+ PointPen protocol.
+ """
+
+ def __init__(self, pointPen, guessSmooth=True):
+ if guessSmooth:
+ self.pen = GuessSmoothPointPen(pointPen)
+ else:
+ self.pen = pointPen
+ self.contour = None
+
+ def _flushContour(self):
+ pen = self.pen
+ pen.beginPath()
+ for pt, segmentType in self.contour:
+ pen.addPoint(pt, segmentType=segmentType)
+ pen.endPath()
+
+ def moveTo(self, pt):
+ self.contour = []
+ self.contour.append((pt, "move"))
+
+ def lineTo(self, pt):
+ if self.contour is None:
+ raise PenError("Contour missing required initial moveTo")
+ self.contour.append((pt, "line"))
+
+ def curveTo(self, *pts):
+ if not pts:
+ raise TypeError("Must pass in at least one point")
+ if self.contour is None:
+ raise PenError("Contour missing required initial moveTo")
+ for pt in pts[:-1]:
+ self.contour.append((pt, None))
+ self.contour.append((pts[-1], "curve"))
+
+ def qCurveTo(self, *pts):
+ if not pts:
+ raise TypeError("Must pass in at least one point")
+ if pts[-1] is None:
+ self.contour = []
+ else:
+ if self.contour is None:
+ raise PenError("Contour missing required initial moveTo")
+ for pt in pts[:-1]:
+ self.contour.append((pt, None))
+ if pts[-1] is not None:
+ self.contour.append((pts[-1], "qcurve"))
+
+ def closePath(self):
+ if self.contour is None:
+ raise PenError("Contour missing required initial moveTo")
+ if len(self.contour) > 1 and self.contour[0][0] == self.contour[-1][0]:
+ self.contour[0] = self.contour[-1]
+ del self.contour[-1]
+ else:
+ # There's an implied line at the end, replace "move" with "line"
+ # for the first point
+ pt, tp = self.contour[0]
+ if tp == "move":
+ self.contour[0] = pt, "line"
+ self._flushContour()
+ self.contour = None
+
+ def endPath(self):
+ if self.contour is None:
+ raise PenError("Contour missing required initial moveTo")
+ self._flushContour()
+ self.contour = None
+
+ def addComponent(self, glyphName, transform):
+ if self.contour is not None:
+ raise PenError("Components must be added before or after contours")
+ self.pen.addComponent(glyphName, transform)
class GuessSmoothPointPen(AbstractPointPen):
- """
- Filtering PointPen that tries to determine whether an on-curve point
- should be "smooth", ie. that it's a "tangent" point or a "curve" point.
- """
-
- def __init__(self, outPen, error=0.05):
- self._outPen = outPen
- self._error = error
- self._points = None
-
- def _flushContour(self):
- if self._points is None:
- raise PenError("Path not begun")
- points = self._points
- nPoints = len(points)
- if not nPoints:
- return
- if points[0][1] == "move":
- # Open path.
- indices = range(1, nPoints - 1)
- elif nPoints > 1:
- # Closed path. To avoid having to mod the contour index, we
- # simply abuse Python's negative index feature, and start at -1
- indices = range(-1, nPoints - 1)
- else:
- # closed path containing 1 point (!), ignore.
- indices = []
- for i in indices:
- pt, segmentType, _, name, kwargs = points[i]
- if segmentType is None:
- continue
- prev = i - 1
- next = i + 1
- if points[prev][1] is not None and points[next][1] is not None:
- continue
- # At least one of our neighbors is an off-curve point
- pt = points[i][0]
- prevPt = points[prev][0]
- nextPt = points[next][0]
- if pt != prevPt and pt != nextPt:
- dx1, dy1 = pt[0] - prevPt[0], pt[1] - prevPt[1]
- dx2, dy2 = nextPt[0] - pt[0], nextPt[1] - pt[1]
- a1 = math.atan2(dy1, dx1)
- a2 = math.atan2(dy2, dx2)
- if abs(a1 - a2) < self._error:
- points[i] = pt, segmentType, True, name, kwargs
-
- for pt, segmentType, smooth, name, kwargs in points:
- self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs)
-
- def beginPath(self, identifier=None, **kwargs):
- if self._points is not None:
- raise PenError("Path already begun")
- self._points = []
- if identifier is not None:
- kwargs["identifier"] = identifier
- self._outPen.beginPath(**kwargs)
-
- def endPath(self):
- self._flushContour()
- self._outPen.endPath()
- self._points = None
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None,
- identifier=None, **kwargs):
- if self._points is None:
- raise PenError("Path not begun")
- if identifier is not None:
- kwargs["identifier"] = identifier
- self._points.append((pt, segmentType, False, name, kwargs))
-
- def addComponent(self, glyphName, transformation, identifier=None, **kwargs):
- if self._points is not None:
- raise PenError("Components must be added before or after contours")
- if identifier is not None:
- kwargs["identifier"] = identifier
- self._outPen.addComponent(glyphName, transformation, **kwargs)
+ """
+ Filtering PointPen that tries to determine whether an on-curve point
+ should be "smooth", ie. that it's a "tangent" point or a "curve" point.
+ """
+
+ def __init__(self, outPen, error=0.05):
+ self._outPen = outPen
+ self._error = error
+ self._points = None
+
+ def _flushContour(self):
+ if self._points is None:
+ raise PenError("Path not begun")
+ points = self._points
+ nPoints = len(points)
+ if not nPoints:
+ return
+ if points[0][1] == "move":
+ # Open path.
+ indices = range(1, nPoints - 1)
+ elif nPoints > 1:
+ # Closed path. To avoid having to mod the contour index, we
+ # simply abuse Python's negative index feature, and start at -1
+ indices = range(-1, nPoints - 1)
+ else:
+ # closed path containing 1 point (!), ignore.
+ indices = []
+ for i in indices:
+ pt, segmentType, _, name, kwargs = points[i]
+ if segmentType is None:
+ continue
+ prev = i - 1
+ next = i + 1
+ if points[prev][1] is not None and points[next][1] is not None:
+ continue
+ # At least one of our neighbors is an off-curve point
+ pt = points[i][0]
+ prevPt = points[prev][0]
+ nextPt = points[next][0]
+ if pt != prevPt and pt != nextPt:
+ dx1, dy1 = pt[0] - prevPt[0], pt[1] - prevPt[1]
+ dx2, dy2 = nextPt[0] - pt[0], nextPt[1] - pt[1]
+ a1 = math.atan2(dy1, dx1)
+ a2 = math.atan2(dy2, dx2)
+ if abs(a1 - a2) < self._error:
+ points[i] = pt, segmentType, True, name, kwargs
+
+ for pt, segmentType, smooth, name, kwargs in points:
+ self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs)
+
+ def beginPath(self, identifier=None, **kwargs):
+ if self._points is not None:
+ raise PenError("Path already begun")
+ self._points = []
+ if identifier is not None:
+ kwargs["identifier"] = identifier
+ self._outPen.beginPath(**kwargs)
+
+ def endPath(self):
+ self._flushContour()
+ self._outPen.endPath()
+ self._points = None
+
+ def addPoint(
+ self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs
+ ):
+ if self._points is None:
+ raise PenError("Path not begun")
+ if identifier is not None:
+ kwargs["identifier"] = identifier
+ self._points.append((pt, segmentType, False, name, kwargs))
+
+ def addComponent(self, glyphName, transformation, identifier=None, **kwargs):
+ if self._points is not None:
+ raise PenError("Components must be added before or after contours")
+ if identifier is not None:
+ kwargs["identifier"] = identifier
+ self._outPen.addComponent(glyphName, transformation, **kwargs)
+
+ def addVarComponent(
+ self, glyphName, transformation, location, identifier=None, **kwargs
+ ):
+ if self._points is not None:
+ raise PenError("VarComponents must be added before or after contours")
+ if identifier is not None:
+ kwargs["identifier"] = identifier
+ self._outPen.addVarComponent(glyphName, transformation, location, **kwargs)
class ReverseContourPointPen(AbstractPointPen):
- """
- This is a PointPen that passes outline data to another PointPen, but
- reversing the winding direction of all contours. Components are simply
- passed through unchanged.
-
- Closed contours are reversed in such a way that the first point remains
- the first point.
- """
-
- def __init__(self, outputPointPen):
- self.pen = outputPointPen
- # a place to store the points for the current sub path
- self.currentContour = None
-
- def _flushContour(self):
- pen = self.pen
- contour = self.currentContour
- if not contour:
- pen.beginPath(identifier=self.currentContourIdentifier)
- pen.endPath()
- return
-
- closed = contour[0][1] != "move"
- if not closed:
- lastSegmentType = "move"
- else:
- # Remove the first point and insert it at the end. When
- # the list of points gets reversed, this point will then
- # again be at the start. In other words, the following
- # will hold:
- # for N in range(len(originalContour)):
- # originalContour[N] == reversedContour[-N]
- contour.append(contour.pop(0))
- # Find the first on-curve point.
- firstOnCurve = None
- for i in range(len(contour)):
- if contour[i][1] is not None:
- firstOnCurve = i
- break
- if firstOnCurve is None:
- # There are no on-curve points, be basically have to
- # do nothing but contour.reverse().
- lastSegmentType = None
- else:
- lastSegmentType = contour[firstOnCurve][1]
-
- contour.reverse()
- if not closed:
- # Open paths must start with a move, so we simply dump
- # all off-curve points leading up to the first on-curve.
- while contour[0][1] is None:
- contour.pop(0)
- pen.beginPath(identifier=self.currentContourIdentifier)
- for pt, nextSegmentType, smooth, name, kwargs in contour:
- if nextSegmentType is not None:
- segmentType = lastSegmentType
- lastSegmentType = nextSegmentType
- else:
- segmentType = None
- pen.addPoint(pt, segmentType=segmentType, smooth=smooth, name=name, **kwargs)
- pen.endPath()
-
- def beginPath(self, identifier=None, **kwargs):
- if self.currentContour is not None:
- raise PenError("Path already begun")
- self.currentContour = []
- self.currentContourIdentifier = identifier
- self.onCurve = []
-
- def endPath(self):
- if self.currentContour is None:
- raise PenError("Path not begun")
- self._flushContour()
- self.currentContour = None
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs):
- if self.currentContour is None:
- raise PenError("Path not begun")
- if identifier is not None:
- kwargs["identifier"] = identifier
- self.currentContour.append((pt, segmentType, smooth, name, kwargs))
-
- def addComponent(self, glyphName, transform, identifier=None, **kwargs):
- if self.currentContour is not None:
- raise PenError("Components must be added before or after contours")
- self.pen.addComponent(glyphName, transform, identifier=identifier, **kwargs)
+ """
+ This is a PointPen that passes outline data to another PointPen, but
+ reversing the winding direction of all contours. Components are simply
+ passed through unchanged.
+
+ Closed contours are reversed in such a way that the first point remains
+ the first point.
+ """
+
+ def __init__(self, outputPointPen):
+ self.pen = outputPointPen
+ # a place to store the points for the current sub path
+ self.currentContour = None
+
+ def _flushContour(self):
+ pen = self.pen
+ contour = self.currentContour
+ if not contour:
+ pen.beginPath(identifier=self.currentContourIdentifier)
+ pen.endPath()
+ return
+
+ closed = contour[0][1] != "move"
+ if not closed:
+ lastSegmentType = "move"
+ else:
+ # Remove the first point and insert it at the end. When
+ # the list of points gets reversed, this point will then
+ # again be at the start. In other words, the following
+ # will hold:
+ # for N in range(len(originalContour)):
+ # originalContour[N] == reversedContour[-N]
+ contour.append(contour.pop(0))
+ # Find the first on-curve point.
+ firstOnCurve = None
+ for i in range(len(contour)):
+ if contour[i][1] is not None:
+ firstOnCurve = i
+ break
+ if firstOnCurve is None:
+ # There are no on-curve points, be basically have to
+ # do nothing but contour.reverse().
+ lastSegmentType = None
+ else:
+ lastSegmentType = contour[firstOnCurve][1]
+
+ contour.reverse()
+ if not closed:
+ # Open paths must start with a move, so we simply dump
+ # all off-curve points leading up to the first on-curve.
+ while contour[0][1] is None:
+ contour.pop(0)
+ pen.beginPath(identifier=self.currentContourIdentifier)
+ for pt, nextSegmentType, smooth, name, kwargs in contour:
+ if nextSegmentType is not None:
+ segmentType = lastSegmentType
+ lastSegmentType = nextSegmentType
+ else:
+ segmentType = None
+ pen.addPoint(
+ pt, segmentType=segmentType, smooth=smooth, name=name, **kwargs
+ )
+ pen.endPath()
+
+ def beginPath(self, identifier=None, **kwargs):
+ if self.currentContour is not None:
+ raise PenError("Path already begun")
+ self.currentContour = []
+ self.currentContourIdentifier = identifier
+ self.onCurve = []
+
+ def endPath(self):
+ if self.currentContour is None:
+ raise PenError("Path not begun")
+ self._flushContour()
+ self.currentContour = None
+
+ def addPoint(
+ self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs
+ ):
+ if self.currentContour is None:
+ raise PenError("Path not begun")
+ if identifier is not None:
+ kwargs["identifier"] = identifier
+ self.currentContour.append((pt, segmentType, smooth, name, kwargs))
+
+ def addComponent(self, glyphName, transform, identifier=None, **kwargs):
+ if self.currentContour is not None:
+ raise PenError("Components must be added before or after contours")
+ self.pen.addComponent(glyphName, transform, identifier=identifier, **kwargs)