• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""_g_l_y_f.py -- Converter classes for the 'glyf' table."""
2
3from __future__ import print_function, division, absolute_import
4from collections import namedtuple
5from fontTools.misc.py23 import *
6from fontTools.misc import sstruct
7from fontTools import ttLib
8from fontTools import version
9from fontTools.misc.textTools import safeEval, pad
10from fontTools.misc.arrayTools import calcBounds, calcIntBounds, pointInRect
11from fontTools.misc.bezierTools import calcQuadraticBounds
12from fontTools.misc.fixedTools import (
13	fixedToFloat as fi2fl,
14	floatToFixed as fl2fi,
15	otRound,
16)
17from numbers import Number
18from . import DefaultTable
19from . import ttProgram
20import sys
21import struct
22import array
23import logging
24import os
25from fontTools.misc import xmlWriter
26from fontTools.misc.filenames import userNameToFileName
27
28log = logging.getLogger(__name__)
29
30# We compute the version the same as is computed in ttlib/__init__
31# so that we can write 'ttLibVersion' attribute of the glyf TTX files
32# when glyf is written to separate files.
33version = ".".join(version.split('.')[:2])
34
35#
36# The Apple and MS rasterizers behave differently for
37# scaled composite components: one does scale first and then translate
38# and the other does it vice versa. MS defined some flags to indicate
39# the difference, but it seems nobody actually _sets_ those flags.
40#
41# Funny thing: Apple seems to _only_ do their thing in the
42# WE_HAVE_A_SCALE (eg. Chicago) case, and not when it's WE_HAVE_AN_X_AND_Y_SCALE
43# (eg. Charcoal)...
44#
45SCALE_COMPONENT_OFFSET_DEFAULT = 0   # 0 == MS, 1 == Apple
46
47
48class table__g_l_y_f(DefaultTable.DefaultTable):
49
50	# this attribute controls the amount of padding applied to glyph data upon compile.
51	# Glyph lenghts are aligned to multiples of the specified value.
52	# Allowed values are (0, 1, 2, 4). '0' means no padding; '1' (default) also means
53	# no padding, except for when padding would allow to use short loca offsets.
54	padding = 1
55
56	def decompile(self, data, ttFont):
57		loca = ttFont['loca']
58		last = int(loca[0])
59		noname = 0
60		self.glyphs = {}
61		self.glyphOrder = glyphOrder = ttFont.getGlyphOrder()
62		for i in range(0, len(loca)-1):
63			try:
64				glyphName = glyphOrder[i]
65			except IndexError:
66				noname = noname + 1
67				glyphName = 'ttxautoglyph%s' % i
68			next = int(loca[i+1])
69			glyphdata = data[last:next]
70			if len(glyphdata) != (next - last):
71				raise ttLib.TTLibError("not enough 'glyf' table data")
72			glyph = Glyph(glyphdata)
73			self.glyphs[glyphName] = glyph
74			last = next
75		if len(data) - next >= 4:
76			log.warning(
77				"too much 'glyf' table data: expected %d, received %d bytes",
78				next, len(data))
79		if noname:
80			log.warning('%s glyphs have no name', noname)
81		if ttFont.lazy is False: # Be lazy for None and True
82			for glyph in self.glyphs.values():
83				glyph.expand(self)
84
85	def compile(self, ttFont):
86		if not hasattr(self, "glyphOrder"):
87			self.glyphOrder = ttFont.getGlyphOrder()
88		padding = self.padding
89		assert padding in (0, 1, 2, 4)
90		locations = []
91		currentLocation = 0
92		dataList = []
93		recalcBBoxes = ttFont.recalcBBoxes
94		for glyphName in self.glyphOrder:
95			glyph = self.glyphs[glyphName]
96			glyphData = glyph.compile(self, recalcBBoxes)
97			if padding > 1:
98				glyphData = pad(glyphData, size=padding)
99			locations.append(currentLocation)
100			currentLocation = currentLocation + len(glyphData)
101			dataList.append(glyphData)
102		locations.append(currentLocation)
103
104		if padding == 1 and currentLocation < 0x20000:
105			# See if we can pad any odd-lengthed glyphs to allow loca
106			# table to use the short offsets.
107			indices = [i for i,glyphData in enumerate(dataList) if len(glyphData) % 2 == 1]
108			if indices and currentLocation + len(indices) < 0x20000:
109				# It fits.  Do it.
110				for i in indices:
111					dataList[i] += b'\0'
112				currentLocation = 0
113				for i,glyphData in enumerate(dataList):
114					locations[i] = currentLocation
115					currentLocation += len(glyphData)
116				locations[len(dataList)] = currentLocation
117
118		data = bytesjoin(dataList)
119		if 'loca' in ttFont:
120			ttFont['loca'].set(locations)
121		if 'maxp' in ttFont:
122			ttFont['maxp'].numGlyphs = len(self.glyphs)
123		return data
124
125	def toXML(self, writer, ttFont, splitGlyphs=False):
126		notice = (
127			"The xMin, yMin, xMax and yMax values\n"
128			"will be recalculated by the compiler.")
129		glyphNames = ttFont.getGlyphNames()
130		if not splitGlyphs:
131			writer.newline()
132			writer.comment(notice)
133			writer.newline()
134			writer.newline()
135		numGlyphs = len(glyphNames)
136		if splitGlyphs:
137			path, ext = os.path.splitext(writer.file.name)
138			existingGlyphFiles = set()
139		for glyphName in glyphNames:
140			glyph = self[glyphName]
141			if glyph.numberOfContours:
142				if splitGlyphs:
143					glyphPath = userNameToFileName(
144						tounicode(glyphName, 'utf-8'),
145						existingGlyphFiles,
146						prefix=path + ".",
147						suffix=ext)
148					existingGlyphFiles.add(glyphPath.lower())
149					glyphWriter = xmlWriter.XMLWriter(
150						glyphPath, idlefunc=writer.idlefunc,
151						newlinestr=writer.newlinestr)
152					glyphWriter.begintag("ttFont", ttLibVersion=version)
153					glyphWriter.newline()
154					glyphWriter.begintag("glyf")
155					glyphWriter.newline()
156					glyphWriter.comment(notice)
157					glyphWriter.newline()
158					writer.simpletag("TTGlyph", src=os.path.basename(glyphPath))
159				else:
160					glyphWriter = writer
161				glyphWriter.begintag('TTGlyph', [
162							("name", glyphName),
163							("xMin", glyph.xMin),
164							("yMin", glyph.yMin),
165							("xMax", glyph.xMax),
166							("yMax", glyph.yMax),
167							])
168				glyphWriter.newline()
169				glyph.toXML(glyphWriter, ttFont)
170				glyphWriter.endtag('TTGlyph')
171				glyphWriter.newline()
172				if splitGlyphs:
173					glyphWriter.endtag("glyf")
174					glyphWriter.newline()
175					glyphWriter.endtag("ttFont")
176					glyphWriter.newline()
177					glyphWriter.close()
178			else:
179				writer.simpletag('TTGlyph', name=glyphName)
180				writer.comment("contains no outline data")
181				if not splitGlyphs:
182					writer.newline()
183			writer.newline()
184
185	def fromXML(self, name, attrs, content, ttFont):
186		if name != "TTGlyph":
187			return
188		if not hasattr(self, "glyphs"):
189			self.glyphs = {}
190		if not hasattr(self, "glyphOrder"):
191			self.glyphOrder = ttFont.getGlyphOrder()
192		glyphName = attrs["name"]
193		log.debug("unpacking glyph '%s'", glyphName)
194		glyph = Glyph()
195		for attr in ['xMin', 'yMin', 'xMax', 'yMax']:
196			setattr(glyph, attr, safeEval(attrs.get(attr, '0')))
197		self.glyphs[glyphName] = glyph
198		for element in content:
199			if not isinstance(element, tuple):
200				continue
201			name, attrs, content = element
202			glyph.fromXML(name, attrs, content, ttFont)
203		if not ttFont.recalcBBoxes:
204			glyph.compact(self, 0)
205
206	def setGlyphOrder(self, glyphOrder):
207		self.glyphOrder = glyphOrder
208
209	def getGlyphName(self, glyphID):
210		return self.glyphOrder[glyphID]
211
212	def getGlyphID(self, glyphName):
213		# XXX optimize with reverse dict!!!
214		return self.glyphOrder.index(glyphName)
215
216	def removeHinting(self):
217		for glyph in self.glyphs.values():
218			glyph.removeHinting()
219
220	def keys(self):
221		return self.glyphs.keys()
222
223	def has_key(self, glyphName):
224		return glyphName in self.glyphs
225
226	__contains__ = has_key
227
228	def __getitem__(self, glyphName):
229		glyph = self.glyphs[glyphName]
230		glyph.expand(self)
231		return glyph
232
233	def __setitem__(self, glyphName, glyph):
234		self.glyphs[glyphName] = glyph
235		if glyphName not in self.glyphOrder:
236			self.glyphOrder.append(glyphName)
237
238	def __delitem__(self, glyphName):
239		del self.glyphs[glyphName]
240		self.glyphOrder.remove(glyphName)
241
242	def __len__(self):
243		assert len(self.glyphOrder) == len(self.glyphs)
244		return len(self.glyphs)
245
246	def getPhantomPoints(self, glyphName, ttFont, defaultVerticalOrigin=None):
247		"""Compute the four "phantom points" for the given glyph from its bounding box
248		and the horizontal and vertical advance widths and sidebearings stored in the
249		ttFont's "hmtx" and "vmtx" tables.
250
251		If the ttFont doesn't contain a "vmtx" table, the hhea.ascent is used as the
252		vertical origin, and the head.unitsPerEm as the vertical advance.
253
254		The "defaultVerticalOrigin" (Optional[int]) is needed when the ttFont contains
255		neither a "vmtx" nor an "hhea" table, as may happen with 'sparse' masters.
256		The value should be the hhea.ascent of the default master.
257
258		https://docs.microsoft.com/en-us/typography/opentype/spec/tt_instructing_glyphs#phantoms
259		"""
260		glyph = self[glyphName]
261		assert glyphName in ttFont["hmtx"].metrics, ttFont["hmtx"].metrics
262		horizontalAdvanceWidth, leftSideBearing = ttFont["hmtx"].metrics[glyphName]
263		if not hasattr(glyph, 'xMin'):
264			glyph.recalcBounds(self)
265		leftSideX = glyph.xMin - leftSideBearing
266		rightSideX = leftSideX + horizontalAdvanceWidth
267		if "vmtx" in ttFont:
268			verticalAdvanceWidth, topSideBearing = ttFont["vmtx"].metrics[glyphName]
269			topSideY = topSideBearing + glyph.yMax
270		else:
271			# without vmtx, use ascent as vertical origin and UPEM as vertical advance
272			# like HarfBuzz does
273			verticalAdvanceWidth = ttFont["head"].unitsPerEm
274			if "hhea" in ttFont:
275				topSideY = ttFont["hhea"].ascent
276			else:
277				# sparse masters may not contain an hhea table; use the ascent
278				# of the default master as the vertical origin
279				if defaultVerticalOrigin is not None:
280					topSideY = defaultVerticalOrigin
281				else:
282					log.warning(
283						"font is missing both 'vmtx' and 'hhea' tables, "
284						"and no 'defaultVerticalOrigin' was provided; "
285						"the vertical phantom points may be incorrect."
286					)
287					topSideY = verticalAdvanceWidth
288		bottomSideY = topSideY - verticalAdvanceWidth
289		return [
290			(leftSideX, 0),
291			(rightSideX, 0),
292			(0, topSideY),
293			(0, bottomSideY),
294		]
295
296	def getCoordinatesAndControls(self, glyphName, ttFont, defaultVerticalOrigin=None):
297		"""Return glyph coordinates and controls as expected by "gvar" table.
298
299		The coordinates includes four "phantom points" for the glyph metrics,
300		as mandated by the "gvar" spec.
301
302		The glyph controls is a namedtuple with the following attributes:
303			- numberOfContours: -1 for composite glyphs.
304			- endPts: list of indices of end points for each contour in simple
305			glyphs, or component indices in composite glyphs (used for IUP
306			optimization).
307			- flags: array of contour point flags for simple glyphs (None for
308			composite glyphs).
309			- components: list of base glyph names (str) for each component in
310			composite glyphs (None for simple glyphs).
311
312		The "ttFont" and "defaultVerticalOrigin" args are used to compute the
313		"phantom points" (see "getPhantomPoints" method).
314
315		Return None if the requested glyphName is not present.
316		"""
317		if glyphName not in self.glyphs:
318			return None
319		glyph = self[glyphName]
320		if glyph.isComposite():
321			coords = GlyphCoordinates(
322				[(getattr(c, 'x', 0), getattr(c, 'y', 0)) for c in glyph.components]
323			)
324			controls = _GlyphControls(
325				numberOfContours=glyph.numberOfContours,
326				endPts=list(range(len(glyph.components))),
327				flags=None,
328				components=[c.glyphName for c in glyph.components],
329			)
330		else:
331			coords, endPts, flags = glyph.getCoordinates(self)
332			coords = coords.copy()
333			controls = _GlyphControls(
334				numberOfContours=glyph.numberOfContours,
335				endPts=endPts,
336				flags=flags,
337				components=None,
338			)
339		# Add phantom points for (left, right, top, bottom) positions.
340		phantomPoints = self.getPhantomPoints(
341			glyphName, ttFont, defaultVerticalOrigin=defaultVerticalOrigin
342		)
343		coords.extend(phantomPoints)
344		return coords, controls
345
346	def setCoordinates(self, glyphName, coord, ttFont):
347		"""Set coordinates and metrics for the given glyph.
348
349		"coord" is an array of GlyphCoordinates which must include the "phantom
350		points" as the last four coordinates.
351
352		Both the horizontal/vertical advances and left/top sidebearings in "hmtx"
353		and "vmtx" tables (if any) are updated from four phantom points and
354		the glyph's bounding boxes.
355		"""
356		# TODO: Create new glyph if not already present
357		assert glyphName in self.glyphs
358		glyph = self[glyphName]
359
360		# Handle phantom points for (left, right, top, bottom) positions.
361		assert len(coord) >= 4
362		leftSideX = coord[-4][0]
363		rightSideX = coord[-3][0]
364		topSideY = coord[-2][1]
365		bottomSideY = coord[-1][1]
366
367		coord = coord[:-4]
368
369		if glyph.isComposite():
370			assert len(coord) == len(glyph.components)
371			for p, comp in zip(coord, glyph.components):
372				if hasattr(comp, 'x'):
373					comp.x, comp.y = p
374		elif glyph.numberOfContours == 0:
375			assert len(coord) == 0
376		else:
377			assert len(coord) == len(glyph.coordinates)
378			glyph.coordinates = GlyphCoordinates(coord)
379
380		glyph.recalcBounds(self)
381
382		horizontalAdvanceWidth = otRound(rightSideX - leftSideX)
383		if horizontalAdvanceWidth < 0:
384			# unlikely, but it can happen, see:
385			# https://github.com/fonttools/fonttools/pull/1198
386			horizontalAdvanceWidth = 0
387		leftSideBearing = otRound(glyph.xMin - leftSideX)
388		ttFont["hmtx"].metrics[glyphName] = horizontalAdvanceWidth, leftSideBearing
389
390		if "vmtx" in ttFont:
391			verticalAdvanceWidth = otRound(topSideY - bottomSideY)
392			if verticalAdvanceWidth < 0:  # unlikely but do the same as horizontal
393				verticalAdvanceWidth = 0
394			topSideBearing = otRound(topSideY - glyph.yMax)
395			ttFont["vmtx"].metrics[glyphName] = verticalAdvanceWidth, topSideBearing
396
397
398_GlyphControls = namedtuple(
399	"_GlyphControls", "numberOfContours endPts flags components"
400)
401
402
403glyphHeaderFormat = """
404		>	# big endian
405		numberOfContours:	h
406		xMin:				h
407		yMin:				h
408		xMax:				h
409		yMax:				h
410"""
411
412# flags
413flagOnCurve = 0x01
414flagXShort = 0x02
415flagYShort = 0x04
416flagRepeat = 0x08
417flagXsame =  0x10
418flagYsame = 0x20
419flagOverlapSimple = 0x40
420flagReserved = 0x80
421
422# These flags are kept for XML output after decompiling the coordinates
423keepFlags = flagOnCurve + flagOverlapSimple
424
425_flagSignBytes = {
426	0: 2,
427	flagXsame: 0,
428	flagXShort|flagXsame: +1,
429	flagXShort: -1,
430	flagYsame: 0,
431	flagYShort|flagYsame: +1,
432	flagYShort: -1,
433}
434
435def flagBest(x, y, onCurve):
436	"""For a given x,y delta pair, returns the flag that packs this pair
437	most efficiently, as well as the number of byte cost of such flag."""
438
439	flag = flagOnCurve if onCurve else 0
440	cost = 0
441	# do x
442	if x == 0:
443		flag = flag | flagXsame
444	elif -255 <= x <= 255:
445		flag = flag | flagXShort
446		if x > 0:
447			flag = flag | flagXsame
448		cost += 1
449	else:
450		cost += 2
451	# do y
452	if y == 0:
453		flag = flag | flagYsame
454	elif -255 <= y <= 255:
455		flag = flag | flagYShort
456		if y > 0:
457			flag = flag | flagYsame
458		cost += 1
459	else:
460		cost += 2
461	return flag, cost
462
463def flagFits(newFlag, oldFlag, mask):
464	newBytes = _flagSignBytes[newFlag & mask]
465	oldBytes = _flagSignBytes[oldFlag & mask]
466	return newBytes == oldBytes or abs(newBytes) > abs(oldBytes)
467
468def flagSupports(newFlag, oldFlag):
469	return ((oldFlag & flagOnCurve) == (newFlag & flagOnCurve) and
470		flagFits(newFlag, oldFlag, flagXsame|flagXShort) and
471		flagFits(newFlag, oldFlag, flagYsame|flagYShort))
472
473def flagEncodeCoord(flag, mask, coord, coordBytes):
474	byteCount = _flagSignBytes[flag & mask]
475	if byteCount == 1:
476		coordBytes.append(coord)
477	elif byteCount == -1:
478		coordBytes.append(-coord)
479	elif byteCount == 2:
480		coordBytes.append((coord >> 8) & 0xFF)
481		coordBytes.append(coord & 0xFF)
482
483def flagEncodeCoords(flag, x, y, xBytes, yBytes):
484	flagEncodeCoord(flag, flagXsame|flagXShort, x, xBytes)
485	flagEncodeCoord(flag, flagYsame|flagYShort, y, yBytes)
486
487
488ARG_1_AND_2_ARE_WORDS		= 0x0001  # if set args are words otherwise they are bytes
489ARGS_ARE_XY_VALUES		= 0x0002  # if set args are xy values, otherwise they are points
490ROUND_XY_TO_GRID		= 0x0004  # for the xy values if above is true
491WE_HAVE_A_SCALE			= 0x0008  # Sx = Sy, otherwise scale == 1.0
492NON_OVERLAPPING			= 0x0010  # set to same value for all components (obsolete!)
493MORE_COMPONENTS			= 0x0020  # indicates at least one more glyph after this one
494WE_HAVE_AN_X_AND_Y_SCALE	= 0x0040  # Sx, Sy
495WE_HAVE_A_TWO_BY_TWO		= 0x0080  # t00, t01, t10, t11
496WE_HAVE_INSTRUCTIONS		= 0x0100  # instructions follow
497USE_MY_METRICS			= 0x0200  # apply these metrics to parent glyph
498OVERLAP_COMPOUND		= 0x0400  # used by Apple in GX fonts
499SCALED_COMPONENT_OFFSET		= 0x0800  # composite designed to have the component offset scaled (designed for Apple)
500UNSCALED_COMPONENT_OFFSET	= 0x1000  # composite designed not to have the component offset scaled (designed for MS)
501
502
503CompositeMaxpValues = namedtuple('CompositeMaxpValues', ['nPoints', 'nContours', 'maxComponentDepth'])
504
505
506class Glyph(object):
507
508	def __init__(self, data=""):
509		if not data:
510			# empty char
511			self.numberOfContours = 0
512			return
513		self.data = data
514
515	def compact(self, glyfTable, recalcBBoxes=True):
516		data = self.compile(glyfTable, recalcBBoxes)
517		self.__dict__.clear()
518		self.data = data
519
520	def expand(self, glyfTable):
521		if not hasattr(self, "data"):
522			# already unpacked
523			return
524		if not self.data:
525			# empty char
526			del self.data
527			self.numberOfContours = 0
528			return
529		dummy, data = sstruct.unpack2(glyphHeaderFormat, self.data, self)
530		del self.data
531		# Some fonts (eg. Neirizi.ttf) have a 0 for numberOfContours in
532		# some glyphs; decompileCoordinates assumes that there's at least
533		# one, so short-circuit here.
534		if self.numberOfContours == 0:
535			return
536		if self.isComposite():
537			self.decompileComponents(data, glyfTable)
538		else:
539			self.decompileCoordinates(data)
540
541	def compile(self, glyfTable, recalcBBoxes=True):
542		if hasattr(self, "data"):
543			if recalcBBoxes:
544				# must unpack glyph in order to recalculate bounding box
545				self.expand(glyfTable)
546			else:
547				return self.data
548		if self.numberOfContours == 0:
549			return ""
550		if recalcBBoxes:
551			self.recalcBounds(glyfTable)
552		data = sstruct.pack(glyphHeaderFormat, self)
553		if self.isComposite():
554			data = data + self.compileComponents(glyfTable)
555		else:
556			data = data + self.compileCoordinates()
557		return data
558
559	def toXML(self, writer, ttFont):
560		if self.isComposite():
561			for compo in self.components:
562				compo.toXML(writer, ttFont)
563			haveInstructions = hasattr(self, "program")
564		else:
565			last = 0
566			for i in range(self.numberOfContours):
567				writer.begintag("contour")
568				writer.newline()
569				for j in range(last, self.endPtsOfContours[i] + 1):
570					attrs = [
571							("x", self.coordinates[j][0]),
572							("y", self.coordinates[j][1]),
573							("on", self.flags[j] & flagOnCurve),
574						]
575					if self.flags[j] & flagOverlapSimple:
576						# Apple's rasterizer uses flagOverlapSimple in the first contour/first pt to flag glyphs that contain overlapping contours
577						attrs.append(("overlap", 1))
578					writer.simpletag("pt", attrs)
579					writer.newline()
580				last = self.endPtsOfContours[i] + 1
581				writer.endtag("contour")
582				writer.newline()
583			haveInstructions = self.numberOfContours > 0
584		if haveInstructions:
585			if self.program:
586				writer.begintag("instructions")
587				writer.newline()
588				self.program.toXML(writer, ttFont)
589				writer.endtag("instructions")
590			else:
591				writer.simpletag("instructions")
592			writer.newline()
593
594	def fromXML(self, name, attrs, content, ttFont):
595		if name == "contour":
596			if self.numberOfContours < 0:
597				raise ttLib.TTLibError("can't mix composites and contours in glyph")
598			self.numberOfContours = self.numberOfContours + 1
599			coordinates = GlyphCoordinates()
600			flags = []
601			for element in content:
602				if not isinstance(element, tuple):
603					continue
604				name, attrs, content = element
605				if name != "pt":
606					continue  # ignore anything but "pt"
607				coordinates.append((safeEval(attrs["x"]), safeEval(attrs["y"])))
608				flag = not not safeEval(attrs["on"])
609				if "overlap" in attrs and bool(safeEval(attrs["overlap"])):
610					flag |= flagOverlapSimple
611				flags.append(flag)
612			flags = array.array("B", flags)
613			if not hasattr(self, "coordinates"):
614				self.coordinates = coordinates
615				self.flags = flags
616				self.endPtsOfContours = [len(coordinates)-1]
617			else:
618				self.coordinates.extend (coordinates)
619				self.flags.extend(flags)
620				self.endPtsOfContours.append(len(self.coordinates)-1)
621		elif name == "component":
622			if self.numberOfContours > 0:
623				raise ttLib.TTLibError("can't mix composites and contours in glyph")
624			self.numberOfContours = -1
625			if not hasattr(self, "components"):
626				self.components = []
627			component = GlyphComponent()
628			self.components.append(component)
629			component.fromXML(name, attrs, content, ttFont)
630		elif name == "instructions":
631			self.program = ttProgram.Program()
632			for element in content:
633				if not isinstance(element, tuple):
634					continue
635				name, attrs, content = element
636				self.program.fromXML(name, attrs, content, ttFont)
637
638	def getCompositeMaxpValues(self, glyfTable, maxComponentDepth=1):
639		assert self.isComposite()
640		nContours = 0
641		nPoints = 0
642		for compo in self.components:
643			baseGlyph = glyfTable[compo.glyphName]
644			if baseGlyph.numberOfContours == 0:
645				continue
646			elif baseGlyph.numberOfContours > 0:
647				nP, nC = baseGlyph.getMaxpValues()
648			else:
649				nP, nC, maxComponentDepth = baseGlyph.getCompositeMaxpValues(
650						glyfTable, maxComponentDepth + 1)
651			nPoints = nPoints + nP
652			nContours = nContours + nC
653		return CompositeMaxpValues(nPoints, nContours, maxComponentDepth)
654
655	def getMaxpValues(self):
656		assert self.numberOfContours > 0
657		return len(self.coordinates), len(self.endPtsOfContours)
658
659	def decompileComponents(self, data, glyfTable):
660		self.components = []
661		more = 1
662		haveInstructions = 0
663		while more:
664			component = GlyphComponent()
665			more, haveInstr, data = component.decompile(data, glyfTable)
666			haveInstructions = haveInstructions | haveInstr
667			self.components.append(component)
668		if haveInstructions:
669			numInstructions, = struct.unpack(">h", data[:2])
670			data = data[2:]
671			self.program = ttProgram.Program()
672			self.program.fromBytecode(data[:numInstructions])
673			data = data[numInstructions:]
674			if len(data) >= 4:
675				log.warning(
676					"too much glyph data at the end of composite glyph: %d excess bytes",
677					len(data))
678
679	def decompileCoordinates(self, data):
680		endPtsOfContours = array.array("h")
681		endPtsOfContours.fromstring(data[:2*self.numberOfContours])
682		if sys.byteorder != "big": endPtsOfContours.byteswap()
683		self.endPtsOfContours = endPtsOfContours.tolist()
684
685		data = data[2*self.numberOfContours:]
686
687		instructionLength, = struct.unpack(">h", data[:2])
688		data = data[2:]
689		self.program = ttProgram.Program()
690		self.program.fromBytecode(data[:instructionLength])
691		data = data[instructionLength:]
692		nCoordinates = self.endPtsOfContours[-1] + 1
693		flags, xCoordinates, yCoordinates = \
694				self.decompileCoordinatesRaw(nCoordinates, data)
695
696		# fill in repetitions and apply signs
697		self.coordinates = coordinates = GlyphCoordinates.zeros(nCoordinates)
698		xIndex = 0
699		yIndex = 0
700		for i in range(nCoordinates):
701			flag = flags[i]
702			# x coordinate
703			if flag & flagXShort:
704				if flag & flagXsame:
705					x = xCoordinates[xIndex]
706				else:
707					x = -xCoordinates[xIndex]
708				xIndex = xIndex + 1
709			elif flag & flagXsame:
710				x = 0
711			else:
712				x = xCoordinates[xIndex]
713				xIndex = xIndex + 1
714			# y coordinate
715			if flag & flagYShort:
716				if flag & flagYsame:
717					y = yCoordinates[yIndex]
718				else:
719					y = -yCoordinates[yIndex]
720				yIndex = yIndex + 1
721			elif flag & flagYsame:
722				y = 0
723			else:
724				y = yCoordinates[yIndex]
725				yIndex = yIndex + 1
726			coordinates[i] = (x, y)
727		assert xIndex == len(xCoordinates)
728		assert yIndex == len(yCoordinates)
729		coordinates.relativeToAbsolute()
730		# discard all flags except "keepFlags"
731		self.flags = array.array("B", (f & keepFlags for f in flags))
732
733	def decompileCoordinatesRaw(self, nCoordinates, data):
734		# unpack flags and prepare unpacking of coordinates
735		flags = array.array("B", [0] * nCoordinates)
736		# Warning: deep Python trickery going on. We use the struct module to unpack
737		# the coordinates. We build a format string based on the flags, so we can
738		# unpack the coordinates in one struct.unpack() call.
739		xFormat = ">" # big endian
740		yFormat = ">" # big endian
741		i = j = 0
742		while True:
743			flag = byteord(data[i])
744			i = i + 1
745			repeat = 1
746			if flag & flagRepeat:
747				repeat = byteord(data[i]) + 1
748				i = i + 1
749			for k in range(repeat):
750				if flag & flagXShort:
751					xFormat = xFormat + 'B'
752				elif not (flag & flagXsame):
753					xFormat = xFormat + 'h'
754				if flag & flagYShort:
755					yFormat = yFormat + 'B'
756				elif not (flag & flagYsame):
757					yFormat = yFormat + 'h'
758				flags[j] = flag
759				j = j + 1
760			if j >= nCoordinates:
761				break
762		assert j == nCoordinates, "bad glyph flags"
763		data = data[i:]
764		# unpack raw coordinates, krrrrrr-tching!
765		xDataLen = struct.calcsize(xFormat)
766		yDataLen = struct.calcsize(yFormat)
767		if len(data) - (xDataLen + yDataLen) >= 4:
768			log.warning(
769				"too much glyph data: %d excess bytes", len(data) - (xDataLen + yDataLen))
770		xCoordinates = struct.unpack(xFormat, data[:xDataLen])
771		yCoordinates = struct.unpack(yFormat, data[xDataLen:xDataLen+yDataLen])
772		return flags, xCoordinates, yCoordinates
773
774	def compileComponents(self, glyfTable):
775		data = b""
776		lastcomponent = len(self.components) - 1
777		more = 1
778		haveInstructions = 0
779		for i in range(len(self.components)):
780			if i == lastcomponent:
781				haveInstructions = hasattr(self, "program")
782				more = 0
783			compo = self.components[i]
784			data = data + compo.compile(more, haveInstructions, glyfTable)
785		if haveInstructions:
786			instructions = self.program.getBytecode()
787			data = data + struct.pack(">h", len(instructions)) + instructions
788		return data
789
790	def compileCoordinates(self):
791		assert len(self.coordinates) == len(self.flags)
792		data = []
793		endPtsOfContours = array.array("h", self.endPtsOfContours)
794		if sys.byteorder != "big": endPtsOfContours.byteswap()
795		data.append(endPtsOfContours.tostring())
796		instructions = self.program.getBytecode()
797		data.append(struct.pack(">h", len(instructions)))
798		data.append(instructions)
799
800		deltas = self.coordinates.copy()
801		if deltas.isFloat():
802			# Warn?
803			deltas.toInt()
804		deltas.absoluteToRelative()
805
806		# TODO(behdad): Add a configuration option for this?
807		deltas = self.compileDeltasGreedy(self.flags, deltas)
808		#deltas = self.compileDeltasOptimal(self.flags, deltas)
809
810		data.extend(deltas)
811		return bytesjoin(data)
812
813	def compileDeltasGreedy(self, flags, deltas):
814		# Implements greedy algorithm for packing coordinate deltas:
815		# uses shortest representation one coordinate at a time.
816		compressedflags = []
817		xPoints = []
818		yPoints = []
819		lastflag = None
820		repeat = 0
821		for flag,(x,y) in zip(flags, deltas):
822			# Oh, the horrors of TrueType
823			# do x
824			if x == 0:
825				flag = flag | flagXsame
826			elif -255 <= x <= 255:
827				flag = flag | flagXShort
828				if x > 0:
829					flag = flag | flagXsame
830				else:
831					x = -x
832				xPoints.append(bytechr(x))
833			else:
834				xPoints.append(struct.pack(">h", x))
835			# do y
836			if y == 0:
837				flag = flag | flagYsame
838			elif -255 <= y <= 255:
839				flag = flag | flagYShort
840				if y > 0:
841					flag = flag | flagYsame
842				else:
843					y = -y
844				yPoints.append(bytechr(y))
845			else:
846				yPoints.append(struct.pack(">h", y))
847			# handle repeating flags
848			if flag == lastflag and repeat != 255:
849				repeat = repeat + 1
850				if repeat == 1:
851					compressedflags.append(flag)
852				else:
853					compressedflags[-2] = flag | flagRepeat
854					compressedflags[-1] = repeat
855			else:
856				repeat = 0
857				compressedflags.append(flag)
858			lastflag = flag
859		compressedFlags = array.array("B", compressedflags).tostring()
860		compressedXs = bytesjoin(xPoints)
861		compressedYs = bytesjoin(yPoints)
862		return (compressedFlags, compressedXs, compressedYs)
863
864	def compileDeltasOptimal(self, flags, deltas):
865		# Implements optimal, dynaic-programming, algorithm for packing coordinate
866		# deltas.  The savings are negligible :(.
867		candidates = []
868		bestTuple = None
869		bestCost = 0
870		repeat = 0
871		for flag,(x,y) in zip(flags, deltas):
872			# Oh, the horrors of TrueType
873			flag, coordBytes = flagBest(x, y, flag)
874			bestCost += 1 + coordBytes
875			newCandidates = [(bestCost, bestTuple, flag, coordBytes),
876							(bestCost+1, bestTuple, (flag|flagRepeat), coordBytes)]
877			for lastCost,lastTuple,lastFlag,coordBytes in candidates:
878				if lastCost + coordBytes <= bestCost + 1 and (lastFlag & flagRepeat) and (lastFlag < 0xff00) and flagSupports(lastFlag, flag):
879					if (lastFlag & 0xFF) == (flag|flagRepeat) and lastCost == bestCost + 1:
880						continue
881					newCandidates.append((lastCost + coordBytes, lastTuple, lastFlag+256, coordBytes))
882			candidates = newCandidates
883			bestTuple = min(candidates, key=lambda t:t[0])
884			bestCost = bestTuple[0]
885
886		flags = []
887		while bestTuple:
888			cost, bestTuple, flag, coordBytes = bestTuple
889			flags.append(flag)
890		flags.reverse()
891
892		compressedFlags = array.array("B")
893		compressedXs = array.array("B")
894		compressedYs = array.array("B")
895		coords = iter(deltas)
896		ff = []
897		for flag in flags:
898			repeatCount, flag = flag >> 8, flag & 0xFF
899			compressedFlags.append(flag)
900			if flag & flagRepeat:
901				assert(repeatCount > 0)
902				compressedFlags.append(repeatCount)
903			else:
904				assert(repeatCount == 0)
905			for i in range(1 + repeatCount):
906				x,y = next(coords)
907				flagEncodeCoords(flag, x, y, compressedXs, compressedYs)
908				ff.append(flag)
909		try:
910			next(coords)
911			raise Exception("internal error")
912		except StopIteration:
913			pass
914		compressedFlags = compressedFlags.tostring()
915		compressedXs = compressedXs.tostring()
916		compressedYs = compressedYs.tostring()
917
918		return (compressedFlags, compressedXs, compressedYs)
919
920	def recalcBounds(self, glyfTable):
921		coords, endPts, flags = self.getCoordinates(glyfTable)
922		if len(coords) > 0:
923			if 0:
924				# This branch calculates exact glyph outline bounds
925				# analytically, handling cases without on-curve
926				# extremas, etc.  However, the glyf table header
927				# simply says that the bounds should be min/max x/y
928				# "for coordinate data", so I suppose that means no
929				# fancy thing here, just get extremas of all coord
930				# points (on and off).  As such, this branch is
931				# disabled.
932
933				# Collect on-curve points
934				onCurveCoords = [coords[j] for j in range(len(coords))
935								if flags[j] & flagOnCurve]
936				# Add implicit on-curve points
937				start = 0
938				for end in endPts:
939					last = end
940					for j in range(start, end + 1):
941						if not ((flags[j] | flags[last]) & flagOnCurve):
942							x = (coords[last][0] + coords[j][0]) / 2
943							y = (coords[last][1] + coords[j][1]) / 2
944							onCurveCoords.append((x,y))
945						last = j
946					start = end + 1
947				# Add bounds for curves without an explicit extrema
948				start = 0
949				for end in endPts:
950					last = end
951					for j in range(start, end + 1):
952						if not (flags[j] & flagOnCurve):
953							next = j + 1 if j < end else start
954							bbox = calcBounds([coords[last], coords[next]])
955							if not pointInRect(coords[j], bbox):
956								# Ouch!
957								log.warning("Outline has curve with implicit extrema.")
958								# Ouch!  Find analytical curve bounds.
959								pthis = coords[j]
960								plast = coords[last]
961								if not (flags[last] & flagOnCurve):
962									plast = ((pthis[0]+plast[0])/2, (pthis[1]+plast[1])/2)
963								pnext = coords[next]
964								if not (flags[next] & flagOnCurve):
965									pnext = ((pthis[0]+pnext[0])/2, (pthis[1]+pnext[1])/2)
966								bbox = calcQuadraticBounds(plast, pthis, pnext)
967								onCurveCoords.append((bbox[0],bbox[1]))
968								onCurveCoords.append((bbox[2],bbox[3]))
969						last = j
970					start = end + 1
971
972				self.xMin, self.yMin, self.xMax, self.yMax = calcIntBounds(onCurveCoords)
973			else:
974				self.xMin, self.yMin, self.xMax, self.yMax = calcIntBounds(coords)
975		else:
976			self.xMin, self.yMin, self.xMax, self.yMax = (0, 0, 0, 0)
977
978	def isComposite(self):
979		"""Can be called on compact or expanded glyph."""
980		if hasattr(self, "data") and self.data:
981			return struct.unpack(">h", self.data[:2])[0] == -1
982		else:
983			return self.numberOfContours == -1
984
985	def __getitem__(self, componentIndex):
986		if not self.isComposite():
987			raise ttLib.TTLibError("can't use glyph as sequence")
988		return self.components[componentIndex]
989
990	def getCoordinates(self, glyfTable):
991		if self.numberOfContours > 0:
992			return self.coordinates, self.endPtsOfContours, self.flags
993		elif self.isComposite():
994			# it's a composite
995			allCoords = GlyphCoordinates()
996			allFlags = array.array("B")
997			allEndPts = []
998			for compo in self.components:
999				g = glyfTable[compo.glyphName]
1000				try:
1001					coordinates, endPts, flags = g.getCoordinates(glyfTable)
1002				except RecursionError:
1003					raise ttLib.TTLibError("glyph '%s' contains a recursive component reference" % compo.glyphName)
1004				if hasattr(compo, "firstPt"):
1005					# move according to two reference points
1006					x1,y1 = allCoords[compo.firstPt]
1007					x2,y2 = coordinates[compo.secondPt]
1008					move = x1-x2, y1-y2
1009				else:
1010					move = compo.x, compo.y
1011
1012				coordinates = GlyphCoordinates(coordinates)
1013				if not hasattr(compo, "transform"):
1014					coordinates.translate(move)
1015				else:
1016					apple_way = compo.flags & SCALED_COMPONENT_OFFSET
1017					ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET
1018					assert not (apple_way and ms_way)
1019					if not (apple_way or ms_way):
1020						scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT  # see top of this file
1021					else:
1022						scale_component_offset = apple_way
1023					if scale_component_offset:
1024						# the Apple way: first move, then scale (ie. scale the component offset)
1025						coordinates.translate(move)
1026						coordinates.transform(compo.transform)
1027					else:
1028						# the MS way: first scale, then move
1029						coordinates.transform(compo.transform)
1030						coordinates.translate(move)
1031				offset = len(allCoords)
1032				allEndPts.extend(e + offset for e in endPts)
1033				allCoords.extend(coordinates)
1034				allFlags.extend(flags)
1035			return allCoords, allEndPts, allFlags
1036		else:
1037			return GlyphCoordinates(), [], array.array("B")
1038
1039	def getComponentNames(self, glyfTable):
1040		if not hasattr(self, "data"):
1041			if self.isComposite():
1042				return [c.glyphName for c in self.components]
1043			else:
1044				return []
1045
1046		# Extract components without expanding glyph
1047
1048		if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
1049			return []  # Not composite
1050
1051		data = self.data
1052		i = 10
1053		components = []
1054		more = 1
1055		while more:
1056			flags, glyphID = struct.unpack(">HH", data[i:i+4])
1057			i += 4
1058			flags = int(flags)
1059			components.append(glyfTable.getGlyphName(int(glyphID)))
1060
1061			if flags & ARG_1_AND_2_ARE_WORDS: i += 4
1062			else: i += 2
1063			if flags & WE_HAVE_A_SCALE: i += 2
1064			elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
1065			elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
1066			more = flags & MORE_COMPONENTS
1067
1068		return components
1069
1070	def trim(self, remove_hinting=False):
1071		""" Remove padding and, if requested, hinting, from a glyph.
1072			This works on both expanded and compacted glyphs, without
1073			expanding it."""
1074		if not hasattr(self, "data"):
1075			if remove_hinting:
1076				if self.isComposite():
1077					if hasattr(self, "program"):
1078						del self.program
1079				else:
1080					self.program = ttProgram.Program()
1081					self.program.fromBytecode([])
1082			# No padding to trim.
1083			return
1084		if not self.data:
1085			return
1086		numContours = struct.unpack(">h", self.data[:2])[0]
1087		data = array.array("B", self.data)
1088		i = 10
1089		if numContours >= 0:
1090			i += 2 * numContours # endPtsOfContours
1091			nCoordinates = ((data[i-2] << 8) | data[i-1]) + 1
1092			instructionLen = (data[i] << 8) | data[i+1]
1093			if remove_hinting:
1094				# Zero instruction length
1095				data[i] = data [i+1] = 0
1096				i += 2
1097				if instructionLen:
1098					# Splice it out
1099					data = data[:i] + data[i+instructionLen:]
1100				instructionLen = 0
1101			else:
1102				i += 2 + instructionLen
1103
1104			coordBytes = 0
1105			j = 0
1106			while True:
1107				flag = data[i]
1108				i = i + 1
1109				repeat = 1
1110				if flag & flagRepeat:
1111					repeat = data[i] + 1
1112					i = i + 1
1113				xBytes = yBytes = 0
1114				if flag & flagXShort:
1115					xBytes = 1
1116				elif not (flag & flagXsame):
1117					xBytes = 2
1118				if flag & flagYShort:
1119					yBytes = 1
1120				elif not (flag & flagYsame):
1121					yBytes = 2
1122				coordBytes += (xBytes + yBytes) * repeat
1123				j += repeat
1124				if j >= nCoordinates:
1125					break
1126			assert j == nCoordinates, "bad glyph flags"
1127			i += coordBytes
1128			# Remove padding
1129			data = data[:i]
1130		else:
1131			more = 1
1132			we_have_instructions = False
1133			while more:
1134				flags =(data[i] << 8) | data[i+1]
1135				if remove_hinting:
1136					flags &= ~WE_HAVE_INSTRUCTIONS
1137				if flags & WE_HAVE_INSTRUCTIONS:
1138					we_have_instructions = True
1139				data[i+0] = flags >> 8
1140				data[i+1] = flags & 0xFF
1141				i += 4
1142				flags = int(flags)
1143
1144				if flags & ARG_1_AND_2_ARE_WORDS: i += 4
1145				else: i += 2
1146				if flags & WE_HAVE_A_SCALE: i += 2
1147				elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
1148				elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
1149				more = flags & MORE_COMPONENTS
1150			if we_have_instructions:
1151				instructionLen = (data[i] << 8) | data[i+1]
1152				i += 2 + instructionLen
1153			# Remove padding
1154			data = data[:i]
1155
1156		self.data = data.tostring()
1157
1158	def removeHinting(self):
1159		self.trim (remove_hinting=True)
1160
1161	def draw(self, pen, glyfTable, offset=0):
1162
1163		if self.isComposite():
1164			for component in self.components:
1165				glyphName, transform = component.getComponentInfo()
1166				pen.addComponent(glyphName, transform)
1167			return
1168
1169		coordinates, endPts, flags = self.getCoordinates(glyfTable)
1170		if offset:
1171			coordinates = coordinates.copy()
1172			coordinates.translate((offset, 0))
1173		start = 0
1174		for end in endPts:
1175			end = end + 1
1176			contour = coordinates[start:end]
1177			cFlags = flags[start:end]
1178			start = end
1179			if 1 not in cFlags:
1180				# There is not a single on-curve point on the curve,
1181				# use pen.qCurveTo's special case by specifying None
1182				# as the on-curve point.
1183				contour.append(None)
1184				pen.qCurveTo(*contour)
1185			else:
1186				# Shuffle the points so that contour the is guaranteed
1187				# to *end* in an on-curve point, which we'll use for
1188				# the moveTo.
1189				firstOnCurve = cFlags.index(1) + 1
1190				contour = contour[firstOnCurve:] + contour[:firstOnCurve]
1191				cFlags = cFlags[firstOnCurve:] + cFlags[:firstOnCurve]
1192				pen.moveTo(contour[-1])
1193				while contour:
1194					nextOnCurve = cFlags.index(1) + 1
1195					if nextOnCurve == 1:
1196						pen.lineTo(contour[0])
1197					else:
1198						pen.qCurveTo(*contour[:nextOnCurve])
1199					contour = contour[nextOnCurve:]
1200					cFlags = cFlags[nextOnCurve:]
1201			pen.closePath()
1202
1203	def drawPoints(self, pen, glyfTable, offset=0):
1204		"""Draw the glyph using the supplied pointPen. Opposed to Glyph.draw(),
1205		this will not change the point indices.
1206		"""
1207
1208		if self.isComposite():
1209			for component in self.components:
1210				glyphName, transform = component.getComponentInfo()
1211				pen.addComponent(glyphName, transform)
1212			return
1213
1214		coordinates, endPts, flags = self.getCoordinates(glyfTable)
1215		if offset:
1216			coordinates = coordinates.copy()
1217			coordinates.translate((offset, 0))
1218		start = 0
1219		for end in endPts:
1220			end = end + 1
1221			contour = coordinates[start:end]
1222			cFlags = flags[start:end]
1223			start = end
1224			pen.beginPath()
1225			# Start with the appropriate segment type based on the final segment
1226			segmentType = "line" if cFlags[-1] == 1 else "qcurve"
1227			for i, pt in enumerate(contour):
1228				if cFlags[i] == 1:
1229					pen.addPoint(pt, segmentType=segmentType)
1230					segmentType = "line"
1231				else:
1232					pen.addPoint(pt)
1233					segmentType = "qcurve"
1234			pen.endPath()
1235
1236	def __eq__(self, other):
1237		if type(self) != type(other):
1238			return NotImplemented
1239		return self.__dict__ == other.__dict__
1240
1241	def __ne__(self, other):
1242		result = self.__eq__(other)
1243		return result if result is NotImplemented else not result
1244
1245class GlyphComponent(object):
1246
1247	def __init__(self):
1248		pass
1249
1250	def getComponentInfo(self):
1251		"""Return the base glyph name and a transform."""
1252		# XXX Ignoring self.firstPt & self.lastpt for now: I need to implement
1253		# something equivalent in fontTools.objects.glyph (I'd rather not
1254		# convert it to an absolute offset, since it is valuable information).
1255		# This method will now raise "AttributeError: x" on glyphs that use
1256		# this TT feature.
1257		if hasattr(self, "transform"):
1258			[[xx, xy], [yx, yy]] = self.transform
1259			trans = (xx, xy, yx, yy, self.x, self.y)
1260		else:
1261			trans = (1, 0, 0, 1, self.x, self.y)
1262		return self.glyphName, trans
1263
1264	def decompile(self, data, glyfTable):
1265		flags, glyphID = struct.unpack(">HH", data[:4])
1266		self.flags = int(flags)
1267		glyphID = int(glyphID)
1268		self.glyphName = glyfTable.getGlyphName(int(glyphID))
1269		data = data[4:]
1270
1271		if self.flags & ARG_1_AND_2_ARE_WORDS:
1272			if self.flags & ARGS_ARE_XY_VALUES:
1273				self.x, self.y = struct.unpack(">hh", data[:4])
1274			else:
1275				x, y = struct.unpack(">HH", data[:4])
1276				self.firstPt, self.secondPt = int(x), int(y)
1277			data = data[4:]
1278		else:
1279			if self.flags & ARGS_ARE_XY_VALUES:
1280				self.x, self.y = struct.unpack(">bb", data[:2])
1281			else:
1282				x, y = struct.unpack(">BB", data[:2])
1283				self.firstPt, self.secondPt = int(x), int(y)
1284			data = data[2:]
1285
1286		if self.flags & WE_HAVE_A_SCALE:
1287			scale, = struct.unpack(">h", data[:2])
1288			self.transform = [[fi2fl(scale,14), 0], [0, fi2fl(scale,14)]]  # fixed 2.14
1289			data = data[2:]
1290		elif self.flags & WE_HAVE_AN_X_AND_Y_SCALE:
1291			xscale, yscale = struct.unpack(">hh", data[:4])
1292			self.transform = [[fi2fl(xscale,14), 0], [0, fi2fl(yscale,14)]]  # fixed 2.14
1293			data = data[4:]
1294		elif self.flags & WE_HAVE_A_TWO_BY_TWO:
1295			(xscale, scale01,
1296					scale10, yscale) = struct.unpack(">hhhh", data[:8])
1297			self.transform = [[fi2fl(xscale,14), fi2fl(scale01,14)],
1298							[fi2fl(scale10,14), fi2fl(yscale,14)]] # fixed 2.14
1299			data = data[8:]
1300		more = self.flags & MORE_COMPONENTS
1301		haveInstructions = self.flags & WE_HAVE_INSTRUCTIONS
1302		self.flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
1303				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
1304				NON_OVERLAPPING | OVERLAP_COMPOUND)
1305		return more, haveInstructions, data
1306
1307	def compile(self, more, haveInstructions, glyfTable):
1308		data = b""
1309
1310		# reset all flags we will calculate ourselves
1311		flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
1312				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
1313				NON_OVERLAPPING | OVERLAP_COMPOUND)
1314		if more:
1315			flags = flags | MORE_COMPONENTS
1316		if haveInstructions:
1317			flags = flags | WE_HAVE_INSTRUCTIONS
1318
1319		if hasattr(self, "firstPt"):
1320			if (0 <= self.firstPt <= 255) and (0 <= self.secondPt <= 255):
1321				data = data + struct.pack(">BB", self.firstPt, self.secondPt)
1322			else:
1323				data = data + struct.pack(">HH", self.firstPt, self.secondPt)
1324				flags = flags | ARG_1_AND_2_ARE_WORDS
1325		else:
1326			x = otRound(self.x)
1327			y = otRound(self.y)
1328			flags = flags | ARGS_ARE_XY_VALUES
1329			if (-128 <= x <= 127) and (-128 <= y <= 127):
1330				data = data + struct.pack(">bb", x, y)
1331			else:
1332				data = data + struct.pack(">hh", x, y)
1333				flags = flags | ARG_1_AND_2_ARE_WORDS
1334
1335		if hasattr(self, "transform"):
1336			transform = [[fl2fi(x,14) for x in row] for row in self.transform]
1337			if transform[0][1] or transform[1][0]:
1338				flags = flags | WE_HAVE_A_TWO_BY_TWO
1339				data = data + struct.pack(">hhhh",
1340						transform[0][0], transform[0][1],
1341						transform[1][0], transform[1][1])
1342			elif transform[0][0] != transform[1][1]:
1343				flags = flags | WE_HAVE_AN_X_AND_Y_SCALE
1344				data = data + struct.pack(">hh",
1345						transform[0][0], transform[1][1])
1346			else:
1347				flags = flags | WE_HAVE_A_SCALE
1348				data = data + struct.pack(">h",
1349						transform[0][0])
1350
1351		glyphID = glyfTable.getGlyphID(self.glyphName)
1352		return struct.pack(">HH", flags, glyphID) + data
1353
1354	def toXML(self, writer, ttFont):
1355		attrs = [("glyphName", self.glyphName)]
1356		if not hasattr(self, "firstPt"):
1357			attrs = attrs + [("x", self.x), ("y", self.y)]
1358		else:
1359			attrs = attrs + [("firstPt", self.firstPt), ("secondPt", self.secondPt)]
1360
1361		if hasattr(self, "transform"):
1362			transform = self.transform
1363			if transform[0][1] or transform[1][0]:
1364				attrs = attrs + [
1365						("scalex", transform[0][0]), ("scale01", transform[0][1]),
1366						("scale10", transform[1][0]), ("scaley", transform[1][1]),
1367						]
1368			elif transform[0][0] != transform[1][1]:
1369				attrs = attrs + [
1370						("scalex", transform[0][0]), ("scaley", transform[1][1]),
1371						]
1372			else:
1373				attrs = attrs + [("scale", transform[0][0])]
1374		attrs = attrs + [("flags", hex(self.flags))]
1375		writer.simpletag("component", attrs)
1376		writer.newline()
1377
1378	def fromXML(self, name, attrs, content, ttFont):
1379		self.glyphName = attrs["glyphName"]
1380		if "firstPt" in attrs:
1381			self.firstPt = safeEval(attrs["firstPt"])
1382			self.secondPt = safeEval(attrs["secondPt"])
1383		else:
1384			self.x = safeEval(attrs["x"])
1385			self.y = safeEval(attrs["y"])
1386		if "scale01" in attrs:
1387			scalex = safeEval(attrs["scalex"])
1388			scale01 = safeEval(attrs["scale01"])
1389			scale10 = safeEval(attrs["scale10"])
1390			scaley = safeEval(attrs["scaley"])
1391			self.transform = [[scalex, scale01], [scale10, scaley]]
1392		elif "scalex" in attrs:
1393			scalex = safeEval(attrs["scalex"])
1394			scaley = safeEval(attrs["scaley"])
1395			self.transform = [[scalex, 0], [0, scaley]]
1396		elif "scale" in attrs:
1397			scale = safeEval(attrs["scale"])
1398			self.transform = [[scale, 0], [0, scale]]
1399		self.flags = safeEval(attrs["flags"])
1400
1401	def __eq__(self, other):
1402		if type(self) != type(other):
1403			return NotImplemented
1404		return self.__dict__ == other.__dict__
1405
1406	def __ne__(self, other):
1407		result = self.__eq__(other)
1408		return result if result is NotImplemented else not result
1409
1410class GlyphCoordinates(object):
1411
1412	def __init__(self, iterable=[], typecode="h"):
1413		self._a = array.array(typecode)
1414		self.extend(iterable)
1415
1416	@property
1417	def array(self):
1418		return self._a
1419
1420	def isFloat(self):
1421		return self._a.typecode == 'd'
1422
1423	def _ensureFloat(self):
1424		if self.isFloat():
1425			return
1426		# The conversion to list() is to work around Jython bug
1427		self._a = array.array("d", list(self._a))
1428
1429	def _checkFloat(self, p):
1430		if self.isFloat():
1431			return p
1432		if any(v > 0x7FFF or v < -0x8000 for v in p):
1433			self._ensureFloat()
1434			return p
1435		if any(isinstance(v, float) for v in p):
1436			p = [int(v) if int(v) == v else v for v in p]
1437			if any(isinstance(v, float) for v in p):
1438				self._ensureFloat()
1439		return p
1440
1441	@staticmethod
1442	def zeros(count):
1443		return GlyphCoordinates([(0,0)] * count)
1444
1445	def copy(self):
1446		c = GlyphCoordinates(typecode=self._a.typecode)
1447		c._a.extend(self._a)
1448		return c
1449
1450	def __len__(self):
1451		return len(self._a) // 2
1452
1453	def __getitem__(self, k):
1454		if isinstance(k, slice):
1455			indices = range(*k.indices(len(self)))
1456			return [self[i] for i in indices]
1457		return self._a[2*k],self._a[2*k+1]
1458
1459	def __setitem__(self, k, v):
1460		if isinstance(k, slice):
1461			indices = range(*k.indices(len(self)))
1462			# XXX This only works if len(v) == len(indices)
1463			for j,i in enumerate(indices):
1464				self[i] = v[j]
1465			return
1466		v = self._checkFloat(v)
1467		self._a[2*k],self._a[2*k+1] = v
1468
1469	def __delitem__(self, i):
1470		i = (2*i) % len(self._a)
1471		del self._a[i]
1472		del self._a[i]
1473
1474	def __repr__(self):
1475		return 'GlyphCoordinates(['+','.join(str(c) for c in self)+'])'
1476
1477	def append(self, p):
1478		p = self._checkFloat(p)
1479		self._a.extend(tuple(p))
1480
1481	def extend(self, iterable):
1482		for p in iterable:
1483			p = self._checkFloat(p)
1484			self._a.extend(p)
1485
1486	def toInt(self):
1487		if not self.isFloat():
1488			return
1489		a = array.array("h")
1490		for n in self._a:
1491			a.append(otRound(n))
1492		self._a = a
1493
1494	def relativeToAbsolute(self):
1495		a = self._a
1496		x,y = 0,0
1497		for i in range(len(a) // 2):
1498			x = a[2*i  ] + x
1499			y = a[2*i+1] + y
1500			self[i] = (x, y)
1501
1502	def absoluteToRelative(self):
1503		a = self._a
1504		x,y = 0,0
1505		for i in range(len(a) // 2):
1506			dx = a[2*i  ] - x
1507			dy = a[2*i+1] - y
1508			x = a[2*i  ]
1509			y = a[2*i+1]
1510			self[i] = (dx, dy)
1511
1512	def translate(self, p):
1513		"""
1514		>>> GlyphCoordinates([(1,2)]).translate((.5,0))
1515		"""
1516		(x,y) = self._checkFloat(p)
1517		a = self._a
1518		for i in range(len(a) // 2):
1519			self[i] = (a[2*i] + x, a[2*i+1] + y)
1520
1521	def scale(self, p):
1522		"""
1523		>>> GlyphCoordinates([(1,2)]).scale((.5,0))
1524		"""
1525		(x,y) = self._checkFloat(p)
1526		a = self._a
1527		for i in range(len(a) // 2):
1528			self[i] = (a[2*i] * x, a[2*i+1] * y)
1529
1530	def transform(self, t):
1531		"""
1532		>>> GlyphCoordinates([(1,2)]).transform(((.5,0),(.2,.5)))
1533		"""
1534		a = self._a
1535		for i in range(len(a) // 2):
1536			x = a[2*i  ]
1537			y = a[2*i+1]
1538			px = x * t[0][0] + y * t[1][0]
1539			py = x * t[0][1] + y * t[1][1]
1540			self[i] = (px, py)
1541
1542	def __eq__(self, other):
1543		"""
1544		>>> g = GlyphCoordinates([(1,2)])
1545		>>> g2 = GlyphCoordinates([(1.0,2)])
1546		>>> g3 = GlyphCoordinates([(1.5,2)])
1547		>>> g == g2
1548		True
1549		>>> g == g3
1550		False
1551		>>> g2 == g3
1552		False
1553		"""
1554		if type(self) != type(other):
1555			return NotImplemented
1556		return self._a == other._a
1557
1558	def __ne__(self, other):
1559		"""
1560		>>> g = GlyphCoordinates([(1,2)])
1561		>>> g2 = GlyphCoordinates([(1.0,2)])
1562		>>> g3 = GlyphCoordinates([(1.5,2)])
1563		>>> g != g2
1564		False
1565		>>> g != g3
1566		True
1567		>>> g2 != g3
1568		True
1569		"""
1570		result = self.__eq__(other)
1571		return result if result is NotImplemented else not result
1572
1573	# Math operations
1574
1575	def __pos__(self):
1576		"""
1577		>>> g = GlyphCoordinates([(1,2)])
1578		>>> g
1579		GlyphCoordinates([(1, 2)])
1580		>>> g2 = +g
1581		>>> g2
1582		GlyphCoordinates([(1, 2)])
1583		>>> g2.translate((1,0))
1584		>>> g2
1585		GlyphCoordinates([(2, 2)])
1586		>>> g
1587		GlyphCoordinates([(1, 2)])
1588		"""
1589		return self.copy()
1590	def __neg__(self):
1591		"""
1592		>>> g = GlyphCoordinates([(1,2)])
1593		>>> g
1594		GlyphCoordinates([(1, 2)])
1595		>>> g2 = -g
1596		>>> g2
1597		GlyphCoordinates([(-1, -2)])
1598		>>> g
1599		GlyphCoordinates([(1, 2)])
1600		"""
1601		r = self.copy()
1602		a = r._a
1603		for i in range(len(a)):
1604			a[i] = -a[i]
1605		return r
1606	def __round__(self):
1607		"""
1608		Note: This is Python 3 only.  Python 2 does not call __round__.
1609		As such, we cannot test this method either. :(
1610		"""
1611		r = self.copy()
1612		r.toInt()
1613		return r
1614
1615	def __add__(self, other): return self.copy().__iadd__(other)
1616	def __sub__(self, other): return self.copy().__isub__(other)
1617	def __mul__(self, other): return self.copy().__imul__(other)
1618	def __truediv__(self, other): return self.copy().__itruediv__(other)
1619
1620	__radd__ = __add__
1621	__rmul__ = __mul__
1622	def __rsub__(self, other): return other + (-self)
1623
1624	def __iadd__(self, other):
1625		"""
1626		>>> g = GlyphCoordinates([(1,2)])
1627		>>> g += (.5,0)
1628		>>> g
1629		GlyphCoordinates([(1.5, 2.0)])
1630		>>> g2 = GlyphCoordinates([(3,4)])
1631		>>> g += g2
1632		>>> g
1633		GlyphCoordinates([(4.5, 6.0)])
1634		"""
1635		if isinstance(other, tuple):
1636			assert len(other) ==  2
1637			self.translate(other)
1638			return self
1639		if isinstance(other, GlyphCoordinates):
1640			if other.isFloat(): self._ensureFloat()
1641			other = other._a
1642			a = self._a
1643			assert len(a) == len(other)
1644			for i in range(len(a) // 2):
1645				self[i] = (a[2*i] + other[2*i], a[2*i+1] + other[2*i+1])
1646			return self
1647		return NotImplemented
1648
1649	def __isub__(self, other):
1650		"""
1651		>>> g = GlyphCoordinates([(1,2)])
1652		>>> g -= (.5,0)
1653		>>> g
1654		GlyphCoordinates([(0.5, 2.0)])
1655		>>> g2 = GlyphCoordinates([(3,4)])
1656		>>> g -= g2
1657		>>> g
1658		GlyphCoordinates([(-2.5, -2.0)])
1659		"""
1660		if isinstance(other, tuple):
1661			assert len(other) ==  2
1662			self.translate((-other[0],-other[1]))
1663			return self
1664		if isinstance(other, GlyphCoordinates):
1665			if other.isFloat(): self._ensureFloat()
1666			other = other._a
1667			a = self._a
1668			assert len(a) == len(other)
1669			for i in range(len(a) // 2):
1670				self[i] = (a[2*i] - other[2*i], a[2*i+1] - other[2*i+1])
1671			return self
1672		return NotImplemented
1673
1674	def __imul__(self, other):
1675		"""
1676		>>> g = GlyphCoordinates([(1,2)])
1677		>>> g *= (2,.5)
1678		>>> g *= 2
1679		>>> g
1680		GlyphCoordinates([(4.0, 2.0)])
1681		>>> g = GlyphCoordinates([(1,2)])
1682		>>> g *= 2
1683		>>> g
1684		GlyphCoordinates([(2, 4)])
1685		"""
1686		if isinstance(other, Number):
1687			other = (other, other)
1688		if isinstance(other, tuple):
1689			if other == (1,1):
1690				return self
1691			assert len(other) ==  2
1692			self.scale(other)
1693			return self
1694		return NotImplemented
1695
1696	def __itruediv__(self, other):
1697		"""
1698		>>> g = GlyphCoordinates([(1,3)])
1699		>>> g /= (.5,1.5)
1700		>>> g /= 2
1701		>>> g
1702		GlyphCoordinates([(1.0, 1.0)])
1703		"""
1704		if isinstance(other, Number):
1705			other = (other, other)
1706		if isinstance(other, tuple):
1707			if other == (1,1):
1708				return self
1709			assert len(other) ==  2
1710			self.scale((1./other[0],1./other[1]))
1711			return self
1712		return NotImplemented
1713
1714	def __bool__(self):
1715		"""
1716		>>> g = GlyphCoordinates([])
1717		>>> bool(g)
1718		False
1719		>>> g = GlyphCoordinates([(0,0), (0.,0)])
1720		>>> bool(g)
1721		True
1722		>>> g = GlyphCoordinates([(0,0), (1,0)])
1723		>>> bool(g)
1724		True
1725		>>> g = GlyphCoordinates([(0,.5), (0,0)])
1726		>>> bool(g)
1727		True
1728		"""
1729		return bool(self._a)
1730
1731	__nonzero__ = __bool__
1732
1733
1734def reprflag(flag):
1735	bin = ""
1736	if isinstance(flag, str):
1737		flag = byteord(flag)
1738	while flag:
1739		if flag & 0x01:
1740			bin = "1" + bin
1741		else:
1742			bin = "0" + bin
1743		flag = flag >> 1
1744	bin = (14 - len(bin)) * "0" + bin
1745	return bin
1746
1747
1748if __name__ == "__main__":
1749	import doctest, sys
1750	sys.exit(doctest.testmod().failed)
1751