Skip to content

SVGtoTTF

Source code in handwrite/svgtottf.py
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
class SVGtoTTF:
    def convert(self, directory, outdir, config, metadata=None):
        """Convert a directory with SVG images to TrueType Font.

        Calls a subprocess to the run this script with Fontforge Python
        environment.

        Parameters
        ----------
        directory : str
            Path to directory with SVGs to be converted.
        outdir : str
            Path to output directory.
        config : str
            Path to config file.
        metadata : dict
            Dictionary containing the metadata (filename, family or style)
        """
        import subprocess
        import platform

        subprocess.run(
            (
                ["ffpython"]
                if platform.system() == "Windows"
                else ["fontforge", "-script"]
            )
            + [
                os.path.abspath(__file__),
                config,
                directory,
                outdir,
                json.dumps(metadata),
            ]
        )

    def set_properties(self):
        """Set metadata of the font from config."""
        props = self.config["props"]
        lang = props.get("lang", "English (US)")
        fontname = self.metadata.get("filename", None) or props.get(
            "filename", "Example"
        )
        family = self.metadata.get("family", None) or fontname
        style = self.metadata.get("style", None) or props.get("style", "Regular")

        self.font.familyname = fontname
        self.font.fontname = fontname + "-" + style
        self.font.fullname = fontname + " " + style
        self.font.encoding = props.get("encoding", "UnicodeFull")

        for k, v in props.items():
            if hasattr(self.font, k):
                if isinstance(v, list):
                    v = tuple(v)
                setattr(self.font, k, v)

        if self.config.get("sfnt_names", None):
            self.config["sfnt_names"]["Family"] = family
            self.config["sfnt_names"]["Fullname"] = family + " " + style
            self.config["sfnt_names"]["PostScriptName"] = family + "-" + style
            self.config["sfnt_names"]["SubFamily"] = style

        self.config["sfnt_names"]["UniqueID"] = family + " " + str(uuid.uuid4())

        for k, v in self.config.get("sfnt_names", {}).items():
            self.font.appendSFNTName(str(lang), str(k), str(v))

    def add_glyphs(self, directory):
        """Read and add SVG images as glyphs to the font.

        Walks through the provided directory and uses each ord(character).svg file
        as glyph for the character. Then using the provided config, set the font
        parameters and export TTF file to outdir.

        Parameters
        ----------
        directory : str
            Path to directory with SVGs to be converted.
        """
        space = self.font.createMappedChar(ord(" "))
        space.width = 500

        for k in self.config["glyphs"]:
            # Create character glyph
            g = self.font.createMappedChar(k)
            self.unicode_mapping.setdefault(k, g.glyphname)
            # Get outlines
            src = "{}/{}.svg".format(k, k)
            src = directory + os.sep + src
            g.importOutlines(src, ("removeoverlap", "correctdir"))
            g.removeOverlap()

    def set_bearings(self, bearings):
        """Add left and right bearing from config

        Parameters
        ----------
        bearings : dict
            Map from character: [left bearing, right bearing]
        """
        default = bearings.get("Default", [60, 60])

        for k, v in bearings.items():
            if v[0] is None:
                v[0] = default[0]
            if v[1] is None:
                v[1] = default[1]

            if k != "Default":
                glyph_name = self.unicode_mapping[ord(str(k))]
                self.font[glyph_name].left_side_bearing = v[0]
                self.font[glyph_name].right_side_bearing = v[1]

    def set_kerning(self, table):
        """Set kerning values in the font.

        Parameters
        ----------
        table : dict
            Config dictionary with kerning values/autokern bool.
        """
        rows = table["rows"]
        rows = [list(i) if i != None else None for i in rows]
        cols = table["cols"]
        cols = [list(i) if i != None else None for i in cols]

        self.font.addLookup("kern", "gpos_pair", 0, [["kern", [["latn", ["dflt"]]]]])

        if table.get("autokern", True):
            self.font.addKerningClass(
                "kern", "kern-1", table.get("seperation", 0), rows, cols, True
            )
        else:
            kerning_table = table.get("table", False)
            if not kerning_table:
                raise ValueError("Kerning offsets not found in the config file.")
            flatten_list = lambda y: (
                [x for a in y for x in flatten_list(a)] if type(y) is list else [y]
            )
            offsets = [0 if x is None else x for x in flatten_list(kerning_table)]
            self.font.addKerningClass("kern", "kern-1", rows, cols, offsets)

    def generate_font_file(self, filename, outdir, config_file):
        """Output TTF file.

        Additionally checks for multiple outputs and duplicates.

        Parameters
        ----------
        filename : str
            Output filename.
        outdir : str
            Path to output directory.
        config_file : str
            Path to config file.
        """
        if filename is None:
            raise NameError("filename not found in config file.")

        outfile = str(
            outdir
            + os.sep
            + (filename + ".ttf" if not filename.endswith(".ttf") else filename)
        )

        while os.path.exists(outfile):
            outfile = os.path.splitext(outfile)[0] + " (1).ttf"

        sys.stderr.write("\nGenerating %s...\n" % outfile)
        self.font.generate(outfile)

    def convert_main(self, config_file, directory, outdir, metadata):
        try:
            self.font = fontforge.font()
        except:
            import fontforge

        with open(config_file) as f:
            self.config = json.load(f)
        self.metadata = json.loads(metadata) or {}

        self.font = fontforge.font()
        self.unicode_mapping = {}
        self.set_properties()
        self.add_glyphs(directory)

        # bearing table
        self.set_bearings(self.config["typography_parameters"].get("bearing_table", {}))

        # kerning table
        self.set_kerning(self.config["typography_parameters"].get("kerning_table", {}))

        # Generate font and save as a .ttf file
        filename = self.metadata.get("filename", None) or self.config["props"].get(
            "filename", None
        )
        self.generate_font_file(str(filename), outdir, config_file)

