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