__init__.py revision 14fb031125b773f0a15eb19be4f02ed8540b2db6
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
48import sys
49import os
50
51haveMacSupport = 0
52if sys.platform == "mac":
53	haveMacSupport = 1
54elif sys.platform == "darwin" and sys.version_info[:3] != (2, 2, 0):
55	# Python 2.2's Mac support is broken, so don't enable it there.
56	haveMacSupport = 1
57
58
59class TTLibError(Exception): pass
60
61
62class TTFont:
63
64	"""The main font object. It manages file input and output, and offers
65	a convenient way of accessing tables.
66	Tables will be only decompiled when necessary, ie. when they're actually
67	accessed. This means that simple operations can be extremely fast.
68	"""
69
70	def __init__(self, file=None, res_name_or_index=None,
71			sfntVersion="\000\001\000\000", flavor=None, checkChecksums=0,
72			verbose=0, recalcBBoxes=1, allowVID=0, ignoreDecompileErrors=False,
73			fontNumber=-1, lazy=True, quiet=False):
74
75		"""The constructor can be called with a few different arguments.
76		When reading a font from disk, 'file' should be either a pathname
77		pointing to a file, or a readable file object.
78
79		It we're running on a Macintosh, 'res_name_or_index' maybe an sfnt
80		resource name or an sfnt resource index number or zero. The latter
81		case will cause TTLib to autodetect whether the file is a flat file
82		or a suitcase. (If it's a suitcase, only the first 'sfnt' resource
83		will be read!)
84
85		The 'checkChecksums' argument is used to specify how sfnt
86		checksums are treated upon reading a file from disk:
87			0: don't check (default)
88			1: check, print warnings if a wrong checksum is found
89			2: check, raise an exception if a wrong checksum is found.
90
91		The TTFont constructor can also be called without a 'file'
92		argument: this is the way to create a new empty font.
93		In this case you can optionally supply the 'sfntVersion' argument,
94		and a 'flavor' which can be None, or 'woff'.
95
96		If the recalcBBoxes argument is false, a number of things will *not*
97		be recalculated upon save/compile:
98			1) glyph bounding boxes
99			2) maxp font bounding box
100			3) hhea min/max values
101		(1) is needed for certain kinds of CJK fonts (ask Werner Lemberg ;-).
102		Additionally, upon importing an TTX file, this option cause glyphs
103		to be compiled right away. This should reduce memory consumption
104		greatly, and therefore should have some impact on the time needed
105		to parse/compile large fonts.
106
107		If the allowVID argument is set to true, then virtual GID's are
108		supported. Asking for a glyph ID with a glyph name or GID that is not in
109		the font will return a virtual GID.   This is valid for GSUB and cmap
110		tables. For SING glyphlets, the cmap table is used to specify Unicode
111		values for virtual GI's used in GSUB/GPOS rules. If the gid Nis requested
112		and does not exist in the font, or the glyphname has the form glyphN
113		and does not exist in the font, then N is used as the virtual GID.
114		Else, the first virtual GID is assigned as 0x1000 -1; for subsequent new
115		virtual GIDs, the next is one less than the previous.
116
117		If ignoreDecompileErrors is set to True, exceptions raised in
118		individual tables during decompilation will be ignored, falling
119		back to the DefaultTable implementation, which simply keeps the
120		binary data.
121
122		If lazy is set to True, many data structures are loaded lazily, upon
123		access only.
124		"""
125
126		from fontTools.ttLib import sfnt
127		self.verbose = verbose
128		self.quiet = quiet
129		self.lazy = lazy
130		self.recalcBBoxes = recalcBBoxes
131		self.tables = {}
132		self.reader = None
133
134		# Permit the user to reference glyphs that are not int the font.
135		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.
136		self.reverseVIDDict = {}
137		self.VIDDict = {}
138		self.allowVID = allowVID
139		self.ignoreDecompileErrors = ignoreDecompileErrors
140
141		if not file:
142			self.sfntVersion = sfntVersion
143			self.flavor = flavor
144			self.flavorData = None
145			return
146		if not hasattr(file, "read"):
147			# assume file is a string
148			if haveMacSupport and res_name_or_index is not None:
149				# on the mac, we deal with sfnt resources as well as flat files
150				from . import macUtils
151				if res_name_or_index == 0:
152					if macUtils.getSFNTResIndices(file):
153						# get the first available sfnt font.
154						file = macUtils.SFNTResourceReader(file, 1)
155					else:
156						file = open(file, "rb")
157				else:
158					file = macUtils.SFNTResourceReader(file, res_name_or_index)
159			else:
160				file = open(file, "rb")
161		else:
162			pass # assume "file" is a readable file object
163		self.reader = sfnt.SFNTReader(file, checkChecksums, fontNumber=fontNumber)
164		self.sfntVersion = self.reader.sfntVersion
165		self.flavor = self.reader.flavor
166		self.flavorData = self.reader.flavorData
167
168	def close(self):
169		"""If we still have a reader object, close it."""
170		if self.reader is not None:
171			self.reader.close()
172
173	def save(self, file, makeSuitcase=0, reorderTables=1):
174		"""Save the font to disk. Similarly to the constructor,
175		the 'file' argument can be either a pathname or a writable
176		file object.
177
178		On the Mac, if makeSuitcase is true, a suitcase (resource fork)
179		file will we made instead of a flat .ttf file.
180		"""
181		from fontTools.ttLib import sfnt
182		if not hasattr(file, "write"):
183			closeStream = 1
184			if os.name == "mac" and makeSuitcase:
185				from . import macUtils
186				file = macUtils.SFNTResourceWriter(file, self)
187			else:
188				file = open(file, "wb")
189				if os.name == "mac":
190					from fontTools.misc.macCreator import setMacCreatorAndType
191					setMacCreatorAndType(file.name, 'mdos', 'BINA')
192		else:
193			# assume "file" is a writable file object
194			closeStream = 0
195
196		tags = self.keys()
197		if "GlyphOrder" in tags:
198			tags.remove("GlyphOrder")
199		numTables = len(tags)
200		if reorderTables:
201			import tempfile
202			tmp = tempfile.TemporaryFile(prefix="ttx-fonttools")
203		else:
204			tmp = file
205		writer = sfnt.SFNTWriter(tmp, numTables, self.sfntVersion, self.flavor, self.flavorData)
206
207		done = []
208		for tag in tags:
209			self._writeTable(tag, writer, done)
210
211		writer.close()
212
213		if reorderTables:
214			tmp.flush()
215			tmp.seek(0)
216			reorderFontTables(tmp, file)
217			tmp.close()
218
219		if closeStream:
220			file.close()
221
222	def saveXML(self, fileOrPath, progress=None, quiet=False,
223			tables=None, skipTables=None, splitTables=False, disassembleInstructions=True,
224			bitmapGlyphDataFormat='raw'):
225		"""Export the font as TTX (an XML-based text file), or as a series of text
226		files when splitTables is true. In the latter case, the 'fileOrPath'
227		argument should be a path to a directory.
228		The 'tables' argument must either be false (dump all tables) or a
229		list of tables to dump. The 'skipTables' argument may be a list of tables
230		to skip, but only when the 'tables' argument is false.
231		"""
232		from fontTools import version
233		from fontTools.misc import xmlWriter
234
235		self.disassembleInstructions = disassembleInstructions
236		self.bitmapGlyphDataFormat = bitmapGlyphDataFormat
237		if not tables:
238			tables = self.keys()
239			if "GlyphOrder" not in tables:
240				tables = ["GlyphOrder"] + tables
241			if skipTables:
242				for tag in skipTables:
243					if tag in tables:
244						tables.remove(tag)
245		numTables = len(tables)
246		if progress:
247			progress.set(0, numTables)
248			idlefunc = getattr(progress, "idle", None)
249		else:
250			idlefunc = None
251
252		writer = xmlWriter.XMLWriter(fileOrPath, idlefunc=idlefunc)
253		writer.begintag("ttFont", sfntVersion=repr(self.sfntVersion)[1:-1],
254				ttLibVersion=version)
255		writer.newline()
256
257		if not splitTables:
258			writer.newline()
259		else:
260			# 'fileOrPath' must now be a path
261			path, ext = os.path.splitext(fileOrPath)
262			fileNameTemplate = path + ".%s" + ext
263
264		for i in range(numTables):
265			if progress:
266				progress.set(i)
267			tag = tables[i]
268			if splitTables:
269				tablePath = fileNameTemplate % tagToIdentifier(tag)
270				tableWriter = xmlWriter.XMLWriter(tablePath, idlefunc=idlefunc)
271				tableWriter.begintag("ttFont", ttLibVersion=version)
272				tableWriter.newline()
273				tableWriter.newline()
274				writer.simpletag(tagToXML(tag), src=os.path.basename(tablePath))
275				writer.newline()
276			else:
277				tableWriter = writer
278			self._tableToXML(tableWriter, tag, progress, quiet)
279			if splitTables:
280				tableWriter.endtag("ttFont")
281				tableWriter.newline()
282				tableWriter.close()
283		if progress:
284			progress.set((i + 1))
285		writer.endtag("ttFont")
286		writer.newline()
287		writer.close()
288		if self.verbose:
289			debugmsg("Done dumping TTX")
290
291	def _tableToXML(self, writer, tag, progress, quiet):
292		if tag in self:
293			table = self[tag]
294			report = "Dumping '%s' table..." % tag
295		else:
296			report = "No '%s' table found." % tag
297		if progress:
298			progress.setLabel(report)
299		elif self.verbose:
300			debugmsg(report)
301		else:
302			if not quiet:
303				print(report)
304		if tag not in self:
305			return
306		xmlTag = tagToXML(tag)
307		if hasattr(table, "ERROR"):
308			writer.begintag(xmlTag, ERROR="decompilation error")
309		else:
310			writer.begintag(xmlTag)
311		writer.newline()
312		if tag in ("glyf", "CFF "):
313			table.toXML(writer, self, progress)
314		else:
315			table.toXML(writer, self)
316		writer.endtag(xmlTag)
317		writer.newline()
318		writer.newline()
319
320	def importXML(self, file, progress=None, quiet=False):
321		"""Import a TTX file (an XML-based text format), so as to recreate
322		a font object.
323		"""
324		if "maxp" in self and "post" in self:
325			# Make sure the glyph order is loaded, as it otherwise gets
326			# lost if the XML doesn't contain the glyph order, yet does
327			# contain the table which was originally used to extract the
328			# glyph names from (ie. 'post', 'cmap' or 'CFF ').
329			self.getGlyphOrder()
330
331		from fontTools.misc import xmlReader
332
333		reader = xmlReader.XMLReader(file, self, progress, quiet)
334		reader.read()
335
336	def isLoaded(self, tag):
337		"""Return true if the table identified by 'tag' has been
338		decompiled and loaded into memory."""
339		return tag in self.tables
340
341	def has_key(self, tag):
342		if self.isLoaded(tag):
343			return 1
344		elif self.reader and tag in self.reader:
345			return 1
346		elif tag == "GlyphOrder":
347			return 1
348		else:
349			return 0
350
351	__contains__ = has_key
352
353	def keys(self):
354		keys = self.tables.keys()
355		if self.reader:
356			for key in self.reader.keys():
357				if key not in keys:
358					keys.append(key)
359
360		if "GlyphOrder" in keys:
361			keys.remove("GlyphOrder")
362		keys = sortedTagList(keys)
363		return ["GlyphOrder"] + keys
364
365	def __len__(self):
366		return len(self.keys())
367
368	def __getitem__(self, tag):
369		try:
370			return self.tables[tag]
371		except KeyError:
372			if tag == "GlyphOrder":
373				table = GlyphOrder(tag)
374				self.tables[tag] = table
375				return table
376			if self.reader is not None:
377				import traceback
378				if self.verbose:
379					debugmsg("Reading '%s' table from disk" % tag)
380				data = self.reader[tag]
381				tableClass = getTableClass(tag)
382				table = tableClass(tag)
383				self.tables[tag] = table
384				if self.verbose:
385					debugmsg("Decompiling '%s' table" % tag)
386				try:
387					table.decompile(data, self)
388				except:
389					if not self.ignoreDecompileErrors:
390						raise
391					# fall back to DefaultTable, retaining the binary table data
392					print("An exception occurred during the decompilation of the '%s' table" % tag)
393					from .tables.DefaultTable import DefaultTable
394					try:
395						from cStringIO import StringIO
396					except ImportError:
397						from io import StringIO
398					file = StringIO()
399					traceback.print_exc(file=file)
400					table = DefaultTable(tag)
401					table.ERROR = file.getvalue()
402					self.tables[tag] = table
403					table.decompile(data, self)
404				return table
405			else:
406				raise KeyError("'%s' table not found" % tag)
407
408	def __setitem__(self, tag, table):
409		self.tables[tag] = table
410
411	def __delitem__(self, tag):
412		if tag not in self:
413			raise KeyError("'%s' table not found" % tag)
414		if tag in self.tables:
415			del self.tables[tag]
416		if self.reader and tag in self.reader:
417			del self.reader[tag]
418
419	def setGlyphOrder(self, glyphOrder):
420		self.glyphOrder = glyphOrder
421
422	def getGlyphOrder(self):
423		try:
424			return self.glyphOrder
425		except AttributeError:
426			pass
427		if 'CFF ' in self:
428			cff = self['CFF ']
429			self.glyphOrder = cff.getGlyphOrder()
430		elif 'post' in self:
431			# TrueType font
432			glyphOrder = self['post'].getGlyphOrder()
433			if glyphOrder is None:
434				#
435				# No names found in the 'post' table.
436				# Try to create glyph names from the unicode cmap (if available)
437				# in combination with the Adobe Glyph List (AGL).
438				#
439				self._getGlyphNamesFromCmap()
440			else:
441				self.glyphOrder = glyphOrder
442		else:
443			self._getGlyphNamesFromCmap()
444		return self.glyphOrder
445
446	def _getGlyphNamesFromCmap(self):
447		#
448		# This is rather convoluted, but then again, it's an interesting problem:
449		# - we need to use the unicode values found in the cmap table to
450		#   build glyph names (eg. because there is only a minimal post table,
451		#   or none at all).
452		# - but the cmap parser also needs glyph names to work with...
453		# So here's what we do:
454		# - make up glyph names based on glyphID
455		# - load a temporary cmap table based on those names
456		# - extract the unicode values, build the "real" glyph names
457		# - unload the temporary cmap table
458		#
459		if self.isLoaded("cmap"):
460			# Bootstrapping: we're getting called by the cmap parser
461			# itself. This means self.tables['cmap'] contains a partially
462			# loaded cmap, making it impossible to get at a unicode
463			# subtable here. We remove the partially loaded cmap and
464			# restore it later.
465			# This only happens if the cmap table is loaded before any
466			# other table that does f.getGlyphOrder()  or f.getGlyphName().
467			cmapLoading = self.tables['cmap']
468			del self.tables['cmap']
469		else:
470			cmapLoading = None
471		# Make up glyph names based on glyphID, which will be used by the
472		# temporary cmap and by the real cmap in case we don't find a unicode
473		# cmap.
474		numGlyphs = int(self['maxp'].numGlyphs)
475		glyphOrder = [None] * numGlyphs
476		glyphOrder[0] = ".notdef"
477		for i in range(1, numGlyphs):
478			glyphOrder[i] = "glyph%.5d" % i
479		# Set the glyph order, so the cmap parser has something
480		# to work with (so we don't get called recursively).
481		self.glyphOrder = glyphOrder
482		# Get a (new) temporary cmap (based on the just invented names)
483		tempcmap = self['cmap'].getcmap(3, 1)
484		if tempcmap is not None:
485			# we have a unicode cmap
486			from fontTools import agl
487			cmap = tempcmap.cmap
488			# create a reverse cmap dict
489			reversecmap = {}
490			for unicode, name in cmap.items():
491				reversecmap[name] = unicode
492			allNames = {}
493			for i in range(numGlyphs):
494				tempName = glyphOrder[i]
495				if tempName in reversecmap:
496					unicode = reversecmap[tempName]
497					if unicode in agl.UV2AGL:
498						# get name from the Adobe Glyph List
499						glyphName = agl.UV2AGL[unicode]
500					else:
501						# create uni<CODE> name
502						glyphName = "uni%04X" % unicode
503					tempName = glyphName
504					n = 1
505					while tempName in allNames:
506						tempName = glyphName + "#" + repr(n)
507						n = n + 1
508					glyphOrder[i] = tempName
509					allNames[tempName] = 1
510			# Delete the temporary cmap table from the cache, so it can
511			# be parsed again with the right names.
512			del self.tables['cmap']
513		else:
514			pass # no unicode cmap available, stick with the invented names
515		self.glyphOrder = glyphOrder
516		if cmapLoading:
517			# restore partially loaded cmap, so it can continue loading
518			# using the proper names.
519			self.tables['cmap'] = cmapLoading
520
521	def getGlyphNames(self):
522		"""Get a list of glyph names, sorted alphabetically."""
523		glyphNames = sorted(self.getGlyphOrder()[:])
524		return glyphNames
525
526	def getGlyphNames2(self):
527		"""Get a list of glyph names, sorted alphabetically,
528		but not case sensitive.
529		"""
530		from fontTools.misc import textTools
531		return textTools.caselessSort(self.getGlyphOrder())
532
533	def getGlyphName(self, glyphID, requireReal=0):
534		try:
535			return self.getGlyphOrder()[glyphID]
536		except IndexError:
537			if requireReal or not self.allowVID:
538				# XXX The ??.W8.otf font that ships with OSX uses higher glyphIDs in
539				# the cmap table than there are glyphs. I don't think it's legal...
540				return "glyph%.5d" % glyphID
541			else:
542				# user intends virtual GID support
543				try:
544					glyphName = self.VIDDict[glyphID]
545				except KeyError:
546					glyphName  ="glyph%.5d" % glyphID
547					self.last_vid = min(glyphID, self.last_vid )
548					self.reverseVIDDict[glyphName] = glyphID
549					self.VIDDict[glyphID] = glyphName
550				return glyphName
551
552	def getGlyphID(self, glyphName, requireReal = 0):
553		if not hasattr(self, "_reverseGlyphOrderDict"):
554			self._buildReverseGlyphOrderDict()
555		glyphOrder = self.getGlyphOrder()
556		d = self._reverseGlyphOrderDict
557		if glyphName not in d:
558			if glyphName in glyphOrder:
559				self._buildReverseGlyphOrderDict()
560				return self.getGlyphID(glyphName)
561			else:
562				if requireReal or not self.allowVID:
563					raise KeyError(glyphName)
564				else:
565					# user intends virtual GID support
566					try:
567						glyphID = self.reverseVIDDict[glyphName]
568					except KeyError:
569						# if name is in glyphXXX format, use the specified name.
570						if glyphName[:5] == "glyph":
571							try:
572								glyphID = int(glyphName[5:])
573							except (NameError, ValueError):
574								glyphID = None
575						if glyphID == None:
576							glyphID = self.last_vid -1
577							self.last_vid = glyphID
578						self.reverseVIDDict[glyphName] = glyphID
579						self.VIDDict[glyphID] = glyphName
580					return glyphID
581
582		glyphID = d[glyphName]
583		if glyphName != glyphOrder[glyphID]:
584			self._buildReverseGlyphOrderDict()
585			return self.getGlyphID(glyphName)
586		return glyphID
587
588	def getReverseGlyphMap(self, rebuild=0):
589		if rebuild or not hasattr(self, "_reverseGlyphOrderDict"):
590			self._buildReverseGlyphOrderDict()
591		return self._reverseGlyphOrderDict
592
593	def _buildReverseGlyphOrderDict(self):
594		self._reverseGlyphOrderDict = d = {}
595		glyphOrder = self.getGlyphOrder()
596		for glyphID in range(len(glyphOrder)):
597			d[glyphOrder[glyphID]] = glyphID
598
599	def _writeTable(self, tag, writer, done):
600		"""Internal helper function for self.save(). Keeps track of
601		inter-table dependencies.
602		"""
603		if tag in done:
604			return
605		tableClass = getTableClass(tag)
606		for masterTable in tableClass.dependencies:
607			if masterTable not in done:
608				if masterTable in self:
609					self._writeTable(masterTable, writer, done)
610				else:
611					done.append(masterTable)
612		tabledata = self.getTableData(tag)
613		if self.verbose:
614			debugmsg("writing '%s' table to disk" % tag)
615		writer[tag] = tabledata
616		done.append(tag)
617
618	def getTableData(self, tag):
619		"""Returns raw table data, whether compiled or directly read from disk.
620		"""
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=1):
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 self["CFF "].cff.values()[0].CharStrings
646		if "glyf" in self:
647			return _TTGlyphSet(self)
648		if "CFF " in self:
649			return self["CFF "].cff.values()[0].CharStrings
650		raise TTLibError("Font contains no outlines")
651
652
653class _TTGlyphSet:
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 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:
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:
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(ord(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	if tag == "GlyphOrder":
838		return tag
839	assert len(tag) == 4, "tag should be 4 characters long"
840	while len(tag) > 1 and tag[-1] == ' ':
841		tag = tag[:-1]
842	ident = ""
843	for c in tag:
844		ident = ident + _escapechar(c)
845	if re.match("[0-9]", ident):
846		ident = "_" + ident
847	return ident
848
849
850def identifierToTag(ident):
851	"""the opposite of tagToIdentifier()"""
852	if ident == "GlyphOrder":
853		return ident
854	if len(ident) % 2 and ident[0] == "_":
855		ident = ident[1:]
856	assert not (len(ident) % 2)
857	tag = ""
858	for i in range(0, len(ident), 2):
859		if ident[i] == "_":
860			tag = tag + ident[i+1]
861		elif ident[i+1] == "_":
862			tag = tag + ident[i]
863		else:
864			# assume hex
865			tag = tag + chr(int(ident[i:i+2], 16))
866	# append trailing spaces
867	tag = tag + (4 - len(tag)) * ' '
868	return tag
869
870
871def tagToXML(tag):
872	"""Similarly to tagToIdentifier(), this converts a TT tag
873	to a valid XML element name. Since XML element names are
874	case sensitive, this is a fairly simple/readable translation.
875	"""
876	import re
877	if tag == "OS/2":
878		return "OS_2"
879	elif tag == "GlyphOrder":
880		return tag
881	if re.match("[A-Za-z_][A-Za-z_0-9]* *$", tag):
882		return tag.strip()
883	else:
884		return tagToIdentifier(tag)
885
886
887def xmlToTag(tag):
888	"""The opposite of tagToXML()"""
889	if tag == "OS_2":
890		return "OS/2"
891	if len(tag) == 8:
892		return identifierToTag(tag)
893	else:
894		return tag + " " * (4 - len(tag))
895	return tag
896
897
898def debugmsg(msg):
899	import time
900	print(msg + time.strftime("  (%H:%M:%S)", time.localtime(time.time())))
901
902
903# Table order as recommended in the OpenType specification 1.4
904TTFTableOrder = ["head", "hhea", "maxp", "OS/2", "hmtx", "LTSH", "VDMX",
905                  "hdmx", "cmap", "fpgm", "prep", "cvt ", "loca", "glyf",
906                  "kern", "name", "post", "gasp", "PCLT"]
907
908OTFTableOrder = ["head", "hhea", "maxp", "OS/2", "name", "cmap", "post",
909                  "CFF "]
910
911def sortedTagList(tagList, tableOrder=None):
912	"""Return a sorted copy of tagList, sorted according to the OpenType
913	specification, or according to a custom tableOrder. If given and not
914	None, tableOrder needs to be a list of tag names.
915	"""
916	tagList = sorted(tagList)
917	if tableOrder is None:
918		if "DSIG" in tagList:
919			# DSIG should be last (XXX spec reference?)
920			tagList.remove("DSIG")
921			tagList.append("DSIG")
922		if "CFF " in tagList:
923			tableOrder = OTFTableOrder
924		else:
925			tableOrder = TTFTableOrder
926	orderedTables = []
927	for tag in tableOrder:
928		if tag in tagList:
929			orderedTables.append(tag)
930			tagList.remove(tag)
931	orderedTables.extend(tagList)
932	return orderedTables
933
934
935def reorderFontTables(inFile, outFile, tableOrder=None, checkChecksums=0):
936	"""Rewrite a font file, ordering the tables as recommended by the
937	OpenType specification 1.4.
938	"""
939	from fontTools.ttLib.sfnt import SFNTReader, SFNTWriter
940	reader = SFNTReader(inFile, checkChecksums=checkChecksums)
941	writer = SFNTWriter(outFile, len(reader.tables), reader.sfntVersion, reader.flavor, reader.flavorData)
942	tables = reader.keys()
943	for tag in sortedTagList(tables, tableOrder):
944		writer[tag] = reader[tag]
945	writer.close()
946