Importing SVG outlines into font object? #3090
-
Is it possible to import outlines from SVG files into a font object? There is a SVGPen object to write the path out, but there seems to be no way to get outlines from SVG into a font. (No, I'm not talking OpenType/SVG colour font). |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 15 replies
-
Yes, there is fontTools.svgLib.SVGPath |
Beta Was this translation helpful? Give feedback.
-
you can parse the SVG paths using fontTools.svgLib.SVGPath (using the above snippet as reference) then draw them on a TTGlyphPen and use fontTools.fontBuilder to create a font from them |
Beta Was this translation helpful? Give feedback.
-
you can also try https://github.com/googlefonts/nanoemoji which is a color font compiler that takes SVG files as input. You could discard the COLR table if you don't need one |
Beta Was this translation helpful? Give feedback.
-
Went a little out and wrote a full program that import SVGs and make a font. Do tell if there is any problems. Full Python code hereimport os
from fontTools.fontBuilder import FontBuilder
from fontTools.svgLib import SVGPath
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.pens.t2CharStringPen import T2CharStringPen
from fontTools.pens.transformPen import TransformPen
from fontTools.misc.transform import Scale
from picosvg.svg import SVG as picoSVG
# draw a .notdef glyph of 1000x1000
def drawNotdefGlyph(type="bezier", upm=1000, ascender=880):
if type == "quad":
ori_pen = TTGlyphPen(None)
else:
ori_pen = T2CharStringPen(1000, None)
t = Scale(upm/1000).translate(0, ascender*1000/upm - 880)
pen = TransformPen(ori_pen, t)
pen.moveTo((100, -120))
pen.lineTo((900, -120))
pen.lineTo((900, 880))
pen.lineTo((100, 880))
pen.lineTo((100, -120))
pen.closePath()
pen.moveTo((150, -29))
pen.lineTo((150, 789))
pen.lineTo((468, 380))
pen.lineTo((150, -29))
pen.closePath()
pen.moveTo((182, -70))
pen.lineTo((500, 339))
pen.lineTo((818, -70))
pen.lineTo((182, -70))
pen.closePath()
pen.moveTo((532, 380))
pen.lineTo((850, 789))
pen.lineTo((850, -29))
pen.lineTo((532, 380))
pen.closePath()
pen.moveTo((500, 421))
pen.lineTo((182, 830))
pen.lineTo((817, 830))
pen.lineTo((500, 421))
pen.closePath()
if type == "quad":
return ori_pen.glyph()
else:
return ori_pen.getCharString()
def getOutlineFromSVG(picosvg, upm: int, ascender: int):
xori, yori, width, height = picosvg.view_box()
scale = upm / height
# prepare transformation
move_height = ascender - (yori * -scale)
t = Scale(scale, -scale).translate(-xori/-scale, move_height/-scale)
# calculate glyph advance with x coord
glyphadv = t.transformPoint((width, 0))[0]
# new pen
# glyphpen = TTGlyphPen(None)
glyphpen = T2CharStringPen(glyphadv, None)
# wrap pen with transform pen
transformPen = TransformPen(glyphpen, t)
# draw glyph
svgpen = SVGPath(None)
svgpen.root = picosvg.svg_root
svgpen.draw(transformPen)
# glyph = glyphpen.glyph()
glyph = glyphpen.getCharString()
return (glyph, glyphadv)
def uniName(char):
num = ord(char[0])
if num < 65536:
name = "uni" + hex(num).upper()[2:]
else:
name = "u" + hex(num).upper()[2:]
return (name, num)
folder = "."
familyName = "HelloTestFont"
styleName = "Regular"
version = "0.1"
outputName = "test.ttf"
upm = 1000
ascender = 880
descender = 120
# glyph order
glyphOrder = [".notdef"]
# unicode mapping
charmap = {}
# glyph advance width
advanceWidths = {}
advanceWidths[".notdef"] = 1000
# outlines
glyphs = {}
glyphs[".notdef"] = drawNotdefGlyph("bezier", upm, ascender)
# looping
for filename in os.listdir(folder):
name, ext = os.path.splitext(os.path.basename(filename))
# check if svg
if ext.lower() != ".svg":
print(f"%s is not a SVG file" % (filename))
continue
name = name.strip()
# check filename
if len(name) == 1:
#single char
glyphname, uni = uniName(name)
glyphOrder.append(glyphname)
if uni not in charmap:
charmap[uni] = glyphname
else:
print(f"Unicode %s is assigned to glyph name %s" % (hex(uni).upper()[2:], charmap[uni]))
else:
glyphOrder.append(name)
glyphname = name
# parse and format svg to spec
svg_fulltext = open(filename, "r", encoding="utf-8-sig").read()
svg_normalise = picoSVG.fromstring(svg_fulltext).topicosvg()
# get glyph outline
glyph, glyphadv = getOutlineFromSVG(svg_normalise, upm, ascender)
# save glyph into glyphs dict
glyphs[glyphname] = glyph
#set glyph advance
advanceWidths[glyphname] = glyphadv
# build name
nameStrings = dict(
familyName=familyName,
styleName=styleName,
uniqueFontIdentifier="fontBuilder: " + familyName + "." + styleName,
fullName=familyName + "-" + styleName,
psName=familyName + "-" + styleName,
version="Version " + version,
)
# start building
fb = FontBuilder(upm, isTTF=False)
fb.setupGlyphOrder(glyphOrder)
fb.setupCharacterMap(charmap)
# for glyf (quadratic)
# fb.setupGlyf(glyphs)
# glyphTable = fb.font["glyf"]
# set metrics
# metrics = {}
# for gn, advanceWidth in advanceWidths.items():
# metrics[gn] = (advanceWidth, glyphTable[gn].xMin)
# for CFF (cubic)
fb.setupCFF(nameStrings["psName"], {"FullName": nameStrings["psName"]}, glyphs, {})
lsb = {gn: cs.calcBounds(None)[0] for gn, cs in glyphs.items()}
metrics = {}
for gn, advanceWidth in advanceWidths.items():
metrics[gn] = (advanceWidth, lsb[gn])
fb.setupHorizontalMetrics(metrics)
fb.setupHorizontalHeader(ascent=ascender, descent=-descender)
#other font info
fb.setupNameTable(nameStrings)
fb.setupOS2(sTypoAscender=ascender, sTypoDescender=-descender, usWinAscent=ascender, usWinDescent=descender)
fb.setupPost()
fb.save(outputName) |
Beta Was this translation helpful? Give feedback.
Yes, there is fontTools.svgLib.SVGPath
have a look at for an example https://github.com/fonttools/fonttools/blob/main/Snippets/svg2glif.py