Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Create a svg from truetype font with freetype and svgpathtools in python

Create a svg from truetype font in python

I am trying to create a svg file from a truetype font with freetype-py and svgpathtools. I have a working python code which generates the svg file but unfortunately there are some corners and edges where instead should be smooth curves. Here in this example I try to render an special char which does not look good at all.

Question

What do I need to change in order to get curve symbols redered correctly?

Prerequisites

pip3 install freetype-py
pip3 install svgpathtools
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-

import sys

from freetype import Face, FT_Curve_Tag, FT_Curve_Tag_On
from svgpathtools import (wsvg, Line, CubicBezier, QuadraticBezier, Path)



fontpath = sys.argv[1]

face = Face(fontpath)
face.set_char_size(128 * 256)

for char in face.get_chars():
    # char = chr(char[0])
    char = 'ȸ'
    non_chars = [32, 160, 847, 858, 1995, 8192, 8193, 8194, 8195, 8196,
                 8197, 8198, 8199, 8200, 8201, 8202, 8203, 8204, 8205,
                 8206, 8207, 8209, 8232, 8233, 8234, 8235, 8236, 8237,
                 8238, 8239, 8287, 8288, 8289, 8290, 8291, 8292, 8293,
                 8294, 8295, 8296, 8297, 8298, 8299, 8300, 8301, 8302,
                 8303, 9864, 10240, 11604, 65024, 65025, 65026, 65027,
                 65028, 65029, 65030, 65031, 65032, 65033, 65034, 65035,
                 65036, 65037, 65038, 65039, 65279, 65529, 65530, 65531,
                 65532, 65533, 66319]

    if ord(char) in non_chars:
        continue

    face.load_char(char)

    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]
        print(points)
        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 (FT_Curve_Tag(tags[j]) == FT_Curve_Tag_On) and j < (len(points) - 1):
                segments.append([points[j], ])

        for segment in segments:
            if len(segment) == 10:
                print(char)
                sys.exit(1)

            if len(segment) == 2:
                paths.append(Line(
                    start=tuple_to_image(segment[0]),
                    end=tuple_to_image(segment[1])))

            elif len(segment) == 3:
                print('0', segment[0])
                print('1', segment[1])
                print('2', segment[2])

                paths.append(QuadraticBezier(
                    start=tuple_to_image(segment[0]),
                    control=tuple_to_image(segment[1]),
                    end=tuple_to_image(segment[2])))

            elif len(segment) == 4:
                C = ((segment[1][0] + segment[2][0]) / 2.0,
                     (segment[1][1] + segment[2][1]) / 2.0)

                print('0', segment[0])
                print('1', segment[1])
                print('C', C)
                print('2', segment[2])
                print('3', segment[3])

                paths.append(QuadraticBezier(
                    start=tuple_to_image(segment[0]),
                    control=tuple_to_image(segment[1]),
                    end=tuple_to_image(C)))

                paths.append(QuadraticBezier(
                    start=tuple_to_image(C),
                    control=tuple_to_image(segment[2]),
                    end=tuple_to_image(segment[3])))

            elif len(segment) == 5:
                C = ((segment[1][0] + segment[2][0]) / 2.0,
                     (segment[1][1] + segment[2][1]) / 2.0)

                paths.append(QuadraticBezier(
                    start=tuple_to_image(segment[0]),
                    control=tuple_to_image(segment[1]),
                    end=tuple_to_image(C)))

                paths.append(CubicBezier(
                    start=tuple_to_image(C),
                    control1=tuple_to_image(segment[2]),
                    control2=tuple_to_image(segment[3]),
                    end=tuple_to_image(segment[4])))

            elif len(segment) == 6:
                C = ((segment[0][0] + segment[1][0]) / 2.0,
                     (segment[0][1] + segment[1][1]) / 2.0)

                C = ((segment[1][0] + segment[2][0]) / 2.0,
                     (segment[1][1] + segment[2][1]) / 2.0)

                D = ((segment[2][0] + segment[3][0]) / 2.0,
                     (segment[2][1] + segment[3][1]) / 2.0)

                E = ((segment[3][0] + segment[4][0]) / 2.0,
                     (segment[3][1] + segment[4][1]) / 2.0)

                print('0', segment[0])
                print('1', segment[1])
                print('C', C)
                print('2', segment[2])
                print('D', D)
                print('3', segment[3])
                print('E', E)
                print('4', segment[4])
                print('5', segment[5])

                paths.append(QuadraticBezier(
                    start=tuple_to_image(segment[0]),
                    control=tuple_to_image(segment[1]),
                    end=tuple_to_image(C)))

                paths.append(QuadraticBezier(
                    start=tuple_to_image(C),
                    control=tuple_to_image(segment[2]),
                    end=tuple_to_image(D)))

                paths.append(QuadraticBezier(
                    start=tuple_to_image(D),
                    control=tuple_to_image(segment[3]),
                    end=tuple_to_image(E)))

                paths.append(QuadraticBezier(
                    start=tuple_to_image(E),
                    control=tuple_to_image(segment[4]),
                    end=tuple_to_image(segment[5])))

            elif len(segment) == 7:
                C = ((segment[0][0] + segment[1][0]) / 2.0,
                     (segment[0][1] + segment[1][1]) / 2.0)

                C = ((segment[1][0] + segment[2][0]) / 2.0,
                     (segment[1][1] + segment[2][1]) / 2.0)

                D = ((segment[2][0] + segment[3][0]) / 2.0,
                     (segment[2][1] + segment[3][1]) / 2.0)

                E = ((segment[3][0] + segment[4][0]) / 2.0,
                     (segment[3][1] + segment[4][1]) / 2.0)

                F = ((segment[4][0] + segment[5][0]) / 2.0,
                     (segment[4][1] + segment[5][1]) / 2.0)

                print('0', segment[0])
                print('1', segment[1])
                print('C', C)
                print('2', segment[2])
                print('D', D)
                print('3', segment[3])
                print('E', E)
                print('4', segment[4])
                print('F', F)
                print('5', segment[5])
                print('6', segment[6])

                paths.append(QuadraticBezier(
                    start=tuple_to_image(segment[0]),
                    control=tuple_to_image(segment[1]),
                    end=tuple_to_image(C)))

                paths.append(QuadraticBezier(
                    start=tuple_to_image(C),
                    control=tuple_to_image(segment[2]),
                    end=tuple_to_image(D)))

                paths.append(QuadraticBezier(
                    start=tuple_to_image(D),
                    control=tuple_to_image(segment[3]),
                    end=tuple_to_image(E)))

                paths.append(QuadraticBezier(
                    start=tuple_to_image(E),
                    control=tuple_to_image(segment[4]),
                    end=tuple_to_image(F)))

                paths.append(QuadraticBezier(
                    start=tuple_to_image(F),
                    control=tuple_to_image(segment[5]),
                    end=tuple_to_image(segment[6])))

            elif len(segment) == 8:
                print('0', segment[0])
                print('1', segment[1])
                print('2', segment[2])
                print('3', segment[3])
                print('4', segment[4])
                print('5', segment[5])
                print('6', segment[6])
                print('7', segment[7])

                paths.append(CubicBezier(
                    start=tuple_to_image(segment[0]),
                    control1=tuple_to_image(segment[1]),
                    control2=tuple_to_image(segment[2]),
                    end=tuple_to_image(segment[3])))

                paths.append(CubicBezier(
                    start=tuple_to_image(segment[4]),
                    control1=tuple_to_image(segment[5]),
                    control2=tuple_to_image(segment[6]),
                    end=tuple_to_image(segment[7])))

            elif len(segment) == 9:
                print('0', segment[0])
                print('1', segment[1])
                print('2', segment[2])
                print('3', segment[3])
                print('4', segment[4])
                print('5', segment[5])
                print('6', segment[6])
                print('7', segment[7])
                print('8', segment[8])

                paths.append(QuadraticBezier(
                    start=tuple_to_image(segment[0]),
                    control=tuple_to_image(segment[1]),
                    end=tuple_to_image(segment[2])))

                paths.append(QuadraticBezier(
                    start=tuple_to_image(segment[2]),
                    control=tuple_to_image(segment[3]),
                    end=tuple_to_image(segment[4])))

                paths.append(QuadraticBezier(
                    start=tuple_to_image(segment[4]),
                    control=tuple_to_image(segment[5]),
                    end=tuple_to_image(segment[6])))

                paths.append(QuadraticBezier(
                    start=tuple_to_image(segment[6]),
                    control=tuple_to_image(segment[7]),
                    end=tuple_to_image(segment[8])))

            print('')
        start = end + 1

    path = Path(*paths)

    file_name = '{:x}'.format(ord(char))
    file_path = 'fonts/{}.svg'.format(file_name.upper())

    xmin, xmax, ymin, ymax = bounding_box(path)

    dx = xmax - xmin
    dy = ymax - ymin

    viewbox = '{} {} {} {}'.format(xmin, ymin, dx, dy)

    attr = {
        'width': '50%',
        'height': '50%',
        'viewBox': viewbox,
        'preserveAspectRatio': 'xMidYMid meet'
    }

    wsvg(paths=path, colors=['#016FB9'], fill='none',
         svg_attributes=attr, stroke_widths=[100], filename=file_path)

    break

Call the script with a path to a truetype font like so

./scriptname.py DejaVuSans.ttf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment