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