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