Browse Source

pgraph text

David Rose 24 years ago
parent
commit
16cece3711
3 changed files with 2679 additions and 0 deletions
  1. 1295 0
      panda/src/text/qptextNode.I
  2. 1069 0
      panda/src/text/qptextNode.cxx
  3. 315 0
      panda/src/text/qptextNode.h

+ 1295 - 0
panda/src/text/qptextNode.I

@@ -0,0 +1,1295 @@
+// Filename: qptextNode.I
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::freeze
+//       Access: Published
+//  Description: Freezes the qpTextNode in its current state, so that
+//               updates will not immediately be displayed.  A series
+//               of state changes may then be applied in succession,
+//               which will not force the qpTextNode to be recomputed.
+//               When thaw() is later called, the qpTextNode will update
+//               itself exactly once to reflect all the state changes
+//               that were made.
+//
+//               freeze() and thaw() can nest.  Strictly speaking,
+//               each call to freeze() increments the current freeze
+//               level, while each call to thaw() decrements it.  The
+//               qpTextNode will only be updated when the current freeze
+//               level is zero.
+//
+//               The return value of freeze() is the freeze level
+//               *before* the freeze took place.  This number should
+//               match the return value of the matching thaw().
+////////////////////////////////////////////////////////////////////
+INLINE int qpTextNode::
+freeze() {
+  if (text_cat.is_debug()) {
+    text_cat.debug()
+      << "Freezing " << this->get_name() << ", level = "
+      << _freeze_level << "\n";
+  }
+  return _freeze_level++;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_freeze_level
+//       Access: Published
+//  Description: Returns the current freeze level.  The qpTextNode will
+//               not be updated visually unless this number is zero.
+//               See freeze().
+////////////////////////////////////////////////////////////////////
+INLINE int qpTextNode::
+get_freeze_level() const {
+  return _freeze_level;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::thaw
+//       Access: Published
+//  Description: Allows changes made since the last freeze() to be
+//               visible.  Strictly speaking, this actually decrements
+//               the freeze level, and updates the qpTextNode if the
+//               level reaches zero.  The return value is the new
+//               freeze level after adjusting.  See freeze().
+////////////////////////////////////////////////////////////////////
+INLINE int qpTextNode::
+thaw() {
+  if (text_cat.is_debug()) {
+    text_cat.debug()
+      << "Thawing " << this->get_name() << ", level = "
+      << _freeze_level-1 << "\n";
+  }
+  nassertr(_freeze_level > 0, _freeze_level);
+  _freeze_level--;
+
+  if (_freeze_level == 0 && _needs_rebuild) {
+    do_rebuild();
+  }
+
+  return _freeze_level;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_font
+//       Access: Published
+//  Description: Sets the font that will be used when making text.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_font(TextFont *font) {
+  if (_font != font) {
+    _font = font;
+    rebuild(true);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_font
+//       Access: Published
+//  Description: Returns the font currently in use.
+////////////////////////////////////////////////////////////////////
+INLINE TextFont *qpTextNode::
+get_font() const {
+  return _font;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_encoding
+//       Access: Published
+//  Description: Specifies how the string set via set_text() is to be
+//               interpreted.  The default, E_iso8859, means a
+//               standard string with one-byte characters
+//               (i.e. ASCII).  Other encodings are possible to take
+//               advantage of character sets with more than 256
+//               characters.
+//
+//               This affects only future calls to set_text(); it does
+//               not change text that was set previously.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_encoding(qpTextNode::Encoding encoding) {
+  _encoding = encoding;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_encoding
+//       Access: Published
+//  Description: Returns the encoding by which the string set via
+//               set_text() is to be interpreted.  See set_encoding().
+////////////////////////////////////////////////////////////////////
+INLINE qpTextNode::Encoding qpTextNode::
+get_encoding() const {
+  return _encoding;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_expand_amp
+//       Access: Published
+//  Description: Sets the state of the expand_amp flag.  When this is
+//               true, embedded ampersands in the text string are
+//               expanded to special characters according to a subset
+//               of the HTML conventions.  When this is false,
+//               ampersands are treated as ordinary characters.
+//
+//               This affects only future calls to set_text(); it does
+//               not change text that was set previously.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_expand_amp(bool expand_amp) {
+  if (expand_amp) {
+    _flags |= F_expand_amp;
+  } else {
+    _flags &= ~F_expand_amp;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_expand_amp
+//       Access: Published
+//  Description: Returns the state of the expand_amp flag.  See
+//               set_expand_amp().
+////////////////////////////////////////////////////////////////////
+INLINE bool qpTextNode::
+get_expand_amp() const {
+  return (_flags & F_expand_amp) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::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 qpTextNode::
+get_line_height() const {
+  return (_font == (TextFont *)NULL) ? 0.0 : _font->get_line_height();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_slant
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_slant(float slant) {
+  if (_slant != slant) {
+    _slant = slant;
+    rebuild(true);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_slant
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE float qpTextNode::
+get_slant() const {
+  return _slant;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_align
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_align(qpTextNode::Alignment align_type) {
+  if (_align != align_type) {
+    _align = align_type;
+    rebuild(true);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_align
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpTextNode::Alignment qpTextNode::
+get_align() const {
+  return _align;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_wordwrap
+//       Access: Published
+//  Description: Sets the qpTextNode up to automatically wordwrap text
+//               that exceeds the indicated width.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_wordwrap(float wordwrap) {
+  if (!has_wordwrap() || _wordwrap_width != wordwrap) {
+    _flags |= F_has_wordwrap;
+    _wordwrap_width = wordwrap;
+    rebuild(true);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::clear_wordwrap
+//       Access: Published
+//  Description: Removes the wordwrap setting from the qpTextNode.  Text
+//               will be as wide as it is.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+clear_wordwrap() {
+  if (has_wordwrap()) {
+    _flags &= ~F_has_wordwrap;
+    rebuild(true);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::has_wordwrap
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool qpTextNode::
+has_wordwrap() const {
+  return (_flags & F_has_wordwrap) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_wordwrap
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE float qpTextNode::
+get_wordwrap() const {
+  return _wordwrap_width;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_text_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_text_color(float r, float g, float b, float a) {
+  set_text_color(Colorf(r, g, b, a));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_text_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_text_color(const Colorf &text_color) {
+  if (!has_text_color() || _text_color != text_color) {
+    _text_color = text_color;
+    _flags |= F_has_text_color;
+    rebuild(false);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::clear_text_color
+//       Access: Published
+//  Description: Removes the text color specification; the text will
+//               be colored whatever it was in the source font file.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+clear_text_color() {
+  if (has_text_color()) {
+    _flags &= ~F_has_text_color;
+    rebuild(false);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::has_text_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool qpTextNode::
+has_text_color() const {
+  return (_flags & F_has_text_color) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_text_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Colorf qpTextNode::
+get_text_color() const {
+  return _text_color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_frame_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_frame_color(float r, float g, float b, float a) {
+  set_frame_color(Colorf(r, g, b, a));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_frame_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_frame_color(const Colorf &frame_color) {
+  if (_frame_color != frame_color) {
+    _frame_color = frame_color;
+    rebuild(false);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_frame_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Colorf qpTextNode::
+get_frame_color() const {
+  return _frame_color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_card_border
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_card_border(float size, float uv_portion) {
+  if (!has_card_border() || _card_border_size != size || _card_border_uv_portion != uv_portion) {
+    _flags |= F_has_card_border;
+    _card_border_size = size;
+    _card_border_uv_portion = uv_portion;
+    rebuild(false);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::clear_card_border
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+clear_card_border() {
+  if (has_card_border()) {
+    _flags &= ~F_has_card_border;
+    rebuild(false);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_card_border_size
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE float qpTextNode::
+get_card_border_size() const {
+  return _card_border_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_card_border_uv_portion
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE float qpTextNode::
+get_card_border_uv_portion() const {
+  return _card_border_uv_portion;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::has_card_border
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool qpTextNode::
+has_card_border() const {
+  return (_flags & F_has_card_border) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_card_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_card_color(float r, float g, float b, float a) {
+  set_card_color(Colorf(r, g, b, a));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_card_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_card_color(const Colorf &card_color) {
+  if (_card_color != card_color) {
+    _card_color = card_color;
+    rebuild(false);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_card_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Colorf qpTextNode::
+get_card_color() const {
+  return _card_color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_card_texture
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_card_texture(Texture *card_texture) {
+  if (card_texture == (Texture *)NULL) {
+    clear_card_texture();
+  } else {
+    if (!has_card_texture() || _card_texture != card_texture) {
+      _flags |= F_has_card_texture;
+      _card_texture = card_texture;
+      rebuild(false);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::clear_card_texture
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+clear_card_texture() {
+  if (has_card_texture()) {
+    _flags &= ~F_has_card_texture;
+    _card_texture = NULL;
+    rebuild(false);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::has_card_texture
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool qpTextNode::
+has_card_texture() const {
+  return (_flags & F_has_card_texture) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_card_texture
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Texture *qpTextNode::
+get_card_texture() const {
+  return _card_texture;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_shadow_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_shadow_color(float r, float g, float b, float a) {
+  set_shadow_color(Colorf(r, g, b, a));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_shadow_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_shadow_color(const Colorf &shadow_color) {
+  if (_shadow_color != shadow_color) {
+    _shadow_color = shadow_color;
+    rebuild(false);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_shadow_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Colorf qpTextNode::
+get_shadow_color() const {
+  return _shadow_color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_frame_as_margin
+//       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
+//               frame and the text in each dimension, and all should
+//               generally be positive.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_frame_as_margin(float left, float right, float bottom, float top) {
+  _flags |= (F_has_frame | F_frame_as_margin);
+  _frame_ul.set(left, top);
+  _frame_lr.set(right, bottom);
+  rebuild(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_frame_actual
+//       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
+//               text.  The left and bottom coordinates should
+//               generally be negative, while the right and top
+//               coordinates should generally be positive.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_frame_actual(float left, float right, float bottom, float top) {
+  _flags |= F_has_frame;
+  _flags &= ~F_frame_as_margin;
+  _frame_ul.set(left, top);
+  _frame_lr.set(right, bottom);
+  rebuild(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::clear_frame
+//       Access: Published
+//  Description: Specifies that a border will not be drawn around the
+//               text.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+clear_frame() {
+  _flags &= ~F_has_frame;
+  rebuild(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::has_frame
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool qpTextNode::
+has_frame() const {
+  return (_flags & F_has_frame) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::is_frame_as_margin
+//       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
+//               all around the text.  If false, then the frame was
+//               set via a call to set_frame_actual(), and the
+//               dimensions of the frame as returned by
+//               get_frame_as_set() are relative to the text's origin.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpTextNode::
+is_frame_as_margin() const {
+  nassertr(has_frame(), false);
+  return (_flags & F_frame_as_margin) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_frame_as_set
+//       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
+//               values returned by this function.  It is an error to
+//               call this if has_frame() is false.
+////////////////////////////////////////////////////////////////////
+INLINE LVecBase4f qpTextNode::
+get_frame_as_set() const {
+  nassertr(has_frame(), LVecBase4f(0.0, 0.0, 0.0, 0.0));
+  return LVecBase4f(_frame_ul[0], _frame_lr[0], _frame_lr[1], _frame_ul[1]);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_frame_actual
+//       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
+//               size of the current text; if the frame was set via
+//               set_frame_actual(), this returns the values
+//               actually set.
+////////////////////////////////////////////////////////////////////
+INLINE LVecBase4f qpTextNode::
+get_frame_actual() const {
+  nassertr(has_frame(), LVecBase4f(0.0, 0.0, 0.0, 0.0));
+  if (is_frame_as_margin()) {
+    return LVecBase4f(get_left() - _frame_ul[0],
+                      get_right() + _frame_lr[0],
+                      get_bottom() - _frame_lr[1],
+                      get_top() + _frame_ul[1]);
+  } else {
+    return get_frame_as_set();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_frame_line_width
+//       Access: Published
+//  Description: Specifies the thickness of the lines that will be
+//               used to draw the frame.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_frame_line_width(float frame_width) {
+  _frame_width = frame_width;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_frame_line_width
+//       Access: Published
+//  Description: Returns the thickness of the lines that will be
+//               used to draw the frame.
+////////////////////////////////////////////////////////////////////
+INLINE float qpTextNode::
+get_frame_line_width() const {
+  return _frame_width;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_frame_corners
+//       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
+//               the line width is greater than one.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_frame_corners(bool corners) {
+  if (corners) {
+    _flags |= F_frame_corners;
+  } else {
+    _flags &= ~F_frame_corners;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_frame_corners
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool qpTextNode::
+get_frame_corners() const {
+  return (_flags & F_frame_corners) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_card_as_margin
+//       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
+//               the amount of additional padding to insert around the
+//               text in each dimension, and all should generally be
+//               positive.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_card_as_margin(float left, float right, float bottom, float top) {
+  _flags |= (F_has_card | F_card_as_margin);
+  _card_ul.set(left, top);
+  _card_lr.set(right, bottom);
+  rebuild(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_card_actual
+//       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
+//               text.  The left and bottom coordinates should
+//               generally be negative, while the right and top
+//               coordinates should generally be positive.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_card_actual(float left, float right, float bottom, float top) {
+  _flags |= F_has_card;
+  _flags &= ~F_card_as_margin;
+  _card_ul.set(left, top);
+  _card_lr.set(right, bottom);
+  rebuild(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::clear_card
+//       Access: Published
+//  Description: Specifies that a card will not be drawn behind the
+//               text.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+clear_card() {
+  _flags &= ~F_has_card;
+  rebuild(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::has_card
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool qpTextNode::
+has_card() const {
+  return (_flags & F_has_card) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::is_card_as_margin
+//       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
+//               all around the text.  If false, then the card was
+//               set via a call to set_card_actual(), and the
+//               dimensions of the card as returned by
+//               get_card_as_set() are relative to the text's origin.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpTextNode::
+is_card_as_margin() const {
+  nassertr(has_card(), false);
+  return (_flags & F_card_as_margin) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_card_as_set
+//       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
+//               values returned by this function.  It is an error to
+//               call this if has_card() is false.
+////////////////////////////////////////////////////////////////////
+INLINE LVecBase4f qpTextNode::
+get_card_as_set() const {
+  nassertr(has_card(), LVecBase4f(0.0, 0.0, 0.0, 0.0));
+  return LVecBase4f(_card_ul[0], _card_lr[0], _card_lr[1], _card_ul[1]);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_card_actual
+//       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
+//               size of the current text; if the card was set via
+//               set_card_actual(), this returns the values
+//               actually set.
+//
+//               If the text has no card at all, this returns the
+//               dimensions of the text itself, as if the card were
+//               set with a margin of 0, 0, 0, 0.
+////////////////////////////////////////////////////////////////////
+INLINE LVecBase4f qpTextNode::
+get_card_actual() const {
+  if (!has_card()) {
+    return LVecBase4f(get_left(), get_right(), get_bottom(), get_top());
+
+  } else if (is_card_as_margin()) {
+    return LVecBase4f(get_left() - _card_ul[0],
+                      get_right() + _card_lr[0],
+                      get_bottom() - _card_lr[1],
+                      get_top() + _card_ul[1]);
+  } else {
+    return get_card_as_set();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_card_transformed
+//       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
+//               rest of the world.  Also see get_upper_left_3d() and
+//               get_lower_right_3d().
+////////////////////////////////////////////////////////////////////
+INLINE LVecBase4f qpTextNode::
+get_card_transformed() const {
+  LVecBase4f card = get_card_actual();
+  LPoint3f ul = LPoint3f(card[0], 0.0, card[3]) * _transform;
+  LPoint3f lr = LPoint3f(card[1], 0.0, card[2]) * _transform;
+
+  return LVecBase4f(ul[0], lr[0], lr[2], ul[2]);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_shadow
+//       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.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_shadow(float xoffset, float yoffset) {
+  _flags |= F_has_shadow;
+  _shadow_offset.set(xoffset, yoffset);
+  rebuild(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::clear_shadow
+//       Access: Published
+//  Description: Specifies that a shadow will not be drawn behind the
+//               text.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+clear_shadow() {
+  _flags &= ~F_has_shadow;
+  rebuild(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::has_shadow
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool qpTextNode::
+has_shadow() const {
+  return (_flags & F_has_shadow) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_shadow
+//       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.
+////////////////////////////////////////////////////////////////////
+INLINE LVecBase2f qpTextNode::
+get_shadow() const {
+  nassertr(has_shadow(), LVecBase2f(0.0, 0.0));
+  return _shadow_offset;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_bin
+//       Access: Published
+//  Description: Names the GeomBin that the qpTextNode geometry should
+//               be assigned to.  If this is set, then a
+//               GeomBinTransition will be created to explicitly place
+//               each component in the named bin.
+//
+//               The draw_order value will also be passed to each
+//               GeomBinTransition as appropriate; this is
+//               particularly useful if this names a GeomBinFixed,
+//               e.g. "fixed".
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_bin(const string &bin) {
+  _bin = bin;
+  rebuild(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::clear_bin
+//       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
+//               ordering.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+clear_bin() {
+  _bin = string();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::has_bin
+//       Access: Published
+//  Description: Returns true if an explicit drawing bin has been
+//               set via set_bin(), false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpTextNode::
+has_bin() const {
+  return !_bin.empty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_bin
+//       Access: Published
+//  Description: Returns the drawing bin set with set_bin(), or empty
+//               string if no bin has been set.
+////////////////////////////////////////////////////////////////////
+INLINE const string &qpTextNode::
+get_bin() const {
+  return _bin;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_draw_order
+//       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
+//               _draw_order+1, and the text at _draw_order+2.
+//
+//               This affects the sorting order assigned to the arcs
+//               as they are created, and also is passed to whatever
+//               bin may be assigned via set_bin().
+//
+//               The return value is the first unused draw_order
+//               number, e.g. _draw_order + 3.
+////////////////////////////////////////////////////////////////////
+INLINE int qpTextNode::
+set_draw_order(int draw_order) {
+  _draw_order = draw_order;
+  rebuild(false);
+  return _draw_order + 3;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_draw_order
+//       Access: Published
+//  Description: Returns the drawing order set with set_draw_order().
+////////////////////////////////////////////////////////////////////
+INLINE int qpTextNode::
+get_draw_order() const {
+  return _draw_order;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_transform
+//       Access: Published
+//  Description: Sets an additional transform that is applied to the
+//               entire text paragraph.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_transform(const LMatrix4f &transform) {
+  _transform = transform;
+  rebuild(true);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_transform
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE LMatrix4f qpTextNode::
+get_transform() const {
+  return _transform;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_coordinate_system
+//       Access: Published
+//  Description: Specifies the coordinate system in which the text
+//               will be generated.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_coordinate_system(CoordinateSystem coordinate_system) {
+  _coordinate_system = coordinate_system;
+  rebuild(true);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_coordinate_system
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE CoordinateSystem qpTextNode::
+get_coordinate_system() const {
+  return _coordinate_system;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_text
+//       Access: Published
+//  Description: Changes the text that is displayed under the
+//               qpTextNode.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_text(const string &text) {
+  _text = text;
+  _wtext = decode_text(text);
+  rebuild(true);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::clear_text
+//       Access: Published
+//  Description: Removes the text from the qpTextNode.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+clear_text() {
+  _text = "";
+  rebuild(true);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::has_text
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool qpTextNode::
+has_text() const {
+  return !_text.empty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_text
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE string qpTextNode::
+get_text() const {
+  return _text;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::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 qpTextNode::
+calc_width(int character) const {
+  nassertr(_font != (TextFont *)NULL, 0.0);
+  return _font->calc_width(character);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::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 qpTextNode::
+calc_width(const string &line) const {
+  nassertr(_font != (TextFont *)NULL, 0.0);
+  return _font->calc_width(decode_text(line));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::rebuild
+//       Access: Published
+//  Description: Updates the qpTextNode, if it is not frozen, or marks
+//               the qpTextNode as requiring an update if it is.  If the
+//               text is currently frozen, nothing will be done until
+//               it is thawed, unless needs_measure is true, in which
+//               case the text will be re-measured even if it is
+//               currently frozen.
+//
+//               Normally, this function is called automatically
+//               whenever any of the parameters changes.  It should
+//               not need to be called explicitly unless something
+//               goes wrong.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+rebuild(bool needs_measure) {
+  if (_freeze_level <= 0) {
+    do_rebuild();
+  } else {
+    _needs_rebuild = true;
+
+    if (needs_measure) {
+      measure();
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::measure
+//       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
+//               parameters changes.  It should not need to be called
+//               explicitly unless something goes wrong.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+measure() {
+  do_measure();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_left
+//       Access: Published
+//  Description: Returns the leftmost extent of the text in local 2-d
+//               coordinates, unmodified by the set_transform()
+//               matrix.
+////////////////////////////////////////////////////////////////////
+INLINE float qpTextNode::
+get_left() const {
+  return _ul2d[0];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_right
+//       Access: Published
+//  Description: Returns the rightmost extent of the text in local 2-d
+//               coordinates, unmodified by the set_transform()
+//               matrix.
+////////////////////////////////////////////////////////////////////
+INLINE float qpTextNode::
+get_right() const {
+  return _lr2d[0];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_bottom
+//       Access: Published
+//  Description: Returns the bottommost extent of the text in local
+//               2-d coordinates, unmodified by the set_transform()
+//               matrix.
+////////////////////////////////////////////////////////////////////
+INLINE float qpTextNode::
+get_bottom() const {
+  return _lr2d[1];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_top
+//       Access: Published
+//  Description: Returns the topmost extent of the text in local 2-d
+//               coordinates, unmodified by the set_transform()
+//               matrix.
+////////////////////////////////////////////////////////////////////
+INLINE float qpTextNode::
+get_top() const {
+  return _ul2d[1];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_height
+//       Access: Published
+//  Description: Returns the net height of the text in local 2-d
+//               coordinates.
+////////////////////////////////////////////////////////////////////
+INLINE float qpTextNode::
+get_height() const {
+  return get_top() - get_bottom();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_width
+//       Access: Published
+//  Description: Returns the net width of the text in local 2-d
+//               coordinates.
+////////////////////////////////////////////////////////////////////
+INLINE float qpTextNode::
+get_width() const {
+  return get_right() - get_left();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_upper_left_3d
+//       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.
+////////////////////////////////////////////////////////////////////
+INLINE LPoint3f qpTextNode::
+get_upper_left_3d() const {
+  return _ul3d;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_lower_right_3d
+//       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.
+////////////////////////////////////////////////////////////////////
+INLINE LPoint3f qpTextNode::
+get_lower_right_3d() const {
+  return _lr3d;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_num_rows
+//       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.
+////////////////////////////////////////////////////////////////////
+INLINE int qpTextNode::
+get_num_rows() const {
+  return _num_rows;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::set_wtext
+//       Access: Public
+//  Description: Changes the text that is displayed under the
+//               qpTextNode, with a wide text.  This automatically sets
+//               the string reported by get_text() to the 8-bit
+//               encoded version of the same string.
+////////////////////////////////////////////////////////////////////
+INLINE void qpTextNode::
+set_wtext(const wstring &wtext) {
+  _wtext = wtext;
+  _text = encode_wtext(wtext);
+  rebuild(true);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::get_wtext
+//       Access: Public
+//  Description: Returns the text associated with the qpTextNode, as a
+//               wide-character string.
+////////////////////////////////////////////////////////////////////
+INLINE const wstring &qpTextNode::
+get_wtext() const {
+  return _wtext;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::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 qpTextNode::
+calc_width(const wstring &line) const {
+  nassertr(_font != (TextFont *)NULL, 0.0);
+  return _font->calc_width(line);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::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 wstring qpTextNode::
+wordwrap_to(const wstring &wtext, float wordwrap_width,
+            bool preserve_trailing_whitespace) const {
+  nassertr(_font != (TextFont *)NULL, wtext);
+  return _font->wordwrap_to(wtext, wordwrap_width, preserve_trailing_whitespace);
+}

+ 1069 - 0
panda/src/text/qptextNode.cxx

@@ -0,0 +1,1069 @@
+// Filename: qptextNode.cxx
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qptextNode.h"
+#include "textGlyph.h"
+#include "stringDecoder.h"
+#include "config_text.h"
+
+#include "compose_matrix.h"
+#include "geom.h"
+#include "geomTristrip.h"
+#include "geomLinestrip.h"
+#include "geomPoint.h"
+#include "qpgeomNode.h"
+#include "notify.h"
+#include "transformState.h"
+#include "colorAttrib.h"
+#include "cullBinAttrib.h"
+#include "textureAttrib.h"
+#include "transparencyAttrib.h"
+#include "indent.h"
+
+#include <stdio.h>
+#include <ctype.h>
+
+TypeHandle qpTextNode::_type_handle;
+
+qpTextNode::Encoding qpTextNode::_default_encoding;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpTextNode::
+qpTextNode(const string &name) : PandaNode(name) {
+  _encoding = _default_encoding;
+  _slant = 0.0f;
+
+  _flags = 0;
+  _align = A_left;
+  _wordwrap_width = 1.0f;
+
+  _text_color.set(1.0f, 1.0f, 1.0f, 1.0f);
+  _frame_color.set(1.0f, 1.0f, 1.0f, 1.0f);
+  _card_color.set(1.0f, 1.0f, 1.0f, 1.0f);
+  _shadow_color.set(1.0f, 1.0f, 1.0f, 1.0f);
+
+  _frame_width = 1.0f;
+
+  _frame_ul.set(0.0f, 0.0f);
+  _frame_lr.set(0.0f, 0.0f);
+  _card_ul.set(0.0f, 0.0f);
+  _card_lr.set(0.0f, 0.0f);
+  _shadow_offset.set(0.0f, 0.0f);
+
+  _draw_order = 1;
+
+  _transform = LMatrix4f::ident_mat();
+  _coordinate_system = CS_default;
+
+  _ul2d.set(0.0f, 0.0f);
+  _lr2d.set(0.0f, 0.0f);
+  _ul3d.set(0.0f, 0.0f, 0.0f);
+  _lr3d.set(0.0f, 0.0f, 0.0f);
+  _num_rows = 0;
+
+  _freeze_level = 0;
+ _needs_rebuild = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::Destructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpTextNode::
+~qpTextNode() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::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 qpTextNode::
+wordwrap_to(const string &text, float wordwrap_width,
+            bool preserve_trailing_whitespace) const {
+  nassertr(_font != (TextFont *)NULL, text);
+  wstring decoded = decode_text(text);
+  wstring wrapped = 
+    _font->wordwrap_to(decoded, wordwrap_width, preserve_trailing_whitespace);
+  return encode_wtext(wrapped);
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::write
+//       Access: Published, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpTextNode::
+write(ostream &out, int indent_level) const {
+  indent(out, indent_level)
+    << "qpTextNode " << 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 + 2)
+      << "text color is " << _text_color << "\n";
+  } else {
+    indent(out, indent_level + 2)
+      << "text color is unchanged from source\n";
+  }
+  indent(out, indent_level + 2)
+    << "alignment is ";
+  switch (_align) {
+  case A_left:
+    out << "A_left\n";
+    break;
+
+  case A_right:
+    out << "A_right\n";
+    break;
+
+  case A_center:
+    out << "A_center\n";
+    break;
+  }
+
+  if (has_wordwrap()) {
+    indent(out, indent_level + 2)
+      << "Word-wrapping at " << _wordwrap_width << " units.\n";
+  }
+
+  if (has_frame()) {
+    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 + 2)
+        << "frame corners are enabled\n";
+    }
+    if (is_frame_as_margin()) {
+      indent(out, indent_level + 2)
+        << "frame coordinates are specified as margin; actual frame is:\n"
+        << get_frame_actual() << "\n";
+    } else {
+      indent(out, indent_level + 2)
+        << "frame coordinates are actual\n";
+    }
+  }
+  if (has_card()) {
+    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 + 2)
+        << "card coordinates are specified as margin; actual card is:\n"
+        << get_card_actual() << "\n";
+    } else {
+      indent(out, indent_level + 2)
+        << "card coordinates are actual\n";
+    }
+  }
+  if (has_shadow()) {
+    indent(out, indent_level + 2)
+      << "shadow of color " << _shadow_color << " at "
+      << _shadow_offset << "\n";
+  }
+  if (has_bin()) {
+    indent(out, indent_level + 2)
+      << "bin is " << _bin << "\n";
+  }
+  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 + 2)
+    << "transform is:\n"
+    << "  scale: " << scale << "\n"
+    << "    hpr: " << hpr << "\n"
+    << "  trans: " << hpr << "\n";
+  } else {
+    indent(out, indent_level + 2)
+      << "transform is:\n" << _transform;
+  }
+  indent(out, indent_level + 2)
+    << "in coordinate system " << _coordinate_system << "\n";
+
+  indent(out, indent_level + 2)
+    << "\ntext is " << _text << "\n";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::generate
+//       Access: Published
+//  Description: Generates the text, according to the parameters
+//               indicated within the qpTextNode, and returns a Node
+//               that may be parented within the tree to represent it.
+////////////////////////////////////////////////////////////////////
+PT(PandaNode) qpTextNode::
+generate() {
+  if (text_cat.is_debug()) {
+    text_cat.debug()
+      << "Rebuilding " << *this << " with '" << _text << "'\n";
+  }
+
+  // The strategy here will be to assemble together a bunch of
+  // letters, instanced from the letter hierarchy of font_def, into
+  // our own little hierarchy.
+
+  // There will be one root over the whole text block, that
+  // contains the transform passed in.  Under this root there will be
+  // another node for each row, that moves the row into the right place
+  // horizontally and vertically, and for each row, there is another
+  // node for each character.
+
+  _ul2d.set(0.0f, 0.0f);
+  _lr2d.set(0.0f, 0.0f);
+  _ul3d.set(0.0f, 0.0f, 0.0f);
+  _lr3d.set(0.0f, 0.0f, 0.0f);
+  _num_rows = 0;
+
+  // Now build a new sub-tree for all the text components.
+  PT(PandaNode) root = new PandaNode(_text);
+
+  if (_text.empty() || _font.is_null()) {
+    return 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
+  // asked for.
+  LMatrix4f mat =
+    LMatrix4f::convert_mat(CS_zup_right, _coordinate_system) *
+    _transform;
+
+  root->set_transform(TransformState::make_mat(mat));
+
+  wstring wtext = _wtext;
+  if (has_wordwrap()) {
+    wtext = _font->wordwrap_to(wtext, _wordwrap_width, false);
+  }
+
+  // Assemble the text.
+  LVector2f ul, lr;
+  int num_rows = 0;
+  PT(PandaNode) text_root = assemble_text(wtext.begin(), wtext.end(), ul, lr, num_rows);
+
+  // Parent the text in.
+  PT(PandaNode) text = new PandaNode("text");
+  root->add_child(text, _draw_order + 2);
+  text->add_child(text_root);
+
+  if (has_text_color()) {
+    text->set_attrib(ColorAttrib::make_flat(_text_color));
+    if (_text_color[3] != 1.0) {
+      text->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
+    }
+  }
+
+  if (has_bin()) {
+    text->set_attrib(CullBinAttrib::make(_bin, _draw_order + 2));
+  }
+
+  // Save the bounding-box information about the text in a form
+  // friendly to the user.
+  _num_rows = num_rows;
+  _ul2d = ul;
+  _lr2d = lr;
+  _ul3d.set(ul[0], 0.0f, ul[1]);
+  _lr3d.set(lr[0], 0.0f, lr[1]);
+
+  _ul3d = _ul3d * _transform;
+  _lr3d = _lr3d * _transform;
+
+
+  // Now deal with all the decorations.
+
+  if (has_shadow()) {
+    // Make a shadow by instancing the text behind itself.
+
+    // For now, the depth offset is 0.0 because we don't expect to see
+    // text with shadows in the 3-d world that aren't decals.  Maybe
+    // this will need to be addressed in the future.
+
+    LMatrix4f offset =
+      LMatrix4f::translate_mat(_shadow_offset[0], 0.0f, -_shadow_offset[1]);
+    PT(PandaNode) shadow = new PandaNode("shadow");
+    root->add_child(shadow, _draw_order + 1);
+    shadow->add_child(text_root);
+    shadow->set_transform(TransformState::make_mat(offset));
+    shadow->set_attrib(ColorAttrib::make_flat(_shadow_color));
+
+    if (_shadow_color[3] != 1.0f) {
+      shadow->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
+    }
+
+    if (has_bin()) {
+      shadow->set_attrib(CullBinAttrib::make(_bin, _draw_order + 1));
+    }
+  }
+
+  if (has_frame()) {
+    PT(PandaNode) frame_root = make_frame();
+    root->add_child(frame_root, _draw_order + 1);
+    frame_root->set_attrib(ColorAttrib::make_flat(_frame_color));
+    if (_frame_color[3] != 1.0f) {
+      frame_root->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
+    }
+
+    if (has_bin()) {
+      frame_root->set_attrib(CullBinAttrib::make(_bin, _draw_order + 1));
+    }
+  }
+
+  if (has_card()) {
+    PT(PandaNode) card_root;
+    if (has_card_border())
+      card_root = make_card_with_border();
+    else
+      card_root = make_card();
+    root->add_child(card_root, _draw_order);
+    card_root->set_attrib(ColorAttrib::make_flat(_card_color));
+    if (_card_color[3] != 1.0f) {
+      card_root->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
+    }
+    if (has_card_texture()) {
+      card_root->set_attrib(TextureAttrib::make(_card_texture));
+    }
+
+    if (has_bin()) {
+      card_root->set_attrib(CullBinAttrib::make(_bin, _draw_order));
+    }
+  }
+
+  // Now flatten our hierarchy to get rid of the transforms we put in,
+  // applying them to the vertices.
+
+  if (text_flatten) {
+    /* ****
+    SceneGraphReducer gr(RenderRelation::get_class_type());
+    gr.apply_transitions(root_arc);
+    gr.flatten(root, true);
+    */
+  }
+
+  return root;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::encode_wchar
+//       Access: Public
+//  Description: Encodes a single wide char into a one-, two-, or
+//               three-byte string, according to the current encoding
+//               system in effect.
+////////////////////////////////////////////////////////////////////
+string qpTextNode::
+encode_wchar(wchar_t ch) const {
+  switch (_encoding) {
+  case E_iso8859:
+    if (isascii(ch)) {
+      return string(1, (char)ch);
+    } else {
+      return ".";
+    }
+
+  case E_utf8:
+    if (ch < 0x80) {
+      return string(1, (char)ch);
+    } else if (ch < 0x800) {
+      return 
+        string(1, (char)((ch >> 6) | 0xc0)) +
+        string(1, (char)((ch & 0x3f) | 0x80));
+    } else {
+      return 
+        string(1, (char)((ch >> 12) | 0xe0)) +
+        string(1, (char)(((ch >> 6) & 0x3f) | 0x80)) +
+        string(1, (char)((ch & 0x3f) | 0x80));
+    }
+
+  case E_unicode:
+    return
+      string(1, (char)(ch >> 8)) + 
+      string(1, (char)(ch & 0xff));
+  }
+
+  return "";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::encode_wtext
+//       Access: Public
+//  Description: Encodes a wide-text string into a single-char string,
+//               accoding to the current encoding.
+////////////////////////////////////////////////////////////////////
+string qpTextNode::
+encode_wtext(const wstring &wtext) const {
+  string result;
+
+  for (wstring::const_iterator pi = wtext.begin(); pi != wtext.end(); ++pi) {
+    result += encode_wchar(*pi);
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::decode_text
+//       Access: Public
+//  Description: Returns the given wstring decoded to a single-byte
+//               string, via the current encoding system.
+////////////////////////////////////////////////////////////////////
+wstring qpTextNode::
+decode_text(const string &text) const {
+  switch (_encoding) {
+  case E_utf8:
+    {
+      StringUtf8Decoder decoder(text);
+      return decode_text_impl(decoder);
+    }
+    break;
+
+  case E_unicode:
+    {
+      StringUnicodeDecoder decoder(text);
+      return decode_text_impl(decoder);
+    }
+    break;
+
+  case E_iso8859:
+  default:
+    {
+      StringDecoder decoder(text);
+      return decode_text_impl(decoder);
+    }
+  };
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::decode_text_impl
+//       Access: Private
+//  Description: Decodes the eight-bit stream from the indicated
+//               decoder, storing the decoded unicode characters in
+//               _wtext.
+////////////////////////////////////////////////////////////////////
+wstring qpTextNode::
+decode_text_impl(StringDecoder &decoder) const {
+  wstring result;
+  bool expand_amp = get_expand_amp();
+
+  wchar_t character = decoder.get_next_character();
+  while (!decoder.is_eof()) {
+    if (character == '&' && expand_amp) {
+      // An ampersand in expand_amp mode is treated as an escape
+      // character.
+      character = expand_amp_sequence(decoder);
+    }
+    result += character;
+    character = decoder.get_next_character();
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::expand_amp_sequence
+//       Access: Private
+//  Description: Given that we have just read an ampersand from the
+//               StringDecoder, and that we have expand_amp in effect
+//               and are therefore expected to expand the sequence
+//               that this ampersand begins into a single unicode
+//               character, do the expansion and return the character.
+////////////////////////////////////////////////////////////////////
+int qpTextNode::
+expand_amp_sequence(StringDecoder &decoder) const {
+  int result = 0;
+
+  int character = decoder.get_next_character();
+  if (!decoder.is_eof() && character == '#') {
+    // An explicit numeric sequence: &#nnn;
+    result = 0;
+    character = decoder.get_next_character();
+    while (!decoder.is_eof() && character < 128 && isdigit(character)) {
+      result = (result * 10) + (character - '0');
+      character = decoder.get_next_character();
+    }
+    if (character != ';') {
+      // Invalid sequence.
+      return 0;
+    }
+
+    return result;
+  }
+
+  string sequence;
+  
+  // Some non-numeric sequence.
+  while (!decoder.is_eof() && character < 128 && isalpha(character)) {
+    sequence += character;
+    character = decoder.get_next_character();
+  }
+  if (character != ';') {
+    // Invalid sequence.
+    return 0;
+  }
+
+  static const struct {
+    const char *name;
+    int code;
+  } tokens[] = {
+    { "amp", '&' }, { "lt", '<' }, { "gt", '>' }, { "quot", '"' },
+    { "nbsp", ' ' /* 160 */ },
+
+    { "iexcl", 161 }, { "cent", 162 }, { "pound", 163 }, { "curren", 164 },
+    { "yen", 165 }, { "brvbar", 166 }, { "brkbar", 166 }, { "sect", 167 },
+    { "uml", 168 }, { "die", 168 }, { "copy", 169 }, { "ordf", 170 },
+    { "laquo", 171 }, { "not", 172 }, { "shy", 173 }, { "reg", 174 },
+    { "macr", 175 }, { "hibar", 175 }, { "deg", 176 }, { "plusmn", 177 },
+    { "sup2", 178 }, { "sup3", 179 }, { "acute", 180 }, { "micro", 181 },
+    { "para", 182 }, { "middot", 183 }, { "cedil", 184 }, { "sup1", 185 },
+    { "ordm", 186 }, { "raquo", 187 }, { "frac14", 188 }, { "frac12", 189 },
+    { "frac34", 190 }, { "iquest", 191 }, { "Agrave", 192 }, { "Aacute", 193 },
+    { "Acirc", 194 }, { "Atilde", 195 }, { "Auml", 196 }, { "Aring", 197 },
+    { "AElig", 198 }, { "Ccedil", 199 }, { "Egrave", 200 }, { "Eacute", 201 },
+    { "Ecirc", 202 }, { "Euml", 203 }, { "Igrave", 204 }, { "Iacute", 205 },
+    { "Icirc", 206 }, { "Iuml", 207 }, { "ETH", 208 }, { "Dstrok", 208 },
+    { "Ntilde", 209 }, { "Ograve", 210 }, { "Oacute", 211 }, { "Ocirc", 212 },
+    { "Otilde", 213 }, { "Ouml", 214 }, { "times", 215 }, { "Oslash", 216 },
+    { "Ugrave", 217 }, { "Uacute", 218 }, { "Ucirc", 219 }, { "Uuml", 220 },
+    { "Yacute", 221 }, { "THORN", 222 }, { "szlig", 223 }, { "agrave", 224 },
+    { "aacute", 225 }, { "acirc", 226 }, { "atilde", 227 }, { "auml", 228 },
+    { "aring", 229 }, { "aelig", 230 }, { "ccedil", 231 }, { "egrave", 232 },
+    { "eacute", 233 }, { "ecirc", 234 }, { "euml", 235 }, { "igrave", 236 },
+    { "iacute", 237 }, { "icirc", 238 }, { "iuml", 239 }, { "eth", 240 },
+    { "ntilde", 241 }, { "ograve", 242 }, { "oacute", 243 }, { "ocirc", 244 },
+    { "otilde", 245 }, { "ouml", 246 }, { "divide", 247 }, { "oslash", 248 },
+    { "ugrave", 249 }, { "uacute", 250 }, { "ucirc", 251 }, { "uuml", 252 },
+    { "yacute", 253 }, { "thorn", 254 }, { "yuml", 255 },
+
+    { NULL, 0 },
+  };
+
+  for (int i = 0; tokens[i].name != NULL; i++) {
+    if (sequence == tokens[i].name) {
+      // Here's a match.
+      return tokens[i].code;
+    }
+  }
+
+  // Some unrecognized sequence.
+  return 0;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::do_rebuild
+//       Access: Private
+//  Description: Removes any existing children of the qpTextNode, and
+//               adds the newly generated text instead.
+////////////////////////////////////////////////////////////////////
+void qpTextNode::
+do_rebuild() {
+  _needs_rebuild = false;
+
+  remove_all_children();
+
+  PT(PandaNode) new_text = generate();
+  if (new_text != (PandaNode *)NULL) {
+    add_child(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: qpTextNode::do_measure
+//       Access: Private
+//  Description: Can be called in lieu of do_rebuild() to measure the
+//               text and set up the bounding boxes properly without
+//               actually assembling it.
+////////////////////////////////////////////////////////////////////
+void qpTextNode::
+do_measure() {
+  _ul2d.set(0.0f, 0.0f);
+  _lr2d.set(0.0f, 0.0f);
+  _ul3d.set(0.0f, 0.0f, 0.0f);
+  _lr3d.set(0.0f, 0.0f, 0.0f);
+  _num_rows = 0;
+
+  if (_text.empty() || _font.is_null()) {
+    return;
+  }
+
+  wstring wtext = _wtext;
+  if (has_wordwrap()) {
+    wtext = _font->wordwrap_to(wtext, _wordwrap_width, false);
+  }
+
+  LVector2f ul, lr;
+  int num_rows = 0;
+  measure_text(wtext.begin(), wtext.end(), ul, lr, num_rows);
+
+  _num_rows = num_rows;
+  _ul2d = ul;
+  _lr2d = lr;
+  _ul3d.set(ul[0], 0.0f, ul[1]);
+  _lr3d.set(lr[0], 0.0f, lr[1]);
+
+  _ul3d = _ul3d * _transform;
+  _lr3d = _lr3d * _transform;
+}
+
+#ifndef CPPPARSER  // interrogate has a bit of trouble with wstring.
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::assemble_row
+//       Access: Private
+//  Description: Assembles the letters in the source string, up until
+//               the first newline or the end of the string into a
+//               single row (which is parented to 'dest'), and returns
+//               the length of the row.  The source pointer is moved
+//               to the terminating character.
+////////////////////////////////////////////////////////////////////
+float qpTextNode::
+assemble_row(wstring::iterator &si, const wstring::iterator &send, 
+             PandaNode *dest) {
+  nassertr(_font != (TextFont *)NULL, 0.0f);
+
+  float xpos = 0.0f;
+  while (si != send && (*si) != '\n') {
+    wchar_t character = *si;
+
+    if (character == ' ') {
+      // A space is a special case.
+      xpos += _font->get_space_advance();
+
+    } else {
+      // A printable character.
+
+      const TextGlyph *glyph;
+      float glyph_scale;
+      if (!_font->get_glyph(character, glyph, glyph_scale)) {
+        text_cat.warning()
+          << "No definition in " << _font->get_name() 
+          << " for character " << character;
+        if (character < 128 && isprint(character)) {
+          text_cat.warning(false)
+            << " ('" << (char)character << "')";
+        }
+        text_cat.warning(false)
+          << "\n";
+      }
+
+      if (glyph != (TextGlyph *)NULL) {
+        PT(Geom) char_geom = glyph->get_geom();
+        const RenderState *state = glyph->get_state();
+
+        if (char_geom != (Geom *)NULL) {
+          LMatrix4f mat = LMatrix4f::scale_mat(glyph_scale);
+          mat.set_row(3, LVector3f(xpos, 0.0f, 0.0f));
+
+          string ch(1, (char)character);
+          PT(qpGeomNode) geode = new qpGeomNode(ch);
+          geode->add_geom(char_geom, state);
+          dest->add_child(geode);
+          geode->set_transform(TransformState::make_mat(mat));
+        }
+
+        xpos += glyph->get_advance() * glyph_scale;
+      }
+    }
+    ++si;
+  }
+
+  return xpos;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::assemble_text
+//       Access: Private
+//  Description: Constructs a hierarchy of nodes that contain the
+//               geometry representing the indicated source text, and
+//               returns it.  Also sets the ul, lr corners.
+////////////////////////////////////////////////////////////////////
+PT(PandaNode) qpTextNode::
+assemble_text(wstring::iterator si, const wstring::iterator &send,
+              LVector2f &ul, LVector2f &lr, int &num_rows) {
+  nassertr(_font != (TextFont *)NULL, (PandaNode *)NULL);
+  float line_height = get_line_height();
+
+  ul.set(0.0f, 0.8f * line_height);
+  lr.set(0.0f, 0.0f);
+
+  // Make a group node to hold our formatted text geometry.
+  PT(PandaNode) root_node = new PandaNode("text");
+
+  float posy = 0.0f;
+  int row_index = 0;
+  while (si != send) {
+    char numstr[20];
+    sprintf(numstr, "row%d", row_index);
+    nassertr(strlen(numstr) < 20, root_node);
+
+    PT(PandaNode) row = new PandaNode(numstr);
+    float row_width = assemble_row(si, send, row);
+    if (si != send) {
+      // Skip past the newline.
+      ++si;
+    }
+
+    LMatrix4f mat = LMatrix4f::ident_mat();
+    if (_align == A_left) {
+      mat.set_row(3, LVector3f(0.0f, 0.0f, posy));
+      lr[0] = max(lr[0], row_width);
+
+    } else if (_align == A_right) {
+      mat.set_row(3, LVector3f(-row_width, 0.0f, posy));
+      ul[0] = min(ul[0], -row_width);
+
+    } else {
+      float half_row_width=0.5f*row_width;
+      mat.set_row(3, LVector3f(-half_row_width, 0.0f, posy));
+      lr[0] = max(lr[0], half_row_width);
+      ul[0] = min(ul[0], -half_row_width);
+    }
+
+    // Also apply whatever slant the user has asked for to the entire
+    // row.  This is an X shear.
+    if (_slant != 0.0f) {
+      LMatrix4f shear(1.0f, 0.0f, 0.0f, 0.0f,
+                      0.0f, 1.0f, 0.0f, 0.0f,
+                      _slant, 0.0f, 1.0f, 0.0f,
+                      0.0f, 0.0f, 0.0f, 1.0f);
+      mat = shear * mat;
+    }
+
+    row->set_transform(TransformState::make_mat(mat));
+    root_node->add_child(row);
+
+    posy -= line_height;
+    num_rows++;
+  }
+
+  lr[1] = posy + 0.8f * line_height;
+
+  return root_node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::measure_row
+//       Access: Private
+//  Description: Returns the length of the row in units, as it would
+//               be if it were assembled, without actually assembling
+//               it.
+////////////////////////////////////////////////////////////////////
+float qpTextNode::
+measure_row(wstring::iterator &si, const wstring::iterator &send) {
+  float xpos = 0.0f;
+  while (si != send && *si != '\n') {
+    wchar_t character = *si;
+
+    if (character == ' ') {
+      // A space is a special case.
+      xpos += _font->get_space_advance();
+
+    } else {
+      // A printable character.
+
+      const TextGlyph *glyph;
+      float glyph_scale;
+      if (_font->get_glyph(character, glyph, glyph_scale)) {
+        xpos += glyph->get_advance() * glyph_scale;
+      }
+    }
+    ++si;
+  }
+
+  return xpos;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::measure_text
+//       Access: Private
+//  Description: Sets the ul, lr corners to fit the text, without
+//               actually assembling it.
+////////////////////////////////////////////////////////////////////
+void qpTextNode::
+measure_text(wstring::iterator si, const wstring::iterator &send,
+             LVector2f &ul, LVector2f &lr, int &num_rows) {
+  nassertv(_font != (TextFont *)NULL);
+  float line_height = get_line_height();
+
+  ul.set(0.0f, 0.8f * line_height);
+  lr.set(0.0f, 0.0f);
+
+  float posy = 0.0f;
+  while (si != send) {
+    float row_width = measure_row(si, send);
+    if (si != send) {
+      // Skip past the newline.
+      ++si;
+    }
+
+    if (_align == A_left) {
+      lr[0] = max(lr[0], row_width);
+
+    } else if (_align == A_right) {
+      ul[0] = min(ul[0], -row_width);
+
+    } else {
+      float half_row_width=0.5f*row_width;
+
+      lr[0] = max(lr[0], half_row_width);
+      ul[0] = min(ul[0], -half_row_width);
+    }
+
+    posy -= line_height;
+    num_rows++;
+  }
+
+  lr[1] = posy + 0.8f * line_height;
+}
+#endif  // CPPPARSER
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::make_frame
+//       Access: Private
+//  Description: Creates a frame around the text.
+////////////////////////////////////////////////////////////////////
+PT(PandaNode) qpTextNode::
+make_frame() {
+  PT(qpGeomNode) frame_geode = new qpGeomNode("frame");
+
+  LVector4f dimensions = get_frame_actual();
+  float left = dimensions[0];
+  float right = dimensions[1];
+  float bottom = dimensions[2];
+  float top = dimensions[3];
+
+  GeomLinestrip *geoset = new GeomLinestrip;
+  PTA_int lengths=PTA_int::empty_array(0);
+  PTA_Vertexf verts;
+  lengths.push_back(5);
+  verts.push_back(Vertexf(left, 0.0f, top));
+  verts.push_back(Vertexf(left, 0.0f, bottom));
+  verts.push_back(Vertexf(right, 0.0f, bottom));
+  verts.push_back(Vertexf(right, 0.0f, top));
+  verts.push_back(Vertexf(left, 0.0f, top));
+
+  geoset->set_num_prims(1);
+  geoset->set_lengths(lengths);
+
+  geoset->set_coords(verts);
+  geoset->set_width(_frame_width);
+  frame_geode->add_geom(geoset);
+
+  if (get_frame_corners()) {
+    GeomPoint *geoset = new GeomPoint;
+
+    geoset->set_num_prims(4);
+    geoset->set_coords(verts);
+    geoset->set_size(_frame_width);
+    frame_geode->add_geom(geoset);
+  }
+
+  return frame_geode.p();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::make_card
+//       Access: Private
+//  Description: Creates a card behind the text.
+////////////////////////////////////////////////////////////////////
+PT(PandaNode) qpTextNode::
+make_card() {
+  PT(qpGeomNode) card_geode = new qpGeomNode("card");
+
+  LVector4f dimensions = get_card_actual();
+  float left = dimensions[0];
+  float right = dimensions[1];
+  float bottom = dimensions[2];
+  float top = dimensions[3];
+
+  GeomTristrip *geoset = new GeomTristrip;
+  PTA_int lengths=PTA_int::empty_array(0);
+  lengths.push_back(4);
+
+  PTA_Vertexf verts;
+  verts.push_back(Vertexf::rfu(left, 0.02f, top));
+  verts.push_back(Vertexf::rfu(left, 0.02f, bottom));
+  verts.push_back(Vertexf::rfu(right, 0.02f, top));
+  verts.push_back(Vertexf::rfu(right, 0.02f, bottom));
+
+  geoset->set_num_prims(1);
+  geoset->set_lengths(lengths);
+
+  geoset->set_coords(verts);
+
+  if (has_card_texture()) {
+    PTA_TexCoordf uvs;
+    uvs.push_back(TexCoordf(0.0f, 1.0f));
+    uvs.push_back(TexCoordf(0.0f, 0.0f));
+    uvs.push_back(TexCoordf(1.0f, 1.0f));
+    uvs.push_back(TexCoordf(1.0f, 0.0f));
+
+    geoset->set_texcoords(uvs, G_PER_VERTEX);
+  }
+
+  card_geode->add_geom(geoset);
+
+  return card_geode.p();
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpTextNode::make_card_with_border
+//       Access: Private
+//  Description: Creates a card behind the text with a specified border
+//               for button edge or what have you.
+////////////////////////////////////////////////////////////////////
+PT(PandaNode) qpTextNode::
+make_card_with_border() {
+  PT(qpGeomNode) card_geode = new qpGeomNode("card");
+
+  LVector4f dimensions = get_card_actual();
+  float left = dimensions[0];
+  float right = dimensions[1];
+  float bottom = dimensions[2];
+  float top = dimensions[3];
+
+  // we now create three tri-strips instead of one
+  // with vertices arranged as follows:
+  //
+  //  1 3            5 7  - one
+  //  2 4            6 8  /  \ two
+  //  9 11          13 15 \  /
+  // 10 12          14 16 - three
+  //
+  GeomTristrip *geoset = new GeomTristrip;
+  PTA_int lengths;
+  lengths.push_back(8);
+  lengths.push_back(8);
+  lengths.push_back(8);
+
+  PTA_Vertexf verts;
+  // verts 1,2,3,4
+  verts.push_back(Vertexf::rfu(left, 0.02f, top));
+  verts.push_back(Vertexf::rfu(left, 0.02f, top - _card_border_size));
+  verts.push_back(Vertexf::rfu(left + _card_border_size, 0.02f, top));
+  verts.push_back(Vertexf::rfu(left + _card_border_size, 0.02f,
+                               top - _card_border_size));
+  // verts 5,6,7,8
+  verts.push_back(Vertexf::rfu(right - _card_border_size, 0.02f, top));
+  verts.push_back(Vertexf::rfu(right - _card_border_size, 0.02f,
+                               top - _card_border_size));
+  verts.push_back(Vertexf::rfu(right, 0.02f, top));
+  verts.push_back(Vertexf::rfu(right, 0.02f, top - _card_border_size));
+  // verts 9,10,11,12
+  verts.push_back(Vertexf::rfu(left, 0.02f, bottom + _card_border_size));
+  verts.push_back(Vertexf::rfu(left, 0.02f, bottom));
+  verts.push_back(Vertexf::rfu(left + _card_border_size, 0.02f,
+                               bottom + _card_border_size));
+  verts.push_back(Vertexf::rfu(left + _card_border_size, 0.02f, bottom));
+  // verts 13,14,15,16
+  verts.push_back(Vertexf::rfu(right - _card_border_size, 0.02f,
+                               bottom + _card_border_size));
+  verts.push_back(Vertexf::rfu(right - _card_border_size, 0.02f, bottom));
+  verts.push_back(Vertexf::rfu(right, 0.02f, bottom + _card_border_size));
+  verts.push_back(Vertexf::rfu(right, 0.02f, bottom));
+
+  PTA_ushort indices;
+  // tristrip #1
+  indices.push_back(0);
+  indices.push_back(1);
+  indices.push_back(2);
+  indices.push_back(3);
+  indices.push_back(4);
+  indices.push_back(5);
+  indices.push_back(6);
+  indices.push_back(7);
+  // tristrip #2
+  indices.push_back(1);
+  indices.push_back(8);
+  indices.push_back(3);
+  indices.push_back(10);
+  indices.push_back(5);
+  indices.push_back(12);
+  indices.push_back(7);
+  indices.push_back(14);
+  // tristrip #3
+  indices.push_back(8);
+  indices.push_back(9);
+  indices.push_back(10);
+  indices.push_back(11);
+  indices.push_back(12);
+  indices.push_back(13);
+  indices.push_back(14);
+  indices.push_back(15);
+
+  geoset->set_num_prims(3);
+  geoset->set_lengths(lengths);
+
+  geoset->set_coords(verts,indices);
+
+  if (has_card_texture()) {
+    PTA_TexCoordf uvs;
+    uvs.push_back(TexCoordf(0.0f, 1.0f)); //1
+    uvs.push_back(TexCoordf(0.0f, 1.0f - _card_border_uv_portion)); //2
+    uvs.push_back(TexCoordf(0.0f + _card_border_uv_portion, 1.0f)); //3
+    uvs.push_back(TexCoordf(0.0f + _card_border_uv_portion,
+      1.0f - _card_border_uv_portion)); //4
+    uvs.push_back(TexCoordf( 1.0f -_card_border_uv_portion, 1.0f)); //5
+    uvs.push_back(TexCoordf( 1.0f -_card_border_uv_portion,
+      1.0f - _card_border_uv_portion)); //6
+    uvs.push_back(TexCoordf(1.0f, 1.0f)); //7
+    uvs.push_back(TexCoordf(1.0f, 1.0f - _card_border_uv_portion)); //8
+
+    uvs.push_back(TexCoordf(0.0f, _card_border_uv_portion)); //9
+    uvs.push_back(TexCoordf(0.0f, 0.0f)); //10
+    uvs.push_back(TexCoordf(_card_border_uv_portion, _card_border_uv_portion)); //11
+    uvs.push_back(TexCoordf(_card_border_uv_portion, 0.0f)); //12
+
+    uvs.push_back(TexCoordf(1.0f - _card_border_uv_portion, _card_border_uv_portion));//13
+    uvs.push_back(TexCoordf(1.0f - _card_border_uv_portion, 0.0f));//14
+    uvs.push_back(TexCoordf(1.0f, _card_border_uv_portion));//15
+    uvs.push_back(TexCoordf(1.0f, 0.0f));//16
+
+    // we can use same ref's as before (same order)
+    geoset->set_texcoords(uvs, G_PER_VERTEX, indices);
+
+  }
+
+  card_geode->add_geom(geoset);
+
+  return card_geode.p();
+}

+ 315 - 0
panda/src/text/qptextNode.h

@@ -0,0 +1,315 @@
+// Filename: qptextNode.h
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef qpTEXTNODE_H
+#define qpTEXTNODE_H
+
+#include "pandabase.h"
+
+#include "config_text.h"
+#include "textFont.h"
+#include "pandaNode.h"
+
+#include "luse.h"
+
+class StringDecoder;
+
+////////////////////////////////////////////////////////////////////
+//       Class : TextNode
+// Description : The primary interface to this module.  This class
+//               does basic text assembly; given a string of text and
+//               a TextFont object, it creates a piece of geometry
+//               that may be placed in the 3-d or 2-d world to
+//               represent the indicated text.
+//
+//               The TextNode may be used in one of two ways.
+//               Naively, it may be parented to the scene graph
+//               directly; used in this way, you can optionally call
+//               freeze() and thaw() between changing many parameters
+//               in the text at once, to avoid unnecessary expensive
+//               regeneration with each parameter change.  However, it
+//               will work, if slowly, even if you never call freeze()
+//               and thaw().
+//
+//               The second way TextNode may be used is as a text
+//               generator.  To use it in this way, call freeze() once
+//               on the TextNode when you create it, and never call
+//               thaw().  Do not parent the TextNode to the scene
+//               graph; instea, set the properties of the text and
+//               call generate() to return a node which you may parent
+//               wherever you like.  Each time you call generate() a
+//               new node is returned.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpTextNode : public PandaNode {
+PUBLISHED:
+  qpTextNode(const string &name);
+  ~qpTextNode();
+
+  enum Alignment {
+    A_left,
+    A_right,
+    A_center,
+  };
+
+  enum Encoding {
+    E_iso8859,
+    E_utf8,
+    E_unicode
+  };
+
+  INLINE int freeze();
+  INLINE int get_freeze_level() const;
+  INLINE int thaw();
+
+  INLINE void set_font(TextFont *font);
+  INLINE TextFont *get_font() const;
+
+  INLINE void set_encoding(Encoding encoding);
+  INLINE Encoding get_encoding() const;
+
+  INLINE void set_expand_amp(bool expand_amp);
+  INLINE bool get_expand_amp() const;
+
+  INLINE float get_line_height() const;
+
+  INLINE void set_slant(float slant);
+  INLINE float get_slant() const;
+
+  INLINE void set_align(Alignment align_type);
+  INLINE Alignment get_align() const;
+
+  INLINE void set_wordwrap(float width);
+  INLINE void clear_wordwrap();
+  INLINE bool has_wordwrap() const;
+  INLINE float get_wordwrap() const;
+
+  INLINE void set_text_color(float r, float g, float b, float a);
+  INLINE void set_text_color(const Colorf &text_color);
+  INLINE void clear_text_color();
+  INLINE bool has_text_color() const;
+  INLINE Colorf get_text_color() const;
+
+  INLINE void set_frame_color(float r, float g, float b, float a);
+  INLINE void set_frame_color(const Colorf &frame_color);
+  INLINE Colorf get_frame_color() const;
+
+  INLINE void set_card_border(float size, float uv_portion);
+  INLINE void clear_card_border();
+  INLINE float get_card_border_size() const;
+  INLINE float get_card_border_uv_portion() const;
+  INLINE bool has_card_border() const;
+
+  INLINE void set_card_color(float r, float g, float b, float a);
+  INLINE void set_card_color(const Colorf &card_color);
+  INLINE Colorf get_card_color() const;
+
+  INLINE void set_card_texture(Texture *card_texture);
+  INLINE void clear_card_texture();
+  INLINE bool has_card_texture() const;
+  INLINE Texture *get_card_texture() const;
+
+  INLINE void set_shadow_color(float r, float g, float b, float a);
+  INLINE void set_shadow_color(const Colorf &shadow_color);
+  INLINE Colorf get_shadow_color() const;
+
+  INLINE void set_frame_as_margin(float left, float right,
+                                  float bottom, float top);
+  INLINE void set_frame_actual(float left, float right,
+                               float bottom, float top);
+  INLINE void clear_frame();
+  INLINE bool has_frame() const;
+  INLINE bool is_frame_as_margin() const;
+  INLINE LVecBase4f get_frame_as_set() const;
+  INLINE LVecBase4f get_frame_actual() const;
+
+  INLINE void set_frame_line_width(float line_width);
+  INLINE float get_frame_line_width() const;
+  INLINE void set_frame_corners(bool corners);
+  INLINE bool get_frame_corners() const;
+
+  INLINE void set_card_as_margin(float left, float right,
+                                 float bottom, float top);
+  INLINE void set_card_actual(float left, float right,
+                              float bottom, float top);
+  INLINE void clear_card();
+  INLINE bool has_card() const;
+  INLINE bool is_card_as_margin() const;
+  INLINE LVecBase4f get_card_as_set() const;
+  INLINE LVecBase4f get_card_actual() const;
+  INLINE LVecBase4f get_card_transformed() const;
+
+  INLINE void set_shadow(float xoffset, float yoffset);
+  INLINE void clear_shadow();
+  INLINE bool has_shadow() const;
+  INLINE LVecBase2f get_shadow() const;
+
+  INLINE void set_bin(const string &bin);
+  INLINE void clear_bin();
+  INLINE bool has_bin() const;
+  INLINE const string &get_bin() const;
+
+  INLINE int set_draw_order(int draw_order);
+  INLINE int get_draw_order() const;
+
+  INLINE void set_transform(const LMatrix4f &transform);
+  INLINE LMatrix4f get_transform() const;
+
+  INLINE void set_coordinate_system(CoordinateSystem cs);
+  INLINE CoordinateSystem get_coordinate_system() const;
+
+  INLINE void set_text(const string &text);
+  INLINE void clear_text();
+  INLINE bool has_text() const;
+  INLINE string get_text() const;
+
+  INLINE float calc_width(int character) const;
+  INLINE float calc_width(const string &line) const;
+  string wordwrap_to(const string &text, float wordwrap_width,
+                     bool preserve_trailing_whitespace) const;
+
+  virtual void write(ostream &out, int indent_level = 0) const;
+
+  INLINE void rebuild(bool needs_measure);
+  INLINE void measure();
+
+  // The following functions return information about the text that
+  // was last built (and is currently visible).
+  INLINE float get_left() const;
+  INLINE float get_right() const;
+  INLINE float get_bottom() const;
+  INLINE float get_top() const;
+  INLINE float get_height() const;
+  INLINE float get_width() const;
+
+  INLINE LPoint3f get_upper_left_3d() const;
+  INLINE LPoint3f get_lower_right_3d() const;
+
+  INLINE int get_num_rows() const;
+
+  PT(PandaNode) generate();
+
+public:
+  // Direct support for wide-character strings.
+  INLINE void set_wtext(const wstring &wtext);
+  INLINE const wstring &get_wtext() const;
+
+  INLINE float calc_width(const wstring &line) const;
+  INLINE wstring wordwrap_to(const wstring &wtext, float wordwrap_width,
+                             bool preserve_trailing_whitespace) const;
+
+  string encode_wchar(wchar_t ch) const;
+  string encode_wtext(const wstring &wtext) const;
+  wstring decode_text(const string &text) const;
+
+private:
+  wstring decode_text_impl(StringDecoder &decoder) const;
+  int expand_amp_sequence(StringDecoder &decoder) const;
+
+  void do_rebuild();
+  void do_measure();
+
+#ifndef CPPPARSER  // interrogate has a bit of trouble with wstring.
+  float assemble_row(wstring::iterator &si, const wstring::iterator &send, 
+                     PandaNode *dest);
+  PT(PandaNode) assemble_text(wstring::iterator si, const wstring::iterator &send,
+                              LVector2f &ul, LVector2f &lr, int &num_rows);
+  float measure_row(wstring::iterator &si, const wstring::iterator &send);
+  void measure_text(wstring::iterator si, const wstring::iterator &send,
+                    LVector2f &ul, LVector2f &lr, int &num_rows);
+#endif  // CPPPARSER
+
+  PT(PandaNode) make_frame();
+  PT(PandaNode) make_card();
+  PT(PandaNode) make_card_with_border();
+
+  PT(TextFont) _font;
+
+  Encoding _encoding;
+  float _slant;
+
+  PT(Texture) _card_texture;
+  Colorf _text_color;
+  Colorf _shadow_color;
+  Colorf _frame_color;
+  Colorf _card_color;
+
+  enum Flags {
+    F_has_text_color   =  0x0001,
+    F_has_wordwrap     =  0x0002,
+    F_has_frame        =  0x0004,
+    F_frame_as_margin  =  0x0008,
+    F_has_card         =  0x0010,
+    F_card_as_margin   =  0x0020,
+    F_has_card_texture =  0x0040,
+    F_has_shadow       =  0x0080,
+    F_frame_corners    =  0x0100,
+    F_card_transp      =  0x0200,
+    F_has_card_border  =  0x0400,
+    F_expand_amp       =  0x0800,
+  };
+
+  int _flags;
+  Alignment _align;
+  float _wordwrap_width;
+  float _frame_width;
+  float _card_border_size;
+  float _card_border_uv_portion;
+
+  LVector2f _frame_ul, _frame_lr;
+  LVector2f _card_ul, _card_lr;
+  LVector2f _shadow_offset;
+
+  string _bin;
+  int _draw_order;
+
+  LMatrix4f _transform;
+  CoordinateSystem _coordinate_system;
+
+  string _text;
+  wstring _wtext;
+
+  LPoint2f _ul2d, _lr2d;
+  LPoint3f _ul3d, _lr3d;
+  int _num_rows;
+  int _freeze_level;
+  bool _needs_rebuild;
+
+public:
+  static Encoding _default_encoding;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    PandaNode::init_type();
+    register_type(_type_handle, "qpTextNode",
+                  PandaNode::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "qptextNode.I"
+
+#endif