Panda3D

textAssembler.cxx

00001 // Filename: textAssembler.cxx
00002 // Created by:  drose (06Apr04)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "textAssembler.h"
00016 #include "textGlyph.h"
00017 #include "cullFaceAttrib.h"
00018 #include "colorAttrib.h"
00019 #include "cullBinAttrib.h"
00020 #include "textureAttrib.h"
00021 #include "transparencyAttrib.h"
00022 #include "textPropertiesManager.h"
00023 #include "textEncoder.h"
00024 #include "config_text.h"
00025 #include "geomTriangles.h"
00026 #include "geomLines.h"
00027 #include "geomPoints.h"
00028 #include "geomVertexReader.h"
00029 #include "geomVertexWriter.h"
00030 #include "geomLines.h"
00031 #include "geomVertexFormat.h"
00032 #include "geomVertexData.h"
00033 #include "geom.h"
00034 #include "modelNode.h"
00035 
00036 #include <ctype.h>
00037 #include <stdio.h>  // for sprintf
00038   
00039 // This is the factor by which CT_small scales the character down.
00040 static const float small_accent_scale = 0.6f;
00041 
00042 // This is the factor by which CT_tiny scales the character down.
00043 static const float tiny_accent_scale = 0.4f;
00044 
00045 // This is the factor by which CT_squash scales the character in X and Y.
00046 static const float squash_accent_scale_x = 0.8f;
00047 static const float squash_accent_scale_y = 0.5f;
00048 
00049 // This is the factor by which CT_small_squash scales the character in X and Y.
00050 static const float small_squash_accent_scale_x = 0.6f;
00051 static const float small_squash_accent_scale_y = 0.3f;
00052 
00053 // This is the factor by which the advance is reduced for the first
00054 // character of a two-character ligature.
00055 static const float ligature_advance_scale = 0.6f;
00056 
00057 
00058 ////////////////////////////////////////////////////////////////////
00059 //     Function: isspacew
00060 //  Description: An internal function that works like isspace() but is
00061 //               safe to call for a wide character.
00062 ////////////////////////////////////////////////////////////////////
00063 static INLINE bool
00064 isspacew(unsigned int ch) {
00065   return isascii(ch) && isspace(ch);
00066 }
00067 
00068 ////////////////////////////////////////////////////////////////////
00069 //     Function: isbreakpoint
00070 //  Description: An internal function, similar to isspace(), except it
00071 //               does not consider newlines to be whitespace.  It also
00072 //               includes the soft-hyphen character.
00073 ////////////////////////////////////////////////////////////////////
00074 static INLINE bool
00075 isbreakpoint(unsigned int ch) {
00076   return (ch == ' ' || ch == '\t' || 
00077           ch == (unsigned int)text_soft_hyphen_key ||
00078           ch == (unsigned int)text_soft_break_key);
00079 }
00080 
00081 
00082 ////////////////////////////////////////////////////////////////////
00083 //     Function: TextAssembler::Constructor
00084 //       Access: Published
00085 //  Description: 
00086 ////////////////////////////////////////////////////////////////////
00087 TextAssembler::
00088 TextAssembler(TextEncoder *encoder) : 
00089   _encoder(encoder),
00090   _usage_hint(Geom::UH_static),
00091   _max_rows(0),
00092   _dynamic_merge(text_dynamic_merge),
00093   _multiline_mode(true)
00094 {
00095   _initial_cprops = new ComputedProperties(TextProperties());
00096   clear();
00097 }
00098 
00099 ////////////////////////////////////////////////////////////////////
00100 //     Function: TextAssembler::Copy Constructor
00101 //       Access: Published
00102 //  Description: 
00103 ////////////////////////////////////////////////////////////////////
00104 TextAssembler::
00105 TextAssembler(const TextAssembler &copy) :
00106   _initial_cprops(copy._initial_cprops),
00107   _text_string(copy._text_string),
00108   _text_block(copy._text_block),
00109   _ul(copy._ul),
00110   _lr(copy._lr),
00111   _next_row_ypos(copy._next_row_ypos),
00112   _encoder(copy._encoder),
00113   _usage_hint(copy._usage_hint),
00114   _max_rows(copy._max_rows),
00115   _dynamic_merge(copy._dynamic_merge),
00116   _multiline_mode(copy._multiline_mode)
00117 {
00118 }
00119 
00120 ////////////////////////////////////////////////////////////////////
00121 //     Function: TextAssembler::Copy Assignment Operator
00122 //       Access: Published
00123 //  Description: 
00124 ////////////////////////////////////////////////////////////////////
00125 void TextAssembler::
00126 operator = (const TextAssembler &copy) {
00127   _initial_cprops = copy._initial_cprops;
00128   _text_string = copy._text_string;
00129   _text_block = copy._text_block;
00130   _ul = copy._ul;
00131   _lr = copy._lr;
00132   _next_row_ypos = copy._next_row_ypos;
00133   _encoder = copy._encoder;
00134   _usage_hint = copy._usage_hint;
00135   _max_rows = copy._max_rows;
00136   _dynamic_merge = copy._dynamic_merge;
00137   _multiline_mode = copy._multiline_mode;
00138 }
00139 
00140 ////////////////////////////////////////////////////////////////////
00141 //     Function: TextAssembler::Destructor
00142 //       Access: Published
00143 //  Description: 
00144 ////////////////////////////////////////////////////////////////////
00145 TextAssembler::
00146 ~TextAssembler() {
00147 }
00148 
00149 ////////////////////////////////////////////////////////////////////
00150 //     Function: TextAssembler::clear
00151 //       Access: Published
00152 //  Description: Reinitializes the contents of the TextAssembler.
00153 ////////////////////////////////////////////////////////////////////
00154 void TextAssembler::
00155 clear() {
00156   _ul.set(0.0f, 0.0f);
00157   _lr.set(0.0f, 0.0f);
00158   _next_row_ypos = 0.0f;
00159 
00160   _text_string.clear();
00161   _text_block.clear();
00162 }
00163 
00164 ////////////////////////////////////////////////////////////////////
00165 //     Function: TextAssembler::set_wtext
00166 //       Access: Published
00167 //  Description: Accepts a new text string and associated properties
00168 //               structure, and precomputes the wordwrapping layout
00169 //               appropriately.  After this call,
00170 //               get_wordwrapped_wtext() and get_num_rows() can be
00171 //               called.
00172 //
00173 //               The return value is true if all the text is accepted,
00174 //               or false if some was truncated (see set_max_rows()).
00175 ////////////////////////////////////////////////////////////////////
00176 bool TextAssembler::
00177 set_wtext(const wstring &wtext) {
00178   clear();
00179 
00180   // First, expand all of the embedded TextProperties references
00181   // within the string.
00182   wstring::const_iterator si = wtext.begin();
00183   scan_wtext(_text_string, si, wtext.end(), _initial_cprops);
00184 
00185   while (si != wtext.end()) {
00186     // If we returned without consuming the whole string, it means
00187     // there was an embedded text_pop_properties_key that didn't match
00188     // the push.  That's worth a warning, and then go back and pick up
00189     // the rest of the string.
00190     text_cat.warning()
00191       << "pop_properties encountered without preceding push_properties.\n";
00192     scan_wtext(_text_string, si, wtext.end(), _initial_cprops);
00193   }
00194 
00195   // Then apply any wordwrap requirements.
00196   return wordwrap_text();
00197 }
00198 
00199 ////////////////////////////////////////////////////////////////////
00200 //     Function: TextAssembler::set_wsubstr
00201 //       Access: Published
00202 //  Description: Replaces the 'count' characters from 'start' of the
00203 //               current text with the indicated replacement text.  If
00204 //               the replacement text does not have count characters,
00205 //               the length of the string will be changed accordingly.
00206 //
00207 //               The substring may include nested formatting
00208 //               characters, but they must be self-contained and
00209 //               self-closed.  The formatting characters are not
00210 //               literally saved in the internal string; they are
00211 //               parsed at the time of the set_wsubstr() call.
00212 //
00213 //               The return value is true if all the text is accepted,
00214 //               or false if some was truncated (see set_max_rows()).
00215 ////////////////////////////////////////////////////////////////////
00216 bool TextAssembler::
00217 set_wsubstr(const wstring &wtext, int start, int count) {
00218   nassertr(start >= 0 && start <= (int)_text_string.size(), false);
00219   nassertr(count >= 0 && start + count <= (int)_text_string.size(), false);
00220 
00221   // Use scan_wtext to unroll the substring we wish to insert, as in
00222   // set_wtext(), above.
00223   TextString substr;
00224   wstring::const_iterator si = wtext.begin();
00225   scan_wtext(substr, si, wtext.end(), _initial_cprops);
00226   while (si != wtext.end()) {
00227     text_cat.warning()
00228       << "pop_properties encountered without preceding push_properties.\n";
00229     scan_wtext(substr, si, wtext.end(), _initial_cprops);
00230   }
00231 
00232   _text_string.erase(_text_string.begin() + start, _text_string.begin() + start + count);
00233   _text_string.insert(_text_string.begin() + start, substr.begin(), substr.end());
00234 
00235   return wordwrap_text();
00236 }
00237 
00238 ////////////////////////////////////////////////////////////////////
00239 //     Function: TextAssembler::get_plain_wtext
00240 //       Access: Published
00241 //  Description: Returns a wstring that represents the contents of the
00242 //               text, without any embedded properties characters.  If
00243 //               there is an embedded graphic object, a zero value is
00244 //               inserted in that position.
00245 //
00246 //               This string has the same length as
00247 //               get_num_characters(), and the characters in this
00248 //               string correspond one-to-one with the characters
00249 //               returned by get_character(n).
00250 ////////////////////////////////////////////////////////////////////
00251 wstring TextAssembler::
00252 get_plain_wtext() const {
00253   wstring wtext;
00254 
00255   TextString::const_iterator si;
00256   for (si = _text_string.begin(); si != _text_string.end(); ++si) {
00257     const TextCharacter &tch = (*si);
00258     if (tch._graphic == (TextGraphic *)NULL) {
00259       wtext += tch._character;
00260     } else {
00261       wtext.push_back(0);
00262     }
00263   }
00264   
00265   return wtext;
00266 }
00267 
00268 ////////////////////////////////////////////////////////////////////
00269 //     Function: TextAssembler::get_wordwrapped_plain_wtext
00270 //       Access: Published
00271 //  Description: Returns a wstring that represents the contents of the
00272 //               text, with newlines inserted according to the
00273 //               wordwrapping.  The string will contain no embedded
00274 //               properties characters.  If there is an embedded
00275 //               graphic object, a zero value is inserted in that
00276 //               position.
00277 //
00278 //               This string has the same number of newline characters
00279 //               as get_num_rows(), and the characters in this string
00280 //               correspond one-to-one with the characters returned by
00281 //               get_character(r, c).
00282 ////////////////////////////////////////////////////////////////////
00283 wstring TextAssembler::
00284 get_wordwrapped_plain_wtext() const {
00285   wstring wtext;
00286 
00287   TextBlock::const_iterator bi;
00288   for (bi = _text_block.begin(); bi != _text_block.end(); ++bi) {
00289     const TextRow &row = (*bi);
00290     if (bi != _text_block.begin()) {
00291       wtext += '\n';
00292     }
00293       
00294     TextString::const_iterator si;
00295     for (si = row._string.begin(); si != row._string.end(); ++si) {
00296       const TextCharacter &tch = (*si);
00297       if (tch._graphic == (TextGraphic *)NULL) {
00298         wtext += tch._character;
00299       } else {
00300         wtext.push_back(0);
00301       }
00302     }
00303   }
00304 
00305   return wtext;
00306 }
00307 
00308 ////////////////////////////////////////////////////////////////////
00309 //     Function: TextAssembler::get_wtext
00310 //       Access: Published
00311 //  Description: Returns a wstring that represents the contents of the
00312 //               text.
00313 //
00314 //               The string will contain embedded properties
00315 //               characters, which may not exactly match the embedded
00316 //               properties characters of the original string, but it
00317 //               will encode the same way.
00318 ////////////////////////////////////////////////////////////////////
00319 wstring TextAssembler::
00320 get_wtext() const {
00321   wstring wtext;
00322   PT(ComputedProperties) current_cprops = _initial_cprops;
00323 
00324   TextString::const_iterator si;
00325   for (si = _text_string.begin(); si != _text_string.end(); ++si) {
00326     const TextCharacter &tch = (*si);
00327     current_cprops->append_delta(wtext, tch._cprops);
00328     if (tch._graphic == (TextGraphic *)NULL) {
00329       wtext += tch._character;
00330     } else {
00331       wtext.push_back(text_embed_graphic_key);
00332       wtext += tch._graphic_wname;
00333       wtext.push_back(text_embed_graphic_key);
00334     }
00335     current_cprops = tch._cprops;
00336   }
00337   current_cprops->append_delta(wtext, _initial_cprops);
00338 
00339   return wtext;
00340 }
00341 
00342 ////////////////////////////////////////////////////////////////////
00343 //     Function: TextAssembler::get_wordwrapped_wtext
00344 //       Access: Published
00345 //  Description: Returns a wstring that represents the contents of the
00346 //               text, with newlines inserted according to the
00347 //               wordwrapping.
00348 //
00349 //               The string will contain embedded properties
00350 //               characters, which may not exactly match the embedded
00351 //               properties characters of the original string, but it
00352 //               will encode the same way.
00353 //
00354 //               Embedded properties characters will be closed before
00355 //               every newline, then reopened (if necessary) on the
00356 //               subsequent character following the newline.  This
00357 //               means it will be safe to divide the text up at the
00358 //               newline characters and treat each line as an
00359 //               independent piece.
00360 ////////////////////////////////////////////////////////////////////
00361 wstring TextAssembler::
00362 get_wordwrapped_wtext() const {
00363   wstring wtext;
00364 
00365   PT(ComputedProperties) current_cprops = _initial_cprops;
00366   
00367   TextBlock::const_iterator bi;
00368   for (bi = _text_block.begin(); bi != _text_block.end(); ++bi) {
00369     const TextRow &row = (*bi);
00370     if (bi != _text_block.begin()) {
00371       current_cprops->append_delta(wtext, _initial_cprops);
00372       current_cprops = _initial_cprops;
00373       wtext += '\n';
00374     }
00375     
00376     TextString::const_iterator si;
00377     for (si = row._string.begin(); si != row._string.end(); ++si) {
00378       const TextCharacter &tch = (*si);
00379       current_cprops->append_delta(wtext, tch._cprops);
00380       if (tch._graphic == (TextGraphic *)NULL) {
00381         wtext += tch._character;
00382       } else {
00383         wtext.push_back(text_embed_graphic_key);
00384         wtext += tch._graphic_wname;
00385         wtext.push_back(text_embed_graphic_key);
00386       }
00387       current_cprops = tch._cprops;
00388     }
00389   }
00390   current_cprops->append_delta(wtext, _initial_cprops);
00391 
00392   return wtext;
00393 }
00394 
00395 ////////////////////////////////////////////////////////////////////
00396 //     Function: TextAssembler::calc_r_c
00397 //       Access: Published
00398 //  Description: Computes the row and column index of the nth
00399 //               character or graphic object in the text.  Fills r and
00400 //               c accordingly.
00401 //
00402 //               Returns true if the nth character is valid and has a
00403 //               corresponding r and c position, false otherwise (for
00404 //               instance, a soft-hyphen character, or a newline
00405 //               character, may not have a corresponding position).
00406 //               In either case, r and c will be filled in sensibly.
00407 ////////////////////////////////////////////////////////////////////
00408 bool TextAssembler::
00409 calc_r_c(int &r, int &c, int n) const {
00410   nassertr(n >= 0 && n <= (int)_text_string.size(), false);
00411 
00412   if (n == (int)_text_string.size()) {
00413     // A special case for one past the last character.
00414     if (_text_string.empty()) {
00415       r = 0;
00416       c = 0;
00417     } else {
00418       r = _text_block.size() - 1;
00419       c = _text_block[r]._string.size();
00420     }
00421     return true;
00422 
00423   } else if (n == 0) {
00424     // Another special case for the beginning.
00425     r = 0;
00426     c = 0;
00427     return true;
00428   }
00429 
00430   r = 0;
00431   while (r + 1 < (int)_text_block.size() &&
00432          _text_block[r + 1]._row_start < n) {
00433     r += 1;
00434   }
00435 
00436   const TextRow &row = _text_block[r];
00437   bool is_real_char = true;
00438 
00439   nassertr(n > 0, false);
00440   if (row._got_soft_hyphens) {
00441     // If there are any soft hyphen or soft break keys in the source
00442     // text, we have to scan past them to get c precisely.
00443     c = 0;
00444     int i = row._row_start;
00445     while (i < n - 1) {
00446       if (_text_string[i]._character != text_soft_hyphen_key && 
00447           _text_string[i]._character != text_soft_break_key) {
00448         ++c;
00449       }
00450       ++i;
00451     }
00452     if (_text_string[n - 1]._character != text_soft_hyphen_key && 
00453         _text_string[n - 1]._character != text_soft_break_key) {
00454       ++c;
00455       if (_text_string[n - 1]._character == '\n') {
00456         is_real_char = false;
00457       }
00458     } else {
00459       is_real_char = false;
00460     }
00461 
00462   } else {
00463     // If there are no soft characters, then the string maps
00464     // one-to-one.
00465     c = min(n - row._row_start, (int)row._string.size());
00466     if (_text_string[n - 1]._character == '\n') {
00467       is_real_char = false;
00468     }
00469   }
00470 
00471   return is_real_char;
00472 }
00473 
00474 ////////////////////////////////////////////////////////////////////
00475 //     Function: TextAssembler::calc_index
00476 //       Access: Published
00477 //  Description: Computes the character index of the character at the
00478 //               rth row and cth column position.  This is the inverse
00479 //               of calc_r_c().
00480 //
00481 //               It is legal for c to exceed the index number of the
00482 //               last column by 1, and it is legal for r to exceed the
00483 //               index number of the last row by 1, if c is 0.
00484 ////////////////////////////////////////////////////////////////////
00485 int TextAssembler::
00486 calc_index(int r, int c) const {
00487   nassertr(r >= 0 && r <= (int)_text_block.size(), 0);
00488   if (r == (int)_text_block.size()) {
00489     nassertr(c == 0, 0);
00490     return _text_string.size();
00491 
00492   } else {
00493     nassertr(c >= 0 && c <= (int)_text_block[r]._string.size(), 0);
00494     const TextRow &row = _text_block[r];
00495 
00496     if (row._got_soft_hyphens) {
00497       // If there are any soft hyphen or soft break keys in the source
00498       // text, we have to scan past them to get n precisely.
00499       int n = row._row_start;
00500       while (c > 0) {
00501         if (_text_string[n]._character != text_soft_hyphen_key && 
00502             _text_string[n]._character != text_soft_break_key) {
00503           --c;
00504         }
00505         ++n;
00506       }
00507       return n;
00508 
00509     } else {
00510       // If there are no soft characters, then the string maps
00511       // one-to-one.
00512       return row._row_start + c;
00513     }
00514   }
00515 }
00516 
00517 ////////////////////////////////////////////////////////////////////
00518 //     Function: TextAssembler::get_xpos
00519 //       Access: Published
00520 //  Description: Returns the x position of the origin of the character
00521 //               or graphic object at the indicated position in the
00522 //               indicated row.
00523 //
00524 //               It is legal for c to exceed the index number of the
00525 //               last column by 1, and it is legal for r to exceed the
00526 //               index number of the last row by 1, if c is 0.
00527 ////////////////////////////////////////////////////////////////////
00528 float TextAssembler::
00529 get_xpos(int r, int c) const {
00530   nassertr(r >= 0 && r <= (int)_text_block.size(), 0.0f);
00531   if (r == (int)_text_block.size()) {
00532     nassertr(c == 0, 0.0f);
00533     return 0.0f;
00534 
00535   } else {
00536     nassertr(c >= 0 && c <= (int)_text_block[r]._string.size(), 0.0f);
00537     const TextRow &row = _text_block[r];
00538     float xpos = row._xpos;
00539     for (int i = 0; i < c; ++i) {
00540       xpos += calc_width(row._string[i]);
00541     }
00542     return xpos;
00543   }
00544 }
00545 
00546 ////////////////////////////////////////////////////////////////////
00547 //     Function: TextAssembler::assemble_text
00548 //       Access: Published
00549 //  Description: Actually assembles all of the text into a GeomNode,
00550 //               and returns the node (or possibly a parent of the
00551 //               node, to keep the shadow separate).  Once this has
00552 //               been called, you may query the extents of the text
00553 //               via get_ul(), get_lr().
00554 ////////////////////////////////////////////////////////////////////
00555 PT(PandaNode) TextAssembler::
00556 assemble_text() {
00557   // Now assemble the text into glyphs.
00558   PlacedGlyphs placed_glyphs;
00559   assemble_paragraph(placed_glyphs);
00560 
00561   // Now that we have a bunch of GlyphPlacements, pull out the Geoms
00562   // and put them under a common node.
00563   PT(PandaNode) parent_node = new PandaNode("common");
00564 
00565   PT(PandaNode) shadow_node = new PandaNode("shadow");
00566   PT(GeomNode) shadow_geom_node = new GeomNode("shadow_geom");
00567   shadow_node->add_child(shadow_geom_node);
00568 
00569   PT(PandaNode) text_node = new PandaNode("text");
00570   PT(GeomNode) text_geom_node = new GeomNode("text_geom");
00571   text_node->add_child(text_geom_node);
00572 
00573   const TextProperties *properties = NULL;
00574   CPT(RenderState) text_state;
00575   CPT(RenderState) shadow_state;
00576   LMatrix4f shadow_xform;
00577 
00578   bool any_shadow = false;
00579 
00580   GeomCollectorMap geom_collector_map;
00581   GeomCollectorMap geom_shadow_collector_map;
00582 
00583   PlacedGlyphs::const_iterator pgi;
00584   for (pgi = placed_glyphs.begin(); pgi != placed_glyphs.end(); ++pgi) {
00585     const GlyphPlacement *placement = (*pgi);
00586 
00587     if (placement->_properties != properties) {
00588       // Get a new set of properties for future glyphs.
00589       properties = placement->_properties;
00590       text_state = RenderState::make_empty();
00591       shadow_state = RenderState::make_empty();
00592       shadow_xform = LMatrix4f::ident_mat();
00593 
00594       if (properties->has_text_color()) {
00595         text_state = text_state->add_attrib(ColorAttrib::make_flat(properties->get_text_color()));
00596         if (properties->get_text_color()[3] != 1.0) {
00597           text_state = text_state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
00598         }
00599       }
00600 
00601       if (properties->has_bin()) {
00602         text_state = text_state->add_attrib(CullBinAttrib::make(properties->get_bin(), properties->get_draw_order() + 2));
00603       }
00604 
00605       if (properties->has_shadow()) {
00606         shadow_state = shadow_state->add_attrib(ColorAttrib::make_flat(properties->get_shadow_color()));
00607         if (properties->get_shadow_color()[3] != 1.0) {
00608           shadow_state = shadow_state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
00609         }
00610 
00611         if (properties->has_bin()) {
00612           shadow_state = shadow_state->add_attrib(CullBinAttrib::make(properties->get_bin(), properties->get_draw_order() + 1));
00613         }
00614 
00615         LVector2f offset = properties->get_shadow();
00616         shadow_xform = LMatrix4f::translate_mat(offset[0], 0.0f, -offset[1]);
00617       }
00618     }
00619 
00620     // We have to place the shadow first, because it copies as it
00621     // goes, while the place-text function just stomps on the
00622     // vertices.
00623     if (properties->has_shadow()) {
00624       if (_dynamic_merge) {
00625         placement->assign_append_to(geom_shadow_collector_map, shadow_state, shadow_xform);
00626       } else {
00627         placement->assign_copy_to(shadow_geom_node, shadow_state, shadow_xform);
00628       }
00629 
00630       // Don't shadow the graphics.  That can result in duplication of
00631       // button objects, plus it looks weird.  If you want a shadowed
00632       // graphic, you can shadow it yourself before you add it.
00633       //placement->copy_graphic_to(shadow_node, shadow_state, shadow_xform);
00634       any_shadow = true;
00635     }
00636 
00637     if (_dynamic_merge) {
00638       placement->assign_append_to(geom_collector_map, text_state, LMatrix4f::ident_mat());
00639     } else {
00640       placement->assign_to(text_geom_node, text_state);
00641     }
00642     placement->copy_graphic_to(text_node, text_state, LMatrix4f::ident_mat());
00643     delete placement;
00644   }  
00645   placed_glyphs.clear();
00646 
00647   if (any_shadow) {
00648     // The shadow_geom_node must appear first to guarantee the correct
00649     // rendering order.
00650     parent_node->add_child(shadow_node);
00651   }
00652 
00653   GeomCollectorMap::iterator gc;
00654   for (gc = geom_collector_map.begin(); gc != geom_collector_map.end(); ++gc) {
00655     (*gc).second.append_geom(text_geom_node, (*gc).first._state);
00656   }
00657 
00658   if (any_shadow) {
00659     for (gc = geom_shadow_collector_map.begin(); 
00660          gc != geom_shadow_collector_map.end();
00661          ++gc) {
00662       (*gc).second.append_geom(shadow_geom_node, (*gc).first._state);
00663     }
00664   }
00665   
00666   parent_node->add_child(text_node);
00667 
00668   return parent_node;
00669 }
00670 
00671 ////////////////////////////////////////////////////////////////////
00672 //     Function: TextAssembler::calc_width
00673 //       Access: Published, Static
00674 //  Description: Returns the width of a single character, according to
00675 //               its associated font.  This also correctly calculates
00676 //               the width of cheesy ligatures and accented
00677 //               characters, which may not exist in the font as such.
00678 ////////////////////////////////////////////////////////////////////
00679 float TextAssembler::
00680 calc_width(wchar_t character, const TextProperties &properties) {
00681   if (character == ' ') {
00682     // A space is a special case.
00683     TextFont *font = properties.get_font();
00684     nassertr(font != (TextFont *)NULL, 0.0f);
00685     return font->get_space_advance() * properties.get_glyph_scale() * properties.get_text_scale();
00686   }
00687 
00688   bool got_glyph;
00689   const TextGlyph *first_glyph = NULL;
00690   const TextGlyph *second_glyph = NULL;
00691   UnicodeLatinMap::AccentType accent_type;
00692   int additional_flags;
00693   float glyph_scale;
00694   float advance_scale;
00695   get_character_glyphs(character, &properties, 
00696                        got_glyph, first_glyph, second_glyph, accent_type,
00697                        additional_flags, glyph_scale, advance_scale);
00698 
00699   float advance = 0.0f;
00700   
00701   if (first_glyph != (TextGlyph *)NULL) {
00702     advance = first_glyph->get_advance() * advance_scale;
00703   }
00704   if (second_glyph != (TextGlyph *)NULL) {
00705     advance += second_glyph->get_advance();
00706   }
00707 
00708   glyph_scale *= properties.get_glyph_scale() * properties.get_text_scale();
00709 
00710   return advance * glyph_scale;
00711 }
00712 
00713 ////////////////////////////////////////////////////////////////////
00714 //     Function: TextAssembler::calc_width
00715 //       Access: Published, Static
00716 //  Description: Returns the width of a single TextGraphic image.
00717 ////////////////////////////////////////////////////////////////////
00718 float TextAssembler::
00719 calc_width(const TextGraphic *graphic, const TextProperties &properties) {
00720   LVecBase4f frame = graphic->get_frame();
00721   return (frame[1] - frame[0]) * properties.get_glyph_scale() * properties.get_text_scale();
00722 }
00723 
00724 ////////////////////////////////////////////////////////////////////
00725 //     Function: TextAssembler::has_exact_character
00726 //       Access: Published, Static
00727 //  Description: Returns true if the named character exists in the
00728 //               font exactly as named, false otherwise.  Note that
00729 //               because Panda can assemble glyphs together
00730 //               automatically using cheesy accent marks, this is not
00731 //               a reliable indicator of whether a suitable glyph can
00732 //               be rendered for the character.  For that, use
00733 //               has_character() instead.
00734 //
00735 //               This returns true for whitespace and Unicode
00736 //               whitespace characters (if they exist in the font),
00737 //               but returns false for characters that would render
00738 //               with the "invalid glyph".  It also returns false for
00739 //               characters that would be synthesized within Panda,
00740 //               but see has_character().
00741 ////////////////////////////////////////////////////////////////////
00742 bool TextAssembler::
00743 has_exact_character(wchar_t character, const TextProperties &properties) {
00744   if (character == ' ' || character == '\n') {
00745     // A space is a special case.  Every font implicitly has a space.
00746     // We also treat newlines specially.
00747     return true;
00748   }
00749 
00750   TextFont *font = properties.get_font();
00751   nassertr(font != (TextFont *)NULL, false);
00752 
00753   const TextGlyph *glyph = NULL;
00754   return font->get_glyph(character, glyph);
00755 }
00756 
00757 ////////////////////////////////////////////////////////////////////
00758 //     Function: TextAssembler::has_character
00759 //       Access: Published, Static
00760 //  Description: Returns true if the named character exists in the
00761 //               font or can be synthesized by Panda, false otherwise.
00762 //               (Panda can synthesize some accented characters by
00763 //               combining similar-looking glyphs from the font.)
00764 //
00765 //               This returns true for whitespace and Unicode
00766 //               whitespace characters (if they exist in the font),
00767 //               but returns false for characters that would render
00768 //               with the "invalid glyph".
00769 ////////////////////////////////////////////////////////////////////
00770 bool TextAssembler::
00771 has_character(wchar_t character, const TextProperties &properties) {
00772   if (character == ' ' || character == '\n') {
00773     // A space is a special case.  Every font implicitly has a space.
00774     // We also treat newlines specially.
00775     return true;
00776   }
00777 
00778   bool got_glyph;
00779   const TextGlyph *first_glyph = NULL;
00780   const TextGlyph *second_glyph = NULL;
00781   UnicodeLatinMap::AccentType accent_type;
00782   int additional_flags;
00783   float glyph_scale;
00784   float advance_scale;
00785   get_character_glyphs(character, &properties, 
00786                        got_glyph, first_glyph, second_glyph, accent_type,
00787                        additional_flags, glyph_scale, advance_scale);
00788   return got_glyph;
00789 }
00790 
00791 ////////////////////////////////////////////////////////////////////
00792 //     Function: TextAssembler::is_whitespace
00793 //       Access: Published, Static
00794 //  Description: Returns true if the indicated character represents
00795 //               whitespace in the font, or false if anything visible
00796 //               will be rendered for it.
00797 //
00798 //               This returns true for whitespace and Unicode
00799 //               whitespace characters (if they exist in the font),
00800 //               and returns false for any other characters, including
00801 //               characters that do not exist in the font (these would
00802 //               be rendered with the "invalid glyph", which is
00803 //               visible).
00804 //
00805 //               Note that this function can be reliably used to
00806 //               identify Unicode whitespace characters only if the
00807 //               font has all of the whitespace characters defined.
00808 //               It will return false for any character not in the
00809 //               font, even if it is an official Unicode whitespace
00810 //               character.
00811 ////////////////////////////////////////////////////////////////////
00812 bool TextAssembler::
00813 is_whitespace(wchar_t character, const TextProperties &properties) {
00814   if (character == ' ' || character == '\n') {
00815     // A space or a newline is a special case.
00816     return true;
00817   }
00818 
00819 
00820   TextFont *font = properties.get_font();
00821   nassertr(font != (TextFont *)NULL, false);
00822 
00823   const TextGlyph *glyph = NULL;
00824   if (!font->get_glyph(character, glyph)) {
00825     return false;
00826   }
00827 
00828   return glyph->is_whitespace();
00829 }
00830 
00831 #ifndef CPPPARSER  // interrogate has a bit of trouble with wstring.
00832 ////////////////////////////////////////////////////////////////////
00833 //     Function: TextAssembler::scan_wtext
00834 //       Access: Private
00835 //  Description: Scans through the text string, decoding embedded
00836 //               references to TextProperties.  The decoded string is
00837 //               copied character-by-character into _text_string.
00838 ////////////////////////////////////////////////////////////////////
00839 void TextAssembler::
00840 scan_wtext(TextAssembler::TextString &output_string,
00841            wstring::const_iterator &si, 
00842            const wstring::const_iterator &send,
00843            TextAssembler::ComputedProperties *current_cprops) {
00844   while (si != send) {
00845     if ((*si) == text_push_properties_key) {
00846       // This indicates a nested properties structure.  Pull off the
00847       // name of the TextProperties structure, which is everything
00848       // until the next text_push_properties_key.
00849       wstring wname;
00850       ++si;
00851       while (si != send && (*si) != text_push_properties_key) {
00852         wname += (*si);
00853         ++si;
00854       }
00855 
00856       if (si == send) {
00857         // We didn't close the text_push_properties_key.  That's an
00858         // error.
00859         text_cat.warning()
00860           << "Unclosed push_properties in text.\n";
00861         return;
00862       }
00863 
00864       ++si;
00865       
00866       // Define the new properties by extending the current properties.
00867       PT(ComputedProperties) new_cprops = 
00868         new ComputedProperties(current_cprops, wname, _encoder);
00869       
00870       // And recursively scan with the nested properties.
00871       scan_wtext(output_string, si, send, new_cprops);
00872 
00873       if (text_cat.is_debug()) {
00874         if (si == send) {
00875           // The push was not closed by a pop.  That's not an error,
00876           // since we allow people to be sloppy about that; but we'll
00877           // print a debug message at least.
00878           text_cat.debug()
00879             << "push_properties not matched by pop_properties.\n";
00880         }
00881       }
00882 
00883     } else if ((*si) == text_pop_properties_key) {
00884       // This indicates the undoing of a previous push_properties_key.
00885       // We simply return to the previous level.
00886       ++si;
00887       return;
00888 
00889     } else if ((*si) == text_embed_graphic_key) {
00890       // This indicates an embedded graphic.  Pull off the name of the
00891       // TextGraphic structure, which is everything until the next
00892       // text_embed_graphic_key.
00893 
00894       wstring graphic_wname;
00895       ++si;
00896       while (si != send && (*si) != text_embed_graphic_key) {
00897         graphic_wname += (*si);
00898         ++si;
00899       }
00900 
00901       if (si == send) {
00902         // We didn't close the text_embed_graphic_key.  That's an
00903         // error.
00904         text_cat.warning()
00905           << "Unclosed embed_graphic in text.\n";
00906         return;
00907       }
00908 
00909       ++si;
00910 
00911       // Now we have to encode the wstring into a string, for lookup
00912       // in the TextPropertiesManager.
00913       string graphic_name = _encoder->encode_wtext(graphic_wname);
00914       
00915       TextPropertiesManager *manager = 
00916         TextPropertiesManager::get_global_ptr();
00917       
00918       // Get the graphic image.
00919       const TextGraphic *named_graphic = manager->get_graphic_ptr(graphic_name);
00920       if (named_graphic != (TextGraphic *)NULL) {
00921         output_string.push_back(TextCharacter(named_graphic, graphic_wname, current_cprops));
00922 
00923       } else {
00924         text_cat.warning()
00925           << "Unknown TextGraphic: " << graphic_name << "\n";
00926       }
00927 
00928     } else {
00929       // A normal character.  Apply it.
00930       output_string.push_back(TextCharacter(*si, current_cprops));
00931       ++si;
00932     }
00933   }
00934 }
00935 #endif  // CPPPARSER
00936 
00937 ////////////////////////////////////////////////////////////////////
00938 //     Function: TextAssembler::wordwrap_text
00939 //       Access: Private
00940 //  Description: Inserts newlines into the _text_string at the
00941 //               appropriate places in order to make each line be the
00942 //               longest possible line that is not longer than
00943 //               wordwrap_width (and does not break any words, if
00944 //               possible).  Stores the result in _text_block.
00945 //
00946 //               If _max_rows is greater than zero, no more than
00947 //               _max_rows will be accepted.  Text beyond that will be
00948 //               truncated.
00949 //
00950 //               The return value is true if all the text is accepted,
00951 //               or false if some was truncated.
00952 ////////////////////////////////////////////////////////////////////
00953 bool TextAssembler::
00954 wordwrap_text() {
00955   _text_block.clear();
00956 
00957   if (_text_string.empty()) {
00958     // A special case: empty text means no rows.
00959     return true;
00960   }
00961 
00962   size_t p = 0;
00963 
00964   _text_block.push_back(TextRow(p));
00965 
00966   // Preserve any initial whitespace and newlines.
00967   float initial_width = 0.0f;
00968   while (p < _text_string.size() && isspacew(_text_string[p]._character)) {
00969     if (_text_string[p]._character == '\n') {
00970       initial_width = 0.0f;
00971       if (_max_rows > 0 && (int)_text_block.size() >= _max_rows) {
00972         // Truncate.
00973         return false;
00974       }
00975       _text_block.back()._eol_cprops = _text_string[p]._cprops;
00976       _text_block.push_back(TextRow(p + 1));
00977     } else {
00978       initial_width += calc_width(_text_string[p]);
00979       _text_block.back()._string.push_back(_text_string[p]);
00980     }
00981     p++;
00982   }
00983   bool needs_newline = false;
00984 
00985   while (p < _text_string.size()) {
00986     nassertr(!isspacew(_text_string[p]._character), false);
00987 
00988     // Scan the next n characters, until the end of the string or an
00989     // embedded newline character, or we exceed wordwrap_width.
00990 
00991     size_t q = p;
00992     bool any_spaces = false;
00993     size_t last_space = 0;
00994     float last_space_width = 0.0f;
00995 
00996     bool any_hyphens = false;
00997     size_t last_hyphen = 0;
00998     bool output_hyphen = false;
00999 
01000     bool overflow = false;
01001     float wordwrap_width = -1.0f;
01002 
01003     bool last_was_space = false;
01004     float width = initial_width;
01005     while (q < _text_string.size() && _text_string[q]._character != '\n') {
01006       if (_text_string[q]._cprops->_properties.has_wordwrap()) {
01007         wordwrap_width = _text_string[q]._cprops->_properties.get_wordwrap();
01008       } else {
01009         wordwrap_width = -1.0f;
01010       }
01011 
01012       if (isspacew(_text_string[q]._character) || 
01013           _text_string[q]._character == text_soft_break_key) {
01014         if (!last_was_space) {
01015           any_spaces = true;
01016           // We only care about logging whether there is a soft-hyphen
01017           // character to the right of the rightmost space.  Each time
01018           // we encounter a space, we reset this counter.
01019           any_hyphens = false;
01020           last_space = q;
01021           last_space_width = width;
01022           last_was_space = true;
01023         }
01024       } else {
01025         last_was_space = false;
01026       }
01027 
01028       // A soft hyphen character is not printed, but marks a point
01029       // at which we might hyphenate a word if we need to.
01030       if (_text_string[q]._character == text_soft_hyphen_key) {
01031         if (wordwrap_width > 0.0f) {
01032           // We only consider this as a possible hyphenation point if
01033           // (a) it is not the very first character, and (b) there is
01034           // enough room for a hyphen character to be printed following
01035           // it.
01036           if (q != p && width + calc_hyphen_width(_text_string[q]) <= wordwrap_width) {
01037             any_hyphens = true;
01038             last_hyphen = q;
01039           }
01040         }
01041       } else {
01042         // Some normal, printable character.
01043         width += calc_width(_text_string[q]);
01044       }
01045 
01046       q++;
01047         
01048       if (wordwrap_width > 0.0f && width > wordwrap_width) {
01049         // Oops, too many.
01050         q--;
01051         overflow = true;
01052         break;
01053       }
01054     }
01055 
01056     if (overflow) {
01057       // If we stopped because we exceeded the wordwrap width, then
01058       // try to find an appropriate place to wrap the line or to
01059       // hyphenate, if necessary.
01060       nassertr(wordwrap_width > 0.0f, false);
01061 
01062       if (any_spaces && last_space_width / wordwrap_width >= text_hyphen_ratio) {
01063         // If we have a space that ended up within our safety margin,
01064         // don't use any soft-hyphen characters.
01065         any_hyphens = false;
01066       }
01067 
01068       if (any_hyphens) {
01069         // If we still have a soft-hyphen character, use it.
01070         q = last_hyphen;
01071         output_hyphen = true;
01072 
01073       } else if (any_spaces) {
01074         // Otherwise, break at a space if we can.
01075         q = last_space;
01076 
01077       } else {
01078         // Otherwise, this is a forced break.  Accept the longest line
01079         // we can that does not leave the next line beginning with one
01080         // of our forbidden characters.
01081         size_t i = 0;
01082         while ((int)i < text_max_never_break && q - i > p && 
01083                get_text_never_break_before().find(_text_string[q - i]._character) != wstring::npos) {
01084           i++;
01085         }
01086         if ((int)i < text_max_never_break) {
01087           q -= i;
01088         }
01089       }
01090     }
01091 
01092     // Skip additional whitespace between the lines.
01093     size_t next_start = q;
01094     while (next_start < _text_string.size() && 
01095            isbreakpoint(_text_string[next_start]._character)) {
01096       next_start++;
01097     }
01098 
01099     // Trim off any more blanks on the end.
01100     while (q > p && isspacew(_text_string[q - 1]._character)) {
01101       q--;
01102     }
01103 
01104     if (next_start == p) {
01105       // No characters got in at all.  This could only happen if the
01106       // wordwrap width is narrower than a single character, or if we
01107       // have a substantial number of leading spaces in a line.
01108 
01109       if (initial_width == 0.0f) {
01110         // There was no leading whitespace on the line, so the
01111         // character itself didn't fit within the margins.  Let it in
01112         // anyway; what else can we do?
01113         q++;
01114         next_start++;
01115         while (next_start < _text_string.size() && 
01116                isbreakpoint(_text_string[next_start]._character)) {
01117           next_start++;
01118         }
01119       }
01120     }
01121     
01122     if (needs_newline) {
01123       if (_max_rows > 0 && (int)_text_block.size() >= _max_rows) {
01124         // Truncate.
01125         return false;
01126       }
01127       _text_block.push_back(TextRow(p));
01128     }
01129     if (get_multiline_mode()){
01130         needs_newline = true;
01131     }
01132 
01133     if (_text_string[next_start - 1]._cprops->_properties.get_preserve_trailing_whitespace()) {
01134       q = next_start;
01135     }
01136 
01137     for (size_t pi = p; pi < q; pi++) {
01138       if (_text_string[pi]._character != text_soft_hyphen_key && 
01139           _text_string[pi]._character != text_soft_break_key) {
01140         _text_block.back()._string.push_back(_text_string[pi]);
01141       } else {
01142         _text_block.back()._got_soft_hyphens = true;
01143       }
01144     }
01145     if (output_hyphen) {
01146       wstring text_soft_hyphen_output = get_text_soft_hyphen_output();
01147       wstring::const_iterator wi;
01148       for (wi = text_soft_hyphen_output.begin();
01149            wi != text_soft_hyphen_output.end();
01150            ++wi) {
01151         _text_block.back()._string.push_back(TextCharacter(*wi, _text_string[last_hyphen]._cprops));
01152       }
01153     }
01154 
01155     // Now prepare to wrap the next line.
01156 
01157     if (next_start < _text_string.size() && _text_string[next_start]._character == '\n') {
01158       // Preserve a single embedded newline.
01159       if (_max_rows > 0 && (int)_text_block.size() >= _max_rows) {
01160         // Truncate.
01161         return false;
01162       }
01163       _text_block.back()._eol_cprops = _text_string[next_start]._cprops;
01164       next_start++;
01165       _text_block.push_back(TextRow(next_start));
01166       needs_newline = false;
01167     }
01168     p = next_start;
01169 
01170     // Preserve any initial whitespace and newlines.
01171     initial_width = 0.0f;
01172     while (p < _text_string.size() && isspacew(_text_string[p]._character)) {
01173       if (_text_string[p]._character == '\n') {
01174         initial_width = 0.0f;
01175         if (_max_rows > 0 && (int)_text_block.size() >= _max_rows) {
01176           // Truncate.
01177           return false;
01178         }
01179         _text_block.back()._eol_cprops = _text_string[p]._cprops;
01180         _text_block.push_back(TextRow(p + 1));
01181       } else {
01182         initial_width += calc_width(_text_string[p]);
01183         _text_block.back()._string.push_back(_text_string[p]);
01184       }
01185       p++;
01186     }
01187   }
01188 
01189   return true;
01190 }
01191 
01192 ////////////////////////////////////////////////////////////////////
01193 //     Function: TextAssembler::calc_hyphen_width
01194 //       Access: Private, Static
01195 //  Description: Returns the width of the soft-hyphen replacement
01196 //               string, according to the indicated character's
01197 //               associated font.
01198 ////////////////////////////////////////////////////////////////////
01199 float TextAssembler::
01200 calc_hyphen_width(const TextCharacter &tch) {
01201   TextFont *font = tch._cprops->_properties.get_font();
01202   nassertr(font != (TextFont *)NULL, 0.0f);
01203 
01204   float hyphen_width = 0.0f;
01205   wstring text_soft_hyphen_output = get_text_soft_hyphen_output();
01206   wstring::const_iterator wi;
01207   for (wi = text_soft_hyphen_output.begin();
01208        wi != text_soft_hyphen_output.end();
01209        ++wi) {
01210     hyphen_width += calc_width(*wi, tch._cprops->_properties);
01211   }
01212   
01213   return hyphen_width;
01214 }
01215 
01216 ////////////////////////////////////////////////////////////////////
01217 //     Function: TextAssembler::assemble_paragraph
01218 //       Access: Private
01219 //  Description: Fills up placed_glyphs, _ul, _lr with
01220 //               the contents of _text_block.  Also updates _xpos and
01221 //               _ypos within the _text_block structure.
01222 ////////////////////////////////////////////////////////////////////
01223 void TextAssembler::
01224 assemble_paragraph(TextAssembler::PlacedGlyphs &placed_glyphs) {
01225   _ul.set(0.0f, 0.0f);
01226   _lr.set(0.0f, 0.0f);
01227   int num_rows = 0;
01228 
01229   float ypos = 0.0f;
01230   _next_row_ypos = 0.0f;
01231   TextBlock::iterator bi;
01232   for (bi = _text_block.begin(); bi != _text_block.end(); ++bi) {
01233     TextRow &row = (*bi);
01234 
01235     // First, assemble all the glyphs of this row.
01236     PlacedGlyphs row_placed_glyphs;
01237     float row_width, line_height, wordwrap;
01238     TextProperties::Alignment align;
01239     assemble_row(row, row_placed_glyphs,
01240                  row_width, line_height, align, wordwrap);
01241     // Now move the row to its appropriate position.  This might
01242     // involve a horizontal as well as a vertical translation.
01243     LMatrix4f mat = LMatrix4f::ident_mat();
01244 
01245     if (num_rows == 0) {
01246       // If this is the first row, account for its space.
01247       _ul[1] = 0.8f * line_height;
01248 
01249     } else {
01250       // If it is not the first row, shift the text downward by
01251       // line_height from the previous row.
01252       ypos -= line_height;
01253     }
01254     _lr[1] = ypos - 0.2f * line_height;
01255 
01256     // Apply the requested horizontal alignment to the row.
01257     //[fabius] added a different concept of text alignment based upon a boxed region where his width is defined by the wordwrap size with the upper left corner starting from 0,0,0
01258     // if the wordwrap size is unspecified the alignment could eventually result wrong.
01259     float xpos;
01260     switch (align) {
01261     case TextProperties::A_left:
01262       xpos = 0.0f;
01263       _lr[0] = max(_lr[0], row_width);
01264       break;
01265 
01266     case TextProperties::A_right:
01267       xpos = -row_width;
01268       _ul[0] = min(_ul[0], xpos);
01269       break;
01270 
01271     case TextProperties::A_center:
01272       xpos = -0.5f * row_width;
01273       _ul[0] = min(_ul[0], xpos);
01274       _lr[0] = max(_lr[0], -xpos);
01275       break;
01276 
01277     case TextProperties::A_boxed_left:
01278       xpos = 0.0f;
01279       _lr[0] = max(_lr[0], max(row_width, wordwrap));
01280       break;
01281 
01282     case TextProperties::A_boxed_right:
01283       xpos = wordwrap - row_width;
01284       _ul[0] = min(_ul[0], xpos);
01285       break;
01286 
01287     case TextProperties::A_boxed_center:
01288       xpos = -0.5f * row_width;
01289       if (wordwrap > row_width) xpos += (wordwrap * 0.5f);
01290       _ul[0] = min(_ul[0], max(xpos,(wordwrap * 0.5f)));
01291       _lr[0] = max(_lr[0], min(-xpos,-(wordwrap * 0.5f)));
01292       break;
01293     }
01294 
01295     mat.set_row(3, LVector3f(xpos, 0.0f, ypos));
01296     row._xpos = xpos;
01297     row._ypos = ypos;
01298 
01299     // Now store the geoms we assembled.
01300     PlacedGlyphs::iterator pi;
01301     for (pi = row_placed_glyphs.begin(); pi != row_placed_glyphs.end(); ++pi) {
01302       (*pi)->_xform *= mat;
01303       placed_glyphs.push_back(*pi);
01304     }
01305 
01306     // Advance to the next line.
01307     num_rows++;
01308     _next_row_ypos = ypos - line_height;
01309   }
01310 
01311   // num_rows may be smaller than _text_block.size(), if there are
01312   // trailing newlines on the string.
01313 }
01314 
01315 ////////////////////////////////////////////////////////////////////
01316 //     Function: TextAssembler::assemble_row
01317 //       Access: Private
01318 //  Description: Assembles the letters in the source string, up until
01319 //               the first newline or the end of the string into a
01320 //               single row (which is parented to _geom_node), and
01321 //               computes the length of the row and the maximum
01322 //               line_height of all the fonts used in the row.  The
01323 //               source pointer is moved to the terminating character.
01324 ////////////////////////////////////////////////////////////////////
01325 void TextAssembler::
01326 assemble_row(TextAssembler::TextRow &row,
01327              TextAssembler::PlacedGlyphs &row_placed_glyphs,
01328              float &row_width, float &line_height, 
01329              TextProperties::Alignment &align, float &wordwrap) {
01330   Thread *current_thread = Thread::get_current_thread();
01331 
01332   line_height = 0.0f;
01333   float xpos = 0.0f;
01334   align = TextProperties::A_left;
01335 
01336   bool underscore = false;
01337   float underscore_start = 0.0f;
01338   const TextProperties *underscore_properties = NULL;
01339 
01340   TextString::const_iterator si;
01341   for (si = row._string.begin(); si != row._string.end(); ++si) {
01342     const TextCharacter &tch = (*si);
01343     wchar_t character = tch._character;
01344     const TextGraphic *graphic = tch._graphic;
01345     const TextProperties *properties = &(tch._cprops->_properties);
01346 
01347     if (properties->get_underscore() != underscore ||
01348         (underscore && (properties->get_text_color() != underscore_properties->get_text_color() ||
01349                         properties->get_underscore_height() != underscore_properties->get_underscore_height()))) {
01350       // Change the underscore status.
01351       if (underscore && underscore_start != xpos) {
01352         draw_underscore(row_placed_glyphs, underscore_start, xpos,
01353                         underscore_properties);
01354       }
01355       underscore = properties->get_underscore();
01356       underscore_start = xpos;
01357       underscore_properties = properties;
01358     }
01359 
01360     TextFont *font = properties->get_font();
01361     nassertv(font != (TextFont *)NULL);
01362 
01363     // We get the row's alignment property from the first character of the row
01364     if ((align == TextProperties::A_left) &&
01365         (properties->get_align() != TextProperties::A_left)) {
01366       align = properties->get_align();
01367     }
01368 
01369     // And the height of the row is the maximum of all the fonts used
01370     // within the row.
01371     if (graphic != (TextGraphic *)NULL) {
01372       LVecBase4f frame = graphic->get_frame();
01373       line_height = max(line_height, frame[3] - frame[2]);
01374     } else {
01375       //[fabius] this is not the right place to calc line height (see below)
01376       //       line_height = max(line_height, font->get_line_height());
01377     }
01378 
01379     if (character == ' ') {
01380       // A space is a special case.
01381       xpos += font->get_space_advance();
01382 
01383     } else if (character == '\t') {
01384       // So is a tab character.
01385       float tab_width = properties->get_tab_width();
01386       xpos = (floor(xpos / tab_width) + 1.0f) * tab_width;
01387 
01388     } else if (character == text_soft_hyphen_key) {
01389       // And so is the 'soft-hyphen' key character.
01390 
01391     } else if (graphic != (TextGraphic *)NULL) {
01392       // A special embedded graphic.
01393       GlyphPlacement *placement = new GlyphPlacement;
01394       row_placed_glyphs.push_back(placement);
01395 
01396       PT(PandaNode) model = graphic->get_model().node();
01397       if (graphic->get_instance_flag()) {
01398         // Instance the model in.  Create a ModelNode so it doesn't
01399         // get flattened.
01400         PT(ModelNode) model_node = new ModelNode("");
01401         model_node->set_preserve_transform(ModelNode::PT_no_touch);
01402         model_node->add_child(model);
01403         placement->_graphic_model = model_node.p();
01404       } else {
01405         // Copy the model in.  This the preferred way; it's a little
01406         // cheaper to render than instancing (because flattening is
01407         // more effective).
01408         placement->_graphic_model = model->copy_subgraph();
01409       }
01410 
01411       LVecBase4f frame = graphic->get_frame();
01412       float glyph_scale = properties->get_glyph_scale() * properties->get_text_scale();
01413 
01414       float advance = (frame[1] - frame[0]);
01415 
01416       // Now compute the matrix that will transform the glyph (or
01417       // glyphs) into position.
01418       LMatrix4f glyph_xform = LMatrix4f::scale_mat(glyph_scale);
01419 
01420       glyph_xform(3, 0) += (xpos - frame[0]);
01421       glyph_xform(3, 2) += (properties->get_glyph_shift() - frame[2]);
01422 
01423       if (properties->has_slant()) {
01424         LMatrix4f shear(1.0f, 0.0f, 0.0f, 0.0f,
01425                         0.0f, 1.0f, 0.0f, 0.0f,
01426                         properties->get_slant(), 0.0f, 1.0f, 0.0f,
01427                         0.0f, 0.0f, 0.0f, 1.0f);
01428         glyph_xform = shear * glyph_xform;
01429       }
01430       
01431       placement->_xform = glyph_xform;
01432       placement->_properties = properties;
01433 
01434       xpos += advance * glyph_scale;
01435 
01436     } else {
01437       // A printable character.
01438       bool got_glyph;
01439       const TextGlyph *first_glyph;
01440       const TextGlyph *second_glyph;
01441       UnicodeLatinMap::AccentType accent_type;
01442       int additional_flags;
01443       float glyph_scale;
01444       float advance_scale;
01445       get_character_glyphs(character, properties, 
01446                            got_glyph, first_glyph, second_glyph, accent_type,
01447                            additional_flags, glyph_scale, advance_scale);
01448 
01449       if (!got_glyph) {
01450         char buffer[512];
01451         sprintf(buffer, "U+%04x", character);
01452         text_cat.warning()
01453           << "No definition in " << font->get_name() 
01454           << " for character " << buffer;
01455         if (character < 128 && isprint((unsigned int)character)) {
01456           text_cat.warning(false)
01457             << " ('" << (char)character << "')";
01458         }
01459         text_cat.warning(false)
01460           << "\n";
01461       }
01462 
01463       // Build up a GlyphPlacement, indicating all of the Geoms that go
01464       // into this character.  Normally, there is only one Geom per
01465       // character, but it may involve multiple Geoms if we need to
01466       // add cheesy accents or ligatures.
01467       GlyphPlacement *placement = new GlyphPlacement;
01468       row_placed_glyphs.push_back(placement);
01469 
01470       float advance = 0.0f;
01471 
01472       if (first_glyph != (TextGlyph *)NULL) {
01473         PT(Geom) first_char_geom = first_glyph->get_geom(_usage_hint);
01474         if (first_char_geom != (Geom *)NULL) {
01475           placement->add_piece(first_char_geom, first_glyph->get_state());
01476         }
01477         advance = first_glyph->get_advance() * advance_scale;
01478       }
01479       if (second_glyph != (TextGlyph *)NULL) {
01480         PT(Geom) second_char_geom = second_glyph->get_geom(_usage_hint);
01481         if (second_char_geom != (Geom *)NULL) {
01482           second_char_geom->transform_vertices(LMatrix4f::translate_mat(advance, 0.0f, 0.0f));
01483           placement->add_piece(second_char_geom, second_glyph->get_state());
01484         }
01485         advance += second_glyph->get_advance();
01486       }
01487 
01488       glyph_scale *= properties->get_glyph_scale() * properties->get_text_scale();
01489       //[fabius] a good place to take wordwrap size
01490       if (properties->get_wordwrap() > 0.0f) {
01491         wordwrap = properties->get_wordwrap();
01492       }
01493       // Now compute the matrix that will transform the glyph (or
01494       // glyphs) into position.
01495       LMatrix4f glyph_xform = LMatrix4f::scale_mat(glyph_scale);
01496 
01497       if (accent_type != UnicodeLatinMap::AT_none || additional_flags != 0) {
01498         // If we have some special handling to perform, do so now.
01499         // This will probably require the bounding volume of the
01500         // glyph, so go get that.
01501         LPoint3f min_vert, max_vert;
01502         bool found_any = false;
01503         placement->calc_tight_bounds(min_vert, max_vert, found_any,
01504                                      current_thread);
01505 
01506         if (found_any) {
01507           LPoint3f centroid = (min_vert + max_vert) / 2.0f;
01508           tack_on_accent(accent_type, min_vert, max_vert, centroid, 
01509                          properties, placement);
01510     
01511           if ((additional_flags & UnicodeLatinMap::AF_turned) != 0) {
01512             // Invert the character.  Should we also invert the accent
01513             // mark, so that an accent that would have been above the
01514             // glyph will now be below it?  That's what we do here,
01515             // which is probably the right thing to do for n-tilde,
01516             // but not for most of the rest of the accent marks.  For
01517             // now we'll assume there are no characters with accent
01518             // marks that also have the turned flag.
01519 
01520             // We rotate the character around its centroid, which may
01521             // not always be the right point, but it's the best we've
01522             // got and it's probably pretty close.
01523             LMatrix4f rotate =
01524               LMatrix4f::translate_mat(-centroid) *
01525               LMatrix4f::rotate_mat_normaxis(180.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) *
01526               LMatrix4f::translate_mat(centroid);
01527             glyph_xform *= rotate;
01528           }
01529         }
01530       }
01531 
01532       glyph_xform(3, 0) += xpos;
01533       glyph_xform(3, 2) += properties->get_glyph_shift();
01534 
01535       if (properties->has_slant()) {
01536         LMatrix4f shear(1.0f, 0.0f, 0.0f, 0.0f,
01537                         0.0f, 1.0f, 0.0f, 0.0f,
01538                         properties->get_slant(), 0.0f, 1.0f, 0.0f,
01539                         0.0f, 0.0f, 0.0f, 1.0f);
01540         glyph_xform = shear * glyph_xform;
01541       }
01542       
01543       placement->_xform = glyph_xform;
01544       placement->_properties = properties;
01545 
01546       xpos += advance * glyph_scale;
01547       line_height = max(line_height, font->get_line_height() * glyph_scale);
01548     }
01549   }
01550 
01551   if (underscore && underscore_start != xpos) {
01552     draw_underscore(row_placed_glyphs, underscore_start, xpos,
01553                     underscore_properties);
01554   }
01555 
01556   row_width = xpos;
01557 
01558   if (row._eol_cprops != (ComputedProperties *)NULL) {
01559     // If there's an _eol_cprops, it represents the cprops of the
01560     // newline character that ended the line, which should also
01561     // contribute towards the line_height.
01562 
01563     const TextProperties *properties = &(row._eol_cprops->_properties);
01564     TextFont *font = properties->get_font();
01565     nassertv(font != (TextFont *)NULL);
01566 
01567     if (line_height == 0.0f) {
01568       float glyph_scale = properties->get_glyph_scale() * properties->get_text_scale();
01569       line_height = max(line_height, font->get_line_height() * glyph_scale);
01570     }
01571   }
01572 }
01573   
01574 ////////////////////////////////////////////////////////////////////
01575 //     Function: TextAssembler::draw_underscore
01576 //       Access: Private, Static
01577 //  Description: Creates the geometry to render the underscore line
01578 //               for the indicated range of glyphs in this row.
01579 ////////////////////////////////////////////////////////////////////
01580 void TextAssembler::
01581 draw_underscore(TextAssembler::PlacedGlyphs &row_placed_glyphs,
01582                 float underscore_start, float underscore_end, 
01583                 const TextProperties *underscore_properties) {
01584   CPT(GeomVertexFormat) format = GeomVertexFormat::get_v3cp();
01585   PT(GeomVertexData) vdata = 
01586     new GeomVertexData("text", format, Geom::UH_static);
01587   GeomVertexWriter vertex(vdata, InternalName::get_vertex());
01588   GeomVertexWriter color(vdata, InternalName::get_color());
01589 
01590   float y = underscore_properties->get_underscore_height();
01591   vertex.add_data3f(underscore_start, 0.0f, y);
01592   color.add_data4f(underscore_properties->get_text_color());
01593   vertex.add_data3f(underscore_end, 0.0f, y);
01594   color.add_data4f(underscore_properties->get_text_color());
01595 
01596   PT(GeomLines) lines = new GeomLines(Geom::UH_static);
01597   lines->add_vertices(0, 1);
01598   lines->close_primitive();
01599 
01600   PT(Geom) geom = new Geom(vdata);
01601   geom->add_primitive(lines);
01602 
01603   GlyphPlacement *placement = new GlyphPlacement;
01604   placement->add_piece(geom, RenderState::make_empty());
01605   placement->_xform = LMatrix4f::ident_mat();
01606   placement->_properties = underscore_properties;
01607 
01608   row_placed_glyphs.push_back(placement);
01609 }
01610 
01611 ////////////////////////////////////////////////////////////////////
01612 //     Function: TextAssembler::get_character_glyphs
01613 //       Access: Private, Static
01614 //  Description: Looks up the glyph(s) from the font for the
01615 //               appropriate character.  If the desired glyph isn't
01616 //               available (especially in the case of an accented
01617 //               letter), tries to find a suitable replacement.
01618 //               Normally, only one glyph is returned per character,
01619 //               but in the case in which we have to simulate a
01620 //               missing ligature in the font, two glyphs might be
01621 //               returned.
01622 //
01623 //               All parameters except the first two are output
01624 //               parameters.  got_glyph is set true if the glyph (or
01625 //               an acceptable substitute) is successfully found,
01626 //               false otherwise; but even if it is false, glyph might
01627 //               still be non-NULL, indicating a stand-in glyph for a
01628 //               missing character.
01629 ////////////////////////////////////////////////////////////////////
01630 void TextAssembler::
01631 get_character_glyphs(int character, const TextProperties *properties,
01632                      bool &got_glyph, const TextGlyph *&glyph,
01633                      const TextGlyph *&second_glyph,
01634                      UnicodeLatinMap::AccentType &accent_type,
01635                      int &additional_flags,
01636                      float &glyph_scale, float &advance_scale) {
01637   TextFont *font = properties->get_font();
01638   nassertv_always(font != (TextFont *)NULL);
01639 
01640   got_glyph = false;
01641   glyph = NULL;
01642   second_glyph = NULL;
01643   accent_type = UnicodeLatinMap::AT_none;
01644   additional_flags = 0;
01645   glyph_scale = 1.0f;
01646   advance_scale = 1.0f;
01647 
01648   // Maybe we should remap the character to something else--e.g. a
01649   // small capital.
01650   const UnicodeLatinMap::Entry *map_entry = 
01651     UnicodeLatinMap::look_up(character);
01652   if (map_entry != NULL) {
01653     if (properties->get_small_caps() && 
01654         map_entry->_toupper_character != character) {
01655       character = map_entry->_toupper_character;
01656       map_entry = UnicodeLatinMap::look_up(character);
01657       glyph_scale = properties->get_small_caps_scale();
01658     }
01659   }
01660   
01661   got_glyph = font->get_glyph(character, glyph);
01662   if (!got_glyph && map_entry != NULL && map_entry->_ascii_equiv != 0) {
01663     // If we couldn't find the Unicode glyph, try the ASCII
01664     // equivalent (without the accent marks).
01665     got_glyph = font->get_glyph(map_entry->_ascii_equiv, glyph);
01666     
01667     if (!got_glyph && map_entry->_toupper_character != character) {
01668       // If we still couldn't find it, try the uppercase
01669       // equivalent.
01670       character = map_entry->_toupper_character;
01671       map_entry = UnicodeLatinMap::look_up(character);
01672       if (map_entry != NULL) {
01673         got_glyph = font->get_glyph(map_entry->_ascii_equiv, glyph);
01674       }
01675     }
01676     
01677     if (got_glyph) {
01678       accent_type = map_entry->_accent_type;
01679       additional_flags = map_entry->_additional_flags;
01680       
01681       bool got_second_glyph = false;
01682       if (map_entry->_ascii_additional != 0) {
01683         // There's another character, too--probably a ligature.
01684         got_second_glyph = 
01685           font->get_glyph(map_entry->_ascii_additional, second_glyph);
01686       }
01687       
01688       if ((additional_flags & UnicodeLatinMap::AF_ligature) != 0 &&
01689           got_second_glyph) {
01690         // If we have two letters that are supposed to be in a
01691         // ligature, just jam them together.
01692         additional_flags &= ~UnicodeLatinMap::AF_ligature;
01693         advance_scale = ligature_advance_scale;
01694       }
01695       
01696       if ((additional_flags & UnicodeLatinMap::AF_smallcap) != 0) {
01697         additional_flags &= ~UnicodeLatinMap::AF_smallcap;
01698         glyph_scale = properties->get_small_caps_scale();
01699       }
01700     }
01701   }
01702 }
01703   
01704   
01705 ////////////////////////////////////////////////////////////////////
01706 //     Function: TextAssembler::tack_on_accent
01707 //       Access: Private
01708 //  Description: This is a cheesy attempt to tack on an accent to an
01709 //               ASCII letter for which we don't have the appropriate
01710 //               already-accented glyph in the font.
01711 ////////////////////////////////////////////////////////////////////
01712 void TextAssembler::
01713 tack_on_accent(UnicodeLatinMap::AccentType accent_type,
01714                const LPoint3f &min_vert, const LPoint3f &max_vert,
01715                const LPoint3f &centroid,
01716                const TextProperties *properties, 
01717                TextAssembler::GlyphPlacement *placement) const {
01718   switch (accent_type) {
01719   case UnicodeLatinMap::AT_grave:
01720     // We use the slash as the grave and acute accents.  ASCII does
01721     // have a grave accent character, but a lot of fonts put the
01722     // reverse apostrophe there instead.  And some fonts (particularly
01723     // fonts from mf) don't even do backslash.
01724     tack_on_accent('/', CP_above, CT_small_squash_mirror_y, min_vert, max_vert, centroid,
01725                    properties, placement);
01726     break;
01727 
01728   case UnicodeLatinMap::AT_acute:
01729     tack_on_accent('/', CP_above, CT_small_squash, min_vert, max_vert, centroid,
01730                    properties, placement);
01731     break;
01732 
01733   case UnicodeLatinMap::AT_breve:
01734     tack_on_accent(')', CP_above, CT_tiny_rotate_270, min_vert, max_vert, centroid,
01735                    properties, placement);
01736     break;
01737 
01738   case UnicodeLatinMap::AT_inverted_breve:
01739     tack_on_accent('(', CP_above, CT_tiny_rotate_270, min_vert, max_vert, centroid,
01740                    properties, placement);
01741     break;
01742 
01743   case UnicodeLatinMap::AT_circumflex:
01744     tack_on_accent('^', CP_above, CT_none, min_vert, max_vert, centroid,
01745                    properties, placement) ||
01746       tack_on_accent('v', CP_above, CT_squash_mirror_y, min_vert, max_vert, centroid,
01747                      properties, placement);
01748     break;
01749 
01750   case UnicodeLatinMap::AT_circumflex_below:
01751     tack_on_accent('^', CP_below, CT_none, min_vert, max_vert, centroid,
01752                    properties, placement) ||
01753       tack_on_accent('v', CP_below, CT_squash_mirror_y, min_vert, max_vert, centroid,
01754                      properties, placement);
01755     break;
01756 
01757   case UnicodeLatinMap::AT_caron:
01758     tack_on_accent('^', CP_above, CT_mirror_y, min_vert, max_vert, centroid,
01759                    properties, placement) ||
01760       tack_on_accent('v', CP_above, CT_squash, min_vert, max_vert, centroid,
01761                      properties, placement);
01762 
01763     break;
01764 
01765   case UnicodeLatinMap::AT_tilde:
01766     tack_on_accent('~', CP_above, CT_none, min_vert, max_vert, centroid,
01767                    properties, placement) ||
01768       tack_on_accent('s', CP_above, CT_squash_mirror_diag, min_vert, max_vert, centroid,
01769                      properties, placement);
01770 
01771     break;
01772 
01773   case UnicodeLatinMap::AT_tilde_below:
01774     tack_on_accent('~', CP_below, CT_none, min_vert, max_vert, centroid,
01775                    properties, placement) ||
01776       tack_on_accent('s', CP_below, CT_squash_mirror_diag, min_vert, max_vert, centroid,
01777                      properties, placement);
01778     break;
01779 
01780   case UnicodeLatinMap::AT_diaeresis:
01781     tack_on_accent(':', CP_above, CT_small_rotate_270, min_vert, max_vert, centroid,
01782                    properties, placement);
01783     break;
01784 
01785   case UnicodeLatinMap::AT_diaeresis_below:
01786     tack_on_accent(':', CP_below, CT_small_rotate_270, min_vert, max_vert, centroid,
01787                    properties, placement);
01788     break;
01789 
01790   case UnicodeLatinMap::AT_dot_above:
01791     tack_on_accent('.', CP_above, CT_none, min_vert, max_vert, centroid,
01792                    properties, placement);
01793     break;
01794 
01795   case UnicodeLatinMap::AT_dot_below:
01796     tack_on_accent('.', CP_below, CT_none, min_vert, max_vert, centroid,
01797                    properties, placement);
01798     break;
01799 
01800   case UnicodeLatinMap::AT_macron:
01801     tack_on_accent('-', CP_above, CT_none, min_vert, max_vert, centroid,
01802                    properties, placement);
01803     break;
01804 
01805   case UnicodeLatinMap::AT_line_below:
01806     tack_on_accent('-', CP_below, CT_none, min_vert, max_vert, centroid,
01807                    properties, placement);
01808     break;
01809 
01810   case UnicodeLatinMap::AT_ring_above:
01811     tack_on_accent('o', CP_top, CT_tiny, min_vert, max_vert, centroid,
01812                    properties, placement);
01813     break;
01814 
01815   case UnicodeLatinMap::AT_ring_below:
01816     tack_on_accent('o', CP_bottom, CT_tiny, min_vert, max_vert, centroid,
01817                    properties, placement);
01818     break;
01819 
01820   case UnicodeLatinMap::AT_cedilla:
01821     tack_on_accent('c', CP_bottom, CT_tiny_mirror_x, min_vert, max_vert, centroid,
01822                    properties, placement);
01823     //tack_on_accent(',', CP_bottom, CT_none, min_vert, max_vert, centroid,
01824     //               properties, placement);
01825     break;
01826 
01827   case UnicodeLatinMap::AT_comma_below:
01828     tack_on_accent(',', CP_below, CT_none, min_vert, max_vert, centroid,
01829                    properties, placement);
01830     break;
01831 
01832   case UnicodeLatinMap::AT_ogonek:
01833     tack_on_accent(',', CP_bottom, CT_mirror_x, min_vert, max_vert, centroid,
01834                    properties, placement);
01835     break;
01836 
01837   case UnicodeLatinMap::AT_stroke:
01838     tack_on_accent('/', CP_within, CT_none, min_vert, max_vert, centroid,
01839                    properties, placement);
01840     break;
01841 
01842   default:
01843     // There are lots of other crazy kinds of accents.  Forget 'em.
01844     break;
01845   }    
01846 }
01847 
01848 ////////////////////////////////////////////////////////////////////
01849 //     Function: TextAssembler::tack_on_accent
01850 //       Access: Private
01851 //  Description: Generates a cheesy accent mark above (or below, etc.)
01852 //               the character.  Returns true if successful, or false
01853 //               if the named accent character doesn't exist in the
01854 //               font.
01855 ////////////////////////////////////////////////////////////////////
01856 bool TextAssembler::
01857 tack_on_accent(char accent_mark, TextAssembler::CheesyPosition position,
01858                TextAssembler::CheesyTransform transform,
01859                const LPoint3f &min_vert, const LPoint3f &max_vert,
01860                const LPoint3f &centroid,
01861                const TextProperties *properties,
01862                TextAssembler::GlyphPlacement *placement) const {
01863   TextFont *font = properties->get_font();
01864   nassertr(font != (TextFont *)NULL, false);
01865 
01866   Thread *current_thread = Thread::get_current_thread();
01867 
01868   const TextGlyph *accent_glyph;
01869   if (font->get_glyph(accent_mark, accent_glyph) ||
01870       font->get_glyph(toupper(accent_mark), accent_glyph)) {
01871     PT(Geom) accent_geom = accent_glyph->get_geom(_usage_hint);
01872     if (accent_geom != (Geom *)NULL) {
01873       LPoint3f min_accent, max_accent;
01874       bool found_any = false;
01875       accent_geom->calc_tight_bounds(min_accent, max_accent, found_any,
01876                                      current_thread);
01877       if (found_any) {
01878         float t, u;
01879         LMatrix4f accent_mat;
01880 
01881         // This gets set to true if the glyph gets mirrored and needs
01882         // to have backface culling disabled.
01883         bool mirrored = false;
01884 
01885         switch (transform) {
01886         case CT_none:
01887           accent_mat = LMatrix4f::ident_mat();
01888           break;
01889 
01890         case CT_mirror_x:
01891           accent_mat = LMatrix4f::scale_mat(-1.0f, 1.0f, 1.0f);
01892           t = min_accent[0];
01893           min_accent[0] = -max_accent[0];
01894           max_accent[0] = -t;
01895           mirrored = true;
01896           break;
01897 
01898         case CT_mirror_y:
01899           accent_mat = LMatrix4f::scale_mat(1.0f, 1.0f, -1.0f);
01900           t = min_accent[2];
01901           min_accent[2] = -max_accent[2];
01902           max_accent[2] = -t;
01903           mirrored = true;
01904           break;
01905 
01906         case CT_rotate_90:
01907           accent_mat.set_rotate_mat_normaxis(90.0f, LVecBase3f(0.0f, -1.0f, 0.0f));
01908           // rotate min, max
01909           t = min_accent[0];
01910           u = max_accent[0];
01911           max_accent[0] = -min_accent[2];
01912           min_accent[0] = -max_accent[2];
01913           max_accent[2] = u;
01914           min_accent[2] = t;
01915           break;
01916 
01917         case CT_rotate_180:
01918           accent_mat = LMatrix4f::scale_mat(-1.0f, -1.0f, 1.0f);
01919           
01920           t = min_accent[0];
01921           min_accent[0] = -max_accent[0];
01922           max_accent[0] = -t;
01923           t = min_accent[2];
01924           min_accent[2] = -max_accent[2];
01925           max_accent[2] = -t;
01926           break;
01927 
01928         case CT_rotate_270:
01929           accent_mat.set_rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f));
01930           // rotate min, max
01931           t = min_accent[0];
01932           u = max_accent[0];
01933           min_accent[0] = min_accent[2];
01934           max_accent[0] = max_accent[2];
01935           min_accent[2] = -u;
01936           max_accent[2] = -t;
01937           break;
01938 
01939         case CT_squash:
01940           accent_mat = LMatrix4f::scale_mat(squash_accent_scale_x, 1.0f, squash_accent_scale_y);
01941           min_accent[0] *= squash_accent_scale_x;
01942           max_accent[0] *= squash_accent_scale_x;
01943           min_accent[2] *= squash_accent_scale_y;
01944           max_accent[2] *= squash_accent_scale_y;
01945           break;
01946 
01947         case CT_squash_mirror_y:
01948           accent_mat = LMatrix4f::scale_mat(squash_accent_scale_x, 1.0f, -squash_accent_scale_y);
01949           min_accent[0] *= squash_accent_scale_x;
01950           max_accent[0] *= squash_accent_scale_x;
01951           t = min_accent[2];
01952           min_accent[2] = -max_accent[2] * squash_accent_scale_y;
01953           max_accent[2] = -t * squash_accent_scale_y;
01954           mirrored = true;
01955           break;
01956 
01957         case CT_squash_mirror_diag:
01958           accent_mat =
01959             LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) *
01960             LMatrix4f::scale_mat(-squash_accent_scale_x, 1.0f, squash_accent_scale_y);
01961           
01962           // rotate min, max
01963           t = min_accent[0];
01964           u = max_accent[0];
01965           min_accent[0] = min_accent[2] * -squash_accent_scale_x;
01966           max_accent[0] = max_accent[2] * -squash_accent_scale_x;
01967           min_accent[2] = -u * squash_accent_scale_y;
01968           max_accent[2] = -t * squash_accent_scale_y;
01969           mirrored = true;
01970           break;
01971 
01972         case CT_small_squash:
01973           accent_mat = LMatrix4f::scale_mat(small_squash_accent_scale_x, 1.0f, small_squash_accent_scale_y);
01974           min_accent[0] *= small_squash_accent_scale_x;
01975           max_accent[0] *= small_squash_accent_scale_x;
01976           min_accent[2] *= small_squash_accent_scale_y;
01977           max_accent[2] *= small_squash_accent_scale_y;
01978           break;
01979 
01980         case CT_small_squash_mirror_y:
01981           accent_mat = LMatrix4f::scale_mat(small_squash_accent_scale_x, 1.0f, -small_squash_accent_scale_y);
01982           min_accent[0] *= small_squash_accent_scale_x;
01983           max_accent[0] *= small_squash_accent_scale_x;
01984           t = min_accent[2];
01985           min_accent[2] = -max_accent[2] * small_squash_accent_scale_y;
01986           max_accent[2] = -t * small_squash_accent_scale_y;
01987           mirrored = true;
01988           break;
01989 
01990         case CT_small_squash_mirror_diag:
01991           accent_mat =
01992             LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) *
01993             LMatrix4f::scale_mat(-small_squash_accent_scale_x, 1.0f, small_squash_accent_scale_y);
01994           
01995           // rotate min, max
01996           t = min_accent[0];
01997           u = max_accent[0];
01998           min_accent[0] = min_accent[2] * -small_squash_accent_scale_x;
01999           max_accent[0] = max_accent[2] * -small_squash_accent_scale_x;
02000           min_accent[2] = -u * small_squash_accent_scale_y;
02001           max_accent[2] = -t * small_squash_accent_scale_y;
02002           mirrored = true;
02003           break;
02004 
02005         case CT_small:
02006           accent_mat = LMatrix4f::scale_mat(small_accent_scale);
02007           min_accent *= small_accent_scale;
02008           max_accent *= small_accent_scale;
02009           break;
02010 
02011         case CT_small_rotate_270:
02012           accent_mat =
02013             LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) *
02014             LMatrix4f::scale_mat(small_accent_scale);
02015 
02016           // rotate min, max
02017           t = min_accent[0];
02018           u = max_accent[0];
02019           min_accent[0] = min_accent[2] * small_accent_scale;
02020           max_accent[0] = max_accent[2] * small_accent_scale;
02021           min_accent[2] = -u * small_accent_scale;
02022           max_accent[2] = -t * small_accent_scale;
02023           break;
02024 
02025         case CT_tiny:
02026           accent_mat = LMatrix4f::scale_mat(tiny_accent_scale);
02027           min_accent *= tiny_accent_scale;
02028           max_accent *= tiny_accent_scale;
02029           break;
02030 
02031         case CT_tiny_mirror_x:
02032           accent_mat = LMatrix4f::scale_mat(-tiny_accent_scale, 1.0f, tiny_accent_scale);
02033           
02034           t = min_accent[0];
02035           min_accent[0] = -max_accent[0] * tiny_accent_scale;
02036           max_accent[0] = -t * tiny_accent_scale;
02037           min_accent[2] *= tiny_accent_scale;
02038           max_accent[2] *= tiny_accent_scale;
02039           mirrored = true;
02040           break;
02041 
02042         case CT_tiny_rotate_270:
02043           accent_mat =
02044             LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) *
02045             LMatrix4f::scale_mat(tiny_accent_scale);
02046 
02047           // rotate min, max
02048           t = min_accent[0];
02049           u = max_accent[0];
02050           min_accent[0] = min_accent[2] * tiny_accent_scale;
02051           max_accent[0] = max_accent[2] * tiny_accent_scale;
02052           min_accent[2] = -u * tiny_accent_scale;
02053           max_accent[2] = -t * tiny_accent_scale;
02054           break;
02055         }
02056 
02057         LPoint3f accent_centroid = (min_accent + max_accent) / 2.0f;
02058         float accent_height = max_accent[2] - min_accent[2];
02059         LVector3f trans;
02060         switch (position) {
02061         case CP_above:
02062           // A little above the character.
02063           trans.set(centroid[0] - accent_centroid[0], 0.0f,
02064                     max_vert[2] - accent_centroid[2] + accent_height * 0.5);
02065           break;
02066 
02067         case CP_below:
02068           // A little below the character.
02069           trans.set(centroid[0] - accent_centroid[0], 0.0f,
02070                     min_vert[2] - accent_centroid[2] - accent_height * 0.5);
02071           break;
02072 
02073         case CP_top:
02074           // Touching the top of the character.
02075           trans.set(centroid[0] - accent_centroid[0], 0.0f,
02076                     max_vert[2] - accent_centroid[2]);
02077           break;
02078 
02079         case CP_bottom:
02080           // Touching the bottom of the character.
02081           trans.set(centroid[0] - accent_centroid[0], 0.0f,
02082                     min_vert[2] - accent_centroid[2]);
02083           break;
02084 
02085         case CP_within:
02086           // Centered within the character.
02087           trans.set(centroid[0] - accent_centroid[0], 0.0f,
02088                     centroid[2] - accent_centroid[2]);
02089           break;
02090         }
02091 
02092         accent_mat.set_row(3, trans);
02093         accent_geom->transform_vertices(accent_mat);
02094 
02095         if (mirrored) {
02096           // Once someone asks for this pointer, we hold its reference
02097           // count and never free it.
02098           static CPT(RenderState) disable_backface;
02099           if (disable_backface == (const RenderState *)NULL) {
02100             disable_backface = RenderState::make
02101               (CullFaceAttrib::make(CullFaceAttrib::M_cull_none));
02102           }
02103             
02104           CPT(RenderState) state = 
02105             accent_glyph->get_state()->compose(disable_backface);
02106           placement->add_piece(accent_geom, state);
02107         } else {
02108           placement->add_piece(accent_geom, accent_glyph->get_state());
02109         }
02110 
02111         return true;
02112       }
02113     }
02114   }
02115   return false;
02116 }
02117 
02118 ////////////////////////////////////////////////////////////////////
02119 //     Function: TextAssembler::ComputedProperties::append_delta
02120 //       Access: Public
02121 //  Description: Appends to wtext the control sequences necessary to
02122 //               change from this ComputedProperties to the indicated
02123 //               ComputedProperties.
02124 ////////////////////////////////////////////////////////////////////
02125 void TextAssembler::ComputedProperties::
02126 append_delta(wstring &wtext, TextAssembler::ComputedProperties *other) {
02127   if (this != other) {
02128     if (_depth > other->_depth) {
02129       // Back up a level from this properties.
02130       nassertv(_based_on != NULL);
02131 
02132       wtext.push_back(text_pop_properties_key);
02133       _based_on->append_delta(wtext, other);
02134       
02135     } else if (other->_depth > _depth) {
02136       // Back up a level from the other properties.
02137       nassertv(other->_based_on != NULL);
02138 
02139       append_delta(wtext, other->_based_on);
02140       wtext.push_back(text_push_properties_key);
02141       wtext += other->_wname;
02142       wtext.push_back(text_push_properties_key);
02143       
02144     } else if (_depth != 0) {
02145       // Back up a level from both properties.
02146       nassertv(_based_on != NULL && other->_based_on != NULL);
02147       
02148       wtext.push_back(text_pop_properties_key);
02149       _based_on->append_delta(wtext, other->_based_on);
02150       wtext.push_back(text_push_properties_key);
02151       wtext += other->_wname;
02152       wtext.push_back(text_push_properties_key);
02153     }
02154   }
02155 }
02156 
02157 ////////////////////////////////////////////////////////////////////
02158 //     Function: TextAssembler::GlyphPlacement::calc_tight_bounds
02159 //       Access: Private
02160 //  Description: Expands min_point and max_point to include all of the
02161 //               vertices in the glyph(s), if any.  found_any is set
02162 //               true if any points are found.  It is the caller's
02163 //               responsibility to initialize min_point, max_point,
02164 //               and found_any before calling this function.
02165 ////////////////////////////////////////////////////////////////////
02166 void TextAssembler::GlyphPlacement::
02167 calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point,
02168                   bool &found_any, Thread *current_thread) const {
02169   Pieces::const_iterator pi;
02170   for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
02171     (*pi)._geom->calc_tight_bounds(min_point, max_point, found_any,
02172                                    current_thread);
02173   }
02174 }
02175 
02176 ////////////////////////////////////////////////////////////////////
02177 //     Function: TextAssembler::GlyphPlacement::assign_to
02178 //       Access: Private
02179 //  Description: Puts the pieces of the GlyphPlacement in the
02180 //               indicated GeomNode.  The vertices of the Geoms are
02181 //               modified by this operation.
02182 ////////////////////////////////////////////////////////////////////
02183 void TextAssembler::GlyphPlacement::
02184 assign_to(GeomNode *geom_node, const RenderState *state) const {
02185   Pieces::const_iterator pi;
02186   for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
02187     (*pi)._geom->transform_vertices(_xform);
02188     geom_node->add_geom((*pi)._geom, state->compose((*pi)._state));
02189   }
02190 }
02191 
02192 ////////////////////////////////////////////////////////////////////
02193 //     Function: TextAssembler::GlyphPlacement::assign_copy_to
02194 //       Access: Private
02195 //  Description: Puts the pieces of the GlyphPlacement in the
02196 //               indicated GeomNode.  This flavor will make a copy of
02197 //               the Geoms first, and then apply the additional
02198 //               transform to the vertices.
02199 ////////////////////////////////////////////////////////////////////
02200 void TextAssembler::GlyphPlacement::
02201 assign_copy_to(GeomNode *geom_node, const RenderState *state,
02202                const LMatrix4f &extra_xform) const {
02203   LMatrix4f new_xform = _xform * extra_xform;
02204   Pieces::const_iterator pi;
02205   for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
02206     const Geom *geom = (*pi)._geom;
02207     PT(Geom) new_geom = geom->make_copy();
02208     new_geom->transform_vertices(new_xform);
02209     geom_node->add_geom(new_geom, state->compose((*pi)._state));
02210   }
02211 }
02212 
02213 ////////////////////////////////////////////////////////////////////
02214 //     Function: TextAssembler::GlyphPlacement::assign_append_to
02215 //       Access: Private
02216 //  Description: Puts the pieces of the GlyphPlacement in the
02217 //               indicated GeomNode.  This flavor will append the
02218 //               Geoms with the additional transform applied to the
02219 //               vertices.
02220 ////////////////////////////////////////////////////////////////////
02221 void TextAssembler::GlyphPlacement::
02222 assign_append_to(GeomCollectorMap &geom_collector_map, 
02223                  const RenderState *state,
02224                  const LMatrix4f &extra_xform) const {
02225   LMatrix4f new_xform = _xform * extra_xform;
02226   Pieces::const_iterator pi;
02227 
02228   int p, sp, s, e, i;
02229   for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
02230     const Geom *geom = (*pi)._geom;
02231     const GeomVertexData *vdata = geom->get_vertex_data();
02232     CPT(RenderState) rs = (*pi)._state->compose(state);
02233     GeomCollectorKey key(rs, vdata->get_format());
02234 
02235     GeomCollectorMap::iterator mi = geom_collector_map.find(key);
02236     if (mi == geom_collector_map.end()) {
02237       mi = geom_collector_map.insert(GeomCollectorMap::value_type(key, GeomCollector(vdata->get_format()))).first;
02238     }
02239     GeomCollector &geom_collector = (*mi).second;
02240     geom_collector.count_geom(geom);
02241 
02242     // We use this map to keep track of vertex indices we have already
02243     // added, so that we don't needlessly duplicate vertices into our
02244     // output vertex data.
02245     VertexIndexMap vimap;
02246 
02247     for (p = 0; p < geom->get_num_primitives(); p++) {
02248       CPT(GeomPrimitive) primitive = geom->get_primitive(p)->decompose();
02249 
02250       // Get a new GeomPrimitive of the corresponding type.
02251       GeomPrimitive *new_prim = geom_collector.get_primitive(primitive->get_type());
02252 
02253       // Walk through all of the components (e.g. triangles) of the
02254       // primitive.
02255       for (sp = 0; sp < primitive->get_num_primitives(); sp++) {
02256         s = primitive->get_primitive_start(sp);
02257         e = primitive->get_primitive_end(sp);
02258 
02259         // Walk through all of the vertices in the component.
02260         for (i = s; i < e; i++) {
02261           int vi = primitive->get_vertex(i);
02262 
02263           // Attempt to insert number "vi" into the map.
02264           pair<VertexIndexMap::iterator, bool> added = vimap.insert(VertexIndexMap::value_type(vi, 0));
02265           int new_vertex;
02266           if (added.second) {
02267             // The insert succeeded.  That means this is the first
02268             // time we have encountered this vertex.
02269             new_vertex = geom_collector.append_vertex(vdata, vi, new_xform);
02270             // Update the map with the newly-created target vertex index.
02271             (*(added.first)).second = new_vertex;
02272 
02273           } else {
02274             // The insert failed.  This means we have previously
02275             // encountered this vertex, and we have already entered
02276             // its target vertex index into the vimap.  Extract that
02277             // vertex index, so we can reuse it.
02278             new_vertex = (*(added.first)).second;
02279           }
02280           new_prim->add_vertex(new_vertex);
02281         }
02282         new_prim->close_primitive();
02283       }
02284     }
02285   }
02286 }
02287 
02288 ////////////////////////////////////////////////////////////////////
02289 //     Function: TextAssembler::GlyphPlacement::copy_graphic_to
02290 //       Access: Private
02291 //  Description: If the GlyphPlacement includes a special graphic,
02292 //               copies it to the indicated node.
02293 ////////////////////////////////////////////////////////////////////
02294 void TextAssembler::GlyphPlacement::
02295 copy_graphic_to(PandaNode *node, const RenderState *state,
02296                 const LMatrix4f &extra_xform) const {
02297   if (_graphic_model != (PandaNode *)NULL) {
02298     LMatrix4f new_xform = _xform * extra_xform;
02299 
02300     // We need an intermediate node to hold the transform and state.
02301     PT(PandaNode) intermediate_node = new PandaNode("");
02302     node->add_child(intermediate_node);
02303 
02304     intermediate_node->set_transform(TransformState::make_mat(new_xform));
02305     intermediate_node->set_state(state);
02306     intermediate_node->add_child(_graphic_model);
02307   }
02308 }
02309 
02310 ////////////////////////////////////////////////////////////////////
02311 //     Function: TextAssembler::GeomCollector Constructor
02312 //       Access: Public
02313 //  Description: constructs the GeomCollector class 
02314 //               (Geom, GeomTriangles, vertexWriter, texcoordWriter..)
02315 ////////////////////////////////////////////////////////////////////
02316 TextAssembler::GeomCollector::
02317 GeomCollector(const GeomVertexFormat *format) :
02318   _vdata(new GeomVertexData("merged_geom", format, Geom::UH_static)),
02319   _geom(new GeomTextGlyph(_vdata))
02320 {
02321 }
02322 
02323 ////////////////////////////////////////////////////////////////////
02324 //     Function: TextAssembler::GeomCollector Copy Constructor
02325 //       Access: Public
02326 //  Description: 
02327 ////////////////////////////////////////////////////////////////////
02328 TextAssembler::GeomCollector::
02329 GeomCollector(const TextAssembler::GeomCollector &copy) :
02330   _vdata(copy._vdata),
02331   _geom(copy._geom)
02332 {
02333 }
02334 
02335 ////////////////////////////////////////////////////////////////////
02336 //     Function: TextAssembler::GeomCollector::get_primitive
02337 //       Access: Public
02338 //  Description: Returns a GeomPrimitive of the appropriate type.  If
02339 //               one has not yet been created, returns a newly-created
02340 //               one; if one has previously been created of this type,
02341 //               returns the previously-created one.
02342 ////////////////////////////////////////////////////////////////////
02343 GeomPrimitive *TextAssembler::GeomCollector::
02344 get_primitive(TypeHandle prim_type) {
02345   if (prim_type == GeomTriangles::get_class_type()) {
02346     if (_triangles == (GeomPrimitive *)NULL) {
02347       _triangles = new GeomTriangles(Geom::UH_static);
02348       _geom->add_primitive(_triangles);
02349     }
02350     return _triangles;
02351 
02352   } else if (prim_type == GeomLines::get_class_type()) {
02353     if (_lines == (GeomPrimitive *)NULL) {
02354       _lines = new GeomLines(Geom::UH_static);
02355       _geom->add_primitive(_lines);
02356     }
02357     return _lines;
02358 
02359   } else if (prim_type == GeomPoints::get_class_type()) {
02360     if (_points == (GeomPrimitive *)NULL) {
02361       _points = new GeomPoints(Geom::UH_static);
02362       _geom->add_primitive(_points);
02363     }
02364     return _points;
02365   }
02366 
02367   nassertr(false, NULL);
02368   return NULL;
02369 }
02370 
02371 ////////////////////////////////////////////////////////////////////
02372 //     Function: TextAssembler::GeomCollector::append_vertex
02373 //       Access: Public
02374 //  Description: Adds one vertex to the GeomVertexData.
02375 //               Returns the row number of the added vertex.
02376 ////////////////////////////////////////////////////////////////////
02377 int TextAssembler::GeomCollector::
02378 append_vertex(const GeomVertexData *orig_vdata, int orig_row,
02379               const LMatrix4f &xform) {
02380   int new_row = _vdata->get_num_rows();
02381   _vdata->copy_row_from(new_row, orig_vdata, orig_row, Thread::get_current_thread());
02382 
02383   GeomVertexRewriter vertex_rewriter(_vdata, InternalName::get_vertex());
02384   vertex_rewriter.set_row(new_row);
02385   LPoint3f point = vertex_rewriter.get_data3f();
02386   vertex_rewriter.set_data3f(point * xform);
02387 
02388   return new_row;
02389 }
02390 
02391 
02392 ////////////////////////////////////////////////////////////////////
02393 //     Function: TextAssembler::GeomCollector::append_geom
02394 //       Access: Public
02395 //  Description: closes the geomTriangles and appends the geom to 
02396 //               the given GeomNode
02397 ////////////////////////////////////////////////////////////////////
02398 void TextAssembler::GeomCollector::
02399 append_geom(GeomNode *geom_node, const RenderState *state) {
02400   if (_geom->get_num_primitives() > 0) {
02401     geom_node->add_geom(_geom, state);
02402   }
02403 }
02404 
 All Classes Functions Variables Enumerations