Home | History | Annotate | Download | only in tables
      1 """_g_l_y_f.py -- Converter classes for the 'glyf' table."""
      2 
      3 
      4 from __future__ import print_function, division, absolute_import
      5 from fontTools.misc.py23 import *
      6 from fontTools.misc import sstruct
      7 from fontTools import ttLib
      8 from fontTools.misc.textTools import safeEval
      9 from fontTools.misc.arrayTools import calcBounds, calcIntBounds, pointInRect
     10 from fontTools.misc.bezierTools import calcQuadraticBounds
     11 from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
     12 from . import DefaultTable
     13 from . import ttProgram
     14 import sys
     15 import struct
     16 import array
     17 import warnings
     18 
     19 #
     20 # The Apple and MS rasterizers behave differently for 
     21 # scaled composite components: one does scale first and then translate
     22 # and the other does it vice versa. MS defined some flags to indicate
     23 # the difference, but it seems nobody actually _sets_ those flags.
     24 #
     25 # Funny thing: Apple seems to _only_ do their thing in the
     26 # WE_HAVE_A_SCALE (eg. Chicago) case, and not when it's WE_HAVE_AN_X_AND_Y_SCALE 
     27 # (eg. Charcoal)...
     28 #
     29 SCALE_COMPONENT_OFFSET_DEFAULT = 0   # 0 == MS, 1 == Apple
     30 
     31 
     32 class table__g_l_y_f(DefaultTable.DefaultTable):
     33 	
     34 	def decompile(self, data, ttFont):
     35 		loca = ttFont['loca']
     36 		last = int(loca[0])
     37 		noname = 0
     38 		self.glyphs = {}
     39 		self.glyphOrder = glyphOrder = ttFont.getGlyphOrder()
     40 		for i in range(0, len(loca)-1):
     41 			try:
     42 				glyphName = glyphOrder[i]
     43 			except IndexError:
     44 				noname = noname + 1
     45 				glyphName = 'ttxautoglyph%s' % i
     46 			next = int(loca[i+1])
     47 			glyphdata = data[last:next]
     48 			if len(glyphdata) != (next - last):
     49 				raise ttLib.TTLibError("not enough 'glyf' table data")
     50 			glyph = Glyph(glyphdata)
     51 			self.glyphs[glyphName] = glyph
     52 			last = next
     53 		if len(data) > next:
     54 			warnings.warn("too much 'glyf' table data")
     55 		if noname:
     56 			warnings.warn('%s glyphs have no name' % i)
     57 		if not ttFont.lazy:
     58 			for glyph in self.glyphs.values():
     59 				glyph.expand(self)
     60 	
     61 	def compile(self, ttFont):
     62 		if not hasattr(self, "glyphOrder"):
     63 			self.glyphOrder = ttFont.getGlyphOrder()
     64 		locations = []
     65 		currentLocation = 0
     66 		dataList = []
     67 		recalcBBoxes = ttFont.recalcBBoxes
     68 		for glyphName in self.glyphOrder:
     69 			glyph = self.glyphs[glyphName]
     70 			glyphData = glyph.compile(self, recalcBBoxes)
     71 			locations.append(currentLocation)
     72 			currentLocation = currentLocation + len(glyphData)
     73 			dataList.append(glyphData)
     74 		locations.append(currentLocation)
     75 		data = bytesjoin(dataList)
     76 		if 'loca' in ttFont:
     77 			ttFont['loca'].set(locations)
     78 		ttFont['maxp'].numGlyphs = len(self.glyphs)
     79 		return data
     80 	
     81 	def toXML(self, writer, ttFont, progress=None):
     82 		writer.newline()
     83 		glyphNames = ttFont.getGlyphNames()
     84 		writer.comment("The xMin, yMin, xMax and yMax values\nwill be recalculated by the compiler.")
     85 		writer.newline()
     86 		writer.newline()
     87 		counter = 0
     88 		progressStep = 10
     89 		numGlyphs = len(glyphNames)
     90 		for glyphName in glyphNames:
     91 			if not counter % progressStep and progress is not None:
     92 				progress.setLabel("Dumping 'glyf' table... (%s)" % glyphName)
     93 				progress.increment(progressStep / numGlyphs)
     94 			counter = counter + 1
     95 			glyph = self[glyphName]
     96 			if glyph.numberOfContours:
     97 				writer.begintag('TTGlyph', [
     98 						("name", glyphName),
     99 						("xMin", glyph.xMin),
    100 						("yMin", glyph.yMin),
    101 						("xMax", glyph.xMax),
    102 						("yMax", glyph.yMax),
    103 						])
    104 				writer.newline()
    105 				glyph.toXML(writer, ttFont)
    106 				writer.endtag('TTGlyph')
    107 				writer.newline()
    108 			else:
    109 				writer.simpletag('TTGlyph', name=glyphName)
    110 				writer.comment("contains no outline data")
    111 				writer.newline()
    112 			writer.newline()
    113 	
    114 	def fromXML(self, name, attrs, content, ttFont):
    115 		if name != "TTGlyph":
    116 			return
    117 		if not hasattr(self, "glyphs"):
    118 			self.glyphs = {}
    119 		if not hasattr(self, "glyphOrder"):
    120 			self.glyphOrder = ttFont.getGlyphOrder()
    121 		glyphName = attrs["name"]
    122 		if ttFont.verbose:
    123 			ttLib.debugmsg("unpacking glyph '%s'" % glyphName)
    124 		glyph = Glyph()
    125 		for attr in ['xMin', 'yMin', 'xMax', 'yMax']:
    126 			setattr(glyph, attr, safeEval(attrs.get(attr, '0')))
    127 		self.glyphs[glyphName] = glyph
    128 		for element in content:
    129 			if not isinstance(element, tuple):
    130 				continue
    131 			name, attrs, content = element
    132 			glyph.fromXML(name, attrs, content, ttFont)
    133 		if not ttFont.recalcBBoxes:
    134 			glyph.compact(self, 0)
    135 	
    136 	def setGlyphOrder(self, glyphOrder):
    137 		self.glyphOrder = glyphOrder
    138 	
    139 	def getGlyphName(self, glyphID):
    140 		return self.glyphOrder[glyphID]
    141 	
    142 	def getGlyphID(self, glyphName):
    143 		# XXX optimize with reverse dict!!!
    144 		return self.glyphOrder.index(glyphName)
    145 	
    146 	def keys(self):
    147 		return self.glyphs.keys()
    148 	
    149 	def has_key(self, glyphName):
    150 		return glyphName in self.glyphs
    151 	
    152 	__contains__ = has_key
    153 	
    154 	def __getitem__(self, glyphName):
    155 		glyph = self.glyphs[glyphName]
    156 		glyph.expand(self)
    157 		return glyph
    158 	
    159 	def __setitem__(self, glyphName, glyph):
    160 		self.glyphs[glyphName] = glyph
    161 		if glyphName not in self.glyphOrder:
    162 			self.glyphOrder.append(glyphName)
    163 	
    164 	def __delitem__(self, glyphName):
    165 		del self.glyphs[glyphName]
    166 		self.glyphOrder.remove(glyphName)
    167 	
    168 	def __len__(self):
    169 		assert len(self.glyphOrder) == len(self.glyphs)
    170 		return len(self.glyphs)
    171 
    172 
    173 glyphHeaderFormat = """
    174 		>	# big endian
    175 		numberOfContours:	h
    176 		xMin:				h
    177 		yMin:				h
    178 		xMax:				h
    179 		yMax:				h
    180 """
    181 
    182 # flags
    183 flagOnCurve = 0x01
    184 flagXShort = 0x02
    185 flagYShort = 0x04
    186 flagRepeat = 0x08
    187 flagXsame =  0x10
    188 flagYsame = 0x20
    189 flagReserved1 = 0x40
    190 flagReserved2 = 0x80
    191 
    192 
    193 ARG_1_AND_2_ARE_WORDS      = 0x0001  # if set args are words otherwise they are bytes 
    194 ARGS_ARE_XY_VALUES         = 0x0002  # if set args are xy values, otherwise they are points 
    195 ROUND_XY_TO_GRID           = 0x0004  # for the xy values if above is true 
    196 WE_HAVE_A_SCALE            = 0x0008  # Sx = Sy, otherwise scale == 1.0 
    197 NON_OVERLAPPING            = 0x0010  # set to same value for all components (obsolete!)
    198 MORE_COMPONENTS            = 0x0020  # indicates at least one more glyph after this one 
    199 WE_HAVE_AN_X_AND_Y_SCALE   = 0x0040  # Sx, Sy 
    200 WE_HAVE_A_TWO_BY_TWO       = 0x0080  # t00, t01, t10, t11 
    201 WE_HAVE_INSTRUCTIONS       = 0x0100  # instructions follow 
    202 USE_MY_METRICS             = 0x0200  # apply these metrics to parent glyph 
    203 OVERLAP_COMPOUND           = 0x0400  # used by Apple in GX fonts 
    204 SCALED_COMPONENT_OFFSET    = 0x0800  # composite designed to have the component offset scaled (designed for Apple) 
    205 UNSCALED_COMPONENT_OFFSET  = 0x1000  # composite designed not to have the component offset scaled (designed for MS) 
    206 
    207 
    208 class Glyph(object):
    209 	
    210 	def __init__(self, data=""):
    211 		if not data:
    212 			# empty char
    213 			self.numberOfContours = 0
    214 			return
    215 		self.data = data
    216 	
    217 	def compact(self, glyfTable, recalcBBoxes=True):
    218 		data = self.compile(glyfTable, recalcBBoxes)
    219 		self.__dict__.clear()
    220 		self.data = data
    221 	
    222 	def expand(self, glyfTable):
    223 		if not hasattr(self, "data"):
    224 			# already unpacked
    225 			return
    226 		if not self.data:
    227 			# empty char
    228 			self.numberOfContours = 0
    229 			return
    230 		dummy, data = sstruct.unpack2(glyphHeaderFormat, self.data, self)
    231 		del self.data
    232 		if self.isComposite():
    233 			self.decompileComponents(data, glyfTable)
    234 		else:
    235 			self.decompileCoordinates(data)
    236 	
    237 	def compile(self, glyfTable, recalcBBoxes=True):
    238 		if hasattr(self, "data"):
    239 			return self.data
    240 		if self.numberOfContours == 0:
    241 			return ""
    242 		if recalcBBoxes:
    243 			self.recalcBounds(glyfTable)
    244 		data = sstruct.pack(glyphHeaderFormat, self)
    245 		if self.isComposite():
    246 			data = data + self.compileComponents(glyfTable)
    247 		else:
    248 			data = data + self.compileCoordinates()
    249 		# From the spec: "Note that the local offsets should be word-aligned"
    250 		# From a later MS spec: "Note that the local offsets should be long-aligned"
    251 		# Let's be modern and align on 4-byte boundaries.
    252 		if len(data) % 4:
    253 			# add pad bytes
    254 			nPadBytes = 4 - (len(data) % 4)
    255 			data = data + b"\0" * nPadBytes
    256 		return data
    257 	
    258 	def toXML(self, writer, ttFont):
    259 		if self.isComposite():
    260 			for compo in self.components:
    261 				compo.toXML(writer, ttFont)
    262 			if hasattr(self, "program"):
    263 				writer.begintag("instructions")
    264 				self.program.toXML(writer, ttFont)
    265 				writer.endtag("instructions")
    266 				writer.newline()
    267 		else:
    268 			last = 0
    269 			for i in range(self.numberOfContours):
    270 				writer.begintag("contour")
    271 				writer.newline()
    272 				for j in range(last, self.endPtsOfContours[i] + 1):
    273 					writer.simpletag("pt", [
    274 							("x", self.coordinates[j][0]), 
    275 							("y", self.coordinates[j][1]),
    276 							("on", self.flags[j] & flagOnCurve)])
    277 					writer.newline()
    278 				last = self.endPtsOfContours[i] + 1
    279 				writer.endtag("contour")
    280 				writer.newline()
    281 			if self.numberOfContours:
    282 				writer.begintag("instructions")
    283 				self.program.toXML(writer, ttFont)
    284 				writer.endtag("instructions")
    285 				writer.newline()
    286 	
    287 	def fromXML(self, name, attrs, content, ttFont):
    288 		if name == "contour":
    289 			if self.numberOfContours < 0:
    290 				raise ttLib.TTLibError("can't mix composites and contours in glyph")
    291 			self.numberOfContours = self.numberOfContours + 1
    292 			coordinates = GlyphCoordinates()
    293 			flags = []
    294 			for element in content:
    295 				if not isinstance(element, tuple):
    296 					continue
    297 				name, attrs, content = element
    298 				if name != "pt":
    299 					continue  # ignore anything but "pt"
    300 				coordinates.append((safeEval(attrs["x"]), safeEval(attrs["y"])))
    301 				flags.append(not not safeEval(attrs["on"]))
    302 			flags = array.array("B", flags)
    303 			if not hasattr(self, "coordinates"):
    304 				self.coordinates = coordinates
    305 				self.flags = flags
    306 				self.endPtsOfContours = [len(coordinates)-1]
    307 			else:
    308 				self.coordinates.extend (coordinates)
    309 				self.flags.extend(flags)
    310 				self.endPtsOfContours.append(len(self.coordinates)-1)
    311 		elif name == "component":
    312 			if self.numberOfContours > 0:
    313 				raise ttLib.TTLibError("can't mix composites and contours in glyph")
    314 			self.numberOfContours = -1
    315 			if not hasattr(self, "components"):
    316 				self.components = []
    317 			component = GlyphComponent()
    318 			self.components.append(component)
    319 			component.fromXML(name, attrs, content, ttFont)
    320 		elif name == "instructions":
    321 			self.program = ttProgram.Program()
    322 			for element in content:
    323 				if not isinstance(element, tuple):
    324 					continue
    325 				name, attrs, content = element
    326 				self.program.fromXML(name, attrs, content, ttFont)
    327 	
    328 	def getCompositeMaxpValues(self, glyfTable, maxComponentDepth=1):
    329 		assert self.isComposite()
    330 		nContours = 0
    331 		nPoints = 0
    332 		for compo in self.components:
    333 			baseGlyph = glyfTable[compo.glyphName]
    334 			if baseGlyph.numberOfContours == 0:
    335 				continue
    336 			elif baseGlyph.numberOfContours > 0:
    337 				nP, nC = baseGlyph.getMaxpValues()
    338 			else:
    339 				nP, nC, maxComponentDepth = baseGlyph.getCompositeMaxpValues(
    340 						glyfTable, maxComponentDepth + 1)
    341 			nPoints = nPoints + nP
    342 			nContours = nContours + nC
    343 		return nPoints, nContours, maxComponentDepth
    344 	
    345 	def getMaxpValues(self):
    346 		assert self.numberOfContours > 0
    347 		return len(self.coordinates), len(self.endPtsOfContours)
    348 	
    349 	def decompileComponents(self, data, glyfTable):
    350 		self.components = []
    351 		more = 1
    352 		haveInstructions = 0
    353 		while more:
    354 			component = GlyphComponent()
    355 			more, haveInstr, data = component.decompile(data, glyfTable)
    356 			haveInstructions = haveInstructions | haveInstr
    357 			self.components.append(component)
    358 		if haveInstructions:
    359 			numInstructions, = struct.unpack(">h", data[:2])
    360 			data = data[2:]
    361 			self.program = ttProgram.Program()
    362 			self.program.fromBytecode(data[:numInstructions])
    363 			data = data[numInstructions:]
    364 			assert len(data) < 4, "bad composite data"
    365 	
    366 	def decompileCoordinates(self, data):
    367 		endPtsOfContours = array.array("h")
    368 		endPtsOfContours.fromstring(data[:2*self.numberOfContours])
    369 		if sys.byteorder != "big":
    370 			endPtsOfContours.byteswap()
    371 		self.endPtsOfContours = endPtsOfContours.tolist()
    372 		
    373 		data = data[2*self.numberOfContours:]
    374 		
    375 		instructionLength, = struct.unpack(">h", data[:2])
    376 		data = data[2:]
    377 		self.program = ttProgram.Program()
    378 		self.program.fromBytecode(data[:instructionLength])
    379 		data = data[instructionLength:]
    380 		nCoordinates = self.endPtsOfContours[-1] + 1
    381 		flags, xCoordinates, yCoordinates = \
    382 				self.decompileCoordinatesRaw(nCoordinates, data)
    383 		
    384 		# fill in repetitions and apply signs
    385 		self.coordinates = coordinates = GlyphCoordinates.zeros(nCoordinates)
    386 		xIndex = 0
    387 		yIndex = 0
    388 		for i in range(nCoordinates):
    389 			flag = flags[i]
    390 			# x coordinate
    391 			if flag & flagXShort:
    392 				if flag & flagXsame:
    393 					x = xCoordinates[xIndex]
    394 				else:
    395 					x = -xCoordinates[xIndex]
    396 				xIndex = xIndex + 1
    397 			elif flag & flagXsame:
    398 				x = 0
    399 			else:
    400 				x = xCoordinates[xIndex]
    401 				xIndex = xIndex + 1
    402 			# y coordinate
    403 			if flag & flagYShort:
    404 				if flag & flagYsame:
    405 					y = yCoordinates[yIndex]
    406 				else:
    407 					y = -yCoordinates[yIndex]
    408 				yIndex = yIndex + 1
    409 			elif flag & flagYsame:
    410 				y = 0
    411 			else:
    412 				y = yCoordinates[yIndex]
    413 				yIndex = yIndex + 1
    414 			coordinates[i] = (x, y)
    415 		assert xIndex == len(xCoordinates)
    416 		assert yIndex == len(yCoordinates)
    417 		coordinates.relativeToAbsolute()
    418 		# discard all flags but for "flagOnCurve"
    419 		self.flags = array.array("B", (f & flagOnCurve for f in flags))
    420 
    421 	def decompileCoordinatesRaw(self, nCoordinates, data):
    422 		# unpack flags and prepare unpacking of coordinates
    423 		flags = array.array("B", [0] * nCoordinates)
    424 		# Warning: deep Python trickery going on. We use the struct module to unpack
    425 		# the coordinates. We build a format string based on the flags, so we can
    426 		# unpack the coordinates in one struct.unpack() call.
    427 		xFormat = ">" # big endian
    428 		yFormat = ">" # big endian
    429 		i = j = 0
    430 		while True:
    431 			flag = byteord(data[i])
    432 			i = i + 1
    433 			repeat = 1
    434 			if flag & flagRepeat:
    435 				repeat = byteord(data[i]) + 1
    436 				i = i + 1
    437 			for k in range(repeat):
    438 				if flag & flagXShort:
    439 					xFormat = xFormat + 'B'
    440 				elif not (flag & flagXsame):
    441 					xFormat = xFormat + 'h'
    442 				if flag & flagYShort:
    443 					yFormat = yFormat + 'B'
    444 				elif not (flag & flagYsame):
    445 					yFormat = yFormat + 'h'
    446 				flags[j] = flag
    447 				j = j + 1
    448 			if j >= nCoordinates:
    449 				break
    450 		assert j == nCoordinates, "bad glyph flags"
    451 		data = data[i:]
    452 		# unpack raw coordinates, krrrrrr-tching!
    453 		xDataLen = struct.calcsize(xFormat)
    454 		yDataLen = struct.calcsize(yFormat)
    455 		if len(data) - (xDataLen + yDataLen) >= 4:
    456 			warnings.warn("too much glyph data: %d excess bytes" % (len(data) - (xDataLen + yDataLen)))
    457 		xCoordinates = struct.unpack(xFormat, data[:xDataLen])
    458 		yCoordinates = struct.unpack(yFormat, data[xDataLen:xDataLen+yDataLen])
    459 		return flags, xCoordinates, yCoordinates
    460 	
    461 	def compileComponents(self, glyfTable):
    462 		data = b""
    463 		lastcomponent = len(self.components) - 1
    464 		more = 1
    465 		haveInstructions = 0
    466 		for i in range(len(self.components)):
    467 			if i == lastcomponent:
    468 				haveInstructions = hasattr(self, "program")
    469 				more = 0
    470 			compo = self.components[i]
    471 			data = data + compo.compile(more, haveInstructions, glyfTable)
    472 		if haveInstructions:
    473 			instructions = self.program.getBytecode()
    474 			data = data + struct.pack(">h", len(instructions)) + instructions
    475 		return data
    476 			
    477 	
    478 	def compileCoordinates(self):
    479 		assert len(self.coordinates) == len(self.flags)
    480 		data = b""
    481 		endPtsOfContours = array.array("h", self.endPtsOfContours)
    482 		if sys.byteorder != "big":
    483 			endPtsOfContours.byteswap()
    484 		data = data + endPtsOfContours.tostring()
    485 		instructions = self.program.getBytecode()
    486 		data = data + struct.pack(">h", len(instructions)) + instructions
    487 		nCoordinates = len(self.coordinates)
    488 		
    489 		coordinates = self.coordinates.copy()
    490 		coordinates.absoluteToRelative()
    491 		flags = self.flags
    492 		compressedflags = []
    493 		xPoints = []
    494 		yPoints = []
    495 		xFormat = ">"
    496 		yFormat = ">"
    497 		lastflag = None
    498 		repeat = 0
    499 		for i in range(len(coordinates)):
    500 			# Oh, the horrors of TrueType
    501 			flag = flags[i]
    502 			x, y = coordinates[i]
    503 			# do x
    504 			if x == 0:
    505 				flag = flag | flagXsame
    506 			elif -255 <= x <= 255:
    507 				flag = flag | flagXShort
    508 				if x > 0:
    509 					flag = flag | flagXsame
    510 				else:
    511 					x = -x
    512 				xPoints.append(x)
    513 				xFormat = xFormat + 'B'
    514 			else:
    515 				xPoints.append(x)
    516 				xFormat = xFormat + 'h'
    517 			# do y
    518 			if y == 0:
    519 				flag = flag | flagYsame
    520 			elif -255 <= y <= 255:
    521 				flag = flag | flagYShort
    522 				if y > 0:
    523 					flag = flag | flagYsame
    524 				else:
    525 					y = -y
    526 				yPoints.append(y)
    527 				yFormat = yFormat + 'B'
    528 			else:
    529 				yPoints.append(y)
    530 				yFormat = yFormat + 'h'
    531 			# handle repeating flags
    532 			if flag == lastflag and repeat != 255:
    533 				repeat = repeat + 1
    534 				if repeat == 1:
    535 					compressedflags.append(flag)
    536 				else:
    537 					compressedflags[-2] = flag | flagRepeat
    538 					compressedflags[-1] = repeat
    539 			else:
    540 				repeat = 0
    541 				compressedflags.append(flag)
    542 			lastflag = flag
    543 		data = data + array.array("B", compressedflags).tostring()
    544 		if coordinates.isFloat():
    545 			# Warn?
    546 			xPoints = [int(round(x)) for x in xPoints]
    547 			yPoints = [int(round(y)) for y in xPoints]
    548 		data = data + struct.pack(*(xFormat,)+tuple(xPoints))
    549 		data = data + struct.pack(*(yFormat,)+tuple(yPoints))
    550 		return data
    551 	
    552 	def recalcBounds(self, glyfTable):
    553 		coords, endPts, flags = self.getCoordinates(glyfTable)
    554 		if len(coords) > 0:
    555 			if 0:
    556 				# This branch calculates exact glyph outline bounds
    557 				# analytically, handling cases without on-curve
    558 				# extremas, etc.  However, the glyf table header
    559 				# simply says that the bounds should be min/max x/y
    560 				# "for coordinate data", so I suppose that means no
    561 				# fancy thing here, just get extremas of all coord
    562 				# points (on and off).  As such, this branch is
    563 				# disabled.
    564 
    565 				# Collect on-curve points
    566 				onCurveCoords = [coords[j] for j in range(len(coords))
    567 						 if flags[j] & flagOnCurve]
    568 				# Add implicit on-curve points
    569 				start = 0
    570 				for end in endPts:
    571 					last = end
    572 					for j in range(start, end + 1):
    573 						if not ((flags[j] | flags[last]) & flagOnCurve):
    574 							x = (coords[last][0] + coords[j][0]) / 2
    575 							y = (coords[last][1] + coords[j][1]) / 2
    576 							onCurveCoords.append((x,y))
    577 						last = j
    578 					start = end + 1
    579 				# Add bounds for curves without an explicit extrema
    580 				start = 0
    581 				for end in endPts:
    582 					last = end
    583 					for j in range(start, end + 1):
    584 						if not (flags[j] & flagOnCurve):
    585 							next = j + 1 if j < end else start
    586 							bbox = calcBounds([coords[last], coords[next]])
    587 							if not pointInRect(coords[j], bbox):
    588 								# Ouch!
    589 								warnings.warn("Outline has curve with implicit extrema.")
    590 								# Ouch!  Find analytical curve bounds.
    591 								pthis = coords[j]
    592 								plast = coords[last]
    593 								if not (flags[last] & flagOnCurve):
    594 									plast = ((pthis[0]+plast[0])/2, (pthis[1]+plast[1])/2)
    595 								pnext = coords[next]
    596 								if not (flags[next] & flagOnCurve):
    597 									pnext = ((pthis[0]+pnext[0])/2, (pthis[1]+pnext[1])/2)
    598 								bbox = calcQuadraticBounds(plast, pthis, pnext)
    599 								onCurveCoords.append((bbox[0],bbox[1]))
    600 								onCurveCoords.append((bbox[2],bbox[3]))
    601 						last = j
    602 					start = end + 1
    603 
    604 				self.xMin, self.yMin, self.xMax, self.yMax = calcIntBounds(onCurveCoords)
    605 			else:
    606 				self.xMin, self.yMin, self.xMax, self.yMax = calcIntBounds(coords)
    607 		else:
    608 			self.xMin, self.yMin, self.xMax, self.yMax = (0, 0, 0, 0)
    609 	
    610 	def isComposite(self):
    611 		"""Can be called on compact or expanded glyph."""
    612 		if hasattr(self, "data"):
    613 			return struct.unpack(">h", self.data[:2])[0] == -1
    614 		else:
    615 			return self.numberOfContours == -1
    616 	
    617 	def __getitem__(self, componentIndex):
    618 		if not self.isComposite():
    619 			raise ttLib.TTLibError("can't use glyph as sequence")
    620 		return self.components[componentIndex]
    621 	
    622 	def getCoordinates(self, glyfTable):
    623 		if self.numberOfContours > 0:
    624 			return self.coordinates, self.endPtsOfContours, self.flags
    625 		elif self.isComposite():
    626 			# it's a composite
    627 			allCoords = GlyphCoordinates()
    628 			allFlags = array.array("B")
    629 			allEndPts = []
    630 			for compo in self.components:
    631 				g = glyfTable[compo.glyphName]
    632 				coordinates, endPts, flags = g.getCoordinates(glyfTable)
    633 				if hasattr(compo, "firstPt"):
    634 					# move according to two reference points
    635 					x1,y1 = allCoords[compo.firstPt]
    636 					x2,y2 = coordinates[compo.secondPt]
    637 					move = x1-x2, y1-y2
    638 				else:
    639 					move = compo.x, compo.y
    640 				
    641 				coordinates = GlyphCoordinates(coordinates)
    642 				if not hasattr(compo, "transform"):
    643 					coordinates.translate(move)
    644 				else:
    645 					apple_way = compo.flags & SCALED_COMPONENT_OFFSET
    646 					ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET
    647 					assert not (apple_way and ms_way)
    648 					if not (apple_way or ms_way):
    649 						scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT  # see top of this file
    650 					else:
    651 						scale_component_offset = apple_way
    652 					if scale_component_offset:
    653 						# the Apple way: first move, then scale (ie. scale the component offset)
    654 						coordinates.translate(move)
    655 						coordinates.transform(compo.transform)
    656 					else:
    657 						# the MS way: first scale, then move
    658 						coordinates.transform(compo.transform)
    659 						coordinates.translate(move)
    660 				offset = len(allCoords)
    661 				allEndPts.extend(e + offset for e in endPts)
    662 				allCoords.extend(coordinates)
    663 				allFlags.extend(flags)
    664 			return allCoords, allEndPts, allFlags
    665 		else:
    666 			return GlyphCoordinates(), [], array.array("B")
    667 
    668 	def getComponentNames(self, glyfTable):
    669 		if not hasattr(self, "data"):
    670 			if self.isComposite():
    671 				return [c.glyphName for c in self.components]
    672 			else:
    673 				return []
    674 
    675 		# Extract components without expanding glyph
    676 
    677 		if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
    678 			return []  # Not composite
    679 
    680 		data = self.data
    681 		i = 10
    682 		components = []
    683 		more = 1
    684 		while more:
    685 			flags, glyphID = struct.unpack(">HH", data[i:i+4])
    686 			i += 4
    687 			flags = int(flags)
    688 			components.append(glyfTable.getGlyphName(int(glyphID)))
    689 
    690 			if flags & ARG_1_AND_2_ARE_WORDS: i += 4
    691 			else: i += 2
    692 			if flags & WE_HAVE_A_SCALE: i += 2
    693 			elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
    694 			elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
    695 			more = flags & MORE_COMPONENTS
    696 
    697 		return components
    698 
    699 	def removeHinting(self):
    700 		if not hasattr(self, "data"):
    701 			self.program = ttProgram.Program()
    702 			self.program.fromBytecode([])
    703 			return
    704 
    705 		# Remove instructions without expanding glyph
    706 
    707 		if not self.data:
    708 			return
    709 		numContours = struct.unpack(">h", self.data[:2])[0]
    710 		data = array.array("B", self.data)
    711 		i = 10
    712 		if numContours >= 0:
    713 			i += 2 * numContours # endPtsOfContours
    714 			nCoordinates = ((data[i-2] << 8) | data[i-1]) + 1
    715 			instructionLen = (data[i] << 8) | data[i+1]
    716 			# Zero it
    717 			data[i] = data [i+1] = 0
    718 			i += 2
    719 			if instructionLen:
    720 				# Splice it out
    721 				data = data[:i] + data[i+instructionLen:]
    722 				if instructionLen % 4:
    723 					# We now have to go ahead and drop
    724 					# the old padding.  Otherwise with
    725 					# padding we have to add, we may
    726 					# end up with more than 3 bytes of
    727 					# padding.
    728 					coordBytes = 0
    729 					j = 0
    730 					while True:
    731 						flag = data[i]
    732 						i = i + 1
    733 						repeat = 1
    734 						if flag & flagRepeat:
    735 							repeat = data[i] + 1
    736 							i = i + 1
    737 						xBytes = yBytes = 0
    738 						if flag & flagXShort:
    739 							xBytes = 1
    740 						elif not (flag & flagXsame):
    741 							xBytes = 2
    742 						if flag & flagYShort:
    743 							yBytes = 1
    744 						elif not (flag & flagYsame):
    745 							yBytes = 2
    746 						coordBytes += (xBytes + yBytes) * repeat
    747 						j += repeat
    748 						if j >= nCoordinates:
    749 							break
    750 					assert j == nCoordinates, "bad glyph flags"
    751 					data = data[:i + coordBytes]
    752 		else:
    753 			more = 1
    754 			while more:
    755 				flags =(data[i] << 8) | data[i+1]
    756 				# Turn instruction flag off
    757 				flags &= ~WE_HAVE_INSTRUCTIONS
    758 				data[i+0] = flags >> 8
    759 				data[i+1] = flags & 0xFF
    760 				i += 4
    761 				flags = int(flags)
    762 
    763 				if flags & ARG_1_AND_2_ARE_WORDS: i += 4
    764 				else: i += 2
    765 				if flags & WE_HAVE_A_SCALE: i += 2
    766 				elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
    767 				elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
    768 				more = flags & MORE_COMPONENTS
    769 
    770 			# Cut off
    771 			data = data[:i]
    772 
    773 		data = data.tostring()
    774 
    775 		if len(data) % 4:
    776 			# add pad bytes
    777 			nPadBytes = 4 - (len(data) % 4)
    778 			data = data + b"\0" * nPadBytes
    779 
    780 		self.data = data
    781 
    782 	def __ne__(self, other):
    783 		return not self.__eq__(other)
    784 	def __eq__(self, other):
    785 		if type(self) != type(other):
    786 			return NotImplemented
    787 		return self.__dict__ == other.__dict__
    788 
    789 
    790 class GlyphComponent(object):
    791 	
    792 	def __init__(self):
    793 		pass
    794 	
    795 	def getComponentInfo(self):
    796 		"""Return the base glyph name and a transform."""
    797 		# XXX Ignoring self.firstPt & self.lastpt for now: I need to implement
    798 		# something equivalent in fontTools.objects.glyph (I'd rather not 
    799 		# convert it to an absolute offset, since it is valuable information).
    800 		# This method will now raise "AttributeError: x" on glyphs that use
    801 		# this TT feature.
    802 		if hasattr(self, "transform"):
    803 			[[xx, xy], [yx, yy]] = self.transform
    804 			trans = (xx, xy, yx, yy, self.x, self.y)
    805 		else:
    806 			trans = (1, 0, 0, 1, self.x, self.y)
    807 		return self.glyphName, trans
    808 	
    809 	def decompile(self, data, glyfTable):
    810 		flags, glyphID = struct.unpack(">HH", data[:4])
    811 		self.flags = int(flags)
    812 		glyphID = int(glyphID)
    813 		self.glyphName = glyfTable.getGlyphName(int(glyphID))
    814 		#print ">>", reprflag(self.flags)
    815 		data = data[4:]
    816 		
    817 		if self.flags & ARG_1_AND_2_ARE_WORDS:
    818 			if self.flags & ARGS_ARE_XY_VALUES:
    819 				self.x, self.y = struct.unpack(">hh", data[:4])
    820 			else:
    821 				x, y = struct.unpack(">HH", data[:4])
    822 				self.firstPt, self.secondPt = int(x), int(y)
    823 			data = data[4:]
    824 		else:
    825 			if self.flags & ARGS_ARE_XY_VALUES:
    826 				self.x, self.y = struct.unpack(">bb", data[:2])
    827 			else:
    828 				x, y = struct.unpack(">BB", data[:2])
    829 				self.firstPt, self.secondPt = int(x), int(y)
    830 			data = data[2:]
    831 		
    832 		if self.flags & WE_HAVE_A_SCALE:
    833 			scale, = struct.unpack(">h", data[:2])
    834 			self.transform = [[fi2fl(scale,14), 0], [0, fi2fl(scale,14)]]  # fixed 2.14
    835 			data = data[2:]
    836 		elif self.flags & WE_HAVE_AN_X_AND_Y_SCALE:
    837 			xscale, yscale = struct.unpack(">hh", data[:4])
    838 			self.transform = [[fi2fl(xscale,14), 0], [0, fi2fl(yscale,14)]]  # fixed 2.14
    839 			data = data[4:]
    840 		elif self.flags & WE_HAVE_A_TWO_BY_TWO:
    841 			(xscale, scale01, 
    842 					scale10, yscale) = struct.unpack(">hhhh", data[:8])
    843 			self.transform = [[fi2fl(xscale,14), fi2fl(scale01,14)],
    844 					  [fi2fl(scale10,14), fi2fl(yscale,14)]] # fixed 2.14
    845 			data = data[8:]
    846 		more = self.flags & MORE_COMPONENTS
    847 		haveInstructions = self.flags & WE_HAVE_INSTRUCTIONS
    848 		self.flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS | 
    849 				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
    850 				NON_OVERLAPPING)
    851 		return more, haveInstructions, data
    852 	
    853 	def compile(self, more, haveInstructions, glyfTable):
    854 		data = b""
    855 		
    856 		# reset all flags we will calculate ourselves
    857 		flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS | 
    858 				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
    859 				NON_OVERLAPPING)
    860 		if more:
    861 			flags = flags | MORE_COMPONENTS
    862 		if haveInstructions:
    863 			flags = flags | WE_HAVE_INSTRUCTIONS
    864 		
    865 		if hasattr(self, "firstPt"):
    866 			if (0 <= self.firstPt <= 255) and (0 <= self.secondPt <= 255):
    867 				data = data + struct.pack(">BB", self.firstPt, self.secondPt)
    868 			else:
    869 				data = data + struct.pack(">HH", self.firstPt, self.secondPt)
    870 				flags = flags | ARG_1_AND_2_ARE_WORDS
    871 		else:
    872 			flags = flags | ARGS_ARE_XY_VALUES
    873 			if (-128 <= self.x <= 127) and (-128 <= self.y <= 127):
    874 				data = data + struct.pack(">bb", self.x, self.y)
    875 			else:
    876 				data = data + struct.pack(">hh", self.x, self.y)
    877 				flags = flags | ARG_1_AND_2_ARE_WORDS
    878 		
    879 		if hasattr(self, "transform"):
    880 			transform = [[fl2fi(x,14) for x in row] for row in self.transform]
    881 			if transform[0][1] or transform[1][0]:
    882 				flags = flags | WE_HAVE_A_TWO_BY_TWO
    883 				data = data + struct.pack(">hhhh", 
    884 						transform[0][0], transform[0][1],
    885 						transform[1][0], transform[1][1])
    886 			elif transform[0][0] != transform[1][1]:
    887 				flags = flags | WE_HAVE_AN_X_AND_Y_SCALE
    888 				data = data + struct.pack(">hh", 
    889 						transform[0][0], transform[1][1])
    890 			else:
    891 				flags = flags | WE_HAVE_A_SCALE
    892 				data = data + struct.pack(">h", 
    893 						transform[0][0])
    894 		
    895 		glyphID = glyfTable.getGlyphID(self.glyphName)
    896 		return struct.pack(">HH", flags, glyphID) + data
    897 	
    898 	def toXML(self, writer, ttFont):
    899 		attrs = [("glyphName", self.glyphName)]
    900 		if not hasattr(self, "firstPt"):
    901 			attrs = attrs + [("x", self.x), ("y", self.y)]
    902 		else:
    903 			attrs = attrs + [("firstPt", self.firstPt), ("secondPt", self.secondPt)]
    904 		
    905 		if hasattr(self, "transform"):
    906 			transform = self.transform
    907 			if transform[0][1] or transform[1][0]:
    908 				attrs = attrs + [
    909 						("scalex", transform[0][0]), ("scale01", transform[0][1]),
    910 						("scale10", transform[1][0]), ("scaley", transform[1][1]),
    911 						]
    912 			elif transform[0][0] != transform[1][1]:
    913 				attrs = attrs + [
    914 						("scalex", transform[0][0]), ("scaley", transform[1][1]),
    915 						]
    916 			else:
    917 				attrs = attrs + [("scale", transform[0][0])]
    918 		attrs = attrs + [("flags", hex(self.flags))]
    919 		writer.simpletag("component", attrs)
    920 		writer.newline()
    921 	
    922 	def fromXML(self, name, attrs, content, ttFont):
    923 		self.glyphName = attrs["glyphName"]
    924 		if "firstPt" in attrs:
    925 			self.firstPt = safeEval(attrs["firstPt"])
    926 			self.secondPt = safeEval(attrs["secondPt"])
    927 		else:
    928 			self.x = safeEval(attrs["x"])
    929 			self.y = safeEval(attrs["y"])
    930 		if "scale01" in attrs:
    931 			scalex = safeEval(attrs["scalex"])
    932 			scale01 = safeEval(attrs["scale01"])
    933 			scale10 = safeEval(attrs["scale10"])
    934 			scaley = safeEval(attrs["scaley"])
    935 			self.transform = [[scalex, scale01], [scale10, scaley]]
    936 		elif "scalex" in attrs:
    937 			scalex = safeEval(attrs["scalex"])
    938 			scaley = safeEval(attrs["scaley"])
    939 			self.transform = [[scalex, 0], [0, scaley]]
    940 		elif "scale" in attrs:
    941 			scale = safeEval(attrs["scale"])
    942 			self.transform = [[scale, 0], [0, scale]]
    943 		self.flags = safeEval(attrs["flags"])
    944 	
    945 	def __ne__(self, other):
    946 		return not self.__eq__(other)
    947 	def __eq__(self, other):
    948 		if type(self) != type(other):
    949 			return NotImplemented
    950 		return self.__dict__ == other.__dict__
    951 
    952 class GlyphCoordinates(object):
    953 
    954 	def __init__(self, iterable=[]):
    955 		self._a = array.array("h")
    956 		self.extend(iterable)
    957 
    958 	def isFloat(self):
    959 		return self._a.typecode == 'f'
    960 
    961 	def _ensureFloat(self):
    962 		if self.isFloat():
    963 			return
    964 		self._a = array.array("f", self._a)
    965 
    966 	def _checkFloat(self, p):
    967 		if any(isinstance(v, float) for v in p):
    968 			p = [int(v) if int(v) == v else v for v in p]
    969 			if any(isinstance(v, float) for v in p):
    970 				self._ensureFloat()
    971 		return p
    972 
    973 	@staticmethod
    974 	def zeros(count):
    975 		return GlyphCoordinates([(0,0)] * count)
    976 
    977 	def copy(self):
    978 		c = GlyphCoordinates()
    979 		c._a.extend(self._a)
    980 		return c
    981 
    982 	def __len__(self):
    983 		return len(self._a) // 2
    984 
    985 	def __getitem__(self, k):
    986 		if isinstance(k, slice):
    987 			indices = range(*k.indices(len(self)))
    988 			return [self[i] for i in indices]
    989 		return self._a[2*k],self._a[2*k+1]
    990 
    991 	def __setitem__(self, k, v):
    992 		if isinstance(k, slice):
    993 			indices = range(*k.indices(len(self)))
    994 			# XXX This only works if len(v) == len(indices)
    995 			# TODO Implement __delitem__
    996 			for j,i in enumerate(indices):
    997 				self[i] = v[j]
    998 			return
    999 		v = self._checkFloat(v)
   1000 		self._a[2*k],self._a[2*k+1] = v
   1001 
   1002 	def __repr__(self):
   1003 		return 'GlyphCoordinates(['+','.join(str(c) for c in self)+'])'
   1004 
   1005 	def append(self, p):
   1006 		p = self._checkFloat(p)
   1007 		self._a.extend(tuple(p))
   1008 
   1009 	def extend(self, iterable):
   1010 		for p in iterable:
   1011 			p = self._checkFloat(p)
   1012 			self._a.extend(p)
   1013 
   1014 	def relativeToAbsolute(self):
   1015 		a = self._a
   1016 		x,y = 0,0
   1017 		for i in range(len(a) // 2):
   1018 			a[2*i  ] = x = a[2*i  ] + x
   1019 			a[2*i+1] = y = a[2*i+1] + y
   1020 
   1021 	def absoluteToRelative(self):
   1022 		a = self._a
   1023 		x,y = 0,0
   1024 		for i in range(len(a) // 2):
   1025 			dx = a[2*i  ] - x
   1026 			dy = a[2*i+1] - y
   1027 			x = a[2*i  ]
   1028 			y = a[2*i+1]
   1029 			a[2*i  ] = dx
   1030 			a[2*i+1] = dy
   1031 
   1032 	def translate(self, p):
   1033 		(x,y) = p
   1034 		a = self._a
   1035 		for i in range(len(a) // 2):
   1036 			a[2*i  ] += x
   1037 			a[2*i+1] += y
   1038 
   1039 	def transform(self, t):
   1040 		a = self._a
   1041 		for i in range(len(a) // 2):
   1042 			x = a[2*i  ]
   1043 			y = a[2*i+1]
   1044 			px = x * t[0][0] + y * t[1][0]
   1045 			py = x * t[0][1] + y * t[1][1]
   1046 			self[i] = (px, py)
   1047 
   1048 	def __ne__(self, other):
   1049 		return not self.__eq__(other)
   1050 	def __eq__(self, other):
   1051 		if type(self) != type(other):
   1052 			return NotImplemented
   1053 		return self._a == other._a
   1054 
   1055 
   1056 def reprflag(flag):
   1057 	bin = ""
   1058 	if isinstance(flag, str):
   1059 		flag = byteord(flag)
   1060 	while flag:
   1061 		if flag & 0x01:
   1062 			bin = "1" + bin
   1063 		else:
   1064 			bin = "0" + bin
   1065 		flag = flag >> 1
   1066 	bin = (14 - len(bin)) * "0" + bin
   1067 	return bin
   1068 
   1069