diff options
Diffstat (limited to 'misc/pylib/booleanOperations/booleanGlyph.pyx')
-rw-r--r-- | misc/pylib/booleanOperations/booleanGlyph.pyx | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/misc/pylib/booleanOperations/booleanGlyph.pyx b/misc/pylib/booleanOperations/booleanGlyph.pyx new file mode 100644 index 000000000..af3979f05 --- /dev/null +++ b/misc/pylib/booleanOperations/booleanGlyph.pyx @@ -0,0 +1,257 @@ +from __future__ import print_function, division, absolute_import +import weakref +from copy import deepcopy + +try: + from robofab.pens.pointPen import AbstractPointPen + from robofab.pens.adapterPens import PointToSegmentPen, SegmentToPointPen + from robofab.pens.boundsPen import BoundsPen +except: + from ufoLib.pointPen import ( + AbstractPointPen, PointToSegmentPen, SegmentToPointPen) + from fontTools.pens.boundsPen import BoundsPen + +from fontTools.pens.areaPen import AreaPen +from .booleanOperationManager import BooleanOperationManager + +manager = BooleanOperationManager() + + +class BooleanGlyphDataPointPen(AbstractPointPen): + + def __init__(self, glyph): + self._glyph = glyph + self._points = [] + self.copyContourData = True + + def _flushContour(self): + points = self._points + if len(points) == 1 and points[0][0] == "move": + # it's an anchor + segmentType, pt, smooth, name = points[0] + self._glyph.anchors.append((pt, name)) + elif self.copyContourData: + # ignore double points on start and end + firstPoint = points[0] + if firstPoint[0] == "move": + # remove trailing off curves in an open path + while points[-1][0] is None: + points.pop() + lastPoint = points[-1] + if firstPoint[0] is not None and lastPoint[0] is not None: + if firstPoint[1] == lastPoint[1]: + if firstPoint[0] in ("line", "move"): + del points[0] + else: + raise AssertionError("Unhandled point type sequence") + elif firstPoint[0] == "move": + # auto close the path + _, pt, smooth, name = firstPoint + points[0] = "line", pt, smooth, name + + contour = self._glyph.contourClass() + contour._points = points + self._glyph.contours.append(contour) + + def beginPath(self): + self._points = [] + + def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): + self._points.append((segmentType, pt, smooth, name)) + + def endPath(self): + self._flushContour() + + def addComponent(self, baseGlyphName, transformation): + self._glyph.components.append((baseGlyphName, transformation)) + + +class BooleanContour(object): + + """ + Contour like object. + """ + + def __init__(self): + self._points = [] + self._clockwise = None + self._bounds = None + + def __len__(self): + return len(self._points) + + # shallow contour API + + def draw(self, pen): + pointPen = PointToSegmentPen(pen) + self.drawPoints(pointPen) + + def drawPoints(self, pointPen): + pointPen.beginPath() + for segmentType, pt, smooth, name in self._points: + pointPen.addPoint(pt=pt, segmentType=segmentType, smooth=smooth, name=name) + pointPen.endPath() + + def _get_clockwise(self): + if self._clockwise is None: + pen = AreaPen() + pen.endPath = pen.closePath + self.draw(pen) + self._clockwise = pen.value < 0 + return self._clockwise + + clockwise = property(_get_clockwise) + + def _get_bounds(self): + if self._bounds is None: + pen = BoundsPen(None) + self.draw(pen) + self._bounds = pen.bounds + return self._bounds + + bounds = property(_get_bounds) + + +class BooleanGlyph(object): + + """ + Glyph like object handling boolean operations. + + union: + result = BooleanGlyph(glyph).union(BooleanGlyph(glyph2)) + result = BooleanGlyph(glyph) | BooleanGlyph(glyph2) + + difference: + result = BooleanGlyph(glyph).difference(BooleanGlyph(glyph2)) + result = BooleanGlyph(glyph) % BooleanGlyph(glyph2) + + intersection: + result = BooleanGlyph(glyph).intersection(BooleanGlyph(glyph2)) + result = BooleanGlyph(glyph) & BooleanGlyph(glyph2) + + xor: + result = BooleanGlyph(glyph).xor(BooleanGlyph(glyph2)) + result = BooleanGlyph(glyph) ^ BooleanGlyph(glyph2) + + """ + + contourClass = BooleanContour + + def __init__(self, glyph=None, copyContourData=True): + self.contours = [] + self.components = [] + self.anchors = [] + + self.name = None + self.unicodes = None + self.width = None + self.lib = {} + self.note = None + + if glyph: + pen = self.getPointPen() + pen.copyContourData = copyContourData + glyph.drawPoints(pen) + + self.name = glyph.name + self.unicodes = glyph.unicodes + self.width = glyph.width + self.lib = deepcopy(glyph.lib) + self.note = glyph.note + + if not isinstance(glyph, self.__class__): + self.getSourceGlyph = weakref.ref(glyph) + + def __repr__(self): + return "<BooleanGlyph %s>" % self.name + + def __len__(self): + return len(self.contours) + + def __getitem__(self, index): + return self.contours[index] + + def getSourceGlyph(self): + return None + + def getParent(self): + source = self.getSourceGlyph() + if source: + return source.getParent() + return None + + # shalllow glyph API + + def draw(self, pen): + pointPen = PointToSegmentPen(pen) + self.drawPoints(pointPen) + + def drawPoints(self, pointPen): + for contour in self.contours: + contour.drawPoints(pointPen) + for baseName, transformation in self.components: + pointPen.addComponent(baseName, transformation) + for pt, name in self.anchors: + pointPen.beginPath() + pointPen.addPoint(pt=pt, segmentType="move", smooth=False, name=name) + pointPen.endPath() + + def getPen(self): + return SegmentToPointPen(self.getPointPen()) + + def getPointPen(self): + return BooleanGlyphDataPointPen(self) + + # boolean operations + + def _booleanMath(self, operation, other): + if not isinstance(other, self.__class__): + other = self.__class__(other) + destination = self.__class__(self, copyContourData=False) + func = getattr(manager, operation) + + if operation == "union": + contours = self.contours + if other is not None: + contours += other.contours + func(contours, destination.getPointPen()) + else: + subjectContours = self.contours + clipContours = other.contours + func(subjectContours, clipContours, destination.getPointPen()) + return destination + + def __or__(self, other): + return self.union(other) + + __ror__ = __ior__ = __or__ + + def __mod__(self, other): + return self.difference(other) + + __rmod__ = __imod__ = __mod__ + + def __and__(self, other): + return self.intersection(other) + + __rand__ = __iand__ = __and__ + + def __xor__(self, other): + return self.xor(other) + + __rxor__ = __ixor__ = __xor__ + + def union(self, other): + return self._booleanMath("union", other) + + def difference(self, other): + return self._booleanMath("difference", other) + + def intersection(self, other): + return self._booleanMath("intersection", other) + + def xor(self, other): + return self._booleanMath("xor", other) + + def removeOverlap(self): + return self._booleanMath("union", None) |