import math
import string
import os


error = cvar.FreeTypeError

def getHdmxRecords(*_args, **_kwargs):
    """Returns the 'records' member as a list of TT_Hdmx_Record's. 
"""
    val = apply(freetypec.getHdmxRecords,_args,_kwargs)
    return map(TT_Hdmx_RecordPtr, val)

def TT_Init_FreeType():
    """Initialize the engine. 

  Python Note:
    Returns a TT_Engine.  You do not need to pass this function
    a pointer like you do in C.   
"""
    val = freetypec.TT_Init_FreeType()
    if val:
        val = TT_EnginePtr(val)
        val.thisown = 1
    return val

def FLOOR(val):
    """Round to the next lower multiple of 64."""
    return val & -64

def CEIL(val):
    """Round to the next highest multiple of 64."""
    return (val + 63) & -64

def rotation(theta):
    """Return a TT_Matrix that represents a counter-clockwise rotation
    of 'theta' degrees.

    [[ cos(theta)   -sin(theta) ]
     [ sin(theta)   cos(theta)  ]]

    rotation(theta) --> TT_Matrix"""

    matrix = TT_Matrix()

    angle = theta * math.pi / 180.

    matrix.xx = math.cos(angle) * 2**16
    matrix.xy = - math.sin(angle) * 2**16
    matrix.yx = - matrix.xy
    matrix.yy = matrix.xx

    return matrix

def skew(alpha, beta):
    """Return a TT_Matrix that represents a skew transformation which skews
    the x-axis by the angle 'alpha' and the y-axis by the angle 'beta'.

    [[ 1           tan(beta) ]
     [ tan(alpha)  1         ]]

    |      /
    |beta /
    |    /    
    |-->/        _.
    |  /     _.--
    | /  _.-- ^
    |/.--     | alpha
    +----------------

    skew(alpha, beta) --> TT_Matrix"""

    matrix = TT_Matrix()

    alpha = alpha * math.pi / 180.
    beta = beta * math.pi / 180.

    matrix.xx = 2**16
    matrix.xy = math.tan(alpha) * 2**16
    matrix.yx = math.tan(beta) * 2**16
    matrix.yy = 2 ** 16

    return matrix


def drawTransString(text, font='times.ttf', size=12, trans=0., format='bit'):
    """If 'trans' is a number, then the text will be rotated 'trans' 
degrees.  If trans is a TT_Matrix instance, however, the matrix 
will be applied."""

    if format not in ('bit', 'pix'):
        raise ValueError, 'format must be either "bit" or "pix"'

    if type(trans) in (type(0.), type(0), type(0L)):
        trans = rotation(trans)
    elif isinstance(trans, TT_Matrix):
        pass
    else:
        raise TypeError, 'trans must be a number or a TT_Matrix instance'
	
    text = string.expandtabs(text)

    e = TT_Init_FreeType()

    face = TT_Open_Face(e, font)

    props = TT_Get_Face_Properties(face)

    ins = TT_New_Instance(face)
    TT_Set_Instance_Resolutions(ins, 96, 96)
    TT_Set_Instance_PointSize(ins, size)

    for id in range(props.num_CharMaps):
        tup = TT_Get_CharMap_ID(face, id)
        if tup == (3,1):
            # Windows Unicode mapping
            cm = TT_Get_CharMap(face, id)
            break
       
    glyphIndices = pTT_String_Indices(cm, text)

    glyphCache = {}
    metricCache = {}
    outlineCache = {}
    bboxCache = {}
    minyMin = 0
    maxyMax = 0
    minxMin = 0
    maxxMax = 0

    x = 0
    y = 0

    offsets = []

    for i in range(len(text)):
        if not glyphCache.has_key(text[i]):
            glyph = TT_New_Glyph(face)
            TT_Load_Glyph(ins, glyph, glyphIndices[i], TTLOAD_DEFAULT)
            outline = TT_Get_Glyph_Outline(glyph)
            metric= TT_Get_Glyph_Metrics(glyph)

            TT_Transform_Outline(outline, trans)
            bbox = TT_Get_Outline_BBox(outline)

            glyphCache[text[i]] = glyph
            outlineCache[text[i]] = outline
            metricCache[text[i]] = metric
            bboxCache[text[i]] = bbox

        advanceVec = TT_Transform_Vector(metricCache[text[i]].advance, 0., trans)

        maxyMax = max(maxyMax, bboxCache[text[i]].yMax + y)
        maxxMax = max(maxxMax, bboxCache[text[i]].xMax + x)

        if i != 0:
            minyMin = min(minyMin, bboxCache[text[i]].yMin + y)
            minxMin = min(minxMin, bboxCache[text[i]].xMin + x)
        else:
            minyMin = bboxCache[text[i]].yMin + y
            minxMin = bboxCache[text[i]].xMin + x

        offsets.append((x, y))

        x = x + advanceVec[0]
        y = y + advanceVec[1]

    width = (maxxMax - minxMin) / 64
    height = (maxyMax - minyMin) / 64

    raster = TT_Raster_Map(width, height, format)

    if format == 'bit':
        for i in range(len(text)):
            TT_Translate_Outline(outlineCache[text[i]],
                                 offsets[i][0] - minxMin,
                                 offsets[i][1] - minyMin)
            TT_Get_Outline_Bitmap(e, outlineCache[text[i]], raster)
            TT_Translate_Outline(outlineCache[text[i]],
                                 -offsets[i][0] + minxMin,
                                 -offsets[i][1] + minyMin)
    elif format == 'pix':
        TT_Set_Raster_Gray_Palette(e, (0, 64, 128, 192, 255))

        for i in range(len(text)):
            TT_Translate_Outline(outlineCache[text[i]],
                                 offsets[i][0] - minxMin,
                                 offsets[i][1] - minyMin)
            TT_Get_Outline_Pixmap(e, outlineCache[text[i]], raster)
            TT_Translate_Outline(outlineCache[text[i]],
                                 -offsets[i][0] + minxMin,
                                 -offsets[i][1] + minyMin)
    return raster

