Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

converting an SVG to FontForge's SplineSet format

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

enter image description here

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?

like image 537
Jossef Harush Kadouri Avatar asked Oct 28 '22 13:10

Jossef Harush Kadouri


1 Answers

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 ([email protected])

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.

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
like image 67
Aviv Yaniv Avatar answered Nov 15 '22 06:11

Aviv Yaniv