Merge branch 'fibref-msdf-stage2'
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -25,6 +25,10 @@ namespace FlaxEditor.Windows.Assets
|
||||
[EditorOrder(5), EditorDisplay("Properties"), Tooltip("The rasterization mode used when generating font atlases.")]
|
||||
public FontRasterMode RasterMode;
|
||||
|
||||
[DefaultValue(32.0f), VisibleIf(nameof(IsMSDF))]
|
||||
[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;
|
||||
@@ -41,12 +45,15 @@ namespace FlaxEditor.Windows.Assets
|
||||
[EditorOrder(40), EditorDisplay("Properties"), Tooltip("Enables slant effect, emulating italic style.")]
|
||||
public bool Italic;
|
||||
|
||||
private bool IsMSDF => RasterMode == FontRasterMode.MSDF;
|
||||
|
||||
public void Get(out FontOptions options)
|
||||
{
|
||||
options = new FontOptions
|
||||
{
|
||||
Hinting = Hinting,
|
||||
RasterMode = RasterMode,
|
||||
MSDFSize = MSDFSize,
|
||||
};
|
||||
if (AntiAliasing)
|
||||
options.Flags |= FontFlags.AntiAliasing;
|
||||
@@ -63,6 +70,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 +142,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
|
||||
|
||||
@@ -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,25 @@ 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 FontOptions& options = _asset->GetOptions();
|
||||
const auto key = Pair<float, Char>(options.RasterMode == FontRasterMode::MSDF ? options.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 +62,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(options.RasterMode)->CreateFont(GetSize())->GetCharacter(c, result, enableFallback);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -69,7 +75,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 +122,32 @@ void Font::Invalidate()
|
||||
{
|
||||
ScopeLock lock(_asset->Locker);
|
||||
|
||||
for (auto i = _characters.Begin(); i.IsNotEnd(); ++i)
|
||||
// Invalidate cached characters (from atlas)
|
||||
for (auto i = _asset->_characterCache.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
FontManager::Invalidate(i->Value);
|
||||
if (i->Value.Font == this)
|
||||
{
|
||||
FontManager::Invalidate(i->Value);
|
||||
_asset->_characterCache.Remove(i);
|
||||
}
|
||||
}
|
||||
_characters.Clear();
|
||||
|
||||
// Rebuild font metrics
|
||||
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
|
||||
{
|
||||
const FontOptions& options = _asset->GetOptions();
|
||||
return layoutScale / FontManager::FontScale * (options.RasterMode == FontRasterMode::MSDF ? _size / options.MSDFSize : 1.0f);
|
||||
}
|
||||
|
||||
void Font::ProcessText(const StringView& text, Array<FontLineCache, InlinedAllocation<8>>& outputLines, const TextLayoutOptions& layout)
|
||||
@@ -133,7 +160,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 +204,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 +216,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 +367,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 +388,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 +413,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 +456,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 +477,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 +504,9 @@ 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);
|
||||
const FontOptions& options = _asset->GetOptions();
|
||||
float size = options.RasterMode == FontRasterMode::MSDF ? options.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);
|
||||
|
||||
+10
-108
@@ -10,7 +10,7 @@
|
||||
#include "TextLayoutOptions.h"
|
||||
|
||||
class FontAsset;
|
||||
struct FontTextureAtlasSlot;
|
||||
struct FontCharacterEntry;
|
||||
|
||||
// The default DPI that engine is using
|
||||
#define DefaultDPI 96
|
||||
@@ -119,110 +119,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 +135,6 @@ private:
|
||||
int32 _descender;
|
||||
int32 _lineGap;
|
||||
bool _hasKerning;
|
||||
Dictionary<Char, FontCharacterEntry> _characters;
|
||||
mutable Dictionary<uint32, int32> _kerningTable;
|
||||
|
||||
public:
|
||||
@@ -315,7 +210,7 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="c">The character.</param>
|
||||
/// <param name="result">The output character entry.</param>
|
||||
/// <param name="enableFallback">True if fallback to secondary font when the primary font doesn't contains this character.</param>
|
||||
/// <param name="enableFallback">True if fallback to secondary font when the primary font doesn't contain this character.</param>
|
||||
void GetCharacter(Char c, FontCharacterEntry& result, bool enableFallback = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -333,11 +228,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 and invalidates all cached characters (in atlases). 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>
|
||||
|
||||
@@ -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,13 @@ bool FontAsset::ContainsChar(Char c) const
|
||||
void FontAsset::Invalidate()
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Invalidate cached characters (from atlas)
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
|
||||
#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"
|
||||
#include "Engine/Render2D/FontCharacterEntry.h"
|
||||
|
||||
class Font;
|
||||
class FontManager;
|
||||
@@ -105,6 +109,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 +121,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 +129,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 +192,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.
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Math/Vector2.h"
|
||||
|
||||
// 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 struct FontTextureAtlasSlot* Slot;
|
||||
|
||||
/// <summary>
|
||||
/// The owner font.
|
||||
/// </summary>
|
||||
API_FIELD() const class Font* Font;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TIsPODType<FontCharacterEntry>
|
||||
{
|
||||
enum { Value = true };
|
||||
};
|
||||
@@ -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;
|
||||
const 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;
|
||||
@@ -1231,6 +1232,9 @@ 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,18 +1266,16 @@ 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;
|
||||
|
||||
Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * 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 * entryScale, entry.UVSize.Y * entryScale);
|
||||
Float2 upperLeftUV = entry.UV * invAtlasSize;
|
||||
Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize;
|
||||
|
||||
@@ -1285,7 +1287,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 +1320,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;
|
||||
const 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 +1338,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++)
|
||||
@@ -1355,6 +1358,9 @@ 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,19 +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);
|
||||
|
||||
Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * 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 * entryScale, entry.UVSize.Y * entryScale);
|
||||
charRect.Offset(layout.Bounds.Location);
|
||||
|
||||
Float2 upperLeftUV = entry.UV * invAtlasSize;
|
||||
Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize;
|
||||
|
||||
@@ -1408,7 +1412,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
|
||||
}
|
||||
|
||||
// Move
|
||||
pointer.X += entry.AdvanceX * scale;
|
||||
pointer.X += entry.AdvanceX * entryScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user