Skip to content

Instantly share code, notes, and snippets.

@anthrotype
Created November 12, 2020 19:14
Show Gist options
  • Save anthrotype/9f32325827968200b2f6e871cc2e78c8 to your computer and use it in GitHub Desktop.
Save anthrotype/9f32325827968200b2f6e871cc2e78c8 to your computer and use it in GitHub Desktop.
Convert TrueType glyph outline to SVG
#!/usr/bin/env python3
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from picosvg.svg_transform import Affine2D, Rect
from nanoemoji.svg_path import SVGPathPen
from fontTools.pens.transformPen import TransformPen
from fontTools import ttLib
from lxml import etree
import sys
SVG_TEMPLATE = """\
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"></svg>
"""
def draw_svg_path(
svg_path: etree.Element,
ttfont: ttLib.TTFont,
glyph_name: str,
glyph_set: ttLib.ttFont._TTGlyphSet,
transform: Affine2D = Affine2D.identity(),
):
# use glyph set to resolve references in composite glyphs
svg_pen = SVGPathPen(glyph_set)
transform_pen = TransformPen(svg_pen, transform)
glyph = glyph_set[glyph_name]
glyph.draw(transform_pen)
svg_path.attrib["d"] = svg_pen.path.d
def map_font_emsqure_to_viewbox(upem: int, view_box: Rect) -> Affine2D:
# map font UPEM space to SVG space, in which Y axis goes down.
# The point (0, UPEM) is mapped to the origin (0, 0) in the SVG viewBox
return Affine2D.rect_to_rect(Rect(x=0, y=upem, w=upem, h=-upem), view_box)
def make_svg_document(view_box: Rect) -> etree.ElementTree:
parser = etree.XMLParser(remove_blank_text=True)
svg_root = etree.XML(SVG_TEMPLATE, parser=parser)
svg_root.attrib["viewBox"] = f"{view_box.x} {view_box.y} {view_box.w} {view_box.h}"
return etree.ElementTree(svg_root)
def main():
try:
fontfile, glyph_name = sys.argv[1:]
except ValueError:
sys.exit("usage: glyf2svg FONTFILE GLYPH_NAME")
font = ttLib.TTFont(fontfile)
glyph_set = font.getGlyphSet()
upem = font["head"].unitsPerEm
view_box = Rect(x=0, y=0, w=upem, h=upem)
upem_to_viewbox = map_font_emsqure_to_viewbox(upem, view_box)
svg = make_svg_document(view_box)
svg_root = svg.getroot()
svg_path = etree.SubElement(svg_root, "path")
draw_svg_path(svg_path, font, glyph_name, glyph_set, upem_to_viewbox)
# dump raw UTF-8 bytes to stdout (you can also write to file object or path)
svg.write(
sys.stdout.buffer, pretty_print=True, xml_declaration=True, encoding="utf-8"
)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment