I'm trying to re-compile a ttf glyph-font based on it's source svg files.
I'm using python and FontForge and struggling with converting SVG shape into FontForge's SplineSet
format. For example, this SVG file
Is represented in a font generated by FontForge as this SplineSet
format
SplineSet
85 235 m 1,0,-1
85 85 l 1,1,-1
235 85 l 1,2,-1
235 43 l 1,3,-1
85 43 l 2,4,5
68 43 68 43 55.5 55.5 c 128,-1,6
43 68 43 68 43 85 c 2,7,-1
43 235 l 1,8,-1
85 235 l 1,0,-1
427 85 m 1,9,-1
427 235 l 1,10,-1
469 235 l 1,11,-1
469 85 l 2,12,13
469 68 469 68 456.5 55.5 c 128,-1,14
444 43 444 43 427 43 c 2,15,-1
277 43 l 1,16,-1
277 85 l 1,17,-1
427 85 l 1,9,-1
427 469 m 2,18,19
444 469 444 469 456.5 456.5 c 128,-1,20
469 444 469 444 469 427 c 2,21,-1
469 277 l 1,22,-1
427 277 l 1,23,-1
427 427 l 1,24,-1
277 427 l 1,25,-1
277 469 l 1,26,-1
427 469 l 2,18,19
363 331 m 128,-1,28
363 318 363 318 353.5 308.5 c 128,-1,29
344 299 344 299 331 299 c 128,-1,30
318 299 318 299 308.5 308.5 c 128,-1,31
299 318 299 318 299 331 c 128,-1,32
299 344 299 344 308.5 353.5 c 128,-1,33
318 363 318 363 331 363 c 128,-1,34
344 363 344 363 353.5 353.5 c 128,-1,27
363 344 363 344 363 331 c 128,-1,28
213 235 m 1,35,-1
277 156 l 1,36,-1
320 213 l 1,37,-1
384 128 l 1,38,-1
128 128 l 1,39,-1
213 235 l 1,35,-1
85 427 m 1,40,-1
85 277 l 1,41,-1
43 277 l 1,42,-1
43 427 l 2,43,44
43 444 43 444 55.5 456.5 c 128,-1,45
68 469 68 469 85 469 c 2,46,-1
235 469 l 1,47,-1
235 427 l 1,48,-1
85 427 l 1,40,-1
EndSplineSet
I want to create a function that receives an SVG and outputs SplineSet
.
This is what I've tried so far (an inefficient, naive way to generate a temporary font and later scrape the SplineSet
data. this is BTW badly inaccurate comparing to the real font)
import fontforge
def main():
font = fontforge.font()
unicode_id = 57865
unicode_name = 'uni' + hex(unicode_id).upper()[2:]
char = font.createChar(unicode_id, unicode_name)
glyph = char.importOutlines('file.svg')
font.save('result.sfd')
Any reference on how to do that?
Use this svg2sfd.py from FontForge GitHub project.
File documents:
A simple script to convert svg files (generated by Illustrator plugin) to sfd, from which Fontforge can then create a font file.
File Content:
#!/usr/bin/python
#
# Copyright 2013 Google Inc. All rights reserved.
#
# 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.
#
# A simple script to convert svg files (generated by Illustrator plugin)
# to sfd, from which Fontforge can then create a font file.
#
# Contributors: Raph Levien (raph@google.com)
import sys
import os.path
import glob
import xml.dom.minidom
import re
import math
lastglyphnum = 0
char_num = 0x40
font_name = 'Untitled'
header_printed = False
def num_args_cmd(cmd):
if cmd.upper() == 'C': return 6
elif cmd.upper() in 'HV': return 1
elif cmd.upper() == 'S': return 4
elif cmd == 'z': return 0
return 2
def print_one_cmd(cmd, args):
scale = 40
yoff = 720
result = []
for i in range(len(args)):
if i & 1:
result.append('%f' % (yoff - scale * args[i]))
else:
result.append('%f' % (scale * args[i]))
result.append(cmd)
result.append('0') # TODO: should mark corner points
print ' '.join(result)
def apply_rel_xy(xy, args):
x0, y0 = xy
result = []
for i in range(0, len(args), 2):
x = x0 + args[i]
result.append(x)
y = y0 + args[i + 1]
result.append(y)
return result
def path_to_sfd(path):
# convert svg path syntax into sfd
# written for conciseness, not efficiency
x0, y0 = 0, 0
fre = re.compile(r'(\-?[0-9\.]+)\s*,?\s*')
while path.strip() != '':
path = path.strip()
if path[0].isalpha():
cmd = path[0]
path = path[1:].lstrip()
args = []
for i in range(num_args_cmd(cmd)):
m = fre.match(path)
if m is None:
print 'no float match:', path
args.append(float(m.group(1)))
path = path[m.end():]
#print cmd, args
if cmd.upper() == 'M':
if cmd.islower(): (x, y), args = apply_rel_xy([x, y], args)
x0, y0 = args
print_one_cmd('m', args)
x, y = args[-2:]
if cmd == 'm': cmd = 'l'
elif cmd == 'M': cmd = 'L'
elif cmd.upper() in 'CLVHS':
if cmd == 'H':
args = args + [y]
cmd = 'L'
elif cmd == 'h':
args = args + [0]
cmd = 'l'
if cmd == 'V':
args = [x] + args
cmd = 'L'
elif cmd == 'v':
args = [0] + args
cmd = 'l'
if cmd.islower(): args = apply_rel_xy([x, y], args)
if cmd.upper() == 'S':
# smooth curveto; reflect
args = [2 * x - xs, 2 * y - ys] + args
cmd = 'c'
print_one_cmd(cmd.lower(), args)
x, y = args[-2:]
if len(args) > 2:
xs, ys = args[-4:-2]
elif cmd.upper() == 'Z':
if x != x0 or y != y0:
print_one_cmd('l', [x0, y0])
def circle_to_sfd(cx, cy, r):
k = 4 * (math.sqrt(2) - 1) / 3
print_one_cmd('m', [cx, cy - r])
print_one_cmd('c', [cx + k * r, cy - r, cx + r, cy - k * r, cx + r, cy])
print_one_cmd('c', [cx + r, cy + k * r, cx + k * r, cy + r, cx, cy + r])
print_one_cmd('c', [cx - k * r, cy + r, cx - r, cy + k * r, cx - r, cy])
print_one_cmd('c', [cx - r, cy - k * r, cx - k * r, cy - r, cx, cy - r])
def conv_svg(fn, char, glyphnum = None):
global lastglyphnum
global header_printed
if not header_printed:
print_header()
if glyphnum == None:
glyphnum = lastglyphnum + 1
lastglyphnum = glyphnum
print 'StartChar:', os.path.basename(fn)[:-4]
print 'Encoding: %d %d %d' % (char, glyphnum, char)
print 'Width: %d' % (21 * 40)
print 'Flags: W'
print 'LayerCount: 2'
print 'Fore'
print 'SplineSet'
doc = xml.dom.minidom.parse(fn)
# TODO: reverse paths if fill color is white-ish (this is more code,
# and in the meantime, we'll rely on correct path direction in FF)
for path in doc.getElementsByTagName('path'):
path_to_sfd(path.getAttribute('d'))
for polygon in doc.getElementsByTagName('polygon'):
path_to_sfd('M' + polygon.getAttribute('points') + 'z')
for circle in doc.getElementsByTagName('circle'):
cx = float(circle.getAttribute('cx'))
cy = float(circle.getAttribute('cy'))
r = float(circle.getAttribute('r'))
circle_to_sfd(cx, cy, r)
print 'EndSplineSet'
print 'EndChar'
def print_header():
global header_printed
print '''SplineFontDB: 3.0
FontName: %s
FullName: %s
FamilyName: %s''' % (font_name, font_name, font_name)
print '''Weight: Medium
Copyright: Copyright (C) 2011 Google Inc.
Version: 001.000
UnderlinePosition: -120
UnderlineWidth: 40
Ascent: 800
Descent: 200
LayerCount: 2
Layer: 0 0 "Back" 1
Layer: 1 0 "Fore" 0
Encoding: unicode
OS2TypoAscent: 800
OS2TypeAOffset: 0
OS2TypoDescent: -200
OS2TypoDOffset: 0
OS2WinAscent: 800
OS2WinAOffset: 0
OS2WinDescent: 200
OS2WinDOffset: 0
HheadAscent: 800
HheadAOffset: 0
HheadDescent: 200
HheadDOffset: 0
BeginChars: 57600 57600
'''
header_printed = True
def print_footer():
print '''EndChars
EndSplineFont'''
def parse_int(x):
if x.startswith('0x'):
return int(x[2:], 16)
else:
return int(x)
def run_file(fn):
global char_num
global font_name
directory = ''
for l in file(fn).xreadlines():
if l.startswith('#'):
continue
s = l.strip().split()
if len(s) == 0:
continue
if s[0] == 'dir':
directory = s[1]
elif s[0] == 'fontname':
font_name = s[1]
elif s[0] == 'unicode':
char_num = parse_int(s[1])
elif s[0] == 'icon':
icon_fn = s[1]
if not icon_fn.endswith('.svg'):
icon_fn += '.svg'
if len(s) > 2:
char_num = parse_int(s[2])
conv_svg(os.path.join(directory, icon_fn), char_num)
char_num += 1
def main(args):
global char_num
for arg in args:
if os.path.isdir(arg):
for fn in glob.glob(arg + '/*.svg'):
conv_svg(fn, char_num)
char_num += 1
elif arg.endswith('.svg'):
conv_svg(arg, char_num)
char_num += 1
else:
run_file(arg)
print_footer()
if __name__ == '__main__':
main(sys.argv[1:])
Example:
Converted this Adobe Logo and WordMark.
To:
SplineFontDB: 3.0
FontName: Untitled
FullName: Untitled
FamilyName: Untitled
Weight: Medium
Copyright: Copyright (C) 2011 Google Inc.
Version: 001.000
UnderlinePosition: -120
UnderlineWidth: 40
Ascent: 800
Descent: 200
LayerCount: 2
Layer: 0 0 "Back" 1
Layer: 1 0 "Fore" 0
Encoding: unicode
OS2TypoAscent: 800
OS2TypeAOffset: 0
OS2TypoDescent: -200
OS2TypoDOffset: 0
OS2WinAscent: 800
OS2WinAOffset: 0
OS2WinDescent: 200
OS2WinDOffset: 0
HheadAscent: 800
HheadAOffset: 0
HheadDescent: 200
HheadDOffset: 0
BeginChars: 57600 57600
StartChar: adobe
Encoding: 64 1 64
Width: 840
Flags: W
LayerCount: 2
Fore
SplineSet
6820.000000 720.000000 m 0
10832.000000 -8852.000000 l 0
10832.000000 720.000000 l 0
6820.000000 720.000000 l 0
0.000000 720.000000 m 0
0.000000 -8852.000000 l 0
4012.000000 720.000000 l 0
0.000000 720.000000 l 0
3668.000000 -6900.000000 m 0
5500.000000 -6900.000000 l 0
6304.000000 -8848.000000 l 0
7964.000000 -8848.000000 l 0
5388.000000 -2776.000000 l 0
3668.000000 -6900.000000 l 0
17556.000000 -5456.000000 m 0
17116.000000 -6780.000000 l 0
17100.000000 -6828.000000 17076.000000 -6844.000000 17028.000000 -6844.000000 c 0
16224.000000 -6844.000000 l 0
16176.000000 -6844.000000 16160.000000 -6820.000000 16168.000000 -6764.000000 c 0
17824.000000 -2056.000000 l 0
17856.000000 -1976.000000 17880.000000 -1896.000000 17896.000000 -1624.000000 c 0
17896.000000 -1592.000000 17912.000000 -1568.000000 17944.000000 -1568.000000 c 0
19060.000000 -1568.000000 l 0
19100.000000 -1568.000000 19108.000000 -1576.000000 19116.000000 -1616.000000 c 0
20972.000000 -6776.000000 l 0
20980.000000 -6824.000000 20972.000000 -6848.000000 20924.000000 -6848.000000 c 0
20024.000000 -6848.000000 l 0
19984.000000 -6848.000000 19960.000000 -6832.000000 19944.000000 -6800.000000 c 0
19476.000000 -5460.000000 l 0
17556.000000 -5460.000000 l 0
17556.000000 -5456.000000 l 0
19220.000000 -4580.000000 m 0
19052.000000 -4048.000000 18672.000000 -2932.000000 18512.000000 -2372.000000 c 0
18504.000000 -2372.000000 l 0
18376.000000 -2912.000000 18052.000000 -3852.000000 17812.000000 -4580.000000 c 0
19220.000000 -4580.000000 l 0
21260.000000 -4900.000000 m 0
21260.000000 -3736.000000 22128.000000 -2804.000000 23544.000000 -2804.000000 c 0
23656.000000 -2804.000000 23752.000000 -2812.000000 23888.000000 -2828.000000 c 0
23888.000000 -1180.000000 l 0
23888.000000 -1140.000000 23904.000000 -1124.000000 23936.000000 -1124.000000 c 0
24812.000000 -1124.000000 l 0
24852.000000 -1124.000000 24852.000000 -1140.000000 24852.000000 -1172.000000 c 0
24852.000000 -6024.000000 l 0
24852.000000 -6184.000000 24868.000000 -6392.000000 24884.000000 -6540.000000 c 0
24884.000000 -6580.000000 24876.000000 -6596.000000 24836.000000 -6612.000000 c 0
24312.000000 -6836.000000 23800.000000 -6924.000000 23316.000000 -6924.000000 c 0
22152.000000 -6928.000000 21260.000000 -6252.000000 21260.000000 -4900.000000 c 0
23888.000000 -3672.000000 m 0
23784.000000 -3632.000000 23656.000000 -3616.000000 23512.000000 -3616.000000 c 0
22796.000000 -3616.000000 22260.000000 -4076.000000 22260.000000 -4852.000000 c 0
22260.000000 -5728.000000 22784.000000 -6088.000000 23424.000000 -6088.000000 c 0
23584.000000 -6088.000000 23744.000000 -6072.000000 23892.000000 -6024.000000 c 0
23892.000000 -3672.000000 l 0
23888.000000 -3672.000000 l 0
29488.000000 -4844.000000 m 0
29488.000000 -6088.000000 28692.000000 -6924.000000 27560.000000 -6924.000000 c 0
26216.000000 -6924.000000 25624.000000 -5888.000000 25624.000000 -4868.000000 c 0
25624.000000 -3728.000000 26372.000000 -2804.000000 27576.000000 -2804.000000 c 0
28816.000000 -2804.000000 29488.000000 -3744.000000 29488.000000 -4844.000000 c 0
26604.000000 -4852.000000 m 0
26604.000000 -5592.000000 26964.000000 -6104.000000 27576.000000 -6104.000000 c 0
28076.000000 -6104.000000 28500.000000 -5696.000000 28500.000000 -4868.000000 c 0
28500.000000 -4200.000000 28212.000000 -3616.000000 27528.000000 -3616.000000 c 0
26992.000000 -3616.000000 26604.000000 -4088.000000 26604.000000 -4852.000000 c 0
31144.000000 -1124.000000 m 0
31200.000000 -1124.000000 31216.000000 -1132.000000 31216.000000 -1188.000000 c 0
31224.000000 -2916.000000 l 0
31440.000000 -2844.000000 31692.000000 -2804.000000 31956.000000 -2804.000000 c 0
33112.000000 -2804.000000 33852.000000 -3624.000000 33852.000000 -4700.000000 c 0
33852.000000 -6188.000000 32696.000000 -6924.000000 31504.000000 -6924.000000 c 0
31088.000000 -6924.000000 30684.000000 -6868.000000 30300.000000 -6748.000000 c 0
30268.000000 -6740.000000 30244.000000 -6700.000000 30244.000000 -6676.000000 c 0
30244.000000 -1180.000000 l 0
30244.000000 -1140.000000 30268.000000 -1124.000000 30300.000000 -1124.000000 c 0
31144.000000 -1124.000000 l 0
31788.000000 -3632.000000 m 0
31508.000000 -3632.000000 31356.000000 -3672.000000 31224.000000 -3720.000000 c 0
31224.000000 -6068.000000 l 0
31336.000000 -6100.000000 31456.000000 -6108.000000 31592.000000 -6108.000000 c 0
32252.000000 -6108.000000 32860.000000 -5692.000000 32860.000000 -4800.000000 c 0
32864.000000 -4032.000000 32424.000000 -3632.000000 31788.000000 -3632.000000 c 0
35404.000000 -5088.000000 m 0
35428.000000 -5652.000000 35796.000000 -6100.000000 36616.000000 -6100.000000 c 0
36976.000000 -6100.000000 37308.000000 -6036.000000 37612.000000 -5908.000000 c 0
37636.000000 -5892.000000 37660.000000 -5900.000000 37660.000000 -5940.000000 c 0
37660.000000 -6608.000000 l 0
37660.000000 -6656.000000 37644.000000 -6680.000000 37612.000000 -6696.000000 c 0
37308.000000 -6840.000000 36952.000000 -6928.000000 36408.000000 -6928.000000 c 0
34944.000000 -6928.000000 34424.000000 -5900.000000 34424.000000 -4912.000000 c 0
34424.000000 -3796.000000 35100.000000 -2808.000000 36312.000000 -2808.000000 c 0
37500.000000 -2808.000000 37960.000000 -3740.000000 37960.000000 -4504.000000 c 0
37960.000000 -4728.000000 37952.000000 -4912.000000 37928.000000 -5004.000000 c 0
37920.000000 -5036.000000 37904.000000 -5052.000000 37864.000000 -5060.000000 c 0
37752.000000 -5084.000000 37432.000000 -5092.000000 37036.000000 -5092.000000 c 0
35404.000000 -5092.000000 l 0
35404.000000 -5088.000000 l 0
36600.000000 -4396.000000 m 0
36904.000000 -4396.000000 37008.000000 -4396.000000 37040.000000 -4388.000000 c 0
37040.000000 -4364.000000 37040.000000 -4332.000000 37040.000000 -4324.000000 c 0
37040.000000 -4092.000000 36880.000000 -3576.000000 36268.000000 -3576.000000 c 0
35712.000000 -3576.000000 35464.000000 -4000.000000 35400.000000 -4396.000000 c 0
36600.000000 -4396.000000 l 0
EndSplineSet
EndChar
EndChars
EndSplineFont