import struct, sstruct
import string
import types


# FontRec header
nfntHeaderFormat = """
	>  # big endian
	fontType:    h     # font type
	firstChar:   h     # ASCII code of first character
	lastChar:    h     # ASCII code of last character
	widMax:      h     # maximum character width
	kernMax:     h     # negative of maximum character kern
	nDescent:    h     # negative of descent
	fRectWidth:  h     # width of font rectangle
	fRectHeight: h     # height of font rectangle
	owTLoc:      H     # offset to offset/width table (in words from _this_ point)
	ascent:      h     # ascent
	descent:     h     # descent
	leading:     h     # leading
	rowWords:    h     # row width of bit image / 2
"""
headerSize = sstruct.calcsize(nfntHeaderFormat)
assert headerSize == 26


class NFNT:
	
	def __init__(self, data=None):
		if data is not None:
			self.decompile(data)
	
	def decompile(self, data):
		# header; FontRec
		sstruct.unpack(nfntHeaderFormat, data[:headerSize], self)
		
		#assert self.fRectHeight == (self.ascent + self.descent)
		
		# rest
		tableSize = 2 * (self.lastChar - self.firstChar + 3)
		bitmapSize = 2 * self.rowWords * self.fRectHeight
		
		self.bits = data[headerSize:headerSize + bitmapSize]
		
		# XXX deal with self.nDescent being a positive number
		assert (headerSize + bitmapSize + tableSize - 16) / 2 == self.owTLoc  # ugh...
		
		locTable = data[headerSize + bitmapSize:headerSize + bitmapSize + tableSize]
		if len(locTable) <> tableSize:
			raise ValueError, 'invalid NFNT format'
		
		owTable = data[headerSize + bitmapSize + tableSize:headerSize + bitmapSize + 2 * tableSize]
		if len(owTable) <> tableSize:
			raise ValueError, 'invalid NFNT format'
		
		# fill tables
		self.offsetTable = []
		self.widthTable = []
		self.locTable = []
		for i in range(0, tableSize, 2):
			self.offsetTable.append(ord(owTable[i]))
			self.widthTable.append(ord(owTable[i+1]))
			loc, = struct.unpack("h", locTable[i:i+2])
			self.locTable.append(loc)
	
	def compile(self):
		header = sstruct.pack(nfntHeaderFormat, self)
		nEntries = len(self.widthTable)
		owTable = [None] * nEntries
		locTable = [None] * nEntries
		for i in range(nEntries):
			owTable[i] = chr(self.offsetTable[i]) + chr(self.widthTable[i])
			locTable[i] = struct.pack("h", self.locTable[i])
		owTable = string.join(owTable, "")
		locTable = string.join(locTable, "")
		assert len(locTable) == len(owTable) == 2 * (self.lastChar - self.firstChar + 3)
		return header + self.bits + locTable + owTable
	
	def unpackGlyphs(self):
		import numpy
		nGlyphs = len(self.locTable) - 1
		self.glyphs = [None] * nGlyphs
		
		rowBytes = self.rowWords * 2
		imageWidth = self.rowWords * 16
		imageHeight = self.fRectHeight
		bits = self.bits
		bitImage = numpy.zeros((imageWidth, imageHeight), numpy.int8)
		
		for y in range(imageHeight):
			for xByte in range(rowBytes):
				byte = bits[y * rowBytes + xByte]
				for xBit in range(8):
					x = 8 * xByte + xBit
					bit = (ord(byte) >> (7 - xBit)) & 0x01
					bitImage[x, y] = bit
		
		for i in range(nGlyphs):
			width = self.widthTable[i]
			offset = self.offsetTable[i]
			if width == 255 and offset == 255:
				self.glyphs[i] = None
			else:
				imageL = self.locTable[i]
				imageR = self.locTable[i+1]
				imageWidth = imageR - imageL
				offset = offset + self.kernMax
				self.glyphs[i] = glyph = Glyph(width, offset, bitImage[imageL:imageR])
	
	def packGlyphs(self):
		import numpy
		imageWidth = 0
		kernMax = 0
		imageHeight = None
		widMax = 0
		fRectWidth = 0
		for glyph in self.glyphs:
			if glyph is None:
				continue
			if imageHeight is None:
				imageHeight = glyph.pixels.shape[1]
			else:
				assert imageHeight == glyph.pixels.shape[1]
			imageWidth = imageWidth + glyph.pixels.shape[0]
			kernMax = min(kernMax, glyph.offset)
			widMax = max(widMax, glyph.width)
			fRectWidth = max(fRectWidth, glyph.pixels.shape[0] + glyph.offset)
		
		fRectWidth = fRectWidth - kernMax
		imageWidth = 16 * ((imageWidth - 1) / 16 + 1)
		rowBytes = imageWidth / 8
		rowWords = rowBytes / 2
		bitImage = numpy.zeros((imageWidth, imageHeight), numpy.int8)
		locTable = []
		widthTable = []
		offsetTable = []
		loc = 0
		for glyph in self.glyphs:
			locTable.append(loc)
			if glyph is None:
				widthTable.append(255)
				offsetTable.append(255)
				continue
			widthTable.append(glyph.width)
			offsetTable.append(glyph.offset - kernMax)
			imageWidth = glyph.pixels.shape[0]
			bitImage[loc:loc+imageWidth] = glyph.pixels
			loc = loc + imageWidth
		
		locTable.append(loc)
		widthTable.append(255)
		offsetTable.append(255)
		
		bits = []
		for y in range(imageHeight):
			for xByte in range(rowBytes):
				byte = 0
				for x in range(8):
					byte = byte | ((bitImage[8 * xByte + x, y] & 0x01) << (7 - x))
				bits.append(chr(byte))
		bits = string.join(bits, "")
		
		# assign values
		self.fontType = 0x9000
		self.lastChar = self.firstChar + len(self.glyphs) - 2
		self.widMax = widMax
		self.kernMax = kernMax
		self.descent = imageHeight - self.ascent
		self.nDescent = -self.descent
		self.fRectWidth = fRectWidth
		self.fRectHeight = imageHeight
		self.rowWords = rowWords
		
		tableSize = 2 * (self.lastChar - self.firstChar + 3)
		self.owTLoc = (headerSize + len(bits) + tableSize - 16) / 2
		
		self.bits = bits
		self.locTable = locTable
		self.widthTable = widthTable
		self.offsetTable = offsetTable
	
	def getMissing(self):
		return self.glyphs[-1]
	
	def __getitem__(self, charNum):
		if charNum > self.lastChar or charNum < 0:
			raise IndexError, "no such character"
		index = charNum - self.firstChar
		if index < 0:
			return None
		return self.glyphs[index]
	
	def __setitem__(self, charNum, glyph):
		if charNum > self.lastChar or charNum < 0:
			raise IndexError, "no such character"
		index = charNum - self.firstChar
		if index < 0:
			raise IndexError, "no such character"
		self.glyphs[index] = glyph
	
	def __len__(self):
		return len(self.locTable) - 2 + self.firstChar
	
	#
	# XXX old cruft
	#
	
	def createQdBitImage(self):
		import Qd
		self.bitImage = Qd.BitMap(self.bits, 2 * self.rowWords, (0, 0, self.rowWords * 16, self.fRectHeight))
	
	def drawstring(self, astring, destbits, xOffset=0, yOffset=0):
		drawchar = self.drawchar
		for ch in astring:
			xOffset = drawchar(ch, destbits, xOffset, yOffset)
		return xOffset
	
	def drawchar(self, ch, destbits, xOffset, yOffset=0):
		import Qd
		width, bounds, destbounds = self.getcharbounds(ch)
		destbounds = Qd.OffsetRect(destbounds, xOffset, yOffset)
		Qd.CopyBits(self.bitImage, destbits, bounds, destbounds, 1, None)
		return xOffset + width
	
	def stringwidth(self, astring):
		charwidth = self.charwidth
		width = 0
		for ch in astring:
			width = width + charwidth(ch)
		return width
	
	def charwidth(self, ch):
		cindex = ord(ch) - self.firstChar
		if cindex > self.lastChar or 	\
				(self.offsetTable[cindex] == 255 and self.widthTable[cindex] == 255):
			cindex = -2		# missing char
		return self.widthTable[cindex]
	
	def getcharbounds(self, ch):
		cindex = ord(ch) - self.firstChar
		if cindex > self.lastChar or 	\
				(self.offsetTable[cindex] == 255 and self.widthTable[cindex] == 255):
			return self.getcharboundsindex(-2)	# missing char
		return self.getcharboundsindex(cindex)
	
	def getcharboundsindex(self, cindex):
		offset = self.offsetTable[cindex]
		width = self.widthTable[cindex]
		if offset == 255 and width == 255:
			raise ValueError, "character not defined"
		location0 = self.locTable[cindex]
		location1 = self.locTable[cindex + 1]
		srcbounds = (location0, 0, location1, self.fRectHeight)
		destbounds = (	offset + self.kernMax, 
					0, 
					offset + self.kernMax + location1 - location0, 
					self.fRectHeight	)
		return width, srcbounds, destbounds


