diff --git a/.gitignore b/.gitignore index b7e11e554..30c2caeb2 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,6 @@ Source/*.Gen.* Source/*.csproj /Package_*/ !Source/Engine/Debug -/Source/Platforms/Editor/Linux/Mono/etc/mono/registry PackageEditor_Cert.command PackageEditor_Cert.bat PackagePlatforms_Cert.bat diff --git a/Content/Editor/Fonts/NotoSansSC-Regular.flax b/Content/Editor/Fonts/NotoSansSC-Regular.flax new file mode 100644 index 000000000..39964e6c5 --- /dev/null +++ b/Content/Editor/Fonts/NotoSansSC-Regular.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19fa43eb7b31ee3936348b1f271c464c79d7020a21d33e3cdbe54f98c3b14304 +size 10560899 diff --git a/Source/Editor/EditorAssets.cs b/Source/Editor/EditorAssets.cs index eb2f21356..8b2049ebd 100644 --- a/Source/Editor/EditorAssets.cs +++ b/Source/Editor/EditorAssets.cs @@ -54,6 +54,11 @@ namespace FlaxEditor /// public static string PrimaryFont = "Editor/Fonts/Roboto-Regular"; + /// + /// The secondary (fallback) font to use for missing characters rendering (CJK - Chinese/Japanese/Korean characters). + /// + public static string FallbackFont = "Editor/Fonts/NotoSansSC-Regular"; + /// /// The Inconsolata Regular font. /// diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs index ca45ec6f5..3e2bb63bd 100644 --- a/Source/Editor/GUI/Row.cs +++ b/Source/Editor/GUI/Row.cs @@ -43,8 +43,9 @@ namespace FlaxEditor.GUI { Depth = -1; - if (Height < Style.Current.FontMedium.Height) - Height = Style.Current.FontMedium.Height + 4; + var fontHeight = Style.Current.FontMedium.Height; + if (Height < fontHeight) + Height = fontHeight + 4; } /// diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 95a273f19..6180b5a66 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -172,9 +172,9 @@ namespace FlaxEditor.Options set { if (value == null) - _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10); + _outputLogFont = new FontReference(ConsoleFont, 10); else if (!value.Font) - _outputLogFont.Font = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont); + _outputLogFont.Font = ConsoleFont; else _outputLogFont = value; } @@ -237,11 +237,19 @@ namespace FlaxEditor.Options public int NumberOfGameClientsToLaunch = 1; private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont); + private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont); + private FontReference _titleFont = new FontReference(DefaultFont, 18); private FontReference _largeFont = new FontReference(DefaultFont, 14); private FontReference _mediumFont = new FontReference(DefaultFont, 9); private FontReference _smallFont = new FontReference(DefaultFont, 9); - private FontReference _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10); + private FontReference _outputLogFont = new FontReference(ConsoleFont, 10); + + /// + /// The list of fallback fonts to use when main text font is missing certain characters. Empty to use fonts from GraphicsSettings. + /// + [EditorDisplay("Fonts"), EditorOrder(650)] + public FontAsset[] FallbackFonts = [FlaxEngine.Content.LoadAsyncInternal(EditorAssets.FallbackFont)]; /// /// Gets or sets the title font for editor UI. diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 1137e4c37..bb90be6d9 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using FlaxEditor.Content.Settings; using FlaxEditor.Modules; using FlaxEngine; using FlaxEngine.GUI; @@ -217,12 +219,18 @@ namespace FlaxEditor.Options if (styleName == ThemeOptions.LightDefault) { Style.Current = CreateLightStyle(); - } + } else { Style.Current = CreateDefaultStyle(); } } + + // Set fallback fonts + var fallbackFonts = Options.Interface.FallbackFonts; + if (fallbackFonts == null || fallbackFonts.Length == 0 || fallbackFonts.All(x => x == null)) + fallbackFonts = GameSettings.Load().FallbackFonts; + Font.FallbackFonts = fallbackFonts; } /// diff --git a/Source/Editor/Windows/Assets/FontWindow.cs b/Source/Editor/Windows/Assets/FontWindow.cs index ff4135165..bc91ca5e2 100644 --- a/Source/Editor/Windows/Assets/FontWindow.cs +++ b/Source/Editor/Windows/Assets/FontWindow.cs @@ -144,7 +144,7 @@ namespace FlaxEditor.Windows.Assets protected override void OnAssetLinked() { Asset.WaitForLoaded(); - _textPreview.Font = new FontReference(Asset.CreateFont(30)); + _textPreview.Font = new FontReference(Asset, 30); _inputText.Text = string.Format("This is a sample text using font {0}.", Asset.FamilyName); var options = Asset.Options; _proxy.Set(ref options); diff --git a/Source/Engine/Core/Config/GameSettings.cs b/Source/Engine/Core/Config/GameSettings.cs index 40d9f5dbc..76c968968 100644 --- a/Source/Engine/Core/Config/GameSettings.cs +++ b/Source/Engine/Core/Config/GameSettings.cs @@ -119,7 +119,7 @@ namespace FlaxEditor.Content.Settings /// /// The custom settings to use with a game. Can be specified by the user to define game-specific options and be used by the external plugins (used as key-value pair). /// - [EditorOrder(1100), EditorDisplay("Other Settings"), Tooltip("The custom settings to use with a game. Can be specified by the user to define game-specific options and be used by the external plugins (used as key-value pair).")] + [EditorOrder(1500), EditorDisplay("Other Settings"), Tooltip("The custom settings to use with a game. Can be specified by the user to define game-specific options and be used by the external plugins (used as key-value pair).")] public Dictionary CustomSettings; #if FLAX_EDITOR || PLATFORM_WINDOWS diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 81d80cb35..b639b9e64 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -6,6 +6,8 @@ #include "Engine/Graphics/Enums.h" #include "Engine/Graphics/PostProcessSettings.h" +class FontAsset; + /// /// Graphics rendering settings. /// @@ -13,6 +15,7 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class { API_AUTO_SERIALIZATION(); DECLARE_SCRIPTING_TYPE_MINIMAL(GraphicsSettings); + public: /// /// Enables rendering synchronization with the refresh rate of the display device to avoid "tearing" artifacts. @@ -118,6 +121,12 @@ public: API_FIELD(Attributes="EditorOrder(10000), EditorDisplay(\"Post Process Settings\", EditorDisplayAttribute.InlineStyle)") PostProcessSettings PostProcessSettings; + /// + /// The list of fallback fonts used for text rendering. Ignored if empty. + /// + API_FIELD(Attributes="EditorOrder(5000), EditorDisplay(\"Text\")") + Array> FallbackFonts; + private: /// /// Renamed UeeHDRProbes into UseHDRProbes diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index f91c58cea..a9bfef470 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -8,6 +8,7 @@ #include "Engine/Core/Config/GraphicsSettings.h" #include "Engine/Engine/CommandLine.h" #include "Engine/Engine/EngineService.h" +#include "Engine/Render2D/Font.h" bool Graphics::UseVSync = false; Quality Graphics::AAQuality = Quality::Medium; @@ -69,6 +70,9 @@ void GraphicsSettings::Apply() Graphics::GIQuality = GIQuality; Graphics::PostProcessSettings = ::PostProcessSettings(); Graphics::PostProcessSettings.BlendWith(PostProcessSettings, 1.0f); +#if !USE_EDITOR // OptionsModule handles fallback fonts in Editor + Font::FallbackFonts = FallbackFonts; +#endif } void Graphics::DisposeDevice() diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index 90423f5ce..71eca1f59 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -7,6 +7,8 @@ #include "Engine/Threading/Threading.h" #include "IncludeFreeType.h" +Array, HeapAllocation> Font::FallbackFonts; + Font::Font(FontAsset* parentAsset, float size) : ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer)) , _asset(parentAsset) @@ -32,7 +34,7 @@ Font::~Font() _asset->_fonts.Remove(this); } -void Font::GetCharacter(Char c, FontCharacterEntry& result) +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)) @@ -44,6 +46,20 @@ void Font::GetCharacter(Char c, FontCharacterEntry& result) if (_characters.TryGet(c, result)) return; + // Try to use fallback font if character is missing + if (enableFallback && !_asset->ContainsChar(c)) + { + for (int32 fallbackIndex = 0; fallbackIndex < FallbackFonts.Count(); fallbackIndex++) + { + FontAsset* fallbackFont = FallbackFonts.Get()[fallbackIndex].Get(); + if (fallbackFont && fallbackFont->ContainsChar(c)) + { + fallbackFont->CreateFont(GetSize())->GetCharacter(c, result, enableFallback); + return; + } + } + } + // Create character cache FontManager::AddNewEntry(this, c, result); @@ -87,7 +103,7 @@ void Font::CacheText(const StringView& text) FontCharacterEntry entry; for (int32 i = 0; i < text.Length(); i++) { - GetCharacter(text[i], entry); + GetCharacter(text[i], entry, false); } } @@ -104,12 +120,14 @@ void Font::Invalidate() void Font::ProcessText(const StringView& text, Array& outputLines, const TextLayoutOptions& layout) { + int32 textLength = text.Length(); + if (textLength == 0) + return; float cursorX = 0; int32 kerning; FontLineCache tmpLine; FontCharacterEntry entry; FontCharacterEntry previous; - int32 textLength = text.Length(); float scale = layout.Scale / FontManager::FontScale; float boundsWidth = layout.Bounds.GetWidth(); float baseLinesDistance = static_cast(_height) * layout.BaseLinesGapScale * scale; @@ -157,7 +175,7 @@ void Font::ProcessText(const StringView& text, Array& outputLines // Get kerning if (!isWhitespace && previous.IsValid) { - kerning = GetKerning(previous.Character, entry.Character); + kerning = entry.Font->GetKerning(previous.Character, entry.Character); } else { @@ -178,8 +196,8 @@ void Font::ProcessText(const StringView& text, Array& outputLines if (lastWrapCharIndex != INVALID_INDEX) { // Skip moving twice for the same character - int32 lastLineLasCharIndex = outputLines.HasItems() ? outputLines.Last().LastCharIndex : -10000; - if (lastLineLasCharIndex == lastWrapCharIndex || lastLineLasCharIndex == lastWrapCharIndex - 1 || lastLineLasCharIndex == lastWrapCharIndex - 2) + int32 lastLineLastCharIndex = outputLines.HasItems() ? outputLines.Last().LastCharIndex : -10000; + if (lastLineLastCharIndex == lastWrapCharIndex || lastLineLastCharIndex == lastWrapCharIndex - 1 || lastLineLastCharIndex == lastWrapCharIndex - 2) { currentIndex = nextCharIndex; lastMoveLine = moveLine; @@ -238,7 +256,8 @@ void Font::ProcessText(const StringView& text, Array& outputLines lastMoveLine = moveLine; } - if (textLength != 0 && (tmpLine.LastCharIndex >= tmpLine.FirstCharIndex || text[textLength - 1] == '\n')) + // Check if an additional line should be created + if (tmpLine.LastCharIndex >= tmpLine.FirstCharIndex || text[textLength - 1] == '\n') { // Add line tmpLine.Size.X = cursorX; @@ -341,7 +360,7 @@ int32 Font::HitTestText(const StringView& text, const Float2& location, const Te // Apply kerning if (!isWhitespace && previous.IsValid) { - x += GetKerning(previous.Character, entry.Character); + x += entry.Font->GetKerning(previous.Character, entry.Character); } previous = entry; @@ -415,7 +434,7 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo // Apply kerning if (!isWhitespace && previous.IsValid) { - x += GetKerning(previous.Character, entry.Character); + x += entry.Font->GetKerning(previous.Character, entry.Character); } previous = entry; diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index 90f723cd8..cd92103cb 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -20,7 +20,7 @@ struct FontTextureAtlasSlot; /// API_STRUCT(NoDefault) struct TextRange { -DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange); + DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange); /// /// The start index (inclusive). @@ -35,7 +35,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange); /// /// Gets the range length. /// - int32 Length() const + FORCE_INLINE int32 Length() const { return EndIndex - StartIndex; } @@ -43,7 +43,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange); /// /// Gets a value indicating whether range is empty. /// - bool IsEmpty() const + FORCE_INLINE bool IsEmpty() const { return (EndIndex - StartIndex) <= 0; } @@ -53,7 +53,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange); /// /// The index. /// true if range contains the specified character index; otherwise, false. - bool Contains(int32 index) const + FORCE_INLINE bool Contains(int32 index) const { return index >= StartIndex && index < EndIndex; } @@ -90,7 +90,7 @@ struct TIsPODType /// API_STRUCT(NoDefault) struct FontLineCache { -DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache); + DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache); /// /// The root position of the line (upper left corner). @@ -108,7 +108,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache); API_FIELD() int32 FirstCharIndex; /// - /// The last character index (from the input text). + /// The last character index (from the input text), inclusive. /// API_FIELD() int32 LastCharIndex; }; @@ -154,7 +154,7 @@ struct TIsPODType /// API_STRUCT(NoDefault) struct FontCharacterEntry { -DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry); + DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry); /// /// The character represented by this entry. @@ -210,6 +210,11 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry); /// 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<> @@ -223,10 +228,10 @@ struct TIsPODType /// API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API Font : public ManagedScriptingObject { -DECLARE_SCRIPTING_TYPE_NO_SPAWN(Font); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(Font); friend FontAsset; -private: +private: FontAsset* _asset; float _size; int32 _height; @@ -238,7 +243,6 @@ private: mutable Dictionary _kerningTable; public: - /// /// Initializes a new instance of the class. /// @@ -252,6 +256,10 @@ public: ~Font(); public: + /// + /// The active fallback fonts. + /// + API_FIELD() static Array, HeapAllocation> FallbackFonts; /// /// Gets parent font asset that contains font family used by this font. @@ -302,13 +310,13 @@ public: } public: - /// /// Gets character entry. /// /// The character. /// The output character entry. - void GetCharacter(Char c, FontCharacterEntry& result); + /// True if fallback to secondary font when the primary font doesn't contains this character. + void GetCharacter(Char c, FontCharacterEntry& result, bool enableFallback = true); /// /// Gets the kerning amount for a pair of characters. @@ -330,7 +338,6 @@ public: API_FUNCTION() void Invalidate(); public: - /// /// Processes text to get cached lines for rendering. /// @@ -524,7 +531,6 @@ public: void FlushFaceSize() const; public: - // [Object] String ToString() const override; }; diff --git a/Source/Engine/Render2D/FontAsset.cpp b/Source/Engine/Render2D/FontAsset.cpp index a477e5f4d..53fbdc930 100644 --- a/Source/Engine/Render2D/FontAsset.cpp +++ b/Source/Engine/Render2D/FontAsset.cpp @@ -199,14 +199,29 @@ bool FontAsset::Save(const StringView& path) #endif +bool FontAsset::ContainsChar(Char c) const +{ + return FT_Get_Char_Index(_face, c) > 0; +} + void FontAsset::Invalidate() { ScopeLock lock(Locker); - for (auto font : _fonts) - { font->Invalidate(); - } +} + +uint64 FontAsset::GetMemoryUsage() const +{ + Locker.Lock(); + uint64 result = BinaryAsset::GetMemoryUsage(); + result += sizeof(FontAsset) - sizeof(BinaryAsset); + result += sizeof(FT_FaceRec); + result += _fontFile.Length(); + for (auto font : _fonts) + result += sizeof(Font); + Locker.Unlock(); + return result; } bool FontAsset::init(AssetInitData& initData) diff --git a/Source/Engine/Render2D/FontAsset.h b/Source/Engine/Render2D/FontAsset.h index 4dea84a5b..a93276bf1 100644 --- a/Source/Engine/Render2D/FontAsset.h +++ b/Source/Engine/Render2D/FontAsset.h @@ -93,6 +93,7 @@ API_CLASS(NoSpawn) class FLAXENGINE_API FontAsset : public BinaryAsset { DECLARE_BINARY_ASSET_HEADER(FontAsset, 3); friend Font; + private: FT_Face _face; FontOptions _options; @@ -174,11 +175,22 @@ public: API_FUNCTION() bool Save(const StringView& path = StringView::Empty); #endif + /// + /// Check if the font contains the glyph of a char. + /// + /// The char to test. + /// True if the font contains the glyph of the char, otherwise false. + API_FUNCTION() bool ContainsChar(Char c) const; + /// /// Invalidates all cached dynamic font atlases using this font. Can be used to reload font characters after changing font asset options. /// API_FUNCTION() void Invalidate(); +public: + // [BinaryAsset] + uint64 GetMemoryUsage() const override; + protected: // [BinaryAsset] bool init(AssetInitData& initData) override; diff --git a/Source/Engine/Render2D/FontManager.cpp b/Source/Engine/Render2D/FontManager.cpp index bb7edf974..2e1ba8c6b 100644 --- a/Source/Engine/Render2D/FontManager.cpp +++ b/Source/Engine/Render2D/FontManager.cpp @@ -27,7 +27,6 @@ using namespace FontManagerImpl; class FontManagerService : public EngineService { public: - FontManagerService() : EngineService(TEXT("Font Manager"), -700) { @@ -155,6 +154,12 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) // Get the index to the glyph in the font face const FT_UInt glyphIndex = FT_Get_Char_Index(face, c); +#if !BUILD_RELEASE + if (glyphIndex == 0) + { + LOG(Warning, "Font `{}` doesn't contain character `\\u{:x}`, consider choosing another font. ", String(face->family_name), c); + } +#endif // Load the glyph const FT_Error error = FT_Load_Glyph(face, glyphIndex, glyphFlags); @@ -284,6 +289,7 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) entry.UVSize.X = static_cast(slot->Width - 2 * padding); entry.UVSize.Y = static_cast(slot->Height - 2 * padding); entry.Slot = slot; + entry.Font = font; return false; } diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index b56c3e1a2..34cb2c693 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -27,11 +27,11 @@ #if USE_EDITOR #define RENDER2D_CHECK_RENDERING_STATE \ - if (!Render2D::IsRendering()) \ - { \ - LOG(Error, "Calling Render2D is only valid during rendering."); \ - return; \ - } + if (!Render2D::IsRendering()) \ + { \ + LOG(Error, "Calling Render2D is only valid during rendering."); \ + return; \ + } #else #define RENDER2D_CHECK_RENDERING_STATE #endif @@ -180,7 +180,7 @@ struct ClipMask Rectangle Bounds; }; -Render2D::RenderingFeatures Render2D::Features = RenderingFeatures::VertexSnapping; +Render2D::RenderingFeatures Render2D::Features = RenderingFeatures::VertexSnapping | RenderingFeatures::FallbackFonts; namespace { @@ -1137,8 +1137,8 @@ void DrawBatch(int32 startIndex, int32 count) } // Draw - Context->BindVB(ToSpan(&vb, 1)); // TODO: reduce bindings frequency - Context->BindIB(ib); // TODO: reduce bindings frequency + Context->BindVB(ToSpan(&vb, 1)); + Context->BindIB(ib); Context->DrawIndexed(countIb, 0, d.StartIB); } @@ -1159,6 +1159,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, FontCharacterEntry previous; int32 kerning; float scale = 1.0f / FontManager::FontScale; + const bool enableFallbackFonts = EnumHasAllFlags(Features, RenderingFeatures::FallbackFonts); // Render all characters FontCharacterEntry entry; @@ -1183,7 +1184,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, if (currentChar != '\n') { // Get character entry - font->GetCharacter(currentChar, entry); + font->GetCharacter(currentChar, entry, enableFallbackFonts); // 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) @@ -1210,7 +1211,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, // Get kerning if (!isWhitespace && previous.IsValid) { - kerning = font->GetKerning(previous.Character, entry.Character); + kerning = entry.Font->GetKerning(previous.Character, entry.Character); } else { @@ -1273,6 +1274,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, FontCharacterEntry previous; int32 kerning; float scale = layout.Scale / FontManager::FontScale; + const bool enableFallbackFonts = EnumHasAllFlags(Features, RenderingFeatures::FallbackFonts); // Process text to get lines Lines.Clear(); @@ -1299,10 +1301,14 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, // Render all characters from the line for (int32 charIndex = line.FirstCharIndex; charIndex <= line.LastCharIndex; charIndex++) { - const Char c = text[charIndex]; - if (c != '\n') + // Cache current character + const Char currentChar = text[charIndex]; + + // Check if it isn't a newline character + if (currentChar != '\n') { - font->GetCharacter(c, entry); + // Get character entry + font->GetCharacter(currentChar, entry, enableFallbackFonts); // 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) @@ -1324,10 +1330,10 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, } // Get kerning - const bool isWhitespace = StringUtils::IsWhitespace(c); + const bool isWhitespace = StringUtils::IsWhitespace(currentChar); if (!isWhitespace && previous.IsValid) { - kerning = font->GetKerning(previous.Character, entry.Character); + kerning = entry.Font->GetKerning(previous.Character, entry.Character); } else { @@ -1931,7 +1937,7 @@ void Render2D::DrawBlur(const Rectangle& rect, float blurStrength) void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& vertices, const Span& uvs) { RENDER2D_CHECK_RENDERING_STATE; - CHECK(vertices.Length() == uvs.Length()) + CHECK(vertices.Length() == uvs.Length()); Render2DDrawCall& drawCall = DrawCalls.AddOne(); drawCall.Type = DrawCallType::FillTexture; @@ -1977,7 +1983,7 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& indices, drawCall.StartIB = IBIndex; drawCall.CountIB = indices.Length(); drawCall.AsTexture.Ptr = t; - + for (int32 i = 0; i < indices.Length();) { const uint16 i0 = indices.Get()[i++]; diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h index 2b890ced9..5e5e952dc 100644 --- a/Source/Engine/Render2D/Render2D.h +++ b/Source/Engine/Render2D/Render2D.h @@ -44,6 +44,11 @@ API_CLASS(Static) class FLAXENGINE_API Render2D /// Enables automatic geometry vertices snapping to integer coordinates in screen space. Reduces aliasing and sampling artifacts. Might be disabled for 3D projection viewport or for complex UI transformations. /// VertexSnapping = 1, + + /// + /// Enables automatic characters usage from fallback fonts. + /// + FallbackFonts = 2, }; struct CustomData @@ -452,3 +457,5 @@ public: /// The color. API_FUNCTION() static void FillTriangle(const Float2& p0, const Float2& p1, const Float2& p2, const Color& color); }; + +DECLARE_ENUM_OPERATORS(Render2D::RenderingFeatures); diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs index 044c65044..b4351da75 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs @@ -280,7 +280,7 @@ namespace FlaxEngine.GUI foreach (var id in ids) { var path = Content.GetEditorAssetPath(id); - if (!string.IsNullOrEmpty(path) && + if (!string.IsNullOrEmpty(path) && string.Equals(name, System.IO.Path.GetFileNameWithoutExtension(path), System.StringComparison.OrdinalIgnoreCase)) { return Content.LoadAsync(id, type); diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index 55b58634d..35819da79 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -280,13 +280,13 @@ namespace FlaxEngine.GUI /// [EditorDisplay("Border Style"), EditorOrder(2010), Tooltip("Whether to have a border."), ExpandGroups] public bool HasBorder { get; set; } = true; - + /// /// Gets or sets the border thickness. /// [EditorDisplay("Border Style"), EditorOrder(2011), Tooltip("The thickness of the border."), Limit(0)] public float BorderThickness { get; set; } = 1.0f; - + /// /// Gets or sets the color of the border (Transparent if not used). /// diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index aa49dac45..d190f1123 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -239,7 +239,7 @@ void TextRender::UpdateLayout() const bool isWhitespace = StringUtils::IsWhitespace(c); if (!isWhitespace && previous.IsValid) { - kerning = font->GetKerning(previous.Character, entry.Character); + kerning = entry.Font->GetKerning(previous.Character, entry.Character); } else {