// 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;
};