Text::setFillColor improvements for shaped text.

This commit is contained in:
Martín Lucas Golini
2026-01-03 18:38:33 -03:00
parent 45e1a9bf54
commit b084cab449
8 changed files with 1218 additions and 999 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -1 +1 @@
مَرْحَبًا بِالْعَالَم
مرحباً بالعالم

View File

@@ -188,6 +188,8 @@ class EE_API Text {
void setFillColor( const Color& color, Uint32 from, Uint32 to );
void setFillColor( const std::vector<Color>& colors );
void setOutlineColor( const Color& color );
void setOutlineThickness( Float thickness );

View File

@@ -1561,8 +1561,8 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl
invalidate();
}
ensureColorUpdate();
ensureGeometryUpdate();
ensureColorUpdate();
if ( mFontStyleConfig.Style & Shadow ) {
std::vector<Color> colors;
@@ -2087,8 +2087,15 @@ void Text::setFillColor( const Color& color, Uint32 from, Uint32 to ) {
if ( mString.empty() )
return;
ensureGeometryUpdate();
ensureColorUpdate();
size_t numVerts = mVertices.size();
if ( mColors.size() < numVerts ) {
mColors.resize( numVerts, mFontStyleConfig.FontColor );
mColorsNeedUpdate = false;
}
bool underlined = ( mFontStyleConfig.Style & Underlined ) != 0;
bool strikeThrough = ( mFontStyleConfig.Style & StrikeThrough ) != 0;
std::size_t s = mString.size();
@@ -2098,6 +2105,41 @@ void Text::setFillColor( const Color& color, Uint32 from, Uint32 to ) {
}
if ( from <= to && from < s && to <= s ) {
#ifdef EE_TEXT_SHAPER_ENABLED
if ( TextShaperEnabled && mFontStyleConfig.Font->getType() == FontType::TTF &&
!canSkipShaping( mTextHints ) ) {
FontTrueType* rFont = static_cast<FontTrueType*>( mFontStyleConfig.Font );
auto layout = TextLayout::layout( mString, rFont, mFontStyleConfig.CharacterSize,
mFontStyleConfig.Style, mTabWidth,
mFontStyleConfig.OutlineThickness );
size_t vIdx = 0;
bool bold = ( mFontStyleConfig.Style & Bold ) != 0;
bool italic = ( mFontStyleConfig.Style & Italic ) != 0;
for ( const ShapedGlyph& sg : layout->shapedGlyphs ) {
if ( mString[sg.stringIndex] == '\t' )
continue;
Glyph glyph =
sg.font->getGlyphByIndex( sg.glyphIndex, mFontStyleConfig.CharacterSize, bold,
italic, mFontStyleConfig.OutlineThickness,
rFont->getPage( mFontStyleConfig.CharacterSize ) );
if ( glyph.bounds.Right > 0 && glyph.bounds.Bottom > 0 ) {
if ( vIdx + GLi->quadVertex() <= mColors.size() && sg.stringIndex >= from &&
sg.stringIndex <= to ) {
for ( int i = 0; i < GLi->quadVertex(); ++i )
mColors[vIdx + i] = color;
}
vIdx += GLi->quadVertex();
}
}
mColorsNeedUpdate = false;
return;
}
#endif
size_t realTo = to + 1;
Int32 rpos = from;
Int32 lpos = 0;
@@ -2178,6 +2220,78 @@ void Text::setFillColor( const Color& color, Uint32 from, Uint32 to ) {
}
}
void Text::setFillColor( const std::vector<Color>& colors ) {
if ( mString.empty() || colors.empty() )
return;
ensureGeometryUpdate();
ensureColorUpdate();
size_t numVerts = mVertices.size();
if ( mColors.size() < numVerts ) {
mColors.resize( numVerts, mFontStyleConfig.FontColor );
mColorsNeedUpdate = false;
}
#ifdef EE_TEXT_SHAPER_ENABLED
if ( TextShaperEnabled && mFontStyleConfig.Font->getType() == FontType::TTF &&
!canSkipShaping( mTextHints ) ) {
FontTrueType* rFont = static_cast<FontTrueType*>( mFontStyleConfig.Font );
auto layout = TextLayout::layout( mString, rFont, mFontStyleConfig.CharacterSize,
mFontStyleConfig.Style, mTabWidth,
mFontStyleConfig.OutlineThickness );
size_t vIdx = 0;
bool bold = ( mFontStyleConfig.Style & Bold ) != 0;
bool italic = ( mFontStyleConfig.Style & Italic ) != 0;
for ( const ShapedGlyph& sg : layout->shapedGlyphs ) {
if ( mString[sg.stringIndex] == '\t' )
continue;
Glyph glyph =
sg.font->getGlyphByIndex( sg.glyphIndex, mFontStyleConfig.CharacterSize, bold,
italic, mFontStyleConfig.OutlineThickness,
rFont->getPage( mFontStyleConfig.CharacterSize ) );
if ( glyph.bounds.Right > 0 && glyph.bounds.Bottom > 0 ) {
if ( vIdx + GLi->quadVertex() <= mColors.size() &&
sg.stringIndex < colors.size() ) {
Color color = colors[sg.stringIndex];
if ( mContainsColorEmoji && Font::isEmojiCodePoint( mString[sg.stringIndex] ) )
color = Color( 255, 255, 255, color.a );
for ( int i = 0; i < GLi->quadVertex(); ++i )
mColors[vIdx + i] = color;
vIdx += GLi->quadVertex();
}
}
}
mColorsNeedUpdate = false;
return;
}
#endif
size_t s = mString.size();
size_t vIdx = 0;
for ( size_t i = 0; i < s; i++ ) {
String::StringBaseType curChar = mString[i];
if ( ' ' == curChar || '\n' == curChar || '\t' == curChar || '\r' == curChar )
continue;
if ( vIdx + GLi->quadVertex() <= mColors.size() && i < colors.size() ) {
Color color = colors[i];
if ( mContainsColorEmoji && Font::isEmojiCodePoint( curChar ) )
color = Color( 255, 255, 255, color.a );
for ( int v = 0; v < GLi->quadVertex(); v++ )
mColors[vIdx + v] = color;
vIdx += GLi->quadVertex();
}
}
mColorsNeedUpdate = false;
}
// Add an underline or strikethrough line to the vertex array
void Text::addLine( std::vector<VertexCoords>& vertices, Float lineLength, Float lineTop,
Float offset, Float thickness, Float outlineThickness, Int32 centerDiffX ) {

View File

@@ -951,17 +951,24 @@ Text* SyntaxTokenizer::tokenizeText( const SyntaxDefinition& syntax,
text->setString( txt );
}
std::vector<Color> colors( text->getString().size(), text->getFillColor() );
size_t start = startIndex;
for ( const auto& token : tokens ) {
if ( start < endIndex ) {
if ( token.len > 0 )
text->setFillColor( colorScheme.getSyntaxStyle( token.type ).color, start,
std::min( start + token.len, endIndex ) );
if ( token.len > 0 ) {
Color color = colorScheme.getSyntaxStyle( token.type ).color;
size_t end = std::min( start + token.len, endIndex );
if ( end > colors.size() )
end = colors.size();
for ( size_t i = start; i < end; i++ )
colors[i] = color;
}
start += token.len;
} else {
break;
}
}
text->setFillColor( colors );
return text;
}

View File

@@ -728,3 +728,70 @@ cupidatat non proident👽, sunt in culpa qui officia deserunt mollit anim id es
Engine::destroySingleton();
}
UTEST( FontRendering, textSetFillColor ) {
auto win = Engine::instance()->createWindow(
WindowSettings( 1024, 230, "eepp - Text Set Fill Color", WindowStyle::Default,
WindowBackend::Default, 32, {}, 1, false, true ) );
ASSERT_TRUE_MSG( win->isOpen(), "Failed to create Window" );
UTEST_PRINT_INFO( GLi->getRenderer().c_str() );
win->setClearColor( RGB( 230, 230, 230 ) );
FontTrueType* arabicFont =
FontTrueType::New( "NotoNaskhArabic-Regular", "assets/fonts/NotoNaskhArabic-Regular.ttf" );
Text text;
text.setFont( arabicFont );
text.setFontSize( 64 );
text.setAlign( TEXT_ALIGN_CENTER );
std::string arabicTxtUtf8;
FileSystem::fileGet( "assets/textfiles/test-arabic-simple.uext", arabicTxtUtf8 );
String arabicTxt( arabicTxtUtf8 );
text.setString( arabicTxt );
text.setFillColor( Color::Black );
const auto runTest = [&]( std::string_view testName ) {
win->clear();
text.draw( 0, win->getHeight() * 0.5f - text.getTextHeight() * 0.5f );
compareImages( utest_state, utest_result, win,
std::string( "eepp-text-set-fill-color-" ) + std::string( testName ) );
};
UTEST_PRINT_STEP( "Text Shaper enabled" );
{
BoolScopedOp op( Text::TextShaperEnabled, true );
// Test Vector Fill
{
std::vector<Color> colors;
for ( size_t i = 0; i < arabicTxt.size(); i++ ) {
// Alternating colors
if ( i % 3 == 0 )
colors.push_back( Color::Red );
else if ( i % 3 == 1 )
colors.push_back( Color::Green );
else
colors.push_back( Color::Blue );
}
text.setFillColor( colors );
runTest( "vector" );
}
// Test Range Fill
{
text.setFillColor( Color::Black );
// Color "World" (بالعالم) in Red. It's at the end of the string.
// "مرحباً" (Hello) is 6 chars + space = 7.
// "بالعالم" (World) starts at index 7.
if ( arabicTxt.size() > 7 ) {
text.setFillColor( Color::Red, 7, arabicTxt.size() );
}
runTest( "range" );
}
}
Engine::destroySingleton();
}