[recordingPen] Add DecomposingRecordingPointPen, test new decomposing pen options

This commit is contained in:
Cosimo Lupo 2024-03-04 13:32:47 +00:00
parent c0074ee26f
commit cccc358c79
No known key found for this signature in database
GPG Key ID: DF65A8A5A119C9A8

View File

@ -1,7 +1,7 @@
"""Pen recording operations that can be accessed or replayed.""" """Pen recording operations that can be accessed or replayed."""
from fontTools.pens.basePen import AbstractPen, DecomposingPen from fontTools.pens.basePen import AbstractPen, DecomposingPen
from fontTools.pens.pointPen import AbstractPointPen from fontTools.pens.pointPen import AbstractPointPen, DecomposingPointPen
__all__ = [ __all__ = [
@ -85,9 +85,10 @@ class DecomposingRecordingPen(DecomposingPen, RecordingPen):
"""Same as RecordingPen, except that it doesn't keep components """Same as RecordingPen, except that it doesn't keep components
as references, but draws them decomposed as regular contours. 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 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): >>> class SimpleGlyph(object):
... def draw(self, pen): ... def draw(self, pen):
@ -97,16 +98,42 @@ class DecomposingRecordingPen(DecomposingPen, RecordingPen):
>>> class CompositeGlyph(object): >>> class CompositeGlyph(object):
... def draw(self, pen): ... def draw(self, pen):
... pen.addComponent('a', (1, 0, 0, 1, -1, 1)) ... pen.addComponent('a', (1, 0, 0, 1, -1, 1))
>>> glyphSet = {'a': SimpleGlyph(), 'b': CompositeGlyph()} >>> 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()): >>> for name, glyph in sorted(glyphSet.items()):
... pen = DecomposingRecordingPen(glyphSet) ... 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) ... glyph.draw(pen)
... print("{}: {}".format(name, pen.value)) ... print("{}: {}".format(name, pen.value))
a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())] a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())]
b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('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 skipMissingComponents = False
@ -174,6 +201,96 @@ class RecordingPointPen(AbstractPointPen):
drawPoints = replay 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): def lerpRecordings(recording1, recording2, factor=0.5):
"""Linearly interpolate between two recordings. The recordings """Linearly interpolate between two recordings. The recordings
must be decomposed, i.e. they must not contain any components. must be decomposed, i.e. they must not contain any components.