From: Thomas Geymayer Date: Sat, 23 Aug 2014 13:54:07 +0000 (+0200) Subject: canvas::Text: clean up and expose character/cursor positions. X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=e1791b300624e7e27ca7233dbfbe36fff23ee8ef;p=simgear.git canvas::Text: clean up and expose character/cursor positions. --- diff --git a/simgear/canvas/elements/CanvasText.cxx b/simgear/canvas/elements/CanvasText.cxx index 239383ca..8658a06a 100644 --- a/simgear/canvas/elements/CanvasText.cxx +++ b/simgear/canvas/elements/CanvasText.cxx @@ -28,11 +28,11 @@ namespace simgear { namespace canvas { + class TextLine; class Text::TextOSG: public osgText::Text { public: - TextOSG(canvas::Text* text); void setFontResolution(int res); @@ -42,8 +42,19 @@ namespace canvas void setStroke(const std::string& color); void setBackgroundColor(const std::string& fill); + float lineHeight() const; + + /// Get the number of lines + size_t lineCount() const; + + /// Get line @a i + TextLine lineAt(size_t i) const; + + /// Get nearest line to given y-coordinate + TextLine nearestLine(float pos_y) const; + + SGVec2i sizeForWidth(int w) const; - osg::Vec2 handleHit(const osg::Vec2f& pos); virtual osg::BoundingBox #if OSG_VERSION_LESS_THAN(3,3,2) @@ -55,11 +66,153 @@ namespace canvas protected: + friend class TextLine; + canvas::Text *_text_element; virtual void computePositions(unsigned int contextID) const; }; + class TextLine + { + public: + TextLine(); + TextLine(size_t line, Text::TextOSG const* text); + + /// Number of characters on this line + size_t size() const; + bool empty() const; + + osg::Vec2 cursorPos(size_t i) const; + osg::Vec2 nearestCursor(float x) const; + + protected: + typedef Text::TextOSG::GlyphQuads GlyphQuads; + + Text::TextOSG const *_text; + GlyphQuads const *_quads; + + size_t _line, + _begin, + _end; + }; + + //---------------------------------------------------------------------------- + TextLine::TextLine(): + _text(NULL), + _quads(NULL), + _line(0), + _begin(-1), + _end(-1) + { + + } + + //---------------------------------------------------------------------------- + TextLine::TextLine(size_t line, Text::TextOSG const* text): + _text(text), + _quads(NULL), + _line(line), + _begin(-1), + _end(-1) + { + if( !text || text->_textureGlyphQuadMap.empty() || !_text->lineCount() ) + return; + + _quads = &text->_textureGlyphQuadMap.begin()->second; + + GlyphQuads::LineNumbers const& line_numbers = _quads->_lineNumbers; + GlyphQuads::LineNumbers::const_iterator begin_it = + std::lower_bound(line_numbers.begin(), line_numbers.end(), _line); + + if( begin_it == line_numbers.end() || *begin_it != _line ) + // empty line or past last line + return; + + _begin = begin_it - line_numbers.begin(); + _end = std::upper_bound(begin_it, line_numbers.end(), _line) + - line_numbers.begin(); + } + + //---------------------------------------------------------------------------- + size_t TextLine::size() const + { + return _end - _begin; + } + + //---------------------------------------------------------------------------- + bool TextLine::empty() const + { + return _end == _begin; + } + + //---------------------------------------------------------------------------- + osg::Vec2 TextLine::cursorPos(size_t i) const + { + if( !_quads || i > size() ) + return osg::Vec2(-1, -1); + + osg::Vec2 pos(0, _text->_offset.y() + _line * _text->lineHeight()); + + if( empty() ) + return pos; + + GlyphQuads::Coords2 const& coords = _quads->_coords; + size_t global_i = _begin + i; + + if( global_i == _begin ) + // before first character of line + pos.x() = coords[_begin * 4].x(); + else if( global_i == _end ) + // After Last character of line + pos.x() = coords[(_end - 1) * 4 + 2].x(); + else + { + float prev_l = coords[(global_i - 1) * 4].x(), + prev_r = coords[(global_i - 1) * 4 + 2].x(), + cur_l = coords[global_i * 4].x(); + + if( prev_l == prev_r ) + // If previous character width is zero set to begin of next character + // (Happens eg. with spaces) + pos.x() = cur_l; + else + // position at center between characters + pos.x() = 0.5 * (prev_r + cur_l); + } + + return pos; + } + + //---------------------------------------------------------------------------- + osg::Vec2 TextLine::nearestCursor(float x) const + { + if( empty() ) + return cursorPos(0); + + GlyphQuads::Glyphs const& glyphs = _quads->_glyphs; + GlyphQuads::Coords2 const& coords = _quads->_coords; + + float const HIT_FRACTION = 0.6; + float const character_width = _text->getCharacterHeight() + * _text->getCharacterAspectRatio(); + + size_t i = _begin; + for(; i < _end; ++i) + { + // Get threshold for mouse x position for setting cursor before or after + // current character + float threshold = coords[i * 4].x() + + HIT_FRACTION * glyphs[i]->getHorizontalAdvance() + * character_width; + + if( x <= threshold ) + break; + } + + return cursorPos(i - _begin); + } + //---------------------------------------------------------------------------- Text::TextOSG::TextOSG(canvas::Text* text): _text_element(text) @@ -117,6 +270,45 @@ namespace canvas setBoundingBoxColor( color ); } + //---------------------------------------------------------------------------- + float Text::TextOSG::lineHeight() const + { + return (1 + _lineSpacing) * _characterHeight; + } + + //---------------------------------------------------------------------------- + size_t Text::TextOSG::lineCount() const + { + return _lineCount; + } + + //---------------------------------------------------------------------------- + TextLine Text::TextOSG::lineAt(size_t i) const + { + return TextLine(i, this); + } + + //---------------------------------------------------------------------------- + TextLine Text::TextOSG::nearestLine(float pos_y) const + { + osgText::Font const* font = getActiveFont(); + if( !font || lineCount() <= 0 ) + return TextLine(0, this); + + float asc = .9f, desc = -.2f; + font->getVerticalSize(asc, desc); + + float first_line_y = _offset.y() + - (1 + _lineSpacing / 2 + desc) * _characterHeight; + + size_t line_num = std::min( + std::max(0, (pos_y - first_line_y) / lineHeight()), + lineCount() - 1 + ); + + return TextLine(line_num, this); + } + //---------------------------------------------------------------------------- // simplified version of osgText::Text::computeGlyphRepresentation() to // just calculate the size for a given weight. Glpyh calculations/creating @@ -392,86 +584,6 @@ namespace canvas return bb.size(); } - //---------------------------------------------------------------------------- - osg::Vec2 Text::TextOSG::handleHit(const osg::Vec2f& pos) - { - float line_height = _characterHeight + _lineSpacing; - - // TODO check with align other than TOP - float first_line_y = -0.5 * _lineSpacing;//_offset.y() - _characterHeight; - size_t line = std::max(0, (pos.y() - first_line_y) / line_height); - - if( _textureGlyphQuadMap.empty() ) - return osg::Vec2(-1, -1); - - // TODO check when it can be larger - assert( _textureGlyphQuadMap.size() == 1 ); - - const GlyphQuads& glyphquad = _textureGlyphQuadMap.begin()->second; - const GlyphQuads::Glyphs& glyphs = glyphquad._glyphs; - const GlyphQuads::Coords2& coords = glyphquad._coords; - const GlyphQuads::LineNumbers& line_numbers = glyphquad._lineNumbers; - - const float HIT_FRACTION = 0.6; - const float character_width = getCharacterHeight() - * getCharacterAspectRatio(); - - float y = (line + 0.5) * line_height; - - bool line_found = false; - for(size_t i = 0; i < line_numbers.size(); ++i) - { - if( line_numbers[i] != line ) - { - if( !line_found ) - { - if( line_numbers[i] < line ) - // Wait for the correct line... - continue; - - // We have already passed the correct line -> It's empty... - return osg::Vec2(0, y); - } - - // Next line and not returned -> not before any character - // -> return position after last character of line - return osg::Vec2(coords[(i - 1) * 4 + 2].x(), y); - } - - line_found = true; - - // Get threshold for mouse x position for setting cursor before or after - // current character - float threshold = coords[i * 4].x() - + HIT_FRACTION * glyphs[i]->getHorizontalAdvance() - * character_width; - - if( pos.x() <= threshold ) - { - osg::Vec2 hit(0, y); - if( i == 0 || line_numbers[i - 1] != line ) - // first character of line - hit.x() = coords[i * 4].x(); - else if( coords[(i - 1) * 4].x() == coords[(i - 1) * 4 + 2].x() ) - // If previous character width is zero set to begin of next character - // (Happens eg. with spaces) - hit.x() = coords[i * 4].x(); - else - // position at center between characters - hit.x() = 0.5 * (coords[(i - 1) * 4 + 2].x() + coords[i * 4].x()); - - return hit; - } - } - - // Nothing found -> return position after last character - return osg::Vec2 - ( - coords.back().x(), - (_lineCount - 0.5) * line_height - ); - } - //---------------------------------------------------------------------------- osg::BoundingBox #if OSG_VERSION_LESS_THAN(3,3,2) @@ -694,10 +806,28 @@ namespace canvas return _text->sizeForWidth(INT_MAX).x(); } + //---------------------------------------------------------------------------- + size_t Text::lineCount() const + { + return _text->lineCount(); + } + + //---------------------------------------------------------------------------- + size_t Text::lineLength(size_t line) const + { + return _text->lineAt(line).size(); + } + //---------------------------------------------------------------------------- osg::Vec2 Text::getNearestCursor(const osg::Vec2& pos) const { - return _text->handleHit(pos); + return _text->nearestLine(pos.y()).nearestCursor(pos.x()); + } + + //---------------------------------------------------------------------------- + osg::Vec2 Text::getCursorPos(size_t line, size_t character) const + { + return _text->lineAt(line).cursorPos(character); } } // namespace canvas diff --git a/simgear/canvas/elements/CanvasText.hxx b/simgear/canvas/elements/CanvasText.hxx index b198d05a..738a9737 100644 --- a/simgear/canvas/elements/CanvasText.hxx +++ b/simgear/canvas/elements/CanvasText.hxx @@ -29,6 +29,7 @@ namespace simgear namespace canvas { + class TextLine; class Text: public Element { @@ -48,10 +49,19 @@ namespace canvas int heightForWidth(int w) const; int maxWidth() const; + + /// Number of text lines. + size_t lineCount() const; + + /// Number of characters in @a line. + size_t lineLength(size_t line) const; + osg::Vec2 getNearestCursor(const osg::Vec2& pos) const; + osg::Vec2 getCursorPos(size_t line, size_t character) const; protected: + friend class TextLine; class TextOSG; osg::ref_ptr _text;