Cython is used to compile some hot paths into native Python extensions. These hot paths were identified through running ufocompile with the hotshot profiler and then converting file by file to Cython, starting with the "hottest" paths and continuing until returns were deminishing. This means that only a few Python files were converted to Cython. Closes #23 Closes #20 (really this time)
274 lines
7.9 KiB
Python
Executable file
274 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()
|