def test_insertGlyph(self): font = Font(getTestFontPath()) glyph = Glyph() glyph.name = "NewGlyphTest" self.assertEqual(sorted(font.keys()), ["A", "B", "C"]) font.insertGlyph(glyph) self.assertEqual(sorted(font.keys()), ["A", "B", "C", "NewGlyphTest"])
class OutlinePen(BasePen): pointClass = MathPoint magicCurve = 0.5522847498 def __init__(self, glyphSet, offset=10, contrast=0, contrastAngle=0, connection="square", cap="round", miterLimit=None, closeOpenPaths=True, optimizeCurve=False, preserveComponents=False): 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.closeOpenPaths = closeOpenPaths 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.preserveComponents = preserveComponents 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 test_appendContour(self): glyph = Glyph() glyph.dirty = False contour = Contour() glyph.appendContour(contour) self.assertEqual(len(glyph), 1) self.assertTrue(glyph.dirty) self.assertEqual(contour.getParent(), glyph)
def test_appendGuideline(self): glyph = Glyph() glyph.dirty = False guideline = Guideline() glyph.appendGuideline(guideline) self.assertEqual(len(glyph.guidelines), 1) self.assertTrue(glyph.dirty) self.assertEqual(guideline.getParent(), glyph)
def test_appendAnchor(self): glyph = Glyph() glyph.dirty = False anchor = Anchor() glyph.appendAnchor(anchor) self.assertEqual(len(glyph.anchors), 1) self.assertTrue(glyph.dirty) self.assertEqual(anchor.getParent(), glyph)
def test_appendComponent(self): glyph = Glyph() glyph.dirty = False component = Component() glyph.appendComponent(component) self.assertEqual(len(glyph.components), 1) self.assertTrue(glyph.dirty) self.assertEqual(component.getParent(), glyph)
def __init__( self, glyphSet, offset=10, contrast=0, contrastAngle=0, connection="square", cap="round", miterLimit=None, closeOpenPaths=True, optimizeCurve=False, preserveComponents=False, ): 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.closeOpenPaths = closeOpenPaths 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.preserveComponents = preserveComponents self.components = [] self.drawSettings()
class OutlinePen(BasePen): pointClass = MathPoint magicCurve = 0.5522847498 def __init__(self, glyphSet, offset=10, connection="square", cap="round", mitterLimit=None, closeOpenPaths=True): BasePen.__init__(self, glyphSet) self.offset = abs(offset) self._inputMitterLimit = mitterLimit if mitterLimit is None: mitterLimit = self.offset self.mitterLimit = abs(mitterLimit) self.closeOpenPaths = closeOpenPaths self.connectionCallback = getattr(self, "connection%s" %(connection[0].capitalize() + connection[1:])) self.capCallback = getattr(self, "cap%s" %(cap[0].capitalize() + cap[1:])) 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.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 test_remove_tiny_sub_paths_large_contour(): g = Glyph() p = g.getPen() p.moveTo((100, 100)) p.lineTo((200, 200)) p.lineTo((0, 100)) p.closePath() assert len(g[0]) == 3 assert g.bounds == (0, 100, 200, 200) bg = BooleanGlyph(g) assert remove_tiny_sub_paths(bg, 25, []) == []
def test_remove_tiny_sub_paths_small_contour(): g = Glyph() p = g.getPen() p.moveTo((1, 1)) p.lineTo((2, 2)) p.lineTo((0, 1)) p.closePath() assert len(g[0]) == 3 assert g.bounds == (0, 1, 2, 2) bg = BooleanGlyph(g) assert remove_tiny_sub_paths(bg, 25, []) == \ ['Contour 0 is too small: bounding box is less than minimum area. ' 'Start point: ((1, 1)).']
def glyph_to_quadratic(glyph, max_n, max_err, correctDirection=True, verbose=False): """ Convert the glyph outline to TrueType quadratic splines. """ new = Glyph() writerPen = new.getPointPen() cu2quPen = Cu2QuPen(writerPen, max_n, max_err, verbose) if correctDirection: reversePen = ReverseContourPointPen(cu2quPen) glyph.drawPoints(reversePen) else: glyph.drawPoints(cu2quPen) # clear glyph but keep anchors for mark, mkmk features glyph.clearContours() glyph.clearComponents() writerPen = glyph.getPointPen() new.drawPoints(writerPen)
def test_correct_direction_same_area(self): glyph = Glyph() pen = glyph.getPointPen() pen.beginPath() pen.addPoint((0, 0), segmentType="line") pen.addPoint((0, 50), segmentType="line") pen.addPoint((50, 50), segmentType="line") pen.endPath() pen.beginPath() pen.addPoint((50, 50), segmentType="line") pen.addPoint((50, 100), segmentType="line") pen.addPoint((100, 100), segmentType="line") pen.endPath() try: glyph.correctContourDirection() except Exception as e: self.fail("glyph.correctContourDirection() raised unexpected exception: " + str(e))
def test_extract_scaled_glyph_as_Defcon_Glyph(self): """Test scaled glyph retrieval as a Defcon glyph.""" from defcon import Glyph for testFont in [self.smallFont, self.stemedSmallFont]: scaledGlyph = Glyph() for glyphName in self.glyphNames: testFont.extractGlyph(glyphName, scaledGlyph) self.assertIsInstance(scaledGlyph, Glyph) self.assertEqual(scaledGlyph.name, glyphName)
def create_glyph(codepoint, width, contours): glyph = Glyph() glyph.name = get_glyph_name(codepoint) glyph.unicode = ord(codepoint) glyph.width = width for contour in contours: glyph.appendContour(contour) return glyph
def test_glyph(self): self.assertIsNone(self.contour.glyph) self.contour = Contour(self.glyph) self.assertEqual(self.contour.glyph, self.glyph) glyph = Glyph() self.contour = Contour() self.contour.glyph = glyph self.assertEqual(self.contour.glyph, glyph) with self.assertRaises(AssertionError): self.contour.glyph = self.glyph
def to_glyph(self, name=None, width=None, allow_blank=False): """ Create a glyph (like from `defcon`) using this pen’s value. *Warning*: if path is unended, closedPath will be called """ from defcon import Glyph if not allow_blank: if self.unended(): self.closePath() bounds = self.bounds() glyph = Glyph() glyph.name = name glyph.width = width or bounds.w try: sp = glyph.getPen() self.replay(sp) except AssertionError: if not allow_blank: print(">>>blank glyph:", glyph.name) return glyph
def test_overlapping_start_end_points(self): # https://github.com/googlefonts/fontmake/issues/572 glyph1 = Glyph() pen = glyph1.getPointPen() pen.beginPath() pen.addPoint((0, 651), segmentType="line") pen.addPoint((0, 101), segmentType="line") pen.addPoint((0, 101), segmentType="line") pen.addPoint((0, 651), segmentType="line") pen.endPath() glyph2 = Glyph() pen = glyph2.getPointPen() pen.beginPath() pen.addPoint((1, 651), segmentType="line") pen.addPoint((2, 101), segmentType="line") pen.addPoint((3, 101), segmentType="line") pen.addPoint((4, 651), segmentType="line") pen.endPath() glyphs = [glyph1, glyph2] assert glyphs_to_quadratic(glyphs, reverse_direction=True) assert [[(p.x, p.y) for p in glyph[0]] for glyph in glyphs] == [ [ (0, 651), (0, 651), (0, 101), (0, 101), ], [(1, 651), (4, 651), (3, 101), (2, 101)], ]
def __init__(self, glyphSet, offset=10, contrast=0, contrastAngle=0, connection="square", cap="round", miterLimit=None, closeOpenPaths=True, optimizeCurve=False, preserveComponents=False, filterDoubles=True, alwaysConnect=False): 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.closeOpenPaths = closeOpenPaths 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.preserveComponents = preserveComponents self.components = [] self.filterDoubles = filterDoubles self.alwaysConnect = alwaysConnect self.drawSettings()
def find_shape_diffs(self): """Report differences in glyph shapes, using BooleanOperations.""" self.build_names() area_pen = GlyphAreaPen(None) pen = PointToSegmentPen(area_pen) mismatched = {} for name in self.names: glyph_a = Glyph() glyph_b = Glyph() self.glyph_set_a[name].draw( Qu2CuPen(glyph_a.getPen(), self.glyph_set_a)) self.glyph_set_b[name].draw( Qu2CuPen(glyph_b.getPen(), self.glyph_set_b)) booleanOperations.xor(list(glyph_a), list(glyph_b), pen) area = abs(area_pen.pop()) if area: mismatched[name] = (area) stats = self.stats['compared'] for name, area in mismatched.items(): stats.append((area, name, self.basepath))
def __init__(self, glyphSet, offset=10, connection="square", cap="round", miterLimit=None, closeOpenPaths=True): BasePen.__init__(self, glyphSet) self.offset = abs(offset) self._inputmiterLimit = miterLimit if miterLimit is None: miterLimit = self.offset self.miterLimit = abs(miterLimit) self.closeOpenPaths = closeOpenPaths self.connectionCallback = getattr(self, "connection%s" %(connection[0].capitalize() + connection[1:])) self.capCallback = getattr(self, "cap%s" %(cap[0].capitalize() + cap[1:])) 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.drawSettings()
def test_contoursToZs_open_contour(): glyph = Glyph() pen = glyph.getPointPen() pen.beginPath() pen.addPoint((0, 0), 'move') pen.addPoint((1, 1), 'line') pen.addPoint((2, 2), 'line') pen.endPath() shape = contoursToZs(glyph) assert shape == [[{ 'x': 0, 'y': 0, 'on': True }, { 'x': 1, 'y': 1, 'on': True }, { 'x': 2, 'y': 2, 'on': True }]]
def test_insertGlyph(self): font = Font() layer = font.layers[None] source = Glyph() source.unicodes = [1, 2] source.name = "a" dest = layer.insertGlyph(source, name="nota") self.assertNotEqual(dest, source) self.assertEqual(dest.name, "nota") self.assertEqual(list(layer.unicodeData.items()), [(1, ["nota"]), (2, ["nota"])]) source = Glyph() source.unicodes = [3] source.name = "b" dest = layer.insertGlyph(source) self.assertNotEqual(dest, source) self.assertEqual(dest.name, "b") self.assertEqual(list(layer.unicodeData.items()), [(1, ["nota"]), (2, ["nota"]), (3, ["b"])])
def __init__(self, glyphSet, offset=10, connection="square", cap="round", miterLimit=None, closeOpenPaths=True): BasePen.__init__(self, glyphSet) self.offset = abs(offset) self._inputmiterLimit = miterLimit if miterLimit is None: miterLimit = self.offset self.miterLimit = abs(miterLimit) self.closeOpenPaths = closeOpenPaths self.connectionCallback = getattr( self, "connection%s" % (connection[0].capitalize() + connection[1:])) self.capCallback = getattr(self, "cap%s" % (cap[0].capitalize() + cap[1:])) 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.drawSettings()
def test_identifier(self): glyph = Glyph() contour = Contour() glyph.appendContour(contour) contour.identifier = "contour 1" self.assertEqual(contour.identifier, "contour 1") self.assertEqual(sorted(glyph.identifiers), ["contour 1"]) contour = Contour() glyph.appendContour(contour) with self.assertRaises(AssertionError): contour.identifier = "contour 1" contour.identifier = "contour 2" self.assertEqual(sorted(glyph.identifiers), ["contour 1", "contour 2"]) contour.identifier = "not contour 2 anymore" self.assertEqual(contour.identifier, "contour 2") self.assertEqual(sorted(glyph.identifiers), ["contour 1", "contour 2"]) contour.identifier = None self.assertEqual(contour.identifier, "contour 2") self.assertEqual(sorted(glyph.identifiers), ["contour 1", "contour 2"])
def find_shape_diffs(self): """Report differences in glyph shapes, using BooleanOperations.""" self.build_names() area_pen = GlyphAreaPen(None) pen = PointToSegmentPen(area_pen) mismatched = {} for name in self.names: glyph_a = Glyph() glyph_b = Glyph() self.glyph_set_a[name].draw(Qu2CuPen(glyph_a.getPen(), self.glyph_set_a)) self.glyph_set_b[name].draw(Qu2CuPen(glyph_b.getPen(), self.glyph_set_b)) booleanOperations.xor(list(glyph_a), list(glyph_b), pen) area = abs(area_pen.pop()) if area: mismatched[name] = area stats = self.stats["compared"] for name, area in mismatched.items(): stats.append((area, name, self.basepath))
class OutlineFitterPen(BasePen): pointClass = MathPoint magicCurve = 0.5522847498 def __init__(self, glyphSet, offset=10, contrast=0, contrastAngle=0, connection="square", cap="round", miterLimit=None, closeOpenPaths=True, optimizeCurve=False, preserveComponents=False, filterDoubles=True, alwaysConnect=False): 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.closeOpenPaths = closeOpenPaths 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.preserveComponents = preserveComponents self.components = [] self.filterDoubles = filterDoubles self.alwaysConnect = alwaysConnect self.drawSettings() def _moveTo(self, pt): x, y = pt 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, pt): x, y = pt 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, pt1, pt2, pt3): if self.optimizeCurve: curves = splitCubicAtT(self.prevPoint, pt1, pt2, pt3, .5) else: curves = [(self.prevPoint, pt1, pt2, pt3)] for curve in curves: p1, h1, h2, p2 = curve self._processCurveToOne(h1, h2, p2) def _processCurveToOne(self, pt1, pt2, pt3): if self.offset == 0: self.outerPen.curveTo(pt1, pt2, pt3) self.innerPen.curveTo(pt1, pt2, pt3) return self.originalPen.curveTo(pt1, pt2, pt3) p1 = self.pointClass(*pt1) p2 = self.pointClass(*pt2) p3 = self.pointClass(*pt3) 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) 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() # Collect four original points originalPoints = [self.prevPoint, p1, p2, p3] # Collect four inner points h1 = p1 - self.pointClass(cos(a1), sin(a1)) * tickness1 h2 = p2 - self.pointClass(cos(a2), sin(a2)) * tickness2 innerPoints = [self.innerCurrentPoint, h1, h2] self.innerCurrentPoint = p3 - self.pointClass(cos(a2), sin(a2)) * tickness2 innerPoints.append(self.innerCurrentPoint) # Collect four outer points h1 = p1 + self.pointClass(cos(a1), sin(a1)) * tickness1 h2 = p2 + self.pointClass(cos(a2), sin(a2)) * tickness2 outerPoints = [self.outerCurrentPoint, h1, h2] self.outerCurrentPoint = p3 + self.pointClass(cos(a2), sin(a2)) * tickness2 outerPoints.append(self.outerCurrentPoint) """ # @@@ NEW TECHNIQUE: # Determine if a curve in/out of a BCP is rotating clockwise or counterclockwise # Compare vectors of the point to the bcp and the point to a split location just after/before the point splits = splitCubicAtT(self.prevPoint, p1, p2, p3, 0.01, 0.99) splitPt0 = self.pointClass(splits[0][-1]) splitPt1 = self.pointClass(splits[-1][0]) # vectors vBcp0 = self.prevPoint - p1 vNext0 = self.prevPoint - splitPt0 vBcp1 = p3 - p2 vNext1 = p3 - splitPt1 # Find which way the vector is rotating by looking at the sign of the dot product dot0 = vBcp0[0] * vNext0[1] - vBcp0[1] * vNext0[0] dot1 = vBcp1[0] * vNext1[1] - vBcp1[1] * vNext1[0] dir0 = 1 dir1 = 1 if dot0 < 0: dir0 = -1 if dot1 < 0: dir1 = -1 """ # @@@ OLD TECHNIQUE: # Determine if a curve in/out of a BCP is rotating clockwise or counterclockwise # Compare vectors of the point to the bcp and the point to the next point vBcp0 = self.prevPoint - p1 vNext0 = self.prevPoint - p3 vBcp1 = self.pointClass(pt3) - p2 vNext1 = self.pointClass(pt3) - self.prevPoint # Find which way the vector is rotating by looking at the sign of the dot product dot0 = vBcp0[0] * vNext0[1] - vBcp0[1] * vNext0[0] dot1 = vBcp1[0] * vNext1[1] - vBcp1[1] * vNext1[0] # Find the angles # ang0 = self.prevPoint.angle(p1) # ang1 = self.pointClass(pt3).angle(p2) # print(ang0, ang1) dir0 = 1 dir1 = 1 if dot0 < 0: dir0 = -1 if dot1 < 0: dir1 = -1 # print(dot0, dot1) """ # If the onCurves are closer to each other than 2x the thickness, reverse the direction of the inner curve angle = self.prevPoint.angle(p3) distance = self.prevPoint.distance(p3) thickness = self.getThickness(angle) reverseInner = False #print(distance, thickness) if distance < thickness * 2: reverseInner = True # @@@ Do this differently, it's not helping Two bugs (maybe related) - When the on-curves are closer to each other than 2x the offset, need to reverse the direction on the inner handles - When the BCPs are in the same direction and are on top of each other, the curve asymmetrically sets the direction (wrong) """ # Flatten the original curve flatPoints = flattenCurve(originalPoints) # Move the BCPs to fit the curve innerPoints = self.fitCurve(innerPoints, flatPoints, dir0, dir1) outerPoints = self.fitCurve(outerPoints, flatPoints, -dir0, -dir1) # Draw the four points self.innerPen.curveTo(innerPoints[1], innerPoints[2], innerPoints[3]) self.innerPrevPoint = self.innerCurrentPoint self.outerPen.curveTo(outerPoints[1], outerPoints[2], outerPoints[3]) self.outerPrevPoint = self.outerCurrentPoint self.prevPoint = p3 self.currentAngle = a2 self.prevAngle = a2 def _closePath(self): if self.shouldHandleMove: return if self.offset == 0: self.outerPen.closePath() self.innerPen.closePath() return if not self.prevPoint == self.firstPoint: self._lineTo(self.firstPoint) self.originalPen.closePath() self.innerPrevPoint = self.innerCurrentPoint self.innerCurrentPoint = self.innerFirstPoint self.outerPrevPoint = self.outerCurrentPoint self.outerCurrentPoint = self.outerFirstPoint self.prevAngle = self.currentAngle self.currentAngle = self.firstAngle self.buildConnection(close=True) self.innerPen.closePath() self.outerPen.closePath() def _endPath(self): if self.shouldHandleMove: return self.originalPen.endPath() self.innerPen.endPath() self.outerPen.endPath() if self.closeOpenPaths: 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 addComponent(self, glyphName, transform): if self.preserveComponents: self.components.append((glyphName, transform)) else: BasePen.addComponent(self, 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 # fit curve def _testCurve(self, pts, flatPts, f0, f1, inRange=False): # Normalize and scale the BCPs scaled0 = (pts[1] - pts[0]) * f0 pts[1] = pts[1] + scaled0 dist1 = abs(self.pointClass(pts[3]).distance(pts[2])) scaled1 = (pts[3] - pts[2]) * f1 pts[2] = pts[2] + scaled1 # Split at a few locations splitFactors = [0.25, 0.5, 0.75] newSplit = splitCubicAtT(pts[0], pts[1], pts[2], pts[3], *splitFactors) newSplitLocs = [self.pointClass(pts[-1]) for pts in newSplit] newSplitLocs = newSplitLocs[:-1] # Measure these splits back to the flattened original segment # Prepare chunks of the flatPts, if we should be testing inRange # ...a factor of 0.25 should only be compared against 0.2-0.3, 0.5 should be 0.45-0.55, 0.75 should be 0.7-0.8 totalFlats = len(flatPts) flatRanges = [(int(totalFlats * 0.2), int(totalFlats * 0.3)), (int(totalFlats * 0.45), int(totalFlats * 0.55)), (int(totalFlats * 0.7), int(totalFlats * 0.8)) ] # These relate directly to the factors in splitFactors totalDiff = 0 for i, loc in enumerate(newSplitLocs): if inRange: # If it should only check a smaller range of flat points # (experimental) flatPtChunk = flatPts[flatRanges[i][0]:flatRanges[i][1]] else: flatPtChunk = flatPts closestLoc, closestDist = self.findClosest(loc, flatPtChunk) angle = loc.angle(self.pointClass(closestLoc)) expectedDistance = self.getThickness(angle) diff = expectedDistance - closestDist totalDiff += diff return pts, totalDiff def findClosest(self, loc, locs): closest = None closestDist = None for testLoc in locs: d = abs(self.pointClass(loc).distance(self.pointClass(testLoc))) if not closestDist: closestDist = d closest = testLoc else: if d < closestDist: closestDist = d closest = testLoc return closest, closestDist def fitCurve(self, pts, flatPts, dir0, dir1): # pts is a list of four points defining a curve # Use three sample points along the curve to move the BCPs in directions dir0 and dir1 # until the sampled points average out to the correct distance from flatPoints # positive or negative factor for moving the BCPs, depending on the direction the curve is turning f = 0.05 if dir0 > 0: f0 = f else: f0 = -f if dir1 > 0: f1 = f else: f1 = -f closestPts = pts.copy() totalPasses = 0 # First pass, coarse while True: totalPasses += 1 if totalPasses > 50: break closestPts, totalDiff = self._testCurve(closestPts, flatPts, f0, f1) if totalDiff < 0: # The diff crossed zero break # Second pass, more fine and in the other direction f0 *= -0.1 f1 *= -0.1 totalPasses = 0 while True: totalPasses += 1 if totalPasses > 50: break closestPts, totalDiff = self._testCurve(closestPts, flatPts, f0, f1) if totalDiff > 0: # The diff crossed zero in the other direction this time break # Third pass, more fine and in the other direction f0 *= -0.1 f1 *= -0.1 totalPasses = 0 while True: totalPasses += 1 if totalPasses > 50: break closestPts, totalDiff = self._testCurve(closestPts, flatPts, f0, f1) if totalDiff < 0: # The diff crossed zero break return closestPts # connections def buildConnection(self, close=False): if self.alwaysConnect: # Always force a connection for compatibility self.connectionCallback(self.outerPrevPoint, self.outerCurrentPoint, self.outerPen, close) self.connectionCallback(self.innerPrevPoint, self.innerCurrentPoint, self.innerPen, close) else: 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) elif not self.filterDoubles: self.innerPen.lineTo(self.innerCurrentPoint) self.outerPen.lineTo(self.outerCurrentPoint) def connectionSquare(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 not None: if self._inputmiterLimit is not None and roundFloat( newPoint.distance(first)) > self._inputmiterLimit: pen.lineTo(tempFirst) pen.lineTo(tempLast) else: pen.lineTo(newPoint) if not close: pen.lineTo(last) 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(sin(angle_1), -cos(angle_1)) tempLast = last + self.pointClass(sin(angle_2), -cos(angle_2)) centerPoint = interSect((first, tempFirst), (last, tempLast)) if centerPoint is None: # the lines are parallel, let's just take the middle centerPoint = (first + last) / 2 angle_diff = (angle_1 - angle_2) % (2 * pi) if angle_diff > pi: angle_diff = 2 * pi - angle_diff angle_half = angle_diff / 2 radius = centerPoint.distance(first) D = radius * (1 - cos(angle_half)) try: handleLength = (4 * D / 3) / sin( angle_half) # length of the bcp line except: handleLength = 0 bcp1 = first - self.pointClass(cos(angle_1), sin(angle_1)) * handleLength bcp2 = last + self.pointClass(cos(angle_2), sin(angle_2)) * handleLength pen.curveTo(bcp1, bcp2, last) def connectionButt(self, first, last, pen, close): if not close: pen.lineTo(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 capButt(self, firstContour, lastContour, first, last, angle): # not nothing pass 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 # should be self.magicCurve 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 capSquare(self, firstContour, lastContour, first, last, angle): angle = radians(degrees(angle) + 90) firstContour[-1].smooth = True lastContour[0].smooth = True p1 = first - self.pointClass(cos(angle), sin(angle)) * self.offset firstContour.addPoint((p1.x, p1.y), smooth=False, segmentType="line") p2 = last - self.pointClass(cos(angle), sin(angle)) * self.offset firstContour.addPoint((p2.x, p2.y), smooth=False, segmentType="line") def capRoundsimple(self, firstContour, lastContour, first, last, angle): angle = radians(degrees(angle) + 90) firstContour[-1].smooth = True lastContour[0].smooth = True p1 = first - self.pointClass(cos(angle), sin(angle)) * (self.offset * 1.5) firstContour.addPoint((p1.x, p1.y)) p2 = last - self.pointClass(cos(angle), sin(angle)) * (self.offset * 1.5) firstContour.addPoint((p2.x, p2.y)) lastContour[0].segmentType = "curve" lastContour[0].smooth = True #firstContour.addPoint((last.x, last.y), smooth=True, segmentType="curve") 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
def setUp(self): self.font = Font() self.glyph = Glyph() self.component = Component(self.glyph)
def createGlyphs(self): self.UFO.newGlyph('.notdef') missing = '' for glf in self.GDB.Master2Search: glfUnicode = int(self.GDB.Master2Unicode[glf], 16) print('') layer = 0 stopAt = 'arAlef.fina.la' glfSrc = self.GDB.Master2Search[glf].split(',') if glf == stopAt: m = 1 log = (glf + ' ' * 50)[0:20] if glf in self.GDB.MAPPING: try: mgName = [self.GDB.MAPPING[glf]] glyph = Glyph() glyph.copyDataFromGlyph(self.srcUFO[mgName[0]]) glyph.name = glf if layer == 0: glyph.unicode = glfUnicode glyph.unicodes = [glfUnicode] else: glyph.unicode = None glyph.anchors = [] glyph.decomposeAllComponents() if layer > 0: currentLayer = self.getLayer(layer) currentLayer.insertGlyph(glyph) else: self.UFO.insertGlyph(glyph) layer += 1 # print(g + ' found :)' + ' L ' + str(layer)) gLog = log + (mgName[0] + ' ' * 50)[0:20] print(gLog + '[' + str(layer) + '] *') except: pass for g in glfSrc: mgName = None gCode = None gLog = log + (g + ' ' * 50)[0:20] try: try: gCode = self.GDB.Prod2Decimal[g] mgName = self.srcUFO.unicodeData[gCode] except: try: mgName = [g] except: pass glyph = Glyph() glyph.copyDataFromGlyph(self.srcUFO[mgName[0]]) glyph.name = glf if layer == 0: glyph.unicode = glfUnicode glyph.unicodes = [glfUnicode] else: glyph.unicode = None glyph.anchors = [] glyph.decomposeAllComponents() if layer > 0: currentLayer = self.getLayer(layer) currentLayer.insertGlyph(glyph) else: self.UFO.insertGlyph(glyph) layer += 1 print(gLog + '[' + str(layer) + ']') except: print(gLog + '[ ]') glyph = None if layer == 0: self.UFO.newGlyph(glf) missing += log + "\n" if len(missing) > 1: print('\n') print('=' * 60) print('Missing glyphes ' + str(len(missing.splitlines()))) print('=' * 60) print(missing) print('=' * 60)
def test_identifiers(self): glyph = Glyph() pointPen = glyph.getPointPen() pointPen.beginPath(identifier="contour 1") pointPen.addPoint((0, 0), identifier="point 1") pointPen.addPoint((0, 0), identifier="point 2") pointPen.endPath() pointPen.beginPath(identifier="contour 2") pointPen.endPath() pointPen.addComponent("A", (1, 1, 1, 1, 1, 1), identifier="component 1") pointPen.addComponent("A", (1, 1, 1, 1, 1, 1), identifier="component 2") guideline = Guideline() guideline.identifier = "guideline 1" glyph.appendGuideline(guideline) guideline = Guideline() guideline.identifier = "guideline 2" glyph.appendGuideline(guideline) self.assertEqual([contour.identifier for contour in glyph], ["contour 1", "contour 2"]) self.assertEqual([point.identifier for point in glyph[0]], ["point 1", "point 2"]) self.assertEqual( [component.identifier for component in glyph.components], ["component 1", "component 2"]) with self.assertRaises(AssertionError): pointPen.beginPath(identifier="contour 1") pointPen.endPath() pointPen.beginPath() pointPen.addPoint((0, 0)) with self.assertRaises(AssertionError): pointPen.addPoint((0, 0), identifier="point 1") pointPen.endPath() with self.assertRaises(AssertionError): pointPen.addComponent("A", (1, 1, 1, 1, 1, 1), identifier="component 1") g = Guideline() g.identifier = "guideline 1" with self.assertRaises(AssertionError): glyph.appendGuideline(g) self.assertEqual(sorted(glyph.identifiers), [ "component 1", "component 2", "contour 1", "contour 2", "guideline 1", "guideline 2", "point 1", "point 2" ]) glyph.removeContour(glyph[0]) self.assertEqual(sorted(glyph.identifiers), [ "component 1", "component 2", "contour 2", "guideline 1", "guideline 2" ]) glyph.removeComponent(glyph.components[0]) self.assertEqual( sorted(glyph.identifiers), ["component 2", "contour 2", "guideline 1", "guideline 2"]) glyph.removeGuideline(glyph.guidelines[0]) self.assertEqual(sorted(glyph.identifiers), ["component 2", "contour 2", "guideline 2"])
def test_copyDataFromGlyph(self): source = Glyph() source.name = "a" source.width = 1 source.height = 2 source.unicodes = [3, 4] source.note = "test image" source.image = dict(fileName="test image", xScale=1, xyScale=1, yxScale=1, yScale=1, xOffset=0, yOffset=0, color=None) source.anchors = [dict(x=100, y=200, name="test anchor")] source.guidelines = [dict(x=10, y=20, name="test guideline")] source.lib = {"foo": "bar"} pen = source.getPointPen() pen.beginPath() pen.addPoint((100, 200), segmentType="line") pen.addPoint((300, 400), segmentType="line") pen.endPath() component = Component() component.base = "b" source.appendComponent(component) dest = Glyph() dest.copyDataFromGlyph(source) self.assertNotEqual(source.name, dest.name) self.assertEqual(source.width, dest.width) self.assertEqual(source.height, dest.height) self.assertEqual(source.unicodes, dest.unicodes) self.assertEqual(source.note, dest.note) self.assertEqual(source.image.items(), dest.image.items()) self.assertEqual([g.items() for g in source.guidelines], [g.items() for g in dest.guidelines]) self.assertEqual([g.items() for g in source.anchors], [g.items() for g in dest.anchors]) self.assertEqual(len(source), len(dest)) self.assertEqual(len(source.components), len(dest.components)) sourceContours = [] for contour in source: sourceContours.append([]) for point in contour: sourceContours[-1].append( (point.x, point.x, point.segmentType, point.name)) destContours = [] for contour in dest: destContours.append([]) for point in contour: destContours[-1].append( (point.x, point.x, point.segmentType, point.name)) self.assertEqual(sourceContours, destContours) self.assertEqual(source.components[0].baseGlyph, dest.components[0].baseGlyph)
class OutlinePen(BasePen): pointClass = MathPoint magicCurve = 0.5522847498 def __init__(self, glyphSet, offset=10, contrast=0, contrastAngle=0, connection="square", cap="round", miterLimit=None, closeOpenPaths=True, optimizeCurve=False, preserveComponents=False, filterDoubles=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.closeOpenPaths = closeOpenPaths 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.preserveComponents = preserveComponents self.components = [] self.filterDoubles = filterDoubles self.drawSettings() def _moveTo(self, pt): x, y = pt 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, pt): x, y = pt 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, pt1, pt2, pt3): if self.optimizeCurve: curves = splitCubicAtT(self.prevPoint, pt1, pt2, pt3, .5) else: curves = [(self.prevPoint, pt1, pt2, pt3)] for curve in curves: p1, h1, h2, p2 = curve self._processCurveToOne(h1, h2, p2) def _processCurveToOne(self, pt1, pt2, pt3): if self.offset == 0: self.outerPen.curveTo(pt1, pt2, pt3) self.innerPen.curveTo(pt1, pt2, pt3) return self.originalPen.curveTo(pt1, pt2, pt3) p1 = self.pointClass(*pt1) p2 = self.pointClass(*pt2) p3 = self.pointClass(*pt3) 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 if self.offset == 0: self.outerPen.closePath() self.innerPen.closePath() return if not self.prevPoint == self.firstPoint: self._lineTo(self.firstPoint) self.originalPen.closePath() self.innerPrevPoint = self.innerCurrentPoint self.innerCurrentPoint = self.innerFirstPoint self.outerPrevPoint = self.outerCurrentPoint self.outerCurrentPoint = self.outerFirstPoint self.prevAngle = self.currentAngle self.currentAngle = self.firstAngle self.buildConnection(close=True) self.innerPen.closePath() self.outerPen.closePath() def _endPath(self): if self.shouldHandleMove: return self.originalPen.endPath() self.innerPen.endPath() self.outerPen.endPath() if self.closeOpenPaths: 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 addComponent(self, glyphName, transform): if self.preserveComponents: self.components.append((glyphName, transform)) else: BasePen.addComponent(self, 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) elif not self.filterDoubles: self.innerPen.lineTo(self.innerCurrentPoint) self.outerPen.lineTo(self.outerCurrentPoint) def connectionSquare(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 not None: if self._inputmiterLimit is not None and roundFloat( newPoint.distance(first)) > self._inputmiterLimit: pen.lineTo(tempFirst) pen.lineTo(tempLast) else: pen.lineTo(newPoint) if not close: pen.lineTo(last) 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(sin(angle_1), -cos(angle_1)) tempLast = last + self.pointClass(sin(angle_2), -cos(angle_2)) centerPoint = interSect((first, tempFirst), (last, tempLast)) if centerPoint is None: # the lines are parallel, let's just take the middle centerPoint = (first + last) / 2 angle_diff = (angle_1 - angle_2) % (2 * pi) if angle_diff > pi: angle_diff = 2 * pi - angle_diff angle_half = angle_diff / 2 radius = centerPoint.distance(first) D = radius * (1 - cos(angle_half)) handleLength = (4 * D / 3) / sin(angle_half) # length of the bcp line bcp1 = first - self.pointClass(cos(angle_1), sin(angle_1)) * handleLength bcp2 = last + self.pointClass(cos(angle_2), sin(angle_2)) * handleLength pen.curveTo(bcp1, bcp2, last) def connectionButt(self, first, last, pen, close): if not close: pen.lineTo(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 capButt(self, firstContour, lastContour, first, last, angle): # not nothing pass 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 # should be self.magicCurve 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 capSquare(self, firstContour, lastContour, first, last, angle): angle = radians(degrees(angle) + 90) firstContour[-1].smooth = True lastContour[0].smooth = True p1 = first - self.pointClass(cos(angle), sin(angle)) * self.offset firstContour.addPoint((p1.x, p1.y), smooth=False, segmentType="line") p2 = last - self.pointClass(cos(angle), sin(angle)) * self.offset firstContour.addPoint((p2.x, p2.y), smooth=False, segmentType="line") 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
def test_CallingPenBallFilter(self): testGlyph = Glyph() pen = testGlyph.getPen() self.drawTestGlyph(pen) filteredGlyph = self.filter(testGlyph)
subjectPaths = contoursToZs(subjectContours) clippingPaths = contoursToZs(clippingContours) result = shapeops.intersection(subjectPaths, clippingPaths, **kwargs) drawZsWithPointPen(result, outPen, guessSmooth=guessSmooth) def xor(subjectContours, clippingContours, outPen, guessSmooth=True, **kwargs): subjectPaths = contoursToZs(subjectContours) clippingPaths = contoursToZs(clippingContours) result = shapeops.xor(subjectPaths, clippingPaths, **kwargs) drawZsWithPointPen(result, outPen, guessSmooth=guessSmooth) if __name__ == "__main__": import sys from defcon import Glyph from ufoLib.glifLib import readGlyphFromString, writeGlyphToString data = sys.stdin.read() glyph = Glyph() readGlyphFromString(data, glyph, glyph.getPointPen()) contours = list(glyph) glyph.clearContours() union(contours, glyph.getPointPen()) output = writeGlyphToString(glyph.name, glyph, glyph.drawPoints) sys.stdout.write(output)
def getGlyphFromDict(glyph_dict): g = Glyph() # Set attributes g.height = glyph_dict.get('height', 0) g.lib = glyph_dict.get('lib', {}) g.name = glyph_dict.get('name', '') g.note = glyph_dict.get('note', None) g.unicode = glyph_dict.get('unicode', None) g.unicodes = glyph_dict.get('unicodes', []) g.width = glyph_dict.get('width', 0) # Draw the outlines with a pen pen = g.getPointPen() for contour in glyph_dict.get('contours', []): pen.beginPath() for point in contour: pen.addPoint( ( point.get('x'), point.get('y') ), segmentType = point.get('type', None), name = point.get('name', None), smooth = point.get('smooth', None), ) pen.endPath() # Add components for component in glyph_dict.get('components', []): c = Component() c.baseGlyph = component.get('ref', '') c.transformation = component.get('transformation', (1, 0, 0, 1, 0, 0)) g.appendComponent(c) # Add anchors for anchor in glyph_dict.get('anchors', []): a = Anchor(anchorDict = anchor) g.appendAnchor(a) # Return the completed glyph object return g
def setUp(self): testGlyph = Glyph() pen = testGlyph.getPen() self.drawTestGlyph(pen) self.testGlyph = testGlyph
def test_copyDataFromGlyph(self): source = Glyph() source.name = "a" source.width = 1 source.height = 2 source.unicodes = [3, 4] source.note = "test image" source.image = dict(fileName="test image", xScale=1, xyScale=1, yxScale=1, yScale=1, xOffset=0, yOffset=0, color=None) source.anchors = [dict(x=100, y=200, name="test anchor")] source.guidelines = [dict(x=10, y=20, name="test guideline")] source.lib = {"foo": "bar"} pen = source.getPointPen() pen.beginPath() pen.addPoint((100, 200), segmentType="line") pen.addPoint((300, 400), segmentType="line") pen.endPath() component = Component() component.base = "b" source.appendComponent(component) dest = Glyph() dest.copyDataFromGlyph(source) self.assertNotEqual(source.name, dest.name) self.assertEqual(source.width, dest.width) self.assertEqual(source.height, dest.height) self.assertEqual(source.unicodes, dest.unicodes) self.assertEqual(source.note, dest.note) self.assertEqual(source.image.items(), dest.image.items()) self.assertEqual([g.items() for g in source.guidelines], [g.items() for g in dest.guidelines]) self.assertEqual([g.items() for g in source.anchors], [g.items() for g in dest.anchors]) self.assertEqual(len(source), len(dest)) self.assertEqual(len(source.components), len(dest.components)) sourceContours = [] for contour in source: sourceContours.append([]) for point in contour: sourceContours[-1].append((point.x, point.x, point.segmentType, point.name)) destContours = [] for contour in dest: destContours.append([]) for point in contour: destContours[-1].append((point.x, point.x, point.segmentType, point.name)) self.assertEqual(sourceContours, destContours) self.assertEqual(source.components[0].baseGlyph, dest.components[0].baseGlyph)
def getGlyph(self): glyph = Glyph() pointPen = glyph.getPointPen() self.drawPoints(pointPen) return glyph
class OutlinePen(BasePen): pointClass = MathPoint magicCurve = 0.5522847498 def __init__(self, glyphSet, offset=10, contrast=0, contrastAngle=0, connection="square", cap="round", miterLimit=None, closeOpenPaths=True, preserveComponents=False): 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.closeOpenPaths = closeOpenPaths 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.preserveComponents = preserveComponents self.components = [] self.drawSettings() def _moveTo(self, xyFix): (x, y) = xyFix 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, xyFix): (x, y) = xyFix 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, xyFix1, xyFix2, xyFix3): (x1, y1) = xyFix1 (x2, y2) = xyFix2 (x3, y3) = xyFix3 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 if self.offset == 0: self.outerPen.closePath() self.innerPen.closePath() return if not self.prevPoint == self.firstPoint: self._lineTo(self.firstPoint) self.originalPen.closePath() self.innerPrevPoint = self.innerCurrentPoint self.innerCurrentPoint = self.innerFirstPoint self.outerPrevPoint = self.outerCurrentPoint self.outerCurrentPoint = self.outerFirstPoint self.prevAngle = self.currentAngle self.currentAngle = self.firstAngle self.buildConnection(close=True) self.innerPen.closePath() self.outerPen.closePath() def _endPath(self): if self.shouldHandleMove: return self.originalPen.endPath() self.innerPen.endPath() self.outerPen.endPath() if self.closeOpenPaths: 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 addComponent(self, glyphName, transform): if self.preserveComponents: self.components.append((glyphName, transform)) else: BasePen.addComponent(self, 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 connectionSquare(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 not None: if self._inputmiterLimit is not None and roundFloat( newPoint.distance(first)) > self._inputmiterLimit: pen.lineTo(tempFirst) pen.lineTo(tempLast) else: pen.lineTo(newPoint) if not close: pen.lineTo(last) 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 #print "(%s, %s)," % (newPoint.x, newPoint.y) distance1 = newPoint.distance(first) distance2 = newPoint.distance(last) #print distance1, distance2 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 connectionButt(self, first, last, pen, close): if not close: pen.lineTo(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 capButt(self, firstContour, lastContour, first, last, angle): ## not nothing pass 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 capSquare(self, firstContour, lastContour, first, last, angle): angle = radians(degrees(angle) + 90) firstContour[-1].smooth = True lastContour[0].smooth = True p1 = first - self.pointClass(cos(angle), sin(angle)) * self.offset firstContour.addPoint((p1.x, p1.y), smooth=False, segmentType="line") p2 = last - self.pointClass(cos(angle), sin(angle)) * self.offset firstContour.addPoint((p2.x, p2.y), smooth=False, segmentType="line") 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
class OutlinePen(BasePen): pointClass = MathPoint magicCurve = 0.5522847498 def __init__(self, glyphSet, offset=10, connection="square", cap="round", miterLimit=None, closeOpenPaths=True): BasePen.__init__(self, glyphSet) self.offset = abs(offset) self._inputmiterLimit = miterLimit if miterLimit is None: miterLimit = self.offset self.miterLimit = abs(miterLimit) self.closeOpenPaths = closeOpenPaths self.connectionCallback = getattr( self, "connection%s" % (connection[0].capitalize() + connection[1:])) self.capCallback = getattr(self, "cap%s" % (cap[0].capitalize() + cap[1:])) 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.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
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 test_identifiers(self): glyph = Glyph() pointPen = glyph.getPointPen() pointPen.beginPath(identifier="contour 1") pointPen.addPoint((0, 0), identifier="point 1") pointPen.addPoint((0, 0), identifier="point 2") pointPen.endPath() pointPen.beginPath(identifier="contour 2") pointPen.endPath() pointPen.addComponent("A", (1, 1, 1, 1, 1, 1), identifier="component 1") pointPen.addComponent("A", (1, 1, 1, 1, 1, 1), identifier="component 2") guideline = Guideline() guideline.identifier = "guideline 1" glyph.appendGuideline(guideline) guideline = Guideline() guideline.identifier = "guideline 2" glyph.appendGuideline(guideline) self.assertEqual([contour.identifier for contour in glyph], ["contour 1", "contour 2"]) self.assertEqual([point.identifier for point in glyph[0]], ["point 1", "point 2"]) self.assertEqual( [component.identifier for component in glyph.components], ["component 1", "component 2"]) with self.assertRaises(AssertionError): pointPen.beginPath(identifier="contour 1") pointPen.endPath() pointPen.beginPath() pointPen.addPoint((0, 0)) with self.assertRaises(AssertionError): pointPen.addPoint((0, 0), identifier="point 1") pointPen.endPath() with self.assertRaises(AssertionError): pointPen.addComponent("A", (1, 1, 1, 1, 1, 1), identifier="component 1") g = Guideline() g.identifier = "guideline 1" with self.assertRaises(AssertionError): glyph.appendGuideline(g) self.assertEqual( sorted(glyph.identifiers), ["component 1", "component 2", "contour 1", "contour 2", "guideline 1", "guideline 2", "point 1", "point 2"]) glyph.removeContour(glyph[0]) self.assertEqual( sorted(glyph.identifiers), ["component 1", "component 2", "contour 2", "guideline 1", "guideline 2"]) glyph.removeComponent(glyph.components[0]) self.assertEqual( sorted(glyph.identifiers), ["component 2", "contour 2", "guideline 1", "guideline 2"]) glyph.removeGuideline(glyph.guidelines[0]) self.assertEqual( sorted(glyph.identifiers), ["component 2", "contour 2", "guideline 2"])
class OutlinePen(BasePen): pointClass = MathPoint magicCurve = 0.5522847498 def __init__(self, glyphSet, offset=10, contrast=0, contrastAngle=0, connection="square", cap="round", miterLimit=None, closeOpenPaths=True, preserveComponents=False): 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.closeOpenPaths = closeOpenPaths 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.preserveComponents = preserveComponents self.components = [] self.drawSettings() def _moveTo(self, xy): (x, y) = xy 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, xy): (x, y) = xy 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, xy1, xy2, xy3): (x1, y1), (x2, y2), (x3, y3) = xy1, xy2, xy3 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 if self.offset == 0: self.outerPen.closePath() self.innerPen.closePath() return if not self.prevPoint == self.firstPoint: self._lineTo(self.firstPoint) self.originalPen.closePath() self.innerPrevPoint = self.innerCurrentPoint self.innerCurrentPoint = self.innerFirstPoint self.outerPrevPoint = self.outerCurrentPoint self.outerCurrentPoint = self.outerFirstPoint self.prevAngle = self.currentAngle self.currentAngle = self.firstAngle self.buildConnection(close=True) self.innerPen.closePath() self.outerPen.closePath() def _endPath(self): if self.shouldHandleMove: return self.originalPen.endPath() self.innerPen.endPath() self.outerPen.endPath() if self.closeOpenPaths: 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 addComponent(self, glyphName, transform): if self.preserveComponents: self.components.append((glyphName, transform)) else: BasePen.addComponent(self, 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 connectionSquare(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 not None: if self._inputmiterLimit is not None and roundFloat(newPoint.distance(first)) > self._inputmiterLimit: pen.lineTo(tempFirst) pen.lineTo(tempLast) else: pen.lineTo(newPoint) if not close: pen.lineTo(last) 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 #print "(%s, %s)," % (newPoint.x, newPoint.y) distance1 = newPoint.distance(first) distance2 = newPoint.distance(last) #print distance1, distance2 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 connectionButt(self, first, last, pen, close): if not close: pen.lineTo(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 capButt(self, firstContour, lastContour, first, last, angle): ## not nothing pass 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 capSquare(self, firstContour, lastContour, first, last, angle): angle = radians(degrees(angle)+90) firstContour[-1].smooth = True lastContour[0].smooth = True p1 = first - self.pointClass(cos(angle), sin(angle)) * self.offset firstContour.addPoint((p1.x, p1.y), smooth=False, segmentType="line") p2 = last - self.pointClass(cos(angle), sin(angle)) * self.offset firstContour.addPoint((p2.x, p2.y), smooth=False, segmentType="line") 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