def _round_path( path: pathops.Path, round: Callable[[float], float] = otRound ) -> pathops.Path: rounded_path = pathops.Path() for verb, points in path: rounded_path.add(verb, *((round(p[0]), round(p[1])) for p in points)) return rounded_path
def __init__(self, subject: VMobject, clip: VMobject, **kwargs): super().__init__(**kwargs) outpen = pathops.Path() pathops.difference( [_convert_vmobject_to_skia_path(subject)], [_convert_vmobject_to_skia_path(clip)], outpen.getPen(), ) _convert_skia_path_to_vmobject(outpen, self)
def __init__(self, *vmobjects: VMobject, **kwargs): if len(vmobjects) < 2: raise ValueError("At least 2 mobjects needed for Union.") super().__init__(**kwargs) outpen = pathops.Path() paths = [ _convert_vmobject_to_skia_path(vmobject) for vmobject in vmobjects ] pathops.union(paths, outpen.getPen()) _convert_skia_path_to_vmobject(outpen, self)
def __init__(self, *vmobjects: VMobject, **kwargs): if len(vmobjects) < 2: raise ValueError("At least 2 mobjects needed for Exclusion.") super().__init__(**kwargs) outpen = pathops.Path() pathops.xor( [_convert_vmobject_to_skia_path(vmobjects[0])], [_convert_vmobject_to_skia_path(vmobjects[1])], outpen.getPen(), ) new_outpen = outpen for _i in range(2, len(vmobjects)): new_outpen = pathops.Path() pathops.xor( [outpen], [_convert_vmobject_to_skia_path(vmobjects[_i])], new_outpen.getPen(), ) outpen = new_outpen _convert_skia_path_to_vmobject(outpen, self)
def skia_path(svg_cmds: SVGCommandSeq, fill_rule: str) -> pathops.Path: try: fill_type = _SVG_FILL_RULE_TO_SKIA_FILL_TYPE[fill_rule] except KeyError: raise ValueError(f"Invalid fill rule: {fill_rule!r}") sk_path = pathops.Path(fillType=fill_type) for cmd, args in svg_cmds: if cmd not in _SVG_CMD_TO_SKIA_FN: raise ValueError(f'No mapping to Skia for "{cmd} {args}"') _SVG_CMD_TO_SKIA_FN[cmd](sk_path, *args) return sk_path
def _convert_vmobject_to_skia_path(vmobject: VMobject) -> pathops.Path: path = pathops.Path() subpaths = vmobject.get_subpaths_from_points(vmobject.get_all_points()) for subpath in subpaths: quads = vmobject.get_bezier_tuples_from_points(subpath) start = subpath[0] path.moveTo(*start[:2]) for p0, p1, p2 in quads: path.quadTo(*p1[:2], *p2[:2]) if vmobject.consider_points_equals(subpath[0], subpath[-1]): path.close() return path
def skia_path(shape: SVGShape): path = ( shape.as_path().explicit_lines() # hHvV => lL .absolute(inplace=True).arcs_to_cubics(inplace=True)) sk_path = pathops.Path() for cmd, args in path: if cmd not in _SVG_CMD_TO_SKIA_FN: raise ValueError(f'No mapping to Skia for "{cmd}"') _SVG_CMD_TO_SKIA_FN[cmd](sk_path, *args) return sk_path
def ttfont_glyph_to_skia_path(glyph_name: str, tt_font: ttFont.TTFont) -> pathops.Path: """ Converts fontTools.ttLib.TTFont glyph to a pathops.Path object by glyph name. During this conversion, all composite paths are decomposed. """ glyf_table = tt_font["glyf"] glyph_set: ttFont._TTGlyphSet = tt_font.getGlyphSet() tt_glyph = glyf_table[glyph_name] skia_path = pathops.Path() skia_path_pen = skia_path.getPen() if tt_glyph.isComposite(): decompose_pen = DecomposingRecordingPen(glyph_set) glyph_set[glyph_name].draw(decompose_pen) decompose_pen.replay(skia_path_pen) return skia_path else: glyph_set[glyph_name].draw(skia_path_pen) return skia_path
def decomposeAndRemoveOverlap(font, glyphName): glyfTable = font["glyf"] glyphSet = font.getGlyphSet() # record TTGlyph outlines without components dcPen = DecomposingRecordingPen(glyphSet) glyphSet[glyphName].draw(dcPen) # replay recording onto a skia-pathops Path path = pathops.Path() pathPen = path.getPen() dcPen.replay(pathPen) # remove overlaps path.simplify() # create new TTGlyph from Path ttPen = TTGlyphPen(None) path.draw(ttPen) glyfTable[glyphName] = ttPen.glyph()
src = sys.argv[1] dst = sys.argv[2] with TTFont(src) as f: glyfTable = f["glyf"] glyphSet = f.getGlyphSet() for glyphName in glyphSet.keys(): if not glyfTable[glyphName].isComposite(): continue # record TTGlyph outlines without components dcPen = DecomposingRecordingPen(glyphSet) glyphSet[glyphName].draw(dcPen) # replay recording onto a skia-pathops Path path = pathops.Path() pathPen = path.getPen() dcPen.replay(pathPen) # remove overlaps path.simplify() # create new TTGlyph from Path ttPen = TTGlyphPen(None) path.draw(ttPen) glyfTable[glyphName] = ttPen.glyph() f.save(dst)
def skPathFromGlyph(glyphName: str, glyphSet: _TTGlyphMapping) -> pathops.Path: path = pathops.Path() pathPen = path.getPen(glyphSet=glyphSet) glyphSet[glyphName].draw(pathPen) return path