diff --git a/panda/src/text/stringDecoder.I b/panda/src/text/stringDecoder.I index bf2f6519ec..bae9754415 100644 --- a/panda/src/text/stringDecoder.I +++ b/panda/src/text/stringDecoder.I @@ -25,6 +25,7 @@ INLINE StringDecoder:: StringDecoder(const string &input) : _input(input) { _p = 0; + _eof = false; } //////////////////////////////////////////////////////////////////// @@ -36,7 +37,22 @@ StringDecoder(const string &input) : _input(input) { //////////////////////////////////////////////////////////////////// INLINE bool StringDecoder:: is_eof() { - return (_p >= _input.size()); + return _eof; +} + +//////////////////////////////////////////////////////////////////// +// Function: StringDecoder::test_eof +// Access: Protected +// Description: If the pointer is past the last character of the +// string, set the eof flag and return true. +//////////////////////////////////////////////////////////////////// +INLINE bool StringDecoder:: +test_eof() { + if (_p >= _input.size()) { + _eof = true; + return true; + } + return false; } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/text/stringDecoder.cxx b/panda/src/text/stringDecoder.cxx index f698fa7282..525d6a1c54 100644 --- a/panda/src/text/stringDecoder.cxx +++ b/panda/src/text/stringDecoder.cxx @@ -34,7 +34,7 @@ StringDecoder:: //////////////////////////////////////////////////////////////////// int StringDecoder:: get_next_character() { - if (is_eof()) { + if (test_eof()) { return -1; } return (unsigned char)_input[_p++]; @@ -71,7 +71,7 @@ The value of each individual byte indicates its UTF-8 function, as follows: //////////////////////////////////////////////////////////////////// int StringUtf8Decoder:: get_next_character() { - if (is_eof()) { + if (test_eof()) { return -1; } @@ -79,7 +79,7 @@ get_next_character() { if ((result & 0xe0) == 0xc0) { // First byte of two. unsigned int two = 0; - if (!is_eof()) { + if (!test_eof()) { two = (unsigned char)_input[_p++]; } result = ((result & 0x1f) << 6) | (two & 0x3f); @@ -88,10 +88,10 @@ get_next_character() { // First byte of three. unsigned int two = 0; unsigned int three = 0; - if (!is_eof()) { + if (!test_eof()) { two = (unsigned char)_input[_p++]; } - if (!is_eof()) { + if (!test_eof()) { three = (unsigned char)_input[_p++]; } result = ((result & 0x0f) << 12) | ((two & 0x3f) << 6) | (three & 0x3f); @@ -107,13 +107,13 @@ get_next_character() { //////////////////////////////////////////////////////////////////// int StringUnicodeDecoder:: get_next_character() { - if (is_eof()) { + if (test_eof()) { return -1; } unsigned int high = (unsigned char)_input[_p++]; unsigned int low = 0; - if (!is_eof()) { + if (!test_eof()) { low = (unsigned char)_input[_p++]; } return ((high << 8) | low); diff --git a/panda/src/text/stringDecoder.h b/panda/src/text/stringDecoder.h index bc797bda9c..69ef47b759 100644 --- a/panda/src/text/stringDecoder.h +++ b/panda/src/text/stringDecoder.h @@ -39,8 +39,11 @@ public: INLINE bool is_eof(); protected: + INLINE bool test_eof(); + string _input; size_t _p; + bool _eof; }; //////////////////////////////////////////////////////////////////// diff --git a/panda/src/text/textFont.cxx b/panda/src/text/textFont.cxx index 598ca3aaea..2558e61943 100644 --- a/panda/src/text/textFont.cxx +++ b/panda/src/text/textFont.cxx @@ -29,7 +29,7 @@ TypeHandle TextFont::_type_handle; // does not consider newlines to be whitespace. //////////////////////////////////////////////////////////////////// INLINE bool -isblank(char ch) { +isblank(int ch) { return (ch == ' ' || ch == '\t'); } @@ -126,7 +126,7 @@ wordwrap_to(const string &text, float wordwrap_width, bool needs_newline = false; while (p < text.length()) { - nassertr(!isspace(text[p]), ""); + nassertr(!isspace(text[p]), string()); // Scan the next n characters, until the end of the string or an // embedded newline character, or we exceed wordwrap_width. @@ -228,3 +228,144 @@ write(ostream &out, int indent_level) const { indent(out, indent_level) << "TextFont " << get_name() << "\n"; } + +//////////////////////////////////////////////////////////////////// +// Function: TextFont::calc_width (wide char) +// Access: Public +// Description: Returns the width of a line of text of arbitrary +// characters. The line should not include the newline +// character. +//////////////////////////////////////////////////////////////////// +float TextFont:: +calc_width(const wstring &line) { + float width = 0.0f; + + wstring::const_iterator si; + for (si = line.begin(); si != line.end(); ++si) { + width += calc_width(*si); + } + + return width; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextFont::wordwrap_to (wide char) +// Access: Public +// Description: Inserts newlines into the given text at the +// appropriate places in order to make each line be the +// longest possible line that is not longer than +// wordwrap_width (and does not break any words, if +// possible). Returns the new string. +//////////////////////////////////////////////////////////////////// +wstring TextFont:: +wordwrap_to(const wstring &text, float wordwrap_width, + bool preserve_trailing_whitespace) { + wstring output_text; + + size_t p = 0; + + // Preserve any initial whitespace and newlines. + float initial_width = 0.0f; + while (p < text.length() && isspace(text[p])) { + if (text[p] == '\n') { + initial_width = 0.0f; + } else { + initial_width += calc_width(text[p]); + } + output_text += text[p]; + p++; + } + bool needs_newline = false; + + while (p < text.length()) { + nassertr(!isspace(text[p]), wstring()); + + // Scan the next n characters, until the end of the string or an + // embedded newline character, or we exceed wordwrap_width. + + size_t q = p; + bool any_spaces = false; + bool overflow = false; + + float width = initial_width; + while (q < text.length() && text[q] != '\n') { + if (isspace(text[q])) { + any_spaces = true; + } + + width += calc_width(text[q]); + q++; + + if (width > wordwrap_width) { + // Oops, too many. + q--; + overflow = true; + break; + } + } + + if (overflow && any_spaces) { + // If we stopped because we exceeded the wordwrap width, then + // back up to the end of the last complete word. + while (q > p && !isspace(text[q])) { + q--; + } + } + + // Skip additional whitespace between the lines. + size_t next_start = q; + while (next_start < text.length() && isblank(text[next_start])) { + next_start++; + } + + // Trim off any more blanks on the end. + while (q > p && isspace(text[q - 1])) { + q--; + } + + if (next_start == p) { + // No characters got in at all. This could only happen if the + // wordwrap width is narrower than a single character, or if we + // have a substantial number of leading spaces in a line. + q++; + next_start++; + while (next_start < text.length() && isblank(text[next_start])) { + next_start++; + } + } + + if (needs_newline) { + output_text += '\n'; + } + needs_newline = true; + + if (preserve_trailing_whitespace) { + q = next_start; + } + output_text += text.substr(p, q - p); + + // Now prepare to wrap the next line. + + if (next_start < text.length() && text[next_start] == '\n') { + // Preserve a single embedded newline. + output_text += '\n'; + next_start++; + needs_newline = false; + } + p = next_start; + + // Preserve any initial whitespace and newlines. + initial_width = 0.0f; + while (p < text.length() && isspace(text[p])) { + if (text[p] == '\n') { + initial_width = 0.0f; + } else { + initial_width += calc_width(text[p]); + } + output_text += text[p]; + p++; + } + } + + return output_text; +} diff --git a/panda/src/text/textFont.h b/panda/src/text/textFont.h index 75a1e50e36..6b6b403d1b 100644 --- a/panda/src/text/textFont.h +++ b/panda/src/text/textFont.h @@ -29,6 +29,9 @@ class Node; class TextGlyph; +// For some reason, gcc's doesn't define this. +typedef basic_string wstring; + //////////////////////////////////////////////////////////////////// // Class : TextFont // Description : An encapsulation of a font; i.e. a set of glyphs that @@ -57,6 +60,10 @@ PUBLISHED: virtual void write(ostream &out, int indent_level) const; public: + float calc_width(const wstring &line); + wstring wordwrap_to(const wstring &text, float wordwrap_width, + bool preserve_trailing_whitespace); + INLINE float get_space_advance() const; virtual bool get_glyph(int character, const TextGlyph *&glyph, float &glyph_scale)=0; diff --git a/panda/src/text/textNode.I b/panda/src/text/textNode.I index c61b5928b7..0d41bcc7ac 100644 --- a/panda/src/text/textNode.I +++ b/panda/src/text/textNode.I @@ -118,13 +118,13 @@ get_font() const { // (i.e. ASCII). Other encodings are possible to take // advantage of character sets with more than 256 // characters. +// +// This affects only future calls to set_text(); it does +// not change text that was set previously. //////////////////////////////////////////////////////////////////// INLINE void TextNode:: set_encoding(TextNode::Encoding encoding) { - if (_encoding != encoding) { - _encoding = encoding; - rebuild(true); - } + _encoding = encoding; } //////////////////////////////////////////////////////////////////// @@ -146,16 +146,16 @@ get_encoding() const { // expanded to special characters according to a subset // of the HTML conventions. When this is false, // ampersands are treated as ordinary characters. +// +// This affects only future calls to set_text(); it does +// not change text that was set previously. //////////////////////////////////////////////////////////////////// INLINE void TextNode:: set_expand_amp(bool expand_amp) { - bool current_expand_amp = get_expand_amp(); - if (expand_amp && !current_expand_amp) { + if (expand_amp) { _flags |= F_expand_amp; - rebuild(true); } else { _flags &= ~F_expand_amp; - rebuild(true); } } @@ -1022,18 +1022,6 @@ get_coordinate_system() const { return _coordinate_system; } -//////////////////////////////////////////////////////////////////// -// Function: TextNode::set_text -// Access: Published -// Description: Changes the text that is displayed under the -// TextNode. -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -set_text(const string &text) { - _text = text; - rebuild(true); -} - //////////////////////////////////////////////////////////////////// // Function: TextNode::clear_text // Access: Published diff --git a/panda/src/text/textNode.cxx b/panda/src/text/textNode.cxx index c35ac36bc7..da67a35787 100644 --- a/panda/src/text/textNode.cxx +++ b/panda/src/text/textNode.cxx @@ -78,7 +78,7 @@ TextNode(const string &name) : NamedNode(name) { _num_rows = 0; _freeze_level = 0; - _needs_rebuild = false; + _needs_rebuild = false; } //////////////////////////////////////////////////////////////////// @@ -90,6 +90,41 @@ TextNode:: ~TextNode() { } +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_text +// Access: Published +// Description: Changes the text that is displayed under the +// TextNode. +//////////////////////////////////////////////////////////////////// +void TextNode:: +set_text(const string &text) { + _text = text; + switch (_encoding) { + case E_utf8: + { + StringUtf8Decoder decoder(_text); + decode_wtext(decoder); + } + break; + + case E_unicode: + { + StringUnicodeDecoder decoder(_text); + decode_wtext(decoder); + } + break; + + case E_iso8859: + default: + { + StringDecoder decoder(_text); + decode_wtext(decoder); + } + }; + + rebuild(true); +} + //////////////////////////////////////////////////////////////////// // Function: TextNode::write @@ -242,21 +277,18 @@ generate() { root_arc->set_transition(new TransformTransition(mat)); - string text = _text; + wstring wtext = _wtext; if (has_wordwrap()) { - text = wordwrap_to(text, _wordwrap_width, false); + wtext = _font->wordwrap_to(wtext, _wordwrap_width, false); } - StringDecoder *decoder = make_decoder(text); - // Assemble the text. LVector2f ul, lr; int num_rows = 0; - PT_Node text_root = assemble_text(decoder, ul, lr, num_rows); + PT_Node text_root = assemble_text(wtext.begin(), wtext.end(), ul, lr, num_rows); RenderRelation *text_arc = new RenderRelation(sub_root, text_root, _draw_order + 2); - delete decoder; if (has_text_color()) { text_arc->set_transition(new ColorTransition(_text_color)); @@ -362,326 +394,27 @@ generate() { } //////////////////////////////////////////////////////////////////// -// Function: TextNode::do_rebuild +// Function: TextNode::decode_wtext // Access: Private -// Description: Removes any existing children of the TextNode, and -// adds the newly generated text instead. +// Description: Decodes the eight-bit stream from the indicated +// decoder, storing the decoded unicode characters in +// _wtext. //////////////////////////////////////////////////////////////////// void TextNode:: -do_rebuild() { - _needs_rebuild = false; - - int num_children = get_num_children(RenderRelation::get_class_type()); - while (num_children > 0) { - NodeRelation *arc = get_child(RenderRelation::get_class_type(), 0); - remove_arc(arc); - num_children--; - } - - PT_Node new_text = generate(); - if (new_text != (Node *)NULL) { - new RenderRelation(this, new_text); - - // And we flatten one more time, to remove the new node itself if - // possible (it might be an unneeded node above multiple - // children). This flatten operation should be fairly - // lightweight; it's already pretty flat. - SceneGraphReducer gr(RenderRelation::get_class_type()); - gr.flatten(this, false); - } -} - - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::do_measure -// Access: Private -// Description: Can be called in lieu of do_rebuild() to measure the -// text and set up the bounding boxes properly without -// actually assembling it. -//////////////////////////////////////////////////////////////////// -void TextNode:: -do_measure() { - _ul2d.set(0.0f, 0.0f); - _lr2d.set(0.0f, 0.0f); - _ul3d.set(0.0f, 0.0f, 0.0f); - _lr3d.set(0.0f, 0.0f, 0.0f); - _num_rows = 0; - - if (_text.empty() || _font.is_null()) { - return; - } - - string text = _text; - if (has_wordwrap()) { - text = wordwrap_to(text, _wordwrap_width, false); - } - - StringDecoder *decoder = make_decoder(text); - - LVector2f ul, lr; - int num_rows = 0; - measure_text(decoder, ul, lr, num_rows); - - delete decoder; - - _num_rows = num_rows; - _ul2d = ul; - _lr2d = lr; - _ul3d.set(ul[0], 0.0f, ul[1]); - _lr3d.set(lr[0], 0.0f, lr[1]); - - _ul3d = _ul3d * _transform; - _lr3d = _lr3d * _transform; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::make_decoder -// Access: Private -// Description: Creates and returns a new StringDecoder suitable for -// decoding the given input text, and corresponding to -// our input encoding type. The decoder must be freed -// via delete later. -//////////////////////////////////////////////////////////////////// -StringDecoder *TextNode:: -make_decoder(const string &text) { - switch (_encoding) { - case E_utf8: - return new StringUtf8Decoder(text); - - case E_unicode: - return new StringUnicodeDecoder(text); - - case E_iso8859: - default: - return new StringDecoder(text); - }; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::assemble_row -// Access: Private -// Description: Assembles the letters in the source string, up until -// the first newline or the end of the string into a -// single row (which is parented to 'dest'), and returns -// the length of the row. The source pointer is moved -// to the terminating character. -//////////////////////////////////////////////////////////////////// -float TextNode:: -assemble_row(StringDecoder *decoder, Node *dest) { - nassertr(_font != (TextFont *)NULL, 0.0f); - - float xpos = 0.0f; +decode_wtext(StringDecoder &decoder) { + _wtext.erase(_wtext.begin(), _wtext.end()); bool expand_amp = get_expand_amp(); - if (decoder->is_eof()) { - return xpos; - } - int character = decoder->get_next_character(); - while (character != '\n') { + + wchar_t character = decoder.get_next_character(); + while (!decoder.is_eof()) { if (character == '&' && expand_amp) { // An ampersand in expand_amp mode is treated as an escape // character. character = expand_amp_sequence(decoder); } - - if (character == ' ') { - // A space is a special case. - xpos += _font->get_space_advance(); - - } else { - // A printable character. - - const TextGlyph *glyph; - float glyph_scale; - if (!_font->get_glyph(character, glyph, glyph_scale)) { - text_cat.warning() - << "No definition in " << _font->get_name() - << " for character " << character; - if (character < 128 && isprint(character)) { - text_cat.warning(false) - << " ('" << (char)character << "')"; - } - text_cat.warning(false) - << "\n"; - } - - if (glyph != (TextGlyph *)NULL) { - PT(Geom) char_geom = glyph->get_geom(); - const AllTransitionsWrapper &trans = glyph->get_trans(); - - if (char_geom != (Geom *)NULL) { - LMatrix4f mat = LMatrix4f::scale_mat(glyph_scale); - mat.set_row(3, LVector3f(xpos, 0.0f, 0.0f)); - - string ch(1, (char)character); - GeomNode *geode = new GeomNode(ch); - geode->add_geom(char_geom); - RenderRelation* rel = new RenderRelation(dest, geode); - rel->set_transition(new TransformTransition(mat)); - trans.store_to(rel); - } - - xpos += glyph->get_advance() * glyph_scale; - } - } - if (decoder->is_eof()) { - return xpos; - } - character = decoder->get_next_character(); + _wtext += character; + character = decoder.get_next_character(); } - - return xpos; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::assemble_text -// Access: Private -// Description: Constructs a hierarchy of nodes that contain the -// geometry representing the indicated source text, and -// returns it. Also sets the ul, lr corners. -//////////////////////////////////////////////////////////////////// -Node *TextNode:: -assemble_text(StringDecoder *decoder, LVector2f &ul, LVector2f &lr, - int &num_rows) { - nassertr(_font != (TextFont *)NULL, (Node *)NULL); - float line_height = get_line_height(); - - ul.set(0.0f, 0.8f * line_height); - lr.set(0.0f, 0.0f); - - // Make a group node to hold our formatted text geometry. - Node *root_node = new NamedNode("text"); - - float posy = 0.0f; - int row_index = 0; - while (!decoder->is_eof()) { - char numstr[20]; - sprintf(numstr, "row%d", row_index); - nassertr(strlen(numstr) < 20, root_node); - - Node *row = new NamedNode(numstr); - float row_width = assemble_row(decoder, row); - - LMatrix4f mat = LMatrix4f::ident_mat(); - if (_align == A_left) { - mat.set_row(3, LVector3f(0.0f, 0.0f, posy)); - lr[0] = max(lr[0], row_width); - - } else if (_align == A_right) { - mat.set_row(3, LVector3f(-row_width, 0.0f, posy)); - ul[0] = min(ul[0], -row_width); - - } else { - float half_row_width=0.5f*row_width; - mat.set_row(3, LVector3f(-half_row_width, 0.0f, posy)); - lr[0] = max(lr[0], half_row_width); - ul[0] = min(ul[0], -half_row_width); - } - - // Also apply whatever slant the user has asked for to the entire - // row. This is an X shear. - if (_slant != 0.0f) { - LMatrix4f shear(1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - _slant, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f); - mat = shear * mat; - } - - RenderRelation *arc = new RenderRelation(root_node, row); - arc->set_transition(new TransformTransition(mat)); - - posy -= line_height; - num_rows++; - } - - lr[1] = posy + 0.8f * line_height; - - return root_node; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::measure_row -// Access: Private -// Description: Returns the length of the row in units, as it would -// be if it were assembled, without actually assembling -// it. -//////////////////////////////////////////////////////////////////// -float TextNode:: -measure_row(StringDecoder *decoder) { - nassertr(_font != (TextFont *)NULL, 0.0f); - - float xpos = 0.0f; - bool expand_amp = get_expand_amp(); - if (decoder->is_eof()) { - return xpos; - } - int character = decoder->get_next_character(); - while (character != '\n') { - if (character == '&' && expand_amp) { - // An ampersand in expand_amp mode is treated as an escape - // character. - character = expand_amp_sequence(decoder); - } - - if (character == ' ') { - // A space is a special case. - xpos += 0.25f; - - } else { - // A printable character. - - const TextGlyph *glyph; - float glyph_scale; - if (_font->get_glyph(character, glyph, glyph_scale)) { - xpos += glyph->get_advance() * glyph_scale; - } - } - if (decoder->is_eof()) { - return xpos; - } - character = decoder->get_next_character(); - } - - return xpos; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::measure_text -// Access: Private -// Description: Sets the ul, lr corners to fit the text, without -// actually assembling it. -//////////////////////////////////////////////////////////////////// -void TextNode:: -measure_text(StringDecoder *decoder, LVector2f &ul, LVector2f &lr, - int &num_rows) { - nassertv(_font != (TextFont *)NULL); - float line_height = get_line_height(); - - ul.set(0.0f, 0.8f * line_height); - lr.set(0.0f, 0.0f); - - float posy = 0.0f; - while (!decoder->is_eof()) { - float row_width = measure_row(decoder); - - if (_align == A_left) { - lr[0] = max(lr[0], row_width); - - } else if (_align == A_right) { - ul[0] = min(ul[0], -row_width); - - } else { - float half_row_width=0.5f*row_width; - - lr[0] = max(lr[0], half_row_width); - ul[0] = min(ul[0], -half_row_width); - } - - posy -= line_height; - num_rows++; - } - - lr[1] = posy + 0.8f * line_height; } //////////////////////////////////////////////////////////////////// @@ -694,17 +427,17 @@ measure_text(StringDecoder *decoder, LVector2f &ul, LVector2f &lr, // character, do the expansion and return the character. //////////////////////////////////////////////////////////////////// int TextNode:: -expand_amp_sequence(StringDecoder *decoder) { +expand_amp_sequence(StringDecoder &decoder) { int result = 0; - int character = decoder->get_next_character(); - if (character == '#') { + int character = decoder.get_next_character(); + if (!decoder.is_eof() && character == '#') { // An explicit numeric sequence: &#nnn; result = 0; - character = decoder->get_next_character(); - while (!decoder->is_eof() && character < 128 && isdigit(character)) { + character = decoder.get_next_character(); + while (!decoder.is_eof() && character < 128 && isdigit(character)) { result = (result * 10) + (character - '0'); - character = decoder->get_next_character(); + character = decoder.get_next_character(); } if (character != ';') { // Invalid sequence. @@ -717,9 +450,9 @@ expand_amp_sequence(StringDecoder *decoder) { string sequence; // Some non-numeric sequence. - while (!decoder->is_eof() && character < 128 && isalpha(character)) { + while (!decoder.is_eof() && character < 128 && isalpha(character)) { sequence += character; - character = decoder->get_next_character(); + character = decoder.get_next_character(); } if (character != ';') { // Invalid sequence. @@ -773,6 +506,289 @@ expand_amp_sequence(StringDecoder *decoder) { return 0; } + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::do_rebuild +// Access: Private +// Description: Removes any existing children of the TextNode, and +// adds the newly generated text instead. +//////////////////////////////////////////////////////////////////// +void TextNode:: +do_rebuild() { + _needs_rebuild = false; + + int num_children = get_num_children(RenderRelation::get_class_type()); + while (num_children > 0) { + NodeRelation *arc = get_child(RenderRelation::get_class_type(), 0); + remove_arc(arc); + num_children--; + } + + PT_Node new_text = generate(); + if (new_text != (Node *)NULL) { + new RenderRelation(this, new_text); + + // And we flatten one more time, to remove the new node itself if + // possible (it might be an unneeded node above multiple + // children). This flatten operation should be fairly + // lightweight; it's already pretty flat. + SceneGraphReducer gr(RenderRelation::get_class_type()); + gr.flatten(this, false); + } +} + + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::do_measure +// Access: Private +// Description: Can be called in lieu of do_rebuild() to measure the +// text and set up the bounding boxes properly without +// actually assembling it. +//////////////////////////////////////////////////////////////////// +void TextNode:: +do_measure() { + _ul2d.set(0.0f, 0.0f); + _lr2d.set(0.0f, 0.0f); + _ul3d.set(0.0f, 0.0f, 0.0f); + _lr3d.set(0.0f, 0.0f, 0.0f); + _num_rows = 0; + + if (_text.empty() || _font.is_null()) { + return; + } + + wstring wtext = _wtext; + if (has_wordwrap()) { + wtext = _font->wordwrap_to(wtext, _wordwrap_width, false); + } + + LVector2f ul, lr; + int num_rows = 0; + measure_text(wtext.begin(), wtext.end(), ul, lr, num_rows); + + _num_rows = num_rows; + _ul2d = ul; + _lr2d = lr; + _ul3d.set(ul[0], 0.0f, ul[1]); + _lr3d.set(lr[0], 0.0f, lr[1]); + + _ul3d = _ul3d * _transform; + _lr3d = _lr3d * _transform; +} + +#ifndef CPPPARSER // interrogate has a bit of trouble with wstring. + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::assemble_row +// Access: Private +// Description: Assembles the letters in the source string, up until +// the first newline or the end of the string into a +// single row (which is parented to 'dest'), and returns +// the length of the row. The source pointer is moved +// to the terminating character. +//////////////////////////////////////////////////////////////////// +float TextNode:: +assemble_row(wstring::iterator &si, const wstring::iterator &send, + Node *dest) { + nassertr(_font != (TextFont *)NULL, 0.0f); + + float xpos = 0.0f; + while (si != send && (*si) != '\n') { + wchar_t character = *si; + + if (character == ' ') { + // A space is a special case. + xpos += _font->get_space_advance(); + + } else { + // A printable character. + + const TextGlyph *glyph; + float glyph_scale; + if (!_font->get_glyph(character, glyph, glyph_scale)) { + text_cat.warning() + << "No definition in " << _font->get_name() + << " for character " << character; + if (character < 128 && isprint(character)) { + text_cat.warning(false) + << " ('" << (char)character << "')"; + } + text_cat.warning(false) + << "\n"; + } + + if (glyph != (TextGlyph *)NULL) { + PT(Geom) char_geom = glyph->get_geom(); + const AllTransitionsWrapper &trans = glyph->get_trans(); + + if (char_geom != (Geom *)NULL) { + LMatrix4f mat = LMatrix4f::scale_mat(glyph_scale); + mat.set_row(3, LVector3f(xpos, 0.0f, 0.0f)); + + string ch(1, (char)character); + GeomNode *geode = new GeomNode(ch); + geode->add_geom(char_geom); + RenderRelation* rel = new RenderRelation(dest, geode); + rel->set_transition(new TransformTransition(mat)); + trans.store_to(rel); + } + + xpos += glyph->get_advance() * glyph_scale; + } + } + ++si; + } + + return xpos; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::assemble_text +// Access: Private +// Description: Constructs a hierarchy of nodes that contain the +// geometry representing the indicated source text, and +// returns it. Also sets the ul, lr corners. +//////////////////////////////////////////////////////////////////// +Node *TextNode:: +assemble_text(wstring::iterator si, const wstring::iterator &send, + LVector2f &ul, LVector2f &lr, int &num_rows) { + nassertr(_font != (TextFont *)NULL, (Node *)NULL); + float line_height = get_line_height(); + + ul.set(0.0f, 0.8f * line_height); + lr.set(0.0f, 0.0f); + + // Make a group node to hold our formatted text geometry. + Node *root_node = new NamedNode("text"); + + float posy = 0.0f; + int row_index = 0; + while (si != send) { + char numstr[20]; + sprintf(numstr, "row%d", row_index); + nassertr(strlen(numstr) < 20, root_node); + + Node *row = new NamedNode(numstr); + float row_width = assemble_row(si, send, row); + if (si != send) { + // Skip past the newline. + ++si; + } + + LMatrix4f mat = LMatrix4f::ident_mat(); + if (_align == A_left) { + mat.set_row(3, LVector3f(0.0f, 0.0f, posy)); + lr[0] = max(lr[0], row_width); + + } else if (_align == A_right) { + mat.set_row(3, LVector3f(-row_width, 0.0f, posy)); + ul[0] = min(ul[0], -row_width); + + } else { + float half_row_width=0.5f*row_width; + mat.set_row(3, LVector3f(-half_row_width, 0.0f, posy)); + lr[0] = max(lr[0], half_row_width); + ul[0] = min(ul[0], -half_row_width); + } + + // Also apply whatever slant the user has asked for to the entire + // row. This is an X shear. + if (_slant != 0.0f) { + LMatrix4f shear(1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + _slant, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + mat = shear * mat; + } + + RenderRelation *arc = new RenderRelation(root_node, row); + arc->set_transition(new TransformTransition(mat)); + + posy -= line_height; + num_rows++; + } + + lr[1] = posy + 0.8f * line_height; + + return root_node; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::measure_row +// Access: Private +// Description: Returns the length of the row in units, as it would +// be if it were assembled, without actually assembling +// it. +//////////////////////////////////////////////////////////////////// +float TextNode:: +measure_row(wstring::iterator &si, const wstring::iterator &send) { + float xpos = 0.0f; + while (si != send && *si != '\n') { + wchar_t character = *si; + + if (character == ' ') { + // A space is a special case. + xpos += 0.25f; + + } else { + // A printable character. + + const TextGlyph *glyph; + float glyph_scale; + if (_font->get_glyph(character, glyph, glyph_scale)) { + xpos += glyph->get_advance() * glyph_scale; + } + } + ++si; + } + + return xpos; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::measure_text +// Access: Private +// Description: Sets the ul, lr corners to fit the text, without +// actually assembling it. +//////////////////////////////////////////////////////////////////// +void TextNode:: +measure_text(wstring::iterator si, const wstring::iterator &send, + LVector2f &ul, LVector2f &lr, int &num_rows) { + nassertv(_font != (TextFont *)NULL); + float line_height = get_line_height(); + + ul.set(0.0f, 0.8f * line_height); + lr.set(0.0f, 0.0f); + + float posy = 0.0f; + while (si != send) { + float row_width = measure_row(si, send); + if (si != send) { + // Skip past the newline. + ++si; + } + + if (_align == A_left) { + lr[0] = max(lr[0], row_width); + + } else if (_align == A_right) { + ul[0] = min(ul[0], -row_width); + + } else { + float half_row_width=0.5f*row_width; + + lr[0] = max(lr[0], half_row_width); + ul[0] = min(ul[0], -half_row_width); + } + + posy -= line_height; + num_rows++; + } + + lr[1] = posy + 0.8f * line_height; +} +#endif // CPPPARSER + //////////////////////////////////////////////////////////////////// // Function: TextNode::make_frame // Access: Private diff --git a/panda/src/text/textNode.h b/panda/src/text/textNode.h index d7b3148ba5..333486014d 100644 --- a/panda/src/text/textNode.h +++ b/panda/src/text/textNode.h @@ -186,7 +186,7 @@ PUBLISHED: INLINE void set_coordinate_system(CoordinateSystem cs); INLINE CoordinateSystem get_coordinate_system() const; - INLINE void set_text(const string &str); + void set_text(const string &str); INLINE void clear_text(); INLINE bool has_text() const; INLINE string get_text() const; @@ -218,19 +218,21 @@ PUBLISHED: PT_Node generate(); private: + void decode_wtext(StringDecoder &decoder); + int expand_amp_sequence(StringDecoder &decoder); + void do_rebuild(); void do_measure(); - StringDecoder *make_decoder(const string &text); - - float assemble_row(StringDecoder *decoder, Node *dest); - Node *assemble_text(StringDecoder *decoder, LVector2f &ul, LVector2f &lr, - int &num_rows); - float measure_row(StringDecoder *decoder); - void measure_text(StringDecoder *decoder, LVector2f &ul, LVector2f &lr, - int &num_rows); - - int expand_amp_sequence(StringDecoder *decoder); +#ifndef CPPPARSER // interrogate has a bit of trouble with wstring. + float assemble_row(wstring::iterator &si, const wstring::iterator &send, + Node *dest); + Node *assemble_text(wstring::iterator si, const wstring::iterator &send, + LVector2f &ul, LVector2f &lr, int &num_rows); + float measure_row(wstring::iterator &si, const wstring::iterator &send); + void measure_text(wstring::iterator si, const wstring::iterator &send, + LVector2f &ul, LVector2f &lr, int &num_rows); +#endif // CPPPARSER Node *make_frame(); Node *make_card(); @@ -280,6 +282,7 @@ private: CoordinateSystem _coordinate_system; string _text; + wstring _wtext; LPoint2f _ul2d, _lr2d; LPoint3f _ul3d, _lr3d;