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