1 // A text on the Canvas
3 // Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Library General Public
7 // License as published by the Free Software Foundation; either
8 // version 2 of the License, or (at your option) any later version.
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Library General Public License for more details.
15 // You should have received a copy of the GNU Library General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 #include "CanvasText.hxx"
20 #include <simgear/canvas/Canvas.hxx>
21 #include <simgear/canvas/CanvasSystemAdapter.hxx>
22 #include <simgear/scene/util/parse_color.hxx>
23 #include <osg/Version>
24 #include <osgDB/Registry>
25 #include <osgText/Text>
35 TextOSG(canvas::Text* text);
37 void setFontResolution(int res);
38 void setCharacterAspect(float aspect);
39 void setLineHeight(float factor);
40 void setFill(const std::string& fill);
41 void setStroke(const std::string& color);
42 void setBackgroundColor(const std::string& fill);
44 float lineHeight() const;
46 /// Get the number of lines
47 size_t lineCount() const;
50 TextLine lineAt(size_t i) const;
52 /// Get nearest line to given y-coordinate
53 TextLine nearestLine(float pos_y) const;
56 SGVec2i sizeForWidth(int w) const;
58 virtual osg::BoundingBox
59 #if OSG_VERSION_LESS_THAN(3,3,2)
68 friend class TextLine;
70 canvas::Text *_text_element;
72 virtual void computePositions(unsigned int contextID) const;
79 TextLine(size_t line, Text::TextOSG const* text);
81 /// Number of characters on this line
85 osg::Vec2 cursorPos(size_t i) const;
86 osg::Vec2 nearestCursor(float x) const;
89 typedef Text::TextOSG::GlyphQuads GlyphQuads;
91 Text::TextOSG const *_text;
92 GlyphQuads const *_quads;
99 //----------------------------------------------------------------------------
100 TextLine::TextLine():
110 //----------------------------------------------------------------------------
111 TextLine::TextLine(size_t line, Text::TextOSG const* text):
118 if( !text || text->_textureGlyphQuadMap.empty() || !_text->lineCount() )
121 _quads = &text->_textureGlyphQuadMap.begin()->second;
123 GlyphQuads::LineNumbers const& line_numbers = _quads->_lineNumbers;
124 GlyphQuads::LineNumbers::const_iterator begin_it =
125 std::lower_bound(line_numbers.begin(), line_numbers.end(), _line);
127 if( begin_it == line_numbers.end() || *begin_it != _line )
128 // empty line or past last line
131 _begin = begin_it - line_numbers.begin();
132 _end = std::upper_bound(begin_it, line_numbers.end(), _line)
133 - line_numbers.begin();
136 //----------------------------------------------------------------------------
137 size_t TextLine::size() const
139 return _end - _begin;
142 //----------------------------------------------------------------------------
143 bool TextLine::empty() const
145 return _end == _begin;
148 //----------------------------------------------------------------------------
149 osg::Vec2 TextLine::cursorPos(size_t i) const
152 return osg::Vec2(0, 0);
155 // Position after last character if out of range (TODO better exception?)
158 osg::Vec2 pos(0, _text->_offset.y() + _line * _text->lineHeight());
162 #if OSG_VERSION_LESS_THAN(3,3,5)
163 GlyphQuads::Coords2 const& coords = _quads->_coords;
165 GlyphQuads::Coords2 refCoords = _quads->_coords;
166 GlyphQuads::Coords2::element_type &coords = *refCoords.get();
168 size_t global_i = _begin + i;
170 if( global_i == _begin )
171 // before first character of line
172 pos.x() = coords[_begin * 4].x();
173 else if( global_i == _end )
174 // After Last character of line
175 pos.x() = coords[(_end - 1) * 4 + 2].x();
178 float prev_l = coords[(global_i - 1) * 4].x(),
179 prev_r = coords[(global_i - 1) * 4 + 2].x(),
180 cur_l = coords[global_i * 4].x();
182 if( prev_l == prev_r )
183 // If previous character width is zero set to begin of next character
184 // (Happens eg. with spaces)
187 // position at center between characters
188 pos.x() = 0.5 * (prev_r + cur_l);
194 //----------------------------------------------------------------------------
195 osg::Vec2 TextLine::nearestCursor(float x) const
200 GlyphQuads::Glyphs const& glyphs = _quads->_glyphs;
201 #if OSG_VERSION_LESS_THAN(3,3,5)
202 GlyphQuads::Coords2 const& coords = _quads->_coords;
204 GlyphQuads::Coords2 refCoords = _quads->_coords;
205 GlyphQuads::Coords2::element_type &coords = *refCoords.get();
208 float const HIT_FRACTION = 0.6;
209 float const character_width = _text->getCharacterHeight()
210 * _text->getCharacterAspectRatio();
215 // Get threshold for mouse x position for setting cursor before or after
217 float threshold = coords[i * 4].x()
218 + HIT_FRACTION * glyphs[i]->getHorizontalAdvance()
225 return cursorPos(i - _begin);
228 //----------------------------------------------------------------------------
229 Text::TextOSG::TextOSG(canvas::Text* text):
232 setBackdropImplementation(NO_DEPTH_BUFFER);
235 //----------------------------------------------------------------------------
236 void Text::TextOSG::setFontResolution(int res)
238 TextBase::setFontResolution(res, res);
241 //----------------------------------------------------------------------------
242 void Text::TextOSG::setCharacterAspect(float aspect)
244 setCharacterSize(getCharacterHeight(), aspect);
247 //----------------------------------------------------------------------------
248 void Text::TextOSG::setLineHeight(float factor)
250 setLineSpacing(factor - 1);
253 //----------------------------------------------------------------------------
254 void Text::TextOSG::setFill(const std::string& fill)
256 // if( fill == "none" )
260 if( parseColor(fill, color) )
264 //----------------------------------------------------------------------------
265 void Text::TextOSG::setStroke(const std::string& stroke)
268 if( stroke == "none" || !parseColor(stroke, color) )
269 setBackdropType(NONE);
272 setBackdropType(OUTLINE);
273 setBackdropColor(color);
277 //----------------------------------------------------------------------------
278 void Text::TextOSG::setBackgroundColor(const std::string& fill)
281 if( parseColor(fill, color) )
282 setBoundingBoxColor( color );
285 //----------------------------------------------------------------------------
286 float Text::TextOSG::lineHeight() const
288 return (1 + _lineSpacing) * _characterHeight;
291 //----------------------------------------------------------------------------
292 size_t Text::TextOSG::lineCount() const
297 //----------------------------------------------------------------------------
298 TextLine Text::TextOSG::lineAt(size_t i) const
300 return TextLine(i, this);
303 //----------------------------------------------------------------------------
304 TextLine Text::TextOSG::nearestLine(float pos_y) const
306 osgText::Font const* font = getActiveFont();
307 if( !font || lineCount() <= 0 )
308 return TextLine(0, this);
310 float asc = .9f, desc = -.2f;
311 font->getVerticalSize(asc, desc);
313 float first_line_y = _offset.y()
314 - (1 + _lineSpacing / 2 + desc) * _characterHeight;
316 size_t line_num = std::min<size_t>(
317 std::max<size_t>(0, (pos_y - first_line_y) / lineHeight()),
321 return TextLine(line_num, this);
324 //----------------------------------------------------------------------------
325 // simplified version of osgText::Text::computeGlyphRepresentation() to
326 // just calculate the size for a given weight. Glpyh calculations/creating
327 // is not necessary for this...
328 SGVec2i Text::TextOSG::sizeForWidth(int w) const
331 return SGVec2i(0, 0);
333 osgText::Font* activefont = const_cast<osgText::Font*>(getActiveFont());
335 return SGVec2i(-1, -1);
337 float max_width_safe = _maximumWidth;
338 const_cast<TextOSG*>(this)->_maximumWidth = w;
342 osg::Vec2 startOfLine_coords(0.0f,0.0f);
343 osg::Vec2 cursor(startOfLine_coords);
344 osg::Vec2 local(0.0f,0.0f);
346 unsigned int previous_charcode = 0;
347 unsigned int line_length = 0;
348 bool horizontal = _layout != VERTICAL;
351 float hr = _characterHeight;
352 float wr = hr / getCharacterAspectRatio();
354 // osg should really care more about const :-/
355 osgText::String& text = const_cast<osgText::String&>(_text);
356 typedef osgText::String::iterator TextIterator;
358 for( TextIterator itr = text.begin(); itr != text.end(); )
360 // record the start of the current line
361 TextIterator startOfLine_itr = itr;
363 // find the end of the current line.
364 osg::Vec2 endOfLine_coords(cursor);
365 TextIterator endOfLine_itr =
366 const_cast<TextOSG*>(this)->computeLastCharacterOnLine(
367 endOfLine_coords, itr, text.end()
370 line_length = endOfLine_itr - startOfLine_itr;
372 // Set line position to correct alignment.
379 // nothing to be done for these
383 //case LEFT_BASE_LINE:
384 //case LEFT_BOTTOM_BASE_LINE:
389 case CENTER_BASE_LINE:
390 case CENTER_BOTTOM_BASE_LINE:
391 cursor.x() = (cursor.x() - endOfLine_coords.x()) * 0.5f;
396 case RIGHT_BASE_LINE:
397 case RIGHT_BOTTOM_BASE_LINE:
398 cursor.x() = cursor.x() - endOfLine_coords.x();
413 case LEFT_BOTTOM_BASE_LINE:
414 cursor.x() = 2 * cursor.x() - endOfLine_coords.x();
419 case CENTER_BASE_LINE:
420 case CENTER_BOTTOM_BASE_LINE:
421 cursor.x() = cursor.x()
422 + (cursor.x() - endOfLine_coords.x()) * 0.5f;
424 // nothing to be done for these
428 //case RIGHT_BASE_LINE:
429 //case RIGHT_BOTTOM_BASE_LINE:
440 // TODO: current behaviour top baselines lined up in both cases - need to implement
441 // top of characters alignment - Question is this necessary?
442 // ... otherwise, nothing to be done for these 6 cases
447 //case LEFT_BASE_LINE:
448 //case CENTER_BASE_LINE:
449 //case RIGHT_BASE_LINE:
454 cursor.y() = cursor.y()
455 + (cursor.y() - endOfLine_coords.y()) * 0.5f;
457 case LEFT_BOTTOM_BASE_LINE:
458 case CENTER_BOTTOM_BASE_LINE:
459 case RIGHT_BOTTOM_BASE_LINE:
460 cursor.y() = cursor.y() - (line_length * _characterHeight);
465 cursor.y() = 2 * cursor.y() - endOfLine_coords.y();
474 if( itr != endOfLine_itr )
477 for(;itr != endOfLine_itr;++itr)
479 unsigned int charcode = *itr;
481 osgText::Glyph* glyph = activefont->getGlyph(_fontSize, charcode);
484 float width = (float) (glyph->getWidth()) * wr;
485 float height = (float) (glyph->getHeight()) * hr;
487 if( _layout == RIGHT_TO_LEFT )
489 cursor.x() -= glyph->getHorizontalAdvance() * wr;
492 // adjust cursor position w.r.t any kerning.
493 if( kerning && previous_charcode )
499 osg::Vec2 delta( activefont->getKerning( previous_charcode,
502 cursor.x() += delta.x() * wr;
503 cursor.y() += delta.y() * hr;
508 osg::Vec2 delta( activefont->getKerning( charcode,
511 cursor.x() -= delta.x() * wr;
512 cursor.y() -= delta.y() * hr;
516 break; // no kerning when vertical.
521 osg::Vec2 bearing( horizontal ? glyph->getHorizontalBearing()
522 : glyph->getVerticalBearing() );
523 local.x() += bearing.x() * wr;
524 local.y() += bearing.y() * hr;
526 // set up the coords of the quad
527 osg::Vec2 upLeft = local + osg::Vec2(0.f, height);
528 osg::Vec2 lowLeft = local;
529 osg::Vec2 lowRight = local + osg::Vec2(width, 0.f);
530 osg::Vec2 upRight = local + osg::Vec2(width, height);
532 // move the cursor onto the next character.
533 // also expand bounding box
537 cursor.x() += glyph->getHorizontalAdvance() * wr;
538 bb.expandBy(lowLeft.x(), lowLeft.y());
539 bb.expandBy(upRight.x(), upRight.y());
542 cursor.y() -= glyph->getVerticalAdvance() * hr;
543 bb.expandBy(upLeft.x(), upLeft.y());
544 bb.expandBy(lowRight.x(), lowRight.y());
547 bb.expandBy(lowRight.x(), lowRight.y());
548 bb.expandBy(upLeft.x(), upLeft.y());
551 previous_charcode = charcode;
555 // skip over spaces and return.
556 while( itr != text.end() && *itr == ' ' )
558 if( itr != text.end() && *itr == '\n' )
571 startOfLine_coords.y() -= _characterHeight * (1.0 + _lineSpacing);
572 cursor = startOfLine_coords;
573 previous_charcode = 0;
578 startOfLine_coords.y() -= _characterHeight * (1.0 + _lineSpacing);
579 cursor = startOfLine_coords;
580 previous_charcode = 0;
585 startOfLine_coords.x() += _characterHeight * (1.0 + _lineSpacing)
586 / getCharacterAspectRatio();
587 cursor = startOfLine_coords;
588 previous_charcode = 0;
594 const_cast<TextOSG*>(this)->_maximumWidth = max_width_safe;
599 //----------------------------------------------------------------------------
601 #if OSG_VERSION_LESS_THAN(3,3,2)
602 Text::TextOSG::computeBound()
604 Text::TextOSG::computeBoundingBox()
608 osg::BoundingBox bb =
609 #if OSG_VERSION_LESS_THAN(3,3,2)
610 osgText::Text::computeBound();
612 osgText::Text::computeBoundingBox();
615 #if OSG_VERSION_LESS_THAN(3,1,0)
618 // TODO bounding box still doesn't seem always right (eg. with center
619 // horizontal alignment not completely accurate)
620 bb._min.y() += _offset.y();
621 bb._max.y() += _offset.y();
628 //----------------------------------------------------------------------------
629 void Text::TextOSG::computePositions(unsigned int contextID) const
631 if( _textureGlyphQuadMap.empty() || _layout == VERTICAL )
632 return osgText::Text::computePositions(contextID);
634 // TODO check when it can be larger
635 assert( _textureGlyphQuadMap.size() == 1 );
637 const GlyphQuads& quads = _textureGlyphQuadMap.begin()->second;
638 const GlyphQuads::Glyphs& glyphs = quads._glyphs;
639 #if OSG_VERSION_LESS_THAN(3,3,5)
640 GlyphQuads::Coords2 const& coords = quads._coords;
642 GlyphQuads::Coords2 refCoords = quads._coords;
643 GlyphQuads::Coords2::element_type &coords = *refCoords.get();
646 const GlyphQuads::LineNumbers& line_numbers = quads._lineNumbers;
648 float wr = _characterHeight / getCharacterAspectRatio();
650 size_t cur_line = static_cast<size_t>(-1);
651 for(size_t i = 0; i < glyphs.size(); ++i)
653 // Check horizontal offsets
655 bool first_char = cur_line != line_numbers[i];
656 cur_line = line_numbers[i];
658 bool last_char = (i + 1 == glyphs.size())
659 || (cur_line != line_numbers[i + 1]);
661 if( first_char || last_char )
663 // From osg/src/osgText/Text.cpp:
665 // osg::Vec2 upLeft = local+osg::Vec2(0.0f-fHorizQuadMargin, ...);
666 // osg::Vec2 lowLeft = local+osg::Vec2(0.0f-fHorizQuadMargin, ...);
667 // osg::Vec2 lowRight = local+osg::Vec2(width+fHorizQuadMargin, ...);
668 // osg::Vec2 upRight = local+osg::Vec2(width+fHorizQuadMargin, ...);
670 float left = coords[i * 4].x(),
671 right = coords[i * 4 + 2].x(),
672 width = glyphs[i]->getWidth() * wr;
674 // (local + width + fHoriz) - (local - fHoriz) = width + 2*fHoriz | -width
675 float margin = 0.5f * (right - left - width),
676 cursor_x = left + margin
677 - glyphs[i]->getHorizontalBearing().x() * wr;
681 if( cur_line == 0 || cursor_x < _textBB._min.x() )
682 _textBB._min.x() = cursor_x;
687 float cursor_w = cursor_x + glyphs[i]->getHorizontalAdvance() * wr;
689 if( cur_line == 0 || cursor_w > _textBB._max.x() )
690 _textBB._max.x() = cursor_w;
695 return osgText::Text::computePositions(contextID);
698 //----------------------------------------------------------------------------
699 const std::string Text::TYPE_NAME = "text";
701 //----------------------------------------------------------------------------
702 void Text::staticInit()
707 osg::ref_ptr<TextOSG> Text::*text = &Text::_text;
709 addStyle("fill", "color", &TextOSG::setFill, text);
710 addStyle("background", "color", &TextOSG::setBackgroundColor, text);
711 addStyle("stroke", "color", &TextOSG::setStroke, text);
712 addStyle("character-size",
715 void (TextOSG::*)(float)
716 > (&TextOSG::setCharacterSize),
718 addStyle("character-aspect-ratio",
720 &TextOSG::setCharacterAspect, text);
721 addStyle("line-height", "numeric", &TextOSG::setLineHeight, text);
722 addStyle("font-resolution", "numeric", &TextOSG::setFontResolution, text);
723 addStyle("padding", "numeric", &TextOSG::setBoundingBoxMargin, text);
726 // FILLEDBOUNDINGBOX = 4
728 addStyle<int>("draw-mode", "", &TextOSG::setDrawMode, text);
729 addStyle("max-width", "numeric", &TextOSG::setMaximumWidth, text);
730 addStyle("font", "", &Text::setFont);
731 addStyle("alignment", "", &Text::setAlignment);
732 addStyle("text", "", &Text::setText, false);
734 osgDB::Registry* reg = osgDB::Registry::instance();
735 if( !reg->getReaderWriterForExtension("ttf") )
736 SG_LOG(SG_GL, SG_ALERT, "canvas::Text: Missing 'ttf' font reader");
739 //----------------------------------------------------------------------------
740 Text::Text( const CanvasWeakPtr& canvas,
741 const SGPropertyNode_ptr& node,
742 const Style& parent_style,
743 ElementWeakPtr parent ):
744 Element(canvas, node, parent_style, parent),
745 _text( new Text::TextOSG(this) )
750 _text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS);
751 _text->setAxisAlignment(osgText::Text::USER_DEFINED_ROTATION);
752 _text->setRotation(osg::Quat(osg::PI, osg::X_AXIS));
757 //----------------------------------------------------------------------------
763 //----------------------------------------------------------------------------
764 void Text::setText(const char* text)
766 _text->setText(text, osgText::String::ENCODING_UTF8);
769 //----------------------------------------------------------------------------
770 void Text::setFont(const char* name)
772 _text->setFont( Canvas::getSystemAdapter()->getFont(name) );
775 //----------------------------------------------------------------------------
776 void Text::setAlignment(const char* align)
778 const std::string align_string(align);
780 #define ENUM_MAPPING(enum_val, string_val) \
781 else if( align_string == string_val )\
782 _text->setAlignment( osgText::Text::enum_val );
783 #include "text-alignment.hxx"
787 if( !align_string.empty() )
792 "canvas::Text: unknown alignment '" << align_string << "'"
794 _text->setAlignment(osgText::Text::LEFT_BASE_LINE);
798 //----------------------------------------------------------------------------
800 const char* Text::getAlignment() const
802 switch( _text->getAlignment() )
804 #define ENUM_MAPPING(enum_val, string_val) \
805 case osgText::Text::enum_val:\
807 #include "text-alignment.hxx"
815 //----------------------------------------------------------------------------
816 int Text::heightForWidth(int w) const
818 return _text->sizeForWidth(w).y();
821 //----------------------------------------------------------------------------
822 int Text::maxWidth() const
824 return _text->sizeForWidth(INT_MAX).x();
827 //----------------------------------------------------------------------------
828 size_t Text::lineCount() const
830 return _text->lineCount();
833 //----------------------------------------------------------------------------
834 size_t Text::lineLength(size_t line) const
836 return _text->lineAt(line).size();
839 //----------------------------------------------------------------------------
840 osg::Vec2 Text::getNearestCursor(const osg::Vec2& pos) const
842 return _text->nearestLine(pos.y()).nearestCursor(pos.x());
845 //----------------------------------------------------------------------------
846 osg::Vec2 Text::getCursorPos(size_t line, size_t character) const
848 return _text->lineAt(line).cursorPos(character);
851 //----------------------------------------------------------------------------
852 osg::StateSet* Text::getOrCreateStateSet()
854 if( !_transform.valid() )
857 // Only check for StateSet on Transform, as the text stateset is shared
858 // between all text instances using the same font (texture).
859 return _transform->getOrCreateStateSet();
862 } // namespace canvas
863 } // namespace simgear