python - 将 SVG 转换为 FontForge 的 SplineSet 格式

标签 python google-material-icons fontforge

我正在尝试根据其源 svg 文件重新编译 ttf 字形字体。
我正在使用 python 和 FontForge 并努力将 SVG 形状转换为 FontForge 的 SplineSet 格式。例如,this SVG file
enter image description here
以 FontForge 生成的字体表示为这种 SplineSet 格式

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

我想创建一个接收 SVG 并输出 SplineSet 的函数。
这是我到目前为止所尝试的(一种生成临时字体并随后刮取 SplineSet 数据的低效、幼稚的方法。顺便说一句,与真实字体相比,这是非常不准确的)
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')
关于如何做到这一点的任何引用?

最佳答案

使用来自 FontForge GitHub 项目的 svg2sfd.py
归档文件:

A simple script to convert svg files (generated by Illustrator plugin) to sfd, from which Fontforge can then create a font file.


文件内容:
#!/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:])
例子:
转换了这个 Adobe Logo and WordMark
Adobe Logo and WordMark
到:
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

关于python - 将 SVG 转换为 FontForge 的 SplineSet 格式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56506104/

相关文章:

python - 使用 Pandas Data Frame 如何将计数应用于多级分组列?

python - 我无法多次加载其中包含空列表的文件

java - Itext 7 在 pdf 中使用 Material ui 图标

macos - 将字形/字符从一个字体文件复制/粘贴到另一个

poppler - pdf2HtmlEX - html 上的文本与源 pdf 不同

python - 获取 AWS 中实例的公共(public) ip

python - 使用哪个 REST 动词?

AngularJS 和 Material 图标

css - Material 图标将 Electron OSX连字

python - 通过 FontForge Python 绑定(bind)更改字体的 em 大小