add_glyphs(directory)

Read and add SVG images as glyphs to the font.

Walks through the provided directory and uses each ord(character).svg file as glyph for the character. Then using the provided config, set the font parameters and export TTF file to outdir.

Parameters

directory : str Path to directory with SVGs to be converted.

Source code in handwrite/svgtottf.py
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def add_glyphs(self, directory):
    """Read and add SVG images as glyphs to the font.

    Walks through the provided directory and uses each ord(character).svg file
    as glyph for the character. Then using the provided config, set the font
    parameters and export TTF file to outdir.

    Parameters
    ----------
    directory : str
        Path to directory with SVGs to be converted.
    """
    space = self.font.createMappedChar(ord(" "))
    space.width = 500

    for k in self.config["glyphs"]:
        # Create character glyph
        g = self.font.createMappedChar(k)
        self.unicode_mapping.setdefault(k, g.glyphname)
        # Get outlines
        src = "{}/{}.svg".format(k, k)
        src = directory + os.sep + src
        g.importOutlines(src, ("removeoverlap", "correctdir"))
        g.removeOverlap()

convert(directory, outdir, config, metadata=None)

Convert a directory with SVG images to TrueType Font.

Calls a subprocess to the run this script with Fontforge Python environment.

Parameters

directory : str Path to directory with SVGs to be converted. outdir : str Path to output directory. config : str Path to config file. metadata : dict Dictionary containing the metadata (filename, family or style)

Source code in handwrite/svgtottf.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def convert(self, directory, outdir, config, metadata=None):
    """Convert a directory with SVG images to TrueType Font.

    Calls a subprocess to the run this script with Fontforge Python
    environment.

    Parameters
    ----------
    directory : str
        Path to directory with SVGs to be converted.
    outdir : str
        Path to output directory.
    config : str
        Path to config file.
    metadata : dict
        Dictionary containing the metadata (filename, family or style)
    """
    import subprocess
    import platform

    subprocess.run(
        (
            ["ffpython"]
            if platform.system() == "Windows"
            else ["fontforge", "-script"]
        )
        + [
            os.path.abspath(__file__),
            config,
            directory,
            outdir,
            json.dumps(metadata),
        ]
    )

generate_font_file(filename, outdir, config_file)

Output TTF file.

