diff options
author | Rasmus Andersson <rasmus@notion.se> | 2018-09-03 22:55:49 +0300 |
---|---|---|
committer | Rasmus Andersson <rasmus@notion.se> | 2018-09-03 22:55:49 +0300 |
commit | c833e252c925e8dd68108660710ca835d95daa6f (patch) | |
tree | 6b2e28264ed45efd7f054e453b622098d0d875b8 /misc/pylib/robofab/pens/filterPen.py | |
parent | 8c1a4c181ef12000179dfec541f1af87e9b03122 (diff) | |
download | inter-c833e252c925e8dd68108660710ca835d95daa6f.tar.xz |
Major overhaul, moving from UFO2 to Glyphs and UFO3, plus a brand new and much simpler fontbuild
Diffstat (limited to 'misc/pylib/robofab/pens/filterPen.py')
-rwxr-xr-x | misc/pylib/robofab/pens/filterPen.py | 407 |
1 files changed, 0 insertions, 407 deletions
diff --git a/misc/pylib/robofab/pens/filterPen.py b/misc/pylib/robofab/pens/filterPen.py deleted file mode 100755 index f7c84c082..000000000 --- a/misc/pylib/robofab/pens/filterPen.py +++ /dev/null @@ -1,407 +0,0 @@ -"""A couple of point pens to filter contours in various ways.""" - -from fontTools.pens.basePen import AbstractPen, BasePen - -from robofab.pens.pointPen import AbstractPointPen -from robofab.objects.objectsRF import RGlyph as _RGlyph -from robofab.objects.objectsBase import _interpolatePt - -import math - -# -# threshold filtering -# - -def distance(pt1, pt2): - return math.hypot(pt1[0]-pt2[0], pt1[1]-pt2[1]) - -class ThresholdPointPen(AbstractPointPen): - - """ - Rewrite of the ThresholdPen as a PointPen - so that we can preserve named points and other arguments. - This pen will add components from the original glyph, but - but it won't filter those components. - - "move", "line", "curve" or "qcurve" - - """ - def __init__(self, otherPointPen, threshold=10): - self.threshold = threshold - self._lastPt = None - self._offCurveBuffer = [] - self.otherPointPen = otherPointPen - - def beginPath(self): - """Start a new sub path.""" - self.otherPointPen.beginPath() - self._lastPt = None - - def endPath(self): - """End the current sub path.""" - self.otherPointPen.endPath() - - def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): - """Add a point to the current sub path.""" - if segmentType in ['curve', 'qcurve']: - # it's an offcurve, let's buffer them until we get another oncurve - # and we know what to do with them - self._offCurveBuffer.append((pt, segmentType, smooth, name, kwargs)) - return - - elif segmentType == "move": - # start of an open contour - self.otherPointPen.addPoint(pt, segmentType, smooth, name) # how to add kwargs? - self._lastPt = pt - self._offCurveBuffer = [] - - elif segmentType == "line": - if self._lastPt is None: - self.otherPointPen.addPoint(pt, segmentType, smooth, name) # how to add kwargs? - self._lastPt = pt - elif distance(pt, self._lastPt) >= self.threshold: - # we're oncurve and far enough from the last oncurve - if self._offCurveBuffer: - # empty any buffered offcurves - for buf_pt, buf_segmentType, buf_smooth, buf_name, buf_kwargs in self._offCurveBuffer: - self.otherPointPen.addPoint(buf_pt, buf_segmentType, buf_smooth, buf_name) # how to add kwargs? - self._offCurveBuffer = [] - # finally add the oncurve. - self.otherPointPen.addPoint(pt, segmentType, smooth, name) # how to add kwargs? - self._lastPt = pt - else: - # we're too short, so we're not going to make it. - # we need to clear out the offcurve buffer. - self._offCurveBuffer = [] - - def addComponent(self, baseGlyphName, transformation): - """Add a sub glyph. Note: this way components are not filtered.""" - self.otherPointPen.addComponent(baseGlyphName, transformation) - - -class ThresholdPen(AbstractPen): - - """Removes segments shorter in length than the threshold value.""" - - def __init__(self, otherPen, threshold=10): - self.threshold = threshold - self._lastPt = None - self.otherPen = otherPen - - def moveTo(self, pt): - self._lastPt = pt - self.otherPen.moveTo(pt) - - def lineTo(self, pt, smooth=False): - if self.threshold <= distance(pt, self._lastPt): - self.otherPen.lineTo(pt) - self._lastPt = pt - - def curveTo(self, pt1, pt2, pt3): - if self.threshold <= distance(pt3, self._lastPt): - self.otherPen.curveTo(pt1, pt2, pt3) - self._lastPt = pt3 - - def qCurveTo(self, *points): - if self.threshold <= distance(points[-1], self._lastPt): - self.otherPen.qCurveTo(*points) - self._lastPt = points[-1] - - def closePath(self): - self.otherPen.closePath() - - def endPath(self): - self.otherPen.endPath() - - def addComponent(self, glyphName, transformation): - self.otherPen.addComponent(glyphName, transformation) - - -def thresholdGlyph(aGlyph, threshold=10): - """ Convenience function that handles the filtering. """ - from robofab.pens.adapterPens import PointToSegmentPen - new = _RGlyph() - filterpen = ThresholdPen(new.getPen(), threshold) - wrappedPen = PointToSegmentPen(filterpen) - aGlyph.drawPoints(wrappedPen) - aGlyph.clear() - aGlyph.appendGlyph(new) - aGlyph.update() - return aGlyph - -def thresholdGlyphPointPen(aGlyph, threshold=10): - """ Same a thresholdGlyph, but using the ThresholdPointPen, which should respect anchors.""" - from robofab.pens.adapterPens import PointToSegmentPen - new = _RGlyph() - wrappedPen = new.getPointPen() - filterpen = ThresholdPointPen(wrappedPen, threshold) - aGlyph.drawPoints(filterpen) - aGlyph.clear() - new.drawPoints(aGlyph.getPointPen()) - aGlyph.update() - return aGlyph - - -# -# Curve flattening -# - -def _estimateCubicCurveLength(pt0, pt1, pt2, pt3, precision=10): - """Estimate the length of this curve by iterating - through it and averaging the length of the flat bits. - """ - points = [] - length = 0 - step = 1.0/precision - factors = range(0, precision+1) - for i in factors: - points.append(_getCubicPoint(i*step, pt0, pt1, pt2, pt3)) - for i in range(len(points)-1): - pta = points[i] - ptb = points[i+1] - length += distance(pta, ptb) - return length - -def _mid((x0, y0), (x1, y1)): - """(Point, Point) -> Point\nReturn the point that lies in between the two input points.""" - return 0.5 * (x0 + x1), 0.5 * (y0 + y1) - -def _getCubicPoint(t, pt0, pt1, pt2, pt3): - if t == 0: - return pt0 - if t == 1: - return pt3 - if t == 0.5: - a = _mid(pt0, pt1) - b = _mid(pt1, pt2) - c = _mid(pt2, pt3) - d = _mid(a, b) - e = _mid(b, c) - return _mid(d, e) - else: - cx = (pt1[0] - pt0[0]) * 3 - cy = (pt1[1] - pt0[1]) * 3 - bx = (pt2[0] - pt1[0]) * 3 - cx - by = (pt2[1] - pt1[1]) * 3 - cy - ax = pt3[0] - pt0[0] - cx - bx - ay = pt3[1] - pt0[1] - cy - by - t3 = t ** 3 - t2 = t * t - x = ax * t3 + bx * t2 + cx * t + pt0[0] - y = ay * t3 + by * t2 + cy * t + pt0[1] - return x, y - - -class FlattenPen(BasePen): - - """Process the contours into a series of straight lines by flattening the curves. - """ - - def __init__(self, otherPen, approximateSegmentLength=5, segmentLines=False, filterDoubles=True): - self.approximateSegmentLength = approximateSegmentLength - BasePen.__init__(self, {}) - self.otherPen = otherPen - self.currentPt = None - self.firstPt = None - self.segmentLines = segmentLines - self.filterDoubles = filterDoubles - - def _moveTo(self, pt): - self.otherPen.moveTo(pt) - self.currentPt = pt - self.firstPt = pt - - def _lineTo(self, pt): - if self.filterDoubles: - if pt == self.currentPt: - return - if not self.segmentLines: - self.otherPen.lineTo(pt) - self.currentPt = pt - return - d = distance(self.currentPt, pt) - maxSteps = int(round(d / self.approximateSegmentLength)) - if maxSteps < 1: - self.otherPen.lineTo(pt) - self.currentPt = pt - return - step = 1.0/maxSteps - factors = range(0, maxSteps+1) - for i in factors[1:]: - self.otherPen.lineTo(_interpolatePt(self.currentPt, pt, i*step)) - self.currentPt = pt - - def _curveToOne(self, pt1, pt2, pt3): - est = _estimateCubicCurveLength(self.currentPt, pt1, pt2, pt3)/self.approximateSegmentLength - maxSteps = int(round(est)) - falseCurve = (pt1==self.currentPt) and (pt2==pt3) - if maxSteps < 1 or falseCurve: - self.otherPen.lineTo(pt3) - self.currentPt = pt3 - return - step = 1.0/maxSteps - factors = range(0, maxSteps+1) - for i in factors[1:]: - pt = _getCubicPoint(i*step, self.currentPt, pt1, pt2, pt3) - self.otherPen.lineTo(pt) - self.currentPt = pt3 - - def _closePath(self): - self.lineTo(self.firstPt) - self.otherPen.closePath() - self.currentPt = None - - def _endPath(self): - self.otherPen.endPath() - self.currentPt = None - - def addComponent(self, glyphName, transformation): - self.otherPen.addComponent(glyphName, transformation) - - -def flattenGlyph(aGlyph, threshold=10, segmentLines=True): - - """Replace curves with series of straight l ines.""" - - from robofab.pens.adapterPens import PointToSegmentPen - if len(aGlyph.contours) == 0: - return - new = _RGlyph() - writerPen = new.getPen() - filterpen = FlattenPen(writerPen, threshold, segmentLines) - wrappedPen = PointToSegmentPen(filterpen) - aGlyph.drawPoints(wrappedPen) - aGlyph.clear() - aGlyph.appendGlyph(new) - aGlyph.update() - return aGlyph - - -def spikeGlyph(aGlyph, segmentLength=20, spikeLength=40, patternFunc=None): - - """Add narly spikes or dents to the glyph. - patternFunc is an optional function which recalculates the offset.""" - - from math import atan2, sin, cos, pi - - new = _RGlyph() - new.appendGlyph(aGlyph) - new.width = aGlyph.width - - if len(new.contours) == 0: - return - flattenGlyph(new, segmentLength, segmentLines=True) - for contour in new: - l = len(contour.points) - lastAngle = None - for i in range(0, len(contour.points), 2): - prev = contour.points[i-1] - cur = contour.points[i] - next = contour.points[(i+1)%l] - angle = atan2(prev.x - next.x, prev.y - next.y) - lastAngle = angle - if patternFunc is not None: - thisSpikeLength = patternFunc(spikeLength) - else: - thisSpikeLength = spikeLength - cur.x -= sin(angle+.5*pi)*thisSpikeLength - cur.y -= cos(angle+.5*pi)*thisSpikeLength - new.update() - aGlyph.clear() - aGlyph.appendGlyph(new) - aGlyph.update() - return aGlyph - - -def halftoneGlyph(aGlyph, invert=False): - - """Convert the glyph into some sort of halftoning pattern. - Measure a bunch of inside/outside points to simulate grayscale levels. - Slow. - """ - print 'halftoneGlyph is running...' - grid = {} - drawing = {} - dataDistance = 10 - scan = 2 - preload = 0 - cellDistance = dataDistance * 5 - overshoot = dataDistance * 2 - (xMin, yMin, xMax, yMax) = aGlyph.box - for x in range(xMin-overshoot, xMax+overshoot, dataDistance): - print 'scanning..', x - for y in range(yMin-overshoot, yMax+overshoot, dataDistance): - if aGlyph.pointInside((x, y)): - grid[(x, y)] = True - else: - grid[(x, y)] = False - #print 'gathering data', x, y, grid[(x, y)] - print 'analyzing..' - for x in range(xMin-overshoot, xMax+overshoot, cellDistance): - for y in range(yMin-overshoot, yMax+overshoot, cellDistance): - total = preload - for scanx in range(-scan, scan): - for scany in range(-scan, scan): - if grid.get((x+scanx*dataDistance, y+scany*dataDistance)): - total += 1 - if invert: - drawing[(x, y)] = 2*scan**2 - float(total) - else: - drawing[(x, y)] = float(total) - aGlyph.clear() - print drawing - for (x,y) in drawing.keys(): - size = drawing[(x,y)] / float(2*scan**2) * 5 - pen = aGlyph.getPen() - pen.moveTo((x-size, y-size)) - pen.lineTo((x+size, y-size)) - pen.lineTo((x+size, y+size)) - pen.lineTo((x-size, y+size)) - pen.lineTo((x-size, y-size)) - pen.closePath() - aGlyph.update() - - -if __name__ == "__main__": - from robofab.pens.pointPen import PrintingPointPen - pp = PrintingPointPen() - #pp.beginPath() - #pp.addPoint((100, 100)) - #pp.endPath() - - tpp = ThresholdPointPen(pp, threshold=20) - tpp.beginPath() - #segmentType=None, smooth=False, name=None - tpp.addPoint((100, 100), segmentType="line", smooth=True) - # section that should be too small - tpp.addPoint((100, 102), segmentType="line", smooth=True) - tpp.addPoint((200, 200), segmentType="line", smooth=True) - # curve section with final point that's far enough, but with offcurves that are under the threshold - tpp.addPoint((200, 205), segmentType="curve", smooth=True) - tpp.addPoint((300, 295), segmentType="curve", smooth=True) - tpp.addPoint((300, 300), segmentType="line", smooth=True) - # curve section with final point that is not far enough - tpp.addPoint((550, 350), segmentType="curve", smooth=True) - tpp.addPoint((360, 760), segmentType="curve", smooth=True) - tpp.addPoint((310, 310), segmentType="line", smooth=True) - - tpp.addPoint((400, 400), segmentType="line", smooth=True) - tpp.addPoint((100, 100), segmentType="line", smooth=True) - tpp.endPath() - - # couple of single points with names - tpp.beginPath() - tpp.addPoint((500, 500), segmentType="move", smooth=True, name="named point") - tpp.addPoint((600, 500), segmentType="move", smooth=True, name="named point") - tpp.addPoint((601, 501), segmentType="move", smooth=True, name="named point") - tpp.endPath() - - # open path - tpp.beginPath() - tpp.addPoint((500, 500), segmentType="move", smooth=True) - tpp.addPoint((501, 500), segmentType="line", smooth=True) - tpp.addPoint((101, 500), segmentType="line", smooth=True) - tpp.addPoint((101, 100), segmentType="line", smooth=True) - tpp.addPoint((498, 498), segmentType="line", smooth=True) - tpp.endPath() -
\ No newline at end of file |