• 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
247glyphHeaderFormat = """
248		>	# big endian
249		numberOfContours:	h
250		xMin:				h
251		yMin:				h
252		xMax:				h
253		yMax:				h
254"""
255
256# flags
257flagOnCurve = 0x01
258flagXShort = 0x02
259flagYShort = 0x04
260flagRepeat = 0x08
261flagXsame =  0x10
262flagYsame = 0x20
263flagOverlapSimple = 0x40
264flagReserved = 0x80
265
266# These flags are kept for XML output after decompiling the coordinates
267keepFlags = flagOnCurve + flagOverlapSimple
268
269_flagSignBytes = {
270	0: 2,
271	flagXsame: 0,
272	flagXShort|flagXsame: +1,
273	flagXShort: -1,
274	flagYsame: 0,
275	flagYShort|flagYsame: +1,
276	flagYShort: -1,
277}
278
279def flagBest(x, y, onCurve):
280	"""For a given x,y delta pair, returns the flag that packs this pair
281	most efficiently, as well as the number of byte cost of such flag."""
282
283	flag = flagOnCurve if onCurve else 0
284	cost = 0
285	# do x
286	if x == 0:
287		flag = flag | flagXsame
288	elif -255 <= x <= 255:
289		flag = flag | flagXShort
290		if x > 0:
291			flag = flag | flagXsame
292		cost += 1
293	else:
294		cost += 2
295	# do y
296	if y == 0:
297		flag = flag | flagYsame
298	elif -255 <= y <= 255:
299		flag = flag | flagYShort
300		if y > 0:
301			flag = flag | flagYsame
302		cost += 1
303	else:
304		cost += 2
305	return flag, cost
306
307def flagFits(newFlag, oldFlag, mask):
308	newBytes = _flagSignBytes[newFlag & mask]
309	oldBytes = _flagSignBytes[oldFlag & mask]
310	return newBytes == oldBytes or abs(newBytes) > abs(oldBytes)
311
312def flagSupports(newFlag, oldFlag):
313	return ((oldFlag & flagOnCurve) == (newFlag & flagOnCurve) and
314		flagFits(newFlag, oldFlag, flagXsame|flagXShort) and
315		flagFits(newFlag, oldFlag, flagYsame|flagYShort))
316
317def flagEncodeCoord(flag, mask, coord, coordBytes):
318	byteCount = _flagSignBytes[flag & mask]
319	if byteCount == 1:
320		coordBytes.append(coord)
321	elif byteCount == -1:
322		coordBytes.append(-coord)
323	elif byteCount == 2:
324		coordBytes.append((coord >> 8) & 0xFF)
325		coordBytes.append(coord & 0xFF)
326
327def flagEncodeCoords(flag, x, y, xBytes, yBytes):
328	flagEncodeCoord(flag, flagXsame|flagXShort, x, xBytes)
329	flagEncodeCoord(flag, flagYsame|flagYShort, y, yBytes)
330
331
332ARG_1_AND_2_ARE_WORDS		= 0x0001  # if set args are words otherwise they are bytes
333ARGS_ARE_XY_VALUES		= 0x0002  # if set args are xy values, otherwise they are points
334ROUND_XY_TO_GRID		= 0x0004  # for the xy values if above is true
335WE_HAVE_A_SCALE			= 0x0008  # Sx = Sy, otherwise scale == 1.0
336NON_OVERLAPPING			= 0x0010  # set to same value for all components (obsolete!)
337MORE_COMPONENTS			= 0x0020  # indicates at least one more glyph after this one
338WE_HAVE_AN_X_AND_Y_SCALE	= 0x0040  # Sx, Sy
339WE_HAVE_A_TWO_BY_TWO		= 0x0080  # t00, t01, t10, t11
340WE_HAVE_INSTRUCTIONS		= 0x0100  # instructions follow
341USE_MY_METRICS			= 0x0200  # apply these metrics to parent glyph
342OVERLAP_COMPOUND		= 0x0400  # used by Apple in GX fonts
343SCALED_COMPONENT_OFFSET		= 0x0800  # composite designed to have the component offset scaled (designed for Apple)
344UNSCALED_COMPONENT_OFFSET	= 0x1000  # composite designed not to have the component offset scaled (designed for MS)
345
346
347CompositeMaxpValues = namedtuple('CompositeMaxpValues', ['nPoints', 'nContours', 'maxComponentDepth'])
348
349
350class Glyph(object):
351
352	def __init__(self, data=""):
353		if not data:
354			# empty char
355			self.numberOfContours = 0
356			return
357		self.data = data
358
359	def compact(self, glyfTable, recalcBBoxes=True):
360		data = self.compile(glyfTable, recalcBBoxes)
361		self.__dict__.clear()
362		self.data = data
363
364	def expand(self, glyfTable):
365		if not hasattr(self, "data"):
366			# already unpacked
367			return
368		if not self.data:
369			# empty char
370			del self.data
371			self.numberOfContours = 0
372			return
373		dummy, data = sstruct.unpack2(glyphHeaderFormat, self.data, self)
374		del self.data
375		# Some fonts (eg. Neirizi.ttf) have a 0 for numberOfContours in
376		# some glyphs; decompileCoordinates assumes that there's at least
377		# one, so short-circuit here.
378		if self.numberOfContours == 0:
379			return
380		if self.isComposite():
381			self.decompileComponents(data, glyfTable)
382		else:
383			self.decompileCoordinates(data)
384
385	def compile(self, glyfTable, recalcBBoxes=True):
386		if hasattr(self, "data"):
387			if recalcBBoxes:
388				# must unpack glyph in order to recalculate bounding box
389				self.expand(glyfTable)
390			else:
391				return self.data
392		if self.numberOfContours == 0:
393			return ""
394		if recalcBBoxes:
395			self.recalcBounds(glyfTable)
396		data = sstruct.pack(glyphHeaderFormat, self)
397		if self.isComposite():
398			data = data + self.compileComponents(glyfTable)
399		else:
400			data = data + self.compileCoordinates()
401		return data
402
403	def toXML(self, writer, ttFont):
404		if self.isComposite():
405			for compo in self.components:
406				compo.toXML(writer, ttFont)
407			haveInstructions = hasattr(self, "program")
408		else:
409			last = 0
410			for i in range(self.numberOfContours):
411				writer.begintag("contour")
412				writer.newline()
413				for j in range(last, self.endPtsOfContours[i] + 1):
414					attrs = [
415							("x", self.coordinates[j][0]),
416							("y", self.coordinates[j][1]),
417							("on", self.flags[j] & flagOnCurve),
418						]
419					if self.flags[j] & flagOverlapSimple:
420						# Apple's rasterizer uses flagOverlapSimple in the first contour/first pt to flag glyphs that contain overlapping contours
421						attrs.append(("overlap", 1))
422					writer.simpletag("pt", attrs)
423					writer.newline()
424				last = self.endPtsOfContours[i] + 1
425				writer.endtag("contour")
426				writer.newline()
427			haveInstructions = self.numberOfContours > 0
428		if haveInstructions:
429			if self.program:
430				writer.begintag("instructions")
431				writer.newline()
432				self.program.toXML(writer, ttFont)
433				writer.endtag("instructions")
434			else:
435				writer.simpletag("instructions")
436			writer.newline()
437
438	def fromXML(self, name, attrs, content, ttFont):
439		if name == "contour":
440			if self.numberOfContours < 0:
441				raise ttLib.TTLibError("can't mix composites and contours in glyph")
442			self.numberOfContours = self.numberOfContours + 1
443			coordinates = GlyphCoordinates()
444			flags = []
445			for element in content:
446				if not isinstance(element, tuple):
447					continue
448				name, attrs, content = element
449				if name != "pt":
450					continue  # ignore anything but "pt"
451				coordinates.append((safeEval(attrs["x"]), safeEval(attrs["y"])))
452				flag = not not safeEval(attrs["on"])
453				if "overlap" in attrs and bool(safeEval(attrs["overlap"])):
454					flag |= flagOverlapSimple
455				flags.append(flag)
456			flags = array.array("B", flags)
457			if not hasattr(self, "coordinates"):
458				self.coordinates = coordinates
459				self.flags = flags
460				self.endPtsOfContours = [len(coordinates)-1]
461			else:
462				self.coordinates.extend (coordinates)
463				self.flags.extend(flags)
464				self.endPtsOfContours.append(len(self.coordinates)-1)
465		elif name == "component":
466			if self.numberOfContours > 0:
467				raise ttLib.TTLibError("can't mix composites and contours in glyph")
468			self.numberOfContours = -1
469			if not hasattr(self, "components"):
470				self.components = []
471			component = GlyphComponent()
472			self.components.append(component)
473			component.fromXML(name, attrs, content, ttFont)
474		elif name == "instructions":
475			self.program = ttProgram.Program()
476			for element in content:
477				if not isinstance(element, tuple):
478					continue
479				name, attrs, content = element
480				self.program.fromXML(name, attrs, content, ttFont)
481
482	def getCompositeMaxpValues(self, glyfTable, maxComponentDepth=1):
483		assert self.isComposite()
484		nContours = 0
485		nPoints = 0
486		for compo in self.components:
487			baseGlyph = glyfTable[compo.glyphName]
488			if baseGlyph.numberOfContours == 0:
489				continue
490			elif baseGlyph.numberOfContours > 0:
491				nP, nC = baseGlyph.getMaxpValues()
492			else:
493				nP, nC, maxComponentDepth = baseGlyph.getCompositeMaxpValues(
494						glyfTable, maxComponentDepth + 1)
495			nPoints = nPoints + nP
496			nContours = nContours + nC
497		return CompositeMaxpValues(nPoints, nContours, maxComponentDepth)
498
499	def getMaxpValues(self):
500		assert self.numberOfContours > 0
501		return len(self.coordinates), len(self.endPtsOfContours)
502
503	def decompileComponents(self, data, glyfTable):
504		self.components = []
505		more = 1
506		haveInstructions = 0
507		while more:
508			component = GlyphComponent()
509			more, haveInstr, data = component.decompile(data, glyfTable)
510			haveInstructions = haveInstructions | haveInstr
511			self.components.append(component)
512		if haveInstructions:
513			numInstructions, = struct.unpack(">h", data[:2])
514			data = data[2:]
515			self.program = ttProgram.Program()
516			self.program.fromBytecode(data[:numInstructions])
517			data = data[numInstructions:]
518			if len(data) >= 4:
519				log.warning(
520					"too much glyph data at the end of composite glyph: %d excess bytes",
521					len(data))
522
523	def decompileCoordinates(self, data):
524		endPtsOfContours = array.array("h")
525		endPtsOfContours.fromstring(data[:2*self.numberOfContours])
526		if sys.byteorder != "big": endPtsOfContours.byteswap()
527		self.endPtsOfContours = endPtsOfContours.tolist()
528
529		data = data[2*self.numberOfContours:]
530
531		instructionLength, = struct.unpack(">h", data[:2])
532		data = data[2:]
533		self.program = ttProgram.Program()
534		self.program.fromBytecode(data[:instructionLength])
535		data = data[instructionLength:]
536		nCoordinates = self.endPtsOfContours[-1] + 1
537		flags, xCoordinates, yCoordinates = \
538				self.decompileCoordinatesRaw(nCoordinates, data)
539
540		# fill in repetitions and apply signs
541		self.coordinates = coordinates = GlyphCoordinates.zeros(nCoordinates)
542		xIndex = 0
543		yIndex = 0
544		for i in range(nCoordinates):
545			flag = flags[i]
546			# x coordinate
547			if flag & flagXShort:
548				if flag & flagXsame:
549					x = xCoordinates[xIndex]
550				else:
551					x = -xCoordinates[xIndex]
552				xIndex = xIndex + 1
553			elif flag & flagXsame:
554				x = 0
555			else:
556				x = xCoordinates[xIndex]
557				xIndex = xIndex + 1
558			# y coordinate
559			if flag & flagYShort:
560				if flag & flagYsame:
561					y = yCoordinates[yIndex]
562				else:
563					y = -yCoordinates[yIndex]
564				yIndex = yIndex + 1
565			elif flag & flagYsame:
566				y = 0
567			else:
568				y = yCoordinates[yIndex]
569				yIndex = yIndex + 1
570			coordinates[i] = (x, y)
571		assert xIndex == len(xCoordinates)
572		assert yIndex == len(yCoordinates)
573		coordinates.relativeToAbsolute()
574		# discard all flags except "keepFlags"
575		self.flags = array.array("B", (f & keepFlags for f in flags))
576
577	def decompileCoordinatesRaw(self, nCoordinates, data):
578		# unpack flags and prepare unpacking of coordinates
579		flags = array.array("B", [0] * nCoordinates)
580		# Warning: deep Python trickery going on. We use the struct module to unpack
581		# the coordinates. We build a format string based on the flags, so we can
582		# unpack the coordinates in one struct.unpack() call.
583		xFormat = ">" # big endian
584		yFormat = ">" # big endian
585		i = j = 0
586		while True:
587			flag = byteord(data[i])
588			i = i + 1
589			repeat = 1
590			if flag & flagRepeat:
591				repeat = byteord(data[i]) + 1
592				i = i + 1
593			for k in range(repeat):
594				if flag & flagXShort:
595					xFormat = xFormat + 'B'
596				elif not (flag & flagXsame):
597					xFormat = xFormat + 'h'
598				if flag & flagYShort:
599					yFormat = yFormat + 'B'
600				elif not (flag & flagYsame):
601					yFormat = yFormat + 'h'
602				flags[j] = flag
603				j = j + 1
604			if j >= nCoordinates:
605				break
606		assert j == nCoordinates, "bad glyph flags"
607		data = data[i:]
608		# unpack raw coordinates, krrrrrr-tching!
609		xDataLen = struct.calcsize(xFormat)
610		yDataLen = struct.calcsize(yFormat)
611		if len(data) - (xDataLen + yDataLen) >= 4:
612			log.warning(
613				"too much glyph data: %d excess bytes", len(data) - (xDataLen + yDataLen))
614		xCoordinates = struct.unpack(xFormat, data[:xDataLen])
615		yCoordinates = struct.unpack(yFormat, data[xDataLen:xDataLen+yDataLen])
616		return flags, xCoordinates, yCoordinates
617
618	def compileComponents(self, glyfTable):
619		data = b""
620		lastcomponent = len(self.components) - 1
621		more = 1
622		haveInstructions = 0
623		for i in range(len(self.components)):
624			if i == lastcomponent:
625				haveInstructions = hasattr(self, "program")
626				more = 0
627			compo = self.components[i]
628			data = data + compo.compile(more, haveInstructions, glyfTable)
629		if haveInstructions:
630			instructions = self.program.getBytecode()
631			data = data + struct.pack(">h", len(instructions)) + instructions
632		return data
633
634	def compileCoordinates(self):
635		assert len(self.coordinates) == len(self.flags)
636		data = []
637		endPtsOfContours = array.array("h", self.endPtsOfContours)
638		if sys.byteorder != "big": endPtsOfContours.byteswap()
639		data.append(endPtsOfContours.tostring())
640		instructions = self.program.getBytecode()
641		data.append(struct.pack(">h", len(instructions)))
642		data.append(instructions)
643
644		deltas = self.coordinates.copy()
645		if deltas.isFloat():
646			# Warn?
647			deltas.toInt()
648		deltas.absoluteToRelative()
649
650		# TODO(behdad): Add a configuration option for this?
651		deltas = self.compileDeltasGreedy(self.flags, deltas)
652		#deltas = self.compileDeltasOptimal(self.flags, deltas)
653
654		data.extend(deltas)
655		return bytesjoin(data)
656
657	def compileDeltasGreedy(self, flags, deltas):
658		# Implements greedy algorithm for packing coordinate deltas:
659		# uses shortest representation one coordinate at a time.
660		compressedflags = []
661		xPoints = []
662		yPoints = []
663		lastflag = None
664		repeat = 0
665		for flag,(x,y) in zip(flags, deltas):
666			# Oh, the horrors of TrueType
667			# do x
668			if x == 0:
669				flag = flag | flagXsame
670			elif -255 <= x <= 255:
671				flag = flag | flagXShort
672				if x > 0:
673					flag = flag | flagXsame
674				else:
675					x = -x
676				xPoints.append(bytechr(x))
677			else:
678				xPoints.append(struct.pack(">h", x))
679			# do y
680			if y == 0:
681				flag = flag | flagYsame
682			elif -255 <= y <= 255:
683				flag = flag | flagYShort
684				if y > 0:
685					flag = flag | flagYsame
686				else:
687					y = -y
688				yPoints.append(bytechr(y))
689			else:
690				yPoints.append(struct.pack(">h", y))
691			# handle repeating flags
692			if flag == lastflag and repeat != 255:
693				repeat = repeat + 1
694				if repeat == 1:
695					compressedflags.append(flag)
696				else:
697					compressedflags[-2] = flag | flagRepeat
698					compressedflags[-1] = repeat
699			else:
700				repeat = 0
701				compressedflags.append(flag)
702			lastflag = flag
703		compressedFlags = array.array("B", compressedflags).tostring()
704		compressedXs = bytesjoin(xPoints)
705		compressedYs = bytesjoin(yPoints)
706		return (compressedFlags, compressedXs, compressedYs)
707
708	def compileDeltasOptimal(self, flags, deltas):
709		# Implements optimal, dynaic-programming, algorithm for packing coordinate
710		# deltas.  The savings are negligible :(.
711		candidates = []
712		bestTuple = None
713		bestCost = 0
714		repeat = 0
715		for flag,(x,y) in zip(flags, deltas):
716			# Oh, the horrors of TrueType
717			flag, coordBytes = flagBest(x, y, flag)
718			bestCost += 1 + coordBytes
719			newCandidates = [(bestCost, bestTuple, flag, coordBytes),
720							(bestCost+1, bestTuple, (flag|flagRepeat), coordBytes)]
721			for lastCost,lastTuple,lastFlag,coordBytes in candidates:
722				if lastCost + coordBytes <= bestCost + 1 and (lastFlag & flagRepeat) and (lastFlag < 0xff00) and flagSupports(lastFlag, flag):
723					if (lastFlag & 0xFF) == (flag|flagRepeat) and lastCost == bestCost + 1:
724						continue
725					newCandidates.append((lastCost + coordBytes, lastTuple, lastFlag+256, coordBytes))
726			candidates = newCandidates
727			bestTuple = min(candidates, key=lambda t:t[0])
728			bestCost = bestTuple[0]
729
730		flags = []
731		while bestTuple:
732			cost, bestTuple, flag, coordBytes = bestTuple
733			flags.append(flag)
734		flags.reverse()
735
736		compressedFlags = array.array("B")
737		compressedXs = array.array("B")
738		compressedYs = array.array("B")
739		coords = iter(deltas)
740		ff = []
741		for flag in flags:
742			repeatCount, flag = flag >> 8, flag & 0xFF
743			compressedFlags.append(flag)
744			if flag & flagRepeat:
745				assert(repeatCount > 0)
746				compressedFlags.append(repeatCount)
747			else:
748				assert(repeatCount == 0)
749			for i in range(1 + repeatCount):
750				x,y = next(coords)
751				flagEncodeCoords(flag, x, y, compressedXs, compressedYs)
752				ff.append(flag)
753		try:
754			next(coords)
755			raise Exception("internal error")
756		except StopIteration:
757			pass
758		compressedFlags = compressedFlags.tostring()
759		compressedXs = compressedXs.tostring()
760		compressedYs = compressedYs.tostring()
761
762		return (compressedFlags, compressedXs, compressedYs)
763
764	def recalcBounds(self, glyfTable):
765		coords, endPts, flags = self.getCoordinates(glyfTable)
766		if len(coords) > 0:
767			if 0:
768				# This branch calculates exact glyph outline bounds
769				# analytically, handling cases without on-curve
770				# extremas, etc.  However, the glyf table header
771				# simply says that the bounds should be min/max x/y
772				# "for coordinate data", so I suppose that means no
773				# fancy thing here, just get extremas of all coord
774				# points (on and off).  As such, this branch is
775				# disabled.
776
777				# Collect on-curve points
778				onCurveCoords = [coords[j] for j in range(len(coords))
779								if flags[j] & flagOnCurve]
780				# Add implicit on-curve points
781				start = 0
782				for end in endPts:
783					last = end
784					for j in range(start, end + 1):
785						if not ((flags[j] | flags[last]) & flagOnCurve):
786							x = (coords[last][0] + coords[j][0]) / 2
787							y = (coords[last][1] + coords[j][1]) / 2
788							onCurveCoords.append((x,y))
789						last = j
790					start = end + 1
791				# Add bounds for curves without an explicit extrema
792				start = 0
793				for end in endPts:
794					last = end
795					for j in range(start, end + 1):
796						if not (flags[j] & flagOnCurve):
797							next = j + 1 if j < end else start
798							bbox = calcBounds([coords[last], coords[next]])
799							if not pointInRect(coords[j], bbox):
800								# Ouch!
801								log.warning("Outline has curve with implicit extrema.")
802								# Ouch!  Find analytical curve bounds.
803								pthis = coords[j]
804								plast = coords[last]
805								if not (flags[last] & flagOnCurve):
806									plast = ((pthis[0]+plast[0])/2, (pthis[1]+plast[1])/2)
807								pnext = coords[next]
808								if not (flags[next] & flagOnCurve):
809									pnext = ((pthis[0]+pnext[0])/2, (pthis[1]+pnext[1])/2)
810								bbox = calcQuadraticBounds(plast, pthis, pnext)
811								onCurveCoords.append((bbox[0],bbox[1]))
812								onCurveCoords.append((bbox[2],bbox[3]))
813						last = j
814					start = end + 1
815
816				self.xMin, self.yMin, self.xMax, self.yMax = calcIntBounds(onCurveCoords)
817			else:
818				self.xMin, self.yMin, self.xMax, self.yMax = calcIntBounds(coords)
819		else:
820			self.xMin, self.yMin, self.xMax, self.yMax = (0, 0, 0, 0)
821
822	def isComposite(self):
823		"""Can be called on compact or expanded glyph."""
824		if hasattr(self, "data") and self.data:
825			return struct.unpack(">h", self.data[:2])[0] == -1
826		else:
827			return self.numberOfContours == -1
828
829	def __getitem__(self, componentIndex):
830		if not self.isComposite():
831			raise ttLib.TTLibError("can't use glyph as sequence")
832		return self.components[componentIndex]
833
834	def getCoordinates(self, glyfTable):
835		if self.numberOfContours > 0:
836			return self.coordinates, self.endPtsOfContours, self.flags
837		elif self.isComposite():
838			# it's a composite
839			allCoords = GlyphCoordinates()
840			allFlags = array.array("B")
841			allEndPts = []
842			for compo in self.components:
843				g = glyfTable[compo.glyphName]
844				try:
845					coordinates, endPts, flags = g.getCoordinates(glyfTable)
846				except RecursionError:
847					raise ttLib.TTLibError("glyph '%s' contains a recursive component reference" % compo.glyphName)
848				if hasattr(compo, "firstPt"):
849					# move according to two reference points
850					x1,y1 = allCoords[compo.firstPt]
851					x2,y2 = coordinates[compo.secondPt]
852					move = x1-x2, y1-y2
853				else:
854					move = compo.x, compo.y
855
856				coordinates = GlyphCoordinates(coordinates)
857				if not hasattr(compo, "transform"):
858					coordinates.translate(move)
859				else:
860					apple_way = compo.flags & SCALED_COMPONENT_OFFSET
861					ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET
862					assert not (apple_way and ms_way)
863					if not (apple_way or ms_way):
864						scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT  # see top of this file
865					else:
866						scale_component_offset = apple_way
867					if scale_component_offset:
868						# the Apple way: first move, then scale (ie. scale the component offset)
869						coordinates.translate(move)
870						coordinates.transform(compo.transform)
871					else:
872						# the MS way: first scale, then move
873						coordinates.transform(compo.transform)
874						coordinates.translate(move)
875				offset = len(allCoords)
876				allEndPts.extend(e + offset for e in endPts)
877				allCoords.extend(coordinates)
878				allFlags.extend(flags)
879			return allCoords, allEndPts, allFlags
880		else:
881			return GlyphCoordinates(), [], array.array("B")
882
883	def getComponentNames(self, glyfTable):
884		if not hasattr(self, "data"):
885			if self.isComposite():
886				return [c.glyphName for c in self.components]
887			else:
888				return []
889
890		# Extract components without expanding glyph
891
892		if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
893			return []  # Not composite
894
895		data = self.data
896		i = 10
897		components = []
898		more = 1
899		while more:
900			flags, glyphID = struct.unpack(">HH", data[i:i+4])
901			i += 4
902			flags = int(flags)
903			components.append(glyfTable.getGlyphName(int(glyphID)))
904
905			if flags & ARG_1_AND_2_ARE_WORDS: i += 4
906			else: i += 2
907			if flags & WE_HAVE_A_SCALE: i += 2
908			elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
909			elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
910			more = flags & MORE_COMPONENTS
911
912		return components
913
914	def trim(self, remove_hinting=False):
915		""" Remove padding and, if requested, hinting, from a glyph.
916			This works on both expanded and compacted glyphs, without
917			expanding it."""
918		if not hasattr(self, "data"):
919			if remove_hinting:
920				self.program = ttProgram.Program()
921				self.program.fromBytecode([])
922			# No padding to trim.
923			return
924		if not self.data:
925			return
926		numContours = struct.unpack(">h", self.data[:2])[0]
927		data = array.array("B", self.data)
928		i = 10
929		if numContours >= 0:
930			i += 2 * numContours # endPtsOfContours
931			nCoordinates = ((data[i-2] << 8) | data[i-1]) + 1
932			instructionLen = (data[i] << 8) | data[i+1]
933			if remove_hinting:
934				# Zero instruction length
935				data[i] = data [i+1] = 0
936				i += 2
937				if instructionLen:
938					# Splice it out
939					data = data[:i] + data[i+instructionLen:]
940				instructionLen = 0
941			else:
942				i += 2 + instructionLen
943
944			coordBytes = 0
945			j = 0
946			while True:
947				flag = data[i]
948				i = i + 1
949				repeat = 1
950				if flag & flagRepeat:
951					repeat = data[i] + 1
952					i = i + 1
953				xBytes = yBytes = 0
954				if flag & flagXShort:
955					xBytes = 1
956				elif not (flag & flagXsame):
957					xBytes = 2
958				if flag & flagYShort:
959					yBytes = 1
960				elif not (flag & flagYsame):
961					yBytes = 2
962				coordBytes += (xBytes + yBytes) * repeat
963				j += repeat
964				if j >= nCoordinates:
965					break
966			assert j == nCoordinates, "bad glyph flags"
967			i += coordBytes
968			# Remove padding
969			data = data[:i]
970		else:
971			more = 1
972			we_have_instructions = False
973			while more:
974				flags =(data[i] << 8) | data[i+1]
975				if remove_hinting:
976					flags &= ~WE_HAVE_INSTRUCTIONS
977				if flags & WE_HAVE_INSTRUCTIONS:
978					we_have_instructions = True
979				data[i+0] = flags >> 8
980				data[i+1] = flags & 0xFF
981				i += 4
982				flags = int(flags)
983
984				if flags & ARG_1_AND_2_ARE_WORDS: i += 4
985				else: i += 2
986				if flags & WE_HAVE_A_SCALE: i += 2
987				elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
988				elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
989				more = flags & MORE_COMPONENTS
990			if we_have_instructions:
991				instructionLen = (data[i] << 8) | data[i+1]
992				i += 2 + instructionLen
993			# Remove padding
994			data = data[:i]
995
996		self.data = data.tostring()
997
998	def removeHinting(self):
999		self.trim (remove_hinting=True)
1000
1001	def draw(self, pen, glyfTable, offset=0):
1002
1003		if self.isComposite():
1004			for component in self.components:
1005				glyphName, transform = component.getComponentInfo()
1006				pen.addComponent(glyphName, transform)
1007			return
1008
1009		coordinates, endPts, flags = self.getCoordinates(glyfTable)
1010		if offset:
1011			coordinates = coordinates.copy()
1012			coordinates.translate((offset, 0))
1013		start = 0
1014		for end in endPts:
1015			end = end + 1
1016			contour = coordinates[start:end]
1017			cFlags = flags[start:end]
1018			start = end
1019			if 1 not in cFlags:
1020				# There is not a single on-curve point on the curve,
1021				# use pen.qCurveTo's special case by specifying None
1022				# as the on-curve point.
1023				contour.append(None)
1024				pen.qCurveTo(*contour)
1025			else:
1026				# Shuffle the points so that contour the is guaranteed
1027				# to *end* in an on-curve point, which we'll use for
1028				# the moveTo.
1029				firstOnCurve = cFlags.index(1) + 1
1030				contour = contour[firstOnCurve:] + contour[:firstOnCurve]
1031				cFlags = cFlags[firstOnCurve:] + cFlags[:firstOnCurve]
1032				pen.moveTo(contour[-1])
1033				while contour:
1034					nextOnCurve = cFlags.index(1) + 1
1035					if nextOnCurve == 1:
1036						pen.lineTo(contour[0])
1037					else:
1038						pen.qCurveTo(*contour[:nextOnCurve])
1039					contour = contour[nextOnCurve:]
1040					cFlags = cFlags[nextOnCurve:]
1041			pen.closePath()
1042
1043	def drawPoints(self, pen, glyfTable, offset=0):
1044		"""Draw the glyph using the supplied pointPen. Opposed to Glyph.draw(),
1045		this will not change the point indices.
1046		"""
1047
1048		if self.isComposite():
1049			for component in self.components:
1050				glyphName, transform = component.getComponentInfo()
1051				pen.addComponent(glyphName, transform)
1052			return
1053
1054		coordinates, endPts, flags = self.getCoordinates(glyfTable)
1055		if offset:
1056			coordinates = coordinates.copy()
1057			coordinates.translate((offset, 0))
1058		start = 0
1059		for end in endPts:
1060			end = end + 1
1061			contour = coordinates[start:end]
1062			cFlags = flags[start:end]
1063			start = end
1064			pen.beginPath()
1065			# Start with the appropriate segment type based on the final segment
1066			segmentType = "line" if cFlags[-1] == 1 else "qcurve"
1067			for i, pt in enumerate(contour):
1068				if cFlags[i] == 1:
1069					pen.addPoint(pt, segmentType=segmentType)
1070					segmentType = "line"
1071				else:
1072					pen.addPoint(pt)
1073					segmentType = "qcurve"
1074			pen.endPath()
1075
1076	def __eq__(self, other):
1077		if type(self) != type(other):
1078			return NotImplemented
1079		return self.__dict__ == other.__dict__
1080
1081	def __ne__(self, other):
1082		result = self.__eq__(other)
1083		return result if result is NotImplemented else not result
1084
1085class GlyphComponent(object):
1086
1087	def __init__(self):
1088		pass
1089
1090	def getComponentInfo(self):
1091		"""Return the base glyph name and a transform."""
1092		# XXX Ignoring self.firstPt & self.lastpt for now: I need to implement
1093		# something equivalent in fontTools.objects.glyph (I'd rather not
1094		# convert it to an absolute offset, since it is valuable information).
1095		# This method will now raise "AttributeError: x" on glyphs that use
1096		# this TT feature.
1097		if hasattr(self, "transform"):
1098			[[xx, xy], [yx, yy]] = self.transform
1099			trans = (xx, xy, yx, yy, self.x, self.y)
1100		else:
1101			trans = (1, 0, 0, 1, self.x, self.y)
1102		return self.glyphName, trans
1103
1104	def decompile(self, data, glyfTable):
1105		flags, glyphID = struct.unpack(">HH", data[:4])
1106		self.flags = int(flags)
1107		glyphID = int(glyphID)
1108		self.glyphName = glyfTable.getGlyphName(int(glyphID))
1109		data = data[4:]
1110
1111		if self.flags & ARG_1_AND_2_ARE_WORDS:
1112			if self.flags & ARGS_ARE_XY_VALUES:
1113				self.x, self.y = struct.unpack(">hh", data[:4])
1114			else:
1115				x, y = struct.unpack(">HH", data[:4])
1116				self.firstPt, self.secondPt = int(x), int(y)
1117			data = data[4:]
1118		else:
1119			if self.flags & ARGS_ARE_XY_VALUES:
1120				self.x, self.y = struct.unpack(">bb", data[:2])
1121			else:
1122				x, y = struct.unpack(">BB", data[:2])
1123				self.firstPt, self.secondPt = int(x), int(y)
1124			data = data[2:]
1125
1126		if self.flags & WE_HAVE_A_SCALE:
1127			scale, = struct.unpack(">h", data[:2])
1128			self.transform = [[fi2fl(scale,14), 0], [0, fi2fl(scale,14)]]  # fixed 2.14
1129			data = data[2:]
1130		elif self.flags & WE_HAVE_AN_X_AND_Y_SCALE:
1131			xscale, yscale = struct.unpack(">hh", data[:4])
1132			self.transform = [[fi2fl(xscale,14), 0], [0, fi2fl(yscale,14)]]  # fixed 2.14
1133			data = data[4:]
1134		elif self.flags & WE_HAVE_A_TWO_BY_TWO:
1135			(xscale, scale01,
1136					scale10, yscale) = struct.unpack(">hhhh", data[:8])
1137			self.transform = [[fi2fl(xscale,14), fi2fl(scale01,14)],
1138							[fi2fl(scale10,14), fi2fl(yscale,14)]] # fixed 2.14
1139			data = data[8:]
1140		more = self.flags & MORE_COMPONENTS
1141		haveInstructions = self.flags & WE_HAVE_INSTRUCTIONS
1142		self.flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
1143				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
1144				NON_OVERLAPPING | OVERLAP_COMPOUND)
1145		return more, haveInstructions, data
1146
1147	def compile(self, more, haveInstructions, glyfTable):
1148		data = b""
1149
1150		# reset all flags we will calculate ourselves
1151		flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
1152				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
1153				NON_OVERLAPPING | OVERLAP_COMPOUND)
1154		if more:
1155			flags = flags | MORE_COMPONENTS
1156		if haveInstructions:
1157			flags = flags | WE_HAVE_INSTRUCTIONS
1158
1159		if hasattr(self, "firstPt"):
1160			if (0 <= self.firstPt <= 255) and (0 <= self.secondPt <= 255):
1161				data = data + struct.pack(">BB", self.firstPt, self.secondPt)
1162			else:
1163				data = data + struct.pack(">HH", self.firstPt, self.secondPt)
1164				flags = flags | ARG_1_AND_2_ARE_WORDS
1165		else:
1166			x = otRound(self.x)
1167			y = otRound(self.y)
1168			flags = flags | ARGS_ARE_XY_VALUES
1169			if (-128 <= x <= 127) and (-128 <= y <= 127):
1170				data = data + struct.pack(">bb", x, y)
1171			else:
1172				data = data + struct.pack(">hh", x, y)
1173				flags = flags | ARG_1_AND_2_ARE_WORDS
1174
1175		if hasattr(self, "transform"):
1176			transform = [[fl2fi(x,14) for x in row] for row in self.transform]
1177			if transform[0][1] or transform[1][0]:
1178				flags = flags | WE_HAVE_A_TWO_BY_TWO
1179				data = data + struct.pack(">hhhh",
1180						transform[0][0], transform[0][1],
1181						transform[1][0], transform[1][1])
1182			elif transform[0][0] != transform[1][1]:
1183				flags = flags | WE_HAVE_AN_X_AND_Y_SCALE
1184				data = data + struct.pack(">hh",
1185						transform[0][0], transform[1][1])
1186			else:
1187				flags = flags | WE_HAVE_A_SCALE
1188				data = data + struct.pack(">h",
1189						transform[0][0])
1190
1191		glyphID = glyfTable.getGlyphID(self.glyphName)
1192		return struct.pack(">HH", flags, glyphID) + data
1193
1194	def toXML(self, writer, ttFont):
1195		attrs = [("glyphName", self.glyphName)]
1196		if not hasattr(self, "firstPt"):
1197			attrs = attrs + [("x", self.x), ("y", self.y)]
1198		else:
1199			attrs = attrs + [("firstPt", self.firstPt), ("secondPt", self.secondPt)]
1200
1201		if hasattr(self, "transform"):
1202			transform = self.transform
1203			if transform[0][1] or transform[1][0]:
1204				attrs = attrs + [
1205						("scalex", transform[0][0]), ("scale01", transform[0][1]),
1206						("scale10", transform[1][0]), ("scaley", transform[1][1]),
1207						]
1208			elif transform[0][0] != transform[1][1]:
1209				attrs = attrs + [
1210						("scalex", transform[0][0]), ("scaley", transform[1][1]),
1211						]
1212			else:
1213				attrs = attrs + [("scale", transform[0][0])]
1214		attrs = attrs + [("flags", hex(self.flags))]
1215		writer.simpletag("component", attrs)
1216		writer.newline()
1217
1218	def fromXML(self, name, attrs, content, ttFont):
1219		self.glyphName = attrs["glyphName"]
1220		if "firstPt" in attrs:
1221			self.firstPt = safeEval(attrs["firstPt"])
1222			self.secondPt = safeEval(attrs["secondPt"])
1223		else:
1224			self.x = safeEval(attrs["x"])
1225			self.y = safeEval(attrs["y"])
1226		if "scale01" in attrs:
1227			scalex = safeEval(attrs["scalex"])
1228			scale01 = safeEval(attrs["scale01"])
1229			scale10 = safeEval(attrs["scale10"])
1230			scaley = safeEval(attrs["scaley"])
1231			self.transform = [[scalex, scale01], [scale10, scaley]]
1232		elif "scalex" in attrs:
1233			scalex = safeEval(attrs["scalex"])
1234			scaley = safeEval(attrs["scaley"])
1235			self.transform = [[scalex, 0], [0, scaley]]
1236		elif "scale" in attrs:
1237			scale = safeEval(attrs["scale"])
1238			self.transform = [[scale, 0], [0, scale]]
1239		self.flags = safeEval(attrs["flags"])
1240
1241	def __eq__(self, other):
1242		if type(self) != type(other):
1243			return NotImplemented
1244		return self.__dict__ == other.__dict__
1245
1246	def __ne__(self, other):
1247		result = self.__eq__(other)
1248		return result if result is NotImplemented else not result
1249
1250class GlyphCoordinates(object):
1251
1252	def __init__(self, iterable=[], typecode="h"):
1253		self._a = array.array(typecode)
1254		self.extend(iterable)
1255
1256	@property
1257	def array(self):
1258		return self._a
1259
1260	def isFloat(self):
1261		return self._a.typecode == 'd'
1262
1263	def _ensureFloat(self):
1264		if self.isFloat():
1265			return
1266		# The conversion to list() is to work around Jython bug
1267		self._a = array.array("d", list(self._a))
1268
1269	def _checkFloat(self, p):
1270		if self.isFloat():
1271			return p
1272		if any(v > 0x7FFF or v < -0x8000 for v in p):
1273			self._ensureFloat()
1274			return p
1275		if any(isinstance(v, float) for v in p):
1276			p = [int(v) if int(v) == v else v for v in p]
1277			if any(isinstance(v, float) for v in p):
1278				self._ensureFloat()
1279		return p
1280
1281	@staticmethod
1282	def zeros(count):
1283		return GlyphCoordinates([(0,0)] * count)
1284
1285	def copy(self):
1286		c = GlyphCoordinates(typecode=self._a.typecode)
1287		c._a.extend(self._a)
1288		return c
1289
1290	def __len__(self):
1291		return len(self._a) // 2
1292
1293	def __getitem__(self, k):
1294		if isinstance(k, slice):
1295			indices = range(*k.indices(len(self)))
1296			return [self[i] for i in indices]
1297		return self._a[2*k],self._a[2*k+1]
1298
1299	def __setitem__(self, k, v):
1300		if isinstance(k, slice):
1301			indices = range(*k.indices(len(self)))
1302			# XXX This only works if len(v) == len(indices)
1303			for j,i in enumerate(indices):
1304				self[i] = v[j]
1305			return
1306		v = self._checkFloat(v)
1307		self._a[2*k],self._a[2*k+1] = v
1308
1309	def __delitem__(self, i):
1310		i = (2*i) % len(self._a)
1311		del self._a[i]
1312		del self._a[i]
1313
1314	def __repr__(self):
1315		return 'GlyphCoordinates(['+','.join(str(c) for c in self)+'])'
1316
1317	def append(self, p):
1318		p = self._checkFloat(p)
1319		self._a.extend(tuple(p))
1320
1321	def extend(self, iterable):
1322		for p in iterable:
1323			p = self._checkFloat(p)
1324			self._a.extend(p)
1325
1326	def toInt(self):
1327		if not self.isFloat():
1328			return
1329		a = array.array("h")
1330		for n in self._a:
1331			a.append(otRound(n))
1332		self._a = a
1333
1334	def relativeToAbsolute(self):
1335		a = self._a
1336		x,y = 0,0
1337		for i in range(len(a) // 2):
1338			x = a[2*i  ] + x
1339			y = a[2*i+1] + y
1340			self[i] = (x, y)
1341
1342	def absoluteToRelative(self):
1343		a = self._a
1344		x,y = 0,0
1345		for i in range(len(a) // 2):
1346			dx = a[2*i  ] - x
1347			dy = a[2*i+1] - y
1348			x = a[2*i  ]
1349			y = a[2*i+1]
1350			self[i] = (dx, dy)
1351
1352	def translate(self, p):
1353		"""
1354		>>> GlyphCoordinates([(1,2)]).translate((.5,0))
1355		"""
1356		(x,y) = self._checkFloat(p)
1357		a = self._a
1358		for i in range(len(a) // 2):
1359			self[i] = (a[2*i] + x, a[2*i+1] + y)
1360
1361	def scale(self, p):
1362		"""
1363		>>> GlyphCoordinates([(1,2)]).scale((.5,0))
1364		"""
1365		(x,y) = self._checkFloat(p)
1366		a = self._a
1367		for i in range(len(a) // 2):
1368			self[i] = (a[2*i] * x, a[2*i+1] * y)
1369
1370	def transform(self, t):
1371		"""
1372		>>> GlyphCoordinates([(1,2)]).transform(((.5,0),(.2,.5)))
1373		"""
1374		a = self._a
1375		for i in range(len(a) // 2):
1376			x = a[2*i  ]
1377			y = a[2*i+1]
1378			px = x * t[0][0] + y * t[1][0]
1379			py = x * t[0][1] + y * t[1][1]
1380			self[i] = (px, py)
1381
1382	def __eq__(self, other):
1383		"""
1384		>>> g = GlyphCoordinates([(1,2)])
1385		>>> g2 = GlyphCoordinates([(1.0,2)])
1386		>>> g3 = GlyphCoordinates([(1.5,2)])
1387		>>> g == g2
1388		True
1389		>>> g == g3
1390		False
1391		>>> g2 == g3
1392		False
1393		"""
1394		if type(self) != type(other):
1395			return NotImplemented
1396		return self._a == other._a
1397
1398	def __ne__(self, other):
1399		"""
1400		>>> g = GlyphCoordinates([(1,2)])
1401		>>> g2 = GlyphCoordinates([(1.0,2)])
1402		>>> g3 = GlyphCoordinates([(1.5,2)])
1403		>>> g != g2
1404		False
1405		>>> g != g3
1406		True
1407		>>> g2 != g3
1408		True
1409		"""
1410		result = self.__eq__(other)
1411		return result if result is NotImplemented else not result
1412
1413	# Math operations
1414
1415	def __pos__(self):
1416		"""
1417		>>> g = GlyphCoordinates([(1,2)])
1418		>>> g
1419		GlyphCoordinates([(1, 2)])
1420		>>> g2 = +g
1421		>>> g2
1422		GlyphCoordinates([(1, 2)])
1423		>>> g2.translate((1,0))
1424		>>> g2
1425		GlyphCoordinates([(2, 2)])
1426		>>> g
1427		GlyphCoordinates([(1, 2)])
1428		"""
1429		return self.copy()
1430	def __neg__(self):
1431		"""
1432		>>> g = GlyphCoordinates([(1,2)])
1433		>>> g
1434		GlyphCoordinates([(1, 2)])
1435		>>> g2 = -g
1436		>>> g2
1437		GlyphCoordinates([(-1, -2)])
1438		>>> g
1439		GlyphCoordinates([(1, 2)])
1440		"""
1441		r = self.copy()
1442		a = r._a
1443		for i in range(len(a)):
1444			a[i] = -a[i]
1445		return r
1446	def __round__(self):
1447		"""
1448		Note: This is Python 3 only.  Python 2 does not call __round__.
1449		As such, we cannot test this method either. :(
1450		"""
1451		r = self.copy()
1452		r.toInt()
1453		return r
1454
1455	def __add__(self, other): return self.copy().__iadd__(other)
1456	def __sub__(self, other): return self.copy().__isub__(other)
1457	def __mul__(self, other): return self.copy().__imul__(other)
1458	def __truediv__(self, other): return self.copy().__itruediv__(other)
1459
1460	__radd__ = __add__
1461	__rmul__ = __mul__
1462	def __rsub__(self, other): return other + (-self)
1463
1464	def __iadd__(self, other):
1465		"""
1466		>>> g = GlyphCoordinates([(1,2)])
1467		>>> g += (.5,0)
1468		>>> g
1469		GlyphCoordinates([(1.5, 2.0)])
1470		>>> g2 = GlyphCoordinates([(3,4)])
1471		>>> g += g2
1472		>>> g
1473		GlyphCoordinates([(4.5, 6.0)])
1474		"""
1475		if isinstance(other, tuple):
1476			assert len(other) ==  2
1477			self.translate(other)
1478			return self
1479		if isinstance(other, GlyphCoordinates):
1480			if other.isFloat(): self._ensureFloat()
1481			other = other._a
1482			a = self._a
1483			assert len(a) == len(other)
1484			for i in range(len(a) // 2):
1485				self[i] = (a[2*i] + other[2*i], a[2*i+1] + other[2*i+1])
1486			return self
1487		return NotImplemented
1488
1489	def __isub__(self, other):
1490		"""
1491		>>> g = GlyphCoordinates([(1,2)])
1492		>>> g -= (.5,0)
1493		>>> g
1494		GlyphCoordinates([(0.5, 2.0)])
1495		>>> g2 = GlyphCoordinates([(3,4)])
1496		>>> g -= g2
1497		>>> g
1498		GlyphCoordinates([(-2.5, -2.0)])
1499		"""
1500		if isinstance(other, tuple):
1501			assert len(other) ==  2
1502			self.translate((-other[0],-other[1]))
1503			return self
1504		if isinstance(other, GlyphCoordinates):
1505			if other.isFloat(): self._ensureFloat()
1506			other = other._a
1507			a = self._a
1508			assert len(a) == len(other)
1509			for i in range(len(a) // 2):
1510				self[i] = (a[2*i] - other[2*i], a[2*i+1] - other[2*i+1])
1511			return self
1512		return NotImplemented
1513
1514	def __imul__(self, other):
1515		"""
1516		>>> g = GlyphCoordinates([(1,2)])
1517		>>> g *= (2,.5)
1518		>>> g *= 2
1519		>>> g
1520		GlyphCoordinates([(4.0, 2.0)])
1521		>>> g = GlyphCoordinates([(1,2)])
1522		>>> g *= 2
1523		>>> g
1524		GlyphCoordinates([(2, 4)])
1525		"""
1526		if isinstance(other, Number):
1527			other = (other, other)
1528		if isinstance(other, tuple):
1529			if other == (1,1):
1530				return self
1531			assert len(other) ==  2
1532			self.scale(other)
1533			return self
1534		return NotImplemented
1535
1536	def __itruediv__(self, other):
1537		"""
1538		>>> g = GlyphCoordinates([(1,3)])
1539		>>> g /= (.5,1.5)
1540		>>> g /= 2
1541		>>> g
1542		GlyphCoordinates([(1.0, 1.0)])
1543		"""
1544		if isinstance(other, Number):
1545			other = (other, other)
1546		if isinstance(other, tuple):
1547			if other == (1,1):
1548				return self
1549			assert len(other) ==  2
1550			self.scale((1./other[0],1./other[1]))
1551			return self
1552		return NotImplemented
1553
1554	def __bool__(self):
1555		"""
1556		>>> g = GlyphCoordinates([])
1557		>>> bool(g)
1558		False
1559		>>> g = GlyphCoordinates([(0,0), (0.,0)])
1560		>>> bool(g)
1561		True
1562		>>> g = GlyphCoordinates([(0,0), (1,0)])
1563		>>> bool(g)
1564		True
1565		>>> g = GlyphCoordinates([(0,.5), (0,0)])
1566		>>> bool(g)
1567		True
1568		"""
1569		return bool(self._a)
1570
1571	__nonzero__ = __bool__
1572
1573
1574def reprflag(flag):
1575	bin = ""
1576	if isinstance(flag, str):
1577		flag = byteord(flag)
1578	while flag:
1579		if flag & 0x01:
1580			bin = "1" + bin
1581		else:
1582			bin = "0" + bin
1583		flag = flag >> 1
1584	bin = (14 - len(bin)) * "0" + bin
1585	return bin
1586
1587
1588if __name__ == "__main__":
1589	import doctest, sys
1590	sys.exit(doctest.testmod().failed)
1591