Browse Source

support multi-state text blocks; also show IME candidate strings in DirectEntry fields

David Rose 22 years ago
parent
commit
5d121b8ed9

+ 4 - 5
direct/src/gui/DirectEntry.py

@@ -73,11 +73,10 @@ class DirectEntry(DirectFrame):
             scale = 1,
             # Don't get rid of the text node
             mayChange = 1)
-        # We want to keep this sucker frozen since its not
-        # in the scene graph
-        self.onscreenText.freeze()
-        # We can also get rid of the node path since we're just using the
-        # onscreenText as an easy way to access a text node as a component
+
+        # We can get rid of the node path since we're just using the
+        # onscreenText as an easy way to access a text node as a
+        # component
         self.onscreenText.removeNode()
 
         # Bind command function

+ 3 - 3
direct/src/gui/DirectGuiTest.py

@@ -90,12 +90,12 @@ def printEntryText(text):
 # CALL de1.get() and de1.set('new text') to get and set entry contents
 de1 = DirectEntry(initialText = 'Hello, how are you?',
                   image = 'models/maps/noise.rgb',
-                  #image = 'phase_3/maps/toowntown-logo.jpg',
-                  image_pos = (4.55, 0, -1.65),
-                  image_scale = (5.5, 1, 2.55),
+                  image_pos = (4.55, 0, -2.55),
+                  image_scale = (5.5, 1, 4),
                   command = printEntryText,
                   pos = (-1.1875, 0, 0.879167),
                   scale = 0.0707855,
+                  cursorKeys = 1,
                   )
 
 # DIRECT DIALOG EXAMPLE

+ 5 - 22
direct/src/gui/OnscreenText.py

@@ -157,8 +157,6 @@ class OnscreenText(PandaObject, NodePath):
         if font == None:
             font = DirectGuiGlobals.getDefaultFont()
         
-        # Freeze the node while we set all the properties
-        textNode.freeze()
         textNode.setFont(font)
         textNode.setTextColor(fg[0], fg[1], fg[2], fg[3])
         textNode.setAlign(align)
@@ -202,12 +200,9 @@ class OnscreenText(PandaObject, NodePath):
             self.mayChange = mayChange
 
         # Ok, now update the node.
-        if self.mayChange:
-            # If we might change the text later, we have to keep the
-            # TextNode around.
-            textNode.thaw()
-        else:
-            # Otherwise, we can throw it away.
+        if not self.mayChange:
+            # If we aren't going to change the text later, we can
+            # throw away the TextNode.
             self.textNode = textNode.generate()
         
         self.isClean = 0
@@ -225,10 +220,10 @@ class OnscreenText(PandaObject, NodePath):
         self.cleanup()
 
     def freeze(self):
-        self.textNode.freeze()
+        pass
 
     def thaw(self):
-        self.textNode.thaw()
+        pass
 
     # Allow changing of several of the parameters after the text has
     # been created.  These should be used with caution; it is better
@@ -237,9 +232,6 @@ class OnscreenText(PandaObject, NodePath):
     # text, and for those rare occasions when you actually want to
     # change a text's property after it has been created.
 
-    # If you need to change several properties at the same time at
-    # runtime, you should call freeze() first and thaw() afterward.
-
 
     def setFont(self, font):
         self.textNode.setFont(font)
@@ -321,7 +313,6 @@ class OnscreenText(PandaObject, NodePath):
         self.textNode.setTextColor(fg[0], fg[1], fg[2], fg[3])
 
     def setBg(self, bg):
-        self.textNode.freeze()
         if bg[3] != 0:
             # If we have a background color, create a card.
             self.textNode.setCardColor(bg[0], bg[1], bg[2], bg[3])
@@ -329,10 +320,8 @@ class OnscreenText(PandaObject, NodePath):
         else:
             # Otherwise, remove the card.
             self.textNode.clearCard()
-        self.textNode.thaw()
 
     def setShadow(self, shadow):
-        self.textNode.freeze()
         if shadow[3] != 0:
             # If we have a shadow color, create a shadow.
             self.textNode.setShadowColor(shadow[0], shadow[1], shadow[2], shadow[3])
@@ -340,10 +329,8 @@ class OnscreenText(PandaObject, NodePath):
         else:
             # Otherwise, remove the shadow.
             self.textNode.clearShadow()
-        self.textNode.thaw()
 
     def setFrame(self, frame):
-        self.textNode.freeze()
         if frame[3] != 0:
             # If we have a frame color, create a frame.
             self.textNode.setFrameColor(frame[0], frame[1], frame[2], frame[3])
@@ -351,15 +338,12 @@ class OnscreenText(PandaObject, NodePath):
         else:
             # Otherwise, remove the frame.
             self.textNode.clearFrame()
-        self.textNode.thaw()
 
     def configure(self, option=None, **kw):
         # These is for compatability with DirectGui functions
         if not self.mayChange:
             print 'OnscreenText.configure: mayChange == 0'
             return
-        # Freeze text node prior to making changes
-        self.freeze()
         for option, value in kw.items():
             # Use option string to access setter function
             try:
@@ -371,7 +355,6 @@ class OnscreenText(PandaObject, NodePath):
                     setter(value)
             except AttributeError:
                 print 'OnscreenText.configure: invalid option:', option
-        self.thaw()
 
     # Allow index style references
     def __setitem__(self, key, value):

+ 0 - 4
direct/src/leveleditor/LevelEditor.py

@@ -3988,13 +3988,11 @@ class LevelStyleManager:
         for i in range (numItems):
             # Create a text node--just a card, really--of the right color.
             tn = TextNode('colorChip')
-            tn.freeze()
             tn.setFont(getDefaultFont())
             tn.setTransform(Mat4.scaleMat(0.07, 0.07, 0.07 * aspectRatio))
             tn.setCardColor(colorList[i])
             tn.setCardActual(0, 1.1111, 0, 0.8333)
             tn.setText(' ')
-            tn.thaw()
 
             # Reposition it
             card = tn.getCardTransformed()
@@ -4113,14 +4111,12 @@ class LevelStyleManager:
             # Create text node for each item
             if (textList[i] != None):
                 tn = TextNode('TextItem')
-                tn.freeze()
                 tn.setFont(getDefaultFont())
                 tn.setTransform(Mat4.scaleMat(0.07, 0.07, 0.07 * aspectRatio))
                 tn.setTextColor(0, 0, 0, 1)
                 tn.setCardColor(1, 1, 1, 1)
                 tn.setCardAsMargin(0.1, 0.1, 0.1, 0.1)
                 tn.setText(str(textList[i]))
-                tn.thaw()
 
                 # Reposition it
                 card = tn.getCardTransformed()

+ 0 - 2
direct/src/leveleditor/PieMenu.py

@@ -138,14 +138,12 @@ class TextPieMenu(PieMenu):
             # Create text node for each item
             if (textList[i] != None):
                 tn = TextNode('TextItem')
-                tn.freeze()
                 tn.setFont(getDefaultFont())
                 tn.setTransform(Mat4.scaleMat(0.07, 0.07, 0.07 * aspectRatio))
                 tn.setTextColor(0, 0, 0, 1)
                 tn.setCardColor(1, 1, 1, 1)
                 tn.setCardAsMargin(0.1, 0.1, 0.1, 0.1)
                 tn.setText(str(textList[i]))
-                tn.thaw()
 
                 # Reposition it
                 card = tn.getCardTransformed()

+ 0 - 14
panda/src/glxdisplay/glxGraphicsWindow.cxx

