further refinements, proper caching for dynamic text

This commit is contained in:
David Rose 2002-02-11 19:21:44 +00:00
parent d53603f85b
commit d0b7f1b24c
24 changed files with 829 additions and 140 deletions

View File

@ -16,6 +16,7 @@
dynamicTextFont.I dynamicTextFont.h \
dynamicTextGlyph.I dynamicTextGlyph.h \
dynamicTextPage.I dynamicTextPage.h \
geomTextGlyph.I geomTextGlyph.h \
staticTextFont.I staticTextFont.h \
textFont.I textFont.h \
textGlyph.I textGlyph.h \
@ -26,6 +27,7 @@
dynamicTextFont.cxx \
dynamicTextGlyph.cxx \
dynamicTextPage.cxx \
geomTextGlyph.cxx \
staticTextFont.cxx \
textFont.cxx textGlyph.cxx
@ -34,6 +36,7 @@
dynamicTextFont.I dynamicTextFont.h \
dynamicTextGlyph.I dynamicTextGlyph.h \
dynamicTextPage.I dynamicTextPage.h \
geomTextGlyph.I geomTextGlyph.h \
staticTextFont.I staticTextFont.h \
textFont.I textFont.h \
textGlyph.I textGlyph.h \

View File

@ -22,6 +22,7 @@
#include "textNode.h"
#include "dynamicTextFont.h"
#include "dynamicTextPage.h"
#include "geomTextGlyph.h"
#include <dconfig.h>
@ -36,7 +37,18 @@ ConfigureFn(config_text) {
#ifdef HAVE_FREETYPE
DynamicTextFont::init_type();
DynamicTextPage::init_type();
GeomTextGlyph::init_type();
GeomTextGlyph::register_with_read_factory();
#endif
}
const bool flatten_text = config_text.GetBool("flatten-text", true);
const bool text_flatten = config_text.GetBool("text-flatten", true);
const bool text_update_cleared_glyphs = config_text.GetBool("text-update-cleared-glyphs", false);
const int text_texture_margin = config_text.GetInt("text-texture-margin", 2);
const float text_poly_margin = config_text.GetFloat("text-poly-margin", 1.0f);
const int text_page_x_size = config_text.GetInt("text-page-x-size", 256);
const int text_page_y_size = config_text.GetInt("text-page-y-size", 256);
const float text_point_size = config_text.GetFloat("text-point-size", 10.0f);
const float text_pixels_per_unit = config_text.GetFloat("text-pixels-per-unit", 40.0f);
const bool text_small_caps = config_text.GetBool("text-small-caps", false);
const float text_small_caps_scale = config_text.GetFloat("text-small-caps-scale", 0.8f);

View File

@ -26,6 +26,15 @@ class DSearchPath;
NotifyCategoryDecl(text, EXPCL_PANDA, EXPTP_PANDA);
extern const bool flatten_text;
extern const bool text_flatten;
extern const bool text_update_cleared_glyphs;
extern const int text_texture_margin;
extern const float text_poly_margin;
extern const int text_page_x_size;
extern const int text_page_y_size;
extern const float text_point_size;
extern const float text_pixels_per_unit;
extern const bool text_small_caps;
extern const float text_small_caps_scale;
#endif

View File

