diff --git a/Content/Editor/Fonts/Inconsolata-Regular.flax b/Content/Editor/Fonts/Inconsolata-Regular.flax index d85ef610a..6046f2793 100644 --- a/Content/Editor/Fonts/Inconsolata-Regular.flax +++ b/Content/Editor/Fonts/Inconsolata-Regular.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:139e2f54a268f4febc753aaa9807a33a6eb2dd84e788c5b8b4f50ca8683d2b3b -size 93116 +oid sha256:e931314736f3fd3aaa7e22a83af1fc8512824e6d6c83cde9edfc21d69d83ad23 +size 93121 diff --git a/Content/Editor/Fonts/NotoSansSC-Regular.flax b/Content/Editor/Fonts/NotoSansSC-Regular.flax index 8f8f00157..c458b3255 100644 --- a/Content/Editor/Fonts/NotoSansSC-Regular.flax +++ b/Content/Editor/Fonts/NotoSansSC-Regular.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ddc7af5fab1dd4ad858ad2abf11c6c243d908345720892cd56242fbc9c33b02 -size 10560900 +oid sha256:b19ad5b09e6fe82f7547e13d8b5dc63b84fb8253654a1f98ef784109b3d33e80 +size 10560905 diff --git a/Content/Editor/Fonts/Roboto-Regular.flax b/Content/Editor/Fonts/Roboto-Regular.flax index 01251cac6..fca17395b 100644 --- a/Content/Editor/Fonts/Roboto-Regular.flax +++ b/Content/Editor/Fonts/Roboto-Regular.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0584b3a3c186364464de155edece5cb6a8c46000676ac82a45f951f3a67fac5 -size 172194 +oid sha256:b96520c348ef1b63e7514dd739ce7440503e8b4462edd830a271b20dd60730c2 +size 172199 diff --git a/Content/Editor/Fonts/SegMDL2.flax b/Content/Editor/Fonts/SegMDL2.flax index b627849d9..ea9e3c4e2 100644 --- a/Content/Editor/Fonts/SegMDL2.flax +++ b/Content/Editor/Fonts/SegMDL2.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7a9d723d6342b8a484dfb2ee784cef506b3311a6092be0e9de35f941d698ff5 -size 204150 +oid sha256:eeeb5ea36b751130cce7b03127f7d1c26b05e906252c3532ed83a3e154a82bf9 +size 204155 diff --git a/Content/Editor/Fonts/Segoe Media Center Regular.flax b/Content/Editor/Fonts/Segoe Media Center Regular.flax index 6c17d96bf..06efc4cf0 100644 --- a/Content/Editor/Fonts/Segoe Media Center Regular.flax +++ b/Content/Editor/Fonts/Segoe Media Center Regular.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c21443a111daede658cb634f56b4ae0838dbb2eaa75d4ba8ba74a4c77df44de8 -size 72798 +oid sha256:0c203e22557bbdb86f6b4c13f9fc954d88fff92911cc90706508be57506a5dd6 +size 72803 diff --git a/Source/Editor/Windows/Assets/FontWindow.cs b/Source/Editor/Windows/Assets/FontWindow.cs index e08a87b05..2f27a3f1b 100644 --- a/Source/Editor/Windows/Assets/FontWindow.cs +++ b/Source/Editor/Windows/Assets/FontWindow.cs @@ -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(); } } diff --git a/Source/Engine/Content/Upgraders/FontAssetUpgrader.h b/Source/Engine/Content/Upgraders/FontAssetUpgrader.h index 4ca6e6d73..b6e8ec12a 100644 --- a/Source/Engine/Content/Upgraders/FontAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/FontAssetUpgrader.h @@ -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 diff --git a/Source/Engine/ContentImporters/ImportFont.cpp b/Source/Engine/ContentImporters/ImportFont.cpp index 964f3907e..b01b36627 100644 --- a/Source/Engine/ContentImporters/ImportFont.cpp +++ b/Source/Engine/ContentImporters/ImportFont.cpp @@ -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 diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index b2e6e8b75..37ce0f505 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -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(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(FT_MulFix(face->height, face->size->metrics.y_scale)); + _hasKerning = FT_HAS_KERNING(face) != 0; + _ascender = Convert26Dot6ToRoundedPixel(face->size->metrics.ascender); + _descender = Convert26Dot6ToRoundedPixel(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>& outputLines, const TextLayoutOptions& layout) @@ -133,7 +160,7 @@ void Font::ProcessText(const StringView& text, Array(_height) * layout.BaseLinesGapScale * scale; tmpLine.Location = Float2::Zero; @@ -177,6 +204,7 @@ void Font::ProcessText(const StringView& text, ArrayGetScale(layout.Scale); // Get kerning if (!isWhitespace && previous.IsValid) @@ -188,7 +216,7 @@ void Font::ProcessText(const StringView& text, Array> lines; ProcessText(text, lines, layout); ASSERT(lines.HasItems()); - float scale = layout.Scale / FontManager::FontScale; + const float scale = GetScale(layout.Scale); float baseLinesDistance = static_cast(_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> lines; ProcessText(text, lines, layout); ASSERT(lines.HasItems()); - float scale = layout.Scale / FontManager::FontScale; + const float scale = GetScale(layout.Scale); float baseLinesDistance = static_cast(_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(_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(size * FontManager::FontScale), DefaultDPI, DefaultDPI); if (error) { LOG_FT_ERROR(error); diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index fc40cc16c..b3ddd25f8 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -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 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 ----------->| - -/// -/// The cached font character entry (read for rendering and further processing). -/// -API_STRUCT(NoDefault) struct FLAXENGINE_API FontCharacterEntry -{ - DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry); - - /// - /// The character represented by this entry. - /// - API_FIELD() Char Character; - - /// - /// True if entry is valid, otherwise false. - /// - API_FIELD() bool IsValid = false; - - /// - /// The index to a specific texture in the font cache. - /// - API_FIELD() byte TextureIndex; - - /// - /// The left bearing expressed in integer pixels. - /// - API_FIELD() int16 OffsetX; - - /// - /// The top bearing expressed in integer pixels. - /// - API_FIELD() int16 OffsetY; - - /// - /// The amount to advance in X before drawing the next character in a string. - /// - API_FIELD() int16 AdvanceX; - - /// - /// The distance from baseline to glyph top most point. - /// - API_FIELD() int16 BearingY; - - /// - /// The height in pixels of the glyph. - /// - API_FIELD() int16 Height; - - /// - /// The start location of the character in the texture (in texture coordinates space). - /// - API_FIELD() Float2 UV; - - /// - /// The size the character in the texture (in texture coordinates space). - /// - API_FIELD() Float2 UVSize; - - /// - /// The slot in texture atlas, containing the pixel data of the glyph. - /// - API_FIELD() const FontTextureAtlasSlot* Slot; - - /// - /// The owner font. - /// - API_FIELD() const class Font* Font; -}; - -template<> -struct TIsPODType -{ - enum { Value = true }; -}; - /// /// Represents font object that can be using during text rendering (it uses Font Asset but with pre-cached data for chosen font properties). /// @@ -239,7 +135,6 @@ private: int32 _descender; int32 _lineGap; bool _hasKerning; - Dictionary _characters; mutable Dictionary _kerningTable; public: @@ -315,7 +210,7 @@ public: /// /// The character. /// The output character entry. - /// True if fallback to secondary font when the primary font doesn't contains this character. + /// True if fallback to secondary font when the primary font doesn't contain this character. void GetCharacter(Char c, FontCharacterEntry& result, bool enableFallback = true); /// @@ -333,11 +228,18 @@ public: API_FUNCTION() void CacheText(const StringView& text); /// - /// 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. /// API_FUNCTION() void Invalidate(); public: + /// + /// Gets the scale factor that maps the rasterized font size to the actual rendered size. + /// + /// The layout scale. + /// The scale factor. + float GetScale(float layoutScale) const; + /// /// Processes text to get cached lines for rendering. /// diff --git a/Source/Engine/Render2D/FontAsset.cpp b/Source/Engine/Render2D/FontAsset.cpp index 34ffe7330..1b58d472c 100644 --- a/Source/Engine/Render2D/FontAsset.cpp +++ b/Source/Engine/Render2D/FontAsset.cpp @@ -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(); - _virtualMSDF->Init(_fontFile); + _virtualRasterMode = Content::CreateVirtualAsset(); + _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(); } diff --git a/Source/Engine/Render2D/FontAsset.cs b/Source/Engine/Render2D/FontAsset.cs index 624daab5b..f49ae4ebb 100644 --- a/Source/Engine/Render2D/FontAsset.cs +++ b/Source/Engine/Render2D/FontAsset.cs @@ -13,7 +13,7 @@ namespace FlaxEngine /// true if this object has the same value as ; otherwise, false 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; } /// @@ -25,7 +25,7 @@ namespace FlaxEngine /// public override int GetHashCode() { - return HashCode.Combine((int)Hinting, (int)Flags, (int)RasterMode); + return HashCode.Combine((int)Hinting, (int)Flags, (int)RasterMode, MSDFSize); } /// @@ -36,7 +36,7 @@ namespace FlaxEngine /// true if has the same value as ; otherwise, false. 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; } /// @@ -47,7 +47,7 @@ namespace FlaxEngine /// true if has a different value than ; otherwise,false. 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; } } } diff --git a/Source/Engine/Render2D/FontAsset.h b/Source/Engine/Render2D/FontAsset.h index c3aa141a8..0a5c67255 100644 --- a/Source/Engine/Render2D/FontAsset.h +++ b/Source/Engine/Render2D/FontAsset.h @@ -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. /// API_FIELD() FontRasterMode RasterMode; + + /// + /// The font size used when generating MSDF font atlases. + /// + API_FIELD() float MSDFSize; }; /// @@ -112,7 +121,7 @@ API_STRUCT() struct FontOptions /// 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> _fonts; + Dictionary, FontCharacterEntry> _characterCache; AssetReference _virtualBold; AssetReference _virtualItalic; - AssetReference _virtualMSDF; + AssetReference _virtualRasterMode; public: /// @@ -182,10 +192,10 @@ public: API_FUNCTION() FontAsset* GetItalic(); /// - /// 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. /// /// The virtual font or this. - API_FUNCTION() FontAsset* GetMSDF(); + API_FUNCTION() FontAsset* GetRasterMode(FontRasterMode rasterMode); /// /// Initializes the font with a custom font file data. diff --git a/Source/Engine/Render2D/FontCharacterEntry.h b/Source/Engine/Render2D/FontCharacterEntry.h new file mode 100644 index 000000000..f73099beb --- /dev/null +++ b/Source/Engine/Render2D/FontCharacterEntry.h @@ -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 ----------->| + +/// +/// The cached font character entry (read for rendering and further processing). +/// +API_STRUCT(NoDefault) struct FLAXENGINE_API FontCharacterEntry +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry); + + /// + /// The character represented by this entry. + /// + API_FIELD() Char Character; + + /// + /// True if entry is valid, otherwise false. + /// + API_FIELD() bool IsValid = false; + + /// + /// The index to a specific texture in the font cache. + /// + API_FIELD() byte TextureIndex; + + /// + /// The left bearing expressed in integer pixels. + /// + API_FIELD() int16 OffsetX; + + /// + /// The top bearing expressed in integer pixels. + /// + API_FIELD() int16 OffsetY; + + /// + /// The amount to advance in X before drawing the next character in a string. + /// + API_FIELD() int16 AdvanceX; + + /// + /// The distance from baseline to glyph top most point. + /// + API_FIELD() int16 BearingY; + + /// + /// The height in pixels of the glyph. + /// + API_FIELD() int16 Height; + + /// + /// The start location of the character in the texture (in texture coordinates space). + /// + API_FIELD() Float2 UV; + + /// + /// The size the character in the texture (in texture coordinates space). + /// + API_FIELD() Float2 UVSize; + + /// + /// The slot in texture atlas, containing the pixel data of the glyph. + /// + API_FIELD() const struct FontTextureAtlasSlot* Slot; + + /// + /// The owner font. + /// + API_FIELD() const class Font* Font; +}; + +template<> +struct TIsPODType +{ + enum { Value = true }; +}; diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index 58f6f208e..359190665 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -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; } } } diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index c2f6789c7..ef95d27c4 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -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; } } }