]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/elements/CanvasText.cxx
Canvas: Fix bounding box calculation for paths with subpaths.
[simgear.git] / simgear / canvas / elements / CanvasText.cxx
1 // A text on the Canvas
2 //
3 // Copyright (C) 2012  Thomas Geymayer <tomgey@gmail.com>
4 //
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.
9 //
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.
14 //
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
18
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 <osgText/Text>
25
26 namespace simgear
27 {
28 namespace canvas
29 {
30   class Text::TextOSG:
31     public osgText::Text
32   {
33     public:
34
35       TextOSG(canvas::Text* text);
36
37       void setFontResolution(int res);
38       void setCharacterAspect(float aspect);
39       void setLineHeight(float factor);
40       void setFill(const std::string& fill);
41       void setBackgroundColor(const std::string& fill);
42
43       osg::Vec2 handleHit(const osg::Vec2f& pos);
44
45       virtual osg::BoundingBox computeBound() const;
46
47     protected:
48
49       canvas::Text *_text_element;
50
51       virtual void computePositions(unsigned int contextID) const;
52   };
53
54   //----------------------------------------------------------------------------
55   Text::TextOSG::TextOSG(canvas::Text* text):
56     _text_element(text)
57   {
58     setBackdropImplementation(NO_DEPTH_BUFFER);
59   }
60
61   //----------------------------------------------------------------------------
62   void Text::TextOSG::setFontResolution(int res)
63   {
64     TextBase::setFontResolution(res, res);
65   }
66
67   //----------------------------------------------------------------------------
68   void Text::TextOSG::setCharacterAspect(float aspect)
69   {
70     setCharacterSize(getCharacterHeight(), aspect);
71   }
72
73   //----------------------------------------------------------------------------
74   void Text::TextOSG::setLineHeight(float factor)
75   {
76     setLineSpacing(factor - 1);
77   }
78
79   //----------------------------------------------------------------------------
80   void Text::TextOSG::setFill(const std::string& fill)
81   {
82 //    if( fill == "none" )
83 //      TODO No text
84 //    else
85     osg::Vec4 color;
86     if( parseColor(fill, color) )
87       setColor( color );
88   }
89
90   //----------------------------------------------------------------------------
91   void Text::TextOSG::setBackgroundColor(const std::string& fill)
92   {
93     osg::Vec4 color;
94     if( parseColor(fill, color) )
95       setBoundingBoxColor( color );
96   }
97
98   //----------------------------------------------------------------------------
99   osg::Vec2 Text::TextOSG::handleHit(const osg::Vec2f& pos)
100   {
101     float line_height = _characterHeight + _lineSpacing;
102
103     // TODO check with align other than TOP
104     float first_line_y = -0.5 * _lineSpacing;//_offset.y() - _characterHeight;
105     size_t line = std::max<int>(0, (pos.y() - first_line_y) / line_height);
106
107     if( _textureGlyphQuadMap.empty() )
108       return osg::Vec2(-1, -1);
109
110     // TODO check when it can be larger
111     assert( _textureGlyphQuadMap.size() == 1 );
112
113     const GlyphQuads& glyphquad = _textureGlyphQuadMap.begin()->second;
114     const GlyphQuads::Glyphs& glyphs = glyphquad._glyphs;
115     const GlyphQuads::Coords2& coords = glyphquad._coords;
116     const GlyphQuads::LineNumbers& line_numbers = glyphquad._lineNumbers;
117
118     const float HIT_FRACTION = 0.6;
119     const float character_width = getCharacterHeight()
120                                 * getCharacterAspectRatio();
121
122     float y = (line + 0.5) * line_height;
123
124     bool line_found = false;
125     for(size_t i = 0; i < line_numbers.size(); ++i)
126     {
127       if( line_numbers[i] != line )
128       {
129         if( !line_found )
130         {
131           if( line_numbers[i] < line )
132             // Wait for the correct line...
133             continue;
134
135           // We have already passed the correct line -> It's empty...
136           return osg::Vec2(0, y);
137         }
138
139         // Next line and not returned -> not before any character
140         // -> return position after last character of line
141         return osg::Vec2(coords[(i - 1) * 4 + 2].x(), y);
142       }
143
144       line_found = true;
145
146       // Get threshold for mouse x position for setting cursor before or after
147       // current character
148       float threshold = coords[i * 4].x()
149                       + HIT_FRACTION * glyphs[i]->getHorizontalAdvance()
150                                      * character_width;
151
152       if( pos.x() <= threshold )
153       {
154         osg::Vec2 hit(0, y);
155         if( i == 0 || line_numbers[i - 1] != line )
156           // first character of line
157           hit.x() = coords[i * 4].x();
158         else if( coords[(i - 1) * 4].x() == coords[(i - 1) * 4 + 2].x() )
159           // If previous character width is zero set to begin of next character
160           // (Happens eg. with spaces)
161           hit.x() = coords[i * 4].x();
162         else
163           // position at center between characters
164           hit.x() = 0.5 * (coords[(i - 1) * 4 + 2].x() + coords[i * 4].x());
165
166         return hit;
167       }
168     }
169
170     // Nothing found -> return position after last character
171     return osg::Vec2
172     (
173       coords.back().x(),
174       (_lineCount - 0.5) * line_height
175     );
176   }
177
178   //----------------------------------------------------------------------------
179   osg::BoundingBox Text::TextOSG::computeBound() const
180   {
181     osg::BoundingBox bb = osgText::Text::computeBound();
182     if( !bb.valid() )
183       return bb;
184
185 #if OSG_VERSION_LESS_THAN(3,1,0)
186     // TODO bounding box still doesn't seem always right (eg. with center
187     //      horizontal alignment not completely accurate)
188     bb._min.y() += _offset.y();
189     bb._max.y() += _offset.y();
190 #endif
191
192     _text_element->setBoundingBox(bb);
193
194     return bb;
195   }
196
197   //----------------------------------------------------------------------------
198   void Text::TextOSG::computePositions(unsigned int contextID) const
199   {
200     if( _textureGlyphQuadMap.empty() || _layout == VERTICAL )
201       return osgText::Text::computePositions(contextID);
202
203     // TODO check when it can be larger
204     assert( _textureGlyphQuadMap.size() == 1 );
205
206     const GlyphQuads& quads = _textureGlyphQuadMap.begin()->second;
207     const GlyphQuads::Glyphs& glyphs = quads._glyphs;
208     const GlyphQuads::Coords2& coords = quads._coords;
209     const GlyphQuads::LineNumbers& line_numbers = quads._lineNumbers;
210
211     float wr = _characterHeight / getCharacterAspectRatio();
212
213     size_t cur_line = static_cast<size_t>(-1);
214     for(size_t i = 0; i < glyphs.size(); ++i)
215     {
216       // Check horizontal offsets
217
218       bool first_char = cur_line != line_numbers[i];
219       cur_line = line_numbers[i];
220
221       bool last_char = (i + 1 == glyphs.size())
222                     || (cur_line != line_numbers[i + 1]);
223
224       if( first_char || last_char )
225       {
226         // From osg/src/osgText/Text.cpp:
227         //
228         // osg::Vec2 upLeft = local+osg::Vec2(0.0f-fHorizQuadMargin, ...);
229         // osg::Vec2 lowLeft = local+osg::Vec2(0.0f-fHorizQuadMargin, ...);
230         // osg::Vec2 lowRight = local+osg::Vec2(width+fHorizQuadMargin, ...);
231         // osg::Vec2 upRight = local+osg::Vec2(width+fHorizQuadMargin, ...);
232
233         float left = coords[i * 4].x(),
234               right = coords[i * 4 + 2].x(),
235               width = glyphs[i]->getWidth() * wr;
236
237         // (local + width + fHoriz) - (local - fHoriz) = width + 2*fHoriz | -width
238         float margin = 0.5f * (right - left - width),
239               cursor_x = left + margin
240                        - glyphs[i]->getHorizontalBearing().x() * wr;
241
242         if( first_char )
243         {
244           if( cur_line == 0 || cursor_x < _textBB._min.x() )
245             _textBB._min.x() = cursor_x;
246         }
247
248         if( last_char )
249         {
250           float cursor_w = cursor_x + glyphs[i]->getHorizontalAdvance() * wr;
251
252           if( cur_line == 0 || cursor_w > _textBB._max.x() )
253             _textBB._max.x() = cursor_w;
254         }
255       }
256     }
257
258     return osgText::Text::computePositions(contextID);
259   }
260
261   //----------------------------------------------------------------------------
262   const std::string Text::TYPE_NAME = "text";
263
264   //----------------------------------------------------------------------------
265   void Text::staticInit()
266   {
267     if( isInit<Text>() )
268       return;
269
270     osg::ref_ptr<TextOSG> Text::*text = &Text::_text;
271
272     addStyle("fill", "color", &TextOSG::setFill, text);
273     addStyle("background", "color", &TextOSG::setBackgroundColor, text);
274     addStyle("character-size",
275              "numeric",
276              static_cast<
277                void (TextOSG::*)(float)
278              > (&TextOSG::setCharacterSize),
279              text);
280     addStyle("character-aspect-ratio",
281              "numeric",
282              &TextOSG::setCharacterAspect, text);
283     addStyle("line-height", "numeric", &TextOSG::setLineHeight, text);
284     addStyle("font-resolution", "numeric", &TextOSG::setFontResolution, text);
285     addStyle("padding", "numeric", &TextOSG::setBoundingBoxMargin, text);
286     //  TEXT              = 1 default
287     //  BOUNDINGBOX       = 2
288     //  FILLEDBOUNDINGBOX = 4
289     //  ALIGNMENT         = 8
290     addStyle<int>("draw-mode", "", &TextOSG::setDrawMode, text);
291     addStyle("max-width", "numeric", &TextOSG::setMaximumWidth, text);
292     addStyle("font", "", &Text::setFont);
293     addStyle("alignment", "", &Text::setAlignment);
294     addStyle("text", "", &Text::setText, false);
295   }
296
297   //----------------------------------------------------------------------------
298   Text::Text( const CanvasWeakPtr& canvas,
299               const SGPropertyNode_ptr& node,
300               const Style& parent_style,
301               Element* parent ):
302     Element(canvas, node, parent_style, parent),
303     _text( new Text::TextOSG(this) )
304   {
305     staticInit();
306
307     setDrawable(_text);
308     _text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS);
309     _text->setAxisAlignment(osgText::Text::USER_DEFINED_ROTATION);
310     _text->setRotation(osg::Quat(osg::PI, osg::X_AXIS));
311
312     setupStyle();
313   }
314
315   //----------------------------------------------------------------------------
316   Text::~Text()
317   {
318
319   }
320
321   //----------------------------------------------------------------------------
322   void Text::setText(const char* text)
323   {
324     _text->setText(text, osgText::String::ENCODING_UTF8);
325   }
326
327   //----------------------------------------------------------------------------
328   void Text::setFont(const char* name)
329   {
330     _text->setFont( Canvas::getSystemAdapter()->getFont(name) );
331   }
332
333   //----------------------------------------------------------------------------
334   void Text::setAlignment(const char* align)
335   {
336     const std::string align_string(align);
337     if( 0 ) return;
338 #define ENUM_MAPPING(enum_val, string_val) \
339     else if( align_string == string_val )\
340       _text->setAlignment( osgText::Text::enum_val );
341 #include "text-alignment.hxx"
342 #undef ENUM_MAPPING
343     else
344     {
345       if( !align_string.empty() )
346         SG_LOG
347         (
348           SG_GENERAL,
349           SG_WARN,
350           "canvas::Text: unknown alignment '" << align_string << "'"
351         );
352       _text->setAlignment(osgText::Text::LEFT_BASE_LINE);
353     }
354   }
355
356   //----------------------------------------------------------------------------
357 #if 0
358   const char* Text::getAlignment() const
359   {
360     switch( _text->getAlignment() )
361     {
362 #define ENUM_MAPPING(enum_val, string_val) \
363       case osgText::Text::enum_val:\
364         return string_val;
365 #include "text-alignment.hxx"
366 #undef ENUM_MAPPING
367       default:
368         return "unknown";
369     }
370   }
371 #endif
372
373   //----------------------------------------------------------------------------
374   osg::Vec2 Text::getNearestCursor(const osg::Vec2& pos) const
375   {
376     return _text->handleHit(pos);
377   }
378
379 } // namespace canvas
380 } // namespace simgear