diff --git a/Lib/fontTools/pens/filterPen.py b/Lib/fontTools/pens/filterPen.py index 6c8712c26..f104e67dd 100644 --- a/Lib/fontTools/pens/filterPen.py +++ b/Lib/fontTools/pens/filterPen.py @@ -1,5 +1,7 @@ -from fontTools.pens.basePen import AbstractPen -from fontTools.pens.pointPen import AbstractPointPen +from __future__ import annotations + +from fontTools.pens.basePen import AbstractPen, DecomposingPen +from fontTools.pens.pointPen import AbstractPointPen, DecomposingPointPen from fontTools.pens.recordingPen import RecordingPen @@ -150,8 +152,8 @@ class FilterPointPen(_PassThruComponentsMixin, AbstractPointPen): ('endPath', (), {}) """ - def __init__(self, outPointPen): - self._outPen = outPointPen + def __init__(self, outPen): + self._outPen = outPen def beginPath(self, **kwargs): self._outPen.beginPath(**kwargs) @@ -161,3 +163,79 @@ class FilterPointPen(_PassThruComponentsMixin, AbstractPointPen): def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs) + + +class _DecomposingFilterPenMixin: + """Mixin class that decomposes components as regular contours. + + Shared by both DecomposingFilterPen and DecomposingFilterPointPen. + + Takes two required parameters, another (segment or point) pen 'outPen' to draw + with, and a 'glyphSet' dict of drawable glyph objects to draw components from. + + The 'skipMissingComponents' and 'reverseFlipped' optional arguments work the + same as in the DecomposingPen/DecomposingPointPen. Both are False by default. + + In addition, the decomposing filter pens also take the following two options: + + 'include' is an optional set of component base glyph names to consider for + decomposition; the default include=None means decompose all components no matter + the base glyph name). + + 'decomposeNested' (bool) controls whether to recurse decomposition into nested + components of components (this only matters when 'include' was also provided); + if False, only decompose top-level components included in the set, but not + also their children. + """ + + # raises MissingComponentError if base glyph is not found in glyphSet + skipMissingComponents = False + + def __init__( + self, + outPen, + glyphSet, + skipMissingComponents=None, + reverseFlipped=False, + include: set[str] | None = None, + decomposeNested: bool = True, + ): + super().__init__( + outPen=outPen, + glyphSet=glyphSet, + skipMissingComponents=skipMissingComponents, + reverseFlipped=reverseFlipped, + ) + self.include = include + self.decomposeNested = decomposeNested + + def addComponent(self, baseGlyphName, transformation, **kwargs): + # only decompose the component if it's included in the set + if self.include is None or baseGlyphName in self.include: + # if we're decomposing nested components, temporarily set include to None + include_bak = self.include + if self.decomposeNested and self.include: + self.include = None + try: + super().addComponent(baseGlyphName, transformation, **kwargs) + finally: + if self.include != include_bak: + self.include = include_bak + else: + _PassThruComponentsMixin.addComponent( + self, baseGlyphName, transformation, **kwargs + ) + + +class DecomposingFilterPen(_DecomposingFilterPenMixin, DecomposingPen, FilterPen): + """Filter pen that draws components as regular contours.""" + + pass + + +class DecomposingFilterPointPen( + _DecomposingFilterPenMixin, DecomposingPointPen, FilterPointPen +): + """Filter point pen that draws components as regular contours.""" + + pass