Turn AbstractPen and AbstractPointPen into ABCs plus typing

This commit is contained in:
Nikolaus Waxweiler 2021-02-21 10:20:37 +00:00
parent a3acb1426b
commit ab1883da1d
4 changed files with 130 additions and 20 deletions

View File

@ -36,25 +36,52 @@ Coordinates are usually expressed as (x, y) tuples, but generally any
sequence of length 2 will do.
"""
import abc
from typing import Any, Tuple
from fontTools.misc.loggingTools import LogMixin
__all__ = ["AbstractPen", "NullPen", "BasePen",
"decomposeSuperBezierSegment", "decomposeQuadraticSegment"]
class AbstractPen(object):
class AbstractPen(abc.ABC):
@classmethod
def __subclasshook__(cls, subclass: Any) -> bool:
if cls is not AbstractPen:
return NotImplemented
return (
hasattr(subclass, "moveTo")
and callable(subclass.moveTo)
and hasattr(subclass, "lineTo")
and callable(subclass.lineTo)
and hasattr(subclass, "curveTo")
and callable(subclass.curveTo)
and hasattr(subclass, "qCurveTo")
and callable(subclass.qCurveTo)
and hasattr(subclass, "closePath")
and callable(subclass.closePath)
and hasattr(subclass, "endPath")
and callable(subclass.endPath)
and hasattr(subclass, "addComponent")
and callable(subclass.addComponent)
or NotImplemented
)
def moveTo(self, pt):
@abc.abstractmethod
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):
@abc.abstractmethod
def lineTo(self, pt: Tuple[float, float]) -> None:
"""Draw a straight line from the current point to 'pt'."""
raise NotImplementedError
def curveTo(self, *points):
@abc.abstractmethod
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
@ -75,7 +102,8 @@ class AbstractPen(object):
"""
raise NotImplementedError
def qCurveTo(self, *points):
@abc.abstractmethod
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
@ -92,19 +120,26 @@ class AbstractPen(object):
"""
raise NotImplementedError
def closePath(self):
@abc.abstractmethod
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):
@abc.abstractmethod
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, transformation):
@abc.abstractmethod
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

View File

@ -11,8 +11,12 @@ steps through all the points in a call from glyph.drawPoints().
This allows the caller to provide more data for each point.
For instance, whether or not a point is smooth, and its name.
"""
from fontTools.pens.basePen import AbstractPen
import abc
import math
from typing import Any, List, Optional, Tuple
from fontTools.pens.basePen import AbstractPen
__all__ = [
"AbstractPointPen",
@ -24,26 +28,56 @@ __all__ = [
]
class AbstractPointPen(object):
"""
Baseclass for all PointPens.
"""
class AbstractPointPen(abc.ABC):
"""Baseclass for all PointPens."""
def beginPath(self, identifier=None, **kwargs):
@classmethod
def __subclasshook__(cls, subclass: Any) -> bool:
if cls is not AbstractPointPen:
return NotImplemented
return (
hasattr(subclass, "beginPath")
and callable(subclass.beginPath)
and hasattr(subclass, "endPath")
and callable(subclass.endPath)
and hasattr(subclass, "addPoint")
and callable(subclass.addPoint)
and hasattr(subclass, "addComponent")
and callable(subclass.addComponent)
or NotImplemented
)
@abc.abstractmethod
def beginPath(self, identifier: Optional[str] = None, **kwargs: Any) -> None:
"""Start a new sub path."""
raise NotImplementedError
def endPath(self):
@abc.abstractmethod
def endPath(self) -> None:
"""End the current sub path."""
raise NotImplementedError
def addPoint(self, pt, segmentType=None, smooth=False, name=None,
identifier=None, **kwargs):
@abc.abstractmethod
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, transformation, identifier=None,
**kwargs):
@abc.abstractmethod
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

View File

@ -1,10 +1,34 @@
from fontTools.misc.py23 import *
from fontTools.pens.basePen import \
BasePen, decomposeSuperBezierSegment, decomposeQuadraticSegment
AbstractPen, BasePen, decomposeSuperBezierSegment, decomposeQuadraticSegment
from fontTools.pens.pointPen import AbstractPointPen
from fontTools.misc.loggingTools import CapturingLogHandler
import unittest
def test_subclasshook():
class NullPen:
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
assert issubclass(NullPen, AbstractPen)
assert isinstance(NullPen(), AbstractPen)
assert not issubclass(NullPen, AbstractPointPen)
assert not isinstance(NullPen(), AbstractPointPen)
class _TestPen(BasePen):
def __init__(self):
BasePen.__init__(self, glyphSet={})

View File

@ -7,6 +7,23 @@ from fontTools.pens.pointPen import AbstractPointPen, PointToSegmentPen, \
SegmentToPointPen, GuessSmoothPointPen, ReverseContourPointPen
def test_subclasshook():
class NullPen:
def beginPath(self, identifier, **kwargs) -> None:
pass
def endPath(self) -> None:
pass
def addPoint(self, pt, segmentType, smooth, name, identifier, **kwargs) -> None:
pass
def addComponent(self, baseGlyphName, transformation, identifier, **kwargs) -> None:
pass
assert issubclass(NullPen, AbstractPointPen)
assert isinstance(NullPen(), AbstractPointPen)
assert not issubclass(NullPen, AbstractPen)
assert not isinstance(NullPen(), AbstractPen)
class _TestSegmentPen(AbstractPen):
def __init__(self):