def drawString(text, font='times.ttf', size=24, format='bit'):
    if format not in ('bit', 'pix'):
        raise ValueError, 'format must be either "bit" or "pix"'

    try:
        text = string.expandtabs(text)
    except TypeError:
        pass
    
    e = TT_Init_FreeType()

    face = TT_Open_Face(e, font)

    props = TT_Get_Face_Properties(face)

    ins = TT_New_Instance(face)
    TT_Set_Instance_Resolutions(ins, 96, 96)
    TT_Set_Instance_PointSize(ins, size)

    for id in range(props.num_CharMaps):
        tup = TT_Get_CharMap_ID(face, id)
        if tup == (3,1):
            # Windows Unicode mapping
            cm = TT_Get_CharMap(face, id)
            break
        
    glyphIndices = pTT_String_Indices(cm, text)
    
    glyphCache = {}
    metricCache = {}
    minyMin = 0
    maxyMax = 0

    for i in range(len(text)):
        if not glyphCache.has_key(text[i]):
            glyphCache[text[i]] = TT_New_Glyph(face)
            TT_Load_Glyph(ins, glyphCache[text[i]], glyphIndices[i], TTLOAD_DEFAULT)
            metricCache[text[i]] = TT_Get_Glyph_Metrics(glyphCache[text[i]])
            maxyMax = max(maxyMax, metricCache[text[i]].bbox.yMax)
            if i != 0:
                minyMin = min(minyMin, metricCache[text[i]].bbox.yMin)
            else:
                minyMin = metricCache[text[i]].bbox.yMin

    width = 0
    for i in text:
        width = width + metricCache[i].advance
    width = width / 64

    height = (maxyMax - minyMin) / 64

    raster = TT_Raster_Map(width, height, format)

    curX = 0

    if format == 'bit':
        for char in text:
            TT_Get_Glyph_Bitmap(glyphCache[char], raster, 
                                curX - metricCache[char].bbox.xMin, -minyMin)
            curX = curX + metricCache[char].advance

    elif format == 'pix':
        TT_Set_Raster_Gray_Palette(e, (0, 64, 128, 192, 255))

        for char in text:
            TT_Get_Glyph_Pixmap(glyphCache[char], raster, 
                                curX - metricCache[char].bbox.xMin, -minyMin)
            curX = curX + metricCache[char].advance

    return raster

def toImage(rasterMap, format='bit'):
    """Create a black-on-white PIL Image from the rasterMap.

toImage(rasterMap, format='bit') --> Image"""
    import Image

    if format == 'bit':
        im = Image.fromstring("1", (rasterMap.width, rasterMap.rows),
                              rasterMap.tostring(), "raw", "1;I")
    elif format == 'pix':
        im = Image.fromstring("L", (rasterMap.cols, rasterMap.rows),
                              rasterMap.tostring(), "raw", "L;I")
    return im

def toMask(rasterMap, format='bit'):
    """Create a PIL alpha mask (white-on-black) from the rasterMap.

toMask(rasterMap, format='bit') --> Image"""
    import Image

    if format == 'bit':
        im = Image.fromstring("1", (rasterMap.width, rasterMap.rows),
                              rasterMap.tostring(), "raw", "1")
    elif format == 'pix':
        im = Image.fromstring("L", (rasterMap.cols, rasterMap.rows),
                              rasterMap.tostring(), "raw", "L")
    return im

def renderString(image, pos, text, font, color=(0,0,0), 
		 size=12, transform=0., aa=0):
    """Render a string onto a PIL image.

renderString(image, pos, text, font, color=(0,0,0), 
             size=12, transform=0., aa=0) --> Image

'pos' is the position of the text.  'text' is the string that you want
to render.  'font' is the filename of the font.  'color' is a tuple
that represents the color you want the text to be rendered as.  'size'
is the point size of the font.  'transform' is either a number
representing the counter-clockwise angle you want to rotate the string
or a TT_Matrix instance of the transformation you want to perform.
'aa' should be True for anti-aliased text and False otherwise."""
	
    import Image

    x, y = pos

    if aa == 0:
        format = 'bit'
    else:
        format = 'pix'

    if transform == 0.:
        mask = toMask(drawString(text, font, size, format), format)
    else:
        mask = toMask(drawTransString(text, font, size, transform, format), format)

    image.paste(Image.new('RGB', mask.size, color), (pos[0], pos[1]-mask.size[1]), mask)
