#!/usr/bin/env python """ Adds an SVG table to a TTF or OTF font. The file names of the SVG glyphs need to match their corresponding glyph final names. """ import os import sys import re try: from fontTools import ttLib, version except ImportError: print >> sys.stderr, "ERROR: FontTools Python module is not installed." sys.exit(1) TABLE_TAG = 'SVG ' # Regexp patterns reSVGelement = re.compile(r".+?", re.DOTALL) reIDvalue = re.compile(r"]+?(id=\".*?\").+?>", re.DOTALL) reViewBox = re.compile(r"", re.DOTALL) reWhiteSpace = re.compile(r">\s+<", re.DOTALL) def readFile(filePath): f = open(filePath, "rt") data = f.read() f.close() return data def setIDvalue(data, gid): id = reIDvalue.search(data) if id: newData = re.sub(id.group(1), 'id="glyph%s"' % gid, data) else: newData = re.sub('> sys.stderr, "ERROR: Could not find a glyph named %s in the font %s." % (gName, os.path.split(fontFilePath)[1]) continue svgItemsList = [] svgItemData = readFile(svgFilePath) svgItemData = setIDvalue(svgItemData, gid) svgItemData = fixViewBox(svgItemData) # Remove all white space between elements for whiteSpace in set(reWhiteSpace.findall(svgItemData)): svgItemData = svgItemData.replace(whiteSpace, '><') svgItemsList.append(svgItemData.strip()) svgItemsList.extend([gid, gid]) svgDocsDict[gid] = svgItemsList # don't do any changes to the source OTF/TTF font if there's no SVG data if not svgDocsDict: print >> sys.stderr, "ERROR: Could not find any artwork files that can be added to the font." return svgDocsList = [svgDocsDict[index] for index in sorted(svgDocsDict.keys())] svgTable = ttLib.newTable(TABLE_TAG) svgTable.compressed = False # GZIP the SVG docs svgTable.docList = svgDocsList font[TABLE_TAG] = svgTable # FontTools can't overwrite a font on save, # so save to a hidden file, and then rename it # https://github.com/behdad/fonttools/issues/302 folderPath, fontFileName = os.path.split(fontFilePath) fileNameNoExtension, fileExtension = os.path.splitext(fontFileName) newFontFilePath = os.path.join(folderPath, "%s%s%s" % ('.', fileNameNoExtension, fileExtension)) font.save(newFontFilePath) font.close() # On Windows a file can't be renamed to a file that already exists os.remove(fontFilePath) os.rename(newFontFilePath, fontFilePath) print >> sys.stdout, "SVG table successfully added to %s" % fontFilePath def validateSVGfiles(svgFilePathsList): """ Light validation of SVG files. Checks that there is an element. """ validatedPaths = [] for filePath in svgFilePathsList: # skip hidden files (filenames that start with period) fileName = os.path.basename(filePath) if fileName[0] == '.': continue # read file data = readFile(filePath) # find blob svg = reSVGelement.search(data) if not svg: print "WARNING: Could not find element in the file. Skiping %s" % (filePath) continue validatedPaths.append(filePath) return validatedPaths def getFontFormat(fontFilePath): # these lines were scavenged from fontTools f = open(fontFilePath, "rb") header = f.read(256) head = header[:4] f.close() if head == "OTTO": return "OTF" elif head in ("\0\1\0\0", "true"): return "TTF" return None def run(): fontFilePath = os.path.realpath(sys.argv[1]) svgFolderPath = os.path.realpath(sys.argv[2]) # Font file path if os.path.isfile(fontFilePath): if getFontFormat(fontFilePath) not in ["OTF", "TTF"]: print >> sys.stderr, "ERROR: The path is not a valid OTF or TTF font." return else: print >> sys.stderr, "ERROR: The path to the font is invalid." return # SVG folder path if os.path.isdir(svgFolderPath): svgFilePathsList = [] for dirName, subdirList, fileList in os.walk(svgFolderPath): # Support nested folders for file in fileList: svgFilePathsList.append(os.path.join(dirName, file)) # Assemble the full paths, not just file names else: print >> sys.stderr, "ERROR: The path to the folder containing the SVG files is invalid." return # validate the SVGs svgFilePathsList = validateSVGfiles(svgFilePathsList) if not svgFilePathsList: print >> sys.stderr, "WARNING: No SVG files were found." return processFontFile(fontFilePath, svgFilePathsList) if __name__ == "__main__": if len(sys.argv) != 3: print "To run this script type:\n python %s " % sys.argv[0] else: run()