diff --git a/Lib/fontTools/misc/xmlReader.py b/Lib/fontTools/misc/xmlReader.py
index a8931bb10..438549d7d 100644
--- a/Lib/fontTools/misc/xmlReader.py
+++ b/Lib/fontTools/misc/xmlReader.py
@@ -17,7 +17,8 @@ BUFSIZE = 0x4000
class XMLReader(object):
- def __init__(self, fileOrPath, ttFont, progress=None, quiet=None):
+ def __init__(self, fileOrPath, ttFont, progress=None, quiet=None, contentOnly=False):
+
if fileOrPath == '-':
fileOrPath = sys.stdin
if not hasattr(fileOrPath, "read"):
@@ -35,6 +36,7 @@ class XMLReader(object):
self.quiet = quiet
self.root = None
self.contentStack = []
+ self.contentOnly = contentOnly
self.stackSize = 0
def read(self, rootless=False):
@@ -73,8 +75,24 @@ class XMLReader(object):
parser.Parse(chunk, 0)
def _startElementHandler(self, name, attrs):
+ if self.stackSize == 1 and self.contentOnly:
+ # We already know the table we're parsing, skip
+ # parsing the table tag and continue to
+ # stack '2' which begins parsing content
+ self.contentStack.append([])
+ self.stackSize = 2
+ return
stackSize = self.stackSize
self.stackSize = stackSize + 1
+ subFile = attrs.get("src")
+ if subFile is not None:
+ if hasattr(self.file, 'name'):
+ # if file has a name, get its parent directory
+ dirname = os.path.dirname(self.file.name)
+ else:
+ # else fall back to using the current working directory
+ dirname = os.getcwd()
+ subFile = os.path.join(dirname, subFile)
if not stackSize:
if name != "ttFont":
raise TTXParseError("illegal root tag: %s" % name)
@@ -85,15 +103,7 @@ class XMLReader(object):
self.ttFont.sfntVersion = sfntVersion
self.contentStack.append([])
elif stackSize == 1:
- subFile = attrs.get("src")
if subFile is not None:
- if hasattr(self.file, 'name'):
- # if file has a name, get its parent directory
- dirname = os.path.dirname(self.file.name)
- else:
- # else fall back to using the current working directory
- dirname = os.getcwd()
- subFile = os.path.join(dirname, subFile)
subReader = XMLReader(subFile, self.ttFont, self.progress)
subReader.read()
self.contentStack.append([])
@@ -119,6 +129,11 @@ class XMLReader(object):
self.currentTable = tableClass(tag)
self.ttFont[tag] = self.currentTable
self.contentStack.append([])
+ elif stackSize == 2 and subFile is not None:
+ subReader = XMLReader(subFile, self.ttFont, self.progress, contentOnly=True)
+ subReader.read()
+ self.contentStack.append([])
+ self.root = subReader.root
elif stackSize == 2:
self.contentStack.append([])
self.root = (name, attrs, self.contentStack[-1])
@@ -134,12 +149,13 @@ class XMLReader(object):
def _endElementHandler(self, name):
self.stackSize = self.stackSize - 1
del self.contentStack[-1]
- if self.stackSize == 1:
- self.root = None
- elif self.stackSize == 2:
- name, attrs, content = self.root
- self.currentTable.fromXML(name, attrs, content, self.ttFont)
- self.root = None
+ if not self.contentOnly:
+ if self.stackSize == 1:
+ self.root = None
+ elif self.stackSize == 2:
+ name, attrs, content = self.root
+ self.currentTable.fromXML(name, attrs, content, self.ttFont)
+ self.root = None
class ProgressPrinter(object):
diff --git a/Lib/fontTools/ttLib/__init__.py b/Lib/fontTools/ttLib/__init__.py
index a9db4bba0..100903f9a 100644
--- a/Lib/fontTools/ttLib/__init__.py
+++ b/Lib/fontTools/ttLib/__init__.py
@@ -247,7 +247,8 @@ class TTFont(object):
def saveXML(self, fileOrPath, progress=None, quiet=None,
tables=None, skipTables=None, splitTables=False, disassembleInstructions=True,
- bitmapGlyphDataFormat='raw', newlinestr=None):
+ splitGlyphs=False, bitmapGlyphDataFormat='raw', newlinestr=None):
+
"""Export the font as TTX (an XML-based text file), or as a series of text
files when splitTables is true. In the latter case, the 'fileOrPath'
argument should be a path to a directory.
@@ -290,7 +291,7 @@ class TTFont(object):
if not splitTables:
writer.newline()
- else:
+ if splitTables or splitGlyphs:
# 'fileOrPath' must now be a path
path, ext = os.path.splitext(fileOrPath)
fileNameTemplate = path + ".%s" + ext
@@ -299,8 +300,11 @@ class TTFont(object):
if progress:
progress.set(i)
tag = tables[i]
- if splitTables:
+ if splitTables or (splitGlyphs and tag == 'glyf'):
tablePath = fileNameTemplate % tagToIdentifier(tag)
+ else:
+ tablePath = None
+ if splitTables:
tableWriter = xmlWriter.XMLWriter(tablePath, idlefunc=idlefunc,
newlinestr=newlinestr)
tableWriter.begintag("ttFont", ttLibVersion=version)
@@ -310,7 +314,7 @@ class TTFont(object):
writer.newline()
else:
tableWriter = writer
- self._tableToXML(tableWriter, tag, progress)
+ self._tableToXML(tableWriter, tag, progress, splitGlyphs=splitGlyphs)
if splitTables:
tableWriter.endtag("ttFont")
tableWriter.newline()
@@ -324,7 +328,7 @@ class TTFont(object):
if not hasattr(fileOrPath, "write") and fileOrPath != "-":
writer.close()
- def _tableToXML(self, writer, tag, progress, quiet=None):
+ def _tableToXML(self, writer, tag, progress, splitGlyphs=False, quiet=None):
if quiet is not None:
deprecateArgument("quiet", "configure logging instead")
if tag in self:
@@ -346,7 +350,9 @@ class TTFont(object):
attrs['raw'] = True
writer.begintag(xmlTag, **attrs)
writer.newline()
- if tag in ("glyf", "CFF "):
+ if tag == "glyf":
+ table.toXML(writer, self, progress, splitGlyphs)
+ elif tag == "CFF ":
table.toXML(writer, self, progress)
else:
table.toXML(writer, self)
@@ -873,8 +879,8 @@ def _escapechar(c):
return hex(byteord(c))[2:]
-def tagToIdentifier(tag):
- """Convert a table tag to a valid (but UGLY) python identifier,
+def nameToIdentifier(name):
+ """Convert a name to a valid (but UGLY) python identifier,
as well as a filename that's guaranteed to be unique even on a
caseless file system. Each character is mapped to two characters.
Lowercase letters get an underscore before the letter, uppercase
@@ -887,19 +893,25 @@ def tagToIdentifier(tag):
'OS/2' -> 'O_S_2f_2'
"""
import re
- tag = Tag(tag)
- if tag == "GlyphOrder":
- return tag
- assert len(tag) == 4, "tag should be 4 characters long"
- while len(tag) > 1 and tag[-1] == ' ':
- tag = tag[:-1]
+ while len(name) > 1 and name[-1] == ' ':
+ name = name[:-1]
ident = ""
- for c in tag:
+ for c in name:
ident = ident + _escapechar(c)
if re.match("[0-9]", ident):
ident = "_" + ident
return ident
+def tagToIdentifier(tag):
+ """This performs the same conversion which nameToIdentifiier does
+ with the additional assertion that the source tag is 4 characters
+ long which is criteria for a valid tag name.
+ """
+ if tag == "GlyphOrder":
+ return tag
+ ret = nameToIdentifier(tag)
+ assert len(tag) == 4, "tag should be 4 characters long"
+ return ret
def identifierToTag(ident):
"""the opposite of tagToIdentifier()"""
diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
index 52117129f..966e77ea2 100644
--- a/Lib/fontTools/ttLib/tables/_g_l_y_f.py
+++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
@@ -5,6 +5,7 @@ from collections import namedtuple
from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools import ttLib
+from fontTools import version
from fontTools.misc.textTools import safeEval, pad
from fontTools.misc.arrayTools import calcBounds, calcIntBounds, pointInRect
from fontTools.misc.bezierTools import calcQuadraticBounds
@@ -16,10 +17,17 @@ import sys
import struct
import array
import logging
-
+import os
+from fontTools.misc import xmlWriter
+from fontTools.ttLib import nameToIdentifier
log = logging.getLogger(__name__)
+# We compute the version the same as is computed in ttlib/__init__
+# so that we can write 'ttLibVersion' attribute of the glyf TTX files
+# when glyf is written to separate files.
+version = ".".join(version.split('.')[:2])
+
#
# The Apple and MS rasterizers behave differently for
# scaled composite components: one does scale first and then translate
@@ -110,7 +118,8 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
ttFont['maxp'].numGlyphs = len(self.glyphs)
return data
- def toXML(self, writer, ttFont, progress=None):
+ def toXML(self, writer, ttFont, progress=None, splitGlyphs=False):
+
writer.newline()
glyphNames = ttFont.getGlyphNames()
writer.comment("The xMin, yMin, xMax and yMax values\nwill be recalculated by the compiler.")
@@ -126,17 +135,39 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
counter = counter + 1
glyph = self[glyphName]
if glyph.numberOfContours:
- writer.begintag('TTGlyph', [
- ("name", glyphName),
- ("xMin", glyph.xMin),
- ("yMin", glyph.yMin),
- ("xMax", glyph.xMax),
- ("yMax", glyph.yMax),
- ])
- writer.newline()
- glyph.toXML(writer, ttFont)
- writer.endtag('TTGlyph')
- writer.newline()
+ if splitGlyphs:
+ path, ext = os.path.splitext(writer.file.name)
+ fileNameTemplate = path + ".%s" + ext
+ glyphPath = fileNameTemplate % nameToIdentifier(glyphName)
+ glyphWriter = xmlWriter.XMLWriter(glyphPath, idlefunc=writer.idlefunc,
+ newlinestr=writer.newlinestr)
+ glyphWriter.begintag("ttFont", ttLibVersion=version)
+ glyphWriter.newline()
+ glyphWriter.newline()
+ glyphWriter.begintag("glyf")
+ glyphWriter.newline()
+ glyphWriter.newline()
+ writer.simpletag("TTGlyph", src=os.path.basename(glyphPath))
+ writer.newline()
+ else:
+ glyphWriter = writer
+ glyphWriter.begintag('TTGlyph', [
+ ("name", glyphName),
+ ("xMin", glyph.xMin),
+ ("yMin", glyph.yMin),
+ ("xMax", glyph.xMax),
+ ("yMax", glyph.yMax),
+ ])
+ glyphWriter.newline()
+ glyph.toXML(glyphWriter, ttFont)
+ glyphWriter.endtag('TTGlyph')
+ glyphWriter.newline()
+ if splitGlyphs:
+ glyphWriter.endtag("glyf")
+ glyphWriter.newline()
+ glyphWriter.endtag("ttFont")
+ glyphWriter.newline()
+ glyphWriter.close()
else:
writer.simpletag('TTGlyph', name=glyphName)
writer.comment("contains no outline data")
diff --git a/Lib/fontTools/ttx.py b/Lib/fontTools/ttx.py
index 002215ce5..6c7fca8c4 100644
--- a/Lib/fontTools/ttx.py
+++ b/Lib/fontTools/ttx.py
@@ -38,6 +38,9 @@ usage: ttx [options] inputfile1 [... inputfileN]
to the individual table dumps. This file can be used as
input to ttx, as long as the table files are in the
same directory.
+ -g Split glyf table: Save the glyf data into separate TTX files
+ per glyph and write a small TTX for the glyf table which
+ contains references to the individual TTGlyph elements.
-i Do NOT disassemble TT instructions: when this option is given,
all TrueType programs (glyph programs, the font program and the
pre-program) will be written to the TTX file as hex data
@@ -110,6 +113,7 @@ class Options(object):
verbose = False
quiet = False
splitTables = False
+ splitGlyphs = False
disassembleInstructions = True
mergeFile = None
recalcBBoxes = True
@@ -160,6 +164,8 @@ class Options(object):
self.skipTables.append(value)
elif option == "-s":
self.splitTables = True
+ elif option == "-g":
+ self.splitGlyphs = True
elif option == "-i":
self.disassembleInstructions = False
elif option == "-z":
@@ -255,6 +261,7 @@ def ttDump(input, output, options):
tables=options.onlyTables,
skipTables=options.skipTables,
splitTables=options.splitTables,
+ splitGlyphs=options.splitGlyphs,
disassembleInstructions=options.disassembleInstructions,
bitmapGlyphDataFormat=options.bitmapGlyphDataFormat,
newlinestr=options.newlinestr)
@@ -318,7 +325,7 @@ def guessFileType(fileName):
def parseOptions(args):
- rawOptions, files = getopt.getopt(args, "ld:o:fvqht:x:sim:z:baey:",
+ rawOptions, files = getopt.getopt(args, "ld:o:fvqht:x:sgim:z:baey:",
['unicodedata=', "recalc-timestamp", 'flavor=', 'version',
'with-zopfli', 'newline='])
diff --git a/Tests/misc/xmlReader_test.py b/Tests/misc/xmlReader_test.py
index 622abf5cd..93afd8956 100644
--- a/Tests/misc/xmlReader_test.py
+++ b/Tests/misc/xmlReader_test.py
@@ -142,7 +142,48 @@ class TestXMLReader(unittest.TestCase):
self.assertTrue(reader.file.closed)
os.remove(tmp.name)
+ def test_read_sub_file(self):
+ # Verifies that sub-file content is able to be read to a table.
+ expectedContent = u'testContent'
+ expectedNameID = '1'
+ expectedPlatform = '3'
+ expectedLangId = '0x409'
+ with tempfile.NamedTemporaryFile(delete=False) as tmp:
+
+ subFileData = (
+ ''
+ ''
+ ''
+ '%s'
+ ''
+ ''
+ ''
+ )%(expectedNameID, expectedPlatform, expectedLangId, expectedContent)
+ tmp.write(subFileData.encode("utf-8"))
+ with tempfile.NamedTemporaryFile(delete=False) as tmp2:
+ fileData = (
+ ''
+ ''
+ ''
+ ''
+ ''
+ )%(tmp.name)
+ tmp2.write(fileData.encode('utf-8'))
+
+ ttf = TTFont()
+ with open(tmp2.name, "rb") as f:
+ reader = XMLReader(f, ttf)
+ reader.read()
+ reader.close()
+ nameTable = ttf['name']
+ self.assertTrue(int(expectedNameID) == nameTable.names[0].nameID)
+ self.assertTrue(int(expectedLangId, 16) == nameTable.names[0].langID)
+ self.assertTrue(int(expectedPlatform) == nameTable.names[0].platformID)
+ self.assertEqual(expectedContent, nameTable.names[0].string.decode(nameTable.names[0].getEncoding()))
+
+ os.remove(tmp.name)
+ os.remove(tmp2.name)
if __name__ == '__main__':
import sys