holman rugama
holman rugama

Reputation: 21

Problem with Creating a Custom Icon Font Using FontTools

I have a class that handles loading and converting SVG icons to glyphs using svgpathtools. Then, I have a separate font class that takes those glyphs and generates the actual font. However, I'm encountering some issues in this transformation process and generating the final font.

Here’s an outline of my approach:

The Icons class is responsible for loading the SVGs and converting them into glyphs using svgpathtools. The Font class takes those glyphs and generates the final font. I’d appreciate any insights or suggestions on how to improve this process or troubleshoot the issues I'm running into.

import os
from svgpathtools import svg2paths2

class Icons():

    svg_path = os.path.join(os.path.dirname(__file__), "svg")

    def __init__(self):
        self._icons = []
        self.load_icons()

    @property
    def get_icons(self):
        return self._icons
    
    def convert_svg_to_glyph(self, svg_file):
        paths, attributes, _ = svg2paths2(os.path.join(self.svg_path, svg_file))
        glyph = {
            "name": svg_file.split(".")[0],
            "paths": paths,
            "attributes": attributes
        }
        
        return glyph
    
    def load_icons(self):
        if not os.listdir(self.svg_path):
            return "Not svg files found"
        icon_files = [f for f in os.listdir(self.svg_path) if f.endswith('.svg')]
        for icon_file in icon_files:
            glyph = self.convert_svg_to_glyph(icon_file)
            self._icons.append(glyph)
from typing import Tuple
from icons import Icons
from fontTools.ttLib import TTFont, newTable
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.ttLib.tables._c_m_a_p import CmapSubtable
from svgpathtools import Path, Line, CubicBezier, QuadraticBezier, Arc

class Font:
    def __init__(self):
        self.icons = Icons().get_icons
        self.font = TTFont()
        self.font.setGlyphOrder([".notdef"])
        
        self.font["head"] = newTable("head")
        self.font["hhea"] = newTable("hhea")
        self.font["maxp"] = newTable("maxp")
        self.font["OS/2"] = newTable("OS/2")
        self.font["name"] = newTable("name")
        self.font["post"] = newTable("post")
        self.font["glyf"] = newTable("glyf")
        self.font["hmtx"] = newTable("hmtx")
        self.font["cmap"] = newTable("cmap")
        
        self.create_font()

    def create_font(self):
        cmap = {}
        glyf_table = self.font["glyf"]
        hmtx_table = self.font["hmtx"]
        hmtx_table.metrics = {}

        unicode_start = 0xE000

        for icon in self.icons:
            glyph_name = icon['name']
            glyph_paths = icon['paths']

            pen = CustomPen(None)
            for path in glyph_paths:
                self.draw_path(path, pen)
            glyph = pen.glyph()

            glyf_table[glyph_name] = glyph
            hmtx_table.metrics[glyph_name] = (600, 0)

            cmap[unicode_start] = glyph_name
            unicode_start += 1

        cmap_table = self.font["cmap"]
        cmap_subtable = CmapSubtable.newSubtable(4)
        cmap_subtable.platformID = 3
        cmap_subtable.platEncID = 1
        cmap_subtable.language = 0
        cmap_subtable.cmap = cmap
        cmap_table.tables = [cmap_subtable]

        self.font["head"].unitsPerEm = 1000
        self.font["head"].magicNumber = 0x5F0F3CF5
        self.font["head"].created = 0

        self.font["hhea"].ascent = 800
        self.font["hhea"].descent = -200
        self.font["hhea"].lineGap = 200
        self.font["hhea"].numOfLongMetrics = len(self.icons)

        self.font["maxp"].numGlyphs = len(self.icons) + 1

        self.font["post"].formatType = 3.0

        self.font.save("custom_font.ttf")

    def draw_path(self, path, pen):
        contour_started = False
        first_point = None

        for segment in path:
            if isinstance(segment, Line):
                if not contour_started:
                    pen.moveTo((segment.start.real, segment.start.imag))
                    first_point = (segment.start.real, segment.start.imag)
                    contour_started = True
                pen.lineTo((segment.end.real, segment.end.imag))
            elif isinstance(segment, CubicBezier):
                if not contour_started:
                    pen.moveTo((segment.start.real, segment.start.imag))
                    first_point = (segment.start.real, segment.start.imag)
                    contour_started = True
                pen.curveTo(
                    (segment.control1.real, segment.control1.imag),
                    (segment.control2.real, segment.control2.imag),
                    (segment.end.real, segment.end.imag)
                )
            elif isinstance(segment, QuadraticBezier):
                if not contour_started:
                    pen.moveTo((segment.start.real, segment.start.imag))
                    first_point = (segment.start.real, segment.start.imag)
                    contour_started = True
                pen.qCurveTo(
                    (segment.control.real, segment.control.imag),
                    (segment.end.real, segment.end.imag)
                )

        if contour_started and first_point and (pen.current_position != first_point):
            pen.lineTo(first_point)

class CustomPen(TTGlyphPen):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.current_position = None 

    def moveTo(self, pt: Tuple[float, float]) -> None:
        if not self._isClosed():
            self.closePath()
        super().moveTo(pt)
        self.current_position = pt

    def lineTo(self, pt: Tuple[float, float]) -> None:
        super().lineTo(pt)
        self.current_position = pt

    def curveTo(self, *points) -> None:
        super().curveTo(*points)
        self.current_position = points[-1]

    def closePath(self) -> None:
        if self.current_position != self.points[0]:
            self.lineTo(self.points[0])
        super().closePath()

    def endPath(self) -> None:
        if not self._isClosed():
            self.closePath()
        super().endPath()

    def glyph(self):
        if not self._isClosed():
            self.closePath()
        return super().glyph()

Upvotes: 0

Views: 38

Answers (0)

Related Questions