Browse Source

further refinements, proper caching for dynamic text

David Rose 24 years ago
parent
commit
d0b7f1b24c

+ 3 - 0
panda/src/text/Sources.pp

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

+ 13 - 1
panda/src/text/config_text.cxx

@@ -22,6 +22,7 @@
 #include "textNode.h"
 #include "textNode.h"
 #include "dynamicTextFont.h"
 #include "dynamicTextFont.h"
 #include "dynamicTextPage.h"
 #include "dynamicTextPage.h"
+#include "geomTextGlyph.h"
 
 
 #include <dconfig.h>
 #include <dconfig.h>
 
 
@@ -36,7 +37,18 @@ ConfigureFn(config_text) {
 #ifdef HAVE_FREETYPE
 #ifdef HAVE_FREETYPE
   DynamicTextFont::init_type();
   DynamicTextFont::init_type();
   DynamicTextPage::init_type();
   DynamicTextPage::init_type();
+  GeomTextGlyph::init_type();
+  GeomTextGlyph::register_with_read_factory();
 #endif
 #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);

+ 10 - 1
panda/src/text/config_text.h

@@ -26,6 +26,15 @@ class DSearchPath;
 
 
 NotifyCategoryDecl(text, EXPCL_PANDA, EXPTP_PANDA);
 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
 #endif

+ 98 - 0
panda/src/text/dynamicTextFont.I

@@ -82,6 +82,67 @@ get_pixels_per_unit() const {
   return _pixels_per_unit;
   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
 //     Function: DynamicTextFont::set_texture_margin
 //       Access: Published
 //       Access: Published
@@ -168,3 +229,40 @@ INLINE int DynamicTextFont::
 get_page_y_size() const {
 get_page_y_size() const {
   return _page_y_size;
   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;
+}

+ 205 - 71
panda/src/text/dynamicTextFont.cxx

@@ -22,6 +22,9 @@
 
 
 #include "config_text.h"
 #include "config_text.h"
 #include "config_util.h"
 #include "config_util.h"
+#include "ctype.h"
+
+bool DynamicTextFont::_update_cleared_glyphs = text_update_cleared_glyphs;
 
 
 FT_Library DynamicTextFont::_ft_library;
 FT_Library DynamicTextFont::_ft_library;
 bool DynamicTextFont::_ft_initialized = false;
 bool DynamicTextFont::_ft_initialized = false;
@@ -31,10 +34,11 @@ TypeHandle DynamicTextFont::_type_handle;
 
 
 
 
 // This constant determines how big a particular point size font
 // 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;
 static const float points_per_unit = 10.0f;
 
 
-// A universal convention.
+// A universal typographic convention.
 static const float points_per_inch = 72.0f;
 static const float points_per_inch = 72.0f;
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -47,19 +51,24 @@ static const float points_per_inch = 72.0f;
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 DynamicTextFont::
 DynamicTextFont::
 DynamicTextFont(const Filename &font_filename, int face_index) {
 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) {
   if (!_ft_initialized) {
     initialize_ft_library();
     initialize_ft_library();
   }
   }
   if (!_ft_ok) {
   if (!_ft_ok) {
     text_cat.error()
     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;
     return;
   }
   }
 
 
@@ -128,6 +137,64 @@ get_page(int n) const {
   return _pages[n];
   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
 //     Function: DynamicTextFont::clear
 //       Access: Published
 //       Access: Published
@@ -143,8 +210,9 @@ get_page(int n) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void DynamicTextFont::
 void DynamicTextFont::
 clear() {
 clear() {
-  _pages.clear();
   _cache.clear();
   _cache.clear();
+  _pages.clear();
+  _empty_glyphs.clear();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -156,29 +224,53 @@ void DynamicTextFont::
 write(ostream &out, int indent_level) const {
 write(ostream &out, int indent_level) const {
   indent(out, indent_level)
   indent(out, indent_level)
     << "DynamicTextFont " << get_name() << ", " 
     << "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
 //     Function: DynamicTextFont::get_glyph
 //       Access: Public, Virtual
 //       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) {
   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?
   // have a scalable font or otherwise?
   float pixel_size = _point_size * (_pixels_per_unit / points_per_unit);
   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);
   _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;
   return true;
 }
 }
 
 
@@ -221,46 +324,55 @@ reset_scale() {
 //               glyph cannot be created for some reason.
 //               glyph cannot be created for some reason.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 DynamicTextGlyph *DynamicTextFont::
 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) {
   if (error) {
     text_cat.error()
     text_cat.error()
-      << "Unable to render character " << character << "\n";
+      << "Unable to render glyph " << glyph_index << "\n";
     return (DynamicTextGlyph *)NULL;
     return (DynamicTextGlyph *)NULL;
   }
   }
 
 
   FT_GlyphSlot slot = _face->glyph;
   FT_GlyphSlot slot = _face->glyph;
   FT_Bitmap &bitmap = slot->bitmap;
   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";
-  }
+  float advance = slot->advance.x / 64.0;
 
 
-  DynamicTextGlyph *glyph = slot_glyph(bitmap.width, bitmap.rows);
+  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;
 
 
-  // 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;
+  } 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;
   }
   }
-  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;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -278,28 +390,50 @@ slot_glyph(int x_size, int y_size) {
   x_size += _texture_margin * 2;
   x_size += _texture_margin * 2;
   y_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;
+
+    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;
+      }
 
 
-    DynamicTextGlyph *glyph = page->slot_glyph(x_size, y_size, _texture_margin);
-    if (glyph != (DynamicTextGlyph *)NULL) {
-      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;
+      }
 
 
-    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;
-    }
+      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);
+  }
 }
 }
 
 
 
 

