Merge branch 'msdf-stage2' of https://github.com/fibref/FlaxEngine into fibref-msdf-stage2

This commit is contained in:
2026-09-19 19:49:29 +02:00
10 changed files with 271 additions and 171 deletions
+7 -1
View File
@@ -1,5 +1,6 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.ComponentModel;
using FlaxEditor.Content;
using FlaxEditor.CustomEditors;
@@ -25,6 +26,10 @@ namespace FlaxEditor.Windows.Assets
[EditorOrder(5), EditorDisplay("Properties"), Tooltip("The rasterization mode used when generating font atlases.")]
public FontRasterMode RasterMode;
[DefaultValue(32.0f)]
[EditorOrder(6), Limit(4, 512), EditorDisplay("Properties"), Tooltip("The font size used when generating MSDF font atlases.")]
public float MSDFSize;
[DefaultValue(FontHinting.Default)]
[EditorOrder(10), EditorDisplay("Properties"), Tooltip("The font hinting used when rendering characters.")]
public FontHinting Hinting;
@@ -47,6 +52,7 @@ namespace FlaxEditor.Windows.Assets
{
Hinting = Hinting,
RasterMode = RasterMode,
MSDFSize = MSDFSize,
};
if (AntiAliasing)
options.Flags |= FontFlags.AntiAliasing;
@@ -63,6 +69,7 @@ namespace FlaxEditor.Windows.Assets
Bold = (options.Flags & FontFlags.Bold) == FontFlags.Bold;
Italic = (options.Flags & FontFlags.Italic) == FontFlags.Italic;
RasterMode = options.RasterMode;
MSDFSize = options.MSDFSize;
}
}
@@ -134,7 +141,6 @@ namespace FlaxEditor.Windows.Assets
if (assetOptions != options)
{
Asset.Options = options;
Asset.Invalidate();
}
}
@@ -19,12 +19,13 @@ public:
const Upgrader upgraders[] =
{
{ 3, 4, &Upgrade_3_To_4 },
{ 4, 5, &Upgrade_4_To_5 },
};
setup(upgraders, ARRAY_COUNT(upgraders));
}
private:
struct FontOptionsOld
struct FontOptions3
{
FontHinting Hinting;
FontFlags Flags;
@@ -34,8 +35,8 @@ private:
{
ASSERT(context.Input.SerializedVersion == 3 && context.Output.SerializedVersion == 4);
FontOptionsOld optionsOld;
Platform::MemoryCopy(&optionsOld, context.Input.CustomData.Get(), sizeof(FontOptionsOld));
FontOptions3 optionsOld;
Platform::MemoryCopy(&optionsOld, context.Input.CustomData.Get(), sizeof(FontOptions3));
FontOptions options;
options.Hinting = optionsOld.Hinting;
@@ -45,6 +46,30 @@ private:
return CopyChunk(context, 0);
}
struct FontOptions4
{
FontHinting Hinting;
FontFlags Flags;
FontRasterMode RasterMode;
};
static bool Upgrade_4_To_5(AssetMigrationContext& context)
{
ASSERT(context.Input.SerializedVersion == 4 && context.Output.SerializedVersion == 5);
FontOptions4 optionsOld;
Platform::MemoryCopy(&optionsOld, context.Input.CustomData.Get(), sizeof(FontOptions4));
FontOptions options;
options.Hinting = optionsOld.Hinting;
options.Flags = optionsOld.Flags;
options.RasterMode = optionsOld.RasterMode;
options.MSDFSize = 32.0f;
context.Output.CustomData.Copy(&options);
return CopyChunk(context, 0);
}
};
#endif
@@ -12,13 +12,14 @@
CreateAssetResult ImportFont::Import(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(FontAsset, 4);
IMPORT_SETUP(FontAsset, 5);
// Setup header
FontOptions options;
options.Hinting = FontHinting::Default;
options.Flags = FontFlags::AntiAliasing;
options.RasterMode = FontRasterMode::Bitmap;
options.MSDFSize = 32.0f;
context.Data.CustomData.Copy(&options);
// Open the file
+41 -23
View File
@@ -13,7 +13,6 @@ Font::Font(FontAsset* parentAsset, float size)
: ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer))
, _asset(parentAsset)
, _size(size)
, _characters(512)
{
_asset->_fonts.Add(this);
@@ -37,14 +36,24 @@ Font::~Font()
void Font::GetCharacter(Char c, FontCharacterEntry& result, bool enableFallback)
{
// Try to get the character or cache it if cannot be found
if (!_characters.TryGet(c, result))
const auto key = Pair<float, Char>(_asset->GetOptions().RasterMode == FontRasterMode::MSDF ? _asset->GetOptions().MSDFSize : GetSize(), c);
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 (_characters.TryGet(c, result))
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;
}
}
@@ -69,7 +74,7 @@ void Font::GetCharacter(Char c, FontCharacterEntry& result, bool enableFallback)
ASSERT(result.Font);
// Add to the dictionary
_characters.Add(c, result);
_asset->_characterCache.Add(key, result);
}
}
@@ -116,11 +121,20 @@ void Font::Invalidate()
{
ScopeLock lock(_asset->Locker);
for (auto i = _characters.Begin(); i.IsNotEnd(); ++i)
{
FontManager::Invalidate(i->Value);
}
_characters.Clear();
FlushFaceSize();
const FT_Face face = _asset->GetFTFace();
ASSERT(face != nullptr);
_height = Convert26Dot6ToRoundedPixel<int32>(FT_MulFix(face->height, face->size->metrics.y_scale));
_hasKerning = FT_HAS_KERNING(face) != 0;
_ascender = Convert26Dot6ToRoundedPixel<int16>(face->size->metrics.ascender);
_descender = Convert26Dot6ToRoundedPixel<int16>(face->size->metrics.descender);
_lineGap = _height - _ascender + _descender;
_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)
@@ -133,7 +147,7 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache, InlinedAlloc
FontLineCache tmpLine;
FontCharacterEntry entry;
FontCharacterEntry previous;
float scale = layout.Scale / FontManager::FontScale;
const float scale = GetScale(layout.Scale);
float boundsWidth = layout.Bounds.GetWidth();
float baseLinesDistance = static_cast<float>(_height) * layout.BaseLinesGapScale * scale;
tmpLine.Location = Float2::Zero;
@@ -177,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)
@@ -188,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)
@@ -339,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;
const float scale = GetScale(layout.Scale);
float baseLinesDistance = static_cast<float>(_height) * layout.BaseLinesGapScale * scale;
// Offset position to match lines origin space
@@ -360,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;
@@ -384,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
@@ -427,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;
const float scale = GetScale(layout.Scale);
float baseLinesDistance = static_cast<float>(_height) * layout.BaseLinesGapScale * scale;
// Find line with that position
@@ -448,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
@@ -474,7 +491,8 @@ void Font::FlushFaceSize() const
{
// Set the character size
const FT_Face face = _asset->GetFTFace();
const FT_Error error = FT_Set_Char_Size(face, 0, ConvertPixelTo26Dot6<FT_F26Dot6>(_size * FontManager::FontScale), DefaultDPI, DefaultDPI);
float size = _asset->GetOptions().RasterMode == FontRasterMode::MSDF ? _asset->GetOptions().MSDFSize : _size;
const FT_Error error = FT_Set_Char_Size(face, 0, ConvertPixelTo26Dot6<FT_F26Dot6>(size * FontManager::FontScale), DefaultDPI, DefaultDPI);
if (error)
{
LOG_FT_ERROR(error);
+9 -106
View File
@@ -11,6 +11,7 @@
class FontAsset;
struct FontTextureAtlasSlot;
struct FontCharacterEntry;
// The default DPI that engine is using
#define DefaultDPI 96
@@ -119,110 +120,6 @@ struct TIsPODType<FontLineCache>
enum { Value = true };
};
// Font glyph metrics:
//
// xmin xmax
// | |
// |<-------- width -------->|
// | |
// | +-------------------------+----------------- ymax
// | | ggggggggg ggggg | ^ ^
// | | g:::::::::ggg::::g | | |
// | | g:::::::::::::::::g | | |
// | | g::::::ggggg::::::gg | | |
// | | g:::::g g:::::g | | |
// offsetX -|-------->| g:::::g g:::::g | offsetY |
// | | g:::::g g:::::g | | |
// | | g::::::g g:::::g | | |
// | | g:::::::ggggg:::::g | | |
// | | g::::::::::::::::g | | height
// | | gg::::::::::::::g | | |
// baseline ---*---------|---- gggggggg::::::g-----*-------- |
// / | | g:::::g | |
// origin | | gggggg g:::::g | |
// | | g:::::gg gg:::::g | |
// | | g::::::ggg:::::::g | |
// | | gg:::::::::::::g | |
// | | ggg::::::ggg | |
// | | gggggg | v
// | +-------------------------+----------------- ymin
// | |
// |------------- advanceX ----------->|
/// <summary>
/// The cached font character entry (read for rendering and further processing).
/// </summary>
API_STRUCT(NoDefault) struct FLAXENGINE_API FontCharacterEntry
{
DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry);
/// <summary>
/// The character represented by this entry.
/// </summary>
API_FIELD() Char Character;
/// <summary>
/// True if entry is valid, otherwise false.
/// </summary>
API_FIELD() bool IsValid = false;
/// <summary>
/// The index to a specific texture in the font cache.
/// </summary>
API_FIELD() byte TextureIndex;
/// <summary>
/// The left bearing expressed in integer pixels.
/// </summary>
API_FIELD() int16 OffsetX;
/// <summary>
/// The top bearing expressed in integer pixels.
/// </summary>
API_FIELD() int16 OffsetY;
/// <summary>
/// The amount to advance in X before drawing the next character in a string.
/// </summary>
API_FIELD() int16 AdvanceX;
/// <summary>
/// The distance from baseline to glyph top most point.
/// </summary>
API_FIELD() int16 BearingY;
/// <summary>
/// The height in pixels of the glyph.
/// </summary>
API_FIELD() int16 Height;
/// <summary>
/// The start location of the character in the texture (in texture coordinates space).
/// </summary>
API_FIELD() Float2 UV;
/// <summary>
/// The size the character in the texture (in texture coordinates space).
/// </summary>
API_FIELD() Float2 UVSize;
/// <summary>
/// The slot in texture atlas, containing the pixel data of the glyph.
/// </summary>
API_FIELD() const FontTextureAtlasSlot* Slot;
/// <summary>
/// The owner font.
/// </summary>
API_FIELD() const class Font* Font;
};
template<>
struct TIsPODType<FontCharacterEntry>
{
enum { Value = true };
};
/// <summary>
/// Represents font object that can be using during text rendering (it uses Font Asset but with pre-cached data for chosen font properties).
/// </summary>
@@ -239,7 +136,6 @@ private:
int32 _descender;
int32 _lineGap;
bool _hasKerning;
Dictionary<Char, FontCharacterEntry> _characters;
mutable Dictionary<uint32, int32> _kerningTable;
public:
@@ -333,11 +229,18 @@ public:
API_FUNCTION() void CacheText(const StringView& text);
/// <summary>
/// Invalidates all cached dynamic font atlases using this font. Can be used to reload font characters after changing font asset options.
/// Refresh cached metrics. Can be used after changing font asset options.
/// </summary>
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>
+35 -9
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
@@ -101,6 +101,26 @@ FontFlags FontAsset::GetStyle() const
void FontAsset::SetOptions(const FontOptions& value)
{
_options = value;
Invalidate();
if (_virtualBold)
{
auto options = _options;
options.Flags |= FontFlags::Bold;
_virtualBold->SetOptions(options);
}
if (_virtualItalic)
{
auto options = _options;
options.Flags |= FontFlags::Italic;
_virtualItalic->SetOptions(options);
}
if (_virtualRasterMode)
{
auto options = _options;
options.RasterMode = _options.RasterMode == FontRasterMode::MSDF ? FontRasterMode::Bitmap : FontRasterMode::MSDF;
_virtualRasterMode->SetOptions(options);
}
}
Font* FontAsset::CreateFont(float size)
@@ -156,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)
@@ -214,6 +234,12 @@ bool FontAsset::ContainsChar(Char c) const
void FontAsset::Invalidate()
{
ScopeLock lock(Locker);
for (auto& entry : _characterCache)
FontManager::Invalidate(entry.Value);
_characterCache.Clear();
// Refresh cached metrics of all fonts created from this asset
for (auto font : _fonts)
font->Invalidate();
}
+4 -4
View File
@@ -13,7 +13,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if this object has the same value as <paramref name="other" />; otherwise, <c>false</c> </returns>
public bool Equals(FontOptions other)
{
return Hinting == other.Hinting && Flags == other.Flags && RasterMode == other.RasterMode;
return Hinting == other.Hinting && Flags == other.Flags && RasterMode == other.RasterMode && MSDFSize == other.MSDFSize;
}
/// <inheritdoc />
@@ -25,7 +25,7 @@ namespace FlaxEngine
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine((int)Hinting, (int)Flags, (int)RasterMode);
return HashCode.Combine((int)Hinting, (int)Flags, (int)RasterMode, MSDFSize);
}
/// <summary>
@@ -36,7 +36,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if <paramref name="left" /> has the same value as <paramref name="right" />; otherwise, <c>false</c>.</returns>
public static bool operator ==(FontOptions left, FontOptions right)
{
return left.Hinting == right.Hinting && left.Flags == right.Flags && left.RasterMode == right.RasterMode;
return left.Hinting == right.Hinting && left.Flags == right.Flags && left.RasterMode == right.RasterMode && left.MSDFSize == right.MSDFSize;
}
/// <summary>
@@ -47,7 +47,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if <paramref name="left" /> has a different value than <paramref name="right" />; otherwise,<c>false</c>.</returns>
public static bool operator !=(FontOptions left, FontOptions right)
{
return left.Hinting != right.Hinting || left.Flags != right.Flags || left.RasterMode != right.RasterMode;
return left.Hinting != right.Hinting || left.Flags != right.Flags || left.RasterMode != right.RasterMode || left.MSDFSize != right.MSDFSize;
}
}
}
+118 -4
View File
@@ -4,11 +4,119 @@
#include "Engine/Content/BinaryAsset.h"
#include "Engine/Content/AssetReference.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Core/Math/Vector2.h"
class Font;
class FontManager;
struct FontTextureAtlasSlot;
typedef struct FT_FaceRec_* FT_Face;
// Font glyph metrics:
//
// xmin xmax
// | |
// |<-------- width -------->|
// | |
// | +-------------------------+----------------- ymax
// | | ggggggggg ggggg | ^ ^
// | | g:::::::::ggg::::g | | |
// | | g:::::::::::::::::g | | |
// | | g::::::ggggg::::::gg | | |
// | | g:::::g g:::::g | | |
// offsetX -|-------->| g:::::g g:::::g | offsetY |
// | | g:::::g g:::::g | | |
// | | g::::::g g:::::g | | |
// | | g:::::::ggggg:::::g | | |
// | | g::::::::::::::::g | | height
// | | gg::::::::::::::g | | |
// baseline ---*---------|---- gggggggg::::::g-----*-------- |
// / | | g:::::g | |
// origin | | gggggg g:::::g | |
// | | g:::::gg gg:::::g | |
// | | g::::::ggg:::::::g | |
// | | gg:::::::::::::g | |
// | | ggg::::::ggg | |
// | | gggggg | v
// | +-------------------------+----------------- ymin
// | |
// |------------- advanceX ----------->|
/// <summary>
/// The cached font character entry (read for rendering and further processing).
/// </summary>
API_STRUCT(NoDefault) struct FLAXENGINE_API FontCharacterEntry
{
DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry);
/// <summary>
/// The character represented by this entry.
/// </summary>
API_FIELD() Char Character;
/// <summary>
/// True if entry is valid, otherwise false.
/// </summary>
API_FIELD() bool IsValid = false;
/// <summary>
/// The index to a specific texture in the font cache.
/// </summary>
API_FIELD() byte TextureIndex;
/// <summary>
/// The left bearing expressed in integer pixels.
/// </summary>
API_FIELD() int16 OffsetX;
/// <summary>
/// The top bearing expressed in integer pixels.
/// </summary>
API_FIELD() int16 OffsetY;
/// <summary>
/// The amount to advance in X before drawing the next character in a string.
/// </summary>
API_FIELD() int16 AdvanceX;
/// <summary>
/// The distance from baseline to glyph top most point.
/// </summary>
API_FIELD() int16 BearingY;
/// <summary>
/// The height in pixels of the glyph.
/// </summary>
API_FIELD() int16 Height;
/// <summary>
/// The start location of the character in the texture (in texture coordinates space).
/// </summary>
API_FIELD() Float2 UV;
/// <summary>
/// The size the character in the texture (in texture coordinates space).
/// </summary>
API_FIELD() Float2 UVSize;
/// <summary>
/// The slot in texture atlas, containing the pixel data of the glyph.
/// </summary>
API_FIELD() const FontTextureAtlasSlot* Slot;
/// <summary>
/// The owner font.
/// </summary>
API_FIELD() const class Font* Font;
};
template<>
struct TIsPODType<FontCharacterEntry>
{
enum { Value = true };
};
/// <summary>
/// The font hinting used when rendering characters.
/// </summary>
@@ -105,6 +213,11 @@ API_STRUCT() struct FontOptions
/// The font rasterization mode.
/// </summary>
API_FIELD() FontRasterMode RasterMode;
/// <summary>
/// The font size used when generating MSDF font atlases.
/// </summary>
API_FIELD() float MSDFSize;
};
/// <summary>
@@ -112,7 +225,7 @@ API_STRUCT() struct FontOptions
/// </summary>
API_CLASS(NoSpawn) class FLAXENGINE_API FontAsset : public BinaryAsset
{
DECLARE_BINARY_ASSET_HEADER(FontAsset, 4);
DECLARE_BINARY_ASSET_HEADER(FontAsset, 5);
friend Font;
private:
@@ -120,9 +233,10 @@ private:
FontOptions _options;
BytesContainer _fontFile;
Array<Font*, InlinedAllocation<32>> _fonts;
Dictionary<Pair<float, Char>, FontCharacterEntry> _characterCache;
AssetReference<FontAsset> _virtualBold;
AssetReference<FontAsset> _virtualItalic;
AssetReference<FontAsset> _virtualMSDF;
AssetReference<FontAsset> _virtualRasterMode;
public:
/// <summary>
@@ -182,10 +296,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.
+20 -14
View File
@@ -1203,7 +1203,8 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
Float2 invAtlasSize = Float2::One;
FontCharacterEntry previous;
int32 kerning;
float scale = 1.0f / FontManager::FontScale;
FontOptions options = font->GetAsset()->GetOptions();
const float scale = font->GetScale(1.0f);
const bool enableFallbackFonts = EnumHasAllFlags(Features, RenderingFeatures::FallbackFonts);
// Render all characters
@@ -1216,7 +1217,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
}
else
{
drawCall.Type = font->GetAsset()->GetOptions().RasterMode == FontRasterMode::MSDF ? DrawCallType::DrawCharMSDF : DrawCallType::DrawChar;
drawCall.Type = options.RasterMode == FontRasterMode::MSDF ? DrawCallType::DrawCharMSDF : DrawCallType::DrawChar;
drawCall.AsChar.Mat = nullptr;
}
Float2 pointer = location;
@@ -1230,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)
@@ -1262,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;
@@ -1285,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
{
@@ -1318,7 +1321,8 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
Float2 invAtlasSize = Float2::One;
FontCharacterEntry previous;
int32 kerning;
float scale = layout.Scale / FontManager::FontScale;
FontOptions options = font->GetAsset()->GetOptions();
const float scale = font->GetScale(layout.Scale);
const bool enableFallbackFonts = EnumHasAllFlags(Features, RenderingFeatures::FallbackFonts);
// Process text to get lines
@@ -1335,7 +1339,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
}
else
{
drawCall.Type = font->GetAsset()->GetOptions().RasterMode == FontRasterMode::MSDF ? DrawCallType::DrawCharMSDF : DrawCallType::DrawChar;
drawCall.Type = options.RasterMode == FontRasterMode::MSDF ? DrawCallType::DrawCharMSDF : DrawCallType::DrawChar;
drawCall.AsChar.Mat = nullptr;
}
for (int32 lineIndex = 0; lineIndex < Lines.Count(); lineIndex++)
@@ -1354,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)
@@ -1384,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;
@@ -1408,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;
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;
}
}
}