summaryrefslogtreecommitdiff
path: root/third_party/freetype-py/examples/texture_font.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/freetype-py/examples/texture_font.py')
-rw-r--r--third_party/freetype-py/examples/texture_font.py421
1 files changed, 421 insertions, 0 deletions
diff --git a/third_party/freetype-py/examples/texture_font.py b/third_party/freetype-py/examples/texture_font.py
new file mode 100644
index 0000000..8abdd2e
--- /dev/null
+++ b/third_party/freetype-py/examples/texture_font.py
@@ -0,0 +1,421 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+#
+# FreeType high-level python API - Copyright 2011 Nicolas P. Rougier
+# Distributed under the terms of the new BSD license.
+#
+# -----------------------------------------------------------------------------
+'''
+Texture font class
+
+'''
+import sys
+import math
+import numpy as np
+import OpenGL.GL as gl
+from freetype import *
+
+
+class TextureAtlas:
+ '''
+ Group multiple small data regions into a larger texture.
+
+ The algorithm is based on the article by Jukka Jylänki : "A Thousand Ways
+ to Pack the Bin - A Practical Approach to Two-Dimensional Rectangle Bin
+ Packing", February 27, 2010. More precisely, this is an implementation of
+ the Skyline Bottom-Left algorithm based on C++ sources provided by Jukka
+ Jylänki at: http://clb.demon.fi/files/RectangleBinPack/
+
+ Example usage:
+ --------------
+
+ atlas = TextureAtlas(512,512,3)
+ region = atlas.get_region(20,20)
+ ...
+ atlas.set_region(region, data)
+ '''
+
+ def __init__(self, width=1024, height=1024, depth=1):
+ '''
+ Initialize a new atlas of given size.
+
+ Parameters
+ ----------
+
+ width : int
+ Width of the underlying texture
+
+ height : int
+ Height of the underlying texture
+
+ depth : 1 or 3
+ Depth of the underlying texture
+ '''
+ self.width = int(math.pow(2, int(math.log(width, 2) + 0.5)))
+ self.height = int(math.pow(2, int(math.log(height, 2) + 0.5)))
+ self.depth = depth
+ self.nodes = [ (0,0,self.width), ]
+ self.data = np.zeros((self.height, self.width, self.depth),
+ dtype=np.ubyte)
+ self.texid = 0
+ self.used = 0
+
+
+
+ def upload(self):
+ '''
+ Upload atlas data into video memory.
+ '''
+
+ if not self.texid:
+ self.texid = gl.glGenTextures(1)
+
+ gl.glBindTexture( gl.GL_TEXTURE_2D, self.texid )
+ gl.glTexParameteri( gl.GL_TEXTURE_2D,
+ gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP )
+ gl.glTexParameteri( gl.GL_TEXTURE_2D,
+ gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP )
+ gl.glTexParameteri( gl.GL_TEXTURE_2D,
+ gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR )
+ gl.glTexParameteri( gl.GL_TEXTURE_2D,
+ gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR )
+ if self.depth == 1:
+ gl.glTexImage2D( gl.GL_TEXTURE_2D, 0, gl.GL_ALPHA,
+ self.width, self.height, 0,
+ gl.GL_ALPHA, gl.GL_UNSIGNED_BYTE, self.data )
+ else:
+ gl.glTexImage2D( gl.GL_TEXTURE_2D, 0, gl.GL_RGB,
+ self.width, self.height, 0,
+ gl.GL_RGB, gl.GL_UNSIGNED_BYTE, self.data )
+
+
+
+ def set_region(self, region, data):
+ '''
+ Set a given region width provided data.
+
+ Parameters
+ ----------
+
+ region : (int,int,int,int)
+ an allocated region (x,y,width,height)
+
+ data : numpy array
+ data to be copied into given region
+ '''
+
+ x, y, width, height = region
+ self.data[y:y+height,x:x+width, :] = data
+
+
+
+ def get_region(self, width, height):
+ '''
+ Get a free region of given size and allocate it
+
+ Parameters
+ ----------
+
+ width : int
+ Width of region to allocate
+
+ height : int
+ Height of region to allocate
+
+ Return
+ ------
+ A newly allocated region as (x,y,width,height) or (-1,-1,0,0)
+ '''
+
+ best_height = sys.maxint
+ best_index = -1
+ best_width = sys.maxint
+ region = 0, 0, width, height
+
+ for i in range(len(self.nodes)):
+ y = self.fit(i, width, height)
+ if y >= 0:
+ node = self.nodes[i]
+ if (y+height < best_height or
+ (y+height == best_height and node[2] < best_width)):
+ best_height = y+height
+ best_index = i
+ best_width = node[2]
+ region = node[0], y, width, height
+
+ if best_index == -1:
+ return -1,-1,0,0
+
+ node = region[0], region[1]+height, width
+ self.nodes.insert(best_index, node)
+
+ i = best_index+1
+ while i < len(self.nodes):
+ node = self.nodes[i]
+ prev_node = self.nodes[i-1]
+ if node[0] < prev_node[0]+prev_node[2]:
+ shrink = prev_node[0]+prev_node[2] - node[0]
+ x,y,w = self.nodes[i]
+ self.nodes[i] = x+shrink, y, w-shrink
+ if self.nodes[i][2] <= 0:
+ del self.nodes[i]
+ i -= 1
+ else:
+ break
+ else:
+ break
+ i += 1
+
+ self.merge()
+ self.used += width*height
+ return region
+
+
+
+ def fit(self, index, width, height):
+ '''
+ Test if region (width,height) fit into self.nodes[index]
+
+ Parameters
+ ----------
+
+ index : int
+ Index of the internal node to be tested
+
+ width : int
+ Width or the region to be tested
+
+ height : int
+ Height or the region to be tested
+
+ '''
+
+ node = self.nodes[index]
+ x,y = node[0], node[1]
+ width_left = width
+
+ if x+width > self.width:
+ return -1
+
+ i = index
+ while width_left > 0:
+ node = self.nodes[i]
+ y = max(y, node[1])
+ if y+height > self.height:
+ return -1
+ width_left -= node[2]
+ i += 1
+ return y
+
+
+
+ def merge(self):
+ '''
+ Merge nodes
+ '''
+
+ i = 0
+ while i < len(self.nodes)-1:
+ node = self.nodes[i]
+ next_node = self.nodes[i+1]
+ if node[1] == next_node[1]:
+ self.nodes[i] = node[0], node[1], node[2]+next_node[2]
+ del self.nodes[i+1]
+ else:
+ i += 1
+
+
+class TextureFont:
+ '''
+ A texture font gathers a set of glyph relatively to a given font filename
+ and size.
+ '''
+
+ def __init__(self, atlas, filename, size):
+ '''
+ Initialize font
+
+ Parameters:
+ -----------
+
+ atlas: TextureAtlas
+ Texture atlas where glyph texture will be stored
+
+ filename: str
+ Font filename
+
+ size : float
+ Font size
+ '''
+ self.atlas = atlas
+ self.filename = filename
+ self.size = size
+ self.glyphs = {}
+ face = Face( self.filename )
+ face.set_char_size( int(self.size*64))
+ self._dirty = False
+ metrics = face.size
+ self.ascender = metrics.ascender/64.0
+ self.descender = metrics.descender/64.0
+ self.height = metrics.height/64.0
+ self.linegap = self.height - self.ascender + self.descender
+ self.depth = atlas.depth
+ set_lcd_filter(FT_LCD_FILTER_LIGHT)
+
+
+ def __getitem__(self, charcode):
+ '''
+ x.__getitem__(y) <==> x[y]
+ '''
+ if charcode not in self.glyphs.keys():
+ self.load('%c' % charcode)
+ return self.glyphs[charcode]
+
+
+
+ def get_texid(self):
+ '''
+ Get underlying texture identity .
+ '''
+
+ if self._dirty:
+ self.atlas.upload()
+ self._dirty = False
+ return self.atlas.texid
+
+ texid = property(get_texid,
+ doc='''Underlying texture identity.''')
+
+
+
+ def load(self, charcodes = ''):
+ '''
+ Build glyphs corresponding to individual characters in charcodes.
+
+ Parameters:
+ -----------
+
+ charcodes: [str | unicode]
+ Set of characters to be represented
+ '''
+ face = Face( self.filename )
+ pen = Vector(0,0)
+ hres = 16*72
+ hscale = 1.0/16
+
+ for charcode in charcodes:
+ face.set_char_size( int(self.size * 64), 0, hres, 72 )
+ matrix = Matrix( int((hscale) * 0x10000L), int((0.0) * 0x10000L),
+ int((0.0) * 0x10000L), int((1.0) * 0x10000L) )
+ face.set_transform( matrix, pen )
+ if charcode in self.glyphs.keys():
+ continue
+
+ self.dirty = True
+ flags = FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT
+ flags |= FT_LOAD_TARGET_LCD
+
+ face.load_char( charcode, flags )
+ bitmap = face.glyph.bitmap
+ left = face.glyph.bitmap_left
+ top = face.glyph.bitmap_top
+ width = face.glyph.bitmap.width
+ rows = face.glyph.bitmap.rows
+ pitch = face.glyph.bitmap.pitch
+
+ x,y,w,h = self.atlas.get_region(width/self.depth+2, rows+2)
+ if x < 0:
+ print 'Missed !'
+ continue
+ x,y = x+1, y+1
+ w,h = w-2, h-2
+ data = []
+ for i in range(rows):
+ data.extend(bitmap.buffer[i*pitch:i*pitch+width])
+ data = np.array(data,dtype=np.ubyte).reshape(h,w,3)
+ gamma = 1.5
+ Z = ((data/255.0)**(gamma))
+ data = (Z*255).astype(np.ubyte)
+ self.atlas.set_region((x,y,w,h), data)
+
+ # Build glyph
+ size = w,h
+ offset = left, top
+ advance= face.glyph.advance.x, face.glyph.advance.y
+
+ u0 = (x + 0.0)/float(self.atlas.width)
+ v0 = (y + 0.0)/float(self.atlas.height)
+ u1 = (x + w - 0.0)/float(self.atlas.width)
+ v1 = (y + h - 0.0)/float(self.atlas.height)
+ texcoords = (u0,v0,u1,v1)
+ glyph = TextureGlyph(charcode, size, offset, advance, texcoords)
+ self.glyphs[charcode] = glyph
+
+ # Generate kerning
+ for g in self.glyphs.values():
+ # 64 * 64 because of 26.6 encoding AND the transform matrix used
+ # in texture_font_load_face (hres = 64)
+ kerning = face.get_kerning(g.charcode, charcode, mode=FT_KERNING_UNFITTED)
+ if kerning.x != 0:
+ glyph.kerning[g.charcode] = kerning.x/(64.0*64.0)
+ kerning = face.get_kerning(charcode, g.charcode, mode=FT_KERNING_UNFITTED)
+ if kerning.x != 0:
+ g.kerning[charcode] = kerning.x/(64.0*64.0)
+
+ # High resolution advance.x calculation
+ # gindex = face.get_char_index( charcode )
+ # a = face.get_advance(gindex, FT_LOAD_RENDER | FT_LOAD_TARGET_LCD)/(64*72)
+ # glyph.advance = a, glyph.advance[1]
+
+
+class TextureGlyph:
+ '''
+ A texture glyph gathers information relative to the size/offset/advance and
+ texture coordinates of a single character. It is generally built
+ automatically by a TextureFont.
+ '''
+
+ def __init__(self, charcode, size, offset, advance, texcoords):
+ '''
+ Build a new texture glyph
+
+ Parameter:
+ ----------
+
+ charcode : char
+ Represented character
+
+ size: tuple of 2 ints
+ Glyph size in pixels
+
+ offset: tuple of 2 floats
+ Glyph offset relatively to anchor point
+
+ advance: tuple of 2 floats
+ Glyph advance
+
+ texcoords: tuple of 4 floats
+ Texture coordinates of bottom-left and top-right corner
+ '''
+ self.charcode = charcode
+ self.size = size
+ self.offset = offset
+ self.advance = advance
+ self.texcoords = texcoords
+ self.kerning = {}
+
+
+ def get_kerning(self, charcode):
+ ''' Get kerning information
+
+ Parameters:
+ -----------
+
+ charcode: char
+ Character preceding this glyph
+ '''
+ if charcode in self.kerning.keys():
+ return self.kerning[charcode]
+ else:
+ return 0