@@ -310,20 +310,6 @@ process_events() {
     case KeyPress:
       handle_keystroke(event.xkey);
       handle_keypress(event.xkey);
-
-      /*
-      // Temp hack for testing.  We pretend that we have received a
-      // candidate string every type the user types the backslash key.
-      {
-        KeySym key = XLookupKeysym(&event.xkey, 0);
-
-        if (key == XK_backslash) {
-          cerr << "Pressed backslash, sending candidate\n";
-          wstring candidate_string = TextEncoder::decode_text("This is a candidate string", TextEncoder::get_default_encoding());
-          _input_devices[0].candidate(candidate_string, 5, 7);
-        }
-      }
-      */
       break;
 
     case KeyRelease:

+ 64 - 0
panda/src/pgui/pgEntry.I

@@ -265,6 +265,70 @@ get_obscure_mode() const {
   return _obscure_mode;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::set_candidate_active
+//       Access: Published
+//  Description: Specifies the name of the TextProperties structure
+//               added to the TextPropertiesManager that will be used
+//               to render candidate strings from the IME, used for
+//               typing characters in east Asian languages.  Each
+//               candidate string represents one possible way to
+//               interpret the sequence of keys the user has just
+//               entered; it should not be considered typed yet, but
+//               it is important for the user to be able to see what
+//               he is considering entering.
+//
+//               This particular method sets the properties for the
+//               subset of the current candidate string that the user
+//               can actively scroll through.
+////////////////////////////////////////////////////////////////////
+INLINE void PGEntry:: 
+set_candidate_active(const string &candidate_active) {
+  _candidate_active = candidate_active;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::get_candidate_active
+//       Access: Published
+//  Description: See set_candidate_active().
+////////////////////////////////////////////////////////////////////
+INLINE const string &PGEntry:: 
+get_candidate_active() const {
+  return _candidate_active;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::set_candidate_inactive
+//       Access: Published
+//  Description: Specifies the name of the TextProperties structure
+//               added to the TextPropertiesManager that will be used
+//               to render candidate strings from the IME, used for
+//               typing characters in east Asian languages.  Each
+//               candidate string represents one possible way to
+//               interpret the sequence of keys the user has just
+//               entered; it should not be considered typed yet, but
+//               it is important for the user to be able to see what
+//               he is considering entering.
+//
+//               This particular method sets the properties for the
+//               subset of the current candidate string that the user
+//               is not actively scrolling through.
+////////////////////////////////////////////////////////////////////
+INLINE void PGEntry:: 
+set_candidate_inactive(const string &candidate_inactive) {
+  _candidate_inactive = candidate_inactive;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::get_candidate_inactive
+//       Access: Published
+//  Description: See set_candidate_inactive().
+////////////////////////////////////////////////////////////////////
+INLINE const string &PGEntry:: 
+get_candidate_inactive() const {
+  return _candidate_inactive;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGEntry::get_accept_prefix
 //       Access: Published, Static

+ 155 - 149
panda/src/pgui/pgEntry.cxx

@@ -28,6 +28,7 @@
 #include "mouseButton.h"
 #include "lineSegs.h"
 #include "textEncoder.h"
+#include "config_text.h"
 
 #include <math.h>
 
@@ -44,6 +45,8 @@ PGEntry(const string &name) :
 {
   _cursor_position = 0;
   _cursor_stale = true;
+  _candidate_highlight_start = 0;
+  _candidate_highlight_end = 0;
   _max_chars = 0;
   _max_width = 0.0f;
   _num_lines = 1;
@@ -56,6 +59,12 @@ PGEntry(const string &name) :
   _cursor_def = _text_render_root.attach_new_node("cursor");
   _cursor_visible = true;
 
+  // These strings are used to specify the TextProperties to apply to
+  // candidate strings generated from the IME (for entering text in an
+  // east Asian language).
+  _candidate_active = "candidate_active";
+  _candidate_inactive = "candidate_inactive";
+
   _cursor_keys_active = true;
   _obscure_mode = false;
 
@@ -178,6 +187,11 @@ press(const MouseWatcherParameter &param, bool background) {
       } else if ((!background && get_focus()) || 
                  (background && get_background_focus())) {
         // Keyboard button.
+        if (!_candidate_wtext.empty()) {
+          _candidate_wtext.clear();
+          _text_geom_stale = true;
+        }
+
         _cursor_position = min(_cursor_position, (int)_wtext.length());
         _blink_start = ClockObject::get_global_clock()->get_frame_time();
         if (button == KeyboardButton::enter()) {
@@ -266,53 +280,42 @@ press(const MouseWatcherParameter &param, bool background) {
               bool too_long = false;
               if (_max_width > 0.0f) {
                 TextNode *text_node = get_text_def(S_focus);
-                if (_num_lines <= 1) {
-                  // If we have only one line, we can check the length
-                  // by simply measuring the width of the text.
-                  too_long = (text_node->calc_width(measure_text) > _max_width);
+                text_node->set_wtext(measure_text);
+                text_node->set_wordwrap(_max_width);
+                text_node->set_preserve_trailing_whitespace(true);
+                text_node->set_max_rows(_num_lines);
 
-                } else {
-                  // If we have multiple lines, we have to check the
-                  // length by wordwrapping it and counting up the
-                  // number of lines.
-                  wstring ww_text = text_node->wordwrap_to(measure_text, _max_width, true);
-                  int num_lines = 1;
-                  size_t last_line_start = 0;
-                  for (size_t p = 0;
-                       p < ww_text.length() && !too_long;
-                       ++p) {
-                    if (ww_text[p] == '\n') {
-                      last_line_start = p + 1;
-                      num_lines++;
-                      too_long = (num_lines > _num_lines);
-                    }
-                  }
+                too_long = text_node->has_overflow();
 
-                  if (!too_long) {
-                    // We must also ensure that the last line is not too
-                    // long (it might be, because of additional
-                    // whitespace on the end).
-                    wstring last_line = ww_text.substr(last_line_start);
-                    float last_line_width = text_node->calc_width(last_line);
-                    if (num_lines == _num_lines) {
-                      // Mainly we only care about this if we're on
-                      // the very last line.
-                      too_long = (last_line_width > _max_width);
-
-                    } else {
-                      // If we're not on the very last line, the width
-                      // is still important, just so we don't allow an
-                      // infinite number of spaces to accumulate.
-                      // However, we must allow at least *one* space
-                      // on the end of a line.
-                      if (_wtext.length() >= 1 && 
-                          _wtext[_wtext.length() - 1] == ' ') {
-                        if (last_line_width > _max_width) {
-                          // In this case, however, it's not exactly
-                          // an overflow; we just want to reject the
-                          // space.
-                          return;
-                        }
+                if (!too_long) {
+                  // We must also ensure that the last line is not too
+                  // long (it might be, because of additional
+                  // whitespace on the end).
+                  wstring ww_text = text_node->get_wordwrapped_wtext();
+                  size_t last_line_start = ww_text.rfind('\n');
+                  if (last_line_start == string::npos) {
+                    last_line_start = 0;
+                  }
+                  wstring last_line = ww_text.substr(last_line_start);
+                  float last_line_width = text_node->calc_width(last_line);
+                  if (text_node->get_num_rows() == _num_lines) {
+                    // Mainly we only care about this if we're on
+                    // the very last line.
+                    too_long = (last_line_width > _max_width);
+                    
+                  } else {
+                    // If we're not on the very last line, the width
+                    // is still important, just so we don't allow an
+                    // infinite number of spaces to accumulate.
+                    // However, we must allow at least *one* space
+                    // on the end of a line.
+                    if (_wtext.length() >= 1 && 
+                        _wtext[_wtext.length() - 1] == ' ') {
+                      if (last_line_width > _max_width) {
+                        // In this case, however, it's not exactly
+                        // an overflow; we just want to reject the
+                        // space.
+                        return;
                       }
                     }
                   }
@@ -355,9 +358,14 @@ keystroke(const MouseWatcherParameter &param, bool background) {
       int keycode = param.get_keycode();
           
       if (use_keystrokes) {
+
         if (!isascii(keycode) || isprint(keycode)) {
           // A normal visible character.  Add a new character to the
           // text entry, if there's room.
+          if (!_candidate_wtext.empty()) {
+            _candidate_wtext.clear();
+            _text_geom_stale = true;
+          }
           wstring new_char(1, (wchar_t)keycode);
 
           if (get_max_chars() > 0 && (int)_wtext.length() >= get_max_chars()) {
@@ -382,53 +390,42 @@ keystroke(const MouseWatcherParameter &param, bool background) {
             bool too_long = false;
             if (_max_width > 0.0f) {
               TextNode *text_node = get_text_def(S_focus);
-              if (_num_lines <= 1) {
-                // If we have only one line, we can check the length
-                // by simply measuring the width of the text.
-                too_long = (text_node->calc_width(measure_text) > _max_width);
-                
-              } else {
-                // If we have multiple lines, we have to check the
-                // length by wordwrapping it and counting up the
-                // number of lines.
-                wstring ww_text = text_node->wordwrap_to(measure_text, _max_width, true);
-                int num_lines = 1;
-                size_t last_line_start = 0;
-                for (size_t p = 0;
-                     p < ww_text.length() && !too_long;
-                     ++p) {
-                  if (ww_text[p] == '\n') {
-                    last_line_start = p + 1;
-                    num_lines++;
-                    too_long = (num_lines > _num_lines);
-                  }
+              text_node->set_wtext(measure_text);
+              text_node->set_wordwrap(_max_width);
+              text_node->set_preserve_trailing_whitespace(true);
+              text_node->set_max_rows(_num_lines);
+              
+              too_long = text_node->has_overflow();
+              
+              if (!too_long) {
+                // We must also ensure that the last line is not too
+                // long (it might be, because of additional
+                // whitespace on the end).
+                wstring ww_text = text_node->get_wordwrapped_wtext();
+                size_t last_line_start = ww_text.rfind('\n');
+                if (last_line_start == string::npos) {
+                  last_line_start = 0;
                 }
-                
-                if (!too_long) {
-                  // We must also ensure that the last line is not too
-                  // long (it might be, because of additional
-                  // whitespace on the end).
-                  wstring last_line = ww_text.substr(last_line_start);
-                  float last_line_width = text_node->calc_width(last_line);
-                  if (num_lines == _num_lines) {
-                    // Mainly we only care about this if we're on
-                    // the very last line.
-                    too_long = (last_line_width > _max_width);
-                    
-                  } else {
-                    // If we're not on the very last line, the width
-                    // is still important, just so we don't allow an
-                    // infinite number of spaces to accumulate.
-                    // However, we must allow at least *one* space
-                    // on the end of a line.
-                    if (_wtext.length() >= 1 && 
-                        _wtext[_wtext.length() - 1] == ' ') {
-                      if (last_line_width > _max_width) {
-                        // In this case, however, it's not exactly
-                        // an overflow; we just want to reject the
-                        // space.
-                        return;
-                      }
+                wstring last_line = ww_text.substr(last_line_start);
+                float last_line_width = text_node->calc_width(last_line);
+                if (text_node->get_num_rows() == _num_lines) {
+                  // Mainly we only care about this if we're on
+                  // the very last line.
+                  too_long = (last_line_width > _max_width);
+                  
+                } else {
+                  // If we're not on the very last line, the width
+                  // is still important, just so we don't allow an
+                  // infinite number of spaces to accumulate.
+                  // However, we must allow at least *one* space
+                  // on the end of a line.
+                  if (_wtext.length() >= 1 && 
+                      _wtext[_wtext.length() - 1] == ' ') {
+                    if (last_line_width > _max_width) {
+                      // In this case, however, it's not exactly
+                      // an overflow; we just want to reject the
+                      // space.
+                      return;
                     }
                   }
                 }
@@ -467,18 +464,11 @@ void PGEntry::
 candidate(const MouseWatcherParameter &param, bool background) {
   if (get_active()) {
     if (param.has_candidate()) {
-      // Do something with the candidate string.
-      TextEncoder te;
-      const wstring &cs = param.get_candidate_string();
-      size_t hs = param.get_highlight_start();
-      size_t he = param.get_highlight_end();
-
-      pgui_cat.info()
-        << "Candidate: "
-        << te.encode_wtext(cs.substr(0, hs))
-        << " (" << te.encode_wtext(cs.substr(hs, he - hs)) << ") "
-        << te.encode_wtext(cs.substr(he))
-        << "\n";
+      // Save the candidate string so it can be displayed.
+      _candidate_wtext = param.get_candidate_string();
+      _candidate_highlight_start = param.get_highlight_start();
+      _candidate_highlight_end = param.get_highlight_end();
+      _text_geom_stale = true;
     }
   }
   PGItem::candidate(param, background);
@@ -563,7 +553,7 @@ setup(float width, int num_lines) {
   TextNode *text_node = get_text_def(S_focus);
   float line_height = text_node->get_line_height();
 
-  // Define determine the four corners of the frame.
+  // Determine the four corners of the frame.
   LPoint3f ll(0.0f, 0.0f, -0.3f * line_height - (line_height * (num_lines - 1)));
   LPoint3f ur(width, 0.0f, line_height);
   LPoint3f lr(ur[0], 0.0f, ll[2]);
@@ -640,11 +630,6 @@ setup(float width, int num_lines) {
 //               indicated state.  The default if nothing is specified
 //               is the same TextNode returned by
 //               PGItem::get_text_node().
-//
-//               It is the responsibility of the user to ensure that
-//               this TextNode has been frozen by a call to freeze().
-//               Passing in an unfrozen TextNode will result in
-//               needless work.
 ////////////////////////////////////////////////////////////////////
 void PGEntry::
 set_text_def(int state, TextNode *node) {
@@ -726,7 +711,7 @@ get_display_wtext() {
     return _obscured_wtext;
 
   } else {
-    // In normal, non-obscure mode, we display the actual text.
+    // In normal, non-obscure mode, we display the actual text
     return _wtext;
   }
 }
@@ -756,38 +741,54 @@ update_text() {
   nassertv(node != (TextNode *)NULL);
 
   if (_text_geom_stale || node != _last_text_def) {
-    const wstring &display_wtext = get_display_wtext();
+    wstring display_wtext;
 
-    // We need to regenerate.
-    _last_text_def = node;
+    if (_candidate_wtext.empty() || _obscure_mode) {
+      // If we're not trying to display a candidate string, it's easy:
+      // just display the current text contents.
+      display_wtext = get_display_wtext();
 
-    if (_max_width > 0.0f && _num_lines > 1) {
-      // Fold the text into multiple lines.
-      wstring ww_text = 
-        _last_text_def->wordwrap_to(display_wtext, _max_width, true);
-
-      // And chop the lines up into pieces.
-      _ww_lines.clear();
-      size_t p = 0;
-      size_t q = ww_text.find((wchar_t)'\n');
-      while (q != string::npos) {
-        _ww_lines.push_back(WWLine());
-        WWLine &line = _ww_lines.back();
-        line._str = ww_text.substr(p, q - p);
-
-        // Get the left edge of the text at this line.
-        line._left = 0.0f;
-        if (_last_text_def->get_align() != TextNode::A_left) {
-          _last_text_def->set_wtext(line._str);
-          line._left = _last_text_def->get_left();
-        }
+    } else {
+      // Insert the complex sequence of characters required to show
+      // the candidate string in a different color.  This gets
+      // inserted at the current cursor position.
+      wstring source_wtext = get_display_wtext();
+
+      display_wtext = source_wtext.substr(0, _cursor_position);
+      display_wtext += wstring(1, text_push_properties_key);
+      display_wtext += node->decode_text(_candidate_inactive);
+      display_wtext += wstring(1, text_push_properties_key);
+      display_wtext += _candidate_wtext.substr(0, _candidate_highlight_start);
+      display_wtext += wstring(1, text_push_properties_key);
+      display_wtext += node->decode_text(_candidate_active);
+      display_wtext += wstring(1, text_push_properties_key);
+      display_wtext += _candidate_wtext.substr(_candidate_highlight_start,
+                                               _candidate_highlight_end - _candidate_highlight_start);
+      display_wtext += wstring(1, text_pop_properties_key);
+      display_wtext += _candidate_wtext.substr(_candidate_highlight_end);
+      display_wtext += wstring(1, text_pop_properties_key);
+
+      display_wtext += source_wtext.substr(_cursor_position);
+    }
 
-        p = q + 1;
-        q = ww_text.find('\n', p);
-      }
+    // We need to regenerate.
+    _last_text_def = node;
+    _last_text_def->set_wtext(display_wtext);
+    _last_text_def->set_wordwrap(_max_width);
+    _last_text_def->set_preserve_trailing_whitespace(true);
+    _last_text_def->set_max_rows(_num_lines);
+
+    // Check for multiple lines.
+    wstring ww_text = _last_text_def->get_wordwrapped_wtext();
+
+    // And chop the lines up into pieces.
+    _ww_lines.clear();
+    size_t p = 0;
+    size_t q = ww_text.find((wchar_t)'\n');
+    while (q != string::npos) {
       _ww_lines.push_back(WWLine());
       WWLine &line = _ww_lines.back();
-      line._str = ww_text.substr(p);
+      line._str = ww_text.substr(p, q - p);
       
       // Get the left edge of the text at this line.
       line._left = 0.0f;
@@ -795,17 +796,18 @@ update_text() {
         _last_text_def->set_wtext(line._str);
         line._left = _last_text_def->get_left();
       }
-
-      _last_text_def->set_wtext(ww_text);
-
-    } else {
-      // Only one line.
-      _ww_lines.clear();
-      _ww_lines.push_back(WWLine());
-      WWLine &line = _ww_lines.back();
-      line._str = display_wtext;
-
-      _last_text_def->set_wtext(display_wtext);
+      
+      p = q + 1;
+      q = ww_text.find('\n', p);
+    }
+    _ww_lines.push_back(WWLine());
+    WWLine &line = _ww_lines.back();
+    line._str = ww_text.substr(p);
+    
+    // Get the left edge of the text at this line.
+    line._left = 0.0f;
+    if (_last_text_def->get_align() != TextNode::A_left) {
+      _last_text_def->set_wtext(line._str);
       line._left = _last_text_def->get_left();
     }
 
@@ -844,7 +846,11 @@ update_cursor() {
     }
 
     nassertv(row >= 0 && row < (int)_ww_lines.size());
-    nassertv(column >= 0 && column <= (int)_ww_lines[row]._str.length());
+
+    // It is possible for this to become untrue legitimately, if due
+    // to a candidate string we have wordwrapped down the last part of
+    // the line containing the cursor.
+    //nassertv(column >= 0 && column <= (int)_ww_lines[row]._str.length());
 
     float width = 
       _last_text_def->calc_width(_ww_lines[row]._str.substr(0, column));

+ 13 - 0
panda/src/pgui/pgEntry.h

@@ -98,6 +98,12 @@ PUBLISHED:
   INLINE void set_obscure_mode(bool flag);
   INLINE bool get_obscure_mode() const;
 
+  INLINE void set_candidate_active(const string &candidate_active);
+  INLINE const string &get_candidate_active() const;
+
+  INLINE void set_candidate_inactive(const string &candidate_inactive);
+  INLINE const string &get_candidate_inactive() const;
+
   void set_text_def(int state, TextNode *node);
   TextNode *get_text_def(int state) const;
 
@@ -133,10 +139,17 @@ private:
   bool _cursor_stale;
   bool _cursor_visible;
 
+  wstring _candidate_wtext;
+  size_t _candidate_highlight_start;
+  size_t _candidate_highlight_end;
+
   int _max_chars;
   float _max_width;
   int _num_lines;
 
+  string _candidate_active;
+  string _candidate_inactive;
+
   typedef pvector< PT(TextNode) > TextDefs;
   TextDefs _text_defs;
 

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

@@ -9,7 +9,7 @@
     putil gobj pgraph linmath \
     pnmtext pnmimage gsgbase mathutil
     
-  #define COMBINED_SOURCES $[TARGET]_composite1.cxx 
+  #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx 
 
   #define SOURCES \
     config_text.h \
@@ -20,9 +20,12 @@
     fontPool.I fontPool.h \
     geomTextGlyph.I geomTextGlyph.h \
     staticTextFont.I staticTextFont.h \
+    textAssembler.I textAssembler.h \
     textFont.I textFont.h \
     textGlyph.I textGlyph.h \
-    textNode.I textNode.h textNode.cxx
+    textNode.I textNode.h \
+    textProperties.I textProperties.h \
+    textPropertiesManager.I textPropertiesManager.h
 
   #define INCLUDED_SOURCES \
     config_text.cxx \
@@ -33,7 +36,11 @@
     fontPool.cxx \
     geomTextGlyph.cxx \
     staticTextFont.cxx \
-    textFont.cxx textGlyph.cxx
+    textAssembler.cxx \
+    textFont.cxx textGlyph.cxx \
+    textNode.cxx \
+    textProperties.cxx \
+    textPropertiesManager.cxx
 
   #define INSTALL_HEADERS \
     config_text.h \
@@ -43,9 +50,12 @@
     fontPool.I fontPool.h \
     geomTextGlyph.I geomTextGlyph.h \
     staticTextFont.I staticTextFont.h \
+    textAssembler.I textAssembler.h \
     textFont.I textFont.h \
     textGlyph.I textGlyph.h \
-    textNode.I textNode.h
+    textNode.I textNode.h \
+    textProperties.I textProperties.h \
+    textPropertiesManager.I textPropertiesManager.h
 
 
   #define IGATESCAN all

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

@@ -20,6 +20,7 @@
 #include "staticTextFont.h"
 #include "textFont.h"
 #include "textNode.h"
+#include "textProperties.h"
 #include "dynamicTextFont.h"
 #include "dynamicTextPage.h"
 #include "geomTextGlyph.h"
@@ -48,6 +49,16 @@ const string text_default_font = config_text.GetString("text-default-font", "");
 const float text_tab_width = config_text.GetFloat("text-tab-width", 5.0f);
 
 
+// This is the decimal character number that, embedded in a string, is
+// used to bracket the name of a TextProperties structure added to the
+// TextPropertiesManager object, to control the appearance of
+// subsequent text.
+const int text_push_properties_key = config_text.GetInt("text-push-properties-key", 1);
+
+// This is the decimal character number that undoes the effect of a
+// previous appearance of text_push_properties_key.
+const int text_pop_properties_key = config_text.GetInt("text-pop-properties-key", 2);
+
 // This is the decimal character number that, embedded in a string, is
 // identified as the soft-hyphen character.
 const int text_soft_hyphen_key = config_text.GetInt("text-soft-hyphen-key", 3);
@@ -98,6 +109,7 @@ init_libtext() {
   StaticTextFont::init_type();
   TextFont::init_type();
   TextNode::init_type();
+  TextProperties::init_type();
 
 #ifdef HAVE_FREETYPE
   DynamicTextFont::init_type();

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

@@ -38,6 +38,8 @@ extern const bool text_small_caps;
 extern const float text_small_caps_scale;
 extern const string text_default_font;
 extern const float text_tab_width;
+extern const int text_push_properties_key;
+extern const int text_pop_properties_key;
 extern const int text_soft_hyphen_key;
 extern const int text_soft_break_key;
 extern wstring *text_soft_hyphen_output;

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

@@ -17,6 +17,8 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "fontPool.h"
+#include "staticTextFont.h"
+#include "dynamicTextFont.h"
 #include "config_util.h"
 #include "config_express.h"
 #include "virtualFileSystem.h"

+ 91 - 0
panda/src/text/textAssembler.I

@@ -0,0 +1,91 @@
+// Filename: textAssembler.I
+// Created by:  drose (06Apr04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: TextAssembler::get_ul
+//       Access: Public
+//  Description: Returns the upper-left corner of the assembled text,
+//               in 2-d text coordinates.
+////////////////////////////////////////////////////////////////////
+INLINE const LVector2f &TextAssembler::
+get_ul() const {
+  return _ul;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::get_lr
+//       Access: Public
+//  Description: Returns the lower-right corner of the assembled text,
+//               in 2-d text coordinates.
+////////////////////////////////////////////////////////////////////
+INLINE const LVector2f &TextAssembler::
+get_lr() const {
+  return _lr;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::get_num_rows
+//       Access: Public
+//  Description: Returns the number of rows of text after it has all
+//               been wordwrapped and assembled.
+////////////////////////////////////////////////////////////////////
+INLINE int TextAssembler::
+get_num_rows() const {
+  return _num_rows;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::calc_width
+//       Access: Private, Static
+//  Description: Returns the width of a single character, according to
+//               its associated font.
+////////////////////////////////////////////////////////////////////
+INLINE float TextAssembler::
+calc_width(const TextCharacter &tch) {
+  return calc_width(tch._character, *tch._properties);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::TextCharacter::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE TextAssembler::TextCharacter::
+TextCharacter(wchar_t character, const TextProperties *properties) :
+  _character(character),
+  _properties(properties)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::GlyphPlacement::add_piece
+//       Access: Public
+//  Description: Adds a piece of the glyph, consisting of a single
+//               Geom and an associated RenderState.  Typically, a
+//               glyph will have exactly one piece; there will only be
+//               multiple pieces in the case of cheesy accent marks or
+//               ligatures.
+////////////////////////////////////////////////////////////////////
+INLINE void TextAssembler::GlyphPlacement::
+add_piece(Geom *geom, const RenderState *state) {
+  Piece piece;
+  piece._geom = geom;
+  piece._state = state;
+  _pieces.push_back(piece);
+}

+ 1450 - 0
panda/src/text/textAssembler.cxx

@@ -0,0 +1,1450 @@
+// Filename: textAssembler.cxx
+// Created by:  drose (06Apr04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "textAssembler.h"
+#include "textGlyph.h"
+#include "cullFaceAttrib.h"
+#include "colorAttrib.h"
+#include "cullBinAttrib.h"
+#include "textureAttrib.h"
+#include "transparencyAttrib.h"
+#include "textPropertiesManager.h"
+#include "textEncoder.h"
+#include "config_text.h"
+
+#include <ctype.h>
+  
+#ifndef CPPPARSER  // interrogate has a bit of trouble with wstring.
+
+
+
+// This is the factor by which CT_small scales the character down.
+static const float small_accent_scale = 0.6f;
+
+// This is the factor by which CT_tiny scales the character down.
+static const float tiny_accent_scale = 0.4f;
+
+// This is the factor by which CT_squash scales the character in X and Y.
+static const float squash_accent_scale_x = 0.8f;
+static const float squash_accent_scale_y = 0.5f;
+
+// This is the factor by which CT_small_squash scales the character in X and Y.
+static const float small_squash_accent_scale_x = 0.6f;
+static const float small_squash_accent_scale_y = 0.3f;
+
+// This is the factor by which the advance is reduced for the first
+// character of a two-character ligature.
+static const float ligature_advance_scale = 0.6f;
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: isspacew
+//  Description: An internal function that works like isspace() but is
+//               safe to call for a wide character.
+////////////////////////////////////////////////////////////////////
+static INLINE bool
+isspacew(unsigned int ch) {
+  return isascii(ch) && isspace(ch);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: isbreakpoint
+//  Description: An internal function, similar to isspace(), except it
+//               does not consider newlines to be whitespace.  It also
+//               includes the soft-hyphen character.
+////////////////////////////////////////////////////////////////////
+static INLINE bool
+isbreakpoint(unsigned int ch) {
+  return (ch == ' ' || ch == '\t' || 
+          ch == (unsigned int)text_soft_hyphen_key ||
+          ch == (unsigned int)text_soft_break_key);
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::Constructor
+//       Access: Public
+//  Description: Places all of the indicated text according to the
+//               associated TextProperties.
+////////////////////////////////////////////////////////////////////
+TextAssembler::
+TextAssembler(TextEncoder *encoder) : _encoder(encoder) {
+  clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+TextAssembler::
+~TextAssembler() {
+  clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::clear
+//       Access: Public
+//  Description: Reinitializes the contents of the TextAssembler.
+////////////////////////////////////////////////////////////////////
+void TextAssembler::
+clear() {
+  _num_rows = 0;
+  _ul.set(0.0f, 0.0f);
+  _lr.set(0.0f, 0.0f);
+
+  _wordwrapped_string.clear();
+
+  PropertiesList::iterator li;
+  for (li = _properties_list.begin(); li != _properties_list.end(); ++li) {
+    delete (*li);
+  }
+  _properties_list.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::set_wtext
+//       Access: Public
+//  Description: Accepts a new text string and associated properties
+//               structure, and precomputes the wordwrapping layout
+//               appropriately.  After this call,
+//               get_wordwrapped_wtext() and get_num_rows() can be
+//               called.
+//
+//               If max_rows is greater than zero, no more than
+//               max_rows will be accepted.  Text beyond that will be
+//               truncated.
+//
+//               The return value is true if all the text is accepted,
+//               or false if some was truncated.
+////////////////////////////////////////////////////////////////////
+bool TextAssembler::
+set_wtext(const wstring &wtext, const TextProperties &properties,
+          int max_rows) {
+  clear();
+
+  // First, expand all of the embedded TextProperties references
+  // within the string.
+  TextString text_string;
+  wstring::const_iterator si = wtext.begin();
+  scan_wtext(si, wtext.end(), &properties, text_string);
+
+  while (si != wtext.end()) {
+    // If we returned without consuming the whole string, it means
+    // there was an embedded text_pop_properties_key that didn't match
+    // the push.  That's worth a warning, and then go back and pick up
+    // the rest of the string.
+    text_cat.warning()
+      << "pop_properties encountered without preceding push_properties.\n";
+    scan_wtext(si, wtext.end(), &properties, text_string);
+  }
+
+  // Then apply any wordwrap requirements.
+  return wordwrap_text(text_string, _wordwrapped_string, max_rows);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::get_wordwrapped_wtext
+//       Access: Public
+//  Description: Returns a wstring that represents the contents of the
+//               text, as it has been formatted by wordwrap rules.
+//               This will not contain any embedded special characters
+//               like \1 or \3.
+////////////////////////////////////////////////////////////////////
+wstring TextAssembler::
+get_wordwrapped_wtext() const {
+  wstring wtext;
+
+  TextString::const_iterator ti;
+  for (ti = _wordwrapped_string.begin(); 
+       ti != _wordwrapped_string.end(); 
+       ++ti) {
+    wtext += (*ti)._character;
+  }
+
+  return wtext;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::assemble_text
+//       Access: Public
+//  Description: Actually assembles all of the text into a GeomNode,
+//               and returns the node (or possibly a parent of the
+//               node, to keep the shadow separate).  Once this has
+//               been called, you may query the extents of the text
+//               via get_ul(), get_lr().
+////////////////////////////////////////////////////////////////////
+PT(PandaNode) TextAssembler::
+assemble_text() {
+  // Now assemble the text into glyphs.
+  PlacedGlyphs placed_glyphs;
+  assemble_paragraph(_wordwrapped_string.begin(), _wordwrapped_string.end(), 
+                     placed_glyphs);
+
+  // Now that we have a bunch of GlyphPlacements, pull out the Geoms
+  // 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");
+
+  const TextProperties *properties = NULL;
+  CPT(RenderState) text_state;
+  CPT(RenderState) shadow_state;
+  LMatrix4f shadow_xform;
+
+  bool any_shadow = false;
+  
+  PlacedGlyphs::const_iterator pgi;
+  for (pgi = placed_glyphs.begin(); pgi != placed_glyphs.end(); ++pgi) {
+    const GlyphPlacement *placement = (*pgi);
+
+    if (placement->_properties != properties) {
+      // Get a new set of properties for future glyphs.
+      properties = placement->_properties;
+      text_state = RenderState::make_empty();
+      shadow_state = RenderState::make_empty();
+      shadow_xform = LMatrix4f::ident_mat();
+
+      if (properties->has_text_color()) {
+        text_state = text_state->add_attrib(ColorAttrib::make_flat(properties->get_text_color()));
+        if (properties->get_text_color()[3] != 1.0) {
+          text_state = text_state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
+        }
+      }
+
+      if (properties->has_bin()) {
+        text_state = text_state->add_attrib(CullBinAttrib::make(properties->get_bin(), properties->get_draw_order() + 2));
+      }
+
+      if (properties->has_shadow()) {
+        if (properties->has_shadow_color()) {
+          shadow_state = shadow_state->add_attrib(ColorAttrib::make_flat(properties->get_shadow_color()));
+          if (properties->get_shadow_color()[3] != 1.0) {
+            shadow_state = shadow_state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
+          }
+        }
+
+        if (properties->has_bin()) {
+          shadow_state = shadow_state->add_attrib(CullBinAttrib::make(properties->get_bin(), properties->get_draw_order() + 1));
+        }
+
+        LVector2f offset = properties->get_shadow();
+        shadow_xform = LMatrix4f::translate_mat(offset[0], 0.0f, -offset[1]);
+      }
+    }
+
+    // We have to place the shadow first, because it copies as it
+    // 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);
+      any_shadow = true;
+    }
+    placement->assign_to(text_node, text_state);
+    delete placement;
+  }  
+  placed_glyphs.clear();
+
+  if (any_shadow) {
+    // The shadow_node must appear first to guarantee the correct
+    // rendering order.
+    parent_node->add_child(shadow_node);
+  }
+  parent_node->add_child(text_node);
+
+  return parent_node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::calc_width
+//       Access: Private, Static
+//  Description: Returns the width of a single character, according to
+//               its associated font.  This also correctly calculates
+//               the width of cheesy ligatures and accented
+//               characters, which may not exist in the font as such.
+////////////////////////////////////////////////////////////////////
+float TextAssembler::
+calc_width(wchar_t character, const TextProperties &properties) {
+  if (character == ' ') {
+    // A space is a special case.
+    TextFont *font = properties.get_font();
+    nassertr(font != (TextFont *)NULL, 0.0f);
+    return font->get_space_advance();
+  }
+
+  bool got_glyph;
+  const TextGlyph *first_glyph;
+  const TextGlyph *second_glyph;
+  UnicodeLatinMap::AccentType accent_type;
+  int additional_flags;
+  float glyph_scale;
+  float advance_scale;
+  get_character_glyphs(character, &properties, 
+                       got_glyph, first_glyph, second_glyph, accent_type,
+                       additional_flags, glyph_scale, advance_scale);
+
+  float advance = 0.0f;
+  
+  if (first_glyph != (TextGlyph *)NULL) {
+    advance = first_glyph->get_advance() * advance_scale;
+  }
+  if (second_glyph != (TextGlyph *)NULL) {
+    advance += second_glyph->get_advance();
+  }
+
+  glyph_scale *= properties.get_glyph_scale();
+
+  return advance * glyph_scale;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::scan_wtext
+//       Access: Private
+//  Description: Scans through the text string, decoding embedded
+//               references to TextProperties.  The string is copied
+//               character-by-character into the indicated TextString
+//               output.
+//
+//               As new TextProperties are discovered, TextProperties
+//               structures are allocated and pushed into the
+//               _properties_list list; these pointers are
+//               referenced by the text_string.  When the text_string
+//               is no longer need, the TextProperties structures in
+//               _properties_list should be deleted to free up
+//               memory.
+////////////////////////////////////////////////////////////////////
+void TextAssembler::
+scan_wtext(wstring::const_iterator &si, 
+           const wstring::const_iterator &send,
+           const TextProperties *current_properties,
+           TextAssembler::TextString &text_string) {
+  while (si != send) {
+    if ((*si) == text_push_properties_key) {
+      // This indicates a nested properties structure.  Pull off the
+      // name of the TextProperties structure, which is everything
+      // until the next text_push_properties_key.
+      wstring wname;
+      ++si;
+      while (si != send && (*si) != text_push_properties_key) {
+        wname += (*si);
+        ++si;
+      }
+
+      if (si == send) {
+        // We didn't close the text_push_properties_key.  That's an
+        // error.
+        text_cat.warning()
+          << "Unclosed push_properties 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();
+      
+      // Define the new properties by extending the current properties.
+      TextProperties *new_properties = new TextProperties(*current_properties);
+      new_properties->add_properties(manager->get_properties(name));
+      _properties_list.push_back(new_properties);
+      
+      // And recursively scan with the nested properties.
+      scan_wtext(si, send, new_properties, text_string);
+
+      if (text_cat.is_debug()) {
+        if (si == send) {
+          // The push was not closed by a pop.  That's not an error,
+          // since we allow people to be sloppy about that; but we'll
+          // print a debug message at least.
+          text_cat.debug()
+            << "push_properties not matched by pop_properties.\n";
+        }
+      }
+
+    } else if ((*si) == text_pop_properties_key) {
+      // This indicates the undoing of a previous push_properties_key.
+      // We simply return to the previous level.
+      ++si;
+      return;
+
+    } else {
+      // A normal character.  Apply it.
+      text_string.push_back(TextCharacter(*si, current_properties));
+      ++si;
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::wordwrap_text
+//       Access: Private
+//  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).
+//
+//               If max_rows is greater than zero, no more than
+//               max_rows will be accepted.  Text beyond that will be
+//               truncated.
+//
+//               The return value is true if all the text is accepted,
+//               or false if some was truncated.
+////////////////////////////////////////////////////////////////////
+bool TextAssembler::
+wordwrap_text(const TextAssembler::TextString &text,
+              TextAssembler::TextString &output_text,
+              int max_rows) {
+  if (text.empty()) {
+    // A special case: empty text means no rows.
+    _num_rows = 0;
+    return true;
+  }
+
+  size_t p = 0;
+  _num_rows = 1;
+
+  // Preserve any initial whitespace and newlines.
+  float initial_width = 0.0f;
+  while (p < text.size() && isspacew(text[p]._character)) {
+    if (text[p]._character == '\n') {
+      initial_width = 0.0f;
+      if (max_rows > 0 && _num_rows >= max_rows) {
+        // Truncate.
+        return false;
+      }
+      _num_rows++;
+    } else {
+      initial_width += calc_width(text[p]);
+    }
+    output_text.push_back(text[p]);
+    p++;
+  }
+  bool needs_newline = false;
+
+  while (p < text.size()) {
+    nassertr(!isspacew(text[p]._character), false);
+
+    // Scan the next n characters, until the end of the string or an
+    // embedded newline character, or we exceed wordwrap_width.
+
+    size_t q = p;
+    bool any_spaces = false;
+    size_t last_space = 0;
+    float last_space_width = 0.0f;
+
+    bool any_hyphens = false;
+    size_t last_hyphen = 0;
+    bool output_hyphen = false;
+
+    bool overflow = false;
+    float wordwrap_width = -1.0f;
+
+    bool last_was_space = false;
+    float width = initial_width;
+    while (q < text.size() && text[q]._character != '\n') {
+      if (text[q]._properties->has_wordwrap()) {
+        wordwrap_width = text[q]._properties->get_wordwrap();
+      } else {
+        wordwrap_width = -1.0f;
+      }
+
+      if (isspacew(text[q]._character) || 
+          text[q]._character == text_soft_break_key) {
+        if (!last_was_space) {
+          any_spaces = true;
+          // We only care about logging whether there is a soft-hyphen
+          // character to the right of the rightmost space.  Each time
+          // we encounter a space, we reset this counter.
+          any_hyphens = false;
+          last_space = q;
+          last_space_width = width;
+          last_was_space = true;
+        }
+      } else {
+        last_was_space = false;
+      }
+
+      // A soft hyphen character is not printed, but marks a point
+      // at which we might hyphenate a word if we need to.
+      if (text[q]._character == text_soft_hyphen_key && 
+          wordwrap_width > 0.0f) {
+        // We only consider this as a possible hyphenation point if
+        // (a) it is not the very first character, and (b) there is
+        // enough room for a hyphen character to be printed following
+        // it.
+        if (q != p && width + calc_hyphen_width(text[q]) <= wordwrap_width) {
+          any_hyphens = true;
+          last_hyphen = q;
+        }
+
+      } else if (text[q]._character != text_soft_break_key) {
+        // Some normal, printable character.
+        width += calc_width(text[q]);
+      }
+
+      q++;
+        
+      if (wordwrap_width > 0.0f && width > wordwrap_width) {
+        // Oops, too many.
+        q--;
+        overflow = true;
+        break;
+      }
+    }
+
+    if (overflow) {
+      // If we stopped because we exceeded the wordwrap width, then
+      // try to find an appropriate place to wrap the line or to
+      // hyphenate, if necessary.
+      nassertr(wordwrap_width > 0.0f, false);
+
+      if (any_spaces && last_space_width / wordwrap_width >= text_hyphen_ratio) {
+        // If we have a space that ended up within our safety margin,
+        // don't use any soft-hyphen characters.
+        any_hyphens = false;
+      }
+
+      if (any_hyphens) {
+        // If we still have a soft-hyphen character, use it.
+        q = last_hyphen;
+        output_hyphen = true;
+
+      } else if (any_spaces) {
+        // Otherwise, break at a space if we can.
+        q = last_space;
+
+      } else {
+        // Otherwise, this is a forced break.  Accept the longest line
+        // we can that does not leave the next line beginning with one
+        // of our forbidden characters.
+        size_t i = 0;
+        while ((int)i < text_max_never_break && q - i > p && 
+               text_never_break_before->find(text[q - i]._character) != wstring::npos) {
+          i++;
+        }
+        if ((int)i < text_max_never_break) {
+          q -= i;
+        }
+      }
+    }
+
+    // Skip additional whitespace between the lines.
+    size_t next_start = q;
+    while (next_start < text.size() && 
+           isbreakpoint(text[next_start]._character)) {
+      next_start++;
+    }
+
+    // Trim off any more blanks on the end.
+    while (q > p && isspacew(text[q - 1]._character)) {
+      q--;
+    }
+
+    if (next_start == p) {
+      // No characters got in at all.  This could only happen if the
+      // wordwrap width is narrower than a single character, or if we
+      // have a substantial number of leading spaces in a line.
+      q++;
+      next_start++;
+      while (next_start < text.size() && 
+             isbreakpoint(text[next_start]._character)) {
+        next_start++;
+      }
+    }
+    
+    if (needs_newline) {
+      if (max_rows > 0 && _num_rows >= max_rows) {
+        // Truncate.
+        return false;
+      }
+      _num_rows++;
+      output_text.push_back(TextCharacter('\n', text[next_start - 1]._properties));
+    }
+    needs_newline = true;
+
+    if (text[next_start - 1]._properties->get_preserve_trailing_whitespace()) {
+      q = next_start;
+    }
+
+    for (size_t pi = p; pi < q; pi++) {
+      if (text[pi]._character != text_soft_hyphen_key && 
+          text[pi]._character != text_soft_break_key) {
+        output_text.push_back(text[pi]);
+      }
+    }
+    if (output_hyphen) {
+      wstring::const_iterator wi;
+      for (wi = text_soft_hyphen_output->begin();
+           wi != text_soft_hyphen_output->end();
+           ++wi) {
+        output_text.push_back(TextCharacter(*wi, text[last_hyphen]._properties));
+      }
+    }
+
+    // Now prepare to wrap the next line.
+
+    if (next_start < text.size() && text[next_start]._character == '\n') {
+      // Preserve a single embedded newline.
+      if (max_rows > 0 && _num_rows >= max_rows) {
+        // Truncate.
+        return false;
+      }
+      _num_rows++;
+      output_text.push_back(text[next_start]);
+      next_start++;
+      needs_newline = false;
+    }
+    p = next_start;
+
+    // Preserve any initial whitespace and newlines.
+    initial_width = 0.0f;
+    while (p < text.size() && isspacew(text[p]._character)) {
+      if (text[p]._character == '\n') {
+        initial_width = 0.0f;
+        if (max_rows > 0 && _num_rows >= max_rows) {
+          // Truncate.
+          return false;
+        }
+        _num_rows++;
+      } else {
+        initial_width += calc_width(text[p]);
+      }
+      output_text.push_back(text[p]);
+      p++;
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::calc_hyphen_width
+//       Access: Private, Static
+//  Description: Returns the width of the soft-hyphen replacement
+//               string, according to the indicated character's
+//               associated font.
+////////////////////////////////////////////////////////////////////
+float TextAssembler::
+calc_hyphen_width(const TextCharacter &tch) {
+  TextFont *font = tch._properties->get_font();
+  nassertr(font != (TextFont *)NULL, 0.0f);
+
+  float hyphen_width = 0.0f;
+  wstring::const_iterator wi;
+  for (wi = text_soft_hyphen_output->begin();
+       wi != text_soft_hyphen_output->end();
+       ++wi) {
+    hyphen_width += calc_width(*wi, *tch._properties);
+  }
+  
+  return hyphen_width;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::assemble_paragraph
+//       Access: Private
+//  Description: Fills up _placed_glyphs, _ul, _lr, and _num_rows with
+//               the contents of the indicated text.
+////////////////////////////////////////////////////////////////////
+void TextAssembler::
+assemble_paragraph(TextAssembler::TextString::const_iterator si, 
+                   const TextAssembler::TextString::const_iterator &send,
+                   TextAssembler::PlacedGlyphs &placed_glyphs) {
+  _ul.set(0.0f, 0.0f);
+  _lr.set(0.0f, 0.0f);
+  int num_rows = 0;
+
+  float posy = 0.0f;
+  while (si != send) {
+    // First, assemble all the glyphs of this row.
+    PlacedGlyphs row_placed_glyphs;
+    float row_width, line_height;
+    TextProperties::Alignment align;
+    assemble_row(si, send, row_placed_glyphs,
+                 row_width, line_height, align);
+
+    // Now move the row to its appropriate position.  This might
+    // involve a horizontal as well as a vertical translation.
+    LMatrix4f mat = LMatrix4f::ident_mat();
+
+    if (num_rows == 0) {
+      // If this is the first row, account for its space.
+      _ul[1] = 0.8f * line_height;
+
+    } else {
+      // If it is not the first row, shift the text downward by
+      // line_height from the previous row.
+      posy -= line_height;
+    }
+    _lr[1] = posy - 0.2f * line_height;
+
+    // Apply the requested horizontal alignment to the row.
+    switch (align) {
+    case TextProperties::A_left:
+      mat.set_row(3, LVector3f(0.0f, 0.0f, posy));
+      _lr[0] = max(_lr[0], row_width);
+      break;
+
+    case TextProperties::A_right:
+      mat.set_row(3, LVector3f(-row_width, 0.0f, posy));
+      _ul[0] = min(_ul[0], -row_width);
+      break;
+
+    case TextProperties::A_center:
+      {
+        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);
+      }
+      break;
+    }
+
+    // Now store the geoms we assembled.
+    PlacedGlyphs::iterator pi;
+    for (pi = row_placed_glyphs.begin(); pi != row_placed_glyphs.end(); ++pi) {
+      (*pi)->_xform *= mat;
+      placed_glyphs.push_back(*pi);
+    }
+
+    // Advance to the next line.
+    if (si != send) {
+      // Skip past the newline.
+      ++si;
+    }
+    num_rows++;
+  }
+
+  // num_rows may be smaller than _num_rows, if there are trailing
+  // newlines on the string.
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::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 _geom_node), and
+//               computes the length of the row and the maximum
+//               line_height of all the fonts used in the row.  The
+//               source pointer is moved to the terminating character.
+////////////////////////////////////////////////////////////////////
+void TextAssembler::
+assemble_row(TextAssembler::TextString::const_iterator &si, 
+             const TextAssembler::TextString::const_iterator &send, 
+             TextAssembler::PlacedGlyphs &row_placed_glyphs,
+             float &row_width, float &line_height, 
+             TextProperties::Alignment &align) {
+  line_height = 0.0f;
+  float xpos = 0.0f;
+  align = TextProperties::A_left;
+
+  while (si != send && (*si)._character != '\n') {
+    wchar_t character = (*si)._character;
+    const TextProperties *properties = (*si)._properties;
+
+    TextFont *font = properties->get_font();
+    nassertv(font != (TextFont *)NULL);
+
+    // We get the row's alignment property from that of the last
+    // character to be placed in the row.
+    align = properties->get_align();
+
+    // 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 (character == ' ') {
+      // A space is a special case.
+      xpos += font->get_space_advance();
+
+    } else if (character == '\t') {
+      // So is a tab character.
+      float tab_width = properties->get_tab_width();
+      xpos = (floor(xpos / tab_width) + 1.0f) * tab_width;
+
+    } else if (character == text_soft_hyphen_key) {
+      // And so is the 'soft-hyphen' key character.
+
+    } else {
+      // A printable character.
+      bool got_glyph;
+      const TextGlyph *first_glyph;
+      const TextGlyph *second_glyph;
+      UnicodeLatinMap::AccentType accent_type;
+      int additional_flags;
+      float glyph_scale;
+      float advance_scale;
+      get_character_glyphs(character, properties, 
+                           got_glyph, first_glyph, second_glyph, accent_type,
+                           additional_flags, glyph_scale, advance_scale);
+
+      if (!got_glyph) {
+        text_cat.warning()
+          << "No definition in " << font->get_name() 
+          << " for character " << character;
+        if (character < 128 && isprint((unsigned int)character)) {
+          text_cat.warning(false)
+            << " ('" << (char)character << "')";
+        }
+        text_cat.warning(false)
+          << "\n";
+      }
+
+      // Build up a GlyphPlacement, indicating all of the Geoms that go
+      // into this character.  Normally, there is only one Geom per
+      // character, but it may involve multiple Geoms if we need to
+      // add cheesy accents or ligatures.
+      GlyphPlacement *placement = new GlyphPlacement;
+      row_placed_glyphs.push_back(placement);
+
+      float advance = 0.0f;
+
+      if (first_glyph != (TextGlyph *)NULL) {
+        PT(Geom) first_char_geom = first_glyph->get_geom();
+        if (first_char_geom != (Geom *)NULL) {
+          placement->add_piece(first_char_geom, first_glyph->get_state());
+        }
+        advance = first_glyph->get_advance() * advance_scale;
+      }
+      if (second_glyph != (TextGlyph *)NULL) {
+        PT(Geom) second_char_geom = second_glyph->get_geom();
+        if (second_char_geom != (Geom *)NULL) {
+          second_char_geom->transform_vertices(LMatrix4f::translate_mat(advance, 0.0f, 0.0f));
+          placement->add_piece(second_char_geom, second_glyph->get_state());
+        }
+        advance += second_glyph->get_advance();
+      }
+
+      glyph_scale *= properties->get_glyph_scale();
+
+      // Now compute the matrix that will transform the glyph (or
+      // glyphs) into position.
+      LMatrix4f glyph_xform = LMatrix4f::scale_mat(glyph_scale);
+
+      if (accent_type != UnicodeLatinMap::AT_none || additional_flags != 0) {
+        // If we have some special handling to perform, do so now.
+        // This will probably require the bounding volume of the
+        // glyph, so go get that.
+        LPoint3f min_vert, max_vert;
+        bool found_any = false;
+        placement->calc_tight_bounds(min_vert, max_vert, found_any);
+
+        if (found_any) {
+          LPoint3f centroid = (min_vert + max_vert) / 2.0f;
+          tack_on_accent(accent_type, min_vert, max_vert, centroid, 
+                         properties, placement);
+    
+          if ((additional_flags & UnicodeLatinMap::AF_turned) != 0) {
+            // Invert the character.  Should we also invert the accent
+            // mark, so that an accent that would have been above the
+            // glyph will now be below it?  That's what we do here,
+            // which is probably the right thing to do for n-tilde,
+            // but not for most of the rest of the accent marks.  For
+            // now we'll assume there are no characters with accent
+            // marks that also have the turned flag.
+
+            // We rotate the character around its centroid, which may
+            // not always be the right point, but it's the best we've
+            // got and it's probably pretty close.
+            LMatrix4f rotate =
+              LMatrix4f::translate_mat(-centroid) *
+              LMatrix4f::rotate_mat_normaxis(180.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) *
+              LMatrix4f::translate_mat(centroid);
+            glyph_xform *= rotate;
+          }
+        }
+      }
+
+      glyph_xform(3, 0) += xpos;
+      glyph_xform(3, 2) += properties->get_glyph_shift();
+
+      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;
+    }
+    ++si;
+  }
+
+  row_width = xpos;
+}
+  
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::get_character_glyphs
+//       Access: Private, Static
+//  Description: Looks up the glyph(s) from the font for the
+//               appropriate character.  If the desired glyph isn't
+//               available (especially in the case of an accented
+//               letter), tries to find a suitable replacement.
+//               Normally, only one glyph is returned per character,
+//               but in the case in which we have to simulate a
+//               missing ligature in the font, two glyphs might be
+//               returned.
+//
+//               All parameters except the first two are output
+//               parameters.  got_glyph is set true if the glyph (or
+//               an acceptable substitute) is successfully found,
+//               false otherwise; but even if it is false, glyph might
+//               still be non-NULL, indicating a stand-in glyph for a
+//               missing character.
+////////////////////////////////////////////////////////////////////
+void TextAssembler::
+get_character_glyphs(int character, const TextProperties *properties,
+                     bool &got_glyph, const TextGlyph *&glyph,
+                     const TextGlyph *&second_glyph,
+                     UnicodeLatinMap::AccentType &accent_type,
+                     int &additional_flags,
+                     float &glyph_scale, float &advance_scale) {
+  TextFont *font = properties->get_font();
+  nassertv(font != (TextFont *)NULL);
+
+  got_glyph = false;
+  glyph = NULL;
+  second_glyph = NULL;
+  accent_type = UnicodeLatinMap::AT_none;
+  additional_flags = 0;
+  glyph_scale = 1.0f;
+  advance_scale = 1.0f;
+
+  // Maybe we should remap the character to something else--e.g. a
+  // small capital.
+  const UnicodeLatinMap::Entry *map_entry = 
+    UnicodeLatinMap::look_up(character);
+  if (map_entry != NULL) {
+    if (properties->get_small_caps() && 
+        map_entry->_toupper_character != character) {
+      character = map_entry->_toupper_character;
+      map_entry = UnicodeLatinMap::look_up(character);
+      glyph_scale = properties->get_small_caps_scale();
+    }
+  }
+  
+  got_glyph = font->get_glyph(character, glyph);
+  if (!got_glyph && map_entry != NULL && map_entry->_ascii_equiv != 0) {
+    // If we couldn't find the Unicode glyph, try the ASCII
+    // equivalent (without the accent marks).
+    got_glyph = font->get_glyph(map_entry->_ascii_equiv, glyph);
+    
+    if (!got_glyph && map_entry->_toupper_character != character) {
+      // If we still couldn't find it, try the uppercase
+      // equivalent.
+      character = map_entry->_toupper_character;
+      map_entry = UnicodeLatinMap::look_up(character);
+      if (map_entry != NULL) {
+        got_glyph = font->get_glyph(map_entry->_ascii_equiv, glyph);
+      }
+    }
+    
+    if (got_glyph) {
+      accent_type = map_entry->_accent_type;
+      additional_flags = map_entry->_additional_flags;
+      
+      bool got_second_glyph = false;
+      if (map_entry->_ascii_additional != 0) {
+        // There's another character, too--probably a ligature.
+        got_second_glyph = 
+          font->get_glyph(map_entry->_ascii_additional, second_glyph);
+      }
+      
+      if ((additional_flags & UnicodeLatinMap::AF_ligature) != 0 &&
+          got_second_glyph) {
+        // If we have two letters that are supposed to be in a
+        // ligature, just jam them together.
+        additional_flags &= ~UnicodeLatinMap::AF_ligature;
+        advance_scale = ligature_advance_scale;
+      }
+      
+      if ((additional_flags & UnicodeLatinMap::AF_smallcap) != 0) {
+        additional_flags &= ~UnicodeLatinMap::AF_smallcap;
+        glyph_scale = properties->get_small_caps_scale();
+      }
+    }
+  }
+}
+  
+  
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::tack_on_accent
+//       Access: Private, Static
+//  Description: This is a cheesy attempt to tack on an accent to an
+//               ASCII letter for which we don't have the appropriate
+//               already-accented glyph in the font.
+////////////////////////////////////////////////////////////////////
+void TextAssembler::
+tack_on_accent(UnicodeLatinMap::AccentType accent_type,
+               const LPoint3f &min_vert, const LPoint3f &max_vert,
+               const LPoint3f &centroid,
+               const TextProperties *properties, 
+               TextAssembler::GlyphPlacement *placement) {
+  switch (accent_type) {
+  case UnicodeLatinMap::AT_grave:
+    // We use the slash as the grave and acute accents.  ASCII does
+    // have a grave accent character, but a lot of fonts put the
+    // reverse apostrophe there instead.  And some fonts (particularly
+    // fonts from mf) don't even do backslash.
+    tack_on_accent('/', CP_above, CT_small_squash_mirror_y, min_vert, max_vert, centroid,
+                   properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_acute:
+    tack_on_accent('/', CP_above, CT_small_squash, min_vert, max_vert, centroid,
+                   properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_breve:
+    tack_on_accent(')', CP_above, CT_tiny_rotate_270, min_vert, max_vert, centroid,
+                   properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_inverted_breve:
+    tack_on_accent('(', CP_above, CT_tiny_rotate_270, min_vert, max_vert, centroid,
+                   properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_circumflex:
+    tack_on_accent('^', CP_above, CT_none, min_vert, max_vert, centroid,
+                   properties, placement) ||
+      tack_on_accent('v', CP_above, CT_squash_mirror_y, min_vert, max_vert, centroid,
+                     properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_circumflex_below:
+    tack_on_accent('^', CP_below, CT_none, min_vert, max_vert, centroid,
+                   properties, placement) ||
+      tack_on_accent('v', CP_below, CT_squash_mirror_y, min_vert, max_vert, centroid,
+                     properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_caron:
+    tack_on_accent('^', CP_above, CT_mirror_y, min_vert, max_vert, centroid,
+                   properties, placement) ||
+      tack_on_accent('v', CP_above, CT_squash, min_vert, max_vert, centroid,
+                     properties, placement);
+
+    break;
+
+  case UnicodeLatinMap::AT_tilde:
+    tack_on_accent('~', CP_above, CT_none, min_vert, max_vert, centroid,
+                   properties, placement) ||
+      tack_on_accent('s', CP_above, CT_squash_mirror_diag, min_vert, max_vert, centroid,
+                     properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_tilde_below:
+    tack_on_accent('~', CP_below, CT_none, min_vert, max_vert, centroid,
+                   properties, placement) ||
+      tack_on_accent('s', CP_below, CT_squash_mirror_diag, min_vert, max_vert, centroid,
+                     properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_diaeresis:
+    tack_on_accent(':', CP_above, CT_small_rotate_270, min_vert, max_vert, centroid,
+                   properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_diaeresis_below:
+    tack_on_accent(':', CP_below, CT_small_rotate_270, min_vert, max_vert, centroid,
+                   properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_dot_above:
+    tack_on_accent('.', CP_above, CT_none, min_vert, max_vert, centroid,
+                   properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_dot_below:
+    tack_on_accent('.', CP_below, CT_none, min_vert, max_vert, centroid,
+                   properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_macron:
+    tack_on_accent('-', CP_above, CT_none, min_vert, max_vert, centroid,
+                   properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_line_below:
+    tack_on_accent('-', CP_below, CT_none, min_vert, max_vert, centroid,
+                   properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_ring_above:
+    tack_on_accent('o', CP_top, CT_tiny, min_vert, max_vert, centroid,
+                   properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_ring_below:
+    tack_on_accent('o', CP_bottom, CT_tiny, min_vert, max_vert, centroid,
+                   properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_cedilla:
+    tack_on_accent('c', CP_bottom, CT_tiny_mirror_x, min_vert, max_vert, centroid,
+                   properties, placement);
+    /*
+    tack_on_accent(',', CP_bottom, CT_none, min_vert, max_vert, centroid,
+                   properties, placement);
+    */
+    break;
+
+  case UnicodeLatinMap::AT_comma_below:
+    tack_on_accent(',', CP_below, CT_none, min_vert, max_vert, centroid,
+                   properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_ogonek:
+    tack_on_accent(',', CP_bottom, CT_mirror_x, min_vert, max_vert, centroid,
+                   properties, placement);
+    break;
+
+  case UnicodeLatinMap::AT_stroke:
+    tack_on_accent('/', CP_within, CT_none, min_vert, max_vert, centroid,
+                   properties, placement);
+    break;
+
+  default:
+    // There are lots of other crazy kinds of accents.  Forget 'em.
+    break;
+  }    
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::tack_on_accent
+//       Access: Private, Static
+//  Description: Generates a cheesy accent mark above (or below, etc.)
+//               the character.  Returns true if successful, or false
+//               if the named accent character doesn't exist in the
+//               font.
+////////////////////////////////////////////////////////////////////
+bool TextAssembler::
+tack_on_accent(char accent_mark, TextAssembler::CheesyPosition position,
+               TextAssembler::CheesyTransform transform,
+               const LPoint3f &min_vert, const LPoint3f &max_vert,
+               const LPoint3f &centroid,
+               const TextProperties *properties,
+               TextAssembler::GlyphPlacement *placement) {
+  TextFont *font = properties->get_font();
+  nassertr(font != (TextFont *)NULL, false);
+  
+  const TextGlyph *accent_glyph;
+  if (font->get_glyph(accent_mark, accent_glyph)) {
+    PT(Geom) accent_geom = accent_glyph->get_geom();
+    if (accent_geom != (Geom *)NULL) {
+      LPoint3f min_accent, max_accent;
+      bool found_any = false;
+      accent_geom->calc_tight_bounds(min_accent, max_accent, found_any);
+      if (found_any) {
+        float t, u;
+        LMatrix4f accent_mat;
+
+        // This gets set to true if the glyph gets mirrored and needs
+        // to have backface culling disabled.
+        bool mirrored = false;
+
+        switch (transform) {
+        case CT_none:
+          accent_mat = LMatrix4f::ident_mat();
+          break;
+
+        case CT_mirror_x:
+          accent_mat = LMatrix4f::scale_mat(-1.0f, 1.0f, 1.0f);
+          t = min_accent[0];
+          min_accent[0] = -max_accent[0];
+          max_accent[0] = -t;
+          mirrored = true;
+          break;
+
+        case CT_mirror_y:
+          accent_mat = LMatrix4f::scale_mat(1.0f, 1.0f, -1.0f);
+          t = min_accent[2];
+          min_accent[2] = -max_accent[2];
+          max_accent[2] = -t;
+          mirrored = true;
+          break;
+
+        case CT_rotate_90:
+          accent_mat =
+            LMatrix4f::rotate_mat_normaxis(90.0f, LVecBase3f(0.0f, -1.0f, 0.0f));
+          // rotate min, max
+          t = min_accent[0];
+          u = max_accent[0];
+          max_accent[0] = -min_accent[2];
+          min_accent[0] = -max_accent[2];
+          max_accent[2] = u;
+          min_accent[2] = t;
+          break;
+
+        case CT_rotate_180:
+          accent_mat = LMatrix4f::scale_mat(-1.0f, -1.0f, 1.0f);
+          
+          t = min_accent[0];
+          min_accent[0] = -max_accent[0];
+          max_accent[0] = -t;
+          t = min_accent[2];
+          min_accent[2] = -max_accent[2];
+          max_accent[2] = -t;
+          break;
+
+        case CT_rotate_270:
+          accent_mat =
+            LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f));
+          // rotate min, max
+          t = min_accent[0];
+          u = max_accent[0];
+          min_accent[0] = min_accent[2];
+          max_accent[0] = max_accent[2];
+          min_accent[2] = -u;
+          max_accent[2] = -t;
+          break;
+
+        case CT_squash:
+          accent_mat = LMatrix4f::scale_mat(squash_accent_scale_x, 1.0f, squash_accent_scale_y);
+          min_accent[0] *= squash_accent_scale_x;
+          max_accent[0] *= squash_accent_scale_x;
+          min_accent[2] *= squash_accent_scale_y;
+          max_accent[2] *= squash_accent_scale_y;
+          break;
+
+        case CT_squash_mirror_y:
+          accent_mat = LMatrix4f::scale_mat(squash_accent_scale_x, 1.0f, -squash_accent_scale_y);
+          min_accent[0] *= squash_accent_scale_x;
+          max_accent[0] *= squash_accent_scale_x;
+          t = min_accent[2];
+          min_accent[2] = -max_accent[2] * squash_accent_scale_y;
+          max_accent[2] = -t * squash_accent_scale_y;
+          mirrored = true;
+          break;
+
+        case CT_squash_mirror_diag:
+          accent_mat =
+            LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) *
+            LMatrix4f::scale_mat(-squash_accent_scale_x, 1.0f, squash_accent_scale_y);
+          
+          // rotate min, max
+          t = min_accent[0];
+          u = max_accent[0];
+          min_accent[0] = min_accent[2] * -squash_accent_scale_x;
+          max_accent[0] = max_accent[2] * -squash_accent_scale_x;
+          min_accent[2] = -u * squash_accent_scale_y;
+          max_accent[2] = -t * squash_accent_scale_y;
+          mirrored = true;
+          break;
+
+        case CT_small_squash:
+          accent_mat = LMatrix4f::scale_mat(small_squash_accent_scale_x, 1.0f, small_squash_accent_scale_y);
+          min_accent[0] *= small_squash_accent_scale_x;
+          max_accent[0] *= small_squash_accent_scale_x;
+          min_accent[2] *= small_squash_accent_scale_y;
+          max_accent[2] *= small_squash_accent_scale_y;
+          break;
+
+        case CT_small_squash_mirror_y:
+          accent_mat = LMatrix4f::scale_mat(small_squash_accent_scale_x, 1.0f, -small_squash_accent_scale_y);
+          min_accent[0] *= small_squash_accent_scale_x;
+          max_accent[0] *= small_squash_accent_scale_x;
+          t = min_accent[2];
+          min_accent[2] = -max_accent[2] * small_squash_accent_scale_y;
+          max_accent[2] = -t * small_squash_accent_scale_y;
+          mirrored = true;
+          break;
+
+        case CT_small:
+          accent_mat = LMatrix4f::scale_mat(small_accent_scale);
+          min_accent *= small_accent_scale;
+          max_accent *= small_accent_scale;
+          break;
+
+        case CT_small_rotate_270:
+          accent_mat =
+            LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) *
+            LMatrix4f::scale_mat(small_accent_scale);
+
+          // rotate min, max
+          t = min_accent[0];
+          u = max_accent[0];
+          min_accent[0] = min_accent[2] * small_accent_scale;
+          max_accent[0] = max_accent[2] * small_accent_scale;
+          min_accent[2] = -u * small_accent_scale;
+          max_accent[2] = -t * small_accent_scale;
+          break;
+
+        case CT_tiny:
+          accent_mat = LMatrix4f::scale_mat(tiny_accent_scale);
+          min_accent *= tiny_accent_scale;
+          max_accent *= tiny_accent_scale;
+          break;
+
+        case CT_tiny_mirror_x:
+          accent_mat = LMatrix4f::scale_mat(-tiny_accent_scale, 1.0f, tiny_accent_scale);
+          
+          t = min_accent[0];
+          min_accent[0] = -max_accent[0] * tiny_accent_scale;
+          max_accent[0] = -t * tiny_accent_scale;
+          min_accent[2] *= tiny_accent_scale;
+          max_accent[2] *= tiny_accent_scale;
+          mirrored = true;
+          break;
+
+        case CT_tiny_rotate_270:
+          accent_mat =
+            LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) *
+            LMatrix4f::scale_mat(tiny_accent_scale);
+
+          // rotate min, max
+          t = min_accent[0];
+          u = max_accent[0];
+          min_accent[0] = min_accent[2] * tiny_accent_scale;
+          max_accent[0] = max_accent[2] * tiny_accent_scale;
+          min_accent[2] = -u * tiny_accent_scale;
+          max_accent[2] = -t * tiny_accent_scale;
+          break;
+        }
+
+        LPoint3f accent_centroid = (min_accent + max_accent) / 2.0f;
+        float accent_height = max_accent[2] - min_accent[2];
+        LVector3f trans;
+        switch (position) {
+        case CP_above:
+          // A little above the character.
+          trans.set(centroid[0] - accent_centroid[0], 0.0f,
+                    max_vert[2] - accent_centroid[2] + accent_height * 0.5);
+          break;
+
+        case CP_below:
+          // A little below the character.
+          trans.set(centroid[0] - accent_centroid[0], 0.0f,
+                    min_vert[2] - accent_centroid[2] - accent_height * 0.5);
+          break;
+
+        case CP_top:
+          // Touching the top of the character.
+          trans.set(centroid[0] - accent_centroid[0], 0.0f,
+                    max_vert[2] - accent_centroid[2]);
+          break;
+
+        case CP_bottom:
+          // Touching the bottom of the character.
+          trans.set(centroid[0] - accent_centroid[0], 0.0f,
+                    min_vert[2] - accent_centroid[2]);
+          break;
+
+        case CP_within:
+          // Centered within the character.
+          trans.set(centroid[0] - accent_centroid[0], 0.0f,
+                    centroid[2] - accent_centroid[2]);
+          break;
+        }
+
+        accent_mat.set_row(3, trans);
+        accent_geom->transform_vertices(accent_mat);
+
+        if (mirrored) {
+          // Once someone asks for this pointer, we hold its reference
+          // count and never free it.
+          static CPT(RenderState) disable_backface;
+          if (disable_backface == (const RenderState *)NULL) {
+            disable_backface = RenderState::make
+              (CullFaceAttrib::make(CullFaceAttrib::M_cull_none));
+          }
+            
+          CPT(RenderState) state = 
+            accent_glyph->get_state()->compose(disable_backface);
+          placement->add_piece(accent_geom, state);
+        } else {
+          placement->add_piece(accent_geom, accent_glyph->get_state());
+        }
+
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::GlyphPlacement::calc_tight_bounds
+//       Access: Private
+//  Description: Expands min_point and max_point to include all of the
+//               vertices in the glyph(s), if any.  found_any is set
+//               true if any points are found.  It is the caller's
+//               responsibility to initialize min_point, max_point,
+//               and found_any before calling this function.
+////////////////////////////////////////////////////////////////////
+void TextAssembler::GlyphPlacement::
+calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point,
+                  bool &found_any) const {
+  Pieces::const_iterator pi;
+  for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
+    (*pi)._geom->calc_tight_bounds(min_point, max_point, found_any);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::GlyphPlacement::assign_to
+//       Access: Private
+//  Description: Puts the pieces of the GlyphPlacement in the
+//               indicated GeomNode.  The vertices of the Geoms are
+//               modified by this operation.
+////////////////////////////////////////////////////////////////////
+void TextAssembler::GlyphPlacement::
+assign_to(GeomNode *geom_node, const RenderState *state) const {
+  Pieces::const_iterator pi;
+  for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
+    (*pi)._geom->transform_vertices(_xform);
+    geom_node->add_geom((*pi)._geom, state->compose((*pi)._state));
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextAssembler::GlyphPlacement::assign_copy_to
+//       Access: Private
+//  Description: Puts the pieces of the GlyphPlacement in the
+//               indicated GeomNode.  This flavor will make a copy of
+//               the Geoms first, and then apply the additional
+//               transform to the vertices.
+////////////////////////////////////////////////////////////////////
+void TextAssembler::GlyphPlacement::
+assign_copy_to(GeomNode *geom_node, const RenderState *state,
+               const LMatrix4f &extra_xform) const {
+  LMatrix4f new_xform = _xform * extra_xform;
+  Pieces::const_iterator pi;
+  for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
+    PT(Geom) new_geom = (*pi)._geom->make_copy();
+    new_geom->transform_vertices(new_xform);
+    geom_node->add_geom(new_geom, state->compose((*pi)._state));
+  }
+}
+
+#endif  // CPPPARSER
+

+ 188 - 0
panda/src/text/textAssembler.h

@@ -0,0 +1,188 @@
+// Filename: textAssembler.h
+// Created by:  drose (06Apr04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 TEXTASSEMBLER_H
+#define TEXTASSEMBLER_H
+
+#ifndef CPPPARSER  // interrogate has a bit of trouble with wstring.
+
+#include "pandabase.h"
+
+#include "textProperties.h"
+#include "textFont.h"
+#include "unicodeLatinMap.h"
+#include "geomNode.h"
+#include "pointerTo.h"
+
+class TextEncoder;
+
+////////////////////////////////////////////////////////////////////
+//       Class : TextAssembler
+// Description : This class is not intended to be used directly by
+//               user code, but is used by the TextNode to lay out a
+//               block of text and convert it into rows of Geoms
+//               according to the TextProperties.
+//
+//               It is not exported from the DLL since it is not
+//               intended to be used outside of this module.
+////////////////////////////////////////////////////////////////////
+class TextAssembler {
+public:
+  TextAssembler(TextEncoder *encoder);
+  ~TextAssembler();
+
+  void clear();
+
+  bool set_wtext(const wstring &wtext, const TextProperties &properties,
+                 int max_rows = 0);
+  INLINE int get_num_rows() const;
+  wstring get_wordwrapped_wtext() const;
+
+  PT(PandaNode) assemble_text();
+
+  INLINE const LVector2f &get_ul() const;
+  INLINE const LVector2f &get_lr() const;
+
+  static float calc_width(wchar_t character, const TextProperties &properties);
+
+private:
+  // These structures are built up and operated on by scan_wtext() and
+  // wordwrap_text().  It represents the unrolling of the embedded \1
+  // .. \2 sequences embedded in the string into a TextProperties
+  // pointer associated with each character.
+  typedef pvector<TextProperties *> PropertiesList;
+
+  class TextCharacter {
+  public:
+    INLINE TextCharacter(wchar_t character, const TextProperties *properties);
+    wchar_t _character;
+    const TextProperties *_properties;
+  };
+  typedef pvector<TextCharacter> TextString;
+
+  PropertiesList _properties_list;
+  TextString _wordwrapped_string;
+  int _num_rows;
+
+  void scan_wtext(wstring::const_iterator &si, 
+                  const wstring::const_iterator &send,
+                  const TextProperties *current_properties,
+                  TextString &text_string);
+  bool wordwrap_text(const TextAssembler::TextString &text,
+                     TextAssembler::TextString &output_text,
+                     int max_rows);
+
+  INLINE static float calc_width(const TextCharacter &tch);
+  static float calc_hyphen_width(const TextCharacter &tch);
+
+  // These structures are built up by assemble_paragraph() and
+  // assemble_row().  They represent the actual Geoms as laid out in a
+  // paragraph.
+  
+  class Piece {
+  public:
+    PT(Geom) _geom;
+    CPT(RenderState) _state;
+  };
+  typedef pvector<Piece> Pieces;
+
+  class GlyphPlacement {
+  public:
+    INLINE void add_piece(Geom *geom, const RenderState *state);
+    void calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point,
+                           bool &found_any) 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;
+
+    Pieces _pieces;
+    LMatrix4f _xform;
+    const TextProperties *_properties;
+  };
+  typedef pvector<GlyphPlacement *> PlacedGlyphs;
+
+  void assemble_paragraph(TextString::const_iterator si, 
+                          const TextString::const_iterator &send,
+                          PlacedGlyphs &placed_glyphs);
+  void assemble_row(TextString::const_iterator &si, 
+                    const TextString::const_iterator &send,
+                    PlacedGlyphs &row_placed_glyphs,
+                    float &row_width, float &line_height, 
+                    TextProperties::Alignment &align);
+
+  // These interfaces are for implementing cheesy accent marks and
+  // ligatures when the font doesn't support them.
+  enum CheesyPosition {
+    CP_above,
+    CP_below,
+    CP_top,
+    CP_bottom,
+    CP_within,
+  };
+  enum CheesyTransform {
+    CT_none,
+    CT_mirror_x,
+    CT_mirror_y,
+    CT_rotate_90,
+    CT_rotate_180,
+    CT_rotate_270,
+    CT_squash,
+    CT_squash_mirror_y,
+    CT_squash_mirror_diag,
+    CT_small_squash,
+    CT_small_squash_mirror_y,
+    CT_small,
+    CT_small_rotate_270,
+    CT_tiny,
+    CT_tiny_mirror_x,
+    CT_tiny_rotate_270,
+  };
+
+  static void
+  get_character_glyphs(int character, const TextProperties *properties,
+                       bool &got_glyph, const TextGlyph *&glyph,
+                       const TextGlyph *&second_glyph,
+                       UnicodeLatinMap::AccentType &accent_type,
+                       int &additional_flags,
+                       float &glyph_scale, float &advance_scale);
+
+  static void
+  tack_on_accent(UnicodeLatinMap::AccentType accent_type,
+                 const LPoint3f &min_vert, const LPoint3f &max_vert,
+                 const LPoint3f &centroid,
+                 const TextProperties *properties, GlyphPlacement *placement);
+  static bool 
+  tack_on_accent(char accent_mark, CheesyPosition position,
+                 CheesyTransform transform,
+                 const LPoint3f &min_vert, const LPoint3f &max_vert,
+                 const LPoint3f &centroid,
+                 const TextProperties *properties, GlyphPlacement *placement);
+
+  // These are filled in by assemble_paragraph().
+  LVector2f _ul;
+  LVector2f _lr;
+
+  TextEncoder *_encoder;
+};
+
+#include "textAssembler.I"
+
+#endif  // CPPPARSER
+
+#endif
+

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

@@ -22,29 +22,6 @@
 
 TypeHandle TextFont::_type_handle;
 
-////////////////////////////////////////////////////////////////////
-//     Function: isbreakpoint
-//  Description: An internal function, similar to isspace(), except it
-//               does not consider newlines to be whitespace.  It also
-//               includes the soft-hyphen character.
-////////////////////////////////////////////////////////////////////
-static INLINE bool
-isbreakpoint(unsigned int ch) {
-  return (ch == ' ' || ch == '\t' || 
-          ch == (unsigned int)text_soft_hyphen_key ||
-          ch == (unsigned int)text_soft_break_key);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: isspacew
-//  Description: An internal function that works like isspace() but is
-//               safe to call for a wide character.
-////////////////////////////////////////////////////////////////////
-static INLINE bool
-isspacew(unsigned int ch) {
-  return isascii(ch) && isspace(ch);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: TextFont::Constructor
 //       Access: Public
@@ -66,81 +43,6 @@ TextFont::
 ~TextFont() {
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: TextFont::calc_width
-//       Access: Published
-//  Description: Returns the width of a single character of the font,
-//               or 0.0 if the character is not known.
-////////////////////////////////////////////////////////////////////
-float TextFont::
-calc_width(int character) {
-  if (character == ' ') {
-    // A space is a special case.
-    return _space_advance;
-  }
-
-  const TextGlyph *glyph;
-  get_glyph(character, glyph);
-  if (glyph == (TextGlyph *)NULL) {
-    // Unknown character.
-    return 0.0f;
-  }
-
-  return glyph->get_advance();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TextFont::calc_width
-//       Access: Published
-//  Description: Returns the width of a line of text of arbitrary
-//               characters.  The line should not include the newline
-//               character.
-////////////////////////////////////////////////////////////////////
-float TextFont::
-calc_width(const string &line) {
-  float width = 0.0f;
-
-  string::const_iterator si;
-  for (si = line.begin(); si != line.end(); ++si) {
-    width += calc_width(*si);
-  }
-
-  return width;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TextFont::wordwrap_to
-//       Access: Published
-//  Description: Inserts newlines into the given text at the
-//               appropriate places in order to make each line be the
-//               longest possible line that is not longer than
-//               wordwrap_width (and does not break any words, if
-//               possible).  Returns the new string.
-////////////////////////////////////////////////////////////////////
-string TextFont::
-wordwrap_to(const string &text, float wordwrap_width, 
-            bool preserve_trailing_whitespace) {
-  // Elevate the string to a wstring for this operation.
-  wstring wtext;
-  wtext.reserve(text.size());
-  for (string::const_iterator ti = text.begin(); ti != text.end(); ++ti) {
-    wtext += (unsigned int)(*ti);
-  }
-
-  wtext = wordwrap_to(wtext, wordwrap_width, preserve_trailing_whitespace);
-
-  // Back down from wstring to string.
-  string result;
-  result.reserve(wtext.size());
-  for (wstring::const_iterator wi = wtext.begin();
-       wi != wtext.end();
-       ++wi) {
-    result += (char)(*wi);
-  }
-
-  return result;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: TextFont::write
 //       Access: Published, Virtual
@@ -151,215 +53,3 @@ write(ostream &out, int indent_level) const {
   indent(out, indent_level)
     << "TextFont " << get_name() << "\n";
 }
-
-////////////////////////////////////////////////////////////////////
-//     Function: TextFont::calc_width (wide char)
-//       Access: Public
-//  Description: Returns the width of a line of text of arbitrary
-//               characters.  The line should not include the newline
-//               character.
-////////////////////////////////////////////////////////////////////
-float TextFont::
-calc_width(const wstring &line) {
-  float width = 0.0f;
-
-  wstring::const_iterator si;
-  for (si = line.begin(); si != line.end(); ++si) {
-    width += calc_width(*si);
-  }
-
-  return width;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TextFont::wordwrap_to (wide char)
-//       Access: Public
-//  Description: Inserts newlines into the given text at the
-//               appropriate places in order to make each line be the
-//               longest possible line that is not longer than
-//               wordwrap_width (and does not break any words, if
-//               possible).  Returns the new string.
-////////////////////////////////////////////////////////////////////
-wstring TextFont::
-wordwrap_to(const wstring &text, float wordwrap_width, 
-            bool preserve_trailing_whitespace) {
-  wstring output_text;
-
-  size_t p = 0;
-
-  // Preserve any initial whitespace and newlines.
-  float initial_width = 0.0f;
-  while (p < text.length() && isspacew(text[p])) {
-    if (text[p] == '\n') {
-      initial_width = 0.0f;
-    } else {
-      initial_width += calc_width(text[p]);
-    }
-    output_text += text[p];
-    p++;
-  }
-  bool needs_newline = false;
-
-  while (p < text.length()) {
-    nassertr(!isspacew(text[p]), wstring());
-
-    // Scan the next n characters, until the end of the string or an
-    // embedded newline character, or we exceed wordwrap_width.
-
-    size_t q = p;
-    bool any_spaces = false;
-    size_t last_space = 0;
-    float last_space_width = 0.0f;
-
-    bool any_hyphens = false;
-    size_t last_hyphen = 0;
-    bool output_hyphen = false;
-
-    bool overflow = false;
-
-    bool last_was_space = false;
-    float hyphen_width = calc_width(*text_soft_hyphen_output);
-    float width = initial_width;
-    while (q < text.length() && text[q] != '\n') {
-      if (isspacew(text[q]) || text[q] == text_soft_break_key) {
-        if (!last_was_space) {
-          any_spaces = true;
-          // We only care about logging whether there is a soft-hyphen
-          // character to the right of the rightmost space.  Each time
-          // we encounter a space, we reset this counter.
-          any_hyphens = false;
-          last_space = q;
-          last_space_width = width;
-          last_was_space = true;
-        }
-      } else {
-        last_was_space = false;
-      }
-
-      // A soft hyphen character is not printed, but marks a point
-      // at which we might hyphenate a word if we need to.
-      if (text[q] == text_soft_hyphen_key) {
-        // We only consider this as a possible hyphenation point if
-        // (a) it is not the very first character, and (b) there is
-        // enough room for a hyphen character to be printed following
-        // it.
-        if (q != p && width + hyphen_width <= wordwrap_width) {
-          any_hyphens = true;
-          last_hyphen = q;
-        }
-
-      } else if (text[q] != text_soft_break_key) {
-        // Some normal, printable character.
-        width += calc_width(text[q]);
-      }
-
-      q++;
-        
-      if (width > wordwrap_width) {
-        // Oops, too many.
-        q--;
-        overflow = true;
-        break;
-      }
-    }
-
-    if (overflow) {
-      // If we stopped because we exceeded the wordwrap width, then
-      // try to find an appropriate place to wrap the line or to
-      // hyphenate, if necessary.
-      if (any_spaces && last_space_width / wordwrap_width >= text_hyphen_ratio) {
-        // If we have a space that ended up within our safety margin,
-        // don't use any soft-hyphen characters.
-        any_hyphens = false;
-      }
-
-      if (any_hyphens) {
-        // If we still have a soft-hyphen character, use it.
-        q = last_hyphen;
-        output_hyphen = true;
-
-      } else if (any_spaces) {
-        // Otherwise, break at a space if we can.
-        q = last_space;
-
-      } else {
-        // Otherwise, this is a forced break.  Accept the longest line
-        // we can that does not leave the next line beginning with one
-        // of our forbidden characters.
-        size_t i = 0;
-        while ((int)i < text_max_never_break && q - i > p && 
-               text_never_break_before->find(text[q - i]) != wstring::npos) {
-          i++;
-        }
-        if ((int)i < text_max_never_break) {
-          q -= i;
-        }
-      }
-    }
-
-    // Skip additional whitespace between the lines.
-    size_t next_start = q;
-    while (next_start < text.length() && isbreakpoint(text[next_start])) {
-      next_start++;
-    }
-
-    // Trim off any more blanks on the end.
-    while (q > p && isspacew(text[q - 1])) {
-      q--;
-    }
-
-    if (next_start == p) {
-      // No characters got in at all.  This could only happen if the
-      // wordwrap width is narrower than a single character, or if we
-      // have a substantial number of leading spaces in a line.
-      q++;
-      next_start++;
-      while (next_start < text.length() && isbreakpoint(text[next_start])) {
-        next_start++;
-      }
-    }
-    
-    if (needs_newline) {
-      output_text += '\n';
-    }
-    needs_newline = true;
-
-    if (preserve_trailing_whitespace) {
-      q = next_start;
-    }
-
-    for (size_t pi = p; pi < q; pi++) {
-      if (text[pi] != text_soft_hyphen_key && 
-          text[pi] != text_soft_break_key) {
-        output_text += text[pi];
-      }
-    }
-    if (output_hyphen) {
-      output_text += *text_soft_hyphen_output;
-    }
-
-    // Now prepare to wrap the next line.
-
-    if (next_start < text.length() && text[next_start] == '\n') {
-      // Preserve a single embedded newline.
-      output_text += '\n';
-      next_start++;
-      needs_newline = false;
-    }
-    p = next_start;
-
-    // Preserve any initial whitespace and newlines.
-    initial_width = 0.0f;
-    while (p < text.length() && isspacew(text[p])) {
-      if (text[p] == '\n') {
-        initial_width = 0.0f;
-      } else {
-        initial_width += calc_width(text[p]);
-      }
-      output_text += text[p];
-      p++;
-    }
-  }
-
-  return output_text;
-}

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

@@ -53,15 +53,9 @@ PUBLISHED:
   INLINE float get_space_advance() const;
   INLINE void set_space_advance(float space_advance);
 
-  float calc_width(int character);
-  float calc_width(const string &line);
-  string wordwrap_to(const string &text, float wordwrap_width,
-                     bool preserve_trailing_whitespace);
-
   virtual void write(ostream &out, int indent_level) const;
 
 public:
-  float calc_width(const wstring &line);
   wstring wordwrap_to(const wstring &text, float wordwrap_width,
                      bool preserve_trailing_whitespace);
 

File diff suppressed because it is too large
+ 416 - 416
panda/src/text/textNode.I


File diff suppressed because it is too large
+ 119 - 1058
panda/src/text/textNode.cxx


+ 83 - 152
panda/src/text/textNode.h

@@ -23,13 +23,12 @@
 
 #include "config_text.h"
 #include "textEncoder.h"
+#include "textProperties.h"
 #include "textFont.h"
-#include "unicodeLatinMap.h"
+#include "textAssembler.h"
 #include "pandaNode.h"
 #include "luse.h"
 
-class StringDecoder;
-
 ////////////////////////////////////////////////////////////////////
 //       Class : TextNode
 // Description : The primary interface to this module.  This class
@@ -53,49 +52,19 @@ class StringDecoder;
 //               you may use however you like.  Each time you call
 //               generate() a new node is returned.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA TextNode : public PandaNode, public TextEncoder {
+class EXPCL_PANDA TextNode : public PandaNode, public TextEncoder, public TextProperties {
 PUBLISHED:
   TextNode(const string &name);
+  TextNode(const string &name, const TextProperties &copy);
   ~TextNode();
 
-  enum Alignment {
-    A_left,
-    A_right,
-    A_center,
-  };
- 
-  INLINE int freeze();
-  INLINE int thaw();
-
-  INLINE void set_font(TextFont *font);
-  INLINE TextFont *get_font() const;
-
-  INLINE static void set_default_font(TextFont *);
-  INLINE static TextFont *get_default_font();
-
   INLINE float get_line_height() const;
 
-  INLINE void set_small_caps(bool small_caps);
-  INLINE bool get_small_caps() const;
-  INLINE void set_small_caps_scale(float small_caps_scale);
-  INLINE float get_small_caps_scale() const;
-
-  INLINE void set_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_max_rows(int max_rows);
+  INLINE void clear_max_rows();
+  INLINE bool has_max_rows() const;
+  INLINE int get_max_rows() const;
+  INLINE bool has_overflow() const;
 
   INLINE void set_frame_color(float r, float g, float b, float a);
   INLINE void set_frame_color(const Colorf &frame_color);
@@ -116,10 +85,6 @@ PUBLISHED:
   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,
@@ -146,27 +111,63 @@ PUBLISHED:
   INLINE LVecBase4f get_card_actual() const;
   INLINE LVecBase4f get_card_transformed() 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;
+
+  // These methods are inherited from TextProperties, but we override
+  // here so we can flag the TextNode as dirty when they have been
+  // change.
+
+  INLINE void set_font(TextFont *font);
+  INLINE void clear_font();
+
+  INLINE void set_small_caps(bool small_caps);
+  INLINE void clear_small_caps();
+
+  INLINE void set_small_caps_scale(float small_caps_scale);
+  INLINE void clear_small_caps_scale();
+
+  INLINE void set_slant(float slant);
+  INLINE void clear_slant();
+
+  INLINE void set_align(Alignment align_type);
+  INLINE void clear_align();
+
+  INLINE void set_indent(float indent);
+  INLINE void clear_indent();
+
+  INLINE void set_wordwrap(float wordwrap);
+  INLINE void clear_wordwrap();
+
+  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 void set_shadow_color(float r, float g, float b, float a);
+  INLINE void set_shadow_color(const Colorf &shadow_color);
+  INLINE void clear_shadow_color();
+
   INLINE void set_shadow(float xoffset, float yoffset);
+  INLINE void set_shadow(const LVecBase2f &shadow_offset);
   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 clear_draw_order();
 
   INLINE void set_tab_width(float tab_width);
-  INLINE float get_tab_width() const;
+  INLINE void clear_tab_width();
 
-  INLINE void set_transform(const LMatrix4f &transform);
-  INLINE LMatrix4f get_transform() const;
+  INLINE void set_glyph_scale(float glyph_scale);
+  INLINE void clear_glyph_scale();
 
-  INLINE void set_coordinate_system(CoordinateSystem cs);
-  INLINE CoordinateSystem get_coordinate_system() const;
+  INLINE void set_glyph_shift(float glyph_shift);
+  INLINE void clear_glyph_shift();
 
   // These methods are inherited from TextEncoder, but we override
   // here so we can flag the TextNode as dirty when they have been
@@ -177,10 +178,14 @@ PUBLISHED:
   INLINE void append_text(const string &text);
   INLINE void append_unicode_char(int character);
 
-  INLINE float calc_width(int character) const;
+  // After the text has been set, you can query this to determine how
+  // it will be wordwrapped.
+  INLINE string get_wordwrapped_text() const;
+
+  // These methods calculate the width of a single character or a line
+  // of text in the current font.
+  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;
 
@@ -207,9 +212,8 @@ public:
   INLINE void set_wtext(const wstring &wtext);
   INLINE void append_wtext(const wstring &text);
 
-  INLINE float calc_width(const wstring &line) const;
-  INLINE wstring wordwrap_to(const wstring &wtext, float wordwrap_width,
-                             bool preserve_trailing_whitespace) const;
+  INLINE wstring get_wordwrapped_wtext() const;
+  float calc_width(const wstring &line) const;
 
   // From parent class PandaNode
   virtual int get_unsafe_to_apply_attribs() const;
@@ -235,123 +239,47 @@ private:
   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, 
-                     TextFont *font, GeomNode *dest, const LMatrix4f &mat);
-  PT(PandaNode) assemble_text(wstring::iterator si, const wstring::iterator &send,
-                              TextFont *font,
-                              LVector2f &ul, LVector2f &lr, int &num_rows);
-  float measure_row(wstring::iterator &si, const wstring::iterator &send,
-                    TextFont *font);
-  void measure_text(wstring::iterator si, const wstring::iterator &send,
-                    TextFont *font,
-                    LVector2f &ul, LVector2f &lr, int &num_rows);
-#endif  // CPPPARSER
-
-  enum CheesyPlacement {
-    CP_above,
-    CP_below,
-    CP_top,
-    CP_bottom,
-    CP_within,
-  };
-  enum CheesyTransform {
-    CT_none,
-    CT_mirror_x,
-    CT_mirror_y,
-    CT_rotate_90,
-    CT_rotate_180,
-    CT_rotate_270,
-    CT_squash,
-    CT_squash_mirror_y,
-    CT_squash_mirror_diag,
-    CT_small_squash,
-    CT_small_squash_mirror_y,
-    CT_small,
-    CT_small_rotate_270,
-    CT_tiny,
-    CT_tiny_mirror_x,
-    CT_tiny_rotate_270,
-  };
-
-  void get_character_glyphs(int character, TextFont *font,
-                            bool &got_glyph, const TextGlyph *&glyph,
-                            const TextGlyph *&second_glyph,
-                            UnicodeLatinMap::AccentType &accent_type,
-                            int &additional_flags,
-                            float &glyph_scale, float &advance_scale);
-
-  void tack_on_accent(UnicodeLatinMap::AccentType accent_type,
-                      const LPoint3f &min_vert, const LPoint3f &max_vert,
-                      const LPoint3f &centroid,
-                      TextFont *font, GeomNode *dest, 
-                      Geom *geom_array[], int &num_geoms);
-  bool tack_on_accent(char accent_mark, CheesyPlacement placement,
-                      CheesyTransform transform,
-                      const LPoint3f &min_vert, const LPoint3f &max_vert,
-                      const LPoint3f &centroid,
-                      TextFont *font, GeomNode *dest, 
-                      Geom *geom_array[], int &num_geoms);
-
   PT(PandaNode) make_frame();
   PT(PandaNode) make_card();
   PT(PandaNode) make_card_with_border();
 
-  static void load_default_font();
-
-  PT(TextFont) _font;
   PT(PandaNode) _internal_geom;
 
-  float _slant;
-
   PT(Texture) _card_texture;
-  Colorf _text_color;
-  Colorf _shadow_color;
   Colorf _frame_color;
   Colorf _card_color;
 
   enum Flags {
-    F_has_text_color   =  0x00000001,
-    F_has_wordwrap     =  0x00000002,
-    F_has_frame        =  0x00000004,
-    F_frame_as_margin  =  0x00000008,
-    F_has_card         =  0x00000010,
-    F_card_as_margin   =  0x00000020,
-    F_has_card_texture =  0x00000040,
-    F_has_shadow       =  0x00000080,
-    F_frame_corners    =  0x00000100,
-    F_card_transp      =  0x00000200,
-    F_has_card_border  =  0x00000400,
-    F_needs_rebuild    =  0x00004000,
-    F_needs_measure    =  0x00008000,
-    F_small_caps       =  0x00010000,
+    F_has_frame        =  0x0001,
+    F_frame_as_margin  =  0x0002,
+    F_has_card         =  0x0004,
+    F_card_as_margin   =  0x0008,
+    F_has_card_texture =  0x0010,
+    F_frame_corners    =  0x0020,
+    F_card_transp      =  0x0040,
+    F_has_card_border  =  0x0080,
+    F_needs_rebuild    =  0x0100,
+    F_needs_measure    =  0x0200,
+    F_has_overflow     =  0x0400,
   };
 
   int _flags;
-  Alignment _align;
-  float _wordwrap_width;
+  int _max_rows;
   float _frame_width;
   float _card_border_size;
   float _card_border_uv_portion;
-  float _small_caps_scale;
 
   LVector2f _frame_ul, _frame_lr;
   LVector2f _card_ul, _card_lr;
-  LVector2f _shadow_offset;
-
-  string _bin;
-  int _draw_order;
-  float _tab_width;
 
   LMatrix4f _transform;
   CoordinateSystem _coordinate_system;
 
-  LPoint2f _ul2d, _lr2d;
   LPoint3f _ul3d, _lr3d;
-  int _num_rows;
 
-  static PT(TextFont) _default_font;
-  static bool _loaded_default_font;
+#ifndef CPPPARSER
+  TextAssembler _assembler;
+#endif
 
 public:
   static TypeHandle get_class_type() {
@@ -359,9 +287,12 @@ public:
   }
   static void init_type() {
     PandaNode::init_type();
+    TextEncoder::init_type();
+    TextProperties::init_type();
     register_type(_type_handle, "TextNode",
                   PandaNode::get_class_type(),
-                  TextEncoder::get_class_type());
+                  TextEncoder::get_class_type(),
+                  TextProperties::get_class_type());
   }
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 842 - 0
panda/src/text/textProperties.I

@@ -0,0 +1,842 @@
+// Filename: textProperties.I
+// Created by:  drose (06Apr04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: TextProperties::is_any_specified
+//       Access: Published
+//  Description: Returns true if any properties have been specified,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+is_any_specified() const {
+  return (_specified != 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_default_font
+//       Access: Published, Static
+//  Description: Specifies the default font to be used for any
+//               TextNode whose font is uninitialized or NULL.  See
+//               set_font().
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_default_font(TextFont *font) {
+  // If the user overrides the default, we don't need to try to load
+  // whatever it would have been.
+  _loaded_default_font = true;
+  _default_font = font;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::get_default_font
+//       Access: Published, Static
+//  Description: Specifies the default font to be used for any
+//               TextNode whose font is uninitialized or NULL.  See
+//               set_font().
+////////////////////////////////////////////////////////////////////
+INLINE TextFont *TextProperties::
+get_default_font() {
+  if (!_loaded_default_font) {
+    load_default_font();
+  }
+  return _default_font;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_font
+//       Access: Published
+//  Description: Sets the font that will be used when making text.  If
+//               this is set to NULL, the default font will be used,
+//               which can be set via set_default_font().
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_font(TextFont *font) {
+  _font = font;
+  _specified |= F_has_font;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::clear_font
+//       Access: Published
+//  Description: Restores the default font to the text.
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+clear_font() {
+  _font.clear();
+  _specified &= ~F_has_font;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_font
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_font() const {
+  return (_specified & F_has_font) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::get_font
+//       Access: Published
+//  Description: Returns the font currently in use, if any.  If no
+//               font is in use, this returns the default font.
+////////////////////////////////////////////////////////////////////
+INLINE TextFont *TextProperties::
+get_font() const {
+  return has_font() ? _font.p() : get_default_font();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_small_caps
+//       Access: Published
+//  Description: Sets the small_caps flag.  When this is set,
+//               lowercase letters are generated as scaled-down
+//               versions of their uppercase equivalents.  This is
+//               particularly useful to set for fonts that do not have
+//               lowercase letters.
+//
+//               It is also a good idea to set this for a (dynamic)
+//               font that has already implemented lowercase letters
+//               as scaled-down versions of their uppercase
+//               equivalents, since without this flag the texture
+//               memory may needlessly duplicate equivalent glyphs for
+//               upper and lowercase letters.  Setting this flag
+//               causes the texture memory to share the mixed-case
+//               letters.
+//
+//               The amount by which the lowercase letters are scaled
+//               is specified by set_small_caps_scale().
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_small_caps(bool small_caps) {
+  _small_caps = small_caps;
+  _specified |= F_has_small_caps;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::clear_small_caps
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+clear_small_caps() {
+  _small_caps = false;
+  _specified &= ~F_has_small_caps;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_small_caps
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_small_caps() const {
+  return (_specified & F_has_small_caps) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::get_small_caps
+//       Access: Published
+//  Description: Returns the small_caps flag.  See set_small_caps().
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+get_small_caps() const {
+  return _small_caps;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_small_caps_scale
+//       Access: Published
+//  Description: Sets the scale factor applied to lowercase letters
+//               from their uppercase equivalents, when the small_caps
+//               flag is in effect.  See set_small_caps().  Normally,
+//               this will be a number less than one.
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_small_caps_scale(float small_caps_scale) {
+  _small_caps_scale = small_caps_scale;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::clear_small_caps_scale
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+clear_small_caps_scale() {
+  _small_caps_scale = text_small_caps_scale;
+  _specified &= ~F_has_small_caps_scale;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_small_caps_scale
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_small_caps_scale() const {
+  return (_specified & F_has_small_caps_scale) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::get_small_caps_scale
+//       Access: Published
+//  Description: Returns the scale factor applied to lowercase letters
+//               from their uppercase equivalents, when the small_caps
+//               flag is in effect.  See set_small_caps() and
+//               set_small_caps_scale().
+////////////////////////////////////////////////////////////////////
+INLINE float TextProperties::
+get_small_caps_scale() const {
+  return _small_caps_scale;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_slant
+//       Access: Published
+//  Description: Specifies the factor by which the text slants to the
+//               right.
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_slant(float slant) {
+  _slant = slant;
+  _specified |= F_has_slant;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::clear_slant
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+clear_slant() {
+  _slant = 0.0f;
+  _specified &= ~F_has_slant;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_slant
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_slant() const {
+  return (_specified & F_has_slant) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::get_slant
+//       Access: Published
+//  Description: Returns the factor by which the text is specified to
+//               slant to the right.
+////////////////////////////////////////////////////////////////////
+INLINE float TextProperties::
+get_slant() const {
+  return _slant;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_align
+//       Access: Published
+//  Description: Specifies the alignment of the text within its
+//               margins.
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_align(TextProperties::Alignment align_type) {
+  _align = align_type;
+  _specified |= F_has_align;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::clear_align
+//       Access: Published
+//  Description: Restores the default alignment of the text.
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+clear_align() {
+  _align = A_left;
+  _specified &= ~F_has_align;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_align
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_align() const {
+  return (_specified & F_has_align) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::get_align
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE TextProperties::Alignment TextProperties::
+get_align() const {
+  return _align;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_indent
+//       Access: Published
+//  Description: Specifies the amount of extra space that is inserted
+//               before the first character of each line.  This can be
+//               thought of as a left margin.
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_indent(float indent) {
+  _indent_width = indent;
+  _specified |= F_has_indent;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::clear_indent
+//       Access: Published
+//  Description: Removes the indent setting from the text.  Text
+//               will be as wide as it is.
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+clear_indent() {
+  _indent_width = 0.0f;
+  _specified &= ~F_has_indent;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_indent
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_indent() const {
+  return (_specified & F_has_indent) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::get_indent
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE float TextProperties::
+get_indent() const {
+  return _indent_width;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_wordwrap
+//       Access: Published
+//  Description: Sets the text up to automatically wordwrap when it
+//               exceeds the indicated width.  This can be thought of
+//               as a right margin or margin width.
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_wordwrap(float wordwrap) {
+  _wordwrap_width = wordwrap;
+  _specified |= F_has_wordwrap;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::clear_wordwrap
+//       Access: Published
+//  Description: Removes the wordwrap setting from the text.  Text
+//               will be as wide as it is.
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+clear_wordwrap() {
+  _wordwrap_width = 0.0f;
+  _specified &= ~F_has_wordwrap;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_wordwrap
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_wordwrap() const {
+  return (_specified & F_has_wordwrap) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::get_wordwrap
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE float TextProperties::
+get_wordwrap() const {
+  return _wordwrap_width;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_preserve_trailing_whitespace
+//       Access: Published
+//  Description: Sets the preserve_trailing_whitespace flag.  When
+//               this is set, trailing whitespace at the end of the
+//               line is not stripped when the text is wordwrapped (it
+//               is stripped by default).  Since the trailing
+//               whitespace is invisible, this is important primarily
+//               for determining the proper width of a frame or card
+//               behind the text.
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_preserve_trailing_whitespace(bool preserve_trailing_whitespace) {
+  _preserve_trailing_whitespace = preserve_trailing_whitespace;
+  _specified |= F_has_preserve_trailing_whitespace;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::clear_preserve_trailing_whitespace
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+clear_preserve_trailing_whitespace() {
+  _preserve_trailing_whitespace = false;
+  _specified &= ~F_has_preserve_trailing_whitespace;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_preserve_trailing_whitespace
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_preserve_trailing_whitespace() const {
+  return (_specified & F_has_preserve_trailing_whitespace) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::get_preserve_trailing_whitespace
+//       Access: Published
+//  Description: Returns the preserve_trailing_whitespace flag.  See
+//               set_preserve_trailing_whitespace().
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+get_preserve_trailing_whitespace() const {
+  return _preserve_trailing_whitespace;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_text_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_text_color(float r, float g, float b, float a) {
+  set_text_color(Colorf(r, g, b, a));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_text_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_text_color(const Colorf &text_color) {
+  _text_color = text_color;
+  _specified |= F_has_text_color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::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 TextProperties::
+clear_text_color() {
+  _text_color.set(1.0f, 1.0f, 1.0f, 1.0f);
+  _specified &= ~F_has_text_color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_text_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_text_color() const {
+  return (_specified & F_has_text_color) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::get_text_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Colorf TextProperties::
+get_text_color() const {
+  return _text_color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_shadow_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_shadow_color(float r, float g, float b, float a) {
+  set_shadow_color(Colorf(r, g, b, a));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_shadow_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_shadow_color(const Colorf &shadow_color) {
+  _shadow_color = shadow_color;
+  _specified |= F_has_shadow_color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::clear_shadow_color
+//       Access: Published
+//  Description: Removes the shadow color specification.
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+clear_shadow_color() {
+  _shadow_color.set(0.0f, 0.0f, 0.0f, 1.0f);
+  _specified &= ~F_has_shadow_color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_shadow_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_shadow_color() const {
+  return (_specified & F_has_shadow_color) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::get_shadow_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Colorf TextProperties::
+get_shadow_color() const {
+  return _shadow_color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::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 TextProperties::
+set_shadow(float xoffset, float yoffset) {
+  set_shadow(LVecBase2f(xoffset, yoffset));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::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 TextProperties::
+set_shadow(const LVecBase2f &shadow_offset) {
+  _shadow_offset = shadow_offset;
+  _specified |= F_has_shadow;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::clear_shadow
+//       Access: Published
+//  Description: Specifies that a shadow will not be drawn behind the
+//               text.
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+clear_shadow() {
+  _specified &= ~F_has_shadow;
+  _shadow_offset.set(0.0f, 0.0f);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_shadow
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_shadow() const {
+  return (_specified & F_has_shadow) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::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 LVector2f TextProperties::
+get_shadow() const {
+  return _shadow_offset;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_bin
+//       Access: Published
+//  Description: Names the CullBin that the text geometry should be
+//               assigned to.  If this is set, then a CullBinAttrib
+//               will be created to explicitly place each component in
+//               the named bin.
+//
+//               The draw_order value will also be passed to each
+//               CullBinAttrib as appropriate; this is particularly
+//               useful if this names a CullBinFixed, e.g. "fixed".
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_bin(const string &bin) {
+  _bin = bin;
+  _specified |= F_has_bin;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::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 TextProperties::
+clear_bin() {
+  _bin = string();
+  _specified &= ~F_has_bin;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_bin
+//       Access: Published
+//  Description: Returns true if an explicit drawing bin has been
+//               set via set_bin(), false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_bin() const {
+  return (_specified & F_has_bin) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::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 &TextProperties::
+get_bin() const {
+  return _bin;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_draw_order
+//       Access: Published
+//  Description: Sets the drawing order of text created by the
+//               TextNode.  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 nodes
+//               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 TextProperties::
+set_draw_order(int draw_order) {
+  _draw_order = draw_order;
+  _specified |= F_has_draw_order;
+  return _draw_order + 3;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::clear_draw_order
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+clear_draw_order() {
+  _draw_order = 1;
+  _specified &= ~F_has_draw_order;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_draw_order
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_draw_order() const {
+  return (_specified & F_has_draw_order) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::get_draw_order
+//       Access: Published
+//  Description: Returns the drawing order set with set_draw_order().
+////////////////////////////////////////////////////////////////////
+INLINE int TextProperties::
+get_draw_order() const {
+  return _draw_order;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_tab_width
+//       Access: Published
+//  Description: Sets the width of each tab stop, in screen units.  A
+//               tab character embedded in the text will advance the
+//               horizontal position to the next tab stop.
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_tab_width(float tab_width) {
+  _tab_width = tab_width;
+  _specified |= F_has_tab_width;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::clear_tab_width
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+clear_tab_width() {
+  _tab_width = text_tab_width;
+  _specified &= ~F_has_tab_width;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_tab_width
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_tab_width() const {
+  return (_specified & F_has_tab_width) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::get_tab_width
+//       Access: Published
+//  Description: Returns the width set via set_tab_width().
+////////////////////////////////////////////////////////////////////
+INLINE float TextProperties::
+get_tab_width() const {
+  return _tab_width;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_glyph_scale
+//       Access: Published
+//  Description: Specifies the factor by which to scale each letter of
+//               the text as it is placed.  This can be used (possibly
+//               in conjunction with set_glyph_shift()) to implement
+//               superscripting or subscripting.
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_glyph_scale(float glyph_scale) {
+  _glyph_scale = glyph_scale;
+  _specified |= F_has_glyph_scale;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::clear_glyph_scale
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+clear_glyph_scale() {
+  _specified &= ~F_has_glyph_scale;
+  _glyph_scale = 0.0f;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_glyph_scale
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_glyph_scale() const {
+  return (_specified & F_has_glyph_scale) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::get_glyph_scale
+//       Access: Published
+//  Description: Returns the scale factor of each letter as specified
+//               by set_glyph_scale().
+////////////////////////////////////////////////////////////////////
+INLINE float TextProperties::
+get_glyph_scale() const {
+  return _glyph_scale;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::set_glyph_shift
+//       Access: Published
+//  Description: Specifies a vertical amount to shift each letter of
+//               the text as it is placed.  This can be used (possibly
+//               in conjunction with set_glyph_scale()) to implement
+//               superscripting or subscripting.
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+set_glyph_shift(float glyph_shift) {
+  _glyph_shift = glyph_shift;
+  _specified |= F_has_glyph_shift;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::clear_glyph_shift
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void TextProperties::
+clear_glyph_shift() {
+  _specified &= ~F_has_glyph_shift;
+  _glyph_shift = 0.0f;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::has_glyph_shift
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool TextProperties::
+has_glyph_shift() const {
+  return (_specified & F_has_glyph_shift) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::get_glyph_shift
+//       Access: Published
+//  Description: Returns the vertical shift of each letter as
+//               specified by set_glyph_shift().
+////////////////////////////////////////////////////////////////////
+INLINE float TextProperties::
+get_glyph_shift() const {
+  return _glyph_shift;
+}

+ 318 - 0
panda/src/text/textProperties.cxx

@@ -0,0 +1,318 @@
+// Filename: textProperties.cxx
+// Created by:  drose (06Apr04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "textProperties.h"
+#include "config_text.h"
+#include "default_font.h"
+#include "dynamicTextFont.h"
+#include "staticTextFont.h"
+#include "bamFile.h"
+#include "fontPool.h"
+
+PT(TextFont) TextProperties::_default_font;
+bool TextProperties::_loaded_default_font = false;
+
+TypeHandle TextProperties::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+TextProperties::
+TextProperties() {
+  _specified = 0;
+
+  _small_caps = false;
+  _small_caps_scale = text_small_caps_scale;
+  _slant = 0.0f;
+  _align = A_left;
+  _indent_width = 0.0f;
+  _wordwrap_width = 0.0f;
+  _preserve_trailing_whitespace = false;
+  _text_color.set(1.0f, 1.0f, 1.0f, 1.0f);
+  _shadow_color.set(1.0f, 1.0f, 1.0f, 1.0f);
+  _shadow_offset.set(0.0f, 0.0f);
+  _draw_order = 1;
+  _tab_width = text_tab_width;
+  _glyph_scale = 1.0f;
+  _glyph_shift = 0.0f;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::Copy Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+TextProperties::
+TextProperties(const TextProperties &copy) {
+  (*this) = copy;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::Copy Assignment Operator
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+void TextProperties::
+operator = (const TextProperties &copy) {
+  _specified = copy._specified;
+
+  _font = copy._font;
+  _small_caps = copy._small_caps;
+  _small_caps_scale = copy._small_caps_scale;
+  _slant = copy._slant;
+  _align = copy._align;
+  _indent_width = copy._indent_width;
+  _wordwrap_width = copy._wordwrap_width;
+  _preserve_trailing_whitespace = copy._preserve_trailing_whitespace;
+  _text_color = copy._text_color;
+  _shadow_color = copy._shadow_color;
+  _shadow_offset = copy._shadow_offset;
+  _bin = copy._bin;
+  _draw_order = copy._draw_order;
+  _tab_width = copy._tab_width;
+  _glyph_scale = copy._glyph_scale;
+  _glyph_shift = copy._glyph_shift;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::clear
+//       Access: Published
+//  Description: Unsets all properties that have been specified so
+//               far, and resets the TextProperties structure to its
+//               initial empty state.
+////////////////////////////////////////////////////////////////////
+void TextProperties::
+clear() {
+  (*this) = TextProperties();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::add_properties
+//       Access: Published
+//  Description: Sets any properties that are explicitly specified in
+//               other on this object.  Leaves other properties
+//               unchanged.
+////////////////////////////////////////////////////////////////////
+void TextProperties::
+add_properties(const TextProperties &other) {
+  if (other.has_font()) {
+    set_font(other.get_font());
+  }
+  if (other.has_small_caps()) {
+    set_small_caps(other.get_small_caps());
+    set_small_caps_scale(other.get_small_caps_scale());
+  }
+  if (other.has_slant()) {
+    set_slant(other.get_slant());
+  }
+  if (other.has_align()) {
+    set_align(other.get_align());
+  }
+  if (other.has_indent()) {
+    set_indent(other.get_indent());
+  }
+  if (other.has_wordwrap()) {
+    set_wordwrap(other.get_wordwrap());
+  }
+  if (other.has_text_color()) {
+    set_text_color(other.get_text_color());
+  }
+  if (other.has_shadow_color()) {
+    set_shadow_color(other.get_shadow_color());
+  }
+  if (other.has_shadow()) {
+    set_shadow(other.get_shadow());
+  }
+  if (other.has_bin()) {
+    set_bin(other.get_bin());
+  }
+  if (other.has_draw_order()) {
+    set_draw_order(other.get_draw_order());
+  }
+  if (other.has_tab_width()) {
+    set_tab_width(other.get_tab_width());
+  }
+
+
+  // The glyph scale and shift are a special case: rather than
+  // replacing the previous value, they modify it.
+  if (other.has_glyph_shift()) {
+    set_glyph_shift(other.get_glyph_shift() * get_glyph_scale() + get_glyph_shift());
+  }
+  if (other.has_glyph_scale()) {
+    set_glyph_scale(other.get_glyph_scale() * get_glyph_scale());
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::write
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+void TextProperties::
+write(ostream &out, int indent_level) const {
+  if (!is_any_specified()) {
+    indent(out, indent_level)
+      << "default properties\n";
+  }
+  if (has_font()) {
+    if (get_font() != (TextFont *)NULL) {
+      indent(out, indent_level)
+        << "with font " << _font->get_name() << "\n";
+    } else {
+      indent(out, indent_level)
+        << "with NULL font\n";
+    }
+  }
+  if (has_small_caps()) {
+    indent(out, indent_level)
+      << "small caps = " << get_small_caps() << "\n";
+  }
+  if (has_small_caps_scale()) {
+    indent(out, indent_level)
+      << "small caps scale = " << get_small_caps_scale() << "\n";
+  }
+  if (has_slant()) {
+    indent(out, indent_level)
+      << "slant = " << get_slant() << "\n";
+  }
+
+  if (has_align()) {
+    indent(out, indent_level)
+      << "alignment is ";
+    switch (get_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_indent()) {
+    indent(out, indent_level)
+      << "indent at " << get_indent() << " units.\n";
+  }
+
+  if (has_wordwrap()) {
+    indent(out, indent_level)
+      << "word-wrapping at " << get_wordwrap() << " units.\n";
+  }
+
+  if (has_text_color()) {
+    indent(out, indent_level)
+      << "text color is " << get_text_color() << "\n";
+  }
+
+  if (has_shadow()) {
+    indent(out, indent_level)
+      << "shadow at " << get_shadow() << "\n";
+  }
+  if (has_shadow_color()) {
+    indent(out, indent_level)
+      << "shadow color is " << get_shadow_color() << "\n";
+  }
+
+  if (has_bin()) {
+    indent(out, indent_level)
+      << "bin is " << get_bin() << "\n";
+  }
+  if (has_draw_order()) {
+    indent(out, indent_level)
+      << "draw order is " << get_draw_order() << "\n";
+  }
+
+  if (has_tab_width()) {
+    indent(out, indent_level)
+      << "tab width is " << get_tab_width() << "\n";
+  }
+
+  if (has_glyph_scale()) {
+    indent(out, indent_level)
+      << "glyph scale is " << get_glyph_scale() << "\n";
+  }
+  if (has_glyph_shift()) {
+    indent(out, indent_level)
+      << "glyph shift is " << get_glyph_shift() << "\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextProperties::load_default_font
+//       Access: Private, Static
+//  Description: This function is called once (or never), the first
+//               time someone attempts to render a TextNode using the
+//               default font.  It should attempt to load the default
+//               font, using the compiled-in version if it is
+//               available, or whatever system file may be named in
+//               Configrc.
+////////////////////////////////////////////////////////////////////
+void TextProperties::
+load_default_font() {
+  _loaded_default_font = true;
+
+  if (!text_default_font.empty()) {
+    // First, attempt to load the user-specified filename.
+    _default_font = FontPool::load_font(text_default_font);
+    if (_default_font->is_valid()) {
+      return;
+    }
+  }
+
+  // Then, attempt to load the compiled-in font, if we have one.
+#ifdef COMPILE_IN_DEFAULT_FONT
+#ifdef HAVE_FREETYPE
+  // Loading the compiled-in FreeType font is relatively easy.
+  _default_font = new DynamicTextFont((const char *)default_font_data, 
+                                      default_font_size, 0);
+
+#else
+  // The compiled-in Bam font requires creating a BamFile object to
+  // decode it.
+  string data((const char *)default_font_data, default_font_size);
+
+#ifdef HAVE_ZLIB
+  // The font data is stored compressed; decompress it on-the-fly.
+  istringstream inz(data);
+  IDecompressStream in(&inz, false);
+  
+#else
+  // The font data is stored uncompressed, so just load it.
+  istringstream in(data);
+#endif  // HAVE_ZLIB
+  
+  BamFile bam_file;
+  if (bam_file.open_read(in, "default font stream")) {
+    PT(PandaNode) node = bam_file.read_node();
+    if (node != (PandaNode *)NULL) {
+      _default_font = new StaticTextFont(node);
+    }
+  }
+  
+#endif  // HAVE_FREETYPE
+#endif  // COMPILE_IN_DEFAULT_FONT
+}

+ 213 - 0
panda/src/text/textProperties.h

@@ -0,0 +1,213 @@
+// Filename: textProperties.h
+// Created by:  drose (06Apr04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 TEXTPROPERTIES_H
+#define TEXTPROPERTIES_H
+
+#include "pandabase.h"
+
+#include "config_text.h"
+#include "luse.h"
+#include "textFont.h"
+#include "pointerTo.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : TextProperties
+// Description : This defines the set of visual properties that may be
+//               assigned to the individual characters of the text.
+//               (Properties which affect the overall block of text
+//               can only be specified on the TextNode directly).
+//
+//               Typically, there is just one set of properties on a
+//               given block of text, which is set directly on the
+//               TextNode (TextNode inherits from TextProperties).
+//               That makes all of the text within a particular block
+//               have the same appearance.
+//
+//               This separate class exists in order to implement
+//               multiple different kinds of text appearing within one
+//               block.  The text string itself may reference a
+//               TextProperties structure by name using the \1 and \2
+//               tokens embedded within the string; each nested
+//               TextProperties structure modifies the appearance of
+//               subsequent text within the block.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA TextProperties {
+PUBLISHED:
+  enum Alignment {
+    A_left,
+    A_right,
+    A_center,
+  };
+
+  TextProperties();
+  TextProperties(const TextProperties &copy);
+  void operator = (const TextProperties &copy);
+
+  void clear();
+  INLINE bool is_any_specified() const;
+
+  INLINE static void set_default_font(TextFont *);
+  INLINE static TextFont *get_default_font();
+
+  INLINE void set_font(TextFont *font);
+  INLINE void clear_font();
+  INLINE bool has_font() const;
+  INLINE TextFont *get_font() const;
+
+  INLINE void set_small_caps(bool small_caps);
+  INLINE void clear_small_caps();
+  INLINE bool has_small_caps() const;
+  INLINE bool get_small_caps() const;
+
+  INLINE void set_small_caps_scale(float small_caps_scale);
+  INLINE void clear_small_caps_scale();
+  INLINE bool has_small_caps_scale() const;
+  INLINE float get_small_caps_scale() const;
+
+  INLINE void set_slant(float slant);
+  INLINE void clear_slant();
+  INLINE bool has_slant() const;
+  INLINE float get_slant() const;
+
+  INLINE void set_align(Alignment align_type);
+  INLINE void clear_align();
+  INLINE bool has_align() const;
+  INLINE Alignment get_align() const;
+
+  INLINE void set_indent(float indent);
+  INLINE void clear_indent();
+  INLINE bool has_indent() const;
+  INLINE float get_indent() const;
+
+  INLINE void set_wordwrap(float wordwrap);
+  INLINE void clear_wordwrap();
+  INLINE bool has_wordwrap() const;
+  INLINE float get_wordwrap() const;
+
+  INLINE void set_preserve_trailing_whitespace(bool preserve_trailing_whitespace);
+  INLINE void clear_preserve_trailing_whitespace();
+  INLINE bool has_preserve_trailing_whitespace() const;
+  INLINE bool get_preserve_trailing_whitespace() 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_shadow_color(float r, float g, float b, float a);
+  INLINE void set_shadow_color(const Colorf &shadow_color);
+  INLINE void clear_shadow_color();
+  INLINE bool has_shadow_color() const;
+  INLINE Colorf get_shadow_color() const;
+
+  INLINE void set_shadow(float xoffset, float yoffset);
+  INLINE void set_shadow(const LVecBase2f &shadow_offset);
+  INLINE void clear_shadow();
+  INLINE bool has_shadow() const;
+  INLINE LVector2f 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 void clear_draw_order();
+  INLINE bool has_draw_order() const;
+  INLINE int get_draw_order() const;
+
+  INLINE void set_tab_width(float tab_width);
+  INLINE void clear_tab_width();
+  INLINE bool has_tab_width() const;
+  INLINE float get_tab_width() const;
+
+  INLINE void set_glyph_scale(float glyph_scale);
+  INLINE void clear_glyph_scale();
+  INLINE bool has_glyph_scale() const;
+  INLINE float get_glyph_scale() const;
+
+  INLINE void set_glyph_shift(float glyph_shift);
+  INLINE void clear_glyph_shift();
+  INLINE bool has_glyph_shift() const;
+  INLINE float get_glyph_shift() const;
+
+  void add_properties(const TextProperties &other);
+
+  void write(ostream &out, int indent_level = 0) const;
+
+private:
+  static void load_default_font();
+
+  enum Flags {
+    F_has_font                         = 0x0001,
+    F_has_small_caps                   = 0x0002,
+    F_has_small_caps_scale             = 0x0004,
+    F_has_slant                        = 0x0008,
+    F_has_align                        = 0x0010,
+    F_has_indent                       = 0x0020,
+    F_has_wordwrap                     = 0x0040,
+    F_has_preserve_trailing_whitespace = 0x0080,
+    F_has_text_color                   = 0x0100,
+    F_has_shadow_color                 = 0x0200,
+    F_has_shadow                       = 0x0400,
+    F_has_bin                          = 0x0800,
+    F_has_draw_order                   = 0x1000,
+    F_has_tab_width                    = 0x2000,
+    F_has_glyph_scale                  = 0x4000,
+    F_has_glyph_shift                  = 0x8000,
+  };
+
+  int _specified;
+
+  PT(TextFont) _font;
+  bool _small_caps;
+  float _small_caps_scale;
+  float _slant;
+  Alignment _align;
+  float _indent_width;
+  float _wordwrap_width;
+  bool _preserve_trailing_whitespace;
+  Colorf _text_color;
+  Colorf _shadow_color;
+  LVector2f _shadow_offset;
+  string _bin;
+  int _draw_order;
+  float _tab_width;
+  float _glyph_scale;
+  float _glyph_shift;
+
+  static PT(TextFont) _default_font;
+  static bool _loaded_default_font;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    register_type(_type_handle, "TextProperties");
+  }
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "textProperties.I"
+
+#endif

+ 18 - 0
panda/src/text/textPropertiesManager.I

@@ -0,0 +1,18 @@
+// Filename: textPropertiesManager.I
+// Created by:  drose (07Apr04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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] .
+//
+////////////////////////////////////////////////////////////////////
+

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

@@ -0,0 +1,148 @@
+// Filename: textPropertiesManager.cxx
+// Created by:  drose (07Apr04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "textPropertiesManager.h"
+#include "indent.h"
+
+TextPropertiesManager *TextPropertiesManager::_global_ptr = (TextPropertiesManager *)NULL;
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextPropertiesManager::Constructor
+//       Access: Protected
+//  Description: The constructor is not intended to be called
+//               directly; there is only one TextPropertiesManager and
+//               it constructs itself.  This could have been a private
+//               constructor, but gcc issues a spurious warning if the
+//               constructor is private and the class has no friends.
+////////////////////////////////////////////////////////////////////
+TextPropertiesManager::
+TextPropertiesManager() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextPropertiesManager::Destructor
+//       Access: Protected
+//  Description: Don't call the destructor.
+////////////////////////////////////////////////////////////////////
+TextPropertiesManager::
+~TextPropertiesManager() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextPropertiesManager::set_properties
+//       Access: Published
+//  Description: Defines the TextProperties associated with the
+//               indicated name.  When the name is subsequently
+//               encountered in text embedded between \1 characters in
+//               a TextNode string, the following text will be
+//               rendered with these properties.
+//
+//               If there was already a TextProperties structure
+//               associated with this name, it is quietly replaced
+//               with the new definition.
+////////////////////////////////////////////////////////////////////
+void TextPropertiesManager::
+set_properties(const string &name, const TextProperties &properties) {
+  _properties[name] = properties;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextPropertiesManager::get_properties
+//       Access: Published
+//  Description: Returns the TextProperties associated with the
+//               indicated name.  If there was not previously a
+//               TextProperties associated with this name, a warning
+//               is printed and then a default TextProperties
+//               structure is associated with the name, and returned.
+//
+//               Call has_properties() instead to check whether a
+//               particular name has been defined.
+////////////////////////////////////////////////////////////////////
+TextProperties TextPropertiesManager::
+get_properties(const string &name) {
+  Properties::const_iterator pi;
+  pi = _properties.find(name);
+  if (pi != _properties.end()) {
+    return (*pi).second;
+  }
+
+  text_cat.warning()
+    << "Creating default TextProperties for name '" << name << "'\n";
+
+  TextProperties default_properties;
+  _properties[name] = default_properties;
+  return default_properties;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextPropertiesManager::has_properties
+//       Access: Published
+//  Description: Returns true if a TextProperties structure has been
+//               associated with the indicated name, false otherwise.
+//               Normally this means set_properties() has been called
+//               with this name, but because get_properties() will
+//               implicitly create a default TextProperties structure,
+//               it may also mean simply that get_properties() has
+//               been called with the indicated name.
+////////////////////////////////////////////////////////////////////
+bool TextPropertiesManager::
+has_properties(const string &name) const {
+  Properties::const_iterator pi;
+  pi = _properties.find(name);
+  return (pi != _properties.end());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextPropertiesManager::clear_properties
+//       Access: Published
+//  Description: Removes the named TextProperties structure from the
+//               manager.
+////////////////////////////////////////////////////////////////////
+void TextPropertiesManager::
+clear_properties(const string &name) {
+  _properties.erase(name);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextPropertiesManager::write
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void TextPropertiesManager::
+write(ostream &out, int indent_level) const {
+  Properties::const_iterator pi;
+  for (pi = _properties.begin(); pi != _properties.end(); ++pi) {
+    indent(out, indent_level)
+      << "TextProperties " << (*pi).first << ":\n";
+    (*pi).second.write(out, indent_level + 2);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextPropertiesManager::get_global_ptr
+//       Access: Published, Static
+//  Description: Returns the pointer to the global TextPropertiesManager
+//               object.
+////////////////////////////////////////////////////////////////////
+TextPropertiesManager *TextPropertiesManager::
+get_global_ptr() {
+  if (_global_ptr == (TextPropertiesManager *)NULL) {
+    _global_ptr = new TextPropertiesManager;
+  }
+  return _global_ptr;
+}

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

@@ -0,0 +1,72 @@
+// Filename: textPropertiesManager.h
+// Created by:  drose (07Apr04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 TEXTPROPERTIESMANAGER_H
+#define TEXTPROPERTIESMANAGER_H
+
+#include "pandabase.h"
+
+#include "config_text.h"
+#include "textProperties.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : TextPropertiesManager
+// Description : This defines all of the TextProperties structures
+//               that might be referenced by name from an embedded
+//               text string.
+//
+//               A text string, as rendered by a TextNode, can contain
+//               embedded references to one of the TextProperties
+//               defined here, by enclosing the name between \1 (ASCII
+//               0x01) characters; this causes a "push" to the named
+//               state.  All text following the closing \1 character
+//               will then be rendered in the new state.  The next \2
+//               (ASCII 0x02) character will then restore the previous
+//               state for subsequent text.
+//
+//               For instance, "x\1up\1n\2 + y" indicates that the
+//               character "x" will be rendered in the normal state,
+//               the character "n" will be rendered in the "up" state,
+//               and then " + y" will be rendered in the normal state
+//               again.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA TextPropertiesManager {
+protected:
+  TextPropertiesManager();
+  ~TextPropertiesManager();
+
+PUBLISHED:
+  void set_properties(const string &name, const TextProperties &properties);
+  TextProperties get_properties(const string &name);
+  bool has_properties(const string &name) const;
+  void clear_properties(const string &name);
+
+  void write(ostream &out, int indent_level = 0) const;
+
+  static TextPropertiesManager *get_global_ptr();
+
+private:
+  typedef pmap<string, TextProperties> Properties;
+  Properties _properties;
+
+  static TextPropertiesManager *_global_ptr;
+};
+
+#include "textPropertiesManager.I"
+
+#endif

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

@@ -6,5 +6,3 @@
 #include "fontPool.cxx"
 #include "geomTextGlyph.cxx"
 #include "staticTextFont.cxx"
-#include "textFont.cxx"
-#include "textGlyph.cxx"

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

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

Some files were not shown because too many files changed in this diff