+ 26 - 2
panda/src/text/dynamicTextFont.h

@@ -52,6 +52,11 @@ PUBLISHED:
   INLINE bool set_pixels_per_unit(float pixels_per_unit);
   INLINE bool set_pixels_per_unit(float pixels_per_unit);
   INLINE float get_pixels_per_unit() const;
   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 void set_texture_margin(int texture_margin);
   INLINE int get_texture_margin() const;
   INLINE int get_texture_margin() const;
   INLINE void set_poly_margin(float poly_margin);
   INLINE void set_poly_margin(float poly_margin);
@@ -61,35 +66,54 @@ PUBLISHED:
   INLINE int get_page_x_size() const;
   INLINE int get_page_x_size() const;
   INLINE int get_page_y_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;
   int get_num_pages() const;
   DynamicTextPage *get_page(int n) const;
   DynamicTextPage *get_page(int n) const;
 
 
+  int garbage_collect();
+  void update_texture_memory();
   void clear();
   void clear();
 
 
   virtual void write(ostream &out, int indent_level) const;
   virtual void write(ostream &out, int indent_level) const;
 
 
 public:
 public:
-  virtual const TextGlyph *get_glyph(int character);
+  virtual bool get_glyph(int character, const TextGlyph *&glyph,
+                         float &glyph_scale);
 
 
 private:
 private:
   bool reset_scale();
   bool reset_scale();
-  DynamicTextGlyph *make_glyph(int character);
+  DynamicTextGlyph *make_glyph(int glyph_index);
   DynamicTextGlyph *slot_glyph(int x_size, int y_size);
   DynamicTextGlyph *slot_glyph(int x_size, int y_size);
 
 
   static void initialize_ft_library();
   static void initialize_ft_library();
 
 
   float _point_size;
   float _point_size;
   float _pixels_per_unit;
   float _pixels_per_unit;
+  bool _small_caps;
+  float _small_caps_scale;
   int _texture_margin;
   int _texture_margin;
   float _poly_margin;
   float _poly_margin;
   int _page_x_size, _page_y_size;
   int _page_x_size, _page_y_size;
+  static bool _update_cleared_glyphs;
 
 
   typedef pvector< PT(DynamicTextPage) > Pages;
   typedef pvector< PT(DynamicTextPage) > Pages;
   Pages _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;
   typedef pmap<int, DynamicTextGlyph *> Cache;
   Cache _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;
   FT_Face _face;
 
 
   static FT_Library _ft_library;
   static FT_Library _ft_library;

+ 37 - 0
panda/src/text/dynamicTextGlyph.I

