open_toontown_panda3d/panda/src/text/dynamicTextFont.cxx

584 lines
20 KiB
C++

// Filename: dynamicTextFont.cxx
// Created by: drose (08Feb02)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license. You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://www.panda3d.org/license.txt .
//
// To contact the maintainers of this program write to
// panda3d@yahoogroups.com .
//
////////////////////////////////////////////////////////////////////
#include "dynamicTextFont.h"
#ifdef HAVE_FREETYPE
#include "config_text.h"
#include "config_util.h"
bool DynamicTextFont::_update_cleared_glyphs = text_update_cleared_glyphs;
FT_Library DynamicTextFont::_ft_library;
bool DynamicTextFont::_ft_initialized = false;
bool DynamicTextFont::_ft_ok = false;
TypeHandle DynamicTextFont::_type_handle;
// This constant determines how big a particular point size font
// appears to be. By convention, 10 points is 1 unit (e.g. 1 foot)
// high.
static const float points_per_unit = 10.0f;
// A universal typographic convention.
static const float points_per_inch = 72.0f;
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::Constructor
// Access: Published
// Description: The constructor expects the name of some font file
// that FreeType can read, along with face_index,
// indicating which font within the file to load
// (usually 0).
////////////////////////////////////////////////////////////////////
DynamicTextFont::
DynamicTextFont(const Filename &font_filename, int face_index) {
_texture_margin = text_texture_margin;
_poly_margin = text_poly_margin;
_page_x_size = text_page_x_size;
_page_y_size = text_page_y_size;
_point_size = text_point_size;
_pixels_per_unit = text_pixels_per_unit;
_small_caps = text_small_caps;
_small_caps_scale = text_small_caps_scale;
// We don't necessarily want to use mipmaps, since we don't want to
// regenerate those every time the texture changes, but we probably
// do want at least linear filtering. Use whatever the Configrc
// file suggests.
_minfilter = text_minfilter;
_magfilter = text_magfilter;
// Anisotropic filtering can help the look of the text, and doesn't
// require generating mipmaps, but does require hardware support.
_anisotropic_degree = text_anisotropic_degree;
_preferred_page = 0;
if (!_ft_initialized) {
initialize_ft_library();
}
if (!_ft_ok) {
text_cat.error()
<< "Unable to read font " << font_filename
<< ": FreeType library not initialized properly.\n";
return;
}
Filename path(font_filename);
path.resolve_filename(get_model_path());
if (!path.exists()) {
text_cat.error()
<< "Unable to find font file " << font_filename << "\n";
} else {
string os_specific = path.to_os_specific();
int error = FT_New_Face(_ft_library,
os_specific.c_str(),
face_index,
&_face);
if (error == FT_Err_Unknown_File_Format) {
text_cat.error()
<< "Unable to read font " << font_filename << ": unknown file format.\n";
} else if (error) {
text_cat.error()
<< "Unable to read font " << font_filename << ": invalid.\n";
} else {
string name = _face->family_name;
if (_face->style_name != NULL) {
name += " ";
name += _face->style_name;
}
set_name(name);
text_cat.info()
<< "Loaded font " << get_name() << "\n";
_is_valid = true;
reset_scale();
}
}
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::Constructor
// Access: Published, Virtual
// Description:
////////////////////////////////////////////////////////////////////
DynamicTextFont::
~DynamicTextFont() {
if (_is_valid) {
FT_Done_Face(_face);
_is_valid = false;
}
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::get_num_pages
// Access: Published
// Description: Returns the number of pages associated with the font.
// Initially, the font has zero pages; when the first
// piece of text is rendered with the font, it will add
// additional pages as needed. Each page is a Texture
// object that contains the images for each of the
// glyphs currently in use somewhere.
////////////////////////////////////////////////////////////////////
int DynamicTextFont::
get_num_pages() const {
return _pages.size();
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::get_page
// Access: Published
// Description: Returns the nth page associated with the font.
// Initially, the font has zero pages; when the first
// piece of text is rendered with the font, it will add
// additional pages as needed. Each page is a Texture
// object that contains the images for each of the
// glyphs currently in use somewhere.
////////////////////////////////////////////////////////////////////
DynamicTextPage *DynamicTextFont::
get_page(int n) const {
nassertr(n >= 0 && n < (int)_pages.size(), (DynamicTextPage *)NULL);
return _pages[n];
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::garbage_collect
// Access: Published
// Description: Removes all of the glyphs from the font that are no
// longer being used by any Geoms. Returns the number
// of glyphs removed.
////////////////////////////////////////////////////////////////////
int DynamicTextFont::
garbage_collect() {
int removed_count = 0;
// First, remove all the old entries from our cache index.
Cache new_cache;
Cache::iterator ci;
for (ci = _cache.begin(); ci != _cache.end(); ++ci) {
DynamicTextGlyph *glyph = (*ci).second;
if (glyph->_geom_count != 0) {
// Keep this one.
new_cache.insert(new_cache.end(), (*ci));
} else {
// Drop this one.
removed_count++;
}
}
_cache.swap(new_cache);
// Now, go through each page and do the same thing.
Pages::iterator pi;
for (pi = _pages.begin(); pi != _pages.end(); ++pi) {
DynamicTextPage *page = (*pi);
page->garbage_collect();
}
return removed_count;
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::update_texture_memory
// Access: Published
// Description: Marks all of the pages dirty so they will be reloaded
// into texture memory. This is necessary only if
// set_update_cleared_glyphs() is false, and some
// textures have recently been removed from the pages
// (for instance, after a call to garbage_collect()).
//
// Calling this just ensures that what you see when you
// apply the texture page to a polygon represents what
// is actually stored on the page.
////////////////////////////////////////////////////////////////////
void DynamicTextFont::
update_texture_memory() {
Pages::iterator pi;
for (pi = _pages.begin(); pi != _pages.end(); ++pi) {
DynamicTextPage *page = (*pi);
page->mark_dirty(Texture::DF_image);
}
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::clear
// Access: Published
// Description: Drops all the glyphs out of the cache and frees any
// association with any previously-generated pages.
//
// Calling this frequently can result in wasted texture
// memory, as any previously rendered text will still
// keep a pointer to the old, previously-generated
// pages. As long as the previously rendered text
// remains around, the old pages will also remain
// around.
////////////////////////////////////////////////////////////////////
void DynamicTextFont::
clear() {
_cache.clear();
_pages.clear();
_empty_glyphs.clear();
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::write
// Access: Published, Virtual
// Description:
////////////////////////////////////////////////////////////////////
void DynamicTextFont::
write(ostream &out, int indent_level) const {
static const int max_glyph_name = 1024;
char glyph_name[max_glyph_name];
indent(out, indent_level)
<< "DynamicTextFont " << get_name() << ", "
<< get_num_pages() << " pages, "
<< _cache.size() << " glyphs:\n";
Cache::const_iterator ci;
for (ci = _cache.begin(); ci != _cache.end(); ++ci) {
int glyph_index = (*ci).first;
DynamicTextGlyph *glyph = (*ci).second;
indent(out, indent_level + 2)
<< glyph_index;
if (FT_HAS_GLYPH_NAMES(_face)) {
int error = FT_Get_Glyph_Name(_face, glyph_index,
glyph_name, max_glyph_name);
// Some fonts, notably MS Mincho, claim to have glyph names but
// only report ".notdef" as the name of each glyph. Thanks.
if (!error && strcmp(glyph_name, ".notdef") != 0) {
out << " (" << glyph_name << ")";
}
}
out << ", count = " << glyph->_geom_count << "\n";
}
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::get_glyph
// Access: Public, Virtual
// Description: Gets the glyph associated with the given character
// code, as well as an optional scaling parameter that
// should be applied to the glyph's geometry and advance
// parameters. Returns true if the glyph exists, false
// if it does not. Even if the return value is false,
// the value for glyph might be filled in with a
// printable glyph.
////////////////////////////////////////////////////////////////////
bool DynamicTextFont::
get_glyph(int character, const TextGlyph *&glyph, float &glyph_scale) {
if (!_is_valid) {
glyph = (TextGlyph *)NULL;
return false;
}
glyph_scale = 1.0f;
if (character < 128 && islower(character) && get_small_caps()) {
// If we have small_caps on, we implement lowercase letters by
// applying a scale to the corresponding uppercase letter.
glyph_scale = get_small_caps_scale();
character = toupper(character);
}
int glyph_index = FT_Get_Char_Index(_face, character);
Cache::iterator ci = _cache.find(glyph_index);
if (ci != _cache.end()) {
glyph = (*ci).second;
} else {
DynamicTextGlyph *dynamic_glyph = make_glyph(glyph_index);
_cache.insert(Cache::value_type(glyph_index, dynamic_glyph));
glyph = dynamic_glyph;
}
return (glyph_index != 0 && glyph != (DynamicTextGlyph *)NULL);
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::update_filters
// Access: Private
// Description: Reapplies all current filter settings to all of the
// pages. This is normally called whenever the filter
// settings change.
////////////////////////////////////////////////////////////////////
void DynamicTextFont::
update_filters() {
Pages::iterator pi;
for (pi = _pages.begin(); pi != _pages.end(); ++pi) {
DynamicTextPage *page = (*pi);
page->set_minfilter(_minfilter);
page->set_magfilter(_magfilter);
page->set_anisotropic_degree(_anisotropic_degree);
}
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::reset_scale
// Access: Private
// Description: Resets the font to use the current _point_size and
// _pixels_per_unit. Returns true if successful, false
// otherwise.
////////////////////////////////////////////////////////////////////
bool DynamicTextFont::
reset_scale() {
float units_per_inch = (points_per_inch / points_per_unit);
int dpi = (int)(_pixels_per_unit * units_per_inch);
int error = FT_Set_Char_Size(_face,
(int)(_point_size * 64), (int)(_point_size * 64),
dpi, dpi);
if (error) {
// If we were unable to set a particular char size, perhaps we
// have a non-scalable font. Try to figure out the closest
// available size.
int desired_height = (int)(_pixels_per_unit * _point_size / points_per_unit + 0.5f);
int best_size = -1;
if (_face->num_fixed_sizes > 0) {
best_size = 0;
int best_diff = abs(desired_height - _face->available_sizes[0].height);
for (int i = 1; i < _face->num_fixed_sizes; i++) {
int diff = abs(desired_height - _face->available_sizes[i].height);
if (diff < best_diff) {
best_size = i;
best_diff = diff;
}
}
}
if (best_size >= 0) {
int pixel_height = _face->available_sizes[best_size].height;
int pixel_width = _face->available_sizes[best_size].width;
error = FT_Set_Pixel_Sizes(_face, pixel_width, pixel_height);
if (!error) {
text_cat.info()
<< "Using " << pixel_height << "-pixel font for "
<< get_name() << "\n";
_pixels_per_unit = pixel_height * points_per_unit / _point_size;
}
}
}
if (error) {
text_cat.warning()
<< "Unable to set " << get_name()
<< " to " << _point_size << "pt at " << dpi << " dpi.\n";
_line_height = 1.0f;
return false;
}
_line_height = _face->size->metrics.height / (_pixels_per_unit * 64.0f);
// Determine the correct width for a space.
error = FT_Load_Char(_face, ' ', FT_LOAD_DEFAULT);
if (error) {
// Space isn't defined. Oh well.
_space_advance = 0.25f * _line_height;
} else {
_space_advance = _face->glyph->advance.x / (_pixels_per_unit * 64.0f);
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::make_glyph
// Access: Private
// Description: Slots a space in the texture map for the new
// character and renders the glyph, returning the
// newly-created TextGlyph object, or NULL if the
// glyph cannot be created for some reason.
////////////////////////////////////////////////////////////////////
DynamicTextGlyph *DynamicTextFont::
make_glyph(int glyph_index) {
int error = FT_Load_Glyph(_face, glyph_index, FT_LOAD_RENDER);
if (error) {
text_cat.error()
<< "Unable to render glyph " << glyph_index << "\n";
return (DynamicTextGlyph *)NULL;
}
FT_GlyphSlot slot = _face->glyph;
FT_Bitmap &bitmap = slot->bitmap;
float advance = slot->advance.x / 64.0;
if (bitmap.width == 0 || bitmap.rows == 0) {
// If we got an empty bitmap, it's a special case.
PT(DynamicTextGlyph) glyph = new DynamicTextGlyph(advance / _pixels_per_unit);
_empty_glyphs.push_back(glyph);
return glyph;
} else {
DynamicTextGlyph *glyph = slot_glyph(bitmap.width, bitmap.rows);
if (bitmap.pixel_mode == ft_pixel_mode_grays && bitmap.num_grays == 256) {
// This is the easy case: we can memcpy the rendered glyph
// directly into our texture image, one row at a time.
unsigned char *buffer_row = bitmap.buffer;
for (int yi = 0; yi < bitmap.rows; yi++) {
unsigned char *texture_row = glyph->get_row(yi);
nassertr(texture_row != (unsigned char *)NULL, (DynamicTextGlyph *)NULL);
memcpy(texture_row, buffer_row, bitmap.width);
buffer_row += bitmap.pitch;
}
} else if (bitmap.pixel_mode == ft_pixel_mode_mono) {
// This is a little bit more work: we have to expand the
// one-bit-per-pixel bitmap into a one-byte-per-pixel texture.
unsigned char *buffer_row = bitmap.buffer;
for (int yi = 0; yi < bitmap.rows; yi++) {
unsigned char *texture_row = glyph->get_row(yi);
nassertr(texture_row != (unsigned char *)NULL, (DynamicTextGlyph *)NULL);
int bit = 0x80;
unsigned char *b = buffer_row;
for (int xi = 0; xi < bitmap.width; xi++) {
if (*b & bit) {
texture_row[xi] = 0xff;
} else {
texture_row[xi] = 0x00;
}
bit >>= 1;
if (bit == 0) {
++b;
bit = 0x80;
}
}
buffer_row += bitmap.pitch;
}
} else if (bitmap.pixel_mode == ft_pixel_mode_grays) {
// Here we must expand a grayscale pixmap with n levels of gray
// into our 256-level texture.
unsigned char *buffer_row = bitmap.buffer;
for (int yi = 0; yi < bitmap.rows; yi++) {
unsigned char *texture_row = glyph->get_row(yi);
nassertr(texture_row != (unsigned char *)NULL, (DynamicTextGlyph *)NULL);
for (int xi = 0; xi < bitmap.width; xi++) {
texture_row[xi] = (int)(buffer_row[xi] * 255) / (bitmap.num_grays - 1);
}
buffer_row += bitmap.pitch;
}
} else {
text_cat.error()
<< "Unexpected pixel mode in bitmap: " << (int)bitmap.pixel_mode << "\n";
}
glyph->_page->mark_dirty(Texture::DF_image);
glyph->make_geom(slot->bitmap_top, slot->bitmap_left, advance,
_poly_margin, _pixels_per_unit);
return glyph;
}
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::slot_glyph
// Access: Private
// Description: Chooses a page that will have room for a glyph of the
// indicated size (after expanding the indicated size by
// the current margin). Returns the newly-allocated
// glyph on the chosen page; the glyph has not been
// filled in yet except with its size.
////////////////////////////////////////////////////////////////////
DynamicTextGlyph *DynamicTextFont::
slot_glyph(int x_size, int y_size) {
// Increase the indicated size by the current margin.
x_size += _texture_margin * 2;
y_size += _texture_margin * 2;
if (!_pages.empty()) {
// Start searching on the preferred page. That way, we'll fill up
// the preferred page first, and we can gradually rotate this page
// around; it keeps us from spending too much time checking
// already-filled pages for space.
_preferred_page = _preferred_page % _pages.size();
int pi = _preferred_page;
do {
DynamicTextPage *page = _pages[pi];
DynamicTextGlyph *glyph = page->slot_glyph(x_size, y_size, _texture_margin);
if (glyph != (DynamicTextGlyph *)NULL) {
// Once we found a page to hold the glyph, that becomes our
// new preferred page.
_preferred_page = pi;
return glyph;
}
if (page->is_empty()) {
// If we couldn't even put it on an empty page, we're screwed.
text_cat.error()
<< "Glyph of size " << x_size << " by " << y_size
<< " pixels won't fit on an empty page.\n";
return (DynamicTextGlyph *)NULL;
}
pi = (pi + 1) % _pages.size();
} while (pi != _preferred_page);
}
// All pages are filled. Can we free up space by removing some old
// glyphs?
if (garbage_collect() != 0) {
// Yes, we just freed up some space. Try once more, recursively.
return slot_glyph(x_size, y_size);
} else {
// No good; all recorded glyphs are actually in use. We need to
// make a new page.
_preferred_page = _pages.size();
PT(DynamicTextPage) page = new DynamicTextPage(this);
_pages.push_back(page);
return page->slot_glyph(x_size, y_size, _texture_margin);
}
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::initialize_ft_library
// Access: Private, Static
// Description: Should be called exactly once to initialize the
// FreeType library.
////////////////////////////////////////////////////////////////////
void DynamicTextFont::
initialize_ft_library() {
if (!_ft_initialized) {
int error = FT_Init_FreeType(&_ft_library);
_ft_initialized = true;
if (error) {
text_cat.error()
<< "Unable to initialize FreeType; DynamicTextFonts will not load.\n";
} else {
_ft_ok = true;
}
}
}
#endif // HAVE_FREETYPE