fix fallback font not scaled correctly

This commit is contained in:
fibref
2026-08-15 18:11:40 +08:00
parent cf05034d7a
commit 32d3f69d00
6 changed files with 72 additions and 47 deletions
+27 -14
View File
@@ -37,14 +37,23 @@ void Font::GetCharacter(Char c, FontCharacterEntry& result, bool enableFallback)
{
// Try to get the character or cache it if cannot be found
const auto key = Pair<float, Char>(_asset->GetOptions().RasterMode == FontRasterMode::MSDF ? _asset->GetOptions().MSDFSize : GetSize(), c);
if (!_asset->_characterCache.TryGet(key, result))
if (_asset->_characterCache.TryGet(key, result))
{
// With MSDF font introduced, cached entry may be created by a different font (with same MSDFSize)
// This is to ensure returned entry has a reference to a font whose size matches the font being used to render
result.Font = this;
}
else
{
// This thread race condition may happen in editor but in game we usually do all stuff with fonts on main thread (chars caching)
ScopeLock lock(_asset->Locker);
// Handle situation when more than one thread wants to get the same character
if (_asset->_characterCache.TryGet(key, result))
{
result.Font = this;
return;
}
// Try to use fallback font if character is missing
if (enableFallback && !_asset->ContainsChar(c))
@@ -52,13 +61,9 @@ void Font::GetCharacter(Char c, FontCharacterEntry& result, bool enableFallback)
for (int32 fallbackIndex = 0; fallbackIndex < FallbackFonts.Count(); fallbackIndex++)
{
FontAsset* fallbackFont = FallbackFonts.Get()[fallbackIndex].Get();
if (fallbackFont && _asset->GetOptions().RasterMode == FontRasterMode::MSDF)
{
fallbackFont = fallbackFont->GetMSDF();
}
if (fallbackFont && fallbackFont->ContainsChar(c))
{
fallbackFont->CreateFont(GetSize())->GetCharacter(c, result, enableFallback);
fallbackFont->GetRasterMode(_asset->GetOptions().RasterMode)->CreateFont(GetSize())->GetCharacter(c, result, enableFallback);
return;
}
}
@@ -127,6 +132,11 @@ void Font::Invalidate()
_kerningTable.Clear();
}
float Font::GetScale(float layoutScale) const
{
return layoutScale / FontManager::FontScale * (_asset->GetOptions().RasterMode == FontRasterMode::MSDF ? _size / _asset->GetOptions().MSDFSize : 1.0f);
}
void Font::ProcessText(const StringView& text, Array<FontLineCache, InlinedAllocation<8>>& outputLines, const TextLayoutOptions& layout)
{
int32 textLength = text.Length();
@@ -137,7 +147,7 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache, InlinedAlloc
FontLineCache tmpLine;
FontCharacterEntry entry;
FontCharacterEntry previous;
float scale = layout.Scale / FontManager::FontScale * (_asset->GetOptions().RasterMode == FontRasterMode::MSDF ? _size / _asset->GetOptions().MSDFSize : 1.0f);
const float scale = GetScale(layout.Scale);
float boundsWidth = layout.Bounds.GetWidth();
float baseLinesDistance = static_cast<float>(_height) * layout.BaseLinesGapScale * scale;
tmpLine.Location = Float2::Zero;
@@ -181,6 +191,7 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache, InlinedAlloc
{
// Get character entry
GetCharacter(currentChar, entry);
const float entryScale = entry.Font->GetScale(layout.Scale);
// Get kerning
if (!isWhitespace && previous.IsValid)
@@ -192,7 +203,7 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache, InlinedAlloc
kerning = 0;
}
previous = entry;
xAdvance = (kerning + entry.AdvanceX) * scale;
xAdvance = (kerning + entry.AdvanceX) * entryScale;
// Check if character fits the line or skip wrapping
if (cursorX + xAdvance <= boundsWidth || layout.TextWrapping == TextWrapping::NoWrap)
@@ -343,7 +354,7 @@ int32 Font::HitTestText(const StringView& text, const Float2& location, const Te
Array<FontLineCache, InlinedAllocation<8>> lines;
ProcessText(text, lines, layout);
ASSERT(lines.HasItems());
float scale = layout.Scale / FontManager::FontScale * (_asset->GetOptions().RasterMode == FontRasterMode::MSDF ? _size / _asset->GetOptions().MSDFSize : 1.0f);
const float scale = GetScale(layout.Scale);
float baseLinesDistance = static_cast<float>(_height) * layout.BaseLinesGapScale * scale;
// Offset position to match lines origin space
@@ -364,12 +375,13 @@ int32 Font::HitTestText(const StringView& text, const Float2& location, const Te
// Cache current character
const Char currentChar = text[currentIndex];
GetCharacter(currentChar, entry);
const float entryScale = entry.Font->GetScale(layout.Scale);
const bool isWhitespace = StringUtils::IsWhitespace(currentChar);
// Apply kerning
if (!isWhitespace && previous.IsValid)
{
x += entry.Font->GetKerning(previous.Character, entry.Character);
x += entry.Font->GetKerning(previous.Character, entry.Character) * entryScale;
}
previous = entry;
@@ -388,7 +400,7 @@ int32 Font::HitTestText(const StringView& text, const Float2& location, const Te
}
// Move
x += entry.AdvanceX * scale;
x += entry.AdvanceX * entryScale;
}
// Test line end edge
@@ -431,7 +443,7 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo
Array<FontLineCache, InlinedAllocation<8>> lines;
ProcessText(text, lines, layout);
ASSERT(lines.HasItems());
float scale = layout.Scale / FontManager::FontScale * (_asset->GetOptions().RasterMode == FontRasterMode::MSDF ? _size / _asset->GetOptions().MSDFSize : 1.0f);
const float scale = GetScale(layout.Scale);
float baseLinesDistance = static_cast<float>(_height) * layout.BaseLinesGapScale * scale;
// Find line with that position
@@ -452,17 +464,18 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo
// Cache current character
const Char currentChar = text[currentIndex];
GetCharacter(currentChar, entry);
const float entryScale = entry.Font->GetScale(layout.Scale);
const bool isWhitespace = StringUtils::IsWhitespace(currentChar);
// Apply kerning
if (!isWhitespace && previous.IsValid)
{
charPos.X += entry.Font->GetKerning(previous.Character, entry.Character);
charPos.X += entry.Font->GetKerning(previous.Character, entry.Character) * entryScale;
}
previous = entry;
// Move
charPos.X += entry.AdvanceX * scale;
charPos.X += entry.AdvanceX * entryScale;
}
// Upper left corner of the character
+7
View File
@@ -234,6 +234,13 @@ public:
API_FUNCTION() void Invalidate();
public:
/// <summary>
/// Gets the scale factor that maps the rasterized font size to the actual rendered size.
/// </summary>
/// <param name="layoutScale">The layout scale.</param>
/// <returns>The scale factor.</returns>
float GetScale(float layoutScale) const;
/// <summary>
/// Processes text to get cached lines for rendering.
/// </summary>
+12 -12
View File
@@ -69,7 +69,7 @@ void FontAsset::unload(bool isReloading)
_fontFile.Release();
_virtualBold = nullptr;
_virtualItalic = nullptr;
_virtualMSDF = nullptr;
_virtualRasterMode = nullptr;
}
AssetChunksFlag FontAsset::getChunksToPreload() const
@@ -115,11 +115,11 @@ void FontAsset::SetOptions(const FontOptions& value)
options.Flags |= FontFlags::Italic;
_virtualItalic->SetOptions(options);
}
if (_virtualMSDF)
if (_virtualRasterMode)
{
auto options = _options;
options.RasterMode = FontRasterMode::MSDF;
_virtualMSDF->SetOptions(options);
options.RasterMode = _options.RasterMode == FontRasterMode::MSDF ? FontRasterMode::Bitmap : FontRasterMode::MSDF;
_virtualRasterMode->SetOptions(options);
}
}
@@ -176,20 +176,20 @@ FontAsset* FontAsset::GetItalic()
return _virtualItalic;
}
FontAsset* FontAsset::GetMSDF()
FontAsset* FontAsset::GetRasterMode(FontRasterMode rasterMode)
{
ScopeLock lock(Locker);
if (_options.RasterMode == FontRasterMode::MSDF)
if (_options.RasterMode == rasterMode)
return this;
if (!_virtualMSDF)
if (!_virtualRasterMode)
{
_virtualMSDF = Content::CreateVirtualAsset<FontAsset>();
_virtualMSDF->Init(_fontFile);
_virtualRasterMode = Content::CreateVirtualAsset<FontAsset>();
_virtualRasterMode->Init(_fontFile);
auto options = _options;
options.RasterMode = FontRasterMode::MSDF;
_virtualMSDF->SetOptions(options);
options.RasterMode = rasterMode;
_virtualRasterMode->SetOptions(options);
}
return _virtualMSDF;
return _virtualRasterMode;
}
bool FontAsset::Init(const BytesContainer& fontFile)
+3 -3
View File
@@ -233,7 +233,7 @@ private:
Dictionary<Pair<float, Char>, FontCharacterEntry> _characterCache;
AssetReference<FontAsset> _virtualBold;
AssetReference<FontAsset> _virtualItalic;
AssetReference<FontAsset> _virtualMSDF;
AssetReference<FontAsset> _virtualRasterMode;
public:
/// <summary>
@@ -293,10 +293,10 @@ public:
API_FUNCTION() FontAsset* GetItalic();
/// <summary>
/// Gets the MSDF version of the font. Returns itself or creates a new virtual font asset using this font but rasterized with Multi-channel Signed Distance Field (MSDF).
/// Gets the different rasterization mode of the font. Returns itself or creates a new virtual font asset using this font but rasterized with the specified mode.
/// </summary>
/// <returns>The virtual font or this.</returns>
API_FUNCTION() FontAsset* GetMSDF();
API_FUNCTION() FontAsset* GetRasterMode(FontRasterMode rasterMode);
/// <summary>
/// Initializes the font with a custom font file data.
+16 -12
View File
@@ -1204,7 +1204,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
FontCharacterEntry previous;
int32 kerning;
FontOptions options = font->GetAsset()->GetOptions();
float scale = 1.0f / FontManager::FontScale * (options.RasterMode == FontRasterMode::MSDF ? font->GetSize() / options.MSDFSize : 1.0f);
const float scale = font->GetScale(1.0f);
const bool enableFallbackFonts = EnumHasAllFlags(Features, RenderingFeatures::FallbackFonts);
// Render all characters
@@ -1231,6 +1231,8 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
{
// Get character entry
font->GetCharacter(currentChar, entry, enableFallbackFonts);
// Fallback fonts may have different MSDFSize, so we need to calculate scale per character
const float entryScale = entry.Font->GetScale(1.0f);
// Check if need to select/change font atlas (since characters even in the same font may be located in different atlases)
if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex)
@@ -1263,17 +1265,17 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
{
kerning = 0;
}
pointer.X += kerning * scale;
pointer.X += kerning * entryScale;
previous = entry;
// Omit whitespace characters
if (!isWhitespace)
{
// Calculate character size and atlas coordinates
const float x = pointer.X + entry.OffsetX * scale;
const float y = pointer.Y + (font->GetHeight() + font->GetDescender() - entry.OffsetY) * scale;
const float x = pointer.X + entry.OffsetX * entryScale;
const float y = pointer.Y - entry.OffsetY * entryScale + (font->GetHeight() + font->GetDescender()) * scale;
Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale);
Rectangle charRect(x, y, entry.UVSize.X * entryScale, entry.UVSize.Y * entryScale);
Float2 upperLeftUV = entry.UV * invAtlasSize;
Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize;
@@ -1286,7 +1288,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
}
// Move
pointer.X += entry.AdvanceX * scale;
pointer.X += entry.AdvanceX * entryScale;
}
else
{
@@ -1320,7 +1322,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
FontCharacterEntry previous;
int32 kerning;
FontOptions options = font->GetAsset()->GetOptions();
float scale = layout.Scale / FontManager::FontScale * (options.RasterMode == FontRasterMode::MSDF ? font->GetSize() / options.MSDFSize : 1.0f);
const float scale = font->GetScale(layout.Scale);
const bool enableFallbackFonts = EnumHasAllFlags(Features, RenderingFeatures::FallbackFonts);
// Process text to get lines
@@ -1356,6 +1358,8 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
{
// Get character entry
font->GetCharacter(currentChar, entry, enableFallbackFonts);
// Fallback fonts may have different MSDFSize, so we need to calculate scale per character
const float entryScale = entry.Font->GetScale(layout.Scale);
// Check if need to select/change font atlas (since characters even in the same font may be located in different atlases)
if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex)
@@ -1386,17 +1390,17 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
{
kerning = 0;
}
pointer.X += (float)kerning * scale;
pointer.X += (float)kerning * entryScale;
previous = entry;
// Omit whitespace characters
if (!isWhitespace)
{
// Calculate character size and atlas coordinates
const float x = pointer.X + entry.OffsetX * scale;
const float y = pointer.Y - entry.OffsetY * scale + Math::Ceil((font->GetHeight() + font->GetDescender()) * scale);
const float x = pointer.X + entry.OffsetX * entryScale;
const float y = pointer.Y - entry.OffsetY * entryScale + Math::Ceil((font->GetHeight() + font->GetDescender()) * scale);
Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale);
Rectangle charRect(x, y, entry.UVSize.X * entryScale, entry.UVSize.Y * entryScale);
charRect.Offset(layout.Bounds.Location);
Float2 upperLeftUV = entry.UV * invAtlasSize;
@@ -1410,7 +1414,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
}
// Move
pointer.X += entry.AdvanceX * scale;
pointer.X += entry.AdvanceX * entryScale;
}
}
}
+7 -6
View File
@@ -172,7 +172,7 @@ void TextRender::UpdateLayout()
// Pick a font (remove DPI text scale as the text is being placed in the world)
auto font = Font->CreateFont(_size);
float scale = _layoutOptions.Scale / FontManager::FontScale * (Font->GetOptions().RasterMode == FontRasterMode::MSDF ? _size / Font->GetOptions().MSDFSize : 1.0f);
const float scale = font->GetScale(_layoutOptions.Scale);
// Prepare
FontTextureAtlas* fontAtlas = nullptr;
@@ -217,6 +217,7 @@ void TextRender::UpdateLayout()
if (c != '\n')
{
font->GetCharacter(c, entry);
const float entryScale = entry.Font->GetScale(_layoutOptions.Scale);
// Check if need to select/change font atlas (since characters even in the same font may be located in different atlases)
if (fontAtlas == nullptr || entry.TextureIndex != drawChunk.FontAtlasIndex)
@@ -273,17 +274,17 @@ void TextRender::UpdateLayout()
{
kerning = 0;
}
pointer.X += (float)kerning * scale;
pointer.X += (float)kerning * entryScale;
previous = entry;
// Omit whitespace characters
if (!isWhitespace)
{
// Calculate character size and atlas coordinates
const float x = pointer.X + (float)entry.OffsetX * scale;
const float y = pointer.Y + (float)(font->GetHeight() + font->GetDescender() - entry.OffsetY) * scale;
const float x = pointer.X + (float)entry.OffsetX * entryScale;
const float y = pointer.Y - (float)entry.OffsetY * entryScale + (float)(font->GetHeight() + font->GetDescender()) * scale;
Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale);
Rectangle charRect(x, y, entry.UVSize.X * entryScale, entry.UVSize.Y * entryScale);
charRect.Offset(_layoutOptions.Bounds.Location);
Float2 upperLeftUV = entry.UV * invAtlasSize;
@@ -326,7 +327,7 @@ void TextRender::UpdateLayout()
}
// Move
pointer.X += (float)entry.AdvanceX * scale;
pointer.X += (float)entry.AdvanceX * entryScale;
}
}
}