__init__.py revision 283fb26820a8f6a1f8ae7104699de708364ffb96
1"""fontTools.ttLib -- a package for dealing with TrueType fonts.
2
3This package offers translators to convert TrueType fonts to Python
4objects and vice versa, and additionally from Python to TTX (an XML-based
5text format) and vice versa.
6
7Example interactive session:
8
9Python 1.5.2c1 (#43, Mar  9 1999, 13:06:43)  [CW PPC w/GUSI w/MSL]
10Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
11>>> from fontTools import ttLib
12>>> tt = ttLib.TTFont("afont.ttf")
13>>> tt['maxp'].numGlyphs
14242
15>>> tt['OS/2'].achVendID
16'B&H\000'
17>>> tt['head'].unitsPerEm
182048
19>>> tt.saveXML("afont.ttx")
20Dumping 'LTSH' table...
21Dumping 'OS/2' table...
22Dumping 'VDMX' table...
23Dumping 'cmap' table...
24Dumping 'cvt ' table...
25Dumping 'fpgm' table...
26Dumping 'glyf' table...
27Dumping 'hdmx' table...
28Dumping 'head' table...
29Dumping 'hhea' table...
30Dumping 'hmtx' table...
31Dumping 'loca' table...
32Dumping 'maxp' table...
33Dumping 'name' table...
34Dumping 'post' table...
35Dumping 'prep' table...
36>>> tt2 = ttLib.TTFont()
37>>> tt2.importXML("afont.ttx")
38>>> tt2['maxp'].numGlyphs
39242
40>>>
41
42"""
43
44#
45# $Id: __init__.py,v 1.51 2009-02-22 08:55:00 pabs3 Exp $
46#
47
48from __future__ import print_function, division
49from fontTools.misc.py23 import *
50import os
51import sys
52
53haveMacSupport = 0
54if sys.platform == "mac":
55	haveMacSupport = 1
56elif sys.platform == "darwin" and sys.version_info[:3] != (2, 2, 0):
57	# Python 2.2's Mac support is broken, so don't enable it there.
58	haveMacSupport = 1
59
60
61class TTLibError(Exception): pass
62
63
64class TTFont(object):
65
66	"""The main font object. It manages file input and output, and offers
67	a convenient way of accessing tables.
68	Tables will be only decompiled when necessary, ie. when they're actually
69	accessed. This means that simple operations can be extremely fast.
70	"""
71
72	def __init__(self, file=None, res_name_or_index=None,
73			sfntVersion="\000\001\000\000", flavor=None, checkChecksums=False,
74			verbose=False, recalcBBoxes=True, allowVID=False, ignoreDecompileErrors=False,
75			fontNumber=-1, lazy=False, quiet=False):
76
77		"""The constructor can be called with a few different arguments.
78		When reading a font from disk, 'file' should be either a pathname
79		pointing to a file, or a readable file object.
80
81		It we're running on a Macintosh, 'res_name_or_index' maybe an sfnt
82		resource name or an sfnt resource index number or zero. The latter
83		case will cause TTLib to autodetect whether the file is a flat file
84		or a suitcase. (If it's a suitcase, only the first 'sfnt' resource
85		will be read!)
86
87		The 'checkChecksums' argument is used to specify how sfnt
88		checksums are treated upon reading a file from disk:
89			0: don't check (default)
90			1: check, print warnings if a wrong checksum is found
91			2: check, raise an exception if a wrong checksum is found.
92
93		The TTFont constructor can also be called without a 'file'
94		argument: this is the way to create a new empty font.
95		In this case you can optionally supply the 'sfntVersion' argument,
96		and a 'flavor' which can be None, or 'woff'.
97
98		If the recalcBBoxes argument is false, a number of things will *not*
99		be recalculated upon save/compile:
100			1) glyph bounding boxes
101			2) maxp font bounding box
102			3) hhea min/max values
103		(1) is needed for certain kinds of CJK fonts (ask Werner Lemberg ;-).
104		Additionally, upon importing an TTX file, this option cause glyphs
105		to be compiled right away. This should reduce memory consumption
106		greatly, and therefore should have some impact on the time needed
107		to parse/compile large fonts.
108
109		If the allowVID argument is set to true, then virtual GID's are
110		supported. Asking for a glyph ID with a glyph name or GID that is not in
111		the font will return a virtual GID.   This is valid for GSUB and cmap
112		tables. For SING glyphlets, the cmap table is used to specify Unicode
113		values for virtual GI's used in GSUB/GPOS rules. If the gid N is requested
114		and does not exist in the font, or the glyphname has the form glyphN
115		and does not exist in the font, then N is used as the virtual GID.
116		Else, the first virtual GID is assigned as 0x1000 -1; for subsequent new
117		virtual GIDs, the next is one less than the previous.
118
119		If ignoreDecompileErrors is set to True, exceptions raised in
120		individual tables during decompilation will be ignored, falling
121		back to the DefaultTable implementation, which simply keeps the
122		binary data.
123
124		If lazy is set to True, many data structures are loaded lazily, upon
125		access only.
126		"""
127
128		from fontTools.ttLib import sfnt
129		self.verbose = verbose
130		self.quiet = quiet
131		self.lazy = lazy
132		self.recalcBBoxes = recalcBBoxes
133		self.tables = {}
134		self.reader = None
135
136		# Permit the user to reference glyphs that are not int the font.
137		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.
138		self.reverseVIDDict = {}
139		self.VIDDict = {}
140		self.allowVID = allowVID
141		self.ignoreDecompileErrors = ignoreDecompileErrors
142
143		if not file:
144			self.sfntVersion = sfntVersion
145			self.flavor = flavor
146			self.flavorData = None
147			return
148		if not hasattr(file, "read"):
149			# assume file is a string
150			if haveMacSupport and res_name_or_index is not None:
151				# on the mac, we deal with sfnt resources as well as flat files
152				from . import macUtils
153				if res_name_or_index == 0:
154					if macUtils.getSFNTResIndices(file):
155						# get the first available sfnt font.
156						file = macUtils.SFNTResourceReader(file, 1)
157					else:
158						file = open(file, "rb")
159				else:
160					file = macUtils.SFNTResourceReader(file, res_name_or_index)
161			else:
162				file = open(file, "rb")
163		else:
164			pass # assume "file" is a readable file object
165		self.reader = sfnt.SFNTReader(file, checkChecksums, fontNumber=fontNumber)
166		self.sfntVersion = self.reader.sfntVersion
167		self.flavor = self.reader.flavor
168		self.flavorData = self.reader.flavorData
169
170	def close(self):
171		"""If we still have a reader object, close it."""
172		if self.reader is not None:
173			self.reader.close()
174
175	def save(self, file, makeSuitcase=False, reorderTables=True):
176		"""Save the font to disk. Similarly to the constructor,
177		the 'file' argument can be either a pathname or a writable
178		file object.
179
180		On the Mac, if makeSuitcase is true, a suitcase (resource fork)
181		file will we made instead of a flat .ttf file.
182		"""
183		from fontTools.ttLib import sfnt
184		if not hasattr(file, "write"):
185			closeStream = 1
186			if os.name == "mac" and makeSuitcase:
187				from . import macUtils
188				file = macUtils.SFNTResourceWriter(file, self)
189			else:
190				file = open(file, "wb")
191				if os.name == "mac":
192					from fontTools.misc.macCreator import setMacCreatorAndType
193					setMacCreatorAndType(file.name, 'mdos', 'BINA')
194		else:
195			# assume "file" is a writable file object
196			closeStream = 0
197
198		tags = list(self.keys())
199		if "GlyphOrder" in tags:
200			tags.remove("GlyphOrder")
201		numTables = len(tags)
202		if reorderTables:
203			import tempfile
204			tmp = tempfile.TemporaryFile(prefix="ttx-fonttools")
205		else:
206			tmp = file
207		writer = sfnt.SFNTWriter(tmp, numTables, self.sfntVersion, self.flavor, self.flavorData)
208
209		done = []
210		for tag in tags:
211			self._writeTable(tag, writer, done)
212
213		writer.close()
214
215		if reorderTables:
216			tmp.flush()
217			tmp.seek(0)
218			reorderFontTables(tmp, file)
219			tmp.close()
220
221		if closeStream:
222			file.close()
223
224	def saveXML(self, fileOrPath, progress=None, quiet=False,
225			tables=None, skipTables=None, splitTables=False, disassembleInstructions=True,
226			bitmapGlyphDataFormat='raw'):
227		"""Export the font as TTX (an XML-based text file), or as a series of text
228		files when splitTables is true. In the latter case, the 'fileOrPath'
229		argument should be a path to a directory.
230		The 'tables' argument must either be false (dump all tables) or a
231		list of tables to dump. The 'skipTables' argument may be a list of tables
232		to skip, but only when the 'tables' argument is false.
233		"""
234		from fontTools import version
235		from fontTools.misc import xmlWriter
236
237		self.disassembleInstructions = disassembleInstructions
238		self.bitmapGlyphDataFormat = bitmapGlyphDataFormat
239		if not tables:
240			tables = list(self.keys())
241			if "GlyphOrder" not in tables:
242				tables = ["GlyphOrder"] + tables
243			if skipTables:
244				for tag in skipTables:
245					if tag in tables:
246						tables.remove(tag)
247		numTables = len(tables)
248		if progress:
249			progress.set(0, numTables)
250			idlefunc = getattr(progress, "idle", None)
251		else:
252			idlefunc = None
253
254		writer = xmlWriter.XMLWriter(fileOrPath, idlefunc=idlefunc)
255		writer.begintag("ttFont", sfntVersion=repr(self.sfntVersion)[1:-1],
256				ttLibVersion=version)
257		writer.newline()
258
259		if not splitTables:
260			writer.newline()
261		else:
262			# 'fileOrPath' must now be a path
263			path, ext = os.path.splitext(fileOrPath)
264			fileNameTemplate = path + ".%s" + ext
265
266		for i in range(numTables):
267			if progress:
268				progress.set(i)
269			tag = tables[i]
270			if splitTables:
271				tablePath = fileNameTemplate % tagToIdentifier(tag)
272				tableWriter = xmlWriter.XMLWriter(tablePath, idlefunc=idlefunc)
273				tableWriter.begintag("ttFont", ttLibVersion=version)
274				tableWriter.newline()
275				tableWriter.newline()
276				writer.simpletag(tagToXML(tag), src=os.path.basename(tablePath))
277				writer.newline()
278			else:
279				tableWriter = writer
280			self._tableToXML(tableWriter, tag, progress, quiet)
281			if splitTables:
282				tableWriter.endtag("ttFont")
283				tableWriter.newline()
284				tableWriter.close()
285		if progress:
286			progress.set((i + 1))
287		writer.endtag("ttFont")
288		writer.newline()
289		writer.close()
290		if self.verbose:
291			debugmsg("Done dumping TTX")
292
293	def _tableToXML(self, writer, tag, progress, quiet):
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		if progress:
300			progress.setLabel(report)
301		elif self.verbose:
302			debugmsg(report)
303		else:
304			if not quiet:
305				print(report)
306		if tag not in self:
307			return
308		xmlTag = tagToXML(tag)
309		if hasattr(table, "ERROR"):
310			writer.begintag(xmlTag, ERROR="decompilation error")
311		else:
312			writer.begintag(xmlTag)
313		writer.newline()
314		if tag in ("glyf", "CFF "):
315			table.toXML(writer, self, progress)
316		else:
317			table.toXML(writer, self)
318		writer.endtag(xmlTag)
319		writer.newline()
320		writer.newline()
321
322	def importXML(self, file, progress=None, quiet=False):
323		"""Import a TTX file (an XML-based text format), so as to recreate
324		a font object.
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(file, self, progress, quiet)
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				if self.verbose:
382					debugmsg("Reading '%s' table from disk" % tag)
383				data = self.reader[tag]
384				tableClass = getTableClass(tag)
385				table = tableClass(tag)
386				self.tables[tag] = table
387				if self.verbose:
388					debugmsg("Decompiling '%s' table" % tag)
389				try:
390					table.decompile(data, self)
391				except:
392					if not self.ignoreDecompileErrors:
393						raise
394					# fall back to DefaultTable, retaining the binary table data
395					print("An exception occurred during the decompilation of the '%s' table" % tag)
396					from .tables.DefaultTable import DefaultTable
397					file = StringIO()
398					traceback.print_exc(file=file)
399					table = DefaultTable(tag)
400					table.ERROR = file.getvalue()
401					self.tables[tag] = table
402					table.decompile(data, self)
403				return table
404			else:
405				raise KeyError("'%s' table not found" % tag)
406
407	def __setitem__(self, tag, table):
408		self.tables[Tag(tag)] = table
409
410	def __delitem__(self, tag):
411		if tag not in self:
412			raise KeyError("'%s' table not found" % tag)
413		if tag in self.tables:
414			del self.tables[tag]
415		if self.reader and tag in self.reader:
416			del self.reader[tag]
417
418	def setGlyphOrder(self, glyphOrder):
419		self.glyphOrder = glyphOrder
420
421	def getGlyphOrder(self):
422		try:
423			return self.glyphOrder
424		except AttributeError:
425			pass
426		if 'CFF ' in self:
427			cff = self['CFF ']
428			self.glyphOrder = cff.getGlyphOrder()
429		elif 'post' in self:
430			# TrueType font
431			glyphOrder = self['post'].getGlyphOrder()
432			if glyphOrder is None:
433				#
434				# No names found in the 'post' table.
435				# Try to create glyph names from the unicode cmap (if available)
436				# in combination with the Adobe Glyph List (AGL).
437				#
438				self._getGlyphNamesFromCmap()
439			else:
440				self.glyphOrder = glyphOrder
441		else:
442			self._getGlyphNamesFromCmap()
443		return self.glyphOrder
444
445	def _getGlyphNamesFromCmap(self):
446		#
447		# This is rather convoluted, but then again, it's an interesting problem:
448		# - we need to use the unicode values found in the cmap table to
449		#   build glyph names (eg. because there is only a minimal post table,
450		#   or none at all).
451		# - but the cmap parser also needs glyph names to work with...
452		# So here's what we do:
453		# - make up glyph names based on glyphID
454		# - load a temporary cmap table based on those names
455		# - extract the unicode values, build the "real" glyph names
456		# - unload the temporary cmap table
457		#
458		if self.isLoaded("cmap"):
459			# Bootstrapping: we're getting called by the cmap parser
460			# itself. This means self.tables['cmap'] contains a partially
461			# loaded cmap, making it impossible to get at a unicode
462			# subtable here. We remove the partially loaded cmap and
463			# restore it later.
464			# This only happens if the cmap table is loaded before any
465			# other table that does f.getGlyphOrder()  or f.getGlyphName().
466			cmapLoading = self.tables['cmap']
467			del self.tables['cmap']
468		else:
469			cmapLoading = None
470		# Make up glyph names based on glyphID, which will be used by the
471		# temporary cmap and by the real cmap in case we don't find a unicode
472		# cmap.
473		numGlyphs = int(self['maxp'].numGlyphs)
474		glyphOrder = [None] * numGlyphs
475		glyphOrder[0] = ".notdef"
476		for i in range(1, numGlyphs):
477			glyphOrder[i] = "glyph%.5d" % i
478		# Set the glyph order, so the cmap parser has something
479		# to work with (so we don't get called recursively).
480		self.glyphOrder = glyphOrder
481		# Get a (new) temporary cmap (based on the just invented names)
482		tempcmap = self['cmap'].getcmap(3, 1)
483		if tempcmap is not None:
484			# we have a unicode cmap
485			from fontTools import agl
486			cmap = tempcmap.cmap
487			# create a reverse cmap dict
488			reversecmap = {}
489			for unicode, name in list(cmap.items()):
490				reversecmap[name] = unicode
491			allNames = {}
492			for i in range(numGlyphs):
493				tempName = glyphOrder[i]
494				if tempName in reversecmap:
495					unicode = reversecmap[tempName]
496					if unicode in agl.UV2AGL:
497						# get name from the Adobe Glyph List
498						glyphName = agl.UV2AGL[unicode]
499					else:
500						# create uni<CODE> name
501						glyphName = "uni%04X" % unicode
502					tempName = glyphName
503					n = 1
504					while tempName in allNames:
505						tempName = glyphName + "#" + repr(n)
506						n = n + 1
507					glyphOrder[i] = tempName
508					allNames[tempName] = 1
509			# Delete the temporary cmap table from the cache, so it can
510			# be parsed again with the right names.
511			del self.tables['cmap']
512		else:
513			pass # no unicode cmap available, stick with the invented names
514		self.glyphOrder = glyphOrder
515		if cmapLoading:
516			# restore partially loaded cmap, so it can continue loading
517			# using the proper names.
518			self.tables['cmap'] = cmapLoading
519
520	def getGlyphNames(self):
521		"""Get a list of glyph names, sorted alphabetically."""
522		glyphNames = sorted(self.getGlyphOrder()[:])
523		return glyphNames
524
525	def getGlyphNames2(self):
526		"""Get a list of glyph names, sorted alphabetically,
527		but not case sensitive.
528		"""
529		from fontTools.misc import textTools
530		return textTools.caselessSort(self.getGlyphOrder())
531
532	def getGlyphName(self, glyphID, requireReal=False):
533		try:
534			return self.getGlyphOrder()[glyphID]
535		except IndexError:
536			if requireReal or not self.allowVID:
537				# XXX The ??.W8.otf font that ships with OSX uses higher glyphIDs in
538				# the cmap table than there are glyphs. I don't think it's legal...
539				return "glyph%.5d" % glyphID
540			else:
541				# user intends virtual GID support
542				try:
543					glyphName = self.VIDDict[glyphID]
544				except KeyError:
545					glyphName  ="glyph%.5d" % glyphID
546					self.last_vid = min(glyphID, self.last_vid )
547					self.reverseVIDDict[glyphName] = glyphID
548					self.VIDDict[glyphID] = glyphName
549				return glyphName
550
551	def getGlyphID(self, glyphName, requireReal=False):
552		if not hasattr(self, "_reverseGlyphOrderDict"):
553			self._buildReverseGlyphOrderDict()
554		glyphOrder = self.getGlyphOrder()
555		d = self._reverseGlyphOrderDict
556		if glyphName not in d:
557			if glyphName in glyphOrder:
558				self._buildReverseGlyphOrderDict()
559				return self.getGlyphID(glyphName)
560			else:
561				if requireReal:
562					raise KeyError(glyphName)
563				elif not self.allowVID:
564					# Handle glyphXXX only
565					if glyphName[:5] == "glyph":
566						try:
567							return int(glyphName[5:])
568						except (NameError, ValueError):
569							raise KeyError(glyphName)
570				else:
571					# user intends virtual GID support
572					try:
573						glyphID = self.reverseVIDDict[glyphName]
574					except KeyError:
575						# if name is in glyphXXX format, use the specified name.
576						if glyphName[:5] == "glyph":
577							try:
578								glyphID = int(glyphName[5:])
579							except (NameError, ValueError):
580								glyphID = None
581						if glyphID is None:
582							glyphID = self.last_vid -1
583							self.last_vid = glyphID
584						self.reverseVIDDict[glyphName] = glyphID
585						self.VIDDict[glyphID] = glyphName
586					return glyphID
587
588		glyphID = d[glyphName]
589		if glyphName != glyphOrder[glyphID]:
590			self._buildReverseGlyphOrderDict()
591			return self.getGlyphID(glyphName)
592		return glyphID
593
594	def getReverseGlyphMap(self, rebuild=False):
595		if rebuild or not hasattr(self, "_reverseGlyphOrderDict"):
596			self._buildReverseGlyphOrderDict()
597		return self._reverseGlyphOrderDict
598
599	def _buildReverseGlyphOrderDict(self):
600		self._reverseGlyphOrderDict = d = {}
601		glyphOrder = self.getGlyphOrder()
602		for glyphID in range(len(glyphOrder)):
603			d[glyphOrder[glyphID]] = glyphID
604
605	def _writeTable(self, tag, writer, done):
606		"""Internal helper function for self.save(). Keeps track of
607		inter-table dependencies.
608		"""
609		if tag in done:
610			return
611		tableClass = getTableClass(tag)
612		for masterTable in tableClass.dependencies:
613			if masterTable not in done:
614				if masterTable in self:
615					self._writeTable(masterTable, writer, done)
616				else:
617					done.append(masterTable)
618		tabledata = self.getTableData(tag)
619		if self.verbose:
620			debugmsg("writing '%s' table to disk" % tag)
621		writer[tag] = tabledata
622		done.append(tag)
623
624	def getTableData(self, tag):
625		"""Returns raw table data, whether compiled or directly read from disk.
626		"""
627		tag = Tag(tag)
628		if self.isLoaded(tag):
629			if self.verbose:
630				debugmsg("compiling '%s' table" % tag)
631			return self.tables[tag].compile(self)
632		elif self.reader and tag in self.reader:
633			if self.verbose:
634				debugmsg("Reading '%s' table from disk" % tag)
635			return self.reader[tag]
636		else:
637			raise KeyError(tag)
638
639	def getGlyphSet(self, preferCFF=True):
640		"""Return a generic GlyphSet, which is a dict-like object
641		mapping glyph names to glyph objects. The returned glyph objects
642		have a .draw() method that supports the Pen protocol, and will
643		have an attribute named 'width', but only *after* the .draw() method
644		has been called.
645
646		If the font is CFF-based, the outlines will be taken from the 'CFF '
647		table. Otherwise the outlines will be taken from the 'glyf' table.
648		If the font contains both a 'CFF ' and a 'glyf' table, you can use
649		the 'preferCFF' argument to specify which one should be taken.
650		"""
651		if preferCFF and "CFF " in self:
652			return list(self["CFF "].cff.values())[0].CharStrings
653		if "glyf" in self:
654			return _TTGlyphSet(self)
655		if "CFF " in self:
656			return list(self["CFF "].cff.values())[0].CharStrings
657		raise TTLibError("Font contains no outlines")
658
659
660class _TTGlyphSet(object):
661
662	"""Generic dict-like GlyphSet class, meant as a TrueType counterpart
663	to CFF's CharString dict. See TTFont.getGlyphSet().
664	"""
665
666	# This class is distinct from the 'glyf' table itself because we need
667	# access to the 'hmtx' table, which could cause a dependency problem
668	# there when reading from XML.
669
670	def __init__(self, ttFont):
671		self._ttFont = ttFont
672
673	def keys(self):
674		return list(self._ttFont["glyf"].keys())
675
676	def has_key(self, glyphName):
677		return glyphName in self._ttFont["glyf"]
678
679	__contains__ = has_key
680
681	def __getitem__(self, glyphName):
682		return _TTGlyph(glyphName, self._ttFont)
683
684	def get(self, glyphName, default=None):
685		try:
686			return self[glyphName]
687		except KeyError:
688			return default
689
690
691class _TTGlyph(object):
692
693	"""Wrapper for a TrueType glyph that supports the Pen protocol, meaning
694	that it has a .draw() method that takes a pen object as its only
695	argument. Additionally there is a 'width' attribute.
696	"""
697
698	def __init__(self, glyphName, ttFont):
699		self._glyphName = glyphName
700		self._ttFont = ttFont
701		self.width, self.lsb = self._ttFont['hmtx'][self._glyphName]
702
703	def draw(self, pen):
704		"""Draw the glyph onto Pen. See fontTools.pens.basePen for details
705		how that works.
706		"""
707		glyfTable = self._ttFont['glyf']
708		glyph = glyfTable[self._glyphName]
709		if hasattr(glyph, "xMin"):
710			offset = self.lsb - glyph.xMin
711		else:
712			offset = 0
713		if glyph.isComposite():
714			for component in glyph:
715				glyphName, transform = component.getComponentInfo()
716				pen.addComponent(glyphName, transform)
717		else:
718			coordinates, endPts, flags = glyph.getCoordinates(glyfTable)
719			if offset:
720				coordinates = coordinates + (offset, 0)
721			start = 0
722			for end in endPts:
723				end = end + 1
724				contour = coordinates[start:end].tolist()
725				cFlags = flags[start:end].tolist()
726				start = end
727				if 1 not in cFlags:
728					# There is not a single on-curve point on the curve,
729					# use pen.qCurveTo's special case by specifying None
730					# as the on-curve point.
731					contour.append(None)
732					pen.qCurveTo(*contour)
733				else:
734					# Shuffle the points so that contour the is guaranteed
735					# to *end* in an on-curve point, which we'll use for
736					# the moveTo.
737					firstOnCurve = cFlags.index(1) + 1
738					contour = contour[firstOnCurve:] + contour[:firstOnCurve]
739					cFlags = cFlags[firstOnCurve:] + cFlags[:firstOnCurve]
740					pen.moveTo(contour[-1])
741					while contour:
742						nextOnCurve = cFlags.index(1) + 1
743						if nextOnCurve == 1:
744							pen.lineTo(contour[0])
745						else:
746							pen.qCurveTo(*contour[:nextOnCurve])
747						contour = contour[nextOnCurve:]
748						cFlags = cFlags[nextOnCurve:]
749				pen.closePath()
750
751
752class GlyphOrder(object):
753
754	"""A pseudo table. The glyph order isn't in the font as a separate
755	table, but it's nice to present it as such in the TTX format.
756	"""
757
758	def __init__(self, tag):
759		pass
760
761	def toXML(self, writer, ttFont):
762		glyphOrder = ttFont.getGlyphOrder()
763		writer.comment("The 'id' attribute is only for humans; "
764				"it is ignored when parsed.")
765		writer.newline()
766		for i in range(len(glyphOrder)):
767			glyphName = glyphOrder[i]
768			writer.simpletag("GlyphID", id=i, name=glyphName)
769			writer.newline()
770
771	def fromXML(self, name, attrs, content, ttFont):
772		if not hasattr(self, "glyphOrder"):
773			self.glyphOrder = []
774			ttFont.setGlyphOrder(self.glyphOrder)
775		if name == "GlyphID":
776			self.glyphOrder.append(attrs["name"])
777
778
779def getTableModule(tag):
780	"""Fetch the packer/unpacker module for a table.
781	Return None when no module is found.
782	"""
783	from . import tables
784	pyTag = tagToIdentifier(tag)
785	try:
786		__import__("fontTools.ttLib.tables." + pyTag)
787	except ImportError as err:
788		# If pyTag is found in the ImportError message,
789		# means table is not implemented.  If it's not
790		# there, then some other module is missing, don't
791		# suppress the error.
792		if str(err).find(pyTag) >= 0:
793			return None
794		else:
795			raise err
796	else:
797		return getattr(tables, pyTag)
798
799
800def getTableClass(tag):
801	"""Fetch the packer/unpacker class for a table.
802	Return None when no class is found.
803	"""
804	module = getTableModule(tag)
805	if module is None:
806		from .tables.DefaultTable import DefaultTable
807		return DefaultTable
808	pyTag = tagToIdentifier(tag)
809	tableClass = getattr(module, "table_" + pyTag)
810	return tableClass
811
812
813def newTable(tag):
814	"""Return a new instance of a table."""
815	tableClass = getTableClass(tag)
816	return tableClass(tag)
817
818
819def _escapechar(c):
820	"""Helper function for tagToIdentifier()"""
821	import re
822	if re.match("[a-z0-9]", c):
823		return "_" + c
824	elif re.match("[A-Z]", c):
825		return c + "_"
826	else:
827		return hex(byteord(c))[2:]
828
829
830def tagToIdentifier(tag):
831	"""Convert a table tag to a valid (but UGLY) python identifier,
832	as well as a filename that's guaranteed to be unique even on a
833	caseless file system. Each character is mapped to two characters.
834	Lowercase letters get an underscore before the letter, uppercase
835	letters get an underscore after the letter. Trailing spaces are
836	trimmed. Illegal characters are escaped as two hex bytes. If the
837	result starts with a number (as the result of a hex escape), an
838	extra underscore is prepended. Examples:
839		'glyf' -> '_g_l_y_f'
840		'cvt ' -> '_c_v_t'
841		'OS/2' -> 'O_S_2f_2'
842	"""
843	import re
844	tag = Tag(tag)
845	if tag == "GlyphOrder":
846		return tag
847	assert len(tag) == 4, "tag should be 4 characters long"
848	while len(tag) > 1 and tag[-1] == ' ':
849		tag = tag[:-1]
850	ident = ""
851	for c in tag:
852		ident = ident + _escapechar(c)
853	if re.match("[0-9]", ident):
854		ident = "_" + ident
855	return ident
856
857
858def identifierToTag(ident):
859	"""the opposite of tagToIdentifier()"""
860	if ident == "GlyphOrder":
861		return ident
862	if len(ident) % 2 and ident[0] == "_":
863		ident = ident[1:]
864	assert not (len(ident) % 2)
865	tag = ""
866	for i in range(0, len(ident), 2):
867		if ident[i] == "_":
868			tag = tag + ident[i+1]
869		elif ident[i+1] == "_":
870			tag = tag + ident[i]
871		else:
872			# assume hex
873			tag = tag + bytechr(int(ident[i:i+2], 16))
874	# append trailing spaces
875	tag = tag + (4 - len(tag)) * ' '
876	return Tag(tag)
877
878
879def tagToXML(tag):
880	"""Similarly to tagToIdentifier(), this converts a TT tag
881	to a valid XML element name. Since XML element names are
882	case sensitive, this is a fairly simple/readable translation.
883	"""
884	import re
885	tag = Tag(tag)
886	if tag == "OS/2":
887		return "OS_2"
888	elif tag == "GlyphOrder":
889		return tag
890	if re.match("[A-Za-z_][A-Za-z_0-9]* *$", tag):
891		return tag.strip()
892	else:
893		return tagToIdentifier(tag)
894
895
896def xmlToTag(tag):
897	"""The opposite of tagToXML()"""
898	if tag == "OS_2":
899		return Tag("OS/2")
900	if len(tag) == 8:
901		return identifierToTag(tag)
902	else:
903		return Tag(tag + " " * (4 - len(tag)))
904
905
906def debugmsg(msg):
907	import time
908	print(msg + time.strftime("  (%H:%M:%S)", time.localtime(time.time())))
909
910
911# Table order as recommended in the OpenType specification 1.4
912TTFTableOrder = ["head", "hhea", "maxp", "OS/2", "hmtx", "LTSH", "VDMX",
913                  "hdmx", "cmap", "fpgm", "prep", "cvt ", "loca", "glyf",
914                  "kern", "name", "post", "gasp", "PCLT"]
915
916OTFTableOrder = ["head", "hhea", "maxp", "OS/2", "name", "cmap", "post",
917                  "CFF "]
918
919def sortedTagList(tagList, tableOrder=None):
920	"""Return a sorted copy of tagList, sorted according to the OpenType
921	specification, or according to a custom tableOrder. If given and not
922	None, tableOrder needs to be a list of tag names.
923	"""
924	tagList = sorted(tagList)
925	if tableOrder is None:
926		if "DSIG" in tagList:
927			# DSIG should be last (XXX spec reference?)
928			tagList.remove("DSIG")
929			tagList.append("DSIG")
930		if "CFF " in tagList:
931			tableOrder = OTFTableOrder
932		else:
933			tableOrder = TTFTableOrder
934	orderedTables = []
935	for tag in tableOrder:
936		if tag in tagList:
937			orderedTables.append(tag)
938			tagList.remove(tag)
939	orderedTables.extend(tagList)
940	return orderedTables
941
942
943def reorderFontTables(inFile, outFile, tableOrder=None, checkChecksums=False):
944	"""Rewrite a font file, ordering the tables as recommended by the
945	OpenType specification 1.4.
946	"""
947	from fontTools.ttLib.sfnt import SFNTReader, SFNTWriter
948	reader = SFNTReader(inFile, checkChecksums=checkChecksums)
949	writer = SFNTWriter(outFile, len(reader.tables), reader.sfntVersion, reader.flavor, reader.flavorData)
950	tables = list(reader.keys())
951	for tag in sortedTagList(tables, tableOrder):
952		writer[tag] = reader[tag]
953	writer.close()
954