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 = RecordingPointPen() glyph.drawPoints(rec) glyph.clearContours() glyph.clearComponents() outpen = glyph.getPointPen() filterpen = TransformPointPen(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 scaleGlyph(font, glyph, scale): """Scales the glyph, but keeps it centered around its original bounding box.""" from fontTools.pens.recordingPen import RecordingPointPen from fontTools.pens.transformPen import TransformPointPen from fontTools.misc.transform import Identity width = glyph.width bbox = glyph.getBounds(font) x = (bbox.xMin + bbox.xMax) / 2 y = (bbox.yMin + bbox.yMax) / 2 matrix = Identity matrix = matrix.translate(-x * scale + x, -y * scale + y) matrix = matrix.scale(scale) rec = RecordingPointPen() glyph.drawPoints(rec) glyph.clearContours() glyph.clearComponents() pen = TransformPointPen(glyph.getPointPen(), matrix) rec.replay(pen) if width == 0: glyph.width = width return matrix
def test_getDrawToPointPen(): ftf, ttfGlyphSet = _getFonts("IBMPlexSans-Regular.ttf") for glyphName in ["a", "B", "O", "period"]: refPen = RecordingPointPen() ttfGlyphSet[glyphName].drawPoints(refPen) pen = RecordingPointPen() ftf.drawGlyphToPointPen(glyphName, pen) assert pen.value == refPen.value
def test_getUpdateInfo(tmpdir): ufoSource = getFontPath("MutatorSansBoldWideMutated.ufo") ufoPath = shutil.copytree(ufoSource, tmpdir / "test.ufo") reader = UFOReader(ufoPath, validate=False) glyphSet = reader.getGlyphSet() cmap, unicodes, anchors = fetchCharacterMappingAndAnchors(glyphSet, ufoPath) state = UFOState(reader, glyphSet, getUnicodesAndAnchors=lambda: (unicodes, anchors)) feaPath = pathlib.Path(reader.fs.getsyspath("/features.fea")) feaPath.touch() state = state.newState() (needsFeaturesUpdate, needsGlyphUpdate, needsInfoUpdate, needsCmapUpdate, needsLibUpdate) = state.getUpdateInfo() assert needsFeaturesUpdate assert not needsGlyphUpdate assert not needsInfoUpdate assert not needsCmapUpdate infoPath = pathlib.Path(reader.fs.getsyspath("/fontinfo.plist")) infoPath.touch() state = state.newState() (needsFeaturesUpdate, needsGlyphUpdate, needsInfoUpdate, needsCmapUpdate, needsLibUpdate) = state.getUpdateInfo() assert not needsFeaturesUpdate assert not needsGlyphUpdate assert needsInfoUpdate assert not needsCmapUpdate glyph = Glyph("A", None) ppen = RecordingPointPen() glyphSet.readGlyph("A", glyph, ppen) glyph.anchors[0]["x"] = 123 glyphSet.writeGlyph("A", glyph, ppen.replay) state = state.newState() (needsFeaturesUpdate, needsGlyphUpdate, needsInfoUpdate, needsCmapUpdate, needsLibUpdate) = state.getUpdateInfo() assert needsFeaturesUpdate assert needsGlyphUpdate assert not needsInfoUpdate assert not needsCmapUpdate glyph = Glyph("A", None) ppen = RecordingPointPen() glyphSet.readGlyph("A", glyph, ppen) glyph.unicodes = [123] glyphSet.writeGlyph("A", glyph, ppen.replay) state = state.newState() (needsFeaturesUpdate, needsGlyphUpdate, needsInfoUpdate, needsCmapUpdate, needsLibUpdate) = state.getUpdateInfo() assert not needsFeaturesUpdate assert needsGlyphUpdate assert not needsInfoUpdate assert needsCmapUpdate
def checkGlyphAlternates(project): """Check whether alternate glyphs have base glyphs, and whether they are different from the base glyph. """ glyphSet = project.characterGlyphGlyphSet glyphNames = sorted(glyphSet.getGlyphNamesAndUnicodes()) for baseName, altNames in groupby(glyphNames, key=lambda gn: gn.split(".")[0]): altNames = list(altNames) if len(altNames) == 1: continue glyphs = [glyphSet.getGlyph(glyphName) for glyphName in altNames] locations = set() for g in glyphs: locations.update(tuplifyLocation(vg.location) for vg in g.variations) locations = [dict(loc) for loc in sorted(locations)] locations.insert(0, {}) for loc in locations: outlines = defaultdict(list) for g in glyphs: rpen = RecordingPointPen() try: project.drawPointsCharacterGlyph(g.name, loc, rpen) except InterpolationError as e: yield f"Skipping '{g.name}' {e}" else: outlines[tuplifyOutline(rpen.value)].append(g.name) for sameNames in outlines.values(): if len(sameNames) > 1: sameNames = [f"'{n}'" for n in sameNames] sameNames = ", ".join(sameNames[:-1]) + " and " + sameNames[-1] yield ( f"Glyphs {sameNames} are identical " f"at location {formatLocation(loc)}" )
def test_record_and_replay(self): pen = RecordingPointPen() glyph = _TestGlyph() glyph.drawPoints(pen) pen.addComponent("a", (2, 0, 0, 2, -10, 5)) assert pen.value == [ ("beginPath", (), { "identifier": "abc" }), ("addPoint", ((0.0, 0.0), "line", False, "start"), { "identifier": "0000" }), ("addPoint", ((0.0, 100.0), "line", False, None), { "identifier": "0001" }), ("addPoint", ((50.0, 75.0), None, False, None), { "identifier": "0002" }), ("addPoint", ((60.0, 50.0), None, False, None), { "identifier": "0003" }), ("addPoint", ((50.0, 0.0), "curve", True, "last"), { "identifier": "0004" }), ("endPath", (), {}), ("addComponent", ("a", (2, 0, 0, 2, -10, 5)), {}), ] pen2 = RecordingPointPen() pen.replay(pen2) assert pen2.value == pen.value
def test_bit6_draw_to_pointpen(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 = RecordingPointPen() glyfTable["glyph00003"].drawPoints(pen, glyfTable=glyfTable) expected = [ ('beginPath', (), {}), ('addPoint', ((501, 1430), 'line', False, None), {}), ('addPoint', ((683, 1430), 'line', False, None), {}), ('addPoint', ((1172, 0), 'line', False, None), {}), ('addPoint', ((983, 0), 'line', False, None), {}), ] self.assertEqual(pen.value[:len(expected)], expected)
def test_read_ensure_x_y(self): """Ensure that a proper GlifLibError is raised when point coordinates are missing, regardless of validation setting.""" s = """<?xml version='1.0' encoding='utf-8'?> <glyph name="A" format="2"> <outline> <contour> <point x="545" y="0" type="line"/> <point x="638" type="line"/> </contour> </outline> </glyph> """ pen = RecordingPointPen() with pytest.raises(GlifLibError, match="Required y attribute"): readGlyphFromString(s, _Glyph(), pen) with pytest.raises(GlifLibError, match="Required y attribute"): readGlyphFromString(s, _Glyph(), pen, validate=False)
path = BezierPath() path.moveTo((100, 100)) path.curveTo((200, 100), (300, 200), (300, 300)) path.lineTo((100, 300)) path.closePath() path.oval(0, 0, 200, 90) path.moveTo((250, 250)) path.arc((250, 250), 200, 0, 120, False) # path.skew() will trigger bad results # https://github.com/justvanrossum/drawbot-skia/issues/7 # path.skew(10, 20) fill(None) stroke(0) strokeWidth(2) drawPath(path) pen = RecordingPen() path.drawToPen(pen) path = BezierPath() pen.replay(path) stroke(None) fill(0, 0.3) drawPath(path) ppen = RecordingPointPen() path.drawToPointPen(ppen)