Additionally checks for multiple outputs and duplicates.

Parameters

filename : str Output filename. outdir : str Path to output directory. config_file : str Path to config file.

Source code in handwrite/svgtottf.py
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
def generate_font_file(self, filename, outdir, config_file):
    """Output TTF file.

    Additionally checks for multiple outputs and duplicates.

    Parameters
    ----------
    filename : str
        Output filename.
    outdir : str
        Path to output directory.
    config_file : str
        Path to config file.
    """
    if filename is None:
        raise NameError("filename not found in config file.")

    outfile = str(
        outdir
        + os.sep
        + (filename + ".ttf" if not filename.endswith(".ttf") else filename)
    )

    while os.path.exists(outfile):
        outfile = os.path.splitext(outfile)[0] + " (1).ttf"

    sys.stderr.write("\nGenerating %s...\n" % outfile)
    self.font.generate(outfile)

set_bearings(bearings)

Add left and right bearing from config

Parameters

bearings : dict Map from character: [left bearing, right bearing]

Source code in handwrite/svgtottf.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def set_bearings(self, bearings):
    """Add left and right bearing from config

    Parameters
    ----------
    bearings : dict
        Map from character: [left bearing, right bearing]
    """
    default = bearings.get("Default", [60, 60])

    for k, v in bearings.items():
        if v[0] is None:
            v[0] = default[0]
        if v[1] is None:
            v[1] = default[1]

        if k != "Default":
            glyph_name = self.unicode_mapping[ord(str(k))]
            self.font[glyph_name].left_side_bearing = v[0]
            self.font[glyph_name].right_side_bearing = v[1]

set_kerning(table)

Set kerning values in the font.

Parameters

table : dict Config dictionary with kerning values/autokern bool.

Source code in handwrite/svgtottf.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
def set_kerning(self, table):
    """Set kerning values in the font.

    Parameters
    ----------
    table : dict
        Config dictionary with kerning values/autokern bool.
    """
    rows = table["rows"]
    rows = [list(i) if i != None else None for i in rows]
    cols = table["cols"]
    cols = [list(i) if i != None else None for i in cols]

    self.font.addLookup("kern", "gpos_pair", 0, [["kern", [["latn", ["dflt"]]]]])

    if table.get("autokern", True):
        self.font.addKerningClass(
            "kern", "kern-1", table.get("seperation", 0), rows, cols, True
        )
    else:
        kerning_table = table.get("table", False)
        if not kerning_table:
            raise ValueError("Kerning offsets not found in the config file.")
        flatten_list = lambda y: (
            [x for a in y for x in flatten_list(a)] if type(y) is list else [y]
        )
        offsets = [0 if x is None else x for x in flatten_list(kerning_table)]
        self.font.addKerningClass("kern", "kern-1", rows, cols, offsets)

set_properties()

Set metadata of the font from config.

Source code in handwrite/svgtottf.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def set_properties(self):
    """Set metadata of the font from config."""
    props = self.config["props"]
    lang = props.get("lang", "English (US)")
    fontname = self.metadata.get("filename", None) or props.get(
        "filename", "Example"
    )
    family = self.metadata.get("family", None) or fontname
    style = self.metadata.get("style", None) or props.get("style", "Regular")

    self.font.familyname = fontname
    self.font.fontname = fontname + "-" + style
    self.font.fullname = fontname + " " + style
    self.font.encoding = props.get("encoding", "UnicodeFull")

    for k, v in props.items():
        if hasattr(self.font, k):
            if isinstance(v, list):
                v = tuple(v)
            setattr(self.font, k, v)

    if self.config.get("sfnt_names", None):
        self.config["sfnt_names"]["Family"] = family
        self.config["sfnt_names"]["Fullname"] = family + " " + style
        self.config["sfnt_names"]["PostScriptName"] = family + "-" + style
        self.config["sfnt_names"]["SubFamily"] = style

    self.config["sfnt_names"]["UniqueID"] = family + " " + str(uuid.uuid4())

    for k, v in self.config.get("sfnt_names", {}).items():
        self.font.appendSFNTName(str(lang), str(k), str(v))