def calculate_pathop(pen1, pen2, operation): if USE_SKIA_PATHOPS: p1 = Path() pen1.replay(p1.getPen()) if operation == BooleanOp.Simplify: # ignore pen2 p1.simplify(fix_winding=True, keep_starting_points=True) d0 = RecordingPen() p1.draw(d0) return d0.value if pen2: p2 = Path() pen2.replay(p2.getPen()) builder = OpBuilder(fix_winding=True, keep_starting_points=True) builder.add(p1, PathOp.UNION) if pen2: builder.add(p2, BooleanOp.Skia(operation)) result = builder.resolve() d0 = RecordingPen() result.draw(d0) return d0.value else: bg2 = BooleanGlyph() if pen2: pen2.replay(bg2.getPen()) bg = BooleanGlyph() pen1.replay(bg.getPen()) bg = bg._booleanMath(BooleanOp.BooleanGlyphMethod(operation), bg2) dp = RecordingPen() bg.draw(dp) return dp.value
def filter(self, glyph): matrix = self.context.matrix if matrix == Identity or not (glyph or glyph.components or glyph.anchors): return False # nothing to do modified = self.context.modified glyphSet = self.context.glyphSet for component in glyph.components: base_name = component.baseGlyph if base_name in modified: continue base_glyph = glyphSet[base_name] if self.include(base_glyph) and self.filter(base_glyph): # base glyph is included but was not transformed yet; we # call filter recursively until all the included bases are # transformed, or there are no more components modified.add(base_name) rec = RecordingPen() glyph.draw(rec) glyph.clearContours() glyph.clearComponents() outpen = glyph.getPen() filterpen = TransformPen(outpen, matrix, modified) rec.replay(filterpen) # anchors are not drawn through the pen API, # must be transformed separately for a in glyph.anchors: a.x, a.y = matrix.transformPoint((a.x, a.y)) return True
def test_rules_are_applied_deterministically(data_dir): """Test that a combination of designspace rules that end up mapping serveral input glyphs to the same destination glyph result in a correct and deterministic series of glyph swaps. The example is a font with 2 Q designs that depend on a style axis style < 0.5: Q style >= 0.5: Q.ss01 and each Q also has an alternative shape in bolder weights (like Skia) weight < 780: Q weight >= 780: Q.alt weight < 730: Q.ss01 weight >= 730: Q.ss01.alt Then we generate an instance at style = 1, weight = 900. From the rules, the default CMAP entry for Q should have the outlines of Q.ss01.alt from the black UFO. """ doc = designspaceLib.DesignSpaceDocument.fromfile( data_dir / "DesignspaceRuleOrder" / "MyFont.designspace") instanciator = fontmake.instantiator.Instantiator.from_designspace(doc) instance = instanciator.generate_instance(doc.instances[0]) pen = RecordingPen() instance["Q"].draw(pen) instance_recording = pen.value black_ufo = ufoLib2.Font.open(data_dir / "DesignspaceRuleOrder" / "MyFont_Black.ufo") pen = RecordingPen() black_ufo["Q.ss01.alt"].draw(pen) black_ufo_recording = pen.value assert instance_recording == black_ufo_recording
def test_equivalent_paths(pathdef1, pathdef2): pen1 = RecordingPen() parse_path(pathdef1, pen1) pen2 = RecordingPen() parse_path(pathdef2, pen2) assert pen1.value == pen2.value
def test_getDrawToPen(): ftf, ttfGlyphSet = _getFonts("IBMPlexSans-Regular.ttf") for glyphName in ["a", "B", "O", "period"]: refPen = RecordingPen() ttfGlyphSet[glyphName].draw(refPen) pen = RecordingPen() ftf.drawGlyphToPen(glyphName, pen) assert pen.value == refPen.value
def test_draw_vs_drawpoints(self): font = TTFont(sfntVersion="\x00\x01\x00\x00") font.importXML(GLYF_TTX) glyfTable = font['glyf'] pen1 = RecordingPen() pen2 = RecordingPen() glyfTable["glyph00003"].draw(pen1, glyfTable) glyfTable["glyph00003"].drawPoints(PointToSegmentPen(pen2), glyfTable) self.assertEqual(pen1.value, pen2.value)
def spikeGlyph(aGlyph, segmentLength=20, spikeLength=40, patternFunc=None): from fontTools.pens.recordingPen import RecordingPen recorder = RecordingPen() spikePen = SpikePen(recorder, spikeLength=spikeLength, patternFunc=patternFunc) filterPen = FlattenPen(spikePen, approximateSegmentLength=segmentLength, segmentLines=True) aGlyph.draw(filterPen) aGlyph.clear() recorder.replay(aGlyph.getPen()) return aGlyph
def makeShadow(g, extrusionX, extrusionY): destPen = RecordingPen() myPen = TranslationPen(destPen) g.draw(myPen) destPen.replay(g.getPen()) if g.rightMargin%2==0: g.rightMargin+=int(extrusionX-20) #20 is compensation for frontWidth in the TranslationPen constructor else: g.rightMargin+=int(extrusionX-19) g.changed()
def thresholdGlyph(aGlyph, threshold=10): """ Convenience function that applies the **ThresholdPen** to a glyph in place. """ from fontTools.pens.recordingPen import RecordingPen recorder = RecordingPen() filterpen = ThresholdPen(recorder, threshold) aGlyph.draw(filterpen) aGlyph.clear() recorder.replay(aGlyph.getPen()) return aGlyph
def explode(self): """Read each contour into its own DATPen; returns a DATPens""" dp = RecordingPen() ep = ExplodingPen(dp) self.replay(ep) dps = self.multi_pen_class() for p in ep._pens: dp = type(self)() dp.value = p dp.attrs = deepcopy(self.attrs) dps.append(dp) return dps
def flattenGlyph(aGlyph, threshold=10, segmentLines=True): """ Convenience function that applies the **FlattenPen** pen to a glyph in place. """ if len(aGlyph) == 0: return aGlyph from fontTools.pens.recordingPen import RecordingPen recorder = RecordingPen() filterpen = FlattenPen(recorder, approximateSegmentLength=threshold, segmentLines=segmentLines) aGlyph.draw(filterpen) aGlyph.clear() recorder.replay(aGlyph.getPen()) return aGlyph
def samplingGlyph(aGlyph, steps=10): """ Convenience function that applies the **SamplingPen** pen to a glyph in place. """ if len(aGlyph) == 0: return aGlyph from fontTools.pens.recordingPen import RecordingPen recorder = RecordingPen() filterpen = SamplingPen(recorder, steps=steps) aGlyph.draw(filterpen) aGlyph.clear() recorder.replay(aGlyph.getPen()) return aGlyph
def explode(self, into_set=False): """Read each contour into its own DATPen (or DATPenSet if `into_set` is True); returns a DATPenSet""" dp = RecordingPen() ep = ExplodingPen(dp) self.replay(ep) dps = DATPenSet() for p in ep.pens: dp = DATPen() dp.value = p dp.attrs = deepcopy(self.attrs) if into_set: dps.append(DATPenSet([dp])) else: dps.append(dp) return dps
def post(): phrase = request.form['data'] logger.info('got post request from app: phrase = "{}"'.format(phrase)) seq = get_model_output(phrase) logger.info('receieved model output') paths = [] shape = shape_dict.get(seq[0], 0) if shape: # TODO: 入れ子の考慮 seq = seq[1:(3 if shape not in {3, 4} else 4)] for char in seq: recording_pen = RecordingPen() glyph = get_glyph(glyph_set, cmap, char) if glyph: glyph.draw(recording_pen) paths.append(recording_pen.value) logger.info('return kanji path data to app') return jsonify({ "shape": shape, "paths": {i: { "path": path } for i, path in enumerate(paths)} })
def test_reverse_point_pen(contour, expected): try: from ufoLib.pointPen import (ReverseContourPointPen, PointToSegmentPen, SegmentToPointPen) except ImportError: pytest.skip("ufoLib not installed") recpen = RecordingPen() pt2seg = PointToSegmentPen(recpen, outputImpliedClosingLine=True) revpen = ReverseContourPointPen(pt2seg) seg2pt = SegmentToPointPen(revpen) for operator, operands in contour: getattr(seg2pt, operator)(*operands) # for closed contours that have a lineTo following the moveTo, # and whose points don't overlap, our current implementation diverges # from the ReverseContourPointPen as wrapped by ufoLib's pen converters. # In the latter case, an extra lineTo is added because of # outputImpliedClosingLine=True. This is redundant but not incorrect, # as the number of points is the same in both. if (contour and contour[-1][0] == "closePath" and contour[1][0] == "lineTo" and contour[1][1] != contour[0][1]): expected = expected[:-1] + [("lineTo", contour[0][1])] + expected[-1:] assert recpen.value == expected
def addSmoothPoints(self, length=100): """WIP""" rp = RecordingPen() fp = SmoothPointsPen(rp) self.replay(fp) self.value = rp.value return self
def _build_CFF_contour_list(self): if self._contourlist is None: pen = RecordingPen() self.naked().draw(pen) contours = pen.value lastcontour = [] self._contourlist = [] startPt = (0, 0) lastPt = (0, 0) index = 0 for c in contours: if c[0] == "moveTo": startPt = c[1][0] elif c[0] == "closePath": if startPt != lastPt: lastcontour.append( defcon.Point(startPt, segmentType="line")) contour = self.contourClass(wrap=lastcontour, index=index) self._contourlist.append(contour) index = index + 1 lastcontour = [] elif c[0] == "curveTo": lastcontour.append( defcon.Point(c[1][0], segmentType="offcurve")) lastcontour.append( defcon.Point(c[1][1], segmentType="offcurve")) lastcontour.append( defcon.Point(c[1][2], segmentType="curve")) lastPt = c[1][2] elif c[0] == "lineTo": lastcontour.append( defcon.Point(c[1][0], segmentType="line")) lastPt = c[1][0] elif c[0] == "qCurveTo": self.raiseNotImplementedError()
def test_bit6_draw_to_pen_issue1771(self): # https://github.com/fonttools/fonttools/issues/1771 font = TTFont(sfntVersion="\x00\x01\x00\x00") # glyph00003 contains a bit 6 flag on the first point, # which triggered the issue font.importXML(GLYF_TTX) glyfTable = font['glyf'] pen = RecordingPen() glyfTable["glyph00003"].draw(pen, glyfTable=glyfTable) expected = [('moveTo', ((501, 1430),)), ('lineTo', ((683, 1430),)), ('lineTo', ((1172, 0),)), ('lineTo', ((983, 0),)), ('lineTo', ((591, 1193),)), ('lineTo', ((199, 0),)), ('lineTo', ((12, 0),)), ('lineTo', ((501, 1430),)), ('closePath', ()), ('moveTo', ((249, 514),)), ('lineTo', ((935, 514),)), ('lineTo', ((935, 352),)), ('lineTo', ((249, 352),)), ('lineTo', ((249, 514),)), ('closePath', ())] self.assertEqual(pen.value, expected)
def traceFont(font, char): glyphSet = font.getGlyphSet() cmap = font.getBestCmap() glyph = getGlyph(glyphSet, cmap, char) recordingPen = RecordingPen() glyph.draw(recordingPen) return recordingPen.value
def test_draw(self): from fontTools.pens.recordingPen import RecordingPen component = self.getComponent_generic() component.transformation = (1, 2, 3, 4, 5, 6) pen = RecordingPen() component.draw(pen) expected = [('addComponent', ('A', (1.0, 2.0, 3.0, 4.0, 5.0, 6.0)))] self.assertEqual(pen.value, expected)
def __init__(self, g, inc=0.0015): if isinstance(g, RecordingPen): self.pen = g else: self.pen = RecordingPen() g.draw(self.pen) self.inc = inc self.length = self.calcCurveLength()
def test_pen_closePath(self): # Test CFF2/T2 charstring: it does NOT end in "endchar" # https://github.com/fonttools/fonttools/issues/2455 cs = self.stringToT2CharString( "100 100 rmoveto -50 -150 200 0 -50 150 rrcurveto") pen = RecordingPen() cs.draw(pen) self.assertEqual(pen.value[-1], ('closePath', ()))
def Record(recording, offset=1): op = OutlinePen(None, offset=offset, optimizeCurve=True) replayRecording(recording.value, op) op.drawSettings(drawInner=True, drawOuter=True) g = op.getGlyph() rp2 = RecordingPen() g.draw(rp2) return rp2
def generate_glyph_image(font): for index, label in enumerate(LABELS): bitmap_output_path = path.join(BITMAP_DIR, str(index) + '_' + label, font['post_script_name'] + ".png") if path.exists(bitmap_output_path): continue # bitmap canvas = Image.new("RGB", (IMAGE_WIDTH, IMAGE_HEIGHT), "black") draw = ImageDraw.Draw(canvas) ifont = ImageFont.truetype(font['path'], IMAGE_WIDTH - 10) w, h = draw.textsize(label, font=ifont) draw.text(((IMAGE_WIDTH - w) / 2, (IMAGE_HEIGHT - h) / 2), label, font=ifont, fill="white") canvas.save(bitmap_output_path) # vector vector_output_path = path.join(PREPROCESSED_DIR, font['post_script_name'] + '.ttx') if path.exists(vector_output_path): return ttfont = TTFont(font['path']) glyph_set = ttfont.getGlyphSet() cmap = ttfont.getBestCmap() ttfont.saveXML(vector_output_path) ascender = ttfont['OS/2'].sTypoAscender descender = ttfont['OS/2'].sTypoDescender height = ascender - descender for index, label in enumerate(LABELS): glyph_name = cmap[ord(label)] glyph = glyph_set[glyph_name] width = glyph.width pen = RecordingPen() glyph.draw(pen) # [[x, y, isPenDown, isControlPoint, isContourEnd, isGlyphEnd], ...] matrix = [] for command in pen.value: name = command[0] points = command[1] print('name:', name) print('points:', points) # if name == 'closePath': # pass # if name == 'moveTo': # matrix.append((points[0][0], points[0][1], 0, 0, 0, 0)) # elif name == 'qCurveTo': # matrix.append((points[0][0], points[0][1], 1, 1, 0, 0)) # matrix.append((points[1][0], points[1][1], 1, 0, 0, 0)) # elif name == 'lineTo': # matrix.append((points[1][0], points[1][1], 1, 0, 0, 0)) os.exit()
def _addOutlinePathToGlyph(self, glyph): if self.cocoa: pen = CocoaPen(self.glyphSet) glyph.draw(pen) glyph.outline = pen.path else: pen = RecordingPen() glyph.draw(pen) glyph.outline = pen
def getOutline(self, cocoa=True): if cocoa: pen = CocoaPen(None) # by now there are no more composites self.draw(pen) return pen.path else: pen = RecordingPen() self.draw(pen) return pen
def transform(self, transform, transformFrame=True): """Perform an arbitrary transformation on the pen, using the fontTools `Transform` class.""" op = RecordingPen() tp = TransformPen(op, transform) self.replay(tp) self.value = op.value if transformFrame and self.frame: self.frame = self.frame.transform(transform) return self
def getOutline(self, cocoa=True): if cocoa: return makePathFromArrays(self.getPoints(), self.tags, self.contours) else: #print(self.tags) rp = RecordingPen() self.draw(rp) return rp
def test_from_svg_file(self): pen = RecordingPen() with NamedTemporaryFile(delete=False) as tmp: tmp.write(tobytes(SVG_DATA)) try: svg = SVGPath(tmp.name) svg.draw(pen) finally: os.remove(tmp.name) assert pen.value == EXPECTED_PEN_COMMANDS
def test_exponents(): # It can be e or E, the plus is optional, and a minimum of +/-3.4e38 must be supported. pen = RecordingPen() parse_path("M-3.4e38 3.4E+38L-3.4E-38,3.4e-38", pen) expected = [ ("moveTo", ((-3.4e+38, 3.4e+38), )), ("lineTo", ((-3.4e-38, 3.4e-38), )), ("endPath", ()), ] assert pen.value == expected
def test_addComponent(self): pen = RecordingPen() pen.addComponent("a", (2, 0, 0, 3, -10, 5)) assert pen.value == [("addComponent", ("a", (2, 0, 0, 3, -10, 5)))]