2023-06-20 04:33:09 +00:00
# include "StarTextPainter.hpp"
# include "StarJsonExtra.hpp"
# include <regex>
namespace Star {
namespace Text {
2023-06-27 14:20:22 +00:00
static auto stripEscapeRegex = std : : regex ( strf ( " \\ {:c}[^;]*{:c} " , CmdEsc , EndEsc ) ) ;
2023-06-20 04:33:09 +00:00
String stripEscapeCodes ( String const & s ) {
2023-06-27 14:20:22 +00:00
return std : : regex_replace ( s . utf8 ( ) , stripEscapeRegex , " " ) ;
2023-06-20 04:33:09 +00:00
}
2023-06-28 10:08:11 +00:00
inline bool isEscapeCode ( char c ) {
return c = = CmdEsc | | c = = StartEsc ;
}
static std : : string escapeChars = strf ( " {:c}{:c} " , CmdEsc , StartEsc ) ;
typedef function < bool ( StringView text ) > TextCallback ;
typedef function < bool ( StringView commands ) > CommandsCallback ;
void processText ( StringView text , TextCallback textFunc , CommandsCallback commandsFunc = CommandsCallback ( ) , bool includeCommandSides = false ) {
std : : string_view escChars ( escapeChars ) ;
std : : string_view str = text . utf8 ( ) ;
while ( true ) {
size_t escape = str . find_first_of ( escChars ) ;
if ( escape ! = NPos ) {
escape = str . find_first_not_of ( escChars , escape ) - 1 ; // jump to the last ^
size_t end = str . find_first_of ( EndEsc , escape ) ;
if ( end ! = NPos ) {
if ( escape ! = end & & ! textFunc ( str . substr ( 0 , escape ) ) )
break ;
if ( commandsFunc ) {
StringView commands = includeCommandSides
? str . substr ( escape , end - escape + 1 )
: str . substr ( escape + 1 , end - escape - 1 ) ;
if ( ! commands . empty ( ) & & ! commandsFunc ( commands ) )
break ;
}
str = str . substr ( end + 1 ) ;
continue ;
}
}
if ( ! str . empty ( ) )
textFunc ( str ) ;
break ;
}
}
// The below two functions aren't used anymore, not bothering with StringView for them
2023-06-20 04:33:09 +00:00
String preprocessEscapeCodes ( String const & s ) {
bool escape = false ;
2023-06-28 10:08:11 +00:00
std : : string result = s . utf8 ( ) ;
2023-06-20 04:33:09 +00:00
size_t escapeStartIdx = 0 ;
for ( size_t i = 0 ; i < result . size ( ) ; i + + ) {
auto & c = result [ i ] ;
2023-06-28 10:08:11 +00:00
if ( isEscapeCode ( c ) ) {
2023-06-20 04:33:09 +00:00
escape = true ;
escapeStartIdx = i ;
}
if ( ( c < = SpecialCharLimit ) & & ! ( c = = StartEsc ) )
escape = false ;
if ( ( c = = EndEsc ) & & escape )
result [ escapeStartIdx ] = StartEsc ;
}
return { result } ;
}
String extractCodes ( String const & s ) {
bool escape = false ;
StringList result ;
String escapeCode ;
for ( auto c : preprocessEscapeCodes ( s ) ) {
if ( c = = StartEsc )
escape = true ;
if ( c = = EndEsc ) {
escape = false ;
for ( auto command : escapeCode . split ( ' , ' ) )
result . append ( command ) ;
escapeCode = " " ;
}
if ( escape & & ( c ! = StartEsc ) )
escapeCode . append ( c ) ;
}
if ( ! result . size ( ) )
return " " ;
return " ^ " + result . join ( " , " ) + " ; " ;
}
}
TextPositioning : : TextPositioning ( ) {
pos = Vec2F ( ) ;
hAnchor = HorizontalAnchor : : LeftAnchor ;
vAnchor = VerticalAnchor : : BottomAnchor ;
}
TextPositioning : : TextPositioning ( Vec2F pos , HorizontalAnchor hAnchor , VerticalAnchor vAnchor ,
Maybe < unsigned > wrapWidth , Maybe < unsigned > charLimit )
: pos ( pos ) , hAnchor ( hAnchor ) , vAnchor ( vAnchor ) , wrapWidth ( wrapWidth ) , charLimit ( charLimit ) { }
TextPositioning : : TextPositioning ( Json const & v ) {
pos = v . opt ( " position " ) . apply ( jsonToVec2F ) . value ( ) ;
hAnchor = HorizontalAnchorNames . getLeft ( v . getString ( " horizontalAnchor " , " left " ) ) ;
vAnchor = VerticalAnchorNames . getLeft ( v . getString ( " verticalAnchor " , " top " ) ) ;
wrapWidth = v . optUInt ( " wrapWidth " ) ;
charLimit = v . optUInt ( " charLimit " ) ;
}
Json TextPositioning : : toJson ( ) const {
return JsonObject {
{ " position " , jsonFromVec2F ( pos ) } ,
{ " horizontalAnchor " , HorizontalAnchorNames . getRight ( hAnchor ) } ,
{ " verticalAnchor " , VerticalAnchorNames . getRight ( vAnchor ) } ,
{ " wrapWidth " , jsonFromMaybe ( wrapWidth ) }
} ;
}
TextPositioning TextPositioning : : translated ( Vec2F translation ) const {
return { pos + translation , hAnchor , vAnchor , wrapWidth , charLimit } ;
}
2023-06-21 09:46:23 +00:00
TextPainter : : TextPainter ( RendererPtr renderer , TextureGroupPtr textureGroup )
2023-06-20 04:33:09 +00:00
: m_renderer ( renderer ) ,
2023-06-21 09:46:23 +00:00
m_fontTextureGroup ( textureGroup ) ,
2023-06-20 04:33:09 +00:00
m_fontSize ( 8 ) ,
m_lineSpacing ( 1.30f ) ,
2023-06-26 18:48:27 +00:00
m_renderSettings ( { FontMode : : Normal , Vec4B : : filled ( 255 ) , " hobo " , " " } ) ,
2023-06-20 04:33:09 +00:00
m_splitIgnore ( " \t " ) ,
m_splitForce ( " \n \v " ) ,
2023-06-21 09:46:23 +00:00
m_nonRenderedCharacters ( " \n \v \r " ) {
2023-06-21 13:13:37 +00:00
reloadFonts ( ) ;
m_reloadTracker = make_shared < TrackerListener > ( ) ;
Root : : singleton ( ) . registerReloadListener ( m_reloadTracker ) ;
2023-06-21 09:46:23 +00:00
}
2023-06-20 04:33:09 +00:00
2023-06-28 10:08:11 +00:00
RectF TextPainter : : renderText ( StringView s , TextPositioning const & position ) {
2023-06-20 04:33:09 +00:00
if ( position . charLimit ) {
unsigned charLimit = * position . charLimit ;
return doRenderText ( s , position , true , & charLimit ) ;
} else {
return doRenderText ( s , position , true , nullptr ) ;
}
}
2023-06-28 10:08:11 +00:00
RectF TextPainter : : renderLine ( StringView s , TextPositioning const & position ) {
2023-06-20 04:33:09 +00:00
if ( position . charLimit ) {
unsigned charLimit = * position . charLimit ;
return doRenderLine ( s , position , true , & charLimit ) ;
} else {
return doRenderLine ( s , position , true , nullptr ) ;
}
}
RectF TextPainter : : renderGlyph ( String : : Char c , TextPositioning const & position ) {
return doRenderGlyph ( c , position , true ) ;
}
2023-06-28 10:08:11 +00:00
RectF TextPainter : : determineTextSize ( StringView s , TextPositioning const & position ) {
2023-06-20 04:33:09 +00:00
return doRenderText ( s , position , false , nullptr ) ;
}
2023-06-28 10:08:11 +00:00
RectF TextPainter : : determineLineSize ( StringView s , TextPositioning const & position ) {
2023-06-20 04:33:09 +00:00
return doRenderLine ( s , position , false , nullptr ) ;
}
RectF TextPainter : : determineGlyphSize ( String : : Char c , TextPositioning const & position ) {
return doRenderGlyph ( c , position , false ) ;
}
int TextPainter : : glyphWidth ( String : : Char c ) {
return m_fontTextureGroup . glyphWidth ( c , m_fontSize ) ;
}
2023-06-28 10:08:11 +00:00
int TextPainter : : stringWidth ( StringView s ) {
if ( s . empty ( ) )
return 0 ;
2023-06-21 12:29:40 +00:00
String font = m_renderSettings . font , setFont = font ;
m_fontTextureGroup . switchFont ( font ) ;
2023-06-28 10:08:11 +00:00
2023-06-20 04:33:09 +00:00
int width = 0 ;
2023-06-28 10:08:11 +00:00
Text : : CommandsCallback commandsCallback = [ & ] ( StringView commands ) {
commands . forEachSplitView ( " , " , [ & ] ( StringView command , size_t , size_t ) {
if ( command = = " reset " )
m_fontTextureGroup . switchFont ( font = setFont ) ;
else if ( command = = " set " )
setFont = font ;
else if ( command . beginsWith ( " font= " ) )
m_fontTextureGroup . switchFont ( font = command . substr ( 5 ) ) ;
} ) ;
return true ;
} ;
2023-06-21 12:29:40 +00:00
2023-06-28 10:08:11 +00:00
Text : : TextCallback textCallback = [ & ] ( StringView text ) {
for ( String : : Char c : text )
2023-06-20 04:33:09 +00:00
width + = glyphWidth ( c ) ;
2023-06-28 10:08:11 +00:00
return true ;
} ;
Text : : processText ( s , textCallback , commandsCallback ) ;
2023-06-20 04:33:09 +00:00
return width ;
}
2023-06-28 10:08:11 +00:00
void TextPainter : : processWrapText ( StringView s , Maybe < unsigned > wrapWidth , WrapTextCallback textFunc , WrapCommandsCallback commandsFunc , bool includeCommandSides ) {
2023-06-21 12:29:40 +00:00
String font = m_renderSettings . font , setFont = font ;
m_fontTextureGroup . switchFont ( font ) ;
2023-06-20 04:33:09 +00:00
unsigned linePixelWidth = 0 ; // How wide is this line so far
2023-06-28 10:08:11 +00:00
int lines = 0 ;
StringView splitIgnore ( m_splitIgnore ) ;
StringView splitForce ( m_splitForce ) ;
Text : : CommandsCallback commandsCallback = [ & ] ( StringView commands ) {
StringView inner = commands . utf8 ( ) . substr ( 1 , commands . utf8Size ( ) - 1 ) ;
inner . forEachSplitView ( " , " , [ & ] ( StringView command , size_t , size_t ) {
if ( command = = " reset " )
m_fontTextureGroup . switchFont ( font = setFont ) ;
else if ( command = = " set " )
setFont = font ;
else if ( command . beginsWith ( " font= " ) )
m_fontTextureGroup . switchFont ( font = command . substr ( 5 ) ) ;
} ) ;
if ( commandsFunc )
if ( ! commandsFunc ( includeCommandSides ? commands : inner ) )
return false ;
return true ;
} ;
2023-06-20 04:33:09 +00:00
2023-06-28 10:08:11 +00:00
Text : : TextCallback textCallback = [ & ] ( StringView text ) {
unsigned lineCharSize = 0 ; // how many characters in this line ?
unsigned lineStart = 0 ; // Where does this line start ?
unsigned splitPos = 0 ; // Where did we last see a place to split the string ?
unsigned splitWidth = 0 ; // How wide was the string there ?
for ( auto character : text ) {
2023-06-20 04:33:09 +00:00
lineCharSize + + ; // assume at least one character if we get here.
// is this a linefeed / cr / whatever that forces a line split ?
2023-06-28 10:08:11 +00:00
if ( splitForce . find ( character ) ! = NPos ) {
2023-06-20 04:33:09 +00:00
// knock one off the end because we don't render the CR
2023-06-28 10:08:11 +00:00
if ( ! textFunc ( text . substr ( lineStart , lineCharSize - 1 ) , lines + + ) )
return false ;
2023-06-20 04:33:09 +00:00
lineStart + = lineCharSize ; // next line starts after the CR
lineCharSize = 0 ; // with no characters in it.
linePixelWidth = 0 ; // No width
splitPos = 0 ; // and no known splits.
} else {
int charWidth = glyphWidth ( character ) ;
// is it a place where we might want to split the line ?
2023-06-28 10:08:11 +00:00
if ( splitIgnore . find ( character ) ! = NPos ) {
2023-06-20 04:33:09 +00:00
splitPos = lineStart + lineCharSize ; // this is the character after the space.
splitWidth = linePixelWidth + charWidth ; // the width of the string at
// the split point, i.e. after the space.
}
// would the line be too long if we render this next character ?
if ( wrapWidth & & ( linePixelWidth + charWidth ) > * wrapWidth ) {
// did we find somewhere to split the line ?
if ( splitPos ) {
2023-06-28 10:08:11 +00:00
if ( ! textFunc ( text . substr ( lineStart , ( splitPos - lineStart ) - 1 ) , lines + + ) )
return false ;
2023-06-20 04:33:09 +00:00
unsigned stringEnd = lineStart + lineCharSize ;
lineCharSize = stringEnd - splitPos ; // next line has the characters after the space.
unsigned stringWidth = ( linePixelWidth - splitWidth ) ;
linePixelWidth = stringWidth + charWidth ; // and is as wide as the bit after the space.
lineStart = splitPos ; // next line starts after the space
splitPos = 0 ; // split is used up.
} else {
2023-06-28 10:08:11 +00:00
if ( ! textFunc ( text . substr ( lineStart , lineCharSize - 1 ) , lines + + ) )
return false ;
2023-06-20 04:33:09 +00:00
lineStart + = lineCharSize - 1 ; // skip back by one to include that
2023-06-28 10:08:11 +00:00
// character on the next line.
lineCharSize = 1 ; // next line has that character in
2023-06-20 04:33:09 +00:00
linePixelWidth = charWidth ; // and is as wide as that character
}
} else {
linePixelWidth + = charWidth ;
}
}
}
2023-06-28 10:08:11 +00:00
// if we hit the end of the string before hitting the end of the line.
if ( lineCharSize > 0 & & ! textFunc ( text . substr ( lineStart , lineCharSize ) , lines ) )
return false ;
2023-06-20 04:33:09 +00:00
2023-06-28 10:08:11 +00:00
return true ;
} ;
Text : : processText ( s , textCallback , commandsCallback , true ) ;
2023-06-20 04:33:09 +00:00
}
2023-06-28 10:08:11 +00:00
List < StringView > TextPainter : : wrapTextViews ( StringView s , Maybe < unsigned > wrapWidth ) {
List < StringView > views = { } ;
bool active = false ;
StringView current ;
int lastLine = 0 ;
auto addText = [ & active , & current ] ( StringView text ) {
// Merge views if they are adjacent
if ( active & & current . utf8Ptr ( ) + current . utf8Size ( ) = = text . utf8Ptr ( ) )
current = StringView ( current . utf8Ptr ( ) , current . utf8Size ( ) + text . utf8Size ( ) ) ;
else
current = text ;
active = true ;
} ;
TextPainter : : WrapTextCallback textCallback = [ & ] ( StringView text , int line ) {
if ( lastLine ! = line ) {
views . push_back ( current ) ;
lastLine = line ;
active = false ;
}
addText ( text ) ;
return true ;
} ;
TextPainter : : WrapCommandsCallback commandsCallback = [ & ] ( StringView commands ) {
addText ( commands ) ;
return true ;
} ;
processWrapText ( s , wrapWidth , textCallback , commandsCallback , true ) ;
if ( active )
views . push_back ( current ) ;
return views ;
}
StringList TextPainter : : wrapText ( StringView s , Maybe < unsigned > wrapWidth ) {
StringList result ;
String current ;
int lastLine = 0 ;
TextPainter : : WrapTextCallback textCallback = [ & ] ( StringView text , int line ) {
if ( lastLine ! = line ) {
result . append ( move ( current ) ) ;
lastLine = line ;
}
current + = text ;
return true ;
} ;
TextPainter : : WrapCommandsCallback commandsCallback = [ & ] ( StringView commands ) {
current + = commands ;
return true ;
} ;
processWrapText ( s , wrapWidth , textCallback , commandsCallback , true ) ;
if ( ! current . empty ( ) )
result . append ( move ( current ) ) ;
return result ;
} ;
2023-06-20 04:33:09 +00:00
unsigned TextPainter : : fontSize ( ) const {
return m_fontSize ;
}
void TextPainter : : setFontSize ( unsigned size ) {
m_fontSize = size ;
}
void TextPainter : : setLineSpacing ( float lineSpacing ) {
m_lineSpacing = lineSpacing ;
}
void TextPainter : : setMode ( FontMode mode ) {
m_renderSettings . mode = mode ;
}
void TextPainter : : setSplitIgnore ( String const & splitIgnore ) {
m_splitIgnore = splitIgnore ;
}
void TextPainter : : setFontColor ( Vec4B color ) {
m_renderSettings . color = move ( color ) ;
}
void TextPainter : : setProcessingDirectives ( String directives ) {
2023-06-21 10:29:23 +00:00
m_renderSettings . directives = move ( directives ) ;
2023-06-20 04:33:09 +00:00
}
2023-06-21 09:46:23 +00:00
void TextPainter : : setFont ( String const & font ) {
2023-06-21 12:29:40 +00:00
m_renderSettings . font = font ;
2023-06-21 09:46:23 +00:00
}
void TextPainter : : addFont ( FontPtr const & font , String const & name ) {
m_fontTextureGroup . addFont ( font , name ) ;
}
2023-06-21 13:13:37 +00:00
void TextPainter : : reloadFonts ( ) {
m_fontTextureGroup . clearFonts ( ) ;
m_fontTextureGroup . cleanup ( 0 ) ;
auto assets = Root : : singleton ( ) . assets ( ) ;
auto defaultFont = assets - > font ( " /hobo.ttf " ) ;
for ( auto & fontPath : assets - > scanExtension ( " ttf " ) ) {
auto font = assets - > font ( fontPath ) ;
if ( font = = defaultFont )
continue ;
auto fileName = AssetPath : : filename ( fontPath ) ;
addFont ( font - > clone ( ) , fileName . substr ( 0 , fileName . findLast ( " . " ) ) ) ;
}
m_fontTextureGroup . addFont ( defaultFont - > clone ( ) , " hobo " , true ) ;
}
2023-06-20 04:33:09 +00:00
void TextPainter : : cleanup ( int64_t timeout ) {
m_fontTextureGroup . cleanup ( timeout ) ;
}
2023-06-28 10:08:11 +00:00
void TextPainter : : applyCommands ( StringView unsplitCommands ) {
unsplitCommands . forEachSplitView ( " , " , [ & ] ( StringView command , size_t , size_t ) {
2023-06-23 09:32:41 +00:00
try {
if ( command = = " reset " ) {
m_renderSettings = m_savedRenderSettings ;
} else if ( command = = " set " ) {
m_savedRenderSettings = m_renderSettings ;
} else if ( command = = " shadow " ) {
m_renderSettings . mode = ( FontMode ) ( ( int ) m_renderSettings . mode | ( int ) FontMode : : Shadow ) ;
} else if ( command = = " noshadow " ) {
m_renderSettings . mode = ( FontMode ) ( ( int ) m_renderSettings . mode & ( - 1 ^ ( int ) FontMode : : Shadow ) ) ;
} else if ( command . beginsWith ( " font= " ) ) {
m_renderSettings . font = command . substr ( 5 ) ;
} else if ( command . beginsWith ( " directives= " ) ) {
// Honestly this is really stupid but I just couldn't help myself
// Should probably limit in the future
m_renderSettings . directives = command . substr ( 11 ) ;
} else {
// expects both #... sequences and plain old color names.
2023-06-28 10:08:11 +00:00
Color c = Color ( command ) ;
2023-06-23 09:32:41 +00:00
c . setAlphaF ( c . alphaF ( ) * ( ( float ) m_savedRenderSettings . color [ 3 ] ) / 255 ) ;
m_renderSettings . color = c . toRgba ( ) ;
}
} catch ( JsonException & ) {
} catch ( ColorException & ) {
}
2023-06-28 10:08:11 +00:00
} ) ;
2023-06-23 09:32:41 +00:00
}
2023-06-28 10:08:11 +00:00
RectF TextPainter : : doRenderText ( StringView s , TextPositioning const & position , bool reallyRender , unsigned * charLimit ) {
2023-06-20 04:33:09 +00:00
Vec2F pos = position . pos ;
2023-06-28 10:08:11 +00:00
if ( s . empty ( ) )
return RectF ( pos , pos ) ;
List < StringView > lines = wrapTextViews ( s , position . wrapWidth ) ;
2023-06-20 04:33:09 +00:00
int height = ( lines . size ( ) - 1 ) * m_lineSpacing * m_fontSize + m_fontSize ;
2023-06-23 09:32:41 +00:00
RenderSettings backupRenderSettings = m_renderSettings ;
2023-06-20 04:33:09 +00:00
m_savedRenderSettings = m_renderSettings ;
if ( position . vAnchor = = VerticalAnchor : : BottomAnchor )
pos [ 1 ] + = ( height - m_fontSize ) ;
else if ( position . vAnchor = = VerticalAnchor : : VMidAnchor )
pos [ 1 ] + = ( height - m_fontSize ) / 2 ;
RectF bounds = RectF : : withSize ( pos , Vec2F ( ) ) ;
2023-06-28 10:08:11 +00:00
for ( auto & i : lines ) {
2023-06-20 04:33:09 +00:00
bounds . combine ( doRenderLine ( i , { pos , position . hAnchor , position . vAnchor } , reallyRender , charLimit ) ) ;
pos [ 1 ] - = m_fontSize * m_lineSpacing ;
if ( charLimit & & * charLimit = = 0 )
break ;
}
2023-06-23 09:32:41 +00:00
m_renderSettings = move ( backupRenderSettings ) ;
2023-06-20 04:33:09 +00:00
return bounds ;
}
2023-06-28 10:08:11 +00:00
RectF TextPainter : : doRenderLine ( StringView text , TextPositioning const & position , bool reallyRender , unsigned * charLimit ) {
2023-06-21 13:13:37 +00:00
if ( m_reloadTracker - > pullTriggered ( ) )
reloadFonts ( ) ;
2023-06-20 04:33:09 +00:00
TextPositioning pos = position ;
2023-06-21 12:29:40 +00:00
2023-06-20 04:33:09 +00:00
if ( pos . hAnchor = = HorizontalAnchor : : RightAnchor ) {
2023-06-28 10:08:11 +00:00
StringView trimmedString = charLimit ? text . substr ( 0 , * charLimit ) : text ;
2023-06-20 04:33:09 +00:00
pos . pos [ 0 ] - = stringWidth ( trimmedString ) ;
pos . hAnchor = HorizontalAnchor : : LeftAnchor ;
} else if ( pos . hAnchor = = HorizontalAnchor : : HMidAnchor ) {
2023-06-28 10:08:11 +00:00
StringView trimmedString = charLimit ? text . substr ( 0 , * charLimit ) : text ;
pos . pos [ 0 ] - = std : : floorf ( ( float ) stringWidth ( trimmedString ) / 2 ) ;
2023-06-20 04:33:09 +00:00
pos . hAnchor = HorizontalAnchor : : LeftAnchor ;
}
bool escape = false ;
String escapeCode ;
RectF bounds = RectF : : withSize ( pos . pos , Vec2F ( ) ) ;
2023-06-28 10:08:11 +00:00
Text : : TextCallback textCallback = [ & ] ( StringView text ) {
for ( String : : Char c : text ) {
2023-06-20 04:33:09 +00:00
if ( charLimit ) {
if ( * charLimit = = 0 )
2023-06-28 10:08:11 +00:00
return false ;
2023-06-20 04:33:09 +00:00
else
2023-06-28 10:08:11 +00:00
- - * charLimit ;
2023-06-20 04:33:09 +00:00
}
2023-06-28 10:08:11 +00:00
2023-06-20 04:33:09 +00:00
RectF glyphBounds = doRenderGlyph ( c , pos , reallyRender ) ;
bounds . combine ( glyphBounds ) ;
pos . pos [ 0 ] + = glyphBounds . width ( ) ;
}
2023-06-28 10:08:11 +00:00
return true ;
} ;
Text : : CommandsCallback commandsCallback = [ & ] ( StringView commands ) {
applyCommands ( commands ) ;
return true ;
} ;
Text : : processText ( text , textCallback , commandsCallback ) ;
2023-06-20 04:33:09 +00:00
return bounds ;
}
RectF TextPainter : : doRenderGlyph ( String : : Char c , TextPositioning const & position , bool reallyRender ) {
if ( m_nonRenderedCharacters . find ( String ( c ) ) ! = NPos )
return RectF ( ) ;
2023-06-21 12:29:40 +00:00
m_fontTextureGroup . switchFont ( m_renderSettings . font ) ;
2023-06-20 04:33:09 +00:00
int width = glyphWidth ( c ) ;
// Offset left by width if right anchored.
float hOffset = 0 ;
if ( position . hAnchor = = HorizontalAnchor : : RightAnchor )
hOffset = - width ;
else if ( position . hAnchor = = HorizontalAnchor : : HMidAnchor )
2023-06-28 10:08:11 +00:00
hOffset = - std : : floorf ( ( float ) width / 2 ) ;
2023-06-20 04:33:09 +00:00
float vOffset = 0 ;
if ( position . vAnchor = = VerticalAnchor : : VMidAnchor )
2023-06-28 10:08:11 +00:00
vOffset = - std : : floorf ( ( float ) m_fontSize / 2 ) ;
2023-06-20 04:33:09 +00:00
else if ( position . vAnchor = = VerticalAnchor : : TopAnchor )
vOffset = - ( float ) m_fontSize ;
if ( reallyRender ) {
if ( ( int ) m_renderSettings . mode & ( int ) FontMode : : Shadow ) {
Color shadow = Color : : Black ;
2023-06-20 14:59:41 +00:00
uint8_t alphaU = m_renderSettings . color [ 3 ] ;
if ( alphaU ! = 255 ) {
float alpha = byteToFloat ( alphaU ) ;
shadow . setAlpha ( floatToByte ( alpha * ( 1.5f - 0.5f * alpha ) ) ) ;
}
else
shadow . setAlpha ( alphaU ) ;
//Kae: Draw only one shadow glyph instead of stacking two, alpha modified to appear perceptually the same as vanilla
2023-06-21 10:29:23 +00:00
renderGlyph ( c , position . pos + Vec2F ( hOffset , vOffset - 2 ) , m_fontSize , 1 , shadow . toRgba ( ) , m_renderSettings . directives ) ;
2023-06-20 04:33:09 +00:00
}
2023-06-21 10:29:23 +00:00
renderGlyph ( c , position . pos + Vec2F ( hOffset , vOffset ) , m_fontSize , 1 , m_renderSettings . color , m_renderSettings . directives ) ;
2023-06-20 04:33:09 +00:00
}
return RectF : : withSize ( position . pos + Vec2F ( hOffset , vOffset ) , { ( float ) width , ( int ) m_fontSize } ) ;
}
void TextPainter : : renderGlyph ( String : : Char c , Vec2F const & screenPos , unsigned fontSize ,
float scale , Vec4B const & color , String const & processingDirectives ) {
if ( ! fontSize )
return ;
2023-06-20 14:59:41 +00:00
const FontTextureGroup : : GlyphTexture & glyphTexture = m_fontTextureGroup . glyphTexture ( c , fontSize , processingDirectives ) ;
Vec2F offset = glyphTexture . processingOffset * ( scale * 0.5f ) ; //Kae: Re-center the glyph if the image scale was changed by the directives (it is positioned from the bottom left)
m_renderer - > render ( renderTexturedRect ( glyphTexture . texture , Vec2F ( screenPos ) + offset , scale , color , 0.0f ) ) ;
2023-06-20 04:33:09 +00:00
}
}