hjc-owo
init repo
966ae59
import numpy as np
import freetype as ft
from . import bezier
def glyph_to_cubics(face, x=0):
"""Convert current font face glyph to cubic beziers"""
def linear_to_cubic(Q):
a, b = Q
return [a + (b - a) * t for t in np.linspace(0, 1, 4)]
def quadratic_to_cubic(Q):
return [Q[0],
Q[0] + (2 / 3) * (Q[1] - Q[0]),
Q[2] + (2 / 3) * (Q[1] - Q[2]),
Q[2]]
beziers = []
pt = lambda p: np.array([p.x + x, -p.y]) # Flipping here since freetype has y-up
last = lambda: beziers[-1][-1]
def move_to(a, beziers):
beziers.append([pt(a)])
def line_to(a, beziers):
Q = linear_to_cubic([last(), pt(a)])
beziers[-1] += Q[1:]
def conic_to(a, b, beziers):
Q = quadratic_to_cubic([last(), pt(a), pt(b)])
beziers[-1] += Q[1:]
def cubic_to(a, b, c, beziers):
beziers[-1] += [pt(a), pt(b), pt(c)]
face.glyph.outline.decompose(beziers, move_to=move_to, line_to=line_to, conic_to=conic_to, cubic_to=cubic_to)
beziers = [np.array(C).astype(float) for C in beziers]
return beziers
def font_string_to_beziers(font, txt, size=30, spacing=1.0, merge=True, target_control=None):
"""
Load a font and convert the outlines for a given string to cubic bezier curves,
if merge is True, simply return a list of all bezier curves,
otherwise return a list of lists with the bezier curves for each glyph
"""
face = ft.Face(font)
face.set_char_size(64 * size)
slot = face.glyph
x = 0
beziers = []
previous = 0
for c in txt:
face.load_char(c, ft.FT_LOAD_DEFAULT | ft.FT_LOAD_NO_BITMAP)
bez = glyph_to_cubics(face, x)
# Check number of control points if desired
if target_control is not None:
if c in target_control.keys():
nctrl = np.sum([len(C) for C in bez])
while nctrl < target_control[c]:
longest = np.max(
sum([[bezier.approx_arc_length(b) for b in bezier.chain_to_beziers(C)] for C in bez], []))
thresh = longest * 0.5
bez = [bezier.subdivide_bezier_chain(C, thresh) for C in bez]
nctrl = np.sum([len(C) for C in bez])
print("nctrl: ", nctrl)
if merge:
beziers += bez
else:
beziers.append(bez)
kerning = face.get_kerning(previous, c)
x += (slot.advance.x + kerning.x) * spacing
previous = c
return beziers
def bezier_chain_to_commands(C, closed=True):
curves = bezier.chain_to_beziers(C)
cmds = 'M %f %f ' % (C[0][0], C[0][1])
n = len(curves)
for i, bez in enumerate(curves):
if i == n - 1 and closed:
cmds += 'C %f %f %f %f %f %fz ' % (*bez[1], *bez[2], *bez[3])
else:
cmds += 'C %f %f %f %f %f %f ' % (*bez[1], *bez[2], *bez[3])
return cmds
def write_letter_svg(c, header, fontname, beziers, subdivision_thresh, dest_path):
cmds = ''
svg = header
path = '<g><path d="'
for C in beziers:
if subdivision_thresh is not None:
print('subd')
C = bezier.subdivide_bezier_chain(C, subdivision_thresh)
cmds += bezier_chain_to_commands(C, True)
path += cmds + '"/>\n'
svg += path + '</g></svg>\n'
fname = f"{dest_path}/{fontname}_{c}.svg"
fname = fname.replace(" ", "_")
with open(fname, 'w') as f:
f.write(svg)
return fname, path