@@ -31,6 +31,43 @@ DynamicTextGlyph(DynamicTextPage *page, int x, int y,
   _x_size(x_size), _y_size(y_size),
   _x_size(x_size), _y_size(y_size),
   _margin(margin)
   _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 &) {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 57 - 2
panda/src/text/dynamicTextGlyph.cxx

@@ -21,10 +21,35 @@
 #ifdef HAVE_FREETYPE
 #ifdef HAVE_FREETYPE
 
 
 #include "dynamicTextPage.h"
 #include "dynamicTextPage.h"
-#include "geomTristrip.h"
+#include "geomTextGlyph.h"
 #include "textureTransition.h"
 #include "textureTransition.h"
 #include "transparencyTransition.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
 //     Function: DynamicTextGlyph::get_row
 //       Access: Publiic
 //       Access: Publiic
@@ -51,6 +76,26 @@ get_row(int y) {
   return _page->_pbuffer->_image + offset; 
   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
 //     Function: DynamicTextGlyph::make_geom
 //       Access: Publiic
 //       Access: Publiic
@@ -64,6 +109,11 @@ get_row(int y) {
 void DynamicTextGlyph::
 void DynamicTextGlyph::
 make_geom(int bitmap_top, int bitmap_left, 
 make_geom(int bitmap_top, int bitmap_left, 
           float advance, float poly_margin, float pixels_per_unit) {
           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.
   // Determine the corners of the rectangle in geometric units.
   float top = (bitmap_top + poly_margin) / pixels_per_unit;
   float top = (bitmap_top + poly_margin) / pixels_per_unit;
   float left = (bitmap_left - 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();
   float uv_right = (float)(_x + _x_size + poly_margin) / _page->get_x_size();
 
 
   // Create a corresponding tristrip.
   // 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;
   PTA_Vertexf coords;
   coords.push_back(Vertexf(left, 0, top));
   coords.push_back(Vertexf(left, 0, top));

+ 10 - 0
panda/src/text/dynamicTextGlyph.h

@@ -38,13 +38,23 @@ class EXPCL_PANDA DynamicTextGlyph : public TextGlyph {
 public:
 public:
   INLINE DynamicTextGlyph(DynamicTextPage *page, int x, int y,
   INLINE DynamicTextGlyph(DynamicTextPage *page, int x, int y,
                           int x_size, int y_size, int margin);
                           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;
   INLINE bool intersects(int x, int y, int x_size, int y_size) const;
   unsigned char *get_row(int y);
   unsigned char *get_row(int y);
+  void erase();
   void make_geom(int top, int left, float advance, float poly_margin,
   void make_geom(int top, int left, float advance, float poly_margin,
                  float pixels_per_unit);
                  float pixels_per_unit);
 
 
   DynamicTextPage *_page;
   DynamicTextPage *_page;
+  int _geom_count;
 
 
   int _x, _y;
   int _x, _y;
   int _x_size, _y_size;
   int _x_size, _y_size;

+ 45 - 0
panda/src/text/dynamicTextPage.cxx

@@ -43,11 +43,19 @@ DynamicTextPage(DynamicTextFont *font) :
                              PixelBuffer::F_alpha);
                              PixelBuffer::F_alpha);
   mark_dirty(DF_image);
   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
   // 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
   // regenerate those every time the texture changes, but we do want
   // at least linear filtering.
   // at least linear filtering.
   set_magfilter(FT_linear);
   set_magfilter(FT_linear);
   set_minfilter(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;
   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
 //     Function: DynamicTextPage::find_hole
 //       Access: Private
 //       Access: Private

+ 5 - 0
panda/src/text/dynamicTextPage.h

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

+ 47 - 0
panda/src/text/geomTextGlyph.I

@@ -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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     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++;
+  }
+}

+ 97 - 0
panda/src/text/geomTextGlyph.cxx

@@ -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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#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;
+}

+ 77 - 0
panda/src/text/geomTextGlyph.h

@@ -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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#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

+ 28 - 20
panda/src/text/staticTextFont.cxx

@@ -80,17 +80,19 @@ write(ostream &out, int indent_level) const {
 
 
   for (gi = _glyphs.begin(); gi != _glyphs.end(); ++gi) {
   for (gi = _glyphs.begin(); gi != _glyphs.end(); ++gi) {
     int ch = (*gi).first;
     int ch = (*gi).first;
-    if (islower(ch)) {
-      count_lowercase++;
-      lowercase[ch - 'a'] = true;
-
-    } else if (isupper(ch)) {
-      count_uppercase++;
-      uppercase[ch - 'A'] = true;
-
-    } else if (isdigit(ch)) {
-      count_digits++;
-      digits[ch - '0'] = 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 (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) {
   for (gi = _glyphs.begin(); gi != _glyphs.end(); ++gi) {
     int ch = (*gi).first;
     int ch = (*gi).first;
-    if (!isalnum(ch)) {
+    if (ch >= 128 || !isalnum(ch)) {
       indent(out, indent_level + 2)
       indent(out, indent_level + 2)
         << ch;
         << ch;
-      if (isprint(ch)) {
+      if (ch < isprint(ch)) {
         out << " = '" << (char)ch << "'\n";
         out << " = '" << (char)ch << "'\n";
       }
       }
     }
     }
@@ -154,18 +156,23 @@ write(ostream &out, int indent_level) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: StaticTextFont::get_glyph
 //     Function: StaticTextFont::get_glyph
 //       Access: Public, Virtual
 //       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);
   Glyphs::const_iterator gi = _glyphs.find(character);
   if (gi == _glyphs.end()) {
   if (gi == _glyphs.end()) {
     // No definition for this character.
     // 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 {
       } else {
         _line_height = alist[ilist[0]][2];
         _line_height = alist[ilist[0]][2];
       }
       }
+      _space_advance = 0.25f * _line_height;
     }
     }
 
 
   } else {
   } else {

+ 2 - 1
panda/src/text/staticTextFont.h

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

+ 10 - 0
panda/src/text/textFont.I

@@ -38,3 +38,13 @@ INLINE float TextFont::
 get_line_height() const {
 get_line_height() const {
   return _line_height;
   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;
+}

+ 15 - 13
panda/src/text/textFont.cxx

@@ -41,7 +41,8 @@ isblank(char ch) {
 TextFont::
 TextFont::
 TextFont() {
 TextFont() {
   _is_valid = false;
   _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.
 //               or 0.0 if the character is not known.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 float TextFont::
 float TextFont::
-calc_width(int ch) {
-  if (ch == ' ') {
+calc_width(int character) {
+  if (character == ' ') {
     // A space is a special case.
     // 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.
     // 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::
 float TextFont::
 calc_width(const string &line) {
 calc_width(const string &line) {
-  float width = 0.0;
+  float width = 0.0f;
 
 
   string::const_iterator si;
   string::const_iterator si;
   for (si = line.begin(); si != line.end(); ++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;
   size_t p = 0;
 
 
   // Preserve any initial whitespace and newlines.
   // Preserve any initial whitespace and newlines.
-  float initial_width = 0.0;
+  float initial_width = 0.0f;
   while (p < text.length() && isspace(text[p])) {
   while (p < text.length() && isspace(text[p])) {
     if (text[p] == '\n') {
     if (text[p] == '\n') {
-      initial_width = 0.0;
+      initial_width = 0.0f;
     } else {
     } else {
       initial_width += calc_width(text[p]);
       initial_width += calc_width(text[p]);
     }
     }
@@ -201,10 +203,10 @@ wordwrap_to(const string &text, float wordwrap_width,
     p = next_start;
     p = next_start;
 
 
     // Preserve any initial whitespace and newlines.
     // Preserve any initial whitespace and newlines.
-    initial_width = 0.0;
+    initial_width = 0.0f;
     while (p < text.length() && isspace(text[p])) {
     while (p < text.length() && isspace(text[p])) {
       if (text[p] == '\n') {
       if (text[p] == '\n') {
-        initial_width = 0.0;
+        initial_width = 0.0f;
       } else {
       } else {
         initial_width += calc_width(text[p]);
         initial_width += calc_width(text[p]);
       }
       }

+ 5 - 2
panda/src/text/textFont.h

@@ -49,7 +49,7 @@ PUBLISHED:
   INLINE bool is_valid() const;
   INLINE bool is_valid() const;
   INLINE float get_line_height() const;
   INLINE float get_line_height() const;
 
 
-  float calc_width(int ch);
+  float calc_width(int character);
   float calc_width(const string &line);
   float calc_width(const string &line);
   string wordwrap_to(const string &text, float wordwrap_width,
   string wordwrap_to(const string &text, float wordwrap_width,
                      bool preserve_trailing_whitespace);
                      bool preserve_trailing_whitespace);
@@ -57,11 +57,14 @@ PUBLISHED:
   virtual void write(ostream &out, int indent_level) const;
   virtual void write(ostream &out, int indent_level) const;
 
 
 public:
 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:
 protected:
   bool _is_valid;
   bool _is_valid;
   float _line_height;
   float _line_height;
+  float _space_advance;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {

+ 0 - 10
panda/src/text/textGlyph.I

@@ -64,16 +64,6 @@ operator = (const TextGlyph &copy) {
   _advance = copy._advance;
   _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
 //     Function: TextGlyph::get_trans
 //       Access: Public
 //       Access: Public

+ 19 - 0
panda/src/text/textGlyph.cxx

@@ -17,3 +17,22 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
 #include "textGlyph.h"
 #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;
+}

+ 2 - 1
panda/src/text/textGlyph.h

@@ -39,8 +39,9 @@ public:
   INLINE TextGlyph(Geom *geom, const AllTransitionsWrapper &trans, float advance);
   INLINE TextGlyph(Geom *geom, const AllTransitionsWrapper &trans, float advance);
   INLINE TextGlyph(const TextGlyph &copy);
   INLINE TextGlyph(const TextGlyph &copy);
   INLINE void operator = (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 const AllTransitionsWrapper &get_trans() const;
   INLINE float get_advance() const;
   INLINE float get_advance() const;
 
 

+ 15 - 14
panda/src/text/textNode.cxx

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

+ 1 - 0
panda/src/text/text_composite1.cxx

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