More fixes for RTL.

This commit is contained in:
Martín Lucas Golini
2025-11-12 21:42:41 -03:00
parent 0e1350337b
commit a1b08cdc38
3 changed files with 52 additions and 31 deletions

View File

@@ -15,6 +15,7 @@ struct TextLayout {
std::vector<ShapedGlyph> shapedGlyphs;
std::vector<Float> linesWidth;
Sizef size;
bool isRTL{ false };
};
class EE_API TextLayouter {

View File

@@ -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>{} );
Float advance = Text::tabAdvance( hspace, tabWidth,
tabOffset ? msg->position.x + *tabOffset
: std::optional<Float>{} );
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 );
}
}

View File

@@ -58,7 +58,8 @@ static SBScriptLocator* getThreadLocalSbScriptLocator() {
}
// Split string into segments with uniform text properties
template <typename Callable> static void segmentString( String::View input, Callable cb ) {
template <typename Callable>
static void segmentString( TextLayout& result, String::View input, Callable cb ) {
const SBCodepointSequence codepointSequence{
SBStringEncodingUTF32, static_cast<const void*>( input.data() ), input.size() };
auto* const scriptLocator = getThreadLocalSbScriptLocator();
@@ -109,6 +110,9 @@ template <typename Callable> 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 <typename Callable> static void segmentString( String::View input, Call
}
template <typename Callable>
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<FontTrueType*>( 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<Float>{} );
ShapedGlyph sg;
sg.font = currentRunFont;
sg.glyphIndex = glyphInfo[i].codepoint;
if ( spaceGlyphIndex == 0 && font->getType() == FontType::TTF ) {
spaceGlyphIndex =
static_cast<FontTrueType*>( 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;
}
}