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 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 ©) : 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 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 ¢roid, 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 ¢roid, 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 ©) : 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