recordingPen.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. """Pen recording operations that can be accessed or replayed."""
  2. from fontTools.pens.basePen import AbstractPen, DecomposingPen
  3. from fontTools.pens.pointPen import AbstractPointPen
  4. __all__ = [
  5. "replayRecording",
  6. "RecordingPen",
  7. "DecomposingRecordingPen",
  8. "RecordingPointPen",
  9. ]
  10. def replayRecording(recording, pen):
  11. """Replay a recording, as produced by RecordingPen or DecomposingRecordingPen,
  12. to a pen.
  13. Note that recording does not have to be produced by those pens.
  14. It can be any iterable of tuples of method name and tuple-of-arguments.
  15. Likewise, pen can be any objects receiving those method calls.
  16. """
  17. for operator, operands in recording:
  18. getattr(pen, operator)(*operands)
  19. class RecordingPen(AbstractPen):
  20. """Pen recording operations that can be accessed or replayed.
  21. The recording can be accessed as pen.value; or replayed using
  22. pen.replay(otherPen).
  23. :Example:
  24. from fontTools.ttLib import TTFont
  25. from fontTools.pens.recordingPen import RecordingPen
  26. glyph_name = 'dollar'
  27. font_path = 'MyFont.otf'
  28. font = TTFont(font_path)
  29. glyphset = font.getGlyphSet()
  30. glyph = glyphset[glyph_name]
  31. pen = RecordingPen()
  32. glyph.draw(pen)
  33. print(pen.value)
  34. """
  35. def __init__(self):
  36. self.value = []
  37. def moveTo(self, p0):
  38. self.value.append(("moveTo", (p0,)))
  39. def lineTo(self, p1):
  40. self.value.append(("lineTo", (p1,)))
  41. def qCurveTo(self, *points):
  42. self.value.append(("qCurveTo", points))
  43. def curveTo(self, *points):
  44. self.value.append(("curveTo", points))
  45. def closePath(self):
  46. self.value.append(("closePath", ()))
  47. def endPath(self):
  48. self.value.append(("endPath", ()))
  49. def addComponent(self, glyphName, transformation):
  50. self.value.append(("addComponent", (glyphName, transformation)))
  51. def addVarComponent(self, glyphName, transformation, location):
  52. self.value.append(("addVarComponent", (glyphName, transformation, location)))
  53. def replay(self, pen):
  54. replayRecording(self.value, pen)
  55. draw = replay
  56. class DecomposingRecordingPen(DecomposingPen, RecordingPen):
  57. """Same as RecordingPen, except that it doesn't keep components
  58. as references, but draws them decomposed as regular contours.
  59. The constructor takes a single 'glyphSet' positional argument,
  60. a dictionary of glyph objects (i.e. with a 'draw' method) keyed
  61. by thir name::
  62. >>> class SimpleGlyph(object):
  63. ... def draw(self, pen):
  64. ... pen.moveTo((0, 0))
  65. ... pen.curveTo((1, 1), (2, 2), (3, 3))
  66. ... pen.closePath()
  67. >>> class CompositeGlyph(object):
  68. ... def draw(self, pen):
  69. ... pen.addComponent('a', (1, 0, 0, 1, -1, 1))
  70. >>> glyphSet = {'a': SimpleGlyph(), 'b': CompositeGlyph()}
  71. >>> for name, glyph in sorted(glyphSet.items()):
  72. ... pen = DecomposingRecordingPen(glyphSet)
  73. ... glyph.draw(pen)
  74. ... print("{}: {}".format(name, pen.value))
  75. a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())]
  76. b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())]
  77. """
  78. # raises KeyError if base glyph is not found in glyphSet
  79. skipMissingComponents = False
  80. class RecordingPointPen(AbstractPointPen):
  81. """PointPen recording operations that can be accessed or replayed.
  82. The recording can be accessed as pen.value; or replayed using
  83. pointPen.replay(otherPointPen).
  84. :Example:
  85. from defcon import Font
  86. from fontTools.pens.recordingPen import RecordingPointPen
  87. glyph_name = 'a'
  88. font_path = 'MyFont.ufo'
  89. font = Font(font_path)
  90. glyph = font[glyph_name]
  91. pen = RecordingPointPen()
  92. glyph.drawPoints(pen)
  93. print(pen.value)
  94. new_glyph = font.newGlyph('b')
  95. pen.replay(new_glyph.getPointPen())
  96. """
  97. def __init__(self):
  98. self.value = []
  99. def beginPath(self, identifier=None, **kwargs):
  100. if identifier is not None:
  101. kwargs["identifier"] = identifier
  102. self.value.append(("beginPath", (), kwargs))
  103. def endPath(self):
  104. self.value.append(("endPath", (), {}))
  105. def addPoint(
  106. self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs
  107. ):
  108. if identifier is not None:
  109. kwargs["identifier"] = identifier
  110. self.value.append(("addPoint", (pt, segmentType, smooth, name), kwargs))
  111. def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs):
  112. if identifier is not None:
  113. kwargs["identifier"] = identifier
  114. self.value.append(("addComponent", (baseGlyphName, transformation), kwargs))
  115. def addVarComponent(
  116. self, baseGlyphName, transformation, location, identifier=None, **kwargs
  117. ):
  118. if identifier is not None:
  119. kwargs["identifier"] = identifier
  120. self.value.append(
  121. ("addVarComponent", (baseGlyphName, transformation, location), kwargs)
  122. )
  123. def replay(self, pointPen):
  124. for operator, args, kwargs in self.value:
  125. getattr(pointPen, operator)(*args, **kwargs)
  126. drawPoints = replay
  127. if __name__ == "__main__":
  128. pen = RecordingPen()
  129. pen.moveTo((0, 0))
  130. pen.lineTo((0, 100))
  131. pen.curveTo((50, 75), (60, 50), (50, 25))
  132. pen.closePath()
  133. from pprint import pprint
  134. pprint(pen.value)