// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #pragma once #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Types/StringView.h" #include "Engine/Content/AssetReference.h" #include "Engine/Scripting/ScriptingObject.h" #include "TextLayoutOptions.h" class FontAsset; struct FontTextureAtlasSlot; // The default DPI that engine is using #define DefaultDPI 96 #define USE_ARRAY_CHARACTER_STORAGE 1 /// /// The text range. /// API_STRUCT(NoDefault) struct FLAXENGINE_API TextRange { DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange); /// /// The start index (inclusive). /// API_FIELD() int32 StartIndex; /// /// The end index (exclusive). /// API_FIELD() int32 EndIndex; /// /// Gets the range length. /// FORCE_INLINE int32 Length() const { return EndIndex - StartIndex; } /// /// Gets a value indicating whether range is empty. /// FORCE_INLINE bool IsEmpty() const { return (EndIndex - StartIndex) <= 0; } /// /// Determines whether this range contains the character index. /// /// The index. /// true if range contains the specified character index; otherwise, false. FORCE_INLINE bool Contains(int32 index) const { return index >= StartIndex && index < EndIndex; } /// /// Determines whether this range intersects with the other range. /// /// The other text range. /// true if range intersects with the specified range index;, false. bool Intersect(const TextRange& other) const { return Math::Min(EndIndex, other.EndIndex) > Math::Max(StartIndex, other.StartIndex); } /// /// Gets the substring from the source text. /// /// The text. /// The substring of the original text of the defined range. StringView Substring(const StringView& text) const { return StringView(text.Get() + StartIndex, EndIndex - StartIndex); } }; template<> struct TIsPODType { enum { Value = true }; }; /// /// The font line info generated during text processing. /// API_STRUCT(NoDefault) struct FLAXENGINE_API FontLineCache { DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache); /// /// The root position of the line (upper left corner). /// API_FIELD() Float2 Location; /// /// The line bounds (width and height). /// API_FIELD() Float2 Size; /// /// The first character index (from the input text). /// API_FIELD() int32 FirstCharIndex; /// /// The last character index (from the input text), inclusive. /// API_FIELD() int32 LastCharIndex; }; template<> 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). /// API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API Font : public ManagedScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Font); friend FontAsset; private: FontAsset* _asset; float _size; int32 _height; int32 _ascender; int32 _descender; int32 _lineGap; bool _hasKerning; #if USE_ARRAY_CHARACTER_STORAGE Array _characters; #else Dictionary _characters; #endif mutable Dictionary _kerningTable; public: /// /// Initializes a new instance of the class. /// /// The parent asset. /// The size. Font(FontAsset* parentAsset, float size); /// /// Finalizes an instance of the class. /// ~Font(); public: /// /// The active fallback fonts. /// API_FIELD() static Array, HeapAllocation> FallbackFonts; /// /// Gets parent font asset that contains font family used by this font. /// API_PROPERTY() FORCE_INLINE FontAsset* GetAsset() const { return _asset; } /// /// Gets font size. /// API_PROPERTY() FORCE_INLINE float GetSize() const { return _size; } /// /// Gets characters height. /// API_PROPERTY() FORCE_INLINE int32 GetHeight() const { return _height; } /// /// Gets the largest vertical distance above the baseline for any character in the font. /// API_PROPERTY() FORCE_INLINE int32 GetAscender() const { return _ascender; } /// /// Gets the largest vertical distance below the baseline for any character in the font. /// API_PROPERTY() FORCE_INLINE int32 GetDescender() const { return _descender; } /// /// Gets the line gap property. /// API_PROPERTY() FORCE_INLINE int32 GetLineGap() const { return _lineGap; } public: /// /// Gets character entry. /// /// The character. /// The output character entry. /// 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. /// /// The first character in the pair. /// The second character in the pair. /// The kerning amount or 0 if no kerning. API_FUNCTION() int32 GetKerning(Char first, Char second) const; /// /// Caches the given text to prepared for the rendering. /// /// The text witch characters to cache. 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. /// API_FUNCTION() void Invalidate(); public: /// /// Processes text to get cached lines for rendering. /// /// The input text. /// The layout properties. /// The output lines list. void ProcessText(const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout, Array& characterEntries, Array& characterKernings); /// /// Processes text to get cached lines for rendering. /// /// The input text. /// The layout properties. /// The output lines list. API_FUNCTION() Array ProcessText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) { Array lines; Array characterEntries; Array xAdvances; ProcessText(text, lines, layout, characterEntries, xAdvances); return lines; } /// /// Processes text to get cached lines for rendering. /// /// The input text. /// The input text range (substring range of the input text parameter). /// The layout properties. /// The output lines list. API_FUNCTION() Array ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) { Array lines; Array characterEntries; Array xAdvances; ProcessText(textRange.Substring(text), lines, layout, characterEntries, xAdvances); return lines; } /// /// Processes text to get cached lines for rendering. /// /// The input text. /// The output lines list. API_FUNCTION() FORCE_INLINE Array ProcessText(const StringView& text) { return ProcessText(text, TextLayoutOptions()); } /// /// Processes text to get cached lines for rendering. /// /// The input text. /// The input text range (substring range of the input text parameter). /// The output lines list. API_FUNCTION() FORCE_INLINE Array ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange) { return ProcessText(textRange.Substring(text), TextLayoutOptions()); } /// /// Measures minimum size of the rectangle that will be needed to draw given text. /// /// The input text to test. /// The layout properties. /// The minimum size for that text and fot to render properly. API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); /// /// Measures minimum size of the rectangle that will be needed to draw given text. /// /// The input text to test. /// The input text range (substring range of the input text parameter). /// The layout properties. /// The minimum size for that text and fot to render properly. API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) { return MeasureText(textRange.Substring(text), layout); } /// /// Measures minimum size of the rectangle that will be needed to draw given text /// . /// The input text to test. /// The minimum size for that text and fot to render properly. API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text) { return MeasureText(text, TextLayoutOptions()); } /// /// Measures minimum size of the rectangle that will be needed to draw given text /// . /// The input text to test. /// The input text range (substring range of the input text parameter). /// The minimum size for that text and fot to render properly. API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange) { return MeasureText(textRange.Substring(text), TextLayoutOptions()); } /// /// Calculates hit character index at given location. /// /// The input text to test. /// The input text range (substring range of the input text parameter). /// The input location to test. /// The text layout properties. /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). API_FUNCTION() int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) { return HitTestText(textRange.Substring(text), location, layout); } /// /// Calculates hit character index at given location. /// /// The input text to test. /// The input location to test. /// The text layout properties. /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). API_FUNCTION() int32 HitTestText(const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); /// /// Calculates hit character index at given location. /// /// The input text to test. /// The input location to test. /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, const Float2& location) { return HitTestText(text, location, TextLayoutOptions()); } /// /// Calculates hit character index at given location. /// /// The input text to test. /// The input text range (substring range of the input text parameter). /// The input location to test. /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location) { return HitTestText(textRange.Substring(text), location, TextLayoutOptions()); } /// /// Calculates character position for given text and character index. /// /// The input text to test. /// The text position to get coordinates of. /// The text layout properties. /// The character position (upper left corner which can be used for a caret position). API_FUNCTION() Float2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); /// /// Calculates character position for given text and character index. /// /// The input text to test. /// The input text range (substring range of the input text parameter). /// The text position to get coordinates of. /// The text layout properties. /// The character position (upper left corner which can be used for a caret position). API_FUNCTION() Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) { return GetCharPosition(textRange.Substring(text), index, layout); } /// /// Calculates character position for given text and character index /// /// The input text to test. /// The text position to get coordinates of. /// The character position (upper left corner which can be used for a caret position). API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, int32 index) { return GetCharPosition(text, index, TextLayoutOptions()); } /// /// Calculates character position for given text and character index /// /// The input text to test. /// The input text range (substring range of the input text parameter). /// The text position to get coordinates of. /// The character position (upper left corner which can be used for a caret position). API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index) { return GetCharPosition(textRange.Substring(text), index, TextLayoutOptions()); } /// /// Flushes the size of the face with the Free Type library backend. /// void FlushFaceSize() const; public: // [Object] String ToString() const override; };