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