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"
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
43using std::max;
44using std::min;
45using std::move;
46using std::wstring;
47
48// This is the factor by which CT_small scales the character down.
49static const PN_stdfloat small_accent_scale = 0.6f;
50
51// This is the factor by which CT_tiny scales the character down.
52static 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.
55static const PN_stdfloat squash_accent_scale_x = 0.8f;
56static 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.
60static const PN_stdfloat small_squash_accent_scale_x = 0.6f;
61static 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.
65static 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 */
72static INLINE bool
73isspacew(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 */
81static INLINE bool
82isbreakpoint(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 */
92TextAssembler::
93TextAssembler(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 */
107TextAssembler::
108TextAssembler(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 */
126void TextAssembler::
127operator = (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 */
144TextAssembler::
145~TextAssembler() {
146}
147
148/**
149 * Reinitializes the contents of the TextAssembler.
150 */
152clear() {
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 */
170set_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 */
205set_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 */
236get_plain_wtext() const {
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 */
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 */
295get_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 */
331get_wordwrapped_wtext() const {
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 */
374calc_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 */
447calc_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 */
486get_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 */
509PT(PandaNode) TextAssembler::
510assemble_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 */
626calc_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 */
663calc_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 */
681has_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 */
705has_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 */
740is_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 */
763void TextAssembler::
764scan_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 */
870bool TextAssembler::
871wordwrap_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 */
1112PN_stdfloat TextAssembler::
1113calc_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 */
1132void TextAssembler::
1133generate_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
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 */
1305void TextAssembler::
1306assemble_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 */
1403void TextAssembler::
1404assemble_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 */
1687void TextAssembler::
1688shape_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 */
1771void TextAssembler::
1772draw_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 */
1825void TextAssembler::
1826get_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((char32_t)character);
1847 if (map_entry != nullptr) {
1848 if (properties->get_small_caps() &&
1849 map_entry->_toupper_character != (char32_t)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 != (char32_t)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 */
1914void TextAssembler::
1915tack_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 */
2067bool TextAssembler::
2068tack_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 */
2322void TextAssembler::ComputedProperties::
2323append_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 */
2358void TextAssembler::GlyphPlacement::
2359assign_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 */
2377void TextAssembler::GlyphPlacement::
2378assign_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 */
2448void TextAssembler::GlyphPlacement::
2449assign_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 */
2469void TextAssembler::GlyphPlacement::
2470copy_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 */
2493TextAssembler::GeomCollector::
2494GeomCollector(const GeomVertexFormat *format) :
2495 _vdata(new GeomVertexData("merged_geom", format, Geom::UH_static)),
2496 _geom(new GeomTextGlyph(_vdata))
2497{
2498}
2499
2500/**
2501 *
2502 */
2503TextAssembler::GeomCollector::
2504GeomCollector(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 */
2515GeomPrimitive *TextAssembler::GeomCollector::
2516get_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 */
2547int TextAssembler::GeomCollector::
2548append_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 */
2565void TextAssembler::GeomCollector::
2566append_geom(GeomNode *geom_node, const RenderState *state) {
2567 if (_geom->get_num_primitives() > 0) {
2568 geom_node->add_geom(_geom, state);
2569 }
2570}
Defines a series of disconnected line segments.
Definition: geomLines.h:23
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
void add_geom(Geom *geom, const RenderState *state=RenderState::make_empty())
Adds a new Geom to the node.
Definition: geomNode.cxx:594
Defines a series of disconnected points.
Definition: geomPoints.h:23
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
Definition: geomPrimitive.h:56
bool close_primitive()
Indicates that the previous n calls to add_vertex(), since the last call to close_primitive(),...
int get_primitive_start(int n) const
Returns the element within the _vertices list at which the nth primitive starts.
void add_vertex(int vertex)
Adds the indicated vertex to the list of vertex indices used by the graphics primitive type.
This is a specialization on Geom for containing a primitive intended to represent a TextGlyph.
Definition: geomTextGlyph.h:27
Defines a series of disconnected triangles.
Definition: geomTriangles.h:23
This data object is returned by GeomVertexArrayData::get_handle() or modify_handle().
NumericType get_numeric_type() const
Returns the token representing the numeric type of the data storage.
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
get_format
Returns a pointer to the GeomVertexFormat structure that defines this data.
This class defines the physical layout of the vertex data stored within a Geom.
static const GeomVertexFormat * get_v3t2()
Returns a standard vertex format with a 2-component texture coordinate pair and a 3-component vertex ...
static const GeomVertexFormat * get_v3cp()
Returns a standard vertex format with a packed color and a 3-component vertex position.
get_array
Returns the description of the nth array used by the format.
const GeomVertexColumn * get_vertex_column() const
Returns the column definition of the "vertex" column, or NULL if there is no such column.
get_column
Returns the ith column of the specification, across all arrays.
This object provides the functionality of both a GeomVertexReader and a GeomVertexWriter,...
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
A container for geometry primitives.
Definition: geom.h:54
This node is placed at key points within the scene graph to indicate the roots of "models": subtrees ...
Definition: modelNode.h:31
A basic node of the scene graph or data graph.
Definition: pandaNode.h:65
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:47
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
bool set_wtext(const std::wstring &wtext)
Accepts a new text string and associated properties structure, and precomputes the wordwrapping layou...
get_multiline_mode
Returns the multline_mode flag.
void clear()
Reinitializes the contents of the TextAssembler.
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.
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...
std::wstring get_wordwrapped_wtext() const
Returns a wstring that represents the contents of the text, with newlines inserted according to the w...
std::wstring get_wtext() const
Returns a wstring that represents the contents of the text.
std::wstring get_wordwrapped_plain_wtext() const
Returns a wstring that represents the contents of the text, with newlines inserted according to the w...
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 ...
int calc_index(int r, int c) const
Computes the character index of the character at the rth row and cth column position.
static PN_stdfloat calc_width(wchar_t character, const TextProperties &properties)
Returns the width of a single character, according to its associated font.
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.
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,...
std::wstring get_plain_wtext() const
Returns a wstring that represents the contents of the text, without any embedded properties character...
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.
This class can be used to convert text between multiple representations, e.g.
Definition: textEncoder.h:33
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
An encapsulation of a font; i.e.
Definition: textFont.h:32
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_line_height
Returns the number of units high each line of text is.
Definition: textFont.h:69
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
get_space_advance
Returns the number of units wide a space is.
Definition: textFont.h:73
A representation of a single glyph (character) from a font.
Definition: textGlyph.h:28
This defines a special model that has been constructed for the purposes of embedding an arbitrary gra...
Definition: textGraphic.h:37
get_frame
Returns the frame specified for the graphic.
Definition: textGraphic.h:50
get_model
Returns the NodePath associated with the graphic, that renders the desired image.
Definition: textGraphic.h:45
get_instance_flag
Returns the instance_flag.
Definition: textGraphic.h:54
This defines all of the TextProperties structures that might be referenced by name from an embedded t...
static TextPropertiesManager * get_global_ptr()
Returns the pointer to the global TextPropertiesManager object.
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...
This defines the set of visual properties that may be assigned to the individual characters of the te...
const RenderState * get_shadow_state() const
Returns a RenderState object suitable for rendering the shadow of this text with these properties.
get_slant
Returns the factor by which the text is specified to slant to the right.
get_underscore_height
Returns the vertical height of the underscore; see set_underscore_height().
get_small_caps
Returns the small_caps flag.
get_glyph_shift
Returns the vertical shift of each letter as specified by set_glyph_shift().
const RenderState * get_text_state() const
Returns a RenderState object suitable for rendering text with these properties.
get_glyph_scale
Returns the scale factor of each letter as specified by set_glyph_scale().
get_shadow
Returns the offset of the shadow as set by set_shadow().
get_font
Returns the font currently in use, if any.
get_small_caps_scale
Returns the scale factor applied to lowercase letters from their uppercase equivalents,...
get_underscore
Returns the underscore flag.
get_direction
Returns the direction of the text as specified by set_direction().
get_tab_width
Returns the width set via set_tab_width().
get_text_scale
Returns the scale factor of the text as specified by set_text_scale().
A thread; that is, a lightweight process.
Definition: thread.h:46
get_current_thread
Returns a pointer to the currently-executing Thread object.
Definition: thread.h:109
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:28
This class mainly serves as a container for a largish table of the subset of the Unicode character se...
static wchar_t get_combining_accent(AccentType accent)
Returns the unicode code point for the combining character corresponding with the given accent type,...
static const Entry * look_up(char32_t character)
Returns the Entry associated with the indicated character, if there is one.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.