diff --git a/panda/src/text/qptextNode.I b/panda/src/text/qptextNode.I new file mode 100644 index 0000000000..bd6940df20 --- /dev/null +++ b/panda/src/text/qptextNode.I @@ -0,0 +1,1295 @@ +// Filename: qptextNode.I +// Created by: drose (13Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::freeze +// Access: Published +// Description: Freezes the qpTextNode in its current state, so that +// updates will not immediately be displayed. A series +// of state changes may then be applied in succession, +// which will not force the qpTextNode to be recomputed. +// When thaw() is later called, the qpTextNode will update +// itself exactly once to reflect all the state changes +// that were made. +// +// freeze() and thaw() can nest. Strictly speaking, +// each call to freeze() increments the current freeze +// level, while each call to thaw() decrements it. The +// qpTextNode will only be updated when the current freeze +// level is zero. +// +// The return value of freeze() is the freeze level +// *before* the freeze took place. This number should +// match the return value of the matching thaw(). +//////////////////////////////////////////////////////////////////// +INLINE int qpTextNode:: +freeze() { + if (text_cat.is_debug()) { + text_cat.debug() + << "Freezing " << this->get_name() << ", level = " + << _freeze_level << "\n"; + } + return _freeze_level++; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_freeze_level +// Access: Published +// Description: Returns the current freeze level. The qpTextNode will +// not be updated visually unless this number is zero. +// See freeze(). +//////////////////////////////////////////////////////////////////// +INLINE int qpTextNode:: +get_freeze_level() const { + return _freeze_level; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::thaw +// Access: Published +// Description: Allows changes made since the last freeze() to be +// visible. Strictly speaking, this actually decrements +// the freeze level, and updates the qpTextNode if the +// level reaches zero. The return value is the new +// freeze level after adjusting. See freeze(). +//////////////////////////////////////////////////////////////////// +INLINE int qpTextNode:: +thaw() { + if (text_cat.is_debug()) { + text_cat.debug() + << "Thawing " << this->get_name() << ", level = " + << _freeze_level-1 << "\n"; + } + nassertr(_freeze_level > 0, _freeze_level); + _freeze_level--; + + if (_freeze_level == 0 && _needs_rebuild) { + do_rebuild(); + } + + return _freeze_level; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_font +// Access: Published +// Description: Sets the font that will be used when making text. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_font(TextFont *font) { + if (_font != font) { + _font = font; + rebuild(true); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_font +// Access: Published +// Description: Returns the font currently in use. +//////////////////////////////////////////////////////////////////// +INLINE TextFont *qpTextNode:: +get_font() const { + return _font; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_encoding +// Access: Published +// Description: Specifies how the string set via set_text() is to be +// interpreted. The default, E_iso8859, means a +// standard string with one-byte characters +// (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 qpTextNode:: +set_encoding(qpTextNode::Encoding encoding) { + _encoding = encoding; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_encoding +// Access: Published +// Description: Returns the encoding by which the string set via +// set_text() is to be interpreted. See set_encoding(). +//////////////////////////////////////////////////////////////////// +INLINE qpTextNode::Encoding qpTextNode:: +get_encoding() const { + return _encoding; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_expand_amp +// Access: Published +// Description: Sets the state of the expand_amp flag. When this is +// true, embedded ampersands in the text string are +// 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 qpTextNode:: +set_expand_amp(bool expand_amp) { + if (expand_amp) { + _flags |= F_expand_amp; + } else { + _flags &= ~F_expand_amp; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_expand_amp +// Access: Published +// Description: Returns the state of the expand_amp flag. See +// set_expand_amp(). +//////////////////////////////////////////////////////////////////// +INLINE bool qpTextNode:: +get_expand_amp() const { + return (_flags & F_expand_amp) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_line_height +// Access: Published +// Description: Returns the number of units high each line of text +// is. This is based on the font. +//////////////////////////////////////////////////////////////////// +INLINE float qpTextNode:: +get_line_height() const { + return (_font == (TextFont *)NULL) ? 0.0 : _font->get_line_height(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_slant +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_slant(float slant) { + if (_slant != slant) { + _slant = slant; + rebuild(true); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_slant +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE float qpTextNode:: +get_slant() const { + return _slant; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_align +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_align(qpTextNode::Alignment align_type) { + if (_align != align_type) { + _align = align_type; + rebuild(true); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_align +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE qpTextNode::Alignment qpTextNode:: +get_align() const { + return _align; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_wordwrap +// Access: Published +// Description: Sets the qpTextNode up to automatically wordwrap text +// that exceeds the indicated width. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_wordwrap(float wordwrap) { + if (!has_wordwrap() || _wordwrap_width != wordwrap) { + _flags |= F_has_wordwrap; + _wordwrap_width = wordwrap; + rebuild(true); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::clear_wordwrap +// Access: Published +// Description: Removes the wordwrap setting from the qpTextNode. Text +// will be as wide as it is. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +clear_wordwrap() { + if (has_wordwrap()) { + _flags &= ~F_has_wordwrap; + rebuild(true); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::has_wordwrap +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool qpTextNode:: +has_wordwrap() const { + return (_flags & F_has_wordwrap) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_wordwrap +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE float qpTextNode:: +get_wordwrap() const { + return _wordwrap_width; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_text_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_text_color(float r, float g, float b, float a) { + set_text_color(Colorf(r, g, b, a)); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_text_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_text_color(const Colorf &text_color) { + if (!has_text_color() || _text_color != text_color) { + _text_color = text_color; + _flags |= F_has_text_color; + rebuild(false); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::clear_text_color +// Access: Published +// Description: Removes the text color specification; the text will +// be colored whatever it was in the source font file. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +clear_text_color() { + if (has_text_color()) { + _flags &= ~F_has_text_color; + rebuild(false); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::has_text_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool qpTextNode:: +has_text_color() const { + return (_flags & F_has_text_color) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_text_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE Colorf qpTextNode:: +get_text_color() const { + return _text_color; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_frame_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_frame_color(float r, float g, float b, float a) { + set_frame_color(Colorf(r, g, b, a)); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_frame_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_frame_color(const Colorf &frame_color) { + if (_frame_color != frame_color) { + _frame_color = frame_color; + rebuild(false); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_frame_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE Colorf qpTextNode:: +get_frame_color() const { + return _frame_color; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_card_border +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_card_border(float size, float uv_portion) { + if (!has_card_border() || _card_border_size != size || _card_border_uv_portion != uv_portion) { + _flags |= F_has_card_border; + _card_border_size = size; + _card_border_uv_portion = uv_portion; + rebuild(false); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::clear_card_border +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +clear_card_border() { + if (has_card_border()) { + _flags &= ~F_has_card_border; + rebuild(false); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_card_border_size +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE float qpTextNode:: +get_card_border_size() const { + return _card_border_size; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_card_border_uv_portion +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE float qpTextNode:: +get_card_border_uv_portion() const { + return _card_border_uv_portion; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::has_card_border +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool qpTextNode:: +has_card_border() const { + return (_flags & F_has_card_border) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_card_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_card_color(float r, float g, float b, float a) { + set_card_color(Colorf(r, g, b, a)); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_card_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_card_color(const Colorf &card_color) { + if (_card_color != card_color) { + _card_color = card_color; + rebuild(false); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_card_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE Colorf qpTextNode:: +get_card_color() const { + return _card_color; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_card_texture +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_card_texture(Texture *card_texture) { + if (card_texture == (Texture *)NULL) { + clear_card_texture(); + } else { + if (!has_card_texture() || _card_texture != card_texture) { + _flags |= F_has_card_texture; + _card_texture = card_texture; + rebuild(false); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::clear_card_texture +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +clear_card_texture() { + if (has_card_texture()) { + _flags &= ~F_has_card_texture; + _card_texture = NULL; + rebuild(false); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::has_card_texture +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool qpTextNode:: +has_card_texture() const { + return (_flags & F_has_card_texture) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_card_texture +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE Texture *qpTextNode:: +get_card_texture() const { + return _card_texture; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_shadow_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_shadow_color(float r, float g, float b, float a) { + set_shadow_color(Colorf(r, g, b, a)); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_shadow_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_shadow_color(const Colorf &shadow_color) { + if (_shadow_color != shadow_color) { + _shadow_color = shadow_color; + rebuild(false); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_shadow_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE Colorf qpTextNode:: +get_shadow_color() const { + return _shadow_color; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_frame_as_margin +// Access: Published +// Description: Specifies that a border will be drawn around the text +// when it is next created. The parameters are the +// amount of additional padding to insert between the +// frame and the text in each dimension, and all should +// generally be positive. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_frame_as_margin(float left, float right, float bottom, float top) { + _flags |= (F_has_frame | F_frame_as_margin); + _frame_ul.set(left, top); + _frame_lr.set(right, bottom); + rebuild(false); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_frame_actual +// Access: Published +// Description: Similar to set_frame_as_margin, except the frame is +// specified in actual coordinate units (relative to +// the text's origin), irrespective of the size of the +// text. The left and bottom coordinates should +// generally be negative, while the right and top +// coordinates should generally be positive. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_frame_actual(float left, float right, float bottom, float top) { + _flags |= F_has_frame; + _flags &= ~F_frame_as_margin; + _frame_ul.set(left, top); + _frame_lr.set(right, bottom); + rebuild(false); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::clear_frame +// Access: Published +// Description: Specifies that a border will not be drawn around the +// text. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +clear_frame() { + _flags &= ~F_has_frame; + rebuild(false); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::has_frame +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool qpTextNode:: +has_frame() const { + return (_flags & F_has_frame) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::is_frame_as_margin +// Access: Published +// Description: If this is true, the frame was set via a call to +// set_frame_as_margin(), and the dimension of the frame +// as returned by get_frame_as_set() represent a margin +// all around the text. If false, then the frame was +// set via a call to set_frame_actual(), and the +// dimensions of the frame as returned by +// get_frame_as_set() are relative to the text's origin. +//////////////////////////////////////////////////////////////////// +INLINE bool qpTextNode:: +is_frame_as_margin() const { + nassertr(has_frame(), false); + return (_flags & F_frame_as_margin) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_frame_as_set +// Access: Published +// Description: Returns the dimensions of the frame as set by +// set_frame_as_margin() or set_frame_actual(). Use +// is_frame_actual() to determine how to interpret the +// values returned by this function. It is an error to +// call this if has_frame() is false. +//////////////////////////////////////////////////////////////////// +INLINE LVecBase4f qpTextNode:: +get_frame_as_set() const { + nassertr(has_frame(), LVecBase4f(0.0, 0.0, 0.0, 0.0)); + return LVecBase4f(_frame_ul[0], _frame_lr[0], _frame_lr[1], _frame_ul[1]); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_frame_actual +// Access: Published +// Description: Returns the actual dimensions of the frame around the +// text. If the frame was set via set_frame_as_margin(), +// the result returned by this function reflects the +// size of the current text; if the frame was set via +// set_frame_actual(), this returns the values +// actually set. +//////////////////////////////////////////////////////////////////// +INLINE LVecBase4f qpTextNode:: +get_frame_actual() const { + nassertr(has_frame(), LVecBase4f(0.0, 0.0, 0.0, 0.0)); + if (is_frame_as_margin()) { + return LVecBase4f(get_left() - _frame_ul[0], + get_right() + _frame_lr[0], + get_bottom() - _frame_lr[1], + get_top() + _frame_ul[1]); + } else { + return get_frame_as_set(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_frame_line_width +// Access: Published +// Description: Specifies the thickness of the lines that will be +// used to draw the frame. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_frame_line_width(float frame_width) { + _frame_width = frame_width; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_frame_line_width +// Access: Published +// Description: Returns the thickness of the lines that will be +// used to draw the frame. +//////////////////////////////////////////////////////////////////// +INLINE float qpTextNode:: +get_frame_line_width() const { + return _frame_width; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_frame_corners +// Access: Published +// Description: Enables or disables the drawing of corners for the +// frame. These are extra points drawn at each of the +// four corners, to soften the ugly edges generated when +// the line width is greater than one. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_frame_corners(bool corners) { + if (corners) { + _flags |= F_frame_corners; + } else { + _flags &= ~F_frame_corners; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_frame_corners +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool qpTextNode:: +get_frame_corners() const { + return (_flags & F_frame_corners) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_card_as_margin +// Access: Published +// Description: Specifies that a (possibly opaque or semitransparent) +// card will be held behind the text when it is next +// created. Like set_frame_as_margin, the parameters are +// the amount of additional padding to insert around the +// text in each dimension, and all should generally be +// positive. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_card_as_margin(float left, float right, float bottom, float top) { + _flags |= (F_has_card | F_card_as_margin); + _card_ul.set(left, top); + _card_lr.set(right, bottom); + rebuild(false); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_card_actual +// Access: Published +// Description: Similar to set_card_as_margin, except the card is +// specified in actual coordinate units (relative to +// the text's origin), irrespective of the size of the +// text. The left and bottom coordinates should +// generally be negative, while the right and top +// coordinates should generally be positive. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_card_actual(float left, float right, float bottom, float top) { + _flags |= F_has_card; + _flags &= ~F_card_as_margin; + _card_ul.set(left, top); + _card_lr.set(right, bottom); + rebuild(false); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::clear_card +// Access: Published +// Description: Specifies that a card will not be drawn behind the +// text. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +clear_card() { + _flags &= ~F_has_card; + rebuild(false); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::has_card +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool qpTextNode:: +has_card() const { + return (_flags & F_has_card) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::is_card_as_margin +// Access: Published +// Description: If this is true, the card was set via a call to +// set_card_as_margin(), and the dimension of the card +// as returned by get_card_as_set() represent a margin +// all around the text. If false, then the card was +// set via a call to set_card_actual(), and the +// dimensions of the card as returned by +// get_card_as_set() are relative to the text's origin. +//////////////////////////////////////////////////////////////////// +INLINE bool qpTextNode:: +is_card_as_margin() const { + nassertr(has_card(), false); + return (_flags & F_card_as_margin) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_card_as_set +// Access: Published +// Description: Returns the dimensions of the card as set by +// set_card_as_margin() or set_card_actual(). Use +// is_card_actual() to determine how to interpret the +// values returned by this function. It is an error to +// call this if has_card() is false. +//////////////////////////////////////////////////////////////////// +INLINE LVecBase4f qpTextNode:: +get_card_as_set() const { + nassertr(has_card(), LVecBase4f(0.0, 0.0, 0.0, 0.0)); + return LVecBase4f(_card_ul[0], _card_lr[0], _card_lr[1], _card_ul[1]); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_card_actual +// Access: Published +// Description: Returns the actual dimensions of the card around the +// text. If the card was set via set_card_as_margin(), +// the result returned by this function reflects the +// size of the current text; if the card was set via +// set_card_actual(), this returns the values +// actually set. +// +// If the text has no card at all, this returns the +// dimensions of the text itself, as if the card were +// set with a margin of 0, 0, 0, 0. +//////////////////////////////////////////////////////////////////// +INLINE LVecBase4f qpTextNode:: +get_card_actual() const { + if (!has_card()) { + return LVecBase4f(get_left(), get_right(), get_bottom(), get_top()); + + } else if (is_card_as_margin()) { + return LVecBase4f(get_left() - _card_ul[0], + get_right() + _card_lr[0], + get_bottom() - _card_lr[1], + get_top() + _card_ul[1]); + } else { + return get_card_as_set(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_card_transformed +// Access: Published +// Description: Returns the actual card dimensions, transformed by +// the matrix set by set_transform(). This returns the +// card dimensions in actual coordinates as seen by the +// rest of the world. Also see get_upper_left_3d() and +// get_lower_right_3d(). +//////////////////////////////////////////////////////////////////// +INLINE LVecBase4f qpTextNode:: +get_card_transformed() const { + LVecBase4f card = get_card_actual(); + LPoint3f ul = LPoint3f(card[0], 0.0, card[3]) * _transform; + LPoint3f lr = LPoint3f(card[1], 0.0, card[2]) * _transform; + + return LVecBase4f(ul[0], lr[0], lr[2], ul[2]); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_shadow +// Access: Published +// Description: Specifies that the text should be drawn with a +// shadow, by creating a second copy of the text and +// offsetting it slightly behind the first. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_shadow(float xoffset, float yoffset) { + _flags |= F_has_shadow; + _shadow_offset.set(xoffset, yoffset); + rebuild(false); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::clear_shadow +// Access: Published +// Description: Specifies that a shadow will not be drawn behind the +// text. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +clear_shadow() { + _flags &= ~F_has_shadow; + rebuild(false); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::has_shadow +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool qpTextNode:: +has_shadow() const { + return (_flags & F_has_shadow) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_shadow +// Access: Published +// Description: Returns the offset of the shadow as set by +// set_shadow(). It is an error to call this if +// has_shadow() is false. +//////////////////////////////////////////////////////////////////// +INLINE LVecBase2f qpTextNode:: +get_shadow() const { + nassertr(has_shadow(), LVecBase2f(0.0, 0.0)); + return _shadow_offset; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_bin +// Access: Published +// Description: Names the GeomBin that the qpTextNode geometry should +// be assigned to. If this is set, then a +// GeomBinTransition will be created to explicitly place +// each component in the named bin. +// +// The draw_order value will also be passed to each +// GeomBinTransition as appropriate; this is +// particularly useful if this names a GeomBinFixed, +// e.g. "fixed". +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_bin(const string &bin) { + _bin = bin; + rebuild(false); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::clear_bin +// Access: Published +// Description: Removes the effect of a previous call to +// set_bin(). Text will be drawn in whatever bin +// it would like to be drawn in, with no explicit +// ordering. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +clear_bin() { + _bin = string(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::has_bin +// Access: Published +// Description: Returns true if an explicit drawing bin has been +// set via set_bin(), false otherwise. +//////////////////////////////////////////////////////////////////// +INLINE bool qpTextNode:: +has_bin() const { + return !_bin.empty(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_bin +// Access: Published +// Description: Returns the drawing bin set with set_bin(), or empty +// string if no bin has been set. +//////////////////////////////////////////////////////////////////// +INLINE const string &qpTextNode:: +get_bin() const { + return _bin; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_draw_order +// Access: Published +// Description: Sets the drawing order of text created by the +// TextMaker. This is actually the draw order of the +// card and frame. The shadow is drawn at +// _draw_order+1, and the text at _draw_order+2. +// +// This affects the sorting order assigned to the arcs +// as they are created, and also is passed to whatever +// bin may be assigned via set_bin(). +// +// The return value is the first unused draw_order +// number, e.g. _draw_order + 3. +//////////////////////////////////////////////////////////////////// +INLINE int qpTextNode:: +set_draw_order(int draw_order) { + _draw_order = draw_order; + rebuild(false); + return _draw_order + 3; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_draw_order +// Access: Published +// Description: Returns the drawing order set with set_draw_order(). +//////////////////////////////////////////////////////////////////// +INLINE int qpTextNode:: +get_draw_order() const { + return _draw_order; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_transform +// Access: Published +// Description: Sets an additional transform that is applied to the +// entire text paragraph. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_transform(const LMatrix4f &transform) { + _transform = transform; + rebuild(true); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_transform +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE LMatrix4f qpTextNode:: +get_transform() const { + return _transform; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_coordinate_system +// Access: Published +// Description: Specifies the coordinate system in which the text +// will be generated. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_coordinate_system(CoordinateSystem coordinate_system) { + _coordinate_system = coordinate_system; + rebuild(true); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_coordinate_system +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE CoordinateSystem qpTextNode:: +get_coordinate_system() const { + return _coordinate_system; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_text +// Access: Published +// Description: Changes the text that is displayed under the +// qpTextNode. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_text(const string &text) { + _text = text; + _wtext = decode_text(text); + rebuild(true); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::clear_text +// Access: Published +// Description: Removes the text from the qpTextNode. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +clear_text() { + _text = ""; + rebuild(true); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::has_text +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool qpTextNode:: +has_text() const { + return !_text.empty(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_text +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE string qpTextNode:: +get_text() const { + return _text; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::calc_width +// Access: Published +// Description: Returns the width of a single character of the font, +// or 0.0 if the character is not known. +//////////////////////////////////////////////////////////////////// +INLINE float qpTextNode:: +calc_width(int character) const { + nassertr(_font != (TextFont *)NULL, 0.0); + return _font->calc_width(character); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::calc_width +// Access: Published +// Description: Returns the width of a line of text of arbitrary +// characters. The line should not include the newline +// character. +//////////////////////////////////////////////////////////////////// +INLINE float qpTextNode:: +calc_width(const string &line) const { + nassertr(_font != (TextFont *)NULL, 0.0); + return _font->calc_width(decode_text(line)); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::rebuild +// Access: Published +// Description: Updates the qpTextNode, if it is not frozen, or marks +// the qpTextNode as requiring an update if it is. If the +// text is currently frozen, nothing will be done until +// it is thawed, unless needs_measure is true, in which +// case the text will be re-measured even if it is +// currently frozen. +// +// Normally, this function is called automatically +// whenever any of the parameters changes. It should +// not need to be called explicitly unless something +// goes wrong. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +rebuild(bool needs_measure) { + if (_freeze_level <= 0) { + do_rebuild(); + } else { + _needs_rebuild = true; + + if (needs_measure) { + measure(); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::measure +// Access: Published +// Description: Measures the extent of the text as it will be placed, +// without actually placing it. Normally, this function +// is called automatically whenever any of the +// parameters changes. It should not need to be called +// explicitly unless something goes wrong. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +measure() { + do_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_left +// Access: Published +// Description: Returns the leftmost extent of the text in local 2-d +// coordinates, unmodified by the set_transform() +// matrix. +//////////////////////////////////////////////////////////////////// +INLINE float qpTextNode:: +get_left() const { + return _ul2d[0]; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_right +// Access: Published +// Description: Returns the rightmost extent of the text in local 2-d +// coordinates, unmodified by the set_transform() +// matrix. +//////////////////////////////////////////////////////////////////// +INLINE float qpTextNode:: +get_right() const { + return _lr2d[0]; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_bottom +// Access: Published +// Description: Returns the bottommost extent of the text in local +// 2-d coordinates, unmodified by the set_transform() +// matrix. +//////////////////////////////////////////////////////////////////// +INLINE float qpTextNode:: +get_bottom() const { + return _lr2d[1]; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_top +// Access: Published +// Description: Returns the topmost extent of the text in local 2-d +// coordinates, unmodified by the set_transform() +// matrix. +//////////////////////////////////////////////////////////////////// +INLINE float qpTextNode:: +get_top() const { + return _ul2d[1]; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_height +// Access: Published +// Description: Returns the net height of the text in local 2-d +// coordinates. +//////////////////////////////////////////////////////////////////// +INLINE float qpTextNode:: +get_height() const { + return get_top() - get_bottom(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_width +// Access: Published +// Description: Returns the net width of the text in local 2-d +// coordinates. +//////////////////////////////////////////////////////////////////// +INLINE float qpTextNode:: +get_width() const { + return get_right() - get_left(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_upper_left_3d +// Access: Published +// Description: Returns the upper-left extent of the text object, +// after it has been transformed into 3-d space by +// applying the set_transform() matrix. +//////////////////////////////////////////////////////////////////// +INLINE LPoint3f qpTextNode:: +get_upper_left_3d() const { + return _ul3d; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_lower_right_3d +// Access: Published +// Description: Returns the lower-right extent of the text object, +// after it has been transformed into 3-d space by +// applying the set_transform() matrix. +//////////////////////////////////////////////////////////////////// +INLINE LPoint3f qpTextNode:: +get_lower_right_3d() const { + return _lr3d; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_num_rows +// Access: Published +// Description: Returns the number of rows of text that were +// generated. This counts word-wrapped rows as well as +// rows generated due to embedded newlines. +//////////////////////////////////////////////////////////////////// +INLINE int qpTextNode:: +get_num_rows() const { + return _num_rows; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::set_wtext +// Access: Public +// Description: Changes the text that is displayed under the +// qpTextNode, with a wide text. This automatically sets +// the string reported by get_text() to the 8-bit +// encoded version of the same string. +//////////////////////////////////////////////////////////////////// +INLINE void qpTextNode:: +set_wtext(const wstring &wtext) { + _wtext = wtext; + _text = encode_wtext(wtext); + rebuild(true); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::get_wtext +// Access: Public +// Description: Returns the text associated with the qpTextNode, as a +// wide-character string. +//////////////////////////////////////////////////////////////////// +INLINE const wstring &qpTextNode:: +get_wtext() const { + return _wtext; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::calc_width +// Access: Published +// Description: Returns the width of a line of text of arbitrary +// characters. The line should not include the newline +// character. +//////////////////////////////////////////////////////////////////// +INLINE float qpTextNode:: +calc_width(const wstring &line) const { + nassertr(_font != (TextFont *)NULL, 0.0); + return _font->calc_width(line); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::wordwrap_to +// Access: Published +// 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. +//////////////////////////////////////////////////////////////////// +INLINE wstring qpTextNode:: +wordwrap_to(const wstring &wtext, float wordwrap_width, + bool preserve_trailing_whitespace) const { + nassertr(_font != (TextFont *)NULL, wtext); + return _font->wordwrap_to(wtext, wordwrap_width, preserve_trailing_whitespace); +} diff --git a/panda/src/text/qptextNode.cxx b/panda/src/text/qptextNode.cxx new file mode 100644 index 0000000000..bbe4f217e4 --- /dev/null +++ b/panda/src/text/qptextNode.cxx @@ -0,0 +1,1069 @@ +// Filename: qptextNode.cxx +// Created by: drose (13Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#include "qptextNode.h" +#include "textGlyph.h" +#include "stringDecoder.h" +#include "config_text.h" + +#include "compose_matrix.h" +#include "geom.h" +#include "geomTristrip.h" +#include "geomLinestrip.h" +#include "geomPoint.h" +#include "qpgeomNode.h" +#include "notify.h" +#include "transformState.h" +#include "colorAttrib.h" +#include "cullBinAttrib.h" +#include "textureAttrib.h" +#include "transparencyAttrib.h" +#include "indent.h" + +#include +#include + +TypeHandle qpTextNode::_type_handle; + +qpTextNode::Encoding qpTextNode::_default_encoding; + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +qpTextNode:: +qpTextNode(const string &name) : PandaNode(name) { + _encoding = _default_encoding; + _slant = 0.0f; + + _flags = 0; + _align = A_left; + _wordwrap_width = 1.0f; + + _text_color.set(1.0f, 1.0f, 1.0f, 1.0f); + _frame_color.set(1.0f, 1.0f, 1.0f, 1.0f); + _card_color.set(1.0f, 1.0f, 1.0f, 1.0f); + _shadow_color.set(1.0f, 1.0f, 1.0f, 1.0f); + + _frame_width = 1.0f; + + _frame_ul.set(0.0f, 0.0f); + _frame_lr.set(0.0f, 0.0f); + _card_ul.set(0.0f, 0.0f); + _card_lr.set(0.0f, 0.0f); + _shadow_offset.set(0.0f, 0.0f); + + _draw_order = 1; + + _transform = LMatrix4f::ident_mat(); + _coordinate_system = CS_default; + + _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; + + _freeze_level = 0; + _needs_rebuild = false; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::Destructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +qpTextNode:: +~qpTextNode() { +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::wordwrap_to +// Access: Published +// 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. +//////////////////////////////////////////////////////////////////// +string qpTextNode:: +wordwrap_to(const string &text, float wordwrap_width, + bool preserve_trailing_whitespace) const { + nassertr(_font != (TextFont *)NULL, text); + wstring decoded = decode_text(text); + wstring wrapped = + _font->wordwrap_to(decoded, wordwrap_width, preserve_trailing_whitespace); + return encode_wtext(wrapped); +} + + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::write +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void qpTextNode:: +write(ostream &out, int indent_level) const { + indent(out, indent_level) + << "qpTextNode " << get_name() << "\n"; + if (_font != (TextFont *)NULL) { + indent(out, indent_level + 2) + << "with font " << _font->get_name() << "\n"; + } + if (has_text_color()) { + indent(out, indent_level + 2) + << "text color is " << _text_color << "\n"; + } else { + indent(out, indent_level + 2) + << "text color is unchanged from source\n"; + } + indent(out, indent_level + 2) + << "alignment is "; + switch (_align) { + case A_left: + out << "A_left\n"; + break; + + case A_right: + out << "A_right\n"; + break; + + case A_center: + out << "A_center\n"; + break; + } + + if (has_wordwrap()) { + indent(out, indent_level + 2) + << "Word-wrapping at " << _wordwrap_width << " units.\n"; + } + + if (has_frame()) { + indent(out, indent_level + 2) + << "frame of color " << _frame_color << " at " + << get_frame_as_set() << " line width " << _frame_width << "\n"; + if (get_frame_corners()) { + indent(out, indent_level + 2) + << "frame corners are enabled\n"; + } + if (is_frame_as_margin()) { + indent(out, indent_level + 2) + << "frame coordinates are specified as margin; actual frame is:\n" + << get_frame_actual() << "\n"; + } else { + indent(out, indent_level + 2) + << "frame coordinates are actual\n"; + } + } + if (has_card()) { + indent(out, indent_level + 2) + << "card of color " << _card_color << " at " + << get_card_as_set() << "\n"; + if (is_card_as_margin()) { + indent(out, indent_level + 2) + << "card coordinates are specified as margin; actual card is:\n" + << get_card_actual() << "\n"; + } else { + indent(out, indent_level + 2) + << "card coordinates are actual\n"; + } + } + if (has_shadow()) { + indent(out, indent_level + 2) + << "shadow of color " << _shadow_color << " at " + << _shadow_offset << "\n"; + } + if (has_bin()) { + indent(out, indent_level + 2) + << "bin is " << _bin << "\n"; + } + indent(out, indent_level + 2) + << "draw order is " << _draw_order << ", " + << _draw_order + 1 << ", " << _draw_order + 2 << "\n"; + + LVecBase3f scale, hpr, trans; + if (decompose_matrix(_transform, scale, hpr, trans, _coordinate_system)) { + indent(out, indent_level + 2) + << "transform is:\n" + << " scale: " << scale << "\n" + << " hpr: " << hpr << "\n" + << " trans: " << hpr << "\n"; + } else { + indent(out, indent_level + 2) + << "transform is:\n" << _transform; + } + indent(out, indent_level + 2) + << "in coordinate system " << _coordinate_system << "\n"; + + indent(out, indent_level + 2) + << "\ntext is " << _text << "\n"; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::generate +// Access: Published +// Description: Generates the text, according to the parameters +// indicated within the qpTextNode, and returns a Node +// that may be parented within the tree to represent it. +//////////////////////////////////////////////////////////////////// +PT(PandaNode) qpTextNode:: +generate() { + if (text_cat.is_debug()) { + text_cat.debug() + << "Rebuilding " << *this << " with '" << _text << "'\n"; + } + + // The strategy here will be to assemble together a bunch of + // letters, instanced from the letter hierarchy of font_def, into + // our own little hierarchy. + + // There will be one root over the whole text block, that + // contains the transform passed in. Under this root there will be + // another node for each row, that moves the row into the right place + // horizontally and vertically, and for each row, there is another + // node for each character. + + _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; + + // Now build a new sub-tree for all the text components. + PT(PandaNode) root = new PandaNode(_text); + + if (_text.empty() || _font.is_null()) { + return root; + } + + // Compute the overall text transform matrix. We build the text in + // a Z-up coordinate system and then convert it to whatever the user + // asked for. + LMatrix4f mat = + LMatrix4f::convert_mat(CS_zup_right, _coordinate_system) * + _transform; + + root->set_transform(TransformState::make_mat(mat)); + + wstring wtext = _wtext; + if (has_wordwrap()) { + wtext = _font->wordwrap_to(wtext, _wordwrap_width, false); + } + + // Assemble the text. + LVector2f ul, lr; + int num_rows = 0; + PT(PandaNode) text_root = assemble_text(wtext.begin(), wtext.end(), ul, lr, num_rows); + + // Parent the text in. + PT(PandaNode) text = new PandaNode("text"); + root->add_child(text, _draw_order + 2); + text->add_child(text_root); + + if (has_text_color()) { + text->set_attrib(ColorAttrib::make_flat(_text_color)); + if (_text_color[3] != 1.0) { + text->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha)); + } + } + + if (has_bin()) { + text->set_attrib(CullBinAttrib::make(_bin, _draw_order + 2)); + } + + // Save the bounding-box information about the text in a form + // friendly to the user. + _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; + + + // Now deal with all the decorations. + + if (has_shadow()) { + // Make a shadow by instancing the text behind itself. + + // For now, the depth offset is 0.0 because we don't expect to see + // text with shadows in the 3-d world that aren't decals. Maybe + // this will need to be addressed in the future. + + LMatrix4f offset = + LMatrix4f::translate_mat(_shadow_offset[0], 0.0f, -_shadow_offset[1]); + PT(PandaNode) shadow = new PandaNode("shadow"); + root->add_child(shadow, _draw_order + 1); + shadow->add_child(text_root); + shadow->set_transform(TransformState::make_mat(offset)); + shadow->set_attrib(ColorAttrib::make_flat(_shadow_color)); + + if (_shadow_color[3] != 1.0f) { + shadow->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha)); + } + + if (has_bin()) { + shadow->set_attrib(CullBinAttrib::make(_bin, _draw_order + 1)); + } + } + + if (has_frame()) { + PT(PandaNode) frame_root = make_frame(); + root->add_child(frame_root, _draw_order + 1); + frame_root->set_attrib(ColorAttrib::make_flat(_frame_color)); + if (_frame_color[3] != 1.0f) { + frame_root->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha)); + } + + if (has_bin()) { + frame_root->set_attrib(CullBinAttrib::make(_bin, _draw_order + 1)); + } + } + + if (has_card()) { + PT(PandaNode) card_root; + if (has_card_border()) + card_root = make_card_with_border(); + else + card_root = make_card(); + root->add_child(card_root, _draw_order); + card_root->set_attrib(ColorAttrib::make_flat(_card_color)); + if (_card_color[3] != 1.0f) { + card_root->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha)); + } + if (has_card_texture()) { + card_root->set_attrib(TextureAttrib::make(_card_texture)); + } + + if (has_bin()) { + card_root->set_attrib(CullBinAttrib::make(_bin, _draw_order)); + } + } + + // Now flatten our hierarchy to get rid of the transforms we put in, + // applying them to the vertices. + + if (text_flatten) { + /* **** + SceneGraphReducer gr(RenderRelation::get_class_type()); + gr.apply_transitions(root_arc); + gr.flatten(root, true); + */ + } + + return root; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::encode_wchar +// Access: Public +// Description: Encodes a single wide char into a one-, two-, or +// three-byte string, according to the current encoding +// system in effect. +//////////////////////////////////////////////////////////////////// +string qpTextNode:: +encode_wchar(wchar_t ch) const { + switch (_encoding) { + case E_iso8859: + if (isascii(ch)) { + return string(1, (char)ch); + } else { + return "."; + } + + case E_utf8: + if (ch < 0x80) { + return string(1, (char)ch); + } else if (ch < 0x800) { + return + string(1, (char)((ch >> 6) | 0xc0)) + + string(1, (char)((ch & 0x3f) | 0x80)); + } else { + return + string(1, (char)((ch >> 12) | 0xe0)) + + string(1, (char)(((ch >> 6) & 0x3f) | 0x80)) + + string(1, (char)((ch & 0x3f) | 0x80)); + } + + case E_unicode: + return + string(1, (char)(ch >> 8)) + + string(1, (char)(ch & 0xff)); + } + + return ""; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::encode_wtext +// Access: Public +// Description: Encodes a wide-text string into a single-char string, +// accoding to the current encoding. +//////////////////////////////////////////////////////////////////// +string qpTextNode:: +encode_wtext(const wstring &wtext) const { + string result; + + for (wstring::const_iterator pi = wtext.begin(); pi != wtext.end(); ++pi) { + result += encode_wchar(*pi); + } + + return result; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::decode_text +// Access: Public +// Description: Returns the given wstring decoded to a single-byte +// string, via the current encoding system. +//////////////////////////////////////////////////////////////////// +wstring qpTextNode:: +decode_text(const string &text) const { + switch (_encoding) { + case E_utf8: + { + StringUtf8Decoder decoder(text); + return decode_text_impl(decoder); + } + break; + + case E_unicode: + { + StringUnicodeDecoder decoder(text); + return decode_text_impl(decoder); + } + break; + + case E_iso8859: + default: + { + StringDecoder decoder(text); + return decode_text_impl(decoder); + } + }; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::decode_text_impl +// Access: Private +// Description: Decodes the eight-bit stream from the indicated +// decoder, storing the decoded unicode characters in +// _wtext. +//////////////////////////////////////////////////////////////////// +wstring qpTextNode:: +decode_text_impl(StringDecoder &decoder) const { + wstring result; + bool expand_amp = get_expand_amp(); + + 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); + } + result += character; + character = decoder.get_next_character(); + } + + return result; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::expand_amp_sequence +// Access: Private +// Description: Given that we have just read an ampersand from the +// StringDecoder, and that we have expand_amp in effect +// and are therefore expected to expand the sequence +// that this ampersand begins into a single unicode +// character, do the expansion and return the character. +//////////////////////////////////////////////////////////////////// +int qpTextNode:: +expand_amp_sequence(StringDecoder &decoder) const { + int result = 0; + + 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)) { + result = (result * 10) + (character - '0'); + character = decoder.get_next_character(); + } + if (character != ';') { + // Invalid sequence. + return 0; + } + + return result; + } + + string sequence; + + // Some non-numeric sequence. + while (!decoder.is_eof() && character < 128 && isalpha(character)) { + sequence += character; + character = decoder.get_next_character(); + } + if (character != ';') { + // Invalid sequence. + return 0; + } + + static const struct { + const char *name; + int code; + } tokens[] = { + { "amp", '&' }, { "lt", '<' }, { "gt", '>' }, { "quot", '"' }, + { "nbsp", ' ' /* 160 */ }, + + { "iexcl", 161 }, { "cent", 162 }, { "pound", 163 }, { "curren", 164 }, + { "yen", 165 }, { "brvbar", 166 }, { "brkbar", 166 }, { "sect", 167 }, + { "uml", 168 }, { "die", 168 }, { "copy", 169 }, { "ordf", 170 }, + { "laquo", 171 }, { "not", 172 }, { "shy", 173 }, { "reg", 174 }, + { "macr", 175 }, { "hibar", 175 }, { "deg", 176 }, { "plusmn", 177 }, + { "sup2", 178 }, { "sup3", 179 }, { "acute", 180 }, { "micro", 181 }, + { "para", 182 }, { "middot", 183 }, { "cedil", 184 }, { "sup1", 185 }, + { "ordm", 186 }, { "raquo", 187 }, { "frac14", 188 }, { "frac12", 189 }, + { "frac34", 190 }, { "iquest", 191 }, { "Agrave", 192 }, { "Aacute", 193 }, + { "Acirc", 194 }, { "Atilde", 195 }, { "Auml", 196 }, { "Aring", 197 }, + { "AElig", 198 }, { "Ccedil", 199 }, { "Egrave", 200 }, { "Eacute", 201 }, + { "Ecirc", 202 }, { "Euml", 203 }, { "Igrave", 204 }, { "Iacute", 205 }, + { "Icirc", 206 }, { "Iuml", 207 }, { "ETH", 208 }, { "Dstrok", 208 }, + { "Ntilde", 209 }, { "Ograve", 210 }, { "Oacute", 211 }, { "Ocirc", 212 }, + { "Otilde", 213 }, { "Ouml", 214 }, { "times", 215 }, { "Oslash", 216 }, + { "Ugrave", 217 }, { "Uacute", 218 }, { "Ucirc", 219 }, { "Uuml", 220 }, + { "Yacute", 221 }, { "THORN", 222 }, { "szlig", 223 }, { "agrave", 224 }, + { "aacute", 225 }, { "acirc", 226 }, { "atilde", 227 }, { "auml", 228 }, + { "aring", 229 }, { "aelig", 230 }, { "ccedil", 231 }, { "egrave", 232 }, + { "eacute", 233 }, { "ecirc", 234 }, { "euml", 235 }, { "igrave", 236 }, + { "iacute", 237 }, { "icirc", 238 }, { "iuml", 239 }, { "eth", 240 }, + { "ntilde", 241 }, { "ograve", 242 }, { "oacute", 243 }, { "ocirc", 244 }, + { "otilde", 245 }, { "ouml", 246 }, { "divide", 247 }, { "oslash", 248 }, + { "ugrave", 249 }, { "uacute", 250 }, { "ucirc", 251 }, { "uuml", 252 }, + { "yacute", 253 }, { "thorn", 254 }, { "yuml", 255 }, + + { NULL, 0 }, + }; + + for (int i = 0; tokens[i].name != NULL; i++) { + if (sequence == tokens[i].name) { + // Here's a match. + return tokens[i].code; + } + } + + // Some unrecognized sequence. + return 0; +} + + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::do_rebuild +// Access: Private +// Description: Removes any existing children of the qpTextNode, and +// adds the newly generated text instead. +//////////////////////////////////////////////////////////////////// +void qpTextNode:: +do_rebuild() { + _needs_rebuild = false; + + remove_all_children(); + + PT(PandaNode) new_text = generate(); + if (new_text != (PandaNode *)NULL) { + add_child(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: qpTextNode::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 qpTextNode:: +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: qpTextNode::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 qpTextNode:: +assemble_row(wstring::iterator &si, const wstring::iterator &send, + PandaNode *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 RenderState *state = glyph->get_state(); + + 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); + PT(qpGeomNode) geode = new qpGeomNode(ch); + geode->add_geom(char_geom, state); + dest->add_child(geode); + geode->set_transform(TransformState::make_mat(mat)); + } + + xpos += glyph->get_advance() * glyph_scale; + } + } + ++si; + } + + return xpos; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::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. +//////////////////////////////////////////////////////////////////// +PT(PandaNode) qpTextNode:: +assemble_text(wstring::iterator si, const wstring::iterator &send, + LVector2f &ul, LVector2f &lr, int &num_rows) { + nassertr(_font != (TextFont *)NULL, (PandaNode *)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. + PT(PandaNode) root_node = new PandaNode("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); + + PT(PandaNode) row = new PandaNode(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; + } + + row->set_transform(TransformState::make_mat(mat)); + root_node->add_child(row); + + posy -= line_height; + num_rows++; + } + + lr[1] = posy + 0.8f * line_height; + + return root_node; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::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 qpTextNode:: +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 += _font->get_space_advance(); + + } 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: qpTextNode::measure_text +// Access: Private +// Description: Sets the ul, lr corners to fit the text, without +// actually assembling it. +//////////////////////////////////////////////////////////////////// +void qpTextNode:: +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: qpTextNode::make_frame +// Access: Private +// Description: Creates a frame around the text. +//////////////////////////////////////////////////////////////////// +PT(PandaNode) qpTextNode:: +make_frame() { + PT(qpGeomNode) frame_geode = new qpGeomNode("frame"); + + LVector4f dimensions = get_frame_actual(); + float left = dimensions[0]; + float right = dimensions[1]; + float bottom = dimensions[2]; + float top = dimensions[3]; + + GeomLinestrip *geoset = new GeomLinestrip; + PTA_int lengths=PTA_int::empty_array(0); + PTA_Vertexf verts; + lengths.push_back(5); + verts.push_back(Vertexf(left, 0.0f, top)); + verts.push_back(Vertexf(left, 0.0f, bottom)); + verts.push_back(Vertexf(right, 0.0f, bottom)); + verts.push_back(Vertexf(right, 0.0f, top)); + verts.push_back(Vertexf(left, 0.0f, top)); + + geoset->set_num_prims(1); + geoset->set_lengths(lengths); + + geoset->set_coords(verts); + geoset->set_width(_frame_width); + frame_geode->add_geom(geoset); + + if (get_frame_corners()) { + GeomPoint *geoset = new GeomPoint; + + geoset->set_num_prims(4); + geoset->set_coords(verts); + geoset->set_size(_frame_width); + frame_geode->add_geom(geoset); + } + + return frame_geode.p(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::make_card +// Access: Private +// Description: Creates a card behind the text. +//////////////////////////////////////////////////////////////////// +PT(PandaNode) qpTextNode:: +make_card() { + PT(qpGeomNode) card_geode = new qpGeomNode("card"); + + LVector4f dimensions = get_card_actual(); + float left = dimensions[0]; + float right = dimensions[1]; + float bottom = dimensions[2]; + float top = dimensions[3]; + + GeomTristrip *geoset = new GeomTristrip; + PTA_int lengths=PTA_int::empty_array(0); + lengths.push_back(4); + + PTA_Vertexf verts; + verts.push_back(Vertexf::rfu(left, 0.02f, top)); + verts.push_back(Vertexf::rfu(left, 0.02f, bottom)); + verts.push_back(Vertexf::rfu(right, 0.02f, top)); + verts.push_back(Vertexf::rfu(right, 0.02f, bottom)); + + geoset->set_num_prims(1); + geoset->set_lengths(lengths); + + geoset->set_coords(verts); + + if (has_card_texture()) { + PTA_TexCoordf uvs; + uvs.push_back(TexCoordf(0.0f, 1.0f)); + uvs.push_back(TexCoordf(0.0f, 0.0f)); + uvs.push_back(TexCoordf(1.0f, 1.0f)); + uvs.push_back(TexCoordf(1.0f, 0.0f)); + + geoset->set_texcoords(uvs, G_PER_VERTEX); + } + + card_geode->add_geom(geoset); + + return card_geode.p(); +} + + +//////////////////////////////////////////////////////////////////// +// Function: qpTextNode::make_card_with_border +// Access: Private +// Description: Creates a card behind the text with a specified border +// for button edge or what have you. +//////////////////////////////////////////////////////////////////// +PT(PandaNode) qpTextNode:: +make_card_with_border() { + PT(qpGeomNode) card_geode = new qpGeomNode("card"); + + LVector4f dimensions = get_card_actual(); + float left = dimensions[0]; + float right = dimensions[1]; + float bottom = dimensions[2]; + float top = dimensions[3]; + + // we now create three tri-strips instead of one + // with vertices arranged as follows: + // + // 1 3 5 7 - one + // 2 4 6 8 / \ two + // 9 11 13 15 \ / + // 10 12 14 16 - three + // + GeomTristrip *geoset = new GeomTristrip; + PTA_int lengths; + lengths.push_back(8); + lengths.push_back(8); + lengths.push_back(8); + + PTA_Vertexf verts; + // verts 1,2,3,4 + verts.push_back(Vertexf::rfu(left, 0.02f, top)); + verts.push_back(Vertexf::rfu(left, 0.02f, top - _card_border_size)); + verts.push_back(Vertexf::rfu(left + _card_border_size, 0.02f, top)); + verts.push_back(Vertexf::rfu(left + _card_border_size, 0.02f, + top - _card_border_size)); + // verts 5,6,7,8 + verts.push_back(Vertexf::rfu(right - _card_border_size, 0.02f, top)); + verts.push_back(Vertexf::rfu(right - _card_border_size, 0.02f, + top - _card_border_size)); + verts.push_back(Vertexf::rfu(right, 0.02f, top)); + verts.push_back(Vertexf::rfu(right, 0.02f, top - _card_border_size)); + // verts 9,10,11,12 + verts.push_back(Vertexf::rfu(left, 0.02f, bottom + _card_border_size)); + verts.push_back(Vertexf::rfu(left, 0.02f, bottom)); + verts.push_back(Vertexf::rfu(left + _card_border_size, 0.02f, + bottom + _card_border_size)); + verts.push_back(Vertexf::rfu(left + _card_border_size, 0.02f, bottom)); + // verts 13,14,15,16 + verts.push_back(Vertexf::rfu(right - _card_border_size, 0.02f, + bottom + _card_border_size)); + verts.push_back(Vertexf::rfu(right - _card_border_size, 0.02f, bottom)); + verts.push_back(Vertexf::rfu(right, 0.02f, bottom + _card_border_size)); + verts.push_back(Vertexf::rfu(right, 0.02f, bottom)); + + PTA_ushort indices; + // tristrip #1 + indices.push_back(0); + indices.push_back(1); + indices.push_back(2); + indices.push_back(3); + indices.push_back(4); + indices.push_back(5); + indices.push_back(6); + indices.push_back(7); + // tristrip #2 + indices.push_back(1); + indices.push_back(8); + indices.push_back(3); + indices.push_back(10); + indices.push_back(5); + indices.push_back(12); + indices.push_back(7); + indices.push_back(14); + // tristrip #3 + indices.push_back(8); + indices.push_back(9); + indices.push_back(10); + indices.push_back(11); + indices.push_back(12); + indices.push_back(13); + indices.push_back(14); + indices.push_back(15); + + geoset->set_num_prims(3); + geoset->set_lengths(lengths); + + geoset->set_coords(verts,indices); + + if (has_card_texture()) { + PTA_TexCoordf uvs; + uvs.push_back(TexCoordf(0.0f, 1.0f)); //1 + uvs.push_back(TexCoordf(0.0f, 1.0f - _card_border_uv_portion)); //2 + uvs.push_back(TexCoordf(0.0f + _card_border_uv_portion, 1.0f)); //3 + uvs.push_back(TexCoordf(0.0f + _card_border_uv_portion, + 1.0f - _card_border_uv_portion)); //4 + uvs.push_back(TexCoordf( 1.0f -_card_border_uv_portion, 1.0f)); //5 + uvs.push_back(TexCoordf( 1.0f -_card_border_uv_portion, + 1.0f - _card_border_uv_portion)); //6 + uvs.push_back(TexCoordf(1.0f, 1.0f)); //7 + uvs.push_back(TexCoordf(1.0f, 1.0f - _card_border_uv_portion)); //8 + + uvs.push_back(TexCoordf(0.0f, _card_border_uv_portion)); //9 + uvs.push_back(TexCoordf(0.0f, 0.0f)); //10 + uvs.push_back(TexCoordf(_card_border_uv_portion, _card_border_uv_portion)); //11 + uvs.push_back(TexCoordf(_card_border_uv_portion, 0.0f)); //12 + + uvs.push_back(TexCoordf(1.0f - _card_border_uv_portion, _card_border_uv_portion));//13 + uvs.push_back(TexCoordf(1.0f - _card_border_uv_portion, 0.0f));//14 + uvs.push_back(TexCoordf(1.0f, _card_border_uv_portion));//15 + uvs.push_back(TexCoordf(1.0f, 0.0f));//16 + + // we can use same ref's as before (same order) + geoset->set_texcoords(uvs, G_PER_VERTEX, indices); + + } + + card_geode->add_geom(geoset); + + return card_geode.p(); +} diff --git a/panda/src/text/qptextNode.h b/panda/src/text/qptextNode.h new file mode 100644 index 0000000000..8dc5a9edbb --- /dev/null +++ b/panda/src/text/qptextNode.h @@ -0,0 +1,315 @@ +// Filename: qptextNode.h +// Created by: drose (13Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#ifndef qpTEXTNODE_H +#define qpTEXTNODE_H + +#include "pandabase.h" + +#include "config_text.h" +#include "textFont.h" +#include "pandaNode.h" + +#include "luse.h" + +class StringDecoder; + +//////////////////////////////////////////////////////////////////// +// Class : TextNode +// Description : The primary interface to this module. This class +// does basic text assembly; given a string of text and +// a TextFont object, it creates a piece of geometry +// that may be placed in the 3-d or 2-d world to +// represent the indicated text. +// +// The TextNode may be used in one of two ways. +// Naively, it may be parented to the scene graph +// directly; used in this way, you can optionally call +// freeze() and thaw() between changing many parameters +// in the text at once, to avoid unnecessary expensive +// regeneration with each parameter change. However, it +// will work, if slowly, even if you never call freeze() +// and thaw(). +// +// The second way TextNode may be used is as a text +// generator. To use it in this way, call freeze() once +// on the TextNode when you create it, and never call +// thaw(). Do not parent the TextNode to the scene +// graph; instea, set the properties of the text and +// call generate() to return a node which you may parent +// wherever you like. Each time you call generate() a +// new node is returned. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA qpTextNode : public PandaNode { +PUBLISHED: + qpTextNode(const string &name); + ~qpTextNode(); + + enum Alignment { + A_left, + A_right, + A_center, + }; + + enum Encoding { + E_iso8859, + E_utf8, + E_unicode + }; + + INLINE int freeze(); + INLINE int get_freeze_level() const; + INLINE int thaw(); + + INLINE void set_font(TextFont *font); + INLINE TextFont *get_font() const; + + INLINE void set_encoding(Encoding encoding); + INLINE Encoding get_encoding() const; + + INLINE void set_expand_amp(bool expand_amp); + INLINE bool get_expand_amp() const; + + INLINE float get_line_height() const; + + INLINE void set_slant(float slant); + INLINE float get_slant() const; + + INLINE void set_align(Alignment align_type); + INLINE Alignment get_align() const; + + INLINE void set_wordwrap(float width); + INLINE void clear_wordwrap(); + INLINE bool has_wordwrap() const; + INLINE float get_wordwrap() const; + + INLINE void set_text_color(float r, float g, float b, float a); + INLINE void set_text_color(const Colorf &text_color); + INLINE void clear_text_color(); + INLINE bool has_text_color() const; + INLINE Colorf get_text_color() const; + + INLINE void set_frame_color(float r, float g, float b, float a); + INLINE void set_frame_color(const Colorf &frame_color); + INLINE Colorf get_frame_color() const; + + INLINE void set_card_border(float size, float uv_portion); + INLINE void clear_card_border(); + INLINE float get_card_border_size() const; + INLINE float get_card_border_uv_portion() const; + INLINE bool has_card_border() const; + + INLINE void set_card_color(float r, float g, float b, float a); + INLINE void set_card_color(const Colorf &card_color); + INLINE Colorf get_card_color() const; + + INLINE void set_card_texture(Texture *card_texture); + INLINE void clear_card_texture(); + INLINE bool has_card_texture() const; + INLINE Texture *get_card_texture() const; + + INLINE void set_shadow_color(float r, float g, float b, float a); + INLINE void set_shadow_color(const Colorf &shadow_color); + INLINE Colorf get_shadow_color() const; + + INLINE void set_frame_as_margin(float left, float right, + float bottom, float top); + INLINE void set_frame_actual(float left, float right, + float bottom, float top); + INLINE void clear_frame(); + INLINE bool has_frame() const; + INLINE bool is_frame_as_margin() const; + INLINE LVecBase4f get_frame_as_set() const; + INLINE LVecBase4f get_frame_actual() const; + + INLINE void set_frame_line_width(float line_width); + INLINE float get_frame_line_width() const; + INLINE void set_frame_corners(bool corners); + INLINE bool get_frame_corners() const; + + INLINE void set_card_as_margin(float left, float right, + float bottom, float top); + INLINE void set_card_actual(float left, float right, + float bottom, float top); + INLINE void clear_card(); + INLINE bool has_card() const; + INLINE bool is_card_as_margin() const; + INLINE LVecBase4f get_card_as_set() const; + INLINE LVecBase4f get_card_actual() const; + INLINE LVecBase4f get_card_transformed() const; + + INLINE void set_shadow(float xoffset, float yoffset); + INLINE void clear_shadow(); + INLINE bool has_shadow() const; + INLINE LVecBase2f get_shadow() const; + + INLINE void set_bin(const string &bin); + INLINE void clear_bin(); + INLINE bool has_bin() const; + INLINE const string &get_bin() const; + + INLINE int set_draw_order(int draw_order); + INLINE int get_draw_order() const; + + INLINE void set_transform(const LMatrix4f &transform); + INLINE LMatrix4f get_transform() const; + + INLINE void set_coordinate_system(CoordinateSystem cs); + INLINE CoordinateSystem get_coordinate_system() const; + + INLINE void set_text(const string &text); + INLINE void clear_text(); + INLINE bool has_text() const; + INLINE string get_text() const; + + INLINE float calc_width(int character) const; + INLINE float calc_width(const string &line) const; + string wordwrap_to(const string &text, float wordwrap_width, + bool preserve_trailing_whitespace) const; + + virtual void write(ostream &out, int indent_level = 0) const; + + INLINE void rebuild(bool needs_measure); + INLINE void measure(); + + // The following functions return information about the text that + // was last built (and is currently visible). + INLINE float get_left() const; + INLINE float get_right() const; + INLINE float get_bottom() const; + INLINE float get_top() const; + INLINE float get_height() const; + INLINE float get_width() const; + + INLINE LPoint3f get_upper_left_3d() const; + INLINE LPoint3f get_lower_right_3d() const; + + INLINE int get_num_rows() const; + + PT(PandaNode) generate(); + +public: + // Direct support for wide-character strings. + INLINE void set_wtext(const wstring &wtext); + INLINE const wstring &get_wtext() const; + + INLINE float calc_width(const wstring &line) const; + INLINE wstring wordwrap_to(const wstring &wtext, float wordwrap_width, + bool preserve_trailing_whitespace) const; + + string encode_wchar(wchar_t ch) const; + string encode_wtext(const wstring &wtext) const; + wstring decode_text(const string &text) const; + +private: + wstring decode_text_impl(StringDecoder &decoder) const; + int expand_amp_sequence(StringDecoder &decoder) const; + + void do_rebuild(); + void do_measure(); + +#ifndef CPPPARSER // interrogate has a bit of trouble with wstring. + float assemble_row(wstring::iterator &si, const wstring::iterator &send, + PandaNode *dest); + PT(PandaNode) 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 + + PT(PandaNode) make_frame(); + PT(PandaNode) make_card(); + PT(PandaNode) make_card_with_border(); + + PT(TextFont) _font; + + Encoding _encoding; + float _slant; + + PT(Texture) _card_texture; + Colorf _text_color; + Colorf _shadow_color; + Colorf _frame_color; + Colorf _card_color; + + enum Flags { + F_has_text_color = 0x0001, + F_has_wordwrap = 0x0002, + F_has_frame = 0x0004, + F_frame_as_margin = 0x0008, + F_has_card = 0x0010, + F_card_as_margin = 0x0020, + F_has_card_texture = 0x0040, + F_has_shadow = 0x0080, + F_frame_corners = 0x0100, + F_card_transp = 0x0200, + F_has_card_border = 0x0400, + F_expand_amp = 0x0800, + }; + + int _flags; + Alignment _align; + float _wordwrap_width; + float _frame_width; + float _card_border_size; + float _card_border_uv_portion; + + LVector2f _frame_ul, _frame_lr; + LVector2f _card_ul, _card_lr; + LVector2f _shadow_offset; + + string _bin; + int _draw_order; + + LMatrix4f _transform; + CoordinateSystem _coordinate_system; + + string _text; + wstring _wtext; + + LPoint2f _ul2d, _lr2d; + LPoint3f _ul3d, _lr3d; + int _num_rows; + int _freeze_level; + bool _needs_rebuild; + +public: + static Encoding _default_encoding; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + PandaNode::init_type(); + register_type(_type_handle, "qpTextNode", + PandaNode::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "qptextNode.I" + +#endif