Merge pull request #1766 from anthrotype/more-point-pens
[pointPens] Add {Filter,Recording,Transform,Rounding}PointPen
This commit is contained in:
commit
43599ee3dd
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user