1 ///@file
2 /// A text on the Canvas
3 //
4 // Copyright (C) 2012  Thomas Geymayer <tomgey@gmail.com>
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Library General Public
8 // License as published by the Free Software Foundation; either
9 // version 2 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // Library General Public License for more details.
15 //
16 // You should have received a copy of the GNU Library General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
19 
20 #include <simgear_config.h>
21 
22 #include "CanvasText.hxx"
23 #include <simgear/canvas/Canvas.hxx>
24 #include <simgear/canvas/CanvasSystemAdapter.hxx>
25 #include <simgear/scene/util/parse_color.hxx>
26 #include <osg/Version>
27 #include <osgDB/Registry>
28 #include <osgText/Text>
29 
30 namespace simgear
31 {
32 namespace canvas
33 {
34   class Text::TextOSG:
35     public osgText::Text
36   {
37     public:
38       TextOSG(canvas::Text* text);
39 
40       void setFontResolution(int res);
41       void setCharacterAspect(float aspect);
42       void setLineHeight(float factor);
43       void setFill(const std::string& fill);
44       void setStroke(const std::string& color);
45       void setBackgroundColor(const std::string& fill);
46 
47       float lineHeight() const;
48 
49       /// Get the number of lines
50       size_t lineCount() const;
51 
52       /// Get line @a i
53       TextLine lineAt(size_t i) const;
54 
55       /// Get nearest line to given y-coordinate
56 #if OSG_VERSION_LESS_THAN(3,6,5)
57       TextLine nearestLine(float pos_y) const;
58       SGVec2i sizeForWidth(int w) const;
59 #else
60       TextLine nearestLine(float pos_y);
61       SGVec2i sizeForWidth(int w);
62 #endif
63 
64 #if OSG_VERSION_LESS_THAN(3,3,2)
65       osg::BoundingBox computeBound() const override;
66 #else
67       osg::BoundingBox computeBoundingBox() const override;
68 #endif
69 
70     protected:
71       friend class TextLine;
72 
73       canvas::Text *_text_element;
74 
75 #if OSG_VERSION_LESS_THAN(3,5,6)
76      void computePositions(unsigned int contextID) const override;
77 #else
78      void computePositionsImplementation() override;
79 #endif
80 };
81 
82   class TextLine
83   {
84     public:
85       TextLine();
86       TextLine(size_t line, Text::TextOSG const* text);
87 
88       /// Number of characters on this line
89       size_t size() const;
90       bool empty() const;
91 
92       osg::Vec2 cursorPos(size_t i) const;
93       osg::Vec2 nearestCursor(float x) const;
94 
95     protected:
96       typedef Text::TextOSG::GlyphQuads GlyphQuads;
97 
98       Text::TextOSG const *_text;
99       GlyphQuads const    *_quads;
100 
101       size_t _line,
102              _begin,
103              _end;
104   };
105 
106   //----------------------------------------------------------------------------
TextLine()107   TextLine::TextLine():
108     _text(NULL),
109     _quads(NULL),
110     _line(0),
111     _begin(-1),
112     _end(-1)
113   {
114 
115   }
116 
117   //----------------------------------------------------------------------------
TextLine(size_t line,Text::TextOSG const * text)118   TextLine::TextLine(size_t line, Text::TextOSG const* text):
119     _text(text),
120     _quads(NULL),
121     _line(line),
122     _begin(-1),
123     _end(-1)
124   {
125     if( !text || text->_textureGlyphQuadMap.empty() || !_text->lineCount() )
126       return;
127 
128     _quads = &text->_textureGlyphQuadMap.begin()->second;
129 
130 #if OSG_VERSION_LESS_THAN(3,5,6)
131     GlyphQuads::LineNumbers const& line_numbers = _quads->_lineNumbers;
132     GlyphQuads::LineNumbers::const_iterator begin_it =
133       std::lower_bound(line_numbers.begin(), line_numbers.end(), _line);
134 
135     if( begin_it == line_numbers.end() || *begin_it != _line )
136       // empty line or past last line
137       return;
138 
139     _begin = begin_it - line_numbers.begin();
140     _end = std::upper_bound(begin_it, line_numbers.end(), _line)
141          - line_numbers.begin();
142 #else
143   // TODO: Need 3.5.6 version of this
144 #endif
145   }
146 
147   //----------------------------------------------------------------------------
size() const148   size_t TextLine::size() const
149   {
150     return _end - _begin;
151   }
152 
153   //----------------------------------------------------------------------------
empty() const154   bool TextLine::empty() const
155   {
156     return _end == _begin;
157   }
158 
159   //----------------------------------------------------------------------------
cursorPos(size_t i) const160   osg::Vec2 TextLine::cursorPos(size_t i) const
161   {
162     if( !_quads )
163       return osg::Vec2(0, 0);
164 
165     if( i > size() )
166       // Position after last character if out of range (TODO better exception?)
167       i = size();
168 
169     osg::Vec2 pos(0, _text->_offset.y() + _line * _text->lineHeight());
170 
171     if( empty() )
172       return pos;
173 
174 #if OSG_VERSION_GREATER_OR_EQUAL(3,5,6)
175     // TODO: need 3.5.6 version of this.
176 #else
177 #if OSG_VERSION_LESS_THAN(3,3,5)
178       GlyphQuads::Coords2 const& coords = _quads->_coords;
179 #else
180       GlyphQuads::Coords2 refCoords = _quads->_coords;
181       GlyphQuads::Coords2::element_type &coords = *refCoords.get();
182 #endif
183 
184       size_t global_i = _begin + i;
185 
186       if (global_i == _begin)
187           // before first character of line
188           pos.x() = coords[_begin * 4].x();
189       else if (global_i == _end)
190           // After Last character of line
191           pos.x() = coords[(_end - 1) * 4 + 2].x();
192       else
193       {
194           float prev_l = coords[(global_i - 1) * 4].x(),
195               prev_r = coords[(global_i - 1) * 4 + 2].x(),
196               cur_l = coords[global_i * 4].x();
197 
198           if (prev_l == prev_r)
199               // If previous character width is zero set to begin of next character
200               // (Happens eg. with spaces)
201               pos.x() = cur_l;
202           else
203               // position at center between characters
204               pos.x() = 0.5 * (prev_r + cur_l);
205       }
206 #endif
207 
208     return pos;
209   }
210 
211   //----------------------------------------------------------------------------
nearestCursor(float x) const212   osg::Vec2 TextLine::nearestCursor(float x) const
213   {
214     if (empty())
215       return cursorPos(0);
216 
217 #if OSG_VERSION_GREATER_OR_EQUAL(3,5,6)
218   // TODO: need 3.5.7 version of this.
219 	return cursorPos(0);
220 #else
221 #if OSG_VERSION_LESS_THAN(3,3,5)
222     GlyphQuads::Coords2 const& coords = _quads->_coords;
223 #else
224     GlyphQuads::Coords2 refCoords = _quads->_coords;
225     GlyphQuads::Coords2::element_type &coords = *refCoords.get();
226 #endif
227 
228     GlyphQuads::Glyphs const& glyphs = _quads->_glyphs;
229 
230     float const HIT_FRACTION = 0.6;
231     float const character_width = _text->getCharacterHeight()
232                                 * _text->getCharacterAspectRatio();
233 
234     size_t i = _begin;
235     for(; i < _end; ++i)
236     {
237       // Get threshold for mouse x position for setting cursor before or after
238       // current character
239       float threshold = coords[i * 4].x()
240                       + HIT_FRACTION * glyphs[i]->getHorizontalAdvance()
241                                      * character_width;
242 
243       if( x <= threshold )
244         break;
245     }
246 
247     return cursorPos(i - _begin);
248 #endif
249   }
250 
251   //----------------------------------------------------------------------------
TextOSG(canvas::Text * text)252   Text::TextOSG::TextOSG(canvas::Text* text):
253     _text_element(text)
254   {
255     setBackdropImplementation(NO_DEPTH_BUFFER);
256   }
257 
258   //----------------------------------------------------------------------------
setFontResolution(int res)259   void Text::TextOSG::setFontResolution(int res)
260   {
261     TextBase::setFontResolution(res, res);
262   }
263 
264   //----------------------------------------------------------------------------
setCharacterAspect(float aspect)265   void Text::TextOSG::setCharacterAspect(float aspect)
266   {
267     setCharacterSize(getCharacterHeight(), aspect);
268   }
269 
270   //----------------------------------------------------------------------------
setLineHeight(float factor)271   void Text::TextOSG::setLineHeight(float factor)
272   {
273     setLineSpacing(factor - 1);
274   }
275 
276   //----------------------------------------------------------------------------
setFill(const std::string & fill)277   void Text::TextOSG::setFill(const std::string& fill)
278   {
279 //    if( fill == "none" )
280 //      TODO No text
281 //    else
282     osg::Vec4 color;
283     if( parseColor(fill, color) )
284       setColor( color );
285   }
286 
287   //----------------------------------------------------------------------------
setStroke(const std::string & stroke)288   void Text::TextOSG::setStroke(const std::string& stroke)
289   {
290     osg::Vec4 color;
291     if( stroke == "none" || !parseColor(stroke, color) )
292       setBackdropType(NONE);
293     else
294     {
295       setBackdropType(OUTLINE);
296       setBackdropColor(color);
297     }
298   }
299 
300   //----------------------------------------------------------------------------
setBackgroundColor(const std::string & fill)301   void Text::TextOSG::setBackgroundColor(const std::string& fill)
302   {
303     osg::Vec4 color;
304     if( parseColor(fill, color) )
305       setBoundingBoxColor( color );
306   }
307 
308   //----------------------------------------------------------------------------
lineHeight() const309   float Text::TextOSG::lineHeight() const
310   {
311     return (1 + _lineSpacing) * _characterHeight;
312   }
313 
314   //----------------------------------------------------------------------------
lineCount() const315   size_t Text::TextOSG::lineCount() const
316   {
317     return _lineCount;
318   }
319 
320   //----------------------------------------------------------------------------
lineAt(size_t i) const321   TextLine Text::TextOSG::lineAt(size_t i) const
322   {
323     return TextLine(i, this);
324   }
325 
326   //----------------------------------------------------------------------------
327 #if OSG_VERSION_LESS_THAN(3,6,5)
nearestLine(float pos_y) const328   TextLine Text::TextOSG::nearestLine(float pos_y) const
329   {
330     osgText::Font const* font = getActiveFont();
331 #else
332   TextLine Text::TextOSG::nearestLine(float pos_y)
333   {
334     auto font = getActiveFont();
335 #endif
336 
337     if( !font || lineCount() <= 0 )
338       return TextLine(0, this);
339 
340     float asc = .9f, desc = -.2f;
341     font->getVerticalSize(asc, desc);
342 
343     float first_line_y = _offset.y()
344                        - (1 + _lineSpacing / 2 + desc) * _characterHeight;
345 
346     size_t line_num = std::min<size_t>(
347       std::max<size_t>(0, (pos_y - first_line_y) / lineHeight()),
348       lineCount() - 1
349     );
350 
351     return TextLine(line_num, this);
352   }
353 
354   //----------------------------------------------------------------------------
355   // simplified version of osgText::Text::computeGlyphRepresentation() to
356   // just calculate the size for a given weight. Glpyh calculations/creating
357   // is not necessary for this...
358 #if OSG_VERSION_LESS_THAN(3,6,5)
359   SGVec2i Text::TextOSG::sizeForWidth(int w) const
360 #else
361   SGVec2i Text::TextOSG::sizeForWidth(int w)
362 #endif
363   {
364     if( _text.empty() )
365       return SGVec2i(0, 0);
366 
367 #if OSG_VERSION_LESS_THAN(3,6,5)
368     osgText::Font* activefont = const_cast<osgText::Font*>(getActiveFont());
369 #else
370     auto activefont = getActiveFont();
371 #endif
372 
373     if( !activefont )
374       return SGVec2i(-1, -1);
375 
376     float max_width_safe = _maximumWidth;
377     const_cast<TextOSG*>(this)->_maximumWidth = w;
378 
379     SGRecti bb;
380 
381     osg::Vec2 startOfLine_coords(0.0f,0.0f);
382     osg::Vec2 cursor(startOfLine_coords);
383     osg::Vec2 local(0.0f,0.0f);
384 
385     unsigned int previous_charcode = 0;
386     unsigned int line_length = 0;
387     bool horizontal = _layout != VERTICAL;
388     bool kerning = true;
389 
390     float hr = _characterHeight;
391     float wr = hr / getCharacterAspectRatio();
392 
393     // osg should really care more about const :-/
394     osgText::String& text = const_cast<osgText::String&>(_text);
395     typedef osgText::String::iterator TextIterator;
396 
397     for( TextIterator itr = text.begin(); itr != text.end(); )
398     {
399       // record the start of the current line
400       TextIterator startOfLine_itr = itr;
401 
402       // find the end of the current line.
403       osg::Vec2 endOfLine_coords(cursor);
404       TextIterator endOfLine_itr =
405         const_cast<TextOSG*>(this)->computeLastCharacterOnLine(
406           endOfLine_coords, itr, text.end()
407         );
408 
409       line_length = endOfLine_itr - startOfLine_itr;
410 
411       // Set line position to correct alignment.
412       switch( _layout )
413       {
414         case LEFT_TO_RIGHT:
415         {
416           switch( _alignment )
417           {
418             // nothing to be done for these
419             //case LEFT_TOP:
420             //case LEFT_CENTER:
421             //case LEFT_BOTTOM:
422             //case LEFT_BASE_LINE:
423             //case LEFT_BOTTOM_BASE_LINE:
424             //  break;
425             case CENTER_TOP:
426             case CENTER_CENTER:
427             case CENTER_BOTTOM:
428             case CENTER_BASE_LINE:
429             case CENTER_BOTTOM_BASE_LINE:
430               cursor.x() = (cursor.x() - endOfLine_coords.x()) * 0.5f;
431               break;
432             case RIGHT_TOP:
433             case RIGHT_CENTER:
434             case RIGHT_BOTTOM:
435             case RIGHT_BASE_LINE:
436             case RIGHT_BOTTOM_BASE_LINE:
437               cursor.x() = cursor.x() - endOfLine_coords.x();
438               break;
439             default:
440               break;
441           }
442           break;
443         }
444         case RIGHT_TO_LEFT:
445         {
446           switch( _alignment )
447           {
448             case LEFT_TOP:
449             case LEFT_CENTER:
450             case LEFT_BOTTOM:
451             case LEFT_BASE_LINE:
452             case LEFT_BOTTOM_BASE_LINE:
453               cursor.x() = 2 * cursor.x() - endOfLine_coords.x();
454               break;
455             case CENTER_TOP:
456             case CENTER_CENTER:
457             case CENTER_BOTTOM:
458             case CENTER_BASE_LINE:
459             case CENTER_BOTTOM_BASE_LINE:
460               cursor.x() = cursor.x()
461                   + (cursor.x() - endOfLine_coords.x()) * 0.5f;
462               break;
463               // nothing to be done for these
464               //case RIGHT_TOP:
465               //case RIGHT_CENTER:
466               //case RIGHT_BOTTOM:
467               //case RIGHT_BASE_LINE:
468               //case RIGHT_BOTTOM_BASE_LINE:
469               //  break;
470             default:
471               break;
472           }
473           break;
474         }
475         case VERTICAL:
476         {
477           switch( _alignment )
478           {
479             // TODO: current behaviour top baselines lined up in both cases - need to implement
480             //       top of characters alignment - Question is this necessary?
481             // ... otherwise, nothing to be done for these 6 cases
482             //case LEFT_TOP:
483             //case CENTER_TOP:
484             //case RIGHT_TOP:
485             //  break;
486             //case LEFT_BASE_LINE:
487             //case CENTER_BASE_LINE:
488             //case RIGHT_BASE_LINE:
489             //  break;
490             case LEFT_CENTER:
491             case CENTER_CENTER:
492             case RIGHT_CENTER:
493               cursor.y() = cursor.y()
494                   + (cursor.y() - endOfLine_coords.y()) * 0.5f;
495               break;
496             case LEFT_BOTTOM_BASE_LINE:
497             case CENTER_BOTTOM_BASE_LINE:
498             case RIGHT_BOTTOM_BASE_LINE:
499               cursor.y() = cursor.y() - (line_length * _characterHeight);
500               break;
501             case LEFT_BOTTOM:
502             case CENTER_BOTTOM:
503             case RIGHT_BOTTOM:
504               cursor.y() = 2 * cursor.y() - endOfLine_coords.y();
505               break;
506             default:
507               break;
508           }
509           break;
510         }
511       }
512 
513       if( itr != endOfLine_itr )
514       {
515 
516         for(;itr != endOfLine_itr;++itr)
517         {
518           unsigned int charcode = *itr;
519 
520           osgText::Glyph* glyph = activefont->getGlyph(_fontSize, charcode);
521           if( glyph )
522           {
523             float width = (float) (glyph->getWidth()) * wr;
524             float height = (float) (glyph->getHeight()) * hr;
525 
526             if( _layout == RIGHT_TO_LEFT )
527             {
528               cursor.x() -= glyph->getHorizontalAdvance() * wr;
529             }
530 
531             // adjust cursor position w.r.t any kerning.
532             if( kerning && previous_charcode )
533             {
534               switch( _layout )
535               {
536                 case LEFT_TO_RIGHT:
537                 {
538 #if OSG_VERSION_LESS_THAN(3,5,2)
539                     osg::Vec2 delta(activefont->getKerning(previous_charcode,
540                         charcode,
541                         _kerningType));
542 #else
543                     osg::Vec2 delta(activefont->getKerning(_fontSize,
544                         previous_charcode,
545                         charcode,
546                         _kerningType));
547 #endif
548                   cursor.x() += delta.x() * wr;
549                   cursor.y() += delta.y() * hr;
550                   break;
551                 }
552                 case RIGHT_TO_LEFT:
553                 {
554 #if OSG_VERSION_LESS_THAN(3,5,2)
555                     osg::Vec2 delta(activefont->getKerning(charcode,
556                         previous_charcode,
557                         _kerningType));
558 #else
559                     osg::Vec2 delta(activefont->getKerning(_fontSize, charcode,
560                         previous_charcode,
561                         _kerningType));
562 #endif
563                   cursor.x() -= delta.x() * wr;
564                   cursor.y() -= delta.y() * hr;
565                   break;
566                 }
567                 case VERTICAL:
568                   break; // no kerning when vertical.
569               }
570             }
571 
572             local = cursor;
573             osg::Vec2 bearing( horizontal ? glyph->getHorizontalBearing()
574                                           : glyph->getVerticalBearing() );
575             local.x() += bearing.x() * wr;
576             local.y() += bearing.y() * hr;
577 
578             // set up the coords of the quad
579             osg::Vec2 upLeft = local + osg::Vec2(0.f, height);
580             osg::Vec2 lowLeft = local;
581             osg::Vec2 lowRight = local + osg::Vec2(width, 0.f);
582             osg::Vec2 upRight = local + osg::Vec2(width, height);
583 
584             // move the cursor onto the next character.
585             // also expand bounding box
586             switch( _layout )
587             {
588               case LEFT_TO_RIGHT:
589                 cursor.x() += glyph->getHorizontalAdvance() * wr;
590                 bb.expandBy(lowLeft.x(), lowLeft.y());
591                 bb.expandBy(upRight.x(), upRight.y());
592                 break;
593               case VERTICAL:
594                 cursor.y() -= glyph->getVerticalAdvance() * hr;
595                 bb.expandBy(upLeft.x(), upLeft.y());
596                 bb.expandBy(lowRight.x(), lowRight.y());
597                 break;
598               case RIGHT_TO_LEFT:
599                 bb.expandBy(lowRight.x(), lowRight.y());
600                 bb.expandBy(upLeft.x(), upLeft.y());
601                 break;
602             }
603             previous_charcode = charcode;
604           }
605         }
606 
607         // skip over spaces and return.
608         while( itr != text.end() && *itr == ' ' )
609           ++itr;
610         if( itr != text.end() && *itr == '\n' )
611           ++itr;
612       }
613       else
614       {
615         ++itr;
616       }
617 
618       // move to new line.
619       switch( _layout )
620       {
621         case LEFT_TO_RIGHT:
622         {
623           startOfLine_coords.y() -= _characterHeight * (1.0 + _lineSpacing);
624           cursor = startOfLine_coords;
625           previous_charcode = 0;
626           break;
627         }
628         case RIGHT_TO_LEFT:
629         {
630           startOfLine_coords.y() -= _characterHeight * (1.0 + _lineSpacing);
631           cursor = startOfLine_coords;
632           previous_charcode = 0;
633           break;
634         }
635         case VERTICAL:
636         {
637           startOfLine_coords.x() += _characterHeight * (1.0 + _lineSpacing)
638                                   / getCharacterAspectRatio();
639           cursor = startOfLine_coords;
640           previous_charcode = 0;
641           break;
642         }
643       }
644     }
645 
646     const_cast<TextOSG*>(this)->_maximumWidth = max_width_safe;
647 
648     return bb.size();
649   }
650 
651   //----------------------------------------------------------------------------
652 #if OSG_VERSION_LESS_THAN(3,3,2)
653   osg::BoundingBox Text::TextOSG::computeBound() const
654 #else
655   osg::BoundingBox Text::TextOSG::computeBoundingBox() const
656 #endif
657   {
658 #if OSG_VERSION_LESS_THAN(3,3,2)
659       osg::BoundingBox bb = osgText::Text::computeBound();
660 #else
661       osg::BoundingBox bb = osgText::Text::computeBoundingBox();
662 #endif
663 
664 #if OSG_VERSION_LESS_THAN(3,1,0)
665     if( bb.valid() )
666     {
667       // TODO bounding box still doesn't seem always right (eg. with center
668       //      horizontal alignment not completely accurate)
669       bb._min.y() += _offset.y();
670       bb._max.y() += _offset.y();
671     }
672 #endif
673 
674     return bb;
675   }
676 
677 #if OSG_VERSION_LESS_THAN(3,5,6)
678   void Text::TextOSG::computePositions(unsigned int contextID) const
679   {
680     if( _textureGlyphQuadMap.empty() || _layout == VERTICAL )
681       return osgText::Text::computePositions(contextID);
682 
683     // TODO check when it can be larger
684     assert( _textureGlyphQuadMap.size() == 1 );
685 
686     const GlyphQuads& quads = _textureGlyphQuadMap.begin()->second;
687     const GlyphQuads::Glyphs& glyphs = quads._glyphs;
688 #if OSG_VERSION_LESS_THAN(3,3,5)
689     GlyphQuads::Coords2 const& coords = quads._coords;
690 #else
691     GlyphQuads::Coords2 refCoords = quads._coords;
692     GlyphQuads::Coords2::element_type &coords = *refCoords.get();
693 #endif
694 
695     const GlyphQuads::LineNumbers& line_numbers = quads._lineNumbers;
696 
697     float wr = _characterHeight / getCharacterAspectRatio();
698 
699     size_t cur_line = static_cast<size_t>(-1);
700     for(size_t i = 0; i < glyphs.size(); ++i)
701     {
702       // Check horizontal offsets
703 
704       bool first_char = cur_line != line_numbers[i];
705       cur_line = line_numbers[i];
706 
707       bool last_char = (i + 1 == glyphs.size())
708                     || (cur_line != line_numbers[i + 1]);
709 
710       if( first_char || last_char )
711       {
712         // From osg/src/osgText/Text.cpp:
713         //
714         // osg::Vec2 upLeft = local+osg::Vec2(0.0f-fHorizQuadMargin, ...);
715         // osg::Vec2 lowLeft = local+osg::Vec2(0.0f-fHorizQuadMargin, ...);
716         // osg::Vec2 lowRight = local+osg::Vec2(width+fHorizQuadMargin, ...);
717         // osg::Vec2 upRight = local+osg::Vec2(width+fHorizQuadMargin, ...);
718 
719         float left = coords[i * 4].x(),
720               right = coords[i * 4 + 2].x(),
721               width = glyphs[i]->getWidth() * wr;
722 
723         // (local + width + fHoriz) - (local - fHoriz) = width + 2*fHoriz | -width
724         float margin = 0.5f * (right - left - width),
725               cursor_x = left + margin
726                        - glyphs[i]->getHorizontalBearing().x() * wr;
727 
728         if( first_char )
729         {
730           if( cur_line == 0 || cursor_x < _textBB._min.x() )
731             _textBB._min.x() = cursor_x;
732         }
733 
734         if( last_char )
735         {
736           float cursor_w = cursor_x + glyphs[i]->getHorizontalAdvance() * wr;
737 
738           if( cur_line == 0 || cursor_w > _textBB._max.x() )
739             _textBB._max.x() = cursor_w;
740         }
741       }
742     }
743 
744     return osgText::Text::computePositions(contextID);
745   }
746 
747 #else
748   void Text::TextOSG::computePositionsImplementation()
749   {
750     TextBase::computePositionsImplementation();
751   }
752 #endif
753  //----------------------------------------------------------------------------
754 
755   //----------------------------------------------------------------------------
756   const std::string Text::TYPE_NAME = "text";
757 
758   //----------------------------------------------------------------------------
759   void Text::staticInit()
760   {
761     if( isInit<Text>() )
762       return;
763 
764     osg::ref_ptr<TextOSG> Text::*text = &Text::_text;
765 
766     addStyle("fill", "color", &TextOSG::setFill, text);
767     addStyle("background", "color", &TextOSG::setBackgroundColor, text);
768     addStyle("stroke", "color", &TextOSG::setStroke, text);
769     addStyle("character-size",
770              "numeric",
771              static_cast<
772                void (TextOSG::*)(float)
773              > (&TextOSG::setCharacterSize),
774              text);
775     addStyle("character-aspect-ratio",
776              "numeric",
777              &TextOSG::setCharacterAspect, text);
778     addStyle("line-height", "numeric", &TextOSG::setLineHeight, text);
779     addStyle("font-resolution", "numeric", &TextOSG::setFontResolution, text);
780     addStyle("padding", "numeric", &TextOSG::setBoundingBoxMargin, text);
781     //  TEXT              = 1 default
782     //  BOUNDINGBOX       = 2
783     //  FILLEDBOUNDINGBOX = 4
784     //  ALIGNMENT         = 8
785     addStyle<int>("draw-mode", "", &TextOSG::setDrawMode, text);
786     addStyle("max-width", "numeric", &TextOSG::setMaximumWidth, text);
787     addStyle("font", "", &Text::setFont);
788     addStyle("alignment", "", &Text::setAlignment);
789     addStyle("text", "", &Text::setText, false);
790 
791     osgDB::Registry* reg = osgDB::Registry::instance();
792     if( !reg->getReaderWriterForExtension("ttf") )
793       SG_LOG(SG_GL, SG_ALERT, "canvas::Text: Missing 'ttf' font reader");
794   }
795 
796   //----------------------------------------------------------------------------
797   Text::Text( const CanvasWeakPtr& canvas,
798               const SGPropertyNode_ptr& node,
799               const Style& parent_style,
800               ElementWeakPtr parent ):
801     Element(canvas, node, parent_style, parent),
802     _text( new Text::TextOSG(this) )
803   {
804     staticInit();
805 
806     setDrawable(_text);
807     _text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS);
808     _text->setAxisAlignment(osgText::Text::USER_DEFINED_ROTATION);
809     _text->setRotation(osg::Quat(osg::PI, osg::X_AXIS));
810 
811     setupStyle();
812   }
813 
814   //----------------------------------------------------------------------------
815   Text::~Text()
816   {
817 
818   }
819 
820   //----------------------------------------------------------------------------
821   void Text::setText(const char* text)
822   {
823     _text->setText(text, osgText::String::ENCODING_UTF8);
824   }
825 
826   //----------------------------------------------------------------------------
827   void Text::setFont(const char* name)
828   {
829     _text->setFont( Canvas::getSystemAdapter()->getFont(name) );
830   }
831 
832   //----------------------------------------------------------------------------
833   void Text::setAlignment(const char* align)
834   {
835     const std::string align_string(align);
836     if( 0 ) return;
837 #define ENUM_MAPPING(enum_val, string_val) \
838     else if( align_string == string_val )\
839       _text->setAlignment( osgText::Text::enum_val );
840 #include "text-alignment.hxx"
841 #undef ENUM_MAPPING
842     else
843     {
844       if( !align_string.empty() )
845         SG_LOG
846         (
847           SG_GENERAL,
848           SG_WARN,
849           "canvas::Text: unknown alignment '" << align_string << "'"
850         );
851       _text->setAlignment(osgText::Text::LEFT_BASE_LINE);
852     }
853   }
854 
855   //----------------------------------------------------------------------------
856 #if 0
857   const char* Text::getAlignment() const
858   {
859     switch( _text->getAlignment() )
860     {
861 #define ENUM_MAPPING(enum_val, string_val) \
862       case osgText::Text::enum_val:\
863         return string_val;
864 #include "text-alignment.hxx"
865 #undef ENUM_MAPPING
866       default:
867         return "unknown";
868     }
869   }
870 #endif
871 
872   //----------------------------------------------------------------------------
873   int Text::heightForWidth(int w) const
874   {
875     return _text->sizeForWidth(w).y();
876   }
877 
878   //----------------------------------------------------------------------------
879   int Text::maxWidth() const
880   {
881     return _text->sizeForWidth(INT_MAX).x();
882   }
883 
884   //----------------------------------------------------------------------------
885   size_t Text::lineCount() const
886   {
887     return _text->lineCount();
888   }
889 
890   //----------------------------------------------------------------------------
891   size_t Text::lineLength(size_t line) const
892   {
893     return _text->lineAt(line).size();
894   }
895 
896   //----------------------------------------------------------------------------
897   osg::Vec2 Text::getNearestCursor(const osg::Vec2& pos) const
898   {
899     return _text->nearestLine(pos.y()).nearestCursor(pos.x());
900   }
901 
902   //----------------------------------------------------------------------------
903   osg::Vec2 Text::getCursorPos(size_t line, size_t character) const
904   {
905     return _text->lineAt(line).cursorPos(character);
906   }
907 
908   //----------------------------------------------------------------------------
909   osg::StateSet* Text::getOrCreateStateSet()
910   {
911     if( !_scene_group.valid() )
912       return nullptr;
913 
914     // Only check for StateSet on Transform, as the text stateset is shared
915     // between all text instances using the same font (texture).
916     return _scene_group->getOrCreateStateSet();
917   }
918 
919 } // namespace canvas
920 } // namespace simgear
921