#!/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 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() 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 def glyphToSVG(g): width, xoffs = svgWidth(g) 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, # '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] 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: lnameId = nameToIdMap.get(lname) rnameId = nameToIdMap.get(rname) if lnameId and rnameId: pairs.append([lnameId, rnameId, v]) # 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='', type=str, default='', help='Scale glyph. Should be a number in the range (0-1]. Defaults to %g' % scale) argparser.add_argument('ufopath', metavar='', type=str, help='Path to UFO packages') argparser.add_argument('glyphs', metavar='', type=str, nargs='*', help='Only generate specific glyphs.') args = argparser.parse_args() srcDir = os.path.join(BASEDIR, 'src') deleteNames = set(['.notdef', '.null']) if len(args.scale): scale = float(args.scale) ufopath = args.ufopath.rstrip('/') font = Font(ufopath) effectiveAscender = max(font.info.ascender, font.info.unitsPerEm) # print('\n'.join(font.keys())) # sys.exit(0) deleteNames.add('.notdef') deleteNames.add('.null') glyphnames = args.glyphs if len(args.glyphs) else font.keys() glyphnameSet = set(glyphnames) glyphnames = [gn for gn in glyphnames if gn not in deleteNames] glyphnames.sort() nameToIdMap, idToNameMap = genGlyphIDs(glyphnames) glyphMetrics = {} # jsonLines = [] svgLines = [] for glyphname in glyphnames: generateFrom = None svg, metrics = genGlyph(glyphname) # 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') html = u'' with open(glyphsHtmlFilename, 'r') as f: html = f.read().decode('utf8') startMarker = u'
' startPos = html.find(startMarker) endMarker = u'