_g_l_y_f.py revision 1ebda677eb6061e809c422fc6d3b483f965a8281
1"""_g_l_y_f.py -- Converter classes for the 'glyf' table."""
2
3
4#
5# The Apple and MS rasterizers behave differently for
6# scaled composite components: one does scale first and then translate
7# and the other does it vice versa. MS defined some flags to indicate
8# the difference, but it seems nobody actually _sets_ those flags.
9#
10# Funny thing: Apple seems to _only_ do their thing in the
11# WE_HAVE_A_SCALE (eg. Chicago) case, and not when it's WE_HAVE_AN_X_AND_Y_SCALE
12# (eg. Charcoal)...
13#
14SCALE_COMPONENT_OFFSET_DEFAULT = 0   # 0 == MS, 1 == Apple
15
16
17import sys
18import struct, sstruct
19import DefaultTable
20from fontTools import ttLib
21from fontTools.misc.textTools import safeEval, readHex
22import ttProgram
23import array
24import numpy
25from types import StringType, TupleType
26
27
28class table__g_l_y_f(DefaultTable.DefaultTable):
29
30	def decompile(self, data, ttFont):
31		loca = ttFont['loca']
32		last = int(loca[0])
33		self.glyphs = {}
34		self.glyphOrder = glyphOrder = ttFont.getGlyphOrder()
35		for i in range(0, len(loca)-1):
36			glyphName = glyphOrder[i]
37			next = int(loca[i+1])
38			glyphdata = data[last:next]
39			if len(glyphdata) <> (next - last):
40				raise ttLib.TTLibError, "not enough 'glyf' table data"
41			glyph = Glyph(glyphdata)
42			self.glyphs[glyphName] = glyph
43			last = next
44		# this should become a warning:
45		#if len(data) > next:
46		#	raise ttLib.TTLibError, "too much 'glyf' table data"
47
48	def compile(self, ttFont):
49		if not hasattr(self, "glyphOrder"):
50			self.glyphOrder = ttFont.getGlyphOrder()
51		import string
52		locations = []
53		currentLocation = 0
54		dataList = []
55		recalcBBoxes = ttFont.recalcBBoxes
56		for glyphName in self.glyphOrder:
57			glyph = self.glyphs[glyphName]
58			glyphData = glyph.compile(self, recalcBBoxes)
59			locations.append(currentLocation)
60			currentLocation = currentLocation + len(glyphData)
61			dataList.append(glyphData)
62		locations.append(currentLocation)
63		data = string.join(dataList, "")
64		ttFont['loca'].set(locations)
65		ttFont['maxp'].numGlyphs = len(self.glyphs)
66		return data
67
68	def toXML(self, writer, ttFont, progress=None):
69		writer.newline()
70		glyphNames = ttFont.getGlyphNames()
71		writer.comment("The xMin, yMin, xMax and yMax values\nwill be recalculated by the compiler.")
72		writer.newline()
73		writer.newline()
74		counter = 0
75		progressStep = 10
76		numGlyphs = len(glyphNames)
77		for glyphName in glyphNames:
78			if not counter % progressStep and progress is not None:
79				progress.setLabel("Dumping 'glyf' table... (%s)" % glyphName)
80				progress.increment(progressStep / float(numGlyphs))
81			counter = counter + 1
82			glyph = self[glyphName]
83			if glyph.numberOfContours:
84				writer.begintag('TTGlyph', [
85						("name", glyphName),
86						("xMin", glyph.xMin),
87						("yMin", glyph.yMin),
88						("xMax", glyph.xMax),
89						("yMax", glyph.yMax),
90						])
91				writer.newline()
92				glyph.toXML(writer, ttFont)
93				writer.endtag('TTGlyph')
94				writer.newline()
95			else:
96				writer.simpletag('TTGlyph', name=glyphName)
97				writer.comment("contains no outline data")
98				writer.newline()
99			writer.newline()
100
101	def fromXML(self, (name, attrs, content), ttFont):
102		if name <> "TTGlyph":
103			return
104		if not hasattr(self, "glyphs"):
105			self.glyphs = {}
106		if not hasattr(self, "glyphOrder"):
107			self.glyphOrder = ttFont.getGlyphOrder()
108		glyphName = attrs["name"]
109		if ttFont.verbose:
110			ttLib.debugmsg("unpacking glyph '%s'" % glyphName)
111		glyph = Glyph()
112		for attr in ['xMin', 'yMin', 'xMax', 'yMax']:
113			setattr(glyph, attr, safeEval(attrs.get(attr, '0')))
114		self.glyphs[glyphName] = glyph
115		for element in content:
116			if type(element) <> TupleType:
117				continue
118			glyph.fromXML(element, ttFont)
119		if not ttFont.recalcBBoxes:
120			glyph.compact(self, 0)
121
122	def setGlyphOrder(self, glyphOrder):
123		self.glyphOrder = glyphOrder
124
125	def getGlyphName(self, glyphID):
126		return self.glyphOrder[glyphID]
127
128	def getGlyphID(self, glyphName):
129		# XXX optimize with reverse dict!!!
130		return self.glyphOrder.index(glyphName)
131
132	def keys(self):
133		return self.glyphs.keys()
134
135	def has_key(self, glyphName):
136		return self.glyphs.has_key(glyphName)
137
138	__contains__ = has_key
139
140	def __getitem__(self, glyphName):
141		glyph = self.glyphs[glyphName]
142		glyph.expand(self)
143		return glyph
144
145	def __setitem__(self, glyphName, glyph):
146		self.glyphs[glyphName] = glyph
147		if glyphName not in self.glyphOrder:
148			self.glyphOrder.append(glyphName)
149
150	def __delitem__(self, glyphName):
151		del self.glyphs[glyphName]
152		self.glyphOrder.remove(glyphName)
153
154	def __len__(self):
155		assert len(self.glyphOrder) == len(self.glyphs)
156		return len(self.glyphs)
157
158
159glyphHeaderFormat = """
160		>	# big endian
161		numberOfContours:	h
162		xMin:				h
163		yMin:				h
164		xMax:				h
165		yMax:				h
166"""
167
168# flags
169flagOnCurve = 0x01
170flagXShort = 0x02
171flagYShort = 0x04
172flagRepeat = 0x08
173flagXsame =  0x10
174flagYsame = 0x20
175flagReserved1 = 0x40
176flagReserved2 = 0x80
177
178
179ARG_1_AND_2_ARE_WORDS      = 0x0001  # if set args are words otherwise they are bytes
180ARGS_ARE_XY_VALUES         = 0x0002  # if set args are xy values, otherwise they are points
181ROUND_XY_TO_GRID           = 0x0004  # for the xy values if above is true
182WE_HAVE_A_SCALE            = 0x0008  # Sx = Sy, otherwise scale == 1.0
183NON_OVERLAPPING            = 0x0010  # set to same value for all components (obsolete!)
184MORE_COMPONENTS            = 0x0020  # indicates at least one more glyph after this one
185WE_HAVE_AN_X_AND_Y_SCALE   = 0x0040  # Sx, Sy
186WE_HAVE_A_TWO_BY_TWO       = 0x0080  # t00, t01, t10, t11
187WE_HAVE_INSTRUCTIONS       = 0x0100  # instructions follow
188USE_MY_METRICS             = 0x0200  # apply these metrics to parent glyph
189OVERLAP_COMPOUND           = 0x0400  # used by Apple in GX fonts
190SCALED_COMPONENT_OFFSET    = 0x0800  # composite designed to have the component offset scaled (designed for Apple)
191UNSCALED_COMPONENT_OFFSET  = 0x1000  # composite designed not to have the component offset scaled (designed for MS)
192
193
194class Glyph:
195
196	def __init__(self, data=""):
197		if not data:
198			# empty char
199			self.numberOfContours = 0
200			return
201		self.data = data
202
203	def compact(self, glyfTable, recalcBBoxes=1):
204		data = self.compile(glyfTable, recalcBBoxes)
205		self.__dict__.clear()
206		self.data = data
207
208	def expand(self, glyfTable):
209		if not hasattr(self, "data"):
210			# already unpacked
211			return
212		if not self.data:
213			# empty char
214			self.numberOfContours = 0
215			return
216		dummy, data = sstruct.unpack2(glyphHeaderFormat, self.data, self)
217		del self.data
218		if self.isComposite():
219			self.decompileComponents(data, glyfTable)
220		else:
221			self.decompileCoordinates(data)
222
223	def compile(self, glyfTable, recalcBBoxes=1):
224		if hasattr(self, "data"):
225			return self.data
226		if self.numberOfContours == 0:
227			return ""
228		if recalcBBoxes:
229			self.recalcBounds(glyfTable)
230		data = sstruct.pack(glyphHeaderFormat, self)
231		if self.isComposite():
232			data = data + self.compileComponents(glyfTable)
233		else:
234			data = data + self.compileCoordinates()
235		# From the spec: "Note that the local offsets should be word-aligned"
236		# From a later MS spec: "Note that the local offsets should be long-aligned"
237		# Let's be modern and align on 4-byte boundaries.
238		if len(data) % 4:
239			# add pad bytes
240			nPadBytes = 4 - (len(data) % 4)
241			data = data + "\0" * nPadBytes
242		return data
243
244	def toXML(self, writer, ttFont):
245		if self.isComposite():
246			for compo in self.components:
247				compo.toXML(writer, ttFont)
248			if hasattr(self, "program"):
249				writer.begintag("instructions")
250				self.program.toXML(writer, ttFont)
251				writer.endtag("instructions")
252				writer.newline()
253		else:
254			last = 0
255			for i in range(self.numberOfContours):
256				writer.begintag("contour")
257				writer.newline()
258				for j in range(last, self.endPtsOfContours[i] + 1):
259					writer.simpletag("pt", [
260							("x", self.coordinates[j][0]),
261							("y", self.coordinates[j][1]),
262							("on", self.flags[j] & flagOnCurve)])
263					writer.newline()
264				last = self.endPtsOfContours[i] + 1
265				writer.endtag("contour")
266				writer.newline()
267			if self.numberOfContours:
268				writer.begintag("instructions")
269				self.program.toXML(writer, ttFont)
270				writer.endtag("instructions")
271				writer.newline()
272
273	def fromXML(self, (name, attrs, content), ttFont):
274		if name == "contour":
275			self.numberOfContours = self.numberOfContours + 1
276			if self.numberOfContours < 0:
277				raise ttLib.TTLibError, "can't mix composites and contours in glyph"
278			coordinates = []
279			flags = []
280			for element in content:
281				if type(element) <> TupleType:
282					continue
283				name, attrs, content = element
284				if name <> "pt":
285					continue  # ignore anything but "pt"
286				coordinates.append([safeEval(attrs["x"]), safeEval(attrs["y"])])
287				flags.append(not not safeEval(attrs["on"]))
288			coordinates = numpy.array(coordinates, numpy.int16)
289			flags = numpy.array(flags, numpy.int8)
290			if not hasattr(self, "coordinates"):
291				self.coordinates = coordinates
292				self.flags = flags
293				self.endPtsOfContours = [len(coordinates)-1]
294			else:
295				self.coordinates = numpy.concatenate((self.coordinates, coordinates))
296				self.flags = numpy.concatenate((self.flags, flags))
297				self.endPtsOfContours.append(len(self.coordinates)-1)
298		elif name == "component":
299			if self.numberOfContours > 0:
300				raise ttLib.TTLibError, "can't mix composites and contours in glyph"
301			self.numberOfContours = -1
302			if not hasattr(self, "components"):
303				self.components = []
304			component = GlyphComponent()
305			self.components.append(component)
306			component.fromXML((name, attrs, content), ttFont)
307		elif name == "instructions":
308			self.program = ttProgram.Program()
309			for element in content:
310				if type(element) <> TupleType:
311					continue
312				self.program.fromXML(element, ttFont)
313
314	def getCompositeMaxpValues(self, glyfTable, maxComponentDepth=1):
315		assert self.isComposite()
316		nContours = 0
317		nPoints = 0
318		for compo in self.components:
319			baseGlyph = glyfTable[compo.glyphName]
320			if baseGlyph.numberOfContours == 0:
321				continue
322			elif baseGlyph.numberOfContours > 0:
323				nP, nC = baseGlyph.getMaxpValues()
324			else:
325				nP, nC, maxComponentDepth = baseGlyph.getCompositeMaxpValues(
326						glyfTable, maxComponentDepth + 1)
327			nPoints = nPoints + nP
328			nContours = nContours + nC
329		return nPoints, nContours, maxComponentDepth
330
331	def getMaxpValues(self):
332		assert self.numberOfContours > 0
333		return len(self.coordinates), len(self.endPtsOfContours)
334
335	def decompileComponents(self, data, glyfTable):
336		self.components = []
337		more = 1
338		haveInstructions = 0
339		while more:
340			component = GlyphComponent()
341			more, haveInstr, data = component.decompile(data, glyfTable)
342			haveInstructions = haveInstructions | haveInstr
343			self.components.append(component)
344		if haveInstructions:
345			numInstructions, = struct.unpack(">h", data[:2])
346			data = data[2:]
347			self.program = ttProgram.Program()
348			self.program.fromBytecode(data[:numInstructions])
349			data = data[numInstructions:]
350			assert len(data) < 4, "bad composite data"
351
352	def decompileCoordinates(self, data):
353		endPtsOfContours = array.array("h")
354		endPtsOfContours.fromstring(data[:2*self.numberOfContours])
355		if sys.byteorder <> "big":
356			endPtsOfContours.byteswap()
357		self.endPtsOfContours = endPtsOfContours.tolist()
358
359		data = data[2*self.numberOfContours:]
360
361		instructionLength, = struct.unpack(">h", data[:2])
362		data = data[2:]
363		self.program = ttProgram.Program()
364		self.program.fromBytecode(data[:instructionLength])
365		data = data[instructionLength:]
366		nCoordinates = self.endPtsOfContours[-1] + 1
367		flags, xCoordinates, yCoordinates = \
368				self.decompileCoordinatesRaw(nCoordinates, data)
369
370		# fill in repetitions and apply signs
371		coordinates = numpy.zeros((nCoordinates, 2), numpy.int16)
372		xIndex = 0
373		yIndex = 0
374		for i in range(nCoordinates):
375			flag = flags[i]
376			# x coordinate
377			if flag & flagXShort:
378				if flag & flagXsame:
379					x = xCoordinates[xIndex]
380				else:
381					x = -xCoordinates[xIndex]
382				xIndex = xIndex + 1
383			elif flag & flagXsame:
384				x = 0
385			else:
386				x = xCoordinates[xIndex]
387				xIndex = xIndex + 1
388			# y coordinate
389			if flag & flagYShort:
390				if flag & flagYsame:
391					y = yCoordinates[yIndex]
392				else:
393					y = -yCoordinates[yIndex]
394				yIndex = yIndex + 1
395			elif flag & flagYsame:
396				y = 0
397			else:
398				y = yCoordinates[yIndex]
399				yIndex = yIndex + 1
400			coordinates[i] = (x, y)
401		assert xIndex == len(xCoordinates)
402		assert yIndex == len(yCoordinates)
403		# convert relative to absolute coordinates
404		self.coordinates = numpy.add.accumulate(coordinates)
405		# discard all flags but for "flagOnCurve"
406		self.flags = numpy.bitwise_and(flags, flagOnCurve).astype(numpy.int8)
407
408	def decompileCoordinatesRaw(self, nCoordinates, data):
409		# unpack flags and prepare unpacking of coordinates
410		flags = numpy.array([0] * nCoordinates, numpy.int8)
411		# Warning: deep Python trickery going on. We use the struct module to unpack
412		# the coordinates. We build a format string based on the flags, so we can
413		# unpack the coordinates in one struct.unpack() call.
414		xFormat = ">" # big endian
415		yFormat = ">" # big endian
416		i = j = 0
417		while 1:
418			flag = ord(data[i])
419			i = i + 1
420			repeat = 1
421			if flag & flagRepeat:
422				repeat = ord(data[i]) + 1
423				i = i + 1
424			for k in range(repeat):
425				if flag & flagXShort:
426					xFormat = xFormat + 'B'
427				elif not (flag & flagXsame):
428					xFormat = xFormat + 'h'
429				if flag & flagYShort:
430					yFormat = yFormat + 'B'
431				elif not (flag & flagYsame):
432					yFormat = yFormat + 'h'
433				flags[j] = flag
434				j = j + 1
435			if j >= nCoordinates:
436				break
437		assert j == nCoordinates, "bad glyph flags"
438		data = data[i:]
439		# unpack raw coordinates, krrrrrr-tching!
440		xDataLen = struct.calcsize(xFormat)
441		yDataLen = struct.calcsize(yFormat)
442		if not (0 <= (len(data) - (xDataLen + yDataLen)) < 4):
443			raise ttLib.TTLibError, "bad glyph record (leftover bytes: %s)" % (len(data) - (xDataLen + yDataLen))
444		xCoordinates = struct.unpack(xFormat, data[:xDataLen])
445		yCoordinates = struct.unpack(yFormat, data[xDataLen:xDataLen+yDataLen])
446		return flags, xCoordinates, yCoordinates
447
448	def compileComponents(self, glyfTable):
449		data = ""
450		lastcomponent = len(self.components) - 1
451		more = 1
452		haveInstructions = 0
453		for i in range(len(self.components)):
454			if i == lastcomponent:
455				haveInstructions = hasattr(self, "program")
456				more = 0
457			compo = self.components[i]
458			data = data + compo.compile(more, haveInstructions, glyfTable)
459		if haveInstructions:
460			instructions = self.program.getBytecode()
461			data = data + struct.pack(">h", len(instructions)) + instructions
462		return data
463
464
465	def compileCoordinates(self):
466		assert len(self.coordinates) == len(self.flags)
467		data = ""
468		endPtsOfContours = array.array("h", self.endPtsOfContours)
469		if sys.byteorder <> "big":
470			endPtsOfContours.byteswap()
471		data = data + endPtsOfContours.tostring()
472		instructions = self.program.getBytecode()
473		data = data + struct.pack(">h", len(instructions)) + instructions
474		nCoordinates = len(self.coordinates)
475
476		# make a copy
477		coordinates = numpy.array(self.coordinates)
478		# absolute to relative coordinates
479		coordinates[1:] = numpy.subtract(coordinates[1:], coordinates[:-1])
480		flags = self.flags
481		compressedflags = []
482		xPoints = []
483		yPoints = []
484		xFormat = ">"
485		yFormat = ">"
486		lastflag = None
487		repeat = 0
488		for i in range(len(coordinates)):
489			# Oh, the horrors of TrueType
490			flag = self.flags[i]
491			x, y = coordinates[i]
492			# do x
493			if x == 0:
494				flag = flag | flagXsame
495			elif -255 <= x <= 255:
496				flag = flag | flagXShort
497				if x > 0:
498					flag = flag | flagXsame
499				else:
500					x = -x
501				xPoints.append(x)
502				xFormat = xFormat + 'B'
503			else:
504				xPoints.append(x)
505				xFormat = xFormat + 'h'
506			# do y
507			if y == 0:
508				flag = flag | flagYsame
509			elif -255 <= y <= 255:
510				flag = flag | flagYShort
511				if y > 0:
512					flag = flag | flagYsame
513				else:
514					y = -y
515				yPoints.append(y)
516				yFormat = yFormat + 'B'
517			else:
518				yPoints.append(y)
519				yFormat = yFormat + 'h'
520			# handle repeating flags
521			if flag == lastflag:
522				repeat = repeat + 1
523				if repeat == 1:
524					compressedflags.append(flag)
525				elif repeat > 1:
526					compressedflags[-2] = flag | flagRepeat
527					compressedflags[-1] = repeat
528				else:
529					compressedflags[-1] = repeat
530			else:
531				repeat = 0
532				compressedflags.append(flag)
533			lastflag = flag
534		data = data + array.array("B", compressedflags).tostring()
535		xPoints = map(int, xPoints)  # work around numpy vs. struct >= 2.5 bug
536		yPoints = map(int, yPoints)
537		data = data + apply(struct.pack, (xFormat,)+tuple(xPoints))
538		data = data + apply(struct.pack, (yFormat,)+tuple(yPoints))
539		return data
540
541	def recalcBounds(self, glyfTable):
542		coordinates, endPts, flags = self.getCoordinates(glyfTable)
543		if len(coordinates) > 0:
544			self.xMin, self.yMin = numpy.minimum.reduce(coordinates)
545			self.xMax, self.yMax = numpy.maximum.reduce(coordinates)
546		else:
547			self.xMin, self.yMin, self.xMax, self.yMax = (0, 0, 0, 0)
548
549	def isComposite(self):
550		return self.numberOfContours == -1
551
552	def __getitem__(self, componentIndex):
553		if not self.isComposite():
554			raise ttLib.TTLibError, "can't use glyph as sequence"
555		return self.components[componentIndex]
556
557	def getCoordinates(self, glyfTable):
558		if self.numberOfContours > 0:
559			return self.coordinates, self.endPtsOfContours, self.flags
560		elif self.isComposite():
561			# it's a composite
562			allCoords = None
563			allFlags = None
564			allEndPts = None
565			for compo in self.components:
566				g = glyfTable[compo.glyphName]
567				coordinates, endPts, flags = g.getCoordinates(glyfTable)
568				if hasattr(compo, "firstPt"):
569					# move according to two reference points
570					move = allCoords[compo.firstPt] - coordinates[compo.secondPt]
571				else:
572					move = compo.x, compo.y
573
574				if not hasattr(compo, "transform"):
575					if len(coordinates) > 0:
576						coordinates = coordinates + move  # I love NumPy!
577				else:
578					apple_way = compo.flags & SCALED_COMPONENT_OFFSET
579					ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET
580					assert not (apple_way and ms_way)
581					if not (apple_way or ms_way):
582						scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT  # see top of this file
583					else:
584						scale_component_offset = apple_way
585					if scale_component_offset:
586						# the Apple way: first move, then scale (ie. scale the component offset)
587						coordinates = coordinates + move
588						coordinates = numpy.dot(coordinates, compo.transform)
589					else:
590						# the MS way: first scale, then move
591						coordinates = numpy.dot(coordinates, compo.transform)
592						coordinates = coordinates + move
593					# due to the transformation the coords. are now floats;
594					# round them off nicely, and cast to short
595					coordinates = numpy.floor(coordinates + 0.5).astype(numpy.int16)
596				if allCoords is None or len(allCoords) == 0:
597					allCoords = coordinates
598					allEndPts = endPts
599					allFlags = flags
600				else:
601					allEndPts = allEndPts + (numpy.array(endPts) + len(allCoords)).tolist()
602					if len(coordinates) > 0:
603						allCoords = numpy.concatenate((allCoords, coordinates))
604						allFlags = numpy.concatenate((allFlags, flags))
605			return allCoords, allEndPts, allFlags
606		else:
607			return numpy.array([], numpy.int16), [], numpy.array([], numpy.int8)
608
609	def __cmp__(self, other):
610		if self.numberOfContours <= 0:
611			return cmp(self.__dict__, other.__dict__)
612		else:
613			if cmp(len(self.coordinates), len(other.coordinates)):
614				return 1
615			ctest = numpy.alltrue(numpy.alltrue(numpy.equal(self.coordinates, other.coordinates)))
616			ftest = numpy.alltrue(numpy.equal(self.flags, other.flags))
617			if not ctest or not ftest:
618				return 1
619			return (
620					cmp(self.endPtsOfContours, other.endPtsOfContours) or
621					cmp(self.program, other.instructions)
622				)
623
624
625class GlyphComponent:
626
627	def __init__(self):
628		pass
629
630	def getComponentInfo(self):
631		"""Return the base glyph name and a transform."""
632		# XXX Ignoring self.firstPt & self.lastpt for now: I need to implement
633		# something equivalent in fontTools.objects.glyph (I'd rather not
634		# convert it to an absolute offset, since it is valuable information).
635		# This method will now raise "AttributeError: x" on glyphs that use
636		# this TT feature.
637		if hasattr(self, "transform"):
638			[[xx, xy], [yx, yy]] = self.transform
639			trans = (xx, xy, yx, yy, self.x, self.y)
640		else:
641			trans = (1, 0, 0, 1, self.x, self.y)
642		return self.glyphName, trans
643
644	def decompile(self, data, glyfTable):
645		flags, glyphID = struct.unpack(">HH", data[:4])
646		self.flags = int(flags)
647		glyphID = int(glyphID)
648		self.glyphName = glyfTable.getGlyphName(int(glyphID))
649		#print ">>", reprflag(self.flags)
650		data = data[4:]
651
652		if self.flags & ARG_1_AND_2_ARE_WORDS:
653			if self.flags & ARGS_ARE_XY_VALUES:
654				self.x, self.y = struct.unpack(">hh", data[:4])
655			else:
656				x, y = struct.unpack(">HH", data[:4])
657				self.firstPt, self.secondPt = int(x), int(y)
658			data = data[4:]
659		else:
660			if self.flags & ARGS_ARE_XY_VALUES:
661				self.x, self.y = struct.unpack(">bb", data[:2])
662			else:
663				x, y = struct.unpack(">BB", data[:2])
664				self.firstPt, self.secondPt = int(x), int(y)
665			data = data[2:]
666
667		if self.flags & WE_HAVE_A_SCALE:
668			scale, = struct.unpack(">h", data[:2])
669			self.transform = numpy.array(
670					[[scale, 0], [0, scale]]) / float(0x4000)  # fixed 2.14
671			data = data[2:]
672		elif self.flags & WE_HAVE_AN_X_AND_Y_SCALE:
673			xscale, yscale = struct.unpack(">hh", data[:4])
674			self.transform = numpy.array(
675					[[xscale, 0], [0, yscale]]) / float(0x4000)  # fixed 2.14
676			data = data[4:]
677		elif self.flags & WE_HAVE_A_TWO_BY_TWO:
678			(xscale, scale01,
679					scale10, yscale) = struct.unpack(">hhhh", data[:8])
680			self.transform = numpy.array(
681					[[xscale, scale01], [scale10, yscale]]) / float(0x4000)  # fixed 2.14
682			data = data[8:]
683		more = self.flags & MORE_COMPONENTS
684		haveInstructions = self.flags & WE_HAVE_INSTRUCTIONS
685		self.flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
686				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
687				NON_OVERLAPPING)
688		return more, haveInstructions, data
689
690	def compile(self, more, haveInstructions, glyfTable):
691		data = ""
692
693		# reset all flags we will calculate ourselves
694		flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
695				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
696				NON_OVERLAPPING)
697		if more:
698			flags = flags | MORE_COMPONENTS
699		if haveInstructions:
700			flags = flags | WE_HAVE_INSTRUCTIONS
701
702		if hasattr(self, "firstPt"):
703			if (0 <= self.firstPt <= 255) and (0 <= self.secondPt <= 255):
704				data = data + struct.pack(">BB", self.firstPt, self.secondPt)
705			else:
706				data = data + struct.pack(">HH", self.firstPt, self.secondPt)
707				flags = flags | ARG_1_AND_2_ARE_WORDS
708		else:
709			flags = flags | ARGS_ARE_XY_VALUES
710			if (-128 <= self.x <= 127) and (-128 <= self.y <= 127):
711				data = data + struct.pack(">bb", self.x, self.y)
712			else:
713				data = data + struct.pack(">hh", self.x, self.y)
714				flags = flags | ARG_1_AND_2_ARE_WORDS
715
716		if hasattr(self, "transform"):
717			# XXX needs more testing
718			transform = numpy.floor(self.transform * 0x4000 + 0.5)
719			if transform[0][1] or transform[1][0]:
720				flags = flags | WE_HAVE_A_TWO_BY_TWO
721				data = data + struct.pack(">hhhh",
722						transform[0][0], transform[0][1],
723						transform[1][0], transform[1][1])
724			elif transform[0][0] <> transform[1][1]:
725				flags = flags | WE_HAVE_AN_X_AND_Y_SCALE
726				data = data + struct.pack(">hh",
727						transform[0][0], transform[1][1])
728			else:
729				flags = flags | WE_HAVE_A_SCALE
730				data = data + struct.pack(">h",
731						transform[0][0])
732
733		glyphID = glyfTable.getGlyphID(self.glyphName)
734		return struct.pack(">HH", flags, glyphID) + data
735
736	def toXML(self, writer, ttFont):
737		attrs = [("glyphName", self.glyphName)]
738		if not hasattr(self, "firstPt"):
739			attrs = attrs + [("x", self.x), ("y", self.y)]
740		else:
741			attrs = attrs + [("firstPt", self.firstPt), ("secondPt", self.secondPt)]
742
743		if hasattr(self, "transform"):
744			# XXX needs more testing
745			transform = self.transform
746			if transform[0][1] or transform[1][0]:
747				attrs = attrs + [
748						("scalex", transform[0][0]), ("scale01", transform[0][1]),
749						("scale10", transform[1][0]), ("scaley", transform[1][1]),
750						]
751			elif transform[0][0] <> transform[1][1]:
752				attrs = attrs + [
753						("scalex", transform[0][0]), ("scaley", transform[1][1]),
754						]
755			else:
756				attrs = attrs + [("scale", transform[0][0])]
757		attrs = attrs + [("flags", hex(self.flags))]
758		writer.simpletag("component", attrs)
759		writer.newline()
760
761	def fromXML(self, (name, attrs, content), ttFont):
762		self.glyphName = attrs["glyphName"]
763		if attrs.has_key("firstPt"):
764			self.firstPt = safeEval(attrs["firstPt"])
765			self.secondPt = safeEval(attrs["secondPt"])
766		else:
767			self.x = safeEval(attrs["x"])
768			self.y = safeEval(attrs["y"])
769		if attrs.has_key("scale01"):
770			scalex = safeEval(attrs["scalex"])
771			scale01 = safeEval(attrs["scale01"])
772			scale10 = safeEval(attrs["scale10"])
773			scaley = safeEval(attrs["scaley"])
774			self.transform = numpy.array([[scalex, scale01], [scale10, scaley]])
775		elif attrs.has_key("scalex"):
776			scalex = safeEval(attrs["scalex"])
777			scaley = safeEval(attrs["scaley"])
778			self.transform = numpy.array([[scalex, 0], [0, scaley]])
779		elif attrs.has_key("scale"):
780			scale = safeEval(attrs["scale"])
781			self.transform = numpy.array([[scale, 0], [0, scale]])
782		self.flags = safeEval(attrs["flags"])
783
784	def __cmp__(self, other):
785		if hasattr(self, "transform"):
786			if numpy.alltrue(numpy.equal(self.transform, other.transform)):
787				selfdict = self.__dict__.copy()
788				otherdict = other.__dict__.copy()
789				del selfdict["transform"]
790				del otherdict["transform"]
791				return cmp(selfdict, otherdict)
792			else:
793				return 1
794		else:
795			return cmp(self.__dict__, other.__dict__)
796
797
798def reprflag(flag):
799	bin = ""
800	if type(flag) == StringType:
801		flag = ord(flag)
802	while flag:
803		if flag & 0x01:
804			bin = "1" + bin
805		else:
806			bin = "0" + bin
807		flag = flag >> 1
808	bin = (14 - len(bin)) * "0" + bin
809	return bin
810
811