#    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 sys

if sys.platform == 'win32' or 'darwin':
  # we're using pillow 2.0 on win32
  from PIL import ImageFont
else:
  import ImageFont
  
from OpenGL.GL import *

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


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



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 = next_p2 (glyph_width + 1)
    height = 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

# /// A fairly straight forward function that pushes
# /// a projection matrix that will make object world 
# /// coordinates identical to window coordinates.
def pushScreenCoordinateMatrix():
    glPushAttrib(GL_TRANSFORM_BIT)
    [left,bottom,right,top] = glGetIntegerv(GL_VIEWPORT)
    glMatrixMode(GL_PROJECTION)
    glPushMatrix()
    glLoadIdentity()
    dx = (right-left)/2
    dy = (top-bottom)/2
    glOrtho( -dx, +dx, -dy, +dy, -1,+1)
    glPopAttrib()
    return


# Pops the projection matrix without changing the current
# MatrixMode.
def pop_projection_matrix():
    glPushAttrib(GL_TRANSFORM_BIT)
    glMatrixMode(GL_PROJECTION)
    glPopMatrix()
    glPopAttrib()
    return


class font_data:
    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

        # Try to obtain the FreeType font
        try:
            self.ft = 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):
            make_dlist (self.ft, i, self.m_list_base, self.textures);

        self.m_allocated = True

        return

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

        # //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)
            glScalef (scale, scale, 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()
        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
        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 arial\n"
    ft = ImageFont.truetype ("arial.ttf", 15)
    if ft:
        print "Found the TrueType font 'arial.ttf'"
    else:
        print "faild to find the TrueTYpe font 'arial'\n"
