diff --git a/panda/src/pgui/pgEntry.I b/panda/src/pgui/pgEntry.I index 34655b3125..9e742d2123 100644 --- a/panda/src/pgui/pgEntry.I +++ b/panda/src/pgui/pgEntry.I @@ -32,6 +32,23 @@ set_text(const string &text) { set_wtext(text_node->decode_text(text)); } +//////////////////////////////////////////////////////////////////// +// Function: PGEntry::get_plain_text +// Access: Published +// Description: Returns the text currently displayed within the +// entry, without any embedded properties characters. +// +// This uses the Unicode encoding currently specified +// for the "focus" TextNode; therefore, the TextNode +// must exist before calling get_text(). +//////////////////////////////////////////////////////////////////// +INLINE string PGEntry:: +get_plain_text() const { + TextNode *text_node = get_text_def(S_focus); + nassertr(text_node != (TextNode *)NULL, string()); + return text_node->encode_wtext(get_plain_wtext()); +} + //////////////////////////////////////////////////////////////////// // Function: PGEntry::get_text // Access: Published @@ -47,6 +64,61 @@ get_text() const { return text_node->encode_wtext(get_wtext()); } +//////////////////////////////////////////////////////////////////// +// Function: PGEntry::get_num_characters +// Access: Published +// Description: Returns the number of characters of text in the +// entry. This is the actual number of visible +// characters, not counting implicit newlines due to +// wordwrapping, or formatted characters for text +// properties changes. If there is an embedded +// TextGraphic object, it counts as one character. +// +// This is also the length of the string returned by +// get_plain_text(). +//////////////////////////////////////////////////////////////////// +INLINE int PGEntry:: +get_num_characters() const { + return _text.get_num_characters(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PGEntry::get_character +// Access: Published +// Description: Returns the character at the indicated position in +// the entry. If the object at this position is a +// graphic object instead of a character, returns 0. +//////////////////////////////////////////////////////////////////// +INLINE wchar_t PGEntry:: +get_character(int n) const { + return _text.get_character(n); +} + +//////////////////////////////////////////////////////////////////// +// Function: PGEntry::get_graphic +// Access: Published +// Description: Returns the graphic object at the indicated position +// in the pre-wordwrapped string. If the object at this +// position is a character instead of a graphic object, +// returns NULL. +//////////////////////////////////////////////////////////////////// +INLINE const TextGraphic *PGEntry:: +get_graphic(int n) const { + return _text.get_graphic(n); +} + +//////////////////////////////////////////////////////////////////// +// Function: PGEntry::get_properties +// Access: Published +// Description: Returns the TextProperties in effect for the object +// at the indicated position in the pre-wordwrapped +// string. +//////////////////////////////////////////////////////////////////// +INLINE const TextProperties &PGEntry:: +get_properties(int n) const { + return _text.get_properties(n); +} + //////////////////////////////////////////////////////////////////// // Function: PGEntry::set_cursor_position // Access: Published @@ -431,11 +503,25 @@ get_erase_event() const { //////////////////////////////////////////////////////////////////// INLINE void PGEntry:: set_wtext(const wstring &wtext) { - _wtext = wtext; + _text.set_wtext(wtext); + if (_obscure_mode) { + _obscure_text.set_wtext(wstring(_text.get_num_characters(), '*')); + } _text_geom_stale = true; _cursor_stale = true; _blink_start = ClockObject::get_global_clock()->get_frame_time(); - set_cursor_position(_wtext.size()); + set_cursor_position(_text.get_num_characters()); +} + +//////////////////////////////////////////////////////////////////// +// Function: PGEntry::get_plain_wtext +// Access: Published +// Description: Returns the text currently displayed within the +// entry, without any embedded properties characters. +//////////////////////////////////////////////////////////////////// +INLINE wstring PGEntry:: +get_plain_wtext() const { + return _text.get_plain_wtext(); } //////////////////////////////////////////////////////////////////// @@ -444,9 +530,9 @@ set_wtext(const wstring &wtext) { // Description: Returns the text currently displayed within the // entry. //////////////////////////////////////////////////////////////////// -INLINE const wstring &PGEntry:: +INLINE wstring PGEntry:: get_wtext() const { - return _wtext; + return _text.get_wtext(); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/pgui/pgEntry.cxx b/panda/src/pgui/pgEntry.cxx index 849fdbfefb..2bc9aa4d85 100644 --- a/panda/src/pgui/pgEntry.cxx +++ b/panda/src/pgui/pgEntry.cxx @@ -41,7 +41,9 @@ TypeHandle PGEntry::_type_handle; //////////////////////////////////////////////////////////////////// PGEntry:: PGEntry(const string &name) : - PGItem(name) + PGItem(name), + _text(get_text_node()), + _obscure_text(get_text_node()) { set_cull_callback(); @@ -53,6 +55,7 @@ PGEntry(const string &name) : _max_chars = 0; _max_width = 0.0f; _num_lines = 1; + _accept_enabled = true; _last_text_def = (TextNode *)NULL; _text_geom_stale = true; _blink_start = 0.0f; @@ -93,8 +96,8 @@ PGEntry:: PGEntry:: PGEntry(const PGEntry ©) : PGItem(copy), - _wtext(copy._wtext), - _obscured_wtext(copy._obscured_wtext), + _text(copy._text), + _obscure_text(copy._obscure_text), _cursor_position(copy._cursor_position), _max_chars(copy._max_chars), _max_width(copy._max_width), @@ -203,7 +206,7 @@ press(const MouseWatcherParameter ¶m, bool background) { _text_geom_stale = true; } - _cursor_position = min(_cursor_position, (int)_wtext.length()); + _cursor_position = min(_cursor_position, _text.get_num_characters()); _blink_start = ClockObject::get_global_clock()->get_frame_time(); if (button == KeyboardButton::enter()) { // Enter. Accept the entry. @@ -214,10 +217,7 @@ press(const MouseWatcherParameter ¶m, bool background) { } else if (button == KeyboardButton::backspace()) { // Backspace. Remove the character to the left of the cursor. if (_cursor_position > 0) { - if (_obscure_mode && _obscured_wtext.length() == _wtext.length()) { - _obscured_wtext.erase(_obscured_wtext.begin() + _obscured_wtext.length() - 1); - } - _wtext.erase(_wtext.begin() + _cursor_position - 1); + _text.set_wsubstr(wstring(), _cursor_position - 1, 1); _cursor_position--; _cursor_stale = true; _text_geom_stale = true; @@ -226,11 +226,8 @@ press(const MouseWatcherParameter ¶m, bool background) { } else if (button == KeyboardButton::del()) { // Delete. Remove the character to the right of the cursor. - if (_cursor_position < (int)_wtext.length()) { - if (_obscure_mode && _obscured_wtext.length() == _wtext.length()) { - _obscured_wtext.erase(_obscured_wtext.begin() + _obscured_wtext.length() - 1); - } - _wtext.erase(_wtext.begin() + _cursor_position); + if (_cursor_position < _text.get_num_characters()) { + _text.set_wsubstr(wstring(), _cursor_position, 1); _text_geom_stale = true; erase(param); } @@ -245,7 +242,7 @@ press(const MouseWatcherParameter ¶m, bool background) { } else if (button == KeyboardButton::right()) { if (_cursor_keys_active) { // Right arrow. Move the cursor position to the right. - _cursor_position = min(_cursor_position + 1, (int)_wtext.length()); + _cursor_position = min(_cursor_position + 1, _text.get_num_characters()); _cursor_stale = true; } @@ -259,7 +256,7 @@ press(const MouseWatcherParameter ¶m, bool background) { } else if (button == KeyboardButton::end()) { if (_cursor_keys_active) { // End. Move the cursor position to the end. - _cursor_position = _wtext.length(); + _cursor_position = _text.get_num_characters(); _cursor_stale = true; } } @@ -290,114 +287,61 @@ keystroke(const MouseWatcherParameter ¶m, bool background) { } wstring new_char(1, (wchar_t)keycode); - if (get_max_chars() > 0 && (int)_wtext.length() >= get_max_chars()) { + if (get_max_chars() > 0 && _text.get_num_characters() >= get_max_chars()) { // In max_chars mode, we consider it an overflow after we // have exceeded a fixed number of characters, irrespective // of the formatted width of those characters. overflow(param); } else { - _cursor_position = min(_cursor_position, (int)_wtext.length()); - wstring new_text = - _wtext.substr(0, _cursor_position) + new_char + - _wtext.substr(_cursor_position); - - // Get the new string to measure its length. In normal - // mode, we measure the text itself. In obscure mode, we - // measure a string of n asterisks. - wstring measure_text; + _cursor_position = min(_cursor_position, _text.get_num_characters()); + bool too_long = !_text.set_wsubstr(new_char, _cursor_position, 0); if (_obscure_mode) { - measure_text = get_display_wtext() + (wchar_t)'*'; + too_long = !_obscure_text.set_wtext(wstring(_text.get_num_characters(), '*')); } else { - measure_text = new_text; - } - - // Now check the length. - bool too_long = false; - if (_max_width > 0.0f) { - TextNode *text_node = get_text_def(S_focus); - text_node->set_wtext(measure_text); - text_node->set_wordwrap(_max_width); - text_node->set_preserve_trailing_whitespace(true); - text_node->set_max_rows(_num_lines); - - too_long = text_node->has_overflow(); - - if (!too_long && (text_node->get_num_rows() == _num_lines)) { + if (!too_long && (_text.get_num_rows() == _num_lines)) { // If we've filled up all of the available lines, we // must also ensure that the last line is not too long // (it might be, because of additional whitespace on // the end). - wstring ww_text = text_node->get_wordwrapped_wtext(); - size_t last_line_start = ww_text.rfind('\n'); - if (last_line_start == string::npos) { - last_line_start = 0; - } - wstring last_line = ww_text.substr(last_line_start); - float last_line_width = text_node->calc_width(last_line); - + int r = _num_lines - 1; + int c = _text.get_num_cols(r); + float last_line_width = + _text.get_xpos(r, c) - _text.get_xpos(r, 0); too_long = (last_line_width > _max_width); } - + if (!too_long && keycode == ' ') { // Even if we haven't filled up all of the available // lines, we should reject a space that's typed at the // end of the current line if it would make that line // exceed the maximum width, just so we don't allow an // infinite number of spaces to accumulate. - - // First, we need to figure out our current position - // within the wordwrapped text, by skipping past the - // newlines. - wstring ww_text = text_node->get_wordwrapped_wtext(); - int i = 0; - int current_pos = 0; - while (i < _cursor_position) { - nassertv(current_pos < (int)ww_text.length()); - if (ww_text[current_pos] != '\n') { - i++; - } - current_pos++; - } - - // Is the user typing at the end of the line? Scan for - // the next character that's not a space following the - // current position. - int p = current_pos + 1; - while (p < (int)ww_text.length() && ww_text[p] == ' ') { - p++; - } - - if (p >= (int)ww_text.length() || ww_text[p] == '\n') { + int r, c; + _text.calc_r_c(r, c, _cursor_position); + if (_text.get_num_cols(r) == c + 1) { // The user is typing at the end of the line. But we // must allow at least one space at the end of the // line, so we only make any of the following checks // if there are already multiple spaces at the end of // the line. - if (p - 2 >= 0 && ww_text[p - 2] == ' ') { + if (c - 1 >= 0 && _text.get_character(r, c - 1) == ' ') { // Ok, the user is putting multiple spaces on the // end of a line; we need to make sure the line does - // not grow too wide. Get the beginning of the line - // and measure its width. - int q = current_pos; - while (q >= 0 && ww_text[q] != '\n') { - q--; - } - - wstring current_line = ww_text.substr(q + 1, p - (q + 1)); - float current_line_width = text_node->calc_width(current_line); - + // not grow too wide. Measure the line's width. + float current_line_width = + _text.get_xpos(r, c + 1) - _text.get_xpos(r, 0); if (current_line_width > _max_width) { // We have to reject the space, but we don't treat // it as an overflow condition. - + _text.set_wsubstr(wstring(), _cursor_position, 1); // If the user is typing over existing space // characters, we act as if the right-arrow key // were pressed instead, and advance the cursor to // the next position. Otherwise, we just quietly // eat the space character. - if (_cursor_position < (int)_wtext.length() && - _wtext[_cursor_position] == ' ') { + if (_cursor_position < _text.get_num_characters() && + _text.get_character(_cursor_position) == ' ') { _cursor_position++; _cursor_stale = true; } @@ -409,14 +353,10 @@ keystroke(const MouseWatcherParameter ¶m, bool background) { } if (too_long) { + _text.set_wsubstr(wstring(), _cursor_position, 1); overflow(param); } else { - _wtext = new_text; - if (_obscure_mode) { - _obscured_wtext = measure_text; - } - _cursor_position += new_char.length(); _cursor_stale = true; _text_geom_stale = true; @@ -445,8 +385,9 @@ candidate(const MouseWatcherParameter ¶m, bool background) { _candidate_highlight_end = param.get_highlight_end(); _candidate_cursor_pos = param.get_cursor_pos(); _text_geom_stale = true; - if (!_candidate_wtext.empty()) + if (!_candidate_wtext.empty()) { type(param); + } } } PGItem::candidate(param, background); @@ -678,9 +619,9 @@ set_focus(bool focus) { //////////////////////////////////////////////////////////////////// bool PGEntry:: is_wtext() const { - wstring::const_iterator ti; - for (ti = _wtext.begin(); ti != _wtext.end(); ++ti) { - if (((*ti) & ~0x7f) != 0) { + for (int i = 0; i < _text.get_num_characters(); ++i) { + wchar_t ch = _text.get_character(i); + if ((ch & ~0x7f) != 0) { return true; } } @@ -688,34 +629,6 @@ is_wtext() const { return false; } -//////////////////////////////////////////////////////////////////// -// Function: PGEntry::get_display_wtext -// Access: Private -// Description: Returns the string that should be displayed within -// the entry. This is normally _wtext, but it may be -// _obscured_wtext. -//////////////////////////////////////////////////////////////////// -const wstring &PGEntry:: -get_display_wtext() { - if (_obscure_mode) { - // If obscure mode is enabled, we should just display a bunch of - // asterisks. - if (_obscured_wtext.length() != _wtext.length()) { - _obscured_wtext = wstring(); - wstring::const_iterator ti; - for (ti = _wtext.begin(); ti != _wtext.end(); ++ti) { - _obscured_wtext += (wchar_t)'*'; - } - } - - return _obscured_wtext; - - } else { - // In normal, non-obscure mode, we display the actual text - return _wtext; - } -} - //////////////////////////////////////////////////////////////////// // Function: PGEntry::slot_text_def // Access: Private @@ -741,89 +654,56 @@ update_text() { nassertv(node != (TextNode *)NULL); if (_text_geom_stale || node != _last_text_def) { - wstring display_wtext; + TextProperties props = *node; + props.set_wordwrap(_max_width); + props.set_preserve_trailing_whitespace(true); + _text.set_properties(props); + _text.set_max_rows(_num_lines); - if (_candidate_wtext.empty() || _obscure_mode) { + if (node != _last_text_def) { + // Make sure the default properties are applied to all the + // characters in the text. + _text.set_wtext(_text.get_wtext()); + _last_text_def = node; + } + + PT(PandaNode) assembled; + if (_obscure_mode) { + _obscure_text.set_properties(props); + _obscure_text.set_max_rows(_num_lines); + _obscure_text.set_wtext(wstring(_text.get_num_characters(), '*')); + assembled = _obscure_text.assemble_text(); + + } else if (_candidate_wtext.empty()) { // If we're not trying to display a candidate string, it's easy: // just display the current text contents. - display_wtext = get_display_wtext(); + assembled = _text.assemble_text(); } else { - // Insert the complex sequence of characters required to show - // the candidate string in a different color. This gets - // inserted at the current cursor position. - wstring source_wtext = get_display_wtext(); + TextPropertiesManager *tp_mgr = TextPropertiesManager::get_global_ptr(); + TextProperties inactive = tp_mgr->get_properties(_candidate_inactive); + TextProperties active = tp_mgr->get_properties(_candidate_active); - display_wtext = source_wtext.substr(0, _cursor_position); - display_wtext += wstring(1, (wchar_t)text_push_properties_key); - display_wtext += node->decode_text(_candidate_inactive); - display_wtext += wstring(1, (wchar_t)text_push_properties_key); - display_wtext += _candidate_wtext.substr(0, _candidate_highlight_start); - display_wtext += wstring(1, (wchar_t)text_push_properties_key); - display_wtext += node->decode_text(_candidate_active); - display_wtext += wstring(1, (wchar_t)text_push_properties_key); - display_wtext += _candidate_wtext.substr(_candidate_highlight_start, - _candidate_highlight_end - _candidate_highlight_start); - display_wtext += wstring(1, (wchar_t)text_pop_properties_key); - display_wtext += _candidate_wtext.substr(_candidate_highlight_end); - display_wtext += wstring(1, (wchar_t)text_pop_properties_key); + // Create a special TextAssembler to insert the candidate string + // in its own special colors. - display_wtext += source_wtext.substr(_cursor_position); - } - - // We need to regenerate. - _last_text_def = node; - _last_text_def->set_wtext(display_wtext); - _last_text_def->set_wordwrap(_max_width); - _last_text_def->set_preserve_trailing_whitespace(true); - _last_text_def->set_max_rows(_num_lines); - - // Check for multiple lines. - wstring ww_text = _last_text_def->get_wordwrapped_wtext(); - - // And chop the lines up into pieces. - _ww_lines.clear(); - size_t p = 0; - size_t q = ww_text.find((wchar_t)'\n'); - while (q != string::npos) { - _ww_lines.push_back(WWLine()); - WWLine &line = _ww_lines.back(); - line._str = ww_text.substr(p, q - p); - - // Get the left edge of the text at this line. - line._left = 0.0f; - if (_last_text_def->get_align() != TextNode::A_left) { - // Temporarily set this line's text in the TextNode, just so - // we can measure the left margin. (If align is A_left, we - // know that the left margin is 0.0, so we don't need to do - // this.) - _last_text_def->set_wtext(line._str); - line._left = _last_text_def->get_left(); - } - - p = q + 1; - q = ww_text.find('\n', p); - } - _ww_lines.push_back(WWLine()); - WWLine &line = _ww_lines.back(); - line._str = ww_text.substr(p); - - // Get the left edge of the text at this line. - line._left = 0.0f; - if (_last_text_def->get_align() != TextNode::A_left) { - _last_text_def->set_wtext(line._str); - line._left = _last_text_def->get_left(); + TextAssembler ctext(_text); + ctext.set_wsubstr(_candidate_wtext.substr(_candidate_highlight_end), + _cursor_position, 0, inactive); + ctext.set_wsubstr(_candidate_wtext.substr(_candidate_highlight_start, + _candidate_highlight_end - _candidate_highlight_start), + _cursor_position, 0, active); + ctext.set_wsubstr(_candidate_wtext.substr(0, _candidate_highlight_start), + _cursor_position, 0, inactive); + assembled = ctext.assemble_text(); } if (!_current_text.is_empty()) { _current_text.remove_node(); } - // We need to reset the text, since we might have changed it - // temporarily in the above when align is not A_left. - _last_text_def->set_wtext(display_wtext); _current_text = - _text_render_root.attach_new_node(_last_text_def->generate()); + _text_render_root.attach_new_node(assembled); _text_geom_stale = false; _cursor_stale = true; } @@ -844,32 +724,23 @@ update_cursor() { if (_cursor_stale || node != _last_text_def) { update_text(); - _cursor_position = min(_cursor_position, (int)_wtext.length()); + _cursor_position = min(_cursor_position, _text.get_num_characters()); _candidate_cursor_pos = min(_candidate_cursor_pos, _candidate_wtext.length()); // Determine the row and column of the cursor. - int row = 0; - int column = _cursor_position + _candidate_cursor_pos; - while (row + 1 < (int)_ww_lines.size() && - column > (int)_ww_lines[row]._str.length()) { - column -= _ww_lines[row]._str.length(); - row++; + int row, column; + float xpos, ypos; + if (_obscure_mode) { + _obscure_text.calc_r_c(row, column, _cursor_position + _candidate_cursor_pos); + xpos = _obscure_text.get_xpos(row, column); + ypos = _obscure_text.get_ypos(row, column); + } else { + _text.calc_r_c(row, column, _cursor_position + _candidate_cursor_pos); + xpos = _text.get_xpos(row, column); + ypos = _text.get_ypos(row, column); } - nassertv(row >= 0 && row < (int)_ww_lines.size()); - - // It is possible for this to become untrue legitimately, if due - // to a candidate string we have wordwrapped down the last part of - // the line containing the cursor. - //nassertv(column >= 0 && column <= (int)_ww_lines[row]._str.length()); - - float width = - _last_text_def->calc_width(_ww_lines[row]._str.substr(0, column)); - float line_height = _last_text_def->get_line_height(); - - LVecBase3f trans(_ww_lines[row]._left + width, 0.0f, -line_height * row); - _cursor_def.set_pos(trans); - + _cursor_def.set_pos(xpos, 0.0f, ypos); _cursor_stale = false; } diff --git a/panda/src/pgui/pgEntry.h b/panda/src/pgui/pgEntry.h index e856ae13ba..f9dc40e7b4 100644 --- a/panda/src/pgui/pgEntry.h +++ b/panda/src/pgui/pgEntry.h @@ -27,6 +27,7 @@ #include "pointerTo.h" #include "pvector.h" #include "clockObject.h" +#include "textAssembler.h" //////////////////////////////////////////////////////////////////// // Class : PGEntry @@ -74,8 +75,14 @@ PUBLISHED: void setup(float width, int num_lines); INLINE void set_text(const string &text); + INLINE string get_plain_text() const; INLINE string get_text() const; + INLINE int get_num_characters() const; + INLINE wchar_t get_character(int n) const; + INLINE const TextGraphic *get_graphic(int n) const; + INLINE const TextProperties &get_properties(int n) const; + INLINE void set_cursor_position(int position); INLINE int get_cursor_position() const; @@ -121,21 +128,21 @@ PUBLISHED: INLINE string get_erase_event() const; INLINE void set_wtext(const wstring &wtext); - INLINE const wstring &get_wtext() const; + INLINE wstring get_plain_wtext() const; + INLINE wstring get_wtext() const; INLINE void set_accept_enabled(bool enabled); bool is_wtext() const; private: - const wstring &get_display_wtext(); void slot_text_def(int state); void update_text(); void update_cursor(); void show_hide_cursor(bool visible); void update_state(); - wstring _wtext; - wstring _obscured_wtext; + TextAssembler _text; + TextAssembler _obscure_text; int _cursor_position; bool _cursor_stale; bool _cursor_visible; @@ -166,17 +173,6 @@ private: TextNode *_last_text_def; bool _text_geom_stale; - // This is a list of each row of text in the entry, after it has - // been wordwrapped by update_text(). It's used by update_cursor() - // to compute the correct cursor position. - class WWLine { - public: - wstring _str; - float _left; - }; - typedef pvector WWLines; - WWLines _ww_lines; - // This is the node that represents the cursor geometry. It is also // attached to the above node, and is transformed around and/or // hidden according to the cursor's position and blink state. diff --git a/panda/src/text/textAssembler.I b/panda/src/text/textAssembler.I index 673b52f578..a483d9d8d6 100644 --- a/panda/src/text/textAssembler.I +++ b/panda/src/text/textAssembler.I @@ -43,9 +43,61 @@ get_usage_hint() const { return _usage_hint; } +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::set_max_rows +// Access: Published +// Description: If max_rows is greater than zero, no more than +// max_rows will be accepted. Text beyond that will be +// truncated. +// +// The return value is true if all the text is accepted, +// or false if some was truncated. +//////////////////////////////////////////////////////////////////// +INLINE bool TextAssembler:: +set_max_rows(int max_rows) { + _max_rows = max_rows; + return wordwrap_text(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_max_rows +// Access: Published +// Description: If max_rows is greater than zero, no more than +// max_rows will be accepted. Text beyond that will be +// truncated. +//////////////////////////////////////////////////////////////////// +INLINE int TextAssembler:: +get_max_rows() const { + return _max_rows; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::set_properties +// Access: Published +// Description: Specifies the default TextProperties that are applied +// to the text in the absence of any nested property +// change sequences. +//////////////////////////////////////////////////////////////////// +INLINE void TextAssembler:: +set_properties(const TextProperties &properties) { + _initial_cprops = new ComputedProperties(properties); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_properties +// Access: Published +// Description: Returns the default TextProperties that are applied +// to the text in the absence of any nested property +// change sequences. +//////////////////////////////////////////////////////////////////// +INLINE const TextProperties &TextAssembler:: +get_properties() const { + return _initial_cprops->_properties; +} + //////////////////////////////////////////////////////////////////// // Function: TextAssembler::get_ul -// Access: Public +// Access: Published // Description: Returns the upper-left corner of the assembled text, // in 2-d text coordinates. //////////////////////////////////////////////////////////////////// @@ -56,7 +108,7 @@ get_ul() const { //////////////////////////////////////////////////////////////////// // Function: TextAssembler::get_lr -// Access: Public +// Access: Published // Description: Returns the lower-right corner of the assembled text, // in 2-d text coordinates. //////////////////////////////////////////////////////////////////// @@ -65,15 +117,212 @@ get_lr() const { return _lr; } +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::calc_r +// Access: Published +// Description: Computes the row index of the nth character or +// graphic object in the text and returns it. +// +// If the nth character is not a normal printable +// character with a position in the wordwrapped string, +// returns -1 (for instance, a soft-hyphen character, or +// a newline character, may not have a corresponding +// position). +//////////////////////////////////////////////////////////////////// +int TextAssembler:: +calc_r(int n) const { + int r, c; + if (calc_r_c(r, c, n)) { + return r; + } + return -1; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::calc_c +// Access: Published +// Description: Computes the column index of the nth character or +// graphic object in the text and returns it. +// +// If the nth character is not a normal printable +// character with a position in the wordwrapped string, +// returns -1 (for instance, a soft-hyphen character, or +// a newline character, may not have a corresponding +// position). +//////////////////////////////////////////////////////////////////// +int TextAssembler:: +calc_c(int n) const { + int r, c; + if (calc_r_c(r, c, n)) { + return c; + } + return -1; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_num_characters +// Access: Published +// Description: Returns the number of characters of text, before +// wordwrapping. +//////////////////////////////////////////////////////////////////// +INLINE int TextAssembler:: +get_num_characters() const { + return _text_string.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_character +// Access: Published +// Description: Returns the character at the indicated position in +// the pre-wordwrapped string. If the object at this +// position is a graphic object instead of a character, +// returns 0. +//////////////////////////////////////////////////////////////////// +INLINE wchar_t TextAssembler:: +get_character(int n) const { + nassertr(n >= 0 && n < (int)_text_string.size(), 0); + return _text_string[n]._character; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_graphic +// Access: Published +// Description: Returns the graphic object at the indicated position +// in the pre-wordwrapped string. If the object at this +// position is a character instead of a graphic object, +// returns NULL. +//////////////////////////////////////////////////////////////////// +INLINE const TextGraphic *TextAssembler:: +get_graphic(int n) const { + nassertr(n >= 0 && n < (int)_text_string.size(), 0); + return _text_string[n]._graphic; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_properties +// Access: Published +// Description: Returns the TextProperties in effect for the object +// at the indicated position in the pre-wordwrapped +// string. +//////////////////////////////////////////////////////////////////// +INLINE const TextProperties &TextAssembler:: +get_properties(int n) const { + nassertr(n >= 0 && n < (int)_text_string.size(), *(new TextProperties())); + return _text_string[n]._cprops->_properties; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_width +// Access: Published +// Description: Returns the width of the character or object at the +// indicated position in the pre-wordwrapped string. +//////////////////////////////////////////////////////////////////// +INLINE float TextAssembler:: +get_width(int n) const { + nassertr(n >= 0 && n < (int)_text_string.size(), 0.0f); + + return calc_width(_text_string[n]); +} + //////////////////////////////////////////////////////////////////// // Function: TextAssembler::get_num_rows -// Access: Public +// Access: Published // Description: Returns the number of rows of text after it has all // been wordwrapped and assembled. //////////////////////////////////////////////////////////////////// INLINE int TextAssembler:: get_num_rows() const { - return _num_rows; + return _text_block.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_num_cols +// Access: Published +// Description: Returns the number of characters and/or graphic +// objects in the nth row. +//////////////////////////////////////////////////////////////////// +INLINE int TextAssembler:: +get_num_cols(int r) const { + nassertr(r >= 0 && r <= (int)_text_block.size(), 0); + if (r == _text_block.size()) { + return 0; + } + return _text_block[r]._string.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_character +// Access: Published +// Description: Returns the character at the indicated position in +// the indicated row. If the object at this position is +// a graphic object instead of a character, returns 0. +//////////////////////////////////////////////////////////////////// +INLINE wchar_t TextAssembler:: +get_character(int r, int c) const { + nassertr(r >= 0 && r < (int)_text_block.size(), 0); + nassertr(c >= 0 && c < (int)_text_block[r]._string.size(), 0); + return _text_block[r]._string[c]._character; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_graphic +// Access: Published +// Description: Returns the graphic object at the indicated position +// in the indicated row. If the object at this position +// is a character instead of a graphic object, returns +// NULL. +//////////////////////////////////////////////////////////////////// +INLINE const TextGraphic *TextAssembler:: +get_graphic(int r, int c) const { + nassertr(r >= 0 && r < (int)_text_block.size(), 0); + nassertr(c >= 0 && c < (int)_text_block[r]._string.size(), 0); + return _text_block[r]._string[c]._graphic; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_properties +// Access: Published +// Description: Returns the TextProperties in effect for the object +// at the indicated position in the indicated row. +//////////////////////////////////////////////////////////////////// +INLINE const TextProperties &TextAssembler:: +get_properties(int r, int c) const { + nassertr(r >= 0 && r < (int)_text_block.size(), *(new TextProperties())); + nassertr(c >= 0 && c < (int)_text_block[r]._string.size(), *(new TextProperties())); + return _text_block[r]._string[c]._cprops->_properties; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_width +// Access: Published +// Description: Returns the width of the character or object at the +// indicated position in the indicated row. +//////////////////////////////////////////////////////////////////// +INLINE float TextAssembler:: +get_width(int r, int c) const { + nassertr(r >= 0 && r < (int)_text_block.size(), 0.0f); + nassertr(c >= 0 && c < (int)_text_block[r]._string.size(), 0.0f); + + return calc_width(_text_block[r]._string[c]); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_ypos +// Access: Published +// Description: Returns the y position of the origin of all of the +// characters or graphic objects in the indicated row. +// +// It is legal for r to exceed the index number of the +// last row by 1. The value of c is presently ignored. +//////////////////////////////////////////////////////////////////// +INLINE float TextAssembler:: +get_ypos(int r, int) const { + nassertr(r >= 0 && r <= (int)_text_block.size(), 0.0f); + if (r == _text_block.size()) { + return _next_row_ypos; + } else { + return _text_block[r]._ypos; + } } //////////////////////////////////////////////////////////////////// @@ -85,9 +334,9 @@ get_num_rows() const { INLINE float TextAssembler:: calc_width(const TextCharacter &tch) { if (tch._graphic != (TextGraphic *)NULL) { - return calc_width(tch._graphic, *tch._properties); + return calc_width(tch._graphic, tch._cprops->_properties); } else { - return calc_width(tch._character, *tch._properties); + return calc_width(tch._character, tch._cprops->_properties); } } @@ -97,10 +346,11 @@ calc_width(const TextCharacter &tch) { // Description: //////////////////////////////////////////////////////////////////// INLINE TextAssembler::TextCharacter:: -TextCharacter(wchar_t character, const TextProperties *properties) : +TextCharacter(wchar_t character, + TextAssembler::ComputedProperties *cprops) : _character(character), _graphic(NULL), - _properties(properties) + _cprops(cprops) { } @@ -110,13 +360,126 @@ TextCharacter(wchar_t character, const TextProperties *properties) : // Description: //////////////////////////////////////////////////////////////////// INLINE TextAssembler::TextCharacter:: -TextCharacter(const TextGraphic *graphic, const TextProperties *properties) : +TextCharacter(const TextGraphic *graphic, const wstring &graphic_wname, + TextAssembler::ComputedProperties *cprops) : _character(0), _graphic(graphic), - _properties(properties) + _graphic_wname(graphic_wname), + _cprops(cprops) { } +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::TextCharacter::Copy Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE TextAssembler::TextCharacter:: +TextCharacter(const TextAssembler::TextCharacter ©) : + _character(copy._character), + _graphic(copy._graphic), + _graphic_wname(copy._graphic_wname), + _cprops(copy._cprops) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::TextCharacter::Copy Assignment +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextAssembler::TextCharacter:: +operator = (const TextAssembler::TextCharacter ©) { + _character = copy._character; + _graphic = copy._graphic; + _graphic_wname = copy._graphic_wname; + _cprops = copy._cprops; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::TextRow::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE TextAssembler::TextRow:: +TextRow(int row_start) : + _row_start(row_start), + _got_soft_hyphens(false) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::TextRow::Copy Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE TextAssembler::TextRow:: +TextRow(const TextAssembler::TextRow ©) : + _string(copy._string), + _row_start(copy._row_start), + _got_soft_hyphens(copy._got_soft_hyphens), + _xpos(copy._xpos), + _ypos(copy._ypos) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::TextRow::Copy Assignment +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextAssembler::TextRow:: +operator = (const TextAssembler::TextRow ©) { + _string = copy._string; + _row_start = copy._row_start; + _got_soft_hyphens = copy._got_soft_hyphens; + _xpos = copy._xpos; + _ypos = copy._ypos; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::ComputedProperties::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE TextAssembler::ComputedProperties:: +ComputedProperties(const TextProperties &orig_properties) : + _based_on(NULL), + _depth(0), + _properties(orig_properties) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::ComputedProperties::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE TextAssembler::ComputedProperties:: +ComputedProperties(ComputedProperties *based_on, const wstring &wname, + TextEncoder *encoder) : + _based_on(based_on), + _depth(_based_on->_depth + 1), + _wname(wname), + _properties(based_on->_properties) +{ + TextPropertiesManager *manager = + TextPropertiesManager::get_global_ptr(); + + // Now we have to encode the wstring into a string, for lookup + // in the TextPropertiesManager. + string name = encoder->encode_wtext(wname); + + const TextProperties *named_props = manager->get_properties_ptr(name); + if (named_props != (TextProperties *)NULL) { + _properties.add_properties(*named_props); + } else { + text_cat.warning() + << "Unknown TextProperties: " << name << "\n"; + } +} + + //////////////////////////////////////////////////////////////////// // Function: TextAssembler::GlyphPlacement::add_piece // Access: Public diff --git a/panda/src/text/textAssembler.cxx b/panda/src/text/textAssembler.cxx index 167cebfd31..8ad48e1481 100644 --- a/panda/src/text/textAssembler.cxx +++ b/panda/src/text/textAssembler.cxx @@ -29,10 +29,6 @@ #include -#ifndef CPPPARSER // interrogate has a bit of trouble with wstring. - - - // This is the factor by which CT_small scales the character down. static const float small_accent_scale = 0.6f; @@ -78,74 +74,100 @@ isbreakpoint(unsigned int ch) { //////////////////////////////////////////////////////////////////// // Function: TextAssembler::Constructor -// Access: Public -// Description: Places all of the indicated text according to the -// associated TextProperties. +// Access: Published +// Description: //////////////////////////////////////////////////////////////////// TextAssembler:: TextAssembler(TextEncoder *encoder) : _encoder(encoder), - _usage_hint(Geom::UH_static) + _usage_hint(Geom::UH_static), + _max_rows(0) { + _initial_cprops = new ComputedProperties(TextProperties()); clear(); } +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::Copy Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +TextAssembler:: +TextAssembler(const TextAssembler ©) : + _initial_cprops(copy._initial_cprops), + _text_string(copy._text_string), + _text_block(copy._text_block), + _ul(copy._ul), + _lr(copy._lr), + _next_row_ypos(copy._next_row_ypos), + _encoder(copy._encoder), + _usage_hint(copy._usage_hint), + _max_rows(copy._max_rows) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::Copy Assignment Operator +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void TextAssembler:: +operator = (const TextAssembler ©) { + _initial_cprops = copy._initial_cprops; + _text_string = copy._text_string; + _text_block = copy._text_block; + _ul = copy._ul; + _lr = copy._lr; + _next_row_ypos = copy._next_row_ypos; + _encoder = copy._encoder; + _usage_hint = copy._usage_hint; + _max_rows = copy._max_rows; +} + //////////////////////////////////////////////////////////////////// // Function: TextAssembler::Destructor -// Access: Public +// Access: Published // Description: //////////////////////////////////////////////////////////////////// TextAssembler:: ~TextAssembler() { - clear(); } //////////////////////////////////////////////////////////////////// // Function: TextAssembler::clear -// Access: Public +// Access: Published // Description: Reinitializes the contents of the TextAssembler. //////////////////////////////////////////////////////////////////// void TextAssembler:: clear() { - _num_rows = 0; _ul.set(0.0f, 0.0f); _lr.set(0.0f, 0.0f); + _next_row_ypos = 0.0f; - _wordwrapped_string.clear(); - - PropertiesList::iterator li; - for (li = _properties_list.begin(); li != _properties_list.end(); ++li) { - delete (*li); - } - _properties_list.clear(); + _text_string.clear(); + _text_block.clear(); } //////////////////////////////////////////////////////////////////// // Function: TextAssembler::set_wtext -// Access: Public +// Access: Published // Description: Accepts a new text string and associated properties // structure, and precomputes the wordwrapping layout // appropriately. After this call, // get_wordwrapped_wtext() and get_num_rows() can be // called. // -// If max_rows is greater than zero, no more than -// max_rows will be accepted. Text beyond that will be -// truncated. -// // The return value is true if all the text is accepted, -// or false if some was truncated. +// or false if some was truncated (see set_max_rows()). //////////////////////////////////////////////////////////////////// bool TextAssembler:: -set_wtext(const wstring &wtext, const TextProperties &properties, - int max_rows) { +set_wtext(const wstring &wtext) { clear(); // First, expand all of the embedded TextProperties references // within the string. - TextString text_string; wstring::const_iterator si = wtext.begin(); - scan_wtext(si, wtext.end(), &properties, text_string); + scan_wtext(_text_string, si, wtext.end(), _initial_cprops); while (si != wtext.end()) { // If we returned without consuming the whole string, it means @@ -154,40 +176,358 @@ set_wtext(const wstring &wtext, const TextProperties &properties, // the rest of the string. text_cat.warning() << "pop_properties encountered without preceding push_properties.\n"; - scan_wtext(si, wtext.end(), &properties, text_string); + scan_wtext(_text_string, si, wtext.end(), _initial_cprops); } // Then apply any wordwrap requirements. - return wordwrap_text(text_string, _wordwrapped_string, max_rows); + return wordwrap_text(); } //////////////////////////////////////////////////////////////////// -// Function: TextAssembler::get_wordwrapped_wtext -// Access: Public +// Function: TextAssembler::set_wsubstr +// Access: Published +// Description: Replaces the 'count' characters from 'start' of the +// current text with the indicated replacement text. If +// the replacement text does not have count characters, +// the length of the string will be changed accordingly. +// +// The substring may include nested formatting +// characters, but they must be self-contained and +// self-closed. The indicated TextProperties specifies +// the default TextProperties to apply to the substring; +// it layers on top of the default TextProperties for +// the overall string specified via set_properties(). +// +// The return value is true if all the text is accepted, +// or false if some was truncated (see set_max_rows()). +//////////////////////////////////////////////////////////////////// +bool TextAssembler:: +set_wsubstr(const wstring &wtext, int start, int count, + const TextProperties &properties) { + nassertr(start >= 0 && start <= (int)_text_string.size(), false); + nassertr(count >= 0 && start + count <= (int)_text_string.size(), false); + + PT(ComputedProperties) substr_cprops = _initial_cprops; + if (properties.is_any_specified()) { + TextProperties new_properties = get_properties(); + new_properties.add_properties(properties); + if (substr_cprops->_properties != new_properties) { + substr_cprops = new ComputedProperties(new_properties); + } + } + + // Use scan_wtext to unroll the substring we wish to insert, as in + // set_wtext(), above. + TextString substr; + wstring::const_iterator si = wtext.begin(); + scan_wtext(substr, si, wtext.end(), substr_cprops); + while (si != wtext.end()) { + text_cat.warning() + << "pop_properties encountered without preceding push_properties.\n"; + scan_wtext(substr, si, wtext.end(), substr_cprops); + } + + _text_string.erase(_text_string.begin() + start, _text_string.begin() + start + count); + _text_string.insert(_text_string.begin() + start, substr.begin(), substr.end()); + + return wordwrap_text(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_plain_wtext +// Access: Published // Description: Returns a wstring that represents the contents of the -// text, as it has been formatted by wordwrap rules. -// This will not contain any embedded special characters -// like \1 or \3. +// text, without any embedded properties characters. If +// there is an embedded graphic object, a zero value is +// inserted in that position. +// +// This string has the same length as +// get_num_characters(), and the characters in this +// string correspond one-to-one with the characters +// returned by get_character(n). //////////////////////////////////////////////////////////////////// wstring TextAssembler:: -get_wordwrapped_wtext() const { +get_plain_wtext() const { wstring wtext; - TextString::const_iterator ti; - for (ti = _wordwrapped_string.begin(); - ti != _wordwrapped_string.end(); - ++ti) { - if ((*ti)._graphic == (TextGraphic *)NULL) { - wtext += (*ti)._character; + TextString::const_iterator si; + for (si = _text_string.begin(); si != _text_string.end(); ++si) { + const TextCharacter &tch = (*si); + if (tch._graphic == (TextGraphic *)NULL) { + wtext += tch._character; + } else { + wtext.push_back(0); + } + } + + return wtext; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_wordwrapped_plain_wtext +// Access: Published +// Description: Returns a wstring that represents the contents of the +// text, with newlines inserted according to the +// wordwrapping. The string will contain no embedded +// properties characters. If there is an embedded +// graphic object, a zero value is inserted in that +// position. +// +// This string has the same number of newline characters +// as get_num_rows(), and the characters in this string +// correspond one-to-one with the characters returned by +// get_character(r, c). +//////////////////////////////////////////////////////////////////// +wstring TextAssembler:: +get_wordwrapped_plain_wtext() const { + wstring wtext; + + TextBlock::const_iterator bi; + for (bi = _text_block.begin(); bi != _text_block.end(); ++bi) { + const TextRow &row = (*bi); + if (bi != _text_block.begin()) { + wtext += '\n'; + } + + TextString::const_iterator si; + for (si = row._string.begin(); si != row._string.end(); ++si) { + const TextCharacter &tch = (*si); + if (tch._graphic == (TextGraphic *)NULL) { + wtext += tch._character; + } else { + wtext.push_back(0); + } } } return wtext; } +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_wtext +// Access: Published +// Description: Returns a wstring that represents the contents of the +// text. +// +// The string will contain embedded properties +// characters, which may not exactly match the embedded +// properties characters of the original string, but it +// will encode the same way. +//////////////////////////////////////////////////////////////////// +wstring TextAssembler:: +get_wtext() const { + wstring wtext; + PT(ComputedProperties) current_cprops = _initial_cprops; + + TextString::const_iterator si; + for (si = _text_string.begin(); si != _text_string.end(); ++si) { + const TextCharacter &tch = (*si); + current_cprops->append_delta(wtext, tch._cprops); + if (tch._graphic == (TextGraphic *)NULL) { + wtext += tch._character; + } else { + wtext.push_back(text_embed_graphic_key); + wtext += tch._graphic_wname; + wtext.push_back(text_embed_graphic_key); + } + current_cprops = tch._cprops; + } + current_cprops->append_delta(wtext, _initial_cprops); + + return wtext; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_wordwrapped_wtext +// Access: Published +// Description: Returns a wstring that represents the contents of the +// text, with newlines inserted according to the +// wordwrapping. +// +// The string will contain embedded properties +// characters, which may not exactly match the embedded +// properties characters of the original string, but it +// will encode the same way. +//////////////////////////////////////////////////////////////////// +wstring TextAssembler:: +get_wordwrapped_wtext() const { + wstring wtext; + + PT(ComputedProperties) current_cprops = _initial_cprops; + + TextBlock::const_iterator bi; + for (bi = _text_block.begin(); bi != _text_block.end(); ++bi) { + const TextRow &row = (*bi); + if (bi != _text_block.begin()) { + wtext += '\n'; + } + + TextString::const_iterator si; + for (si = row._string.begin(); si != row._string.end(); ++si) { + const TextCharacter &tch = (*si); + current_cprops->append_delta(wtext, tch._cprops); + if (tch._graphic == (TextGraphic *)NULL) { + wtext += tch._character; + } else { + wtext.push_back(text_embed_graphic_key); + wtext += tch._graphic_wname; + wtext.push_back(text_embed_graphic_key); + } + current_cprops = tch._cprops; + } + } + current_cprops->append_delta(wtext, _initial_cprops); + + return wtext; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::calc_r_c +// Access: Published +// Description: Computes the row and column index of the nth +// character or graphic object in the text. Fills r and +// c accordingly. +// +// Returns true if the nth character is valid and has a +// corresponding r and c position, false otherwise (for +// instance, a soft-hyphen character, or a newline +// character, may not have a corresponding position). +// In either case, r and c will be filled in sensibly. +//////////////////////////////////////////////////////////////////// +bool TextAssembler:: +calc_r_c(int &r, int &c, int n) const { + nassertr(n >= 0 && n <= (int)_text_string.size(), false); + + if (n == _text_string.size()) { + // A special case for one past the last character. + if (_text_string.empty()) { + r = 0; + c = 0; + } else { + r = _text_block.size() - 1; + c = _text_block[r]._string.size(); + } + return true; + } + + r = 0; + while (r + 1 < (int)_text_block.size() && + _text_block[r + 1]._row_start < n) { + r += 1; + } + + const TextRow &row = _text_block[r]; + bool is_real_char = true; + + if (row._got_soft_hyphens) { + // If there are any soft hyphen or soft break keys in the source + // text, we have to scan past them to get c precisely. + c = 0; + int i = row._row_start; + while (i < n - 1) { + if (_text_string[i]._character != text_soft_hyphen_key && + _text_string[i]._character != text_soft_break_key) { + ++c; + } + ++i; + } + if (_text_string[n - 1]._character != text_soft_hyphen_key && + _text_string[n - 1]._character != text_soft_break_key) { + ++c; + if (_text_string[n - 1]._character == '\n') { + is_real_char = false; + } + } else { + is_real_char = false; + } + + } else { + // If there are no soft characters, then the string maps + // one-to-one. + c = min(n - row._row_start, (int)row._string.size()); + if (_text_string[n - 1]._character == '\n') { + is_real_char = false; + } + } + + return is_real_char; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::calc_index +// Access: Published +// Description: Computes the character index of the character at the +// rth row and cth column position. This is the inverse +// of calc_r_c(). +// +// It is legal for c to exceed the index number of the +// last column by 1, and it is legal for r to exceed the +// index number of the last row by 1, if c is 0. +//////////////////////////////////////////////////////////////////// +int TextAssembler:: +calc_index(int r, int c) const { + nassertr(r >= 0 && r <= (int)_text_block.size(), 0); + if (r == _text_block.size()) { + nassertr(c == 0, 0); + return _text_string.size(); + + } else { + nassertr(c >= 0 && c <= (int)_text_block[r]._string.size(), 0); + const TextRow &row = _text_block[r]; + + if (row._got_soft_hyphens) { + // If there are any soft hyphen or soft break keys in the source + // text, we have to scan past them to get n precisely. + int n = row._row_start; + while (c > 0) { + if (_text_string[n]._character != text_soft_hyphen_key && + _text_string[n]._character != text_soft_break_key) { + --c; + } + ++n; + } + return n; + + } else { + // If there are no soft characters, then the string maps + // one-to-one. + return row._row_start + c; + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_xpos +// Access: Published +// Description: Returns the x position of the origin of the character +// or graphic object at the indicated position in the +// indicated row. +// +// It is legal for c to exceed the index number of the +// last column by 1, and it is legal for r to exceed the +// index number of the last row by 1, if c is 0. +//////////////////////////////////////////////////////////////////// +float TextAssembler:: +get_xpos(int r, int c) const { + nassertr(r >= 0 && r <= (int)_text_block.size(), 0.0f); + if (r == _text_block.size()) { + nassertr(c == 0, 0.0f); + return 0.0f; + + } else { + nassertr(c >= 0 && c <= (int)_text_block[r]._string.size(), 0.0f); + const TextRow &row = _text_block[r]; + float xpos = row._xpos; + for (int i = 0; i < c; ++i) { + xpos += calc_width(row._string[i]); + } + return xpos; + } +} + //////////////////////////////////////////////////////////////////// // Function: TextAssembler::assemble_text -// Access: Public +// Access: Published // Description: Actually assembles all of the text into a GeomNode, // and returns the node (or possibly a parent of the // node, to keep the shadow separate). Once this has @@ -198,8 +538,7 @@ PT(PandaNode) TextAssembler:: assemble_text() { // Now assemble the text into glyphs. PlacedGlyphs placed_glyphs; - assemble_paragraph(_wordwrapped_string.begin(), _wordwrapped_string.end(), - placed_glyphs); + assemble_paragraph(placed_glyphs); // Now that we have a bunch of GlyphPlacements, pull out the Geoms // and put them under a common node. @@ -336,27 +675,19 @@ calc_width(const TextGraphic *graphic, const TextProperties &properties) { return (frame[1] - frame[0]) * properties.get_glyph_scale(); } +#ifndef CPPPARSER // interrogate has a bit of trouble with wstring. //////////////////////////////////////////////////////////////////// // Function: TextAssembler::scan_wtext // Access: Private // Description: Scans through the text string, decoding embedded -// references to TextProperties. The string is copied -// character-by-character into the indicated TextString -// output. -// -// As new TextProperties are discovered, TextProperties -// structures are allocated and pushed into the -// _properties_list list; these pointers are -// referenced by the text_string. When the text_string -// is no longer need, the TextProperties structures in -// _properties_list should be deleted to free up -// memory. +// references to TextProperties. The decoded string is +// copied character-by-character into _text_string. //////////////////////////////////////////////////////////////////// void TextAssembler:: -scan_wtext(wstring::const_iterator &si, +scan_wtext(TextAssembler::TextString &output_string, + wstring::const_iterator &si, const wstring::const_iterator &send, - const TextProperties *current_properties, - TextAssembler::TextString &text_string) { + TextAssembler::ComputedProperties *current_cprops) { while (si != send) { if ((*si) == text_push_properties_key) { // This indicates a nested properties structure. Pull off the @@ -378,27 +709,13 @@ scan_wtext(wstring::const_iterator &si, } ++si; - - // Now we have to encode the wstring into a string, for lookup - // in the TextPropertiesManager. - string name = _encoder->encode_wtext(wname); - - TextPropertiesManager *manager = - TextPropertiesManager::get_global_ptr(); // Define the new properties by extending the current properties. - TextProperties *new_properties = new TextProperties(*current_properties); - const TextProperties *named_props = manager->get_properties_ptr(name); - if (named_props != (TextProperties *)NULL) { - new_properties->add_properties(*named_props); - } else { - text_cat.warning() - << "Unknown TextProperties: " << name << "\n"; - } - _properties_list.push_back(new_properties); + PT(ComputedProperties) new_cprops = + new ComputedProperties(current_cprops, wname, _encoder); // And recursively scan with the nested properties. - scan_wtext(si, send, new_properties, text_string); + scan_wtext(output_string, si, send, new_cprops); if (text_cat.is_debug()) { if (si == send) { @@ -421,10 +738,10 @@ scan_wtext(wstring::const_iterator &si, // TextGraphic structure, which is everything until the next // text_embed_graphic_key. - wstring wname; + wstring graphic_wname; ++si; while (si != send && (*si) != text_embed_graphic_key) { - wname += (*si); + graphic_wname += (*si); ++si; } @@ -440,78 +757,79 @@ scan_wtext(wstring::const_iterator &si, // Now we have to encode the wstring into a string, for lookup // in the TextPropertiesManager. - string name = _encoder->encode_wtext(wname); + string graphic_name = _encoder->encode_wtext(graphic_wname); TextPropertiesManager *manager = TextPropertiesManager::get_global_ptr(); // Get the graphic image. - const TextGraphic *named_graphic = manager->get_graphic_ptr(name); + const TextGraphic *named_graphic = manager->get_graphic_ptr(graphic_name); if (named_graphic != (TextGraphic *)NULL) { - text_string.push_back(TextCharacter(named_graphic, current_properties)); + output_string.push_back(TextCharacter(named_graphic, graphic_wname, current_cprops)); } else { text_cat.warning() - << "Unknown TextGraphic: " << name << "\n"; + << "Unknown TextGraphic: " << graphic_name << "\n"; } } else { // A normal character. Apply it. - text_string.push_back(TextCharacter(*si, current_properties)); + output_string.push_back(TextCharacter(*si, current_cprops)); ++si; } } } +#endif // CPPPARSER //////////////////////////////////////////////////////////////////// // Function: TextAssembler::wordwrap_text // Access: Private -// Description: Inserts newlines into the given text at the +// Description: Inserts newlines into the _text_string 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). +// possible). Stores the result in _text_block. // -// If max_rows is greater than zero, no more than -// max_rows will be accepted. Text beyond that will be +// If _max_rows is greater than zero, no more than +// _max_rows will be accepted. Text beyond that will be // truncated. // // The return value is true if all the text is accepted, // or false if some was truncated. //////////////////////////////////////////////////////////////////// bool TextAssembler:: -wordwrap_text(const TextAssembler::TextString &text, - TextAssembler::TextString &output_text, - int max_rows) { - if (text.empty()) { +wordwrap_text() { + _text_block.clear(); + + if (_text_string.empty()) { // A special case: empty text means no rows. - _num_rows = 0; return true; } size_t p = 0; - _num_rows = 1; + + _text_block.push_back(TextRow(p)); // Preserve any initial whitespace and newlines. float initial_width = 0.0f; - while (p < text.size() && isspacew(text[p]._character)) { - if (text[p]._character == '\n') { + while (p < _text_string.size() && isspacew(_text_string[p]._character)) { + if (_text_string[p]._character == '\n') { initial_width = 0.0f; - if (max_rows > 0 && _num_rows >= max_rows) { + if (_max_rows > 0 && (int)_text_block.size() >= _max_rows) { // Truncate. return false; } - _num_rows++; + _text_block.push_back(TextRow(p + 1)); } else { - initial_width += calc_width(text[p]); + initial_width += calc_width(_text_string[p]); + _text_block.back()._string.push_back(_text_string[p]); } - output_text.push_back(text[p]); p++; } bool needs_newline = false; - while (p < text.size()) { - nassertr(!isspacew(text[p]._character), false); + while (p < _text_string.size()) { + nassertr(!isspacew(_text_string[p]._character), false); // Scan the next n characters, until the end of the string or an // embedded newline character, or we exceed wordwrap_width. @@ -530,15 +848,15 @@ wordwrap_text(const TextAssembler::TextString &text, bool last_was_space = false; float width = initial_width; - while (q < text.size() && text[q]._character != '\n') { - if (text[q]._properties->has_wordwrap()) { - wordwrap_width = text[q]._properties->get_wordwrap(); + while (q < _text_string.size() && _text_string[q]._character != '\n') { + if (_text_string[q]._cprops->_properties.has_wordwrap()) { + wordwrap_width = _text_string[q]._cprops->_properties.get_wordwrap(); } else { wordwrap_width = -1.0f; } - if (isspacew(text[q]._character) || - text[q]._character == text_soft_break_key) { + if (isspacew(_text_string[q]._character) || + _text_string[q]._character == text_soft_break_key) { if (!last_was_space) { any_spaces = true; // We only care about logging whether there is a soft-hyphen @@ -555,20 +873,20 @@ wordwrap_text(const TextAssembler::TextString &text, // A soft hyphen character is not printed, but marks a point // at which we might hyphenate a word if we need to. - if (text[q]._character == text_soft_hyphen_key && - wordwrap_width > 0.0f) { - // We only consider this as a possible hyphenation point if - // (a) it is not the very first character, and (b) there is - // enough room for a hyphen character to be printed following - // it. - if (q != p && width + calc_hyphen_width(text[q]) <= wordwrap_width) { - any_hyphens = true; - last_hyphen = q; + if (_text_string[q]._character == text_soft_hyphen_key) { + if (wordwrap_width > 0.0f) { + // We only consider this as a possible hyphenation point if + // (a) it is not the very first character, and (b) there is + // enough room for a hyphen character to be printed following + // it. + if (q != p && width + calc_hyphen_width(_text_string[q]) <= wordwrap_width) { + any_hyphens = true; + last_hyphen = q; + } } - - } else if (text[q]._character != text_soft_break_key) { + } else { // Some normal, printable character. - width += calc_width(text[q]); + width += calc_width(_text_string[q]); } q++; @@ -608,7 +926,7 @@ wordwrap_text(const TextAssembler::TextString &text, // of our forbidden characters. size_t i = 0; while ((int)i < text_max_never_break && q - i > p && - get_text_never_break_before().find(text[q - i]._character) != wstring::npos) { + get_text_never_break_before().find(_text_string[q - i]._character) != wstring::npos) { i++; } if ((int)i < text_max_never_break) { @@ -619,13 +937,13 @@ wordwrap_text(const TextAssembler::TextString &text, // Skip additional whitespace between the lines. size_t next_start = q; - while (next_start < text.size() && - isbreakpoint(text[next_start]._character)) { + while (next_start < _text_string.size() && + isbreakpoint(_text_string[next_start]._character)) { next_start++; } // Trim off any more blanks on the end. - while (q > p && isspacew(text[q - 1]._character)) { + while (q > p && isspacew(_text_string[q - 1]._character)) { q--; } @@ -640,31 +958,32 @@ wordwrap_text(const TextAssembler::TextString &text, // anyway; what else can we do? q++; next_start++; - while (next_start < text.size() && - isbreakpoint(text[next_start]._character)) { + while (next_start < _text_string.size() && + isbreakpoint(_text_string[next_start]._character)) { next_start++; } } } if (needs_newline) { - if (max_rows > 0 && _num_rows >= max_rows) { + if (_max_rows > 0 && (int)_text_block.size() >= _max_rows) { // Truncate. return false; } - _num_rows++; - output_text.push_back(TextCharacter('\n', text[next_start - 1]._properties)); + _text_block.push_back(TextRow(p)); } needs_newline = true; - if (text[next_start - 1]._properties->get_preserve_trailing_whitespace()) { + if (_text_string[next_start - 1]._cprops->_properties.get_preserve_trailing_whitespace()) { q = next_start; } for (size_t pi = p; pi < q; pi++) { - if (text[pi]._character != text_soft_hyphen_key && - text[pi]._character != text_soft_break_key) { - output_text.push_back(text[pi]); + if (_text_string[pi]._character != text_soft_hyphen_key && + _text_string[pi]._character != text_soft_break_key) { + _text_block.back()._string.push_back(_text_string[pi]); + } else { + _text_block.back()._got_soft_hyphens = true; } } if (output_hyphen) { @@ -673,39 +992,38 @@ wordwrap_text(const TextAssembler::TextString &text, for (wi = text_soft_hyphen_output.begin(); wi != text_soft_hyphen_output.end(); ++wi) { - output_text.push_back(TextCharacter(*wi, text[last_hyphen]._properties)); + _text_block.back()._string.push_back(TextCharacter(*wi, _text_string[last_hyphen]._cprops)); } } // Now prepare to wrap the next line. - if (next_start < text.size() && text[next_start]._character == '\n') { + if (next_start < _text_string.size() && _text_string[next_start]._character == '\n') { // Preserve a single embedded newline. - if (max_rows > 0 && _num_rows >= max_rows) { + if (_max_rows > 0 && (int)_text_block.size() >= _max_rows) { // Truncate. return false; } - _num_rows++; - output_text.push_back(text[next_start]); next_start++; + _text_block.push_back(TextRow(next_start)); needs_newline = false; } p = next_start; // Preserve any initial whitespace and newlines. initial_width = 0.0f; - while (p < text.size() && isspacew(text[p]._character)) { - if (text[p]._character == '\n') { + while (p < _text_string.size() && isspacew(_text_string[p]._character)) { + if (_text_string[p]._character == '\n') { initial_width = 0.0f; - if (max_rows > 0 && _num_rows >= max_rows) { + if (_max_rows > 0 && (int)_text_block.size() >= _max_rows) { // Truncate. return false; } - _num_rows++; + _text_block.push_back(TextRow(p + 1)); } else { - initial_width += calc_width(text[p]); + initial_width += calc_width(_text_string[p]); + _text_block.back()._string.push_back(_text_string[p]); } - output_text.push_back(text[p]); p++; } } @@ -722,7 +1040,7 @@ wordwrap_text(const TextAssembler::TextString &text, //////////////////////////////////////////////////////////////////// float TextAssembler:: calc_hyphen_width(const TextCharacter &tch) { - TextFont *font = tch._properties->get_font(); + TextFont *font = tch._cprops->_properties.get_font(); nassertr(font != (TextFont *)NULL, 0.0f); float hyphen_width = 0.0f; @@ -731,7 +1049,7 @@ calc_hyphen_width(const TextCharacter &tch) { for (wi = text_soft_hyphen_output.begin(); wi != text_soft_hyphen_output.end(); ++wi) { - hyphen_width += calc_width(*wi, *tch._properties); + hyphen_width += calc_width(*wi, tch._cprops->_properties); } return hyphen_width; @@ -740,24 +1058,27 @@ calc_hyphen_width(const TextCharacter &tch) { //////////////////////////////////////////////////////////////////// // Function: TextAssembler::assemble_paragraph // Access: Private -// Description: Fills up _placed_glyphs, _ul, _lr, and _num_rows with -// the contents of the indicated text. +// Description: Fills up placed_glyphs, _ul, _lr with +// the contents of _text_block. Also updates _xpos and +// _ypos within the _text_block structure. //////////////////////////////////////////////////////////////////// void TextAssembler:: -assemble_paragraph(TextAssembler::TextString::const_iterator si, - const TextAssembler::TextString::const_iterator &send, - TextAssembler::PlacedGlyphs &placed_glyphs) { +assemble_paragraph(TextAssembler::PlacedGlyphs &placed_glyphs) { _ul.set(0.0f, 0.0f); _lr.set(0.0f, 0.0f); int num_rows = 0; - float posy = 0.0f; - while (si != send) { + float ypos = 0.0f; + _next_row_ypos = 0.0f; + TextBlock::iterator bi; + for (bi = _text_block.begin(); bi != _text_block.end(); ++bi) { + TextRow &row = (*bi); + // First, assemble all the glyphs of this row. PlacedGlyphs row_placed_glyphs; float row_width, line_height; TextProperties::Alignment align; - assemble_row(si, send, row_placed_glyphs, + assemble_row(row, row_placed_glyphs, row_width, line_height, align); // Now move the row to its appropriate position. This might @@ -771,32 +1092,34 @@ assemble_paragraph(TextAssembler::TextString::const_iterator si, } else { // If it is not the first row, shift the text downward by // line_height from the previous row. - posy -= line_height; + ypos -= line_height; } - _lr[1] = posy - 0.2f * line_height; + _lr[1] = ypos - 0.2f * line_height; // Apply the requested horizontal alignment to the row. + float xpos; switch (align) { case TextProperties::A_left: - mat.set_row(3, LVector3f(0.0f, 0.0f, posy)); + xpos = 0.0f; _lr[0] = max(_lr[0], row_width); break; case TextProperties::A_right: - mat.set_row(3, LVector3f(-row_width, 0.0f, posy)); - _ul[0] = min(_ul[0], -row_width); + xpos = -row_width; + _ul[0] = min(_ul[0], xpos); break; case TextProperties::A_center: - { - 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); - } + xpos = -0.5f * row_width; + _ul[0] = min(_ul[0], xpos); + _lr[0] = max(_lr[0], -xpos); break; } + mat.set_row(3, LVector3f(xpos, 0.0f, ypos)); + row._xpos = xpos; + row._ypos = ypos; + // Now store the geoms we assembled. PlacedGlyphs::iterator pi; for (pi = row_placed_glyphs.begin(); pi != row_placed_glyphs.end(); ++pi) { @@ -806,10 +1129,11 @@ assemble_paragraph(TextAssembler::TextString::const_iterator si, // Advance to the next line. num_rows++; + _next_row_ypos = ypos - line_height; } - // num_rows may be smaller than _num_rows, if there are trailing - // newlines on the string. + // num_rows may be smaller than _text_block.size(), if there are + // trailing newlines on the string. } //////////////////////////////////////////////////////////////////// @@ -823,8 +1147,7 @@ assemble_paragraph(TextAssembler::TextString::const_iterator si, // source pointer is moved to the terminating character. //////////////////////////////////////////////////////////////////// void TextAssembler:: -assemble_row(TextAssembler::TextString::const_iterator &si, - const TextAssembler::TextString::const_iterator &send, +assemble_row(TextAssembler::TextRow &row, TextAssembler::PlacedGlyphs &row_placed_glyphs, float &row_width, float &line_height, TextProperties::Alignment &align) { @@ -834,10 +1157,12 @@ assemble_row(TextAssembler::TextString::const_iterator &si, float xpos = 0.0f; align = TextProperties::A_left; - while (si != send) { - wchar_t character = (*si)._character; - const TextGraphic *graphic = (*si)._graphic; - const TextProperties *properties = (*si)._properties; + TextString::const_iterator si; + for (si = row._string.begin(); si != row._string.end(); ++si) { + const TextCharacter &tch = (*si); + wchar_t character = tch._character; + const TextGraphic *graphic = tch._graphic; + const TextProperties *properties = &(tch._cprops->_properties); TextFont *font = properties->get_font(); nassertv(font != (TextFont *)NULL); @@ -855,13 +1180,6 @@ assemble_row(TextAssembler::TextString::const_iterator &si, line_height = max(line_height, font->get_line_height()); } - if (character == '\n') { - // The newline character marks the end of the row. - row_width = xpos; - ++si; - return; - } - if (character == ' ') { // A space is a special case. xpos += font->get_space_advance(); @@ -1013,7 +1331,6 @@ assemble_row(TextAssembler::TextString::const_iterator &si, xpos += advance * glyph_scale; } - ++si; } row_width = xpos; @@ -1527,6 +1844,45 @@ tack_on_accent(char accent_mark, TextAssembler::CheesyPosition position, return false; } +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::ComputedProperties::append_delta +// Access: Public +// Description: Appends to wtext the control sequences necessary to +// change from this ComputedProperties to the indicated +// ComputedProperties. +//////////////////////////////////////////////////////////////////// +void TextAssembler::ComputedProperties:: +append_delta(wstring &wtext, TextAssembler::ComputedProperties *other) { + if (this != other) { + if (_depth > other->_depth) { + // Back up a level from this properties. + nassertv(_based_on != NULL); + + wtext.push_back(text_pop_properties_key); + _based_on->append_delta(wtext, other); + + } else if (other->_depth > _depth) { + // Back up a level from the other properties. + nassertv(other->_based_on != NULL); + + append_delta(wtext, other->_based_on); + wtext.push_back(text_push_properties_key); + wtext += other->_wname; + wtext.push_back(text_push_properties_key); + + } else if (_depth != 0) { + // Back up a level from both properties. + nassertv(_based_on != NULL && other->_based_on != NULL); + + wtext.push_back(text_pop_properties_key); + _based_on->append_delta(wtext, other->_based_on); + wtext.push_back(text_push_properties_key); + wtext += other->_wname; + wtext.push_back(text_push_properties_key); + } + } +} + //////////////////////////////////////////////////////////////////// // Function: TextAssembler::GlyphPlacement::calc_tight_bounds // Access: Private @@ -1605,5 +1961,3 @@ copy_graphic_to(PandaNode *node, const RenderState *state, } } -#endif // CPPPARSER - diff --git a/panda/src/text/textAssembler.h b/panda/src/text/textAssembler.h index 4b66d9d0c8..51b0e3ed52 100644 --- a/panda/src/text/textAssembler.h +++ b/panda/src/text/textAssembler.h @@ -19,8 +19,6 @@ #ifndef TEXTASSEMBLER_H #define TEXTASSEMBLER_H -#ifndef CPPPARSER // interrogate has a bit of trouble with wstring iterators. - #include "pandabase.h" #include "textProperties.h" @@ -29,23 +27,27 @@ #include "geomNode.h" #include "pointerTo.h" #include "geom.h" +#include "textPropertiesManager.h" +#include "textEncoder.h" class TextEncoder; class TextGraphic; +class TextAssembler; //////////////////////////////////////////////////////////////////// // Class : TextAssembler -// Description : This class is not intended to be used directly by -// user code, but is used by the TextNode to lay out a -// block of text and convert it into rows of Geoms -// according to the TextProperties. -// -// It is not exported from the DLL since it is not -// intended to be used outside of this module. +// Description : This class is not normally used directly by user +// code, but is used by the TextNode to lay out a block +// of text and convert it into rows of Geoms according +// to the TextProperties. However, user code may take +// advantage of it, if desired, for very low-level text +// operations. //////////////////////////////////////////////////////////////////// -class TextAssembler { -public: +class EXPCL_PANDA TextAssembler { +PUBLISHED: TextAssembler(TextEncoder *encoder); + TextAssembler(const TextAssembler ©); + void operator = (const TextAssembler ©); ~TextAssembler(); void clear(); @@ -53,11 +55,41 @@ public: INLINE void set_usage_hint(Geom::UsageHint usage_hint); INLINE Geom::UsageHint get_usage_hint() const; - bool set_wtext(const wstring &wtext, const TextProperties &properties, - int max_rows = 0); - INLINE int get_num_rows() const; + INLINE bool set_max_rows(int max_rows); + INLINE int get_max_rows() const; + + INLINE void set_properties(const TextProperties &properties); + INLINE const TextProperties &get_properties() const; + + bool set_wtext(const wstring &wtext); + bool set_wsubstr(const wstring &wtext, int start, int count, + const TextProperties &properties = TextProperties()); + + wstring get_plain_wtext() const; + wstring get_wordwrapped_plain_wtext() const; + wstring get_wtext() const; wstring get_wordwrapped_wtext() const; + bool calc_r_c(int &r, int &c, int n) const; + INLINE int calc_r(int n) const; + INLINE int calc_c(int n) const; + int calc_index(int r, int c) const; + + INLINE int get_num_characters() const; + INLINE wchar_t get_character(int n) const; + INLINE const TextGraphic *get_graphic(int n) const; + INLINE const TextProperties &get_properties(int n) const; + INLINE float get_width(int n) const; + + INLINE int get_num_rows() const; + INLINE int get_num_cols(int r) const; + INLINE wchar_t get_character(int r, int c) const; + INLINE const TextGraphic *get_graphic(int r, int c) const; + INLINE const TextProperties &get_properties(int r, int c) const; + INLINE float get_width(int r, int c) const; + float get_xpos(int r, int c) const; + INLINE float get_ypos(int r, int c) const; + PT(PandaNode) assemble_text(); INLINE const LVector2f &get_ul() const; @@ -67,33 +99,69 @@ public: static float calc_width(const TextGraphic *graphic, const TextProperties &properties); private: + class ComputedProperties : public ReferenceCount { + public: + INLINE ComputedProperties(const TextProperties &orig_properties); + INLINE ComputedProperties(ComputedProperties *based_on, + const wstring &wname, TextEncoder *encoder); + void append_delta(wstring &wtext, ComputedProperties *other); + + PT(ComputedProperties) _based_on; + int _depth; + wstring _wname; + TextProperties _properties; + }; + // These structures are built up and operated on by scan_wtext() and // wordwrap_text(). It represents the unrolling of the embedded \1 // .. \2 sequences embedded in the string into a TextProperties // pointer associated with each character. - typedef pvector PropertiesList; - class TextCharacter { public: - INLINE TextCharacter(wchar_t character, const TextProperties *properties); - INLINE TextCharacter(const TextGraphic *graphic, const TextProperties *properties); + INLINE TextCharacter(wchar_t character, ComputedProperties *cprops); + INLINE TextCharacter(const TextGraphic *graphic, + const wstring &graphic_wname, + ComputedProperties *cprops); + INLINE TextCharacter(const TextCharacter ©); + INLINE void operator = (const TextCharacter ©); + wchar_t _character; const TextGraphic *_graphic; - const TextProperties *_properties; + wstring _graphic_wname; + PT(ComputedProperties) _cprops; }; typedef pvector TextString; - PropertiesList _properties_list; - TextString _wordwrapped_string; - int _num_rows; + class TextRow { + public: + INLINE TextRow(int row_start); + INLINE TextRow(const TextRow ©); + INLINE void operator = (const TextRow ©); - void scan_wtext(wstring::const_iterator &si, + TextString _string; + int _row_start; + bool _got_soft_hyphens; + float _xpos; + float _ypos; + }; + typedef pvector TextBlock; + + PT(ComputedProperties) _initial_cprops; + + // This is the string, unwordwrapped. + TextString _text_string; + + // And here it is, wordwrapped. + TextBlock _text_block; + +#ifndef CPPPARSER // interrogate has a bit of trouble with wstring iterators. + void scan_wtext(TextString &output_string, + wstring::const_iterator &si, const wstring::const_iterator &send, - const TextProperties *current_properties, - TextString &text_string); - bool wordwrap_text(const TextAssembler::TextString &text, - TextAssembler::TextString &output_text, - int max_rows); + ComputedProperties *current_cprops); +#endif // CPPPARSER + + bool wordwrap_text(); INLINE static float calc_width(const TextCharacter &tch); static float calc_hyphen_width(const TextCharacter &tch); @@ -127,11 +195,8 @@ private: }; typedef pvector PlacedGlyphs; - void assemble_paragraph(TextString::const_iterator si, - const TextString::const_iterator &send, - PlacedGlyphs &placed_glyphs); - void assemble_row(TextString::const_iterator &si, - const TextString::const_iterator &send, + void assemble_paragraph(PlacedGlyphs &placed_glyphs); + void assemble_row(TextRow &row, PlacedGlyphs &row_placed_glyphs, float &row_width, float &line_height, TextProperties::Alignment &align); @@ -188,14 +253,14 @@ private: // These are filled in by assemble_paragraph(). LVector2f _ul; LVector2f _lr; + float _next_row_ypos; TextEncoder *_encoder; Geom::UsageHint _usage_hint; + int _max_rows; }; #include "textAssembler.I" -#endif // CPPPARSER - #endif diff --git a/panda/src/text/textNode.I b/panda/src/text/textNode.I index 5d5eeffb0b..1a7242a3ef 100644 --- a/panda/src/text/textNode.I +++ b/panda/src/text/textNode.I @@ -47,6 +47,7 @@ get_line_height() const { INLINE void TextNode:: set_max_rows(int max_rows) { _max_rows = max_rows; + _assembler.set_max_rows(_max_rows); invalidate_with_measure(); } @@ -59,6 +60,7 @@ set_max_rows(int max_rows) { INLINE void TextNode:: clear_max_rows() { _max_rows = 0; + _assembler.set_max_rows(_max_rows); invalidate_with_measure(); } @@ -1183,8 +1185,10 @@ append_unicode_char(int character) { // Access: Public // Description: Returns a string that represents the contents of the // text, as it has been formatted by wordwrap rules. -// This will not contain any embedded special characters -// like \1 or \3. +// +// In earlier versions, this did not contain any +// embedded special characters like \1 or \3; now it +// does. //////////////////////////////////////////////////////////////////// INLINE string TextNode:: get_wordwrapped_text() const { @@ -1234,8 +1238,10 @@ append_wtext(const wstring &wtext) { // Access: Published // Description: Returns a wstring that represents the contents of the // text, as it has been formatted by wordwrap rules. -// This will not contain any embedded special characters -// like \1 or \3. +// +// In earlier versions, this did not contain any +// embedded special characters like \1 or \3; now it +// does. //////////////////////////////////////////////////////////////////// INLINE wstring TextNode:: get_wordwrapped_wtext() const { diff --git a/panda/src/text/textNode.cxx b/panda/src/text/textNode.cxx index b64ab1d422..243ded43a9 100644 --- a/panda/src/text/textNode.cxx +++ b/panda/src/text/textNode.cxx @@ -302,7 +302,8 @@ generate() { wstring wtext = get_wtext(); // Assemble the text. - bool all_set = _assembler.set_wtext(wtext, *this, _max_rows); + _assembler.set_properties(*this); + bool all_set = _assembler.set_wtext(wtext); if (all_set) { // No overflow. _flags &= ~F_has_overflow; diff --git a/panda/src/text/textNode.h b/panda/src/text/textNode.h index 27da20687b..9baf60c5fd 100644 --- a/panda/src/text/textNode.h +++ b/panda/src/text/textNode.h @@ -296,9 +296,7 @@ private: LPoint3f _ul3d, _lr3d; -#ifndef CPPPARSER TextAssembler _assembler; -#endif public: static TypeHandle get_class_type() { diff --git a/panda/src/text/textProperties.I b/panda/src/text/textProperties.I index 0fcccae931..0516288799 100644 --- a/panda/src/text/textProperties.I +++ b/panda/src/text/textProperties.I @@ -17,6 +17,16 @@ //////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::operator != +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +operator != (const TextProperties &other) const { + return !operator == (other); +} + //////////////////////////////////////////////////////////////////// // Function: TextProperties::is_any_specified // Access: Published diff --git a/panda/src/text/textProperties.cxx b/panda/src/text/textProperties.cxx index bb8662f22e..06afb821da 100644 --- a/panda/src/text/textProperties.cxx +++ b/panda/src/text/textProperties.cxx @@ -91,6 +91,71 @@ operator = (const TextProperties ©) { _glyph_shift = copy._glyph_shift; } +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::operator == +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +bool TextProperties:: +operator == (const TextProperties &other) const { + if (_specified != other._specified) { + return false; + } + + if ((_specified & F_has_font) && _font != other._font) { + return false; + } + if ((_specified & F_has_small_caps) && _small_caps != other._small_caps) { + return false; + } + if ((_specified & F_has_small_caps_scale) && _small_caps_scale != other._small_caps_scale) { + return false; + } + if ((_specified & F_has_slant) && _slant != other._slant) { + return false; + } + if ((_specified & F_has_align) && _align != other._align) { + return false; + } + if ((_specified & F_has_indent) && _indent_width != other._indent_width) { + return false; + } + if ((_specified & F_has_wordwrap) && _wordwrap_width != other._wordwrap_width) { + return false; + } + if ((_specified & F_has_preserve_trailing_whitespace) && _preserve_trailing_whitespace != other._preserve_trailing_whitespace) { + return false; + } + if ((_specified & F_has_text_color) && _text_color != other._text_color) { + return false; + } + if ((_specified & F_has_text_color) && _text_color != other._text_color) { + return false; + } + if ((_specified & F_has_shadow_color) && _shadow_color != other._shadow_color) { + return false; + } + if ((_specified & F_has_shadow) && _shadow_offset != other._shadow_offset) { + return false; + } + if ((_specified & F_has_bin) && _bin != other._bin) { + return false; + } + if ((_specified & F_has_draw_order) && _draw_order != other._draw_order) { + return false; + } + if ((_specified & F_has_tab_width) && _tab_width != other._tab_width) { + return false; + } + if ((_specified & F_has_glyph_scale) && _glyph_scale != other._glyph_scale) { + return false; + } + if ((_specified & F_has_glyph_shift) && _glyph_shift != other._glyph_shift) { + return false; + } + return true; +} + //////////////////////////////////////////////////////////////////// // Function: TextProperties::clear // Access: Published diff --git a/panda/src/text/textProperties.h b/panda/src/text/textProperties.h index 2c7a5f45ba..f8547f4d26 100644 --- a/panda/src/text/textProperties.h +++ b/panda/src/text/textProperties.h @@ -59,6 +59,9 @@ PUBLISHED: TextProperties(const TextProperties ©); void operator = (const TextProperties ©); + bool operator == (const TextProperties &other) const; + INLINE bool operator != (const TextProperties &other) const; + void clear(); INLINE bool is_any_specified() const;