Panda3D
textAssembler.cxx
1 // Filename: textAssembler.cxx
2 // Created by: drose (06Apr04)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "textAssembler.h"
16 #include "textGlyph.h"
17 #include "cullFaceAttrib.h"
18 #include "colorAttrib.h"
19 #include "cullBinAttrib.h"
20 #include "textureAttrib.h"
21 #include "transparencyAttrib.h"
22 #include "textPropertiesManager.h"
23 #include "textEncoder.h"
24 #include "config_text.h"
25 #include "geomTriangles.h"
26 #include "geomLines.h"
27 #include "geomPoints.h"
28 #include "geomVertexReader.h"
29 #include "geomVertexWriter.h"
30 #include "geomLines.h"
31 #include "geomVertexFormat.h"
32 #include "geomVertexData.h"
33 #include "geom.h"
34 #include "modelNode.h"
35 
36 #include <ctype.h>
37 #include <stdio.h> // for sprintf
38 
39 // This is the factor by which CT_small scales the character down.
40 static const PN_stdfloat small_accent_scale = 0.6f;
41 
42 // This is the factor by which CT_tiny scales the character down.
43 static const PN_stdfloat tiny_accent_scale = 0.4;
44 
45 // This is the factor by which CT_squash scales the character in X and Y.
46 static const PN_stdfloat squash_accent_scale_x = 0.8f;
47 static const PN_stdfloat squash_accent_scale_y = 0.5f;
48 
49 // This is the factor by which CT_small_squash scales the character in X and Y.
50 static const PN_stdfloat small_squash_accent_scale_x = 0.6f;
51 static const PN_stdfloat small_squash_accent_scale_y = 0.3;
52 
53 // This is the factor by which the advance is reduced for the first
54 // character of a two-character ligature.
55 static const PN_stdfloat ligature_advance_scale = 0.6f;
56 
57 
58 ////////////////////////////////////////////////////////////////////
59 // Function: isspacew
60 // Description: An internal function that works like isspace() but is
61 // safe to call for a wide character.
62 ////////////////////////////////////////////////////////////////////
63 static INLINE bool
64 isspacew(unsigned int ch) {
65  return isascii(ch) && isspace(ch);
66 }
67 
68 ////////////////////////////////////////////////////////////////////
69 // Function: isbreakpoint
70 // Description: An internal function, similar to isspace(), except it
71 // does not consider newlines to be whitespace. It also
72 // includes the soft-hyphen character.
73 ////////////////////////////////////////////////////////////////////
74 static INLINE bool
75 isbreakpoint(unsigned int ch) {
76  return (ch == ' ' || ch == '\t' ||
77  ch == (unsigned int)text_soft_hyphen_key ||
78  ch == (unsigned int)text_soft_break_key);
79 }
80 
81 
82 ////////////////////////////////////////////////////////////////////
83 // Function: TextAssembler::Constructor
84 // Access: Published
85 // Description:
86 ////////////////////////////////////////////////////////////////////
87 TextAssembler::
88 TextAssembler(TextEncoder *encoder) :
89  _encoder(encoder),
90  _usage_hint(Geom::UH_static),
91  _max_rows(0),
92  _dynamic_merge(text_dynamic_merge),
93  _multiline_mode(true)
94 {
95  _initial_cprops = new ComputedProperties(TextProperties());
96  clear();
97 }
98 
99 ////////////////////////////////////////////////////////////////////
100 // Function: TextAssembler::Copy Constructor
101 // Access: Published
102 // Description:
103 ////////////////////////////////////////////////////////////////////
104 TextAssembler::
105 TextAssembler(const TextAssembler &copy) :
106  _initial_cprops(copy._initial_cprops),
107  _text_string(copy._text_string),
108  _text_block(copy._text_block),
109  _ul(copy._ul),
110  _lr(copy._lr),
111  _next_row_ypos(copy._next_row_ypos),
112  _encoder(copy._encoder),
113  _usage_hint(copy._usage_hint),
114  _max_rows(copy._max_rows),
115  _dynamic_merge(copy._dynamic_merge),
116  _multiline_mode(copy._multiline_mode)
117 {
118 }
119 
120 ////////////////////////////////////////////////////////////////////
121 // Function: TextAssembler::Copy Assignment Operator
122 // Access: Published
123 // Description:
124 ////////////////////////////////////////////////////////////////////
125 void TextAssembler::
126 operator = (const TextAssembler &copy) {
127  _initial_cprops = copy._initial_cprops;
128  _text_string = copy._text_string;
129  _text_block = copy._text_block;
130  _ul = copy._ul;
131  _lr = copy._lr;
132  _next_row_ypos = copy._next_row_ypos;
133  _encoder = copy._encoder;
134  _usage_hint = copy._usage_hint;
135  _max_rows = copy._max_rows;
136  _dynamic_merge = copy._dynamic_merge;
137  _multiline_mode = copy._multiline_mode;
138 }
139 
140 ////////////////////////////////////////////////////////////////////
141 // Function: TextAssembler::Destructor
142 // Access: Published
143 // Description:
144 ////////////////////////////////////////////////////////////////////
145 TextAssembler::
146 ~TextAssembler() {
147 }
148 
149 ////////////////////////////////////////////////////////////////////
150 // Function: TextAssembler::clear
151 // Access: Published
152 // Description: Reinitializes the contents of the TextAssembler.
153 ////////////////////////////////////////////////////////////////////
154 void TextAssembler::
155 clear() {
156  _ul.set(0.0f, 0.0f);
157  _lr.set(0.0f, 0.0f);
158  _next_row_ypos = 0.0f;
159 
160  _text_string.clear();
161  _text_block.clear();
162 }
163 
164 ////////////////////////////////////////////////////////////////////
165 // Function: TextAssembler::set_wtext
166 // Access: Published
167 // Description: Accepts a new text string and associated properties
168 // structure, and precomputes the wordwrapping layout
169 // appropriately. After this call,
170 // get_wordwrapped_wtext() and get_num_rows() can be
171 // called.
172 //
173 // The return value is true if all the text is accepted,
174 // or false if some was truncated (see set_max_rows()).
175 ////////////////////////////////////////////////////////////////////
176 bool TextAssembler::
177 set_wtext(const wstring &wtext) {
178  clear();
179 
180  // First, expand all of the embedded TextProperties references
181  // within the string.
182  wstring::const_iterator si = wtext.begin();
183  scan_wtext(_text_string, si, wtext.end(), _initial_cprops);
184 
185  while (si != wtext.end()) {
186  // If we returned without consuming the whole string, it means
187  // there was an embedded text_pop_properties_key that didn't match
188  // the push. That's worth a warning, and then go back and pick up
189  // the rest of the string.
190  text_cat.warning()
191  << "pop_properties encountered without preceding push_properties.\n";
192  scan_wtext(_text_string, si, wtext.end(), _initial_cprops);
193  }
194 
195  // Then apply any wordwrap requirements.
196  return wordwrap_text();
197 }
198 
199 ////////////////////////////////////////////////////////////////////
200 // Function: TextAssembler::set_wsubstr
201 // Access: Published
202 // Description: Replaces the 'count' characters from 'start' of the
203 // current text with the indicated replacement text. If
204 // the replacement text does not have count characters,
205 // the length of the string will be changed accordingly.
206 //
207 // The substring may include nested formatting
208 // characters, but they must be self-contained and
209 // self-closed. The formatting characters are not
210 // literally saved in the internal string; they are
211 // parsed at the time of the set_wsubstr() call.
212 //
213 // The return value is true if all the text is accepted,
214 // or false if some was truncated (see set_max_rows()).
215 ////////////////////////////////////////////////////////////////////
216 bool TextAssembler::
217 set_wsubstr(const wstring &wtext, int start, int count) {
218  nassertr(start >= 0 && start <= (int)_text_string.size(), false);
219  nassertr(count >= 0 && start + count <= (int)_text_string.size(), false);
220 
221  // Use scan_wtext to unroll the substring we wish to insert, as in
222  // set_wtext(), above.
223  TextString substr;
224  wstring::const_iterator si = wtext.begin();
225  scan_wtext(substr, si, wtext.end(), _initial_cprops);
226  while (si != wtext.end()) {
227  text_cat.warning()
228  << "pop_properties encountered without preceding push_properties.\n";
229  scan_wtext(substr, si, wtext.end(), _initial_cprops);
230  }
231 
232  _text_string.erase(_text_string.begin() + start, _text_string.begin() + start + count);
233  _text_string.insert(_text_string.begin() + start, substr.begin(), substr.end());
234 
235  return wordwrap_text();
236 }
237 
238 ////////////////////////////////////////////////////////////////////
239 // Function: TextAssembler::get_plain_wtext
240 // Access: Published
241 // Description: Returns a wstring that represents the contents of the
242 // text, without any embedded properties characters. If
243 // there is an embedded graphic object, a zero value is
244 // inserted in that position.
245 //
246 // This string has the same length as
247 // get_num_characters(), and the characters in this
248 // string correspond one-to-one with the characters
249 // returned by get_character(n).
250 ////////////////////////////////////////////////////////////////////
251 wstring TextAssembler::
253  wstring wtext;
254 
255  TextString::const_iterator si;
256  for (si = _text_string.begin(); si != _text_string.end(); ++si) {
257  const TextCharacter &tch = (*si);
258  if (tch._graphic == (TextGraphic *)NULL) {
259  wtext += tch._character;
260  } else {
261  wtext.push_back(0);
262  }
263  }
264 
265  return wtext;
266 }
267 
268 ////////////////////////////////////////////////////////////////////
269 // Function: TextAssembler::get_wordwrapped_plain_wtext
270 // Access: Published
271 // Description: Returns a wstring that represents the contents of the
272 // text, with newlines inserted according to the
273 // wordwrapping. The string will contain no embedded
274 // properties characters. If there is an embedded
275 // graphic object, a zero value is inserted in that
276 // position.
277 //
278 // This string has the same number of newline characters
279 // as get_num_rows(), and the characters in this string
280 // correspond one-to-one with the characters returned by
281 // get_character(r, c).
282 ////////////////////////////////////////////////////////////////////
283 wstring TextAssembler::
285  wstring wtext;
286 
287  TextBlock::const_iterator bi;
288  for (bi = _text_block.begin(); bi != _text_block.end(); ++bi) {
289  const TextRow &row = (*bi);
290  if (bi != _text_block.begin()) {
291  wtext += '\n';
292  }
293 
294  TextString::const_iterator si;
295  for (si = row._string.begin(); si != row._string.end(); ++si) {
296  const TextCharacter &tch = (*si);
297  if (tch._graphic == (TextGraphic *)NULL) {
298  wtext += tch._character;
299  } else {
300  wtext.push_back(0);
301  }
302  }
303  }
304 
305  return wtext;
306 }
307 
308 ////////////////////////////////////////////////////////////////////
309 // Function: TextAssembler::get_wtext
310 // Access: Published
311 // Description: Returns a wstring that represents the contents of the
312 // text.
313 //
314 // The string will contain embedded properties
315 // characters, which may not exactly match the embedded
316 // properties characters of the original string, but it
317 // will encode the same way.
318 ////////////////////////////////////////////////////////////////////
319 wstring TextAssembler::
320 get_wtext() const {
321  wstring wtext;
322  PT(ComputedProperties) current_cprops = _initial_cprops;
323 
324  TextString::const_iterator si;
325  for (si = _text_string.begin(); si != _text_string.end(); ++si) {
326  const TextCharacter &tch = (*si);
327  current_cprops->append_delta(wtext, tch._cprops);
328  if (tch._graphic == (TextGraphic *)NULL) {
329  wtext += tch._character;
330  } else {
331  wtext.push_back(text_embed_graphic_key);
332  wtext += tch._graphic_wname;
333  wtext.push_back(text_embed_graphic_key);
334  }
335  current_cprops = tch._cprops;
336  }
337  current_cprops->append_delta(wtext, _initial_cprops);
338 
339  return wtext;
340 }
341 
342 ////////////////////////////////////////////////////////////////////
343 // Function: TextAssembler::get_wordwrapped_wtext
344 // Access: Published
345 // Description: Returns a wstring that represents the contents of the
346 // text, with newlines inserted according to the
347 // wordwrapping.
348 //
349 // The string will contain embedded properties
350 // characters, which may not exactly match the embedded
351 // properties characters of the original string, but it
352 // will encode the same way.
353 //
354 // Embedded properties characters will be closed before
355 // every newline, then reopened (if necessary) on the
356 // subsequent character following the newline. This
357 // means it will be safe to divide the text up at the
358 // newline characters and treat each line as an
359 // independent piece.
360 ////////////////////////////////////////////////////////////////////
361 wstring TextAssembler::
363  wstring wtext;
364 
365  PT(ComputedProperties) current_cprops = _initial_cprops;
366 
367  TextBlock::const_iterator bi;
368  for (bi = _text_block.begin(); bi != _text_block.end(); ++bi) {
369  const TextRow &row = (*bi);
370  if (bi != _text_block.begin()) {
371  current_cprops->append_delta(wtext, _initial_cprops);
372  current_cprops = _initial_cprops;
373  wtext += '\n';
374  }
375 
376  TextString::const_iterator si;
377  for (si = row._string.begin(); si != row._string.end(); ++si) {
378  const TextCharacter &tch = (*si);
379  current_cprops->append_delta(wtext, tch._cprops);
380  if (tch._graphic == (TextGraphic *)NULL) {
381  wtext += tch._character;
382  } else {
383  wtext.push_back(text_embed_graphic_key);
384  wtext += tch._graphic_wname;
385  wtext.push_back(text_embed_graphic_key);
386  }
387  current_cprops = tch._cprops;
388  }
389  }
390  current_cprops->append_delta(wtext, _initial_cprops);
391 
392  return wtext;
393 }
394 
395 ////////////////////////////////////////////////////////////////////
396 // Function: TextAssembler::calc_r_c
397 // Access: Published
398 // Description: Computes the row and column index of the nth
399 // character or graphic object in the text. Fills r and
400 // c accordingly.
401 //
402 // Returns true if the nth character is valid and has a
403 // corresponding r and c position, false otherwise (for
404 // instance, a soft-hyphen character, or a newline
405 // character, may not have a corresponding position).
406 // In either case, r and c will be filled in sensibly.
407 ////////////////////////////////////////////////////////////////////
408 bool TextAssembler::
409 calc_r_c(int &r, int &c, int n) const {
410  nassertr(n >= 0 && n <= (int)_text_string.size(), false);
411 
412  if (n == (int)_text_string.size()) {
413  // A special case for one past the last character.
414  if (_text_string.empty()) {
415  r = 0;
416  c = 0;
417  } else {
418  r = _text_block.size() - 1;
419  c = _text_block[r]._string.size();
420  }
421  return true;
422 
423  } else if (n == 0) {
424  // Another special case for the beginning.
425  r = 0;
426  c = 0;
427  return true;
428  }
429 
430  r = 0;
431  while (r + 1 < (int)_text_block.size() &&
432  _text_block[r + 1]._row_start < n) {
433  r += 1;
434  }
435 
436  const TextRow &row = _text_block[r];
437  bool is_real_char = true;
438 
439  nassertr(n > 0, false);
440  if (row._got_soft_hyphens) {
441  // If there are any soft hyphen or soft break keys in the source
442  // text, we have to scan past them to get c precisely.
443  c = 0;
444  int i = row._row_start;
445  while (i < n - 1) {
446  if (_text_string[i]._character != text_soft_hyphen_key &&
447  _text_string[i]._character != text_soft_break_key) {
448  ++c;
449  }
450  ++i;
451  }
452  if (_text_string[n - 1]._character != text_soft_hyphen_key &&
453  _text_string[n - 1]._character != text_soft_break_key) {
454  ++c;
455  if (_text_string[n - 1]._character == '\n') {
456  is_real_char = false;
457  }
458  } else {
459  is_real_char = false;
460  }
461 
462  } else {
463  // If there are no soft characters, then the string maps
464  // one-to-one.
465  c = min(n - row._row_start, (int)row._string.size());
466  if (_text_string[n - 1]._character == '\n') {
467  is_real_char = false;
468  }
469  }
470 
471  return is_real_char;
472 }
473 
474 ////////////////////////////////////////////////////////////////////
475 // Function: TextAssembler::calc_index
476 // Access: Published
477 // Description: Computes the character index of the character at the
478 // rth row and cth column position. This is the inverse
479 // of calc_r_c().
480 //
481 // It is legal for c to exceed the index number of the
482 // last column by 1, and it is legal for r to exceed the
483 // index number of the last row by 1, if c is 0.
484 ////////////////////////////////////////////////////////////////////
485 int TextAssembler::
486 calc_index(int r, int c) const {
487  nassertr(r >= 0 && r <= (int)_text_block.size(), 0);
488  if (r == (int)_text_block.size()) {
489  nassertr(c == 0, 0);
490  return _text_string.size();
491 
492  } else {
493  nassertr(c >= 0 && c <= (int)_text_block[r]._string.size(), 0);
494  const TextRow &row = _text_block[r];
495 
496  if (row._got_soft_hyphens) {
497  // If there are any soft hyphen or soft break keys in the source
498  // text, we have to scan past them to get n precisely.
499  int n = row._row_start;
500  while (c > 0) {
501  if (_text_string[n]._character != text_soft_hyphen_key &&
502  _text_string[n]._character != text_soft_break_key) {
503  --c;
504  }
505  ++n;
506  }
507  return n;
508 
509  } else {
510  // If there are no soft characters, then the string maps
511  // one-to-one.
512  return row._row_start + c;
513  }
514  }
515 }
516 
517 ////////////////////////////////////////////////////////////////////
518 // Function: TextAssembler::get_xpos
519 // Access: Published
520 // Description: Returns the x position of the origin of the character
521 // or graphic object at the indicated position in the
522 // indicated row.
523 //
524 // It is legal for c to exceed the index number of the
525 // last column by 1, and it is legal for r to exceed the
526 // index number of the last row by 1, if c is 0.
527 ////////////////////////////////////////////////////////////////////
528 PN_stdfloat TextAssembler::
529 get_xpos(int r, int c) const {
530  nassertr(r >= 0 && r <= (int)_text_block.size(), 0.0f);
531  if (r == (int)_text_block.size()) {
532  nassertr(c == 0, 0.0f);
533  return 0.0f;
534 
535  } else {
536  nassertr(c >= 0 && c <= (int)_text_block[r]._string.size(), 0.0f);
537  const TextRow &row = _text_block[r];
538  PN_stdfloat xpos = row._xpos;
539  for (int i = 0; i < c; ++i) {
540  xpos += calc_width(row._string[i]);
541  }
542  return xpos;
543  }
544 }
545 
546 ////////////////////////////////////////////////////////////////////
547 // Function: TextAssembler::assemble_text
548 // Access: Published
549 // Description: Actually assembles all of the text into a GeomNode,
550 // and returns the node (or possibly a parent of the
551 // node, to keep the shadow separate). Once this has
552 // been called, you may query the extents of the text
553 // via get_ul(), get_lr().
554 ////////////////////////////////////////////////////////////////////
555 PT(PandaNode) TextAssembler::
556 assemble_text() {
557  // Now assemble the text into glyphs.
558  PlacedGlyphs placed_glyphs;
559  assemble_paragraph(placed_glyphs);
560 
561  // Now that we have a bunch of GlyphPlacements, pull out the Geoms
562  // and put them under a common node.
563  PT(PandaNode) parent_node = new PandaNode("common");
564 
565  PT(PandaNode) shadow_node = new PandaNode("shadow");
566  PT(GeomNode) shadow_geom_node = new GeomNode("shadow_geom");
567  shadow_node->add_child(shadow_geom_node);
568 
569  PT(PandaNode) text_node = new PandaNode("text");
570  PT(GeomNode) text_geom_node = new GeomNode("text_geom");
571  text_node->add_child(text_geom_node);
572 
573  const TextProperties *properties = NULL;
574  CPT(RenderState) text_state;
575  CPT(RenderState) shadow_state;
576  LMatrix4 shadow_xform;
577 
578  bool any_shadow = false;
579 
580  GeomCollectorMap geom_collector_map;
581  GeomCollectorMap geom_shadow_collector_map;
582 
583  PlacedGlyphs::const_iterator pgi;
584  for (pgi = placed_glyphs.begin(); pgi != placed_glyphs.end(); ++pgi) {
585  const GlyphPlacement *placement = (*pgi);
586 
587  if (placement->_properties != properties) {
588  // Get a new set of properties for future glyphs.
589  properties = placement->_properties;
590  text_state = RenderState::make_empty();
591  shadow_state = RenderState::make_empty();
592  shadow_xform = LMatrix4::ident_mat();
593 
594  if (properties->has_text_color()) {
595  text_state = text_state->add_attrib(ColorAttrib::make_flat(properties->get_text_color()));
596  if (properties->get_text_color()[3] != 1.0) {
597  text_state = text_state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
598  }
599  }
600 
601  if (properties->has_bin()) {
602  text_state = text_state->add_attrib(CullBinAttrib::make(properties->get_bin(), properties->get_draw_order() + 2));
603  }
604 
605  if (properties->has_shadow()) {
606  shadow_state = shadow_state->add_attrib(ColorAttrib::make_flat(properties->get_shadow_color()));
607  if (properties->get_shadow_color()[3] != 1.0) {
608  shadow_state = shadow_state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
609  }
610 
611  if (properties->has_bin()) {
612  shadow_state = shadow_state->add_attrib(CullBinAttrib::make(properties->get_bin(), properties->get_draw_order() + 1));
613  }
614 
615  LVector2 offset = properties->get_shadow();
616  shadow_xform = LMatrix4::translate_mat(offset[0], 0.0f, -offset[1]);
617  }
618  }
619 
620  // We have to place the shadow first, because it copies as it
621  // goes, while the place-text function just stomps on the
622  // vertices.
623  if (properties->has_shadow()) {
624  if (_dynamic_merge) {
625  placement->assign_append_to(geom_shadow_collector_map, shadow_state, shadow_xform);
626  } else {
627  placement->assign_copy_to(shadow_geom_node, shadow_state, shadow_xform);
628  }
629 
630  // Don't shadow the graphics. That can result in duplication of
631  // button objects, plus it looks weird. If you want a shadowed
632  // graphic, you can shadow it yourself before you add it.
633  //placement->copy_graphic_to(shadow_node, shadow_state, shadow_xform);
634  any_shadow = true;
635  }
636 
637  if (_dynamic_merge) {
638  placement->assign_append_to(geom_collector_map, text_state, LMatrix4::ident_mat());
639  } else {
640  placement->assign_to(text_geom_node, text_state);
641  }
642  placement->copy_graphic_to(text_node, text_state, LMatrix4::ident_mat());
643  delete placement;
644  }
645  placed_glyphs.clear();
646 
647  if (any_shadow) {
648  // The shadow_geom_node must appear first to guarantee the correct
649  // rendering order.
650  parent_node->add_child(shadow_node);
651  }
652 
653  GeomCollectorMap::iterator gc;
654  for (gc = geom_collector_map.begin(); gc != geom_collector_map.end(); ++gc) {
655  (*gc).second.append_geom(text_geom_node, (*gc).first._state);
656  }
657 
658  if (any_shadow) {
659  for (gc = geom_shadow_collector_map.begin();
660  gc != geom_shadow_collector_map.end();
661  ++gc) {
662  (*gc).second.append_geom(shadow_geom_node, (*gc).first._state);
663  }
664  }
665 
666  parent_node->add_child(text_node);
667 
668  return parent_node;
669 }
670 
671 ////////////////////////////////////////////////////////////////////
672 // Function: TextAssembler::calc_width
673 // Access: Published, Static
674 // Description: Returns the width of a single character, according to
675 // its associated font. This also correctly calculates
676 // the width of cheesy ligatures and accented
677 // characters, which may not exist in the font as such.
678 ////////////////////////////////////////////////////////////////////
679 PN_stdfloat TextAssembler::
680 calc_width(wchar_t character, const TextProperties &properties) {
681  if (character == ' ') {
682  // A space is a special case.
683  TextFont *font = properties.get_font();
684  nassertr(font != (TextFont *)NULL, 0.0f);
685  return font->get_space_advance() * properties.get_glyph_scale() * properties.get_text_scale();
686  }
687 
688  bool got_glyph;
689  const TextGlyph *first_glyph = NULL;
690  const TextGlyph *second_glyph = NULL;
691  UnicodeLatinMap::AccentType accent_type;
692  int additional_flags;
693  PN_stdfloat glyph_scale;
694  PN_stdfloat advance_scale;
695  get_character_glyphs(character, &properties,
696  got_glyph, first_glyph, second_glyph, accent_type,
697  additional_flags, glyph_scale, advance_scale);
698 
699  PN_stdfloat advance = 0.0f;
700 
701  if (first_glyph != (TextGlyph *)NULL) {
702  advance = first_glyph->get_advance() * advance_scale;
703  }
704  if (second_glyph != (TextGlyph *)NULL) {
705  advance += second_glyph->get_advance();
706  }
707 
708  glyph_scale *= properties.get_glyph_scale() * properties.get_text_scale();
709 
710  return advance * glyph_scale;
711 }
712 
713 ////////////////////////////////////////////////////////////////////
714 // Function: TextAssembler::calc_width
715 // Access: Published, Static
716 // Description: Returns the width of a single TextGraphic image.
717 ////////////////////////////////////////////////////////////////////
718 PN_stdfloat TextAssembler::
719 calc_width(const TextGraphic *graphic, const TextProperties &properties) {
720  LVecBase4 frame = graphic->get_frame();
721  return (frame[1] - frame[0]) * properties.get_glyph_scale() * properties.get_text_scale();
722 }
723 
724 ////////////////////////////////////////////////////////////////////
725 // Function: TextAssembler::has_exact_character
726 // Access: Published, Static
727 // Description: Returns true if the named character exists in the
728 // font exactly as named, false otherwise. Note that
729 // because Panda can assemble glyphs together
730 // automatically using cheesy accent marks, this is not
731 // a reliable indicator of whether a suitable glyph can
732 // be rendered for the character. For that, use
733 // has_character() instead.
734 //
735 // This returns true for whitespace and Unicode
736 // whitespace characters (if they exist in the font),
737 // but returns false for characters that would render
738 // with the "invalid glyph". It also returns false for
739 // characters that would be synthesized within Panda,
740 // but see has_character().
741 ////////////////////////////////////////////////////////////////////
742 bool TextAssembler::
743 has_exact_character(wchar_t character, const TextProperties &properties) {
744  if (character == ' ' || character == '\n') {
745  // A space is a special case. Every font implicitly has a space.
746  // We also treat newlines specially.
747  return true;
748  }
749 
750  TextFont *font = properties.get_font();
751  nassertr(font != (TextFont *)NULL, false);
752 
753  const TextGlyph *glyph = NULL;
754  return font->get_glyph(character, glyph);
755 }
756 
757 ////////////////////////////////////////////////////////////////////
758 // Function: TextAssembler::has_character
759 // Access: Published, Static
760 // Description: Returns true if the named character exists in the
761 // font or can be synthesized by Panda, false otherwise.
762 // (Panda can synthesize some accented characters by
763 // combining similar-looking glyphs from the font.)
764 //
765 // This returns true for whitespace and Unicode
766 // whitespace characters (if they exist in the font),
767 // but returns false for characters that would render
768 // with the "invalid glyph".
769 ////////////////////////////////////////////////////////////////////
770 bool TextAssembler::
771 has_character(wchar_t character, const TextProperties &properties) {
772  if (character == ' ' || character == '\n') {
773  // A space is a special case. Every font implicitly has a space.
774  // We also treat newlines specially.
775  return true;
776  }
777 
778  bool got_glyph;
779  const TextGlyph *first_glyph = NULL;
780  const TextGlyph *second_glyph = NULL;
781  UnicodeLatinMap::AccentType accent_type;
782  int additional_flags;
783  PN_stdfloat glyph_scale;
784  PN_stdfloat advance_scale;
785  get_character_glyphs(character, &properties,
786  got_glyph, first_glyph, second_glyph, accent_type,
787  additional_flags, glyph_scale, advance_scale);
788  return got_glyph;
789 }
790 
791 ////////////////////////////////////////////////////////////////////
792 // Function: TextAssembler::is_whitespace
793 // Access: Published, Static
794 // Description: Returns true if the indicated character represents
795 // whitespace in the font, or false if anything visible
796 // will be rendered for it.
797 //
798 // This returns true for whitespace and Unicode
799 // whitespace characters (if they exist in the font),
800 // and returns false for any other characters, including
801 // characters that do not exist in the font (these would
802 // be rendered with the "invalid glyph", which is
803 // visible).
804 //
805 // Note that this function can be reliably used to
806 // identify Unicode whitespace characters only if the
807 // font has all of the whitespace characters defined.
808 // It will return false for any character not in the
809 // font, even if it is an official Unicode whitespace
810 // character.
811 ////////////////////////////////////////////////////////////////////
812 bool TextAssembler::
813 is_whitespace(wchar_t character, const TextProperties &properties) {
814  if (character == ' ' || character == '\n') {
815  // A space or a newline is a special case.
816  return true;
817  }
818 
819 
820  TextFont *font = properties.get_font();
821  nassertr(font != (TextFont *)NULL, false);
822 
823  const TextGlyph *glyph = NULL;
824  if (!font->get_glyph(character, glyph)) {
825  return false;
826  }
827 
828  return glyph->is_whitespace();
829 }
830 
831 #ifndef CPPPARSER // interrogate has a bit of trouble with wstring.
832 ////////////////////////////////////////////////////////////////////
833 // Function: TextAssembler::scan_wtext
834 // Access: Private
835 // Description: Scans through the text string, decoding embedded
836 // references to TextProperties. The decoded string is
837 // copied character-by-character into _text_string.
838 ////////////////////////////////////////////////////////////////////
839 void TextAssembler::
840 scan_wtext(TextAssembler::TextString &output_string,
841  wstring::const_iterator &si,
842  const wstring::const_iterator &send,
843  TextAssembler::ComputedProperties *current_cprops) {
844  while (si != send) {
845  if ((*si) == text_push_properties_key) {
846  // This indicates a nested properties structure. Pull off the
847  // name of the TextProperties structure, which is everything
848  // until the next text_push_properties_key.
849  wstring wname;
850  ++si;
851  while (si != send && (*si) != text_push_properties_key) {
852  wname += (*si);
853  ++si;
854  }
855 
856  if (si == send) {
857  // We didn't close the text_push_properties_key. That's an
858  // error.
859  text_cat.warning()
860  << "Unclosed push_properties in text.\n";
861  return;
862  }
863 
864  ++si;
865 
866  // Define the new properties by extending the current properties.
867  PT(ComputedProperties) new_cprops =
868  new ComputedProperties(current_cprops, wname, _encoder);
869 
870  // And recursively scan with the nested properties.
871  scan_wtext(output_string, si, send, new_cprops);
872 
873  if (text_cat.is_debug()) {
874  if (si == send) {
875  // The push was not closed by a pop. That's not an error,
876  // since we allow people to be sloppy about that; but we'll
877  // print a debug message at least.
878  text_cat.debug()
879  << "push_properties not matched by pop_properties.\n";
880  }
881  }
882 
883  } else if ((*si) == text_pop_properties_key) {
884  // This indicates the undoing of a previous push_properties_key.
885  // We simply return to the previous level.
886  ++si;
887  return;
888 
889  } else if ((*si) == text_embed_graphic_key) {
890  // This indicates an embedded graphic. Pull off the name of the
891  // TextGraphic structure, which is everything until the next
892  // text_embed_graphic_key.
893 
894  wstring graphic_wname;
895  ++si;
896  while (si != send && (*si) != text_embed_graphic_key) {
897  graphic_wname += (*si);
898  ++si;
899  }
900 
901  if (si == send) {
902  // We didn't close the text_embed_graphic_key. That's an
903  // error.
904  text_cat.warning()
905  << "Unclosed embed_graphic in text.\n";
906  return;
907  }
908 
909  ++si;
910 
911  // Now we have to encode the wstring into a string, for lookup
912  // in the TextPropertiesManager.
913  string graphic_name = _encoder->encode_wtext(graphic_wname);
914 
915  TextPropertiesManager *manager =
917 
918  // Get the graphic image.
919  const TextGraphic *named_graphic = manager->get_graphic_ptr(graphic_name);
920  if (named_graphic != (TextGraphic *)NULL) {
921  output_string.push_back(TextCharacter(named_graphic, graphic_wname, current_cprops));
922 
923  } else {
924  text_cat.warning()
925  << "Unknown TextGraphic: " << graphic_name << "\n";
926  }
927 
928  } else {
929  // A normal character. Apply it.
930  output_string.push_back(TextCharacter(*si, current_cprops));
931  ++si;
932  }
933  }
934 }
935 #endif // CPPPARSER
936 
937 ////////////////////////////////////////////////////////////////////
938 // Function: TextAssembler::wordwrap_text
939 // Access: Private
940 // Description: Inserts newlines into the _text_string at the
941 // appropriate places in order to make each line be the
942 // longest possible line that is not longer than
943 // wordwrap_width (and does not break any words, if
944 // possible). Stores the result in _text_block.
945 //
946 // If _max_rows is greater than zero, no more than
947 // _max_rows will be accepted. Text beyond that will be
948 // truncated.
949 //
950 // The return value is true if all the text is accepted,
951 // or false if some was truncated.
952 ////////////////////////////////////////////////////////////////////
953 bool TextAssembler::
954 wordwrap_text() {
955  _text_block.clear();
956 
957  if (_text_string.empty()) {
958  // A special case: empty text means no rows.
959  return true;
960  }
961 
962  size_t p = 0;
963 
964  _text_block.push_back(TextRow(p));
965 
966  // Preserve any initial whitespace and newlines.
967  PN_stdfloat initial_width = 0.0f;
968  while (p < _text_string.size() && isspacew(_text_string[p]._character)) {
969  if (_text_string[p]._character == '\n') {
970  initial_width = 0.0f;
971  if (_max_rows > 0 && (int)_text_block.size() >= _max_rows) {
972  // Truncate.
973  return false;
974  }
975  _text_block.back()._eol_cprops = _text_string[p]._cprops;
976  _text_block.push_back(TextRow(p + 1));
977  } else {
978  initial_width += calc_width(_text_string[p]);
979  _text_block.back()._string.push_back(_text_string[p]);
980  }
981  p++;
982  }
983  bool needs_newline = false;
984 
985  while (p < _text_string.size()) {
986  nassertr(!isspacew(_text_string[p]._character), false);
987 
988  // Scan the next n characters, until the end of the string or an
989  // embedded newline character, or we exceed wordwrap_width.
990 
991  size_t q = p;
992  bool any_spaces = false;
993  size_t last_space = 0;
994  PN_stdfloat last_space_width = 0.0f;
995 
996  bool any_hyphens = false;
997  size_t last_hyphen = 0;
998  bool output_hyphen = false;
999 
1000  bool overflow = false;
1001  PN_stdfloat wordwrap_width = -1.0f;
1002 
1003  bool last_was_space = false;
1004  PN_stdfloat width = initial_width;
1005  while (q < _text_string.size() && _text_string[q]._character != '\n') {
1006  if (_text_string[q]._cprops->_properties.has_wordwrap()) {
1007  wordwrap_width = _text_string[q]._cprops->_properties.get_wordwrap();
1008  } else {
1009  wordwrap_width = -1.0f;
1010  }
1011 
1012  if (isspacew(_text_string[q]._character) ||
1013  _text_string[q]._character == text_soft_break_key) {
1014  if (!last_was_space) {
1015  any_spaces = true;
1016  // We only care about logging whether there is a soft-hyphen
1017  // character to the right of the rightmost space. Each time
1018  // we encounter a space, we reset this counter.
1019  any_hyphens = false;
1020  last_space = q;
1021  last_space_width = width;
1022  last_was_space = true;
1023  }
1024  } else {
1025  last_was_space = false;
1026  }
1027 
1028  // A soft hyphen character is not printed, but marks a point
1029  // at which we might hyphenate a word if we need to.
1030  if (_text_string[q]._character == text_soft_hyphen_key) {
1031  if (wordwrap_width > 0.0f) {
1032  // We only consider this as a possible hyphenation point if
1033  // (a) it is not the very first character, and (b) there is
1034  // enough room for a hyphen character to be printed following
1035  // it.
1036  if (q != p && width + calc_hyphen_width(_text_string[q]) <= wordwrap_width) {
1037  any_hyphens = true;
1038  last_hyphen = q;
1039  }
1040  }
1041  } else {
1042  // Some normal, printable character.
1043  width += calc_width(_text_string[q]);
1044  }
1045 
1046  q++;
1047 
1048  if (wordwrap_width > 0.0f && width > wordwrap_width) {
1049  // Oops, too many.
1050  q--;
1051  overflow = true;
1052  break;
1053  }
1054  }
1055 
1056  if (overflow) {
1057  // If we stopped because we exceeded the wordwrap width, then
1058  // try to find an appropriate place to wrap the line or to
1059  // hyphenate, if necessary.
1060  nassertr(wordwrap_width > 0.0f, false);
1061 
1062  if (any_spaces && last_space_width / wordwrap_width >= text_hyphen_ratio) {
1063  // If we have a space that ended up within our safety margin,
1064  // don't use any soft-hyphen characters.
1065  any_hyphens = false;
1066  }
1067 
1068  if (any_hyphens) {
1069  // If we still have a soft-hyphen character, use it.
1070  q = last_hyphen;
1071  output_hyphen = true;
1072 
1073  } else if (any_spaces) {
1074  // Otherwise, break at a space if we can.
1075  q = last_space;
1076 
1077  } else {
1078  // Otherwise, this is a forced break. Accept the longest line
1079  // we can that does not leave the next line beginning with one
1080  // of our forbidden characters.
1081  size_t i = 0;
1082  while ((int)i < text_max_never_break && q - i > p &&
1083  get_text_never_break_before().find(_text_string[q - i]._character) != wstring::npos) {
1084  i++;
1085  }
1086  if ((int)i < text_max_never_break) {
1087  q -= i;
1088  }
1089  }
1090  }
1091 
1092  // Skip additional whitespace between the lines.
1093  size_t next_start = q;
1094  while (next_start < _text_string.size() &&
1095  isbreakpoint(_text_string[next_start]._character)) {
1096  next_start++;
1097  }
1098 
1099  // Trim off any more blanks on the end.
1100  while (q > p && isspacew(_text_string[q - 1]._character)) {
1101  q--;
1102  }
1103 
1104  if (next_start == p) {
1105  // No characters got in at all. This could only happen if the
1106  // wordwrap width is narrower than a single character, or if we
1107  // have a substantial number of leading spaces in a line.
1108 
1109  if (initial_width == 0.0f) {
1110  // There was no leading whitespace on the line, so the
1111  // character itself didn't fit within the margins. Let it in
1112  // anyway; what else can we do?
1113  q++;
1114  next_start++;
1115  while (next_start < _text_string.size() &&
1116  isbreakpoint(_text_string[next_start]._character)) {
1117  next_start++;
1118  }
1119  }
1120  }
1121 
1122  if (needs_newline) {
1123  if (_max_rows > 0 && (int)_text_block.size() >= _max_rows) {
1124  // Truncate.
1125  return false;
1126  }
1127  _text_block.push_back(TextRow(p));
1128  }
1129  if (get_multiline_mode()){
1130  needs_newline = true;
1131  }
1132 
1133  if (_text_string[next_start - 1]._cprops->_properties.get_preserve_trailing_whitespace()) {
1134  q = next_start;
1135  }
1136 
1137  for (size_t pi = p; pi < q; pi++) {
1138  if (_text_string[pi]._character != text_soft_hyphen_key &&
1139  _text_string[pi]._character != text_soft_break_key) {
1140  _text_block.back()._string.push_back(_text_string[pi]);
1141  } else {
1142  _text_block.back()._got_soft_hyphens = true;
1143  }
1144  }
1145  if (output_hyphen) {
1146  wstring text_soft_hyphen_output = get_text_soft_hyphen_output();
1147  wstring::const_iterator wi;
1148  for (wi = text_soft_hyphen_output.begin();
1149  wi != text_soft_hyphen_output.end();
1150  ++wi) {
1151  _text_block.back()._string.push_back(TextCharacter(*wi, _text_string[last_hyphen]._cprops));
1152  }
1153  }
1154 
1155  // Now prepare to wrap the next line.
1156 
1157  if (next_start < _text_string.size() && _text_string[next_start]._character == '\n') {
1158  // Preserve a single embedded newline.
1159  if (_max_rows > 0 && (int)_text_block.size() >= _max_rows) {
1160  // Truncate.
1161  return false;
1162  }
1163  _text_block.back()._eol_cprops = _text_string[next_start]._cprops;
1164  next_start++;
1165  _text_block.push_back(TextRow(next_start));
1166  needs_newline = false;
1167  }
1168  p = next_start;
1169 
1170  // Preserve any initial whitespace and newlines.
1171  initial_width = 0.0f;
1172  while (p < _text_string.size() && isspacew(_text_string[p]._character)) {
1173  if (_text_string[p]._character == '\n') {
1174  initial_width = 0.0f;
1175  if (_max_rows > 0 && (int)_text_block.size() >= _max_rows) {
1176  // Truncate.
1177  return false;
1178  }
1179  _text_block.back()._eol_cprops = _text_string[p]._cprops;
1180  _text_block.push_back(TextRow(p + 1));
1181  } else {
1182  initial_width += calc_width(_text_string[p]);
1183  _text_block.back()._string.push_back(_text_string[p]);
1184  }
1185  p++;
1186  }
1187  }
1188 
1189  return true;
1190 }
1191 
1192 ////////////////////////////////////////////////////////////////////
1193 // Function: TextAssembler::calc_hyphen_width
1194 // Access: Private, Static
1195 // Description: Returns the width of the soft-hyphen replacement
1196 // string, according to the indicated character's
1197 // associated font.
1198 ////////////////////////////////////////////////////////////////////
1199 PN_stdfloat TextAssembler::
1200 calc_hyphen_width(const TextCharacter &tch) {
1201  TextFont *font = tch._cprops->_properties.get_font();
1202  nassertr(font != (TextFont *)NULL, 0.0f);
1203 
1204  PN_stdfloat hyphen_width = 0.0f;
1205  wstring text_soft_hyphen_output = get_text_soft_hyphen_output();
1206  wstring::const_iterator wi;
1207  for (wi = text_soft_hyphen_output.begin();
1208  wi != text_soft_hyphen_output.end();
1209  ++wi) {
1210  hyphen_width += calc_width(*wi, tch._cprops->_properties);
1211  }
1212 
1213  return hyphen_width;
1214 }
1215 
1216 ////////////////////////////////////////////////////////////////////
1217 // Function: TextAssembler::assemble_paragraph
1218 // Access: Private
1219 // Description: Fills up placed_glyphs, _ul, _lr with
1220 // the contents of _text_block. Also updates _xpos and
1221 // _ypos within the _text_block structure.
1222 ////////////////////////////////////////////////////////////////////
1223 void TextAssembler::
1224 assemble_paragraph(TextAssembler::PlacedGlyphs &placed_glyphs) {
1225  _ul.set(0.0f, 0.0f);
1226  _lr.set(0.0f, 0.0f);
1227  int num_rows = 0;
1228 
1229  PN_stdfloat ypos = 0.0f;
1230  _next_row_ypos = 0.0f;
1231  TextBlock::iterator bi;
1232  for (bi = _text_block.begin(); bi != _text_block.end(); ++bi) {
1233  TextRow &row = (*bi);
1234 
1235  // First, assemble all the glyphs of this row.
1236  PlacedGlyphs row_placed_glyphs;
1237  PN_stdfloat row_width, line_height, wordwrap;
1238  TextProperties::Alignment align;
1239  assemble_row(row, row_placed_glyphs,
1240  row_width, line_height, align, wordwrap);
1241  // Now move the row to its appropriate position. This might
1242  // involve a horizontal as well as a vertical translation.
1243  LMatrix4 mat = LMatrix4::ident_mat();
1244 
1245  if (num_rows == 0) {
1246  // If this is the first row, account for its space.
1247  _ul[1] = 0.8f * line_height;
1248 
1249  } else {
1250  // If it is not the first row, shift the text downward by
1251  // line_height from the previous row.
1252  ypos -= line_height;
1253  }
1254  _lr[1] = ypos - 0.2 * line_height;
1255 
1256  // Apply the requested horizontal alignment to the row.
1257  //[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
1258  // if the wordwrap size is unspecified the alignment could eventually result wrong.
1259  PN_stdfloat xpos;
1260  switch (align) {
1261  case TextProperties::A_left:
1262  xpos = 0.0f;
1263  _lr[0] = max(_lr[0], row_width);
1264  break;
1265 
1266  case TextProperties::A_right:
1267  xpos = -row_width;
1268  _ul[0] = min(_ul[0], xpos);
1269  break;
1270 
1271  case TextProperties::A_center:
1272  xpos = -0.5f * row_width;
1273  _ul[0] = min(_ul[0], xpos);
1274  _lr[0] = max(_lr[0], -xpos);
1275  break;
1276 
1277  case TextProperties::A_boxed_left:
1278  xpos = 0.0f;
1279  _lr[0] = max(_lr[0], max(row_width, wordwrap));
1280  break;
1281 
1282  case TextProperties::A_boxed_right:
1283  xpos = wordwrap - row_width;
1284  _ul[0] = min(_ul[0], xpos);
1285  break;
1286 
1287  case TextProperties::A_boxed_center:
1288  xpos = -0.5f * row_width;
1289  if (wordwrap > row_width) xpos += (wordwrap * 0.5f);
1290  _ul[0] = min(_ul[0], max(xpos,(wordwrap * 0.5f)));
1291  _lr[0] = max(_lr[0], min(-xpos,-(wordwrap * 0.5f)));
1292  break;
1293  }
1294 
1295  mat.set_row(3, LVector3(xpos, 0.0f, ypos));
1296  row._xpos = xpos;
1297  row._ypos = ypos;
1298 
1299  // Now store the geoms we assembled.
1300  PlacedGlyphs::iterator pi;
1301  for (pi = row_placed_glyphs.begin(); pi != row_placed_glyphs.end(); ++pi) {
1302  (*pi)->_xform *= mat;
1303  placed_glyphs.push_back(*pi);
1304  }
1305 
1306  // Advance to the next line.
1307  num_rows++;
1308  _next_row_ypos = ypos - line_height;
1309  }
1310 
1311  // num_rows may be smaller than _text_block.size(), if there are
1312  // trailing newlines on the string.
1313 }
1314 
1315 ////////////////////////////////////////////////////////////////////
1316 // Function: TextAssembler::assemble_row
1317 // Access: Private
1318 // Description: Assembles the letters in the source string, up until
1319 // the first newline or the end of the string into a
1320 // single row (which is parented to _geom_node), and
1321 // computes the length of the row and the maximum
1322 // line_height of all the fonts used in the row. The
1323 // source pointer is moved to the terminating character.
1324 ////////////////////////////////////////////////////////////////////
1325 void TextAssembler::
1326 assemble_row(TextAssembler::TextRow &row,
1327  TextAssembler::PlacedGlyphs &row_placed_glyphs,
1328  PN_stdfloat &row_width, PN_stdfloat &line_height,
1329  TextProperties::Alignment &align, PN_stdfloat &wordwrap) {
1330  Thread *current_thread = Thread::get_current_thread();
1331 
1332  line_height = 0.0f;
1333  PN_stdfloat xpos = 0.0f;
1334  align = TextProperties::A_left;
1335 
1336  bool underscore = false;
1337  PN_stdfloat underscore_start = 0.0f;
1338  const TextProperties *underscore_properties = NULL;
1339 
1340  TextString::const_iterator si;
1341  for (si = row._string.begin(); si != row._string.end(); ++si) {
1342  const TextCharacter &tch = (*si);
1343  wchar_t character = tch._character;
1344  const TextGraphic *graphic = tch._graphic;
1345  const TextProperties *properties = &(tch._cprops->_properties);
1346 
1347  if (properties->get_underscore() != underscore ||
1348  (underscore && (properties->get_text_color() != underscore_properties->get_text_color() ||
1349  properties->get_underscore_height() != underscore_properties->get_underscore_height()))) {
1350  // Change the underscore status.
1351  if (underscore && underscore_start != xpos) {
1352  draw_underscore(row_placed_glyphs, underscore_start, xpos,
1353  underscore_properties);
1354  }
1355  underscore = properties->get_underscore();
1356  underscore_start = xpos;
1357  underscore_properties = properties;
1358  }
1359 
1360  TextFont *font = properties->get_font();
1361  nassertv(font != (TextFont *)NULL);
1362 
1363  // We get the row's alignment property from the first character of the row
1364  if ((align == TextProperties::A_left) &&
1365  (properties->get_align() != TextProperties::A_left)) {
1366  align = properties->get_align();
1367  }
1368 
1369  // And the height of the row is the maximum of all the fonts used
1370  // within the row.
1371  if (graphic != (TextGraphic *)NULL) {
1372  LVecBase4 frame = graphic->get_frame();
1373  line_height = max(line_height, frame[3] - frame[2]);
1374  } else {
1375  //[fabius] this is not the right place to calc line height (see below)
1376  // line_height = max(line_height, font->get_line_height());
1377  }
1378 
1379  if (character == ' ') {
1380  // A space is a special case.
1381  xpos += properties->get_glyph_scale() * properties->get_text_scale() * font->get_space_advance();
1382 
1383  } else if (character == '\t') {
1384  // So is a tab character.
1385  PN_stdfloat tab_width = properties->get_tab_width();
1386  xpos = (floor(xpos / tab_width) + 1.0f) * tab_width;
1387 
1388  } else if (character == text_soft_hyphen_key) {
1389  // And so is the 'soft-hyphen' key character.
1390 
1391  } else if (graphic != (TextGraphic *)NULL) {
1392  // A special embedded graphic.
1393  GlyphPlacement *placement = new GlyphPlacement;
1394  row_placed_glyphs.push_back(placement);
1395 
1396  PT(PandaNode) model = graphic->get_model().node();
1397  if (graphic->get_instance_flag()) {
1398  // Instance the model in. Create a ModelNode so it doesn't
1399  // get flattened.
1400  PT(ModelNode) model_node = new ModelNode("");
1401  model_node->set_preserve_transform(ModelNode::PT_no_touch);
1402  model_node->add_child(model);
1403  placement->_graphic_model = model_node.p();
1404  } else {
1405  // Copy the model in. This the preferred way; it's a little
1406  // cheaper to render than instancing (because flattening is
1407  // more effective).
1408  placement->_graphic_model = model->copy_subgraph();
1409  }
1410 
1411  LVecBase4 frame = graphic->get_frame();
1412  PN_stdfloat glyph_scale = properties->get_glyph_scale() * properties->get_text_scale();
1413 
1414  PN_stdfloat advance = (frame[1] - frame[0]);
1415 
1416  // Now compute the matrix that will transform the glyph (or
1417  // glyphs) into position.
1418  LMatrix4 glyph_xform = LMatrix4::scale_mat(glyph_scale);
1419 
1420  glyph_xform(3, 0) += (xpos - frame[0]);
1421  glyph_xform(3, 2) += (properties->get_glyph_shift() - frame[2]);
1422 
1423  if (properties->has_slant()) {
1424  LMatrix4 shear(1.0f, 0.0f, 0.0f, 0.0f,
1425  0.0f, 1.0f, 0.0f, 0.0f,
1426  properties->get_slant(), 0.0f, 1.0f, 0.0f,
1427  0.0f, 0.0f, 0.0f, 1.0f);
1428  glyph_xform = shear * glyph_xform;
1429  }
1430 
1431  placement->_xform = glyph_xform;
1432  placement->_properties = properties;
1433 
1434  xpos += advance * glyph_scale;
1435 
1436  } else {
1437  // A printable character.
1438  bool got_glyph;
1439  const TextGlyph *first_glyph;
1440  const TextGlyph *second_glyph;
1441  UnicodeLatinMap::AccentType accent_type;
1442  int additional_flags;
1443  PN_stdfloat glyph_scale;
1444  PN_stdfloat advance_scale;
1445  get_character_glyphs(character, properties,
1446  got_glyph, first_glyph, second_glyph, accent_type,
1447  additional_flags, glyph_scale, advance_scale);
1448 
1449  if (!got_glyph) {
1450  char buffer[512];
1451  sprintf(buffer, "U+%04x", character);
1452  text_cat.warning()
1453  << "No definition in " << font->get_name()
1454  << " for character " << buffer;
1455  if (character < 128 && isprint((unsigned int)character)) {
1456  text_cat.warning(false)
1457  << " ('" << (char)character << "')";
1458  }
1459  text_cat.warning(false)
1460  << "\n";
1461  }
1462 
1463  // Build up a GlyphPlacement, indicating all of the Geoms that go
1464  // into this character. Normally, there is only one Geom per
1465  // character, but it may involve multiple Geoms if we need to
1466  // add cheesy accents or ligatures.
1467  GlyphPlacement *placement = new GlyphPlacement;
1468  row_placed_glyphs.push_back(placement);
1469 
1470  PN_stdfloat advance = 0.0f;
1471 
1472  if (first_glyph != (TextGlyph *)NULL) {
1473  PT(Geom) first_char_geom = first_glyph->get_geom(_usage_hint);
1474  if (first_char_geom != (Geom *)NULL) {
1475  placement->add_piece(first_char_geom, first_glyph->get_state());
1476  }
1477  advance = first_glyph->get_advance() * advance_scale;
1478  }
1479  if (second_glyph != (TextGlyph *)NULL) {
1480  PT(Geom) second_char_geom = second_glyph->get_geom(_usage_hint);
1481  if (second_char_geom != (Geom *)NULL) {
1482  second_char_geom->transform_vertices(LMatrix4::translate_mat(advance, 0.0f, 0.0f));
1483  placement->add_piece(second_char_geom, second_glyph->get_state());
1484  }
1485  advance += second_glyph->get_advance();
1486  }
1487 
1488  glyph_scale *= properties->get_glyph_scale() * properties->get_text_scale();
1489  //[fabius] a good place to take wordwrap size
1490  if (properties->get_wordwrap() > 0.0f) {
1491  wordwrap = properties->get_wordwrap();
1492  }
1493  // Now compute the matrix that will transform the glyph (or
1494  // glyphs) into position.
1495  LMatrix4 glyph_xform = LMatrix4::scale_mat(glyph_scale);
1496 
1497  if (accent_type != UnicodeLatinMap::AT_none || additional_flags != 0) {
1498  // If we have some special handling to perform, do so now.
1499  // This will probably require the bounding volume of the
1500  // glyph, so go get that.
1501  LPoint3 min_vert, max_vert;
1502  bool found_any = false;
1503  placement->calc_tight_bounds(min_vert, max_vert, found_any,
1504  current_thread);
1505 
1506  if (found_any) {
1507  LPoint3 centroid = (min_vert + max_vert) / 2.0f;
1508  tack_on_accent(accent_type, min_vert, max_vert, centroid,
1509  properties, placement);
1510 
1511  if ((additional_flags & UnicodeLatinMap::AF_turned) != 0) {
1512  // Invert the character. Should we also invert the accent
1513  // mark, so that an accent that would have been above the
1514  // glyph will now be below it? That's what we do here,
1515  // which is probably the right thing to do for n-tilde,
1516  // but not for most of the rest of the accent marks. For
1517  // now we'll assume there are no characters with accent
1518  // marks that also have the turned flag.
1519 
1520  // We rotate the character around its centroid, which may
1521  // not always be the right point, but it's the best we've
1522  // got and it's probably pretty close.
1523  LMatrix4 rotate =
1524  LMatrix4::translate_mat(-centroid) *
1525  LMatrix4::rotate_mat_normaxis(180.0f, LVecBase3(0.0f, -1.0f, 0.0f)) *
1526  LMatrix4::translate_mat(centroid);
1527  glyph_xform *= rotate;
1528  }
1529  }
1530  }
1531 
1532  glyph_xform(3, 0) += xpos;
1533  glyph_xform(3, 2) += properties->get_glyph_shift();
1534 
1535  if (properties->has_slant()) {
1536  LMatrix4 shear(1.0f, 0.0f, 0.0f, 0.0f,
1537  0.0f, 1.0f, 0.0f, 0.0f,
1538  properties->get_slant(), 0.0f, 1.0f, 0.0f,
1539  0.0f, 0.0f, 0.0f, 1.0f);
1540  glyph_xform = shear * glyph_xform;
1541  }
1542 
1543  placement->_xform = glyph_xform;
1544  placement->_properties = properties;
1545 
1546  xpos += advance * glyph_scale;
1547  line_height = max(line_height, font->get_line_height() * glyph_scale);
1548  }
1549  }
1550 
1551  if (underscore && underscore_start != xpos) {
1552  draw_underscore(row_placed_glyphs, underscore_start, xpos,
1553  underscore_properties);
1554  }
1555 
1556  row_width = xpos;
1557 
1558  if (row._eol_cprops != (ComputedProperties *)NULL) {
1559  // If there's an _eol_cprops, it represents the cprops of the
1560  // newline character that ended the line, which should also
1561  // contribute towards the line_height.
1562 
1563  const TextProperties *properties = &(row._eol_cprops->_properties);
1564  TextFont *font = properties->get_font();
1565  nassertv(font != (TextFont *)NULL);
1566 
1567  if (line_height == 0.0f) {
1568  PN_stdfloat glyph_scale = properties->get_glyph_scale() * properties->get_text_scale();
1569  line_height = max(line_height, font->get_line_height() * glyph_scale);
1570  }
1571  }
1572 }
1573 
1574 ////////////////////////////////////////////////////////////////////
1575 // Function: TextAssembler::draw_underscore
1576 // Access: Private, Static
1577 // Description: Creates the geometry to render the underscore line
1578 // for the indicated range of glyphs in this row.
1579 ////////////////////////////////////////////////////////////////////
1580 void TextAssembler::
1581 draw_underscore(TextAssembler::PlacedGlyphs &row_placed_glyphs,
1582  PN_stdfloat underscore_start, PN_stdfloat underscore_end,
1583  const TextProperties *underscore_properties) {
1584  CPT(GeomVertexFormat) format = GeomVertexFormat::get_v3cp();
1585  PT(GeomVertexData) vdata =
1586  new GeomVertexData("text", format, Geom::UH_static);
1587  vdata->reserve_num_rows(2);
1588  GeomVertexWriter vertex(vdata, InternalName::get_vertex());
1589  GeomVertexWriter color(vdata, InternalName::get_color());
1590 
1591  PN_stdfloat y = underscore_properties->get_underscore_height();
1592  vertex.add_data3(underscore_start, 0.0f, y);
1593  color.add_data4(underscore_properties->get_text_color());
1594  vertex.add_data3(underscore_end, 0.0f, y);
1595  color.add_data4(underscore_properties->get_text_color());
1596 
1597  PT(GeomLines) lines = new GeomLines(Geom::UH_static);
1598  lines->add_vertices(0, 1);
1599  lines->close_primitive();
1600 
1601  PT(Geom) geom = new Geom(vdata);
1602  geom->add_primitive(lines);
1603 
1604  GlyphPlacement *placement = new GlyphPlacement;
1605  placement->add_piece(geom, RenderState::make_empty());
1606  placement->_xform = LMatrix4::ident_mat();
1607  placement->_properties = underscore_properties;
1608 
1609  row_placed_glyphs.push_back(placement);
1610 }
1611 
1612 ////////////////////////////////////////////////////////////////////
1613 // Function: TextAssembler::get_character_glyphs
1614 // Access: Private, Static
1615 // Description: Looks up the glyph(s) from the font for the
1616 // appropriate character. If the desired glyph isn't
1617 // available (especially in the case of an accented
1618 // letter), tries to find a suitable replacement.
1619 // Normally, only one glyph is returned per character,
1620 // but in the case in which we have to simulate a
1621 // missing ligature in the font, two glyphs might be
1622 // returned.
1623 //
1624 // All parameters except the first two are output
1625 // parameters. got_glyph is set true if the glyph (or
1626 // an acceptable substitute) is successfully found,
1627 // false otherwise; but even if it is false, glyph might
1628 // still be non-NULL, indicating a stand-in glyph for a
1629 // missing character.
1630 ////////////////////////////////////////////////////////////////////
1631 void TextAssembler::
1632 get_character_glyphs(int character, const TextProperties *properties,
1633  bool &got_glyph, const TextGlyph *&glyph,
1634  const TextGlyph *&second_glyph,
1635  UnicodeLatinMap::AccentType &accent_type,
1636  int &additional_flags,
1637  PN_stdfloat &glyph_scale, PN_stdfloat &advance_scale) {
1638  TextFont *font = properties->get_font();
1639  nassertv_always(font != (TextFont *)NULL);
1640 
1641  got_glyph = false;
1642  glyph = NULL;
1643  second_glyph = NULL;
1644  accent_type = UnicodeLatinMap::AT_none;
1645  additional_flags = 0;
1646  glyph_scale = 1.0f;
1647  advance_scale = 1.0f;
1648 
1649  // Maybe we should remap the character to something else--e.g. a
1650  // small capital.
1651  const UnicodeLatinMap::Entry *map_entry =
1652  UnicodeLatinMap::look_up(character);
1653  if (map_entry != NULL) {
1654  if (properties->get_small_caps() &&
1655  map_entry->_toupper_character != character) {
1656  character = map_entry->_toupper_character;
1657  map_entry = UnicodeLatinMap::look_up(character);
1658  glyph_scale = properties->get_small_caps_scale();
1659  }
1660  }
1661 
1662  got_glyph = font->get_glyph(character, glyph);
1663  if (!got_glyph && map_entry != NULL && map_entry->_ascii_equiv != 0) {
1664  // If we couldn't find the Unicode glyph, try the ASCII
1665  // equivalent (without the accent marks).
1666  got_glyph = font->get_glyph(map_entry->_ascii_equiv, glyph);
1667 
1668  if (!got_glyph && map_entry->_toupper_character != character) {
1669  // If we still couldn't find it, try the uppercase
1670  // equivalent.
1671  character = map_entry->_toupper_character;
1672  map_entry = UnicodeLatinMap::look_up(character);
1673  if (map_entry != NULL) {
1674  got_glyph = font->get_glyph(map_entry->_ascii_equiv, glyph);
1675  }
1676  }
1677 
1678  if (got_glyph) {
1679  accent_type = map_entry->_accent_type;
1680  additional_flags = map_entry->_additional_flags;
1681 
1682  bool got_second_glyph = false;
1683  if (map_entry->_ascii_additional != 0) {
1684  // There's another character, too--probably a ligature.
1685  got_second_glyph =
1686  font->get_glyph(map_entry->_ascii_additional, second_glyph);
1687  }
1688 
1689  if ((additional_flags & UnicodeLatinMap::AF_ligature) != 0 &&
1690  got_second_glyph) {
1691  // If we have two letters that are supposed to be in a
1692  // ligature, just jam them together.
1693  additional_flags &= ~UnicodeLatinMap::AF_ligature;
1694  advance_scale = ligature_advance_scale;
1695  }
1696 
1697  if ((additional_flags & UnicodeLatinMap::AF_smallcap) != 0) {
1698  additional_flags &= ~UnicodeLatinMap::AF_smallcap;
1699  glyph_scale = properties->get_small_caps_scale();
1700  }
1701  }
1702  }
1703 }
1704 
1705 
1706 ////////////////////////////////////////////////////////////////////
1707 // Function: TextAssembler::tack_on_accent
1708 // Access: Private
1709 // Description: This is a cheesy attempt to tack on an accent to an
1710 // ASCII letter for which we don't have the appropriate
1711 // already-accented glyph in the font.
1712 ////////////////////////////////////////////////////////////////////
1713 void TextAssembler::
1714 tack_on_accent(UnicodeLatinMap::AccentType accent_type,
1715  const LPoint3 &min_vert, const LPoint3 &max_vert,
1716  const LPoint3 &centroid,
1717  const TextProperties *properties,
1718  TextAssembler::GlyphPlacement *placement) const {
1719  switch (accent_type) {
1720  case UnicodeLatinMap::AT_grave:
1721  // We use the slash as the grave and acute accents. ASCII does
1722  // have a grave accent character, but a lot of fonts put the
1723  // reverse apostrophe there instead. And some fonts (particularly
1724  // fonts from mf) don't even do backslash.
1725  tack_on_accent('/', CP_above, CT_small_squash_mirror_y, min_vert, max_vert, centroid,
1726  properties, placement);
1727  break;
1728 
1729  case UnicodeLatinMap::AT_acute:
1730  tack_on_accent('/', CP_above, CT_small_squash, min_vert, max_vert, centroid,
1731  properties, placement);
1732  break;
1733 
1734  case UnicodeLatinMap::AT_breve:
1735  tack_on_accent(')', CP_above, CT_tiny_rotate_270, min_vert, max_vert, centroid,
1736  properties, placement);
1737  break;
1738 
1739  case UnicodeLatinMap::AT_inverted_breve:
1740  tack_on_accent('(', CP_above, CT_tiny_rotate_270, min_vert, max_vert, centroid,
1741  properties, placement);
1742  break;
1743 
1744  case UnicodeLatinMap::AT_circumflex:
1745  tack_on_accent('^', CP_above, CT_none, min_vert, max_vert, centroid,
1746  properties, placement) ||
1747  tack_on_accent('v', CP_above, CT_squash_mirror_y, min_vert, max_vert, centroid,
1748  properties, placement);
1749  break;
1750 
1751  case UnicodeLatinMap::AT_circumflex_below:
1752  tack_on_accent('^', CP_below, CT_none, min_vert, max_vert, centroid,
1753  properties, placement) ||
1754  tack_on_accent('v', CP_below, CT_squash_mirror_y, min_vert, max_vert, centroid,
1755  properties, placement);
1756  break;
1757 
1758  case UnicodeLatinMap::AT_caron:
1759  tack_on_accent('^', CP_above, CT_mirror_y, min_vert, max_vert, centroid,
1760  properties, placement) ||
1761  tack_on_accent('v', CP_above, CT_squash, min_vert, max_vert, centroid,
1762  properties, placement);
1763 
1764  break;
1765 
1766  case UnicodeLatinMap::AT_tilde:
1767  tack_on_accent('~', CP_above, CT_none, min_vert, max_vert, centroid,
1768  properties, placement) ||
1769  tack_on_accent('s', CP_above, CT_squash_mirror_diag, min_vert, max_vert, centroid,
1770  properties, placement);
1771 
1772  break;
1773 
1774  case UnicodeLatinMap::AT_tilde_below:
1775  tack_on_accent('~', CP_below, CT_none, min_vert, max_vert, centroid,
1776  properties, placement) ||
1777  tack_on_accent('s', CP_below, CT_squash_mirror_diag, min_vert, max_vert, centroid,
1778  properties, placement);
1779  break;
1780 
1781  case UnicodeLatinMap::AT_diaeresis:
1782  tack_on_accent(':', CP_above, CT_small_rotate_270, min_vert, max_vert, centroid,
1783  properties, placement);
1784  break;
1785 
1786  case UnicodeLatinMap::AT_diaeresis_below:
1787  tack_on_accent(':', CP_below, CT_small_rotate_270, min_vert, max_vert, centroid,
1788  properties, placement);
1789  break;
1790 
1791  case UnicodeLatinMap::AT_dot_above:
1792  tack_on_accent('.', CP_above, CT_none, min_vert, max_vert, centroid,
1793  properties, placement);
1794  break;
1795 
1796  case UnicodeLatinMap::AT_dot_below:
1797  tack_on_accent('.', CP_below, CT_none, min_vert, max_vert, centroid,
1798  properties, placement);
1799  break;
1800 
1801  case UnicodeLatinMap::AT_macron:
1802  tack_on_accent('-', CP_above, CT_none, min_vert, max_vert, centroid,
1803  properties, placement);
1804  break;
1805 
1806  case UnicodeLatinMap::AT_line_below:
1807  tack_on_accent('-', CP_below, CT_none, min_vert, max_vert, centroid,
1808  properties, placement);
1809  break;
1810 
1811  case UnicodeLatinMap::AT_ring_above:
1812  tack_on_accent('o', CP_top, CT_tiny, min_vert, max_vert, centroid,
1813  properties, placement);
1814  break;
1815 
1816  case UnicodeLatinMap::AT_ring_below:
1817  tack_on_accent('o', CP_bottom, CT_tiny, min_vert, max_vert, centroid,
1818  properties, placement);
1819  break;
1820 
1821  case UnicodeLatinMap::AT_cedilla:
1822  tack_on_accent('c', CP_bottom, CT_tiny_mirror_x, min_vert, max_vert, centroid,
1823  properties, placement);
1824  //tack_on_accent(',', CP_bottom, CT_none, min_vert, max_vert, centroid,
1825  // properties, placement);
1826  break;
1827 
1828  case UnicodeLatinMap::AT_comma_below:
1829  tack_on_accent(',', CP_below, CT_none, min_vert, max_vert, centroid,
1830  properties, placement);
1831  break;
1832 
1833  case UnicodeLatinMap::AT_ogonek:
1834  tack_on_accent(',', CP_bottom, CT_mirror_x, min_vert, max_vert, centroid,
1835  properties, placement);
1836  break;
1837 
1838  case UnicodeLatinMap::AT_stroke:
1839  tack_on_accent('/', CP_within, CT_none, min_vert, max_vert, centroid,
1840  properties, placement);
1841  break;
1842 
1843  default:
1844  // There are lots of other crazy kinds of accents. Forget 'em.
1845  break;
1846  }
1847 }
1848 
1849 ////////////////////////////////////////////////////////////////////
1850 // Function: TextAssembler::tack_on_accent
1851 // Access: Private
1852 // Description: Generates a cheesy accent mark above (or below, etc.)
1853 // the character. Returns true if successful, or false
1854 // if the named accent character doesn't exist in the
1855 // font.
1856 ////////////////////////////////////////////////////////////////////
1857 bool TextAssembler::
1858 tack_on_accent(char accent_mark, TextAssembler::CheesyPosition position,
1859  TextAssembler::CheesyTransform transform,
1860  const LPoint3 &min_vert, const LPoint3 &max_vert,
1861  const LPoint3 &centroid,
1862  const TextProperties *properties,
1863  TextAssembler::GlyphPlacement *placement) const {
1864  TextFont *font = properties->get_font();
1865  nassertr(font != (TextFont *)NULL, false);
1866 
1867  Thread *current_thread = Thread::get_current_thread();
1868 
1869  const TextGlyph *accent_glyph;
1870  if (font->get_glyph(accent_mark, accent_glyph) ||
1871  font->get_glyph(toupper(accent_mark), accent_glyph)) {
1872  PT(Geom) accent_geom = accent_glyph->get_geom(_usage_hint);
1873  if (accent_geom != (Geom *)NULL) {
1874  LPoint3 min_accent, max_accent;
1875  bool found_any = false;
1876  accent_geom->calc_tight_bounds(min_accent, max_accent, found_any,
1877  current_thread);
1878  if (found_any) {
1879  PN_stdfloat t, u;
1880  LMatrix4 accent_mat;
1881 
1882  // This gets set to true if the glyph gets mirrored and needs
1883  // to have backface culling disabled.
1884  bool mirrored = false;
1885 
1886  switch (transform) {
1887  case CT_none:
1888  accent_mat = LMatrix4::ident_mat();
1889  break;
1890 
1891  case CT_mirror_x:
1892  accent_mat = LMatrix4::scale_mat(-1.0f, 1.0f, 1.0f);
1893  t = min_accent[0];
1894  min_accent[0] = -max_accent[0];
1895  max_accent[0] = -t;
1896  mirrored = true;
1897  break;
1898 
1899  case CT_mirror_y:
1900  accent_mat = LMatrix4::scale_mat(1.0f, 1.0f, -1.0f);
1901  t = min_accent[2];
1902  min_accent[2] = -max_accent[2];
1903  max_accent[2] = -t;
1904  mirrored = true;
1905  break;
1906 
1907  case CT_rotate_90:
1908  accent_mat.set_rotate_mat_normaxis(90.0f, LVecBase3(0.0f, -1.0f, 0.0f));
1909  // rotate min, max
1910  t = min_accent[0];
1911  u = max_accent[0];
1912  max_accent[0] = -min_accent[2];
1913  min_accent[0] = -max_accent[2];
1914  max_accent[2] = u;
1915  min_accent[2] = t;
1916  break;
1917 
1918  case CT_rotate_180:
1919  accent_mat = LMatrix4::scale_mat(-1.0f, -1.0f, 1.0f);
1920 
1921  t = min_accent[0];
1922  min_accent[0] = -max_accent[0];
1923  max_accent[0] = -t;
1924  t = min_accent[2];
1925  min_accent[2] = -max_accent[2];
1926  max_accent[2] = -t;
1927  break;
1928 
1929  case CT_rotate_270:
1930  accent_mat.set_rotate_mat_normaxis(270.0f, LVecBase3(0.0f, -1.0f, 0.0f));
1931  // rotate min, max
1932  t = min_accent[0];
1933  u = max_accent[0];
1934  min_accent[0] = min_accent[2];
1935  max_accent[0] = max_accent[2];
1936  min_accent[2] = -u;
1937  max_accent[2] = -t;
1938  break;
1939 
1940  case CT_squash:
1941  accent_mat = LMatrix4::scale_mat(squash_accent_scale_x, 1.0f, squash_accent_scale_y);
1942  min_accent[0] *= squash_accent_scale_x;
1943  max_accent[0] *= squash_accent_scale_x;
1944  min_accent[2] *= squash_accent_scale_y;
1945  max_accent[2] *= squash_accent_scale_y;
1946  break;
1947 
1948  case CT_squash_mirror_y:
1949  accent_mat = LMatrix4::scale_mat(squash_accent_scale_x, 1.0f, -squash_accent_scale_y);
1950  min_accent[0] *= squash_accent_scale_x;
1951  max_accent[0] *= squash_accent_scale_x;
1952  t = min_accent[2];
1953  min_accent[2] = -max_accent[2] * squash_accent_scale_y;
1954  max_accent[2] = -t * squash_accent_scale_y;
1955  mirrored = true;
1956  break;
1957 
1958  case CT_squash_mirror_diag:
1959  accent_mat =
1960  LMatrix4::rotate_mat_normaxis(270.0f, LVecBase3(0.0f, -1.0f, 0.0f)) *
1961  LMatrix4::scale_mat(-squash_accent_scale_x, 1.0f, squash_accent_scale_y);
1962 
1963  // rotate min, max
1964  t = min_accent[0];
1965  u = max_accent[0];
1966  min_accent[0] = min_accent[2] * -squash_accent_scale_x;
1967  max_accent[0] = max_accent[2] * -squash_accent_scale_x;
1968  min_accent[2] = -u * squash_accent_scale_y;
1969  max_accent[2] = -t * squash_accent_scale_y;
1970  mirrored = true;
1971  break;
1972 
1973  case CT_small_squash:
1974  accent_mat = LMatrix4::scale_mat(small_squash_accent_scale_x, 1.0f, small_squash_accent_scale_y);
1975  min_accent[0] *= small_squash_accent_scale_x;
1976  max_accent[0] *= small_squash_accent_scale_x;
1977  min_accent[2] *= small_squash_accent_scale_y;
1978  max_accent[2] *= small_squash_accent_scale_y;
1979  break;
1980 
1981  case CT_small_squash_mirror_y:
1982  accent_mat = LMatrix4::scale_mat(small_squash_accent_scale_x, 1.0f, -small_squash_accent_scale_y);
1983  min_accent[0] *= small_squash_accent_scale_x;
1984  max_accent[0] *= small_squash_accent_scale_x;
1985  t = min_accent[2];
1986  min_accent[2] = -max_accent[2] * small_squash_accent_scale_y;
1987  max_accent[2] = -t * small_squash_accent_scale_y;
1988  mirrored = true;
1989  break;
1990 
1991  case CT_small_squash_mirror_diag:
1992  accent_mat =
1993  LMatrix4::rotate_mat_normaxis(270.0f, LVecBase3(0.0f, -1.0f, 0.0f)) *
1994  LMatrix4::scale_mat(-small_squash_accent_scale_x, 1.0f, small_squash_accent_scale_y);
1995 
1996  // rotate min, max
1997  t = min_accent[0];
1998  u = max_accent[0];
1999  min_accent[0] = min_accent[2] * -small_squash_accent_scale_x;
2000  max_accent[0] = max_accent[2] * -small_squash_accent_scale_x;
2001  min_accent[2] = -u * small_squash_accent_scale_y;
2002  max_accent[2] = -t * small_squash_accent_scale_y;
2003  mirrored = true;
2004  break;
2005 
2006  case CT_small:
2007  accent_mat = LMatrix4::scale_mat(small_accent_scale);
2008  min_accent *= small_accent_scale;
2009  max_accent *= small_accent_scale;
2010  break;
2011 
2012  case CT_small_rotate_270:
2013  accent_mat =
2014  LMatrix4::rotate_mat_normaxis(270.0f, LVecBase3(0.0f, -1.0f, 0.0f)) *
2015  LMatrix4::scale_mat(small_accent_scale);
2016 
2017  // rotate min, max
2018  t = min_accent[0];
2019  u = max_accent[0];
2020  min_accent[0] = min_accent[2] * small_accent_scale;
2021  max_accent[0] = max_accent[2] * small_accent_scale;
2022  min_accent[2] = -u * small_accent_scale;
2023  max_accent[2] = -t * small_accent_scale;
2024  break;
2025 
2026  case CT_tiny:
2027  accent_mat = LMatrix4::scale_mat(tiny_accent_scale);
2028  min_accent *= tiny_accent_scale;
2029  max_accent *= tiny_accent_scale;
2030  break;
2031 
2032  case CT_tiny_mirror_x:
2033  accent_mat = LMatrix4::scale_mat(-tiny_accent_scale, 1.0f, tiny_accent_scale);
2034 
2035  t = min_accent[0];
2036  min_accent[0] = -max_accent[0] * tiny_accent_scale;
2037  max_accent[0] = -t * tiny_accent_scale;
2038  min_accent[2] *= tiny_accent_scale;
2039  max_accent[2] *= tiny_accent_scale;
2040  mirrored = true;
2041  break;
2042 
2043  case CT_tiny_rotate_270:
2044  accent_mat =
2045  LMatrix4::rotate_mat_normaxis(270.0f, LVecBase3(0.0f, -1.0f, 0.0f)) *
2046  LMatrix4::scale_mat(tiny_accent_scale);
2047 
2048  // rotate min, max
2049  t = min_accent[0];
2050  u = max_accent[0];
2051  min_accent[0] = min_accent[2] * tiny_accent_scale;
2052  max_accent[0] = max_accent[2] * tiny_accent_scale;
2053  min_accent[2] = -u * tiny_accent_scale;
2054  max_accent[2] = -t * tiny_accent_scale;
2055  break;
2056  }
2057 
2058  LPoint3 accent_centroid = (min_accent + max_accent) / 2.0f;
2059  PN_stdfloat accent_height = max_accent[2] - min_accent[2];
2060  LVector3 trans;
2061  switch (position) {
2062  case CP_above:
2063  // A little above the character.
2064  trans.set(centroid[0] - accent_centroid[0], 0.0f,
2065  max_vert[2] - accent_centroid[2] + accent_height * 0.5);
2066  break;
2067 
2068  case CP_below:
2069  // A little below the character.
2070  trans.set(centroid[0] - accent_centroid[0], 0.0f,
2071  min_vert[2] - accent_centroid[2] - accent_height * 0.5);
2072  break;
2073 
2074  case CP_top:
2075  // Touching the top of the character.
2076  trans.set(centroid[0] - accent_centroid[0], 0.0f,
2077  max_vert[2] - accent_centroid[2]);
2078  break;
2079 
2080  case CP_bottom:
2081  // Touching the bottom of the character.
2082  trans.set(centroid[0] - accent_centroid[0], 0.0f,
2083  min_vert[2] - accent_centroid[2]);
2084  break;
2085 
2086  case CP_within:
2087  // Centered within the character.
2088  trans.set(centroid[0] - accent_centroid[0], 0.0f,
2089  centroid[2] - accent_centroid[2]);
2090  break;
2091  }
2092 
2093  accent_mat.set_row(3, trans);
2094  accent_geom->transform_vertices(accent_mat);
2095 
2096  if (mirrored) {
2097  // Once someone asks for this pointer, we hold its reference
2098  // count and never free it.
2099  static CPT(RenderState) disable_backface;
2100  if (disable_backface == (const RenderState *)NULL) {
2101  disable_backface = RenderState::make
2102  (CullFaceAttrib::make(CullFaceAttrib::M_cull_none));
2103  }
2104 
2105  CPT(RenderState) state =
2106  accent_glyph->get_state()->compose(disable_backface);
2107  placement->add_piece(accent_geom, state);
2108  } else {
2109  placement->add_piece(accent_geom, accent_glyph->get_state());
2110  }
2111 
2112  return true;
2113  }
2114  }
2115  }
2116  return false;
2117 }
2118 
2119 ////////////////////////////////////////////////////////////////////
2120 // Function: TextAssembler::ComputedProperties::append_delta
2121 // Access: Public
2122 // Description: Appends to wtext the control sequences necessary to
2123 // change from this ComputedProperties to the indicated
2124 // ComputedProperties.
2125 ////////////////////////////////////////////////////////////////////
2126 void TextAssembler::ComputedProperties::
2127 append_delta(wstring &wtext, TextAssembler::ComputedProperties *other) {
2128  if (this != other) {
2129  if (_depth > other->_depth) {
2130  // Back up a level from this properties.
2131  nassertv(_based_on != NULL);
2132 
2133  wtext.push_back(text_pop_properties_key);
2134  _based_on->append_delta(wtext, other);
2135 
2136  } else if (other->_depth > _depth) {
2137  // Back up a level from the other properties.
2138  nassertv(other->_based_on != NULL);
2139 
2140  append_delta(wtext, other->_based_on);
2141  wtext.push_back(text_push_properties_key);
2142  wtext += other->_wname;
2143  wtext.push_back(text_push_properties_key);
2144 
2145  } else if (_depth != 0) {
2146  // Back up a level from both properties.
2147  nassertv(_based_on != NULL && other->_based_on != NULL);
2148 
2149  wtext.push_back(text_pop_properties_key);
2150  _based_on->append_delta(wtext, other->_based_on);
2151  wtext.push_back(text_push_properties_key);
2152  wtext += other->_wname;
2153  wtext.push_back(text_push_properties_key);
2154  }
2155  }
2156 }
2157 
2158 ////////////////////////////////////////////////////////////////////
2159 // Function: TextAssembler::GlyphPlacement::calc_tight_bounds
2160 // Access: Private
2161 // Description: Expands min_point and max_point to include all of the
2162 // vertices in the glyph(s), if any. found_any is set
2163 // true if any points are found. It is the caller's
2164 // responsibility to initialize min_point, max_point,
2165 // and found_any before calling this function.
2166 ////////////////////////////////////////////////////////////////////
2167 void TextAssembler::GlyphPlacement::
2168 calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
2169  bool &found_any, Thread *current_thread) const {
2170  Pieces::const_iterator pi;
2171  for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
2172  (*pi)._geom->calc_tight_bounds(min_point, max_point, found_any,
2173  current_thread);
2174  }
2175 }
2176 
2177 ////////////////////////////////////////////////////////////////////
2178 // Function: TextAssembler::GlyphPlacement::assign_to
2179 // Access: Private
2180 // Description: Puts the pieces of the GlyphPlacement in the
2181 // indicated GeomNode. The vertices of the Geoms are
2182 // modified by this operation.
2183 ////////////////////////////////////////////////////////////////////
2184 void TextAssembler::GlyphPlacement::
2185 assign_to(GeomNode *geom_node, const RenderState *state) const {
2186  Pieces::const_iterator pi;
2187  for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
2188  (*pi)._geom->transform_vertices(_xform);
2189  geom_node->add_geom((*pi)._geom, state->compose((*pi)._state));
2190  }
2191 }
2192 
2193 ////////////////////////////////////////////////////////////////////
2194 // Function: TextAssembler::GlyphPlacement::assign_copy_to
2195 // Access: Private
2196 // Description: Puts the pieces of the GlyphPlacement in the
2197 // indicated GeomNode. This flavor will make a copy of
2198 // the Geoms first, and then apply the additional
2199 // transform to the vertices.
2200 ////////////////////////////////////////////////////////////////////
2201 void TextAssembler::GlyphPlacement::
2202 assign_copy_to(GeomNode *geom_node, const RenderState *state,
2203  const LMatrix4 &extra_xform) const {
2204  LMatrix4 new_xform = _xform * extra_xform;
2205  Pieces::const_iterator pi;
2206  for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
2207  const Geom *geom = (*pi)._geom;
2208  PT(Geom) new_geom = geom->make_copy();
2209  new_geom->transform_vertices(new_xform);
2210  geom_node->add_geom(new_geom, state->compose((*pi)._state));
2211  }
2212 }
2213 
2214 ////////////////////////////////////////////////////////////////////
2215 // Function: TextAssembler::GlyphPlacement::assign_append_to
2216 // Access: Private
2217 // Description: Puts the pieces of the GlyphPlacement in the
2218 // indicated GeomNode. This flavor will append the
2219 // Geoms with the additional transform applied to the
2220 // vertices.
2221 ////////////////////////////////////////////////////////////////////
2222 void TextAssembler::GlyphPlacement::
2223 assign_append_to(GeomCollectorMap &geom_collector_map,
2224  const RenderState *state,
2225  const LMatrix4 &extra_xform) const {
2226  LMatrix4 new_xform = _xform * extra_xform;
2227  Pieces::const_iterator pi;
2228 
2229  int p, sp, s, e, i;
2230  for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) {
2231  const Geom *geom = (*pi)._geom;
2232  const GeomVertexData *vdata = geom->get_vertex_data();
2233  CPT(RenderState) rs = (*pi)._state->compose(state);
2234  GeomCollectorKey key(rs, vdata->get_format());
2235 
2236  GeomCollectorMap::iterator mi = geom_collector_map.find(key);
2237  if (mi == geom_collector_map.end()) {
2238  mi = geom_collector_map.insert(GeomCollectorMap::value_type(key, GeomCollector(vdata->get_format()))).first;
2239  }
2240  GeomCollector &geom_collector = (*mi).second;
2241  geom_collector.count_geom(geom);
2242 
2243  // We use this map to keep track of vertex indices we have already
2244  // added, so that we don't needlessly duplicate vertices into our
2245  // output vertex data.
2246  VertexIndexMap vimap;
2247 
2248  for (p = 0; p < geom->get_num_primitives(); p++) {
2249  CPT(GeomPrimitive) primitive = geom->get_primitive(p)->decompose();
2250 
2251  // Get a new GeomPrimitive of the corresponding type.
2252  GeomPrimitive *new_prim = geom_collector.get_primitive(primitive->get_type());
2253 
2254  // Walk through all of the components (e.g. triangles) of the
2255  // primitive.
2256  for (sp = 0; sp < primitive->get_num_primitives(); sp++) {
2257  s = primitive->get_primitive_start(sp);
2258  e = primitive->get_primitive_end(sp);
2259 
2260  // Walk through all of the vertices in the component.
2261  for (i = s; i < e; i++) {
2262  int vi = primitive->get_vertex(i);
2263 
2264  // Attempt to insert number "vi" into the map.
2265  pair<VertexIndexMap::iterator, bool> added = vimap.insert(VertexIndexMap::value_type(vi, 0));
2266  int new_vertex;
2267  if (added.second) {
2268  // The insert succeeded. That means this is the first
2269  // time we have encountered this vertex.
2270  new_vertex = geom_collector.append_vertex(vdata, vi, new_xform);
2271  // Update the map with the newly-created target vertex index.
2272  (*(added.first)).second = new_vertex;
2273 
2274  } else {
2275  // The insert failed. This means we have previously
2276  // encountered this vertex, and we have already entered
2277  // its target vertex index into the vimap. Extract that
2278  // vertex index, so we can reuse it.
2279  new_vertex = (*(added.first)).second;
2280  }
2281  new_prim->add_vertex(new_vertex);
2282  }
2283  new_prim->close_primitive();
2284  }
2285  }
2286  }
2287 }
2288 
2289 ////////////////////////////////////////////////////////////////////
2290 // Function: TextAssembler::GlyphPlacement::copy_graphic_to
2291 // Access: Private
2292 // Description: If the GlyphPlacement includes a special graphic,
2293 // copies it to the indicated node.
2294 ////////////////////////////////////////////////////////////////////
2295 void TextAssembler::GlyphPlacement::
2296 copy_graphic_to(PandaNode *node, const RenderState *state,
2297  const LMatrix4 &extra_xform) const {
2298  if (_graphic_model != (PandaNode *)NULL) {
2299  LMatrix4 new_xform = _xform * extra_xform;
2300 
2301  // We need an intermediate node to hold the transform and state.
2302  PT(PandaNode) intermediate_node = new PandaNode("");
2303  node->add_child(intermediate_node);
2304 
2305  intermediate_node->set_transform(TransformState::make_mat(new_xform));
2306  intermediate_node->set_state(state);
2307  intermediate_node->add_child(_graphic_model);
2308  }
2309 }
2310 
2311 ////////////////////////////////////////////////////////////////////
2312 // Function: TextAssembler::GeomCollector Constructor
2313 // Access: Public
2314 // Description: constructs the GeomCollector class
2315 // (Geom, GeomTriangles, vertexWriter, texcoordWriter..)
2316 ////////////////////////////////////////////////////////////////////
2317 TextAssembler::GeomCollector::
2318 GeomCollector(const GeomVertexFormat *format) :
2319  _vdata(new GeomVertexData("merged_geom", format, Geom::UH_static)),
2320  _geom(new GeomTextGlyph(_vdata))
2321 {
2322 }
2323 
2324 ////////////////////////////////////////////////////////////////////
2325 // Function: TextAssembler::GeomCollector Copy Constructor
2326 // Access: Public
2327 // Description:
2328 ////////////////////////////////////////////////////////////////////
2329 TextAssembler::GeomCollector::
2330 GeomCollector(const TextAssembler::GeomCollector &copy) :
2331  _vdata(copy._vdata),
2332  _geom(copy._geom)
2333 {
2334 }
2335 
2336 ////////////////////////////////////////////////////////////////////
2337 // Function: TextAssembler::GeomCollector::get_primitive
2338 // Access: Public
2339 // Description: Returns a GeomPrimitive of the appropriate type. If
2340 // one has not yet been created, returns a newly-created
2341 // one; if one has previously been created of this type,
2342 // returns the previously-created one.
2343 ////////////////////////////////////////////////////////////////////
2344 GeomPrimitive *TextAssembler::GeomCollector::
2345 get_primitive(TypeHandle prim_type) {
2346  if (prim_type == GeomTriangles::get_class_type()) {
2347  if (_triangles == (GeomPrimitive *)NULL) {
2348  _triangles = new GeomTriangles(Geom::UH_static);
2349  _geom->add_primitive(_triangles);
2350  }
2351  return _triangles;
2352 
2353  } else if (prim_type == GeomLines::get_class_type()) {
2354  if (_lines == (GeomPrimitive *)NULL) {
2355  _lines = new GeomLines(Geom::UH_static);
2356  _geom->add_primitive(_lines);
2357  }
2358  return _lines;
2359 
2360  } else if (prim_type == GeomPoints::get_class_type()) {
2361  if (_points == (GeomPrimitive *)NULL) {
2362  _points = new GeomPoints(Geom::UH_static);
2363  _geom->add_primitive(_points);
2364  }
2365  return _points;
2366  }
2367 
2368  nassertr(false, NULL);
2369  return NULL;
2370 }
2371 
2372 ////////////////////////////////////////////////////////////////////
2373 // Function: TextAssembler::GeomCollector::append_vertex
2374 // Access: Public
2375 // Description: Adds one vertex to the GeomVertexData.
2376 // Returns the row number of the added vertex.
2377 ////////////////////////////////////////////////////////////////////
2378 int TextAssembler::GeomCollector::
2379 append_vertex(const GeomVertexData *orig_vdata, int orig_row,
2380  const LMatrix4 &xform) {
2381  int new_row = _vdata->get_num_rows();
2382  _vdata->copy_row_from(new_row, orig_vdata, orig_row, Thread::get_current_thread());
2383 
2384  GeomVertexRewriter vertex_rewriter(_vdata, InternalName::get_vertex());
2385  vertex_rewriter.set_row_unsafe(new_row);
2386  LPoint3 point = vertex_rewriter.get_data3();
2387  vertex_rewriter.set_data3(point * xform);
2388 
2389  return new_row;
2390 }
2391 
2392 
2393 ////////////////////////////////////////////////////////////////////
2394 // Function: TextAssembler::GeomCollector::append_geom
2395 // Access: Public
2396 // Description: closes the geomTriangles and appends the geom to
2397 // the given GeomNode
2398 ////////////////////////////////////////////////////////////////////
2399 void TextAssembler::GeomCollector::
2400 append_geom(GeomNode *geom_node, const RenderState *state) {
2401  if (_geom->get_num_primitives() > 0) {
2402  geom_node->add_geom(_geom, state);
2403  }
2404 }
2405 
const TextGraphic * get_graphic_ptr(const string &name)
Returns a pointer to the TextGraphic with the indicated name, or NULL if there is no graphic with tha...
bool close_primitive()
Indicates that the previous n calls to add_vertex(), since the last call to close_primitive(), have fully defined a new primitive.
static const LMatrix4f & ident_mat()
Returns an identity matrix.
Definition: lmatrix.h:903
A basic node of the scene graph or data graph.
Definition: pandaNode.h:72
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
This is the base class for all three-component vectors and points.
Definition: lvecBase3.h:105
This is our own Panda specialization on the default STL map.
Definition: pmap.h:52
PN_stdfloat get_underscore_height() const
Returns the vertical height of the underscore; see set_underscore_height().
PN_stdfloat get_space_advance() const
Returns the number of units wide a space is.
Definition: textFont.I:66
static bool has_character(wchar_t character, const TextProperties &properties)
Returns true if the named character exists in the font or can be synthesized by Panda, false otherwise.
static LMatrix4f scale_mat(const LVecBase3f &scale)
Returns a matrix that applies the indicated scale in each of the three axes.
Definition: lmatrix.h:2456
void transform_vertices(const LMatrix4 &mat)
Applies the indicated transform to all of the vertices in the Geom.
Definition: geom.cxx:980
static LMatrix4f translate_mat(const LVecBase3f &trans)
Returns a matrix that applies the indicated translation.
Definition: lmatrix.h:2397
Defines a series of disconnected points.
Definition: geomPoints.h:25
void set_data3(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z)
Sets the write row to a particular 3-component value, and advances the write row. ...
This class mainly serves as a container for a largish table of the subset of the Unicode character se...
This class can be used to convert text between multiple representations, e.g.
Definition: textEncoder.h:37
TextFont * get_font() const
Returns the font currently in use, if any.
PN_stdfloat get_slant() const
Returns the factor by which the text is specified to slant to the right.
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
Definition: geomPrimitive.h:63
bool get_instance_flag() const
Returns the instance_flag.
Definition: textGraphic.I:123
PN_stdfloat get_tab_width() const
Returns the width set via set_tab_width().
bool get_small_caps() const
Returns the small_caps flag.
bool get_underscore() const
Returns the underscore flag.
This is a three-component vector distance (as opposed to a three-component point, which represents a ...
Definition: lvector3.h:100
static bool is_whitespace(wchar_t character, const TextProperties &properties)
Returns true if the indicated character represents whitespace in the font, or false if anything visib...
This is a three-component point in space (as opposed to a three-component vector, which represents a ...
Definition: lpoint3.h:99
LVecBase4 get_frame() const
Returns the frame specified for the graphic.
Definition: textGraphic.I:91
int calc_index(int r, int c) const
Computes the character index of the character at the rth row and cth column position.
PN_stdfloat get_line_height() const
Returns the number of units high each line of text is.
Definition: textFont.I:45
void add_data4(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z, PN_stdfloat w)
Sets the write row to a particular 4-component value, and advances the write row. ...
static TextPropertiesManager * get_global_ptr()
Returns the pointer to the global TextPropertiesManager object.
static Thread * get_current_thread()
Returns a pointer to the currently-executing Thread object.
Definition: thread.I:145
An encapsulation of a font; i.e.
Definition: textFont.h:36
bool get_multiline_mode() const
Returns the multline_mode flag.
static bool has_exact_character(wchar_t character, const TextProperties &properties)
Returns true if the named character exists in the font exactly as named, false otherwise.
void add_vertex(int vertex)
Adds the indicated vertex to the list of vertex indices used by the graphics primitive type...
void set_row_unsafe(int row)
Sets the start row to the indicated value, without internal checks.
virtual bool is_whitespace() const
Returns true if this glyph represents invisible whitespace, or false if it corresponds to some visibl...
Definition: textGlyph.cxx:36
PN_stdfloat get_glyph_scale() const
Returns the scale factor of each letter as specified by set_glyph_scale().
This node is placed at key points within the scene graph to indicate the roots of "models": subtrees ...
Definition: modelNode.h:34
int get_primitive_start(int n) const
Returns the element within the _vertices list at which the nth primitive starts.
bool set_wtext(const wstring &wtext)
Accepts a new text string and associated properties structure, and precomputes the wordwrapping layou...
static PN_stdfloat calc_width(wchar_t character, const TextProperties &properties)
Returns the width of a single character, according to its associated font.
virtual Geom * make_copy() const
Returns a newly-allocated Geom that is a shallow copy of this one.
Definition: geom.cxx:118
This is a 4-by-4 transform matrix.
Definition: lmatrix.h:451
string encode_wtext(const wstring &wtext) const
Encodes a wide-text string into a single-char string, according to the current encoding.
Definition: textEncoder.I:557
const GeomVertexFormat * get_format() const
Returns a pointer to the GeomVertexFormat structure that defines this data.
int get_draw_order() const
Returns the drawing order set with set_draw_order().
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
A container for geometry primitives.
Definition: geom.h:58
This class is not normally used directly by user code, but is used by the TextNode to lay out a block...
Definition: textAssembler.h:46
const LVecBase3 & get_data3()
Returns the data associated with the read row, expressed as a 3-component value, and advances the rea...
void add_data3(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z)
Sets the write row to a particular 3-component value, and advances the write row. ...
A representation of a single glyph (character) from a font.
Definition: textGlyph.h:31
PN_stdfloat get_xpos(int r, int c) const
Returns the x position of the origin of the character or graphic object at the indicated position in ...
bool set_wsubstr(const wstring &wtext, int start, int count)
Replaces the &#39;count&#39; characters from &#39;start&#39; of the current text with the indicated replacement text...
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:53
Defines a series of disconnected line segments.
Definition: geomLines.h:25
This is the base class for all three-component vectors and points.
Definition: lvecBase4.h:111
This is a two-component vector offset.
Definition: lvector2.h:91
bool calc_r_c(int &r, int &c, int n) const
Computes the row and column index of the nth character or graphic object in the text.
PandaNode * node() const
Returns the referenced node of the path.
Definition: nodePath.I:284
void clear()
Reinitializes the contents of the TextAssembler.
static LMatrix4f rotate_mat_normaxis(float angle, const LVecBase3f &axis, CoordinateSystem cs=CS_default)
Returns a matrix that rotates by the given angle in degrees counterclockwise about the indicated vect...
Definition: lmatrix.h:2440
A thread; that is, a lightweight process.
Definition: thread.h:51
NodePath get_model() const
Returns the NodePath associated with the graphic, that renders the desired image. ...
Definition: textGraphic.I:60
This defines the set of visual properties that may be assigned to the individual characters of the te...
Defines a series of disconnected triangles.
Definition: geomTriangles.h:25
const string & get_bin() const
Returns the drawing bin set with set_bin(), or empty string if no bin has been set.
PN_stdfloat get_glyph_shift() const
Returns the vertical shift of each letter as specified by set_glyph_shift().
wstring get_wtext() const
Returns a wstring that represents the contents of the text.
void set_row(int row, const LVecBase4f &v)
Replaces the indicated row of the matrix.
Definition: lmatrix.h:1187
static const Entry * look_up(wchar_t character)
Returns the Entry associated with the indicated character, if there is one.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:85
PN_stdfloat get_small_caps_scale() const
Returns the scale factor applied to lowercase letters from their uppercase equivalents, when the small_caps flag is in effect.
This defines all of the TextProperties structures that might be referenced by name from an embedded t...
wstring get_plain_wtext() const
Returns a wstring that represents the contents of the text, without any embedded properties character...
wstring get_wordwrapped_wtext() const
Returns a wstring that represents the contents of the text, with newlines inserted according to the w...
const TextGlyph * get_glyph(int character)
Gets the glyph associated with the given character code, as well as an optional scaling parameter tha...
Definition: textFont.I:91
bool has_bin() const
Returns true if an explicit drawing bin has been set via set_bin(), false otherwise.
This defines a special model that has been constructed for the purposes of embedding an arbitrary gra...
Definition: textGraphic.h:43
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:37
void add_geom(Geom *geom, const RenderState *state=RenderState::make_empty())
Adds a new Geom to the node.
Definition: geomNode.cxx:642
This object provides the functionality of both a GeomVertexReader and a GeomVertexWriter, combined together into one convenient package.
PN_stdfloat get_text_scale() const
Returns the scale factor of the text as specified by set_text_scale().
void set_rotate_mat_normaxis(float angle, const LVecBase3f &axis, CoordinateSystem cs=CS_default)
Fills mat with a matrix that rotates by the given angle in degrees counterclockwise about the indicat...
Definition: lmatrix.cxx:817
wstring get_wordwrapped_plain_wtext() const
Returns a wstring that represents the contents of the text, with newlines inserted according to the w...
LVector2 get_shadow() const
Returns the offset of the shadow as set by set_shadow().