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