# -*- indent-tabs-mode: t -*- # Soya 3D # Copyright (C) 2004 Jean-Baptiste LAMY -- jiba@tuxfamily.org # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # A big part of this file is heavily inspired by OGLFT (www.sf.net/project/oglft) # We need to rewrite it because OGLFT is written in C++ :-( cdef FT_Library library cdef int library_inited library_inited = 0 cdef class Glyph: #cdef readonly float _pixels_x1, _pixels_y1, _pixels_x2, _pixels_y2, width, height, y, x #cdef readonly unichar def __init__(self, unichar): self.unichar = unichar def __repr__(self): return "" % (self.unichar, self.width, self.height) cdef class _Font: #cdef FT_Face _face #cdef readonly filename #cdef int _width, _height #cdef _glyphs #cdef GLubyte* _pixels #cdef int _current_x, _current_y #cdef readonly int _current_height, _pixels_height #cdef int _rendering #cdef GLuint _tex_id property height: def __get__(self): return self._height property width: def __get__(self): return self._width def __init__(self, filename, int width = 20, int height = 30): import os.path if not os.path.exists(filename): raise ValueError("Cannot open font file %s", filename) self._width = width self._height = height + 4 self._pixels_height = -1 # Needed, because if the first glyph generated is " " (with a height of 0), the texture won't be resized, and OpenGL doesn't accept a texture with a zero dimension. self._glyphs = {} self.filename = filename cdef void _init(self): """Real initialization, which is not done in __init__, since we need Soya to be fully initialized to make OpenGL calls.""" glGenTextures(1, &(self._tex_id)) glBindTexture(GL_TEXTURE_2D, self._tex_id) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glBindTexture(GL_TEXTURE_2D, 0) # init FreeType global library_inited if library_inited == 0: if FT_Init_FreeType(&library): raise ImportError("Cannot init FreeType") library_inited = 1 # init FreeType face if FT_New_Face(library, self.filename, 0, &self._face): raise ValueError("Cannot open font file %s", self.filename) if FT_Set_Char_Size(self._face, 0, self._face.units_per_EM * 64, 0, 0): raise ValueError("Cannot set char size") if self._face.face_flags & FT_FACE_FLAG_SCALABLE: FT_Set_Pixel_Sizes(self._face, self._width, self._height) else: FT_Set_Pixel_Sizes(self._face, 0 , 0) # As of FreeType 2.1: only a UNICODE charmap is automatically activated. # If no charmap is activated automatically, just use the first one. # BUT I NEED A UNICODE CHARMAP !!!!! #if (self._face.charmap == 0) and (self._face.num_charmaps > 0): # FT_Select_Charmap(self._face, self._face.charmaps[0].encoding) #self._ascender = self._scale * ( self._face.ascender ) #self._descender = self._scale * ( self._face.descender) def __dealloc__(self): FT_Done_Face(self._face) glDeleteTextures(1, &(self._tex_id)) cdef Glyph _get_glyph(self, char_): cdef int code cdef Glyph glyph code = ord(char_) glyph = self._glyphs.get(code) if glyph is None: glyph = self._glyphs[code] = self._gen_glyph(char_, code) return glyph cdef _gen_glyph(self, char_, int code): cdef int glyph_index, j cdef Glyph glyph cdef FT_Bitmap bitmap cdef int g_height # Initialize if needed. if self._pixels_height == -1: self._init() if self._rendering: glEnd(); self._rendering = 0 glyph = Glyph(char_) glyph_index = FT_Get_Char_Index(self._face, code) if FT_Load_Glyph(self._face, glyph_index, FT_LOAD_DEFAULT) != 0: raise ValueError("Glyph %s does not exist !" % glyph_index) if self._face.glyph.format != FT_GLYPH_FORMAT_BITMAP: FT_Render_Glyph(self._face.glyph, FT_RENDER_MODE_NORMAL) bitmap = self._face.glyph.bitmap g_height = bitmap.rows if self._current_x + bitmap.width > MAX_TEXTURE_SIZE:# We need a new row in the texture self._current_x = 0 self._current_y = self._current_y + self._current_height + 1 self._current_height = 0 if g_height > self._current_height: self._current_height = g_height if self._current_y + g_height > self._pixels_height: # We need to extend the texture self._sizeup_pixel(self._current_y + g_height) if char_ == " ": glyph.width = self._width / 2 else: glyph.width = bitmap.width glyph.height = g_height glyph._pixels_x1 = ( self._current_x ) / ( MAX_TEXTURE_SIZE) glyph._pixels_y1 = ( self._current_y ) / ( self._pixels_height) glyph._pixels_x2 = ( self._current_x + glyph.width ) / ( MAX_TEXTURE_SIZE) glyph._pixels_y2 = ( self._current_y + glyph.height) / ( self._pixels_height) glyph.y = self._height - self._face.glyph.bitmap_top glyph.x = self._face.glyph.bitmap_left for j from 0 <= j < bitmap.rows: # get pixels memcpy(self._pixels + self._current_x + (self._current_y + j) * MAX_TEXTURE_SIZE, bitmap.buffer + bitmap.pitch * j, bitmap.pitch) self._current_x = (self._current_x + glyph.width + 5.) glBindTexture(GL_TEXTURE_2D, self._tex_id) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, MAX_TEXTURE_SIZE, self._pixels_height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, self._pixels, ) return glyph cdef void _sizeup_pixel(self, int height): if height < 32: height = 32 elif height < 64: height = 64 elif height < 128: height = 128 elif height < 256: height = 256 elif height < 512: height = 512 elif height < 1024: height = 1024 elif height < 2048: height = 2048 else: raise MemoryError("Too many characters in font %s -- no space left on texture." % self) cdef Glyph glyph for glyph in self._glyphs.values(): glyph._pixels_y1 = glyph._pixels_y1 / height * self._pixels_height glyph._pixels_y2 = glyph._pixels_y2 / height * self._pixels_height self._pixels = realloc(self._pixels, MAX_TEXTURE_SIZE * height * sizeof(GLubyte)) cdef int i # 'max' because self._pixels_height is -1 at the beginning for i from MAX_TEXTURE_SIZE * max(self._pixels_height, 0) <= i < MAX_TEXTURE_SIZE * height: self._pixels[i] = 0 self._pixels_height = height def __repr__(self): return "" % (self.filename, self._width, self._height, len(self._glyphs)) def get_glyph(self, chr): return self._get_glyph(chr) def create_glyphs(self, text): for char_ in text: self._get_glyph(char_) def get_print_size(self, text): cdef float width, height, line_width cdef Glyph glyph width = 0.0 height = self._height line_width = 0.0 for char_ in text: if char_ == "\n": height = height + self._height if width < line_width: width = line_width line_width = 0.0 else: glyph = self._get_glyph(char_) line_width = line_width + glyph.x + glyph.width if width < line_width: width = line_width return width, height def wordwrap(self, text, float width): """ return a tuple like: ( max_line_width, total_height, wrapped text ) """ cdef float height, line_width, max_line_width, char_width cdef int word_start, i, nb cdef Glyph glyph height = self._height line_width = 0.0 max_line_width = 0.0 word_start = -1 i = 0 nb = len(text) text2 = "" while i < nb: char_ = text[i] if char_ == "\n": height = height + self._height if line_width > max_line_width: max_line_width = line_width line_width = 0.0 word_start = -1 else: if char_ == " ": word_start = i glyph = self._get_glyph(char_) line_width = line_width + glyph.width + glyph.x if (line_width > width) and (word_start != -1): height = height + self._height if line_width > max_line_width: max_line_width = line_width line_width = 0.0 text2 = text2 + text[len(text2):word_start] + "\n" i = word_start word_start = -1 i = i + 1 if line_width > max_line_width: max_line_width = line_width text2 = text2 + text[len(text2):] return max_line_width, height, text2 def wordwrap_line(self, text, float width): """ return a tuple like : ( max_line_width, total_height, [ ('line1', line1width), ('line2', linewidth2) ... ] ) """ cdef float height, line_width, max_line_width, char_width cdef int word_start, i, nb, line_start cdef Glyph glyph height = self._height line_width = 0.0 max_line_width = 0.0 word_start = -1 i = 0 nb = len(text) lines = [] line_start = 0 while i < nb: char_ = text[i] if char_ == "\n": lines.append((text[line_start:i], line_width)) height = height + self._height if line_width > max_line_width: max_line_width = line_width line_width = 0.0 word_start = -1 line_start = i + 1 else: if char_ == " ": word_start = i glyph = self._get_glyph(char_) char_width = glyph.width + glyph.x line_width = line_width + char_width if (line_width > width) and (word_start != -1): lines.append((text[line_start:word_start], line_width - char_width)) height = height + self._height if line_width > max_line_width: max_line_width = line_width line_width = 0.0 line_start = word_start + 1 i = word_start word_start = -1 i = i + 1 if line_width > max_line_width: max_line_width = line_width lines.append((text[line_start:], line_width)) return width, height, lines def draw(self, text, float x, float y, float z = 0.0, int cull_face = 0): cdef Glyph glyph cdef float x_orig if cull_face == 0: glDisable(GL_CULL_FACE) glEnable (GL_TEXTURE_2D) glEnable (GL_BLEND) glBindTexture(GL_TEXTURE_2D, self._tex_id) x_orig = x self._rendering = 0 for char_ in text: if char_ == "\n": x = x_orig y = y + self._height else: glyph = self._get_glyph(char_) if self._rendering == 0: glBegin(GL_QUADS); self._rendering = 1 x = x + glyph.x glTexCoord2f(glyph._pixels_x1, glyph._pixels_y1); glVertex3f(x, y + glyph.y, z) glTexCoord2f(glyph._pixels_x1, glyph._pixels_y2); glVertex3f(x, y + glyph.y + glyph.height, z) x = x + glyph.width glTexCoord2f(glyph._pixels_x2, glyph._pixels_y2); glVertex3f(x, y + glyph.y + glyph.height, z) glTexCoord2f(glyph._pixels_x2, glyph._pixels_y1); glVertex3f(x, y + glyph.y, z) if self._rendering == 1: glEnd(); self._rendering = 0 if cull_face == 0: glEnable (GL_CULL_FACE) glDisable(GL_BLEND) glBindTexture(GL_TEXTURE_2D, 0) def draw_area(self, text, float x, float y, float z, float width, float box_height, int align): cdef Glyph glyph cdef float w, h, x_orig glDisable (GL_CULL_FACE) glEnable (GL_TEXTURE_2D) glEnable (GL_BLEND) glBindTexture(GL_TEXTURE_2D, self._tex_id) x_orig = x self._rendering = 0 if align == TEXT_ALIGN_LEFT: width, h, text = self.wordwrap(text, width) for char_ in text: if char_ == "\n": x = x_orig y = y + self._height if y >= box_height: break else: glyph = self._get_glyph(char_) if self._rendering == 0: glBegin(GL_QUADS); self._rendering = 1 x = x + glyph.x glTexCoord2f(glyph._pixels_x1, glyph._pixels_y1); glVertex3f(x, y + glyph.y, z) glTexCoord2f(glyph._pixels_x1, glyph._pixels_y2); glVertex3f(x, y + glyph.y + glyph.height, z) x = x + glyph.width glTexCoord2f(glyph._pixels_x2, glyph._pixels_y2); glVertex3f(x, y + glyph.y + glyph.height, z) glTexCoord2f(glyph._pixels_x2, glyph._pixels_y1); glVertex3f(x, y + glyph.y, z) elif align == TEXT_ALIGN_CENTER: width, h, lines = self.wordwrap_line(text, width) for line, w in lines: if not isinstance(line, unicode): line = line.decode("latin") x = x_orig + (width - w) / 2.0 for char_ in line: glyph = self._get_glyph(char_) if self._rendering == 0: glBegin(GL_QUADS); self._rendering = 1 x = x + glyph.x glTexCoord2f(glyph._pixels_x1, glyph._pixels_y1); glVertex3f(x, y + glyph.y, z) glTexCoord2f(glyph._pixels_x1, glyph._pixels_y2); glVertex3f(x, y + glyph.y + glyph.height, z) x = x + glyph.width glTexCoord2f(glyph._pixels_x2, glyph._pixels_y2); glVertex3f(x, y + glyph.y + glyph.height, z) glTexCoord2f(glyph._pixels_x2, glyph._pixels_y1); glVertex3f(x, y + glyph.y, z) y = y + self._height if y >= box_height: break if self._rendering == 1: glEnd(); self._rendering = 0 glEnable (GL_CULL_FACE) glDisable (GL_BLEND) glBindTexture(GL_TEXTURE_2D, 0) def _image(self): """Debugging stuff -- return the font loaded glyphs as a PIL image.""" import PIL.Image cdef int i, size cdef GLubyte* pixels size = MAX_TEXTURE_SIZE * self._pixels_height * 3 * sizeof(GLubyte) pixels = malloc(size) for i from 0 <= i < MAX_TEXTURE_SIZE * self._pixels_height: pixels[3 * i ] = self._pixels[i] pixels[3 * i + 1] = self._pixels[i] pixels[3 * i + 2] = self._pixels[i] image = PIL.Image.new("RGB", (MAX_TEXTURE_SIZE, self._pixels_height)) image.fromstring(PyString_FromStringAndSize( pixels, size)) return image