1from fontTools import ttLib 2from fontTools.misc.textTools import safeEval 3from fontTools.ttLib.tables.DefaultTable import DefaultTable 4import sys 5import os 6import logging 7 8 9log = logging.getLogger(__name__) 10 11class TTXParseError(Exception): pass 12 13BUFSIZE = 0x4000 14 15 16class XMLReader(object): 17 18 def __init__(self, fileOrPath, ttFont, progress=None, quiet=None, contentOnly=False): 19 if fileOrPath == '-': 20 fileOrPath = sys.stdin 21 if not hasattr(fileOrPath, "read"): 22 self.file = open(fileOrPath, "rb") 23 self._closeStream = True 24 else: 25 # assume readable file object 26 self.file = fileOrPath 27 self._closeStream = False 28 self.ttFont = ttFont 29 self.progress = progress 30 if quiet is not None: 31 from fontTools.misc.loggingTools import deprecateArgument 32 deprecateArgument("quiet", "configure logging instead") 33 self.quiet = quiet 34 self.root = None 35 self.contentStack = [] 36 self.contentOnly = contentOnly 37 self.stackSize = 0 38 39 def read(self, rootless=False): 40 if rootless: 41 self.stackSize += 1 42 if self.progress: 43 self.file.seek(0, 2) 44 fileSize = self.file.tell() 45 self.progress.set(0, fileSize // 100 or 1) 46 self.file.seek(0) 47 self._parseFile(self.file) 48 if self._closeStream: 49 self.close() 50 if rootless: 51 self.stackSize -= 1 52 53 def close(self): 54 self.file.close() 55 56 def _parseFile(self, file): 57 from xml.parsers.expat import ParserCreate 58 parser = ParserCreate() 59 parser.StartElementHandler = self._startElementHandler 60 parser.EndElementHandler = self._endElementHandler 61 parser.CharacterDataHandler = self._characterDataHandler 62 63 pos = 0 64 while True: 65 chunk = file.read(BUFSIZE) 66 if not chunk: 67 parser.Parse(chunk, 1) 68 break 69 pos = pos + len(chunk) 70 if self.progress: 71 self.progress.set(pos // 100) 72 parser.Parse(chunk, 0) 73 74 def _startElementHandler(self, name, attrs): 75 if self.stackSize == 1 and self.contentOnly: 76 # We already know the table we're parsing, skip 77 # parsing the table tag and continue to 78 # stack '2' which begins parsing content 79 self.contentStack.append([]) 80 self.stackSize = 2 81 return 82 stackSize = self.stackSize 83 self.stackSize = stackSize + 1 84 subFile = attrs.get("src") 85 if subFile is not None: 86 if hasattr(self.file, 'name'): 87 # if file has a name, get its parent directory 88 dirname = os.path.dirname(self.file.name) 89 else: 90 # else fall back to using the current working directory 91 dirname = os.getcwd() 92 subFile = os.path.join(dirname, subFile) 93 if not stackSize: 94 if name != "ttFont": 95 raise TTXParseError("illegal root tag: %s" % name) 96 if self.ttFont.reader is None and not self.ttFont.tables: 97 sfntVersion = attrs.get("sfntVersion") 98 if sfntVersion is not None: 99 if len(sfntVersion) != 4: 100 sfntVersion = safeEval('"' + sfntVersion + '"') 101 self.ttFont.sfntVersion = sfntVersion 102 self.contentStack.append([]) 103 elif stackSize == 1: 104 if subFile is not None: 105 subReader = XMLReader(subFile, self.ttFont, self.progress) 106 subReader.read() 107 self.contentStack.append([]) 108 return 109 tag = ttLib.xmlToTag(name) 110 msg = "Parsing '%s' table..." % tag 111 if self.progress: 112 self.progress.setLabel(msg) 113 log.info(msg) 114 if tag == "GlyphOrder": 115 tableClass = ttLib.GlyphOrder 116 elif "ERROR" in attrs or ('raw' in attrs and safeEval(attrs['raw'])): 117 tableClass = DefaultTable 118 else: 119 tableClass = ttLib.getTableClass(tag) 120 if tableClass is None: 121 tableClass = DefaultTable 122 if tag == 'loca' and tag in self.ttFont: 123 # Special-case the 'loca' table as we need the 124 # original if the 'glyf' table isn't recompiled. 125 self.currentTable = self.ttFont[tag] 126 else: 127 self.currentTable = tableClass(tag) 128 self.ttFont[tag] = self.currentTable 129 self.contentStack.append([]) 130 elif stackSize == 2 and subFile is not None: 131 subReader = XMLReader(subFile, self.ttFont, self.progress, contentOnly=True) 132 subReader.read() 133 self.contentStack.append([]) 134 self.root = subReader.root 135 elif stackSize == 2: 136 self.contentStack.append([]) 137 self.root = (name, attrs, self.contentStack[-1]) 138 else: 139 l = [] 140 self.contentStack[-1].append((name, attrs, l)) 141 self.contentStack.append(l) 142 143 def _characterDataHandler(self, data): 144 if self.stackSize > 1: 145 self.contentStack[-1].append(data) 146 147 def _endElementHandler(self, name): 148 self.stackSize = self.stackSize - 1 149 del self.contentStack[-1] 150 if not self.contentOnly: 151 if self.stackSize == 1: 152 self.root = None 153 elif self.stackSize == 2: 154 name, attrs, content = self.root 155 self.currentTable.fromXML(name, attrs, content, self.ttFont) 156 self.root = None 157 158 159class ProgressPrinter(object): 160 161 def __init__(self, title, maxval=100): 162 print(title) 163 164 def set(self, val, maxval=None): 165 pass 166 167 def increment(self, val=1): 168 pass 169 170 def setLabel(self, text): 171 print(text) 172