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