/* * OGLFT: A library for drawing text with OpenGL using the FreeType library * Copyright (C) 2002 lignum Computing, Inc. * $Id: OGLFT.cpp,v 1.11 2003/10/01 14:21:18 allen Exp $ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include "OGLFT.h" int wstrlen(const wchar_t * s) { int r = 0; while (*s++) r++; return r; } namespace OGLFT { // This is the static instance of the FreeType library wrapper ... Library Library::library; // ... and this is the FreeType library handle itself. FT_Library Library::library_; // The static instance above causes this constructor to be called // when the object module is loaded. Library::Library (void) { FT_Error error = FT_Init_FreeType(&library_); if(error != 0) std::cerr << "[OGLFT] Could not initialize the FreeType library." << std::endl; } Library::~Library (void) { FT_Error error = FT_Done_FreeType(library_); if(error != 0) std::cerr << "[OGLFT] Could not terminate the FreeType library." << std::endl; } // Return the only instance in the process FT_Library& Library::instance (void) { return library_; } // Load a new face Face::Face (const char* filename, float point_size, FT_UInt resolution) : point_size_(point_size), resolution_(resolution) { valid_ = true; FT_Face ft_face; FT_Error error = FT_New_Face(Library::instance(), filename, 0, &ft_face); if(error != 0) { valid_ = false; return; } // As of FreeType 2.1: only a UNICODE charmap is automatically activated. // If no charmap is activated automatically, just use the first one. if(ft_face->charmap == 0 && ft_face->num_charmaps > 0) FT_Select_Charmap(ft_face, ft_face->charmaps[0]->encoding); faces_.push_back(FaceData(ft_face)); init(); } // Go with a face that the user has already opened. Face::Face (FT_Face face, float point_size, FT_UInt resolution) : point_size_(point_size), resolution_(resolution) { valid_ = true; // As of FreeType 2.1: only a UNICODE charmap is automatically activated. // If no charmap is activated automatically, just use the first one. if(face->charmap == 0 && face->num_charmaps > 0) FT_Select_Charmap(face, face->charmaps[0]->encoding); faces_.push_back(FaceData(face, false)); init(); } // Standard initialization behavior once the font file is opened. void Face::init (void) { // By default, each glyph is compiled into a display list the first // time it is encountered compile_mode_ = COMPILE; // By default, all drawing is wrapped with push/pop matrix so that the // MODELVIEW matrix is not modified. If advance_ is set, then subsequent // drawings follow from the advance of the last glyph rendered. advance_ = false; // Initialize the default colors foreground_color_[R] = 0.; foreground_color_[G] = 0.; foreground_color_[B] = 0.; foreground_color_[A] = 1.; background_color_[R] = 1.; background_color_[G] = 1.; background_color_[B] = 1.; background_color_[A] = 0.; // The default positioning of the text is at the origin of the first glyph horizontal_justification_ = ORIGIN; vertical_justification_ = BASELINE; // By default, strings are rendered in their nominal direction string_rotation_ = 0; // setCharacterRotationReference calls the virtual function clearCaches() // so it is up to a subclass to set the real default rotation_reference_glyph_ = 0; rotation_reference_face_ = 0; rotation_offset_y_ = 0.; } Face::~Face (void) { for(unsigned int i=0; iglyph, &glyph); if(error != 0) continue; FT_BBox ft_bbox; FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_unscaled, &ft_bbox); FT_Done_Glyph(glyph); char_bbox = ft_bbox; char_bbox.advance_ = faces_[f].face_->glyph->advance; bbox += char_bbox; } return bbox; } BBox Face::measure (const wchar_t* s) { BBox bbox; if(wstrlen(s) > 0) { bbox = measure(s[0]); for(unsigned int i = 1; iglyph, &glyph); if(error != 0) continue; FT_BBox ft_bbox; FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_unscaled, &ft_bbox); FT_Done_Glyph(glyph); char_bbox = ft_bbox; char_bbox.advance_ = faces_[f].face_->glyph->advance; bbox += char_bbox; } return bbox; } // Measure the bounding box as if the (latin1) string were not rotated BBox Face::measure_nominal (const char* s) { if(string_rotation_ == 0.) return measure(s); for(unsigned int f=0; fsecond; unsigned int f; FT_UInt glyph_index = 0; for(f=0; fsecond; unsigned int f; FT_UInt glyph_index = 0; for(f=0; fsecond); return; } unsigned int f; FT_UInt glyph_index = 0; for(f=0; fsecond); return; } unsigned int f; FT_UInt glyph_index = 0; for(f=0; fheight > 0) return faces_[0].face_->height / 64.; else return faces_[0].face_->size->metrics.y_ppem; } BBox Raster::measure (unsigned char c) { BBox bbox; // For starters, just get the unscaled glyph bounding box unsigned int f; FT_UInt glyph_index = 0; for(f=0; fglyph, &glyph); if(error != 0) return bbox; FT_BBox ft_bbox; FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_unscaled, &ft_bbox); FT_Done_Glyph(glyph); bbox = ft_bbox; bbox.advance_ = faces_[f].face_->glyph->advance; // In order to be accurate regarding the placement of text not // aligned at the glyph's origin (CENTER/MIDDLE), the bounding box // of the raster format has to be projected back into the // view's coordinates GLint viewport[4]; GLdouble modelview[16], projection[16]; glGetIntegerv(GL_VIEWPORT, viewport); glGetDoublev(GL_MODELVIEW_MATRIX, modelview); glGetDoublev(GL_PROJECTION_MATRIX, projection); // Well, first we have to get the Origin, since that is the basis // of the bounding box GLdouble x0, y0, z0; gluUnProject(0., 0., 0., modelview, projection, viewport, &x0, &y0, &z0); GLdouble x, y, z; gluUnProject(bbox.x_min_, bbox.y_min_, 0., modelview, projection, viewport, &x, &y, &z); bbox.x_min_ = x - x0; bbox.y_min_ = y - y0; gluUnProject(bbox.x_max_, bbox.y_max_, 0., modelview, projection, viewport, &x, &y, &z); bbox.x_max_ = x - x0; bbox.y_max_ = y - y0; gluUnProject(bbox.advance_.dx_, bbox.advance_.dy_, 0., modelview, projection, viewport, &x, &y, &z); bbox.advance_.dx_ = x - x0; bbox.advance_.dy_ = y - y0; return bbox; } BBox Raster::measure (const wchar_t c) { BBox bbox; // For starters, just get the unscaled glyph bounding box unsigned int f; FT_UInt glyph_index = 0; for(f=0; fglyph, &glyph); if(error != 0) return bbox; FT_BBox ft_bbox; FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_unscaled, &ft_bbox); FT_Done_Glyph(glyph); bbox = ft_bbox; bbox.advance_ = faces_[f].face_->glyph->advance; // In order to be accurate regarding the placement of text not // aligned at the glyph's origin (CENTER/MIDDLE), the bounding box // of the raster format has to be projected back into the // view's coordinates GLint viewport[4]; GLdouble modelview[16], projection[16]; glGetIntegerv(GL_VIEWPORT, viewport); glGetDoublev(GL_MODELVIEW_MATRIX, modelview); glGetDoublev(GL_PROJECTION_MATRIX, projection); // Well, first we have to get the Origin, since that is the basis // of the bounding box GLdouble x0, y0, z0; gluUnProject(0., 0., 0., modelview, projection, viewport, &x0, &y0, &z0); GLdouble x, y, z; gluUnProject(bbox.x_min_, bbox.y_min_, 0., modelview, projection, viewport, &x, &y, &z); bbox.x_min_ = x - x0; bbox.y_min_ = y - y0; gluUnProject(bbox.x_max_, bbox.y_max_, 0., modelview, projection, viewport, &x, &y, &z); bbox.x_max_ = x - x0; bbox.y_max_ = y - y0; gluUnProject(bbox.advance_.dx_, bbox.advance_.dy_, 0., modelview, projection, viewport, &x, &y, &z); bbox.advance_.dx_ = x - x0; bbox.advance_.dy_ = y - y0; return bbox; } GLuint Raster::compileGlyph (FT_Face face, FT_UInt glyph_index) { GLuint dlist = glGenLists(1); glNewList(dlist, GL_COMPILE); renderGlyph(face, glyph_index); glEndList(); return dlist; } void Raster::setCharSize (void) { FT_Error error; for(unsigned int i=0; iglyph->bitmap.rows / 2.; } void Raster::clearCaches (void) { GDLI fgi = glyph_dlists_.begin(); for(; fgi != glyph_dlists_.end(); ++fgi) { glDeleteLists(fgi->second, 1); } glyph_dlists_.clear(); } Monochrome::Monochrome (const char* filename, float point_size, FT_UInt resolution) : Raster(filename, point_size, resolution) { return; } Monochrome::Monochrome (FT_Face face, float point_size, FT_UInt resolution) : Raster(face, point_size, resolution) { return; } Monochrome::~Monochrome (void) { return; } GLubyte* Monochrome::invertBitmap (const FT_Bitmap& bitmap) { // In FreeType 2.0.9, the pitch of bitmaps was rounded up to an // even number. In general, this disagrees with what we had been // using for OpenGL. int width = bitmap.width / 8 + ((bitmap.width & 7)> 0 ? 1 : 0); GLubyte* inverse = new GLubyte[ bitmap.rows * width ]; GLubyte* inverse_ptr = inverse; for(int r=0; rglyph, &original_glyph); if(error != 0) return; error = FT_Glyph_Copy(original_glyph, &glyph); FT_Done_Glyph(original_glyph); if(error != 0) return; // If the individual characters are rotated (as distinct from string // rotation), then apply that extra rotation here. This is equivalent // to the sequence // glTranslate(x_center,y_center); // glRotate(angle); // glTranslate(-x_center,-y_center); // which is used for the polygonal styles. The deal with the raster // styles is that you must retain the advance from the string rotation // so that the glyphs are laid out properly. So, we make a copy of // the string rotated glyph, and then rotate that and add back an // additional offset to (in effect) restore the proper origin and // advance of the glyph. if(character_rotation_z_ != 0.) { FT_Matrix rotation_matrix; FT_Vector sinus; FT_Vector_Unit(&sinus, (FT_Angle)(character_rotation_z_ * 0x10000L)); rotation_matrix.xx = sinus.x; rotation_matrix.xy = -sinus.y; rotation_matrix.yx = sinus.y; rotation_matrix.yy = sinus.x; FT_Vector original_offset, rotation_offset; original_offset.x = (face->glyph->metrics.width / 2 + face->glyph->metrics.horiBearingX)/ 64 * 0x10000L; original_offset.y = (FT_Pos)(rotation_offset_y_ * 0x10000L); rotation_offset = original_offset; FT_Vector_Rotate(&rotation_offset, (FT_Angle)(character_rotation_z_ * 0x10000L)); rotation_offset.x = original_offset.x - rotation_offset.x; rotation_offset.y = original_offset.y - rotation_offset.y; rotation_offset.x /= 1024; rotation_offset.y /= 1024; error = FT_Glyph_Transform(glyph, &rotation_matrix, &rotation_offset); } error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_MONO, 0, 1); if(error != 0) { FT_Done_Glyph(glyph); return; } FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph) glyph; // Evidently, in FreeType2, you can only get "upside-down" bitmaps and // OpenGL won't invert a bitmap with PixelZoom, so we have to invert the // glyph's bitmap ourselves. GLubyte* inverted_bitmap = invertBitmap(bitmap_glyph->bitmap); glBitmap(bitmap_glyph->bitmap.width, bitmap_glyph->bitmap.rows, -bitmap_glyph->left, bitmap_glyph->bitmap.rows - bitmap_glyph->top, face->glyph->advance.x / 64., face->glyph->advance.y / 64., inverted_bitmap); FT_Done_Glyph(glyph); delete[] inverted_bitmap; } Grayscale::Grayscale (const char* filename, float point_size, FT_UInt resolution) : Raster(filename, point_size, resolution) { return; } Grayscale::Grayscale (FT_Face face, float point_size, FT_UInt resolution) : Raster(face, point_size, resolution) { return; } Grayscale::~Grayscale (void) { return; } GLubyte* Grayscale::invertPixmap (const FT_Bitmap& bitmap) { GLubyte* inverse = new GLubyte[ bitmap.rows * bitmap.pitch ]; GLubyte* inverse_ptr = inverse; for(int r=0; rglyph, &original_glyph); if(error != 0) return; error = FT_Glyph_Copy(original_glyph, &glyph); FT_Done_Glyph(original_glyph); if(error != 0) return; if(character_rotation_z_ != 0.) { FT_Matrix rotation_matrix; FT_Vector sinus; FT_Vector_Unit(&sinus, (FT_Angle)(character_rotation_z_ * 0x10000L)); rotation_matrix.xx = sinus.x; rotation_matrix.xy = -sinus.y; rotation_matrix.yx = sinus.y; rotation_matrix.yy = sinus.x; FT_Vector original_offset, rotation_offset; original_offset.x = (face->glyph->metrics.width / 2 + face->glyph->metrics.horiBearingX)/ 64 * 0x10000L; original_offset.y = (FT_Pos)(rotation_offset_y_ * 0x10000L); rotation_offset = original_offset; FT_Vector_Rotate(&rotation_offset, (FT_Angle)(character_rotation_z_ * 0x10000L)); rotation_offset.x = original_offset.x - rotation_offset.x; rotation_offset.y = original_offset.y - rotation_offset.y; rotation_offset.x /= 1024; rotation_offset.y /= 1024; error = FT_Glyph_Transform(glyph, &rotation_matrix, &rotation_offset); } error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1); if(error != 0) { FT_Done_Glyph(glyph); return; } FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph) glyph; // Evidently, in FreeType2, you can only get "upside-down" bitmaps // (this could be cured with PixelZoom, but that an additional function) GLubyte* inverted_pixmap = invertPixmap(bitmap_glyph->bitmap); // :-(If this is compiled in a display list, it may or not be in effect // later when the list is actually called. So, the client should be alerted // to this fact: unpack alignment must be 1 glPushAttrib(GL_PIXEL_MODE_BIT); glPixelTransferf(GL_RED_SCALE, foreground_color_[R] - background_color_[R]); glPixelTransferf(GL_GREEN_SCALE, foreground_color_[G] - background_color_[G]); glPixelTransferf(GL_BLUE_SCALE, foreground_color_[B] - background_color_[B]); glPixelTransferf(GL_ALPHA_SCALE, foreground_color_[A]); glPixelTransferf(GL_RED_BIAS, background_color_[R]); glPixelTransferf(GL_GREEN_BIAS, background_color_[G]); glPixelTransferf(GL_BLUE_BIAS, background_color_[B]); glPixelTransferf(GL_ALPHA_BIAS, background_color_[A]); glBitmap(0, 0, 0, 0, bitmap_glyph->left, bitmap_glyph->top - bitmap_glyph->bitmap.rows, 0); glDrawPixels(bitmap_glyph->bitmap.width, bitmap_glyph->bitmap.rows, GL_LUMINANCE, GL_UNSIGNED_BYTE, inverted_pixmap); // This is how you advance the raster position when drawing PIXMAPS // (without querying the state) glBitmap(0, 0, 0, 0, -bitmap_glyph->left + face->glyph->advance.x / 64., bitmap_glyph->bitmap.rows - bitmap_glyph->top + face->glyph->advance.y / 64., 0); FT_Done_Glyph(glyph); glPopAttrib(); delete[] inverted_pixmap; } Translucent::Translucent (const char* filename, float point_size, FT_UInt resolution) : Raster(filename, point_size, resolution) { return; } Translucent::Translucent (FT_Face face, float point_size, FT_UInt resolution) : Raster(face, point_size, resolution) { return; } Translucent::~Translucent (void) { return; } // The simplest format which glDrawPixels can render with (varying) transparency // is GL_LUMINANCE_ALPHA; so, we take the grayscale bitmap from FreeType // and treat all non-zero values as full luminance (basically the mask for // rendering) and duplicate the grayscale values as alpha values // (as well as turn it upside-down). GLubyte* Translucent::invertPixmapWithAlpha (const FT_Bitmap& bitmap) { GLubyte* inverse = new GLubyte[ 2 * bitmap.rows * bitmap.pitch ]; GLubyte* inverse_ptr = inverse; for(int r=0; rglyph, &original_glyph); if(error != 0) return; error = FT_Glyph_Copy(original_glyph, &glyph); FT_Done_Glyph(original_glyph); if(error != 0) return; if(character_rotation_z_ != 0.) { FT_Matrix rotation_matrix; FT_Vector sinus; FT_Vector_Unit(&sinus, (FT_Angle)(character_rotation_z_ * 0x10000L)); rotation_matrix.xx = sinus.x; rotation_matrix.xy = -sinus.y; rotation_matrix.yx = sinus.y; rotation_matrix.yy = sinus.x; FT_Vector original_offset, rotation_offset; original_offset.x = (face->glyph->metrics.width / 2 + face->glyph->metrics.horiBearingX)/ 64 * 0x10000L; original_offset.y = (FT_Pos)(rotation_offset_y_ * 0x10000L); rotation_offset = original_offset; FT_Vector_Rotate(&rotation_offset, (FT_Angle)(character_rotation_z_ * 0x10000L)); rotation_offset.x = original_offset.x - rotation_offset.x; rotation_offset.y = original_offset.y - rotation_offset.y; rotation_offset.x /= 1024; rotation_offset.y /= 1024; error = FT_Glyph_Transform(glyph, &rotation_matrix, &rotation_offset); } error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1); if(error != 0) { FT_Done_Glyph(glyph); return; } FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph) glyph; // Evidently, in FreeType2, you can only get "upside-down" bitmaps. For // translucency, the grayscale bitmap generated by FreeType is expanded // to include an alpha value (and the non-zero values of the // grayscale bitmap are saturated to provide a "mask" of the glyph). GLubyte* inverted_pixmap = invertPixmapWithAlpha(bitmap_glyph->bitmap); glPushAttrib(GL_PIXEL_MODE_BIT); glPixelTransferf(GL_RED_SCALE, foreground_color_[R] - background_color_[R]); glPixelTransferf(GL_GREEN_SCALE, foreground_color_[G] -background_color_[G]); glPixelTransferf(GL_BLUE_SCALE, foreground_color_[B] - background_color_[B]); glPixelTransferf(GL_ALPHA_SCALE, foreground_color_[A]); glPixelTransferf(GL_RED_BIAS, background_color_[R]); glPixelTransferf(GL_GREEN_BIAS, background_color_[G]); glPixelTransferf(GL_BLUE_BIAS, background_color_[B]); glPixelTransferf(GL_ALPHA_BIAS, background_color_[A]); // Set the proper raster position for rendering this glyph (why doesn't // OpenGL have a similar function for pixmaps?) glBitmap(0, 0, 0, 0, bitmap_glyph->left, bitmap_glyph->top - bitmap_glyph->bitmap.rows, 0); glDrawPixels(bitmap_glyph->bitmap.width, bitmap_glyph->bitmap.rows, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, inverted_pixmap); // This is how you advance the raster position when drawing PIXMAPS // (without querying the state) glBitmap(0, 0, 0, 0, -bitmap_glyph->left + face->glyph->advance.x / 64., bitmap_glyph->bitmap.rows - bitmap_glyph->top + face->glyph->advance.y / 64., 0); FT_Done_Glyph(glyph); glPopAttrib(); delete[] inverted_pixmap; } Polygonal::Polygonal (const char* filename, float point_size, FT_UInt resolution) : Face(filename, point_size, resolution) { if(!isValid()) return; init(); } Polygonal::Polygonal (FT_Face face, float point_size, FT_UInt resolution) : Face(face, point_size, resolution) { init(); } void Polygonal::init (void) { character_rotation_.active_ = false; character_rotation_.x_ = 0; character_rotation_.y_ = 0; character_rotation_.z_ = 0; tessellation_steps_ = DEFAULT_TESSELLATION_STEPS; delta_ = 1. / (double)tessellation_steps_; delta2_ = delta_ * delta_; delta3_ = delta2_ * delta_; // For vector rendition modes, FreeType is allowed to generate the // lines and arcs at the original face definition resolution. To // get to the proper glyph size, the vertices are scaled before // they're passed to the GLU tessellation routines. if(resolution_ != 0) vector_scale_ = (GLdouble)(point_size_ * resolution_) / (GLdouble)(faces_.front().face_->units_per_EM * 72); else // According to the FreeType documentation, resolution == 0 -> 72 DPI vector_scale_ = (GLdouble)(point_size_) / (GLdouble)(faces_.front().face_->units_per_EM); color_tess_ = 0; texture_tess_ = 0; setCharSize(); // Can't call this until a valid character size is set! setCharacterRotationReference('o'); } Polygonal::~Polygonal (void) { clearCaches(); } // Note: Changing the color tessellation object also clears the // display list cache void Polygonal::setColorTess (ColorTess* color_tess) { color_tess_ = color_tess; clearCaches(); } // Note: Changing the texture coordinate tessellation object also // clears the display list cache void Polygonal::setTextureTess (TextureTess* texture_tess) { texture_tess_ = texture_tess; clearCaches(); } // Note: Changing the appoximation steps also clears the display list cache void Polygonal::setTessellationSteps (unsigned int tessellation_steps) { if(tessellation_steps != tessellation_steps_){ tessellation_steps_ = tessellation_steps; delta_ = 1. / (double)tessellation_steps_; delta2_ = delta_ * delta_; delta3_ = delta2_ * delta_; clearCaches(); } } // Note: Changing the character rotation also clears the display list cache. void Polygonal::setCharacterRotationX (GLfloat character_rotation_x) { if(character_rotation_x != character_rotation_.x_) { character_rotation_.x_ = character_rotation_x; if(character_rotation_.x_ != 0. || character_rotation_.y_ != 0. || character_rotation_.z_ != 0.) character_rotation_.active_ = true; else character_rotation_.active_ = false; clearCaches(); } } void Polygonal::setCharacterRotationY (GLfloat character_rotation_y) { if(character_rotation_y != character_rotation_.y_) { character_rotation_.y_ = character_rotation_y; if(character_rotation_.x_ != 0. || character_rotation_.y_ != 0. || character_rotation_.z_ != 0.) character_rotation_.active_ = true; else character_rotation_.active_ = false; clearCaches(); } } void Polygonal::setCharacterRotationZ (GLfloat character_rotation_z) { if(character_rotation_z != character_rotation_.z_){ character_rotation_.z_ = character_rotation_z; if(character_rotation_.x_ != 0. || character_rotation_.y_ != 0. || character_rotation_.z_ != 0.) character_rotation_.active_ = true; else character_rotation_.active_ = false; clearCaches(); } } void Polygonal::setCharSize (void) { for(unsigned int i=0; iunits_per_EM * 64, 0, 0); if(error != 0) return; } if(rotation_reference_glyph_ != 0) setRotationOffset(); } void Polygonal::setRotationOffset (void) { FT_Error error = FT_Load_Glyph(rotation_reference_face_, rotation_reference_glyph_, FT_LOAD_RENDER); if(error != 0) return; vector_scale_ = (point_size_ * resolution_) / (72. * rotation_reference_face_->units_per_EM); rotation_offset_y_ = (rotation_reference_face_->glyph->metrics.horiBearingY / 2.)/ 64. * vector_scale_; } double Polygonal::height (void)const { if(faces_[0].face_->height > 0) return (faces_[0].face_->height * point_size_ * resolution_) / (72. * faces_[0].face_->units_per_EM); else return (faces_[0].face_->size->metrics.y_ppem * point_size_ * resolution_) / (72. * faces_[0].face_->units_per_EM); } BBox Polygonal::measure (unsigned char c) { BBox bbox; // For starters, just get the unscaled glyph bounding box unsigned int f; FT_UInt glyph_index = 0; for(f=0; fglyph, &glyph); if(error != 0) return bbox; FT_BBox ft_bbox; FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_unscaled, &ft_bbox); FT_Done_Glyph(glyph); bbox = ft_bbox; bbox.advance_ = faces_[f].face_->glyph->advance; bbox *= (point_size_ * resolution_)/ (72. * faces_[f].face_->units_per_EM); return bbox; } BBox Polygonal::measure (const wchar_t c) { BBox bbox; // For starters, just get the unscaled glyph bounding box unsigned int f; FT_UInt glyph_index = 0; for(f=0; fglyph, &glyph); if(error != 0) return bbox; FT_BBox ft_bbox; FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_unscaled, &ft_bbox); FT_Done_Glyph(glyph); bbox = ft_bbox; bbox.advance_ = faces_[f].face_->glyph->advance; bbox *= (point_size_ * resolution_)/ (72. * faces_[f].face_->units_per_EM); return bbox; } GLuint Polygonal::compileGlyph (FT_Face face, FT_UInt glyph_index) { GLuint dlist = glGenLists(1); glNewList(dlist, GL_COMPILE); renderGlyph(face, glyph_index); glEndList(); return dlist; } void Polygonal::clearCaches (void) { GDLI fgi = glyph_dlists_.begin(); for(; fgi != glyph_dlists_.end(); ++fgi) glDeleteLists(fgi->second, 1); glyph_dlists_.clear(); } Outline::Outline (const char* filename, float point_size, FT_UInt resolution) : Polygonal(filename, point_size, resolution) { if(!isValid()) return; init(); } Outline::Outline (FT_Face face, float point_size, FT_UInt resolution) : Polygonal(face, point_size, resolution) { init(); } void Outline::init (void) { interface_.move_to = (FT_Outline_MoveTo_Func)moveToCallback; interface_.line_to = (FT_Outline_LineTo_Func)lineToCallback; interface_.conic_to = (FT_Outline_ConicTo_Func)conicToCallback; interface_.cubic_to = (FT_Outline_CubicTo_Func)cubicToCallback; interface_.shift = 0; interface_.delta = 0; } Outline::~Outline (void) { return; } void Outline::renderGlyph (FT_Face face, FT_UInt glyph_index) { FT_Error error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); if(error != 0) return; FT_OutlineGlyph g; error = FT_Get_Glyph(face->glyph, (FT_Glyph*)&g); if(error != 0) return; vector_scale_ = (point_size_ * resolution_)/ (72. * face->units_per_EM); if(character_rotation_.active_) { glPushMatrix(); glTranslatef((face->glyph->metrics.width / 2. + face->glyph->metrics.horiBearingX)/ 64. * vector_scale_, rotation_offset_y_, 0.); if(character_rotation_.x_ != 0.) glRotatef(character_rotation_.x_, 1., 0., 0.); if(character_rotation_.y_ != 0.) glRotatef(character_rotation_.y_, 0., 1., 0.); if(character_rotation_.z_ != 0.) glRotatef(character_rotation_.z_, 0., 0., 1.); glTranslatef(-(face->glyph->metrics.width / 2. + face->glyph->metrics.horiBearingX)/ 64. * vector_scale_, -rotation_offset_y_, 0.); } contour_open_ = false; // The Big Kahuna: the FreeType glyph decomposition routine traverses // the outlines of the font by calling the various routines stored in // outline_interface_. These routines in turn call the GL vertex routines. error = FT_Outline_Decompose(&g->outline, &interface_, this); FT_Done_Glyph((FT_Glyph)g); // Some glyphs may be empty (the 'blank' for instance!) if(contour_open_) glEnd(); if(character_rotation_.active_) glPopMatrix(); // Drawing a character always advances the MODELVIEW. glTranslatef(face->glyph->advance.x / 64. * vector_scale_, face->glyph->advance.y / 64. * vector_scale_, 0.); for(VILI vili = vertices_.begin(); vili != vertices_.end(); vili++) delete *vili; vertices_.clear(); } int Outline::moveToCallback (FT_Vector* to, Outline* outline) { if(outline->contour_open_) glEnd(); outline->last_vertex_ = VertexInfo(to, outline->colorTess(), outline->textureTess()); glBegin(GL_LINE_LOOP); outline->contour_open_ = true; return 0; } int Outline::lineToCallback (FT_Vector* to, Outline* outline) { outline->last_vertex_ = VertexInfo(to, outline->colorTess(), outline->textureTess()); GLdouble g[2]; g[X] = outline->last_vertex_.v_[X] * outline->vector_scale_; g[Y] = outline->last_vertex_.v_[Y] * outline->vector_scale_; glVertex2dv(g); return 0; } int Outline::conicToCallback (FT_Vector* control, FT_Vector* to, Outline* outline) { // This is crude: Step off conics with a fixed number of increments VertexInfo to_vertex(to, outline->colorTess(), outline->textureTess()); VertexInfo control_vertex(control, outline->colorTess(), outline->textureTess()); double b[2], c[2], d[2], f[2], df[2], d2f[2]; GLdouble g[3]; g[Z] = 0.; b[X] = outline->last_vertex_.v_[X] - 2 * control_vertex.v_[X] + to_vertex.v_[X]; b[Y] = outline->last_vertex_.v_[Y] - 2 * control_vertex.v_[Y] + to_vertex.v_[Y]; c[X] = -2 * outline->last_vertex_.v_[X] + 2 * control_vertex.v_[X]; c[Y] = -2 * outline->last_vertex_.v_[Y] + 2 * control_vertex.v_[Y]; d[X] = outline->last_vertex_.v_[X]; d[Y] = outline->last_vertex_.v_[Y]; f[X] = d[X]; f[Y] = d[Y]; df[X] = c[X] * outline->delta_ + b[X] * outline->delta2_; df[Y] = c[Y] * outline->delta_ + b[Y] * outline->delta2_; d2f[X] = 2 * b[X] * outline->delta2_; d2f[Y] = 2 * b[Y] * outline->delta2_; for(unsigned int i=0; itessellation_steps_-1; i++) { f[X] += df[X]; f[Y] += df[Y]; g[X] = f[X] * outline->vector_scale_; g[Y] = f[Y] * outline->vector_scale_; if(outline->colorTess()) glColor4fv(outline->colorTess()->color(g)); glVertex2dv(g); df[X] += d2f[X]; df[Y] += d2f[Y]; } g[X] = to_vertex.v_[X] * outline->vector_scale_; g[Y] = to_vertex.v_[Y] * outline->vector_scale_; if(outline->colorTess()) glColor4fv(outline->colorTess()->color(g)); glVertex2dv(g); outline->last_vertex_ = to_vertex; return 0; } int Outline::cubicToCallback (FT_Vector* control1, FT_Vector* control2,FT_Vector* to, Outline* outline) { // This is crude: Step off cubics with a fixed number of increments VertexInfo to_vertex(to, outline->colorTess(), outline->textureTess()); VertexInfo control1_vertex(control1, outline->colorTess(), outline->textureTess()); VertexInfo control2_vertex(control2, outline->colorTess(), outline->textureTess()); double a[2], b[2], c[2], d[2], f[2], df[2], d2f[2], d3f[2]; GLdouble g[3]; g[Z] = 0.; a[X] = -outline->last_vertex_.v_[X] + 3 * control1_vertex.v_[X] -3 * control2_vertex.v_[X] + to_vertex.v_[X]; a[Y] = -outline->last_vertex_.v_[Y] + 3 * control1_vertex.v_[Y] -3 * control2_vertex.v_[Y] + to_vertex.v_[Y]; b[X] = 3 * outline->last_vertex_.v_[X] - 6 * control1_vertex.v_[X] + 3 * control2_vertex.v_[X]; b[Y] = 3 * outline->last_vertex_.v_[Y] - 6 * control1_vertex.v_[Y] + 3 * control2_vertex.v_[Y]; c[X] = -3 * outline->last_vertex_.v_[X] + 3 * control1_vertex.v_[X]; c[Y] = -3 * outline->last_vertex_.v_[Y] + 3 * control1_vertex.v_[Y]; d[X] = outline->last_vertex_.v_[X]; d[Y] = outline->last_vertex_.v_[Y]; f[X] = d[X]; f[Y] = d[Y]; df[X] = c[X] * outline->delta_ + b[X] * outline->delta2_ + a[X] * outline->delta3_; df[Y] = c[Y] * outline->delta_ + b[Y] * outline->delta2_ + a[Y] * outline->delta3_; d2f[X] = 2 * b[X] * outline->delta2_ + 6 * a[X] * outline->delta3_; d2f[Y] = 2 * b[Y] * outline->delta2_ + 6 * a[Y] * outline->delta3_; d3f[X] = 6 * a[X] * outline->delta3_; d3f[Y] = 6 * a[Y] * outline->delta3_; for(unsigned int i=0; itessellation_steps_-1; i++) { f[X] += df[X]; f[Y] += df[Y]; g[X] = f[X] * outline->vector_scale_; g[Y] = f[Y] * outline->vector_scale_; if(outline->colorTess()) glColor4fv(outline->colorTess()->color(g)); glVertex2dv(g); df[X] += d2f[X]; df[Y] += d2f[Y]; d2f[X] += d3f[X]; d2f[Y] += d3f[Y]; } g[X] = to_vertex.v_[X] * outline->vector_scale_; g[Y] = to_vertex.v_[Y] * outline->vector_scale_; if(outline->colorTess()) glColor4fv(outline->colorTess()->color(g)); glVertex2dv(g); outline->last_vertex_ = to_vertex; return 0; } Filled::Filled (const char* filename, float point_size, FT_UInt resolution) : Polygonal(filename, point_size, resolution) { if(!isValid()) return; init(); } Filled::Filled (FT_Face face, float point_size, FT_UInt resolution) : Polygonal(face, point_size, resolution) { init(); } void Filled::init (void) { depth_offset_ = 0; interface_.move_to = (FT_Outline_MoveTo_Func)moveToCallback; interface_.line_to = (FT_Outline_LineTo_Func)lineToCallback; interface_.conic_to = (FT_Outline_ConicTo_Func)conicToCallback; interface_.cubic_to = (FT_Outline_CubicTo_Func)cubicToCallback; interface_.shift = 0; interface_.delta = 0; tess_obj_ = gluNewTess(); gluTessCallback(tess_obj_, GLU_TESS_VERTEX, (GLUTessCallback)vertexCallback); gluTessCallback(tess_obj_, GLU_TESS_BEGIN, (GLUTessCallback)beginCallback); gluTessCallback(tess_obj_, GLU_TESS_END, (GLUTessCallback)endCallback); gluTessCallback(tess_obj_, GLU_TESS_COMBINE_DATA, (GLUTessCallback)combineCallback); gluTessCallback(tess_obj_, GLU_TESS_ERROR, (GLUTessCallback)errorCallback); } Filled::~Filled (void) { gluDeleteTess(tess_obj_); } void Filled::renderGlyph (FT_Face face, FT_UInt glyph_index) { FT_Error error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); if(error != 0) return; FT_OutlineGlyph g; error = FT_Get_Glyph(face->glyph, (FT_Glyph*)&g); if(error != 0) return; vector_scale_ = (point_size_ * resolution_)/ (72. * face->units_per_EM); if(character_rotation_.active_) { glPushMatrix(); glTranslatef((face->glyph->metrics.width / 2. + face->glyph->metrics.horiBearingX)/ 64. * vector_scale_, rotation_offset_y_, 0.); if(character_rotation_.x_ != 0.) glRotatef(character_rotation_.x_, 1., 0., 0.); if(character_rotation_.y_ != 0.) glRotatef(character_rotation_.y_, 0., 1., 0.); if(character_rotation_.z_ != 0.) glRotatef(character_rotation_.z_, 0., 0., 1.); glTranslatef(-(face->glyph->metrics.width / 2. + face->glyph->metrics.horiBearingX)/ 64. * vector_scale_, -rotation_offset_y_, 0.); } if(depth_offset_ != 0.) { glPushMatrix(); glTranslatef(0., 0., depth_offset_); glNormal3f(0., 0., 1.); } else { glNormal3f(0., 0., -1.); } glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); contour_open_ = false; gluTessBeginPolygon(tess_obj_, this); // The Big Kahuna: the FreeType glyph decomposition routine traverses // the outlines of the font by calling the various routines stored in // interface_. These routines in turn call the GLU tessellation routines // to create OGL polygons. error = FT_Outline_Decompose(&g->outline, &interface_, this); FT_Done_Glyph((FT_Glyph)g); // Some glyphs may be empty (the 'blank' for instance!) if(contour_open_) gluTessEndContour(tess_obj_); gluTessEndPolygon(tess_obj_); if(depth_offset_ != 0.) { glPopMatrix(); } if(character_rotation_.active_) { glPopMatrix(); } // Drawing a character always advances the MODELVIEW. glTranslatef(face->glyph->advance.x / 64 * vector_scale_, face->glyph->advance.y / 64 * vector_scale_, 0.); for(VILI vili = extra_vertices_.begin(); vili != extra_vertices_.end(); vili++) delete *vili; extra_vertices_.clear(); for(VILI vili = vertices_.begin(); vili != vertices_.end(); vili++) delete *vili; vertices_.clear(); } int Filled::moveToCallback (FT_Vector* to, Filled* filled) { if(filled->contour_open_) { gluTessEndContour(filled->tess_obj_); } filled->last_vertex_ = VertexInfo(to, filled->colorTess(), filled->textureTess()); gluTessBeginContour(filled->tess_obj_); filled->contour_open_ = true; return 0; } int Filled::lineToCallback (FT_Vector* to, Filled* filled) { filled->last_vertex_ = VertexInfo(to, filled->colorTess(), filled->textureTess()); VertexInfo* vertex = new VertexInfo(to, filled->colorTess(), filled->textureTess()); vertex->v_[X] *= filled->vector_scale_; vertex->v_[Y] *= filled->vector_scale_; gluTessVertex(filled->tess_obj_, vertex->v_, vertex); filled->vertices_.push_back(vertex); return 0; } int Filled::conicToCallback (FT_Vector* control, FT_Vector* to, Filled* filled) { // This is crude: Step off conics with a fixed number of increments VertexInfo to_vertex(to, filled->colorTess(), filled->textureTess()); VertexInfo control_vertex(control, filled->colorTess(), filled->textureTess()); double b[2], c[2], d[2], f[2], df[2], d2f[2]; b[X] = filled->last_vertex_.v_[X] - 2 * control_vertex.v_[X] + to_vertex.v_[X]; b[Y] = filled->last_vertex_.v_[Y] - 2 * control_vertex.v_[Y] + to_vertex.v_[Y]; c[X] = -2 * filled->last_vertex_.v_[X] + 2 * control_vertex.v_[X]; c[Y] = -2 * filled->last_vertex_.v_[Y] + 2 * control_vertex.v_[Y]; d[X] = filled->last_vertex_.v_[X]; d[Y] = filled->last_vertex_.v_[Y]; f[X] = d[X]; f[Y] = d[Y]; df[X] = c[X] * filled->delta_ + b[X] * filled->delta2_; df[Y] = c[Y] * filled->delta_ + b[Y] * filled->delta2_; d2f[X] = 2 * b[X] * filled->delta2_; d2f[Y] = 2 * b[Y] * filled->delta2_; for(unsigned int i=0; itessellation_steps_-1; i++) { f[X] += df[X]; f[Y] += df[Y]; VertexInfo* vertex = new VertexInfo(f, filled->colorTess(), filled->textureTess()); vertex->v_[X] *= filled->vector_scale_; vertex->v_[Y] *= filled->vector_scale_; filled->vertices_.push_back(vertex); gluTessVertex(filled->tess_obj_, vertex->v_, vertex); df[X] += d2f[X]; df[Y] += d2f[Y]; } VertexInfo* vertex = new VertexInfo(to, filled->colorTess(), filled->textureTess()); vertex->v_[X] *= filled->vector_scale_; vertex->v_[Y] *= filled->vector_scale_; filled->vertices_.push_back(vertex); gluTessVertex(filled->tess_obj_, vertex->v_, vertex); filled->last_vertex_ = to_vertex; return 0; } int Filled::cubicToCallback (FT_Vector* control1, FT_Vector* control2, FT_Vector* to, Filled* filled) { // This is crude: Step off cubics with a fixed number of increments VertexInfo to_vertex(to, filled->colorTess(), filled->textureTess()); VertexInfo control1_vertex(control1, filled->colorTess(), filled->textureTess()); VertexInfo control2_vertex(control2, filled->colorTess(), filled->textureTess()); double a[2], b[2], c[2], d[2], f[2], df[2], d2f[2], d3f[2]; a[X] = -filled->last_vertex_.v_[X] + 3 * control1_vertex.v_[X] -3 * control2_vertex.v_[X] + to_vertex.v_[X]; a[Y] = -filled->last_vertex_.v_[Y] + 3 * control1_vertex.v_[Y] -3 * control2_vertex.v_[Y] + to_vertex.v_[Y]; b[X] = 3 * filled->last_vertex_.v_[X] - 6 * control1_vertex.v_[X] + 3 * control2_vertex.v_[X]; b[Y] = 3 * filled->last_vertex_.v_[Y] - 6 * control1_vertex.v_[Y] + 3 * control2_vertex.v_[Y]; c[X] = -3 * filled->last_vertex_.v_[X] + 3 * control1_vertex.v_[X]; c[Y] = -3 * filled->last_vertex_.v_[Y] + 3 * control1_vertex.v_[Y]; d[X] = filled->last_vertex_.v_[X]; d[Y] = filled->last_vertex_.v_[Y]; f[X] = d[X]; f[Y] = d[Y]; df[X] = c[X] * filled->delta_ + b[X] * filled->delta2_ + a[X] * filled->delta3_; df[Y] = c[Y] * filled->delta_ + b[Y] * filled->delta2_ + a[Y] * filled->delta3_; d2f[X] = 2 * b[X] * filled->delta2_ + 6 * a[X] * filled->delta3_; d2f[Y] = 2 * b[Y] * filled->delta2_ + 6 * a[Y] * filled->delta3_; d3f[X] = 6 * a[X] * filled->delta3_; d3f[Y] = 6 * a[Y] * filled->delta3_; for(unsigned int i=0; itessellation_steps_-1; i++) { f[X] += df[X]; f[Y] += df[Y]; VertexInfo* vertex = new VertexInfo(f, filled->colorTess(), filled->textureTess()); vertex->v_[X] *= filled->vector_scale_; vertex->v_[Y] *= filled->vector_scale_; filled->vertices_.push_back(vertex); gluTessVertex(filled->tess_obj_, vertex->v_, vertex); df[X] += d2f[X]; df[Y] += d2f[Y]; d2f[X] += d3f[X]; d2f[Y] += d3f[Y]; } VertexInfo* vertex = new VertexInfo(to, filled->colorTess(), filled->textureTess()); vertex->v_[X] *= filled->vector_scale_; vertex->v_[Y] *= filled->vector_scale_; filled->vertices_.push_back(vertex); gluTessVertex(filled->tess_obj_, vertex->v_, vertex); filled->last_vertex_ = to_vertex; return 0; } void Filled::vertexCallback (VertexInfo* vertex) { if(vertex->color_tess_ != 0) glColor4fv(vertex->color_tess_->color(vertex->v_)); if(vertex->texture_tess_ != 0) glTexCoord2fv(vertex->texture_tess_->texCoord(vertex->v_)); glVertex3dv(vertex->v_); } void Filled::beginCallback (GLenum which) { glBegin(which); } void Filled::endCallback (void) { glEnd(); } void Filled::combineCallback (GLdouble coords[3], void* vertex_data[4], GLfloat weight[4], void** out_data, Filled* filled) { (void)vertex_data; (void)weight; VertexInfo* vertex = new VertexInfo(coords); *out_data = vertex; filled->extraVertices().push_back(vertex); } void Filled::errorCallback (GLenum error_code) { std::cerr << "hmm. error during tessellation?:" << gluErrorString(error_code)<< std::endl; } Texture::Texture (const char* filename, float point_size, FT_UInt resolution) : Face(filename, point_size, resolution) { if(!isValid()) return; init(); } Texture::Texture (FT_Face face, float point_size, FT_UInt resolution) : Face(face, point_size, resolution) { init(); } void Texture::init (void) { character_rotation_.active_ = false; character_rotation_.x_ = 0; character_rotation_.y_ = 0; character_rotation_.z_ = 0; setCharSize(); setCharacterRotationReference('o'); } Texture::~Texture (void) { clearCaches(); } // Note: Changing the character rotation also clears the display list cache. void Texture::setCharacterRotationX (GLfloat character_rotation_x) { if(character_rotation_x != character_rotation_.x_) { character_rotation_.x_ = character_rotation_x; if(character_rotation_.x_ != 0. || character_rotation_.y_ != 0. || character_rotation_.z_ != 0.) character_rotation_.active_ = true; else character_rotation_.active_ = false; clearCaches(); } } void Texture::setCharacterRotationY (GLfloat character_rotation_y) { if(character_rotation_y != character_rotation_.y_) { character_rotation_.y_ = character_rotation_y; if(character_rotation_.x_ != 0. || character_rotation_.y_ != 0. || character_rotation_.z_ != 0.) character_rotation_.active_ = true; else character_rotation_.active_ = false; clearCaches(); } } void Texture::setCharacterRotationZ (GLfloat character_rotation_z) { if(character_rotation_z != character_rotation_.z_) { character_rotation_.z_ = character_rotation_z; if(character_rotation_.x_ != 0. || character_rotation_.y_ != 0. || character_rotation_.z_ != 0.) character_rotation_.active_ = true; else character_rotation_.active_ = false; clearCaches(); } } void Texture::setCharSize (void) { for(unsigned int f=0; fglyph->bitmap.rows / 2.; } BBox Texture::measure (unsigned char c) { BBox bbox; // For starters, just get the unscaled glyph bounding box unsigned int f; FT_UInt glyph_index = 0; for(f=0; fglyph, &glyph); if(error != 0) return bbox; FT_BBox ft_bbox; FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_unscaled, &ft_bbox); FT_Done_Glyph(glyph); bbox = ft_bbox; bbox.advance_ = faces_[f].face_->glyph->advance; return bbox; } double Texture::height (void)const { if(faces_[0].face_->height > 0) return faces_[0].face_->height / 64.; else return faces_[0].face_->size->metrics.y_ppem; } BBox Texture::measure (const wchar_t c) { BBox bbox; // For starters, just get the unscaled glyph bounding box unsigned int f; FT_UInt glyph_index = 0; for(f=0; fglyph, &glyph); if(error != 0) return bbox; FT_BBox ft_bbox; FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_unscaled, &ft_bbox); FT_Done_Glyph(glyph); bbox = ft_bbox; bbox.advance_ = faces_[f].face_->glyph->advance; return bbox; } GLuint Texture::compileGlyph (FT_Face face, FT_UInt glyph_index) { bindTexture(face, glyph_index); GLuint dlist = glGenLists(1); glNewList(dlist, GL_COMPILE); renderGlyph(face, glyph_index); glEndList(); return dlist; } void Texture::renderGlyph (FT_Face face, FT_UInt glyph_index) { FT_Error error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); if(error != 0) return; TextureInfo texture_info; GTOCI texture_object = glyph_texobjs_.find(glyph_index); if(texture_object == glyph_texobjs_.end()) { bindTexture(face, glyph_index); texture_object = glyph_texobjs_.find(glyph_index); if(texture_object == glyph_texobjs_.end()) return; } texture_info = texture_object->second; glBindTexture(GL_TEXTURE_2D, texture_info.texture_name_); if(character_rotation_.active_) { glPushMatrix(); glTranslatef((texture_info.width_ / 2. + texture_info.left_bearing_), rotation_offset_y_, 0.); if(character_rotation_.x_ != 0.) glRotatef(character_rotation_.x_, 1., 0., 0.); if(character_rotation_.y_ != 0.) glRotatef(character_rotation_.y_, 0., 1., 0.); if(character_rotation_.z_ != 0.) glRotatef(character_rotation_.z_, 0., 0., 1.); glTranslatef(-(texture_info.width_ / 2. + texture_info.left_bearing_), -rotation_offset_y_, 0.); } glBegin(GL_QUADS); glTexCoord2i(0, 0); glVertex2f(texture_info.left_bearing_, texture_info.bottom_bearing_); glTexCoord2f(texture_info.texture_s_, 0.); glVertex2f(texture_info.left_bearing_ + texture_info.width_, texture_info.bottom_bearing_); glTexCoord2f(texture_info.texture_s_, texture_info.texture_t_); glVertex2f(texture_info.left_bearing_ + texture_info.width_, texture_info.bottom_bearing_ + texture_info.height_); glTexCoord2f(0., texture_info.texture_t_); glVertex2f(texture_info.left_bearing_, texture_info.bottom_bearing_ + texture_info.height_); glEnd(); if(character_rotation_.active_) glPopMatrix(); // Drawing a character always advances the MODELVIEW. glTranslatef(texture_info.advance_.x / 64., texture_info.advance_.y / 64.,0.); } void Texture::clearCaches (void) { GDLI fgi = glyph_dlists_.begin(); for(; fgi != glyph_dlists_.end(); ++fgi) glDeleteLists(fgi->second, 1); glyph_dlists_.clear(); GTOI fti = glyph_texobjs_.begin(); for(; fti != glyph_texobjs_.end(); ++fti) glDeleteTextures(1, &fti->second.texture_name_); glyph_texobjs_.clear(); } unsigned int Texture::nearestPowerCeil (unsigned int a) { unsigned int b = a; unsigned int c = 1; if(a == 0)return 1; // Take the log-2 of a for(; ;) { if(b == 1) break; else if(b == 3) { c *= 4; break; } b >>= 1; c *= 2; } // If it's too small, raise it another power if(cglyph, FT_RENDER_MODE_MONO); if(error != 0) return; TextureInfo texture_info; glGenTextures(1, &texture_info.texture_name_); glBindTexture(GL_TEXTURE_2D, texture_info.texture_name_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); int width, height; GLubyte* inverted_pixmap = invertBitmap(face->glyph->bitmap, &width, &height); GLfloat red_map[2] = { background_color_[R], foreground_color_[R] }; GLfloat green_map[2] = { background_color_[G], foreground_color_[G] }; GLfloat blue_map[2] = { background_color_[B], foreground_color_[B] }; GLfloat alpha_map[2] = { background_color_[A], foreground_color_[A] }; glPixelMapfv(GL_PIXEL_MAP_I_TO_R, 2, red_map); glPixelMapfv(GL_PIXEL_MAP_I_TO_G, 2, green_map); glPixelMapfv(GL_PIXEL_MAP_I_TO_B, 2, blue_map); glPixelMapfv(GL_PIXEL_MAP_I_TO_A, 2, alpha_map); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_COLOR_INDEX, GL_BITMAP, inverted_pixmap); // Save a good bit of the data about this glyph texture_info.left_bearing_ = face->glyph->bitmap_left; texture_info.bottom_bearing_ = -(face->glyph->bitmap.rows - face->glyph->bitmap_top); texture_info.width_ = face->glyph->bitmap.width; texture_info.height_ = face->glyph->bitmap.rows; texture_info.texture_s_ = (GLfloat)texture_info.width_ / width; texture_info.texture_t_ = (GLfloat)texture_info.height_ / height; texture_info.advance_ = face->glyph->advance; glyph_texobjs_[ glyph_index ] = texture_info; delete[] inverted_pixmap; } GrayscaleTexture::GrayscaleTexture (const char* filename, float point_size, FT_UInt resolution) : Texture(filename, point_size, resolution) { return; } GrayscaleTexture::GrayscaleTexture (FT_Face face, float point_size, FT_UInt resolution) : Texture(face, point_size, resolution) { return; } GrayscaleTexture::~GrayscaleTexture (void) { return; } // For the grayscale style, the luminance is the grayscale FreeType value, // so this just rounds up to a power of two and inverts the pixmap GLubyte* GrayscaleTexture::invertPixmap (const FT_Bitmap& bitmap, int* width, int* height) { *width = nearestPowerCeil(bitmap.width); *height = nearestPowerCeil(bitmap.rows); GLubyte* inverse = new GLubyte[ *width * *height ]; GLubyte* inverse_ptr = inverse; for(int r=0; rglyph, FT_RENDER_MODE_NORMAL); if(error != 0) return; TextureInfo texture_info; glGenTextures(1, &texture_info.texture_name_); glBindTexture(GL_TEXTURE_2D, texture_info.texture_name_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); int width, height; GLubyte* inverted_pixmap = invertPixmap(face->glyph->bitmap, &width, &height); glPushAttrib(GL_PIXEL_MODE_BIT); glPixelTransferf(GL_RED_SCALE, foreground_color_[R] - background_color_[R]); glPixelTransferf(GL_GREEN_SCALE, foreground_color_[G]-background_color_[G]); glPixelTransferf(GL_BLUE_SCALE, foreground_color_[B]-background_color_[B]); glPixelTransferf(GL_ALPHA_SCALE, foreground_color_[A]-background_color_[A]); glPixelTransferf(GL_RED_BIAS, background_color_[R]); glPixelTransferf(GL_GREEN_BIAS, background_color_[G]); glPixelTransferf(GL_BLUE_BIAS, background_color_[B]); glPixelTransferf(GL_ALPHA_BIAS, background_color_[A]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, inverted_pixmap); glPopAttrib(); texture_info.left_bearing_ = face->glyph->bitmap_left; texture_info.bottom_bearing_ = -(face->glyph->bitmap.rows - face->glyph->bitmap_top); texture_info.width_ = face->glyph->bitmap.width; texture_info.height_ = face->glyph->bitmap.rows; texture_info.texture_s_ = (GLfloat)texture_info.width_ / width; texture_info.texture_t_ = (GLfloat)texture_info.height_ / height; texture_info.advance_ = face->glyph->advance; glyph_texobjs_[ glyph_index ] = texture_info; delete[] inverted_pixmap; } TranslucentTexture::TranslucentTexture (const char* filename, float point_size, FT_UInt resolution) : Texture(filename, point_size, resolution) { return; } TranslucentTexture::TranslucentTexture (FT_Face face, float point_size, FT_UInt resolution) : Texture(face, point_size, resolution) { return; } TranslucentTexture::~TranslucentTexture (void) { return; } // For the translucent style, the luminance is saturated and alpha value // is the translucent FreeType value GLubyte* TranslucentTexture::invertPixmap (const FT_Bitmap& bitmap, int* width, int* height) { *width = nearestPowerCeil(bitmap.width); *height = nearestPowerCeil(bitmap.rows); GLubyte* inverse = new GLubyte[ 2 * *width * *height ]; GLubyte* inverse_ptr = inverse; for(int r=0; rglyph, FT_RENDER_MODE_NORMAL); if(error != 0) return; TextureInfo texture_info; glGenTextures(1, &texture_info.texture_name_); glBindTexture(GL_TEXTURE_2D, texture_info.texture_name_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Texture maps have be a power of 2 in size (is 1 a power of 2?), so // pad it out while flipping it over int width, height; GLubyte* inverted_pixmap = invertPixmap(face->glyph->bitmap, &width, &height); glPushAttrib(GL_PIXEL_MODE_BIT); glPixelTransferf(GL_RED_SCALE, foreground_color_[R] - background_color_[R]); glPixelTransferf(GL_GREEN_SCALE, foreground_color_[G]-background_color_[G]); glPixelTransferf(GL_BLUE_SCALE, foreground_color_[B]-background_color_[B]); glPixelTransferf(GL_ALPHA_SCALE, foreground_color_[A]-background_color_[A]); glPixelTransferf(GL_RED_BIAS, background_color_[R]); glPixelTransferf(GL_GREEN_BIAS, background_color_[G]); glPixelTransferf(GL_BLUE_BIAS, background_color_[B]); glPixelTransferf(GL_ALPHA_BIAS, background_color_[A]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, inverted_pixmap); glPopAttrib(); // Save a good bit of the data about this glyph texture_info.left_bearing_ = face->glyph->bitmap_left; texture_info.bottom_bearing_ = -(face->glyph->bitmap.rows - face->glyph->bitmap_top); texture_info.width_ = face->glyph->bitmap.width; texture_info.height_ = face->glyph->bitmap.rows; texture_info.texture_s_ = (GLfloat)texture_info.width_ / width; texture_info.texture_t_ = (GLfloat)texture_info.height_ / height; texture_info.advance_ = face->glyph->advance; glyph_texobjs_[ glyph_index ] = texture_info; delete[] inverted_pixmap; } } // close OGLFT namespace