• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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