Source code for ufoLib2.objects.contour

import warnings
from collections.abc import MutableSequence
from typing import Iterable, Iterator, List, Optional, Tuple, Union, overload

from attr import define, field
from fontTools.pens.basePen import AbstractPen
from fontTools.pens.pointPen import AbstractPointPen, PointToSegmentPen

from ufoLib2.objects.misc import BoundingBox, getBounds, getControlBounds
from ufoLib2.objects.point import Point
from ufoLib2.typing import GlyphSet


[docs]@define class Contour(MutableSequence): """Represents a contour as a list of points. Behavior: The Contour object has list-like behavior. This behavior allows you to interact with point data directly. For example, to get a particular point:: point = contour[0] To iterate over all points:: for point in contour: ... To get the number of points:: pointCount = len(contour) To delete a particular point:: del contour[0] To set a particular point to another Point object:: contour[0] = anotherPoint """ points: List[Point] = field(factory=list) """The list of points in the contour.""" identifier: Optional[str] = field(default=None, repr=False) """The globally unique identifier of the contour.""" # collections.abc.MutableSequence interface def __delitem__(self, index: Union[int, slice]) -> None: del self.points[index] @overload def __getitem__(self, index: int) -> Point: ... @overload def __getitem__(self, index: slice) -> List[Point]: # noqa: F811 ... def __getitem__( # noqa: F811 self, index: Union[int, slice] ) -> Union[Point, List[Point]]: return self.points[index] def __setitem__( # noqa: F811 self, index: Union[int, slice], point: Union[Point, Iterable[Point]] ) -> None: if isinstance(index, int) and isinstance(point, Point): self.points[index] = point elif ( isinstance(index, slice) and isinstance(point, Iterable) and all(isinstance(p, Point) for p in point) ): self.points[index] = point else: raise TypeError( f"Expected Point or Iterable[Point], found {type(point).__name__}." ) def __iter__(self) -> Iterator[Point]: return iter(self.points) def __len__(self) -> int: return len(self.points)
[docs] def insert(self, index: int, value: Point) -> None: """Insert Point object ``value`` into the contour at ``index``.""" if not isinstance(value, Point): raise TypeError(f"Expected Point, found {type(value).__name__}.") self.points.insert(index, value)
# TODO: rotate method? @property def open(self) -> bool: """Returns whether the contour is open or closed.""" if not self.points: return True return self.points[0].type == "move"
[docs] def move(self, delta: Tuple[float, float]) -> None: """Moves contour by (x, y) font units.""" for point in self.points: point.move(delta)
[docs] def getBounds(self, layer: Optional[GlyphSet] = None) -> Optional[BoundingBox]: """Returns the (xMin, yMin, xMax, yMax) bounding box of the glyph, taking the actual contours into account. Args: layer: Not applicable to contours, here for API symmetry. """ return getBounds(self, layer)
@property def bounds(self) -> Optional[BoundingBox]: """Returns the (xMin, yMin, xMax, yMax) bounding box of the glyph, taking the actual contours into account. |defcon_compat| """ return self.getBounds()
[docs] def getControlBounds( self, layer: Optional[GlyphSet] = None ) -> Optional[BoundingBox]: """Returns the (xMin, yMin, xMax, yMax) bounding box of the glyph, taking only the control points into account. Args: layer: Not applicable to contours, here for API symmetry. """ return getControlBounds(self, layer)
@property def controlPointBounds(self) -> Optional[BoundingBox]: """Returns the (xMin, yMin, xMax, yMax) bounding box of the glyph, taking only the control points into account. |defcon_compat| """ return self.getControlBounds() # ----------- # Pen methods # -----------
[docs] def draw(self, pen: AbstractPen) -> None: """Draws contour into given pen.""" pointPen = PointToSegmentPen(pen) self.drawPoints(pointPen)
[docs] def drawPoints(self, pointPen: AbstractPointPen) -> None: """Draws points of contour into given point pen.""" try: pointPen.beginPath(identifier=self.identifier) for p in self.points: pointPen.addPoint( (p.x, p.y), segmentType=p.type, smooth=p.smooth, name=p.name, identifier=p.identifier, ) except TypeError: pointPen.beginPath() for p in self.points: pointPen.addPoint( (p.x, p.y), segmentType=p.type, smooth=p.smooth, name=p.name ) warnings.warn( "The pointPen needs an identifier kwarg. " "Identifiers have been discarded.", UserWarning, ) pointPen.endPath()