class Glyph:
	
	def __init__(self, width, offset, pixels=None, pixelDepth=1):
		self.width = width
		self.offset = offset
		self.pixelDepth = pixelDepth
		self.pixels = pixels


def dataFromFile(pathOrFSSpec, nameOrID="", resType='NFNT'):
	from Carbon import Res
	resref = Res.FSOpenResFile(pathOrFSSpec, 1)	# readonly
	try:
		Res.UseResFile(resref)
		if not nameOrID:
			# just take the first in the file
			res = Res.Get1IndResource(resType, 1)
		elif type(nameOrID) == types.IntType:
			res = Res.Get1Resource(resType, nameOrID)
		else:
			res = Res.Get1NamedResource(resType, nameOrID)
		theID, theType, name = res.GetResInfo()
		data = res.data
	finally:
		Res.CloseResFile(resref)
	return data


def fromFile(pathOrFSSpec, nameOrID="", resType='NFNT'):
	data = dataFromFile(pathOrFSSpec, nameOrID, resType)
	return NFNT(data)


if __name__ == "__main__":
	import EasyDialogs
	path = EasyDialogs.AskFileForOpen()
	if path:
		data = dataFromFile(path)
		font = NFNT(data)
		font.unpackGlyphs()
		font.packGlyphs()
		data2 = font.compile()
		print "xxxxx", data == data2, len(data) == len(data2)
