diff options
Diffstat (limited to 'misc/pylib/robofab/pens/flPen.py')
-rwxr-xr-x | misc/pylib/robofab/pens/flPen.py | 274 |
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() |