mirror of
https://github.com/mupen64plus/mupen64plus-oldsvn.git
synced 2025-04-02 10:52:35 -04:00
2978 lines
94 KiB
C++
2978 lines
94 KiB
C++
/*
|
|
* OGLFT: A library for drawing text with OpenGL using the FreeType library
|
|
* Copyright (C) 2002 lignum Computing, Inc. <oglft@lignumcomputing.com>
|
|
* $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 <iostream>
|
|
#include <iomanip>
|
|
#include <string.h>
|
|
#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; i<faces_.size(); i++)
|
|
if(faces_[i].free_on_exit_)
|
|
FT_Done_Face(faces_[i].face_);
|
|
}
|
|
|
|
// Add another Face to select characters from
|
|
bool Face::addAuxiliaryFace (const char* filename)
|
|
{
|
|
FT_Face ft_face;
|
|
|
|
FT_Error error = FT_New_Face(Library::instance(), filename, 0, &ft_face);
|
|
|
|
if(error != 0) return false;
|
|
|
|
faces_.push_back(FaceData(ft_face));
|
|
setCharSize();
|
|
|
|
return true;
|
|
}
|
|
|
|
// Add another Face to select characters from
|
|
bool Face::addAuxiliaryFace (FT_Face face)
|
|
{
|
|
faces_.push_back(FaceData(face, false));
|
|
|
|
setCharSize();
|
|
|
|
return true;
|
|
}
|
|
|
|
// Note: Changing the point size also clears the display list cache
|
|
void Face::setPointSize (float point_size)
|
|
{
|
|
if(point_size != point_size_)
|
|
{
|
|
point_size_ = point_size;
|
|
clearCaches();
|
|
setCharSize();
|
|
}
|
|
}
|
|
|
|
// Note: Changing the resolution also clears the display list cache
|
|
void Face::setResolution (FT_UInt resolution)
|
|
{
|
|
if(resolution != resolution_)
|
|
{
|
|
resolution_ = resolution;
|
|
clearCaches();
|
|
setCharSize();
|
|
}
|
|
}
|
|
|
|
// Note: Changing the background color also clears the display list cache.
|
|
void Face::setBackgroundColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)
|
|
{
|
|
if(background_color_[R] != red||background_color_[G] != green||background_color_[B] != blue||background_color_[A] != alpha)
|
|
{
|
|
background_color_[R] = red;
|
|
background_color_[G] = green;
|
|
background_color_[B] = blue;
|
|
background_color_[A] = alpha;
|
|
|
|
clearCaches();
|
|
}
|
|
}
|
|
|
|
// Note: Changing the foreground color also clears the display list cache.
|
|
void Face::setForegroundColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)
|
|
{
|
|
if(foreground_color_[R] != red||foreground_color_[G] != green||foreground_color_[B] != blue||foreground_color_[A] != alpha)
|
|
{
|
|
foreground_color_[R] = red;
|
|
foreground_color_[G] = green;
|
|
foreground_color_[B] = blue;
|
|
foreground_color_[A] = alpha;
|
|
|
|
clearCaches();
|
|
}
|
|
}
|
|
|
|
// Note: Changing the foreground color also clears the display list cache.
|
|
void Face::setForegroundColor (const GLfloat foreground_color[4])
|
|
{
|
|
if(foreground_color_[R] != foreground_color[R] ||
|
|
foreground_color_[G] != foreground_color[G] ||
|
|
foreground_color_[B] != foreground_color[B] ||
|
|
foreground_color_[A] != foreground_color[A])
|
|
{
|
|
foreground_color_[R] = foreground_color[R];
|
|
foreground_color_[G] = foreground_color[G];
|
|
foreground_color_[B] = foreground_color[B];
|
|
foreground_color_[A] = foreground_color[A];
|
|
|
|
clearCaches();
|
|
}
|
|
}
|
|
|
|
// Note: Changing the background color also clears the display list cache.
|
|
void Face::setBackgroundColor (const GLfloat background_color[4])
|
|
{
|
|
if(background_color_[R] != background_color[R] ||
|
|
background_color_[G] != background_color[G] ||
|
|
background_color_[B] != background_color[B] ||
|
|
background_color_[A] != background_color[A])
|
|
{
|
|
background_color_[R] = background_color[R];
|
|
background_color_[G] = background_color[G];
|
|
background_color_[B] = background_color[B];
|
|
background_color_[A] = background_color[A];
|
|
|
|
clearCaches();
|
|
}
|
|
}
|
|
|
|
// Note: Changing the string rotation angle clears the display list cache
|
|
void Face::setStringRotation (GLfloat string_rotation)
|
|
{
|
|
if(string_rotation != string_rotation_)
|
|
{
|
|
string_rotation_ = string_rotation;
|
|
|
|
clearCaches();
|
|
|
|
// Note that this affects ALL glyphs accessed through
|
|
// the Face, both the vector and the raster glyphs. Very nice!
|
|
if (string_rotation_ != 0)
|
|
{
|
|
float angle;
|
|
if (string_rotation_<0)
|
|
angle = 360. - fmod(fabs(string_rotation_), 360.f);
|
|
else
|
|
angle = fmod(string_rotation_, 360.f);
|
|
|
|
FT_Matrix rotation_matrix;
|
|
FT_Vector sinus;
|
|
|
|
FT_Vector_Unit(&sinus, (FT_Angle)(angle * 0x10000L));
|
|
|
|
rotation_matrix.xx = sinus.x;
|
|
rotation_matrix.xy = -sinus.y;
|
|
rotation_matrix.yx = sinus.y;
|
|
rotation_matrix.yy = sinus.x;
|
|
|
|
for(unsigned int i=0; i<faces_.size(); i++) FT_Set_Transform(faces_[i].face_, &rotation_matrix, 0);
|
|
}
|
|
else for(unsigned int i=0; i<faces_.size(); i++) FT_Set_Transform(faces_[i].face_, 0, 0);
|
|
}
|
|
}
|
|
|
|
// Note: Changing the rotation reference character clears the display list cache.
|
|
void Face::setCharacterRotationReference (unsigned char c)
|
|
{
|
|
unsigned int f;
|
|
FT_UInt glyph_index = 0;
|
|
|
|
for(f=0; f<faces_.size(); f++)
|
|
{
|
|
glyph_index = FT_Get_Char_Index(faces_[f].face_, c);
|
|
if(glyph_index != 0) break;
|
|
}
|
|
|
|
if(f<faces_.size() && glyph_index != rotation_reference_glyph_)
|
|
{
|
|
FT_Error error = FT_Load_Glyph(faces_[f].face_, glyph_index, FT_LOAD_DEFAULT);
|
|
|
|
if(error != 0) return;
|
|
|
|
rotation_reference_glyph_ = glyph_index;
|
|
rotation_reference_face_ = faces_[f].face_;
|
|
setRotationOffset();
|
|
|
|
clearCaches();
|
|
}
|
|
}
|
|
|
|
BBox Face::measure (const char* s)
|
|
{
|
|
BBox bbox;
|
|
char c;
|
|
|
|
if((c = *s++) != 0)
|
|
{
|
|
bbox = measure((unsigned char)c);
|
|
|
|
for(c = *s; c != 0; c = *++s)
|
|
{
|
|
BBox char_bbox = measure((unsigned char)c);
|
|
bbox += char_bbox;
|
|
}
|
|
}
|
|
|
|
return bbox;
|
|
}
|
|
|
|
BBox Face::measureRaw (const char* s)
|
|
{
|
|
BBox bbox;
|
|
|
|
for(char c = *s; c != 0; c = *++s)
|
|
{
|
|
BBox char_bbox;
|
|
|
|
unsigned int f;
|
|
FT_UInt glyph_index = 0;
|
|
|
|
for(f=0; f<faces_.size(); f++)
|
|
{
|
|
glyph_index = FT_Get_Char_Index(faces_[f].face_, c);
|
|
if(glyph_index != 0) break;
|
|
}
|
|
|
|
if(glyph_index == 0) continue;
|
|
|
|
FT_Error error = FT_Load_Glyph(faces_[f].face_, glyph_index, FT_LOAD_DEFAULT);
|
|
if(error != 0) continue;
|
|
|
|
FT_Glyph glyph;
|
|
error = FT_Get_Glyph(faces_[f].face_->glyph, &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; i<wstrlen(s); i++)
|
|
{
|
|
BBox char_bbox = measure(s[i]);
|
|
bbox += char_bbox;
|
|
}
|
|
}
|
|
return bbox;
|
|
}
|
|
|
|
BBox Face::measure (const wchar_t* format, double number)
|
|
{
|
|
return measure(format, number);
|
|
}
|
|
|
|
BBox Face::measureRaw (const wchar_t* s)
|
|
{
|
|
BBox bbox;
|
|
for(unsigned int i=0; i<wstrlen(s); i++)
|
|
{
|
|
BBox char_bbox;
|
|
|
|
unsigned int f;
|
|
FT_UInt glyph_index = 0;
|
|
|
|
for(f=0; f<faces_.size(); f++)
|
|
{
|
|
glyph_index = FT_Get_Char_Index(faces_[f].face_, s[i]);
|
|
if(glyph_index != 0) break;
|
|
}
|
|
|
|
if(glyph_index == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FT_Error error = FT_Load_Glyph(faces_[f].face_, glyph_index, FT_LOAD_DEFAULT);
|
|
if(error != 0) continue;
|
|
|
|
FT_Glyph glyph;
|
|
error = FT_Get_Glyph(faces_[f].face_->glyph, &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; f<faces_.size(); f++) FT_Set_Transform(faces_[f].face_, 0, 0);
|
|
|
|
BBox bbox = measure(s);
|
|
|
|
float angle;
|
|
if(string_rotation_<0.)
|
|
angle = 360. - fmod(fabs(string_rotation_), 360.f);
|
|
else
|
|
angle = fmod(string_rotation_, 360.f);
|
|
|
|
FT_Matrix rotation_matrix;
|
|
FT_Vector sinus;
|
|
|
|
FT_Vector_Unit(&sinus, (FT_Angle)(angle * 0x10000L));
|
|
|
|
rotation_matrix.xx = sinus.x;
|
|
rotation_matrix.xy = -sinus.y;
|
|
rotation_matrix.yx = sinus.y;
|
|
rotation_matrix.yy = sinus.x;
|
|
|
|
for(unsigned int f=0; f<faces_.size(); f++) FT_Set_Transform(faces_[f].face_, &rotation_matrix, 0);
|
|
|
|
return bbox;
|
|
}
|
|
|
|
// Measure the bounding box as if the (UNICODE) string were not rotated
|
|
BBox Face::measure_nominal (const wchar_t* s)
|
|
{
|
|
if(string_rotation_ == 0.)return measure(s);
|
|
|
|
for(unsigned int f=0; f<faces_.size(); f++)FT_Set_Transform(faces_[f].face_, 0, 0);
|
|
|
|
BBox bbox = measure(s);
|
|
|
|
float angle;
|
|
if(string_rotation_<0.0)
|
|
angle = 360. - fmod(fabs(string_rotation_), 360.f);
|
|
else
|
|
angle = fmod(string_rotation_, 360.f);
|
|
|
|
FT_Matrix rotation_matrix;
|
|
FT_Vector sinus;
|
|
|
|
FT_Vector_Unit(&sinus, (FT_Angle)(angle * 0x10000L));
|
|
|
|
rotation_matrix.xx = sinus.x;
|
|
rotation_matrix.xy = -sinus.y;
|
|
rotation_matrix.yx = sinus.y;
|
|
rotation_matrix.yy = sinus.x;
|
|
|
|
for(unsigned int f=0; f<faces_.size(); f++)FT_Set_Transform(faces_[f].face_, &rotation_matrix, 0);
|
|
|
|
return bbox;
|
|
}
|
|
|
|
// Compile a (latin1) string into a display list
|
|
GLuint Face::compile (const char* s)
|
|
{
|
|
const char* s_tmp = s;
|
|
|
|
for(char c = *s_tmp; c != 0; c = *++s_tmp) compile((const char*)c);
|
|
|
|
GLuint dlist = glGenLists(1);
|
|
glNewList(dlist, GL_COMPILE);
|
|
|
|
glColor4f(foreground_color_[R], foreground_color_[G], foreground_color_[B], foreground_color_[A]);
|
|
if(!advance_) glPushMatrix();
|
|
draw(s);
|
|
if(!advance_) glPopMatrix();
|
|
|
|
glEndList();
|
|
|
|
return dlist;
|
|
}
|
|
|
|
// Compile a (UNICODE) string into a display list
|
|
GLuint Face::compile (const wchar_t* s)
|
|
{
|
|
// First, make sure all the characters in the string are themselves
|
|
// in display lists
|
|
for(unsigned int i=0; i<wstrlen(s); i++) compile(s[i]);
|
|
|
|
GLuint dlist = glGenLists(1);
|
|
glNewList(dlist, GL_COMPILE);
|
|
|
|
glColor4f(foreground_color_[R], foreground_color_[G], foreground_color_[B], foreground_color_[A]);
|
|
|
|
if(!advance_) glPushMatrix();
|
|
draw(s);
|
|
if(!advance_) glPopMatrix();
|
|
|
|
glEndList();
|
|
|
|
return dlist;
|
|
}
|
|
|
|
// Compile a (latin1) character glyph into a display list and cache
|
|
// it for later
|
|
|
|
GLuint Face::compile (unsigned char c)
|
|
{
|
|
// See if we've done it already
|
|
GDLCI fgi = glyph_dlists_.find(c);
|
|
|
|
if(fgi != glyph_dlists_.end())return fgi->second;
|
|
|
|
unsigned int f;
|
|
FT_UInt glyph_index = 0;
|
|
|
|
for(f=0; f<faces_.size(); f++)
|
|
{
|
|
glyph_index = FT_Get_Char_Index(faces_[f].face_, c);
|
|
if(glyph_index != 0) break;
|
|
}
|
|
|
|
if(glyph_index == 0)return 0;
|
|
|
|
GLuint dlist = compileGlyph(faces_[f].face_, glyph_index);
|
|
glyph_dlists_[ c ] = dlist;
|
|
|
|
return dlist;
|
|
}
|
|
|
|
|
|
// Compile a (UNICODE) character glyph into a display list and cache
|
|
// it for later
|
|
GLuint Face::compile (const wchar_t c)
|
|
{
|
|
// See if we've done it already
|
|
GDLCI fgi = glyph_dlists_.find(c);
|
|
|
|
if(fgi != glyph_dlists_.end())return fgi->second;
|
|
|
|
unsigned int f;
|
|
FT_UInt glyph_index = 0;
|
|
|
|
for(f=0; f<faces_.size(); f++)
|
|
{
|
|
glyph_index = FT_Get_Char_Index(faces_[f].face_, c);
|
|
if(glyph_index != 0) break;
|
|
}
|
|
|
|
if(glyph_index == 0)return 0;
|
|
|
|
GLuint dlist = compileGlyph(faces_[f].face_, glyph_index);
|
|
|
|
glyph_dlists_[ c ] = dlist;
|
|
|
|
return dlist;
|
|
}
|
|
|
|
// Assume the MODELVIEW matrix is already set and draw the (latin1)
|
|
// string. Note: this routine now ignores almost all settings:
|
|
// including the position (both modelview and raster), color,
|
|
// justification and advance settings. Consider this to be the raw
|
|
// drawing routine for which you are responsible for most of the
|
|
// setup.
|
|
void Face::draw (const char* s)
|
|
{
|
|
DLCI character_display_list = character_display_lists_.begin();
|
|
|
|
for(char c = *s; c != 0; c = *++s)
|
|
{
|
|
if(character_display_list != character_display_lists_.end())
|
|
{
|
|
glCallList(*character_display_list);
|
|
character_display_list++;
|
|
}
|
|
draw((unsigned char)c);
|
|
}
|
|
}
|
|
|
|
// Assume the MODELVIEW matrix is already set and draw the (UNICODE)
|
|
// string. Note: this routine now ignores almost all settings:
|
|
// including the position (both modelview and raster), color,
|
|
// justification and advance settings. Consider this to be the raw
|
|
// drawing routine for which you are responsible for most of the
|
|
// setup.
|
|
void Face::draw (const wchar_t* s)
|
|
{
|
|
DLCI character_display_list = character_display_lists_.begin();
|
|
for(unsigned int i=0; i<wstrlen(s); i++)
|
|
{
|
|
if(character_display_list != character_display_lists_.end())
|
|
{
|
|
glCallList(*character_display_list);
|
|
character_display_list++;
|
|
}
|
|
draw(s[i]);
|
|
}
|
|
}
|
|
|
|
// Assume the MODELVIEW matrix is already setup and draw the
|
|
// (latin1) character.
|
|
void Face::draw (unsigned char c)
|
|
{
|
|
// See if we've done it already
|
|
GDLCI fgi = glyph_dlists_.find(c);
|
|
|
|
if(fgi != glyph_dlists_.end())
|
|
{
|
|
glCallList(fgi->second);
|
|
return;
|
|
}
|
|
|
|
unsigned int f;
|
|
FT_UInt glyph_index = 0;
|
|
|
|
for(f=0; f<faces_.size(); f++)
|
|
{
|
|
glyph_index = FT_Get_Char_Index(faces_[f].face_, c);
|
|
if(glyph_index != 0) break;
|
|
}
|
|
|
|
if(glyph_index == 0) return;
|
|
|
|
if(compile_mode_ == COMPILE)
|
|
{
|
|
GLuint dlist = compile(c);
|
|
glCallList(dlist);
|
|
}
|
|
else renderGlyph(faces_[f].face_, glyph_index);
|
|
}
|
|
|
|
// Assume the MODELVIEW matrix is already setup and draw the
|
|
// (UNICODE) character.
|
|
|
|
void Face::draw (const wchar_t c)
|
|
{
|
|
// See if we've done it already
|
|
GDLCI fgi = glyph_dlists_.find(c);
|
|
|
|
if(fgi != glyph_dlists_.end())
|
|
{
|
|
glCallList(fgi->second);
|
|
return;
|
|
}
|
|
|
|
unsigned int f;
|
|
FT_UInt glyph_index = 0;
|
|
|
|
for(f=0; f<faces_.size(); f++)
|
|
{
|
|
glyph_index = FT_Get_Char_Index(faces_[f].face_, c);
|
|
if(glyph_index != 0) break;
|
|
}
|
|
|
|
if(glyph_index == 0) return;
|
|
|
|
if(compile_mode_ == COMPILE)
|
|
{
|
|
GLuint dlist = compile(c);
|
|
glCallList(dlist);
|
|
}
|
|
else renderGlyph(faces_[f].face_, glyph_index);
|
|
}
|
|
|
|
// Draw the (latin1) character at the given position. The MODELVIEW
|
|
// matrix is modified by the glyph advance.
|
|
void Face::draw (GLfloat x, GLfloat y, unsigned char c)
|
|
{
|
|
glTranslatef(x, y, 0.);
|
|
|
|
glColor4f(foreground_color_[R], foreground_color_[G], foreground_color_[B], foreground_color_[A]);
|
|
|
|
glRasterPos2i(0, 0);
|
|
|
|
draw(c);
|
|
}
|
|
|
|
// Draw the (latin1) character at the given position. The MODELVIEW
|
|
// matrix is modified by the glyph advance.
|
|
void Face::draw (GLfloat x, GLfloat y, GLfloat z, unsigned char c)
|
|
{
|
|
glTranslatef(x, y, z);
|
|
|
|
glColor4f(foreground_color_[R], foreground_color_[G], foreground_color_[B], foreground_color_[A]);
|
|
|
|
glRasterPos2i(0, 0);
|
|
|
|
draw(c);
|
|
}
|
|
|
|
// Draw the (UNICODE) character at the given position. The MODELVIEW
|
|
// matrix is modified by the glyph advance.
|
|
void Face::draw (GLfloat x, GLfloat y, wchar_t c)
|
|
{
|
|
glTranslatef(x, y, 0.);
|
|
|
|
glColor4f(foreground_color_[R], foreground_color_[G], foreground_color_[B],
|
|
foreground_color_[A]);
|
|
|
|
glRasterPos2i(0, 0);
|
|
|
|
draw(c);
|
|
}
|
|
|
|
// Draw the (UNICODE) character at the given position. The MODELVIEW
|
|
// matrix is modified by the glyph advance.
|
|
void Face::draw (GLfloat x, GLfloat y, GLfloat z, wchar_t c)
|
|
{
|
|
glTranslatef(x, y, z);
|
|
|
|
glColor4f(foreground_color_[R], foreground_color_[G], foreground_color_[B], foreground_color_[A]);
|
|
|
|
glRasterPos2i(0, 0);
|
|
|
|
draw(c);
|
|
}
|
|
|
|
|
|
// Draw the (latin1) string at the given position.
|
|
void Face::draw (GLfloat x, GLfloat y, const char* s)
|
|
{
|
|
if(!advance_) glPushMatrix();
|
|
|
|
if(horizontal_justification_ != ORIGIN || vertical_justification_ != BASELINE)
|
|
{
|
|
glPushMatrix();
|
|
|
|
BBox bbox = measure_nominal(s);
|
|
GLfloat dx = 0, dy = 0;
|
|
|
|
switch (horizontal_justification_)
|
|
{
|
|
case LEFT: dx = -bbox.x_min_ + 1; break;
|
|
case CENTER: dx = -(bbox.x_min_ + bbox.x_max_)/ 2.; break;
|
|
case RIGHT: dx = -bbox.x_max_ - 1; break;
|
|
default: break;
|
|
}
|
|
switch (vertical_justification_)
|
|
{
|
|
case BOTTOM: dy = -bbox.y_min_ + 1; break;
|
|
case MIDDLE: dy = -(bbox.y_min_ + bbox.y_max_)/ 2.; break;
|
|
case TOP: dy = -bbox.y_max_ - 1; break;
|
|
default: break;
|
|
}
|
|
|
|
// There is probably a less expensive way to compute this
|
|
glRotatef(string_rotation_, 0., 0., 1.);
|
|
glTranslatef(dx, dy, 0);
|
|
glRotatef(-string_rotation_, 0., 0., 1.);
|
|
}
|
|
|
|
glTranslatef(x, y, 0.);
|
|
|
|
glColor4f(foreground_color_[R], foreground_color_[G], foreground_color_[B], foreground_color_[A]);
|
|
|
|
glRasterPos3i(0, 0, 0);
|
|
|
|
draw(s);
|
|
|
|
if(horizontal_justification_ != ORIGIN || vertical_justification_ != BASELINE) glPopMatrix();
|
|
|
|
if(!advance_) glPopMatrix();
|
|
}
|
|
|
|
// Draw the (latin1) string at the given position.
|
|
void Face::draw (GLfloat x, GLfloat y, GLfloat z, const char* s)
|
|
{
|
|
if(!advance_) glPushMatrix();
|
|
|
|
if(horizontal_justification_ != ORIGIN || vertical_justification_ != BASELINE)
|
|
{
|
|
glPushMatrix();
|
|
|
|
BBox bbox = measure_nominal(s);
|
|
|
|
GLfloat dx = 0, dy = 0;
|
|
|
|
switch (horizontal_justification_)
|
|
{
|
|
case LEFT: dx = -bbox.x_min_; break;
|
|
case CENTER: dx = -(bbox.x_min_ + bbox.x_max_)/ 2.; break;
|
|
case RIGHT: dx = -bbox.x_max_; break;
|
|
default: break;
|
|
}
|
|
switch (vertical_justification_)
|
|
{
|
|
case BOTTOM: dy = -bbox.y_min_; break;
|
|
case MIDDLE: dy = -(bbox.y_min_ + bbox.y_max_)/ 2.; break;
|
|
case TOP: dy = -bbox.y_max_; break;
|
|
default: break;
|
|
}
|
|
|
|
// There is probably a less expensive way to compute this
|
|
glRotatef(string_rotation_, 0., 0., 1.);
|
|
glTranslatef(dx, dy, 0);
|
|
glRotatef(-string_rotation_, 0., 0., 1.);
|
|
}
|
|
|
|
glTranslatef(x, y, z);
|
|
|
|
glColor4f(foreground_color_[R], foreground_color_[G], foreground_color_[B], foreground_color_[A]);
|
|
|
|
glRasterPos2i(0, 0);
|
|
|
|
draw(s);
|
|
|
|
if(horizontal_justification_ != ORIGIN || vertical_justification_ != BASELINE) glPopMatrix();
|
|
|
|
if(!advance_) glPopMatrix();
|
|
}
|
|
|
|
// Draw the (UNICODE) string at the given position.
|
|
void Face::draw (GLfloat x, GLfloat y, const wchar_t* s)
|
|
{
|
|
if(!advance_)
|
|
glPushMatrix();
|
|
|
|
if(horizontal_justification_ != ORIGIN ||
|
|
vertical_justification_ != BASELINE){
|
|
glPushMatrix();
|
|
|
|
BBox bbox = measure_nominal(s);
|
|
|
|
GLfloat dx = 0, dy = 0;
|
|
|
|
switch (horizontal_justification_){
|
|
case LEFT:
|
|
dx = -bbox.x_min_; break;
|
|
case CENTER:
|
|
dx = -(bbox.x_min_ + bbox.x_max_)/ 2.; break;
|
|
case RIGHT:
|
|
dx = -bbox.x_max_; break;
|
|
}
|
|
switch (vertical_justification_){
|
|
case BOTTOM:
|
|
dy = -bbox.y_min_; break;
|
|
case MIDDLE:
|
|
dy = -(bbox.y_min_ + bbox.y_max_)/ 2.; break;
|
|
case TOP:
|
|
dy = -bbox.y_max_; break;
|
|
}
|
|
|
|
// There is probably a less expensive way to compute this
|
|
|
|
glRotatef(string_rotation_, 0., 0., 1.);
|
|
glTranslatef(dx, dy, 0);
|
|
glRotatef(-string_rotation_, 0., 0., 1.);
|
|
}
|
|
|
|
glTranslatef(x, y, 0.);
|
|
|
|
glColor4f(foreground_color_[R], foreground_color_[G], foreground_color_[B],
|
|
foreground_color_[A]);
|
|
|
|
glRasterPos2i(0, 0);
|
|
|
|
draw(s);
|
|
|
|
if(horizontal_justification_ != ORIGIN ||
|
|
vertical_justification_ != BASELINE)
|
|
glPopMatrix();
|
|
|
|
if(!advance_)
|
|
glPopMatrix();
|
|
}
|
|
|
|
// Draw the (UNICODE) string at the given position.
|
|
void Face::draw (GLfloat x, GLfloat y, GLfloat z, const wchar_t* s)
|
|
{
|
|
if(!advance_) glPushMatrix();
|
|
|
|
if(horizontal_justification_ != ORIGIN || vertical_justification_ != BASELINE)
|
|
{
|
|
glPushMatrix();
|
|
|
|
// In 3D, we need to exert more care in the computation of the
|
|
// bounding box of the text. NOTE: Needs to be fixed up for
|
|
// polygonal faces, too...
|
|
|
|
BBox bbox;
|
|
// Code from measure_nominal, but changed to use measureRaw instead
|
|
if(string_rotation_ == 0.) bbox = measureRaw(s);
|
|
else
|
|
{
|
|
for(unsigned int f=0; f<faces_.size(); f++)
|
|
FT_Set_Transform(faces_[f].face_, 0, 0);
|
|
|
|
bbox = measureRaw(s);
|
|
|
|
float angle;
|
|
if(string_rotation_<0.)
|
|
{
|
|
angle = 360. - fmod(fabs(string_rotation_), 360.f);
|
|
}
|
|
else
|
|
{
|
|
angle = fmod(string_rotation_, 360.f);
|
|
}
|
|
|
|
FT_Matrix rotation_matrix;
|
|
FT_Vector sinus;
|
|
|
|
FT_Vector_Unit(&sinus, (FT_Angle)(angle * 0x10000L));
|
|
|
|
rotation_matrix.xx = sinus.x;
|
|
rotation_matrix.xy = -sinus.y;
|
|
rotation_matrix.yx = sinus.y;
|
|
rotation_matrix.yy = sinus.x;
|
|
|
|
for(unsigned int f=0; f<faces_.size(); f++) FT_Set_Transform(faces_[f].face_, &rotation_matrix, 0);
|
|
}
|
|
|
|
GLfloat dx = 0, dy = 0;
|
|
switch (horizontal_justification_)
|
|
{
|
|
case LEFT: dx = bbox.x_min_; break;
|
|
case CENTER: dx = (bbox.x_min_ + bbox.x_max_)/ 2; break;
|
|
case RIGHT: dx = bbox.x_max_; break;
|
|
}
|
|
switch (vertical_justification_)
|
|
{
|
|
case BOTTOM: dy = bbox.y_min_; break;
|
|
case MIDDLE: dy = (bbox.y_min_ + bbox.y_max_)/2; break;
|
|
case TOP: dy = bbox.y_max_; break;
|
|
}
|
|
|
|
GLint viewport[4];
|
|
GLdouble modelview[16], projection[16];
|
|
|
|
glGetIntegerv(GL_VIEWPORT, viewport);
|
|
glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
|
|
glGetDoublev(GL_PROJECTION_MATRIX, projection);
|
|
|
|
GLdouble x0, y0, z0;
|
|
gluUnProject(0, 0, 0, modelview, projection, viewport, &x0, &y0, &z0);
|
|
|
|
GLdouble dx_m, dy_m, dz_m;
|
|
gluUnProject(dx, dy, 0., modelview, projection, viewport,&dx_m,&dy_m,&dz_m);
|
|
|
|
glTranslated(x0-dx_m, y0-dy_m, z0-dz_m);
|
|
}
|
|
|
|
glTranslatef(x, y, z);
|
|
|
|
glColor4f(foreground_color_[R], foreground_color_[G], foreground_color_[B], foreground_color_[A]);
|
|
|
|
glRasterPos2i(0, 0);
|
|
|
|
draw(s);
|
|
|
|
if(horizontal_justification_ != ORIGIN || vertical_justification_ != BASELINE) glPopMatrix();
|
|
|
|
if(!advance_) glPopMatrix();
|
|
}
|
|
|
|
// Draw the number at the given position per the given format.
|
|
void Face::draw (GLfloat x, GLfloat y, const wchar_t* format, double number)
|
|
{
|
|
draw(x, y, format, number);
|
|
}
|
|
|
|
// Draw the number at the given position per the given format.
|
|
void Face::draw (GLfloat x, GLfloat y, GLfloat z, const wchar_t* format,double number)
|
|
{
|
|
draw(x, y, z, format, number);
|
|
}
|
|
|
|
Raster::Raster (const char* filename, float point_size, FT_UInt resolution)
|
|
: Face(filename, point_size, resolution)
|
|
{
|
|
if(!isValid()) return;
|
|
|
|
init();
|
|
}
|
|
|
|
Raster::Raster (FT_Face face, float point_size, FT_UInt resolution)
|
|
: Face(face, point_size, resolution)
|
|
{
|
|
init();
|
|
}
|
|
|
|
void Raster::init (void)
|
|
{
|
|
character_rotation_z_ = 0;
|
|
setCharSize();
|
|
setCharacterRotationReference('o');
|
|
}
|
|
|
|
Raster::~Raster (void)
|
|
{
|
|
clearCaches();
|
|
}
|
|
|
|
void Raster::setCharacterRotationZ (GLfloat character_rotation_z)
|
|
{
|
|
if(character_rotation_z != character_rotation_z_)
|
|
{
|
|
character_rotation_z_ = character_rotation_z;
|
|
clearCaches();
|
|
}
|
|
}
|
|
|
|
double Raster::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 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; f<faces_.size(); f++)
|
|
{
|
|
glyph_index = FT_Get_Char_Index(faces_[f].face_, c);
|
|
if(glyph_index != 0) break;
|
|
}
|
|
|
|
if(glyph_index == 0) return bbox;
|
|
|
|
FT_Error error = FT_Load_Glyph(faces_[f].face_, glyph_index, FT_LOAD_DEFAULT);
|
|
if(error != 0) return bbox;
|
|
|
|
FT_Glyph glyph;
|
|
error = FT_Get_Glyph(faces_[f].face_->glyph, &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; f<faces_.size(); f++)
|
|
{
|
|
glyph_index = FT_Get_Char_Index(faces_[f].face_, c);
|
|
if(glyph_index != 0) break;
|
|
}
|
|
|
|
if(glyph_index == 0) return bbox;
|
|
|
|
FT_Error error = FT_Load_Glyph(faces_[f].face_, glyph_index,
|
|
FT_LOAD_DEFAULT);
|
|
if(error != 0) return bbox;
|
|
|
|
FT_Glyph glyph;
|
|
error = FT_Get_Glyph(faces_[f].face_->glyph, &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; i<faces_.size(); i++)
|
|
{
|
|
error = FT_Set_Char_Size(faces_[i].face_,(FT_F26Dot6)(point_size_ * 64),(FT_F26Dot6)(point_size_ * 64),resolution_,resolution_);
|
|
if(error != 0) return;
|
|
}
|
|
|
|
if(rotation_reference_glyph_ != 0) setRotationOffset();
|
|
}
|
|
|
|
void Raster::setRotationOffset (void)
|
|
{
|
|
FT_Error error = FT_Load_Glyph(rotation_reference_face_, rotation_reference_glyph_, FT_LOAD_RENDER);
|
|
|
|
if(error != 0) return;
|
|
|
|
rotation_offset_y_ = rotation_reference_face_->glyph->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; r<bitmap.rows; r++)
|
|
{
|
|
GLubyte* bitmap_ptr = &bitmap.buffer[bitmap.pitch * (bitmap.rows - r - 1)];
|
|
|
|
memmove(inverse_ptr, bitmap_ptr, width);
|
|
inverse_ptr += width;
|
|
bitmap_ptr += width;
|
|
}
|
|
|
|
return inverse;
|
|
}
|
|
|
|
void Monochrome::renderGlyph (FT_Face face, FT_UInt glyph_index)
|
|
{
|
|
// Start by retrieving the glyph's data.
|
|
FT_Error error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
|
|
|
|
if(error != 0) return;
|
|
|
|
FT_Glyph original_glyph;
|
|
FT_Glyph glyph;
|
|
|
|
error = FT_Get_Glyph(face->glyph, &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; r<bitmap.rows; r++)
|
|
{
|
|
GLubyte* bitmap_ptr = &bitmap.buffer[bitmap.pitch * (bitmap.rows - r - 1)];
|
|
for(int p=0; p<bitmap.pitch; p++)
|
|
{
|
|
*inverse_ptr++ = *bitmap_ptr++;
|
|
}
|
|
}
|
|
|
|
return inverse;
|
|
}
|
|
|
|
void Grayscale::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_Glyph original_glyph;
|
|
FT_Glyph glyph;
|
|
|
|
error = FT_Get_Glyph(face->glyph, &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; r<bitmap.rows; r++)
|
|
{
|
|
GLubyte* bitmap_ptr = &bitmap.buffer[bitmap.pitch * (bitmap.rows - r - 1)];
|
|
|
|
for(int p=0; p<bitmap.pitch; p++)
|
|
{
|
|
*inverse_ptr++ = *bitmap_ptr ? 255 : 0;
|
|
*inverse_ptr++ = *bitmap_ptr++;
|
|
}
|
|
}
|
|
|
|
return inverse;
|
|
}
|
|
|
|
void Translucent::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_Glyph original_glyph;
|
|
FT_Glyph glyph;
|
|
|
|
error = FT_Get_Glyph(face->glyph, &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; i<faces_.size(); i++)
|
|
{
|
|
FT_Error error = FT_Set_Char_Size(faces_[i].face_,
|
|
0,
|
|
faces_[i].face_->units_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; f<faces_.size(); f++)
|
|
{
|
|
glyph_index = FT_Get_Char_Index(faces_[f].face_, c);
|
|
if(glyph_index != 0) break;
|
|
}
|
|
|
|
if(glyph_index == 0) return bbox;
|
|
|
|
FT_Error error = FT_Load_Glyph(faces_[f].face_, glyph_index, FT_LOAD_DEFAULT);
|
|
if(error != 0) return bbox;
|
|
|
|
FT_Glyph glyph;
|
|
error = FT_Get_Glyph(faces_[f].face_->glyph, &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; f<faces_.size(); f++)
|
|
{
|
|
glyph_index = FT_Get_Char_Index(faces_[f].face_, c);
|
|
if(glyph_index != 0) break;
|
|
}
|
|
|
|
if(glyph_index == 0) return bbox;
|
|
|
|
FT_Error error = FT_Load_Glyph(faces_[f].face_, glyph_index, FT_LOAD_DEFAULT);
|
|
if(error != 0) return bbox;
|
|
|
|
FT_Glyph glyph;
|
|
error = FT_Get_Glyph(faces_[f].face_->glyph, &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; i<outline->tessellation_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; i<outline->tessellation_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; i<filled->tessellation_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; i<filled->tessellation_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; f<faces_.size(); f++)
|
|
{
|
|
FT_Error error = FT_Set_Char_Size(faces_[f].face_,
|
|
(FT_F26Dot6)(point_size_ * 64),
|
|
(FT_F26Dot6)(point_size_ * 64),
|
|
resolution_,
|
|
resolution_);
|
|
if(error != 0) return;
|
|
}
|
|
|
|
if(rotation_reference_glyph_ != 0) setRotationOffset();
|
|
}
|
|
|
|
void Texture::setRotationOffset (void)
|
|
{
|
|
FT_Error error = FT_Load_Glyph(rotation_reference_face_, rotation_reference_glyph_, FT_LOAD_RENDER);
|
|
|
|
if(error != 0) return;
|
|
|
|
rotation_offset_y_ = rotation_reference_face_->glyph->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; f<faces_.size(); f++)
|
|
{
|
|
glyph_index = FT_Get_Char_Index(faces_[f].face_, c);
|
|
if(glyph_index != 0) break;
|
|
}
|
|
|
|
if(glyph_index == 0) return bbox;
|
|
|
|
FT_Error error = FT_Load_Glyph(faces_[f].face_, glyph_index,
|
|
FT_LOAD_DEFAULT);
|
|
if(error != 0) return bbox;
|
|
|
|
FT_Glyph glyph;
|
|
error = FT_Get_Glyph(faces_[f].face_->glyph, &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; f<faces_.size(); f++)
|
|
{
|
|
glyph_index = FT_Get_Char_Index(faces_[f].face_, c);
|
|
if(glyph_index != 0) break;
|
|
}
|
|
|
|
if(glyph_index == 0) return bbox;
|
|
|
|
FT_Error error = FT_Load_Glyph(faces_[f].face_, glyph_index, FT_LOAD_DEFAULT);
|
|
if(error != 0) return bbox;
|
|
|
|
FT_Glyph glyph;
|
|
error = FT_Get_Glyph(faces_[f].face_->glyph, &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(c<a)c *= 2;
|
|
|
|
return c;
|
|
}
|
|
|
|
MonochromeTexture::MonochromeTexture (const char* filename, float point_size, FT_UInt resolution)
|
|
: Texture(filename, point_size, resolution)
|
|
{
|
|
return;
|
|
}
|
|
|
|
MonochromeTexture::MonochromeTexture (FT_Face face, float point_size, FT_UInt resolution)
|
|
: Texture(face, point_size, resolution)
|
|
{
|
|
return;
|
|
}
|
|
|
|
MonochromeTexture::~MonochromeTexture (void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Round up the size of the image to a power of two, but otherwise
|
|
// use the bitmap as is (i.e., don't expand it into separate
|
|
// luminance and alpha components)
|
|
GLubyte* MonochromeTexture::invertBitmap (const FT_Bitmap& bitmap, int* width, int* height)
|
|
{
|
|
*width = nearestPowerCeil(bitmap.width);
|
|
*height = nearestPowerCeil(bitmap.rows);
|
|
|
|
GLubyte* inverse = new GLubyte[ (*width + 7) / 8 * *height ];
|
|
GLubyte* inverse_ptr = inverse;
|
|
|
|
memset(inverse, 0, sizeof(GLubyte)*(*width + 7)/ 8 * *height);
|
|
|
|
for(int r=0; r<bitmap.rows; r++)
|
|
{
|
|
GLubyte* bitmap_ptr = &bitmap.buffer[bitmap.pitch * (bitmap.rows - r - 1)];
|
|
for(int p=0; p<bitmap.pitch; p++)
|
|
{
|
|
|
|
*inverse_ptr++ = *bitmap_ptr++;
|
|
}
|
|
inverse_ptr += ((*width + 7)/ 8 - bitmap.pitch);
|
|
}
|
|
|
|
return inverse;
|
|
}
|
|
|
|
// Hmm. This is the only routine which is different between the different
|
|
// styles.
|
|
|
|
void MonochromeTexture::bindTexture (FT_Face face, FT_UInt glyph_index)
|
|
{
|
|
GTOCI texobj = glyph_texobjs_.find(glyph_index);
|
|
|
|
if(texobj != glyph_texobjs_.end()) return;
|
|
|
|
// Retrieve the glyph's data.
|
|
FT_Error error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
|
|
|
|
if(error != 0) return;
|
|
|
|
error = FT_Render_Glyph(face->glyph, 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; r<bitmap.rows; r++)
|
|
{
|
|
GLubyte* bitmap_ptr = &bitmap.buffer[bitmap.pitch * (bitmap.rows - r - 1)];
|
|
for(int p=0; p<bitmap.width; p++)
|
|
{
|
|
*inverse_ptr++ = *bitmap_ptr++;
|
|
}
|
|
inverse_ptr += (*width - bitmap.pitch);
|
|
}
|
|
return inverse;
|
|
}
|
|
|
|
// Hmm. This is the only routine which is different between the different
|
|
// styles.
|
|
void GrayscaleTexture::bindTexture (FT_Face face, FT_UInt glyph_index)
|
|
{
|
|
GTOCI texobj = glyph_texobjs_.find(glyph_index);
|
|
|
|
if(texobj != glyph_texobjs_.end()) return;
|
|
|
|
// Retrieve the glyph's data.
|
|
FT_Error error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
|
|
|
|
if(error != 0) return;
|
|
|
|
error = FT_Render_Glyph(face->glyph, 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; r<bitmap.rows; r++)
|
|
{
|
|
GLubyte* bitmap_ptr = &bitmap.buffer[bitmap.pitch * (bitmap.rows - r - 1)];
|
|
|
|
for(int p=0; p<bitmap.width; p++)
|
|
{
|
|
*inverse_ptr++ = 0xff;
|
|
*inverse_ptr++ = *bitmap_ptr++;
|
|
}
|
|
|
|
inverse_ptr += 2 * (*width - bitmap.pitch);
|
|
}
|
|
return inverse;
|
|
}
|
|
|
|
// Hmm. This is the only routine which is different between the different
|
|
// styles.
|
|
void TranslucentTexture::bindTexture (FT_Face face, FT_UInt glyph_index)
|
|
{
|
|
GTOCI texobj = glyph_texobjs_.find(glyph_index);
|
|
|
|
if(texobj != glyph_texobjs_.end())
|
|
return;
|
|
|
|
// Retrieve the glyph's data.
|
|
|
|
FT_Error error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
|
|
|
|
if(error != 0) return;
|
|
|
|
error = FT_Render_Glyph(face->glyph, 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
|
|
|