Skip to content

SVGtoTTF

Source code in handwrite/svgtottf.py
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(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:

Name Type Description Default
directory str

Path to directory with SVGs to be converted.

required
Source code in handwrite/svgtottf.py
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(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:

Name Type Description Default
directory str

Path to directory with SVGs to be converted.

required
outdir str

Path to output directory.

required
config str

Path to config file.

required
metadata dict

Dictionary containing the metadata (filename, family or style)

None
Source code in handwrite/svgtottf.py
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(self, filename, outdir, config_file)

Output TTF file.

Additionally checks for multiple outputs and duplicates.

Parameters:

Name Type Description Default
filename str

Output filename.

required
outdir str

Path to output directory.

required
config_file str

Path to config file.

required
Source code in handwrite/svgtottf.py
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(self, bearings)

Add left and right bearing from config

Parameters:

Name Type Description Default
bearings dict

Map from character: [left bearing, right bearing]

required
Source code in handwrite/svgtottf.py
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(self, table)

Set kerning values in the font.

Parameters:

Name Type Description Default
table dict

Config dictionary with kerning values/autokern bool.

required
Source code in handwrite/svgtottf.py
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(self)

Set metadata of the font from config.

Source code in handwrite/svgtottf.py
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))