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 <simgear/structure/OSGVersion.hxx>
24 #include <osgText/Text>
35 TextOSG(canvas::Text* text);
37 void setCharacterSize(float height);
38 void setCharacterAspect(float aspect);
39 void setFill(const std::string& fill);
40 void setBackgroundColor(const std::string& fill);
42 osg::Vec2 handleHit(const osg::Vec2f& pos);
44 virtual osg::BoundingBox computeBound() const;
48 canvas::Text *_text_element;
50 virtual void computePositions(unsigned int contextID) const;
53 //----------------------------------------------------------------------------
54 Text::TextOSG::TextOSG(canvas::Text* text):
60 //----------------------------------------------------------------------------
61 void Text::TextOSG::setCharacterSize(float height)
63 TextBase::setCharacterSize(height);
65 unsigned int res = 32;
66 CanvasPtr canvas = _text_element->_canvas.lock();
69 float factor = canvas->getSizeY() / canvas->getViewHeight();
70 res = height * factor;
73 // TODO different vertical/horizontal resolution?
75 setFontResolution(res, res);
78 //----------------------------------------------------------------------------
79 void Text::TextOSG::setCharacterAspect(float aspect)
81 TextBase::setCharacterSize(getCharacterHeight(), aspect);
84 //----------------------------------------------------------------------------
85 void Text::TextOSG::setFill(const std::string& fill)
87 // if( fill == "none" )
91 if( parseColor(fill, color) )
95 //----------------------------------------------------------------------------
96 void Text::TextOSG::setBackgroundColor(const std::string& fill)
99 if( parseColor(fill, color) )
100 setBoundingBoxColor( color );
103 //----------------------------------------------------------------------------
104 osg::Vec2 Text::TextOSG::handleHit(const osg::Vec2f& pos)
106 float line_height = _characterHeight + _lineSpacing;
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);
112 if( _textureGlyphQuadMap.empty() )
113 return osg::Vec2(-1, -1);
115 // TODO check when it can be larger
116 assert( _textureGlyphQuadMap.size() == 1 );
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;
123 const float HIT_FRACTION = 0.6;
124 const float character_width = getCharacterHeight()
125 * getCharacterAspectRatio();
127 float y = (line + 0.5) * line_height;
129 bool line_found = false;
130 for(size_t i = 0; i < line_numbers.size(); ++i)
132 if( line_numbers[i] != line )
136 if( line_numbers[i] < line )
137 // Wait for the correct line...
140 // We have already passed the correct line -> It's empty...
141 return osg::Vec2(0, y);
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);
151 // Get threshold for mouse x position for setting cursor before or after
153 float threshold = coords[i * 4].x()
154 + HIT_FRACTION * glyphs[i]->getHorizontalAdvance()
157 if( pos.x() <= threshold )
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();
168 // position at center between characters
169 hit.x() = 0.5 * (coords[(i - 1) * 4 + 2].x() + coords[i * 4].x());
175 // Nothing found -> return position after last character
179 (_lineCount - 0.5) * line_height
183 //----------------------------------------------------------------------------
184 osg::BoundingBox Text::TextOSG::computeBound() const
186 osg::BoundingBox bb = osgText::Text::computeBound();
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();
197 _text_element->setBoundingBox(bb);
202 //----------------------------------------------------------------------------
203 void Text::TextOSG::computePositions(unsigned int contextID) const
205 if( _textureGlyphQuadMap.empty() || _layout == VERTICAL )
206 return osgText::Text::computePositions(contextID);
208 // TODO check when it can be larger
209 assert( _textureGlyphQuadMap.size() == 1 );
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;
216 float wr = _characterHeight / getCharacterAspectRatio();
218 size_t cur_line = static_cast<size_t>(-1);
219 for(size_t i = 0; i < glyphs.size(); ++i)
221 // Check horizontal offsets
223 bool first_char = cur_line != line_numbers[i];
224 cur_line = line_numbers[i];
226 bool last_char = (i + 1 == glyphs.size())
227 || (cur_line != line_numbers[i + 1]);
229 if( first_char || last_char )
231 // From osg/src/osgText/Text.cpp:
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, ...);
238 float left = coords[i * 4].x(),
239 right = coords[i * 4 + 2].x(),
240 width = glyphs[i]->getWidth() * wr;
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;
249 if( cur_line == 0 || cursor_x < _textBB._min.x() )
250 _textBB._min.x() = cursor_x;
255 float cursor_w = cursor_x + glyphs[i]->getHorizontalAdvance() * wr;
257 if( cur_line == 0 || cursor_w > _textBB._max.x() )
258 _textBB._max.x() = cursor_w;
263 return osgText::Text::computePositions(contextID);
266 //----------------------------------------------------------------------------
267 const std::string Text::TYPE_NAME = "text";
269 //----------------------------------------------------------------------------
270 Text::Text( const CanvasWeakPtr& canvas,
271 const SGPropertyNode_ptr& node,
272 const Style& parent_style,
274 Element(canvas, node, parent_style, parent),
275 _text( new Text::TextOSG(this) )
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));
282 if( !isInit<Text>() )
284 osg::ref_ptr<TextOSG> Text::*text = &Text::_text;
286 addStyle("fill", "color", &TextOSG::setFill, text);
287 addStyle("background", "color", &TextOSG::setBackgroundColor, text);
288 addStyle("character-size",
291 void (TextOSG::*)(float)
292 > (&TextOSG::setCharacterSize),
294 addStyle("character-aspect-ratio",
296 &TextOSG::setCharacterAspect, text);
297 addStyle("padding", "numeric", &TextOSG::setBoundingBoxMargin, text);
300 // FILLEDBOUNDINGBOX = 4
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);
312 //----------------------------------------------------------------------------
318 //----------------------------------------------------------------------------
319 void Text::setText(const char* text)
321 _text->setText(text, osgText::String::ENCODING_UTF8);
324 //----------------------------------------------------------------------------
325 void Text::setFont(const char* name)
327 _text->setFont( _canvas.lock()->getSystemAdapter()->getFont(name) );
330 //----------------------------------------------------------------------------
331 void Text::setAlignment(const char* align)
333 const std::string align_string(align);
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"
342 if( !align_string.empty() )
347 "canvas::Text: unknown alignment '" << align_string << "'"
349 _text->setAlignment(osgText::Text::LEFT_BASE_LINE);
353 //----------------------------------------------------------------------------
355 const char* Text::getAlignment() const
357 switch( _text->getAlignment() )
359 #define ENUM_MAPPING(enum_val, string_val) \
360 case osgText::Text::enum_val:\
362 #include "text-alignment.hxx"
370 //----------------------------------------------------------------------------
371 osg::Vec2 Text::getNearestCursor(const osg::Vec2& pos) const
373 return _text->handleHit(pos);
376 } // namespace canvas
377 } // namespace simgear