Last active
December 5, 2023 13:41
-
-
Save CatherineH/499a312a04582a00e7559ac0c8f133fa to your computer and use it in GitHub Desktop.
Convert a text character to an SVG path.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from svgpathtools import wsvg, Line, QuadraticBezier, Path | |
from freetype import Face | |
def tuple_to_imag(t): | |
return t[0] + t[1] * 1j | |
face = Face('./Vera.ttf') | |
face.set_char_size(48 * 64) | |
face.load_char('a') | |
outline = face.glyph.outline | |
y = [t[1] for t in outline.points] | |
# flip the points | |
outline_points = [(p[0], max(y) - p[1]) for p in outline.points] | |
start, end = 0, 0 | |
paths = [] | |
for i in range(len(outline.contours)): | |
end = outline.contours[i] | |
points = outline_points[start:end + 1] | |
points.append(points[0]) | |
tags = outline.tags[start:end + 1] | |
tags.append(tags[0]) | |
segments = [[points[0], ], ] | |
for j in range(1, len(points)): | |
segments[-1].append(points[j]) | |
if tags[j] and j < (len(points) - 1): | |
segments.append([points[j], ]) | |
for segment in segments: | |
if len(segment) == 2: | |
paths.append(Line(start=tuple_to_imag(segment[0]), | |
end=tuple_to_imag(segment[1]))) | |
elif len(segment) == 3: | |
paths.append(QuadraticBezier(start=tuple_to_imag(segment[0]), | |
control=tuple_to_imag(segment[1]), | |
end=tuple_to_imag(segment[2]))) | |
elif len(segment) == 4: | |
C = ((segment[1][0] + segment[2][0]) / 2.0, | |
(segment[1][1] + segment[2][1]) / 2.0) | |
paths.append(QuadraticBezier(start=tuple_to_imag(segment[0]), | |
control=tuple_to_imag(segment[1]), | |
end=tuple_to_imag(C))) | |
paths.append(QuadraticBezier(start=tuple_to_imag(C), | |
control=tuple_to_imag(segment[2]), | |
end=tuple_to_imag(segment[3]))) | |
else: | |
print(f"incompatible segment length: {len(segment)}") | |
start = end + 1 | |
path = Path(*paths) | |
wsvg(path, filename="text.svg") |
... and with ctx:
svg = """
<svg xmlns="http://www.w3.org/2000/svg"
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
<path
transform="scale(0.00338) scale(10)"
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
d="{}"
/>
""".format(" ".join(ctx))
print(svg)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
as test with char 'B' and
face = Face('C:\Windows\Fonts\arial.ttf')
there are some segments of length 5 and 6
this seems to be Ok (as TrueType infact has only quads):
...
elif len(segment) == 5:
C12 = segment[1]
C23 = segment[2]
C34 = segment[3]
By the way:
def move_to(a, ctx):
ctx.append("M {},{}".format(a.x, a.y))
def line_to(a, ctx):
ctx.append("L {},{}".format(a.x, a.y))
def conic_to(a, b, ctx):
ctx.append("Q {},{} {},{}".format(a.x, a.y, b.x, b.y))
def cubic_to(a, b, c, ctx):
ctx.append("C {},{} {},{} {},{}".format(a.x, a.y, b.x, b.y, c.x, c.y))
ctx = []
outline.decompose(ctx, move_to=move_to, line_to=line_to, conic_to=conic_to, cubic_to=cubic_to)
works, but gives svg "y" in the opposite direction
https://github.com/rougier/freetype-py/releases
https://github.com/rougier/freetype-py/blob/master/examples/glyph-vector-decompose.py
Has someone here understood the face.set_char_size() and how to get a 'reasonable' svg font size ?
how to get the right scaling back to for example to style="font-size=11" , when working with svg ?
Thanks in advance,
XM