Spaces:
Runtime error
Runtime error
"""Pen recording operations that can be accessed or replayed.""" | |
from fontTools.pens.basePen import AbstractPen, DecomposingPen | |
from fontTools.pens.pointPen import AbstractPointPen | |
__all__ = [ | |
"replayRecording", | |
"RecordingPen", | |
"DecomposingRecordingPen", | |
"RecordingPointPen", | |
"lerpRecordings", | |
] | |
def replayRecording(recording, pen): | |
"""Replay a recording, as produced by RecordingPen or DecomposingRecordingPen, | |
to a pen. | |
Note that recording does not have to be produced by those pens. | |
It can be any iterable of tuples of method name and tuple-of-arguments. | |
Likewise, pen can be any objects receiving those method calls. | |
""" | |
for operator, operands in recording: | |
getattr(pen, operator)(*operands) | |
class RecordingPen(AbstractPen): | |
"""Pen recording operations that can be accessed or replayed. | |
The recording can be accessed as pen.value; or replayed using | |
pen.replay(otherPen). | |
:Example: | |
from fontTools.ttLib import TTFont | |
from fontTools.pens.recordingPen import RecordingPen | |
glyph_name = 'dollar' | |
font_path = 'MyFont.otf' | |
font = TTFont(font_path) | |
glyphset = font.getGlyphSet() | |
glyph = glyphset[glyph_name] | |
pen = RecordingPen() | |
glyph.draw(pen) | |
print(pen.value) | |
""" | |
def __init__(self): | |
self.value = [] | |
def moveTo(self, p0): | |
self.value.append(("moveTo", (p0,))) | |
def lineTo(self, p1): | |
self.value.append(("lineTo", (p1,))) | |
def qCurveTo(self, *points): | |
self.value.append(("qCurveTo", points)) | |
def curveTo(self, *points): | |
self.value.append(("curveTo", points)) | |
def closePath(self): | |
self.value.append(("closePath", ())) | |
def endPath(self): | |
self.value.append(("endPath", ())) | |
def addComponent(self, glyphName, transformation): | |
self.value.append(("addComponent", (glyphName, transformation))) | |
def addVarComponent(self, glyphName, transformation, location): | |
self.value.append(("addVarComponent", (glyphName, transformation, location))) | |
def replay(self, pen): | |
replayRecording(self.value, pen) | |
draw = replay | |
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, | |
a dictionary of glyph objects (i.e. with a 'draw' method) keyed | |
by thir name:: | |
>>> 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', ())] | |
""" | |
# raises KeyError if base glyph is not found in glyphSet | |
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). | |
: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, identifier=None, **kwargs): | |
if identifier is not None: | |
kwargs["identifier"] = identifier | |
self.value.append(("beginPath", (), kwargs)) | |
def endPath(self): | |
self.value.append(("endPath", (), {})) | |
def addPoint( | |
self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs | |
): | |
if identifier is not None: | |
kwargs["identifier"] = identifier | |
self.value.append(("addPoint", (pt, segmentType, smooth, name), kwargs)) | |
def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs): | |
if identifier is not None: | |
kwargs["identifier"] = identifier | |
self.value.append(("addComponent", (baseGlyphName, transformation), kwargs)) | |
def addVarComponent( | |
self, baseGlyphName, transformation, location, identifier=None, **kwargs | |
): | |
if identifier is not None: | |
kwargs["identifier"] = identifier | |
self.value.append( | |
("addVarComponent", (baseGlyphName, transformation, location), kwargs) | |
) | |
def replay(self, pointPen): | |
for operator, args, kwargs in self.value: | |
getattr(pointPen, operator)(*args, **kwargs) | |
drawPoints = replay | |
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. | |
Factor is typically between 0 and 1. 0 means the first recording, | |
1 means the second recording, and 0.5 means the average of the | |
two recordings. Other values are possible, and can be useful to | |
extrapolate. Defaults to 0.5. | |
Returns a generator with the new recording. | |
""" | |
if len(recording1) != len(recording2): | |
raise ValueError( | |
"Mismatched lengths: %d and %d" % (len(recording1), len(recording2)) | |
) | |
for (op1, args1), (op2, args2) in zip(recording1, recording2): | |
if op1 != op2: | |
raise ValueError("Mismatched operations: %s, %s" % (op1, op2)) | |
if op1 == "addComponent": | |
raise ValueError("Cannot interpolate components") | |
else: | |
mid_args = [ | |
(x1 + (x2 - x1) * factor, y1 + (y2 - y1) * factor) | |
for (x1, y1), (x2, y2) in zip(args1, args2) | |
] | |
yield (op1, mid_args) | |
if __name__ == "__main__": | |
pen = RecordingPen() | |
pen.moveTo((0, 0)) | |
pen.lineTo((0, 100)) | |
pen.curveTo((50, 75), (60, 50), (50, 25)) | |
pen.closePath() | |
from pprint import pprint | |
pprint(pen.value) | |