Merge pull request #1766 from anthrotype/more-point-pens

[pointPens] Add {Filter,Recording,Transform,Rounding}PointPen
This commit is contained in:
Cosimo Lupo 2019-11-28 16:56:38 +00:00 committed by GitHub
commit 43599ee3dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 239 additions and 14 deletions

View File

@ -1,12 +1,13 @@
from fontTools.misc.py23 import *
from fontTools.pens.basePen import AbstractPen
from fontTools.pens.pointPen import AbstractPointPen
from fontTools.pens.recordingPen import RecordingPen
class _PassThruComponentsMixin(object):
def addComponent(self, glyphName, transformation):
self._outPen.addComponent(glyphName, transformation)
def addComponent(self, glyphName, transformation, **kwargs):
self._outPen.addComponent(glyphName, transformation, **kwargs)
class FilterPen(_PassThruComponentsMixin, AbstractPen):
@ -118,3 +119,41 @@ class ContourFilterPen(_PassThruComponentsMixin, RecordingPen):
Otherwise, the return value is drawn with the output pen.
"""
return # or return contour
class FilterPointPen(_PassThruComponentsMixin, AbstractPointPen):
""" Baseclass for point pens that apply some transformation to the
coordinates they receive and pass them to another point pen.
You can override any of its methods. The default implementation does
nothing, but passes the commands unmodified to the other pen.
>>> from fontTools.pens.recordingPen import RecordingPointPen
>>> rec = RecordingPointPen()
>>> pen = FilterPointPen(rec)
>>> v = iter(rec.value)
>>> pen.beginPath(identifier="abc")
>>> next(v)
('beginPath', (), {'identifier': 'abc'})
>>> pen.addPoint((1, 2), "line", False)
>>> next(v)
('addPoint', ((1, 2), 'line', False, None), {})
>>> pen.addComponent("a", (2, 0, 0, 2, 10, -10), identifier="0001")
>>> next(v)
('addComponent', ('a', (2, 0, 0, 2, 10, -10)), {'identifier': '0001'})
>>> pen.endPath()
>>> next(v)
('endPath', (), {})
"""
def __init__(self, outPointPen):
self._outPen = outPointPen
def beginPath(self, **kwargs):
self._outPen.beginPath(**kwargs)
def endPath(self):
self._outPen.endPath()
def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs)

View File

@ -1,9 +1,15 @@
"""Pen recording operations that can be accessed or replayed."""
from fontTools.misc.py23 import *
from fontTools.pens.basePen import AbstractPen, DecomposingPen
from fontTools.pens.pointPen import AbstractPointPen
__all__ = ["replayRecording", "RecordingPen", "DecomposingRecordingPen"]
__all__ = [
"replayRecording",
"RecordingPen",
"DecomposingRecordingPen",
"RecordingPointPen",
]
def replayRecording(recording, pen):
@ -89,6 +95,51 @@ class DecomposingRecordingPen(DecomposingPen, RecordingPen):
skipMissingComponents = False
class RecordingPointPen(AbstractPointPen):
"""PointPen recording operations that can be accessed or replayed.
The recording can be accessed as pen.value; or replayed using
pointPen.replay(otherPointPen).
Usage example:
==============
from defcon import Font
from fontTools.pens.recordingPen import RecordingPointPen
glyph_name = 'a'
font_path = 'MyFont.ufo'
font = Font(font_path)
glyph = font[glyph_name]
pen = RecordingPointPen()
glyph.drawPoints(pen)
print(pen.value)
new_glyph = font.newGlyph('b')
pen.replay(new_glyph.getPointPen())
"""
def __init__(self):
self.value = []
def beginPath(self, **kwargs):
self.value.append(("beginPath", (), kwargs))
def endPath(self):
self.value.append(("endPath", (), {}))
def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
self.value.append(("addPoint", (pt, segmentType, smooth, name), kwargs))
def addComponent(self, baseGlyphName, transformation, **kwargs):
self.value.append(("addComponent", (baseGlyphName, transformation), kwargs))
def replay(self, pointPen):
for operator, args, kwargs in self.value:
getattr(pointPen, operator)(*args, **kwargs)
if __name__ == "__main__":
from fontTools.pens.basePen import _TestPen
pen = RecordingPen()

View File

@ -1,6 +1,9 @@
from fontTools.misc.fixedTools import otRound
from fontTools.misc.transform import Transform
from fontTools.pens.filterPen import FilterPen
from fontTools.pens.filterPen import FilterPen, FilterPointPen
__all__ = ["RoundingPen", "RoundingPointPen"]
class RoundingPen(FilterPen):
@ -54,3 +57,56 @@ class RoundingPen(FilterPen):
self.roundFunc(transformation[5]),
),
)
class RoundingPointPen(FilterPointPen):
"""
Filter point pen that rounds point coordinates and component XY offsets to integer.
>>> from fontTools.pens.recordingPen import RecordingPointPen
>>> recpen = RecordingPointPen()
>>> roundpen = RoundingPointPen(recpen)
>>> roundpen.beginPath()
>>> roundpen.addPoint((0.4, 0.6), 'line')
>>> roundpen.addPoint((1.6, 2.5), 'line')
>>> roundpen.addPoint((2.4, 4.6))
>>> roundpen.addPoint((3.3, 5.7))
>>> roundpen.addPoint((4.9, 6.1), 'qcurve')
>>> roundpen.endPath()
>>> roundpen.addComponent("a", (1.5, 0, 0, 1.5, 10.5, -10.5))
>>> recpen.value == [
... ('beginPath', (), {}),
... ('addPoint', ((0, 1), 'line', False, None), {}),
... ('addPoint', ((2, 3), 'line', False, None), {}),
... ('addPoint', ((2, 5), None, False, None), {}),
... ('addPoint', ((3, 6), None, False, None), {}),
... ('addPoint', ((5, 6), 'qcurve', False, None), {}),
... ('endPath', (), {}),
... ('addComponent', ('a', (1.5, 0, 0, 1.5, 11, -10)), {}),
... ]
True
"""
def __init__(self, outPen, roundFunc=otRound):
super().__init__(outPen)
self.roundFunc = roundFunc
def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
self._outPen.addPoint(
(self.roundFunc(pt[0]), self.roundFunc(pt[1])),
segmentType=segmentType,
smooth=smooth,
name=name,
**kwargs,
)
def addComponent(self, baseGlyphName, transformation, **kwargs):
self._outPen.addComponent(
baseGlyphName,
Transform(
*transformation[:4],
self.roundFunc(transformation[4]),
self.roundFunc(transformation[5]),
),
**kwargs,
)

View File

@ -1,5 +1,5 @@
from fontTools.misc.py23 import *
from fontTools.pens.filterPen import FilterPen
from fontTools.pens.filterPen import FilterPen, FilterPointPen
__all__ = ["TransformPen"]
@ -55,6 +55,51 @@ class TransformPen(FilterPen):
self._outPen.addComponent(glyphName, transformation)
class TransformPointPen(FilterPointPen):
"""PointPen that transforms all coordinates using a Affine transformation,
and passes them to another PointPen.
>>> from fontTools.pens.recordingPen import RecordingPointPen
>>> rec = RecordingPointPen()
>>> pen = TransformPointPen(rec, (2, 0, 0, 2, -10, 5))
>>> v = iter(rec.value)
>>> pen.beginPath(identifier="contour-0")
>>> next(v)
('beginPath', (), {'identifier': 'contour-0'})
>>> pen.addPoint((100, 100), "line")
>>> next(v)
('addPoint', ((190, 205), 'line', False, None), {})
>>> pen.endPath()
>>> next(v)
('endPath', (), {})
>>> pen.addComponent("a", (1, 0, 0, 1, -10, 5), identifier="component-0")
>>> next(v)
('addComponent', ('a', <Transform [2 0 0 2 -30 15]>), {'identifier': 'component-0'})
"""
def __init__(self, outPointPen, transformation):
"""The 'outPointPen' argument is another point pen object.
It will receive the transformed coordinates.
The 'transformation' argument can either be a six-tuple, or a
fontTools.misc.transform.Transform object.
"""
super().__init__(outPointPen)
if not hasattr(transformation, "transformPoint"):
from fontTools.misc.transform import Transform
transformation = Transform(*transformation)
self._transformation = transformation
self._transformPoint = transformation.transformPoint
def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
self._outPen.addPoint(
self._transformPoint(pt), segmentType, smooth, name, **kwargs
)
def addComponent(self, baseGlyphName, transformation, **kwargs):
transformation = self._transformation.transform(transformation)
self._outPen.addComponent(baseGlyphName, transformation, **kwargs)
if __name__ == "__main__":
from fontTools.pens.basePen import _TestPen
pen = TransformPen(_TestPen(None), (2, 0, 0.5, 2, -10, 0))

View File

@ -1,19 +1,29 @@
from fontTools.misc.py23 import *
from fontTools.pens.recordingPen import RecordingPen, DecomposingRecordingPen
from fontTools.pens.recordingPen import (
RecordingPen,
DecomposingRecordingPen,
RecordingPointPen,
)
import pytest
class _TestGlyph(object):
def draw(self, pen):
pen.moveTo((0.0, 0.0))
pen.lineTo((0.0, 100.0))
pen.curveTo((50.0, 75.0), (60.0, 50.0), (50.0, 0.0))
pen.closePath()
def drawPoints(self, pen):
pen.beginPath(identifier="abc")
pen.addPoint((0.0, 0.0), "line", False, "start", identifier="0000")
pen.addPoint((0.0, 100.0), "line", False, None, identifier="0001")
pen.addPoint((50.0, 75.0), None, False, None, identifier="0002")
pen.addPoint((60.0, 50.0), None, False, None, identifier="0003")
pen.addPoint((50.0, 0.0), "curve", True, "last", identifier="0004")
pen.endPath()
class RecordingPenTest(object):
def test_addComponent(self):
pen = RecordingPen()
pen.addComponent("a", (2, 0, 0, 3, -10, 5))
@ -21,18 +31,42 @@ class RecordingPenTest(object):
class DecomposingRecordingPenTest(object):
def test_addComponent_decomposed(self):
pen = DecomposingRecordingPen({"a": _TestGlyph()})
pen.addComponent("a", (2, 0, 0, 3, -10, 5))
assert pen.value == [
('moveTo', ((-10.0, 5.0),)),
('lineTo', ((-10.0, 305.0),)),
('curveTo', ((90.0, 230.0), (110.0, 155.0), (90.0, 5.0),)),
('closePath', ())]
("moveTo", ((-10.0, 5.0),)),
("lineTo", ((-10.0, 305.0),)),
("curveTo", ((90.0, 230.0), (110.0, 155.0), (90.0, 5.0))),
("closePath", ()),
]
def test_addComponent_missing_raises(self):
pen = DecomposingRecordingPen(dict())
with pytest.raises(KeyError) as excinfo:
pen.addComponent("a", (1, 0, 0, 1, 0, 0))
assert excinfo.value.args[0] == "a"
class RecordingPointPenTest:
def test_record_and_replay(self):
pen = RecordingPointPen()
glyph = _TestGlyph()
glyph.drawPoints(pen)
pen.addComponent("a", (2, 0, 0, 2, -10, 5))
assert pen.value == [
("beginPath", (), {"identifier": "abc"}),
("addPoint", ((0.0, 0.0), "line", False, "start"), {"identifier": "0000"}),
("addPoint", ((0.0, 100.0), "line", False, None), {"identifier": "0001"}),
("addPoint", ((50.0, 75.0), None, False, None), {"identifier": "0002"}),
("addPoint", ((60.0, 50.0), None, False, None), {"identifier": "0003"}),
("addPoint", ((50.0, 0.0), "curve", True, "last"), {"identifier": "0004"}),
("endPath", (), {}),
("addComponent", ("a", (2, 0, 0, 2, -10, 5)), {}),
]
pen2 = RecordingPointPen()
pen.replay(pen2)
assert pen2.value == pen.value