#	A quick and simple opengl font library that uses GNU freetype2, written
#	and distributed as part of a tutorial for nehe.gamedev.net.
#	Sven Olsen, 2003
#	Translated to PyOpenGL by Brian Leair, 2004
# 
#



# import freetype
# We are going to use Python Image Library's font handling
# From PIL 1.1.4:
import ImageFont
from OpenGL.GL import *
from OpenGL.GLU import *


# Python 2.2 defines these directly
try:
	True
except NameError:
	True = 1==1
	False = 1==0


class zglFreeType:
	def __init__ (self, facename, pixel_height):
		# We haven't yet allocated textures or display lists
		self.m_allocated = False
		self.m_font_height = pixel_height
		self.m_facename = facename
		
		### since I copied this from the Nehe example, older files still use glPrint
		### but I prefer the name draw
		self.glPrint = self.draw

		# Try to obtain the FreeType font
		try:
			self.font = ImageFont.truetype(facename, pixel_height)
		except:
			raise ValueError, "Unable to locate true type font '%s'" % (facename)

		# Here we ask opengl to allocate resources for
		# all the textures and displays lists which we
		# are about to create.  
		self.m_list_base = glGenLists (128)

		# Consturct a list of 128 elements. This
		# list will be assigned the texture IDs we create for each glyph
		self.textures = [None] * 128

		# This is where we actually create each of the fonts display lists.
		for i in xrange (128):
			zglFreeType.make_dlist (self.font, i, self.m_list_base, self.textures);

		self.m_allocated = True


		# //We don't need the face information now that the display
		# //lists have been created, so we free the assosiated resources.
		# Note: Don't need this, as python will decref and dealloc the ft for us.
		return

	@staticmethod
	def is_font_available (ft, facename):
		""" Returns true if FreeType can find the requested face name 
			Pass the basname of the font e.g. "arial" or "times new roman"
		"""
		if (facename in ft.available_fonts ()):
			return True
		return False
	
	
	@staticmethod
	def next_p2 (num):
		""" If num isn't a power of 2, will return the next higher power of two """
		rval = 1
		while (rval<num):
			rval <<= 1
		return rval
	
	
	
	@staticmethod
	def make_dlist (ft, ch, list_base, tex_base_list):
		""" Given an integer char code, build a GL texture into texture_array,
			build a GL display list for display list number display_list_base + ch.
			Populate the glTexture for the integer ch and construct a display
			list that renders the texture for ch.
			Note, that display_list_base and texture_base are supposed
			to be preallocated for 128 consecutive display lists and and 
			array of textures.
		"""
	
		# //The first thing we do is get FreeType to render our character
		# //into a bitmap.  This actually requires a couple of FreeType commands:
		# //Load the Glyph for our character.
		# //Move the face's glyph into a Glyph object.
		# //Convert the glyph to a bitmap.
		# //This reference will make accessing the bitmap easier
		# - This is the 2 dimensional Numeric array
	
		# Use our helper function to get the widths of
		# the bitmap data that we will need in order to create
		# our texture.
		glyph = ft.getmask (chr (ch))
		glyph_width, glyph_height = glyph.size 
		# We are using PIL's wrapping for FreeType. As a result, we don't have 
		# direct access to glyph.advance or other attributes, so we add a 1 pixel pad.
		width = zglFreeType.next_p2 (glyph_width + 1)
		height = zglFreeType.next_p2 (glyph_height + 1)
	
	
		# python GL will accept lists of integers or strings, but not Numeric arrays
		# so, we buildup a string for our glyph's texture from the Numeric bitmap 
	
		# Here we fill in the data for the expanded bitmap.
		# Notice that we are using two channel bitmap (one for
		# luminocity and one for alpha), but we assign
		# both luminocity and alpha to the value that we
		# find in the FreeType bitmap. 
		# We use the ?: operator so that value which we use
		# will be 0 if we are in the padding zone, and whatever
		# is the the Freetype bitmap otherwise.
		expanded_data = ""
		for j in xrange (height):
			for i in xrange (width):
				if (i >= glyph_width) or (j >= glyph_height):
					value = chr (0)
					expanded_data += value
					expanded_data += value
				else:
					value = chr (glyph.getpixel ((i, j)))
					expanded_data += value
					expanded_data += value
	
		# -------------- Build the gl texture ------------
	
		# Now we just setup some texture paramaters.
		ID = glGenTextures (1)
		tex_base_list [ch] = ID
		glBindTexture (GL_TEXTURE_2D, ID)
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
	
		border = 0
		# Here we actually create the texture itself, notice
		# that we are using GL_LUMINANCE_ALPHA to indicate that
		# we are using 2 channel data.
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height,
			border, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data )
	
		# With the texture created, we don't need to expanded data anymore
		expanded_data = None
	
	
	
		# --- Build the gl display list that draws the texture for this character ---
	
		# So now we can create the display list
		glNewList (list_base + ch, GL_COMPILE)
	
		if (ch == ord (" ")):
			glyph_advance = glyph_width
			glTranslatef(glyph_advance, 0, 0)
			glEndList()
		else:
	
			glBindTexture (GL_TEXTURE_2D, ID)
	
			glPushMatrix()
	
			# // first we need to move over a little so that
			# // the character has the right amount of space
			# // between it and the one before it.
			# glyph_left = glyph.bbox [0]
			# glTranslatef(glyph_left, 0, 0)
	
			# // Now we move down a little in the case that the
			# // bitmap extends past the bottom of the line 
			# // this is only true for characters like 'g' or 'y'.
			# glyph_descent = glyph.decent
			# glTranslatef(0, glyph_descent, 0)
	
			# //Now we need to account for the fact that many of
			# //our textures are filled with empty padding space.
			# //We figure what portion of the texture is used by 
			# //the actual character and store that information in 
			# //the x and y variables, then when we draw the
			# //quad, we will only reference the parts of the texture
			# //that we contain the character itself.
			x=float (glyph_width) / float (width)
			y=float (glyph_height) / float (height)
	
			# //Here we draw the texturemaped quads.
			# //The bitmap that we got from FreeType was not 
			# //oriented quite like we would like it to be,
			# //so we need to link the texture to the quad
			# //so that the result will be properly aligned.
			glBegin(GL_QUADS)
			glTexCoord2f(0,0), glVertex2f(0,glyph_height)
			glTexCoord2f(0,y), glVertex2f(0,0)
			glTexCoord2f(x,y), glVertex2f(glyph_width,0)
			glTexCoord2f(x,0), glVertex2f(glyph_width, glyph_height)
			glEnd()
			glPopMatrix()
	
			# Note, PIL's FreeType interface hides the advance from us.
			# Normal PIL clients are rendering an entire string through FreeType, not
			# a single character at a time like we are doing here.
			# Because the advance value is hidden from we will advance
			# the "pen" based upon the rendered glyph's width. This is imperfect.
			glTranslatef(glyph_width + 0.75, 0, 0)
	
			# //increment the raster position as if we were a bitmap font.
			# //(only needed if you want to calculate text length)
			# //glBitmap(0,0,0,0,face->glyph->advance.x >> 6,0,NULL)
	
			# //Finnish the display list
			glEndList()
	
		return

	# Get the length of a certain string by looking it up in our lookup table
	def getSize(self, string):
		return self.font.getsize(string)
	
	# /// A fairly straight forward function that pushes
	# /// a projection matrix that will make object world 
	# /// coordinates identical to window coordinates.
	@staticmethod
	def pushScreenCoordinateMatrix():
		glPushAttrib(GL_TRANSFORM_BIT)
		viewport = glGetIntegerv(GL_VIEWPORT)
		glMatrixMode(GL_PROJECTION)
		glPushMatrix()
		glLoadIdentity()
		gluOrtho2D(viewport[0],viewport[2],viewport[1],viewport[3])
		glPopAttrib()
		return
	
	
	# Pops the projection matrix without changing the current
	# MatrixMode.
	@staticmethod
	def pop_projection_matrix():
		glPushAttrib(GL_TRANSFORM_BIT)
		glMatrixMode(GL_PROJECTION)
		glPopMatrix()
		glPopAttrib()
		return
	
	# Alternative to draw. Same function definition as
	# FTGL fonts so zglText can be compatible with either
	#
	def draw (self, string):
		self.draw(0, 0, string)
	

	def draw (self, x, y, string):
		"""
		# ///Much like Nehe's glPrint function, but modified to work
		# ///with freetype fonts.
		"""
		# If There's No Text
		# Do Nothing
		if (string == None):
			return
		if (string == ""):
			return
			
		# We want a coordinate system where things coresponding to window pixels.
		zglFreeType.pushScreenCoordinateMatrix()
	
		# //We make the height about 1.5* that of
		h = float (self.m_font_height) / 0.63		
	

		# //Here is some code to split the text that we have been
		# //given into a set of lines.  
		# //This could be made much neater by using
		# //a regular expression library such as the one avliable from
		# //boost.org (I've only done it out by hand to avoid complicating
		# //this tutorial with unnecessary library dependencies).
		# //Note: python string object has convenience method for this :)
		lines = string.split ("\n")

		glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT  | GL_ENABLE_BIT | GL_TRANSFORM_BIT)
		glMatrixMode(GL_MODELVIEW)
		glDisable(GL_LIGHTING)
		glEnable(GL_TEXTURE_2D)
		glDisable(GL_DEPTH_TEST)
		glEnable(GL_BLEND)
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

		glListBase(self.m_list_base)
		modelview_matrix = glGetFloatv(GL_MODELVIEW_MATRIX)

		# //This is where the text display actually happens.
		# //For each line of text we reset the modelview matrix
		# //so that the line's text will start in the correct position.
		# //Notice that we need to reset the matrix, rather than just translating
		# //down by h. This is because when each character is
		# //draw it modifies the current matrix so that the next character
		# //will be drawn immediatly after it.  
		for i in xrange (len (lines)):
			line = lines [i]

			glPushMatrix ()
			glLoadIdentity ()
			glTranslatef (x,y-h*i,0);
			glMultMatrixf (modelview_matrix);

			# //  The commented out raster position stuff can be useful if you need to
			# //  know the length of the text that you are creating.
			# //  If you decide to use it make sure to also uncomment the glBitmap command
			# //  in make_dlist().
			# //	glRasterPos2f(0,0);
			glCallLists (line)
			# //	rpos = glGetFloatv (GL_CURRENT_RASTER_POSITION)
			# //	float len=x-rpos[0];
			glPopMatrix()

		glPopAttrib()
		zglFreeType.pop_projection_matrix()
		return

	def release (self):
		""" Release the gl resources for this Face.
			(This provides the functionality of KillFont () and font_data::clean ()
		"""
		if (self.m_allocated):
			# Free up the glTextures and the display lists for our face
			glDeleteLists ( self.m_list_base, 128);
			for ID in self.textures:
				glDeleteTextures (ID);
			# Extra defensive. Clients that continue to try and use this object
			# will now trigger exceptions.
			self.list_base = None
			self.m_allocated = False
		self.font = None
		return

	def __del__ (self):
		""" Python destructor for when no more refs to this Face object """
		self.release ()
		return


# Unit Test harness if this python module is run directly.
if __name__ == "__main__":
	print "testing availability of freetype font\n"
#	font_name = "arial.ttf"
	font_name = "FreeUniversal-Regular.ttf"
#	ft = ImageFont.load_default()
	ft = ImageFont.truetype(font_name, 15)
	if ft:
		print "Found the TrueType font "+font_name
	else:
		print "faild to find the TrueTYpe font 'arial'\n"
