1
1
mirror of https://github.com/rsms/inter.git synced 2024-12-15 10:43:00 +03:00
inter/misc/tools/gen-metrics-and-svgs.py

345 lines
8.6 KiB
Python
Raw Normal View History

2017-08-28 12:36:40 +03:00
#!/usr/bin/env python
# encoding: utf8
#
# Sync glyph shapes between SVG and UFO, creating a bridge between UFO and Figma.
#
from __future__ import print_function
import os, sys
from os.path import dirname, basename, abspath, relpath, join as pjoin
sys.path.append(abspath(pjoin(dirname(__file__), 'tools')))
from common import BASEDIR
import argparse
import json
import plistlib
import re
from collections import OrderedDict
from math import ceil, floor
from defcon import Font
from svg import SVGPathPen
2017-08-28 12:36:40 +03:00
font = None # RFont
ufopath = ''
effectiveAscender = 0
scale = 0.1
def num(s):
return int(s) if s.find('.') == -1 else float(s)
def decomposeGlyph(font, glyph):
"""Moves the components of a glyph to its outline."""
if len(glyph.components):
deepCopyContours(font, glyph, glyph, (0, 0), (1, 1))
glyph.clearComponents()
def deepCopyContours(font, parent, component, offset, scale):
"""Copy contours to parent from component, including nested components."""
for nested in component.components:
deepCopyContours(
font, parent, font[nested.baseGlyph],
(offset[0] + nested.offset[0], offset[1] + nested.offset[1]),
(scale[0] * nested.scale[0], scale[1] * nested.scale[1]))
if component == parent:
return
for contour in component:
contour = contour.copy()
contour.scale(scale)
contour.move(offset)
parent.appendContour(contour)
def glyphToSVGPath(g, yMul):
pen = SVGPathPen(g.getParent(), yMul)
g.draw(pen)
return pen.getCommands()
2017-08-28 12:36:40 +03:00
def svgWidth(g):
bounds = g.bounds # (xMin, yMin, xMax, yMax)
if bounds is None:
return 0, 0
xMin = bounds[0]
xMax = bounds[2]
width = xMax - xMin
return width, xMin
2017-08-28 12:36:40 +03:00
def glyphToSVG(g):
width, xoffs = svgWidth(g)
svg = '''
<svg id="svg-%(name)s" xmlns="http://www.w3.org/2000/svg" width="%(width)d" height="%(height)d">
<path d="%(glyphSVGPath)s" transform="translate(%(xoffs)g %(yoffs)g) scale(%(scale)g)"/>
2017-08-28 12:36:40 +03:00
</svg>
''' % {
'name': g.name,
'width': int(ceil(width * scale)),
'height': int(ceil((effectiveAscender - font.info.descender) * scale)),
'xoffs': -(xoffs * scale),
'yoffs': effectiveAscender * scale,
# 'leftMargin': g.leftMargin * scale,
# 'rightMargin': g.rightMargin * scale,
'glyphSVGPath': glyphToSVGPath(g, -1),
# 'ascender': font.info.ascender * scale,
# 'descender': font.info.descender * scale,
# 'baselineOffset': (font.info.unitsPerEm + font.info.descender) * scale,
# 'unitsPerEm': font.info.unitsPerEm,
'scale': scale,
2017-08-28 12:36:40 +03:00
# 'margin': [g.leftMargin * scale, g.rightMargin * scale],
}
# (width, advance, left, right)
info = (width, g.width, g.leftMargin, g.rightMargin)
return svg.strip(), info
def stat(path):
try:
return os.stat(path)
except OSError as e:
return None
def writeFile(file, s):
with open(file, 'w') as f:
f.write(s)
def writeFileAndMkDirsIfNeeded(file, s):
try:
writeFile(file, s)
except IOError as e:
if e.errno == 2:
os.makedirs(os.path.dirname(file))
writeFile(file, s)
def findGlifFile(glyphname):
# glyphname.glif
# glyphname_.glif
# glyphname__.glif
# glyphname___.glif
for underscoreCount in range(0, 5):
fn = os.path.join(ufopath, 'glyphs', glyphname + ('_' * underscoreCount) + '.glif')
st = stat(fn)
if st is not None:
return fn, st
if glyphname.find('.') != -1:
# glyph_.name.glif
# glyph__.name.glif
# glyph___.name.glif
for underscoreCount in range(0, 5):
nv = glyphname.split('.')
nv[0] = nv[0] + ('_' * underscoreCount)
ns = '.'.join(nv)
fn = os.path.join(ufopath, 'glyphs', ns + '.glif')
st = stat(fn)
if st is not None:
return fn, st
if glyphname.find('_') != -1:
# glyph_name.glif
# glyph_name_.glif
# glyph_name__.glif
# glyph__name.glif
# glyph__name_.glif
# glyph__name__.glif
# glyph___name.glif
# glyph___name_.glif
# glyph___name__.glif
for x in range(0, 4):
for y in range(0, 5):
ns = glyphname.replace('_', '__' + ('_' * x))
fn = os.path.join(ufopath, 'glyphs', ns + ('_' * y) + '.glif')
st = stat(fn)
if st is not None:
return fn, st
return ('', None)
usedSVGNames = set()
def genGlyph(glyphName):
g = font[glyphName]
2017-08-28 12:36:40 +03:00
return glyphToSVG(g)
def genGlyphIDs(glyphnames):
nameToIdMap = {}
idToNameMap = {}
nextId = 0
for name in glyphnames:
nameToIdMap[name] = nextId
idToNameMap[nextId] = name
nextId += 1
return nameToIdMap, idToNameMap
def genKerningInfo(font, glyphnames, nameToIdMap):
kerning = font.kerning
# load groups
filename = os.path.join(font.path, 'groups.plist')
groups = plistlib.readPlist(filename)
pairs = []
for kt in kerning.keys():
v = kerning[kt]
leftname, rightname = kt
leftnames = []
rightnames = []
if leftname[0] == '@':
leftnames = groups[leftname]
else:
leftnames = [leftname]
if rightname[0] == '@':
rightnames = groups[rightname]
else:
rightnames = [rightname]
for lname in leftnames:
for rname in rightnames:
2017-08-28 21:31:42 +03:00
lnameId = nameToIdMap.get(lname)
rnameId = nameToIdMap.get(rname)
if lnameId and rnameId:
pairs.append([lnameId, rnameId, v])
2017-08-28 12:36:40 +03:00
# print('pairs: %r' % pairs)
return pairs
def fmtJsonDict(d):
keys = sorted(d.keys())
s = '{'
delim = '\n'
delimNth = ',\n'
for k in keys:
v = d[k]
s += delim + json.dumps(str(k)) + ':' + json.dumps(v)
delim = delimNth
return s + '}'
def fmtJsonList(d):
s = '['
delim = '\n'
delimNth = ',\n'
for t in kerning:
s += delim + json.dumps(t, separators=(',',':'))
delim = delimNth
return s + ']'
# ————————————————————————————————————————————————————————————————————————
# main
argparser = argparse.ArgumentParser(description='Generate SVG glyphs from UFO')
argparser.add_argument('-scale', dest='scale', metavar='<scale>', type=str,
default='',
help='Scale glyph. Should be a number in the range (0-1]. Defaults to %g' % scale)
argparser.add_argument('ufopath', metavar='<ufopath>', type=str,
help='Path to UFO packages')
argparser.add_argument('glyphs', metavar='<glyphname>', type=str, nargs='*',
help='Only generate specific glyphs.')
args = argparser.parse_args()
srcDir = os.path.join(BASEDIR, 'src')
deleteNames = set(['.notdef', '.null'])
2017-08-28 12:36:40 +03:00
if len(args.scale):
scale = float(args.scale)
ufopath = args.ufopath.rstrip('/')
font = Font(ufopath)
2017-08-28 12:36:40 +03:00
effectiveAscender = max(font.info.ascender, font.info.unitsPerEm)
# print('\n'.join(font.keys()))
# sys.exit(0)
deleteNames.add('.notdef')
deleteNames.add('.null')
2017-08-28 12:36:40 +03:00
glyphnames = args.glyphs if len(args.glyphs) else font.keys()
glyphnameSet = set(glyphnames)
glyphnames = [gn for gn in glyphnames if gn not in deleteNames]
2017-08-28 12:36:40 +03:00
glyphnames.sort()
nameToIdMap, idToNameMap = genGlyphIDs(glyphnames)
glyphMetrics = {}
# jsonLines = []
svgLines = []
for glyphname in glyphnames:
generateFrom = None
svg, metrics = genGlyph(glyphname)
2017-08-28 12:36:40 +03:00
# metrics: (width, advance, left, right)
glyphMetrics[nameToIdMap[glyphname]] = metrics
svgLines.append(svg.replace('\n', ''))
# print('{\n' + ',\n'.join(jsonLines) + '\n}')
svgtext = '\n'.join(svgLines)
# print(svgtext)
glyphsHtmlFilename = os.path.join(BASEDIR, 'docs', 'glyphs', 'index.html')
2017-08-28 12:36:40 +03:00
html = u''
2017-08-28 12:36:40 +03:00
with open(glyphsHtmlFilename, 'r') as f:
html = f.read().decode('utf8')
2017-08-28 12:36:40 +03:00
startMarker = u'<div id="svgs">'
2017-08-28 12:36:40 +03:00
startPos = html.find(startMarker)
endMarker = u'</div><!--END-SVGS'
2017-08-28 12:36:40 +03:00
endPos = html.find(endMarker, startPos + len(startMarker))
relfilename = os.path.relpath(glyphsHtmlFilename, os.getcwd())
if startPos == -1 or endPos == -1:
msg = 'Could not find `<div id="svgs">...</div><!--END-SVGS` in %s'
print(msg % relfilename, file=sys.stderr)
sys.exit(1)
kerning = genKerningInfo(font, glyphnames, nameToIdMap)
metaJson = '{\n'
metaJson += '"nameids":' + fmtJsonDict(idToNameMap) + ',\n'
metaJson += '"metrics":' + fmtJsonDict(glyphMetrics) + ',\n'
metaJson += '"kerning":' + fmtJsonList(kerning) + '\n'
metaJson += '}'
# metaHtml = '<script>var fontMetaData = ' + metaJson + ';</script>'
html = html[:startPos + len(startMarker)] + '\n' + svgtext.decode('utf8') + '\n' + html[endPos:]
2017-08-28 12:36:40 +03:00
print('write', relfilename)
with open(glyphsHtmlFilename, 'w') as f:
f.write(html.encode('utf8'))
2017-08-28 12:36:40 +03:00
# JSON
jsonFilename = os.path.join(BASEDIR, 'docs', 'glyphs', 'metrics.json')
2017-08-28 12:36:40 +03:00
jsonFilenameRel = os.path.relpath(jsonFilename, os.getcwd())
print('write', jsonFilenameRel)
with open(jsonFilename, 'w') as f:
f.write(metaJson)
metaJson