Panda3D
 All Classes Functions Variables Enumerations
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 ////////////////////////////////////////////////////////////////////
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...
int get_primitive_start(int n) const
Returns the element within the _vertices list at which the nth primitive starts.
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
PN_stdfloat get_advance() const
Returns the distance by which the character pointer should be advanced after placing this character; ...
Definition: textGlyph.I:130
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
static LMatrix4f translate_mat(const LVecBase3f &trans)
Returns a matrix that applies the indicated translation.
Definition: lmatrix.h:2397
PN_stdfloat get_glyph_scale() const
Returns the scale factor of each letter as specified by set_glyph_scale().
wstring get_wordwrapped_plain_wtext() const
Returns a wstring that represents the contents of the text, with newlines inserted according to the w...
Defines a series of disconnected points.
Definition: geomPoints.h:25
PN_stdfloat get_glyph_shift() const
Returns the vertical shift of each letter as specified by set_glyph_shift().
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
bool get_multiline_mode() const
Returns the multline_mode flag.
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
Definition: geomPrimitive.h:63
PN_stdfloat get_slant() const
Returns the factor by which the text is specified to slant to the right.
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
PN_stdfloat get_text_scale() const
Returns the scale factor of the text as specified by set_text_scale().
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
wstring get_wordwrapped_wtext() const
Returns a wstring that represents the contents of the text, with newlines inserted according to the w...
An encapsulation of a font; i.e.
Definition: textFont.h:36
wstring get_plain_wtext() const
Returns a wstring that represents the contents of the text, without any embedded properties character...
LVecBase4 get_frame() const
Returns the frame specified for the graphic.
Definition: textGraphic.I:91
PN_stdfloat get_space_advance() const
Returns the number of units wide a space is.
Definition: textFont.I:66
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.
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 ...
This node is placed at key points within the scene graph to indicate the roots of &quot;models&quot;: subtrees ...
Definition: modelNode.h:34
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.
This is a 4-by-4 transform matrix.
Definition: lmatrix.h:451
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 GeomVertexFormat * get_format() const
Returns a pointer to the GeomVertexFormat structure that defines this data.
A representation of a single glyph (character) from a font.
Definition: textGlyph.h:31
wstring get_wtext() const
Returns a wstring that represents the contents of the text.
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...
bool get_underscore() const
Returns the underscore flag.
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
TextFont * get_font() const
Returns the font currently in use, if any.
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
This is a two-component vector offset.
Definition: lvector2.h:91
void clear()
Reinitializes the contents of the TextAssembler.
PN_stdfloat get_tab_width() const
Returns the width set via set_tab_width().
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
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
int calc_index(int r, int c) const
Computes the character index of the character at the rth row and cth column position.
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
This defines all of the TextProperties structures that might be referenced by name from an embedded t...
PN_stdfloat get_underscore_height() const
Returns the vertical height of the underscore; see set_underscore_height().
PN_stdfloat get_line_height() const
Returns the number of units high each line of text is.
Definition: textFont.I:45
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
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.
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
int get_num_primitives() const
Returns the number of GeomPrimitive objects stored within the Geom, each of which represents a number...
Definition: geom.I:110
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.