• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from __future__ import print_function, division, absolute_import
2from fontTools.misc import xmlWriter
3from fontTools.misc.py23 import *
4from fontTools.misc.loggingTools import deprecateArgument
5from fontTools.ttLib import TTLibError
6from fontTools.ttLib.sfnt import SFNTReader, SFNTWriter
7import os
8import logging
9import itertools
10
11log = logging.getLogger(__name__)
12
13class TTFont(object):
14
15	"""The main font object. It manages file input and output, and offers
16	a convenient way of accessing tables.
17	Tables will be only decompiled when necessary, ie. when they're actually
18	accessed. This means that simple operations can be extremely fast.
19	"""
20
21	def __init__(self, file=None, res_name_or_index=None,
22			sfntVersion="\000\001\000\000", flavor=None, checkChecksums=False,
23			verbose=None, recalcBBoxes=True, allowVID=False, ignoreDecompileErrors=False,
24			recalcTimestamp=True, fontNumber=-1, lazy=None, quiet=None,
25			_tableCache=None):
26
27		"""The constructor can be called with a few different arguments.
28		When reading a font from disk, 'file' should be either a pathname
29		pointing to a file, or a readable file object.
30
31		It we're running on a Macintosh, 'res_name_or_index' maybe an sfnt
32		resource name or an sfnt resource index number or zero. The latter
33		case will cause TTLib to autodetect whether the file is a flat file
34		or a suitcase. (If it's a suitcase, only the first 'sfnt' resource
35		will be read!)
36
37		The 'checkChecksums' argument is used to specify how sfnt
38		checksums are treated upon reading a file from disk:
39			0: don't check (default)
40			1: check, print warnings if a wrong checksum is found
41			2: check, raise an exception if a wrong checksum is found.
42
43		The TTFont constructor can also be called without a 'file'
44		argument: this is the way to create a new empty font.
45		In this case you can optionally supply the 'sfntVersion' argument,
46		and a 'flavor' which can be None, 'woff', or 'woff2'.
47
48		If the recalcBBoxes argument is false, a number of things will *not*
49		be recalculated upon save/compile:
50			1) 'glyf' glyph bounding boxes
51			2) 'CFF ' font bounding box
52			3) 'head' font bounding box
53			4) 'hhea' min/max values
54			5) 'vhea' min/max values
55		(1) is needed for certain kinds of CJK fonts (ask Werner Lemberg ;-).
56		Additionally, upon importing an TTX file, this option cause glyphs
57		to be compiled right away. This should reduce memory consumption
58		greatly, and therefore should have some impact on the time needed
59		to parse/compile large fonts.
60
61		If the recalcTimestamp argument is false, the modified timestamp in the
62		'head' table will *not* be recalculated upon save/compile.
63
64		If the allowVID argument is set to true, then virtual GID's are
65		supported. Asking for a glyph ID with a glyph name or GID that is not in
66		the font will return a virtual GID.   This is valid for GSUB and cmap
67		tables. For SING glyphlets, the cmap table is used to specify Unicode
68		values for virtual GI's used in GSUB/GPOS rules. If the gid N is requested
69		and does not exist in the font, or the glyphname has the form glyphN
70		and does not exist in the font, then N is used as the virtual GID.
71		Else, the first virtual GID is assigned as 0x1000 -1; for subsequent new
72		virtual GIDs, the next is one less than the previous.
73
74		If ignoreDecompileErrors is set to True, exceptions raised in
75		individual tables during decompilation will be ignored, falling
76		back to the DefaultTable implementation, which simply keeps the
77		binary data.
78
79		If lazy is set to True, many data structures are loaded lazily, upon
80		access only.  If it is set to False, many data structures are loaded
81		immediately.  The default is lazy=None which is somewhere in between.
82		"""
83
84		for name in ("verbose", "quiet"):
85			val = locals().get(name)
86			if val is not None:
87				deprecateArgument(name, "configure logging instead")
88			setattr(self, name, val)
89
90		self.lazy = lazy
91		self.recalcBBoxes = recalcBBoxes
92		self.recalcTimestamp = recalcTimestamp
93		self.tables = {}
94		self.reader = None
95
96		# Permit the user to reference glyphs that are not int the font.
97		self.last_vid = 0xFFFE # Can't make it be 0xFFFF, as the world is full unsigned short integer counters that get incremented after the last seen GID value.
98		self.reverseVIDDict = {}
99		self.VIDDict = {}
100		self.allowVID = allowVID
101		self.ignoreDecompileErrors = ignoreDecompileErrors
102
103		if not file:
104			self.sfntVersion = sfntVersion
105			self.flavor = flavor
106			self.flavorData = None
107			return
108		if not hasattr(file, "read"):
109			closeStream = True
110			# assume file is a string
111			if res_name_or_index is not None:
112				# see if it contains 'sfnt' resources in the resource or data fork
113				from . import macUtils
114				if res_name_or_index == 0:
115					if macUtils.getSFNTResIndices(file):
116						# get the first available sfnt font.
117						file = macUtils.SFNTResourceReader(file, 1)
118					else:
119						file = open(file, "rb")
120				else:
121					file = macUtils.SFNTResourceReader(file, res_name_or_index)
122			else:
123				file = open(file, "rb")
124		else:
125			# assume "file" is a readable file object
126			closeStream = False
127			file.seek(0)
128
129		if not self.lazy:
130			# read input file in memory and wrap a stream around it to allow overwriting
131			file.seek(0)
132			tmp = BytesIO(file.read())
133			if hasattr(file, 'name'):
134				# save reference to input file name
135				tmp.name = file.name
136			if closeStream:
137				file.close()
138			file = tmp
139		self._tableCache = _tableCache
140		self.reader = SFNTReader(file, checkChecksums, fontNumber=fontNumber)
141		self.sfntVersion = self.reader.sfntVersion
142		self.flavor = self.reader.flavor
143		self.flavorData = self.reader.flavorData
144
145	def __enter__(self):
146		return self
147
148	def __exit__(self, type, value, traceback):
149		self.close()
150
151	def close(self):
152		"""If we still have a reader object, close it."""
153		if self.reader is not None:
154			self.reader.close()
155
156	def save(self, file, reorderTables=True):
157		"""Save the font to disk. Similarly to the constructor,
158		the 'file' argument can be either a pathname or a writable
159		file object.
160		"""
161		if not hasattr(file, "write"):
162			if self.lazy and self.reader.file.name == file:
163				raise TTLibError(
164					"Can't overwrite TTFont when 'lazy' attribute is True")
165			closeStream = True
166			file = open(file, "wb")
167		else:
168			# assume "file" is a writable file object
169			closeStream = False
170
171		tmp = BytesIO()
172
173		writer_reordersTables = self._save(tmp)
174
175		if (reorderTables is None or writer_reordersTables or
176				(reorderTables is False and self.reader is None)):
177			# don't reorder tables and save as is
178			file.write(tmp.getvalue())
179			tmp.close()
180		else:
181			if reorderTables is False:
182				# sort tables using the original font's order
183				tableOrder = list(self.reader.keys())
184			else:
185				# use the recommended order from the OpenType specification
186				tableOrder = None
187			tmp.flush()
188			tmp2 = BytesIO()
189			reorderFontTables(tmp, tmp2, tableOrder)
190			file.write(tmp2.getvalue())
191			tmp.close()
192			tmp2.close()
193
194		if closeStream:
195			file.close()
196
197	def _save(self, file, tableCache=None):
198		"""Internal function, to be shared by save() and TTCollection.save()"""
199
200		if self.recalcTimestamp and 'head' in self:
201			self['head']  # make sure 'head' is loaded so the recalculation is actually done
202
203		tags = list(self.keys())
204		if "GlyphOrder" in tags:
205			tags.remove("GlyphOrder")
206		numTables = len(tags)
207		# write to a temporary stream to allow saving to unseekable streams
208		writer = SFNTWriter(file, numTables, self.sfntVersion, self.flavor, self.flavorData)
209
210		done = []
211		for tag in tags:
212			self._writeTable(tag, writer, done, tableCache)
213
214		writer.close()
215
216		return writer.reordersTables()
217
218	def saveXML(self, fileOrPath, newlinestr=None, **kwargs):
219		"""Export the font as TTX (an XML-based text file), or as a series of text
220		files when splitTables is true. In the latter case, the 'fileOrPath'
221		argument should be a path to a directory.
222		The 'tables' argument must either be false (dump all tables) or a
223		list of tables to dump. The 'skipTables' argument may be a list of tables
224		to skip, but only when the 'tables' argument is false.
225		"""
226
227		writer = xmlWriter.XMLWriter(fileOrPath, newlinestr=newlinestr)
228		self._saveXML(writer, **kwargs)
229		writer.close()
230
231	def _saveXML(self, writer,
232		     writeVersion=True,
233		     quiet=None, tables=None, skipTables=None, splitTables=False,
234		     splitGlyphs=False, disassembleInstructions=True,
235		     bitmapGlyphDataFormat='raw'):
236
237		if quiet is not None:
238			deprecateArgument("quiet", "configure logging instead")
239
240		self.disassembleInstructions = disassembleInstructions
241		self.bitmapGlyphDataFormat = bitmapGlyphDataFormat
242		if not tables:
243			tables = list(self.keys())
244			if "GlyphOrder" not in tables:
245				tables = ["GlyphOrder"] + tables
246			if skipTables:
247				for tag in skipTables:
248					if tag in tables:
249						tables.remove(tag)
250		numTables = len(tables)
251
252		if writeVersion:
253			from fontTools import version
254			version = ".".join(version.split('.')[:2])
255			writer.begintag("ttFont", sfntVersion=repr(tostr(self.sfntVersion))[1:-1],
256					ttLibVersion=version)
257		else:
258			writer.begintag("ttFont", sfntVersion=repr(tostr(self.sfntVersion))[1:-1])
259		writer.newline()
260
261		# always splitTables if splitGlyphs is enabled
262		splitTables = splitTables or splitGlyphs
263
264		if not splitTables:
265			writer.newline()
266		else:
267			path, ext = os.path.splitext(writer.filename)
268			fileNameTemplate = path + ".%s" + ext
269
270		for i in range(numTables):
271			tag = tables[i]
272			if splitTables:
273				tablePath = fileNameTemplate % tagToIdentifier(tag)
274				tableWriter = xmlWriter.XMLWriter(tablePath,
275						newlinestr=writer.newlinestr)
276				tableWriter.begintag("ttFont", ttLibVersion=version)
277				tableWriter.newline()
278				tableWriter.newline()
279				writer.simpletag(tagToXML(tag), src=os.path.basename(tablePath))
280				writer.newline()
281			else:
282				tableWriter = writer
283			self._tableToXML(tableWriter, tag, splitGlyphs=splitGlyphs)
284			if splitTables:
285				tableWriter.endtag("ttFont")
286				tableWriter.newline()
287				tableWriter.close()
288		writer.endtag("ttFont")
289		writer.newline()
290
291	def _tableToXML(self, writer, tag, quiet=None, splitGlyphs=False):
292		if quiet is not None:
293			deprecateArgument("quiet", "configure logging instead")
294		if tag in self:
295			table = self[tag]
296			report = "Dumping '%s' table..." % tag
297		else:
298			report = "No '%s' table found." % tag
299		log.info(report)
300		if tag not in self:
301			return
302		xmlTag = tagToXML(tag)
303		attrs = dict()
304		if hasattr(table, "ERROR"):
305			attrs['ERROR'] = "decompilation error"
306		from .tables.DefaultTable import DefaultTable
307		if table.__class__ == DefaultTable:
308			attrs['raw'] = True
309		writer.begintag(xmlTag, **attrs)
310		writer.newline()
311		if tag == "glyf":
312			table.toXML(writer, self, splitGlyphs=splitGlyphs)
313		else:
314			table.toXML(writer, self)
315		writer.endtag(xmlTag)
316		writer.newline()
317		writer.newline()
318
319	def importXML(self, fileOrPath, quiet=None):
320		"""Import a TTX file (an XML-based text format), so as to recreate
321		a font object.
322		"""
323		if quiet is not None:
324			deprecateArgument("quiet", "configure logging instead")
325
326		if "maxp" in self and "post" in self:
327			# Make sure the glyph order is loaded, as it otherwise gets
328			# lost if the XML doesn't contain the glyph order, yet does
329			# contain the table which was originally used to extract the
330			# glyph names from (ie. 'post', 'cmap' or 'CFF ').
331			self.getGlyphOrder()
332
333		from fontTools.misc import xmlReader
334
335		reader = xmlReader.XMLReader(fileOrPath, self)
336		reader.read()
337
338	def isLoaded(self, tag):
339		"""Return true if the table identified by 'tag' has been
340		decompiled and loaded into memory."""
341		return tag in self.tables
342
343	def has_key(self, tag):
344		if self.isLoaded(tag):
345			return True
346		elif self.reader and tag in self.reader:
347			return True
348		elif tag == "GlyphOrder":
349			return True
350		else:
351			return False
352
353	__contains__ = has_key
354
355	def keys(self):
356		keys = list(self.tables.keys())
357		if self.reader:
358			for key in list(self.reader.keys()):
359				if key not in keys:
360					keys.append(key)
361
362		if "GlyphOrder" in keys:
363			keys.remove("GlyphOrder")
364		keys = sortedTagList(keys)
365		return ["GlyphOrder"] + keys
366
367	def __len__(self):
368		return len(list(self.keys()))
369
370	def __getitem__(self, tag):
371		tag = Tag(tag)
372		try:
373			return self.tables[tag]
374		except KeyError:
375			if tag == "GlyphOrder":
376				table = GlyphOrder(tag)
377				self.tables[tag] = table
378				return table
379			if self.reader is not None:
380				import traceback
381				log.debug("Reading '%s' table from disk", tag)
382				data = self.reader[tag]
383				if self._tableCache is not None:
384					table = self._tableCache.get((Tag(tag), data))
385					if table is not None:
386						return table
387				tableClass = getTableClass(tag)
388				table = tableClass(tag)
389				self.tables[tag] = table
390				log.debug("Decompiling '%s' table", tag)
391				try:
392					table.decompile(data, self)
393				except:
394					if not self.ignoreDecompileErrors:
395						raise
396					# fall back to DefaultTable, retaining the binary table data
397					log.exception(
398						"An exception occurred during the decompilation of the '%s' table", tag)
399					from .tables.DefaultTable import DefaultTable
400					file = StringIO()
401					traceback.print_exc(file=file)
402					table = DefaultTable(tag)
403					table.ERROR = file.getvalue()
404					self.tables[tag] = table
405					table.decompile(data, self)
406				if self._tableCache is not None:
407					self._tableCache[(Tag(tag), data)] = table
408				return table
409			else:
410				raise KeyError("'%s' table not found" % tag)
411
412	def __setitem__(self, tag, table):
413		self.tables[Tag(tag)] = table
414
415	def __delitem__(self, tag):
416		if tag not in self:
417			raise KeyError("'%s' table not found" % tag)
418		if tag in self.tables:
419			del self.tables[tag]
420		if self.reader and tag in self.reader:
421			del self.reader[tag]
422
423	def get(self, tag, default=None):
424		try:
425			return self[tag]
426		except KeyError:
427			return default
428
429	def setGlyphOrder(self, glyphOrder):
430		self.glyphOrder = glyphOrder
431
432	def getGlyphOrder(self):
433		try:
434			return self.glyphOrder
435		except AttributeError:
436			pass
437		if 'CFF ' in self:
438			cff = self['CFF ']
439			self.glyphOrder = cff.getGlyphOrder()
440		elif 'post' in self:
441			# TrueType font
442			glyphOrder = self['post'].getGlyphOrder()
443			if glyphOrder is None:
444				#
445				# No names found in the 'post' table.
446				# Try to create glyph names from the unicode cmap (if available)
447				# in combination with the Adobe Glyph List (AGL).
448				#
449				self._getGlyphNamesFromCmap()
450			else:
451				self.glyphOrder = glyphOrder
452		else:
453			self._getGlyphNamesFromCmap()
454		return self.glyphOrder
455
456	def _getGlyphNamesFromCmap(self):
457		#
458		# This is rather convoluted, but then again, it's an interesting problem:
459		# - we need to use the unicode values found in the cmap table to
460		#   build glyph names (eg. because there is only a minimal post table,
461		#   or none at all).
462		# - but the cmap parser also needs glyph names to work with...
463		# So here's what we do:
464		# - make up glyph names based on glyphID
465		# - load a temporary cmap table based on those names
466		# - extract the unicode values, build the "real" glyph names
467		# - unload the temporary cmap table
468		#
469		if self.isLoaded("cmap"):
470			# Bootstrapping: we're getting called by the cmap parser
471			# itself. This means self.tables['cmap'] contains a partially
472			# loaded cmap, making it impossible to get at a unicode
473			# subtable here. We remove the partially loaded cmap and
474			# restore it later.
475			# This only happens if the cmap table is loaded before any
476			# other table that does f.getGlyphOrder()  or f.getGlyphName().
477			cmapLoading = self.tables['cmap']
478			del self.tables['cmap']
479		else:
480			cmapLoading = None
481		# Make up glyph names based on glyphID, which will be used by the
482		# temporary cmap and by the real cmap in case we don't find a unicode
483		# cmap.
484		numGlyphs = int(self['maxp'].numGlyphs)
485		glyphOrder = [None] * numGlyphs
486		glyphOrder[0] = ".notdef"
487		for i in range(1, numGlyphs):
488			glyphOrder[i] = "glyph%.5d" % i
489		# Set the glyph order, so the cmap parser has something
490		# to work with (so we don't get called recursively).
491		self.glyphOrder = glyphOrder
492
493		# Make up glyph names based on the reversed cmap table. Because some
494		# glyphs (eg. ligatures or alternates) may not be reachable via cmap,
495		# this naming table will usually not cover all glyphs in the font.
496		# If the font has no Unicode cmap table, reversecmap will be empty.
497		if 'cmap' in self:
498			reversecmap = self['cmap'].buildReversed()
499		else:
500			reversecmap = {}
501		useCount = {}
502		for i in range(numGlyphs):
503			tempName = glyphOrder[i]
504			if tempName in reversecmap:
505				# If a font maps both U+0041 LATIN CAPITAL LETTER A and
506				# U+0391 GREEK CAPITAL LETTER ALPHA to the same glyph,
507				# we prefer naming the glyph as "A".
508				glyphName = self._makeGlyphName(min(reversecmap[tempName]))
509				numUses = useCount[glyphName] = useCount.get(glyphName, 0) + 1
510				if numUses > 1:
511					glyphName = "%s.alt%d" % (glyphName, numUses - 1)
512				glyphOrder[i] = glyphName
513
514		if 'cmap' in self:
515			# Delete the temporary cmap table from the cache, so it can
516			# be parsed again with the right names.
517			del self.tables['cmap']
518			self.glyphOrder = glyphOrder
519			if cmapLoading:
520				# restore partially loaded cmap, so it can continue loading
521				# using the proper names.
522				self.tables['cmap'] = cmapLoading
523
524	@staticmethod
525	def _makeGlyphName(codepoint):
526		from fontTools import agl  # Adobe Glyph List
527		if codepoint in agl.UV2AGL:
528			return agl.UV2AGL[codepoint]
529		elif codepoint <= 0xFFFF:
530			return "uni%04X" % codepoint
531		else:
532			return "u%X" % codepoint
533
534	def getGlyphNames(self):
535		"""Get a list of glyph names, sorted alphabetically."""
536		glyphNames = sorted(self.getGlyphOrder())
537		return glyphNames
538
539	def getGlyphNames2(self):
540		"""Get a list of glyph names, sorted alphabetically,
541		but not case sensitive.
542		"""
543		from fontTools.misc import textTools
544		return textTools.caselessSort(self.getGlyphOrder())
545
546	def getGlyphName(self, glyphID, requireReal=False):
547		try:
548			return self.getGlyphOrder()[glyphID]
549		except IndexError:
550			if requireReal or not self.allowVID:
551				# XXX The ??.W8.otf font that ships with OSX uses higher glyphIDs in
552				# the cmap table than there are glyphs. I don't think it's legal...
553				return "glyph%.5d" % glyphID
554			else:
555				# user intends virtual GID support
556				try:
557					glyphName = self.VIDDict[glyphID]
558				except KeyError:
559					glyphName  ="glyph%.5d" % glyphID
560					self.last_vid = min(glyphID, self.last_vid )
561					self.reverseVIDDict[glyphName] = glyphID
562					self.VIDDict[glyphID] = glyphName
563				return glyphName
564
565	def getGlyphID(self, glyphName, requireReal=False):
566		if not hasattr(self, "_reverseGlyphOrderDict"):
567			self._buildReverseGlyphOrderDict()
568		glyphOrder = self.getGlyphOrder()
569		d = self._reverseGlyphOrderDict
570		if glyphName not in d:
571			if glyphName in glyphOrder:
572				self._buildReverseGlyphOrderDict()
573				return self.getGlyphID(glyphName)
574			else:
575				if requireReal:
576					raise KeyError(glyphName)
577				elif not self.allowVID:
578					# Handle glyphXXX only
579					if glyphName[:5] == "glyph":
580						try:
581							return int(glyphName[5:])
582						except (NameError, ValueError):
583							raise KeyError(glyphName)
584				else:
585					# user intends virtual GID support
586					try:
587						glyphID = self.reverseVIDDict[glyphName]
588					except KeyError:
589						# if name is in glyphXXX format, use the specified name.
590						if glyphName[:5] == "glyph":
591							try:
592								glyphID = int(glyphName[5:])
593							except (NameError, ValueError):
594								glyphID = None
595						if glyphID is None:
596							glyphID = self.last_vid -1
597							self.last_vid = glyphID
598						self.reverseVIDDict[glyphName] = glyphID
599						self.VIDDict[glyphID] = glyphName
600					return glyphID
601
602		glyphID = d[glyphName]
603		if glyphName != glyphOrder[glyphID]:
604			self._buildReverseGlyphOrderDict()
605			return self.getGlyphID(glyphName)
606		return glyphID
607
608	def getReverseGlyphMap(self, rebuild=False):
609		if rebuild or not hasattr(self, "_reverseGlyphOrderDict"):
610			self._buildReverseGlyphOrderDict()
611		return self._reverseGlyphOrderDict
612
613	def _buildReverseGlyphOrderDict(self):
614		self._reverseGlyphOrderDict = d = {}
615		glyphOrder = self.getGlyphOrder()
616		for glyphID in range(len(glyphOrder)):
617			d[glyphOrder[glyphID]] = glyphID
618
619	def _writeTable(self, tag, writer, done, tableCache=None):
620		"""Internal helper function for self.save(). Keeps track of
621		inter-table dependencies.
622		"""
623		if tag in done:
624			return
625		tableClass = getTableClass(tag)
626		for masterTable in tableClass.dependencies:
627			if masterTable not in done:
628				if masterTable in self:
629					self._writeTable(masterTable, writer, done, tableCache)
630				else:
631					done.append(masterTable)
632		done.append(tag)
633		tabledata = self.getTableData(tag)
634		if tableCache is not None:
635			entry = tableCache.get((Tag(tag), tabledata))
636			if entry is not None:
637				log.debug("reusing '%s' table", tag)
638				writer.setEntry(tag, entry)
639				return
640		log.debug("writing '%s' table to disk", tag)
641		writer[tag] = tabledata
642		if tableCache is not None:
643			tableCache[(Tag(tag), tabledata)] = writer[tag]
644
645	def getTableData(self, tag):
646		"""Returns raw table data, whether compiled or directly read from disk.
647		"""
648		tag = Tag(tag)
649		if self.isLoaded(tag):
650			log.debug("compiling '%s' table", tag)
651			return self.tables[tag].compile(self)
652		elif self.reader and tag in self.reader:
653			log.debug("Reading '%s' table from disk", tag)
654			return self.reader[tag]
655		else:
656			raise KeyError(tag)
657
658	def getGlyphSet(self, preferCFF=True):
659		"""Return a generic GlyphSet, which is a dict-like object
660		mapping glyph names to glyph objects. The returned glyph objects
661		have a .draw() method that supports the Pen protocol, and will
662		have an attribute named 'width'.
663
664		If the font is CFF-based, the outlines will be taken from the 'CFF ' or
665		'CFF2' tables. Otherwise the outlines will be taken from the 'glyf' table.
666		If the font contains both a 'CFF '/'CFF2' and a 'glyf' table, you can use
667		the 'preferCFF' argument to specify which one should be taken. If the
668		font contains both a 'CFF ' and a 'CFF2' table, the latter is taken.
669		"""
670		glyphs = None
671		if (preferCFF and any(tb in self for tb in ["CFF ", "CFF2"]) or
672		   ("glyf" not in self and any(tb in self for tb in ["CFF ", "CFF2"]))):
673			table_tag = "CFF2" if "CFF2" in self else "CFF "
674			glyphs = _TTGlyphSet(self,
675			    list(self[table_tag].cff.values())[0].CharStrings, _TTGlyphCFF)
676
677		if glyphs is None and "glyf" in self:
678			glyphs = _TTGlyphSet(self, self["glyf"], _TTGlyphGlyf)
679
680		if glyphs is None:
681			raise TTLibError("Font contains no outlines")
682
683		return glyphs
684
685	def getBestCmap(self, cmapPreferences=((3, 10), (0, 6), (0, 4), (3, 1), (0, 3), (0, 2), (0, 1), (0, 0))):
686		"""Return the 'best' unicode cmap dictionary available in the font,
687		or None, if no unicode cmap subtable is available.
688
689		By default it will search for the following (platformID, platEncID)
690		pairs:
691			(3, 10), (0, 6), (0, 4), (3, 1), (0, 3), (0, 2), (0, 1), (0, 0)
692		This can be customized via the cmapPreferences argument.
693		"""
694		return self["cmap"].getBestCmap(cmapPreferences=cmapPreferences)
695
696
697class _TTGlyphSet(object):
698
699	"""Generic dict-like GlyphSet class that pulls metrics from hmtx and
700	glyph shape from TrueType or CFF.
701	"""
702
703	def __init__(self, ttFont, glyphs, glyphType):
704		self._glyphs = glyphs
705		self._hmtx = ttFont['hmtx']
706		self._vmtx = ttFont['vmtx'] if 'vmtx' in ttFont else None
707		self._glyphType = glyphType
708
709	def keys(self):
710		return list(self._glyphs.keys())
711
712	def has_key(self, glyphName):
713		return glyphName in self._glyphs
714
715	__contains__ = has_key
716
717	def __getitem__(self, glyphName):
718		horizontalMetrics = self._hmtx[glyphName]
719		verticalMetrics = self._vmtx[glyphName] if self._vmtx else None
720		return self._glyphType(
721			self, self._glyphs[glyphName], horizontalMetrics, verticalMetrics)
722
723	def __len__(self):
724		return len(self._glyphs)
725
726	def get(self, glyphName, default=None):
727		try:
728			return self[glyphName]
729		except KeyError:
730			return default
731
732class _TTGlyph(object):
733
734	"""Wrapper for a TrueType glyph that supports the Pen protocol, meaning
735	that it has .draw() and .drawPoints() methods that take a pen object as
736	their only argument. Additionally there are 'width' and 'lsb' attributes,
737	read from the 'hmtx' table.
738
739	If the font contains a 'vmtx' table, there will also be 'height' and 'tsb'
740	attributes.
741	"""
742
743	def __init__(self, glyphset, glyph, horizontalMetrics, verticalMetrics=None):
744		self._glyphset = glyphset
745		self._glyph = glyph
746		self.width, self.lsb = horizontalMetrics
747		if verticalMetrics:
748			self.height, self.tsb = verticalMetrics
749		else:
750			self.height, self.tsb = None, None
751
752	def draw(self, pen):
753		"""Draw the glyph onto Pen. See fontTools.pens.basePen for details
754		how that works.
755		"""
756		self._glyph.draw(pen)
757
758	def drawPoints(self, pen):
759		# drawPoints is only implemented for _TTGlyphGlyf at this time.
760		raise NotImplementedError()
761
762class _TTGlyphCFF(_TTGlyph):
763	pass
764
765class _TTGlyphGlyf(_TTGlyph):
766
767	def draw(self, pen):
768		"""Draw the glyph onto Pen. See fontTools.pens.basePen for details
769		how that works.
770		"""
771		glyfTable = self._glyphset._glyphs
772		glyph = self._glyph
773		offset = self.lsb - glyph.xMin if hasattr(glyph, "xMin") else 0
774		glyph.draw(pen, glyfTable, offset)
775
776	def drawPoints(self, pen):
777		"""Draw the glyph onto PointPen. See fontTools.pens.pointPen
778		for details how that works.
779		"""
780		glyfTable = self._glyphset._glyphs
781		glyph = self._glyph
782		offset = self.lsb - glyph.xMin if hasattr(glyph, "xMin") else 0
783		glyph.drawPoints(pen, glyfTable, offset)
784
785
786class GlyphOrder(object):
787
788	"""A pseudo table. The glyph order isn't in the font as a separate
789	table, but it's nice to present it as such in the TTX format.
790	"""
791
792	def __init__(self, tag=None):
793		pass
794
795	def toXML(self, writer, ttFont):
796		glyphOrder = ttFont.getGlyphOrder()
797		writer.comment("The 'id' attribute is only for humans; "
798				"it is ignored when parsed.")
799		writer.newline()
800		for i in range(len(glyphOrder)):
801			glyphName = glyphOrder[i]
802			writer.simpletag("GlyphID", id=i, name=glyphName)
803			writer.newline()
804
805	def fromXML(self, name, attrs, content, ttFont):
806		if not hasattr(self, "glyphOrder"):
807			self.glyphOrder = []
808			ttFont.setGlyphOrder(self.glyphOrder)
809		if name == "GlyphID":
810			self.glyphOrder.append(attrs["name"])
811
812
813def getTableModule(tag):
814	"""Fetch the packer/unpacker module for a table.
815	Return None when no module is found.
816	"""
817	from . import tables
818	pyTag = tagToIdentifier(tag)
819	try:
820		__import__("fontTools.ttLib.tables." + pyTag)
821	except ImportError as err:
822		# If pyTag is found in the ImportError message,
823		# means table is not implemented.  If it's not
824		# there, then some other module is missing, don't
825		# suppress the error.
826		if str(err).find(pyTag) >= 0:
827			return None
828		else:
829			raise err
830	else:
831		return getattr(tables, pyTag)
832
833
834def getTableClass(tag):
835	"""Fetch the packer/unpacker class for a table.
836	Return None when no class is found.
837	"""
838	module = getTableModule(tag)
839	if module is None:
840		from .tables.DefaultTable import DefaultTable
841		return DefaultTable
842	pyTag = tagToIdentifier(tag)
843	tableClass = getattr(module, "table_" + pyTag)
844	return tableClass
845
846
847def getClassTag(klass):
848	"""Fetch the table tag for a class object."""
849	name = klass.__name__
850	assert name[:6] == 'table_'
851	name = name[6:] # Chop 'table_'
852	return identifierToTag(name)
853
854
855def newTable(tag):
856	"""Return a new instance of a table."""
857	tableClass = getTableClass(tag)
858	return tableClass(tag)
859
860
861def _escapechar(c):
862	"""Helper function for tagToIdentifier()"""
863	import re
864	if re.match("[a-z0-9]", c):
865		return "_" + c
866	elif re.match("[A-Z]", c):
867		return c + "_"
868	else:
869		return hex(byteord(c))[2:]
870
871
872def tagToIdentifier(tag):
873	"""Convert a table tag to a valid (but UGLY) python identifier,
874	as well as a filename that's guaranteed to be unique even on a
875	caseless file system. Each character is mapped to two characters.
876	Lowercase letters get an underscore before the letter, uppercase
877	letters get an underscore after the letter. Trailing spaces are
878	trimmed. Illegal characters are escaped as two hex bytes. If the
879	result starts with a number (as the result of a hex escape), an
880	extra underscore is prepended. Examples:
881		'glyf' -> '_g_l_y_f'
882		'cvt ' -> '_c_v_t'
883		'OS/2' -> 'O_S_2f_2'
884	"""
885	import re
886	tag = Tag(tag)
887	if tag == "GlyphOrder":
888		return tag
889	assert len(tag) == 4, "tag should be 4 characters long"
890	while len(tag) > 1 and tag[-1] == ' ':
891		tag = tag[:-1]
892	ident = ""
893	for c in tag:
894		ident = ident + _escapechar(c)
895	if re.match("[0-9]", ident):
896		ident = "_" + ident
897	return ident
898
899
900def identifierToTag(ident):
901	"""the opposite of tagToIdentifier()"""
902	if ident == "GlyphOrder":
903		return ident
904	if len(ident) % 2 and ident[0] == "_":
905		ident = ident[1:]
906	assert not (len(ident) % 2)
907	tag = ""
908	for i in range(0, len(ident), 2):
909		if ident[i] == "_":
910			tag = tag + ident[i+1]
911		elif ident[i+1] == "_":
912			tag = tag + ident[i]
913		else:
914			# assume hex
915			tag = tag + chr(int(ident[i:i+2], 16))
916	# append trailing spaces
917	tag = tag + (4 - len(tag)) * ' '
918	return Tag(tag)
919
920
921def tagToXML(tag):
922	"""Similarly to tagToIdentifier(), this converts a TT tag
923	to a valid XML element name. Since XML element names are
924	case sensitive, this is a fairly simple/readable translation.
925	"""
926	import re
927	tag = Tag(tag)
928	if tag == "OS/2":
929		return "OS_2"
930	elif tag == "GlyphOrder":
931		return tag
932	if re.match("[A-Za-z_][A-Za-z_0-9]* *$", tag):
933		return tag.strip()
934	else:
935		return tagToIdentifier(tag)
936
937
938def xmlToTag(tag):
939	"""The opposite of tagToXML()"""
940	if tag == "OS_2":
941		return Tag("OS/2")
942	if len(tag) == 8:
943		return identifierToTag(tag)
944	else:
945		return Tag(tag + " " * (4 - len(tag)))
946
947
948
949# Table order as recommended in the OpenType specification 1.4
950TTFTableOrder = ["head", "hhea", "maxp", "OS/2", "hmtx", "LTSH", "VDMX",
951				"hdmx", "cmap", "fpgm", "prep", "cvt ", "loca", "glyf",
952				"kern", "name", "post", "gasp", "PCLT"]
953
954OTFTableOrder = ["head", "hhea", "maxp", "OS/2", "name", "cmap", "post",
955				"CFF "]
956
957def sortedTagList(tagList, tableOrder=None):
958	"""Return a sorted copy of tagList, sorted according to the OpenType
959	specification, or according to a custom tableOrder. If given and not
960	None, tableOrder needs to be a list of tag names.
961	"""
962	tagList = sorted(tagList)
963	if tableOrder is None:
964		if "DSIG" in tagList:
965			# DSIG should be last (XXX spec reference?)
966			tagList.remove("DSIG")
967			tagList.append("DSIG")
968		if "CFF " in tagList:
969			tableOrder = OTFTableOrder
970		else:
971			tableOrder = TTFTableOrder
972	orderedTables = []
973	for tag in tableOrder:
974		if tag in tagList:
975			orderedTables.append(tag)
976			tagList.remove(tag)
977	orderedTables.extend(tagList)
978	return orderedTables
979
980
981def reorderFontTables(inFile, outFile, tableOrder=None, checkChecksums=False):
982	"""Rewrite a font file, ordering the tables as recommended by the
983	OpenType specification 1.4.
984	"""
985	inFile.seek(0)
986	outFile.seek(0)
987	reader = SFNTReader(inFile, checkChecksums=checkChecksums)
988	writer = SFNTWriter(outFile, len(reader.tables), reader.sfntVersion, reader.flavor, reader.flavorData)
989	tables = list(reader.keys())
990	for tag in sortedTagList(tables, tableOrder):
991		writer[tag] = reader[tag]
992	writer.close()
993
994
995def maxPowerOfTwo(x):
996	"""Return the highest exponent of two, so that
997	(2 ** exponent) <= x.  Return 0 if x is 0.
998	"""
999	exponent = 0
1000	while x:
1001		x = x >> 1
1002		exponent = exponent + 1
1003	return max(exponent - 1, 0)
1004
1005
1006def getSearchRange(n, itemSize=16):
1007	"""Calculate searchRange, entrySelector, rangeShift.
1008	"""
1009	# itemSize defaults to 16, for backward compatibility
1010	# with upstream fonttools.
1011	exponent = maxPowerOfTwo(n)
1012	searchRange = (2 ** exponent) * itemSize
1013	entrySelector = exponent
1014	rangeShift = max(0, n * itemSize - searchRange)
1015	return searchRange, entrySelector, rangeShift
1016