summaryrefslogtreecommitdiff
path: root/misc/pylib/robofab/pens/flPen.py
diff options
context:
space:
mode:
Diffstat (limited to 'misc/pylib/robofab/pens/flPen.py')
-rwxr-xr-xmisc/pylib/robofab/pens/flPen.py274
1 files changed, 274 insertions, 0 deletions
diff --git a/misc/pylib/robofab/pens/flPen.py b/misc/pylib/robofab/pens/flPen.py
new file mode 100755
index 000000000..d5a867cd9
--- /dev/null
+++ b/misc/pylib/robofab/pens/flPen.py
@@ -0,0 +1,274 @@
+"""Pens for creating glyphs in FontLab."""
+
+
+__all__ = ["FLPen", "FLPointPen", "drawFLGlyphOntoPointPen"]
+
+
+from FL import *
+
+try:
+ from fl_cmd import *
+except ImportError:
+ print "The fl_cmd module is not available here. flPen.py"
+
+from robofab.tools.toolsFL import NewGlyph
+from robofab.pens.pointPen import AbstractPointPen
+from robofab.pens.adapterPens import SegmentToPointPen
+
+
+def roundInt(x):
+ return int(round(x))
+
+
+class FLPen(SegmentToPointPen):
+
+ def __init__(self, glyph):
+ SegmentToPointPen.__init__(self, FLPointPen(glyph))
+
+
+class FLPointPen(AbstractPointPen):
+
+ def __init__(self, glyph):
+ if hasattr(glyph, "isRobofab"):
+ self.glyph = glyph.naked()
+ else:
+ self.glyph = glyph
+ self.currentPath = None
+
+ def beginPath(self):
+ self.currentPath = []
+
+ def endPath(self):
+ # Love is... abstracting away FL's madness.
+ path = self.currentPath
+ self.currentPath = None
+ glyph = self.glyph
+ if len(path) == 1 and path[0][3] is not None:
+ # Single point on the contour, it has a name. Make it an anchor.
+ x, y = path[0][0]
+ name = path[0][3]
+ anchor = Anchor(name, roundInt(x), roundInt(y))
+ glyph.anchors.append(anchor)
+ return
+ firstOnCurveIndex = None
+ for i in range(len(path)):
+ if path[i][1] is not None:
+ firstOnCurveIndex = i
+ break
+ if firstOnCurveIndex is None:
+ # TT special case: on-curve-less contour. FL doesn't support that,
+ # so we insert an implied point at the end.
+ x1, y1 = path[0][0]
+ x2, y2 = path[-1][0]
+ impliedPoint = 0.5 * (x1 + x2), 0.5 * (y1 + y2)
+ path.append((impliedPoint, "qcurve", True, None))
+ firstOnCurveIndex = 0
+ path = path[firstOnCurveIndex + 1:] + path[:firstOnCurveIndex + 1]
+ firstPoint, segmentType, smooth, name = path[-1]
+ closed = True
+ if segmentType == "move":
+ path = path[:-1]
+ closed = False
+ # XXX The contour is not closed, but I can't figure out how to
+ # create an open contour in FL. Creating one by hand shows type"0x8011"
+ # for a move node in an open contour, but I'm not able to access
+ # that flag.
+ elif segmentType == "line":
+ # The contour is closed and ends in a lineto, which is redundant
+ # as it's implied by closepath.
+ path = path[:-1]
+ x, y = firstPoint
+ node = Node(nMOVE, Point(roundInt(x), roundInt(y)))
+ if smooth and closed:
+ if segmentType == "line" or path[0][1] == "line":
+ node.alignment = nFIXED
+ else:
+ node.alignment = nSMOOTH
+ glyph.Insert(node, len(glyph))
+ segment = []
+ nPoints = len(path)
+ for i in range(nPoints):
+ pt, segmentType, smooth, name = path[i]
+ segment.append(pt)
+ if segmentType is None:
+ continue
+ if segmentType == "curve":
+ if len(segment) < 2:
+ segmentType = "line"
+ elif len(segment) == 2:
+ segmentType = "qcurve"
+ if segmentType == "qcurve":
+ for x, y in segment[:-1]:
+ glyph.Insert(Node(nOFF, Point(roundInt(x), roundInt(y))), len(glyph))
+ x, y = segment[-1]
+ node = Node(nLINE, Point(roundInt(x), roundInt(y)))
+ glyph.Insert(node, len(glyph))
+ elif segmentType == "curve":
+ if len(segment) == 3:
+ cubicSegments = [segment]
+ else:
+ from fontTools.pens.basePen import decomposeSuperBezierSegment
+ cubicSegments = decomposeSuperBezierSegment(segment)
+ nSegments = len(cubicSegments)
+ for i in range(nSegments):
+ pt1, pt2, pt3 = cubicSegments[i]
+ x, y = pt3
+ node = Node(nCURVE, Point(roundInt(x), roundInt(y)))
+ node.points[1].x, node.points[1].y = roundInt(pt1[0]), roundInt(pt1[1])
+ node.points[2].x, node.points[2].y = roundInt(pt2[0]), roundInt(pt2[1])
+ if i != nSegments - 1:
+ node.alignment = nSMOOTH
+ glyph.Insert(node, len(self.glyph))
+ elif segmentType == "line":
+ assert len(segment) == 1, segment
+ x, y = segment[0]
+ node = Node(nLINE, Point(roundInt(x), roundInt(y)))
+ glyph.Insert(node, len(glyph))
+ else:
+ assert 0, "unsupported curve type (%s)" % segmentType
+ if smooth:
+ if i + 1 < nPoints or closed:
+ # Can't use existing node, as you can't change node attributes
+ # AFTER it's been appended to the glyph.
+ node = glyph[-1]
+ if segmentType == "line" or path[(i+1) % nPoints][1] == "line":
+ # tangent
+ node.alignment = nFIXED
+ else:
+ # curve
+ node.alignment = nSMOOTH
+ segment = []
+ if closed:
+ # we may have output a node too much
+ node = glyph[-1]
+ if node.type == nLINE and (node.x, node.y) == (roundInt(firstPoint[0]), roundInt(firstPoint[1])):
+ glyph.DeleteNode(len(glyph) - 1)
+
+ def addPoint(self, pt, segmentType=None, smooth=None, name=None, **kwargs):
+ self.currentPath.append((pt, segmentType, smooth, name))
+
+ def addComponent(self, baseName, transformation):
+ assert self.currentPath is None
+ # make base glyph if needed, Component() needs the index
+ NewGlyph(self.glyph.parent, baseName, updateFont=False)
+ baseIndex = self.glyph.parent.FindGlyph(baseName)
+ if baseIndex == -1:
+ raise KeyError, "couldn't find or make base glyph"
+ xx, xy, yx, yy, dx, dy = transformation
+ # XXX warn when xy or yx != 0
+ new = Component(baseIndex, Point(dx, dy), Point(xx, yy))
+ self.glyph.components.append(new)
+
+
+def drawFLGlyphOntoPointPen(flGlyph, pen):
+ """Draw a FontLab glyph onto a PointPen."""
+ for anchor in flGlyph.anchors:
+ pen.beginPath()
+ pen.addPoint((anchor.x, anchor.y), name=anchor.name)
+ pen.endPath()
+ for contour in _getContours(flGlyph):
+ pen.beginPath()
+ for pt, segmentType, smooth in contour:
+ pen.addPoint(pt, segmentType=segmentType, smooth=smooth)
+ pen.endPath()
+ for baseGlyph, tranform in _getComponents(flGlyph):
+ pen.addComponent(baseGlyph, tranform)
+
+
+
+class FLPointContourPen(FLPointPen):
+ """Same as FLPointPen, except that it ignores components."""
+ def addComponent(self, baseName, transformation):
+ pass
+
+
+NODE_TYPES = {nMOVE: "move", nLINE: "line", nCURVE: "curve",
+ nOFF: None}
+
+def _getContours(glyph):
+ contours = []
+ for i in range(len(glyph)):
+ node = glyph[i]
+ segmentType = NODE_TYPES[node.type]
+ if segmentType == "move":
+ contours.append([])
+ for pt in node.points[1:]:
+ contours[-1].append(((pt.x, pt.y), None, False))
+ smooth = node.alignment != nSHARP
+ contours[-1].append(((node.x, node.y), segmentType, smooth))
+
+ for contour in contours:
+ # filter out or change the move
+ movePt, segmentType, smooth = contour[0]
+ assert segmentType == "move"
+ lastSegmentType = contour[-1][1]
+ if movePt == contour[-1][0] and lastSegmentType == "curve":
+ contour[0] = contour[-1]
+ contour.pop()
+ elif lastSegmentType is None:
+ contour[0] = movePt, "qcurve", smooth
+ else:
+ assert lastSegmentType in ("line", "curve")
+ contour[0] = movePt, "line", smooth
+
+ # change "line" to "qcurve" if appropriate
+ previousSegmentType = "ArbitraryValueOtherThanNone"
+ for i in range(len(contour)):
+ pt, segmentType, smooth = contour[i]
+ if segmentType == "line" and previousSegmentType is None:
+ contour[i] = pt, "qcurve", smooth
+ previousSegmentType = segmentType
+
+ return contours
+
+
+def _simplifyValues(*values):
+ """Given a set of numbers, convert items to ints if they are
+ integer float values, eg. 0.0, 1.0."""
+ newValues = []
+ for v in values:
+ i = int(v)
+ if v == i:
+ v = i
+ newValues.append(v)
+ return newValues
+
+
+def _getComponents(glyph):
+ components = []
+ for comp in glyph.components:
+ baseName = glyph.parent[comp.index].name
+ dx, dy = comp.delta.x, comp.delta.y
+ sx, sy = comp.scale.x, comp.scale.y
+ dx, dy, sx, sy = _simplifyValues(dx, dy, sx, sy)
+ components.append((baseName, (sx, 0, 0, sy, dx, dy)))
+ return components
+
+
+def test():
+ g = fl.glyph
+ g.Clear()
+
+ p = PLPen(g)
+ p.moveTo((50, 50))
+ p.lineTo((150,50))
+ p.lineTo((170, 200), smooth=2)
+ p.curveTo((173, 225), (150, 250), (120, 250), smooth=1)
+ p.curveTo((85, 250), (50, 200), (50, 200))
+ p.closePath()
+
+ p.moveTo((300, 300))
+ p.lineTo((400, 300))
+ p.curveTo((450, 325), (450, 375), (400, 400))
+ p.qCurveTo((400, 500), (350, 550), (300, 500), (300, 400))
+ p.closePath()
+ p.setWidth(600)
+ p.setNote("Hello, this is a note")
+ p.addAnchor("top", (250, 600))
+
+ fl.UpdateGlyph(-1)
+ fl.UpdateFont(-1)
+
+
+if __name__ == "__main__":
+ test()