275 lines
7.9 KiB
Python
Executable File
275 lines
7.9 KiB
Python
Executable File
"""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()
|