2019-09-08 23:17:13 +03:00
|
|
|
#!/usr/bin/env python3
|
2015-06-25 12:07:58 +03:00
|
|
|
|
|
|
|
"""
|
|
|
|
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:
|
2019-10-04 20:33:51 +03:00
|
|
|
from fontTools import ttLib
|
2015-06-25 12:07:58 +03:00
|
|
|
except ImportError:
|
2017-09-13 15:10:14 +03:00
|
|
|
print("ERROR: FontTools Python module is not installed.", file=sys.stderr)
|
|
|
|
sys.exit(1)
|
2015-06-25 12:07:58 +03:00
|
|
|
|
2016-04-22 11:09:40 +03:00
|
|
|
TABLE_TAG = 'SVG '
|
|
|
|
|
2015-11-08 02:38:56 +03:00
|
|
|
# Regexp patterns
|
|
|
|
reSVGelement = re.compile(r"<svg.+?>.+?</svg>", re.DOTALL)
|
|
|
|
reIDvalue = re.compile(r"<svg[^>]+?(id=\".*?\").+?>", re.DOTALL)
|
|
|
|
reViewBox = re.compile(r"<svg.+?(viewBox=[\"|\'][\d, ]+[\"|\']).+?>", re.DOTALL)
|
2016-05-02 12:14:46 +03:00
|
|
|
reWhiteSpace = re.compile(r">\s+<", re.DOTALL)
|
2015-11-08 02:38:56 +03:00
|
|
|
|
|
|
|
|
2015-06-25 12:07:58 +03:00
|
|
|
def readFile(filePath):
|
2019-09-08 23:17:13 +03:00
|
|
|
with open(filePath, "rt") as f:
|
|
|
|
return f.read()
|
2015-06-25 12:07:58 +03:00
|
|
|
|
|
|
|
|
|
|
|
def setIDvalue(data, gid):
|
2017-09-13 15:10:14 +03:00
|
|
|
id = reIDvalue.search(data)
|
|
|
|
if id:
|
|
|
|
newData = re.sub(id.group(1), 'id="glyph{}"'.format(gid), data)
|
|
|
|
else:
|
|
|
|
newData = re.sub('<svg', '<svg id="glyph{}"'.format(gid), data)
|
|
|
|
return newData
|
2015-06-25 12:07:58 +03:00
|
|
|
|
|
|
|
|
|
|
|
def fixViewBox(data):
|
2017-09-13 15:10:14 +03:00
|
|
|
viewBox = reViewBox.search(data)
|
|
|
|
if not viewBox:
|
|
|
|
return data
|
|
|
|
fixedViewBox = 'viewBox=\"0 1000 1000 1000\"'
|
|
|
|
fixedData = re.sub(viewBox.group(1), fixedViewBox, data)
|
|
|
|
return fixedData
|
2015-06-25 12:07:58 +03:00
|
|
|
|
|
|
|
|
|
|
|
def getGlyphNameFromFileName(filePath):
|
2017-09-13 15:10:14 +03:00
|
|
|
folderPath, fontFileName = os.path.split(filePath)
|
|
|
|
fileNameNoExtension, fileExtension = os.path.splitext(fontFileName)
|
|
|
|
return fileNameNoExtension
|
2015-06-25 12:07:58 +03:00
|
|
|
|
|
|
|
|
|
|
|
def processFontFile(fontFilePath, svgFilePathsList):
|
2017-09-13 15:10:14 +03:00
|
|
|
font = ttLib.TTFont(fontFilePath)
|
|
|
|
|
|
|
|
# first create a dictionary because the SVG glyphs need to be sorted in the table
|
|
|
|
svgDocsDict = {}
|
|
|
|
|
|
|
|
for svgFilePath in svgFilePathsList:
|
|
|
|
gName = getGlyphNameFromFileName(svgFilePath)
|
|
|
|
|
|
|
|
try:
|
|
|
|
gid = font.getGlyphID(gName)
|
|
|
|
except KeyError:
|
|
|
|
print(
|
|
|
|
"ERROR: Could not find a glyph named {} in the font {}.".format(
|
|
|
|
gName, os.path.split(fontFilePath)[1]
|
|
|
|
),
|
|
|
|
file=sys.stderr
|
|
|
|
)
|
|
|
|
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(
|
|
|
|
"ERROR: Could not find any artwork files "
|
|
|
|
"that can be added to the font.",
|
|
|
|
file=sys.stderr
|
|
|
|
)
|
|
|
|
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
|
2018-01-19 10:33:51 +03:00
|
|
|
font.save(fontFilePath)
|
2017-09-13 15:10:14 +03:00
|
|
|
font.close()
|
|
|
|
|
|
|
|
print(
|
|
|
|
"SVG table successfully added to {}".format(fontFilePath),
|
|
|
|
file=sys.stderr
|
|
|
|
)
|
2015-07-15 13:08:39 +03:00
|
|
|
|
2015-06-25 12:07:58 +03:00
|
|
|
|
|
|
|
def validateSVGfiles(svgFilePathsList):
|
2017-09-13 15:10:14 +03:00
|
|
|
"""
|
|
|
|
Light validation of SVG files.
|
|
|
|
Checks that there is an <svg> element.
|
|
|
|
"""
|
|
|
|
validatedPaths = []
|
2015-06-25 12:07:58 +03:00
|
|
|
|
2017-09-13 15:10:14 +03:00
|
|
|
for filePath in svgFilePathsList:
|
|
|
|
# skip hidden files (filenames that start with period)
|
|
|
|
fileName = os.path.basename(filePath)
|
|
|
|
if fileName[0] == '.':
|
|
|
|
continue
|
2015-06-25 12:07:58 +03:00
|
|
|
|
2017-09-13 15:10:14 +03:00
|
|
|
# read file
|
|
|
|
data = readFile(filePath)
|
2015-06-25 12:07:58 +03:00
|
|
|
|
2017-09-13 15:10:14 +03:00
|
|
|
# find <svg> blob
|
|
|
|
svg = reSVGelement.search(data)
|
|
|
|
if not svg:
|
|
|
|
print(
|
|
|
|
"WARNING: Could not find <svg> element in the file. "
|
|
|
|
"Skiping {}".format(filePath)
|
|
|
|
)
|
|
|
|
continue
|
2015-06-25 12:07:58 +03:00
|
|
|
|
2017-09-13 15:10:14 +03:00
|
|
|
validatedPaths.append(filePath)
|
2015-06-25 12:07:58 +03:00
|
|
|
|
2017-09-13 15:10:14 +03:00
|
|
|
return validatedPaths
|
2015-06-25 12:07:58 +03:00
|
|
|
|
|
|
|
|
|
|
|
def getFontFormat(fontFilePath):
|
2017-09-13 15:10:14 +03:00
|
|
|
# these lines were scavenged from fontTools
|
2019-09-08 23:17:13 +03:00
|
|
|
with open(fontFilePath, "rb") as f:
|
|
|
|
header = f.read(256)
|
|
|
|
head = header[:4]
|
2019-06-10 10:31:13 +03:00
|
|
|
if head == b"OTTO":
|
2017-09-13 15:10:14 +03:00
|
|
|
return "OTF"
|
2019-06-10 10:31:13 +03:00
|
|
|
elif head in (b"\0\1\0\0", b"true"):
|
2017-09-13 15:10:14 +03:00
|
|
|
return "TTF"
|
|
|
|
return None
|
2015-06-25 12:07:58 +03:00
|
|
|
|
|
|
|
|
|
|
|
def run():
|
2017-09-13 15:10:14 +03:00
|
|
|
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("ERROR: The path is not a valid OTF or TTF font.",
|
|
|
|
file=sys.stderr)
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
print("ERROR: The path to the font is invalid.",
|
|
|
|
file=sys.stderr)
|
|
|
|
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(
|
|
|
|
"ERROR: The path to the folder "
|
|
|
|
"containing the SVG files is invalid.",
|
|
|
|
file=sys.stderr
|
|
|
|
)
|
|
|
|
return
|
|
|
|
|
|
|
|
# validate the SVGs
|
|
|
|
svgFilePathsList = validateSVGfiles(svgFilePathsList)
|
|
|
|
|
|
|
|
if not svgFilePathsList:
|
|
|
|
print("WARNING: No SVG files were found.", file=sys.stderr)
|
|
|
|
return
|
|
|
|
|
|
|
|
processFontFile(fontFilePath, svgFilePathsList)
|
2015-06-25 12:07:58 +03:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2017-09-13 15:10:14 +03:00
|
|
|
if len(sys.argv) != 3:
|
|
|
|
print("To run this script type:\n "
|
|
|
|
"python {} <path to input OTF/TTF file> "
|
|
|
|
"<path to folder tree containing SVG files>".format(sys.argv[0]))
|
|
|
|
else:
|
|
|
|
run()
|