@ -82,6 +82,67 @@ get_pixels_per_unit() const {
return _pixels_per_unit;
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::set_small_caps
// Access: Published
// Description: Sets the small_caps flag. When this is set,
// lowercase letters are generated as scaled-down
// versions of their uppercase equivalents. This is
// particularly useful to set for fonts that do not have
// lowercase letters.
//
// It is also a good idea to set this for a font that
// has already implemented lowercase letters as
// scaled-down versions of their uppercase equivalents,
// since without this flag the texture memory may
// needlessly duplicate equivalent glyphs for upper and
// lowercase letters. Setting this flag causes the
// texture memory to share the mixed-case letters.
//
// The amount by which the lowercase letters are scaled
// is specified by set_small_caps_scale().
////////////////////////////////////////////////////////////////////
INLINE void DynamicTextFont::
set_small_caps(bool small_caps) {
_small_caps = small_caps;
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::get_small_caps
// Access: Published
// Description: Returns the small_caps flag. See set_small_caps().
////////////////////////////////////////////////////////////////////
INLINE bool DynamicTextFont::
get_small_caps() const {
return _small_caps;
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::set_small_caps_scale
// Access: Published
// Description: Sets the scale factor applied to lowercase letters
// from their uppercase equivalents, when the small_caps
// flag is in effect. See set_small_caps(). Normally,
// this will be a number less than one.
////////////////////////////////////////////////////////////////////
INLINE void DynamicTextFont::
set_small_caps_scale(float small_caps_scale) {
_small_caps_scale = small_caps_scale;
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::get_small_caps_scale
// Access: Published
// Description: Returns the scale factor applied to lowercase letters
// from their uppercase equivalents, when the small_caps
// flag is in effect. See set_small_caps() and
// set_small_caps_scale().
////////////////////////////////////////////////////////////////////
INLINE float DynamicTextFont::
get_small_caps_scale() const {
return _small_caps_scale;
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::set_texture_margin
// Access: Published
@ -168,3 +229,40 @@ INLINE int DynamicTextFont::
get_page_y_size() const {
return _page_y_size;
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::set_update_cleared_glyphs
// Access: Published, Static
// Description: Sets the flag indicating whether texture memory
// should be updated immediately as old glyphs are
// removed. If this is true, texture memory will be
// immediately updated when old glyphs are removed from
// the pages. If this is false (the default), texture
// memory may not be updated until the page is next
// written to, that is, the next time a glyph is
// recorded on that page.
//
// Most of the time, there is no reason to set this
// true, unless you are debugging the DynamicTextFont
// code and want to be able to see exactly what is in
// each texture map at any given time.
//
// This is a global flag across all DynamicTextFont
// objects.
////////////////////////////////////////////////////////////////////
INLINE void DynamicTextFont::
set_update_cleared_glyphs(bool update_cleared_glyphs) {
_update_cleared_glyphs = update_cleared_glyphs;
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::get_update_cleared_glyphs
// Access: Published, Static
// Description: Returns the flag indicating whether texture memory
// should be updated immediately as old glyphs are
// removed. See set_update_cleared_glyphs().
////////////////////////////////////////////////////////////////////
INLINE bool DynamicTextFont::
get_update_cleared_glyphs() {
return _update_cleared_glyphs;
}

View File

@ -22,6 +22,9 @@
#include "config_text.h"
#include "config_util.h"
#include "ctype.h"
bool DynamicTextFont::_update_cleared_glyphs = text_update_cleared_glyphs;
FT_Library DynamicTextFont::_ft_library;
bool DynamicTextFont::_ft_initialized = false;
@ -31,10 +34,11 @@ TypeHandle DynamicTextFont::_type_handle;
// This constant determines how big a particular point size font
// appears. By convention, 10 points is 1 foot high.
// 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 convention.
// A universal typographic convention.
static const float points_per_inch = 72.0f;
////////////////////////////////////////////////////////////////////
@ -47,19 +51,24 @@ static const float points_per_inch = 72.0f;
////////////////////////////////////////////////////////////////////
DynamicTextFont::
DynamicTextFont(const Filename &font_filename, int face_index) {
_texture_margin = 2;
_poly_margin = 1.0f;
_page_x_size = 256;
_page_y_size = 256;
_point_size = 10.0f;
_pixels_per_unit = 40.0f;
_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;
_preferred_page = 0;
if (!_ft_initialized) {
initialize_ft_library();
}
if (!_ft_ok) {
text_cat.error()
<< "Unable to read font " << font_filename << ": FreeType library not available.\n";
<< "Unable to read font " << font_filename
<< ": FreeType library not initialized properly.\n";
return;
}
@ -128,6 +137,64 @@ get_page(int n) const {
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
@ -143,8 +210,9 @@ get_page(int n) const {
////////////////////////////////////////////////////////////////////
void DynamicTextFont::
clear() {
_pages.clear();
_cache.clear();
_pages.clear();
_empty_glyphs.clear();
}
////////////////////////////////////////////////////////////////////
@ -156,29 +224,53 @@ void DynamicTextFont::
write(ostream &out, int indent_level) const {
indent(out, indent_level)
<< "DynamicTextFont " << get_name() << ", "
<< _cache.size() << " glyphs, "
<< get_num_pages() << " pages.\n";
<< 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;
out << ", count = " << glyph->_geom_count << "\n";
}
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextFont::get_glyph
// Access: Public, Virtual
// Description: Returns the glyph associated with the given character
// code, or NULL if there is no such glyph.
// 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.
////////////////////////////////////////////////////////////////////
const TextGlyph *DynamicTextFont::
get_glyph(int character) {
Cache::iterator ci = _cache.find(character);
if (ci != _cache.end()) {
return (*ci).second;
}
bool DynamicTextFont::
get_glyph(int character, const TextGlyph *&glyph, float &glyph_scale) {
if (!_is_valid) {
return (TextGlyph *)NULL;
return false;
}
DynamicTextGlyph *glyph = make_glyph(character);
_cache.insert(Cache::value_type(character, glyph));
return glyph;
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 != (DynamicTextGlyph *)NULL);
}
////////////////////////////////////////////////////////////////////
@ -209,6 +301,17 @@ reset_scale() {
// have a scalable font or otherwise?
float pixel_size = _point_size * (_pixels_per_unit / points_per_unit);
_line_height = (float)_face->height * pixel_size / ((float)_face->units_per_EM * 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;
}
@ -221,46 +324,55 @@ reset_scale() {
// glyph cannot be created for some reason.
////////////////////////////////////////////////////////////////////
DynamicTextGlyph *DynamicTextFont::
make_glyph(int character) {
int error = FT_Load_Char(_face, character, FT_LOAD_RENDER);
make_glyph(int glyph_index) {
int error = FT_Load_Glyph(_face, glyph_index, FT_LOAD_RENDER);
if (error) {
text_cat.error()
<< "Unable to render character " << character << "\n";
<< "Unable to render glyph " << glyph_index << "\n";
return (DynamicTextGlyph *)NULL;
}
FT_GlyphSlot slot = _face->glyph;
FT_Bitmap &bitmap = slot->bitmap;
if (bitmap.pixel_mode != ft_pixel_mode_grays) {
text_cat.error()
<< "Unexpected pixel mode in bitmap: " << (int)bitmap.pixel_mode << "\n";
return (DynamicTextGlyph *)NULL;
}
if (bitmap.num_grays != 256) {
// We expect 256 levels of grayscale to come back from FreeType,
// since that's what we asked for.
text_cat.warning()
<< "Expected 256 levels of gray, got " << bitmap.num_grays << "\n";
}
DynamicTextGlyph *glyph = slot_glyph(bitmap.width, bitmap.rows);
// Now copy the rendered glyph into the 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);
memcpy(texture_row, buffer_row, bitmap.width);
buffer_row += bitmap.pitch;
}
glyph->_page->mark_dirty(Texture::DF_image);
float advance = slot->advance.x / 64.0;
glyph->make_geom(slot->bitmap_top, slot->bitmap_left, advance,
_poly_margin, _pixels_per_unit);
return glyph;
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 {
if (bitmap.pixel_mode != ft_pixel_mode_grays) {
text_cat.error()
<< "Unexpected pixel mode in bitmap: " << (int)bitmap.pixel_mode << "\n";
return (DynamicTextGlyph *)NULL;
}
if (bitmap.num_grays != 256) {
// We expect 256 levels of grayscale to come back from FreeType,
// since that's what we asked for.
text_cat.warning()
<< "Expected 256 levels of gray, got " << bitmap.num_grays << "\n";
}
DynamicTextGlyph *glyph = slot_glyph(bitmap.width, bitmap.rows);
// Now copy the rendered glyph into the 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);
memcpy(texture_row, buffer_row, bitmap.width);
buffer_row += bitmap.pitch;
}
glyph->_page->mark_dirty(Texture::DF_image);
glyph->make_geom(slot->bitmap_top, slot->bitmap_left, advance,
_poly_margin, _pixels_per_unit);
return glyph;
}
}
////////////////////////////////////////////////////////////////////
@ -278,28 +390,50 @@ slot_glyph(int x_size, int y_size) {
x_size += _texture_margin * 2;
y_size += _texture_margin * 2;
Pages::iterator pi;
for (pi = _pages.begin(); pi != _pages.end(); ++pi) {
DynamicTextPage *page = (*pi);
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;
DynamicTextGlyph *glyph = page->slot_glyph(x_size, y_size, _texture_margin);
if (glyph != (DynamicTextGlyph *)NULL) {
return glyph;
}
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 in on an empty page, we're screwed.
text_cat.error()
<< "Glyph of size " << x_size << " by " << y_size
<< " won't fit on an empty page.\n";
return (DynamicTextGlyph *)NULL;
}
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);
}
// We need to make a new page.
PT(DynamicTextPage) page = new DynamicTextPage(this);
_pages.push_back(page);
return page->slot_glyph(x_size, y_size, _texture_margin);
// 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);
}
}

View File

@ -52,6 +52,11 @@ PUBLISHED:
INLINE bool set_pixels_per_unit(float pixels_per_unit);
INLINE float get_pixels_per_unit() const;
INLINE void set_small_caps(bool small_caps);
INLINE bool get_small_caps() const;
INLINE void set_small_caps_scale(float small_caps_scale);
INLINE float get_small_caps_scale() const;
INLINE void set_texture_margin(int texture_margin);
INLINE int get_texture_margin() const;
INLINE void set_poly_margin(float poly_margin);
@ -61,35 +66,54 @@ PUBLISHED:
INLINE int get_page_x_size() const;
INLINE int get_page_y_size() const;
INLINE static void set_update_cleared_glyphs(bool update_cleared_glyphs);
INLINE static bool get_update_cleared_glyphs();
int get_num_pages() const;
DynamicTextPage *get_page(int n) const;
int garbage_collect();
void update_texture_memory();
void clear();
virtual void write(ostream &out, int indent_level) const;
public:
virtual const TextGlyph *get_glyph(int character);
virtual bool get_glyph(int character, const TextGlyph *&glyph,
float &glyph_scale);
private:
bool reset_scale();
DynamicTextGlyph *make_glyph(int character);
DynamicTextGlyph *make_glyph(int glyph_index);
DynamicTextGlyph *slot_glyph(int x_size, int y_size);
static void initialize_ft_library();
float _point_size;
float _pixels_per_unit;
bool _small_caps;
float _small_caps_scale;
int _texture_margin;
float _poly_margin;
int _page_x_size, _page_y_size;
static bool _update_cleared_glyphs;
typedef pvector< PT(DynamicTextPage) > Pages;
Pages _pages;
int _preferred_page;
// This doesn't need to be a reference-counting pointer, because the
// reference to each glyph is kept by the DynamicTextPage object.
typedef pmap<int, DynamicTextGlyph *> Cache;
Cache _cache;
// This is a list of the glyphs that do not have any printable
// properties (e.g. space), but still have an advance measure. We
// store them here to keep their reference counts; they also appear
// in the above table.
typedef pvector< PT(DynamicTextGlyph) > EmptyGlyphs;
EmptyGlyphs _empty_glyphs;
FT_Face _face;
static FT_Library _ft_library;

View File

@ -31,6 +31,43 @@ DynamicTextGlyph(DynamicTextPage *page, int x, int y,
_x_size(x_size), _y_size(y_size),
_margin(margin)
{
_geom_count = 0;
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextGlyph::Constructor
// Access: Publiic
// Description: This constructor makes an empty glyph, whose only
// purpose is to remember its width. It has no bitmap
// and no Geom.
////////////////////////////////////////////////////////////////////
INLINE DynamicTextGlyph::
DynamicTextGlyph(float advance) :
_page((DynamicTextPage *)NULL),
_x(0), _y(0),
_x_size(0), _y_size(0),
_margin(0)
{
_advance = advance;
_geom_count = 1;
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextGlyph::Copy Constructor
// Access: Private
// Description: Copying DynamicTextGlyph objects is not allowed.
////////////////////////////////////////////////////////////////////
INLINE DynamicTextGlyph::
DynamicTextGlyph(const DynamicTextGlyph &) {
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextGlyph::Copy Assignment Operator
// Access: Private
// Description: Copying DynamicTextGlyph objects is not allowed.
////////////////////////////////////////////////////////////////////
INLINE void DynamicTextGlyph::
operator = (const DynamicTextGlyph &) {
}
////////////////////////////////////////////////////////////////////

View File

@ -21,10 +21,35 @@
#ifdef HAVE_FREETYPE
#include "dynamicTextPage.h"
#include "geomTristrip.h"
#include "geomTextGlyph.h"
#include "textureTransition.h"
#include "transparencyTransition.h"
////////////////////////////////////////////////////////////////////
// Function: DynamicTextGlyph::Destructor
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
DynamicTextGlyph::
~DynamicTextGlyph() {
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextGlyph::get_geom
// Access: Public, Virtual
// Description: Returns a Geom that renders the particular glyph.
////////////////////////////////////////////////////////////////////
PT(Geom) DynamicTextGlyph::
get_geom() const {
if (_geom == (Geom *)NULL) {
return _geom;
}
// A DynamicTextGlyph must make a copy of its GeomTextGlyph, so that
// it will increase the reference count properly.
return _geom->make_copy();
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextGlyph::get_row
// Access: Publiic
@ -51,6 +76,26 @@ get_row(int y) {
return _page->_pbuffer->_image + offset;
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextGlyph::erase
// Access: Publiic
// Description: Erases the glyph from the texture map.
////////////////////////////////////////////////////////////////////
void DynamicTextGlyph::
erase() {
nassertv(_page != (DynamicTextPage *)NULL);
nassertv(_page->_pbuffer != (PixelBuffer *)NULL);
int ysizetop = _page->_pbuffer->get_ysize() - 1;
int xsize = _page->_pbuffer->get_xsize();
unsigned char *buffer = _page->_pbuffer->_image;
for (int y = _y; y < _y + _y_size; y++) {
int offset = (ysizetop - y) * xsize + _x;
memset(buffer + offset, 0, _x_size);
}
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextGlyph::make_geom
// Access: Publiic
@ -64,6 +109,11 @@ get_row(int y) {
void DynamicTextGlyph::
make_geom(int bitmap_top, int bitmap_left,
float advance, float poly_margin, float pixels_per_unit) {
nassertv(_page != (DynamicTextPage *)NULL);
// This function should not be called twice.
nassertv(_geom_count == 0);
// Determine the corners of the rectangle in geometric units.
float top = (bitmap_top + poly_margin) / pixels_per_unit;
float left = (bitmap_left - poly_margin) / pixels_per_unit;
@ -77,7 +127,12 @@ make_geom(int bitmap_top, int bitmap_left,
float uv_right = (float)(_x + _x_size + poly_margin) / _page->get_x_size();
// Create a corresponding tristrip.
_geom = new GeomTristrip;
_geom = new GeomTextGlyph(this);
// The above will increment our _geom_count to 1. Reset it back
// down to 0, since our own internal Geom doesn't count.
nassertv(_geom_count == 1);
_geom_count--;
PTA_Vertexf coords;
coords.push_back(Vertexf(left, 0, top));

View File

@ -38,13 +38,23 @@ class EXPCL_PANDA DynamicTextGlyph : public TextGlyph {
public:
INLINE DynamicTextGlyph(DynamicTextPage *page, int x, int y,
int x_size, int y_size, int margin);
INLINE DynamicTextGlyph(float advance);
private:
INLINE DynamicTextGlyph(const DynamicTextGlyph &copy);
INLINE void operator = (const DynamicTextGlyph &copy);
public:
virtual ~DynamicTextGlyph();
virtual PT(Geom) get_geom() const;
INLINE bool intersects(int x, int y, int x_size, int y_size) const;
unsigned char *get_row(int y);
void erase();
void make_geom(int top, int left, float advance, float poly_margin,
float pixels_per_unit);
DynamicTextPage *_page;
int _geom_count;
int _x, _y;
int _x_size, _y_size;

View File

@ -43,11 +43,19 @@ DynamicTextPage(DynamicTextFont *font) :
PixelBuffer::F_alpha);
mark_dirty(DF_image);
// We'd better never free this image.
set_keep_ram_image(true);
// We don't necessarily want to use mipmaps, since we don't want to
// regenerate those every time the texture changes, but we do want
// at least linear filtering.
set_magfilter(FT_linear);
set_minfilter(FT_linear);
// It's slightly better to let the texture clamp, rather than
// wrapping, so we're less likely to get bleeding at the edges.
set_wrapu(WM_clamp);
set_wrapv(WM_clamp);
}
////////////////////////////////////////////////////////////////////
@ -72,6 +80,43 @@ slot_glyph(int x_size, int y_size, int margin) {
return glyph;
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextPage::garbage_collect
// Access: Private
// Description: Removes all of the glyphs from the page that are no
// longer being used by any Geoms. This should only be
// called from DynamicTextFont::garbage_collect(), since
// it is important to remove these glyphs from the
// font's index first.
////////////////////////////////////////////////////////////////////
int DynamicTextPage::
garbage_collect() {
int removed_count = 0;
Glyphs new_glyphs;
Glyphs::iterator gi;
for (gi = _glyphs.begin(); gi != _glyphs.end(); ++gi) {
DynamicTextGlyph *glyph = (*gi);
if (glyph->_geom_count != 0) {
// Keep this one.
new_glyphs.insert(new_glyphs.end(), (*gi));
} else {
// Drop this one.
removed_count++;
glyph->erase();
}
}
if (removed_count != 0 && DynamicTextFont::get_update_cleared_glyphs()) {
// Only mark the texture dirty if the user specifically requested
// an automatic texture memory update upon clearing glyphs.
mark_dirty(Texture::DF_image);
}
_glyphs.swap(new_glyphs);
return removed_count;
}
////////////////////////////////////////////////////////////////////
// Function: DynamicTextPage::find_hole
// Access: Private

View File

@ -49,6 +49,9 @@ public:
PUBLISHED:
INLINE bool is_empty() const;
private:
int garbage_collect();
private:
bool find_hole(int &x, int &y, int x_size, int y_size) const;
DynamicTextGlyph *find_overlap(int x, int y, int x_size, int y_size) const;
@ -76,6 +79,8 @@ public:
private:
static TypeHandle _type_handle;
friend DynamicTextFont;
};
#include "dynamicTextPage.I"

View File

@ -0,0 +1,47 @@
// Filename: geomTextGlyph.I
// Created by: drose (11Feb02)
//
////////////////////////////////////////////////////////////////////
//
// 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 .
//
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
// Function: GeomTextGlyph::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
INLINE GeomTextGlyph::
GeomTextGlyph(DynamicTextGlyph *glyph) :
_glyph(glyph)
{
if (_glyph != (DynamicTextGlyph *)NULL) {
_glyph->_geom_count++;
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomTextGlyph::Copy Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
INLINE GeomTextGlyph::
GeomTextGlyph(const GeomTextGlyph &copy) :
GeomTristrip(copy),
_glyph(copy._glyph)
{
if (_glyph != (DynamicTextGlyph *)NULL) {
_glyph->_geom_count++;
}
}

View File

@ -0,0 +1,97 @@
// Filename: geomTextGlyph.cxx
// Created by: drose (11Feb02)
//
////////////////////////////////////////////////////////////////////
//
// 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 "geomTextGlyph.h"
#include "datagramIterator.h"
#include "bamReader.h"
TypeHandle GeomTextGlyph::_type_handle;
////////////////////////////////////////////////////////////////////
// Function: GeomTextGlyph::Copy Assignment Operator
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
void GeomTextGlyph::
operator = (const GeomTextGlyph &copy) {
GeomTristrip::operator = (copy);
if (_glyph != copy._glyph) {
if (_glyph != (DynamicTextGlyph *)NULL) {
_glyph->_geom_count--;
}
_glyph = copy._glyph;
if (_glyph != (DynamicTextGlyph *)NULL) {
_glyph->_geom_count++;
}
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomTextGlyph::Destructor
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
GeomTextGlyph::
~GeomTextGlyph() {
if (_glyph != (DynamicTextGlyph *)NULL) {
_glyph->_geom_count--;
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomTextGlyph::make_copy
// Access: Public, Virtual
// Description: Returns a newly-allocated Geom that is a shallow copy
// of this one. It will be a different Geom pointer,
// but its internal data may or may not be shared with
// that of the original Geom.
////////////////////////////////////////////////////////////////////
Geom *GeomTextGlyph::
make_copy() const {
return new GeomTextGlyph(*this);
}
////////////////////////////////////////////////////////////////////
// Function: GeomTextGlyph::register_with_factory
// Access: Public, Static
// Description: Factory method to generate a GeomTextGlyph object
////////////////////////////////////////////////////////////////////
void GeomTextGlyph::
register_with_read_factory() {
BamReader::get_factory()->register_factory(get_class_type(), make_GeomTextGlyph);
}
////////////////////////////////////////////////////////////////////
// Function: GeomTextGlyph::make_GeomTextGlyph
// Access: Public
// Description: Factory method to generate a GeomTextGlyph object
////////////////////////////////////////////////////////////////////
TypedWritable* GeomTextGlyph::
make_GeomTextGlyph(const FactoryParams &params) {
GeomTextGlyph *me = new GeomTextGlyph((DynamicTextGlyph *)NULL);
DatagramIterator scan;
BamReader *manager;
parse_params(params, scan, manager);
me->fillin(scan, manager);
me->make_dirty();
me->config();
return me;
}

View File

@ -0,0 +1,77 @@
// Filename: geomTextGlyph.h
// Created by: drose (11Feb02)
//
////////////////////////////////////////////////////////////////////
//
// 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 .
//
////////////////////////////////////////////////////////////////////
#ifndef GEOMTEXTGLYPH_H
#define GEOMTEXTGLYPH_H
#include "pandabase.h"
#ifdef HAVE_FREETYPE
#include "geomTristrip.h"
#include "dynamicTextGlyph.h"
////////////////////////////////////////////////////////////////////
// Class : GeomTextGlyph
// Description : This is a specialization on GeomTristrip for
// containing a triangle strip intended to represent a
// DynamicTextGlyph. Its sole purpose is to maintain
// the geom count on the glyph, so we can determine the
// actual usage count on a dynamic glyph (and thus know
// when it is safe to recycle the glyph).
////////////////////////////////////////////////////////////////////
class EXPCL_PANDA GeomTextGlyph : public GeomTristrip {
public:
INLINE GeomTextGlyph(DynamicTextGlyph *glyph);
INLINE GeomTextGlyph(const GeomTextGlyph &copy);
void operator = (const GeomTextGlyph &copy);
virtual ~GeomTextGlyph();
virtual Geom *make_copy() const;
private:
PT(DynamicTextGlyph) _glyph;
public:
static void register_with_read_factory();
static TypedWritable *make_GeomTextGlyph(const FactoryParams &params);
PUBLISHED:
static TypeHandle get_class_type() {
return _type_handle;
}
public:
static void init_type() {
GeomTristrip::init_type();
register_type(_type_handle, "GeomTextGlyph",
GeomTristrip::get_class_type());
}
virtual TypeHandle get_type() const {
return get_class_type();
}
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
private:
static TypeHandle _type_handle;
};
#include "geomTextGlyph.I"
#endif // HAVE_FREETYPE
#endif // GEOMTEXTGLYPH_H

View File

@ -80,17 +80,19 @@ write(ostream &out, int indent_level) const {
for (gi = _glyphs.begin(); gi != _glyphs.end(); ++gi) {
int ch = (*gi).first;
if (islower(ch)) {
count_lowercase++;
lowercase[ch - 'a'] = true;
if (ch < 128) {
if (islower(ch)) {
count_lowercase++;
lowercase[ch - 'a'] = true;
} else if (isupper(ch)) {
count_uppercase++;
uppercase[ch - 'A'] = true;
} else if (isupper(ch)) {
count_uppercase++;
uppercase[ch - 'A'] = true;
} else if (isdigit(ch)) {
count_digits++;
digits[ch - '0'] = true;
} else if (isdigit(ch)) {
count_digits++;
digits[ch - '0'] = true;
}
}
}
@ -141,10 +143,10 @@ write(ostream &out, int indent_level) const {
for (gi = _glyphs.begin(); gi != _glyphs.end(); ++gi) {
int ch = (*gi).first;
if (!isalnum(ch)) {
if (ch >= 128 || !isalnum(ch)) {
indent(out, indent_level + 2)
<< ch;
if (isprint(ch)) {
if (ch < isprint(ch)) {
out << " = '" << (char)ch << "'\n";
}
}
@ -154,18 +156,23 @@ write(ostream &out, int indent_level) const {
////////////////////////////////////////////////////////////////////
// Function: StaticTextFont::get_glyph
// Access: Public, Virtual
// Description: Returns the glyph associated with the given character
// code, or NULL if there is no such glyph.
// 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.
////////////////////////////////////////////////////////////////////
const TextGlyph *StaticTextFont::
get_glyph(int character) {
bool StaticTextFont::
get_glyph(int character, const TextGlyph *&glyph, float &glyph_scale) {
Glyphs::const_iterator gi = _glyphs.find(character);
if (gi == _glyphs.end()) {
// No definition for this character.
return (TextGlyph *)NULL;
} else {
return (*gi).second;
return false;
}
glyph = (*gi).second;
glyph_scale = 1.0f;
return true;
}
////////////////////////////////////////////////////////////////////
@ -275,6 +282,7 @@ find_characters(Node *root) {
} else {
_line_height = alist[ilist[0]][2];
}
_space_advance = 0.25f * _line_height;
}
} else {

View File

@ -46,7 +46,8 @@ PUBLISHED:
virtual void write(ostream &out, int indent_level) const;
public:
virtual const TextGlyph *get_glyph(int character);
virtual bool get_glyph(int character, const TextGlyph *&glyph,
float &glyph_scale);
private:
bool find_character_gsets(Node *root, Geom *&ch, GeomPoint *&dot,

View File

@ -38,3 +38,13 @@ INLINE float TextFont::
get_line_height() const {
return _line_height;
}
////////////////////////////////////////////////////////////////////
// Function: TextFont::get_space_advance
// Access: Public
// Description: Returns the number of units wide a space is.
////////////////////////////////////////////////////////////////////
INLINE float TextFont::
get_space_advance() const {
return _space_advance;
}

View File

@ -41,7 +41,8 @@ isblank(char ch) {
TextFont::
TextFont() {
_is_valid = false;
_line_height = 1.0;
_line_height = 1.0f;
_space_advance = 0.25f;
}
////////////////////////////////////////////////////////////////////
@ -60,19 +61,20 @@ TextFont::
// or 0.0 if the character is not known.
////////////////////////////////////////////////////////////////////
float TextFont::
calc_width(int ch) {
if (ch == ' ') {
calc_width(int character) {
if (character == ' ') {
// A space is a special case.
return 0.25;
return _space_advance;
}
const TextGlyph *glyph = get_glyph(ch);
if (glyph == (TextGlyph *)NULL) {
const TextGlyph *glyph;
float glyph_scale;
if (!get_glyph(character, glyph, glyph_scale)) {
// Unknown character.
return 0.0;
return 0.0f;
}
return glyph->get_advance();
return glyph->get_advance() * glyph_scale;
}
////////////////////////////////////////////////////////////////////
@ -84,7 +86,7 @@ calc_width(int ch) {
////////////////////////////////////////////////////////////////////
float TextFont::
calc_width(const string &line) {
float width = 0.0;
float width = 0.0f;
string::const_iterator si;
for (si = line.begin(); si != line.end(); ++si) {
@ -111,10 +113,10 @@ wordwrap_to(const string &text, float wordwrap_width,
size_t p = 0;
// Preserve any initial whitespace and newlines.
float initial_width = 0.0;
float initial_width = 0.0f;
while (p < text.length() && isspace(text[p])) {
if (text[p] == '\n') {
initial_width = 0.0;
initial_width = 0.0f;
} else {
initial_width += calc_width(text[p]);
}
@ -201,10 +203,10 @@ wordwrap_to(const string &text, float wordwrap_width,
p = next_start;
// Preserve any initial whitespace and newlines.
initial_width = 0.0;
initial_width = 0.0f;
while (p < text.length() && isspace(text[p])) {
if (text[p] == '\n') {
initial_width = 0.0;
initial_width = 0.0f;
} else {
initial_width += calc_width(text[p]);
}

View File

@ -49,7 +49,7 @@ PUBLISHED:
INLINE bool is_valid() const;
INLINE float get_line_height() const;
float calc_width(int ch);
float calc_width(int character);
float calc_width(const string &line);
string wordwrap_to(const string &text, float wordwrap_width,
bool preserve_trailing_whitespace);
@ -57,11 +57,14 @@ PUBLISHED:
virtual void write(ostream &out, int indent_level) const;
public:
virtual const TextGlyph *get_glyph(int character)=0;
INLINE float get_space_advance() const;
virtual bool get_glyph(int character, const TextGlyph *&glyph,
float &glyph_scale)=0;
protected:
bool _is_valid;
float _line_height;
float _space_advance;
public:
static TypeHandle get_class_type() {

View File

@ -64,16 +64,6 @@ operator = (const TextGlyph &copy) {
_advance = copy._advance;
}
////////////////////////////////////////////////////////////////////
// Function: TextGlyph::get_geom
// Access: Public
// Description: Returns a Geom that renders the particular glyph.
////////////////////////////////////////////////////////////////////
INLINE Geom *TextGlyph::
get_geom() const {
return _geom;
}
////////////////////////////////////////////////////////////////////
// Function: TextGlyph::get_trans
// Access: Public

View File

@ -17,3 +17,22 @@
////////////////////////////////////////////////////////////////////
#include "textGlyph.h"
////////////////////////////////////////////////////////////////////
// Function: TextGlyph::Destructor
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
TextGlyph::
~TextGlyph() {
}
////////////////////////////////////////////////////////////////////
// Function: TextGlyph::get_geom
// Access: Public, Virtual
// Description: Returns a Geom that renders the particular glyph.
////////////////////////////////////////////////////////////////////
PT(Geom) TextGlyph::
get_geom() const {
return _geom;
}

View File

@ -39,8 +39,9 @@ public:
INLINE TextGlyph(Geom *geom, const AllTransitionsWrapper &trans, float advance);
INLINE TextGlyph(const TextGlyph &copy);
INLINE void operator = (const TextGlyph &copy);
virtual ~TextGlyph();
INLINE Geom *get_geom() const;
virtual PT(Geom) get_geom() const;
INLINE const AllTransitionsWrapper &get_trans() const;
INLINE float get_advance() const;

View File

@ -346,7 +346,7 @@ generate() {
// Now flatten our hierarchy to get rid of the transforms we put in,
// applying them to the vertices.
if (flatten_text) {
if (text_flatten) {
SceneGraphReducer gr(RenderRelation::get_class_type());
gr.apply_transitions(root_arc);
gr.flatten(root, true);
@ -443,13 +443,14 @@ assemble_row(const char *&source, Node *dest) {
if (character == ' ') {
// A space is a special case.
xpos += 0.25f;
xpos += _font->get_space_advance();
} else {
// A printable character.
const TextGlyph *glyph = _font->get_glyph(character);
if (glyph == (const TextGlyph *)NULL) {
const TextGlyph *glyph;
float glyph_scale;
if (!_font->get_glyph(character, glyph, glyph_scale)) {
text_cat.warning()
<< "No definition in " << _font->get_name()
<< " for character " << character;
@ -461,13 +462,13 @@ assemble_row(const char *&source, Node *dest) {
<< "\n";
} else {
Geom *char_geom = glyph->get_geom();
float char_advance = glyph->get_advance();
PT(Geom) char_geom = glyph->get_geom();
const AllTransitionsWrapper &trans = glyph->get_trans();
LMatrix4f mat = LMatrix4f::ident_mat();
mat.set_row(3, LVector3f(xpos, 0.0f, 0.0f));
if (char_geom != NULL) {
if (char_geom != (Geom *)NULL) {
LMatrix4f mat = LMatrix4f::scale_mat(glyph_scale);
mat.set_row(3, LVector3f(xpos, 0.0f, 0.0f));
string ch(1, (char)character);
GeomNode *geode = new GeomNode(ch);
geode->add_geom(char_geom);
@ -476,7 +477,7 @@ assemble_row(const char *&source, Node *dest) {
trans.store_to(rel);
}
xpos += char_advance;
xpos += glyph->get_advance() * glyph_scale;
}
}
source++;
@ -576,10 +577,10 @@ measure_row(const char *&source) {
} else {
// A printable character.
const TextGlyph *glyph = _font->get_glyph(character);
if (glyph != (const TextGlyph *)NULL) {
float char_advance = glyph->get_advance();
xpos += char_advance;
const TextGlyph *glyph;
float glyph_scale;
if (_font->get_glyph(character, glyph, glyph_scale)) {
xpos += glyph->get_advance() * glyph_scale;
}
}
source++;

View File

@ -2,6 +2,7 @@
#include "dynamicTextFont.cxx"
#include "dynamicTextGlyph.cxx"
#include "dynamicTextPage.cxx"
#include "geomTextGlyph.cxx"
#include "staticTextFont.cxx"
#include "textFont.cxx"
#include "textGlyph.cxx"