641 lines
22 KiB
Python
Executable file
641 lines
22 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
from __future__ import unicode_literals
|
|
'''Expands an unclosed UFO stroke font into monoline forms with a fixed width'''
|
|
__url__ = 'https://github.com/silnrsi/pysilfont'
|
|
__copyright__ = 'Copyright (c) 2017 SIL International (https://www.sil.org), based on outlinerRoboFontExtension Copyright (c) 2016 Frederik Berlaen'
|
|
__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
|
|
__author__ = 'Victor Gaultney'
|
|
|
|
# Usage: psfexpandstroke ifont ofont expansion
|
|
# expansion is the number of units added to each side of the stroke
|
|
|
|
# To Do
|
|
# - Simplify to assume round caps and corners
|
|
|
|
# main input, output, and execution handled by pysilfont framework
|
|
from silfont.core import execute
|
|
|
|
from fontTools.pens.basePen import BasePen
|
|
from fontTools.misc.bezierTools import splitCubicAtT
|
|
from robofab.world import OpenFont
|
|
from robofab.pens.pointPen import AbstractPointPen
|
|
from robofab.pens.reverseContourPointPen import ReverseContourPointPen
|
|
from robofab.pens.adapterPens import PointToSegmentPen
|
|
|
|
from defcon import Glyph
|
|
|
|
from math import sqrt, cos, sin, acos, asin, degrees, radians, pi
|
|
|
|
suffix = '_expanded'
|
|
argspec = [
|
|
('ifont',{'help': 'Input font file'}, {'type': 'filename'}),
|
|
('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'filename', 'def': "_"+suffix}),
|
|
('thickness',{'help': 'Stroke thickness'}, {}),
|
|
('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': suffix+'.log'})]
|
|
|
|
|
|
# The following functions are straight from outlinerRoboFontExtension
|
|
|
|
def roundFloat(f):
|
|
error = 1000000.
|
|
return round(f*error)/error
|
|
|
|
def checkSmooth(firstAngle, lastAngle):
|
|
if firstAngle is None or lastAngle is None:
|
|
return True
|
|
error = 4
|
|
firstAngle = degrees(firstAngle)
|
|
lastAngle = degrees(lastAngle)
|
|
|
|
if int(firstAngle) + error >= int(lastAngle) >= int(firstAngle) - error:
|
|
return True
|
|
return False
|
|
|
|
def checkInnerOuter(firstAngle, lastAngle):
|
|
if firstAngle is None or lastAngle is None:
|
|
return True
|
|
dirAngle = degrees(firstAngle) - degrees(lastAngle)
|
|
|
|
if dirAngle > 180:
|
|
dirAngle = 180 - dirAngle
|
|
elif dirAngle < -180:
|
|
dirAngle = -180 - dirAngle
|
|
|
|
if dirAngle > 0:
|
|
return True
|
|
|
|
if dirAngle <= 0:
|
|
return False
|
|
|
|
|
|
def interSect((seg1s, seg1e), (seg2s, seg2e)):
|
|
denom = (seg2e.y - seg2s.y)*(seg1e.x - seg1s.x) - (seg2e.x - seg2s.x)*(seg1e.y - seg1s.y)
|
|
if roundFloat(denom) == 0:
|
|
# print 'parallel: %s' % denom
|
|
return None
|
|
uanum = (seg2e.x - seg2s.x)*(seg1s.y - seg2s.y) - (seg2e.y - seg2s.y)*(seg1s.x - seg2s.x)
|
|
ubnum = (seg1e.x - seg1s.x)*(seg1s.y - seg2s.y) - (seg1e.y - seg1s.y)*(seg1s.x - seg2s.x)
|
|
ua = uanum / denom
|
|
# ub = ubnum / denom
|
|
x = seg1s.x + ua*(seg1e.x - seg1s.x)
|
|
y = seg1s.y + ua*(seg1e.y - seg1s.y)
|
|
return MathPoint(x, y)
|
|
|
|
|
|
def pointOnACurve((x1, y1), (cx1, cy1), (cx2, cy2), (x2, y2), value):
|
|
dx = x1
|
|
cx = (cx1 - dx) * 3.0
|
|
bx = (cx2 - cx1) * 3.0 - cx
|
|
ax = x2 - dx - cx - bx
|
|
dy = y1
|
|
cy = (cy1 - dy) * 3.0
|
|
by = (cy2 - cy1) * 3.0 - cy
|
|
ay = y2 - dy - cy - by
|
|
mx = ax*(value)**3 + bx*(value)**2 + cx*(value) + dx
|
|
my = ay*(value)**3 + by*(value)**2 + cy*(value) + dy
|
|
return MathPoint(mx, my)
|
|
|
|
|
|
class MathPoint(object):
|
|
|
|
def __init__(self, x, y=None):
|
|
if y is None:
|
|
x, y = x
|
|
self.x = x
|
|
self.y = y
|
|
|
|
def __repr__(self):
|
|
return "<MathPoint x:%s y:%s>" % (self.x, self.y)
|
|
|
|
def __getitem__(self, index):
|
|
if index == 0:
|
|
return self.x
|
|
if index == 1:
|
|
return self.y
|
|
raise IndexError
|
|
|
|
def __iter__(self):
|
|
for value in [self.x, self.y]:
|
|
yield value
|
|
|
|
def __add__(self, p): # p+ p
|
|
if not isinstance(p, self.__class__):
|
|
return self.__class__(self.x + p, self.y + p)
|
|
return self.__class__(self.x + p.x, self.y + p.y)
|
|
|
|
def __sub__(self, p): # p - p
|
|
if not isinstance(p, self.__class__):
|
|
return self.__class__(self.x - p, self.y - p)
|
|
return self.__class__(self.x - p.x, self.y - p.y)
|
|
|
|
def __mul__(self, p): # p * p
|
|
if not isinstance(p, self.__class__):
|
|
return self.__class__(self.x * p, self.y * p)
|
|
return self.__class__(self.x * p.x, self.y * p.y)
|
|
|
|
def __div__(self, p):
|
|
if not isinstance(p, self.__class__):
|
|
return self.__class__(self.x / p, self.y / p)
|
|
return self.__class__(self.x / p.x, self.y / p.y)
|
|
|
|
def __eq__(self, p): # if p == p
|
|
if not isinstance(p, self.__class__):
|
|
return False
|
|
return roundFloat(self.x) == roundFloat(p.x) and roundFloat(self.y) == roundFloat(p.y)
|
|
|
|
def __ne__(self, p): # if p != p
|
|
return not self.__eq__(p)
|
|
|
|
def copy(self):
|
|
return self.__class__(self.x, self.y)
|
|
|
|
def round(self):
|
|
self.x = round(self.x)
|
|
self.y = round(self.y)
|
|
|
|
def distance(self, p):
|
|
return sqrt((p.x - self.x)**2 + (p.y - self.y)**2)
|
|
|
|
def angle(self, other, add=90):
|
|
# returns the angle of a Line in radians
|
|
b = other.x - self.x
|
|
a = other.y - self.y
|
|
c = sqrt(a**2 + b**2)
|
|
if c == 0:
|
|
return None
|
|
if add is None:
|
|
return b/c
|
|
cosAngle = degrees(acos(b/c))
|
|
sinAngle = degrees(asin(a/c))
|
|
if sinAngle < 0:
|
|
cosAngle = 360 - cosAngle
|
|
return radians(cosAngle + add)
|
|
|
|
|
|
class CleanPointPen(AbstractPointPen):
|
|
|
|
def __init__(self, pointPen):
|
|
self.pointPen = pointPen
|
|
self.currentContour = None
|
|
|
|
def processContour(self):
|
|
pointPen = self.pointPen
|
|
contour = self.currentContour
|
|
|
|
index = 0
|
|
prevAngle = None
|
|
toRemove = []
|
|
for data in contour:
|
|
if data["segmentType"] in ["line", "move"]:
|
|
prevPoint = contour[index-1]
|
|
if prevPoint["segmentType"] in ["line", "move"]:
|
|
angle = MathPoint(data["point"]).angle(MathPoint(prevPoint["point"]))
|
|
if prevAngle is not None and angle is not None and roundFloat(prevAngle) == roundFloat(angle):
|
|
prevPoint["uniqueID"] = id(prevPoint)
|
|
toRemove.append(prevPoint)
|
|
prevAngle = angle
|
|
else:
|
|
prevAngle = None
|
|
else:
|
|
prevAngle = None
|
|
index += 1
|
|
|
|
for data in toRemove:
|
|
contour.remove(data)
|
|
|
|
pointPen.beginPath()
|
|
for data in contour:
|
|
pointPen.addPoint(data["point"], **data)
|
|
pointPen.endPath()
|
|
|
|
def beginPath(self):
|
|
assert self.currentContour is None
|
|
self.currentContour = []
|
|
self.onCurve = []
|
|
|
|
def endPath(self):
|
|
assert self.currentContour is not None
|
|
self.processContour()
|
|
self.currentContour = None
|
|
|
|
def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
|
|
data = dict(point=pt, segmentType=segmentType, smooth=smooth, name=name)
|
|
data.update(kwargs)
|
|
self.currentContour.append(data)
|
|
|
|
def addComponent(self, glyphName, transform):
|
|
assert self.currentContour is None
|
|
self.pointPen.addComponent(glyphName, transform)
|
|
|
|
# The following class has been been adjusted to work around how outline types use closePath() and endPath(),
|
|
# to remove unneeded bits, and hard-code some assumptions.
|
|
|
|
class OutlinePen(BasePen):
|
|
|
|
pointClass = MathPoint
|
|
magicCurve = 0.5522847498
|
|
|
|
def __init__(self, glyphSet, offset=10, contrast=0, contrastAngle=0, connection="round", cap="round", miterLimit=None, optimizeCurve=True):
|
|
BasePen.__init__(self, glyphSet)
|
|
|
|
self.offset = abs(offset)
|
|
self.contrast = abs(contrast)
|
|
self.contrastAngle = contrastAngle
|
|
self._inputmiterLimit = miterLimit
|
|
if miterLimit is None:
|
|
miterLimit = self.offset * 2
|
|
self.miterLimit = abs(miterLimit)
|
|
|
|
self.optimizeCurve = optimizeCurve
|
|
|
|
self.connectionCallback = getattr(self, "connection%s" % (connection.title()))
|
|
self.capCallback = getattr(self, "cap%s" % (cap.title()))
|
|
|
|
self.originalGlyph = Glyph()
|
|
self.originalPen = self.originalGlyph.getPen()
|
|
|
|
self.outerGlyph = Glyph()
|
|
self.outerPen = self.outerGlyph.getPen()
|
|
self.outerCurrentPoint = None
|
|
self.outerFirstPoint = None
|
|
self.outerPrevPoint = None
|
|
|
|
self.innerGlyph = Glyph()
|
|
self.innerPen = self.innerGlyph.getPen()
|
|
self.innerCurrentPoint = None
|
|
self.innerFirstPoint = None
|
|
self.innerPrevPoint = None
|
|
|
|
self.prevPoint = None
|
|
self.firstPoint = None
|
|
self.firstAngle = None
|
|
self.prevAngle = None
|
|
|
|
self.shouldHandleMove = True
|
|
|
|
self.components = []
|
|
|
|
self.drawSettings()
|
|
|
|
def _moveTo(self, (x, y)):
|
|
if self.offset == 0:
|
|
self.outerPen.moveTo((x, y))
|
|
self.innerPen.moveTo((x, y))
|
|
return
|
|
self.originalPen.moveTo((x, y))
|
|
|
|
p = self.pointClass(x, y)
|
|
self.prevPoint = p
|
|
self.firstPoint = p
|
|
self.shouldHandleMove = True
|
|
|
|
def _lineTo(self, (x, y)):
|
|
if self.offset == 0:
|
|
self.outerPen.lineTo((x, y))
|
|
self.innerPen.lineTo((x, y))
|
|
return
|
|
self.originalPen.lineTo((x, y))
|
|
|
|
currentPoint = self.pointClass(x, y)
|
|
if currentPoint == self.prevPoint:
|
|
return
|
|
|
|
self.currentAngle = self.prevPoint.angle(currentPoint)
|
|
thickness = self.getThickness(self.currentAngle)
|
|
self.innerCurrentPoint = self.prevPoint - self.pointClass(cos(self.currentAngle), sin(self.currentAngle)) * thickness
|
|
self.outerCurrentPoint = self.prevPoint + self.pointClass(cos(self.currentAngle), sin(self.currentAngle)) * thickness
|
|
|
|
if self.shouldHandleMove:
|
|
self.shouldHandleMove = False
|
|
|
|
self.innerPen.moveTo(self.innerCurrentPoint)
|
|
self.innerFirstPoint = self.innerCurrentPoint
|
|
|
|
self.outerPen.moveTo(self.outerCurrentPoint)
|
|
self.outerFirstPoint = self.outerCurrentPoint
|
|
|
|
self.firstAngle = self.currentAngle
|
|
else:
|
|
self.buildConnection()
|
|
|
|
self.innerCurrentPoint = currentPoint - self.pointClass(cos(self.currentAngle), sin(self.currentAngle)) * thickness
|
|
self.innerPen.lineTo(self.innerCurrentPoint)
|
|
self.innerPrevPoint = self.innerCurrentPoint
|
|
|
|
self.outerCurrentPoint = currentPoint + self.pointClass(cos(self.currentAngle), sin(self.currentAngle)) * thickness
|
|
self.outerPen.lineTo(self.outerCurrentPoint)
|
|
self.outerPrevPoint = self.outerCurrentPoint
|
|
|
|
self.prevPoint = currentPoint
|
|
self.prevAngle = self.currentAngle
|
|
|
|
def _curveToOne(self, (x1, y1), (x2, y2), (x3, y3)):
|
|
if self.optimizeCurve:
|
|
curves = splitCubicAtT(self.prevPoint, (x1, y1), (x2, y2), (x3, y3), .5)
|
|
else:
|
|
curves = [(self.prevPoint, (x1, y1), (x2, y2), (x3, y3))]
|
|
for curve in curves:
|
|
p1, h1, h2, p2 = curve
|
|
self._processCurveToOne(h1, h2, p2)
|
|
|
|
def _processCurveToOne(self, (x1, y1), (x2, y2), (x3, y3)):
|
|
if self.offset == 0:
|
|
self.outerPen.curveTo((x1, y1), (x2, y2), (x3, y3))
|
|
self.innerPen.curveTo((x1, y1), (x2, y2), (x3, y3))
|
|
return
|
|
self.originalPen.curveTo((x1, y1), (x2, y2), (x3, y3))
|
|
|
|
p1 = self.pointClass(x1, y1)
|
|
p2 = self.pointClass(x2, y2)
|
|
p3 = self.pointClass(x3, y3)
|
|
|
|
if p1 == self.prevPoint:
|
|
p1 = pointOnACurve(self.prevPoint, p1, p2, p3, 0.01)
|
|
if p2 == p3:
|
|
p2 = pointOnACurve(self.prevPoint, p1, p2, p3, 0.99)
|
|
|
|
a1 = self.prevPoint.angle(p1)
|
|
a2 = p2.angle(p3)
|
|
|
|
self.currentAngle = a1
|
|
tickness1 = self.getThickness(a1)
|
|
tickness2 = self.getThickness(a2)
|
|
|
|
a1bis = self.prevPoint.angle(p1, 0)
|
|
a2bis = p3.angle(p2, 0)
|
|
intersectPoint = interSect((self.prevPoint, self.prevPoint + self.pointClass(cos(a1), sin(a1)) * 100),
|
|
(p3, p3 + self.pointClass(cos(a2), sin(a2)) * 100))
|
|
self.innerCurrentPoint = self.prevPoint - self.pointClass(cos(a1), sin(a1)) * tickness1
|
|
self.outerCurrentPoint = self.prevPoint + self.pointClass(cos(a1), sin(a1)) * tickness1
|
|
|
|
if self.shouldHandleMove:
|
|
self.shouldHandleMove = False
|
|
|
|
self.innerPen.moveTo(self.innerCurrentPoint)
|
|
self.innerFirstPoint = self.innerPrevPoint = self.innerCurrentPoint
|
|
|
|
self.outerPen.moveTo(self.outerCurrentPoint)
|
|
self.outerFirstPoint = self.outerPrevPoint = self.outerCurrentPoint
|
|
|
|
self.firstAngle = a1
|
|
else:
|
|
self.buildConnection()
|
|
|
|
h1 = None
|
|
if intersectPoint is not None:
|
|
h1 = interSect((self.innerCurrentPoint, self.innerCurrentPoint + self.pointClass(cos(a1bis), sin(a1bis)) * tickness1), (intersectPoint, p1))
|
|
if h1 is None:
|
|
h1 = p1 - self.pointClass(cos(a1), sin(a1)) * tickness1
|
|
|
|
self.innerCurrentPoint = p3 - self.pointClass(cos(a2), sin(a2)) * tickness2
|
|
|
|
h2 = None
|
|
if intersectPoint is not None:
|
|
h2 = interSect((self.innerCurrentPoint, self.innerCurrentPoint + self.pointClass(cos(a2bis), sin(a2bis)) * tickness2), (intersectPoint, p2))
|
|
if h2 is None:
|
|
h2 = p2 - self.pointClass(cos(a1), sin(a1)) * tickness1
|
|
|
|
self.innerPen.curveTo(h1, h2, self.innerCurrentPoint)
|
|
self.innerPrevPoint = self.innerCurrentPoint
|
|
|
|
########
|
|
h1 = None
|
|
if intersectPoint is not None:
|
|
h1 = interSect((self.outerCurrentPoint, self.outerCurrentPoint + self.pointClass(cos(a1bis), sin(a1bis)) * tickness1), (intersectPoint, p1))
|
|
if h1 is None:
|
|
h1 = p1 + self.pointClass(cos(a1), sin(a1)) * tickness1
|
|
|
|
self.outerCurrentPoint = p3 + self.pointClass(cos(a2), sin(a2)) * tickness2
|
|
|
|
h2 = None
|
|
if intersectPoint is not None:
|
|
h2 = interSect((self.outerCurrentPoint, self.outerCurrentPoint + self.pointClass(cos(a2bis), sin(a2bis)) * tickness2), (intersectPoint, p2))
|
|
if h2 is None:
|
|
h2 = p2 + self.pointClass(cos(a1), sin(a1)) * tickness1
|
|
self.outerPen.curveTo(h1, h2, self.outerCurrentPoint)
|
|
self.outerPrevPoint = self.outerCurrentPoint
|
|
|
|
self.prevPoint = p3
|
|
self.currentAngle = a2
|
|
self.prevAngle = a2
|
|
|
|
def _closePath(self):
|
|
if self.shouldHandleMove:
|
|
return
|
|
|
|
self.originalPen.endPath()
|
|
self.innerPen.endPath()
|
|
self.outerPen.endPath()
|
|
|
|
innerContour = self.innerGlyph[-1]
|
|
outerContour = self.outerGlyph[-1]
|
|
|
|
innerContour.reverse()
|
|
|
|
innerContour[0].segmentType = "line"
|
|
outerContour[0].segmentType = "line"
|
|
|
|
self.buildCap(outerContour, innerContour)
|
|
|
|
for point in innerContour:
|
|
outerContour.addPoint((point.x, point.y), segmentType=point.segmentType, smooth=point.smooth)
|
|
|
|
self.innerGlyph.removeContour(innerContour)
|
|
|
|
def _endPath(self):
|
|
# The current way glyph outlines are processed means that _endPath() would not be called
|
|
# _closePath() is used instead
|
|
pass
|
|
|
|
def addComponent(self, glyphName, transform):
|
|
self.components.append((glyphName, transform))
|
|
|
|
# thickness
|
|
|
|
def getThickness(self, angle):
|
|
a2 = angle + pi * .5
|
|
f = abs(sin(a2 + radians(self.contrastAngle)))
|
|
f = f ** 5
|
|
return self.offset + self.contrast * f
|
|
|
|
# connections
|
|
|
|
def buildConnection(self, close=False):
|
|
if not checkSmooth(self.prevAngle, self.currentAngle):
|
|
if checkInnerOuter(self.prevAngle, self.currentAngle):
|
|
self.connectionCallback(self.outerPrevPoint, self.outerCurrentPoint, self.outerPen, close)
|
|
self.connectionInnerCorner(self.innerPrevPoint, self.innerCurrentPoint, self.innerPen, close)
|
|
else:
|
|
self.connectionCallback(self.innerPrevPoint, self.innerCurrentPoint, self.innerPen, close)
|
|
self.connectionInnerCorner(self.outerPrevPoint, self.outerCurrentPoint, self.outerPen, close)
|
|
|
|
def connectionRound(self, first, last, pen, close):
|
|
angle_1 = radians(degrees(self.prevAngle)+90)
|
|
angle_2 = radians(degrees(self.currentAngle)+90)
|
|
|
|
tempFirst = first - self.pointClass(cos(angle_1), sin(angle_1)) * self.miterLimit
|
|
tempLast = last + self.pointClass(cos(angle_2), sin(angle_2)) * self.miterLimit
|
|
|
|
newPoint = interSect((first, tempFirst), (last, tempLast))
|
|
if newPoint is None:
|
|
pen.lineTo(last)
|
|
return
|
|
distance1 = newPoint.distance(first)
|
|
distance2 = newPoint.distance(last)
|
|
if roundFloat(distance1) > self.miterLimit + self.contrast:
|
|
distance1 = self.miterLimit + tempFirst.distance(tempLast) * .7
|
|
if roundFloat(distance2) > self.miterLimit + self.contrast:
|
|
distance2 = self.miterLimit + tempFirst.distance(tempLast) * .7
|
|
|
|
distance1 *= self.magicCurve
|
|
distance2 *= self.magicCurve
|
|
|
|
bcp1 = first - self.pointClass(cos(angle_1), sin(angle_1)) * distance1
|
|
bcp2 = last + self.pointClass(cos(angle_2), sin(angle_2)) * distance2
|
|
pen.curveTo(bcp1, bcp2, last)
|
|
|
|
def connectionInnerCorner(self, first, last, pen, close):
|
|
if not close:
|
|
pen.lineTo(last)
|
|
|
|
# caps
|
|
|
|
def buildCap(self, firstContour, lastContour):
|
|
first = firstContour[-1]
|
|
last = lastContour[0]
|
|
first = self.pointClass(first.x, first.y)
|
|
last = self.pointClass(last.x, last.y)
|
|
|
|
self.capCallback(firstContour, lastContour, first, last, self.prevAngle)
|
|
|
|
first = lastContour[-1]
|
|
last = firstContour[0]
|
|
first = self.pointClass(first.x, first.y)
|
|
last = self.pointClass(last.x, last.y)
|
|
|
|
angle = radians(degrees(self.firstAngle)+180)
|
|
self.capCallback(lastContour, firstContour, first, last, angle)
|
|
|
|
def capRound(self, firstContour, lastContour, first, last, angle):
|
|
hookedAngle = radians(degrees(angle)+90)
|
|
|
|
p1 = first - self.pointClass(cos(hookedAngle), sin(hookedAngle)) * self.offset
|
|
|
|
p2 = last - self.pointClass(cos(hookedAngle), sin(hookedAngle)) * self.offset
|
|
|
|
oncurve = p1 + (p2-p1)*.5
|
|
|
|
roundness = .54
|
|
|
|
h1 = first - self.pointClass(cos(hookedAngle), sin(hookedAngle)) * self.offset * roundness
|
|
h2 = oncurve + self.pointClass(cos(angle), sin(angle)) * self.offset * roundness
|
|
|
|
firstContour[-1].smooth = True
|
|
|
|
firstContour.addPoint((h1.x, h1.y))
|
|
firstContour.addPoint((h2.x, h2.y))
|
|
firstContour.addPoint((oncurve.x, oncurve.y), smooth=True, segmentType="curve")
|
|
|
|
h1 = oncurve - self.pointClass(cos(angle), sin(angle)) * self.offset * roundness
|
|
h2 = last - self.pointClass(cos(hookedAngle), sin(hookedAngle)) * self.offset * roundness
|
|
|
|
firstContour.addPoint((h1.x, h1.y))
|
|
firstContour.addPoint((h2.x, h2.y))
|
|
|
|
lastContour[0].segmentType = "curve"
|
|
lastContour[0].smooth = True
|
|
|
|
def drawSettings(self, drawOriginal=False, drawInner=False, drawOuter=True):
|
|
self.drawOriginal = drawOriginal
|
|
self.drawInner = drawInner
|
|
self.drawOuter = drawOuter
|
|
|
|
def drawPoints(self, pointPen):
|
|
if self.drawInner:
|
|
reversePen = ReverseContourPointPen(pointPen)
|
|
self.innerGlyph.drawPoints(CleanPointPen(reversePen))
|
|
if self.drawOuter:
|
|
self.outerGlyph.drawPoints(CleanPointPen(pointPen))
|
|
|
|
if self.drawOriginal:
|
|
if self.drawOuter:
|
|
pointPen = ReverseContourPointPen(pointPen)
|
|
self.originalGlyph.drawPoints(CleanPointPen(pointPen))
|
|
|
|
for glyphName, transform in self.components:
|
|
pointPen.addComponent(glyphName, transform)
|
|
|
|
def draw(self, pen):
|
|
pointPen = PointToSegmentPen(pen)
|
|
self.drawPoints(pointPen)
|
|
|
|
def getGlyph(self):
|
|
glyph = Glyph()
|
|
pointPen = glyph.getPointPen()
|
|
self.drawPoints(pointPen)
|
|
return glyph
|
|
|
|
# The following functions have been decoupled from the outlinerRoboFontExtension and
|
|
# effectively de-parameterized, with built-in assumptions
|
|
|
|
def calculate(glyph, strokewidth):
|
|
tickness = strokewidth
|
|
contrast = 0
|
|
contrastAngle = 0
|
|
keepBounds = False
|
|
optimizeCurve = True
|
|
miterLimit = None #assumed
|
|
|
|
corner = "round" #assumed - other options not supported
|
|
cap = "round" #assumed - other options not supported
|
|
|
|
drawOriginal = False
|
|
drawInner = True
|
|
drawOuter = True
|
|
|
|
pen = OutlinePen(glyph.getParent(),
|
|
tickness,
|
|
contrast,
|
|
contrastAngle,
|
|
connection=corner,
|
|
cap=cap,
|
|
miterLimit=miterLimit,
|
|
optimizeCurve=optimizeCurve)
|
|
|
|
glyph.draw(pen)
|
|
|
|
pen.drawSettings(drawOriginal=drawOriginal,
|
|
drawInner=drawInner,
|
|
drawOuter=drawOuter)
|
|
|
|
result = pen.getGlyph()
|
|
|
|
return result
|
|
|
|
|
|
def expandGlyph(glyph, strokewidth):
|
|
defconGlyph = glyph
|
|
outline = calculate(defconGlyph, strokewidth)
|
|
|
|
glyph.clearContours()
|
|
outline.drawPoints(glyph.getPointPen())
|
|
|
|
glyph.round()
|
|
|
|
def expandFont(targetfont, strokewidth):
|
|
font = targetfont
|
|
for glyph in font:
|
|
expandGlyph(glyph, strokewidth)
|
|
|
|
def doit(args):
|
|
infont = OpenFont(args.ifont)
|
|
outfont = args.ofont
|
|
# add try to catch bad input
|
|
strokewidth = int(args.thickness)
|
|
expandFont(infont, strokewidth)
|
|
infont.save(outfont)
|
|
|
|
return infont
|
|
|
|
def cmd() : execute(None,doit,argspec)
|
|
if __name__ == "__main__": cmd()
|