from fontTools import ttLib from fontTools.misc.textTools import safeEval from fontTools.ttLib.tables.DefaultTable import DefaultTable import sys import os import logging log = logging.getLogger(__name__) class TTXParseError(Exception): pass BUFSIZE = 0x4000 class XMLReader(object): def __init__( self, fileOrPath, ttFont, progress=None, quiet=None, contentOnly=False ): if fileOrPath == "-": fileOrPath = sys.stdin if not hasattr(fileOrPath, "read"): self.file = open(fileOrPath, "rb") self._closeStream = True else: # assume readable file object self.file = fileOrPath self._closeStream = False self.ttFont = ttFont self.progress = progress if quiet is not None: from fontTools.misc.loggingTools import deprecateArgument deprecateArgument("quiet", "configure logging instead") self.quiet = quiet self.root = None self.contentStack = [] self.contentOnly = contentOnly self.stackSize = 0 def read(self, rootless=False): if rootless: self.stackSize += 1 if self.progress: self.file.seek(0, 2) fileSize = self.file.tell() self.progress.set(0, fileSize // 100 or 1) self.file.seek(0) self._parseFile(self.file) if self._closeStream: self.close() if rootless: self.stackSize -= 1 def close(self): self.file.close() def _parseFile(self, file): from xml.parsers.expat import ParserCreate parser = ParserCreate() parser.StartElementHandler = self._startElementHandler parser.EndElementHandler = self._endElementHandler parser.CharacterDataHandler = self._characterDataHandler pos = 0 while True: chunk = file.read(BUFSIZE) if not chunk: parser.Parse(chunk, 1) break pos = pos + len(chunk) if self.progress: self.progress.set(pos // 100) 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) if self.ttFont.reader is None and not self.ttFont.tables: sfntVersion = attrs.get("sfntVersion") if sfntVersion is not None: if len(sfntVersion) != 4: sfntVersion = safeEval('"' + sfntVersion + '"') self.ttFont.sfntVersion = sfntVersion self.contentStack.append([]) elif stackSize == 1: if subFile is not None: subReader = XMLReader(subFile, self.ttFont, self.progress) subReader.read() self.contentStack.append([]) return tag = ttLib.xmlToTag(name) msg = "Parsing '%s' table..." % tag if self.progress: self.progress.setLabel(msg) log.info(msg) if tag == "GlyphOrder": tableClass = ttLib.GlyphOrder elif "ERROR" in attrs or ("raw" in attrs and safeEval(attrs["raw"])): tableClass = DefaultTable else: tableClass = ttLib.getTableClass(tag) if tableClass is None: tableClass = DefaultTable if tag == "loca" and tag in self.ttFont: # Special-case the 'loca' table as we need the # original if the 'glyf' table isn't recompiled. self.currentTable = self.ttFont[tag] else: 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]) else: l = [] self.contentStack[-1].append((name, attrs, l)) self.contentStack.append(l) def _characterDataHandler(self, data): if self.stackSize > 1: # parser parses in chunks, so we may get multiple calls # for the same text node; thus we need to append the data # to the last item in the content stack: # https://github.com/fonttools/fonttools/issues/2614 if ( data != "\n" and self.contentStack[-1] and isinstance(self.contentStack[-1][-1], str) and self.contentStack[-1][-1] != "\n" ): self.contentStack[-1][-1] += data else: self.contentStack[-1].append(data) def _endElementHandler(self, name): self.stackSize = self.stackSize - 1 del self.contentStack[-1] 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): def __init__(self, title, maxval=100): print(title) def set(self, val, maxval=None): pass def increment(self, val=1): pass def setLabel(self, text): print(text)