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 PN_stdfloat small_accent_scale = 0.6f;
00041 
00042 // This is the factor by which CT_tiny scales the character down.
00043 static const PN_stdfloat tiny_accent_scale = 0.4;
00044 
00045 // This is the factor by which CT_squash scales the character in X and Y.
00046 static const PN_stdfloat squash_accent_scale_x = 0.8f;
00047 static const PN_stdfloat 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 PN_stdfloat small_squash_accent_scale_x = 0.6f;
00051 static const PN_stdfloat small_squash_accent_scale_y = 0.3;
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 PN_stdfloat 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 PN_stdfloat 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     PN_stdfloat 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   LMatrix4 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 = LMatrix4::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         LVector2 offset = properties->get_shadow();
00616         shadow_xform = LMatrix4::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, LMatrix4::ident_mat());
00639     } else {
00640       placement->assign_to(text_geom_node, text_state);
00641     }
00642     placement->copy_graphic_to(text_node, text_state, LMatrix4::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 PN_stdfloat 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   PN_stdfloat glyph_scale;
00694   PN_stdfloat 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   PN_stdfloat 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 PN_stdfloat TextAssembler::
00719 calc_width(const TextGraphic *graphic, const TextProperties &properties) {
00720   LVecBase4 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   PN_stdfloat glyph_scale;
00784   PN_stdfloat 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   PN_stdfloat 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     PN_stdfloat 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     PN_stdfloat wordwrap_width = -1.0f;
01002 
01003     bool last_was_space = false;
01004     PN_stdfloat 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 PN_stdfloat 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   PN_stdfloat 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   PN_stdfloat 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     PN_stdfloat 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     LMatrix4 mat = LMatrix4::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.2 * 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     PN_stdfloat 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, LVector3(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              PN_stdfloat &row_width, PN_stdfloat &line_height, 
01329              TextProperties::Alignment &align, PN_stdfloat &wordwrap) {
01330   Thread *current_thread = Thread::get_current_thread();
01331 
01332   line_height = 0.0f;
01333   PN_stdfloat xpos = 0.0f;
01334   align = TextProperties::A_left;
01335 
01336   bool underscore = false;
01337   PN_stdfloat 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       LVecBase4 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       PN_stdfloat 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       LVecBase4 frame = graphic->get_frame();
01412       PN_stdfloat glyph_scale = properties->get_glyph_scale() * properties->get_text_scale();
01413 
01414       PN_stdfloat advance = (frame[1] - frame[0]);
01415 
01416       // Now compute the matrix that will transform the glyph (or
01417       // glyphs) into position.
01418       LMatrix4 glyph_xform = LMatrix4::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         LMatrix4 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       PN_stdfloat glyph_scale;
01444       PN_stdfloat 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       PN_stdfloat 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(LMatrix4::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       LMatrix4 glyph_xform = LMatrix4::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         LPoint3 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           LPoint3 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             LMatrix4 rotate =
01524               LMatrix4::translate_mat(-centroid) *
01525               LMatrix4::rotate_mat_normaxis(180.0f, LVecBase3(0.0f, -1.0f, 0.0f)) *
01526               LMatrix4::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         LMatrix4 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       PN_stdfloat 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                 PN_stdfloat underscore_start, PN_stdfloat 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   vdata->reserve_num_rows(2);
01588   GeomVertexWriter vertex(vdata, InternalName::get_vertex());
01589   GeomVertexWriter color(vdata, InternalName::get_color());
01590 
01591   PN_stdfloat y = underscore_properties->get_underscore_height();
01592   vertex.add_data3(underscore_start, 0.0f, y);
01593   color.add_data4(underscore_properties->get_text_color());
01594   vertex.add_data3(underscore_end, 0.0f, y);
01595   color.add_data4(underscore_properties->get_text_color());
01596 
01597   PT(GeomLines) lines = new GeomLines(Geom::UH_static);
01598   lines->add_vertices(0, 1);
01599   lines->close_primitive();
01600 
01601   PT(Geom) geom = new Geom(vdata);
01602   geom->add_primitive(lines);
01603 
01604   GlyphPlacement *placement = new GlyphPlacement;
01605   placement->add_piece(geom, RenderState::make_empty());
01606   placement->_xform = LMatrix4::ident_mat();
01607   placement->_properties = underscore_properties;
01608 
01609   row_placed_glyphs.push_back(placement);
01610 }
01611 
01612 ////////////////////////////////////////////////////////////////////
01613 //     Function: TextAssembler::get_character_glyphs
01614 //       Access: Private, Static
01615 //  Description: Looks up the glyph(s) from the font for the
01616 //               appropriate character.  If the desired glyph isn't
01617 //               available (especially in the case of an accented
01618 //               letter), tries to find a suitable replacement.
01619 //               Normally, only one glyph is returned per character,
01620 //               but in the case in which we have to simulate a
01621 //               missing ligature in the font, two glyphs might be
01622 //               returned.
01623 //
01624 //               All parameters except the first two are output
01625 //               parameters.  got_glyph is set true if the glyph (or
01626 //               an acceptable substitute) is successfully found,
01627 //               false otherwise; but even if it is false, glyph might
01628 //               still be non-NULL, indicating a stand-in glyph for a
01629 //               missing character.
01630 ////////////////////////////////////////////////////////////////////
01631 void TextAssembler::
01632 get_character_glyphs(int character, const TextProperties *properties,
01633                      bool &got_glyph, const TextGlyph *&glyph,
01634                      const TextGlyph *&second_glyph,
01635                      UnicodeLatinMap::AccentType &accent_type,
01636                      int &additional_flags,
01637                      PN_stdfloat &glyph_scale, PN_stdfloat &advance_scale) {
01638   TextFont *font = properties->get_font();
01639   nassertv_always(font != (TextFont *)NULL);
01640 
01641   got_glyph = false;
01642   glyph = NULL;
01643   second_glyph = NULL;
01644   accent_type = UnicodeLatinMap::AT_none;
01645   additional_flags = 0;
01646   glyph_scale = 1.0f;
01647   advance_scale = 1.0f;
01648 
01649   // Maybe we should remap the character to something else--e.g. a
01650   // small capital.
01651   const UnicodeLatinMap::Entry *map_entry = 
01652     UnicodeLatinMap::look_up(character);
01653   if (map_entry != NULL) {
01654     if (properties->get_small_caps() && 
01655         map_entry->_toupper_character != character) {
01656       character = map_entry->_toupper_character;
01657       map_entry = UnicodeLatinMap::look_up(character);
01658       glyph_scale = properties->get_small_caps_scale();
01659     }
01660   }
01661   
01662   got_glyph = font->get_glyph(character, glyph);
01663   if (!got_glyph && map_entry != NULL && map_entry->_ascii_equiv != 0) {
01664     // If we couldn't find the Unicode glyph, try the ASCII
01665     // equivalent (without the accent marks).
01666     got_glyph = font->get_glyph(map_entry->_ascii_equiv, glyph);
01667     
01668     if (!got_glyph && map_entry->_toupper_character != character) {
01669       // If we still couldn't find it, try the uppercase
01670       // equivalent.
01671       character = map_entry->_toupper_character;
01672       map_entry = UnicodeLatinMap::look_up(character);
01673       if (map_entry != NULL) {
01674         got_glyph = font->get_glyph(map_entry->_ascii_equiv, glyph);
01675       }
01676     }
01677     
01678     if (got_glyph) {
01679       accent_type = map_entry->_accent_type;
01680       additional_flags = map_entry->_additional_flags;
01681       
01682       bool got_second_glyph = false;
01683       if (map_entry->_ascii_additional != 0) {
01684         // There's another character, too--probably a ligature.
01685         got_second_glyph = 
01686           font->get_glyph(map_entry->_ascii_additional, second_glyph);
01687       }
01688       
01689       if ((additional_flags & UnicodeLatinMap::AF_ligature) != 0 &&
01690           got_second_glyph) {
01691         // If we have two letters that are supposed to be in a
01692         // ligature, just jam them together.
01693         additional_flags &= ~UnicodeLatinMap::AF_ligature;
01694         advance_scale = ligature_advance_scale;
01695       }
01696       
01697       if ((additional_flags & UnicodeLatinMap::AF_smallcap) != 0) {
01698         additional_flags &= ~UnicodeLatinMap::AF_smallcap;
01699         glyph_scale = properties->get_small_caps_scale();
01700       }
01701     }
01702   }
01703 }
01704   
01705   
01706 ////////////////////////////////////////////////////////////////////
01707 //     Function: TextAssembler::tack_on_accent
01708 //       Access: Private
01709 //  Description: This is a cheesy attempt to tack on an accent to an
01710 //               ASCII letter for which we don't have the appropriate
01711 //               already-accented glyph in the font.
01712 ////////////////////////////////////////////////////////////////////
01713 void TextAssembler::
01714 tack_on_accent(UnicodeLatinMap::AccentType accent_type,
01715                const LPoint3 &min_vert, const LPoint3 &max_vert,
01716                const LPoint3 &centroid,
01717                const TextProperties *properties, 
01718                TextAssembler::GlyphPlacement *placement) const {
01719   switch (accent_type) {
01720   case UnicodeLatinMap::AT_grave:
01721     // We use the slash as the grave and acute accents.  ASCII does
01722     // have a grave accent character, but a lot of fonts put the
01723     // reverse apostrophe there instead.  And some fonts (particularly
01724     // fonts from mf) don't even do backslash.
01725     tack_on_accent('/', CP_above, CT_small_squash_mirror_y, min_vert, max_vert, centroid,
01726                    properties, placement);
01727     break;
01728 
01729   case UnicodeLatinMap::AT_acute:
01730     tack_on_accent('/', CP_above, CT_small_squash, min_vert, max_vert, centroid,
01731                    properties, placement);
01732     break;
01733 
01734   case UnicodeLatinMap::AT_breve:
01735     tack_on_accent(')', CP_above, CT_tiny_rotate_270, min_vert, max_vert, centroid,
01736                    properties, placement);
01737     break;
01738 
01739   case UnicodeLatinMap::AT_inverted_breve:
01740     tack_on_accent('(', CP_above, CT_tiny_rotate_270, min_vert, max_vert, centroid,
01741                    properties, placement);
01742     break;
01743 
01744   case UnicodeLatinMap::AT_circumflex:
01745     tack_on_accent('^', CP_above, CT_none, min_vert, max_vert, centroid,
01746                    properties, placement) ||
01747       tack_on_accent('v', CP_above, CT_squash_mirror_y, min_vert, max_vert, centroid,
01748                      properties, placement);
01749     break;
01750 
01751   case UnicodeLatinMap::AT_circumflex_below:
01752     tack_on_accent('^', CP_below, CT_none, min_vert, max_vert, centroid,
01753                    properties, placement) ||
01754       tack_on_accent('v', CP_below, CT_squash_mirror_y, min_vert, max_vert, centroid,
01755                      properties, placement);
01756     break;
01757 
01758   case UnicodeLatinMap::AT_caron:
01759     tack_on_accent('^', CP_above, CT_mirror_y, min_vert, max_vert, centroid,
01760                    properties, placement) ||
01761       tack_on_accent('v', CP_above, CT_squash, min_vert, max_vert, centroid,
01762                      properties, placement);
01763 
01764     break;
01765 
01766   case UnicodeLatinMap::AT_tilde:
01767     tack_on_accent('~', CP_above, CT_none, min_vert, max_vert, centroid,
01768                    properties, placement) ||
01769       tack_on_accent('s', CP_above, CT_squash_mirror_diag, min_vert, max_vert, centroid,
01770                      properties, placement);
01771 
01772     break;
01773 
01774   case UnicodeLatinMap::AT_tilde_below:
01775     tack_on_accent('~', CP_below, CT_none, min_vert, max_vert, centroid,
01776                    properties, placement) ||
01777       tack_on_accent('s', CP_below, CT_squash_mirror_diag, min_vert, max_vert, centroid,
01778                      properties, placement);
01779     break;
01780 
01781   case UnicodeLatinMap::AT_diaeresis:
01782     tack_on_accent(':', CP_above, CT_small_rotate_270, min_vert, max_vert, centroid,
01783                    properties, placement);
01784     break;
01785 
01786   case UnicodeLatinMap::AT_diaeresis_below:
01787     tack_on_accent(':', CP_below, CT_small_rotate_270, min_vert, max_vert, centroid,
01788                    properties, placement);
01789     break;
01790 
01791   case UnicodeLatinMap::AT_dot_above:
01792     tack_on_accent('.', CP_above, CT_none, min_vert, max_vert, centroid,
01793                    properties, placement);
01794     break;
01795 
01796   case UnicodeLatinMap::AT_dot_below:
01797     tack_on_accent('.', CP_below, CT_none, min_vert, max_vert, centroid,
01798                    properties, placement);
01799     break;
01800 
01801   case UnicodeLatinMap::AT_macron:
01802     tack_on_accent('-', CP_above, CT_none, min_vert, max_vert, centroid,
01803                    properties, placement);
01804     break;
01805 
01806   case UnicodeLatinMap::AT_line_below:
01807     tack_on_accent('-', CP_below, CT_none, min_vert, max_vert, centroid,
01808                    properties, placement);
01809     break;
01810 
01811   case UnicodeLatinMap::AT_ring_above:
01812     tack_on_accent('o', CP_top, CT_tiny, min_vert, max_vert, centroid,
01813                    properties, placement);
01814     break;
01815 
01816   case UnicodeLatinMap::AT_ring_below:
01817     tack_on_accent('o', CP_bottom, CT_tiny, min_vert, max_vert, centroid,
01818                    properties, placement);
01819     break;
01820 
01821   case UnicodeLatinMap::AT_cedilla:
01822     tack_on_accent('c', CP_bottom, CT_tiny_mirror_x, min_vert, max_vert, centroid,
01823                    properties, placement);
01824     //tack_on_accent(',', CP_bottom, CT_none, min_vert, max_vert, centroid,
01825     //               properties, placement);
01826     break;
01827 
01828   case UnicodeLatinMap::AT_comma_below:
01829     tack_on_accent(',', CP_below, CT_none, min_vert, max_vert, centroid,
01830                    properties, placement);
01831     break;
01832 
01833   case UnicodeLatinMap::AT_ogonek:
01834     tack_on_accent(',', CP_bottom, CT_mirror_x, min_vert, max_vert, centroid,
01835                    properties, placement);
01836     break;
01837 
01838   case UnicodeLatinMap::AT_stroke:
01839     tack_on_accent('/', CP_within, CT_none, min_vert, max_vert, centroid,
01840                    properties, placement);
01841     break;
01842 
01843   default:
01844     // There are lots of other crazy kinds of accents.  Forget 'em.
01845     break;
01846   }    
01847 }
01848 
01849 ////////////////////////////////////////////////////////////////////
01850 //     Function: TextAssembler::tack_on_accent
01851 //       Access: Private
01852 //  Description: Generates a cheesy accent mark above (or below, etc.)
01853 //               the character.  Returns true if successful, or false
01854 //               if the named accent character doesn't exist in the
01855 //               font.
01856 ////////////////////////////////////////////////////////////////////
01857 bool TextAssembler::
01858 tack_on_accent(char accent_mark, TextAssembler::CheesyPosition position,
01859                TextAssembler::CheesyTransform transform,
01860                const LPoint3 &min_vert, const LPoint3 &max_vert,
01861                const LPoint3 &centroid,
01862                const TextProperties *properties,
01863                TextAssembler::GlyphPlacement *placement) const {
01864   TextFont *font = properties->get_font();
01865   nassertr(font != (TextFont *)NULL, false);
01866 
01867   Thread *current_thread = Thread::get_current_thread();
01868 
01869   const TextGlyph *accent_glyph;
01870   if (font->get_glyph(accent_mark, accent_glyph) ||
01871       font->get_glyph(toupper(accent_mark), accent_glyph)) {
01872     PT(Geom) accent_geom = accent_glyph->get_geom(_usage_hint);
01873     if (accent_geom != (Geom *)NULL) {
01874       LPoint3 min_accent, max_accent;
01875       bool found_any = false;
01876       accent_geom->calc_tight_bounds(min_accent, max_accent, found_any,
01877                                      current_thread);
01878       if (found_any) {
01879         PN_stdfloat t, u;
01880         LMatrix4 accent_mat;
01881 
01882         // This gets set to true if the glyph gets mirrored and needs
01883         // to have backface culling disabled.
01884         bool mirrored = false;
01885 
01886         switch (transform) {
01887         case CT_none:
01888           accent_mat = LMatrix4::ident_mat();
01889           break;
01890 
01891         case CT_mirror_x:
01892           accent_mat = LMatrix4::scale_mat(-1.0f, 1.0f, 1.0f);
01893           t = min_accent[0];
01894           min_accent[0] = -max_accent[0];
01895           max_accent[0] = -t;
01896           mirrored = true;
01897           break;
01898 
01899         case CT_mirror_y:
01900           accent_mat = LMatrix4::scale_mat(1.0f, 1.0f, -1.0f);
01901           t = min_accent[2];
01902           min_accent[2] = -max_accent[2];
01903           max_accent[2] = -t;
01904           mirrored = true;
01905           break;
01906 
01907         case CT_rotate_90:
01908           accent_mat.set_rotate_mat_normaxis(90.0f, LVecBase3(0.0f, -1.0f, 0.0f));
01909           // rotate min, max
01910           t = min_accent[0];
01911           u = max_accent[0];
01912           max_accent[0] = -min_accent[2];
01913           min_accent[0] = -max_accent[2];
01914           max_accent[2] = u;
01915           min_accent[2] = t;
01916           break;
01917 
01918         case CT_rotate_180:
01919           accent_mat = LMatrix4::scale_mat(-1.0f, -1.0f, 1.0f);
01920           
01921           t = min_accent[0];
01922           min_accent[0] = -max_accent[0];
01923           max_accent[0] = -t;
01924           t = min_accent[2];
01925           min_accent[2] = -max_accent[2];
01926           max_accent[2] = -t;
01927           break;
01928 
01929         case CT_rotate_270:
01930           accent_mat.set_rotate_mat_normaxis(270.0f, LVecBase3(0.0f, -1.0f, 0.0f));
01931           // rotate min, max
01932           t = min_accent[0];
01933           u = max_accent[0];
01934           min_accent[0] = min_accent[2];
01935           max_accent[0] = max_accent[2];
01936           min_accent[2] = -u;
01937           max_accent[2] = -t;
01938           break;
01939 
01940         case CT_squash:
01941           accent_mat = LMatrix4::scale_mat(squash_accent_scale_x, 1.0f, squash_accent_scale_y);
01942           min_accent[0] *= squash_accent_scale_x;
01943           max_accent[0] *= squash_accent_scale_x;
01944           min_accent[2] *= squash_accent_scale_y;
01945           max_accent[2] *= squash_accent_scale_y;
01946           break;
01947 
01948         case CT_squash_mirror_y:
01949           accent_mat = LMatrix4::scale_mat(squash_accent_scale_x, 1.0f, -squash_accent_scale_y);
01950           min_accent[0] *= squash_accent_scale_x;
01951           max_accent[0] *= squash_accent_scale_x;
01952           t = min_accent[2];
01953           min_accent[2] = -max_accent[2] * squash_accent_scale_y;
01954           max_accent[2] = -t * squash_accent_scale_y;
01955           mirrored = true;
01956           break;
01957 
01958         case CT_squash_mirror_diag:
01959           accent_mat =
01960             LMatrix4::rotate_mat_normaxis(270.0f, LVecBase3(0.0f, -1.0f, 0.0f)) *
01961             LMatrix4::scale_mat(-squash_accent_scale_x, 1.0f, squash_accent_scale_y);
01962           
01963           // rotate min, max
01964           t = min_accent[0];
01965           u = max_accent[0];
01966           min_accent[0] = min_accent[2] * -squash_accent_scale_x;
01967           max_accent[0] = max_accent[2] * -squash_accent_scale_x;
01968           min_accent[2] = -u * squash_accent_scale_y;
01969           max_accent[2] = -t * squash_accent_scale_y;
01970           mirrored = true;
01971           break;
01972 
01973         case CT_small_squash:
01974           accent_mat = LMatrix4::scale_mat(small_squash_accent_scale_x, 1.0f, small_squash_accent_scale_y);
01975           min_accent[0] *= small_squash_accent_scale_x;
01976           max_accent[0] *= small_squash_accent_scale_x;
01977           min_accent[2] *= small_squash_accent_scale_y;
01978           max_accent[2] *= small_squash_accent_scale_y;
01979           break;
01980 
01981         case CT_small_squash_mirror_y:
01982           accent_mat = LMatrix4::scale_mat(small_squash_accent_scale_x, 1.0f, -small_squash_accent_scale_y);
01983           min_accent[0] *= small_squash_accent_scale_x;
01984           max_accent[0] *= small_squash_accent_scale_x;
01985           t = min_accent[2];
01986           min_accent[2] = -max_accent[2] * small_squash_accent_scale_y;
01987           max_accent[2] = -t * small_squash_accent_scale_y;
01988           mirrored = true;
01989           break;
01990 
01991         case CT_small_squash_mirror_diag:
01992           accent_mat =
01993             LMatrix4::rotate_mat_normaxis(270.0f, LVecBase3(0.0f, -1.0f, 0.0f)) *
01994             LMatrix4::scale_mat(-small_squash_accent_scale_x, 1.0f, small_squash_accent_scale_y);
01995           
01996           // rotate min, max
01997           t = min_accent[0];
01998           u = max_accent[0];
01999           min_accent[0] = min_accent[2] * -small_squash_accent_scale_x;
02000           max_accent[0] = max_accent[2] * -small_squash_accent_scale_x;
02001           min_accent[2] = -u * small_squash_accent_scale_y;
02002           max_accent[2] = -t * small_squash_accent_scale_y;
02003           mirrored = true;
02004           break;
02005 
02006         case CT_small:
02007           accent_mat = LMatrix4::scale_mat(small_accent_scale);
02008           min_accent *= small_accent_scale;
02009           max_accent *= small_accent_scale;
02010           break;
02011 
02012         case CT_small_rotate_270:
02013           accent_mat =
02014             LMatrix4::rotate_mat_normaxis(270.0f, LVecBase3(0.0f, -1.0f, 0.0f)) *
02015             LMatrix4::scale_mat(small_accent_scale);
02016 
02017           // rotate min, max
02018           t = min_accent[0];
02019           u = max_accent[0];
02020           min_accent[0] = min_accent[2] * small_accent_scale;
02021           max_accent[0] = max_accent[2] * small_accent_scale;
02022           min_accent[2] = -u * small_accent_scale;
02023           max_accent[2] = -t * small_accent_scale;
02024           break;
02025 
02026         case CT_tiny:
02027           accent_mat = LMatrix4::scale_mat(tiny_accent_scale);
02028           min_accent *= tiny_accent_scale;
02029           max_accent *= tiny_accent_scale;
02030           break;
02031 
02032         case CT_tiny_mirror_x:
02033           accent_mat = LMatrix4::scale_mat(-tiny_accent_scale, 1.0f, tiny_accent_scale);
02034           
02035           t = min_accent[0];
02036           min_accent[0] = -max_accent[0] * tiny_accent_scale;
02037           max_accent[0] = -t * tiny_accent_scale;
02038           min_accent[2] *= tiny_accent_scale;
02039           max_accent[2] *= tiny_accent_scale;
02040           mirrored = true;
02041           break;
02042 
02043         case CT_tiny_rotate_270:
02044           accent_mat =
02045             LMatrix4::rotate_mat_normaxis(270.0f, LVecBase3(0.0f, -1.0f, 0.0f)) *
02046             LMatrix4::scale_mat(tiny_accent_scale);
02047 
02048           // rotate min, max
02049           t = min_accent[0];
02050           u = max_accent[0];
02051           min_accent[0] = min_accent[2] * tiny_accent_scale;
02052           max_accent[0] = max_accent[2] * tiny_accent_scale;
02053           min_accent[2] = -u * tiny_accent_scale;
02054           max_accent[2] = -t * tiny_accent_scale;
02055           break;
02056         }
02057 
02058         LPoint3 accent_centroid = (min_accent + max_accent) / 2.0f;
02059         PN_stdfloat accent_height = max_accent[2] - min_accent[2];
02060         LVector3 trans;
02061         switch (position) {
02062         case CP_above:
02063           // A little above the character.
02064           trans.set(centroid[0] - accent_centroid[0], 0.0f,
02065                     max_vert[2] - accent_centroid[2] + accent_height * 0.5);
02066           break;
02067 
02068         case CP_below:
02069           // A little below the character.
02070           trans.set(centroid[0] - accent_centroid[0], 0.0f,
02071                     min_vert[2] - accent_centroid[2] - accent_height * 0.5);
02072           break;
02073 
02074         case CP_top:
02075           // Touching the top of the character.
02076           trans.set(centroid[0] - accent_centroid[0], 0.0f,
02077                     max_vert[2] - accent_centroid[2]);
02078           break;
02079 
02080         case CP_bottom:
02081           // Touching the bottom of the character.
02082           trans.set(centroid[0] - accent_centroid[0], 0.0f,
02083                     min_vert[2] - accent_centroid[2]);
02084           break;
02085 
02086         case CP_within:
02087           // Centered within the character.
02088           trans.set(centroid[0] - accent_centroid[0], 0.0f,
02089                     centroid[2] - accent_centroid[2]);
02090           break;
02091         }
02092 
02093         accent_mat.set_row(3, trans);
02094         accent_geom->transform_vertices(accent_mat);
02095 
02096         if (mirrored) {
02097           // Once someone asks for this pointer, we hold its reference
02098           // count and never free it.
02099           static CPT(RenderState) disable_backface;
02100           if (disable_backface == (const RenderState *)NULL) {
02101             disable_backface = RenderState::make
02102               (CullFaceAttrib::make(CullFaceAttrib::M_cull_none));
02103           }
02104             
02105           CPT(RenderState) state = 
02106             accent_glyph->get_state()->compose(disable_backface);
02107           placement->add_piece(accent_geom, state);
02108         } else {
02109           placement->add_piece(accent_geom, accent_glyph->get_state());
02110         }
02111 
02112         return true;
02113       }
02114     }
02115   }
02116   return false;
02117 }
02118 
02119 ////////////////////////////////////////////////////////////////////
02120 //     Function: TextAssembler::ComputedProperties::append_delta
02121 //       Access: Public
02122 //  Description: Appends to wtext the control sequences necessary to
02123 //               change from this ComputedProperties to the indicated
02124 //               ComputedProperties.
02125 ////////////////////////////////////////////////////////////////////
02126 void TextAssembler::ComputedProperties::
02127 append_delta(wstring &wtext, TextAssembler::ComputedProperties *other) {
02128   if (this != other) {
02129     if (_depth > other->_depth) {
02130       // Back up a level from this properties.
02131       nassertv(_based_on != NULL);
02132 
02133       wtext.push_back(text_pop_properties_key);
02134       _based_on->append_delta(wtext, other);
02135       
02136     } else if (other->_depth > _depth) {
02137       // Back up a level from the other properties.
02138       nassertv(other->_based_on != NULL);
02139 
02140       append_delta(wtext, other->_based_on);
02141       wtext.push_back(text_push_properties_key);
02142       wtext += other->_wname;
02143       wtext.push_back(text_push_properties_key);
02144       
02145     } else if (_depth != 0) {
02146       // Back up a level from both properties.
02147       nassertv(_based_on != NULL && other->_based_on != NULL);
02148       
02149       wtext.push_back(text_pop_properties_key);
02150       _based_on->append_delta(wtext, other->_based_on);
02151       wtext.push_back(text_push_properties_key);
02152       wtext += other->_wname;
02153       wtext.push_back(text_push_properties_key);
02154     }
02155   }
02156 }
02157 
02158 ////////////////////////////////////////////////////////////////////
02159 //     Function: TextAssembler::GlyphPlacement::calc_tight_bounds
02160 //       Access: Private
02161 //  Description: Expands min_point and max_point to include all of the
02162 //               vertices in the glyph(s), if any.  found_any is set
02163 //               true if any points are found.  It is the caller's
02164 //               responsibility to initialize min_point, max_point,
02165 //               and found_any before calling this function.
02166 ////////////////////////////////////////////////////////////////////
02167 void TextAssembler::GlyphPlacement::
02168 calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
02169                   bool &found_any, Thread *current_thread) const {
02170   Pieces::const_iterator pi;
02171   for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
02172     (*pi)._geom->calc_tight_bounds(min_point, max_point, found_any,
02173                                    current_thread);
02174   }
02175 }
02176 
02177 ////////////////////////////////////////////////////////////////////
02178 //     Function: TextAssembler::GlyphPlacement::assign_to
02179 //       Access: Private
02180 //  Description: Puts the pieces of the GlyphPlacement in the
02181 //               indicated GeomNode.  The vertices of the Geoms are
02182 //               modified by this operation.
02183 ////////////////////////////////////////////////////////////////////
02184 void TextAssembler::GlyphPlacement::
02185 assign_to(GeomNode *geom_node, const RenderState *state) const {
02186   Pieces::const_iterator pi;
02187   for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
02188     (*pi)._geom->transform_vertices(_xform);
02189     geom_node->add_geom((*pi)._geom, state->compose((*pi)._state));
02190   }
02191 }
02192 
02193 ////////////////////////////////////////////////////////////////////
02194 //     Function: TextAssembler::GlyphPlacement::assign_copy_to
02195 //       Access: Private
02196 //  Description: Puts the pieces of the GlyphPlacement in the
02197 //               indicated GeomNode.  This flavor will make a copy of
02198 //               the Geoms first, and then apply the additional
02199 //               transform to the vertices.
02200 ////////////////////////////////////////////////////////////////////
02201 void TextAssembler::GlyphPlacement::
02202 assign_copy_to(GeomNode *geom_node, const RenderState *state,
02203                const LMatrix4 &extra_xform) const {
02204   LMatrix4 new_xform = _xform * extra_xform;
02205   Pieces::const_iterator pi;
02206   for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
02207     const Geom *geom = (*pi)._geom;
02208     PT(Geom) new_geom = geom->make_copy();
02209     new_geom->transform_vertices(new_xform);
02210     geom_node->add_geom(new_geom, state->compose((*pi)._state));
02211   }
02212 }
02213 
02214 ////////////////////////////////////////////////////////////////////
02215 //     Function: TextAssembler::GlyphPlacement::assign_append_to
02216 //       Access: Private
02217 //  Description: Puts the pieces of the GlyphPlacement in the
02218 //               indicated GeomNode.  This flavor will append the
02219 //               Geoms with the additional transform applied to the
02220 //               vertices.
02221 ////////////////////////////////////////////////////////////////////
02222 void TextAssembler::GlyphPlacement::
02223 assign_append_to(GeomCollectorMap &geom_collector_map, 
02224                  const RenderState *state,
02225                  const LMatrix4 &extra_xform) const {
02226   LMatrix4 new_xform = _xform * extra_xform;
02227   Pieces::const_iterator pi;
02228 
02229   int p, sp, s, e, i;
02230   for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
02231     const Geom *geom = (*pi)._geom;
02232     const GeomVertexData *vdata = geom->get_vertex_data();
02233     CPT(RenderState) rs = (*pi)._state->compose(state);
02234     GeomCollectorKey key(rs, vdata->get_format());
02235 
02236     GeomCollectorMap::iterator mi = geom_collector_map.find(key);
02237     if (mi == geom_collector_map.end()) {
02238       mi = geom_collector_map.insert(GeomCollectorMap::value_type(key, GeomCollector(vdata->get_format()))).first;
02239     }
02240     GeomCollector &geom_collector = (*mi).second;
02241     geom_collector.count_geom(geom);
02242 
02243     // We use this map to keep track of vertex indices we have already
02244     // added, so that we don't needlessly duplicate vertices into our
02245     // output vertex data.
02246     VertexIndexMap vimap;
02247 
02248     for (p = 0; p < geom->get_num_primitives(); p++) {
02249       CPT(GeomPrimitive) primitive = geom->get_primitive(p)->decompose();
02250 
02251       // Get a new GeomPrimitive of the corresponding type.
02252       GeomPrimitive *new_prim = geom_collector.get_primitive(primitive->get_type());
02253 
02254       // Walk through all of the components (e.g. triangles) of the
02255       // primitive.
02256       for (sp = 0; sp < primitive->get_num_primitives(); sp++) {
02257         s = primitive->get_primitive_start(sp);
02258         e = primitive->get_primitive_end(sp);
02259 
02260         // Walk through all of the vertices in the component.
02261         for (i = s; i < e; i++) {
02262           int vi = primitive->get_vertex(i);
02263 
02264           // Attempt to insert number "vi" into the map.
02265           pair<VertexIndexMap::iterator, bool> added = vimap.insert(VertexIndexMap::value_type(vi, 0));
02266           int new_vertex;
02267           if (added.second) {
02268             // The insert succeeded.  That means this is the first
02269             // time we have encountered this vertex.
02270             new_vertex = geom_collector.append_vertex(vdata, vi, new_xform);
02271             // Update the map with the newly-created target vertex index.
02272             (*(added.first)).second = new_vertex;
02273 
02274           } else {
02275             // The insert failed.  This means we have previously
02276             // encountered this vertex, and we have already entered
02277             // its target vertex index into the vimap.  Extract that
02278             // vertex index, so we can reuse it.
02279             new_vertex = (*(added.first)).second;
02280           }
02281           new_prim->add_vertex(new_vertex);
02282         }
02283         new_prim->close_primitive();
02284       }
02285     }
02286   }
02287 }
02288 
02289 ////////////////////////////////////////////////////////////////////
02290 //     Function: TextAssembler::GlyphPlacement::copy_graphic_to
02291 //       Access: Private
02292 //  Description: If the GlyphPlacement includes a special graphic,
02293 //               copies it to the indicated node.
02294 ////////////////////////////////////////////////////////////////////
02295 void TextAssembler::GlyphPlacement::
02296 copy_graphic_to(PandaNode *node, const RenderState *state,
02297                 const LMatrix4 &extra_xform) const {
02298   if (_graphic_model != (PandaNode *)NULL) {
02299     LMatrix4 new_xform = _xform * extra_xform;
02300 
02301     // We need an intermediate node to hold the transform and state.
02302     PT(PandaNode) intermediate_node = new PandaNode("");
02303     node->add_child(intermediate_node);
02304 
02305     intermediate_node->set_transform(TransformState::make_mat(new_xform));
02306     intermediate_node->set_state(state);
02307     intermediate_node->add_child(_graphic_model);
02308   }
02309 }
02310 
02311 ////////////////////////////////////////////////////////////////////
02312 //     Function: TextAssembler::GeomCollector Constructor
02313 //       Access: Public
02314 //  Description: constructs the GeomCollector class 
02315 //               (Geom, GeomTriangles, vertexWriter, texcoordWriter..)
02316 ////////////////////////////////////////////////////////////////////
02317 TextAssembler::GeomCollector::
02318 GeomCollector(const GeomVertexFormat *format) :
02319   _vdata(new GeomVertexData("merged_geom", format, Geom::UH_static)),
02320   _geom(new GeomTextGlyph(_vdata))
02321 {
02322 }
02323 
02324 ////////////////////////////////////////////////////////////////////
02325 //     Function: TextAssembler::GeomCollector Copy Constructor
02326 //       Access: Public
02327 //  Description: 
02328 ////////////////////////////////////////////////////////////////////
02329 TextAssembler::GeomCollector::
02330 GeomCollector(const TextAssembler::GeomCollector &copy) :
02331   _vdata(copy._vdata),
02332   _geom(copy._geom)
02333 {
02334 }
02335 
02336 ////////////////////////////////////////////////////////////////////
02337 //     Function: TextAssembler::GeomCollector::get_primitive
02338 //       Access: Public
02339 //  Description: Returns a GeomPrimitive of the appropriate type.  If
02340 //               one has not yet been created, returns a newly-created
02341 //               one; if one has previously been created of this type,
02342 //               returns the previously-created one.
02343 ////////////////////////////////////////////////////////////////////
02344 GeomPrimitive *TextAssembler::GeomCollector::
02345 get_primitive(TypeHandle prim_type) {
02346   if (prim_type == GeomTriangles::get_class_type()) {
02347     if (_triangles == (GeomPrimitive *)NULL) {
02348       _triangles = new GeomTriangles(Geom::UH_static);
02349       _geom->add_primitive(_triangles);
02350     }
02351     return _triangles;
02352 
02353   } else if (prim_type == GeomLines::get_class_type()) {
02354     if (_lines == (GeomPrimitive *)NULL) {
02355       _lines = new GeomLines(Geom::UH_static);
02356       _geom->add_primitive(_lines);
02357     }
02358     return _lines;
02359 
02360   } else if (prim_type == GeomPoints::get_class_type()) {
02361     if (_points == (GeomPrimitive *)NULL) {
02362       _points = new GeomPoints(Geom::UH_static);
02363       _geom->add_primitive(_points);
02364     }
02365     return _points;
02366   }
02367 
02368   nassertr(false, NULL);
02369   return NULL;
02370 }
02371 
02372 ////////////////////////////////////////////////////////////////////
02373 //     Function: TextAssembler::GeomCollector::append_vertex
02374 //       Access: Public
02375 //  Description: Adds one vertex to the GeomVertexData.
02376 //               Returns the row number of the added vertex.
02377 ////////////////////////////////////////////////////////////////////
02378 int TextAssembler::GeomCollector::
02379 append_vertex(const GeomVertexData *orig_vdata, int orig_row,
02380               const LMatrix4 &xform) {
02381   int new_row = _vdata->get_num_rows();
02382   _vdata->copy_row_from(new_row, orig_vdata, orig_row, Thread::get_current_thread());
02383 
02384   GeomVertexRewriter vertex_rewriter(_vdata, InternalName::get_vertex());
02385   vertex_rewriter.set_row_unsafe(new_row);
02386   LPoint3 point = vertex_rewriter.get_data3();
02387   vertex_rewriter.set_data3(point * xform);
02388 
02389   return new_row;
02390 }
02391 
02392 
02393 ////////////////////////////////////////////////////////////////////
02394 //     Function: TextAssembler::GeomCollector::append_geom
02395 //       Access: Public
02396 //  Description: closes the geomTriangles and appends the geom to 
02397 //               the given GeomNode
02398 ////////////////////////////////////////////////////////////////////
02399 void TextAssembler::GeomCollector::
02400 append_geom(GeomNode *geom_node, const RenderState *state) {
02401   if (_geom->get_num_primitives() > 0) {
02402     geom_node->add_geom(_geom, state);
02403   }
02404 }
02405 
 All Classes Functions Variables Enumerations