Browse Source

*** empty log message ***

David Rose 25 years ago
parent
commit
e96ddd92fd

+ 4 - 2
panda/src/text/Sources.pp

@@ -8,10 +8,12 @@
     mathutil
 
   #define SOURCES \
-    config_text.cxx config_text.h textNode.I textNode.cxx textNode.h
+    config_text.cxx config_text.h \
+    textFont.I textFont.cxx textFont.h \
+    textNode.I textNode.cxx textNode.h
 
   #define INSTALL_HEADERS \
-    config_text.h textNode.I textNode.h
+    config_text.h textFont.I textFont.h textNode.I textNode.h
 
   #define IGATESCAN all
 

+ 2 - 0
panda/src/text/config_text.cxx

@@ -4,6 +4,7 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "config_text.h"
+#include "textFont.h"
 #include "textNode.h"
 
 #include <dconfig.h>
@@ -12,6 +13,7 @@ Configure(config_text);
 NotifyCategoryDef(text, "");
 
 ConfigureFn(config_text) {
+  TextFont::init_type();
   TextNode::init_type();
 }
 

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

@@ -0,0 +1,33 @@
+// Filename: textFont.I
+// Created by:  drose (03May01)
+// 
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextFont::get_line_height 
+//       Access: Published
+//  Description: Returns the number of units high each line of text
+//               is.  This is based on the font.
+////////////////////////////////////////////////////////////////////
+INLINE float TextFont::
+get_line_height() const {
+  return _font_height;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextFont::get_line_height 
+//       Access: Published
+//  Description: Returns the number of units high each line of text
+//               is.  This is based on the font.
+////////////////////////////////////////////////////////////////////
+INLINE const TextFont::CharDef *TextFont::
+get_char(int character) const {
+  CharDefs::const_iterator cdi = _defs.find(character);
+  if (cdi == _defs.end()) {
+    // No definition for this character.
+    return (CharDef *)NULL;
+  } else {
+    return &(*cdi).second;
+  }
+}

+ 342 - 0
panda/src/text/textFont.cxx

@@ -0,0 +1,342 @@
+// Filename: textFont.cxx
+// Created by:  drose (03May01)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "textFont.h"
+#include "config_text.h"
+
+#include <geom.h>
+#include <geomPoint.h>
+#include <geomNode.h>
+#include <namedNode.h>
+#include <renderRelation.h>
+
+TypeHandle TextFont::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: isblank
+//  Description: An internal function, similar to isspace(), except it
+//               does not consider newlines to be whitespace.
+////////////////////////////////////////////////////////////////////
+INLINE bool
+isblank(char ch) {
+  return (ch == ' ' || ch == '\t');
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextFont::CharDef::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+TextFont::CharDef::
+CharDef(Geom *geom, float width, const AllTransitionsWrapper &trans) : 
+  _geom(geom), _width(width), _trans(trans) { }
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextFont::Constructor
+//       Access: Published
+//  Description: The constructor expects the root node to a model
+//               generated via egg-mkfont, which consists of a set of
+//               models, one per each character in the font.
+////////////////////////////////////////////////////////////////////
+TextFont::
+TextFont(Node *font_def) {
+  nassertv(font_def != (Node *)NULL);
+  _font = font_def;
+  _defs.clear();
+  _font_height = 1.0;
+
+  find_characters(font_def);
+
+  if (_font->is_of_type(NamedNode::get_class_type())) {
+    NamedNode *named_node = DCAST(NamedNode, _font);
+    set_name(named_node->get_name());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextFont::Destructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+TextFont::
+~TextFont() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextFont::calc_width
+//       Access: Published
+//  Description: Returns the width of a single character of the font,
+//               or 0.0 if the character is not known.
+////////////////////////////////////////////////////////////////////
+float TextFont::
+calc_width(char ch) const {
+  if (ch == ' ') {
+    // A space is a special case.
+    return 0.25;
+  }
+
+  CharDefs::const_iterator cdi = _defs.find(ch);
+  if (cdi == _defs.end()) {
+    // Unknown character.
+    return 0.0;
+  }
+
+  return (*cdi).second._width;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextFont::calc_width
+//       Access: Published
+//  Description: Returns the width of a line of text of arbitrary
+//               characters.  The line should not include the newline
+//               character.
+////////////////////////////////////////////////////////////////////
+float TextFont::
+calc_width(const string &line) const {
+  float width = 0.0;
+
+  string::const_iterator si;
+  for (si = line.begin(); si != line.end(); ++si) {
+    width += calc_width(*si);
+  }
+
+  return width;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextFont::wordwrap_to
+//       Access: Published
+//  Description: Inserts newlines into the given text at the
+//               appropriate places in order to make each line be the
+//               longest possible line that is not longer than
+//               wordwrap_width (and does not break any words, if
+//               possible).  Returns the new string.
+////////////////////////////////////////////////////////////////////
+string TextFont::
+wordwrap_to(const string &text, float wordwrap_width) const {
+  string output_text;
+
+  size_t p = 0;
+
+  // Preserve any initial whitespace and newlines.
+  while (p < text.length() && isspace(text[p])) {
+    output_text += text[p];
+    p++;
+  }
+  bool first_line = true;
+
+  while (p < text.length()) {
+    nassertr(!isspace(text[p]), "");
+
+    // Scan the next n characters, until the end of the string or an
+    // embedded newline character, or we exceed wordwrap_width.
+
+    size_t q = p;
+    bool any_spaces = false;
+
+    float width = 0.0;
+    while (q < text.length() && text[q] != '\n' && width <= wordwrap_width) {
+      if (isspace(text[q])) {
+	any_spaces = true;
+      }
+
+      width += calc_width(text[q]);
+      q++;
+    }
+
+    if (q < text.length() && any_spaces) {
+      // If we stopped because we exceeded the wordwrap width, then
+      // back up to the end of the last complete word.
+
+      while (q > p && !isspace(text[q])) {
+	q--;
+      }
+    }
+
+    // Skip additional whitespace between the lines.
+    size_t next_start = q;
+    while (next_start < text.length() && isblank(text[next_start])) {
+      next_start++;
+    }
+
+    // Trim off any more blanks on the end.
+    while (q > p && isspace(text[q - 1])) {
+      q--;
+    }
+
+    if (next_start == p) {
+      // No characters got in at all.  This could only happen if the
+      // wordwrap width is narrower than a single character.
+      q++;
+      next_start++;
+      while (next_start < text.length() && isblank(text[next_start])) {
+	next_start++;
+      }
+    }
+
+    if (!first_line) {
+      output_text += '\n';
+    }
+    first_line = false;
+    output_text += text.substr(p, q - p);
+
+    // Now prepare to wrap the next line.
+
+    if (next_start < text.length() && text[next_start] == '\n') {
+      // Skip a single embedded newline.
+      next_start++;
+    }
+    p = next_start;
+
+    // Preserve any initial whitespace and newlines.
+    while (p < text.length() && isspace(text[p])) {
+      output_text += text[p];
+      p++;
+    }
+  }
+
+  return output_text;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextFont::write
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void TextFont::
+write(ostream &out, int indent_level) const {
+  indent(out, indent_level)
+    << "TextFont " << get_name() << "; "
+    << _defs.size() << " characters available in font.\n";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextFont::find_character_gsets
+//       Access: Private
+//  Description: Given that 'root' is a Node containing at least a
+//               polygon and a point which define the character's
+//               appearance and kern position, respectively,
+//               recursively walk the hierarchy and root and locate
+//               those two Geoms.
+////////////////////////////////////////////////////////////////////
+bool TextFont::
+find_character_gsets(Node *root, Geom *&ch, GeomPoint *&dot,
+		     AllTransitionsWrapper &trans) {
+  if (root->is_of_type(GeomNode::get_class_type())) {
+    GeomNode *geode = (GeomNode *)root;
+    
+    bool found = false;
+    for (int i = 0; i < geode->get_num_geoms(); i++) {
+      dDrawable *geom = geode->get_geom(i);
+      if (geom->is_of_type(GeomPoint::get_class_type())) {
+	dot = DCAST(GeomPoint, geom);
+	
+      } else if (geom->is_of_type(Geom::get_class_type())) {
+	ch = DCAST(Geom, geom);
+	found = true;
+      }
+    }
+    return found;
+    
+  } else {
+    DownRelations::const_iterator dri;
+    dri = root->_children.find(RenderRelation::get_class_type());
+    if (dri != root->_children.end()) {
+      const DownRelationPointers &drp = (*dri).second;
+      DownRelationPointers::const_iterator drpi;
+      for (drpi = drp.begin(); drpi != drp.end(); ++drpi) {
+        if (find_character_gsets((*drpi)->get_child(), ch, dot, trans)) {
+	  trans.extract_from(*drpi);
+	}
+      }
+    }
+    return false;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextFont::find_characters
+//       Access: Private
+//  Description: Walk the hierarchy beginning at the indicated root
+//               and locate any nodes whose names are just integers.
+//               These are taken to be characters, and their
+//               definitions and kern informations are retrieved.
+////////////////////////////////////////////////////////////////////
+void TextFont::
+find_characters(Node *root) {
+  string name;
+  if (root->is_of_type(NamedNode::get_class_type())) {
+    name = DCAST(NamedNode, root)->get_name();
+  }
+
+  bool all_digits = !name.empty();
+  const char *p = name.c_str();
+  while (all_digits && *p != '\0') {
+    // VC++ complains if we treat an int as a bool, so we have to do
+    // this != 0 comparsion on the int isdigit() function to shut it
+    // up.
+    all_digits = (isdigit(*p) != 0);
+    p++;
+  }
+  
+  if (all_digits) {
+    int character = atoi(name.c_str());
+    Geom *ch = NULL;
+    GeomPoint *dot = NULL;
+    AllTransitionsWrapper trans;
+    find_character_gsets(root, ch, dot, trans);
+    if (dot != NULL) {
+      // Get the first vertex from the "dot" geoset.  This will be the
+      // origin of the next character.
+      PTA_Vertexf alist;
+      PTA_ushort ilist;
+      GeomBindType bind;
+      float width;
+      dot->get_coords(alist, bind, ilist);
+      if (ilist.empty()) {
+	width = alist[0][0];
+      } else {
+	width = alist[ilist[0]][0];
+      }
+
+      _defs[character] = CharDef(ch, width, trans);
+    }
+
+  } else if (name == "ds") {
+    // The group "ds" is a special node that indicate's the font's
+    // design size, or line height.
+
+    Geom *ch = NULL;
+    GeomPoint *dot = NULL;
+    AllTransitionsWrapper trans;
+    find_character_gsets(root, ch, dot, trans);
+    if (dot != NULL) {
+      // Get the first vertex from the "dot" geoset.  This will be the
+      // design size indicator.
+      PTA_Vertexf alist;
+      PTA_ushort ilist;
+      GeomBindType bind;
+      dot->get_coords(alist, bind, ilist);
+      if (ilist.empty()) {
+	_font_height = alist[0][2];
+      } else {
+	_font_height = alist[ilist[0]][2];
+      }
+    }
+
+  } else {
+    DownRelations::const_iterator dri;
+    dri = root->_children.find(RenderRelation::get_class_type());
+    if (dri != root->_children.end()) {
+      const DownRelationPointers &drp = (*dri).second;
+      DownRelationPointers::const_iterator drpi;
+      for (drpi = drp.begin(); drpi != drp.end(); ++drpi) {
+	Node *node = (*drpi)->get_child();
+	find_characters(node);
+      }
+    }
+  }
+}

+ 86 - 0
panda/src/text/textFont.h

@@ -0,0 +1,86 @@
+// Filename: textFont.h
+// Created by:  drose (03May01)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TEXTFONT_H
+#define TEXTFONT_H
+
+#include <pandabase.h>
+
+#include "config_text.h"
+
+#include <typedReferenceCount.h>
+#include <namable.h>
+#include <pt_Node.h>
+#include <allTransitionsWrapper.h>
+
+#include <map>
+
+class Node;
+class Geom;
+class GeomPoint;
+
+////////////////////////////////////////////////////////////////////
+//       Class : TextFont
+// Description :
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA TextFont : public TypedReferenceCount, public Namable {
+PUBLISHED:
+  TextFont(Node *font_def);
+  ~TextFont();
+
+  INLINE float get_line_height() const;
+
+  float calc_width(char ch) const;
+  float calc_width(const string &line) const;
+  string wordwrap_to(const string &text, float wordwrap_width) const;
+
+  void write(ostream &out, int indent_level) const;
+
+private:
+  // Private interfaces for the benefit of TextNode.
+  class CharDef {
+  public:
+    CharDef() { }
+    CharDef(Geom *geom, float width, const AllTransitionsWrapper &trans);
+    Geom *_geom;
+    float _width;
+    AllTransitionsWrapper _trans;
+  };
+
+  INLINE const CharDef *get_char(int character) const;
+
+private:
+  bool find_character_gsets(Node *root, Geom *&ch, GeomPoint *&dot,
+			    AllTransitionsWrapper &trans);
+  void find_characters(Node *root);
+
+  typedef map<int, CharDef> CharDefs;
+  CharDefs _defs;
+  float _font_height;
+  PT_Node _font;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedReferenceCount::init_type();
+    register_type(_type_handle, "TextFont",
+		  TypedReferenceCount::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;
+
+  friend class TextNode;
+};
+
+#include "textFont.I"
+
+#endif

+ 140 - 95
panda/src/text/textNode.I

@@ -3,9 +3,10 @@
 // 
 ////////////////////////////////////////////////////////////////////
 
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::freeze
-//       Access: Public
+//       Access: Published
 //  Description: Freezes the TextNode in its current state, so that
 //               updates will not immediately be displayed.  A series
 //               of state changes may then be applied in succession,
@@ -36,7 +37,7 @@ freeze() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_freeze_level
-//       Access: Public
+//       Access: Published
 //  Description: Returns the current freeze level.  The TextNode will
 //               not be updated visually unless this number is zero.
 //               See freeze().
@@ -48,7 +49,7 @@ get_freeze_level() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::thaw
-//       Access: Public
+//       Access: Published
 //  Description: Allows changes made since the last freeze() to be
 //               visible.  Strictly speaking, this actually decrements
 //               the freeze level, and updates the TextNode if the
@@ -74,45 +75,49 @@ thaw() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_font
-//       Access: Public
+//       Access: Published
 //  Description: Sets the font that will be used when making text.
-//               This is a model generated via egg-mkfont.
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
 set_font(Node *font_def) {
-  nassertv(font_def != (Node *)NULL);
-  _font = font_def;
-  _defs.clear();
-  _font_height = 1.0;
+  set_font(new TextFont(font_def));
+}
 
-  find_characters(font_def);
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::set_font
+//       Access: Published
+//  Description: Sets the font that will be used when making text.
+////////////////////////////////////////////////////////////////////
+INLINE void TextNode::
+set_font(TextFont *font) {
+  _font = font;
   rebuild(true);
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_font
-//       Access: Public
+//       Access: Published
 //  Description: Returns the font currently in use.
 ////////////////////////////////////////////////////////////////////
-INLINE Node *TextNode::
+INLINE TextFont *TextNode::
 get_font() const {
   return _font;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_line_height 
-//       Access: Public
+//       Access: Published
 //  Description: Returns the number of units high each line of text
 //               is.  This is based on the font.
 ////////////////////////////////////////////////////////////////////
 INLINE float TextNode::
 get_line_height() const {
-  return _font_height;
+  return (_font == (TextFont *)NULL) ? 0.0 : _font->get_line_height();
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_slant
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
@@ -123,7 +128,7 @@ set_slant(float slant) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_slant
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE float TextNode::
@@ -133,7 +138,7 @@ get_slant() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_align
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
@@ -144,7 +149,7 @@ set_align(int align_type) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_align
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE int TextNode::
@@ -154,7 +159,7 @@ get_align() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_wordwrap
-//       Access: Public
+//       Access: Published
 //  Description: Sets the TextNode up to automatically wordwrap text
 //               that exceeds the indicated width.
 ////////////////////////////////////////////////////////////////////
@@ -167,7 +172,7 @@ set_wordwrap(float wordwrap) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::clear_wordwrap
-//       Access: Public
+//       Access: Published
 //  Description: Removes the wordwrap setting from the TextNode.  Text
 //               will be as wide as it is.
 ////////////////////////////////////////////////////////////////////
@@ -179,7 +184,7 @@ clear_wordwrap() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::has_wordwrap
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE bool TextNode::
@@ -189,7 +194,7 @@ has_wordwrap() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_wordwrap
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE float TextNode::
@@ -199,7 +204,7 @@ get_wordwrap() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_text_color
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
@@ -209,7 +214,7 @@ set_text_color(float r, float g, float b, float a) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_text_color
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
@@ -221,7 +226,7 @@ set_text_color(const Colorf &text_color) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::clear_text_color
-//       Access: Public
+//       Access: Published
 //  Description: Removes the text color specification; the text will
 //               be colored whatever it was in the source font file.
 ////////////////////////////////////////////////////////////////////
@@ -232,7 +237,7 @@ clear_text_color() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::has_text_color
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE bool TextNode::
@@ -242,7 +247,7 @@ has_text_color() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_text_color
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE Colorf TextNode::
@@ -252,7 +257,7 @@ get_text_color() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_frame_color
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
@@ -262,7 +267,7 @@ set_frame_color(float r, float g, float b, float a) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_frame_color
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
@@ -273,7 +278,7 @@ set_frame_color(const Colorf &frame_color) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_frame_color
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE Colorf TextNode::
@@ -283,7 +288,7 @@ get_frame_color() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_card_border
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
@@ -296,7 +301,7 @@ set_card_border(float size, float uv_portion) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::clear_card_border
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
@@ -307,7 +312,7 @@ clear_card_border() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_card_border_size
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE float TextNode::
@@ -317,7 +322,7 @@ get_card_border_size() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_card_border_uv_portion
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE float TextNode::
@@ -327,7 +332,7 @@ get_card_border_uv_portion() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::has_card_border
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE bool TextNode::
@@ -337,7 +342,7 @@ has_card_border() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_card_color
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
@@ -347,7 +352,7 @@ set_card_color(float r, float g, float b, float a) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_card_color
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
@@ -358,7 +363,7 @@ set_card_color(const Colorf &card_color) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_card_color
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE Colorf TextNode::
@@ -368,7 +373,7 @@ get_card_color() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_card_texture
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
@@ -380,7 +385,7 @@ set_card_texture(Texture *card_texture) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::clear_card_texture
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
@@ -392,7 +397,7 @@ clear_card_texture() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::has_card_texture
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE bool TextNode::
@@ -402,7 +407,7 @@ has_card_texture() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_card_texture
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE Texture *TextNode::
@@ -412,7 +417,7 @@ get_card_texture() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_shadow_color
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
@@ -422,7 +427,7 @@ set_shadow_color(float r, float g, float b, float a) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_shadow_color
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
@@ -433,7 +438,7 @@ set_shadow_color(const Colorf &shadow_color) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_shadow_color
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE Colorf TextNode::
@@ -443,7 +448,7 @@ get_shadow_color() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_frame_as_margin
-//       Access: Public
+//       Access: Published
 //  Description: Specifies that a border will be drawn around the text
 //               when it is next created.  The parameters are the
 //               amount of additional padding to insert between the
@@ -460,7 +465,7 @@ set_frame_as_margin(float left, float right, float bottom, float top) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_frame_actual
-//       Access: Public
+//       Access: Published
 //  Description: Similar to set_frame_as_margin, except the frame is
 //               specified in actual coordinate units (relative to
 //               the text's origin), irrespective of the size of the
@@ -479,7 +484,7 @@ set_frame_actual(float left, float right, float bottom, float top) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::clear_frame
-//       Access: Public
+//       Access: Published
 //  Description: Specifies that a border will not be drawn around the
 //               text.
 ////////////////////////////////////////////////////////////////////
@@ -491,7 +496,7 @@ clear_frame() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::has_frame
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE bool TextNode::
@@ -501,7 +506,7 @@ has_frame() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::is_frame_as_margin
-//       Access: Public
+//       Access: Published
 //  Description: If this is true, the frame was set via a call to
 //               set_frame_as_margin(), and the dimension of the frame
 //               as returned by get_frame_as_set() represent a margin
@@ -518,7 +523,7 @@ is_frame_as_margin() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_frame_as_set
-//       Access: Public
+//       Access: Published
 //  Description: Returns the dimensions of the frame as set by
 //               set_frame_as_margin() or set_frame_actual().  Use
 //               is_frame_actual() to determine how to interpret the
@@ -533,7 +538,7 @@ get_frame_as_set() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_frame_actual
-//       Access: Public
+//       Access: Published
 //  Description: Returns the actual dimensions of the frame around the
 //               text.  If the frame was set via set_frame_as_margin(),
 //               the result returned by this function reflects the
@@ -556,7 +561,7 @@ get_frame_actual() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_frame_line_width
-//       Access: Public
+//       Access: Published
 //  Description: Specifies the thickness of the lines that will be
 //               used to draw the frame.
 ////////////////////////////////////////////////////////////////////
@@ -567,7 +572,7 @@ set_frame_line_width(float frame_width) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_frame_line_width
-//       Access: Public
+//       Access: Published
 //  Description: Returns the thickness of the lines that will be
 //               used to draw the frame.
 ////////////////////////////////////////////////////////////////////
@@ -578,7 +583,7 @@ get_frame_line_width() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_frame_corners
-//       Access: Public
+//       Access: Published
 //  Description: Enables or disables the drawing of corners for the
 //               frame.  These are extra points drawn at each of the
 //               four corners, to soften the ugly edges generated when
@@ -595,7 +600,7 @@ set_frame_corners(bool corners) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_frame_corners
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE bool TextNode::
@@ -605,7 +610,7 @@ get_frame_corners() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_card_as_margin
-//       Access: Public
+//       Access: Published
 //  Description: Specifies that a (possibly opaque or semitransparent)
 //               card will be held behind the text when it is next
 //               created.  Like set_frame_as_margin, the parameters are
@@ -623,7 +628,7 @@ set_card_as_margin(float left, float right, float bottom, float top) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_card_actual
-//       Access: Public
+//       Access: Published
 //  Description: Similar to set_card_as_margin, except the card is
 //               specified in actual coordinate units (relative to
 //               the text's origin), irrespective of the size of the
@@ -642,7 +647,7 @@ set_card_actual(float left, float right, float bottom, float top) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::clear_card
-//       Access: Public
+//       Access: Published
 //  Description: Specifies that a card will not be drawn behind the
 //               text.
 ////////////////////////////////////////////////////////////////////
@@ -654,7 +659,7 @@ clear_card() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::has_card
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE bool TextNode::
@@ -664,7 +669,7 @@ has_card() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::is_card_as_margin
-//       Access: Public
+//       Access: Published
 //  Description: If this is true, the card was set via a call to
 //               set_card_as_margin(), and the dimension of the card
 //               as returned by get_card_as_set() represent a margin
@@ -681,7 +686,7 @@ is_card_as_margin() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_card_as_set
-//       Access: Public
+//       Access: Published
 //  Description: Returns the dimensions of the card as set by
 //               set_card_as_margin() or set_card_actual().  Use
 //               is_card_actual() to determine how to interpret the
@@ -696,7 +701,7 @@ get_card_as_set() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_card_actual
-//       Access: Public
+//       Access: Published
 //  Description: Returns the actual dimensions of the card around the
 //               text.  If the card was set via set_card_as_margin(),
 //               the result returned by this function reflects the
@@ -725,7 +730,7 @@ get_card_actual() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_card_transformed
-//       Access: Public
+//       Access: Published
 //  Description: Returns the actual card dimensions, transformed by
 //               the matrix set by set_transform().  This returns the
 //               card dimensions in actual coordinates as seen by the
@@ -743,7 +748,7 @@ get_card_transformed() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_shadow
-//       Access: Public
+//       Access: Published
 //  Description: Specifies that the text should be drawn with a
 //               shadow, by creating a second copy of the text and
 //               offsetting it slightly behind the first.
@@ -757,7 +762,7 @@ set_shadow(float xoffset, float yoffset) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::clear_shadow
-//       Access: Public
+//       Access: Published
 //  Description: Specifies that a shadow will not be drawn behind the
 //               text.
 ////////////////////////////////////////////////////////////////////
@@ -769,7 +774,7 @@ clear_shadow() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::has_shadow
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE bool TextNode::
@@ -779,7 +784,7 @@ has_shadow() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_shadow
-//       Access: Public
+//       Access: Published
 //  Description: Returns the offset of the shadow as set by
 //               set_shadow().  It is an error to call this if
 //               has_shadow() is false.
@@ -792,7 +797,7 @@ get_shadow() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_bin
-//       Access: Public
+//       Access: Published
 //  Description: Names the GeomBin that the TextNode geometry should
 //               be assigned to.  If this is set, then a
 //               GeomBinTransition will be created to explicitly place
@@ -811,7 +816,7 @@ set_bin(const string &bin) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::clear_bin
-//       Access: Public
+//       Access: Published
 //  Description: Removes the effect of a previous call to
 //               set_bin().  Text will be drawn in whatever bin
 //               it would like to be drawn in, with no explicit
@@ -824,7 +829,7 @@ clear_bin() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::has_bin
-//       Access: Public
+//       Access: Published
 //  Description: Returns true if an explicit drawing bin has been
 //               set via set_bin(), false otherwise.
 ////////////////////////////////////////////////////////////////////
@@ -835,7 +840,7 @@ has_bin() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_bin
-//       Access: Public
+//       Access: Published
 //  Description: Returns the drawing bin set with set_bin(), or empty
 //               string if no bin has been set.
 ////////////////////////////////////////////////////////////////////
@@ -846,7 +851,7 @@ get_bin() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_draw_order
-//       Access: Public
+//       Access: Published
 //  Description: Sets the drawing order of text created by the
 //               TextMaker.  This is actually the draw order of the
 //               card and frame.  The shadow is drawn at
@@ -868,7 +873,7 @@ set_draw_order(int draw_order) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_draw_order
-//       Access: Public
+//       Access: Published
 //  Description: Returns the drawing order set with set_draw_order().
 ////////////////////////////////////////////////////////////////////
 INLINE int TextNode::
@@ -878,7 +883,7 @@ get_draw_order() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_billboard
-//       Access: Public
+//       Access: Published
 //  Description: Sets the flag indicating whether the text should be
 //               generated as a billboard object or not.  If this is
 //               true, the text will automatically billboard.
@@ -895,7 +900,7 @@ set_billboard(bool billboard) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_billboard
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE bool TextNode::
@@ -905,7 +910,7 @@ get_billboard() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_transform
-//       Access: Public
+//       Access: Published
 //  Description: Sets an additional transform that is applied to the
 //               entire text paragraph.
 ////////////////////////////////////////////////////////////////////
@@ -917,7 +922,7 @@ set_transform(const LMatrix4f &transform) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_transform
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE LMatrix4f TextNode::
@@ -927,7 +932,7 @@ get_transform() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_coordinate_system
-//       Access: Public
+//       Access: Published
 //  Description: Specifies the coordinate system in which the text
 //               will be generated.
 ////////////////////////////////////////////////////////////////////
@@ -939,7 +944,7 @@ set_coordinate_system(CoordinateSystem coordinate_system) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_coordinate_system
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE CoordinateSystem TextNode::
@@ -949,7 +954,7 @@ get_coordinate_system() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_text
-//       Access: Public
+//       Access: Published
 //  Description: Changes the text that is displayed under the
 //               TextNode.
 ////////////////////////////////////////////////////////////////////
@@ -961,7 +966,7 @@ set_text(const string &text) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::clear_text
-//       Access: Public
+//       Access: Published
 //  Description: Removes the text from the TextNode.
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
@@ -972,7 +977,7 @@ clear_text() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::has_text
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE bool TextNode::
@@ -982,7 +987,7 @@ has_text() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_text
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE string TextNode::
@@ -990,9 +995,49 @@ get_text() const {
   return _text;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::calc_width
+//       Access: Published
+//  Description: Returns the width of a single character of the font,
+//               or 0.0 if the character is not known.
+////////////////////////////////////////////////////////////////////
+INLINE float TextNode::
+calc_width(char ch) const {
+  nassertr(_font != (TextFont *)NULL, 0.0);
+  return _font->calc_width(ch);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::calc_width
+//       Access: Published
+//  Description: Returns the width of a line of text of arbitrary
+//               characters.  The line should not include the newline
+//               character.
+////////////////////////////////////////////////////////////////////
+INLINE float TextNode::
+calc_width(const string &line) const {
+  nassertr(_font != (TextFont *)NULL, 0.0);
+  return _font->calc_width(line);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::wordwrap_to
+//       Access: Published
+//  Description: Inserts newlines into the given text at the
+//               appropriate places in order to make each line be the
+//               longest possible line that is not longer than
+//               wordwrap_width (and does not break any words, if
+//               possible).  Returns the new string.
+////////////////////////////////////////////////////////////////////
+INLINE string TextNode::
+wordwrap_to(const string &text, float wordwrap_width) const {
+  nassertr(_font != (TextFont *)NULL, text);
+  return _font->wordwrap_to(text, wordwrap_width);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::rebuild
-//       Access: Public
+//       Access: Published
 //  Description: Updates the TextNode, if it is not frozen, or marks
 //               the TextNode as requiring an update if it is.  If the
 //               text is currently frozen, nothing will be done until
@@ -1020,7 +1065,7 @@ rebuild(bool needs_measure) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::measure
-//       Access: Public
+//       Access: Published
 //  Description: Measures the extent of the text as it will be placed,
 //               without actually placing it.  Normally, this function
 //               is called automatically whenever any of the
@@ -1034,7 +1079,7 @@ measure() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_left
-//       Access: Public
+//       Access: Published
 //  Description: Returns the leftmost extent of the text in local 2-d
 //               coordinates, unmodified by the set_transform()
 //               matrix.
@@ -1046,7 +1091,7 @@ get_left() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_right
-//       Access: Public
+//       Access: Published
 //  Description: Returns the rightmost extent of the text in local 2-d
 //               coordinates, unmodified by the set_transform()
 //               matrix.
@@ -1058,7 +1103,7 @@ get_right() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_bottom
-//       Access: Public
+//       Access: Published
 //  Description: Returns the bottommost extent of the text in local
 //               2-d coordinates, unmodified by the set_transform()
 //               matrix.
@@ -1070,7 +1115,7 @@ get_bottom() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_top
-//       Access: Public
+//       Access: Published
 //  Description: Returns the topmost extent of the text in local 2-d
 //               coordinates, unmodified by the set_transform()
 //               matrix.
@@ -1082,7 +1127,7 @@ get_top() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_height
-//       Access: Public
+//       Access: Published
 //  Description: Returns the net height of the text in local 2-d
 //               coordinates.
 ////////////////////////////////////////////////////////////////////
@@ -1093,7 +1138,7 @@ get_height() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_width
-//       Access: Public
+//       Access: Published
 //  Description: Returns the net width of the text in local 2-d
 //               coordinates.
 ////////////////////////////////////////////////////////////////////
@@ -1104,7 +1149,7 @@ get_width() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_upper_left_3d
-//       Access: Public
+//       Access: Published
 //  Description: Returns the upper-left extent of the text object,
 //               after it has been transformed into 3-d space by
 //               applying the set_transform() matrix.
@@ -1116,7 +1161,7 @@ get_upper_left_3d() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_lower_right_3d
-//       Access: Public
+//       Access: Published
 //  Description: Returns the lower-right extent of the text object,
 //               after it has been transformed into 3-d space by
 //               applying the set_transform() matrix.
@@ -1128,7 +1173,7 @@ get_lower_right_3d() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::get_num_rows
-//       Access: Public
+//       Access: Published
 //  Description: Returns the number of rows of text that were
 //               generated.  This counts word-wrapped rows as well as
 //               rows generated due to embedded newlines.

+ 107 - 362
panda/src/text/textNode.cxx

@@ -29,33 +29,13 @@
 ////////////////////////////////////////////////////////////////////
 TypeHandle TextNode::_type_handle;
 
-////////////////////////////////////////////////////////////////////
-//     Function: isblank
-//  Description: An internal function, similar to isspace(), except it
-//               does not consider newlines to be whitespace.
-////////////////////////////////////////////////////////////////////
-INLINE bool
-isblank(char ch) {
-  return (ch == ' ' || ch == '\t');
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TextNode::CharDef::Constructor
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-TextNode::CharDef::
-CharDef(Geom *geom, float width, const AllTransitionsWrapper &trans) : 
-  _geom(geom), _width(width), _trans(trans) { }
-
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::Constructor
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 TextNode::
 TextNode(const string &name) : NamedNode(name) {
-  _font_height = 1.0;
   _slant = 0.0;
 
   _flags = 0;
@@ -92,169 +72,35 @@ TextNode(const string &name) : NamedNode(name) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::Destructor
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 TextNode::
 ~TextNode() {
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: TextNode::calc_width
-//       Access: Public
-//  Description: Returns the width of a single character of the font,
-//               or 0.0 if the character is not known.
-////////////////////////////////////////////////////////////////////
-float TextNode::
-calc_width(char ch) const {
-  if (ch == ' ') {
-    // A space is a special case.
-    return 0.25;
-  }
-
-  CharDefs::const_iterator cdi = _defs.find(ch);
-  if (cdi == _defs.end()) {
-    // Unknown character.
-    return 0.0;
-  }
-
-  return (*cdi).second._width;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TextNode::calc_width
-//       Access: Public
-//  Description: Returns the width of a line of text of arbitrary
-//               characters.  The line should not include the newline
-//               character.
-////////////////////////////////////////////////////////////////////
-float TextNode::
-calc_width(const string &line) const {
-  float width = 0.0;
-
-  string::const_iterator si;
-  for (si = line.begin(); si != line.end(); ++si) {
-    width += calc_width(*si);
-  }
-
-  return width;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TextNode::wordwrap_to
-//       Access: Public
-//  Description: Inserts newlines into the given text at the
-//               appropriate places in order to make each line be the
-//               longest possible line that is not longer than
-//               wordwrap_width (and does not break any words, if
-//               possible).  Returns the new string.
-////////////////////////////////////////////////////////////////////
-string TextNode::
-wordwrap_to(const string &text, float wordwrap_width) const {
-  string output_text;
-
-  size_t p = 0;
-
-  // Preserve any initial whitespace and newlines.
-  while (p < text.length() && isspace(text[p])) {
-    output_text += text[p];
-    p++;
-  }
-  bool first_line = true;
-
-  while (p < text.length()) {
-    nassertr(!isspace(text[p]), "");
-
-    // Scan the next n characters, until the end of the string or an
-    // embedded newline character, or we exceed wordwrap_width.
-
-    size_t q = p;
-    bool any_spaces = false;
-
-    float width = 0.0;
-    while (q < text.length() && text[q] != '\n' && width <= wordwrap_width) {
-      if (isspace(text[q])) {
-	any_spaces = true;
-      }
-
-      width += calc_width(text[q]);
-      q++;
-    }
-
-    if (q < text.length() && any_spaces) {
-      // If we stopped because we exceeded the wordwrap width, then
-      // back up to the end of the last complete word.
-
-      while (q > p && !isspace(text[q])) {
-	q--;
-      }
-    }
-
-    // Skip additional whitespace between the lines.
-    size_t next_start = q;
-    while (next_start < text.length() && isblank(text[next_start])) {
-      next_start++;
-    }
-
-    // Trim off any more blanks on the end.
-    while (q > p && isspace(text[q - 1])) {
-      q--;
-    }
-
-    if (next_start == p) {
-      // No characters got in at all.  This could only happen if the
-      // wordwrap width is narrower than a single character.
-      q++;
-      next_start++;
-      while (next_start < text.length() && isblank(text[next_start])) {
-	next_start++;
-      }
-    }
-
-    if (!first_line) {
-      output_text += '\n';
-    }
-    first_line = false;
-    output_text += text.substr(p, q - p);
-
-    // Now prepare to wrap the next line.
-
-    if (next_start < text.length() && text[next_start] == '\n') {
-      // Skip a single embedded newline.
-      next_start++;
-    }
-    p = next_start;
-
-    // Preserve any initial whitespace and newlines.
-    while (p < text.length() && isspace(text[p])) {
-      output_text += text[p];
-      p++;
-    }
-  }
-
-  return output_text;
-}
-
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::write
-//       Access: Public, Virtual
+//       Access: Published, Virtual
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 void TextNode::
 write(ostream &out, int indent_level) const {
   indent(out, indent_level)
-    << "TextNode\n"
-    << _defs.size() << " characters available in font.\n"
-    << "line height is " << _font_height << " units.\n";
+    << "TextNode " << get_name() << "\n";
+  if (_font != (TextFont *)NULL) {
+    indent(out, indent_level + 2)
+      << "with font " << _font->get_name() << "\n";
+  }
   if (has_text_color()) {
-    indent(out, indent_level)
+    indent(out, indent_level + 2)
       << "text color is " << _text_color << "\n";
   } else {
-    indent(out, indent_level)
+    indent(out, indent_level + 2)
       << "text color is unchanged from source\n";
   }
-  indent(out, indent_level)
+  indent(out, indent_level + 2)
     << "alignment is ";
   switch (_align) {
   case TM_ALIGN_LEFT:
@@ -271,82 +117,80 @@ write(ostream &out, int indent_level) const {
   }
 
   if (has_wordwrap()) {
-    indent(out, indent_level)
+    indent(out, indent_level + 2)
       << "Word-wrapping at " << _wordwrap_width << " units.\n";
   }
 
   if (has_frame()) {
-    indent(out, indent_level)
+    indent(out, indent_level + 2)
       << "frame of color " << _frame_color << " at " 
       << get_frame_as_set() << " line width " << _frame_width << "\n";
     if (get_frame_corners()) {
-      indent(out, indent_level)
+      indent(out, indent_level + 2)
 	<< "frame corners are enabled\n";
     }
     if (is_frame_as_margin()) {
-      indent(out, indent_level)
+      indent(out, indent_level + 2)
 	<< "frame coordinates are specified as margin; actual frame is:\n"
 	<< get_frame_actual() << "\n";
     } else {
-      indent(out, indent_level)
+      indent(out, indent_level + 2)
 	<< "frame coordinates are actual\n";
     }
   }
   if (has_card()) {
-    indent(out, indent_level)
+    indent(out, indent_level + 2)
       << "card of color " << _card_color << " at " 
       << get_card_as_set() << "\n";
     if (is_card_as_margin()) {
-      indent(out, indent_level)
+      indent(out, indent_level + 2)
 	<< "card coordinates are specified as margin; actual card is:\n"
 	<< get_card_actual() << "\n";
     } else {
-      indent(out, indent_level)
+      indent(out, indent_level + 2)
 	<< "card coordinates are actual\n";
     }
   }
   if (has_shadow()) {
-    indent(out, indent_level)
+    indent(out, indent_level + 2)
       << "shadow of color " << _shadow_color << " at " 
       << _shadow_offset << "\n";
   }
   if (has_bin()) {
-    indent(out, indent_level)
+    indent(out, indent_level + 2)
       << "bin is " << _bin << "\n";
   }
-  indent(out, indent_level)
+  indent(out, indent_level + 2)
     << "draw order is " << _draw_order << ", "
     << _draw_order + 1 << ", " << _draw_order + 2 << "\n";
     
   LVecBase3f scale, hpr, trans;
   if (decompose_matrix(_transform, scale, hpr, trans, _coordinate_system)) {
-  indent(out, indent_level)
+  indent(out, indent_level + 2)
     << "transform is:\n"
     << "  scale: " << scale << "\n"
     << "    hpr: " << hpr << "\n"
     << "  trans: " << hpr << "\n";
   } else {
-    indent(out, indent_level)
+    indent(out, indent_level + 2)
       << "transform is:\n" << _transform;
   }
-  indent(out, indent_level)
+  indent(out, indent_level + 2)
     << "in coordinate system " << _coordinate_system << "\n";
 
-  indent(out, indent_level)
+  indent(out, indent_level + 2)
     << "\ntext is " << _text << "\n";
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: TextNode::do_rebuild
-//       Access: Private
-//  Description: Removes any geometry previously defined in the geode,
-//               and fills it with new geometry that represents the
-//               current text string and all its accoutrements.
+//     Function: TextNode::generate
+//       Access: Published
+//  Description: Generates the text, according to the parameters
+//               indicated within the TextNode, and returns a Node
+//               that may be parented within the tree to represent it.
 ////////////////////////////////////////////////////////////////////
-void TextNode::
-do_rebuild() {
-  _needs_rebuild = false;
-
+PT_Node TextNode::
+generate() {
   if (text_cat.is_debug()) {
     text_cat.debug()
       << "Rebuilding " << *this << " with '" << _text << "'\n";
@@ -362,33 +206,20 @@ do_rebuild() {
   // horizontally and vertically, and for each row, there is another
   // node for each character.
 
-  // First delete the arc below self (the TextNode).  This will eliminate
-  // the entire sub-tree result of a prior call to make_text (if one
-  // exists).
-
-  if (_root_arc != (RenderRelation *)NULL) {
-    remove_arc(_root_arc);
-    _root_arc.clear();
-  }
-
-  _root.clear();
-  _text_root.clear();
-  _frame_root.clear();
-  _card_root.clear();
-
   _ul2d.set(0.0, 0.0);
   _lr2d.set(0.0, 0.0);
   _ul3d.set(0.0, 0.0, 0.0);
   _lr3d.set(0.0, 0.0, 0.0);
   _num_rows = 0;
 
-  if (_text.empty() || _defs.empty()) {
-    return;
+  if (_text.empty() || _font.is_null()) {
+    return (Node *)NULL;
   }
 
   // Now build a new sub-tree for all the text components.
-  _root = new NamedNode(_text);
-  _root_arc = new RenderRelation(this, _root);
+  PT_Node root = new NamedNode(_text);
+  PT_Node sub_root = new NamedNode();
+  RenderRelation *root_arc = new RenderRelation(root, sub_root);
 
   // Compute the overall text transform matrix.  We build the text in
   // a Z-up coordinate system and then convert it to whatever the user
@@ -397,10 +228,10 @@ do_rebuild() {
     LMatrix4f::convert_mat(CS_zup_right, _coordinate_system) * 
     _transform;
 
-  _root_arc->set_transition(new TransformTransition(mat));
+  root_arc->set_transition(new TransformTransition(mat));
 
   if (get_billboard()) {
-    _root_arc->set_transition(new BillboardTransition(BillboardTransition::axis(_coordinate_system)));
+    root_arc->set_transition(new BillboardTransition(BillboardTransition::axis(_coordinate_system)));
   }
 
   string text = _text;
@@ -411,9 +242,9 @@ do_rebuild() {
   // Assemble the text.
   LVector2f ul, lr;
   int num_rows = 0;
-  _text_root = assemble_text(text.c_str(), ul, lr, num_rows);
+  PT_Node text_root = assemble_text(text.c_str(), ul, lr, num_rows);
   RenderRelation *text_arc = 
-    new RenderRelation(_root, _text_root, _draw_order + 2);
+    new RenderRelation(sub_root, text_root, _draw_order + 2);
 
   if (has_text_color()) {
     text_arc->set_transition(new ColorTransition(_text_color));
@@ -452,7 +283,7 @@ do_rebuild() {
     LMatrix4f offset =
       LMatrix4f::translate_mat(_shadow_offset[0], 0.0, -_shadow_offset[1]);
     RenderRelation *shadow_arc = 
-      new RenderRelation(_root, _text_root, _draw_order + 1);
+      new RenderRelation(sub_root, text_root, _draw_order + 1);
     shadow_arc->set_transition(new TransformTransition(offset));
     shadow_arc->set_transition(new ColorTransition(_shadow_color));
 
@@ -468,9 +299,9 @@ do_rebuild() {
   }
 
   if (has_frame()) {
-    _frame_root = make_frame();
+    PT_Node frame_root = make_frame();
     RenderRelation *frame_arc =
-      new RenderRelation(_root, _frame_root, _draw_order + 1);
+      new RenderRelation(sub_root, frame_root, _draw_order + 1);
     frame_arc->set_transition(new ColorTransition(_frame_color));
     if (_frame_color[3] != 1.0) {
       frame_arc->set_transition
@@ -484,12 +315,13 @@ do_rebuild() {
   }
 
   if (has_card()) {
+    PT_Node card_root;
     if (has_card_border())
-      _card_root = make_card_with_border();
+      card_root = make_card_with_border();
     else
-      _card_root = make_card();
+      card_root = make_card();
     RenderRelation *card_arc = 
-      new RenderRelation(_root, _card_root, _draw_order);
+      new RenderRelation(sub_root, card_root, _draw_order);
     card_arc->set_transition(new ColorTransition(_card_color));
     if (_card_color[3] != 1.0) {
       card_arc->set_transition
@@ -510,11 +342,44 @@ do_rebuild() {
 
   if (flatten_text) {
     SceneGraphReducer gr(RenderRelation::get_class_type());
-    gr.apply_transitions(_root_arc);
-    gr.flatten(_root, true);
+    gr.apply_transitions(root_arc);
+    gr.flatten(root, true);
   }
+
+  return root;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::do_rebuild
+//       Access: Private
+//  Description: Removes any existing children of the TextNode, and
+//               adds the newly generated text instead.
+////////////////////////////////////////////////////////////////////
+void TextNode::
+do_rebuild() {
+  _needs_rebuild = false;
+
+  int num_children = get_num_children(RenderRelation::get_class_type());
+  while (num_children > 0) {
+    NodeRelation *arc = get_child(RenderRelation::get_class_type(), 0);
+    remove_arc(arc);
+    num_children--;
+  }
+
+  PT_Node new_text = generate();
+  if (new_text != (Node *)NULL) {
+    new RenderRelation(this, new_text);
+
+    // And we flatten one more time, to remove the new node itself if
+    // possible (it might be an unneeded node above multiple
+    // children).  This flatten operation should be fairly
+    // lightweight; it's already pretty flat.
+    SceneGraphReducer gr(RenderRelation::get_class_type());
+    gr.flatten(this, false);
+  }
+}
+
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::do_measure
 //       Access: Private
@@ -530,7 +395,7 @@ do_measure() {
   _lr3d.set(0.0, 0.0, 0.0);
   _num_rows = 0;
 
-  if (_text.empty() || _defs.empty()) {
+  if (_text.empty() || _font.is_null()) {
     return;
   }
 
@@ -553,134 +418,6 @@ do_measure() {
   _lr3d = _lr3d * _transform;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: TextNode::find_character_gsets
-//       Access: Private
-//  Description: Given that 'root' is a Node containing at least a
-//               polygon and a point which define the character's
-//               appearance and kern position, respectively,
-//               recursively walk the hierarchy and root and locate
-//               those two Geoms.
-////////////////////////////////////////////////////////////////////
-bool TextNode::
-find_character_gsets(Node *root, Geom *&ch, GeomPoint *&dot,
-		     AllTransitionsWrapper &trans) {
-  if (root->is_of_type(GeomNode::get_class_type())) {
-    GeomNode *geode = (GeomNode *)root;
-    
-    bool found = false;
-    for (int i = 0; i < geode->get_num_geoms(); i++) {
-      dDrawable *geom = geode->get_geom(i);
-      if (geom->is_of_type(GeomPoint::get_class_type())) {
-	dot = DCAST(GeomPoint, geom);
-	
-      } else if (geom->is_of_type(Geom::get_class_type())) {
-	ch = DCAST(Geom, geom);
-	found = true;
-      }
-    }
-    return found;
-    
-  } else {
-    DownRelations::const_iterator dri;
-    dri = root->_children.find(RenderRelation::get_class_type());
-    if (dri != root->_children.end()) {
-      const DownRelationPointers &drp = (*dri).second;
-      DownRelationPointers::const_iterator drpi;
-      for (drpi = drp.begin(); drpi != drp.end(); ++drpi) {
-        if (find_character_gsets((*drpi)->get_child(), ch, dot, trans)) {
-	  trans.extract_from(*drpi);
-	}
-      }
-    }
-    return false;
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TextNode::find_characters
-//       Access: Private
-//  Description: Walk the hierarchy beginning at the indicated root
-//               and locate any nodes whose names are just integers.
-//               These are taken to be characters, and their
-//               definitions and kern informations are retrieved.
-////////////////////////////////////////////////////////////////////
-void TextNode::
-find_characters(Node *root) {
-  string name;
-  if (root->is_of_type(NamedNode::get_class_type())) {
-    name = DCAST(NamedNode, root)->get_name();
-  }
-
-  bool all_digits = !name.empty();
-  const char *p = name.c_str();
-  while (all_digits && *p != '\0') {
-    // VC++ complains if we treat an int as a bool, so we have to do
-    // this != 0 comparsion on the int isdigit() function to shut it
-    // up.
-    all_digits = (isdigit(*p) != 0);
-    p++;
-  }
-  
-  if (all_digits) {
-    int character = atoi(name.c_str());
-    Geom *ch = NULL;
-    GeomPoint *dot = NULL;
-    AllTransitionsWrapper trans;
-    find_character_gsets(root, ch, dot, trans);
-    if (dot != NULL) {
-      // Get the first vertex from the "dot" geoset.  This will be the
-      // origin of the next character.
-      PTA_Vertexf alist;
-      PTA_ushort ilist;
-      GeomBindType bind;
-      float width;
-      dot->get_coords(alist, bind, ilist);
-      if (ilist.empty()) {
-	width = alist[0][0];
-      } else {
-	width = alist[ilist[0]][0];
-      }
-
-      _defs[character] = CharDef(ch, width, trans);
-    }
-
-  } else if (name == "ds") {
-    // The group "ds" is a special node that indicate's the font's
-    // design size, or line height.
-
-    Geom *ch = NULL;
-    GeomPoint *dot = NULL;
-    AllTransitionsWrapper trans;
-    find_character_gsets(root, ch, dot, trans);
-    if (dot != NULL) {
-      // Get the first vertex from the "dot" geoset.  This will be the
-      // design size indicator.
-      PTA_Vertexf alist;
-      PTA_ushort ilist;
-      GeomBindType bind;
-      dot->get_coords(alist, bind, ilist);
-      if (ilist.empty()) {
-	_font_height = alist[0][2];
-      } else {
-	_font_height = alist[ilist[0]][2];
-      }
-    }
-
-  } else {
-    DownRelations::const_iterator dri;
-    dri = root->_children.find(RenderRelation::get_class_type());
-    if (dri != root->_children.end()) {
-      const DownRelationPointers &drp = (*dri).second;
-      DownRelationPointers::const_iterator drpi;
-      for (drpi = drp.begin(); drpi != drp.end(); ++drpi) {
-	Node *node = (*drpi)->get_child();
-	find_characters(node);
-      }
-    }
-  }
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::assemble_row
 //       Access: Private
@@ -692,6 +429,8 @@ find_characters(Node *root) {
 ////////////////////////////////////////////////////////////////////
 float TextNode::
 assemble_row(const char *&source, Node *dest) {
+  nassertr(_font != (TextFont *)NULL, 0.0);
+
   float xpos = 0.0;
   while (*source != '\0' && *source != '\n') {
     int character = (unsigned char)*source;
@@ -703,15 +442,15 @@ assemble_row(const char *&source, Node *dest) {
     } else {
       // A printable character.
 
-      CharDefs::const_iterator cdi = _defs.find(character);
-      if (cdi == _defs.end()) {
+      const TextFont::CharDef *def = _font->get_char(character);
+      if (def == (const TextFont::CharDef *)NULL) {
 	text_cat.warning()
 	  << "No definition for character " << character << endl;
   
       } else {
-	Geom *char_geom = (*cdi).second._geom;
-	float char_width = (*cdi).second._width;
-	const AllTransitionsWrapper &trans = (*cdi).second._trans;
+	Geom *char_geom = def->_geom;
+	float char_width = def->_width;
+	const AllTransitionsWrapper &trans = def->_trans;
 	
 	LMatrix4f mat = LMatrix4f::ident_mat();
         mat.set_row(3, LVector3f(xpos, 0, 0)); 
@@ -743,7 +482,10 @@ assemble_row(const char *&source, Node *dest) {
 Node *TextNode::
 assemble_text(const char *source, LVector2f &ul, LVector2f &lr,
 	      int &num_rows) {
-  ul.set(0.0, 0.8 * _font_height);
+  nassertr(_font != (TextFont *)NULL, (Node *)NULL);
+  float line_height = get_line_height();
+
+  ul.set(0.0, 0.8 * line_height);
   lr.set(0.0, 0.0);
 
   // Make a group node to hold our formatted text geometry.
@@ -791,11 +533,11 @@ assemble_text(const char *source, LVector2f &ul, LVector2f &lr,
     RenderRelation *arc = new RenderRelation(root_node, row);
     arc->set_transition(new TransformTransition(mat));
 
-    posy -= _font_height;
+    posy -= line_height;
     num_rows++;
   }
 
-  lr[1] = posy + 0.8 * _font_height;
+  lr[1] = posy + 0.8 * line_height;
 
   return root_node;
 }
@@ -820,13 +562,13 @@ measure_row(const char *&source) {
     } else {
       // A printable character.
 
-      CharDefs::const_iterator cdi = _defs.find(character);
-      if (cdi == _defs.end()) {
+      const TextFont::CharDef *def = _font->get_char(character);
+      if (def == (const TextFont::CharDef *)NULL) {
 	text_cat.warning()
 	  << "No definition for character " << character << endl;
   
       } else {
-	float char_width = (*cdi).second._width;
+	float char_width = def->_width;
 	xpos += char_width;
       }
     }
@@ -845,7 +587,10 @@ measure_row(const char *&source) {
 void TextNode::
 measure_text(const char *source, LVector2f &ul, LVector2f &lr,
 	     int &num_rows) {
-  ul.set(0.0, 0.8 * _font_height);
+  nassertv(_font != (TextFont *)NULL);
+  float line_height = get_line_height();
+
+  ul.set(0.0, 0.8 * line_height);
   lr.set(0.0, 0.0);
 
   float posy = 0.0;
@@ -867,11 +612,11 @@ measure_text(const char *source, LVector2f &ul, LVector2f &lr,
       ul[0] = min(ul[0], -row_width / 2);
     }
 
-    posy -= _font_height;
+    posy -= line_height;
     num_rows++;
   }
 
-  lr[1] = posy + 0.8 * _font_height;
+  lr[1] = posy + 0.8 * line_height;
 }
 
 ////////////////////////////////////////////////////////////////////

+ 11 - 30
panda/src/text/textNode.h

@@ -12,6 +12,7 @@
 #include <pandabase.h>
 
 #include "config_text.h"
+#include "textFont.h"
 
 #include <pt_Node.h>
 #include <namedNode.h>
@@ -23,8 +24,6 @@
 #include <transparencyTransition.h>
 #include <allTransitionsWrapper.h>
 
-#include <map>
-
 ////////////////////////////////////////////////////////////////////
 // Defines
 ////////////////////////////////////////////////////////////////////
@@ -47,8 +46,9 @@ PUBLISHED:
   INLINE int get_freeze_level() const;
   INLINE int thaw();
 
-  INLINE void set_font(Node *font_def);
-  INLINE Node *get_font() const;
+  INLINE void set_font(Node *font);
+  INLINE void set_font(TextFont *font);
+  INLINE TextFont *get_font() const;
 
   INLINE float get_line_height() const;
 
@@ -145,9 +145,9 @@ PUBLISHED:
   INLINE bool has_text() const;
   INLINE string get_text() const;
 
-  float calc_width(char ch) const;
-  float calc_width(const string &line) const;
-  string wordwrap_to(const string &text, float wordwrap_width) const;
+  INLINE float calc_width(char ch) const;
+  INLINE float calc_width(const string &line) const;
+  INLINE string wordwrap_to(const string &text, float wordwrap_width) const;
 
   virtual void write(ostream &out, int indent_level = 0) const;
 
@@ -167,14 +167,13 @@ PUBLISHED:
   INLINE LPoint3f get_lower_right_3d() const;
 
   INLINE int get_num_rows() const;
-  
+
+  PT_Node generate();
 
 private:
   void do_rebuild();
   void do_measure();
-  bool find_character_gsets(Node *root, Geom *&ch, GeomPoint *&dot,
-			    AllTransitionsWrapper &trans);
-  void find_characters(Node *root);
+
   float assemble_row(const char *&source, Node *dest);
   Node *assemble_text(const char *source, LVector2f &ul, LVector2f &lr,
 		      int &num_rows);
@@ -186,28 +185,10 @@ private:
   Node *make_card();
   Node *make_card_with_border();
 
-  class EXPCL_PANDA CharDef {
-  public:
-    CharDef() { }
-    CharDef(Geom *geom, float width, const AllTransitionsWrapper &trans);
-    Geom *_geom;
-    float _width;
-    AllTransitionsWrapper _trans;
-  };
+  PT(TextFont) _font;
 
-  typedef map<int, CharDef> CharDefs;
-  CharDefs _defs;
-  float _font_height;
   float _slant;
 
-  PT(RenderRelation) _root_arc;
-
-  PT_Node _root;
-  PT_Node _text_root;
-  PT_Node _frame_root;
-  PT_Node _card_root;
-  PT_Node _font;
-
   PT(Texture) _card_texture;
   Colorf _text_color;
   Colorf _shadow_color;