From 5d121b8ed932c8ca33f0ad00e8ab0a1b9a3d88ca Mon Sep 17 00:00:00 2001 From: David Rose Date: Thu, 8 Apr 2004 01:17:50 +0000 Subject: [PATCH] support multi-state text blocks; also show IME candidate strings in DirectEntry fields --- direct/src/gui/DirectEntry.py | 9 +- direct/src/gui/DirectGuiTest.py | 6 +- direct/src/gui/OnscreenText.py | 27 +- direct/src/leveleditor/LevelEditor.py | 4 - direct/src/leveleditor/PieMenu.py | 2 - panda/src/glxdisplay/glxGraphicsWindow.cxx | 14 - panda/src/pgui/pgEntry.I | 64 + panda/src/pgui/pgEntry.cxx | 300 ++-- panda/src/pgui/pgEntry.h | 13 + panda/src/text/Sources.pp | 18 +- panda/src/text/config_text.cxx | 12 + panda/src/text/config_text.h | 2 + panda/src/text/fontPool.cxx | 2 + panda/src/text/textAssembler.I | 91 ++ panda/src/text/textAssembler.cxx | 1450 ++++++++++++++++++++ panda/src/text/textAssembler.h | 188 +++ panda/src/text/textFont.cxx | 310 ----- panda/src/text/textFont.h | 6 - panda/src/text/textNode.I | 1022 +++++++------- panda/src/text/textNode.cxx | 1235 ++--------------- panda/src/text/textNode.h | 247 ++-- panda/src/text/textProperties.I | 842 ++++++++++++ panda/src/text/textProperties.cxx | 318 +++++ panda/src/text/textProperties.h | 213 +++ panda/src/text/textPropertiesManager.I | 18 + panda/src/text/textPropertiesManager.cxx | 148 ++ panda/src/text/textPropertiesManager.h | 72 + panda/src/text/text_composite1.cxx | 2 - panda/src/text/text_composite2.cxx | 6 + 29 files changed, 4330 insertions(+), 2311 deletions(-) create mode 100644 panda/src/text/textAssembler.I create mode 100644 panda/src/text/textAssembler.cxx create mode 100644 panda/src/text/textAssembler.h create mode 100644 panda/src/text/textProperties.I create mode 100644 panda/src/text/textProperties.cxx create mode 100644 panda/src/text/textProperties.h create mode 100644 panda/src/text/textPropertiesManager.I create mode 100644 panda/src/text/textPropertiesManager.cxx create mode 100644 panda/src/text/textPropertiesManager.h create mode 100644 panda/src/text/text_composite2.cxx diff --git a/direct/src/gui/DirectEntry.py b/direct/src/gui/DirectEntry.py index a4283377a7..df015b8d60 100644 --- a/direct/src/gui/DirectEntry.py +++ b/direct/src/gui/DirectEntry.py @@ -73,11 +73,10 @@ class DirectEntry(DirectFrame): scale = 1, # Don't get rid of the text node mayChange = 1) - # We want to keep this sucker frozen since its not - # in the scene graph - self.onscreenText.freeze() - # We can also get rid of the node path since we're just using the - # onscreenText as an easy way to access a text node as a component + + # We can get rid of the node path since we're just using the + # onscreenText as an easy way to access a text node as a + # component self.onscreenText.removeNode() # Bind command function diff --git a/direct/src/gui/DirectGuiTest.py b/direct/src/gui/DirectGuiTest.py index d0d2306df2..6de8fe164f 100644 --- a/direct/src/gui/DirectGuiTest.py +++ b/direct/src/gui/DirectGuiTest.py @@ -90,12 +90,12 @@ def printEntryText(text): # CALL de1.get() and de1.set('new text') to get and set entry contents de1 = DirectEntry(initialText = 'Hello, how are you?', image = 'models/maps/noise.rgb', - #image = 'phase_3/maps/toowntown-logo.jpg', - image_pos = (4.55, 0, -1.65), - image_scale = (5.5, 1, 2.55), + image_pos = (4.55, 0, -2.55), + image_scale = (5.5, 1, 4), command = printEntryText, pos = (-1.1875, 0, 0.879167), scale = 0.0707855, + cursorKeys = 1, ) # DIRECT DIALOG EXAMPLE diff --git a/direct/src/gui/OnscreenText.py b/direct/src/gui/OnscreenText.py index 04383e5187..2a29c24ad9 100644 --- a/direct/src/gui/OnscreenText.py +++ b/direct/src/gui/OnscreenText.py @@ -157,8 +157,6 @@ class OnscreenText(PandaObject, NodePath): if font == None: font = DirectGuiGlobals.getDefaultFont() - # Freeze the node while we set all the properties - textNode.freeze() textNode.setFont(font) textNode.setTextColor(fg[0], fg[1], fg[2], fg[3]) textNode.setAlign(align) @@ -202,12 +200,9 @@ class OnscreenText(PandaObject, NodePath): self.mayChange = mayChange # Ok, now update the node. - if self.mayChange: - # If we might change the text later, we have to keep the - # TextNode around. - textNode.thaw() - else: - # Otherwise, we can throw it away. + if not self.mayChange: + # If we aren't going to change the text later, we can + # throw away the TextNode. self.textNode = textNode.generate() self.isClean = 0 @@ -225,10 +220,10 @@ class OnscreenText(PandaObject, NodePath): self.cleanup() def freeze(self): - self.textNode.freeze() + pass def thaw(self): - self.textNode.thaw() + pass # Allow changing of several of the parameters after the text has # been created. These should be used with caution; it is better @@ -237,9 +232,6 @@ class OnscreenText(PandaObject, NodePath): # text, and for those rare occasions when you actually want to # change a text's property after it has been created. - # If you need to change several properties at the same time at - # runtime, you should call freeze() first and thaw() afterward. - def setFont(self, font): self.textNode.setFont(font) @@ -321,7 +313,6 @@ class OnscreenText(PandaObject, NodePath): self.textNode.setTextColor(fg[0], fg[1], fg[2], fg[3]) def setBg(self, bg): - self.textNode.freeze() if bg[3] != 0: # If we have a background color, create a card. self.textNode.setCardColor(bg[0], bg[1], bg[2], bg[3]) @@ -329,10 +320,8 @@ class OnscreenText(PandaObject, NodePath): else: # Otherwise, remove the card. self.textNode.clearCard() - self.textNode.thaw() def setShadow(self, shadow): - self.textNode.freeze() if shadow[3] != 0: # If we have a shadow color, create a shadow. self.textNode.setShadowColor(shadow[0], shadow[1], shadow[2], shadow[3]) @@ -340,10 +329,8 @@ class OnscreenText(PandaObject, NodePath): else: # Otherwise, remove the shadow. self.textNode.clearShadow() - self.textNode.thaw() def setFrame(self, frame): - self.textNode.freeze() if frame[3] != 0: # If we have a frame color, create a frame. self.textNode.setFrameColor(frame[0], frame[1], frame[2], frame[3]) @@ -351,15 +338,12 @@ class OnscreenText(PandaObject, NodePath): else: # Otherwise, remove the frame. self.textNode.clearFrame() - self.textNode.thaw() def configure(self, option=None, **kw): # These is for compatability with DirectGui functions if not self.mayChange: print 'OnscreenText.configure: mayChange == 0' return - # Freeze text node prior to making changes - self.freeze() for option, value in kw.items(): # Use option string to access setter function try: @@ -371,7 +355,6 @@ class OnscreenText(PandaObject, NodePath): setter(value) except AttributeError: print 'OnscreenText.configure: invalid option:', option - self.thaw() # Allow index style references def __setitem__(self, key, value): diff --git a/direct/src/leveleditor/LevelEditor.py b/direct/src/leveleditor/LevelEditor.py index d315737955..9e6e164d4d 100644 --- a/direct/src/leveleditor/LevelEditor.py +++ b/direct/src/leveleditor/LevelEditor.py @@ -3988,13 +3988,11 @@ class LevelStyleManager: for i in range (numItems): # Create a text node--just a card, really--of the right color. tn = TextNode('colorChip') - tn.freeze() tn.setFont(getDefaultFont()) tn.setTransform(Mat4.scaleMat(0.07, 0.07, 0.07 * aspectRatio)) tn.setCardColor(colorList[i]) tn.setCardActual(0, 1.1111, 0, 0.8333) tn.setText(' ') - tn.thaw() # Reposition it card = tn.getCardTransformed() @@ -4113,14 +4111,12 @@ class LevelStyleManager: # Create text node for each item if (textList[i] != None): tn = TextNode('TextItem') - tn.freeze() tn.setFont(getDefaultFont()) tn.setTransform(Mat4.scaleMat(0.07, 0.07, 0.07 * aspectRatio)) tn.setTextColor(0, 0, 0, 1) tn.setCardColor(1, 1, 1, 1) tn.setCardAsMargin(0.1, 0.1, 0.1, 0.1) tn.setText(str(textList[i])) - tn.thaw() # Reposition it card = tn.getCardTransformed() diff --git a/direct/src/leveleditor/PieMenu.py b/direct/src/leveleditor/PieMenu.py index 501dab1bdb..0e2ea5184a 100644 --- a/direct/src/leveleditor/PieMenu.py +++ b/direct/src/leveleditor/PieMenu.py @@ -138,14 +138,12 @@ class TextPieMenu(PieMenu): # Create text node for each item if (textList[i] != None): tn = TextNode('TextItem') - tn.freeze() tn.setFont(getDefaultFont()) tn.setTransform(Mat4.scaleMat(0.07, 0.07, 0.07 * aspectRatio)) tn.setTextColor(0, 0, 0, 1) tn.setCardColor(1, 1, 1, 1) tn.setCardAsMargin(0.1, 0.1, 0.1, 0.1) tn.setText(str(textList[i])) - tn.thaw() # Reposition it card = tn.getCardTransformed() diff --git a/panda/src/glxdisplay/glxGraphicsWindow.cxx b/panda/src/glxdisplay/glxGraphicsWindow.cxx index c31ad72dfb..6324c14c87 100644 --- a/panda/src/glxdisplay/glxGraphicsWindow.cxx +++ b/panda/src/glxdisplay/glxGraphicsWindow.cxx @@ -310,20 +310,6 @@ process_events() { case KeyPress: handle_keystroke(event.xkey); handle_keypress(event.xkey); - - /* - // Temp hack for testing. We pretend that we have received a - // candidate string every type the user types the backslash key. - { - KeySym key = XLookupKeysym(&event.xkey, 0); - - if (key == XK_backslash) { - cerr << "Pressed backslash, sending candidate\n"; - wstring candidate_string = TextEncoder::decode_text("This is a candidate string", TextEncoder::get_default_encoding()); - _input_devices[0].candidate(candidate_string, 5, 7); - } - } - */ break; case KeyRelease: diff --git a/panda/src/pgui/pgEntry.I b/panda/src/pgui/pgEntry.I index d9d09a40fc..d49a399fee 100644 --- a/panda/src/pgui/pgEntry.I +++ b/panda/src/pgui/pgEntry.I @@ -265,6 +265,70 @@ get_obscure_mode() const { return _obscure_mode; } +//////////////////////////////////////////////////////////////////// +// Function: PGEntry::set_candidate_active +// Access: Published +// Description: Specifies the name of the TextProperties structure +// added to the TextPropertiesManager that will be used +// to render candidate strings from the IME, used for +// typing characters in east Asian languages. Each +// candidate string represents one possible way to +// interpret the sequence of keys the user has just +// entered; it should not be considered typed yet, but +// it is important for the user to be able to see what +// he is considering entering. +// +// This particular method sets the properties for the +// subset of the current candidate string that the user +// can actively scroll through. +//////////////////////////////////////////////////////////////////// +INLINE void PGEntry:: +set_candidate_active(const string &candidate_active) { + _candidate_active = candidate_active; +} + +//////////////////////////////////////////////////////////////////// +// Function: PGEntry::get_candidate_active +// Access: Published +// Description: See set_candidate_active(). +//////////////////////////////////////////////////////////////////// +INLINE const string &PGEntry:: +get_candidate_active() const { + return _candidate_active; +} + +//////////////////////////////////////////////////////////////////// +// Function: PGEntry::set_candidate_inactive +// Access: Published +// Description: Specifies the name of the TextProperties structure +// added to the TextPropertiesManager that will be used +// to render candidate strings from the IME, used for +// typing characters in east Asian languages. Each +// candidate string represents one possible way to +// interpret the sequence of keys the user has just +// entered; it should not be considered typed yet, but +// it is important for the user to be able to see what +// he is considering entering. +// +// This particular method sets the properties for the +// subset of the current candidate string that the user +// is not actively scrolling through. +//////////////////////////////////////////////////////////////////// +INLINE void PGEntry:: +set_candidate_inactive(const string &candidate_inactive) { + _candidate_inactive = candidate_inactive; +} + +//////////////////////////////////////////////////////////////////// +// Function: PGEntry::get_candidate_inactive +// Access: Published +// Description: See set_candidate_inactive(). +//////////////////////////////////////////////////////////////////// +INLINE const string &PGEntry:: +get_candidate_inactive() const { + return _candidate_inactive; +} + //////////////////////////////////////////////////////////////////// // Function: PGEntry::get_accept_prefix // Access: Published, Static diff --git a/panda/src/pgui/pgEntry.cxx b/panda/src/pgui/pgEntry.cxx index 1a7ab75458..a457b7ba3e 100644 --- a/panda/src/pgui/pgEntry.cxx +++ b/panda/src/pgui/pgEntry.cxx @@ -28,6 +28,7 @@ #include "mouseButton.h" #include "lineSegs.h" #include "textEncoder.h" +#include "config_text.h" #include @@ -44,6 +45,8 @@ PGEntry(const string &name) : { _cursor_position = 0; _cursor_stale = true; + _candidate_highlight_start = 0; + _candidate_highlight_end = 0; _max_chars = 0; _max_width = 0.0f; _num_lines = 1; @@ -56,6 +59,12 @@ PGEntry(const string &name) : _cursor_def = _text_render_root.attach_new_node("cursor"); _cursor_visible = true; + // These strings are used to specify the TextProperties to apply to + // candidate strings generated from the IME (for entering text in an + // east Asian language). + _candidate_active = "candidate_active"; + _candidate_inactive = "candidate_inactive"; + _cursor_keys_active = true; _obscure_mode = false; @@ -178,6 +187,11 @@ press(const MouseWatcherParameter ¶m, bool background) { } else if ((!background && get_focus()) || (background && get_background_focus())) { // Keyboard button. + if (!_candidate_wtext.empty()) { + _candidate_wtext.clear(); + _text_geom_stale = true; + } + _cursor_position = min(_cursor_position, (int)_wtext.length()); _blink_start = ClockObject::get_global_clock()->get_frame_time(); if (button == KeyboardButton::enter()) { @@ -266,53 +280,42 @@ press(const MouseWatcherParameter ¶m, bool background) { bool too_long = false; if (_max_width > 0.0f) { TextNode *text_node = get_text_def(S_focus); - if (_num_lines <= 1) { - // If we have only one line, we can check the length - // by simply measuring the width of the text. - too_long = (text_node->calc_width(measure_text) > _max_width); + text_node->set_wtext(measure_text); + text_node->set_wordwrap(_max_width); + text_node->set_preserve_trailing_whitespace(true); + text_node->set_max_rows(_num_lines); - } else { - // If we have multiple lines, we have to check the - // length by wordwrapping it and counting up the - // number of lines. - wstring ww_text = text_node->wordwrap_to(measure_text, _max_width, true); - int num_lines = 1; - size_t last_line_start = 0; - for (size_t p = 0; - p < ww_text.length() && !too_long; - ++p) { - if (ww_text[p] == '\n') { - last_line_start = p + 1; - num_lines++; - too_long = (num_lines > _num_lines); - } + too_long = text_node->has_overflow(); + + if (!too_long) { + // We must also ensure that the last line is not too + // long (it might be, because of additional + // whitespace on the end). + wstring ww_text = text_node->get_wordwrapped_wtext(); + size_t last_line_start = ww_text.rfind('\n'); + if (last_line_start == string::npos) { + last_line_start = 0; } - - if (!too_long) { - // We must also ensure that the last line is not too - // long (it might be, because of additional - // whitespace on the end). - wstring last_line = ww_text.substr(last_line_start); - float last_line_width = text_node->calc_width(last_line); - if (num_lines == _num_lines) { - // Mainly we only care about this if we're on - // the very last line. - too_long = (last_line_width > _max_width); - - } else { - // If we're not on the very last line, the width - // is still important, just so we don't allow an - // infinite number of spaces to accumulate. - // However, we must allow at least *one* space - // on the end of a line. - if (_wtext.length() >= 1 && - _wtext[_wtext.length() - 1] == ' ') { - if (last_line_width > _max_width) { - // In this case, however, it's not exactly - // an overflow; we just want to reject the - // space. - return; - } + wstring last_line = ww_text.substr(last_line_start); + float last_line_width = text_node->calc_width(last_line); + if (text_node->get_num_rows() == _num_lines) { + // Mainly we only care about this if we're on + // the very last line. + too_long = (last_line_width > _max_width); + + } else { + // If we're not on the very last line, the width + // is still important, just so we don't allow an + // infinite number of spaces to accumulate. + // However, we must allow at least *one* space + // on the end of a line. + if (_wtext.length() >= 1 && + _wtext[_wtext.length() - 1] == ' ') { + if (last_line_width > _max_width) { + // In this case, however, it's not exactly + // an overflow; we just want to reject the + // space. + return; } } } @@ -355,9 +358,14 @@ keystroke(const MouseWatcherParameter ¶m, bool background) { int keycode = param.get_keycode(); if (use_keystrokes) { + if (!isascii(keycode) || isprint(keycode)) { // A normal visible character. Add a new character to the // text entry, if there's room. + if (!_candidate_wtext.empty()) { + _candidate_wtext.clear(); + _text_geom_stale = true; + } wstring new_char(1, (wchar_t)keycode); if (get_max_chars() > 0 && (int)_wtext.length() >= get_max_chars()) { @@ -382,53 +390,42 @@ keystroke(const MouseWatcherParameter ¶m, bool background) { bool too_long = false; if (_max_width > 0.0f) { TextNode *text_node = get_text_def(S_focus); - if (_num_lines <= 1) { - // If we have only one line, we can check the length - // by simply measuring the width of the text. - too_long = (text_node->calc_width(measure_text) > _max_width); - - } else { - // If we have multiple lines, we have to check the - // length by wordwrapping it and counting up the - // number of lines. - wstring ww_text = text_node->wordwrap_to(measure_text, _max_width, true); - int num_lines = 1; - size_t last_line_start = 0; - for (size_t p = 0; - p < ww_text.length() && !too_long; - ++p) { - if (ww_text[p] == '\n') { - last_line_start = p + 1; - num_lines++; - too_long = (num_lines > _num_lines); - } + text_node->set_wtext(measure_text); + text_node->set_wordwrap(_max_width); + text_node->set_preserve_trailing_whitespace(true); + text_node->set_max_rows(_num_lines); + + too_long = text_node->has_overflow(); + + if (!too_long) { + // We must also ensure that the last line is not too + // long (it might be, because of additional + // whitespace on the end). + wstring ww_text = text_node->get_wordwrapped_wtext(); + size_t last_line_start = ww_text.rfind('\n'); + if (last_line_start == string::npos) { + last_line_start = 0; } - - if (!too_long) { - // We must also ensure that the last line is not too - // long (it might be, because of additional - // whitespace on the end). - wstring last_line = ww_text.substr(last_line_start); - float last_line_width = text_node->calc_width(last_line); - if (num_lines == _num_lines) { - // Mainly we only care about this if we're on - // the very last line. - too_long = (last_line_width > _max_width); - - } else { - // If we're not on the very last line, the width - // is still important, just so we don't allow an - // infinite number of spaces to accumulate. - // However, we must allow at least *one* space - // on the end of a line. - if (_wtext.length() >= 1 && - _wtext[_wtext.length() - 1] == ' ') { - if (last_line_width > _max_width) { - // In this case, however, it's not exactly - // an overflow; we just want to reject the - // space. - return; - } + wstring last_line = ww_text.substr(last_line_start); + float last_line_width = text_node->calc_width(last_line); + if (text_node->get_num_rows() == _num_lines) { + // Mainly we only care about this if we're on + // the very last line. + too_long = (last_line_width > _max_width); + + } else { + // If we're not on the very last line, the width + // is still important, just so we don't allow an + // infinite number of spaces to accumulate. + // However, we must allow at least *one* space + // on the end of a line. + if (_wtext.length() >= 1 && + _wtext[_wtext.length() - 1] == ' ') { + if (last_line_width > _max_width) { + // In this case, however, it's not exactly + // an overflow; we just want to reject the + // space. + return; } } } @@ -467,18 +464,11 @@ void PGEntry:: candidate(const MouseWatcherParameter ¶m, bool background) { if (get_active()) { if (param.has_candidate()) { - // Do something with the candidate string. - TextEncoder te; - const wstring &cs = param.get_candidate_string(); - size_t hs = param.get_highlight_start(); - size_t he = param.get_highlight_end(); - - pgui_cat.info() - << "Candidate: " - << te.encode_wtext(cs.substr(0, hs)) - << " (" << te.encode_wtext(cs.substr(hs, he - hs)) << ") " - << te.encode_wtext(cs.substr(he)) - << "\n"; + // Save the candidate string so it can be displayed. + _candidate_wtext = param.get_candidate_string(); + _candidate_highlight_start = param.get_highlight_start(); + _candidate_highlight_end = param.get_highlight_end(); + _text_geom_stale = true; } } PGItem::candidate(param, background); @@ -563,7 +553,7 @@ setup(float width, int num_lines) { TextNode *text_node = get_text_def(S_focus); float line_height = text_node->get_line_height(); - // Define determine the four corners of the frame. + // Determine the four corners of the frame. LPoint3f ll(0.0f, 0.0f, -0.3f * line_height - (line_height * (num_lines - 1))); LPoint3f ur(width, 0.0f, line_height); LPoint3f lr(ur[0], 0.0f, ll[2]); @@ -640,11 +630,6 @@ setup(float width, int num_lines) { // indicated state. The default if nothing is specified // is the same TextNode returned by // PGItem::get_text_node(). -// -// It is the responsibility of the user to ensure that -// this TextNode has been frozen by a call to freeze(). -// Passing in an unfrozen TextNode will result in -// needless work. //////////////////////////////////////////////////////////////////// void PGEntry:: set_text_def(int state, TextNode *node) { @@ -726,7 +711,7 @@ get_display_wtext() { return _obscured_wtext; } else { - // In normal, non-obscure mode, we display the actual text. + // In normal, non-obscure mode, we display the actual text return _wtext; } } @@ -756,38 +741,54 @@ update_text() { nassertv(node != (TextNode *)NULL); if (_text_geom_stale || node != _last_text_def) { - const wstring &display_wtext = get_display_wtext(); + wstring display_wtext; + + if (_candidate_wtext.empty() || _obscure_mode) { + // If we're not trying to display a candidate string, it's easy: + // just display the current text contents. + display_wtext = get_display_wtext(); + + } else { + // Insert the complex sequence of characters required to show + // the candidate string in a different color. This gets + // inserted at the current cursor position. + wstring source_wtext = get_display_wtext(); + + display_wtext = source_wtext.substr(0, _cursor_position); + display_wtext += wstring(1, text_push_properties_key); + display_wtext += node->decode_text(_candidate_inactive); + display_wtext += wstring(1, text_push_properties_key); + display_wtext += _candidate_wtext.substr(0, _candidate_highlight_start); + display_wtext += wstring(1, text_push_properties_key); + display_wtext += node->decode_text(_candidate_active); + display_wtext += wstring(1, text_push_properties_key); + display_wtext += _candidate_wtext.substr(_candidate_highlight_start, + _candidate_highlight_end - _candidate_highlight_start); + display_wtext += wstring(1, text_pop_properties_key); + display_wtext += _candidate_wtext.substr(_candidate_highlight_end); + display_wtext += wstring(1, text_pop_properties_key); + + display_wtext += source_wtext.substr(_cursor_position); + } // We need to regenerate. _last_text_def = node; + _last_text_def->set_wtext(display_wtext); + _last_text_def->set_wordwrap(_max_width); + _last_text_def->set_preserve_trailing_whitespace(true); + _last_text_def->set_max_rows(_num_lines); - if (_max_width > 0.0f && _num_lines > 1) { - // Fold the text into multiple lines. - wstring ww_text = - _last_text_def->wordwrap_to(display_wtext, _max_width, true); + // Check for multiple lines. + wstring ww_text = _last_text_def->get_wordwrapped_wtext(); - // And chop the lines up into pieces. - _ww_lines.clear(); - size_t p = 0; - size_t q = ww_text.find((wchar_t)'\n'); - while (q != string::npos) { - _ww_lines.push_back(WWLine()); - WWLine &line = _ww_lines.back(); - line._str = ww_text.substr(p, q - p); - - // Get the left edge of the text at this line. - line._left = 0.0f; - if (_last_text_def->get_align() != TextNode::A_left) { - _last_text_def->set_wtext(line._str); - line._left = _last_text_def->get_left(); - } - - p = q + 1; - q = ww_text.find('\n', p); - } + // And chop the lines up into pieces. + _ww_lines.clear(); + size_t p = 0; + size_t q = ww_text.find((wchar_t)'\n'); + while (q != string::npos) { _ww_lines.push_back(WWLine()); WWLine &line = _ww_lines.back(); - line._str = ww_text.substr(p); + line._str = ww_text.substr(p, q - p); // Get the left edge of the text at this line. line._left = 0.0f; @@ -795,17 +796,18 @@ update_text() { _last_text_def->set_wtext(line._str); line._left = _last_text_def->get_left(); } - - _last_text_def->set_wtext(ww_text); - - } else { - // Only one line. - _ww_lines.clear(); - _ww_lines.push_back(WWLine()); - WWLine &line = _ww_lines.back(); - line._str = display_wtext; - - _last_text_def->set_wtext(display_wtext); + + p = q + 1; + q = ww_text.find('\n', p); + } + _ww_lines.push_back(WWLine()); + WWLine &line = _ww_lines.back(); + line._str = ww_text.substr(p); + + // Get the left edge of the text at this line. + line._left = 0.0f; + if (_last_text_def->get_align() != TextNode::A_left) { + _last_text_def->set_wtext(line._str); line._left = _last_text_def->get_left(); } @@ -844,7 +846,11 @@ update_cursor() { } nassertv(row >= 0 && row < (int)_ww_lines.size()); - nassertv(column >= 0 && column <= (int)_ww_lines[row]._str.length()); + + // It is possible for this to become untrue legitimately, if due + // to a candidate string we have wordwrapped down the last part of + // the line containing the cursor. + //nassertv(column >= 0 && column <= (int)_ww_lines[row]._str.length()); float width = _last_text_def->calc_width(_ww_lines[row]._str.substr(0, column)); diff --git a/panda/src/pgui/pgEntry.h b/panda/src/pgui/pgEntry.h index 94d6617354..4a633ef1a5 100644 --- a/panda/src/pgui/pgEntry.h +++ b/panda/src/pgui/pgEntry.h @@ -98,6 +98,12 @@ PUBLISHED: INLINE void set_obscure_mode(bool flag); INLINE bool get_obscure_mode() const; + INLINE void set_candidate_active(const string &candidate_active); + INLINE const string &get_candidate_active() const; + + INLINE void set_candidate_inactive(const string &candidate_inactive); + INLINE const string &get_candidate_inactive() const; + void set_text_def(int state, TextNode *node); TextNode *get_text_def(int state) const; @@ -133,10 +139,17 @@ private: bool _cursor_stale; bool _cursor_visible; + wstring _candidate_wtext; + size_t _candidate_highlight_start; + size_t _candidate_highlight_end; + int _max_chars; float _max_width; int _num_lines; + string _candidate_active; + string _candidate_inactive; + typedef pvector< PT(TextNode) > TextDefs; TextDefs _text_defs; diff --git a/panda/src/text/Sources.pp b/panda/src/text/Sources.pp index 08a8459644..db91aaff10 100644 --- a/panda/src/text/Sources.pp +++ b/panda/src/text/Sources.pp @@ -9,7 +9,7 @@ putil gobj pgraph linmath \ pnmtext pnmimage gsgbase mathutil - #define COMBINED_SOURCES $[TARGET]_composite1.cxx + #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx #define SOURCES \ config_text.h \ @@ -20,9 +20,12 @@ fontPool.I fontPool.h \ geomTextGlyph.I geomTextGlyph.h \ staticTextFont.I staticTextFont.h \ + textAssembler.I textAssembler.h \ textFont.I textFont.h \ textGlyph.I textGlyph.h \ - textNode.I textNode.h textNode.cxx + textNode.I textNode.h \ + textProperties.I textProperties.h \ + textPropertiesManager.I textPropertiesManager.h #define INCLUDED_SOURCES \ config_text.cxx \ @@ -33,7 +36,11 @@ fontPool.cxx \ geomTextGlyph.cxx \ staticTextFont.cxx \ - textFont.cxx textGlyph.cxx + textAssembler.cxx \ + textFont.cxx textGlyph.cxx \ + textNode.cxx \ + textProperties.cxx \ + textPropertiesManager.cxx #define INSTALL_HEADERS \ config_text.h \ @@ -43,9 +50,12 @@ fontPool.I fontPool.h \ geomTextGlyph.I geomTextGlyph.h \ staticTextFont.I staticTextFont.h \ + textAssembler.I textAssembler.h \ textFont.I textFont.h \ textGlyph.I textGlyph.h \ - textNode.I textNode.h + textNode.I textNode.h \ + textProperties.I textProperties.h \ + textPropertiesManager.I textPropertiesManager.h #define IGATESCAN all diff --git a/panda/src/text/config_text.cxx b/panda/src/text/config_text.cxx index c861fe35e7..5d0b6c0015 100644 --- a/panda/src/text/config_text.cxx +++ b/panda/src/text/config_text.cxx @@ -20,6 +20,7 @@ #include "staticTextFont.h" #include "textFont.h" #include "textNode.h" +#include "textProperties.h" #include "dynamicTextFont.h" #include "dynamicTextPage.h" #include "geomTextGlyph.h" @@ -48,6 +49,16 @@ const string text_default_font = config_text.GetString("text-default-font", ""); const float text_tab_width = config_text.GetFloat("text-tab-width", 5.0f); +// This is the decimal character number that, embedded in a string, is +// used to bracket the name of a TextProperties structure added to the +// TextPropertiesManager object, to control the appearance of +// subsequent text. +const int text_push_properties_key = config_text.GetInt("text-push-properties-key", 1); + +// This is the decimal character number that undoes the effect of a +// previous appearance of text_push_properties_key. +const int text_pop_properties_key = config_text.GetInt("text-pop-properties-key", 2); + // This is the decimal character number that, embedded in a string, is // identified as the soft-hyphen character. const int text_soft_hyphen_key = config_text.GetInt("text-soft-hyphen-key", 3); @@ -98,6 +109,7 @@ init_libtext() { StaticTextFont::init_type(); TextFont::init_type(); TextNode::init_type(); + TextProperties::init_type(); #ifdef HAVE_FREETYPE DynamicTextFont::init_type(); diff --git a/panda/src/text/config_text.h b/panda/src/text/config_text.h index 064027d8a4..057b37d03a 100644 --- a/panda/src/text/config_text.h +++ b/panda/src/text/config_text.h @@ -38,6 +38,8 @@ extern const bool text_small_caps; extern const float text_small_caps_scale; extern const string text_default_font; extern const float text_tab_width; +extern const int text_push_properties_key; +extern const int text_pop_properties_key; extern const int text_soft_hyphen_key; extern const int text_soft_break_key; extern wstring *text_soft_hyphen_output; diff --git a/panda/src/text/fontPool.cxx b/panda/src/text/fontPool.cxx index 733e2e34b2..957e0ed3ab 100644 --- a/panda/src/text/fontPool.cxx +++ b/panda/src/text/fontPool.cxx @@ -17,6 +17,8 @@ //////////////////////////////////////////////////////////////////// #include "fontPool.h" +#include "staticTextFont.h" +#include "dynamicTextFont.h" #include "config_util.h" #include "config_express.h" #include "virtualFileSystem.h" diff --git a/panda/src/text/textAssembler.I b/panda/src/text/textAssembler.I new file mode 100644 index 0000000000..be2888a67d --- /dev/null +++ b/panda/src/text/textAssembler.I @@ -0,0 +1,91 @@ +// Filename: textAssembler.I +// Created by: drose (06Apr04) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_ul +// Access: Public +// Description: Returns the upper-left corner of the assembled text, +// in 2-d text coordinates. +//////////////////////////////////////////////////////////////////// +INLINE const LVector2f &TextAssembler:: +get_ul() const { + return _ul; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_lr +// Access: Public +// Description: Returns the lower-right corner of the assembled text, +// in 2-d text coordinates. +//////////////////////////////////////////////////////////////////// +INLINE const LVector2f &TextAssembler:: +get_lr() const { + return _lr; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_num_rows +// Access: Public +// Description: Returns the number of rows of text after it has all +// been wordwrapped and assembled. +//////////////////////////////////////////////////////////////////// +INLINE int TextAssembler:: +get_num_rows() const { + return _num_rows; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::calc_width +// Access: Private, Static +// Description: Returns the width of a single character, according to +// its associated font. +//////////////////////////////////////////////////////////////////// +INLINE float TextAssembler:: +calc_width(const TextCharacter &tch) { + return calc_width(tch._character, *tch._properties); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::TextCharacter::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE TextAssembler::TextCharacter:: +TextCharacter(wchar_t character, const TextProperties *properties) : + _character(character), + _properties(properties) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::GlyphPlacement::add_piece +// Access: Public +// Description: Adds a piece of the glyph, consisting of a single +// Geom and an associated RenderState. Typically, a +// glyph will have exactly one piece; there will only be +// multiple pieces in the case of cheesy accent marks or +// ligatures. +//////////////////////////////////////////////////////////////////// +INLINE void TextAssembler::GlyphPlacement:: +add_piece(Geom *geom, const RenderState *state) { + Piece piece; + piece._geom = geom; + piece._state = state; + _pieces.push_back(piece); +} diff --git a/panda/src/text/textAssembler.cxx b/panda/src/text/textAssembler.cxx new file mode 100644 index 0000000000..484d626be6 --- /dev/null +++ b/panda/src/text/textAssembler.cxx @@ -0,0 +1,1450 @@ +// Filename: textAssembler.cxx +// Created by: drose (06Apr04) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#include "textAssembler.h" +#include "textGlyph.h" +#include "cullFaceAttrib.h" +#include "colorAttrib.h" +#include "cullBinAttrib.h" +#include "textureAttrib.h" +#include "transparencyAttrib.h" +#include "textPropertiesManager.h" +#include "textEncoder.h" +#include "config_text.h" + +#include + +#ifndef CPPPARSER // interrogate has a bit of trouble with wstring. + + + +// This is the factor by which CT_small scales the character down. +static const float small_accent_scale = 0.6f; + +// This is the factor by which CT_tiny scales the character down. +static const float tiny_accent_scale = 0.4f; + +// This is the factor by which CT_squash scales the character in X and Y. +static const float squash_accent_scale_x = 0.8f; +static const float squash_accent_scale_y = 0.5f; + +// This is the factor by which CT_small_squash scales the character in X and Y. +static const float small_squash_accent_scale_x = 0.6f; +static const float small_squash_accent_scale_y = 0.3f; + +// This is the factor by which the advance is reduced for the first +// character of a two-character ligature. +static const float ligature_advance_scale = 0.6f; + + +//////////////////////////////////////////////////////////////////// +// Function: isspacew +// Description: An internal function that works like isspace() but is +// safe to call for a wide character. +//////////////////////////////////////////////////////////////////// +static INLINE bool +isspacew(unsigned int ch) { + return isascii(ch) && isspace(ch); +} + +//////////////////////////////////////////////////////////////////// +// Function: isbreakpoint +// Description: An internal function, similar to isspace(), except it +// does not consider newlines to be whitespace. It also +// includes the soft-hyphen character. +//////////////////////////////////////////////////////////////////// +static INLINE bool +isbreakpoint(unsigned int ch) { + return (ch == ' ' || ch == '\t' || + ch == (unsigned int)text_soft_hyphen_key || + ch == (unsigned int)text_soft_break_key); +} + + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::Constructor +// Access: Public +// Description: Places all of the indicated text according to the +// associated TextProperties. +//////////////////////////////////////////////////////////////////// +TextAssembler:: +TextAssembler(TextEncoder *encoder) : _encoder(encoder) { + clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +TextAssembler:: +~TextAssembler() { + clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::clear +// Access: Public +// Description: Reinitializes the contents of the TextAssembler. +//////////////////////////////////////////////////////////////////// +void TextAssembler:: +clear() { + _num_rows = 0; + _ul.set(0.0f, 0.0f); + _lr.set(0.0f, 0.0f); + + _wordwrapped_string.clear(); + + PropertiesList::iterator li; + for (li = _properties_list.begin(); li != _properties_list.end(); ++li) { + delete (*li); + } + _properties_list.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::set_wtext +// Access: Public +// Description: Accepts a new text string and associated properties +// structure, and precomputes the wordwrapping layout +// appropriately. After this call, +// get_wordwrapped_wtext() and get_num_rows() can be +// called. +// +// If max_rows is greater than zero, no more than +// max_rows will be accepted. Text beyond that will be +// truncated. +// +// The return value is true if all the text is accepted, +// or false if some was truncated. +//////////////////////////////////////////////////////////////////// +bool TextAssembler:: +set_wtext(const wstring &wtext, const TextProperties &properties, + int max_rows) { + clear(); + + // First, expand all of the embedded TextProperties references + // within the string. + TextString text_string; + wstring::const_iterator si = wtext.begin(); + scan_wtext(si, wtext.end(), &properties, text_string); + + while (si != wtext.end()) { + // If we returned without consuming the whole string, it means + // there was an embedded text_pop_properties_key that didn't match + // the push. That's worth a warning, and then go back and pick up + // the rest of the string. + text_cat.warning() + << "pop_properties encountered without preceding push_properties.\n"; + scan_wtext(si, wtext.end(), &properties, text_string); + } + + // Then apply any wordwrap requirements. + return wordwrap_text(text_string, _wordwrapped_string, max_rows); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_wordwrapped_wtext +// Access: Public +// Description: Returns a wstring that represents the contents of the +// text, as it has been formatted by wordwrap rules. +// This will not contain any embedded special characters +// like \1 or \3. +//////////////////////////////////////////////////////////////////// +wstring TextAssembler:: +get_wordwrapped_wtext() const { + wstring wtext; + + TextString::const_iterator ti; + for (ti = _wordwrapped_string.begin(); + ti != _wordwrapped_string.end(); + ++ti) { + wtext += (*ti)._character; + } + + return wtext; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::assemble_text +// Access: Public +// Description: Actually assembles all of the text into a GeomNode, +// and returns the node (or possibly a parent of the +// node, to keep the shadow separate). Once this has +// been called, you may query the extents of the text +// via get_ul(), get_lr(). +//////////////////////////////////////////////////////////////////// +PT(PandaNode) TextAssembler:: +assemble_text() { + // Now assemble the text into glyphs. + PlacedGlyphs placed_glyphs; + assemble_paragraph(_wordwrapped_string.begin(), _wordwrapped_string.end(), + placed_glyphs); + + // Now that we have a bunch of GlyphPlacements, pull out the Geoms + // and put them under a common node. + PT(PandaNode) parent_node = new PandaNode("common"); + + PT(GeomNode) shadow_node = new GeomNode("shadow"); + PT(GeomNode) text_node = new GeomNode("text"); + + const TextProperties *properties = NULL; + CPT(RenderState) text_state; + CPT(RenderState) shadow_state; + LMatrix4f shadow_xform; + + bool any_shadow = false; + + PlacedGlyphs::const_iterator pgi; + for (pgi = placed_glyphs.begin(); pgi != placed_glyphs.end(); ++pgi) { + const GlyphPlacement *placement = (*pgi); + + if (placement->_properties != properties) { + // Get a new set of properties for future glyphs. + properties = placement->_properties; + text_state = RenderState::make_empty(); + shadow_state = RenderState::make_empty(); + shadow_xform = LMatrix4f::ident_mat(); + + if (properties->has_text_color()) { + text_state = text_state->add_attrib(ColorAttrib::make_flat(properties->get_text_color())); + if (properties->get_text_color()[3] != 1.0) { + text_state = text_state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha)); + } + } + + if (properties->has_bin()) { + text_state = text_state->add_attrib(CullBinAttrib::make(properties->get_bin(), properties->get_draw_order() + 2)); + } + + if (properties->has_shadow()) { + if (properties->has_shadow_color()) { + shadow_state = shadow_state->add_attrib(ColorAttrib::make_flat(properties->get_shadow_color())); + if (properties->get_shadow_color()[3] != 1.0) { + shadow_state = shadow_state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha)); + } + } + + if (properties->has_bin()) { + shadow_state = shadow_state->add_attrib(CullBinAttrib::make(properties->get_bin(), properties->get_draw_order() + 1)); + } + + LVector2f offset = properties->get_shadow(); + shadow_xform = LMatrix4f::translate_mat(offset[0], 0.0f, -offset[1]); + } + } + + // We have to place the shadow first, because it copies as it + // goes, while the place-text function just stomps on the + // vertices. + if (properties->has_shadow()) { + placement->assign_copy_to(shadow_node, shadow_state, shadow_xform); + any_shadow = true; + } + placement->assign_to(text_node, text_state); + delete placement; + } + placed_glyphs.clear(); + + if (any_shadow) { + // The shadow_node must appear first to guarantee the correct + // rendering order. + parent_node->add_child(shadow_node); + } + parent_node->add_child(text_node); + + return parent_node; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::calc_width +// Access: Private, Static +// Description: Returns the width of a single character, according to +// its associated font. This also correctly calculates +// the width of cheesy ligatures and accented +// characters, which may not exist in the font as such. +//////////////////////////////////////////////////////////////////// +float TextAssembler:: +calc_width(wchar_t character, const TextProperties &properties) { + if (character == ' ') { + // A space is a special case. + TextFont *font = properties.get_font(); + nassertr(font != (TextFont *)NULL, 0.0f); + return font->get_space_advance(); + } + + bool got_glyph; + const TextGlyph *first_glyph; + const TextGlyph *second_glyph; + UnicodeLatinMap::AccentType accent_type; + int additional_flags; + float glyph_scale; + float advance_scale; + get_character_glyphs(character, &properties, + got_glyph, first_glyph, second_glyph, accent_type, + additional_flags, glyph_scale, advance_scale); + + float advance = 0.0f; + + if (first_glyph != (TextGlyph *)NULL) { + advance = first_glyph->get_advance() * advance_scale; + } + if (second_glyph != (TextGlyph *)NULL) { + advance += second_glyph->get_advance(); + } + + glyph_scale *= properties.get_glyph_scale(); + + return advance * glyph_scale; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::scan_wtext +// Access: Private +// Description: Scans through the text string, decoding embedded +// references to TextProperties. The string is copied +// character-by-character into the indicated TextString +// output. +// +// As new TextProperties are discovered, TextProperties +// structures are allocated and pushed into the +// _properties_list list; these pointers are +// referenced by the text_string. When the text_string +// is no longer need, the TextProperties structures in +// _properties_list should be deleted to free up +// memory. +//////////////////////////////////////////////////////////////////// +void TextAssembler:: +scan_wtext(wstring::const_iterator &si, + const wstring::const_iterator &send, + const TextProperties *current_properties, + TextAssembler::TextString &text_string) { + while (si != send) { + if ((*si) == text_push_properties_key) { + // This indicates a nested properties structure. Pull off the + // name of the TextProperties structure, which is everything + // until the next text_push_properties_key. + wstring wname; + ++si; + while (si != send && (*si) != text_push_properties_key) { + wname += (*si); + ++si; + } + + if (si == send) { + // We didn't close the text_push_properties_key. That's an + // error. + text_cat.warning() + << "Unclosed push_properties in text.\n"; + return; + } + + ++si; + + // Now we have to encode the wstring into a string, for lookup + // in the TextPropertiesManager. + string name = _encoder->encode_wtext(wname); + + TextPropertiesManager *manager = + TextPropertiesManager::get_global_ptr(); + + // Define the new properties by extending the current properties. + TextProperties *new_properties = new TextProperties(*current_properties); + new_properties->add_properties(manager->get_properties(name)); + _properties_list.push_back(new_properties); + + // And recursively scan with the nested properties. + scan_wtext(si, send, new_properties, text_string); + + if (text_cat.is_debug()) { + if (si == send) { + // The push was not closed by a pop. That's not an error, + // since we allow people to be sloppy about that; but we'll + // print a debug message at least. + text_cat.debug() + << "push_properties not matched by pop_properties.\n"; + } + } + + } else if ((*si) == text_pop_properties_key) { + // This indicates the undoing of a previous push_properties_key. + // We simply return to the previous level. + ++si; + return; + + } else { + // A normal character. Apply it. + text_string.push_back(TextCharacter(*si, current_properties)); + ++si; + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::wordwrap_text +// Access: Private +// 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). +// +// If max_rows is greater than zero, no more than +// max_rows will be accepted. Text beyond that will be +// truncated. +// +// The return value is true if all the text is accepted, +// or false if some was truncated. +//////////////////////////////////////////////////////////////////// +bool TextAssembler:: +wordwrap_text(const TextAssembler::TextString &text, + TextAssembler::TextString &output_text, + int max_rows) { + if (text.empty()) { + // A special case: empty text means no rows. + _num_rows = 0; + return true; + } + + size_t p = 0; + _num_rows = 1; + + // Preserve any initial whitespace and newlines. + float initial_width = 0.0f; + while (p < text.size() && isspacew(text[p]._character)) { + if (text[p]._character == '\n') { + initial_width = 0.0f; + if (max_rows > 0 && _num_rows >= max_rows) { + // Truncate. + return false; + } + _num_rows++; + } else { + initial_width += calc_width(text[p]); + } + output_text.push_back(text[p]); + p++; + } + bool needs_newline = false; + + while (p < text.size()) { + nassertr(!isspacew(text[p]._character), false); + + // 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; + size_t last_space = 0; + float last_space_width = 0.0f; + + bool any_hyphens = false; + size_t last_hyphen = 0; + bool output_hyphen = false; + + bool overflow = false; + float wordwrap_width = -1.0f; + + bool last_was_space = false; + float width = initial_width; + while (q < text.size() && text[q]._character != '\n') { + if (text[q]._properties->has_wordwrap()) { + wordwrap_width = text[q]._properties->get_wordwrap(); + } else { + wordwrap_width = -1.0f; + } + + if (isspacew(text[q]._character) || + text[q]._character == text_soft_break_key) { + if (!last_was_space) { + any_spaces = true; + // We only care about logging whether there is a soft-hyphen + // character to the right of the rightmost space. Each time + // we encounter a space, we reset this counter. + any_hyphens = false; + last_space = q; + last_space_width = width; + last_was_space = true; + } + } else { + last_was_space = false; + } + + // A soft hyphen character is not printed, but marks a point + // at which we might hyphenate a word if we need to. + if (text[q]._character == text_soft_hyphen_key && + wordwrap_width > 0.0f) { + // We only consider this as a possible hyphenation point if + // (a) it is not the very first character, and (b) there is + // enough room for a hyphen character to be printed following + // it. + if (q != p && width + calc_hyphen_width(text[q]) <= wordwrap_width) { + any_hyphens = true; + last_hyphen = q; + } + + } else if (text[q]._character != text_soft_break_key) { + // Some normal, printable character. + width += calc_width(text[q]); + } + + q++; + + if (wordwrap_width > 0.0f && width > wordwrap_width) { + // Oops, too many. + q--; + overflow = true; + break; + } + } + + if (overflow) { + // If we stopped because we exceeded the wordwrap width, then + // try to find an appropriate place to wrap the line or to + // hyphenate, if necessary. + nassertr(wordwrap_width > 0.0f, false); + + if (any_spaces && last_space_width / wordwrap_width >= text_hyphen_ratio) { + // If we have a space that ended up within our safety margin, + // don't use any soft-hyphen characters. + any_hyphens = false; + } + + if (any_hyphens) { + // If we still have a soft-hyphen character, use it. + q = last_hyphen; + output_hyphen = true; + + } else if (any_spaces) { + // Otherwise, break at a space if we can. + q = last_space; + + } else { + // Otherwise, this is a forced break. Accept the longest line + // we can that does not leave the next line beginning with one + // of our forbidden characters. + size_t i = 0; + while ((int)i < text_max_never_break && q - i > p && + text_never_break_before->find(text[q - i]._character) != wstring::npos) { + i++; + } + if ((int)i < text_max_never_break) { + q -= i; + } + } + } + + // Skip additional whitespace between the lines. + size_t next_start = q; + while (next_start < text.size() && + isbreakpoint(text[next_start]._character)) { + next_start++; + } + + // Trim off any more blanks on the end. + while (q > p && isspacew(text[q - 1]._character)) { + 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.size() && + isbreakpoint(text[next_start]._character)) { + next_start++; + } + } + + if (needs_newline) { + if (max_rows > 0 && _num_rows >= max_rows) { + // Truncate. + return false; + } + _num_rows++; + output_text.push_back(TextCharacter('\n', text[next_start - 1]._properties)); + } + needs_newline = true; + + if (text[next_start - 1]._properties->get_preserve_trailing_whitespace()) { + q = next_start; + } + + for (size_t pi = p; pi < q; pi++) { + if (text[pi]._character != text_soft_hyphen_key && + text[pi]._character != text_soft_break_key) { + output_text.push_back(text[pi]); + } + } + if (output_hyphen) { + wstring::const_iterator wi; + for (wi = text_soft_hyphen_output->begin(); + wi != text_soft_hyphen_output->end(); + ++wi) { + output_text.push_back(TextCharacter(*wi, text[last_hyphen]._properties)); + } + } + + // Now prepare to wrap the next line. + + if (next_start < text.size() && text[next_start]._character == '\n') { + // Preserve a single embedded newline. + if (max_rows > 0 && _num_rows >= max_rows) { + // Truncate. + return false; + } + _num_rows++; + output_text.push_back(text[next_start]); + next_start++; + needs_newline = false; + } + p = next_start; + + // Preserve any initial whitespace and newlines. + initial_width = 0.0f; + while (p < text.size() && isspacew(text[p]._character)) { + if (text[p]._character == '\n') { + initial_width = 0.0f; + if (max_rows > 0 && _num_rows >= max_rows) { + // Truncate. + return false; + } + _num_rows++; + } else { + initial_width += calc_width(text[p]); + } + output_text.push_back(text[p]); + p++; + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::calc_hyphen_width +// Access: Private, Static +// Description: Returns the width of the soft-hyphen replacement +// string, according to the indicated character's +// associated font. +//////////////////////////////////////////////////////////////////// +float TextAssembler:: +calc_hyphen_width(const TextCharacter &tch) { + TextFont *font = tch._properties->get_font(); + nassertr(font != (TextFont *)NULL, 0.0f); + + float hyphen_width = 0.0f; + wstring::const_iterator wi; + for (wi = text_soft_hyphen_output->begin(); + wi != text_soft_hyphen_output->end(); + ++wi) { + hyphen_width += calc_width(*wi, *tch._properties); + } + + return hyphen_width; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::assemble_paragraph +// Access: Private +// Description: Fills up _placed_glyphs, _ul, _lr, and _num_rows with +// the contents of the indicated text. +//////////////////////////////////////////////////////////////////// +void TextAssembler:: +assemble_paragraph(TextAssembler::TextString::const_iterator si, + const TextAssembler::TextString::const_iterator &send, + TextAssembler::PlacedGlyphs &placed_glyphs) { + _ul.set(0.0f, 0.0f); + _lr.set(0.0f, 0.0f); + int num_rows = 0; + + float posy = 0.0f; + while (si != send) { + // First, assemble all the glyphs of this row. + PlacedGlyphs row_placed_glyphs; + float row_width, line_height; + TextProperties::Alignment align; + assemble_row(si, send, row_placed_glyphs, + row_width, line_height, align); + + // Now move the row to its appropriate position. This might + // involve a horizontal as well as a vertical translation. + LMatrix4f mat = LMatrix4f::ident_mat(); + + if (num_rows == 0) { + // If this is the first row, account for its space. + _ul[1] = 0.8f * line_height; + + } else { + // If it is not the first row, shift the text downward by + // line_height from the previous row. + posy -= line_height; + } + _lr[1] = posy - 0.2f * line_height; + + // Apply the requested horizontal alignment to the row. + switch (align) { + case TextProperties::A_left: + mat.set_row(3, LVector3f(0.0f, 0.0f, posy)); + _lr[0] = max(_lr[0], row_width); + break; + + case TextProperties::A_right: + mat.set_row(3, LVector3f(-row_width, 0.0f, posy)); + _ul[0] = min(_ul[0], -row_width); + break; + + case TextProperties::A_center: + { + float half_row_width = 0.5f * row_width; + mat.set_row(3, LVector3f(-half_row_width, 0.0f, posy)); + _lr[0] = max(_lr[0], half_row_width); + _ul[0] = min(_ul[0], -half_row_width); + } + break; + } + + // Now store the geoms we assembled. + PlacedGlyphs::iterator pi; + for (pi = row_placed_glyphs.begin(); pi != row_placed_glyphs.end(); ++pi) { + (*pi)->_xform *= mat; + placed_glyphs.push_back(*pi); + } + + // Advance to the next line. + if (si != send) { + // Skip past the newline. + ++si; + } + num_rows++; + } + + // num_rows may be smaller than _num_rows, if there are trailing + // newlines on the string. +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::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 _geom_node), and +// computes the length of the row and the maximum +// line_height of all the fonts used in the row. The +// source pointer is moved to the terminating character. +//////////////////////////////////////////////////////////////////// +void TextAssembler:: +assemble_row(TextAssembler::TextString::const_iterator &si, + const TextAssembler::TextString::const_iterator &send, + TextAssembler::PlacedGlyphs &row_placed_glyphs, + float &row_width, float &line_height, + TextProperties::Alignment &align) { + line_height = 0.0f; + float xpos = 0.0f; + align = TextProperties::A_left; + + while (si != send && (*si)._character != '\n') { + wchar_t character = (*si)._character; + const TextProperties *properties = (*si)._properties; + + TextFont *font = properties->get_font(); + nassertv(font != (TextFont *)NULL); + + // We get the row's alignment property from that of the last + // character to be placed in the row. + align = properties->get_align(); + + // And the height of the row is the maximum of all the fonts used + // within the row. + line_height = max(line_height, font->get_line_height()); + + if (character == ' ') { + // A space is a special case. + xpos += font->get_space_advance(); + + } else if (character == '\t') { + // So is a tab character. + float tab_width = properties->get_tab_width(); + xpos = (floor(xpos / tab_width) + 1.0f) * tab_width; + + } else if (character == text_soft_hyphen_key) { + // And so is the 'soft-hyphen' key character. + + } else { + // A printable character. + bool got_glyph; + const TextGlyph *first_glyph; + const TextGlyph *second_glyph; + UnicodeLatinMap::AccentType accent_type; + int additional_flags; + float glyph_scale; + float advance_scale; + get_character_glyphs(character, properties, + got_glyph, first_glyph, second_glyph, accent_type, + additional_flags, glyph_scale, advance_scale); + + if (!got_glyph) { + text_cat.warning() + << "No definition in " << font->get_name() + << " for character " << character; + if (character < 128 && isprint((unsigned int)character)) { + text_cat.warning(false) + << " ('" << (char)character << "')"; + } + text_cat.warning(false) + << "\n"; + } + + // Build up a GlyphPlacement, indicating all of the Geoms that go + // into this character. Normally, there is only one Geom per + // character, but it may involve multiple Geoms if we need to + // add cheesy accents or ligatures. + GlyphPlacement *placement = new GlyphPlacement; + row_placed_glyphs.push_back(placement); + + float advance = 0.0f; + + if (first_glyph != (TextGlyph *)NULL) { + PT(Geom) first_char_geom = first_glyph->get_geom(); + if (first_char_geom != (Geom *)NULL) { + placement->add_piece(first_char_geom, first_glyph->get_state()); + } + advance = first_glyph->get_advance() * advance_scale; + } + if (second_glyph != (TextGlyph *)NULL) { + PT(Geom) second_char_geom = second_glyph->get_geom(); + if (second_char_geom != (Geom *)NULL) { + second_char_geom->transform_vertices(LMatrix4f::translate_mat(advance, 0.0f, 0.0f)); + placement->add_piece(second_char_geom, second_glyph->get_state()); + } + advance += second_glyph->get_advance(); + } + + glyph_scale *= properties->get_glyph_scale(); + + // Now compute the matrix that will transform the glyph (or + // glyphs) into position. + LMatrix4f glyph_xform = LMatrix4f::scale_mat(glyph_scale); + + if (accent_type != UnicodeLatinMap::AT_none || additional_flags != 0) { + // If we have some special handling to perform, do so now. + // This will probably require the bounding volume of the + // glyph, so go get that. + LPoint3f min_vert, max_vert; + bool found_any = false; + placement->calc_tight_bounds(min_vert, max_vert, found_any); + + if (found_any) { + LPoint3f centroid = (min_vert + max_vert) / 2.0f; + tack_on_accent(accent_type, min_vert, max_vert, centroid, + properties, placement); + + if ((additional_flags & UnicodeLatinMap::AF_turned) != 0) { + // Invert the character. Should we also invert the accent + // mark, so that an accent that would have been above the + // glyph will now be below it? That's what we do here, + // which is probably the right thing to do for n-tilde, + // but not for most of the rest of the accent marks. For + // now we'll assume there are no characters with accent + // marks that also have the turned flag. + + // We rotate the character around its centroid, which may + // not always be the right point, but it's the best we've + // got and it's probably pretty close. + LMatrix4f rotate = + LMatrix4f::translate_mat(-centroid) * + LMatrix4f::rotate_mat_normaxis(180.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) * + LMatrix4f::translate_mat(centroid); + glyph_xform *= rotate; + } + } + } + + glyph_xform(3, 0) += xpos; + glyph_xform(3, 2) += properties->get_glyph_shift(); + + if (properties->has_slant()) { + LMatrix4f shear(1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + properties->get_slant(), 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + glyph_xform = shear * glyph_xform; + } + + placement->_xform = glyph_xform; + placement->_properties = properties; + + xpos += advance * glyph_scale; + } + ++si; + } + + row_width = xpos; +} + + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::get_character_glyphs +// Access: Private, Static +// Description: Looks up the glyph(s) from the font for the +// appropriate character. If the desired glyph isn't +// available (especially in the case of an accented +// letter), tries to find a suitable replacement. +// Normally, only one glyph is returned per character, +// but in the case in which we have to simulate a +// missing ligature in the font, two glyphs might be +// returned. +// +// All parameters except the first two are output +// parameters. got_glyph is set true if the glyph (or +// an acceptable substitute) is successfully found, +// false otherwise; but even if it is false, glyph might +// still be non-NULL, indicating a stand-in glyph for a +// missing character. +//////////////////////////////////////////////////////////////////// +void TextAssembler:: +get_character_glyphs(int character, const TextProperties *properties, + bool &got_glyph, const TextGlyph *&glyph, + const TextGlyph *&second_glyph, + UnicodeLatinMap::AccentType &accent_type, + int &additional_flags, + float &glyph_scale, float &advance_scale) { + TextFont *font = properties->get_font(); + nassertv(font != (TextFont *)NULL); + + got_glyph = false; + glyph = NULL; + second_glyph = NULL; + accent_type = UnicodeLatinMap::AT_none; + additional_flags = 0; + glyph_scale = 1.0f; + advance_scale = 1.0f; + + // Maybe we should remap the character to something else--e.g. a + // small capital. + const UnicodeLatinMap::Entry *map_entry = + UnicodeLatinMap::look_up(character); + if (map_entry != NULL) { + if (properties->get_small_caps() && + map_entry->_toupper_character != character) { + character = map_entry->_toupper_character; + map_entry = UnicodeLatinMap::look_up(character); + glyph_scale = properties->get_small_caps_scale(); + } + } + + got_glyph = font->get_glyph(character, glyph); + if (!got_glyph && map_entry != NULL && map_entry->_ascii_equiv != 0) { + // If we couldn't find the Unicode glyph, try the ASCII + // equivalent (without the accent marks). + got_glyph = font->get_glyph(map_entry->_ascii_equiv, glyph); + + if (!got_glyph && map_entry->_toupper_character != character) { + // If we still couldn't find it, try the uppercase + // equivalent. + character = map_entry->_toupper_character; + map_entry = UnicodeLatinMap::look_up(character); + if (map_entry != NULL) { + got_glyph = font->get_glyph(map_entry->_ascii_equiv, glyph); + } + } + + if (got_glyph) { + accent_type = map_entry->_accent_type; + additional_flags = map_entry->_additional_flags; + + bool got_second_glyph = false; + if (map_entry->_ascii_additional != 0) { + // There's another character, too--probably a ligature. + got_second_glyph = + font->get_glyph(map_entry->_ascii_additional, second_glyph); + } + + if ((additional_flags & UnicodeLatinMap::AF_ligature) != 0 && + got_second_glyph) { + // If we have two letters that are supposed to be in a + // ligature, just jam them together. + additional_flags &= ~UnicodeLatinMap::AF_ligature; + advance_scale = ligature_advance_scale; + } + + if ((additional_flags & UnicodeLatinMap::AF_smallcap) != 0) { + additional_flags &= ~UnicodeLatinMap::AF_smallcap; + glyph_scale = properties->get_small_caps_scale(); + } + } + } +} + + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::tack_on_accent +// Access: Private, Static +// Description: This is a cheesy attempt to tack on an accent to an +// ASCII letter for which we don't have the appropriate +// already-accented glyph in the font. +//////////////////////////////////////////////////////////////////// +void TextAssembler:: +tack_on_accent(UnicodeLatinMap::AccentType accent_type, + const LPoint3f &min_vert, const LPoint3f &max_vert, + const LPoint3f ¢roid, + const TextProperties *properties, + TextAssembler::GlyphPlacement *placement) { + switch (accent_type) { + case UnicodeLatinMap::AT_grave: + // We use the slash as the grave and acute accents. ASCII does + // have a grave accent character, but a lot of fonts put the + // reverse apostrophe there instead. And some fonts (particularly + // fonts from mf) don't even do backslash. + tack_on_accent('/', CP_above, CT_small_squash_mirror_y, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_acute: + tack_on_accent('/', CP_above, CT_small_squash, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_breve: + tack_on_accent(')', CP_above, CT_tiny_rotate_270, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_inverted_breve: + tack_on_accent('(', CP_above, CT_tiny_rotate_270, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_circumflex: + tack_on_accent('^', CP_above, CT_none, min_vert, max_vert, centroid, + properties, placement) || + tack_on_accent('v', CP_above, CT_squash_mirror_y, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_circumflex_below: + tack_on_accent('^', CP_below, CT_none, min_vert, max_vert, centroid, + properties, placement) || + tack_on_accent('v', CP_below, CT_squash_mirror_y, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_caron: + tack_on_accent('^', CP_above, CT_mirror_y, min_vert, max_vert, centroid, + properties, placement) || + tack_on_accent('v', CP_above, CT_squash, min_vert, max_vert, centroid, + properties, placement); + + break; + + case UnicodeLatinMap::AT_tilde: + tack_on_accent('~', CP_above, CT_none, min_vert, max_vert, centroid, + properties, placement) || + tack_on_accent('s', CP_above, CT_squash_mirror_diag, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_tilde_below: + tack_on_accent('~', CP_below, CT_none, min_vert, max_vert, centroid, + properties, placement) || + tack_on_accent('s', CP_below, CT_squash_mirror_diag, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_diaeresis: + tack_on_accent(':', CP_above, CT_small_rotate_270, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_diaeresis_below: + tack_on_accent(':', CP_below, CT_small_rotate_270, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_dot_above: + tack_on_accent('.', CP_above, CT_none, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_dot_below: + tack_on_accent('.', CP_below, CT_none, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_macron: + tack_on_accent('-', CP_above, CT_none, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_line_below: + tack_on_accent('-', CP_below, CT_none, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_ring_above: + tack_on_accent('o', CP_top, CT_tiny, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_ring_below: + tack_on_accent('o', CP_bottom, CT_tiny, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_cedilla: + tack_on_accent('c', CP_bottom, CT_tiny_mirror_x, min_vert, max_vert, centroid, + properties, placement); + /* + tack_on_accent(',', CP_bottom, CT_none, min_vert, max_vert, centroid, + properties, placement); + */ + break; + + case UnicodeLatinMap::AT_comma_below: + tack_on_accent(',', CP_below, CT_none, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_ogonek: + tack_on_accent(',', CP_bottom, CT_mirror_x, min_vert, max_vert, centroid, + properties, placement); + break; + + case UnicodeLatinMap::AT_stroke: + tack_on_accent('/', CP_within, CT_none, min_vert, max_vert, centroid, + properties, placement); + break; + + default: + // There are lots of other crazy kinds of accents. Forget 'em. + break; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::tack_on_accent +// Access: Private, Static +// Description: Generates a cheesy accent mark above (or below, etc.) +// the character. Returns true if successful, or false +// if the named accent character doesn't exist in the +// font. +//////////////////////////////////////////////////////////////////// +bool TextAssembler:: +tack_on_accent(char accent_mark, TextAssembler::CheesyPosition position, + TextAssembler::CheesyTransform transform, + const LPoint3f &min_vert, const LPoint3f &max_vert, + const LPoint3f ¢roid, + const TextProperties *properties, + TextAssembler::GlyphPlacement *placement) { + TextFont *font = properties->get_font(); + nassertr(font != (TextFont *)NULL, false); + + const TextGlyph *accent_glyph; + if (font->get_glyph(accent_mark, accent_glyph)) { + PT(Geom) accent_geom = accent_glyph->get_geom(); + if (accent_geom != (Geom *)NULL) { + LPoint3f min_accent, max_accent; + bool found_any = false; + accent_geom->calc_tight_bounds(min_accent, max_accent, found_any); + if (found_any) { + float t, u; + LMatrix4f accent_mat; + + // This gets set to true if the glyph gets mirrored and needs + // to have backface culling disabled. + bool mirrored = false; + + switch (transform) { + case CT_none: + accent_mat = LMatrix4f::ident_mat(); + break; + + case CT_mirror_x: + accent_mat = LMatrix4f::scale_mat(-1.0f, 1.0f, 1.0f); + t = min_accent[0]; + min_accent[0] = -max_accent[0]; + max_accent[0] = -t; + mirrored = true; + break; + + case CT_mirror_y: + accent_mat = LMatrix4f::scale_mat(1.0f, 1.0f, -1.0f); + t = min_accent[2]; + min_accent[2] = -max_accent[2]; + max_accent[2] = -t; + mirrored = true; + break; + + case CT_rotate_90: + accent_mat = + LMatrix4f::rotate_mat_normaxis(90.0f, LVecBase3f(0.0f, -1.0f, 0.0f)); + // rotate min, max + t = min_accent[0]; + u = max_accent[0]; + max_accent[0] = -min_accent[2]; + min_accent[0] = -max_accent[2]; + max_accent[2] = u; + min_accent[2] = t; + break; + + case CT_rotate_180: + accent_mat = LMatrix4f::scale_mat(-1.0f, -1.0f, 1.0f); + + t = min_accent[0]; + min_accent[0] = -max_accent[0]; + max_accent[0] = -t; + t = min_accent[2]; + min_accent[2] = -max_accent[2]; + max_accent[2] = -t; + break; + + case CT_rotate_270: + accent_mat = + LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f)); + // rotate min, max + t = min_accent[0]; + u = max_accent[0]; + min_accent[0] = min_accent[2]; + max_accent[0] = max_accent[2]; + min_accent[2] = -u; + max_accent[2] = -t; + break; + + case CT_squash: + accent_mat = LMatrix4f::scale_mat(squash_accent_scale_x, 1.0f, squash_accent_scale_y); + min_accent[0] *= squash_accent_scale_x; + max_accent[0] *= squash_accent_scale_x; + min_accent[2] *= squash_accent_scale_y; + max_accent[2] *= squash_accent_scale_y; + break; + + case CT_squash_mirror_y: + accent_mat = LMatrix4f::scale_mat(squash_accent_scale_x, 1.0f, -squash_accent_scale_y); + min_accent[0] *= squash_accent_scale_x; + max_accent[0] *= squash_accent_scale_x; + t = min_accent[2]; + min_accent[2] = -max_accent[2] * squash_accent_scale_y; + max_accent[2] = -t * squash_accent_scale_y; + mirrored = true; + break; + + case CT_squash_mirror_diag: + accent_mat = + LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) * + LMatrix4f::scale_mat(-squash_accent_scale_x, 1.0f, squash_accent_scale_y); + + // rotate min, max + t = min_accent[0]; + u = max_accent[0]; + min_accent[0] = min_accent[2] * -squash_accent_scale_x; + max_accent[0] = max_accent[2] * -squash_accent_scale_x; + min_accent[2] = -u * squash_accent_scale_y; + max_accent[2] = -t * squash_accent_scale_y; + mirrored = true; + break; + + case CT_small_squash: + accent_mat = LMatrix4f::scale_mat(small_squash_accent_scale_x, 1.0f, small_squash_accent_scale_y); + min_accent[0] *= small_squash_accent_scale_x; + max_accent[0] *= small_squash_accent_scale_x; + min_accent[2] *= small_squash_accent_scale_y; + max_accent[2] *= small_squash_accent_scale_y; + break; + + case CT_small_squash_mirror_y: + accent_mat = LMatrix4f::scale_mat(small_squash_accent_scale_x, 1.0f, -small_squash_accent_scale_y); + min_accent[0] *= small_squash_accent_scale_x; + max_accent[0] *= small_squash_accent_scale_x; + t = min_accent[2]; + min_accent[2] = -max_accent[2] * small_squash_accent_scale_y; + max_accent[2] = -t * small_squash_accent_scale_y; + mirrored = true; + break; + + case CT_small: + accent_mat = LMatrix4f::scale_mat(small_accent_scale); + min_accent *= small_accent_scale; + max_accent *= small_accent_scale; + break; + + case CT_small_rotate_270: + accent_mat = + LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) * + LMatrix4f::scale_mat(small_accent_scale); + + // rotate min, max + t = min_accent[0]; + u = max_accent[0]; + min_accent[0] = min_accent[2] * small_accent_scale; + max_accent[0] = max_accent[2] * small_accent_scale; + min_accent[2] = -u * small_accent_scale; + max_accent[2] = -t * small_accent_scale; + break; + + case CT_tiny: + accent_mat = LMatrix4f::scale_mat(tiny_accent_scale); + min_accent *= tiny_accent_scale; + max_accent *= tiny_accent_scale; + break; + + case CT_tiny_mirror_x: + accent_mat = LMatrix4f::scale_mat(-tiny_accent_scale, 1.0f, tiny_accent_scale); + + t = min_accent[0]; + min_accent[0] = -max_accent[0] * tiny_accent_scale; + max_accent[0] = -t * tiny_accent_scale; + min_accent[2] *= tiny_accent_scale; + max_accent[2] *= tiny_accent_scale; + mirrored = true; + break; + + case CT_tiny_rotate_270: + accent_mat = + LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) * + LMatrix4f::scale_mat(tiny_accent_scale); + + // rotate min, max + t = min_accent[0]; + u = max_accent[0]; + min_accent[0] = min_accent[2] * tiny_accent_scale; + max_accent[0] = max_accent[2] * tiny_accent_scale; + min_accent[2] = -u * tiny_accent_scale; + max_accent[2] = -t * tiny_accent_scale; + break; + } + + LPoint3f accent_centroid = (min_accent + max_accent) / 2.0f; + float accent_height = max_accent[2] - min_accent[2]; + LVector3f trans; + switch (position) { + case CP_above: + // A little above the character. + trans.set(centroid[0] - accent_centroid[0], 0.0f, + max_vert[2] - accent_centroid[2] + accent_height * 0.5); + break; + + case CP_below: + // A little below the character. + trans.set(centroid[0] - accent_centroid[0], 0.0f, + min_vert[2] - accent_centroid[2] - accent_height * 0.5); + break; + + case CP_top: + // Touching the top of the character. + trans.set(centroid[0] - accent_centroid[0], 0.0f, + max_vert[2] - accent_centroid[2]); + break; + + case CP_bottom: + // Touching the bottom of the character. + trans.set(centroid[0] - accent_centroid[0], 0.0f, + min_vert[2] - accent_centroid[2]); + break; + + case CP_within: + // Centered within the character. + trans.set(centroid[0] - accent_centroid[0], 0.0f, + centroid[2] - accent_centroid[2]); + break; + } + + accent_mat.set_row(3, trans); + accent_geom->transform_vertices(accent_mat); + + if (mirrored) { + // Once someone asks for this pointer, we hold its reference + // count and never free it. + static CPT(RenderState) disable_backface; + if (disable_backface == (const RenderState *)NULL) { + disable_backface = RenderState::make + (CullFaceAttrib::make(CullFaceAttrib::M_cull_none)); + } + + CPT(RenderState) state = + accent_glyph->get_state()->compose(disable_backface); + placement->add_piece(accent_geom, state); + } else { + placement->add_piece(accent_geom, accent_glyph->get_state()); + } + + return true; + } + } + } + + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::GlyphPlacement::calc_tight_bounds +// Access: Private +// Description: Expands min_point and max_point to include all of the +// vertices in the glyph(s), if any. found_any is set +// true if any points are found. It is the caller's +// responsibility to initialize min_point, max_point, +// and found_any before calling this function. +//////////////////////////////////////////////////////////////////// +void TextAssembler::GlyphPlacement:: +calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point, + bool &found_any) const { + Pieces::const_iterator pi; + for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) { + (*pi)._geom->calc_tight_bounds(min_point, max_point, found_any); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::GlyphPlacement::assign_to +// Access: Private +// Description: Puts the pieces of the GlyphPlacement in the +// indicated GeomNode. The vertices of the Geoms are +// modified by this operation. +//////////////////////////////////////////////////////////////////// +void TextAssembler::GlyphPlacement:: +assign_to(GeomNode *geom_node, const RenderState *state) const { + Pieces::const_iterator pi; + for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) { + (*pi)._geom->transform_vertices(_xform); + geom_node->add_geom((*pi)._geom, state->compose((*pi)._state)); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: TextAssembler::GlyphPlacement::assign_copy_to +// Access: Private +// Description: Puts the pieces of the GlyphPlacement in the +// indicated GeomNode. This flavor will make a copy of +// the Geoms first, and then apply the additional +// transform to the vertices. +//////////////////////////////////////////////////////////////////// +void TextAssembler::GlyphPlacement:: +assign_copy_to(GeomNode *geom_node, const RenderState *state, + const LMatrix4f &extra_xform) const { + LMatrix4f new_xform = _xform * extra_xform; + Pieces::const_iterator pi; + for (pi = _pieces.begin(); pi != _pieces.end(); ++pi) { + PT(Geom) new_geom = (*pi)._geom->make_copy(); + new_geom->transform_vertices(new_xform); + geom_node->add_geom(new_geom, state->compose((*pi)._state)); + } +} + +#endif // CPPPARSER + diff --git a/panda/src/text/textAssembler.h b/panda/src/text/textAssembler.h new file mode 100644 index 0000000000..b9d84b8851 --- /dev/null +++ b/panda/src/text/textAssembler.h @@ -0,0 +1,188 @@ +// Filename: textAssembler.h +// Created by: drose (06Apr04) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#ifndef TEXTASSEMBLER_H +#define TEXTASSEMBLER_H + +#ifndef CPPPARSER // interrogate has a bit of trouble with wstring. + +#include "pandabase.h" + +#include "textProperties.h" +#include "textFont.h" +#include "unicodeLatinMap.h" +#include "geomNode.h" +#include "pointerTo.h" + +class TextEncoder; + +//////////////////////////////////////////////////////////////////// +// Class : TextAssembler +// Description : This class is not intended to be used directly by +// user code, but is used by the TextNode to lay out a +// block of text and convert it into rows of Geoms +// according to the TextProperties. +// +// It is not exported from the DLL since it is not +// intended to be used outside of this module. +//////////////////////////////////////////////////////////////////// +class TextAssembler { +public: + TextAssembler(TextEncoder *encoder); + ~TextAssembler(); + + void clear(); + + bool set_wtext(const wstring &wtext, const TextProperties &properties, + int max_rows = 0); + INLINE int get_num_rows() const; + wstring get_wordwrapped_wtext() const; + + PT(PandaNode) assemble_text(); + + INLINE const LVector2f &get_ul() const; + INLINE const LVector2f &get_lr() const; + + static float calc_width(wchar_t character, const TextProperties &properties); + +private: + // These structures are built up and operated on by scan_wtext() and + // wordwrap_text(). It represents the unrolling of the embedded \1 + // .. \2 sequences embedded in the string into a TextProperties + // pointer associated with each character. + typedef pvector PropertiesList; + + class TextCharacter { + public: + INLINE TextCharacter(wchar_t character, const TextProperties *properties); + wchar_t _character; + const TextProperties *_properties; + }; + typedef pvector TextString; + + PropertiesList _properties_list; + TextString _wordwrapped_string; + int _num_rows; + + void scan_wtext(wstring::const_iterator &si, + const wstring::const_iterator &send, + const TextProperties *current_properties, + TextString &text_string); + bool wordwrap_text(const TextAssembler::TextString &text, + TextAssembler::TextString &output_text, + int max_rows); + + INLINE static float calc_width(const TextCharacter &tch); + static float calc_hyphen_width(const TextCharacter &tch); + + // These structures are built up by assemble_paragraph() and + // assemble_row(). They represent the actual Geoms as laid out in a + // paragraph. + + class Piece { + public: + PT(Geom) _geom; + CPT(RenderState) _state; + }; + typedef pvector Pieces; + + class GlyphPlacement { + public: + INLINE void add_piece(Geom *geom, const RenderState *state); + void calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point, + bool &found_any) const; + void assign_to(GeomNode *geom_node, const RenderState *state) const; + void assign_copy_to(GeomNode *geom_node, const RenderState *state, + const LMatrix4f &xform) const; + + Pieces _pieces; + LMatrix4f _xform; + const TextProperties *_properties; + }; + typedef pvector PlacedGlyphs; + + void assemble_paragraph(TextString::const_iterator si, + const TextString::const_iterator &send, + PlacedGlyphs &placed_glyphs); + void assemble_row(TextString::const_iterator &si, + const TextString::const_iterator &send, + PlacedGlyphs &row_placed_glyphs, + float &row_width, float &line_height, + TextProperties::Alignment &align); + + // These interfaces are for implementing cheesy accent marks and + // ligatures when the font doesn't support them. + enum CheesyPosition { + CP_above, + CP_below, + CP_top, + CP_bottom, + CP_within, + }; + enum CheesyTransform { + CT_none, + CT_mirror_x, + CT_mirror_y, + CT_rotate_90, + CT_rotate_180, + CT_rotate_270, + CT_squash, + CT_squash_mirror_y, + CT_squash_mirror_diag, + CT_small_squash, + CT_small_squash_mirror_y, + CT_small, + CT_small_rotate_270, + CT_tiny, + CT_tiny_mirror_x, + CT_tiny_rotate_270, + }; + + static void + get_character_glyphs(int character, const TextProperties *properties, + bool &got_glyph, const TextGlyph *&glyph, + const TextGlyph *&second_glyph, + UnicodeLatinMap::AccentType &accent_type, + int &additional_flags, + float &glyph_scale, float &advance_scale); + + static void + tack_on_accent(UnicodeLatinMap::AccentType accent_type, + const LPoint3f &min_vert, const LPoint3f &max_vert, + const LPoint3f ¢roid, + const TextProperties *properties, GlyphPlacement *placement); + static bool + tack_on_accent(char accent_mark, CheesyPosition position, + CheesyTransform transform, + const LPoint3f &min_vert, const LPoint3f &max_vert, + const LPoint3f ¢roid, + const TextProperties *properties, GlyphPlacement *placement); + + // These are filled in by assemble_paragraph(). + LVector2f _ul; + LVector2f _lr; + + TextEncoder *_encoder; +}; + +#include "textAssembler.I" + +#endif // CPPPARSER + +#endif + diff --git a/panda/src/text/textFont.cxx b/panda/src/text/textFont.cxx index 43027905e4..c865efcdf0 100644 --- a/panda/src/text/textFont.cxx +++ b/panda/src/text/textFont.cxx @@ -22,29 +22,6 @@ TypeHandle TextFont::_type_handle; -//////////////////////////////////////////////////////////////////// -// Function: isbreakpoint -// Description: An internal function, similar to isspace(), except it -// does not consider newlines to be whitespace. It also -// includes the soft-hyphen character. -//////////////////////////////////////////////////////////////////// -static INLINE bool -isbreakpoint(unsigned int ch) { - return (ch == ' ' || ch == '\t' || - ch == (unsigned int)text_soft_hyphen_key || - ch == (unsigned int)text_soft_break_key); -} - -//////////////////////////////////////////////////////////////////// -// Function: isspacew -// Description: An internal function that works like isspace() but is -// safe to call for a wide character. -//////////////////////////////////////////////////////////////////// -static INLINE bool -isspacew(unsigned int ch) { - return isascii(ch) && isspace(ch); -} - //////////////////////////////////////////////////////////////////// // Function: TextFont::Constructor // Access: Public @@ -66,81 +43,6 @@ TextFont:: ~TextFont() { } -//////////////////////////////////////////////////////////////////// -// Function: TextFont::calc_width -// Access: Published -// Description: Returns the width of a single character of the font, -// or 0.0 if the character is not known. -//////////////////////////////////////////////////////////////////// -float TextFont:: -calc_width(int character) { - if (character == ' ') { - // A space is a special case. - return _space_advance; - } - - const TextGlyph *glyph; - get_glyph(character, glyph); - if (glyph == (TextGlyph *)NULL) { - // Unknown character. - return 0.0f; - } - - return glyph->get_advance(); -} - -//////////////////////////////////////////////////////////////////// -// Function: TextFont::calc_width -// Access: Published -// 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 string &line) { - float width = 0.0f; - - string::const_iterator si; - for (si = line.begin(); si != line.end(); ++si) { - width += calc_width(*si); - } - - return width; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextFont::wordwrap_to -// Access: Published -// Description: Inserts newlines into the given text at the -// appropriate places in order to make each line be the -// longest possible line that is not longer than -// wordwrap_width (and does not break any words, if -// possible). Returns the new string. -//////////////////////////////////////////////////////////////////// -string TextFont:: -wordwrap_to(const string &text, float wordwrap_width, - bool preserve_trailing_whitespace) { - // Elevate the string to a wstring for this operation. - wstring wtext; - wtext.reserve(text.size()); - for (string::const_iterator ti = text.begin(); ti != text.end(); ++ti) { - wtext += (unsigned int)(*ti); - } - - wtext = wordwrap_to(wtext, wordwrap_width, preserve_trailing_whitespace); - - // Back down from wstring to string. - string result; - result.reserve(wtext.size()); - for (wstring::const_iterator wi = wtext.begin(); - wi != wtext.end(); - ++wi) { - result += (char)(*wi); - } - - return result; -} - //////////////////////////////////////////////////////////////////// // Function: TextFont::write // Access: Published, Virtual @@ -151,215 +53,3 @@ 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() && isspacew(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(!isspacew(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; - size_t last_space = 0; - float last_space_width = 0.0f; - - bool any_hyphens = false; - size_t last_hyphen = 0; - bool output_hyphen = false; - - bool overflow = false; - - bool last_was_space = false; - float hyphen_width = calc_width(*text_soft_hyphen_output); - float width = initial_width; - while (q < text.length() && text[q] != '\n') { - if (isspacew(text[q]) || text[q] == text_soft_break_key) { - if (!last_was_space) { - any_spaces = true; - // We only care about logging whether there is a soft-hyphen - // character to the right of the rightmost space. Each time - // we encounter a space, we reset this counter. - any_hyphens = false; - last_space = q; - last_space_width = width; - last_was_space = true; - } - } else { - last_was_space = false; - } - - // A soft hyphen character is not printed, but marks a point - // at which we might hyphenate a word if we need to. - if (text[q] == text_soft_hyphen_key) { - // We only consider this as a possible hyphenation point if - // (a) it is not the very first character, and (b) there is - // enough room for a hyphen character to be printed following - // it. - if (q != p && width + hyphen_width <= wordwrap_width) { - any_hyphens = true; - last_hyphen = q; - } - - } else if (text[q] != text_soft_break_key) { - // Some normal, printable character. - width += calc_width(text[q]); - } - - q++; - - if (width > wordwrap_width) { - // Oops, too many. - q--; - overflow = true; - break; - } - } - - if (overflow) { - // If we stopped because we exceeded the wordwrap width, then - // try to find an appropriate place to wrap the line or to - // hyphenate, if necessary. - if (any_spaces && last_space_width / wordwrap_width >= text_hyphen_ratio) { - // If we have a space that ended up within our safety margin, - // don't use any soft-hyphen characters. - any_hyphens = false; - } - - if (any_hyphens) { - // If we still have a soft-hyphen character, use it. - q = last_hyphen; - output_hyphen = true; - - } else if (any_spaces) { - // Otherwise, break at a space if we can. - q = last_space; - - } else { - // Otherwise, this is a forced break. Accept the longest line - // we can that does not leave the next line beginning with one - // of our forbidden characters. - size_t i = 0; - while ((int)i < text_max_never_break && q - i > p && - text_never_break_before->find(text[q - i]) != wstring::npos) { - i++; - } - if ((int)i < text_max_never_break) { - q -= i; - } - } - } - - // Skip additional whitespace between the lines. - size_t next_start = q; - while (next_start < text.length() && isbreakpoint(text[next_start])) { - next_start++; - } - - // Trim off any more blanks on the end. - while (q > p && isspacew(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() && isbreakpoint(text[next_start])) { - next_start++; - } - } - - if (needs_newline) { - output_text += '\n'; - } - needs_newline = true; - - if (preserve_trailing_whitespace) { - q = next_start; - } - - for (size_t pi = p; pi < q; pi++) { - if (text[pi] != text_soft_hyphen_key && - text[pi] != text_soft_break_key) { - output_text += text[pi]; - } - } - if (output_hyphen) { - output_text += *text_soft_hyphen_output; - } - - // 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() && isspacew(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; -} diff --git a/panda/src/text/textFont.h b/panda/src/text/textFont.h index f3f7bd0cc1..6b404890e1 100644 --- a/panda/src/text/textFont.h +++ b/panda/src/text/textFont.h @@ -53,15 +53,9 @@ PUBLISHED: INLINE float get_space_advance() const; INLINE void set_space_advance(float space_advance); - float calc_width(int character); - float calc_width(const string &line); - string wordwrap_to(const string &text, float wordwrap_width, - bool preserve_trailing_whitespace); - 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); diff --git a/panda/src/text/textNode.I b/panda/src/text/textNode.I index 3aecc95daf..829a97efc8 100644 --- a/panda/src/text/textNode.I +++ b/panda/src/text/textNode.I @@ -17,322 +17,84 @@ //////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////// -// Function: TextNode::freeze -// Access: Published -// Description: This method is deprecated and no longer does -// anything. It is included for historical purposes -// only and will shortly be removed. -//////////////////////////////////////////////////////////////////// -INLINE int TextNode:: -freeze() { - return 0; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::thaw -// Access: Published -// Description: This method is deprecated and no longer does -// anything. It is included for historical purposes -// only and will shortly be removed. -//////////////////////////////////////////////////////////////////// -INLINE int TextNode:: -thaw() { - return 0; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::set_font -// Access: Published -// Description: Sets the font that will be used when making text. If -// this is set to NULL, the default font will be used, -// which can be set via set_default_font(). -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -set_font(TextFont *font) { - if (_font != font) { - _font = font; - invalidate_with_measure(); - } -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::get_font -// Access: Published -// Description: Returns the font currently in use, if any. If this -// returns NULL, the default font will be used, which -// can be retrieved via get_default_font(). -//////////////////////////////////////////////////////////////////// -INLINE TextFont *TextNode:: -get_font() const { - return _font; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::set_default_font -// Access: Published, Static -// Description: Specifies the default font to be used for any -// TextNode whose font is uninitialized or NULL. See -// set_font(). -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -set_default_font(TextFont *font) { - // If the user overrides the default, we don't need to try to load - // whatever it would have been. - _loaded_default_font = true; - _default_font = font; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::get_default_font -// Access: Published, Static -// Description: Specifies the default font to be used for any -// TextNode whose font is uninitialized or NULL. See -// set_font(). -//////////////////////////////////////////////////////////////////// -INLINE TextFont *TextNode:: -get_default_font() { - if (!_loaded_default_font) { - load_default_font(); - } - return _default_font; -} - //////////////////////////////////////////////////////////////////// // Function: TextNode::get_line_height // Access: Published // Description: Returns the number of units high each line of text -// is. This is based on the font. +// is. This is based on the font. Note that it is +// possible for the text to include nested font change +// commands, in which case the value of this method is +// questionable. //////////////////////////////////////////////////////////////////// INLINE float TextNode:: get_line_height() const { - if (_font != (TextFont *)NULL) { - return _font->get_line_height(); - } else if (get_default_font() != (TextFont *)NULL) { - return _default_font->get_line_height(); + TextFont *font = get_font(); + if (font == (TextFont *)NULL) { + return 0.0f; } - return 0.0f; + + return font->get_line_height(); } //////////////////////////////////////////////////////////////////// -// Function: TextNode::set_small_caps +// Function: TextNode::set_max_rows // Access: Published -// Description: Sets the small_caps flag. When this is set, -// lowercase letters are generated as scaled-down -// versions of their uppercase equivalents. This is -// particularly useful to set for fonts that do not have -// lowercase letters. -// -// It is also a good idea to set this for a (dynamic) -// font that has already implemented lowercase letters -// as scaled-down versions of their uppercase -// equivalents, since without this flag the texture -// memory may needlessly duplicate equivalent glyphs for -// upper and lowercase letters. Setting this flag -// causes the texture memory to share the mixed-case -// letters. -// -// The amount by which the lowercase letters are scaled -// is specified by set_small_caps_scale(). +// Description: Sets the maximum number of rows that may be formatted +// by the TextNode. If more text than this is +// attempted, it will be truncated and has_overflow() +// will return true. //////////////////////////////////////////////////////////////////// INLINE void TextNode:: -set_small_caps(bool small_caps) { - if (small_caps) { - _flags |= F_small_caps; - } else { - _flags &= ~F_small_caps; - } +set_max_rows(int max_rows) { + _max_rows = max_rows; invalidate_with_measure(); } //////////////////////////////////////////////////////////////////// -// Function: TextNode::get_small_caps +// Function: TextNode::clear_max_rows // Access: Published -// Description: Returns the small_caps flag. See set_small_caps(). -//////////////////////////////////////////////////////////////////// -INLINE bool TextNode:: -get_small_caps() const { - return (_flags & F_small_caps) != 0; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::set_small_caps_scale -// Access: Published -// Description: Sets the scale factor applied to lowercase letters -// from their uppercase equivalents, when the small_caps -// flag is in effect. See set_small_caps(). Normally, -// this will be a number less than one. +// Description: Resets the TextNode's default behavior of not +// limiting the number of rows of text. //////////////////////////////////////////////////////////////////// INLINE void TextNode:: -set_small_caps_scale(float small_caps_scale) { - _small_caps_scale = small_caps_scale; +clear_max_rows() { + _max_rows = 0; invalidate_with_measure(); } //////////////////////////////////////////////////////////////////// -// Function: TextNode::get_small_caps_scale +// Function: TextNode::has_max_rows // Access: Published -// Description: Returns the scale factor applied to lowercase letters -// from their uppercase equivalents, when the small_caps -// flag is in effect. See set_small_caps() and -// set_small_caps_scale(). -//////////////////////////////////////////////////////////////////// -INLINE float TextNode:: -get_small_caps_scale() const { - return _small_caps_scale; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::set_slant -// Access: Published -// Description: -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -set_slant(float slant) { - if (_slant != slant) { - _slant = slant; - invalidate_with_measure(); - } -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::get_slant -// Access: Published -// Description: -//////////////////////////////////////////////////////////////////// -INLINE float TextNode:: -get_slant() const { - return _slant; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::set_align -// Access: Published -// Description: -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -set_align(TextNode::Alignment align_type) { - if (_align != align_type) { - _align = align_type; - invalidate_with_measure(); - } -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::get_align -// Access: Published -// Description: -//////////////////////////////////////////////////////////////////// -INLINE TextNode::Alignment TextNode:: -get_align() const { - return _align; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::set_wordwrap -// Access: Published -// Description: Sets the TextNode up to automatically wordwrap text -// that exceeds the indicated width. -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -set_wordwrap(float wordwrap) { - if (!has_wordwrap() || _wordwrap_width != wordwrap) { - _flags |= F_has_wordwrap; - _wordwrap_width = wordwrap; - invalidate_with_measure(); - } -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::clear_wordwrap -// Access: Published -// Description: Removes the wordwrap setting from the TextNode. Text -// will be as wide as it is. -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -clear_wordwrap() { - if (has_wordwrap()) { - _flags &= ~F_has_wordwrap; - invalidate_with_measure(); - } -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::has_wordwrap -// Access: Published -// Description: +// Description: Returns true if a limit on the height of the TextNode +// has been set via set_max_rows(), false otherwise. //////////////////////////////////////////////////////////////////// INLINE bool TextNode:: -has_wordwrap() const { - return (_flags & F_has_wordwrap) != 0; +has_max_rows() const { + return _max_rows > 0; } //////////////////////////////////////////////////////////////////// -// Function: TextNode::get_wordwrap +// Function: TextNode::get_max_rows // Access: Published -// Description: +// Description: Returns the limit on the height of the TextNode +// specified by set_max_rows(). //////////////////////////////////////////////////////////////////// -INLINE float TextNode:: -get_wordwrap() const { - return _wordwrap_width; +INLINE int TextNode:: +get_max_rows() const { + return _max_rows; } //////////////////////////////////////////////////////////////////// -// Function: TextNode::set_text_color +// Function: TextNode::has_overflow // Access: Published -// Description: -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -set_text_color(float r, float g, float b, float a) { - set_text_color(Colorf(r, g, b, a)); -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::set_text_color -// Access: Published -// Description: -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -set_text_color(const Colorf &text_color) { - if (!has_text_color() || _text_color != text_color) { - _text_color = text_color; - _flags |= F_has_text_color; - invalidate_no_measure(); - } -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::clear_text_color -// Access: Published -// Description: Removes the text color specification; the text will -// be colored whatever it was in the source font file. -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -clear_text_color() { - if (has_text_color()) { - _flags &= ~F_has_text_color; - invalidate_no_measure(); - } -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::has_text_color -// Access: Published -// Description: +// Description: Returns true if the last text set on the text node +// exceeded the max_rows constraint, or false if it all +// fit. //////////////////////////////////////////////////////////////////// INLINE bool TextNode:: -has_text_color() const { - return (_flags & F_has_text_color) != 0; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::get_text_color -// Access: Published -// Description: -//////////////////////////////////////////////////////////////////// -INLINE Colorf TextNode:: -get_text_color() const { - return _text_color; +has_overflow() const { + check_measure(); + return (_flags & F_has_overflow) != 0; } //////////////////////////////////////////////////////////////////// @@ -511,39 +273,6 @@ get_card_texture() const { return _card_texture; } -//////////////////////////////////////////////////////////////////// -// Function: TextNode::set_shadow_color -// Access: Published -// Description: -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -set_shadow_color(float r, float g, float b, float a) { - set_shadow_color(Colorf(r, g, b, a)); -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::set_shadow_color -// Access: Published -// Description: -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -set_shadow_color(const Colorf &shadow_color) { - if (_shadow_color != shadow_color) { - _shadow_color = shadow_color; - invalidate_no_measure(); - } -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::get_shadow_color -// Access: Published -// Description: -//////////////////////////////////////////////////////////////////// -INLINE Colorf TextNode:: -get_shadow_color() const { - return _shadow_color; -} - //////////////////////////////////////////////////////////////////// // Function: TextNode::set_frame_as_margin // Access: Published @@ -652,14 +381,18 @@ INLINE LVecBase4f TextNode:: get_frame_actual() const { if (!has_frame()) { check_measure(); - return LVecBase4f(_ul2d[0], _lr2d[0], _lr2d[1], _ul2d[1]); + const LVector2f &ul = _assembler.get_ul(); + const LVector2f &lr = _assembler.get_lr(); + return LVecBase4f(ul[0], lr[0], lr[1], ul[1]); } else if (is_frame_as_margin()) { check_measure(); - return LVecBase4f(_ul2d[0] - _frame_ul[0], - _lr2d[0] + _frame_lr[0], - _lr2d[1] - _frame_lr[1], - _ul2d[1] + _frame_ul[1]); + const LVector2f &ul = _assembler.get_ul(); + const LVector2f &lr = _assembler.get_lr(); + return LVecBase4f(ul[0] - _frame_ul[0], + lr[0] + _frame_lr[0], + lr[1] - _frame_lr[1], + ul[1] + _frame_ul[1]); } else { return get_frame_as_set(); } @@ -823,14 +556,18 @@ INLINE LVecBase4f TextNode:: get_card_actual() const { if (!has_card()) { check_measure(); - return LVecBase4f(_ul2d[0], _lr2d[0], _lr2d[1], _ul2d[1]); + const LVector2f &ul = _assembler.get_ul(); + const LVector2f &lr = _assembler.get_lr(); + return LVecBase4f(ul[0], lr[0], lr[1], ul[1]); } else if (is_card_as_margin()) { check_measure(); - return LVecBase4f(_ul2d[0] - _card_ul[0], - _lr2d[0] + _card_lr[0], - _lr2d[1] - _card_lr[1], - _ul2d[1] + _card_ul[1]); + const LVector2f &ul = _assembler.get_ul(); + const LVector2f &lr = _assembler.get_lr(); + return LVecBase4f(ul[0] - _card_ul[0], + lr[0] + _card_lr[0], + lr[1] - _card_lr[1], + ul[1] + _card_ul[1]); } else { return get_card_as_set(); } @@ -854,164 +591,6 @@ get_card_transformed() const { return LVecBase4f(ul[0], lr[0], lr[2], ul[2]); } -//////////////////////////////////////////////////////////////////// -// Function: TextNode::set_shadow -// Access: Published -// Description: Specifies that the text should be drawn with a -// shadow, by creating a second copy of the text and -// offsetting it slightly behind the first. -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -set_shadow(float xoffset, float yoffset) { - _flags |= F_has_shadow; - _shadow_offset.set(xoffset, yoffset); - invalidate_no_measure(); -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::clear_shadow -// Access: Published -// Description: Specifies that a shadow will not be drawn behind the -// text. -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -clear_shadow() { - _flags &= ~F_has_shadow; - invalidate_no_measure(); -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::has_shadow -// Access: Published -// Description: -//////////////////////////////////////////////////////////////////// -INLINE bool TextNode:: -has_shadow() const { - return (_flags & F_has_shadow) != 0; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::get_shadow -// Access: Published -// Description: Returns the offset of the shadow as set by -// set_shadow(). It is an error to call this if -// has_shadow() is false. -//////////////////////////////////////////////////////////////////// -INLINE LVecBase2f TextNode:: -get_shadow() const { - nassertr(has_shadow(), LVecBase2f(0.0, 0.0)); - return _shadow_offset; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::set_bin -// Access: Published -// Description: Names the GeomBin that the TextNode geometry should -// be assigned to. If this is set, then a -// GeomBinTransition will be created to explicitly place -// each component in the named bin. -// -// The draw_order value will also be passed to each -// GeomBinTransition as appropriate; this is -// particularly useful if this names a GeomBinFixed, -// e.g. "fixed". -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -set_bin(const string &bin) { - _bin = bin; - invalidate_no_measure(); -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::clear_bin -// Access: Published -// Description: Removes the effect of a previous call to -// set_bin(). Text will be drawn in whatever bin -// it would like to be drawn in, with no explicit -// ordering. -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -clear_bin() { - _bin = string(); -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::has_bin -// Access: Published -// Description: Returns true if an explicit drawing bin has been -// set via set_bin(), false otherwise. -//////////////////////////////////////////////////////////////////// -INLINE bool TextNode:: -has_bin() const { - return !_bin.empty(); -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::get_bin -// Access: Published -// Description: Returns the drawing bin set with set_bin(), or empty -// string if no bin has been set. -//////////////////////////////////////////////////////////////////// -INLINE const string &TextNode:: -get_bin() const { - return _bin; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::set_draw_order -// Access: Published -// Description: Sets the drawing order of text created by the -// TextMaker. This is actually the draw order of the -// card and frame. The shadow is drawn at -// _draw_order+1, and the text at _draw_order+2. -// -// This affects the sorting order assigned to the arcs -// as they are created, and also is passed to whatever -// bin may be assigned via set_bin(). -// -// The return value is the first unused draw_order -// number, e.g. _draw_order + 3. -//////////////////////////////////////////////////////////////////// -INLINE int TextNode:: -set_draw_order(int draw_order) { - _draw_order = draw_order; - invalidate_no_measure(); - return _draw_order + 3; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::get_draw_order -// Access: Published -// Description: Returns the drawing order set with set_draw_order(). -//////////////////////////////////////////////////////////////////// -INLINE int TextNode:: -get_draw_order() const { - return _draw_order; -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::set_tab_width -// Access: Published -// Description: Sets the width of each tab stop, in screen units. A -// tab character embedded in the text will advance the -// horizontal position to the next tab stop. -//////////////////////////////////////////////////////////////////// -INLINE void TextNode:: -set_tab_width(float tab_width) { - _tab_width = tab_width; - invalidate_with_measure(); -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::get_tab_width -// Access: Published -// Description: Returns the width set via set_tab_width(). -//////////////////////////////////////////////////////////////////// -INLINE float TextNode:: -get_tab_width() const { - return _tab_width; -} - //////////////////////////////////////////////////////////////////// // Function: TextNode::set_transform // Access: Published @@ -1056,6 +635,427 @@ get_coordinate_system() const { return _coordinate_system; } +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_font +// Access: Published +// Description: Sets the font that will be used when making text. If +// this is set to NULL, the default font will be used, +// which can be set via set_default_font(). +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_font(TextFont *font) { + TextProperties::set_font(font); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::clear_font +// Access: Published +// Description: Resets the font to the default font. +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +clear_font() { + TextProperties::clear_font(); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_small_caps +// Access: Published +// Description: Sets the small_caps flag. When this is set, +// lowercase letters are generated as scaled-down +// versions of their uppercase equivalents. This is +// particularly useful to set for fonts that do not have +// lowercase letters. +// +// It is also a good idea to set this for a (dynamic) +// font that has already implemented lowercase letters +// as scaled-down versions of their uppercase +// equivalents, since without this flag the texture +// memory may needlessly duplicate equivalent glyphs for +// upper and lowercase letters. Setting this flag +// causes the texture memory to share the mixed-case +// letters. +// +// The amount by which the lowercase letters are scaled +// is specified by set_small_caps_scale(). +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_small_caps(bool small_caps) { + TextProperties::set_small_caps(small_caps); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::clear_small_caps +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +clear_small_caps() { + TextProperties::clear_small_caps(); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_small_caps_scale +// Access: Published +// Description: Sets the scale factor applied to lowercase letters +// from their uppercase equivalents, when the small_caps +// flag is in effect. See set_small_caps(). Normally, +// this will be a number less than one. +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_small_caps_scale(float small_caps_scale) { + TextProperties::set_small_caps_scale(small_caps_scale); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::clear_small_caps_scale +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +clear_small_caps_scale() { + TextProperties::clear_small_caps_scale(); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_slant +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_slant(float slant) { + TextProperties::set_slant(slant); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::clear_slant +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +clear_slant() { + TextProperties::clear_slant(); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_align +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_align(TextNode::Alignment align_type) { + TextProperties::set_align(align_type); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::clear_align +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +clear_align() { + TextProperties::clear_align(); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_indent +// Access: Published +// Description: Specifies the amount of extra space that is inserted +// before the first character of each line. This can be +// thought of as a left margin. +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_indent(float indent) { + TextProperties::set_indent(indent); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::clear_indent +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +clear_indent() { + TextProperties::clear_indent(); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_wordwrap +// Access: Published +// Description: Sets the text up to automatically wordwrap when it +// exceeds the indicated width. This can be thought of +// as a right margin or margin width. +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_wordwrap(float wordwrap) { + TextProperties::set_wordwrap(wordwrap); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::clear_wordwrap +// Access: Published +// Description: Removes the wordwrap setting from the TextNode. Text +// will be as wide as it is. +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +clear_wordwrap() { + TextProperties::clear_wordwrap(); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_text_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_text_color(const Colorf &text_color) { + TextProperties::set_text_color(text_color); + invalidate_no_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_text_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_text_color(float r, float g, float b, float a) { + set_text_color(Colorf(r, g, b, a)); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::clear_text_color +// Access: Published +// Description: Removes the text color specification; the text will +// be colored whatever it was in the source font file. +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +clear_text_color() { + TextProperties::clear_text_color(); + invalidate_no_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_shadow_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_shadow_color(float r, float g, float b, float a) { + set_shadow_color(Colorf(r, g, b, a)); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_shadow_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_shadow_color(const Colorf &shadow_color) { + TextProperties::set_shadow_color(shadow_color); + invalidate_no_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::clear_shadow_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +clear_shadow_color() { + TextProperties::clear_shadow_color(); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_shadow +// Access: Published +// Description: Specifies that the text should be drawn with a +// shadow, by creating a second copy of the text and +// offsetting it slightly behind the first. +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_shadow(float xoffset, float yoffset) { + set_shadow(LVecBase2f(xoffset, yoffset)); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_shadow +// Access: Published +// Description: Specifies that the text should be drawn with a +// shadow, by creating a second copy of the text and +// offsetting it slightly behind the first. +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_shadow(const LVecBase2f &shadow_offset) { + TextProperties::set_shadow(shadow_offset); + invalidate_no_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::clear_shadow +// Access: Published +// Description: Specifies that a shadow will not be drawn behind the +// text. +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +clear_shadow() { + TextProperties::clear_shadow(); + invalidate_no_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_bin +// Access: Published +// Description: Names the GeomBin that the TextNode geometry should +// be assigned to. If this is set, then a +// GeomBinTransition will be created to explicitly place +// each component in the named bin. +// +// The draw_order value will also be passed to each +// GeomBinTransition as appropriate; this is +// particularly useful if this names a GeomBinFixed, +// e.g. "fixed". +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_bin(const string &bin) { + TextProperties::set_bin(bin); + invalidate_no_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::clear_bin +// Access: Published +// Description: Removes the effect of a previous call to +// set_bin(). Text will be drawn in whatever bin +// it would like to be drawn in, with no explicit +// ordering. +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +clear_bin() { + TextProperties::clear_bin(); + invalidate_no_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_draw_order +// Access: Published +// Description: Sets the drawing order of text created by the +// TextMaker. This is actually the draw order of the +// card and frame. The shadow is drawn at +// _draw_order+1, and the text at _draw_order+2. +// +// This affects the sorting order assigned to the arcs +// as they are created, and also is passed to whatever +// bin may be assigned via set_bin(). +// +// The return value is the first unused draw_order +// number, e.g. _draw_order + 3. +//////////////////////////////////////////////////////////////////// +INLINE int TextNode:: +set_draw_order(int draw_order) { + invalidate_no_measure(); + return TextProperties::set_draw_order(draw_order); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::clear_draw_order +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +clear_draw_order() { + TextProperties::clear_draw_order(); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_tab_width +// Access: Published +// Description: Sets the width of each tab stop, in screen units. A +// tab character embedded in the text will advance the +// horizontal position to the next tab stop. +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_tab_width(float tab_width) { + TextProperties::set_tab_width(tab_width); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::clear_tab_width +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +clear_tab_width() { + TextProperties::clear_tab_width(); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_glyph_scale +// Access: Published +// Description: Specifies the factor by which to scale each letter of +// the text as it is placed. This can be used (possibly +// in conjunction with set_glyph_shift()) to implement +// superscripting or subscripting. +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_glyph_scale(float glyph_scale) { + TextProperties::set_glyph_scale(glyph_scale); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::clear_glyph_scale +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +clear_glyph_scale() { + TextProperties::clear_glyph_scale(); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::set_glyph_shift +// Access: Published +// Description: Specifies a vertical amount to shift each letter of +// the text as it is placed. This can be used (possibly +// in conjunction with set_glyph_scale()) to implement +// superscripting or subscripting. +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +set_glyph_shift(float glyph_shift) { + TextProperties::set_glyph_shift(glyph_shift); + invalidate_with_measure(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::clear_glyph_shift +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextNode:: +clear_glyph_shift() { + TextProperties::clear_glyph_shift(); + invalidate_with_measure(); +} + //////////////////////////////////////////////////////////////////// // Function: TextNode::set_text // Access: Published @@ -1121,16 +1121,16 @@ append_unicode_char(int character) { } //////////////////////////////////////////////////////////////////// -// Function: TextNode::calc_width -// Access: Published -// Description: Returns the width of a single character of the font, -// or 0.0 if the character is not known. This may be a -// wide character (greater than 255). +// Function: TextNode::get_wordwrapped_text +// Access: Public +// Description: Returns a string that represents the contents of the +// text, as it has been formatted by wordwrap rules. +// This will not contain any embedded special characters +// like \1 or \3. //////////////////////////////////////////////////////////////////// -INLINE float TextNode:: -calc_width(int character) const { - nassertr(_font != (TextFont *)NULL, 0.0); - return _font->calc_width(character); +INLINE string TextNode:: +get_wordwrapped_text() const { + return encode_wtext(get_wordwrapped_wtext()); } //////////////////////////////////////////////////////////////////// @@ -1142,8 +1142,7 @@ calc_width(int character) const { //////////////////////////////////////////////////////////////////// INLINE float TextNode:: calc_width(const string &line) const { - nassertr(_font != (TextFont *)NULL, 0.0); - return _font->calc_width(decode_text(line)); + return calc_width(decode_text(line)); } //////////////////////////////////////////////////////////////////// @@ -1156,7 +1155,7 @@ calc_width(const string &line) const { INLINE float TextNode:: get_left() const { check_measure(); - return _ul2d[0]; + return _assembler.get_ul()[0]; } //////////////////////////////////////////////////////////////////// @@ -1169,7 +1168,7 @@ get_left() const { INLINE float TextNode:: get_right() const { check_measure(); - return _lr2d[0]; + return _assembler.get_lr()[0]; } //////////////////////////////////////////////////////////////////// @@ -1182,7 +1181,7 @@ get_right() const { INLINE float TextNode:: get_bottom() const { check_measure(); - return _lr2d[1]; + return _assembler.get_lr()[1]; } //////////////////////////////////////////////////////////////////// @@ -1195,7 +1194,7 @@ get_bottom() const { INLINE float TextNode:: get_top() const { check_measure(); - return _ul2d[1]; + return _assembler.get_ul()[1]; } //////////////////////////////////////////////////////////////////// @@ -1207,7 +1206,7 @@ get_top() const { INLINE float TextNode:: get_height() const { check_measure(); - return _ul2d[1] - _lr2d[1]; + return _assembler.get_ul()[1] - _assembler.get_lr()[1]; } //////////////////////////////////////////////////////////////////// @@ -1219,7 +1218,7 @@ get_height() const { INLINE float TextNode:: get_width() const { check_measure(); - return _lr2d[0] - _ul2d[0]; + return _assembler.get_lr()[0] - _assembler.get_ul()[0]; } //////////////////////////////////////////////////////////////////// @@ -1258,7 +1257,7 @@ get_lower_right_3d() const { INLINE int TextNode:: get_num_rows() const { check_measure(); - return _num_rows; + return _assembler.get_num_rows(); } //////////////////////////////////////////////////////////////////// @@ -1316,32 +1315,17 @@ append_wtext(const wstring &wtext) { } //////////////////////////////////////////////////////////////////// -// Function: TextNode::calc_width +// Function: TextNode::get_wordwrapped_wtext // Access: Public -// Description: Returns the width of a line of text of arbitrary -// characters. The line should not include the newline -// character. -//////////////////////////////////////////////////////////////////// -INLINE float TextNode:: -calc_width(const wstring &line) const { - nassertr(_font != (TextFont *)NULL, 0.0); - return _font->calc_width(line); -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::wordwrap_to -// 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. +// Description: Returns a wstring that represents the contents of the +// text, as it has been formatted by wordwrap rules. +// This will not contain any embedded special characters +// like \1 or \3. //////////////////////////////////////////////////////////////////// INLINE wstring TextNode:: -wordwrap_to(const wstring &wtext, float wordwrap_width, - bool preserve_trailing_whitespace) const { - nassertr(_font != (TextFont *)NULL, wtext); - return _font->wordwrap_to(wtext, wordwrap_width, preserve_trailing_whitespace); +get_wordwrapped_wtext() const { + check_measure(); + return _assembler.get_wordwrapped_wtext(); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/text/textNode.cxx b/panda/src/text/textNode.cxx index 601dd18ffb..a20951fe98 100644 --- a/panda/src/text/textNode.cxx +++ b/panda/src/text/textNode.cxx @@ -20,11 +20,7 @@ #include "textGlyph.h" #include "stringDecoder.h" #include "config_text.h" -#include "fontPool.h" -#include "default_font.h" -#include "dynamicTextFont.h" -#include "staticTextFont.h" -#include "unicodeLatinMap.h" +#include "textAssembler.h" #include "compose_matrix.h" #include "geom.h" @@ -46,59 +42,29 @@ #include "geometricBoundingVolume.h" #include "accumulatedAttribs.h" #include "renderState.h" -#include "cullFaceAttrib.h" #include "dcast.h" #include "bamFile.h" #include "zStream.h" #include -#include TypeHandle TextNode::_type_handle; -PT(TextFont) TextNode::_default_font; -bool TextNode::_loaded_default_font = false; - -// This is the factor by which CT_small scales the character down. -static const float small_accent_scale = 0.6f; - -// This is the factor by which CT_tiny scales the character down. -static const float tiny_accent_scale = 0.4f; - -// This is the factor by which CT_squash scales the character in X and Y. -static const float squash_accent_scale_x = 0.8f; -static const float squash_accent_scale_y = 0.5f; - -// This is the factor by which CT_small_squash scales the character in X and Y. -static const float small_squash_accent_scale_x = 0.6f; -static const float small_squash_accent_scale_y = 0.3f; - -// This is the factor by which the advance is reduced for the first -// character of a two-character ligature. -static const float ligature_advance_scale = 0.6f; - //////////////////////////////////////////////////////////////////// // Function: TextNode::Constructor // Access: Published // Description: //////////////////////////////////////////////////////////////////// TextNode:: -TextNode(const string &name) : PandaNode(name) { - _slant = 0.0f; - +TextNode(const string &name) : PandaNode(name), _assembler(this) { _flags = 0; - _align = A_left; - _wordwrap_width = 1.0f; if (text_small_caps) { - _flags |= F_small_caps; + set_small_caps(true); } - _small_caps_scale = text_small_caps_scale; - _text_color.set(1.0f, 1.0f, 1.0f, 1.0f); _frame_color.set(1.0f, 1.0f, 1.0f, 1.0f); _card_color.set(1.0f, 1.0f, 1.0f, 1.0f); - _shadow_color.set(1.0f, 1.0f, 1.0f, 1.0f); _frame_width = 1.0f; @@ -106,19 +72,44 @@ TextNode(const string &name) : PandaNode(name) { _frame_lr.set(0.0f, 0.0f); _card_ul.set(0.0f, 0.0f); _card_lr.set(0.0f, 0.0f); - _shadow_offset.set(0.0f, 0.0f); - - _draw_order = 1; - _tab_width = text_tab_width; _transform = LMatrix4f::ident_mat(); _coordinate_system = CS_default; - _ul2d.set(0.0f, 0.0f); - _lr2d.set(0.0f, 0.0f); _ul3d.set(0.0f, 0.0f, 0.0f); _lr3d.set(0.0f, 0.0f, 0.0f); - _num_rows = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextNode::Copy Constructor +// Access: Published +// Description: It's sort of a copy constructor: it copies the +// indicated TextProperties, without copying a complete +// TextNode. +//////////////////////////////////////////////////////////////////// +TextNode:: +TextNode(const string &name, const TextProperties ©) : + PandaNode(name), TextProperties(copy), + _assembler(this) +{ + _flags = 0; + _max_rows = 0; + + _frame_color.set(1.0f, 1.0f, 1.0f, 1.0f); + _card_color.set(1.0f, 1.0f, 1.0f, 1.0f); + + _frame_width = 1.0f; + + _frame_ul.set(0.0f, 0.0f); + _frame_lr.set(0.0f, 0.0f); + _card_ul.set(0.0f, 0.0f); + _card_lr.set(0.0f, 0.0f); + + _transform = LMatrix4f::ident_mat(); + _coordinate_system = CS_default; + + _ul3d.set(0.0f, 0.0f, 0.0f); + _lr3d.set(0.0f, 0.0f, 0.0f); } //////////////////////////////////////////////////////////////////// @@ -131,33 +122,22 @@ TextNode:: } //////////////////////////////////////////////////////////////////// -// Function: TextNode::wordwrap_to +// Function: TextNode::calc_width // Access: Published -// Description: Inserts newlines into the given text at the -// appropriate places in order to make each line be the -// longest possible line that is not longer than -// wordwrap_width (and does not break any words, if -// possible). Returns the new string. +// Description: Returns the width of a single character of the font, +// or 0.0 if the character is not known. This may be a +// wide character (greater than 255). //////////////////////////////////////////////////////////////////// -string TextNode:: -wordwrap_to(const string &text, float wordwrap_width, - bool preserve_trailing_whitespace) const { +float TextNode:: +calc_width(int character) const { TextFont *font = get_font(); if (font == (TextFont *)NULL) { - font = get_default_font(); + return 0.0f; } - if (font == (TextFont *)NULL) { - return text; - } - - wstring decoded = decode_text(text); - wstring wrapped = - font->wordwrap_to(decoded, wordwrap_width, preserve_trailing_whitespace); - return encode_wtext(wrapped); + return _assembler.calc_width(character, *this); } - //////////////////////////////////////////////////////////////////// // Function: TextNode::write // Access: Published, Virtual @@ -167,81 +147,9 @@ void TextNode:: write(ostream &out, int indent_level) const { indent(out, indent_level) << "TextNode " << get_name() << "\n"; - if (_font != (TextFont *)NULL) { - indent(out, indent_level + 2) - << "with font " << _font->get_name() << "\n"; - } - if (has_text_color()) { - indent(out, indent_level + 2) - << "text color is " << _text_color << "\n"; - } else { - indent(out, indent_level + 2) - << "text color is unchanged from source\n"; - } - indent(out, indent_level + 2) - << "alignment is "; - switch (_align) { - case A_left: - out << "A_left\n"; - break; - - case A_right: - out << "A_right\n"; - break; - - case A_center: - out << "A_center\n"; - break; - } - - if (has_wordwrap()) { - indent(out, indent_level + 2) - << "Word-wrapping at " << _wordwrap_width << " units.\n"; - } - - if (has_frame()) { - indent(out, indent_level + 2) - << "frame of color " << _frame_color << " at " - << get_frame_as_set() << " line width " << _frame_width << "\n"; - if (get_frame_corners()) { - indent(out, indent_level + 2) - << "frame corners are enabled\n"; - } - if (is_frame_as_margin()) { - indent(out, indent_level + 2) - << "frame coordinates are specified as margin; actual frame is:\n" - << get_frame_actual() << "\n"; - } else { - indent(out, indent_level + 2) - << "frame coordinates are actual\n"; - } - } - if (has_card()) { - indent(out, indent_level + 2) - << "card of color " << _card_color << " at " - << get_card_as_set() << "\n"; - if (is_card_as_margin()) { - indent(out, indent_level + 2) - << "card coordinates are specified as margin; actual card is:\n" - << get_card_actual() << "\n"; - } else { - indent(out, indent_level + 2) - << "card coordinates are actual\n"; - } - } - if (has_shadow()) { - indent(out, indent_level + 2) - << "shadow of color " << _shadow_color << " at " - << _shadow_offset << "\n"; - } - if (has_bin()) { - indent(out, indent_level + 2) - << "bin is " << _bin << "\n"; - } - indent(out, indent_level + 2) - << "draw order is " << _draw_order << ", " - << _draw_order + 1 << ", " << _draw_order + 2 << "\n"; + TextProperties::write(out, indent_level + 2); + out << "\n"; LVecBase3f scale, shear, hpr, trans; if (decompose_matrix(_transform, scale, shear, hpr, trans, _coordinate_system)) { indent(out, indent_level + 2) @@ -257,8 +165,9 @@ write(ostream &out, int indent_level) const { indent(out, indent_level + 2) << "in coordinate system " << _coordinate_system << "\n"; + out << "\n"; indent(out, indent_level + 2) - << "\ntext is " << get_text() << "\n"; + << "text is " << get_text() << "\n"; } //////////////////////////////////////////////////////////////////// @@ -285,11 +194,8 @@ generate() { // horizontally and vertically, and for each row, there is another // node for each character. - _ul2d.set(0.0f, 0.0f); - _lr2d.set(0.0f, 0.0f); _ul3d.set(0.0f, 0.0f, 0.0f); _lr3d.set(0.0f, 0.0f, 0.0f); - _num_rows = 0; // Now build a new sub-tree for all the text components. PT(PandaNode) root = new PandaNode(get_text()); @@ -299,10 +205,6 @@ generate() { } TextFont *font = get_font(); - if (font == (TextFont *)NULL) { - font = get_default_font(); - } - if (font == (TextFont *)NULL) { return root; } @@ -317,39 +219,29 @@ generate() { root->set_transform(TransformState::make_mat(mat)); wstring wtext = get_wtext(); - if (has_wordwrap()) { - wtext = font->wordwrap_to(wtext, _wordwrap_width, false); - } // Assemble the text. - LVector2f ul, lr; - int num_rows = 0; - PT(PandaNode) text_root = - assemble_text(wtext.begin(), wtext.end(), font, - ul, lr, num_rows); + bool all_set = _assembler.set_wtext(wtext, *this, _max_rows); + if (all_set) { + // No overflow. + _flags &= ~F_has_overflow; + } else { + // Overflow. + _flags |= F_has_overflow; + } + + PT(PandaNode) text_root = _assembler.assemble_text(); // Parent the text in. We create an intermediate node so we can // choose to reinstance the text_root as the shadow, below. PT(PandaNode) text = new PandaNode("text"); - root->add_child(text, _draw_order + 2); + root->add_child(text, get_draw_order() + 2); text->add_child(text_root); - if (has_text_color()) { - text->set_attrib(ColorAttrib::make_flat(_text_color)); - if (_text_color[3] != 1.0) { - text->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha)); - } - } - - if (has_bin()) { - text->set_attrib(CullBinAttrib::make(_bin, _draw_order + 2)); - } - // Save the bounding-box information about the text in a form // friendly to the user. - _num_rows = num_rows; - _ul2d = ul; - _lr2d = lr; + const LVector2f &ul = _assembler.get_ul(); + const LVector2f &lr = _assembler.get_lr(); _ul3d.set(ul[0], 0.0f, ul[1]); _lr3d.set(lr[0], 0.0f, lr[1]); @@ -360,42 +252,18 @@ generate() { _flags &= ~F_needs_measure; - // Now deal with all the decorations. - - if (has_shadow()) { - // Make a shadow by instancing the text behind itself. - - // For now, the depth offset is 0.0 because we don't expect to see - // text with shadows in the 3-d world that aren't decals. Maybe - // this will need to be addressed in the future. - - LMatrix4f offset = - LMatrix4f::translate_mat(_shadow_offset[0], 0.0f, -_shadow_offset[1]); - PT(PandaNode) shadow = new PandaNode("shadow"); - root->add_child(shadow, _draw_order + 1); - shadow->add_child(text_root); - shadow->set_transform(TransformState::make_mat(offset)); - shadow->set_attrib(ColorAttrib::make_flat(_shadow_color)); - - if (_shadow_color[3] != 1.0f) { - shadow->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha)); - } - - if (has_bin()) { - shadow->set_attrib(CullBinAttrib::make(_bin, _draw_order + 1)); - } - } + // Now deal with the decorations. if (has_frame()) { PT(PandaNode) frame_root = make_frame(); - root->add_child(frame_root, _draw_order + 1); - frame_root->set_attrib(ColorAttrib::make_flat(_frame_color)); - if (_frame_color[3] != 1.0f) { + root->add_child(frame_root, get_draw_order() + 1); + frame_root->set_attrib(ColorAttrib::make_flat(get_frame_color())); + if (get_frame_color()[3] != 1.0f) { frame_root->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha)); } if (has_bin()) { - frame_root->set_attrib(CullBinAttrib::make(_bin, _draw_order + 1)); + frame_root->set_attrib(CullBinAttrib::make(get_bin(), get_draw_order() + 1)); } } @@ -405,17 +273,17 @@ generate() { card_root = make_card_with_border(); else card_root = make_card(); - root->add_child(card_root, _draw_order); - card_root->set_attrib(ColorAttrib::make_flat(_card_color)); - if (_card_color[3] != 1.0f) { + root->add_child(card_root, get_draw_order()); + card_root->set_attrib(ColorAttrib::make_flat(get_card_color())); + if (get_card_color()[3] != 1.0f) { card_root->set_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha)); } if (has_card_texture()) { - card_root->set_attrib(TextureAttrib::make(_card_texture)); + card_root->set_attrib(TextureAttrib::make(get_card_texture())); } if (has_bin()) { - card_root->set_attrib(CullBinAttrib::make(_bin, _draw_order)); + card_root->set_attrib(CullBinAttrib::make(get_bin(), get_draw_order())); } } @@ -431,6 +299,26 @@ generate() { return root; } +//////////////////////////////////////////////////////////////////// +// Function: TextNode::calc_width +// Access: Public +// Description: Returns the width of a line of text of arbitrary +// characters. The line should not include the newline +// character or any embedded control characters like \1 +// or \3. +//////////////////////////////////////////////////////////////////// +float TextNode:: +calc_width(const wstring &line) const { + float width = 0.0f; + + wstring::const_iterator si; + for (si = line.begin(); si != line.end(); ++si) { + width += calc_width(*si); + } + + return width; +} + //////////////////////////////////////////////////////////////////// // Function: TextNode::get_unsafe_to_apply_attribs // Access: Public, Virtual @@ -484,11 +372,10 @@ apply_attribs_to_vertices(const AccumulatedAttribs &attribs, int attrib_types, const ColorAttrib *ca = DCAST(ColorAttrib, attribs._color); if (ca->get_color_type() == ColorAttrib::T_flat) { const Colorf &c = ca->get_color(); - _text_color = c; - _frame_color = c; - _card_color = c; - _shadow_color = c; - _flags |= F_has_text_color; + set_text_color(c); + set_frame_color(c); + set_card_color(c); + set_shadow_color(c); } } } @@ -497,22 +384,30 @@ apply_attribs_to_vertices(const AccumulatedAttribs &attribs, int attrib_types, const ColorScaleAttrib *csa = DCAST(ColorScaleAttrib, attribs._color_scale); const LVecBase4f &s = csa->get_scale(); if (s != LVecBase4f(1.0f, 1.0f, 1.0f, 1.0f)) { - _text_color[0] *= s[0]; - _text_color[1] *= s[1]; - _text_color[2] *= s[2]; - _text_color[3] *= s[3]; - _frame_color[0] *= s[0]; - _frame_color[1] *= s[1]; - _frame_color[2] *= s[2]; - _frame_color[3] *= s[3]; - _card_color[0] *= s[0]; - _card_color[1] *= s[1]; - _card_color[2] *= s[2]; - _card_color[3] *= s[3]; - _shadow_color[0] *= s[0]; - _shadow_color[1] *= s[1]; - _shadow_color[2] *= s[2]; - _shadow_color[3] *= s[3]; + LVecBase4f tc = get_text_color(); + tc[0] *= s[0]; + tc[1] *= s[1]; + tc[2] *= s[2]; + tc[3] *= s[3]; + set_text_color(tc); + LVecBase4f sc = get_shadow_color(); + sc[0] *= s[0]; + sc[1] *= s[1]; + sc[2] *= s[2]; + sc[3] *= s[3]; + set_shadow_color(sc); + LVecBase4f fc = get_frame_color(); + fc[0] *= s[0]; + fc[1] *= s[1]; + fc[2] *= s[2]; + fc[3] *= s[3]; + set_frame_color(fc); + LVecBase4f cc = get_card_color(); + cc[0] *= s[0]; + cc[1] *= s[1]; + cc[2] *= s[2]; + cc[3] *= s[3]; + set_card_color(cc); } } } @@ -662,842 +557,8 @@ do_rebuild() { //////////////////////////////////////////////////////////////////// void TextNode:: do_measure() { - _flags &= ~F_needs_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 (!has_text()) { - return; - } - - TextFont *font = get_font(); - if (font == (TextFont *)NULL) { - font = get_default_font(); - } - - if (font == (TextFont *)NULL) { - return; - } - - wstring wtext = get_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(), font, - 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, - TextFont *font, GeomNode *dest, const LMatrix4f &mat) { - 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 if (character == '\t') { - // So is a tab character. - xpos = (floor(xpos / _tab_width) + 1.0f) * _tab_width; - - } else if (character == text_soft_hyphen_key) { - // And so is the 'soft-hyphen' key character. - - } else { - // A printable character. - bool got_glyph; - const TextGlyph *glyph; - const TextGlyph *second_glyph; - UnicodeLatinMap::AccentType accent_type; - int additional_flags; - float glyph_scale; - float advance_scale; - get_character_glyphs(character, font, - got_glyph, glyph, second_glyph, accent_type, - additional_flags, glyph_scale, advance_scale); - - if (!got_glyph) { - text_cat.warning() - << "No definition in " << font->get_name() - << " for character " << character; - if (character < 128 && isprint((unsigned int)character)) { - text_cat.warning(false) - << " ('" << (char)character << "')"; - } - text_cat.warning(false) - << "\n"; - } - - // Build up a temporary array of the Geoms that go into this - // character. Normally, there is only one Geom per character, - // but it may involve multiple Geoms if we need to add cheesy - // accents or ligatures. - static const int max_geoms = 10; - Geom *geom_array[max_geoms]; - int num_geoms = 0; - int gi; - - float advance = 0.0f; - - if (glyph != (TextGlyph *)NULL) { - PT(Geom) char_geom = glyph->get_geom(); - if (char_geom != (Geom *)NULL) { - dest->add_geom(char_geom, glyph->get_state()); - geom_array[num_geoms++] = char_geom; - } - advance = glyph->get_advance() * advance_scale; - } - if (second_glyph != (TextGlyph *)NULL) { - PT(Geom) second_char_geom = second_glyph->get_geom(); - if (second_char_geom != (Geom *)NULL) { - second_char_geom->transform_vertices(LMatrix4f::translate_mat(advance, 0.0f, 0.0f)); - dest->add_geom(second_char_geom, second_glyph->get_state()); - geom_array[num_geoms++] = second_char_geom; - } - advance += second_glyph->get_advance(); - } - - // Now compute the matrix that will transform the glyph (or - // glyphs) into position. - LMatrix4f glyph_xform = LMatrix4f::scale_mat(glyph_scale); - - if (accent_type != UnicodeLatinMap::AT_none || additional_flags != 0) { - // If we have some special handling to perform, do so now. - // This will probably require the bounding volume of the - // glyph, so go get that. - LPoint3f min_vert, max_vert; - bool found_any = false; - for (gi = 0; gi < num_geoms; gi++) { - geom_array[gi]->calc_tight_bounds(min_vert, max_vert, found_any); - } - - if (found_any) { - LPoint3f centroid = (min_vert + max_vert) / 2.0f; - tack_on_accent(accent_type, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - - if ((additional_flags & UnicodeLatinMap::AF_turned) != 0) { - // Invert the character. Should we also invert the accent - // mark, so that an accent that would have been above the - // glyph will now be below it? That's what we do here, - // which is probably the right thing to do for n-tilde, - // but not for most of the rest of the accent marks. For - // now we'll assume there are no characters with accent - // marks that also have the turned flag. - - // We rotate the character around its centroid, which may - // not always be the right point, but it's the best we've - // got and it's probably pretty close. - LMatrix4f rotate = - LMatrix4f::translate_mat(-centroid) * - LMatrix4f::rotate_mat_normaxis(180.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) * - LMatrix4f::translate_mat(centroid); - glyph_xform *= rotate; - } - } - } - - glyph_xform(3, 0) += xpos; - LMatrix4f net_xform = glyph_xform * mat; - - // Finally, transform all the Geoms for this character into - // place. Again, normally there is only one Geom per character; - // there will only be multiple Geoms if we have added accents or - // ligatures. - for (gi = 0; gi < num_geoms; gi++) { - geom_array[gi]->transform_vertices(net_xform); - } - - xpos += 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. -//////////////////////////////////////////////////////////////////// -PT(PandaNode) TextNode:: -assemble_text(wstring::iterator si, const wstring::iterator &send, - TextFont *font, LVector2f &ul, LVector2f &lr, int &num_rows) { - float line_height = font->get_line_height(); - - ul.set(0.0f, 0.8f * line_height); - lr.set(0.0f, 0.0f); - - // Make a geom node to hold our formatted text geometry. - PT(GeomNode) root_node = new GeomNode("text"); - - float posy = 0.0f; - while (si != send) { - // First, just measure the row, so we know how wide it is. - // (Centered or right-justified text will require us to know this - // up front.) - wstring::iterator tsi = si; - float row_width = measure_row(tsi, send, font); - - 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; - } - - // Now that we've computed the row's transform matrix, generate - // the actual geoms for the row. - assemble_row(si, send, font, root_node, mat); - if (si != send) { - // Skip past the newline. - ++si; - } - - posy -= line_height; - num_rows++; - } - - lr[1] = posy + 0.8f * line_height; - - return root_node.p(); -} - -//////////////////////////////////////////////////////////////////// -// 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, - TextFont *font) { - 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 if (character == '\t') { - // So is a tab character. - xpos = (floor(xpos / _tab_width) + 1.0f) * _tab_width; - - } else if (character == text_soft_hyphen_key) { - // And so is the 'soft-hyphen' key character. - - } else { - // A printable character. - bool got_glyph; - const TextGlyph *glyph; - const TextGlyph *second_glyph; - UnicodeLatinMap::AccentType accent_type; - int additional_flags; - float glyph_scale; - float advance_scale; - get_character_glyphs(character, font, - got_glyph, glyph, second_glyph, accent_type, - additional_flags, glyph_scale, advance_scale); - - float advance = 0.0f; - - if (glyph != (TextGlyph *)NULL) { - advance = glyph->get_advance() * advance_scale; - } - if (second_glyph != (TextGlyph *)NULL) { - advance += second_glyph->get_advance(); - } - - xpos += 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, - TextFont *font, LVector2f &ul, LVector2f &lr, int &num_rows) { - float line_height = font->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, font); - 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::get_character_glyphs -// Access: Private -// Description: Looks up the glyph(s) from the font for the -// appropriate character. If the desired glyph isn't -// available (especially in the case of an accented -// letter), tries to find a suitable replacement. -// Normally, only one glyph is returned per character, -// but in the case in which we have to simulate a -// missing ligature in the font, two glyphs might be -// returned. -// -// All parameters except the first two are output -// parameters. got_glyph is set true if the glyph (or -// an acceptable substitute) is successfully found, -// false otherwise; but even if it is false, glyph might -// still be non-NULL, indicating a stand-in glyph for a -// missing character. -//////////////////////////////////////////////////////////////////// -void TextNode:: -get_character_glyphs(int character, TextFont *font, - bool &got_glyph, const TextGlyph *&glyph, - const TextGlyph *&second_glyph, - UnicodeLatinMap::AccentType &accent_type, - int &additional_flags, - float &glyph_scale, float &advance_scale) { - got_glyph = false; - glyph = NULL; - second_glyph = NULL; - accent_type = UnicodeLatinMap::AT_none; - additional_flags = 0; - glyph_scale = 1.0f; - advance_scale = 1.0f; - - // Maybe we should remap the character to something else--e.g. a - // small capital. - const UnicodeLatinMap::Entry *map_entry = - UnicodeLatinMap::look_up(character); - if (map_entry != NULL) { - if (get_small_caps() && map_entry->_toupper_character != character) { - character = map_entry->_toupper_character; - map_entry = UnicodeLatinMap::look_up(character); - glyph_scale = get_small_caps_scale(); - } - } - - got_glyph = font->get_glyph(character, glyph); - if (!got_glyph && map_entry != NULL && map_entry->_ascii_equiv != 0) { - // If we couldn't find the Unicode glyph, try the ASCII - // equivalent (without the accent marks). - got_glyph = font->get_glyph(map_entry->_ascii_equiv, glyph); - - if (!got_glyph && map_entry->_toupper_character != character) { - // If we still couldn't find it, try the uppercase - // equivalent. - character = map_entry->_toupper_character; - map_entry = UnicodeLatinMap::look_up(character); - if (map_entry != NULL) { - got_glyph = font->get_glyph(map_entry->_ascii_equiv, glyph); - } - } - - if (got_glyph) { - accent_type = map_entry->_accent_type; - additional_flags = map_entry->_additional_flags; - - bool got_second_glyph = false; - if (map_entry->_ascii_additional != 0) { - // There's another character, too--probably a ligature. - got_second_glyph = - font->get_glyph(map_entry->_ascii_additional, second_glyph); - } - - if ((additional_flags & UnicodeLatinMap::AF_ligature) != 0 && - got_second_glyph) { - // If we have two letters that are supposed to be in a - // ligature, just jam them together. - additional_flags &= ~UnicodeLatinMap::AF_ligature; - advance_scale = ligature_advance_scale; - } - - if ((additional_flags & UnicodeLatinMap::AF_smallcap) != 0) { - additional_flags &= ~UnicodeLatinMap::AF_smallcap; - glyph_scale = get_small_caps_scale(); - } - } - } -} - - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::tack_on_accent -// Access: Private -// Description: This is a cheesy attempt to tack on an accent to an -// ASCII letter for which we don't have the appropriate -// already-accented glyph in the font. -//////////////////////////////////////////////////////////////////// -void TextNode:: -tack_on_accent(UnicodeLatinMap::AccentType accent_type, - const LPoint3f &min_vert, const LPoint3f &max_vert, - const LPoint3f ¢roid, - TextFont *font, GeomNode *dest, - Geom *geom_array[], int &num_geoms) { - switch (accent_type) { - case UnicodeLatinMap::AT_grave: - // We use the slash as the grave and acute accents. ASCII does - // have a grave accent character, but a lot of fonts put the - // reverse apostrophe there instead. And some fonts (particularly - // fonts from mf) don't even do backslash. - tack_on_accent('/', CP_above, CT_small_squash_mirror_y, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_acute: - tack_on_accent('/', CP_above, CT_small_squash, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_breve: - tack_on_accent(')', CP_above, CT_tiny_rotate_270, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_inverted_breve: - tack_on_accent('(', CP_above, CT_tiny_rotate_270, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_circumflex: - tack_on_accent('^', CP_above, CT_none, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms) || - tack_on_accent('v', CP_above, CT_squash_mirror_y, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_circumflex_below: - tack_on_accent('^', CP_below, CT_none, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms) || - tack_on_accent('v', CP_below, CT_squash_mirror_y, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_caron: - tack_on_accent('^', CP_above, CT_mirror_y, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms) || - tack_on_accent('v', CP_above, CT_squash, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - - break; - - case UnicodeLatinMap::AT_tilde: - tack_on_accent('~', CP_above, CT_none, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms) || - tack_on_accent('s', CP_above, CT_squash_mirror_diag, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_tilde_below: - tack_on_accent('~', CP_below, CT_none, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms) || - tack_on_accent('s', CP_below, CT_squash_mirror_diag, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_diaeresis: - tack_on_accent(':', CP_above, CT_small_rotate_270, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_diaeresis_below: - tack_on_accent(':', CP_below, CT_small_rotate_270, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_dot_above: - tack_on_accent('.', CP_above, CT_none, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_dot_below: - tack_on_accent('.', CP_below, CT_none, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_macron: - tack_on_accent('-', CP_above, CT_none, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_line_below: - tack_on_accent('-', CP_below, CT_none, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_ring_above: - tack_on_accent('o', CP_top, CT_tiny, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_ring_below: - tack_on_accent('o', CP_bottom, CT_tiny, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_cedilla: - tack_on_accent('c', CP_bottom, CT_tiny_mirror_x, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - /* - tack_on_accent(',', CP_bottom, CT_none, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - */ - break; - - case UnicodeLatinMap::AT_comma_below: - tack_on_accent(',', CP_below, CT_none, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_ogonek: - tack_on_accent(',', CP_bottom, CT_mirror_x, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - case UnicodeLatinMap::AT_stroke: - tack_on_accent('/', CP_within, CT_none, min_vert, max_vert, centroid, - font, dest, geom_array, num_geoms); - break; - - default: - // There are lots of other crazy kinds of accents. Forget 'em. - break; - } -} - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::tack_on_accent -// Access: Private -// Description: Generates a cheesy accent mark above (or below, etc.) -// the character. Returns true if successful, or false -// if the named accent character doesn't exist in the -// font. -//////////////////////////////////////////////////////////////////// -bool TextNode:: -tack_on_accent(char accent_mark, TextNode::CheesyPlacement placement, - TextNode::CheesyTransform transform, - const LPoint3f &min_vert, const LPoint3f &max_vert, - const LPoint3f ¢roid, - TextFont *font, GeomNode *dest, - Geom *geom_array[], int &num_geoms) { - - const TextGlyph *accent_glyph; - if (font->get_glyph(accent_mark, accent_glyph)) { - PT(Geom) accent_geom = accent_glyph->get_geom(); - if (accent_geom != (Geom *)NULL) { - LPoint3f min_accent, max_accent; - bool found_any = false; - accent_geom->calc_tight_bounds(min_accent, max_accent, found_any); - if (found_any) { - float t, u; - LMatrix4f accent_mat; - - // This gets set to true if the glyph gets mirrored and needs - // to have backface culling disabled. - bool mirrored = false; - - switch (transform) { - case CT_none: - accent_mat = LMatrix4f::ident_mat(); - break; - - case CT_mirror_x: - accent_mat = LMatrix4f::scale_mat(-1.0f, 1.0f, 1.0f); - t = min_accent[0]; - min_accent[0] = -max_accent[0]; - max_accent[0] = -t; - mirrored = true; - break; - - case CT_mirror_y: - accent_mat = LMatrix4f::scale_mat(1.0f, 1.0f, -1.0f); - t = min_accent[2]; - min_accent[2] = -max_accent[2]; - max_accent[2] = -t; - mirrored = true; - break; - - case CT_rotate_90: - accent_mat = - LMatrix4f::rotate_mat_normaxis(90.0f, LVecBase3f(0.0f, -1.0f, 0.0f)); - // rotate min, max - t = min_accent[0]; - u = max_accent[0]; - max_accent[0] = -min_accent[2]; - min_accent[0] = -max_accent[2]; - max_accent[2] = u; - min_accent[2] = t; - break; - - case CT_rotate_180: - accent_mat = LMatrix4f::scale_mat(-1.0f, -1.0f, 1.0f); - - t = min_accent[0]; - min_accent[0] = -max_accent[0]; - max_accent[0] = -t; - t = min_accent[2]; - min_accent[2] = -max_accent[2]; - max_accent[2] = -t; - break; - - case CT_rotate_270: - accent_mat = - LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f)); - // rotate min, max - t = min_accent[0]; - u = max_accent[0]; - min_accent[0] = min_accent[2]; - max_accent[0] = max_accent[2]; - min_accent[2] = -u; - max_accent[2] = -t; - break; - - case CT_squash: - accent_mat = LMatrix4f::scale_mat(squash_accent_scale_x, 1.0f, squash_accent_scale_y); - min_accent[0] *= squash_accent_scale_x; - max_accent[0] *= squash_accent_scale_x; - min_accent[2] *= squash_accent_scale_y; - max_accent[2] *= squash_accent_scale_y; - break; - - case CT_squash_mirror_y: - accent_mat = LMatrix4f::scale_mat(squash_accent_scale_x, 1.0f, -squash_accent_scale_y); - min_accent[0] *= squash_accent_scale_x; - max_accent[0] *= squash_accent_scale_x; - t = min_accent[2]; - min_accent[2] = -max_accent[2] * squash_accent_scale_y; - max_accent[2] = -t * squash_accent_scale_y; - mirrored = true; - break; - - case CT_squash_mirror_diag: - accent_mat = - LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) * - LMatrix4f::scale_mat(-squash_accent_scale_x, 1.0f, squash_accent_scale_y); - - // rotate min, max - t = min_accent[0]; - u = max_accent[0]; - min_accent[0] = min_accent[2] * -squash_accent_scale_x; - max_accent[0] = max_accent[2] * -squash_accent_scale_x; - min_accent[2] = -u * squash_accent_scale_y; - max_accent[2] = -t * squash_accent_scale_y; - mirrored = true; - break; - - case CT_small_squash: - accent_mat = LMatrix4f::scale_mat(small_squash_accent_scale_x, 1.0f, small_squash_accent_scale_y); - min_accent[0] *= small_squash_accent_scale_x; - max_accent[0] *= small_squash_accent_scale_x; - min_accent[2] *= small_squash_accent_scale_y; - max_accent[2] *= small_squash_accent_scale_y; - break; - - case CT_small_squash_mirror_y: - accent_mat = LMatrix4f::scale_mat(small_squash_accent_scale_x, 1.0f, -small_squash_accent_scale_y); - min_accent[0] *= small_squash_accent_scale_x; - max_accent[0] *= small_squash_accent_scale_x; - t = min_accent[2]; - min_accent[2] = -max_accent[2] * small_squash_accent_scale_y; - max_accent[2] = -t * small_squash_accent_scale_y; - mirrored = true; - break; - - case CT_small: - accent_mat = LMatrix4f::scale_mat(small_accent_scale); - min_accent *= small_accent_scale; - max_accent *= small_accent_scale; - break; - - case CT_small_rotate_270: - accent_mat = - LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) * - LMatrix4f::scale_mat(small_accent_scale); - - // rotate min, max - t = min_accent[0]; - u = max_accent[0]; - min_accent[0] = min_accent[2] * small_accent_scale; - max_accent[0] = max_accent[2] * small_accent_scale; - min_accent[2] = -u * small_accent_scale; - max_accent[2] = -t * small_accent_scale; - break; - - case CT_tiny: - accent_mat = LMatrix4f::scale_mat(tiny_accent_scale); - min_accent *= tiny_accent_scale; - max_accent *= tiny_accent_scale; - break; - - case CT_tiny_mirror_x: - accent_mat = LMatrix4f::scale_mat(-tiny_accent_scale, 1.0f, tiny_accent_scale); - - t = min_accent[0]; - min_accent[0] = -max_accent[0] * tiny_accent_scale; - max_accent[0] = -t * tiny_accent_scale; - min_accent[2] *= tiny_accent_scale; - max_accent[2] *= tiny_accent_scale; - mirrored = true; - break; - - case CT_tiny_rotate_270: - accent_mat = - LMatrix4f::rotate_mat_normaxis(270.0f, LVecBase3f(0.0f, -1.0f, 0.0f)) * - LMatrix4f::scale_mat(tiny_accent_scale); - - // rotate min, max - t = min_accent[0]; - u = max_accent[0]; - min_accent[0] = min_accent[2] * tiny_accent_scale; - max_accent[0] = max_accent[2] * tiny_accent_scale; - min_accent[2] = -u * tiny_accent_scale; - max_accent[2] = -t * tiny_accent_scale; - break; - } - - LPoint3f accent_centroid = (min_accent + max_accent) / 2.0f; - float accent_height = max_accent[2] - min_accent[2]; - LVector3f trans; - switch (placement) { - case CP_above: - // A little above the character. - trans.set(centroid[0] - accent_centroid[0], 0.0f, - max_vert[2] - accent_centroid[2] + accent_height * 0.5); - break; - - case CP_below: - // A little below the character. - trans.set(centroid[0] - accent_centroid[0], 0.0f, - min_vert[2] - accent_centroid[2] - accent_height * 0.5); - break; - - case CP_top: - // Touching the top of the character. - trans.set(centroid[0] - accent_centroid[0], 0.0f, - max_vert[2] - accent_centroid[2]); - break; - - case CP_bottom: - // Touching the bottom of the character. - trans.set(centroid[0] - accent_centroid[0], 0.0f, - min_vert[2] - accent_centroid[2]); - break; - - case CP_within: - // Centered within the character. - trans.set(centroid[0] - accent_centroid[0], 0.0f, - centroid[2] - accent_centroid[2]); - break; - } - - accent_mat.set_row(3, trans); - accent_geom->transform_vertices(accent_mat); - - if (mirrored) { - // Once someone asks for this pointer, we hold its reference - // count and never free it. - static CPT(RenderState) disable_backface; - if (disable_backface == (const RenderState *)NULL) { - disable_backface = RenderState::make - (CullFaceAttrib::make(CullFaceAttrib::M_cull_none)); - } - - CPT(RenderState) state = - accent_glyph->get_state()->compose(disable_backface); - dest->add_geom(accent_geom, state); - } else { - dest->add_geom(accent_geom, accent_glyph->get_state()); - } - geom_array[num_geoms++] = accent_geom; - - return true; - } - } - } - - return false; + // We no longer make this a special case. + do_rebuild(); } //////////////////////////////////////////////////////////////////// @@ -1712,59 +773,3 @@ make_card_with_border() { return card_geode.p(); } - -//////////////////////////////////////////////////////////////////// -// Function: TextNode::load_default_font -// Access: Private, Static -// Description: This function is called once (or never), the first -// time someone attempts to render a TextNode using the -// default font. It should attempt to load the default -// font, using the compiled-in version if it is -// available, or whatever system file may be named in -// Configrc. -//////////////////////////////////////////////////////////////////// -void TextNode:: -load_default_font() { - _loaded_default_font = true; - - if (!text_default_font.empty()) { - // First, attempt to load the user-specified filename. - _default_font = FontPool::load_font(text_default_font); - if (_default_font->is_valid()) { - return; - } - } - - // Then, attempt to load the compiled-in font, if we have one. -#ifdef COMPILE_IN_DEFAULT_FONT -#ifdef HAVE_FREETYPE - // Loading the compiled-in FreeType font is relatively easy. - _default_font = new DynamicTextFont((const char *)default_font_data, - default_font_size, 0); - -#else - // The compiled-in Bam font requires creating a BamFile object to - // decode it. - string data((const char *)default_font_data, default_font_size); - -#ifdef HAVE_ZLIB - // The font data is stored compressed; decompress it on-the-fly. - istringstream inz(data); - IDecompressStream in(&inz, false); - -#else - // The font data is stored uncompressed, so just load it. - istringstream in(data); -#endif // HAVE_ZLIB - - BamFile bam_file; - if (bam_file.open_read(in, "default font stream")) { - PT(PandaNode) node = bam_file.read_node(); - if (node != (PandaNode *)NULL) { - _default_font = new StaticTextFont(node); - } - } - -#endif // HAVE_FREETYPE -#endif // COMPILE_IN_DEFAULT_FONT -} diff --git a/panda/src/text/textNode.h b/panda/src/text/textNode.h index 9d53af09ac..49bb511abd 100644 --- a/panda/src/text/textNode.h +++ b/panda/src/text/textNode.h @@ -23,13 +23,12 @@ #include "config_text.h" #include "textEncoder.h" +#include "textProperties.h" #include "textFont.h" -#include "unicodeLatinMap.h" +#include "textAssembler.h" #include "pandaNode.h" #include "luse.h" -class StringDecoder; - //////////////////////////////////////////////////////////////////// // Class : TextNode // Description : The primary interface to this module. This class @@ -53,49 +52,19 @@ class StringDecoder; // you may use however you like. Each time you call // generate() a new node is returned. //////////////////////////////////////////////////////////////////// -class EXPCL_PANDA TextNode : public PandaNode, public TextEncoder { +class EXPCL_PANDA TextNode : public PandaNode, public TextEncoder, public TextProperties { PUBLISHED: TextNode(const string &name); + TextNode(const string &name, const TextProperties ©); ~TextNode(); - enum Alignment { - A_left, - A_right, - A_center, - }; - - INLINE int freeze(); - INLINE int thaw(); - - INLINE void set_font(TextFont *font); - INLINE TextFont *get_font() const; - - INLINE static void set_default_font(TextFont *); - INLINE static TextFont *get_default_font(); - INLINE float get_line_height() const; - INLINE void set_small_caps(bool small_caps); - INLINE bool get_small_caps() const; - INLINE void set_small_caps_scale(float small_caps_scale); - INLINE float get_small_caps_scale() const; - - INLINE void set_slant(float slant); - INLINE float get_slant() const; - - INLINE void set_align(Alignment align_type); - INLINE Alignment get_align() const; - - INLINE void set_wordwrap(float width); - INLINE void clear_wordwrap(); - INLINE bool has_wordwrap() const; - INLINE float get_wordwrap() const; - - INLINE void set_text_color(float r, float g, float b, float a); - INLINE void set_text_color(const Colorf &text_color); - INLINE void clear_text_color(); - INLINE bool has_text_color() const; - INLINE Colorf get_text_color() const; + INLINE void set_max_rows(int max_rows); + INLINE void clear_max_rows(); + INLINE bool has_max_rows() const; + INLINE int get_max_rows() const; + INLINE bool has_overflow() const; INLINE void set_frame_color(float r, float g, float b, float a); INLINE void set_frame_color(const Colorf &frame_color); @@ -116,10 +85,6 @@ PUBLISHED: INLINE bool has_card_texture() const; INLINE Texture *get_card_texture() const; - INLINE void set_shadow_color(float r, float g, float b, float a); - INLINE void set_shadow_color(const Colorf &shadow_color); - INLINE Colorf get_shadow_color() const; - INLINE void set_frame_as_margin(float left, float right, float bottom, float top); INLINE void set_frame_actual(float left, float right, @@ -146,28 +111,64 @@ PUBLISHED: INLINE LVecBase4f get_card_actual() const; INLINE LVecBase4f get_card_transformed() const; - INLINE void set_shadow(float xoffset, float yoffset); - INLINE void clear_shadow(); - INLINE bool has_shadow() const; - INLINE LVecBase2f get_shadow() const; - - INLINE void set_bin(const string &bin); - INLINE void clear_bin(); - INLINE bool has_bin() const; - INLINE const string &get_bin() const; - - INLINE int set_draw_order(int draw_order); - INLINE int get_draw_order() const; - - INLINE void set_tab_width(float tab_width); - INLINE float get_tab_width() const; - INLINE void set_transform(const LMatrix4f &transform); INLINE LMatrix4f get_transform() const; INLINE void set_coordinate_system(CoordinateSystem cs); INLINE CoordinateSystem get_coordinate_system() const; + // These methods are inherited from TextProperties, but we override + // here so we can flag the TextNode as dirty when they have been + // change. + + INLINE void set_font(TextFont *font); + INLINE void clear_font(); + + INLINE void set_small_caps(bool small_caps); + INLINE void clear_small_caps(); + + INLINE void set_small_caps_scale(float small_caps_scale); + INLINE void clear_small_caps_scale(); + + INLINE void set_slant(float slant); + INLINE void clear_slant(); + + INLINE void set_align(Alignment align_type); + INLINE void clear_align(); + + INLINE void set_indent(float indent); + INLINE void clear_indent(); + + INLINE void set_wordwrap(float wordwrap); + INLINE void clear_wordwrap(); + + INLINE void set_text_color(float r, float g, float b, float a); + INLINE void set_text_color(const Colorf &text_color); + INLINE void clear_text_color(); + + INLINE void set_shadow_color(float r, float g, float b, float a); + INLINE void set_shadow_color(const Colorf &shadow_color); + INLINE void clear_shadow_color(); + + INLINE void set_shadow(float xoffset, float yoffset); + INLINE void set_shadow(const LVecBase2f &shadow_offset); + INLINE void clear_shadow(); + + INLINE void set_bin(const string &bin); + INLINE void clear_bin(); + + INLINE int set_draw_order(int draw_order); + INLINE void clear_draw_order(); + + INLINE void set_tab_width(float tab_width); + INLINE void clear_tab_width(); + + INLINE void set_glyph_scale(float glyph_scale); + INLINE void clear_glyph_scale(); + + INLINE void set_glyph_shift(float glyph_shift); + INLINE void clear_glyph_shift(); + // These methods are inherited from TextEncoder, but we override // here so we can flag the TextNode as dirty when they have been // change. @@ -177,10 +178,14 @@ PUBLISHED: INLINE void append_text(const string &text); INLINE void append_unicode_char(int character); - INLINE float calc_width(int character) const; + // After the text has been set, you can query this to determine how + // it will be wordwrapped. + INLINE string get_wordwrapped_text() const; + + // These methods calculate the width of a single character or a line + // of text in the current font. + float calc_width(int character) const; INLINE float calc_width(const string &line) const; - string wordwrap_to(const string &text, float wordwrap_width, - bool preserve_trailing_whitespace) const; virtual void write(ostream &out, int indent_level = 0) const; @@ -207,9 +212,8 @@ public: INLINE void set_wtext(const wstring &wtext); INLINE void append_wtext(const wstring &text); - INLINE float calc_width(const wstring &line) const; - INLINE wstring wordwrap_to(const wstring &wtext, float wordwrap_width, - bool preserve_trailing_whitespace) const; + INLINE wstring get_wordwrapped_wtext() const; + float calc_width(const wstring &line) const; // From parent class PandaNode virtual int get_unsafe_to_apply_attribs() const; @@ -235,123 +239,47 @@ private: void do_rebuild(); void do_measure(); -#ifndef CPPPARSER // interrogate has a bit of trouble with wstring. - float assemble_row(wstring::iterator &si, const wstring::iterator &send, - TextFont *font, GeomNode *dest, const LMatrix4f &mat); - PT(PandaNode) assemble_text(wstring::iterator si, const wstring::iterator &send, - TextFont *font, - LVector2f &ul, LVector2f &lr, int &num_rows); - float measure_row(wstring::iterator &si, const wstring::iterator &send, - TextFont *font); - void measure_text(wstring::iterator si, const wstring::iterator &send, - TextFont *font, - LVector2f &ul, LVector2f &lr, int &num_rows); -#endif // CPPPARSER - - enum CheesyPlacement { - CP_above, - CP_below, - CP_top, - CP_bottom, - CP_within, - }; - enum CheesyTransform { - CT_none, - CT_mirror_x, - CT_mirror_y, - CT_rotate_90, - CT_rotate_180, - CT_rotate_270, - CT_squash, - CT_squash_mirror_y, - CT_squash_mirror_diag, - CT_small_squash, - CT_small_squash_mirror_y, - CT_small, - CT_small_rotate_270, - CT_tiny, - CT_tiny_mirror_x, - CT_tiny_rotate_270, - }; - - void get_character_glyphs(int character, TextFont *font, - bool &got_glyph, const TextGlyph *&glyph, - const TextGlyph *&second_glyph, - UnicodeLatinMap::AccentType &accent_type, - int &additional_flags, - float &glyph_scale, float &advance_scale); - - void tack_on_accent(UnicodeLatinMap::AccentType accent_type, - const LPoint3f &min_vert, const LPoint3f &max_vert, - const LPoint3f ¢roid, - TextFont *font, GeomNode *dest, - Geom *geom_array[], int &num_geoms); - bool tack_on_accent(char accent_mark, CheesyPlacement placement, - CheesyTransform transform, - const LPoint3f &min_vert, const LPoint3f &max_vert, - const LPoint3f ¢roid, - TextFont *font, GeomNode *dest, - Geom *geom_array[], int &num_geoms); - PT(PandaNode) make_frame(); PT(PandaNode) make_card(); PT(PandaNode) make_card_with_border(); - static void load_default_font(); - - PT(TextFont) _font; PT(PandaNode) _internal_geom; - float _slant; - PT(Texture) _card_texture; - Colorf _text_color; - Colorf _shadow_color; Colorf _frame_color; Colorf _card_color; enum Flags { - F_has_text_color = 0x00000001, - F_has_wordwrap = 0x00000002, - F_has_frame = 0x00000004, - F_frame_as_margin = 0x00000008, - F_has_card = 0x00000010, - F_card_as_margin = 0x00000020, - F_has_card_texture = 0x00000040, - F_has_shadow = 0x00000080, - F_frame_corners = 0x00000100, - F_card_transp = 0x00000200, - F_has_card_border = 0x00000400, - F_needs_rebuild = 0x00004000, - F_needs_measure = 0x00008000, - F_small_caps = 0x00010000, + F_has_frame = 0x0001, + F_frame_as_margin = 0x0002, + F_has_card = 0x0004, + F_card_as_margin = 0x0008, + F_has_card_texture = 0x0010, + F_frame_corners = 0x0020, + F_card_transp = 0x0040, + F_has_card_border = 0x0080, + F_needs_rebuild = 0x0100, + F_needs_measure = 0x0200, + F_has_overflow = 0x0400, }; int _flags; - Alignment _align; - float _wordwrap_width; + int _max_rows; float _frame_width; float _card_border_size; float _card_border_uv_portion; - float _small_caps_scale; LVector2f _frame_ul, _frame_lr; LVector2f _card_ul, _card_lr; - LVector2f _shadow_offset; - - string _bin; - int _draw_order; - float _tab_width; LMatrix4f _transform; CoordinateSystem _coordinate_system; - LPoint2f _ul2d, _lr2d; LPoint3f _ul3d, _lr3d; - int _num_rows; - static PT(TextFont) _default_font; - static bool _loaded_default_font; +#ifndef CPPPARSER + TextAssembler _assembler; +#endif public: static TypeHandle get_class_type() { @@ -359,9 +287,12 @@ public: } static void init_type() { PandaNode::init_type(); + TextEncoder::init_type(); + TextProperties::init_type(); register_type(_type_handle, "TextNode", PandaNode::get_class_type(), - TextEncoder::get_class_type()); + TextEncoder::get_class_type(), + TextProperties::get_class_type()); } virtual TypeHandle get_type() const { return get_class_type(); diff --git a/panda/src/text/textProperties.I b/panda/src/text/textProperties.I new file mode 100644 index 0000000000..0fcccae931 --- /dev/null +++ b/panda/src/text/textProperties.I @@ -0,0 +1,842 @@ +// Filename: textProperties.I +// Created by: drose (06Apr04) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::is_any_specified +// Access: Published +// Description: Returns true if any properties have been specified, +// false otherwise. +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +is_any_specified() const { + return (_specified != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_default_font +// Access: Published, Static +// Description: Specifies the default font to be used for any +// TextNode whose font is uninitialized or NULL. See +// set_font(). +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_default_font(TextFont *font) { + // If the user overrides the default, we don't need to try to load + // whatever it would have been. + _loaded_default_font = true; + _default_font = font; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_default_font +// Access: Published, Static +// Description: Specifies the default font to be used for any +// TextNode whose font is uninitialized or NULL. See +// set_font(). +//////////////////////////////////////////////////////////////////// +INLINE TextFont *TextProperties:: +get_default_font() { + if (!_loaded_default_font) { + load_default_font(); + } + return _default_font; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_font +// Access: Published +// Description: Sets the font that will be used when making text. If +// this is set to NULL, the default font will be used, +// which can be set via set_default_font(). +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_font(TextFont *font) { + _font = font; + _specified |= F_has_font; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_font +// Access: Published +// Description: Restores the default font to the text. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_font() { + _font.clear(); + _specified &= ~F_has_font; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_font +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_font() const { + return (_specified & F_has_font) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_font +// Access: Published +// Description: Returns the font currently in use, if any. If no +// font is in use, this returns the default font. +//////////////////////////////////////////////////////////////////// +INLINE TextFont *TextProperties:: +get_font() const { + return has_font() ? _font.p() : get_default_font(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_small_caps +// Access: Published +// Description: Sets the small_caps flag. When this is set, +// lowercase letters are generated as scaled-down +// versions of their uppercase equivalents. This is +// particularly useful to set for fonts that do not have +// lowercase letters. +// +// It is also a good idea to set this for a (dynamic) +// font that has already implemented lowercase letters +// as scaled-down versions of their uppercase +// equivalents, since without this flag the texture +// memory may needlessly duplicate equivalent glyphs for +// upper and lowercase letters. Setting this flag +// causes the texture memory to share the mixed-case +// letters. +// +// The amount by which the lowercase letters are scaled +// is specified by set_small_caps_scale(). +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_small_caps(bool small_caps) { + _small_caps = small_caps; + _specified |= F_has_small_caps; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_small_caps +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_small_caps() { + _small_caps = false; + _specified &= ~F_has_small_caps; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_small_caps +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_small_caps() const { + return (_specified & F_has_small_caps) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_small_caps +// Access: Published +// Description: Returns the small_caps flag. See set_small_caps(). +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +get_small_caps() const { + return _small_caps; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_small_caps_scale +// Access: Published +// Description: Sets the scale factor applied to lowercase letters +// from their uppercase equivalents, when the small_caps +// flag is in effect. See set_small_caps(). Normally, +// this will be a number less than one. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_small_caps_scale(float small_caps_scale) { + _small_caps_scale = small_caps_scale; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_small_caps_scale +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_small_caps_scale() { + _small_caps_scale = text_small_caps_scale; + _specified &= ~F_has_small_caps_scale; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_small_caps_scale +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_small_caps_scale() const { + return (_specified & F_has_small_caps_scale) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_small_caps_scale +// Access: Published +// Description: Returns the scale factor applied to lowercase letters +// from their uppercase equivalents, when the small_caps +// flag is in effect. See set_small_caps() and +// set_small_caps_scale(). +//////////////////////////////////////////////////////////////////// +INLINE float TextProperties:: +get_small_caps_scale() const { + return _small_caps_scale; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_slant +// Access: Published +// Description: Specifies the factor by which the text slants to the +// right. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_slant(float slant) { + _slant = slant; + _specified |= F_has_slant; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_slant +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_slant() { + _slant = 0.0f; + _specified &= ~F_has_slant; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_slant +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_slant() const { + return (_specified & F_has_slant) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_slant +// Access: Published +// Description: Returns the factor by which the text is specified to +// slant to the right. +//////////////////////////////////////////////////////////////////// +INLINE float TextProperties:: +get_slant() const { + return _slant; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_align +// Access: Published +// Description: Specifies the alignment of the text within its +// margins. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_align(TextProperties::Alignment align_type) { + _align = align_type; + _specified |= F_has_align; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_align +// Access: Published +// Description: Restores the default alignment of the text. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_align() { + _align = A_left; + _specified &= ~F_has_align; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_align +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_align() const { + return (_specified & F_has_align) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_align +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE TextProperties::Alignment TextProperties:: +get_align() const { + return _align; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_indent +// Access: Published +// Description: Specifies the amount of extra space that is inserted +// before the first character of each line. This can be +// thought of as a left margin. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_indent(float indent) { + _indent_width = indent; + _specified |= F_has_indent; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_indent +// Access: Published +// Description: Removes the indent setting from the text. Text +// will be as wide as it is. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_indent() { + _indent_width = 0.0f; + _specified &= ~F_has_indent; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_indent +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_indent() const { + return (_specified & F_has_indent) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_indent +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE float TextProperties:: +get_indent() const { + return _indent_width; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_wordwrap +// Access: Published +// Description: Sets the text up to automatically wordwrap when it +// exceeds the indicated width. This can be thought of +// as a right margin or margin width. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_wordwrap(float wordwrap) { + _wordwrap_width = wordwrap; + _specified |= F_has_wordwrap; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_wordwrap +// Access: Published +// Description: Removes the wordwrap setting from the text. Text +// will be as wide as it is. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_wordwrap() { + _wordwrap_width = 0.0f; + _specified &= ~F_has_wordwrap; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_wordwrap +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_wordwrap() const { + return (_specified & F_has_wordwrap) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_wordwrap +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE float TextProperties:: +get_wordwrap() const { + return _wordwrap_width; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_preserve_trailing_whitespace +// Access: Published +// Description: Sets the preserve_trailing_whitespace flag. When +// this is set, trailing whitespace at the end of the +// line is not stripped when the text is wordwrapped (it +// is stripped by default). Since the trailing +// whitespace is invisible, this is important primarily +// for determining the proper width of a frame or card +// behind the text. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_preserve_trailing_whitespace(bool preserve_trailing_whitespace) { + _preserve_trailing_whitespace = preserve_trailing_whitespace; + _specified |= F_has_preserve_trailing_whitespace; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_preserve_trailing_whitespace +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_preserve_trailing_whitespace() { + _preserve_trailing_whitespace = false; + _specified &= ~F_has_preserve_trailing_whitespace; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_preserve_trailing_whitespace +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_preserve_trailing_whitespace() const { + return (_specified & F_has_preserve_trailing_whitespace) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_preserve_trailing_whitespace +// Access: Published +// Description: Returns the preserve_trailing_whitespace flag. See +// set_preserve_trailing_whitespace(). +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +get_preserve_trailing_whitespace() const { + return _preserve_trailing_whitespace; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_text_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_text_color(float r, float g, float b, float a) { + set_text_color(Colorf(r, g, b, a)); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_text_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_text_color(const Colorf &text_color) { + _text_color = text_color; + _specified |= F_has_text_color; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_text_color +// Access: Published +// Description: Removes the text color specification; the text will +// be colored whatever it was in the source font file. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_text_color() { + _text_color.set(1.0f, 1.0f, 1.0f, 1.0f); + _specified &= ~F_has_text_color; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_text_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_text_color() const { + return (_specified & F_has_text_color) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_text_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE Colorf TextProperties:: +get_text_color() const { + return _text_color; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_shadow_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_shadow_color(float r, float g, float b, float a) { + set_shadow_color(Colorf(r, g, b, a)); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_shadow_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_shadow_color(const Colorf &shadow_color) { + _shadow_color = shadow_color; + _specified |= F_has_shadow_color; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_shadow_color +// Access: Published +// Description: Removes the shadow color specification. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_shadow_color() { + _shadow_color.set(0.0f, 0.0f, 0.0f, 1.0f); + _specified &= ~F_has_shadow_color; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_shadow_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_shadow_color() const { + return (_specified & F_has_shadow_color) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_shadow_color +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE Colorf TextProperties:: +get_shadow_color() const { + return _shadow_color; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_shadow +// Access: Published +// Description: Specifies that the text should be drawn with a +// shadow, by creating a second copy of the text and +// offsetting it slightly behind the first. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_shadow(float xoffset, float yoffset) { + set_shadow(LVecBase2f(xoffset, yoffset)); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_shadow +// Access: Published +// Description: Specifies that the text should be drawn with a +// shadow, by creating a second copy of the text and +// offsetting it slightly behind the first. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_shadow(const LVecBase2f &shadow_offset) { + _shadow_offset = shadow_offset; + _specified |= F_has_shadow; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_shadow +// Access: Published +// Description: Specifies that a shadow will not be drawn behind the +// text. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_shadow() { + _specified &= ~F_has_shadow; + _shadow_offset.set(0.0f, 0.0f); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_shadow +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_shadow() const { + return (_specified & F_has_shadow) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_shadow +// Access: Published +// Description: Returns the offset of the shadow as set by +// set_shadow(). It is an error to call this if +// has_shadow() is false. +//////////////////////////////////////////////////////////////////// +INLINE LVector2f TextProperties:: +get_shadow() const { + return _shadow_offset; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_bin +// Access: Published +// Description: Names the CullBin that the text geometry should be +// assigned to. If this is set, then a CullBinAttrib +// will be created to explicitly place each component in +// the named bin. +// +// The draw_order value will also be passed to each +// CullBinAttrib as appropriate; this is particularly +// useful if this names a CullBinFixed, e.g. "fixed". +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_bin(const string &bin) { + _bin = bin; + _specified |= F_has_bin; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_bin +// Access: Published +// Description: Removes the effect of a previous call to +// set_bin(). Text will be drawn in whatever bin +// it would like to be drawn in, with no explicit +// ordering. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_bin() { + _bin = string(); + _specified &= ~F_has_bin; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_bin +// Access: Published +// Description: Returns true if an explicit drawing bin has been +// set via set_bin(), false otherwise. +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_bin() const { + return (_specified & F_has_bin) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_bin +// Access: Published +// Description: Returns the drawing bin set with set_bin(), or empty +// string if no bin has been set. +//////////////////////////////////////////////////////////////////// +INLINE const string &TextProperties:: +get_bin() const { + return _bin; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_draw_order +// Access: Published +// Description: Sets the drawing order of text created by the +// TextNode. This is actually the draw order of the +// card and frame. The shadow is drawn at +// _draw_order+1, and the text at _draw_order+2. +// +// This affects the sorting order assigned to the nodes +// as they are created, and also is passed to whatever +// bin may be assigned via set_bin(). +// +// The return value is the first unused draw_order +// number, e.g. _draw_order + 3. +//////////////////////////////////////////////////////////////////// +INLINE int TextProperties:: +set_draw_order(int draw_order) { + _draw_order = draw_order; + _specified |= F_has_draw_order; + return _draw_order + 3; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_draw_order +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_draw_order() { + _draw_order = 1; + _specified &= ~F_has_draw_order; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_draw_order +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_draw_order() const { + return (_specified & F_has_draw_order) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_draw_order +// Access: Published +// Description: Returns the drawing order set with set_draw_order(). +//////////////////////////////////////////////////////////////////// +INLINE int TextProperties:: +get_draw_order() const { + return _draw_order; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_tab_width +// Access: Published +// Description: Sets the width of each tab stop, in screen units. A +// tab character embedded in the text will advance the +// horizontal position to the next tab stop. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_tab_width(float tab_width) { + _tab_width = tab_width; + _specified |= F_has_tab_width; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_tab_width +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_tab_width() { + _tab_width = text_tab_width; + _specified &= ~F_has_tab_width; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_tab_width +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_tab_width() const { + return (_specified & F_has_tab_width) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_tab_width +// Access: Published +// Description: Returns the width set via set_tab_width(). +//////////////////////////////////////////////////////////////////// +INLINE float TextProperties:: +get_tab_width() const { + return _tab_width; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_glyph_scale +// Access: Published +// Description: Specifies the factor by which to scale each letter of +// the text as it is placed. This can be used (possibly +// in conjunction with set_glyph_shift()) to implement +// superscripting or subscripting. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_glyph_scale(float glyph_scale) { + _glyph_scale = glyph_scale; + _specified |= F_has_glyph_scale; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_glyph_scale +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_glyph_scale() { + _specified &= ~F_has_glyph_scale; + _glyph_scale = 0.0f; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_glyph_scale +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_glyph_scale() const { + return (_specified & F_has_glyph_scale) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_glyph_scale +// Access: Published +// Description: Returns the scale factor of each letter as specified +// by set_glyph_scale(). +//////////////////////////////////////////////////////////////////// +INLINE float TextProperties:: +get_glyph_scale() const { + return _glyph_scale; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::set_glyph_shift +// Access: Published +// Description: Specifies a vertical amount to shift each letter of +// the text as it is placed. This can be used (possibly +// in conjunction with set_glyph_scale()) to implement +// superscripting or subscripting. +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +set_glyph_shift(float glyph_shift) { + _glyph_shift = glyph_shift; + _specified |= F_has_glyph_shift; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear_glyph_shift +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void TextProperties:: +clear_glyph_shift() { + _specified &= ~F_has_glyph_shift; + _glyph_shift = 0.0f; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::has_glyph_shift +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool TextProperties:: +has_glyph_shift() const { + return (_specified & F_has_glyph_shift) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::get_glyph_shift +// Access: Published +// Description: Returns the vertical shift of each letter as +// specified by set_glyph_shift(). +//////////////////////////////////////////////////////////////////// +INLINE float TextProperties:: +get_glyph_shift() const { + return _glyph_shift; +} diff --git a/panda/src/text/textProperties.cxx b/panda/src/text/textProperties.cxx new file mode 100644 index 0000000000..5d8e7d8572 --- /dev/null +++ b/panda/src/text/textProperties.cxx @@ -0,0 +1,318 @@ +// Filename: textProperties.cxx +// Created by: drose (06Apr04) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#include "textProperties.h" +#include "config_text.h" +#include "default_font.h" +#include "dynamicTextFont.h" +#include "staticTextFont.h" +#include "bamFile.h" +#include "fontPool.h" + +PT(TextFont) TextProperties::_default_font; +bool TextProperties::_loaded_default_font = false; + +TypeHandle TextProperties::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +TextProperties:: +TextProperties() { + _specified = 0; + + _small_caps = false; + _small_caps_scale = text_small_caps_scale; + _slant = 0.0f; + _align = A_left; + _indent_width = 0.0f; + _wordwrap_width = 0.0f; + _preserve_trailing_whitespace = false; + _text_color.set(1.0f, 1.0f, 1.0f, 1.0f); + _shadow_color.set(1.0f, 1.0f, 1.0f, 1.0f); + _shadow_offset.set(0.0f, 0.0f); + _draw_order = 1; + _tab_width = text_tab_width; + _glyph_scale = 1.0f; + _glyph_shift = 0.0f; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::Copy Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +TextProperties:: +TextProperties(const TextProperties ©) { + (*this) = copy; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::Copy Assignment Operator +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void TextProperties:: +operator = (const TextProperties ©) { + _specified = copy._specified; + + _font = copy._font; + _small_caps = copy._small_caps; + _small_caps_scale = copy._small_caps_scale; + _slant = copy._slant; + _align = copy._align; + _indent_width = copy._indent_width; + _wordwrap_width = copy._wordwrap_width; + _preserve_trailing_whitespace = copy._preserve_trailing_whitespace; + _text_color = copy._text_color; + _shadow_color = copy._shadow_color; + _shadow_offset = copy._shadow_offset; + _bin = copy._bin; + _draw_order = copy._draw_order; + _tab_width = copy._tab_width; + _glyph_scale = copy._glyph_scale; + _glyph_shift = copy._glyph_shift; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::clear +// Access: Published +// Description: Unsets all properties that have been specified so +// far, and resets the TextProperties structure to its +// initial empty state. +//////////////////////////////////////////////////////////////////// +void TextProperties:: +clear() { + (*this) = TextProperties(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::add_properties +// Access: Published +// Description: Sets any properties that are explicitly specified in +// other on this object. Leaves other properties +// unchanged. +//////////////////////////////////////////////////////////////////// +void TextProperties:: +add_properties(const TextProperties &other) { + if (other.has_font()) { + set_font(other.get_font()); + } + if (other.has_small_caps()) { + set_small_caps(other.get_small_caps()); + set_small_caps_scale(other.get_small_caps_scale()); + } + if (other.has_slant()) { + set_slant(other.get_slant()); + } + if (other.has_align()) { + set_align(other.get_align()); + } + if (other.has_indent()) { + set_indent(other.get_indent()); + } + if (other.has_wordwrap()) { + set_wordwrap(other.get_wordwrap()); + } + if (other.has_text_color()) { + set_text_color(other.get_text_color()); + } + if (other.has_shadow_color()) { + set_shadow_color(other.get_shadow_color()); + } + if (other.has_shadow()) { + set_shadow(other.get_shadow()); + } + if (other.has_bin()) { + set_bin(other.get_bin()); + } + if (other.has_draw_order()) { + set_draw_order(other.get_draw_order()); + } + if (other.has_tab_width()) { + set_tab_width(other.get_tab_width()); + } + + + // The glyph scale and shift are a special case: rather than + // replacing the previous value, they modify it. + if (other.has_glyph_shift()) { + set_glyph_shift(other.get_glyph_shift() * get_glyph_scale() + get_glyph_shift()); + } + if (other.has_glyph_scale()) { + set_glyph_scale(other.get_glyph_scale() * get_glyph_scale()); + } +} + + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::write +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void TextProperties:: +write(ostream &out, int indent_level) const { + if (!is_any_specified()) { + indent(out, indent_level) + << "default properties\n"; + } + if (has_font()) { + if (get_font() != (TextFont *)NULL) { + indent(out, indent_level) + << "with font " << _font->get_name() << "\n"; + } else { + indent(out, indent_level) + << "with NULL font\n"; + } + } + if (has_small_caps()) { + indent(out, indent_level) + << "small caps = " << get_small_caps() << "\n"; + } + if (has_small_caps_scale()) { + indent(out, indent_level) + << "small caps scale = " << get_small_caps_scale() << "\n"; + } + if (has_slant()) { + indent(out, indent_level) + << "slant = " << get_slant() << "\n"; + } + + if (has_align()) { + indent(out, indent_level) + << "alignment is "; + switch (get_align()) { + case A_left: + out << "A_left\n"; + break; + + case A_right: + out << "A_right\n"; + break; + + case A_center: + out << "A_center\n"; + break; + } + } + + if (has_indent()) { + indent(out, indent_level) + << "indent at " << get_indent() << " units.\n"; + } + + if (has_wordwrap()) { + indent(out, indent_level) + << "word-wrapping at " << get_wordwrap() << " units.\n"; + } + + if (has_text_color()) { + indent(out, indent_level) + << "text color is " << get_text_color() << "\n"; + } + + if (has_shadow()) { + indent(out, indent_level) + << "shadow at " << get_shadow() << "\n"; + } + if (has_shadow_color()) { + indent(out, indent_level) + << "shadow color is " << get_shadow_color() << "\n"; + } + + if (has_bin()) { + indent(out, indent_level) + << "bin is " << get_bin() << "\n"; + } + if (has_draw_order()) { + indent(out, indent_level) + << "draw order is " << get_draw_order() << "\n"; + } + + if (has_tab_width()) { + indent(out, indent_level) + << "tab width is " << get_tab_width() << "\n"; + } + + if (has_glyph_scale()) { + indent(out, indent_level) + << "glyph scale is " << get_glyph_scale() << "\n"; + } + if (has_glyph_shift()) { + indent(out, indent_level) + << "glyph shift is " << get_glyph_shift() << "\n"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: TextProperties::load_default_font +// Access: Private, Static +// Description: This function is called once (or never), the first +// time someone attempts to render a TextNode using the +// default font. It should attempt to load the default +// font, using the compiled-in version if it is +// available, or whatever system file may be named in +// Configrc. +//////////////////////////////////////////////////////////////////// +void TextProperties:: +load_default_font() { + _loaded_default_font = true; + + if (!text_default_font.empty()) { + // First, attempt to load the user-specified filename. + _default_font = FontPool::load_font(text_default_font); + if (_default_font->is_valid()) { + return; + } + } + + // Then, attempt to load the compiled-in font, if we have one. +#ifdef COMPILE_IN_DEFAULT_FONT +#ifdef HAVE_FREETYPE + // Loading the compiled-in FreeType font is relatively easy. + _default_font = new DynamicTextFont((const char *)default_font_data, + default_font_size, 0); + +#else + // The compiled-in Bam font requires creating a BamFile object to + // decode it. + string data((const char *)default_font_data, default_font_size); + +#ifdef HAVE_ZLIB + // The font data is stored compressed; decompress it on-the-fly. + istringstream inz(data); + IDecompressStream in(&inz, false); + +#else + // The font data is stored uncompressed, so just load it. + istringstream in(data); +#endif // HAVE_ZLIB + + BamFile bam_file; + if (bam_file.open_read(in, "default font stream")) { + PT(PandaNode) node = bam_file.read_node(); + if (node != (PandaNode *)NULL) { + _default_font = new StaticTextFont(node); + } + } + +#endif // HAVE_FREETYPE +#endif // COMPILE_IN_DEFAULT_FONT +} diff --git a/panda/src/text/textProperties.h b/panda/src/text/textProperties.h new file mode 100644 index 0000000000..2c7a5f45ba --- /dev/null +++ b/panda/src/text/textProperties.h @@ -0,0 +1,213 @@ +// Filename: textProperties.h +// Created by: drose (06Apr04) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#ifndef TEXTPROPERTIES_H +#define TEXTPROPERTIES_H + +#include "pandabase.h" + +#include "config_text.h" +#include "luse.h" +#include "textFont.h" +#include "pointerTo.h" + +//////////////////////////////////////////////////////////////////// +// Class : TextProperties +// Description : This defines the set of visual properties that may be +// assigned to the individual characters of the text. +// (Properties which affect the overall block of text +// can only be specified on the TextNode directly). +// +// Typically, there is just one set of properties on a +// given block of text, which is set directly on the +// TextNode (TextNode inherits from TextProperties). +// That makes all of the text within a particular block +// have the same appearance. +// +// This separate class exists in order to implement +// multiple different kinds of text appearing within one +// block. The text string itself may reference a +// TextProperties structure by name using the \1 and \2 +// tokens embedded within the string; each nested +// TextProperties structure modifies the appearance of +// subsequent text within the block. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA TextProperties { +PUBLISHED: + enum Alignment { + A_left, + A_right, + A_center, + }; + + TextProperties(); + TextProperties(const TextProperties ©); + void operator = (const TextProperties ©); + + void clear(); + INLINE bool is_any_specified() const; + + INLINE static void set_default_font(TextFont *); + INLINE static TextFont *get_default_font(); + + INLINE void set_font(TextFont *font); + INLINE void clear_font(); + INLINE bool has_font() const; + INLINE TextFont *get_font() const; + + INLINE void set_small_caps(bool small_caps); + INLINE void clear_small_caps(); + INLINE bool has_small_caps() const; + INLINE bool get_small_caps() const; + + INLINE void set_small_caps_scale(float small_caps_scale); + INLINE void clear_small_caps_scale(); + INLINE bool has_small_caps_scale() const; + INLINE float get_small_caps_scale() const; + + INLINE void set_slant(float slant); + INLINE void clear_slant(); + INLINE bool has_slant() const; + INLINE float get_slant() const; + + INLINE void set_align(Alignment align_type); + INLINE void clear_align(); + INLINE bool has_align() const; + INLINE Alignment get_align() const; + + INLINE void set_indent(float indent); + INLINE void clear_indent(); + INLINE bool has_indent() const; + INLINE float get_indent() const; + + INLINE void set_wordwrap(float wordwrap); + INLINE void clear_wordwrap(); + INLINE bool has_wordwrap() const; + INLINE float get_wordwrap() const; + + INLINE void set_preserve_trailing_whitespace(bool preserve_trailing_whitespace); + INLINE void clear_preserve_trailing_whitespace(); + INLINE bool has_preserve_trailing_whitespace() const; + INLINE bool get_preserve_trailing_whitespace() const; + + INLINE void set_text_color(float r, float g, float b, float a); + INLINE void set_text_color(const Colorf &text_color); + INLINE void clear_text_color(); + INLINE bool has_text_color() const; + INLINE Colorf get_text_color() const; + + INLINE void set_shadow_color(float r, float g, float b, float a); + INLINE void set_shadow_color(const Colorf &shadow_color); + INLINE void clear_shadow_color(); + INLINE bool has_shadow_color() const; + INLINE Colorf get_shadow_color() const; + + INLINE void set_shadow(float xoffset, float yoffset); + INLINE void set_shadow(const LVecBase2f &shadow_offset); + INLINE void clear_shadow(); + INLINE bool has_shadow() const; + INLINE LVector2f get_shadow() const; + + INLINE void set_bin(const string &bin); + INLINE void clear_bin(); + INLINE bool has_bin() const; + INLINE const string &get_bin() const; + + INLINE int set_draw_order(int draw_order); + INLINE void clear_draw_order(); + INLINE bool has_draw_order() const; + INLINE int get_draw_order() const; + + INLINE void set_tab_width(float tab_width); + INLINE void clear_tab_width(); + INLINE bool has_tab_width() const; + INLINE float get_tab_width() const; + + INLINE void set_glyph_scale(float glyph_scale); + INLINE void clear_glyph_scale(); + INLINE bool has_glyph_scale() const; + INLINE float get_glyph_scale() const; + + INLINE void set_glyph_shift(float glyph_shift); + INLINE void clear_glyph_shift(); + INLINE bool has_glyph_shift() const; + INLINE float get_glyph_shift() const; + + void add_properties(const TextProperties &other); + + void write(ostream &out, int indent_level = 0) const; + +private: + static void load_default_font(); + + enum Flags { + F_has_font = 0x0001, + F_has_small_caps = 0x0002, + F_has_small_caps_scale = 0x0004, + F_has_slant = 0x0008, + F_has_align = 0x0010, + F_has_indent = 0x0020, + F_has_wordwrap = 0x0040, + F_has_preserve_trailing_whitespace = 0x0080, + F_has_text_color = 0x0100, + F_has_shadow_color = 0x0200, + F_has_shadow = 0x0400, + F_has_bin = 0x0800, + F_has_draw_order = 0x1000, + F_has_tab_width = 0x2000, + F_has_glyph_scale = 0x4000, + F_has_glyph_shift = 0x8000, + }; + + int _specified; + + PT(TextFont) _font; + bool _small_caps; + float _small_caps_scale; + float _slant; + Alignment _align; + float _indent_width; + float _wordwrap_width; + bool _preserve_trailing_whitespace; + Colorf _text_color; + Colorf _shadow_color; + LVector2f _shadow_offset; + string _bin; + int _draw_order; + float _tab_width; + float _glyph_scale; + float _glyph_shift; + + static PT(TextFont) _default_font; + static bool _loaded_default_font; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + register_type(_type_handle, "TextProperties"); + } + +private: + static TypeHandle _type_handle; +}; + +#include "textProperties.I" + +#endif diff --git a/panda/src/text/textPropertiesManager.I b/panda/src/text/textPropertiesManager.I new file mode 100644 index 0000000000..17e2b54d25 --- /dev/null +++ b/panda/src/text/textPropertiesManager.I @@ -0,0 +1,18 @@ +// Filename: textPropertiesManager.I +// Created by: drose (07Apr04) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + diff --git a/panda/src/text/textPropertiesManager.cxx b/panda/src/text/textPropertiesManager.cxx new file mode 100644 index 0000000000..234e3d1e81 --- /dev/null +++ b/panda/src/text/textPropertiesManager.cxx @@ -0,0 +1,148 @@ +// Filename: textPropertiesManager.cxx +// Created by: drose (07Apr04) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#include "textPropertiesManager.h" +#include "indent.h" + +TextPropertiesManager *TextPropertiesManager::_global_ptr = (TextPropertiesManager *)NULL; + +//////////////////////////////////////////////////////////////////// +// Function: TextPropertiesManager::Constructor +// Access: Protected +// Description: The constructor is not intended to be called +// directly; there is only one TextPropertiesManager and +// it constructs itself. This could have been a private +// constructor, but gcc issues a spurious warning if the +// constructor is private and the class has no friends. +//////////////////////////////////////////////////////////////////// +TextPropertiesManager:: +TextPropertiesManager() { +} + +//////////////////////////////////////////////////////////////////// +// Function: TextPropertiesManager::Destructor +// Access: Protected +// Description: Don't call the destructor. +//////////////////////////////////////////////////////////////////// +TextPropertiesManager:: +~TextPropertiesManager() { +} + +//////////////////////////////////////////////////////////////////// +// Function: TextPropertiesManager::set_properties +// Access: Published +// Description: Defines the TextProperties associated with the +// indicated name. When the name is subsequently +// encountered in text embedded between \1 characters in +// a TextNode string, the following text will be +// rendered with these properties. +// +// If there was already a TextProperties structure +// associated with this name, it is quietly replaced +// with the new definition. +//////////////////////////////////////////////////////////////////// +void TextPropertiesManager:: +set_properties(const string &name, const TextProperties &properties) { + _properties[name] = properties; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextPropertiesManager::get_properties +// Access: Published +// Description: Returns the TextProperties associated with the +// indicated name. If there was not previously a +// TextProperties associated with this name, a warning +// is printed and then a default TextProperties +// structure is associated with the name, and returned. +// +// Call has_properties() instead to check whether a +// particular name has been defined. +//////////////////////////////////////////////////////////////////// +TextProperties TextPropertiesManager:: +get_properties(const string &name) { + Properties::const_iterator pi; + pi = _properties.find(name); + if (pi != _properties.end()) { + return (*pi).second; + } + + text_cat.warning() + << "Creating default TextProperties for name '" << name << "'\n"; + + TextProperties default_properties; + _properties[name] = default_properties; + return default_properties; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextPropertiesManager::has_properties +// Access: Published +// Description: Returns true if a TextProperties structure has been +// associated with the indicated name, false otherwise. +// Normally this means set_properties() has been called +// with this name, but because get_properties() will +// implicitly create a default TextProperties structure, +// it may also mean simply that get_properties() has +// been called with the indicated name. +//////////////////////////////////////////////////////////////////// +bool TextPropertiesManager:: +has_properties(const string &name) const { + Properties::const_iterator pi; + pi = _properties.find(name); + return (pi != _properties.end()); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextPropertiesManager::clear_properties +// Access: Published +// Description: Removes the named TextProperties structure from the +// manager. +//////////////////////////////////////////////////////////////////// +void TextPropertiesManager:: +clear_properties(const string &name) { + _properties.erase(name); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextPropertiesManager::write +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void TextPropertiesManager:: +write(ostream &out, int indent_level) const { + Properties::const_iterator pi; + for (pi = _properties.begin(); pi != _properties.end(); ++pi) { + indent(out, indent_level) + << "TextProperties " << (*pi).first << ":\n"; + (*pi).second.write(out, indent_level + 2); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: TextPropertiesManager::get_global_ptr +// Access: Published, Static +// Description: Returns the pointer to the global TextPropertiesManager +// object. +//////////////////////////////////////////////////////////////////// +TextPropertiesManager *TextPropertiesManager:: +get_global_ptr() { + if (_global_ptr == (TextPropertiesManager *)NULL) { + _global_ptr = new TextPropertiesManager; + } + return _global_ptr; +} diff --git a/panda/src/text/textPropertiesManager.h b/panda/src/text/textPropertiesManager.h new file mode 100644 index 0000000000..2055d23301 --- /dev/null +++ b/panda/src/text/textPropertiesManager.h @@ -0,0 +1,72 @@ +// Filename: textPropertiesManager.h +// Created by: drose (07Apr04) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#ifndef TEXTPROPERTIESMANAGER_H +#define TEXTPROPERTIESMANAGER_H + +#include "pandabase.h" + +#include "config_text.h" +#include "textProperties.h" + +//////////////////////////////////////////////////////////////////// +// Class : TextPropertiesManager +// Description : This defines all of the TextProperties structures +// that might be referenced by name from an embedded +// text string. +// +// A text string, as rendered by a TextNode, can contain +// embedded references to one of the TextProperties +// defined here, by enclosing the name between \1 (ASCII +// 0x01) characters; this causes a "push" to the named +// state. All text following the closing \1 character +// will then be rendered in the new state. The next \2 +// (ASCII 0x02) character will then restore the previous +// state for subsequent text. +// +// For instance, "x\1up\1n\2 + y" indicates that the +// character "x" will be rendered in the normal state, +// the character "n" will be rendered in the "up" state, +// and then " + y" will be rendered in the normal state +// again. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA TextPropertiesManager { +protected: + TextPropertiesManager(); + ~TextPropertiesManager(); + +PUBLISHED: + void set_properties(const string &name, const TextProperties &properties); + TextProperties get_properties(const string &name); + bool has_properties(const string &name) const; + void clear_properties(const string &name); + + void write(ostream &out, int indent_level = 0) const; + + static TextPropertiesManager *get_global_ptr(); + +private: + typedef pmap Properties; + Properties _properties; + + static TextPropertiesManager *_global_ptr; +}; + +#include "textPropertiesManager.I" + +#endif diff --git a/panda/src/text/text_composite1.cxx b/panda/src/text/text_composite1.cxx index fa32780519..a7329d5eb7 100644 --- a/panda/src/text/text_composite1.cxx +++ b/panda/src/text/text_composite1.cxx @@ -6,5 +6,3 @@ #include "fontPool.cxx" #include "geomTextGlyph.cxx" #include "staticTextFont.cxx" -#include "textFont.cxx" -#include "textGlyph.cxx" diff --git a/panda/src/text/text_composite2.cxx b/panda/src/text/text_composite2.cxx new file mode 100644 index 0000000000..208f6e33f1 --- /dev/null +++ b/panda/src/text/text_composite2.cxx @@ -0,0 +1,6 @@ +#include "textAssembler.cxx" +#include "textFont.cxx" +#include "textGlyph.cxx" +#include "textNode.cxx" +#include "textProperties.cxx" +#include "textPropertiesManager.cxx"