Merge pull request #3460 from fonttools/decompose-filter-pen
add filter pens that decompose components
This commit is contained in:
commit
a3b9eddcaf
@ -39,7 +39,7 @@ sequence of length 2 will do.
|
||||
from typing import Tuple, Dict
|
||||
|
||||
from fontTools.misc.loggingTools import LogMixin
|
||||
from fontTools.misc.transform import DecomposedTransform
|
||||
from fontTools.misc.transform import DecomposedTransform, Identity
|
||||
|
||||
__all__ = [
|
||||
"AbstractPen",
|
||||
@ -195,17 +195,40 @@ class DecomposingPen(LoggingPen):
|
||||
|
||||
By default a warning message is logged when a base glyph is missing;
|
||||
set the class variable ``skipMissingComponents`` to False if you want
|
||||
to raise a :class:`MissingComponentError` exception.
|
||||
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):
|
||||
"""Takes a single 'glyphSet' argument (dict), in which the glyphs
|
||||
that are referenced as components are looked up by their name.
|
||||
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__()
|
||||
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."""
|
||||
@ -218,8 +241,19 @@ class DecomposingPen(LoggingPen):
|
||||
raise MissingComponentError(glyphName)
|
||||
self.log.warning("glyph '%s' is missing from glyphSet; skipped" % glyphName)
|
||||
else:
|
||||
tPen = TransformPen(self, transformation)
|
||||
glyph.draw(tPen)
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -15,8 +15,9 @@ For instance, whether or not a point is smooth, and its name.
|
||||
import math
|
||||
from typing import Any, Optional, Tuple, Dict
|
||||
|
||||
from fontTools.pens.basePen import AbstractPen, PenError
|
||||
from fontTools.misc.transform import DecomposedTransform
|
||||
from fontTools.misc.loggingTools import LogMixin
|
||||
from fontTools.pens.basePen import AbstractPen, MissingComponentError, PenError
|
||||
from fontTools.misc.transform import DecomposedTransform, Identity
|
||||
|
||||
__all__ = [
|
||||
"AbstractPointPen",
|
||||
@ -523,3 +524,77 @@ class ReverseContourPointPen(AbstractPointPen):
|
||||
if self.currentContour is not None:
|
||||
raise PenError("Components must be added before or after contours")
|
||||
self.pen.addComponent(glyphName, transform, identifier=identifier, **kwargs)
|
||||
|
||||
|
||||
class DecomposingPointPen(LogMixin, AbstractPointPen):
|
||||
"""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 DecomposingRecordingPointPen).
|
||||
|
||||
You must override beginPath, addPoint, endPath. You may
|
||||
additionally override addVarComponent 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().__init__(*args, **kwargs)
|
||||
self.glyphSet = glyphSet
|
||||
self.skipMissingComponents = (
|
||||
self.__class__.skipMissingComponents
|
||||
if skipMissingComponents is None
|
||||
else skipMissingComponents
|
||||
)
|
||||
self.reverseFlipped = reverseFlipped
|
||||
|
||||
def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs):
|
||||
"""Transform the points of the base glyph and draw it onto self.
|
||||
|
||||
The `identifier` parameter and any extra kwargs are ignored.
|
||||
"""
|
||||
from fontTools.pens.transformPen import TransformPointPen
|
||||
|
||||
try:
|
||||
glyph = self.glyphSet[baseGlyphName]
|
||||
except KeyError:
|
||||
if not self.skipMissingComponents:
|
||||
raise MissingComponentError(baseGlyphName)
|
||||
self.log.warning(
|
||||
"glyph '%s' is missing from glyphSet; skipped" % baseGlyphName
|
||||
)
|
||||
else:
|
||||
pen = self
|
||||
if transformation != Identity:
|
||||
pen = TransformPointPen(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 a * d - b * c < 0:
|
||||
pen = ReverseContourPointPen(pen)
|
||||
glyph.drawPoints(pen)
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""Pen recording operations that can be accessed or replayed."""
|
||||
|
||||
from fontTools.pens.basePen import AbstractPen, DecomposingPen
|
||||
from fontTools.pens.pointPen import AbstractPointPen
|
||||
from fontTools.pens.pointPen import AbstractPointPen, DecomposingPointPen
|
||||
|
||||
|
||||
__all__ = [
|
||||
@ -85,28 +85,55 @@ class DecomposingRecordingPen(DecomposingPen, RecordingPen):
|
||||
"""Same as RecordingPen, except that it doesn't keep components
|
||||
as references, but draws them decomposed as regular contours.
|
||||
|
||||
The constructor takes a single 'glyphSet' positional argument,
|
||||
The constructor takes a required 'glyphSet' positional argument,
|
||||
a dictionary of glyph objects (i.e. with a 'draw' method) keyed
|
||||
by thir name::
|
||||
by thir name; other arguments are forwarded to the DecomposingPen's
|
||||
constructor::
|
||||
|
||||
>>> class SimpleGlyph(object):
|
||||
... def draw(self, pen):
|
||||
... pen.moveTo((0, 0))
|
||||
... pen.curveTo((1, 1), (2, 2), (3, 3))
|
||||
... pen.closePath()
|
||||
>>> class CompositeGlyph(object):
|
||||
... def draw(self, pen):
|
||||
... pen.addComponent('a', (1, 0, 0, 1, -1, 1))
|
||||
>>> glyphSet = {'a': SimpleGlyph(), 'b': CompositeGlyph()}
|
||||
>>> for name, glyph in sorted(glyphSet.items()):
|
||||
... pen = DecomposingRecordingPen(glyphSet)
|
||||
... glyph.draw(pen)
|
||||
... print("{}: {}".format(name, pen.value))
|
||||
a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())]
|
||||
b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())]
|
||||
>>> class SimpleGlyph(object):
|
||||
... def draw(self, pen):
|
||||
... pen.moveTo((0, 0))
|
||||
... pen.curveTo((1, 1), (2, 2), (3, 3))
|
||||
... pen.closePath()
|
||||
>>> class CompositeGlyph(object):
|
||||
... def draw(self, pen):
|
||||
... pen.addComponent('a', (1, 0, 0, 1, -1, 1))
|
||||
>>> class MissingComponent(object):
|
||||
... def draw(self, pen):
|
||||
... pen.addComponent('foobar', (1, 0, 0, 1, 0, 0))
|
||||
>>> class FlippedComponent(object):
|
||||
... def draw(self, pen):
|
||||
... pen.addComponent('a', (-1, 0, 0, 1, 0, 0))
|
||||
>>> glyphSet = {
|
||||
... 'a': SimpleGlyph(),
|
||||
... 'b': CompositeGlyph(),
|
||||
... 'c': MissingComponent(),
|
||||
... 'd': FlippedComponent(),
|
||||
... }
|
||||
>>> for name, glyph in sorted(glyphSet.items()):
|
||||
... pen = DecomposingRecordingPen(glyphSet)
|
||||
... try:
|
||||
... glyph.draw(pen)
|
||||
... except pen.MissingComponentError:
|
||||
... pass
|
||||
... print("{}: {}".format(name, pen.value))
|
||||
a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())]
|
||||
b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())]
|
||||
c: []
|
||||
d: [('moveTo', ((0, 0),)), ('curveTo', ((-1, 1), (-2, 2), (-3, 3))), ('closePath', ())]
|
||||
>>> for name, glyph in sorted(glyphSet.items()):
|
||||
... pen = DecomposingRecordingPen(
|
||||
... glyphSet, skipMissingComponents=True, reverseFlipped=True,
|
||||
... )
|
||||
... glyph.draw(pen)
|
||||
... print("{}: {}".format(name, pen.value))
|
||||
a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())]
|
||||
b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())]
|
||||
c: []
|
||||
d: [('moveTo', ((0, 0),)), ('lineTo', ((-3, 3),)), ('curveTo', ((-2, 2), (-1, 1), (0, 0))), ('closePath', ())]
|
||||
"""
|
||||
|
||||
# raises KeyError if base glyph is not found in glyphSet
|
||||
# raises MissingComponentError(KeyError) if base glyph is not found in glyphSet
|
||||
skipMissingComponents = False
|
||||
|
||||
|
||||
@ -174,6 +201,96 @@ class RecordingPointPen(AbstractPointPen):
|
||||
drawPoints = replay
|
||||
|
||||
|
||||
class DecomposingRecordingPointPen(DecomposingPointPen, RecordingPointPen):
|
||||
"""Same as RecordingPointPen, except that it doesn't keep components
|
||||
as references, but draws them decomposed as regular contours.
|
||||
|
||||
The constructor takes a required 'glyphSet' positional argument,
|
||||
a dictionary of pointPen-drawable glyph objects (i.e. with a 'drawPoints' method)
|
||||
keyed by thir name; other arguments are forwarded to the DecomposingPointPen's
|
||||
constructor::
|
||||
|
||||
>>> from pprint import pprint
|
||||
>>> class SimpleGlyph(object):
|
||||
... def drawPoints(self, pen):
|
||||
... pen.beginPath()
|
||||
... pen.addPoint((0, 0), "line")
|
||||
... pen.addPoint((1, 1))
|
||||
... pen.addPoint((2, 2))
|
||||
... pen.addPoint((3, 3), "curve")
|
||||
... pen.endPath()
|
||||
>>> class CompositeGlyph(object):
|
||||
... def drawPoints(self, pen):
|
||||
... pen.addComponent('a', (1, 0, 0, 1, -1, 1))
|
||||
>>> class MissingComponent(object):
|
||||
... def drawPoints(self, pen):
|
||||
... pen.addComponent('foobar', (1, 0, 0, 1, 0, 0))
|
||||
>>> class FlippedComponent(object):
|
||||
... def drawPoints(self, pen):
|
||||
... pen.addComponent('a', (-1, 0, 0, 1, 0, 0))
|
||||
>>> glyphSet = {
|
||||
... 'a': SimpleGlyph(),
|
||||
... 'b': CompositeGlyph(),
|
||||
... 'c': MissingComponent(),
|
||||
... 'd': FlippedComponent(),
|
||||
... }
|
||||
>>> for name, glyph in sorted(glyphSet.items()):
|
||||
... pen = DecomposingRecordingPointPen(glyphSet)
|
||||
... try:
|
||||
... glyph.drawPoints(pen)
|
||||
... except pen.MissingComponentError:
|
||||
... pass
|
||||
... pprint({name: pen.value})
|
||||
{'a': [('beginPath', (), {}),
|
||||
('addPoint', ((0, 0), 'line', False, None), {}),
|
||||
('addPoint', ((1, 1), None, False, None), {}),
|
||||
('addPoint', ((2, 2), None, False, None), {}),
|
||||
('addPoint', ((3, 3), 'curve', False, None), {}),
|
||||
('endPath', (), {})]}
|
||||
{'b': [('beginPath', (), {}),
|
||||
('addPoint', ((-1, 1), 'line', False, None), {}),
|
||||
('addPoint', ((0, 2), None, False, None), {}),
|
||||
('addPoint', ((1, 3), None, False, None), {}),
|
||||
('addPoint', ((2, 4), 'curve', False, None), {}),
|
||||
('endPath', (), {})]}
|
||||
{'c': []}
|
||||
{'d': [('beginPath', (), {}),
|
||||
('addPoint', ((0, 0), 'line', False, None), {}),
|
||||
('addPoint', ((-1, 1), None, False, None), {}),
|
||||
('addPoint', ((-2, 2), None, False, None), {}),
|
||||
('addPoint', ((-3, 3), 'curve', False, None), {}),
|
||||
('endPath', (), {})]}
|
||||
>>> for name, glyph in sorted(glyphSet.items()):
|
||||
... pen = DecomposingRecordingPointPen(
|
||||
... glyphSet, skipMissingComponents=True, reverseFlipped=True,
|
||||
... )
|
||||
... glyph.drawPoints(pen)
|
||||
... pprint({name: pen.value})
|
||||
{'a': [('beginPath', (), {}),
|
||||
('addPoint', ((0, 0), 'line', False, None), {}),
|
||||
('addPoint', ((1, 1), None, False, None), {}),
|
||||
('addPoint', ((2, 2), None, False, None), {}),
|
||||
('addPoint', ((3, 3), 'curve', False, None), {}),
|
||||
('endPath', (), {})]}
|
||||
{'b': [('beginPath', (), {}),
|
||||
('addPoint', ((-1, 1), 'line', False, None), {}),
|
||||
('addPoint', ((0, 2), None, False, None), {}),
|
||||
('addPoint', ((1, 3), None, False, None), {}),
|
||||
('addPoint', ((2, 4), 'curve', False, None), {}),
|
||||
('endPath', (), {})]}
|
||||
{'c': []}
|
||||
{'d': [('beginPath', (), {}),
|
||||
('addPoint', ((0, 0), 'curve', False, None), {}),
|
||||
('addPoint', ((-3, 3), 'line', False, None), {}),
|
||||
('addPoint', ((-2, 2), None, False, None), {}),
|
||||
('addPoint', ((-1, 1), None, False, None), {}),
|
||||
('endPath', (), {})]}
|
||||
"""
|
||||
|
||||
# raises MissingComponentError(KeyError) if base glyph is not found in glyphSet
|
||||
skipMissingComponents = False
|
||||
|
||||
|
||||
def lerpRecordings(recording1, recording2, factor=0.5):
|
||||
"""Linearly interpolate between two recordings. The recordings
|
||||
must be decomposed, i.e. they must not contain any components.
|
||||
|
179
Tests/pens/filterPen_test.py
Normal file
179
Tests/pens/filterPen_test.py
Normal file
@ -0,0 +1,179 @@
|
||||
import logging
|
||||
from types import MappingProxyType
|
||||
|
||||
from fontTools.pens.basePen import MissingComponentError
|
||||
from fontTools.pens.filterPen import DecomposingFilterPen, DecomposingFilterPointPen
|
||||
from fontTools.pens.pointPen import PointToSegmentPen
|
||||
from fontTools.pens.recordingPen import RecordingPen, RecordingPointPen
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class SimpleGlyph:
|
||||
def draw(self, pen):
|
||||
pen.moveTo((0, 0))
|
||||
pen.curveTo((1, 1), (2, 2), (3, 3))
|
||||
pen.closePath()
|
||||
|
||||
def drawPoints(self, pen):
|
||||
pen.beginPath()
|
||||
pen.addPoint((0, 0), "line")
|
||||
pen.addPoint((1, 1))
|
||||
pen.addPoint((2, 2))
|
||||
pen.addPoint((3, 3), "curve")
|
||||
pen.endPath()
|
||||
|
||||
|
||||
class CompositeGlyph:
|
||||
def draw(self, pen):
|
||||
pen.addComponent("simple_glyph", (1, 0, 0, 1, -1, 1))
|
||||
|
||||
def drawPoints(self, pen):
|
||||
self.draw(pen)
|
||||
|
||||
|
||||
class MissingComponent(CompositeGlyph):
|
||||
def draw(self, pen):
|
||||
pen.addComponent("foobar", (1, 0, 0, 1, 0, 0))
|
||||
|
||||
|
||||
class FlippedComponent(CompositeGlyph):
|
||||
def draw(self, pen):
|
||||
pen.addComponent("simple_glyph", (1, 0, 0, 1, 0, 0))
|
||||
pen.addComponent("composite_glyph", (-1, 0, 0, 1, 0, 0))
|
||||
|
||||
|
||||
GLYPHSET = MappingProxyType(
|
||||
{
|
||||
"simple_glyph": SimpleGlyph(),
|
||||
"composite_glyph": CompositeGlyph(),
|
||||
"missing_component": MissingComponent(),
|
||||
"flipped_component": FlippedComponent(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _is_point_pen(pen):
|
||||
return hasattr(pen, "addPoint")
|
||||
|
||||
|
||||
def _draw(glyph, pen):
|
||||
if _is_point_pen(pen):
|
||||
# point pen
|
||||
glyph.drawPoints(pen)
|
||||
else:
|
||||
# segment pen
|
||||
glyph.draw(pen)
|
||||
|
||||
|
||||
@pytest.fixture(params=[DecomposingFilterPen, DecomposingFilterPointPen])
|
||||
def FilterPen(request):
|
||||
return request.param
|
||||
|
||||
|
||||
def _init_rec_and_filter_pens(FilterPenClass, *args, **kwargs):
|
||||
rec = out = RecordingPen()
|
||||
if _is_point_pen(FilterPenClass):
|
||||
out = PointToSegmentPen(rec)
|
||||
fpen = FilterPenClass(out, GLYPHSET, *args, **kwargs)
|
||||
return rec, fpen
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"glyph_name, expected",
|
||||
[
|
||||
(
|
||||
"simple_glyph",
|
||||
[
|
||||
("moveTo", ((0, 0),)),
|
||||
("curveTo", ((1, 1), (2, 2), (3, 3))),
|
||||
("closePath", ()),
|
||||
],
|
||||
),
|
||||
(
|
||||
"composite_glyph",
|
||||
[
|
||||
("moveTo", ((-1, 1),)),
|
||||
("curveTo", ((0, 2), (1, 3), (2, 4))),
|
||||
("closePath", ()),
|
||||
],
|
||||
),
|
||||
("missing_component", MissingComponentError),
|
||||
(
|
||||
"flipped_component",
|
||||
[
|
||||
("moveTo", ((0, 0),)),
|
||||
("curveTo", ((1, 1), (2, 2), (3, 3))),
|
||||
("closePath", ()),
|
||||
("moveTo", ((1, 1),)),
|
||||
("curveTo", ((0, 2), (-1, 3), (-2, 4))),
|
||||
("closePath", ()),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_decomposing_filter_pen(FilterPen, glyph_name, expected):
|
||||
rec, fpen = _init_rec_and_filter_pens(FilterPen)
|
||||
glyph = GLYPHSET[glyph_name]
|
||||
try:
|
||||
_draw(glyph, fpen)
|
||||
except Exception as e:
|
||||
assert isinstance(e, expected)
|
||||
else:
|
||||
assert rec.value == expected
|
||||
|
||||
|
||||
def test_decomposing_filter_pen_skip_missing(FilterPen, caplog):
|
||||
rec, fpen = _init_rec_and_filter_pens(FilterPen, skipMissingComponents=True)
|
||||
glyph = GLYPHSET["missing_component"]
|
||||
with caplog.at_level(logging.WARNING, logger="fontTools.pens.filterPen"):
|
||||
_draw(glyph, fpen)
|
||||
assert rec.value == []
|
||||
assert "glyph 'foobar' is missing from glyphSet; skipped" in caplog.text
|
||||
|
||||
|
||||
def test_decomposing_filter_pen_reverse_flipped(FilterPen):
|
||||
rec, fpen = _init_rec_and_filter_pens(FilterPen, reverseFlipped=True)
|
||||
glyph = GLYPHSET["flipped_component"]
|
||||
_draw(glyph, fpen)
|
||||
assert rec.value == [
|
||||
("moveTo", ((0, 0),)),
|
||||
("curveTo", ((1, 1), (2, 2), (3, 3))),
|
||||
("closePath", ()),
|
||||
("moveTo", ((1, 1),)),
|
||||
("lineTo", ((-2, 4),)),
|
||||
("curveTo", ((-1, 3), (0, 2), (1, 1))),
|
||||
("closePath", ()),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"decomposeNested, expected",
|
||||
[
|
||||
(
|
||||
True,
|
||||
[
|
||||
("addComponent", ("simple_glyph", (1, 0, 0, 1, 0, 0))),
|
||||
("moveTo", ((1, 1),)),
|
||||
("curveTo", ((0, 2), (-1, 3), (-2, 4))),
|
||||
("closePath", ()),
|
||||
],
|
||||
),
|
||||
(
|
||||
False,
|
||||
[
|
||||
("addComponent", ("simple_glyph", (1, 0, 0, 1, 0, 0))),
|
||||
("addComponent", ("simple_glyph", (-1, 0, 0, 1, 1, 1))),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_decomposing_filter_pen_include_decomposeNested(
|
||||
FilterPen, decomposeNested, expected
|
||||
):
|
||||
rec, fpen = _init_rec_and_filter_pens(
|
||||
FilterPen, include={"composite_glyph"}, decomposeNested=decomposeNested
|
||||
)
|
||||
glyph = GLYPHSET["flipped_component"]
|
||||
_draw(glyph, fpen)
|
||||
assert rec.value == expected
|
Loading…
x
Reference in New Issue
Block a user