better, more robust unicode support

This commit is contained in:
David Rose 2002-02-12 18:39:21 +00:00
parent d3386f0ed1
commit d853aef780
8 changed files with 540 additions and 366 deletions

View File

@ -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;
}
////////////////////////////////////////////////////////////////////

View File

@ -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);

View File

@ -39,8 +39,11 @@ public:
INLINE bool is_eof();
protected:
INLINE bool test_eof();
string _input;
size_t _p;
bool _eof;
};
////////////////////////////////////////////////////////////////////

View File

@ -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;
}

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;