Browse Source

allow embedded graphics in text paragraphs

David Rose 19 years ago
parent
commit
d300c4a10c

+ 14 - 0
panda/src/express/textEncoder.I

@@ -32,6 +32,20 @@ TextEncoder() {
   _flags = (F_got_text | F_got_wtext);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextEncoder::Copy Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE TextEncoder::
+TextEncoder(const TextEncoder &copy) :
+  _flags(copy._flags),
+  _encoding(copy._encoding),
+  _text(copy._text),
+  _wtext(copy._wtext)
+{
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextEncoder::set_encoding
 //       Access: Published

+ 1 - 0
panda/src/express/textEncoder.h

@@ -47,6 +47,7 @@ PUBLISHED:
   };
 
   INLINE TextEncoder();
+  INLINE TextEncoder(const TextEncoder &copy);
 
   INLINE void set_encoding(Encoding encoding);
   INLINE Encoding get_encoding() const;

+ 2 - 1
panda/src/pgui/pgButton.cxx

@@ -57,7 +57,8 @@ PGButton::
 ////////////////////////////////////////////////////////////////////
 PGButton::
 PGButton(const PGButton &copy) :
-  PGItem(copy)
+  PGItem(copy),
+  _click_buttons(copy._click_buttons)
 {
   _button_down = false;
 }

+ 11 - 0
panda/src/pgui/pgItem.I

@@ -552,3 +552,14 @@ compare_largest(const LVecBase4f *&largest, float &largest_area,
     largest_area = new_area;
   }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::StateDef::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE PGItem::StateDef::
+StateDef() :
+  _frame_stale(true)
+{
+}

+ 26 - 4
panda/src/pgui/pgItem.cxx

@@ -100,10 +100,34 @@ PGItem(const PGItem &copy) :
   _has_frame(copy._has_frame),
   _frame(copy._frame),
   _state(copy._state),
-  _flags(copy._flags)
+  _flags(copy._flags),
+  _sounds(copy._sounds)
 {
   _notify = NULL;
   _region = new PGMouseWatcherRegion(this);
+  
+  // We give our region the same name as the region for the PGItem
+  // we're copying--so that this PGItem will generate the same event
+  // names when the user interacts with it.
+  _region->set_name(copy._region->get_name());
+
+  // Make a deep copy of all of the original PGItem's StateDefs.
+  size_t num_state_defs = copy._state_defs.size();
+  _state_defs.reserve(num_state_defs);
+  for (size_t i = 0; i < num_state_defs; ++i) {
+    // We cheat and cast away the const, because the frame is just a
+    // cache.  But we have to get the frame out of the source before we
+    // can safely copy it.
+    StateDef &old_sd = (StateDef &)(copy._state_defs[i]);
+    old_sd._frame.remove_node();
+    old_sd._frame_stale = true;
+
+    StateDef new_sd;
+    new_sd._root = old_sd._root.copy_to(NodePath());
+    new_sd._frame_style = old_sd._frame_style;
+
+    _state_defs.push_back(new_sd);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1135,9 +1159,7 @@ frame_changed() {
 void PGItem::
 slot_state_def(int state) {
   while (state >= (int)_state_defs.size()) {
-    StateDef def;
-    def._frame_stale = true;
-    _state_defs.push_back(def);
+    _state_defs.push_back(StateDef());
   }
 }
 

+ 1 - 0
panda/src/pgui/pgItem.h

@@ -207,6 +207,7 @@ private:
 
   class StateDef {
   public:
+    INLINE StateDef();
     NodePath _root;
     PGFrameStyle _frame_style;
     NodePath _frame;

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

@@ -23,6 +23,7 @@
     textAssembler.I textAssembler.h \
     textFont.I textFont.h \
     textGlyph.I textGlyph.h \
+    textGraphic.I textGraphic.h \
     textNode.I textNode.h \
     textProperties.I textProperties.h \
     textPropertiesManager.I textPropertiesManager.h
@@ -38,6 +39,7 @@
     staticTextFont.cxx \
     textAssembler.cxx \
     textFont.cxx textGlyph.cxx \
+    textGraphic.cxx \
     textNode.cxx \
     textProperties.cxx \
     textPropertiesManager.cxx
@@ -53,6 +55,7 @@
     textAssembler.I textAssembler.h \
     textFont.I textFont.h \
     textGlyph.I textGlyph.h \
+    textGraphic.I textGraphic.h \
     textNode.I textNode.h \
     textProperties.I textProperties.h \
     textPropertiesManager.I textPropertiesManager.h

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

@@ -118,6 +118,13 @@ ConfigVariableInt text_soft_break_key
           "when it is used as a break point, no character is "
           "introduced in its place."));
 
+ConfigVariableInt text_embed_graphic_key
+("text-embed-graphic-key", 5,
+ PRC_DESC("This is the decimal character number that, embedded in "
+          "a string, is used to bracket the name of a model "
+          "added to the TextPropertiesManager object, to "
+          "embed an arbitrary graphic image within a paragraph."));
+
 wstring
 get_text_soft_hyphen_output() {
   static wstring *text_soft_hyphen_output = NULL;

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

@@ -45,6 +45,7 @@ extern ConfigVariableInt text_push_properties_key;
 extern ConfigVariableInt text_pop_properties_key;
 extern ConfigVariableInt text_soft_hyphen_key;
 extern ConfigVariableInt text_soft_break_key;
+extern ConfigVariableInt text_embed_graphic_key;
 extern wstring get_text_soft_hyphen_output();
 extern ConfigVariableDouble text_hyphen_ratio;
 extern wstring get_text_never_break_before();

+ 19 - 1
panda/src/text/textAssembler.I

@@ -84,7 +84,11 @@ get_num_rows() const {
 ////////////////////////////////////////////////////////////////////
 INLINE float TextAssembler::
 calc_width(const TextCharacter &tch) {
-  return calc_width(tch._character, *tch._properties);
+  if (tch._graphic != (TextGraphic *)NULL) {
+    return calc_width(tch._graphic, *tch._properties);
+  } else {
+    return calc_width(tch._character, *tch._properties);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -95,6 +99,20 @@ calc_width(const TextCharacter &tch) {
 INLINE TextAssembler::TextCharacter::
 TextCharacter(wchar_t character, const TextProperties *properties) :
   _character(character),
+  _graphic(NULL),
+  _properties(properties)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::TextCharacter::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE TextAssembler::TextCharacter::
+TextCharacter(const TextGraphic *graphic, const TextProperties *properties) :
+  _character(0),
+  _graphic(graphic),
   _properties(properties)
 {
 }

+ 133 - 8
panda/src/text/textAssembler.cxx

@@ -177,7 +177,9 @@ get_wordwrapped_wtext() const {
   for (ti = _wordwrapped_string.begin(); 
        ti != _wordwrapped_string.end(); 
        ++ti) {
-    wtext += (*ti)._character;
+    if ((*ti)._graphic != (TextGraphic *)NULL) {
+      wtext += (*ti)._character;
+    }
   }
 
   return wtext;
@@ -203,8 +205,13 @@ assemble_text() {
   // and put them under a common node.
   PT(PandaNode) parent_node = new PandaNode("common");
 
-  PT(GeomNode) shadow_node = new GeomNode("shadow");
-  PT(GeomNode) text_node = new GeomNode("text");
+  PT(PandaNode) shadow_node = new PandaNode("shadow");
+  PT(GeomNode) shadow_geom_node = new GeomNode("shadow_geom");
+  shadow_node->add_child(shadow_geom_node);
+
+  PT(PandaNode) text_node = new PandaNode("text");
+  PT(GeomNode) text_geom_node = new GeomNode("text_geom");
+  text_node->add_child(text_geom_node);
 
   const TextProperties *properties = NULL;
   CPT(RenderState) text_state;
@@ -256,16 +263,18 @@ assemble_text() {
     // goes, while the place-text function just stomps on the
     // vertices.
     if (properties->has_shadow()) {
-      placement->assign_copy_to(shadow_node, shadow_state, shadow_xform);
+      placement->assign_copy_to(shadow_geom_node, shadow_state, shadow_xform);
+      placement->copy_graphic_to(shadow_node, shadow_state, shadow_xform);
       any_shadow = true;
     }
-    placement->assign_to(text_node, text_state);
+    placement->assign_to(text_geom_node, text_state);
+    placement->copy_graphic_to(text_node, text_state, LMatrix4f::ident_mat());
     delete placement;
   }  
   placed_glyphs.clear();
 
   if (any_shadow) {
-    // The shadow_node must appear first to guarantee the correct
+    // The shadow_geom_node must appear first to guarantee the correct
     // rendering order.
     parent_node->add_child(shadow_node);
   }
@@ -316,6 +325,17 @@ calc_width(wchar_t character, const TextProperties &properties) {
   return advance * glyph_scale;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::calc_width
+//       Access: Private, Static
+//  Description: Returns the width of a single TextGraphic image.
+////////////////////////////////////////////////////////////////////
+float TextAssembler::
+calc_width(const TextGraphic *graphic, const TextProperties &properties) {
+  LVecBase4f frame = graphic->get_frame();
+  return (frame[1] - frame[0]) * properties.get_glyph_scale();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextAssembler::scan_wtext
 //       Access: Private
@@ -368,7 +388,13 @@ scan_wtext(wstring::const_iterator &si,
       
       // Define the new properties by extending the current properties.
       TextProperties *new_properties = new TextProperties(*current_properties);
-      new_properties->add_properties(manager->get_properties(name));
+      const TextProperties *named_props = manager->get_properties_ptr(name);
+      if (named_props != (TextProperties *)NULL) {
+        new_properties->add_properties(*named_props);
+      } else {
+        text_cat.warning()
+          << "Unknown TextProperties: " << name << "\n";
+      }
       _properties_list.push_back(new_properties);
       
       // And recursively scan with the nested properties.
@@ -390,6 +416,45 @@ scan_wtext(wstring::const_iterator &si,
       ++si;
       return;
 
+    } else if ((*si) == text_embed_graphic_key) {
+      // This indicates an embedded graphic.  Pull off the name of the
+      // TextGraphic structure, which is everything until the next
+      // text_embed_graphic_key.
+
+      wstring wname;
+      ++si;
+      while (si != send && (*si) != text_embed_graphic_key) {
+        wname += (*si);
+        ++si;
+      }
+
+      if (si == send) {
+        // We didn't close the text_embed_graphic_key.  That's an
+        // error.
+        text_cat.warning()
+          << "Unclosed embed_graphic in text.\n";
+        return;
+      }
+
+      ++si;
+
+      // Now we have to encode the wstring into a string, for lookup
+      // in the TextPropertiesManager.
+      string name = _encoder->encode_wtext(wname);
+      
+      TextPropertiesManager *manager = 
+        TextPropertiesManager::get_global_ptr();
+      
+      // Get the graphic image.
+      const TextGraphic *named_graphic = manager->get_graphic_ptr(name);
+      if (named_graphic != (TextGraphic *)NULL) {
+        text_string.push_back(TextCharacter(named_graphic, current_properties));
+
+      } else {
+        text_cat.warning()
+          << "Unknown TextGraphic: " << name << "\n";
+      }
+
     } else {
       // A normal character.  Apply it.
       text_string.push_back(TextCharacter(*si, current_properties));
@@ -771,6 +836,7 @@ assemble_row(TextAssembler::TextString::const_iterator &si,
 
   while (si != send) {
     wchar_t character = (*si)._character;
+    const TextGraphic *graphic = (*si)._graphic;
     const TextProperties *properties = (*si)._properties;
 
     TextFont *font = properties->get_font();
@@ -782,7 +848,12 @@ assemble_row(TextAssembler::TextString::const_iterator &si,
 
     // And the height of the row is the maximum of all the fonts used
     // within the row.
-    line_height = max(line_height, font->get_line_height());
+    if (graphic != (TextGraphic *)NULL) {
+      LVecBase4f frame = graphic->get_frame();
+      line_height = max(line_height, frame[3] - frame[2]);
+    } else {
+      line_height = max(line_height, font->get_line_height());
+    }
 
     if (character == '\n') {
       // The newline character marks the end of the row.
@@ -803,6 +874,38 @@ assemble_row(TextAssembler::TextString::const_iterator &si,
     } else if (character == text_soft_hyphen_key) {
       // And so is the 'soft-hyphen' key character.
 
+    } else if (graphic != (TextGraphic *)NULL) {
+      // A special embedded graphic.
+      GlyphPlacement *placement = new GlyphPlacement;
+      row_placed_glyphs.push_back(placement);
+
+      placement->_graphic_model = graphic->get_model().node();
+
+      LVecBase4f frame = graphic->get_frame();
+      float glyph_scale = properties->get_glyph_scale();
+
+      float advance = (frame[1] - frame[0]);
+
+      // Now compute the matrix that will transform the glyph (or
+      // glyphs) into position.
+      LMatrix4f glyph_xform = LMatrix4f::scale_mat(glyph_scale);
+
+      glyph_xform(3, 0) += (xpos - frame[0]);
+      glyph_xform(3, 2) += (properties->get_glyph_shift() - frame[2]);
+
+      if (properties->has_slant()) {
+        LMatrix4f shear(1.0f, 0.0f, 0.0f, 0.0f,
+                        0.0f, 1.0f, 0.0f, 0.0f,
+                        properties->get_slant(), 0.0f, 1.0f, 0.0f,
+                        0.0f, 0.0f, 0.0f, 1.0f);
+        glyph_xform = shear * glyph_xform;
+      }
+      
+      placement->_xform = glyph_xform;
+      placement->_properties = properties;
+
+      xpos += advance * glyph_scale;
+
     } else {
       // A printable character.
       bool got_glyph;
@@ -1482,5 +1585,27 @@ assign_copy_to(GeomNode *geom_node, const RenderState *state,
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::GlyphPlacement::copy_graphic_to
+//       Access: Private
+//  Description: If the GlyphPlacement includes a special graphic,
+//               copies it to the indicated node.
+////////////////////////////////////////////////////////////////////
+void TextAssembler::GlyphPlacement::
+copy_graphic_to(PandaNode *node, const RenderState *state,
+                const LMatrix4f &extra_xform) const {
+  if (_graphic_model != (PandaNode *)NULL) {
+    LMatrix4f new_xform = _xform * extra_xform;
+
+    // We need an intermediate node to hold the transform and state.
+    PT(PandaNode) intermediate_node = new PandaNode("");
+    node->add_child(intermediate_node);
+
+    intermediate_node->set_transform(TransformState::make_mat(new_xform));
+    intermediate_node->set_state(state);
+    intermediate_node->add_child(_graphic_model->copy_subgraph());
+  }
+}
+
 #endif  // CPPPARSER
 

+ 8 - 1
panda/src/text/textAssembler.h

@@ -31,6 +31,7 @@
 #include "geom.h"
 
 class TextEncoder;
+class TextGraphic;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : TextAssembler
@@ -63,6 +64,7 @@ public:
   INLINE const LVector2f &get_lr() const;
 
   static float calc_width(wchar_t character, const TextProperties &properties);
+  static float calc_width(const TextGraphic *graphic, const TextProperties &properties);
 
 private:
   // These structures are built up and operated on by scan_wtext() and
@@ -74,7 +76,9 @@ private:
   class TextCharacter {
   public:
     INLINE TextCharacter(wchar_t character, const TextProperties *properties);
+    INLINE TextCharacter(const TextGraphic *graphic, const TextProperties *properties);
     wchar_t _character;
+    const TextGraphic *_graphic;
     const TextProperties *_properties;
   };
   typedef pvector<TextCharacter> TextString;
@@ -112,9 +116,12 @@ private:
                            bool &found_any, Thread *current_thread) const;
     void assign_to(GeomNode *geom_node, const RenderState *state) const;
     void assign_copy_to(GeomNode *geom_node, const RenderState *state, 
-                        const LMatrix4f &xform) const;
+                        const LMatrix4f &extra_xform) const;
+    void copy_graphic_to(PandaNode *node, const RenderState *state,
+                         const LMatrix4f &extra_xform) const;
 
     Pieces _pieces;
+    PT(PandaNode) _graphic_model;
     LMatrix4f _xform;
     const TextProperties *_properties;
   };

+ 116 - 0
panda/src/text/textGraphic.I

@@ -0,0 +1,116 @@
+// Filename: textGraphic.I
+// Created by:  drose (18Aug06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, 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://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextGraphic::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE TextGraphic::
+TextGraphic() {
+  _frame = LVecBase4f::zero();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextGraphic::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE TextGraphic::
+TextGraphic(const NodePath &model, const LVecBase4f &frame) :
+  _model(model),
+  _frame(frame)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextGraphic::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE TextGraphic::
+TextGraphic(const NodePath &model, float left, float right, float bottom, float top) :
+  _model(model),
+  _frame(left, right, bottom, top)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextGraphic::get_model
+//       Access: Published
+//  Description: Returns the NodePath associated with the graphic,
+//               that renders the desired image.
+////////////////////////////////////////////////////////////////////
+INLINE NodePath TextGraphic::
+get_model() const {
+  return _model;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextGraphic::set_model
+//       Access: Published
+//  Description: Changes the NodePath associated with the graphic.
+//               This NodePath should contain geometry that will
+//               render the desired graphic image.
+////////////////////////////////////////////////////////////////////
+INLINE void TextGraphic::
+set_model(const NodePath &model) {
+  _model = model;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextGraphic::get_frame
+//       Access: Published
+//  Description: Returns the frame specified for the graphic.  This is
+//               the amount of space that will be reserved for the
+//               graphic when it is embedded in a text paragraph, in
+//               the form (left, right, bottom, top).
+//
+//               The actual graphic, as rendered by the NodePath
+//               specified via set_model(), should more or less fit
+//               within this rectangle.  It is not required to fit
+//               completely within it, but if it does not, it may
+//               visually overlap with nearby text.
+////////////////////////////////////////////////////////////////////
+INLINE LVecBase4f TextGraphic::
+get_frame() const {
+  return _frame;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextGraphic::set_frame
+//       Access: Published
+//  Description: Specifies the (left, right, bottom, top) bounding
+//               frame for the graphic.  See get_frame().
+////////////////////////////////////////////////////////////////////
+INLINE void TextGraphic::
+set_frame(const LVecBase4f &frame) {
+  _frame = frame;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextGraphic::set_frame
+//       Access: Published
+//  Description: Specifies the (left, right, bottom, top) bounding
+//               frame for the graphic.  See get_frame().
+////////////////////////////////////////////////////////////////////
+INLINE void TextGraphic::
+set_frame(float left, float right, float bottom, float top) {
+  _frame.set(left, right, bottom, top);
+}

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

@@ -0,0 +1,19 @@
+// Filename: textGraphic.cxx
+// Created by:  drose (18Aug06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, 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://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "textGraphic.h"

+ 67 - 0
panda/src/text/textGraphic.h

@@ -0,0 +1,67 @@
+// Filename: textGraphic.h
+// Created by:  drose (18Aug06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, 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://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef TEXTGRAPHIC_H
+#define TEXTGRAPHIC_H
+
+#include "pandabase.h"
+
+#include "config_text.h"
+#include "nodePath.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : TextGraphic
+// Description : This defines a special model that has been
+//               constructed for the purposes of embedding an
+//               arbitrary graphic image within a text paragraph.
+//
+//               It can be any arbitrary model, though it should be
+//               built along the same scale as the text, and it should
+//               probably be at least mostly two-dimensional.
+//               Typically, this means it should be constructed in the
+//               X-Z plane, and it should have a maximum vertical (Z)
+//               height of 1.0.
+//
+//               The frame specifies an arbitrary bounding volume in
+//               the form (left, right, bottom, top).  This indicates
+//               the amount of space that will be reserved within the
+//               paragraph.  The actual model is not actually required
+//               to fit within this rectangle, but if it does not, it
+//               may visually overlap with nearby text.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA TextGraphic {
+PUBLISHED:
+  INLINE TextGraphic();
+  INLINE TextGraphic(const NodePath &model, const LVecBase4f &frame);
+  INLINE TextGraphic(const NodePath &model, float left, float right, float bottom, float top);
+
+  INLINE NodePath get_model() const;
+  INLINE void set_model(const NodePath &model);
+
+  INLINE LVecBase4f get_frame() const;
+  INLINE void set_frame(const LVecBase4f &frame);
+  INLINE void set_frame(float left, float right, float bottom, float top);
+
+private:
+  NodePath _model;
+  LVecBase4f _frame;
+};
+
+#include "textGraphic.I"
+
+#endif

+ 45 - 2
panda/src/text/textNode.cxx

@@ -120,6 +120,50 @@ TextNode(const string &name, const TextProperties &copy) :
   _lr3d.set(0.0f, 0.0f, 0.0f);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::Copy Constructor
+//       Access: Published
+//  Description: OK, this is a true copy constructor.
+////////////////////////////////////////////////////////////////////
+TextNode::
+TextNode(const TextNode &copy) : 
+  PandaNode(copy), 
+  TextEncoder(copy),
+  TextProperties(copy),
+  _card_texture(copy._card_texture),
+  _frame_color(copy._frame_color),
+  _card_color(copy._card_color),
+  _flags(copy._flags),
+  _max_rows(copy._max_rows),
+  _frame_width(copy._frame_width),
+  _card_border_size(copy._card_border_size),
+  _card_border_uv_portion(copy._card_border_uv_portion),
+  _frame_ul(copy._frame_ul),
+  _frame_lr(copy._frame_lr),
+  _card_ul(copy._card_ul),
+  _card_lr(copy._card_lr),
+  _transform(copy._transform),
+  _coordinate_system(copy._coordinate_system),
+  _ul3d(copy._ul3d),
+  _lr3d(copy._lr3d),
+  _assembler(this) 
+{
+  invalidate_with_measure();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextNode::make_copy
+//       Access: Protected, Virtual
+//  Description: Returns a newly-allocated Node that is a shallow copy
+//               of this one.  It will be a different Node pointer,
+//               but its internal data may or may not be shared with
+//               that of the original Node.
+////////////////////////////////////////////////////////////////////
+PandaNode *TextNode::
+make_copy() const {
+  return new TextNode(*this);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::Destructor
 //       Access: Published
@@ -267,8 +311,7 @@ generate() {
 
   PT(PandaNode) text_root = _assembler.assemble_text();
 
-  // Parent the text in.  We create an intermediate node so we can
-  // choose to reinstance the text_root as the shadow, below.
+  // Parent the text in.
   PT(PandaNode) text = new PandaNode("text");
   root->add_child(text, get_draw_order() + 2);
   text->add_child(text_root);

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

@@ -57,6 +57,11 @@ class EXPCL_PANDA TextNode : public PandaNode, public TextEncoder, public TextPr
 PUBLISHED:
   TextNode(const string &name);
   TextNode(const string &name, const TextProperties &copy);
+protected:
+  TextNode(const TextNode &copy);
+  virtual PandaNode *make_copy() const;
+
+PUBLISHED:
   ~TextNode();
 
   INLINE float get_line_height() const;

+ 133 - 0
panda/src/text/textPropertiesManager.cxx

@@ -118,6 +118,105 @@ clear_properties(const string &name) {
   _properties.erase(name);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextPropertiesManager::set_graphic
+//       Access: Published
+//  Description: Defines the TextGraphic associated with the
+//               indicated name.  When the name is subsequently
+//               encountered in text embedded between \5 characters in
+//               a TextNode string, the specified graphic will be
+//               embedded in the text at that point.
+//
+//               If there was already a TextGraphic structure
+//               associated with this name, it is quietly replaced
+//               with the new definition.
+////////////////////////////////////////////////////////////////////
+void TextPropertiesManager::
+set_graphic(const string &name, const TextGraphic &graphic) {
+  _graphics[name] = graphic;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextPropertiesManager::set_graphic
+//       Access: Published
+//  Description: This flavor of set_graphic implicitly creates a frame
+//               for the model using the model's actual computed
+//               bounding volume, as derived from
+//               NodePath::calc_tight_bounds().  Create a TextGraphic
+//               object first if you want to have explicit control of
+//               the frame.
+////////////////////////////////////////////////////////////////////
+void TextPropertiesManager::
+set_graphic(const string &name, const NodePath &model) {
+  LPoint3f min_point, max_point;
+  model.calc_tight_bounds(min_point, max_point);
+
+  TextGraphic graphic(model, 
+                      min_point.dot(LVector3f::right()),
+                      max_point.dot(LVector3f::right()),
+                      min_point.dot(LVector3f::up()), 
+                      max_point.dot(LVector3f::up()));
+
+  _graphics[name] = graphic;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextPropertiesManager::get_graphic
+//       Access: Published
+//  Description: Returns the TextGraphic associated with the
+//               indicated name.  If there was not previously a
+//               TextGraphic associated with this name, a warning
+//               is printed and then a default TextGraphic
+//               structure is associated with the name, and returned.
+//
+//               Call has_graphic() instead to check whether a
+//               particular name has been defined.
+////////////////////////////////////////////////////////////////////
+TextGraphic TextPropertiesManager::
+get_graphic(const string &name) {
+  Graphics::const_iterator pi;
+  pi = _graphics.find(name);
+  if (pi != _graphics.end()) {
+    return (*pi).second;
+  }
+
+  text_cat.warning()
+    << "Creating default TextGraphic for name '" << name << "'\n";
+
+  TextGraphic default_graphic;
+  _graphics[name] = default_graphic;
+  return default_graphic;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextPropertiesManager::has_graphic
+//       Access: Published
+//  Description: Returns true if a TextGraphic structure has been
+//               associated with the indicated name, false otherwise.
+//               Normally this means set_graphic() has been called
+//               with this name, but because get_graphic() will
+//               implicitly create a default TextGraphic structure,
+//               it may also mean simply that get_graphic() has
+//               been called with the indicated name.
+////////////////////////////////////////////////////////////////////
+bool TextPropertiesManager::
+has_graphic(const string &name) const {
+  Graphics::const_iterator pi;
+  pi = _graphics.find(name);
+  return (pi != _graphics.end());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextPropertiesManager::clear_graphic
+//       Access: Published
+//  Description: Removes the named TextGraphic structure from the
+//               manager.
+////////////////////////////////////////////////////////////////////
+void TextPropertiesManager::
+clear_graphic(const string &name) {
+  _graphics.erase(name);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextPropertiesManager::write
 //       Access: Published
@@ -146,3 +245,37 @@ get_global_ptr() {
   }
   return _global_ptr;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextPropertiesManager::get_properties_ptr
+//       Access: Public
+//  Description: Returns a pointer to the TextProperties with the
+//               indicated name, or NULL if there is no properties
+//               with that name.
+////////////////////////////////////////////////////////////////////
+const TextProperties *TextPropertiesManager::
+get_properties_ptr(const string &name) {
+  Properties::const_iterator pi;
+  pi = _properties.find(name);
+  if (pi != _properties.end()) {
+    return &(*pi).second;
+  }
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextPropertiesManager::get_graphic_ptr
+//       Access: Public
+//  Description: Returns a pointer to the TextGraphic with the
+//               indicated name, or NULL if there is no graphic
+//               with that name.
+////////////////////////////////////////////////////////////////////
+const TextGraphic *TextPropertiesManager::
+get_graphic_ptr(const string &name) {
+  Graphics::const_iterator pi;
+  pi = _graphics.find(name);
+  if (pi != _graphics.end()) {
+    return &(*pi).second;
+  }
+  return NULL;
+}

+ 22 - 0
panda/src/text/textPropertiesManager.h

@@ -23,6 +23,7 @@
 
 #include "config_text.h"
 #include "textProperties.h"
+#include "textGraphic.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : TextPropertiesManager
@@ -44,6 +45,14 @@
 //               the character "n" will be rendered in the "up" state,
 //               and then " + y" will be rendered in the normal state
 //               again.
+//
+//               This can also be used to define arbitrary models that
+//               can serve as embedded graphic images in a text
+//               paragraph.  This works similarly; the convention is
+//               to create a TextGraphic that describes the graphic
+//               image, and then associate it here via the
+//               set_graphic() call.  Then "\5name\5" will embed the
+//               named graphic.
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA TextPropertiesManager {
 protected:
@@ -56,14 +65,27 @@ PUBLISHED:
   bool has_properties(const string &name) const;
   void clear_properties(const string &name);
 
+  void set_graphic(const string &name, const TextGraphic &graphic);
+  void set_graphic(const string &name, const NodePath &model);
+  TextGraphic get_graphic(const string &name);
+  bool has_graphic(const string &name) const;
+  void clear_graphic(const string &name);
+
   void write(ostream &out, int indent_level = 0) const;
 
   static TextPropertiesManager *get_global_ptr();
 
+public:
+  const TextProperties *get_properties_ptr(const string &name);
+  const TextGraphic *get_graphic_ptr(const string &name);
+
 private:
   typedef pmap<string, TextProperties> Properties;
   Properties _properties;
 
+  typedef pmap<string, TextGraphic> Graphics;
+  Graphics _graphics;
+
   static TextPropertiesManager *_global_ptr;
 };
 

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

@@ -1,6 +1,7 @@
 #include "textAssembler.cxx"
 #include "textFont.cxx"
 #include "textGlyph.cxx"
+#include "textGraphic.cxx"
 #include "textNode.cxx"
 #include "textProperties.cxx"
 #include "textPropertiesManager.cxx"