Search code examples
pythongoogle-material-iconsfontforge

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?


Solution

  • 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