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