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 <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 setBackgroundColor(const std::string& fill);
43 osg::Vec2 handleHit(const osg::Vec2f& pos);
45 virtual osg::BoundingBox computeBound() const;
49 canvas::Text *_text_element;
51 virtual void computePositions(unsigned int contextID) const;
54 //----------------------------------------------------------------------------
55 Text::TextOSG::TextOSG(canvas::Text* text):
58 setBackdropImplementation(NO_DEPTH_BUFFER);
61 //----------------------------------------------------------------------------
62 void Text::TextOSG::setFontResolution(int res)
64 TextBase::setFontResolution(res, res);
67 //----------------------------------------------------------------------------
68 void Text::TextOSG::setCharacterAspect(float aspect)
70 setCharacterSize(getCharacterHeight(), aspect);
73 //----------------------------------------------------------------------------
74 void Text::TextOSG::setLineHeight(float factor)
76 setLineSpacing(factor - 1);
79 //----------------------------------------------------------------------------
80 void Text::TextOSG::setFill(const std::string& fill)
82 // if( fill == "none" )
86 if( parseColor(fill, color) )
90 //----------------------------------------------------------------------------
91 void Text::TextOSG::setBackgroundColor(const std::string& fill)
94 if( parseColor(fill, color) )
95 setBoundingBoxColor( color );
98 //----------------------------------------------------------------------------
99 osg::Vec2 Text::TextOSG::handleHit(const osg::Vec2f& pos)
101 float line_height = _characterHeight + _lineSpacing;
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);
107 if( _textureGlyphQuadMap.empty() )
108 return osg::Vec2(-1, -1);
110 // TODO check when it can be larger
111 assert( _textureGlyphQuadMap.size() == 1 );
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;
118 const float HIT_FRACTION = 0.6;
119 const float character_width = getCharacterHeight()
120 * getCharacterAspectRatio();
122 float y = (line + 0.5) * line_height;
124 bool line_found = false;
125 for(size_t i = 0; i < line_numbers.size(); ++i)
127 if( line_numbers[i] != line )
131 if( line_numbers[i] < line )
132 // Wait for the correct line...
135 // We have already passed the correct line -> It's empty...
136 return osg::Vec2(0, y);
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);
146 // Get threshold for mouse x position for setting cursor before or after
148 float threshold = coords[i * 4].x()
149 + HIT_FRACTION * glyphs[i]->getHorizontalAdvance()
152 if( pos.x() <= threshold )
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();
163 // position at center between characters
164 hit.x() = 0.5 * (coords[(i - 1) * 4 + 2].x() + coords[i * 4].x());
170 // Nothing found -> return position after last character
174 (_lineCount - 0.5) * line_height
178 //----------------------------------------------------------------------------
179 osg::BoundingBox Text::TextOSG::computeBound() const
181 osg::BoundingBox bb = osgText::Text::computeBound();
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();
192 _text_element->setBoundingBox(bb);
197 //----------------------------------------------------------------------------
198 void Text::TextOSG::computePositions(unsigned int contextID) const
200 if( _textureGlyphQuadMap.empty() || _layout == VERTICAL )
201 return osgText::Text::computePositions(contextID);
203 // TODO check when it can be larger
204 assert( _textureGlyphQuadMap.size() == 1 );
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;
211 float wr = _characterHeight / getCharacterAspectRatio();
213 size_t cur_line = static_cast<size_t>(-1);
214 for(size_t i = 0; i < glyphs.size(); ++i)
216 // Check horizontal offsets
218 bool first_char = cur_line != line_numbers[i];
219 cur_line = line_numbers[i];
221 bool last_char = (i + 1 == glyphs.size())
222 || (cur_line != line_numbers[i + 1]);
224 if( first_char || last_char )
226 // From osg/src/osgText/Text.cpp:
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, ...);
233 float left = coords[i * 4].x(),
234 right = coords[i * 4 + 2].x(),
235 width = glyphs[i]->getWidth() * wr;
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;
244 if( cur_line == 0 || cursor_x < _textBB._min.x() )
245 _textBB._min.x() = cursor_x;
250 float cursor_w = cursor_x + glyphs[i]->getHorizontalAdvance() * wr;
252 if( cur_line == 0 || cursor_w > _textBB._max.x() )
253 _textBB._max.x() = cursor_w;
258 return osgText::Text::computePositions(contextID);
261 //----------------------------------------------------------------------------
262 const std::string Text::TYPE_NAME = "text";
264 //----------------------------------------------------------------------------
265 void Text::staticInit()
270 osg::ref_ptr<TextOSG> Text::*text = &Text::_text;
272 addStyle("fill", "color", &TextOSG::setFill, text);
273 addStyle("background", "color", &TextOSG::setBackgroundColor, text);
274 addStyle("character-size",
277 void (TextOSG::*)(float)
278 > (&TextOSG::setCharacterSize),
280 addStyle("character-aspect-ratio",
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);
288 // FILLEDBOUNDINGBOX = 4
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);
297 //----------------------------------------------------------------------------
298 Text::Text( const CanvasWeakPtr& canvas,
299 const SGPropertyNode_ptr& node,
300 const Style& parent_style,
302 Element(canvas, node, parent_style, parent),
303 _text( new Text::TextOSG(this) )
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));
315 //----------------------------------------------------------------------------
321 //----------------------------------------------------------------------------
322 void Text::setText(const char* text)
324 _text->setText(text, osgText::String::ENCODING_UTF8);
327 //----------------------------------------------------------------------------
328 void Text::setFont(const char* name)
330 _text->setFont( Canvas::getSystemAdapter()->getFont(name) );
333 //----------------------------------------------------------------------------
334 void Text::setAlignment(const char* align)
336 const std::string align_string(align);
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"
345 if( !align_string.empty() )
350 "canvas::Text: unknown alignment '" << align_string << "'"
352 _text->setAlignment(osgText::Text::LEFT_BASE_LINE);
356 //----------------------------------------------------------------------------
358 const char* Text::getAlignment() const
360 switch( _text->getAlignment() )
362 #define ENUM_MAPPING(enum_val, string_val) \
363 case osgText::Text::enum_val:\
365 #include "text-alignment.hxx"
373 //----------------------------------------------------------------------------
374 osg::Vec2 Text::getNearestCursor(const osg::Vec2& pos) const
376 return _text->handleHit(pos);
379 } // namespace canvas
380 } // namespace simgear