diff --git a/include/eepp/graphics/textlayouter.hpp b/include/eepp/graphics/textlayouter.hpp index 63904e8e1..31e584f07 100644 --- a/include/eepp/graphics/textlayouter.hpp +++ b/include/eepp/graphics/textlayouter.hpp @@ -15,6 +15,7 @@ struct TextLayout { std::vector shapedGlyphs; std::vector linesWidth; Sizef size; + bool isRTL{ false }; }; class EE_API TextLayouter { diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp index ddc308658..b15d3e84c 100644 --- a/src/eepp/graphics/text.cpp +++ b/src/eepp/graphics/text.cpp @@ -1034,16 +1034,20 @@ Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fo csg = &sg; } - if ( sg.stringIndex == index ) + if ( sg.stringIndex == index ) { + if ( layout.isRTL ) + return ( sg.position + sg.advance ).trunc(); return sg.position.trunc(); + } } - if ( !layout.shapedGlyphs.empty() && index >= maxStringIndex + 1 && msg ) { + if ( !layout.shapedGlyphs.empty() && !layout.isRTL && index >= maxStringIndex + 1 && msg ) { Glyph metrics = msg->font->getGlyphByIndex( msg->glyphIndex, fontSize, bold, italic, outlineThickness ); if ( string[msg->stringIndex] == '\t' ) { - Float advance = Text::tabAdvance( - hspace, tabWidth, tabOffset ? *tabOffset : std::optional{} ); + Float advance = Text::tabAdvance( hspace, tabWidth, + tabOffset ? msg->position.x + *tabOffset + : std::optional{} ); return ( msg->position + Vector2f{ advance, 0 } ).trunc(); } return ( msg->position + Vector2f{ metrics.advance, 0 } ).trunc(); @@ -1143,7 +1147,8 @@ Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font* Float charRight; bool isLastOnLine = - ( i + 1 == sgs || layout.shapedGlyphs[i + 1].position.y != sg.position.y ); + i + 1 == sgs || + ( !layout.isRTL && layout.shapedGlyphs[i + 1].position.y != sg.position.y ); if ( !isLastOnLine ) { // The cell extends to the beginning of the next glyph @@ -1162,13 +1167,24 @@ Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font* // --- Direct Hit Test --- // Check if the point is within the vertical bounds of the current line if ( fpos.y >= charTop && fpos.y <= charBottom ) { - auto findNextInsertionIndex = [&]() -> Int32 { + auto findNextInsertionIndex = []( size_t i, size_t sgs, const ShapedGlyph& sg, + const TextLayout& layout, auto& tSize ) -> Int32 { // Return insertion point after this glyph. Find the next distinct stringIndex. - for ( size_t j = i + 1; j < sgs; ++j ) { - if ( layout.shapedGlyphs[j].stringIndex > sg.stringIndex ) - return layout.shapedGlyphs[j].stringIndex; + if ( layout.isRTL ) { + if ( i > 0 ) { + for ( Int64 j = (Int64)i - 1; j >= 0; j-- ) { + if ( layout.shapedGlyphs[j].stringIndex > sg.stringIndex ) + return layout.shapedGlyphs[j].stringIndex; + } + } + return 0; // Reached the end + } else { + for ( size_t j = i + 1; j < sgs; ++j ) { + if ( layout.shapedGlyphs[j].stringIndex > sg.stringIndex ) + return layout.shapedGlyphs[j].stringIndex; + } + return tSize; // Reached the end } - return tSize; // Reached the end }; // Case 1: Point is within the horizontal bounds of this glyph's cell @@ -1177,12 +1193,12 @@ Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font* if ( fpos.x < midPoint ) { return sg.stringIndex; } else { - return findNextInsertionIndex(); + return findNextInsertionIndex( i, sgs, sg, layout, tSize ); } } // Case 2: Point is to the right of the last glyph on the line else if ( isLastOnLine && fpos.x >= charRight ) { - return findNextInsertionIndex(); + return findNextInsertionIndex( i, sgs, sg, layout, tSize ); } } diff --git a/src/eepp/graphics/textlayouter.cpp b/src/eepp/graphics/textlayouter.cpp index dcb54e330..51e53f630 100644 --- a/src/eepp/graphics/textlayouter.cpp +++ b/src/eepp/graphics/textlayouter.cpp @@ -58,7 +58,8 @@ static SBScriptLocator* getThreadLocalSbScriptLocator() { } // Split string into segments with uniform text properties -template static void segmentString( String::View input, Callable cb ) { +template +static void segmentString( TextLayout& result, String::View input, Callable cb ) { const SBCodepointSequence codepointSequence{ SBStringEncodingUTF32, static_cast( input.data() ), input.size() }; auto* const scriptLocator = getThreadLocalSbScriptLocator(); @@ -109,6 +110,9 @@ template static void segmentString( String::View input, Call SBScriptLocatorReset( scriptLocator ); } + if ( runCount > 0 && paragraphOffset == 0 ) + result.isRTL = ( runArray[0].level % 2 ) != 0; + SBLineRelease( line ); SBParagraphRelease( paragraph ); @@ -119,12 +123,12 @@ template static void segmentString( String::View input, Call } template -static void shapeAndRun( const String& string, FontTrueType* font, Uint32 characterSize, - Uint32 style, Float outlineThickness, Callable cb ) { +static void shapeAndRun( TextLayout& result, const String& string, FontTrueType* font, + Uint32 characterSize, Uint32 style, Float outlineThickness, Callable cb ) { String::View input = string.view(); hb_buffer_t* hbBuffer = getThreadLocalHbBuffer(); - segmentString( input, [&]( const TextSegment& segment ) { + segmentString( result, input, [&]( const TextSegment& segment ) { TextShapeRun run( input.substr( segment.offset, segment.length ), font, characterSize, style, outlineThickness, segment.direction == HB_DIRECTION_RTL ); @@ -225,6 +229,7 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin bool bold = ( style & Text::Bold ) != 0; bool italic = ( style & Text::Italic ) != 0; + Uint32 spaceGlyphIndex = 0; Float hspace = font->getGlyph( ' ', characterSize, bold, italic, outlineThickness ).advance; Float vspace = font->getLineSpacing( characterSize ); Vector2f pen; @@ -235,7 +240,7 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin !Text::canSkipShaping( textDrawHints ) ) { FontTrueType* rFont = static_cast( font ); shapeAndRun( - string, rFont, characterSize, style, outlineThickness, + result, string, rFont, characterSize, style, outlineThickness, [&]( hb_glyph_info_t* glyphInfo, hb_glyph_position_t* glyphPos, Uint32 glyphCount, const hb_segment_properties_t& props, const TextSegment& segment, TextShapeRun& run ) { @@ -256,7 +261,11 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin : std::optional{} ); ShapedGlyph sg; sg.font = currentRunFont; - sg.glyphIndex = glyphInfo[i].codepoint; + if ( spaceGlyphIndex == 0 && font->getType() == FontType::TTF ) { + spaceGlyphIndex = + static_cast( font )->getGlyphIndex( ' ' ); + } + sg.glyphIndex = spaceGlyphIndex; sg.stringIndex = segment.offset + run.pos() + cluster; sg.position = pen; sg.advance = { advance, 0 }; @@ -280,12 +289,9 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin sg.font = currentRunFont; sg.glyphIndex = glyphInfo[i].codepoint; sg.stringIndex = segment.offset + run.pos() + glyphInfo[i].cluster; - - float offsetX = glyphPos[i].x_offset / 64.f; - float offsetY = glyphPos[i].y_offset / 64.f; - sg.advance = { offsetX, offsetY }; - sg.position.x = pen.x + offsetX; - sg.position.y = pen.y - offsetY; + sg.advance = { currentGlyph.advance, 0 }; + sg.position.x = pen.x + ( glyphPos[i].x_offset / 64.f ); + sg.position.y = pen.y - ( glyphPos[i].y_offset / 64.f ); result.shapedGlyphs.emplace_back( std::move( sg ) ); pen.x += currentGlyph.advance; @@ -317,19 +323,17 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin sg.font = currentRunFont; sg.glyphIndex = glyphInfo[i].codepoint; sg.stringIndex = segment.offset + run.pos() + glyphInfo[i].cluster; - float offsetX = glyphPos[i].x_offset / 64.f; - float offsetY = glyphPos[i].y_offset / 64.f; - sg.advance = { offsetX, offsetY }; - sg.position.x = std::round( pen.x + offsetX ); - sg.position.y = std::round( pen.y - offsetY ); + sg.advance = { glyphPos[i].x_advance / 64.f, glyphPos[i].y_advance / 64.f }; + sg.position.x = std::round( pen.x + ( glyphPos[i].x_offset / 64.f ) ); + sg.position.y = std::round( pen.y - ( glyphPos[i].y_offset / 64.f ) ); result.shapedGlyphs.emplace_back( std::move( sg ) ); pen.x += Font::isEmojiCodePoint( ch ) ? currentRunFont ->getGlyphByIndex( glyphInfo[i].codepoint, characterSize, bold, italic, outlineThickness ) .advance - : glyphPos[i].x_advance / 64.f; - pen.y += glyphPos[i].y_advance / 64.f; + : sg.advance.x; + pen.y += sg.advance.y; } }