/* * 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 * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "OGLFT.h" int wstrlen(const wchar_t * s) { int r = 0; while (*s++) r++; return r; } namespace OGLFT { FT_Library ft_library; bool Init_FT(void) { FT_Error error = FT_Init_FreeType(&ft_library); if(error != 0) std::cerr << "[OGLFT] Could not initialize the FreeType library." << std::endl; return (error == 0); } bool Uninit_FT(void) { FT_Error error = FT_Done_FreeType(ft_library); if(error != 0) std::cerr << "[OGLFT] Could not terminate the FreeType library." << std::endl; return (error == 0); } // 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(ft_library, 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; int i; if(wstrlen(s) > 0) { bbox = measure(s[0]); for(i = 1; i < wstrlen(s); i++) { BBox char_bbox = measure(s[i]); bbox += char_bbox; } } // make sure the origin is at 0,0 if (bbox.x_min_ != 0) { bbox.x_max_ -= bbox.x_min_; bbox.x_min_ = 0; } if (bbox.y_min_ != 0) { bbox.y_max_ -= bbox.y_min_; bbox.y_min_ = 0; } return bbox; } BBox Face::measureRaw (const wchar_t* s) { BBox bbox; int i; for(i = 0; i < wstrlen(s); i++) { BBox char_bbox; unsigned int f; FT_UInt glyph_index = 0; for(f=0; fglyph, &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_ = (float) (x - x0); bbox.y_min_ = (float) (y - y0); gluUnProject(bbox.x_max_, bbox.y_max_, 0., modelview, projection, viewport, &x, &y, &z); bbox.x_max_ = (float) (x - x0); bbox.y_max_ = (float) (y - y0); gluUnProject(bbox.advance_.dx_, bbox.advance_.dy_, 0., modelview, projection, viewport, &x, &y, &z); bbox.advance_.dx_ = (float) (x - x0); bbox.advance_.dy_ = (float) (y - y0); return bbox; } BBox Raster::measure (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_ = (float) (x - x0); bbox.y_min_ = (float) (y - y0); gluUnProject(bbox.x_max_, bbox.y_max_, 0., modelview, projection, viewport, &x, &y, &z); bbox.x_max_ = (float) (x - x0); bbox.y_max_ = (float) (y - y0); gluUnProject(bbox.advance_.dx_, bbox.advance_.dy_, 0., modelview, projection, viewport, &x, &y, &z); bbox.advance_.dx_ = (float) (x - x0); bbox.advance_.dy_ = (float) (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.0f; } 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(unsigned 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, (GLfloat) -bitmap_glyph->left, (GLfloat) (bitmap_glyph->bitmap.rows - bitmap_glyph->top), face->glyph->advance.x / 64.0f, face->glyph->advance.y / 64.0f, inverted_bitmap); FT_Done_Glyph(glyph); delete[] inverted_bitmap; } } // close OGLFT namespace