Browse Source

DynamicTextFont::set_outline()

David Rose 17 years ago
parent
commit
89aeadbc72

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

@@ -405,6 +405,164 @@ get_winding_order() const {
   return _winding_order;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::set_fg
+//       Access: Published
+//  Description: Changes the color of the foreground pixels of the
+//               font as they are rendered into the font texture.  The
+//               default is (1, 1, 1, 1), or opaque white, which
+//               allows text created with the font to be colored
+//               individually.  Normally, you would not change this
+//               unless you really need a particular color effect to
+//               appear in the font itself.
+//
+//               This should only be called before any characters have
+//               been requested out of the font, or immediately after
+//               calling clear().
+////////////////////////////////////////////////////////////////////
+INLINE void DynamicTextFont::
+set_fg(const Colorf &fg) {
+  // If this assertion fails, you didn't call clear() first.  RTFM.
+  nassertv(get_num_pages() == 0);
+
+  _fg = fg;
+  determine_tex_format();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::get_fg
+//       Access: Published
+//  Description: Returns the color of the foreground pixels of the
+//               font as they are rendered into the font texture.
+//               See set_fg().
+////////////////////////////////////////////////////////////////////
+INLINE const Colorf &DynamicTextFont::
+get_fg() const {
+  return _fg;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::set_bg
+//       Access: Published
+//  Description: Changes the color of the background pixels of the
+//               font as they are rendered into the font texture.  The
+//               default is (1, 1, 1, 0), or transparent white, which
+//               allows text created with the font to be colored
+//               individually.  (Note that it should not generally be
+//               (0, 0, 0, 0), which would tend to bleed into the
+//               foreground color, unless you have also specified a
+//               outline color of (0, 0, 0, 1)) .
+//
+//               Normally, you would not change this unless you really
+//               need a particular color effect to appear in the font
+//               itself.
+//
+//               This should only be called before any characters have
+//               been requested out of the font, or immediately after
+//               calling clear().
+////////////////////////////////////////////////////////////////////
+INLINE void DynamicTextFont::
+set_bg(const Colorf &bg) {
+  // If this assertion fails, you didn't call clear() first.  RTFM.
+  nassertv(get_num_pages() == 0);
+
+  _bg = bg;
+  determine_tex_format();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::get_bg
+//       Access: Published
+//  Description: Returns the color of the background pixels of the
+//               font as they are rendered into the font texture.
+//               See set_bg().
+////////////////////////////////////////////////////////////////////
+INLINE const Colorf &DynamicTextFont::
+get_bg() const {
+  return _bg;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::set_outline
+//       Access: Published
+//  Description: Sets up the font to have an outline around each font
+//               letter.  This is achieved via a Gaussian post-process
+//               as each letter is generated; there is some runtime
+//               cost for this effect, but it is minimal as each
+//               letter is normally generated only once and then
+//               cached.
+//
+//               The color is the desired color of the outline, width
+//               is the number of pixels beyond the letter that the
+//               outline extends, and feather is a number in the range
+//               0.0 .. 1.0 that controls the softness of the outline.
+//               Set the width to 0.0 to disable the outline.
+//
+//               This should only be called before any characters have
+//               been requested out of the font, or immediately after
+//               calling clear().
+////////////////////////////////////////////////////////////////////
+INLINE void DynamicTextFont::
+set_outline(const Colorf &outline_color, float outline_width,
+            float outline_feather) {
+  // If this assertion fails, you didn't call clear() first.  RTFM.
+  nassertv(get_num_pages() == 0);
+
+  _outline_color = outline_color;
+  _outline_width = outline_width;
+  _outline_feather = outline_feather;
+  determine_tex_format();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::get_outline_color
+//       Access: Published
+//  Description: Returns the color of the outline pixels of the
+//               font as they are rendered into the font texture.
+//               See set_outline().
+////////////////////////////////////////////////////////////////////
+INLINE const Colorf &DynamicTextFont::
+get_outline_color() const {
+  return _outline_color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::get_outline_width
+//       Access: Published
+//  Description: Returns the width of the outline pixels of the
+//               font, as the number of pixels beyond each letter.
+//               See set_outline().
+////////////////////////////////////////////////////////////////////
+INLINE float DynamicTextFont::
+get_outline_width() const {
+  return _outline_width;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::get_outline_feather
+//       Access: Published
+//  Description: Returns the softness of the outline pixels of the
+//               font, as a value in the range 0.0 to 1.0.
+//               See set_outline().
+////////////////////////////////////////////////////////////////////
+INLINE float DynamicTextFont::
+get_outline_feather() const {
+  return _outline_feather;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::get_tex_format
+//       Access: Published
+//  Description: Returns the texture format used to render the
+//               individual pages.  This is set automatically
+//               according to the colors selected.
+////////////////////////////////////////////////////////////////////
+INLINE Texture::Format DynamicTextFont::
+get_tex_format() const {
+  return _tex_format;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: DynamicTextFont::ContourPoint::Constructor
 //       Access: Public

+ 235 - 14
panda/src/text/dynamicTextFont.cxx

@@ -63,6 +63,15 @@ DynamicTextFont(const Filename &font_filename, int face_index) {
   TextFont::set_name(FreetypeFont::get_name());
   TextFont::_line_height = FreetypeFont::get_line_height();
   TextFont::_space_advance = FreetypeFont::get_space_advance();
+
+  _fg.set(1.0f, 1.0f, 1.0f, 1.0f);
+  _bg.set(1.0f, 1.0f, 1.0f, 0.0f);
+  _outline_color.set(1.0f, 1.0f, 1.0f, 0.0f);
+  _outline_width = 0.0f;
+  _outline_feather = 0.0f;
+  _has_outline = false;
+  _tex_format = Texture::F_alpha;
+  _needs_image_processing = false;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -79,6 +88,15 @@ DynamicTextFont(const char *font_data, int data_length, int face_index) {
   TextFont::set_name(FreetypeFont::get_name());
   TextFont::_line_height = FreetypeFont::_line_height;
   TextFont::_space_advance = FreetypeFont::_space_advance;
+
+  _fg.set(1.0f, 1.0f, 1.0f, 1.0f);
+  _bg.set(1.0f, 1.0f, 1.0f, 0.0f);
+  _outline_color.set(1.0f, 1.0f, 1.0f, 0.0f);
+  _outline_width = 0.0f;
+  _outline_feather = 0.0f;
+  _has_outline = false;
+  _tex_format = Texture::F_alpha;
+  _needs_image_processing = false;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -151,7 +169,7 @@ garbage_collect() {
   Pages::iterator pi;
   for (pi = _pages.begin(); pi != _pages.end(); ++pi) {
     DynamicTextPage *page = (*pi);
-    page->garbage_collect();
+    page->garbage_collect(this);
   }
 
   return removed_count;
@@ -303,6 +321,75 @@ update_filters() {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::determine_tex_format
+//       Access: Private
+//  Description: Examines the _fg, _bg, and _outline colors to
+//               determine the appropriate format for the font pages,
+//               including the outline properties.
+////////////////////////////////////////////////////////////////////
+void DynamicTextFont::
+determine_tex_format() {
+  nassertv(get_num_pages() == 0);
+
+  _has_outline = (_outline_color != _bg && _outline_width > 0.0f);
+  _needs_image_processing = true;
+
+  bool needs_color = false;
+  bool needs_grayscale = false;
+  bool needs_alpha = false;
+
+  if (_fg[1] != _fg[0] || _fg[2] != _fg[0] ||
+      _bg[1] != _bg[0] || _bg[2] != _bg[0] ||
+      (_has_outline && (_outline_color[1] != _outline_color[0] || _outline_color[2] != _outline_color[0]))) {
+    // At least one of fg, bg, or outline contains a color, not just a
+    // grayscale value.
+    needs_color = true;
+
+  } else if (_fg[0] != 1.0f || _fg[1] != 1.0f || _fg[2] != 1.0f ||
+             _bg[0] != 1.0f || _bg[1] != 1.0f || _bg[2] != 1.0f ||
+             (_has_outline && (_outline_color[0] != 1.0f || _outline_color[1] != 1.0f || _outline_color[2] != 1.0f))) {
+    // fg, bg, and outline contain non-white grayscale values.
+    needs_grayscale = true;
+  }
+
+  if (_fg[3] != 1.0f || _bg[3] != 1.0f || 
+      (_has_outline && (_outline_color[3] != 1.0f))) {
+    // fg, bg, and outline contain non-opaque alpha values.
+    needs_alpha = true;
+  }
+
+  if (needs_color) {
+    if (needs_alpha) {
+      _tex_format = Texture::F_rgba;
+    } else {
+      _tex_format = Texture::F_rgb;
+    }
+  } else if (needs_grayscale) {
+    if (needs_alpha) {
+      _tex_format = Texture::F_luminance_alpha;
+    } else {
+      _tex_format = Texture::F_luminance;
+    }
+  } else {
+    if (needs_alpha) {
+      _tex_format = Texture::F_alpha;
+
+      if (!_has_outline && 
+          _fg == Colorf(1.0f, 1.0f, 1.0f, 1.0f) &&
+          _bg == Colorf(1.0f, 1.0f, 1.0f, 0.0f)) {
+        // This is the standard font color.  It can be copied directly
+        // without any need for special processing.
+        _needs_image_processing = false;
+      }
+
+    } else {
+      // This won't be a very interesting font.
+      _tex_format = Texture::F_luminance;
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: DynamicTextFont::make_glyph
 //       Access: Private
@@ -432,33 +519,53 @@ make_glyph(int character, int glyph_index) {
     float tex_x_size = bitmap.width;
     float tex_y_size = bitmap.rows;
 
-    if (_tex_pixels_per_unit == _font_pixels_per_unit) {
+    int outline = 0;
+
+    if (_tex_pixels_per_unit == _font_pixels_per_unit &&
+        !_needs_image_processing) {
       // If the bitmap produced from the font doesn't require scaling
-      // before it goes to the texture, we can just copy it directly
-      // into the texture.
+      // or any other processing before it goes to the texture, we can
+      // just copy it directly into the texture.
       glyph = slot_glyph(character, bitmap.width, bitmap.rows);
       copy_bitmap_to_texture(bitmap, glyph);
 
     } else {
       // Otherwise, we need to copy to a PNMImage first, so we can
-      // scale it; and then copy it to the texture from there.
+      // scale it and/or process it; and then copy it to the texture
+      // from there.
       tex_x_size /= _scale_factor;
       tex_y_size /= _scale_factor;
       int int_x_size = (int)ceil(tex_x_size);
       int int_y_size = (int)ceil(tex_y_size);
       int bmp_x_size = (int)(int_x_size * _scale_factor + 0.5f);
       int bmp_y_size = (int)(int_y_size * _scale_factor + 0.5f);
-      glyph = slot_glyph(character, int_x_size, int_y_size);
-      
+
       PNMImage image(bmp_x_size, bmp_y_size, PNMImage::CT_grayscale);
       copy_bitmap_to_pnmimage(bitmap, image);
 
       PNMImage reduced(int_x_size, int_y_size, PNMImage::CT_grayscale);
       reduced.quick_filter_from(image);
-      copy_pnmimage_to_texture(reduced, glyph);
+
+      outline = (int)ceil(_outline_width);
+      int_x_size += outline * 2;
+      int_y_size += outline * 2;
+      tex_x_size += outline * 2;
+      tex_y_size += outline * 2;
+      glyph = slot_glyph(character, int_x_size, int_y_size);
+
+      if (outline != 0) {
+        // Pad the glyph image to make room for the outline.
+        PNMImage padded(int_x_size, int_y_size, PNMImage::CT_grayscale);
+        padded.copy_sub_image(reduced, outline, outline);
+        copy_pnmimage_to_texture(padded, glyph);
+
+      } else {
+        copy_pnmimage_to_texture(reduced, glyph);
+      }
     }
       
-    glyph->make_geom(slot->bitmap_top, slot->bitmap_left,
+    glyph->make_geom(slot->bitmap_top + outline * _scale_factor,
+                     slot->bitmap_left - outline * _scale_factor,
                      advance, _poly_margin,
                      tex_x_size, tex_y_size,
                      _font_pixels_per_unit, _tex_pixels_per_unit);
@@ -541,11 +648,125 @@ copy_bitmap_to_texture(const FT_Bitmap &bitmap, DynamicTextGlyph *glyph) {
 ////////////////////////////////////////////////////////////////////
 void DynamicTextFont::
 copy_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph) {
-  for (int yi = 0; yi < image.get_y_size(); yi++) {
-    unsigned char *texture_row = glyph->get_row(yi);
-    nassertv(texture_row != (unsigned char *)NULL);
-    for (int xi = 0; xi < image.get_x_size(); xi++) {
-      texture_row[xi] = image.get_gray_val(xi, yi);
+  if (!_needs_image_processing) {
+    // Copy the image directly into the alpha component of the
+    // texture.
+    nassertv(glyph->_page->get_num_components() == 1);
+    for (int yi = 0; yi < image.get_y_size(); yi++) {
+      unsigned char *texture_row = glyph->get_row(yi);
+      nassertv(texture_row != (unsigned char *)NULL);
+      for (int xi = 0; xi < image.get_x_size(); xi++) {
+        texture_row[xi] = image.get_gray_val(xi, yi);
+      }
+    }
+
+  } else {
+    if (_has_outline) {
+      // Gaussian blur the glyph to generate an outline.
+      PNMImage outline(image.get_x_size(), image.get_y_size(), PNMImage::CT_grayscale);
+      outline.gaussian_filter_from(_outline_width, image);
+
+      // Filter the resulting outline to make a harder edge.
+      for (int yi = 0; yi < outline.get_y_size(); yi++) {
+        for (int xi = 0; xi < outline.get_x_size(); xi++) {
+          float v = outline.get_gray(xi, yi);
+          if (v == 0.0f) {
+            // Do nothing.
+          } else if (v >= _outline_feather) {
+            // Clamp to 1.
+            outline.set_gray(xi, yi, 1.0);
+          } else {
+            // Linearly scale the range 0 .. _outline_feather onto 0 .. 1.
+            outline.set_gray(xi, yi, v / _outline_feather);
+          }
+        }
+      }
+
+      // Now blend that into the texture.
+      blend_pnmimage_to_texture(outline, glyph, _outline_color);
+    }
+
+    // Colorize the image as we copy it in.  This assumes the previous
+    // color at this part of the texture was already initialized to
+    // the background color.
+    blend_pnmimage_to_texture(image, glyph, _fg);
+  }    
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextFont::blend_pnmimage_to_texture
+//       Access: Private
+//  Description: Blends the PNMImage into the appropriate part of the
+//               texture, where 0.0 in the image indicates the color
+//               remains the same, and 1.0 indicates the color is
+//               assigned the indicated foreground color.
+////////////////////////////////////////////////////////////////////
+void DynamicTextFont::
+blend_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph,
+                          const Colorf &fg) {
+  Colorf fgv = fg * 255.0f;
+
+  int num_components = glyph->_page->get_num_components();
+  if (num_components == 1) {
+    // Luminance or alpha.
+    int ci = 3;
+    if (glyph->_page->get_format() != Texture::F_alpha) {
+      ci = 0;
+    }
+
+    for (int yi = 0; yi < image.get_y_size(); yi++) {
+      unsigned char *texture_row = glyph->get_row(yi);
+      nassertv(texture_row != (unsigned char *)NULL);
+      for (int xi = 0; xi < image.get_x_size(); xi++) {
+        unsigned char *tr = texture_row + xi;
+        float t = (float)image.get_gray(xi, yi);
+        tr[0] = (unsigned char)(tr[0] + t * (fgv[ci] - tr[0]));
+      }
+    }
+
+  } else if (num_components == 2) {
+    // Luminance + alpha.
+
+    for (int yi = 0; yi < image.get_y_size(); yi++) {
+      unsigned char *texture_row = glyph->get_row(yi);
+      nassertv(texture_row != (unsigned char *)NULL);
+      for (int xi = 0; xi < image.get_x_size(); xi++) {
+        unsigned char *tr = texture_row + xi * 2;
+        float t = (float)image.get_gray(xi, yi);
+        tr[0] = (unsigned char)(tr[0] + t * (fgv[0] - tr[0]));
+        tr[1] = (unsigned char)(tr[1] + t * (fgv[3] - tr[1]));
+      }
+    }
+
+  } else if (num_components == 3) {
+    // RGB.
+
+    for (int yi = 0; yi < image.get_y_size(); yi++) {
+      unsigned char *texture_row = glyph->get_row(yi);
+      nassertv(texture_row != (unsigned char *)NULL);
+      for (int xi = 0; xi < image.get_x_size(); xi++) {
+        unsigned char *tr = texture_row + xi * 3;
+        float t = (float)image.get_gray(xi, yi);
+        tr[0] = (unsigned char)(tr[0] + t * (fgv[2] - tr[0]));
+        tr[1] = (unsigned char)(tr[1] + t * (fgv[1] - tr[1]));
+        tr[2] = (unsigned char)(tr[2] + t * (fgv[0] - tr[2]));
+      }
+    }
+
+  } else { // (num_components == 4)
+    // RGBA.
+
+    for (int yi = 0; yi < image.get_y_size(); yi++) {
+      unsigned char *texture_row = glyph->get_row(yi);
+      nassertv(texture_row != (unsigned char *)NULL);
+      for (int xi = 0; xi < image.get_x_size(); xi++) {
+        unsigned char *tr = texture_row + xi * 4;
+        float t = (float)image.get_gray(xi, yi);
+        tr[0] = (unsigned char)(tr[0] + t * (fgv[2] - tr[0]));
+        tr[1] = (unsigned char)(tr[1] + t * (fgv[1] - tr[1]));
+        tr[2] = (unsigned char)(tr[2] + t * (fgv[0] - tr[2]));
+        tr[3] = (unsigned char)(tr[3] + t * (fgv[3] - tr[3]));
+      }
     }
   }
 }

+ 21 - 0
panda/src/text/dynamicTextFont.h

@@ -86,6 +86,17 @@ PUBLISHED:
   INLINE void set_winding_order(WindingOrder winding_order);
   INLINE WindingOrder get_winding_order() const;
 
+  INLINE void set_fg(const Colorf &fg);
+  INLINE const Colorf &get_fg() const;
+  INLINE void set_bg(const Colorf &bg);
+  INLINE const Colorf &get_bg() const;
+  INLINE void set_outline(const Colorf &outline_color, float outline_width,
+                          float outline_feather);
+  INLINE const Colorf &get_outline_color() const;
+  INLINE float get_outline_width() const;
+  INLINE float get_outline_feather() const;
+  INLINE Texture::Format get_tex_format() const;
+
   int get_num_pages() const;
   DynamicTextPage *get_page(int n) const;
   MAKE_SEQ(get_pages, get_num_pages, get_page);
@@ -101,9 +112,12 @@ public:
 private:
   void initialize();
   void update_filters();
+  void determine_tex_format();
   DynamicTextGlyph *make_glyph(int character, int glyph_index);
   void copy_bitmap_to_texture(const FT_Bitmap &bitmap, DynamicTextGlyph *glyph);
   void copy_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph);
+  void blend_pnmimage_to_texture(const PNMImage &image, DynamicTextGlyph *glyph,
+                                 const Colorf &fg);
   DynamicTextGlyph *slot_glyph(int character, int x_size, int y_size);
 
   void render_wireframe_contours(DynamicTextGlyph *glyph);
@@ -129,6 +143,13 @@ private:
   RenderMode _render_mode;
   WindingOrder _winding_order;
 
+  Colorf _fg, _bg, _outline_color;
+  float _outline_width;
+  float _outline_feather;
+  bool _has_outline;
+  Texture::Format _tex_format;
+  bool _needs_image_processing;
+
   typedef pvector< PT(DynamicTextPage) > Pages;
   Pages _pages;
   int _preferred_page;

+ 6 - 8
panda/src/text/dynamicTextGlyph.cxx

@@ -59,7 +59,9 @@ get_row(int y) {
   y = _page->get_y_size() - 1 - y;
 
   int offset = (y * _page->get_x_size()) + x;
-  return _page->modify_ram_image() + offset; 
+  int pixel_width = _page->get_num_components() * _page->get_component_width();
+
+  return _page->modify_ram_image() + offset * pixel_width; 
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -68,18 +70,14 @@ get_row(int y) {
 //  Description: Erases the glyph from the texture map.
 ////////////////////////////////////////////////////////////////////
 void DynamicTextGlyph::
-erase() {
+erase(DynamicTextFont *font) {
   nassertv(_page != (DynamicTextPage *)NULL);
   nassertv(_page->has_ram_image());
 
   int ysizetop = _page->get_y_size() - 1;
   int width = _page->get_x_size();
-  unsigned char *buffer = _page->modify_ram_image();
 
-  for (int y = _y; y < _y + _y_size; y++) {
-    int offset = (ysizetop - y) * width + _x;
-    memset(buffer + offset, 0, _x_size);
-  }
+  _page->fill_region(_x, _y, _x_size, _y_size, font->get_bg());
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -121,7 +119,7 @@ make_geom(int bitmap_top, int bitmap_left, float advance, float poly_margin,
   float uv_bottom = 1.0f - ((float)(_y + poly_margin + tex_y_size) + 0.5f) / _page->get_y_size();
   float uv_right = ((float)(_x + poly_margin + tex_x_size) + 0.5f) / _page->get_x_size();
   // Create a corresponding triangle pair.  We use a pair of indexed
-  // triangles rther than a single triangle strip, to avoid the bad
+  // triangles rather than a single triangle strip, to avoid the bad
   // vertex duplication behavior with lots of two-triangle strips.
   PT(GeomVertexData) vdata = new GeomVertexData
     (string(), GeomVertexFormat::get_v3t2(),

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

@@ -22,6 +22,7 @@
 #include "textGlyph.h"
 
 class DynamicTextPage;
+class DynamicTextFont;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : DynamicTextGlyph
@@ -45,7 +46,7 @@ public:
 
   INLINE bool intersects(int x, int y, int x_size, int y_size) const;
   unsigned char *get_row(int y);
-  void erase();
+  void erase(DynamicTextFont *font);
   void make_geom(int top, int left, float advance, float poly_margin,
                  float tex_x_size, float tex_y_size,
                  float font_pixels_per_unit, float tex_pixels_per_unit);

+ 91 - 5
panda/src/text/dynamicTextPage.cxx

@@ -40,7 +40,7 @@ DynamicTextPage(DynamicTextFont *font, int page_number) :
   _x_size = _font->get_page_x_size();
   _y_size = _font->get_page_y_size();
 
-  setup_2d_texture(_x_size, _y_size, T_unsigned_byte, F_alpha);
+  setup_2d_texture(_x_size, _y_size, T_unsigned_byte, font->get_tex_format());
 
   // Assign a name to the Texture.
   ostringstream strm;
@@ -59,12 +59,15 @@ DynamicTextPage(DynamicTextFont *font, int page_number) :
   // at the edges at all.
   set_wrap_u(text_wrap_mode);
   set_wrap_v(text_wrap_mode);
-  set_border_color(Colorf(0.0f, 0.0f, 0.0f, 0.0f));
+  set_border_color(font->get_bg());
+
+  // Fill the page with the font's background color.
+  fill_region(0, 0, _x_size, _y_size, font->get_bg());
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: DynamicTextPage::slot_glyph
-//       Access: Publiic
+//       Access: Public
 //  Description: Finds space within the page for a glyph of the
 //               indicated size.  If space is found, creates a new
 //               glyph object and returns it; otherwise, returns NULL.
@@ -85,6 +88,89 @@ slot_glyph(int character, int x_size, int y_size, int margin) {
   return glyph;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: DynamicTextPage::fill_region
+//       Access: Private
+//  Description: Fills a rectangular region of the texture with the
+//               indicated color.
+////////////////////////////////////////////////////////////////////
+void DynamicTextPage::
+fill_region(int x, int y, int x_size, int y_size, const Colorf &color) {
+  nassertv(x >= 0 && x < _x_size && y >= 0 && y < _y_size);
+  int num_components = get_num_components();
+  if (num_components == 1) {
+    // Luminance or alpha.
+    int ci = 3;
+    if (get_format() != Texture::F_alpha) {
+      ci = 0;
+    }
+    
+    unsigned char v = (unsigned char)(color[ci] * 255.0f);
+
+    unsigned char *image = modify_ram_image();
+    for (int yi = y; yi < y + y_size; yi++) {
+      unsigned char *row = image + yi * _x_size;
+      memset(row + x, v, x_size);
+    }
+
+  } else if (num_components == 2) {
+    // Luminance + alpha.
+
+    union {
+      unsigned char p[2];
+      PN_uint16 v;
+    } v;
+
+    v.p[0] = (unsigned char)(color[0] * 255.0f);
+    v.p[1] = (unsigned char)(color[3] * 255.0f);
+
+    PN_uint16 *image = (PN_uint16 *)modify_ram_image().p();
+    for (int yi = y; yi < y + y_size; yi++) {
+      PN_uint16 *row = image + yi * _x_size ;
+      for (int xi = x; xi < x + x_size; xi++) {
+        row[xi] = v.v;
+      }
+    }
+
+  } else if (num_components == 3) {
+    // RGB.
+
+    unsigned char p0 = (unsigned char)(color[2] * 255.0f);
+    unsigned char p1 = (unsigned char)(color[1] * 255.0f);
+    unsigned char p2 = (unsigned char)(color[0] * 255.0f);
+
+    unsigned char *image = modify_ram_image();
+    for (int yi = y; yi < y + y_size; yi++) {
+      unsigned char *row = image + yi * _x_size * 3;
+      for (int xi = x; xi < x + x_size; xi++) {
+        row[xi * 3] = p0;
+        row[xi * 3 + 1] = p1;
+        row[xi * 3 + 2] = p2;
+      }
+    }
+    
+  } else { // (num_components == 4)
+    // RGBA.
+    union {
+      unsigned char p[4];
+      PN_uint32 v;
+    } v;
+
+    v.p[0] = (unsigned char)(color[2] * 255.0f);
+    v.p[1] = (unsigned char)(color[1] * 255.0f);
+    v.p[2] = (unsigned char)(color[0] * 255.0f);
+    v.p[3] = (unsigned char)(color[3] * 255.0f);
+
+    PN_uint32 *image = (PN_uint32 *)modify_ram_image().p();
+    for (int yi = y; yi < y + y_size; yi++) {
+      PN_uint32 *row = image + yi * _x_size;
+      for (int xi = x; xi < x + x_size; xi++) {
+        row[xi] = v.v;
+      }
+    }
+  }    
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: DynamicTextPage::garbage_collect
 //       Access: Private
@@ -95,7 +181,7 @@ slot_glyph(int character, int x_size, int y_size, int margin) {
 //               font's index first.
 ////////////////////////////////////////////////////////////////////
 int DynamicTextPage::
-garbage_collect() {
+garbage_collect(DynamicTextFont *font) {
   int removed_count = 0;
 
   Glyphs new_glyphs;
@@ -108,7 +194,7 @@ garbage_collect() {
     } else {
       // Drop this one.
       removed_count++;
-      glyph->erase();
+      glyph->erase(font);
     }
   }
 

+ 3 - 2
panda/src/text/dynamicTextPage.h

@@ -43,13 +43,14 @@ public:
   INLINE int get_x_size() const;
   INLINE int get_y_size() const;
 
+  void fill_region(int x, int y, int x_size, int y_size, const Colorf &color);
+
 PUBLISHED:
   INLINE bool is_empty() const;
 
 private:
-  int garbage_collect();
+  int garbage_collect(DynamicTextFont *font);
 
-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;
 

+ 3 - 5
panda/src/text/textAssembler.cxx

@@ -589,11 +589,9 @@ assemble_text() {
       }
 
       if (properties->has_shadow()) {
-        if (properties->has_shadow_color()) {
-          shadow_state = shadow_state->add_attrib(ColorAttrib::make_flat(properties->get_shadow_color()));
-          if (properties->get_shadow_color()[3] != 1.0) {
-            shadow_state = shadow_state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
-          }
+        shadow_state = shadow_state->add_attrib(ColorAttrib::make_flat(properties->get_shadow_color()));
+        if (properties->get_shadow_color()[3] != 1.0) {
+          shadow_state = shadow_state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
         }
 
         if (properties->has_bin()) {

+ 1 - 1
panda/src/text/textProperties.cxx

@@ -44,7 +44,7 @@ TextProperties() {
   _wordwrap_width = 0.0f;
   _preserve_trailing_whitespace = false;
   _text_color.set(1.0f, 1.0f, 1.0f, 1.0f);
-  _shadow_color.set(1.0f, 1.0f, 1.0f, 1.0f);
+  _shadow_color.set(0.0f, 0.0f, 0.0f, 1.0f);
   _shadow_offset.set(0.0f, 0.0f);
   _draw_order = 1;
   _tab_width = text_tab_width;