mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-17 20:23:47 -04:00
better, more robust unicode support
This commit is contained in:
parent
d3386f0ed1
commit
d853aef780
@ -25,6 +25,7 @@
|
|||||||
INLINE StringDecoder::
|
INLINE StringDecoder::
|
||||||
StringDecoder(const string &input) : _input(input) {
|
StringDecoder(const string &input) : _input(input) {
|
||||||
_p = 0;
|
_p = 0;
|
||||||
|
_eof = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
@ -36,7 +37,22 @@ StringDecoder(const string &input) : _input(input) {
|
|||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
INLINE bool StringDecoder::
|
INLINE bool StringDecoder::
|
||||||
is_eof() {
|
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::
|
int StringDecoder::
|
||||||
get_next_character() {
|
get_next_character() {
|
||||||
if (is_eof()) {
|
if (test_eof()) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return (unsigned char)_input[_p++];
|
return (unsigned char)_input[_p++];
|
||||||
@ -71,7 +71,7 @@ The value of each individual byte indicates its UTF-8 function, as follows:
|
|||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
int StringUtf8Decoder::
|
int StringUtf8Decoder::
|
||||||
get_next_character() {
|
get_next_character() {
|
||||||
if (is_eof()) {
|
if (test_eof()) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ get_next_character() {
|
|||||||
if ((result & 0xe0) == 0xc0) {
|
if ((result & 0xe0) == 0xc0) {
|
||||||
// First byte of two.
|
// First byte of two.
|
||||||
unsigned int two = 0;
|
unsigned int two = 0;
|
||||||
if (!is_eof()) {
|
if (!test_eof()) {
|
||||||
two = (unsigned char)_input[_p++];
|
two = (unsigned char)_input[_p++];
|
||||||
}
|
}
|
||||||
result = ((result & 0x1f) << 6) | (two & 0x3f);
|
result = ((result & 0x1f) << 6) | (two & 0x3f);
|
||||||
@ -88,10 +88,10 @@ get_next_character() {
|
|||||||
// First byte of three.
|
// First byte of three.
|
||||||
unsigned int two = 0;
|
unsigned int two = 0;
|
||||||
unsigned int three = 0;
|
unsigned int three = 0;
|
||||||
if (!is_eof()) {
|
if (!test_eof()) {
|
||||||
two = (unsigned char)_input[_p++];
|
two = (unsigned char)_input[_p++];
|
||||||
}
|
}
|
||||||
if (!is_eof()) {
|
if (!test_eof()) {
|
||||||
three = (unsigned char)_input[_p++];
|
three = (unsigned char)_input[_p++];
|
||||||
}
|
}
|
||||||
result = ((result & 0x0f) << 12) | ((two & 0x3f) << 6) | (three & 0x3f);
|
result = ((result & 0x0f) << 12) | ((two & 0x3f) << 6) | (three & 0x3f);
|
||||||
@ -107,13 +107,13 @@ get_next_character() {
|
|||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
int StringUnicodeDecoder::
|
int StringUnicodeDecoder::
|
||||||
get_next_character() {
|
get_next_character() {
|
||||||
if (is_eof()) {
|
if (test_eof()) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int high = (unsigned char)_input[_p++];
|
unsigned int high = (unsigned char)_input[_p++];
|
||||||
unsigned int low = 0;
|
unsigned int low = 0;
|
||||||
if (!is_eof()) {
|
if (!test_eof()) {
|
||||||
low = (unsigned char)_input[_p++];
|
low = (unsigned char)_input[_p++];
|
||||||
}
|
}
|
||||||
return ((high << 8) | low);
|
return ((high << 8) | low);
|
||||||
|
@ -39,8 +39,11 @@ public:
|
|||||||
INLINE bool is_eof();
|
INLINE bool is_eof();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
INLINE bool test_eof();
|
||||||
|
|
||||||
string _input;
|
string _input;
|
||||||
size_t _p;
|
size_t _p;
|
||||||
|
bool _eof;
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
|
@ -29,7 +29,7 @@ TypeHandle TextFont::_type_handle;
|
|||||||
// does not consider newlines to be whitespace.
|
// does not consider newlines to be whitespace.
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
INLINE bool
|
INLINE bool
|
||||||
isblank(char ch) {
|
isblank(int ch) {
|
||||||
return (ch == ' ' || ch == '\t');
|
return (ch == ' ' || ch == '\t');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ wordwrap_to(const string &text, float wordwrap_width,
|
|||||||
bool needs_newline = false;
|
bool needs_newline = false;
|
||||||
|
|
||||||
while (p < text.length()) {
|
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
|
// Scan the next n characters, until the end of the string or an
|
||||||
// embedded newline character, or we exceed wordwrap_width.
|
// embedded newline character, or we exceed wordwrap_width.
|
||||||
@ -228,3 +228,144 @@ write(ostream &out, int indent_level) const {
|
|||||||
indent(out, indent_level)
|
indent(out, indent_level)
|
||||||
<< "TextFont " << get_name() << "\n";
|
<< "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 Node;
|
||||||
class TextGlyph;
|
class TextGlyph;
|
||||||
|
|
||||||
|
// For some reason, gcc's <string> doesn't define this.
|
||||||
|
typedef basic_string<wchar_t> wstring;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Class : TextFont
|
// Class : TextFont
|
||||||
// Description : An encapsulation of a font; i.e. a set of glyphs that
|
// 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;
|
virtual void write(ostream &out, int indent_level) const;
|
||||||
|
|
||||||
public:
|
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;
|
INLINE float get_space_advance() const;
|
||||||
virtual bool get_glyph(int character, const TextGlyph *&glyph,
|
virtual bool get_glyph(int character, const TextGlyph *&glyph,
|
||||||
float &glyph_scale)=0;
|
float &glyph_scale)=0;
|
||||||
|
@ -118,13 +118,13 @@ get_font() const {
|
|||||||
// (i.e. ASCII). Other encodings are possible to take
|
// (i.e. ASCII). Other encodings are possible to take
|
||||||
// advantage of character sets with more than 256
|
// advantage of character sets with more than 256
|
||||||
// characters.
|
// characters.
|
||||||
|
//
|
||||||
|
// This affects only future calls to set_text(); it does
|
||||||
|
// not change text that was set previously.
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
INLINE void TextNode::
|
INLINE void TextNode::
|
||||||
set_encoding(TextNode::Encoding encoding) {
|
set_encoding(TextNode::Encoding encoding) {
|
||||||
if (_encoding != encoding) {
|
_encoding = encoding;
|
||||||
_encoding = encoding;
|
|
||||||
rebuild(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
@ -146,16 +146,16 @@ get_encoding() const {
|
|||||||
// expanded to special characters according to a subset
|
// expanded to special characters according to a subset
|
||||||
// of the HTML conventions. When this is false,
|
// of the HTML conventions. When this is false,
|
||||||
// ampersands are treated as ordinary characters.
|
// 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::
|
INLINE void TextNode::
|
||||||
set_expand_amp(bool expand_amp) {
|
set_expand_amp(bool expand_amp) {
|
||||||
bool current_expand_amp = get_expand_amp();
|
if (expand_amp) {
|
||||||
if (expand_amp && !current_expand_amp) {
|
|
||||||
_flags |= F_expand_amp;
|
_flags |= F_expand_amp;
|
||||||
rebuild(true);
|
|
||||||
} else {
|
} else {
|
||||||
_flags &= ~F_expand_amp;
|
_flags &= ~F_expand_amp;
|
||||||
rebuild(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1022,18 +1022,6 @@ get_coordinate_system() const {
|
|||||||
return _coordinate_system;
|
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
|
// Function: TextNode::clear_text
|
||||||
// Access: Published
|
// Access: Published
|
||||||
|
@ -78,7 +78,7 @@ TextNode(const string &name) : NamedNode(name) {
|
|||||||
_num_rows = 0;
|
_num_rows = 0;
|
||||||
|
|
||||||
_freeze_level = 0;
|
_freeze_level = 0;
|
||||||
_needs_rebuild = false;
|
_needs_rebuild = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
@ -90,6 +90,41 @@ TextNode::
|
|||||||
~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
|
// Function: TextNode::write
|
||||||
@ -242,21 +277,18 @@ generate() {
|
|||||||
|
|
||||||
root_arc->set_transition(new TransformTransition(mat));
|
root_arc->set_transition(new TransformTransition(mat));
|
||||||
|
|
||||||
string text = _text;
|
wstring wtext = _wtext;
|
||||||
if (has_wordwrap()) {
|
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.
|
// Assemble the text.
|
||||||
LVector2f ul, lr;
|
LVector2f ul, lr;
|
||||||
int num_rows = 0;
|
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 =
|
RenderRelation *text_arc =
|
||||||
new RenderRelation(sub_root, text_root, _draw_order + 2);
|
new RenderRelation(sub_root, text_root, _draw_order + 2);
|
||||||
|
|
||||||
delete decoder;
|
|
||||||
|
|
||||||
if (has_text_color()) {
|
if (has_text_color()) {
|
||||||
text_arc->set_transition(new ColorTransition(_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
|
// Access: Private
|
||||||
// Description: Removes any existing children of the TextNode, and
|
// Description: Decodes the eight-bit stream from the indicated
|
||||||
// adds the newly generated text instead.
|
// decoder, storing the decoded unicode characters in
|
||||||
|
// _wtext.
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
void TextNode::
|
void TextNode::
|
||||||
do_rebuild() {
|
decode_wtext(StringDecoder &decoder) {
|
||||||
_needs_rebuild = false;
|
_wtext.erase(_wtext.begin(), _wtext.end());
|
||||||
|
|
||||||
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;
|
|
||||||
bool expand_amp = get_expand_amp();
|
bool expand_amp = get_expand_amp();
|
||||||
if (decoder->is_eof()) {
|
|
||||||
return xpos;
|
wchar_t character = decoder.get_next_character();
|
||||||
}
|
while (!decoder.is_eof()) {
|
||||||
int character = decoder->get_next_character();
|
|
||||||
while (character != '\n') {
|
|
||||||
if (character == '&' && expand_amp) {
|
if (character == '&' && expand_amp) {
|
||||||
// An ampersand in expand_amp mode is treated as an escape
|
// An ampersand in expand_amp mode is treated as an escape
|
||||||
// character.
|
// character.
|
||||||
character = expand_amp_sequence(decoder);
|
character = expand_amp_sequence(decoder);
|
||||||
}
|
}
|
||||||
|
_wtext += character;
|
||||||
if (character == ' ') {
|
character = decoder.get_next_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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// character, do the expansion and return the character.
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
int TextNode::
|
int TextNode::
|
||||||
expand_amp_sequence(StringDecoder *decoder) {
|
expand_amp_sequence(StringDecoder &decoder) {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
|
|
||||||
int character = decoder->get_next_character();
|
int character = decoder.get_next_character();
|
||||||
if (character == '#') {
|
if (!decoder.is_eof() && character == '#') {
|
||||||
// An explicit numeric sequence: &#nnn;
|
// An explicit numeric sequence: &#nnn;
|
||||||
result = 0;
|
result = 0;
|
||||||
character = decoder->get_next_character();
|
character = decoder.get_next_character();
|
||||||
while (!decoder->is_eof() && character < 128 && isdigit(character)) {
|
while (!decoder.is_eof() && character < 128 && isdigit(character)) {
|
||||||
result = (result * 10) + (character - '0');
|
result = (result * 10) + (character - '0');
|
||||||
character = decoder->get_next_character();
|
character = decoder.get_next_character();
|
||||||
}
|
}
|
||||||
if (character != ';') {
|
if (character != ';') {
|
||||||
// Invalid sequence.
|
// Invalid sequence.
|
||||||
@ -717,9 +450,9 @@ expand_amp_sequence(StringDecoder *decoder) {
|
|||||||
string sequence;
|
string sequence;
|
||||||
|
|
||||||
// Some non-numeric sequence.
|
// Some non-numeric sequence.
|
||||||
while (!decoder->is_eof() && character < 128 && isalpha(character)) {
|
while (!decoder.is_eof() && character < 128 && isalpha(character)) {
|
||||||
sequence += character;
|
sequence += character;
|
||||||
character = decoder->get_next_character();
|
character = decoder.get_next_character();
|
||||||
}
|
}
|
||||||
if (character != ';') {
|
if (character != ';') {
|
||||||
// Invalid sequence.
|
// Invalid sequence.
|
||||||
@ -773,6 +506,289 @@ expand_amp_sequence(StringDecoder *decoder) {
|
|||||||
return 0;
|
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
|
// Function: TextNode::make_frame
|
||||||
// Access: Private
|
// Access: Private
|
||||||
|
@ -186,7 +186,7 @@ PUBLISHED:
|
|||||||
INLINE void set_coordinate_system(CoordinateSystem cs);
|
INLINE void set_coordinate_system(CoordinateSystem cs);
|
||||||
INLINE CoordinateSystem get_coordinate_system() const;
|
INLINE CoordinateSystem get_coordinate_system() const;
|
||||||
|
|
||||||
INLINE void set_text(const string &str);
|
void set_text(const string &str);
|
||||||
INLINE void clear_text();
|
INLINE void clear_text();
|
||||||
INLINE bool has_text() const;
|
INLINE bool has_text() const;
|
||||||
INLINE string get_text() const;
|
INLINE string get_text() const;
|
||||||
@ -218,19 +218,21 @@ PUBLISHED:
|
|||||||
PT_Node generate();
|
PT_Node generate();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void decode_wtext(StringDecoder &decoder);
|
||||||
|
int expand_amp_sequence(StringDecoder &decoder);
|
||||||
|
|
||||||
void do_rebuild();
|
void do_rebuild();
|
||||||
void do_measure();
|
void do_measure();
|
||||||
|
|
||||||
StringDecoder *make_decoder(const string &text);
|
#ifndef CPPPARSER // interrogate has a bit of trouble with wstring.
|
||||||
|
float assemble_row(wstring::iterator &si, const wstring::iterator &send,
|
||||||
float assemble_row(StringDecoder *decoder, Node *dest);
|
Node *dest);
|
||||||
Node *assemble_text(StringDecoder *decoder, LVector2f &ul, LVector2f &lr,
|
Node *assemble_text(wstring::iterator si, const wstring::iterator &send,
|
||||||
int &num_rows);
|
LVector2f &ul, LVector2f &lr, int &num_rows);
|
||||||
float measure_row(StringDecoder *decoder);
|
float measure_row(wstring::iterator &si, const wstring::iterator &send);
|
||||||
void measure_text(StringDecoder *decoder, LVector2f &ul, LVector2f &lr,
|
void measure_text(wstring::iterator si, const wstring::iterator &send,
|
||||||
int &num_rows);
|
LVector2f &ul, LVector2f &lr, int &num_rows);
|
||||||
|
#endif // CPPPARSER
|
||||||
int expand_amp_sequence(StringDecoder *decoder);
|
|
||||||
|
|
||||||
Node *make_frame();
|
Node *make_frame();
|
||||||
Node *make_card();
|
Node *make_card();
|
||||||
@ -280,6 +282,7 @@ private:
|
|||||||
CoordinateSystem _coordinate_system;
|
CoordinateSystem _coordinate_system;
|
||||||
|
|
||||||
string _text;
|
string _text;
|
||||||
|
wstring _wtext;
|
||||||
|
|
||||||
LPoint2f _ul2d, _lr2d;
|
LPoint2f _ul2d, _lr2d;
|
||||||
LPoint3f _ul3d, _lr3d;
|
LPoint3f _ul3d, _lr3d;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user