__init__.py revision ebefbbaa58f9e821dd535b354fcccecc2d24d914
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=True, 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 or not self.allowVID:
562					raise KeyError(glyphName)
563				else:
564					# user intends virtual GID support
565					try:
566						glyphID = self.reverseVIDDict[glyphName]
567					except KeyError:
568						# if name is in glyphXXX format, use the specified name.
569						if glyphName[:5] == "glyph":
570							try:
571								glyphID = int(glyphName[5:])
572							except (NameError, ValueError):
573								glyphID = None
574						if glyphID is None:
575							glyphID = self.last_vid -1
576							self.last_vid = glyphID
577						self.reverseVIDDict[glyphName] = glyphID
578						self.VIDDict[glyphID] = glyphName
579					return glyphID
580
581		glyphID = d[glyphName]
582		if glyphName != glyphOrder[glyphID]:
583			self._buildReverseGlyphOrderDict()
584			return self.getGlyphID(glyphName)
585		return glyphID
586
587	def getReverseGlyphMap(self, rebuild=False):
588		if rebuild or not hasattr(self, "_reverseGlyphOrderDict"):
589			self._buildReverseGlyphOrderDict()
590		return self._reverseGlyphOrderDict
591
592	def _buildReverseGlyphOrderDict(self):
593		self._reverseGlyphOrderDict = d = {}
594		glyphOrder = self.getGlyphOrder()
595		for glyphID in range(len(glyphOrder)):
596			d[glyphOrder[glyphID]] = glyphID
597
598	def _writeTable(self, tag, writer, done):
599		"""Internal helper function for self.save(). Keeps track of
600		inter-table dependencies.
601		"""
602		if tag in done:
603			return
604		tableClass = getTableClass(tag)
605		for masterTable in tableClass.dependencies:
606			if masterTable not in done:
607				if masterTable in self:
608					self._writeTable(masterTable, writer, done)
609				else:
610					done.append(masterTable)
611		tabledata = self.getTableData(tag)
612		if self.verbose:
613			debugmsg("writing '%s' table to disk" % tag)
614		writer[tag] = tabledata
615		done.append(tag)
616
617	def getTableData(self, tag):
618		"""Returns raw table data, whether compiled or directly read from disk.
619		"""
620		tag = Tag(tag)
621		if self.isLoaded(tag):
622			if self.verbose:
623				debugmsg("compiling '%s' table" % tag)
624			return self.tables[tag].compile(self)
625		elif self.reader and tag in self.reader:
626			if self.verbose:
627				debugmsg("Reading '%s' table from disk" % tag)
628			return self.reader[tag]
629		else:
630			raise KeyError(tag)
631
632	def getGlyphSet(self, preferCFF=True):
633		"""Return a generic GlyphSet, which is a dict-like object
634		mapping glyph names to glyph objects. The returned glyph objects
635		have a .draw() method that supports the Pen protocol, and will
636		have an attribute named 'width', but only *after* the .draw() method
637		has been called.
638
639		If the font is CFF-based, the outlines will be taken from the 'CFF '
640		table. Otherwise the outlines will be taken from the 'glyf' table.
641		If the font contains both a 'CFF ' and a 'glyf' table, you can use
642		the 'preferCFF' argument to specify which one should be taken.
643		"""
644		if preferCFF and "CFF " in self:
645			return list(self["CFF "].cff.values())[0].CharStrings
646		if "glyf" in self:
647			return _TTGlyphSet(self)
648		if "CFF " in self:
649			return list(self["CFF "].cff.values())[0].CharStrings
650		raise TTLibError("Font contains no outlines")
651
652
653class _TTGlyphSet(object):
654
655	"""Generic dict-like GlyphSet class, meant as a TrueType counterpart
656	to CFF's CharString dict. See TTFont.getGlyphSet().
657	"""
658
659	# This class is distinct from the 'glyf' table itself because we need
660	# access to the 'hmtx' table, which could cause a dependency problem
661	# there when reading from XML.
662
663	def __init__(self, ttFont):
664		self._ttFont = ttFont
665
666	def keys(self):
667		return list(self._ttFont["glyf"].keys())
668
669	def has_key(self, glyphName):
670		return glyphName in self._ttFont["glyf"]
671
672	__contains__ = has_key
673
674	def __getitem__(self, glyphName):
675		return _TTGlyph(glyphName, self._ttFont)
676
677	def get(self, glyphName, default=None):
678		try:
679			return self[glyphName]
680		except KeyError:
681			return default
682
683
684class _TTGlyph(object):
685
686	"""Wrapper for a TrueType glyph that supports the Pen protocol, meaning
687	that it has a .draw() method that takes a pen object as its only
688	argument. Additionally there is a 'width' attribute.
689	"""
690
691	def __init__(self, glyphName, ttFont):
692		self._glyphName = glyphName
693		self._ttFont = ttFont
694		self.width, self.lsb = self._ttFont['hmtx'][self._glyphName]
695
696	def draw(self, pen):
697		"""Draw the glyph onto Pen. See fontTools.pens.basePen for details
698		how that works.
699		"""
700		glyfTable = self._ttFont['glyf']
701		glyph = glyfTable[self._glyphName]
702		if hasattr(glyph, "xMin"):
703			offset = self.lsb - glyph.xMin
704		else:
705			offset = 0
706		if glyph.isComposite():
707			for component in glyph:
708				glyphName, transform = component.getComponentInfo()
709				pen.addComponent(glyphName, transform)
710		else:
711			coordinates, endPts, flags = glyph.getCoordinates(glyfTable)
712			if offset:
713				coordinates = coordinates + (offset, 0)
714			start = 0
715			for end in endPts:
716				end = end + 1
717				contour = coordinates[start:end].tolist()
718				cFlags = flags[start:end].tolist()
719				start = end
720				if 1 not in cFlags:
721					# There is not a single on-curve point on the curve,
722					# use pen.qCurveTo's special case by specifying None
723					# as the on-curve point.
724					contour.append(None)
725					pen.qCurveTo(*contour)
726				else:
727					# Shuffle the points so that contour the is guaranteed
728					# to *end* in an on-curve point, which we'll use for
729					# the moveTo.
730					firstOnCurve = cFlags.index(1) + 1
731					contour = contour[firstOnCurve:] + contour[:firstOnCurve]
732					cFlags = cFlags[firstOnCurve:] + cFlags[:firstOnCurve]
733					pen.moveTo(contour[-1])
734					while contour:
735						nextOnCurve = cFlags.index(1) + 1
736						if nextOnCurve == 1:
737							pen.lineTo(contour[0])
738						else:
739							pen.qCurveTo(*contour[:nextOnCurve])
740						contour = contour[nextOnCurve:]
741						cFlags = cFlags[nextOnCurve:]
742				pen.closePath()
743
744
745class GlyphOrder(object):
746
747	"""A pseudo table. The glyph order isn't in the font as a separate
748	table, but it's nice to present it as such in the TTX format.
749	"""
750
751	def __init__(self, tag):
752		pass
753
754	def toXML(self, writer, ttFont):
755		glyphOrder = ttFont.getGlyphOrder()
756		writer.comment("The 'id' attribute is only for humans; "
757				"it is ignored when parsed.")
758		writer.newline()
759		for i in range(len(glyphOrder)):
760			glyphName = glyphOrder[i]
761			writer.simpletag("GlyphID", id=i, name=glyphName)
762			writer.newline()
763
764	def fromXML(self, name, attrs, content, ttFont):
765		if not hasattr(self, "glyphOrder"):
766			self.glyphOrder = []
767			ttFont.setGlyphOrder(self.glyphOrder)
768		if name == "GlyphID":
769			self.glyphOrder.append(attrs["name"])
770
771
772def getTableModule(tag):
773	"""Fetch the packer/unpacker module for a table.
774	Return None when no module is found.
775	"""
776	from . import tables
777	pyTag = tagToIdentifier(tag)
778	try:
779		__import__("fontTools.ttLib.tables." + pyTag)
780	except ImportError as err:
781		# If pyTag is found in the ImportError message,
782		# means table is not implemented.  If it's not
783		# there, then some other module is missing, don't
784		# suppress the error.
785		if str(err).find(pyTag) >= 0:
786			return None
787		else:
788			raise err
789	else:
790		return getattr(tables, pyTag)
791
792
793def getTableClass(tag):
794	"""Fetch the packer/unpacker class for a table.
795	Return None when no class is found.
796	"""
797	module = getTableModule(tag)
798	if module is None:
799		from .tables.DefaultTable import DefaultTable
800		return DefaultTable
801	pyTag = tagToIdentifier(tag)
802	tableClass = getattr(module, "table_" + pyTag)
803	return tableClass
804
805
806def newTable(tag):
807	"""Return a new instance of a table."""
808	tableClass = getTableClass(tag)
809	return tableClass(tag)
810
811
812def _escapechar(c):
813	"""Helper function for tagToIdentifier()"""
814	import re
815	if re.match("[a-z0-9]", c):
816		return "_" + c
817	elif re.match("[A-Z]", c):
818		return c + "_"
819	else:
820		return hex(byteord(c))[2:]
821
822
823def tagToIdentifier(tag):
824	"""Convert a table tag to a valid (but UGLY) python identifier,
825	as well as a filename that's guaranteed to be unique even on a
826	caseless file system. Each character is mapped to two characters.
827	Lowercase letters get an underscore before the letter, uppercase
828	letters get an underscore after the letter. Trailing spaces are
829	trimmed. Illegal characters are escaped as two hex bytes. If the
830	result starts with a number (as the result of a hex escape), an
831	extra underscore is prepended. Examples:
832		'glyf' -> '_g_l_y_f'
833		'cvt ' -> '_c_v_t'
834		'OS/2' -> 'O_S_2f_2'
835	"""
836	import re
837	tag = Tag(tag)
838	if tag == "GlyphOrder":
839		return tag
840	assert len(tag) == 4, "tag should be 4 characters long"
841	while len(tag) > 1 and tag[-1] == ' ':
842		tag = tag[:-1]
843	ident = ""
844	for c in tag:
845		ident = ident + _escapechar(c)
846	if re.match("[0-9]", ident):
847		ident = "_" + ident
848	return ident
849
850
851def identifierToTag(ident):
852	"""the opposite of tagToIdentifier()"""
853	if ident == "GlyphOrder":
854		return ident
855	if len(ident) % 2 and ident[0] == "_":
856		ident = ident[1:]
857	assert not (len(ident) % 2)
858	tag = ""
859	for i in range(0, len(ident), 2):
860		if ident[i] == "_":
861			tag = tag + ident[i+1]
862		elif ident[i+1] == "_":
863			tag = tag + ident[i]
864		else:
865			# assume hex
866			tag = tag + bytechr(int(ident[i:i+2], 16))
867	# append trailing spaces
868	tag = tag + (4 - len(tag)) * ' '
869	return Tag(tag)
870
871
872def tagToXML(tag):
873	"""Similarly to tagToIdentifier(), this converts a TT tag
874	to a valid XML element name. Since XML element names are
875	case sensitive, this is a fairly simple/readable translation.
876	"""
877	import re
878	tag = Tag(tag)
879	if tag == "OS/2":
880		return "OS_2"
881	elif tag == "GlyphOrder":
882		return tag
883	if re.match("[A-Za-z_][A-Za-z_0-9]* *$", tag):
884		return tag.strip()
885	else:
886		return tagToIdentifier(tag)
887
888
889def xmlToTag(tag):
890	"""The opposite of tagToXML()"""
891	if tag == "OS_2":
892		return Tag("OS/2")
893	if len(tag) == 8:
894		return identifierToTag(tag)
895	else:
896		return Tag(tag + " " * (4 - len(tag)))
897
898
899def debugmsg(msg):
900	import time
901	print(msg + time.strftime("  (%H:%M:%S)", time.localtime(time.time())))
902
903
904# Table order as recommended in the OpenType specification 1.4
905TTFTableOrder = ["head", "hhea", "maxp", "OS/2", "hmtx", "LTSH", "VDMX",
906                  "hdmx", "cmap", "fpgm", "prep", "cvt ", "loca", "glyf",
907                  "kern", "name", "post", "gasp", "PCLT"]
908
909OTFTableOrder = ["head", "hhea", "maxp", "OS/2", "name", "cmap", "post",
910                  "CFF "]
911
912def sortedTagList(tagList, tableOrder=None):
913	"""Return a sorted copy of tagList, sorted according to the OpenType
914	specification, or according to a custom tableOrder. If given and not
915	None, tableOrder needs to be a list of tag names.
916	"""
917	tagList = sorted(tagList)
918	if tableOrder is None:
919		if "DSIG" in tagList:
920			# DSIG should be last (XXX spec reference?)
921			tagList.remove("DSIG")
922			tagList.append("DSIG")
923		if "CFF " in tagList:
924			tableOrder = OTFTableOrder
925		else:
926			tableOrder = TTFTableOrder
927	orderedTables = []
928	for tag in tableOrder:
929		if tag in tagList:
930			orderedTables.append(tag)
931			tagList.remove(tag)
932	orderedTables.extend(tagList)
933	return orderedTables
934
935
936def reorderFontTables(inFile, outFile, tableOrder=None, checkChecksums=False):
937	"""Rewrite a font file, ordering the tables as recommended by the
938	OpenType specification 1.4.
939	"""
940	from fontTools.ttLib.sfnt import SFNTReader, SFNTWriter
941	reader = SFNTReader(inFile, checkChecksums=checkChecksums)
942	writer = SFNTWriter(outFile, len(reader.tables), reader.sfntVersion, reader.flavor, reader.flavorData)
943	tables = list(reader.keys())
944	for tag in sortedTagList(tables, tableOrder):
945		writer[tag] = reader[tag]
946	writer.close()
947