Add font fallback

Note: All the `First()` in the code are temperary workarounds to make it work and require refractoring
This commit is contained in:
ExMatics HydrogenC
2023-11-28 07:17:46 +08:00
parent b3a18883ca
commit 47a25c7828
47 changed files with 787 additions and 87 deletions

View File

@@ -283,6 +283,206 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache>& outputLines
}
}
void Font::ProcessText(const Array<Font*>& fonts, const StringView& text, Array<FontLineCache>& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout)
{
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 baseLinesDistanceScale = layout.BaseLinesGapScale * scale;
tmpLine.Location = Float2::Zero;
tmpLine.Size = Float2::Zero;
tmpLine.FirstCharIndex = 0;
tmpLine.LastCharIndex = -1;
int32 lastWrapCharIndex = INVALID_INDEX;
float lastWrapCharX = 0;
bool lastMoveLine = false;
int32 previousFontIndex = -1;
// The maximum font height of the current line
float maxHeight = 0;
// Process each character to split text into single lines
for (int32 currentIndex = 0; currentIndex < textLength;)
{
bool moveLine = false;
float xAdvance = 0;
int32 nextCharIndex = currentIndex + 1;
// Cache current character
const Char currentChar = text[currentIndex];
const bool isWhitespace = StringUtils::IsWhitespace(currentChar);
// Check if character can wrap words
const bool isWrapChar = !StringUtils::IsAlnum(currentChar) || isWhitespace || StringUtils::IsUpper(currentChar);
if (isWrapChar && currentIndex != 0)
{
lastWrapCharIndex = currentIndex;
lastWrapCharX = cursorX;
}
// Check if it's a newline character
if (currentChar == '\n')
{
// Break line
moveLine = true;
currentIndex++;
tmpLine.LastCharIndex++;
}
else
{
// Get character entry
int32 fontIndex = 0;
while (fontIndex < fonts.Count() && !fonts[fontIndex]->ContainsChar(currentChar))
{
fontIndex++;
}
// If no font can match the char, then use the first font
if (fontIndex == fonts.Count()) {
fontIndex = 0;
}
// Get character entry
fonts[fontIndex]->GetCharacter(currentChar, entry);
maxHeight = Math::Max(maxHeight, static_cast<float>(fonts[fontIndex]->GetHeight()));
// Get kerning, only when the font hasn't changed
if (!isWhitespace && previous.IsValid && previousFontIndex == fontIndex)
{
kerning = fonts[fontIndex]->GetKerning(previous.Character, entry.Character);
}
else
{
kerning = 0;
}
previous = entry;
previousFontIndex = fontIndex;
xAdvance = (kerning + entry.AdvanceX) * scale;
// Check if character fits the line or skip wrapping
if (cursorX + xAdvance <= boundsWidth || layout.TextWrapping == TextWrapping::NoWrap)
{
// Move character
cursorX += xAdvance;
tmpLine.LastCharIndex++;
}
else if (layout.TextWrapping == TextWrapping::WrapWords)
{
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)
{
currentIndex = nextCharIndex;
lastMoveLine = moveLine;
continue;
}
// Move line
const Char wrapChar = text[lastWrapCharIndex];
moveLine = true;
cursorX = lastWrapCharX;
if (StringUtils::IsWhitespace(wrapChar))
{
// Skip whitespaces
tmpLine.LastCharIndex = lastWrapCharIndex - 1;
nextCharIndex = currentIndex = lastWrapCharIndex + 1;
}
else
{
tmpLine.LastCharIndex = lastWrapCharIndex - 1;
nextCharIndex = currentIndex = lastWrapCharIndex;
}
}
}
else if (layout.TextWrapping == TextWrapping::WrapChars)
{
// Move line
moveLine = true;
nextCharIndex = currentIndex;
// Skip moving twice for the same character
if (lastMoveLine)
break;
}
}
// Check if move to another line
if (moveLine)
{
// Add line
tmpLine.Size.X = cursorX;
tmpLine.Size.Y = baseLinesDistanceScale * maxHeight;
tmpLine.LastCharIndex = Math::Max(tmpLine.LastCharIndex, tmpLine.FirstCharIndex);
outputLines.Add(tmpLine);
// Reset line
tmpLine.Location.Y += baseLinesDistanceScale * maxHeight;
tmpLine.FirstCharIndex = currentIndex;
tmpLine.LastCharIndex = currentIndex - 1;
cursorX = 0;
lastWrapCharIndex = INVALID_INDEX;
lastWrapCharX = 0;
previous.IsValid = false;
// Reset max font height
maxHeight = 0;
}
currentIndex = nextCharIndex;
lastMoveLine = moveLine;
}
if (textLength != 0 && (tmpLine.LastCharIndex >= tmpLine.FirstCharIndex || text[textLength - 1] == '\n'))
{
// Add line
tmpLine.Size.X = cursorX;
tmpLine.Size.Y = baseLinesDistanceScale * maxHeight;
tmpLine.LastCharIndex = textLength - 1;
outputLines.Add(tmpLine);
tmpLine.Location.Y += baseLinesDistanceScale * maxHeight;
}
// Check amount of lines
if (outputLines.IsEmpty())
return;
float totalHeight = tmpLine.Location.Y;
Float2 offset = Float2::Zero;
if (layout.VerticalAlignment == TextAlignment::Center)
{
offset.Y += (layout.Bounds.GetHeight() - totalHeight) * 0.5f;
}
else if (layout.VerticalAlignment == TextAlignment::Far)
{
offset.Y += layout.Bounds.GetHeight() - totalHeight;
}
for (int32 i = 0; i < outputLines.Count(); i++)
{
FontLineCache& line = outputLines[i];
Float2 rootPos = line.Location + offset;
// Fix upper left line corner to match desire text alignment
if (layout.HorizontalAlignment == TextAlignment::Center)
{
rootPos.X += (layout.Bounds.GetWidth() - line.Size.X) * 0.5f;
}
else if (layout.HorizontalAlignment == TextAlignment::Far)
{
rootPos.X += layout.Bounds.GetWidth() - line.Size.X;
}
line.Location = rootPos;
}
}
Float2 Font::MeasureText(const StringView& text, const TextLayoutOptions& layout)
{
// Check if there is no need to do anything
@@ -432,6 +632,11 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo
return rootOffset + Float2(lines.Last().Size.X, static_cast<float>((lines.Count() - 1) * baseLinesDistance));
}
bool Font::ContainsChar(Char c)
{
return FT_Get_Char_Index(GetAsset()->GetFTFace(), c) > 0;
}
void Font::FlushFaceSize() const
{
// Set the character size

View File

@@ -339,6 +339,15 @@ public:
/// <param name="outputLines">The output lines list.</param>
void ProcessText(const StringView& text, Array<FontLineCache>& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
/// Processes text to get cached lines for rendering.
/// </summary>
/// <param name="fonts">The font list.</param>
/// <param name="text">The input text.</param>
/// <param name="layout">The layout properties.</param>
/// <param name="outputLines">The output lines list.</param>
static void ProcessText(const Array<Font*>& fonts, const StringView& text, Array<FontLineCache>& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
/// Processes text to get cached lines for rendering.
/// </summary>
@@ -395,6 +404,15 @@ public:
/// <returns>The minimum size for that text and fot to render properly.</returns>
API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
/// Measures minimum size of the rectangle that will be needed to draw given text.
/// </summary>
/// <param name="fonts">The fonts to render with.</param>
/// <param name="text">The input text to test.</param>
/// <param name="layout">The layout properties.</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
API_FUNCTION() static Float2 MeasureText(const Array<Font*>& fonts, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
/// Measures minimum size of the rectangle that will be needed to draw given text.
/// </summary>
@@ -482,6 +500,16 @@ public:
/// <returns>The character position (upper left corner which can be used for a caret position).</returns>
API_FUNCTION() Float2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
/// Calculates character position for given text and character index.
/// </summary>
/// <param name="fonts">The fonts to use.</param>
/// <param name="text">The input text to test.</param>
/// <param name="index">The text position to get coordinates of.</param>
/// <param name="layout">The text layout properties.</param>
/// <returns>The character position (upper left corner which can be used for a caret position).</returns>
API_FUNCTION() static Float2 GetCharPosition(const Array<Font*>& fonts, const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
/// Calculates character position for given text and character index.
/// </summary>
@@ -518,6 +546,13 @@ public:
return GetCharPosition(textRange.Substring(text), index, TextLayoutOptions());
}
/// <summary>
/// Check if the font contains the glyph of a char
/// </summary>
/// <param name="c">The char to test.</param>
/// <returns>True if the font contains the glyph of the char, otherwise false.</returns>
API_FUNCTION() FORCE_INLINE bool ContainsChar(Char c);
/// <summary>
/// Flushes the size of the face with the Free Type library backend.
/// </summary>

View File

@@ -155,6 +155,9 @@ 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 (glyphIndex == 0) {
LOG(Warning, "Font `{}` doesn't contain character `\\u{:x}`, consider choosing another font. ", String(face->family_name), c);
}
// Load the glyph
const FT_Error error = FT_Load_Glyph(face, glyphIndex, glyphFlags);

View File

@@ -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
@@ -54,7 +54,7 @@ const bool DownsampleForBlur = false;
PACK_STRUCT(struct Data {
Matrix ViewProjection;
});
});
PACK_STRUCT(struct BlurData {
Float2 InvBufferSize;
@@ -62,7 +62,7 @@ PACK_STRUCT(struct BlurData {
float Dummy0;
Float4 Bounds;
Float4 WeightAndOffsets[RENDER2D_BLUR_MAX_SAMPLES / 2];
});
});
enum class DrawCallType : byte
{
@@ -1174,7 +1174,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
drawCall.AsChar.Mat = nullptr;
}
Float2 pointer = location;
for (int32 currentIndex = 0; currentIndex <= text.Length(); currentIndex++)
for (int32 currentIndex = 0; currentIndex < text.Length(); currentIndex++)
{
// Cache current character
const Char currentChar = text[currentIndex];
@@ -1368,6 +1368,318 @@ void Render2D::DrawText(Font* font, const StringView& text, const TextRange& tex
DrawText(font, textRange.Substring(text), color, layout, customMaterial);
}
void Render2D::DrawText(const Array<Font*>& fonts, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial)
{
RENDER2D_CHECK_RENDERING_STATE;
// Check if there is no need to do anything
if (fonts.IsEmpty() || text.Length() < 0)
return;
// Temporary data
uint32 fontAtlasIndex = 0;
FontTextureAtlas* fontAtlas = nullptr;
Float2 invAtlasSize = Float2::One;
FontCharacterEntry previous;
int32 kerning;
float scale = 1.0f / FontManager::FontScale;
// Render all characters
FontCharacterEntry entry;
Render2DDrawCall drawCall;
if (customMaterial)
{
drawCall.Type = DrawCallType::DrawCharMaterial;
drawCall.AsChar.Mat = customMaterial;
}
else
{
drawCall.Type = DrawCallType::DrawChar;
drawCall.AsChar.Mat = nullptr;
}
// The following code cut the text into segments, according to the font used to render
Float2 pointer = location;
// The starting index of the current segment
int32 startIndex = 0;
// The index of the font used by the current segment
int32 segmentFontIndex = 0;
// The maximum font height of the current line
float maxHeight = 0;
for (int32 currentIndex = 0; currentIndex <= text.Length(); currentIndex++)
{
// Cache current character
const Char currentChar = currentIndex < text.Length() ? text[currentIndex] : 0;
// Check if it isn't a newline character
if (currentChar != '\n')
{
int32 fontIndex = 0;
if (currentIndex < text.Length()) {
while (fontIndex < fonts.Count() && !fonts[fontIndex]->ContainsChar(currentChar))
{
fontIndex++;
}
// If no font can match the char, then use the segment font
if (fontIndex == fonts.Count()) {
fontIndex = segmentFontIndex;
}
// Do nothing if the char still belongs to the current segment
if (fontIndex == segmentFontIndex) {
continue;
}
}
// Render the pending segment before beginning the new segment
renderText:auto fontHeight = fonts[segmentFontIndex]->GetHeight();
maxHeight = Math::Max(maxHeight, static_cast<float>(fontHeight));
auto fontDescender = fonts[segmentFontIndex]->GetDescender();
for (int32 renderIndex = startIndex; renderIndex < currentIndex; renderIndex++)
{
// Get character entry
fonts[segmentFontIndex]->GetCharacter(text[renderIndex], entry);
// 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)
{
// Get texture atlas that contains current character
fontAtlasIndex = entry.TextureIndex;
fontAtlas = FontManager::GetAtlas(fontAtlasIndex);
if (fontAtlas)
{
fontAtlas->EnsureTextureCreated();
drawCall.AsChar.Tex = fontAtlas->GetTexture();
invAtlasSize = 1.0f / fontAtlas->GetSize();
}
else
{
drawCall.AsChar.Tex = nullptr;
invAtlasSize = 1.0f;
}
}
// Check if character is a whitespace
const bool isWhitespace = StringUtils::IsWhitespace(text[renderIndex]);
// Get kerning
if (!isWhitespace && previous.IsValid)
{
kerning = fonts[segmentFontIndex]->GetKerning(previous.Character, entry.Character);
}
else
{
kerning = 0;
}
pointer.X += kerning * scale;
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 + (fontHeight + fontDescender - entry.OffsetY) * scale;
Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale);
Float2 upperLeftUV = entry.UV * invAtlasSize;
Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize;
// Add draw call
drawCall.StartIB = IBIndex;
drawCall.CountIB = 6;
DrawCalls.Add(drawCall);
WriteRect(charRect, color, upperLeftUV, rightBottomUV);
}
// Move
pointer.X += entry.AdvanceX * scale;
}
// Start new segment
startIndex = currentIndex;
segmentFontIndex = fontIndex;
if (currentIndex == text.Length() - 1) {
currentIndex++;
goto renderText;
}
}
else
{
// Move
pointer.X = location.X;
pointer.Y += maxHeight * scale;
// Clear max height
maxHeight = 0;
}
}
}
void Render2D::DrawText(const Array<Font*>& fonts, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial)
{
DrawText(fonts, textRange.Substring(text), color, location, customMaterial);
}
void Render2D::DrawText(const Array<Font*>& fonts, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial)
{
RENDER2D_CHECK_RENDERING_STATE;
// Check if there is no need to do anything
if (fonts.IsEmpty() || text.IsEmpty() || layout.Scale <= ZeroTolerance)
return;
// Temporary data
uint32 fontAtlasIndex = 0;
FontTextureAtlas* fontAtlas = nullptr;
Float2 invAtlasSize = Float2::One;
FontCharacterEntry previous;
int32 kerning;
float scale = layout.Scale / FontManager::FontScale;
// Process text to get lines
Lines.Clear();
Font::ProcessText(fonts, text, Lines, layout);
// Render all lines
FontCharacterEntry entry;
Render2DDrawCall drawCall;
if (customMaterial)
{
drawCall.Type = DrawCallType::DrawCharMaterial;
drawCall.AsChar.Mat = customMaterial;
}
else
{
drawCall.Type = DrawCallType::DrawChar;
drawCall.AsChar.Mat = nullptr;
}
for (int32 lineIndex = 0; lineIndex < Lines.Count(); lineIndex++)
{
const FontLineCache& line = Lines[lineIndex];
// The following code cut the text into segments, according to the font used to render
Float2 pointer = line.Location;
// The starting index of the current segment
int32 startIndex = line.FirstCharIndex;
// The index of the font used by the current segment
int32 segmentFontIndex = 0;
// The maximum font height of the current line
float maxHeight = 0;
// Partition and render all characters from the line
for (int32 charIndex = line.FirstCharIndex; charIndex <= line.LastCharIndex + 1; charIndex++)
{
const Char c = charIndex <= line.LastCharIndex ? text[charIndex] : 0;
if (c != '\n')
{
int32 fontIndex = 0;
if (charIndex <= line.LastCharIndex) {
while (fontIndex < fonts.Count() && !fonts[fontIndex]->ContainsChar(c))
{
fontIndex++;
}
// If no font can match the char, then use the segment font
if (fontIndex == fonts.Count()) {
fontIndex = segmentFontIndex;
}
// Do nothing if the char still belongs to the current segment
if (fontIndex == segmentFontIndex) {
continue;
}
}
// Render the pending segment before beginning the new segment
auto fontHeight = fonts[segmentFontIndex]->GetHeight();
maxHeight = Math::Max(maxHeight, static_cast<float>(fontHeight));
auto fontDescender = fonts[segmentFontIndex]->GetDescender();
const Char* pred = L"Type";
if (text.Substring(0, Math::Min(4, text.Length())) == pred) {
// __debugbreak();
}
for (int32 renderIndex = startIndex; renderIndex < charIndex; renderIndex++)
{
// Get character entry
fonts[segmentFontIndex]->GetCharacter(text[renderIndex], entry);
// 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)
{
// Get texture atlas that contains current character
fontAtlasIndex = entry.TextureIndex;
fontAtlas = FontManager::GetAtlas(fontAtlasIndex);
if (fontAtlas)
{
fontAtlas->EnsureTextureCreated();
invAtlasSize = 1.0f / fontAtlas->GetSize();
drawCall.AsChar.Tex = fontAtlas->GetTexture();
}
else
{
invAtlasSize = 1.0f;
drawCall.AsChar.Tex = nullptr;
}
}
// Get kerning
const bool isWhitespace = StringUtils::IsWhitespace(text[renderIndex]);
if (!isWhitespace && previous.IsValid)
{
kerning = fonts[segmentFontIndex]->GetKerning(previous.Character, entry.Character);
}
else
{
kerning = 0;
}
pointer.X += (float)kerning * scale;
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((fontHeight + fontDescender) * scale);
Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale);
charRect.Offset(layout.Bounds.Location);
Float2 upperLeftUV = entry.UV * invAtlasSize;
Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize;
// Add draw call
drawCall.StartIB = IBIndex;
drawCall.CountIB = 6;
DrawCalls.Add(drawCall);
WriteRect(charRect, color, upperLeftUV, rightBottomUV);
}
// Move
pointer.X += entry.AdvanceX * scale;
}
// Start new segment
startIndex = charIndex;
segmentFontIndex = fontIndex;
}
}
}
}
void Render2D::DrawText(const Array<Font*>& fonts, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial)
{
DrawText(fonts, textRange.Substring(text), color, layout, customMaterial);
}
FORCE_INLINE bool NeedAlphaWithTint(const Color& color)
{
return (color.A * TintLayersStack.Peek().A) < 1.0f;
@@ -1931,7 +2243,7 @@ void Render2D::DrawBlur(const Rectangle& rect, float blurStrength)
void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Float2>& vertices, const Span<Float2>& 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 +2289,7 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<uint16>& 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++];

View File

@@ -152,6 +152,59 @@ namespace FlaxEngine
DrawText(font, text, color, ref layout, customMaterial);
}
/// <summary>
/// Draws a text.
/// </summary>
/// <param name="fonts">The fonts to use, ordered by priority.</param>
/// <param name="text">The text to render.</param>
/// <param name="layoutRect">The size and position of the area in which the text is drawn.</param>
/// <param name="color">The text color.</param>
/// <param name="horizontalAlignment">The horizontal alignment of the text in a layout rectangle.</param>
/// <param name="verticalAlignment">The vertical alignment of the text in a layout rectangle.</param>
/// <param name="textWrapping">Describes how wrap text inside a layout rectangle.</param>
/// <param name="baseLinesGapScale">The scale for distance one baseline from another. Default is 1.</param>
/// <param name="scale">The text drawing scale. Default is 1.</param>
public static void DrawText(Font[] fonts, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f)
{
var layout = new TextLayoutOptions
{
Bounds = layoutRect,
HorizontalAlignment = horizontalAlignment,
VerticalAlignment = verticalAlignment,
TextWrapping = textWrapping,
Scale = scale,
BaseLinesGapScale = baseLinesGapScale,
};
DrawText(fonts, text, color, ref layout);
}
/// <summary>
/// Draws a text using a custom material shader. Given material must have GUI domain and a public parameter named Font (texture parameter used for a font atlas sampling).
/// </summary>
/// <param name="fonts">The fonts to use, ordered by priority.</param>
/// <param name="customMaterial">Custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture.</param>
/// <param name="text">The text to render.</param>
/// <param name="layoutRect">The size and position of the area in which the text is drawn.</param>
/// <param name="color">The text color.</param>
/// <param name="horizontalAlignment">The horizontal alignment of the text in a layout rectangle.</param>
/// <param name="verticalAlignment">The vertical alignment of the text in a layout rectangle.</param>
/// <param name="textWrapping">Describes how wrap text inside a layout rectangle.</param>
/// <param name="baseLinesGapScale">The scale for distance one baseline from another. Default is 1.</param>
/// <param name="scale">The text drawing scale. Default is 1.</param>
public static void DrawText(Font[] fonts, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f)
{
var layout = new TextLayoutOptions
{
Bounds = layoutRect,
HorizontalAlignment = horizontalAlignment,
VerticalAlignment = verticalAlignment,
TextWrapping = textWrapping,
Scale = scale,
BaseLinesGapScale = baseLinesGapScale,
};
DrawText(fonts, text, color, ref layout, customMaterial);
}
/// <summary>
/// Calls drawing GUI to the texture.
/// </summary>

View File

@@ -33,7 +33,7 @@ API_CLASS(Static) class FLAXENGINE_API Render2D
/// <summary>
/// The rendering features and options flags.
/// </summary>
API_ENUM(Attributes="Flags") enum class RenderingFeatures
API_ENUM(Attributes = "Flags") enum class RenderingFeatures
{
/// <summary>
/// The none.
@@ -215,6 +215,49 @@ public:
/// <param name="customMaterial">The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture.</param>
API_FUNCTION() static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr);
/// <summary>
/// Draws a text.
/// </summary>
/// <param name="fonts">The fonts to use, ordered by priority.</param>
/// <param name="text">The text to render.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="color">The text color.</param>
/// <param name="location">The text location.</param>
/// <param name="customMaterial">The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture.</param>
API_FUNCTION() static void DrawText(const Array<Font*, HeapAllocation>& fonts, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr);
/// <summary>
/// Draws a text with formatting.
/// </summary>
/// <param name="fonts">The fonts to use, ordered by priority.</param>
/// <param name="text">The text to render.</param>
/// <param name="color">The text color.</param>
/// <param name="layout">The text layout properties.</param>
/// <param name="customMaterial">The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture.</param>
API_FUNCTION() static void DrawText(const Array<Font*, HeapAllocation>& fonts, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr);
/// <summary>
/// Draws a text with formatting.
/// </summary>
/// <param name="fonts">The fonts to use, ordered by priority.</param>
/// <param name="text">The text to render.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="color">The text color.</param>
/// <param name="layout">The text layout properties.</param>
/// <param name="customMaterial">The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture.</param>
API_FUNCTION() static void DrawText(const Array<Font*, HeapAllocation>& fonts, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr);
/// <summary>
/// Draws a text with formatting.
/// </summary>
/// <param name="fonts">The fonts to use, ordered by priority.</param>
/// <param name="text">The text to render.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="color">The text color.</param>
/// <param name="layout">The text layout properties.</param>
/// <param name="customMaterial">The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture.</param>
API_FUNCTION() static void DrawText(const Array<Font*, HeapAllocation>& fonts, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr);
/// <summary>
/// Fills a rectangle area.
/// </summary>

View File

@@ -295,12 +295,13 @@ namespace FlaxEngine
// Use optionally bundled default font (matches Editor)
var defaultFont = Content.LoadAsyncInternal<FontAsset>("Editor/Fonts/Roboto-Regular");
var cjkFont = Content.LoadAsyncInternal<FontAsset>("NotoSansSC-Medium");
if (defaultFont)
{
style.FontTitle = defaultFont.CreateFont(18);
style.FontLarge = defaultFont.CreateFont(14);
style.FontMedium = defaultFont.CreateFont(9);
style.FontSmall = defaultFont.CreateFont(9);
style.FontMedium = new Font[] { defaultFont.CreateFont(9), cjkFont.CreateFont(9) };
style.FontSmall = new Font[] { defaultFont.CreateFont(9), cjkFont.CreateFont(9) };
}
Style.Current = style;

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Linq;
namespace FlaxEngine.GUI
{
@@ -155,7 +156,7 @@ namespace FlaxEngine.GUI
var style = Style.Current;
if (style != null)
{
_font = new FontReference(style.FontMedium);
_font = new FontReference(style.FontMedium.First());
TextColor = style.Foreground;
BackgroundColor = style.BackgroundNormal;
BorderColor = style.BorderNormal;

View File

@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace FlaxEngine.GUI
{
@@ -358,7 +359,7 @@ namespace FlaxEngine.GUI
: base(0, 0, 120, 18.0f)
{
var style = Style.Current;
Font = new FontReference(style.FontMedium);
Font = new FontReference(style.FontMedium.First());
TextColor = style.Foreground;
BackgroundColor = style.BackgroundNormal;
BackgroundColorHighlighted = BackgroundColor;

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.Options;
using System.ComponentModel;
using System.Linq;
namespace FlaxEngine.GUI
{
@@ -190,7 +192,7 @@ namespace FlaxEngine.GUI
{
AutoFocus = false;
var style = Style.Current;
Font = new FontReference(style.FontMedium);
Font = new FontReference(style.FontMedium.First());
TextColor = style.Foreground;
TextColorHighlighted = style.Foreground;
}
@@ -201,7 +203,7 @@ namespace FlaxEngine.GUI
{
AutoFocus = false;
var style = Style.Current;
Font = new FontReference(style.FontMedium);
Font = new FontReference(style.FontMedium.First());
TextColor = style.Foreground;
TextColorHighlighted = style.Foreground;
}
@@ -233,7 +235,7 @@ namespace FlaxEngine.GUI
}
}
Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale);
Render2D.DrawText(new Font[] { _font.GetFont(), Style.Current.FontCJK }, Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale);
if (ClipText)
Render2D.PopClip();

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.Linq;
namespace FlaxEngine.GUI
{
@@ -45,7 +46,7 @@ namespace FlaxEngine.GUI
var style = Style.Current;
_textStyle = new TextBlockStyle
{
Font = new FontReference(style.FontMedium),
Font = new FontReference(style.FontMedium.First()),
Color = style.Foreground,
BackgroundSelectedBrush = new SolidColorBrush(style.BackgroundSelected),
};

View File

@@ -1,5 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.Linq;
namespace FlaxEngine.GUI
{
/// <summary>
@@ -88,7 +90,7 @@ namespace FlaxEngine.GUI
_layout.Bounds = new Rectangle(DefaultMargin, 1, Width - 2 * DefaultMargin, Height - 2);
var style = Style.Current;
Font = new FontReference(style.FontMedium);
Font = new FontReference(style.FontMedium.First());
TextColor = style.Foreground;
WatermarkTextColor = style.ForegroundDisabled;
SelectionColor = style.BackgroundSelected;

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Linq;
namespace FlaxEngine.GUI
{
@@ -237,7 +238,7 @@ namespace FlaxEngine.GUI
var style = Style.Current;
HeaderColor = style.BackgroundNormal;
HeaderColorMouseOver = style.BackgroundHighlighted;
HeaderTextFont = new FontReference(style.FontMedium);
HeaderTextFont = new FontReference(style.FontMedium.First());
HeaderTextColor = style.Foreground;
ArrowImageOpened = new SpriteBrush(style.ArrowDown);
ArrowImageClosed = new SpriteBrush(style.ArrowRight);

View File

@@ -1,5 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.Linq;
namespace FlaxEngine.GUI
{
/// <summary>
@@ -12,6 +14,14 @@ namespace FlaxEngine.GUI
/// </summary>
public static Style Current { get; set; }
public Font FontCJK
{
get => _fontCJK?.GetFont();
set => _fontCJK = new FontReference(value);
}
private FontReference _fontCJK;
[Serialize]
private FontReference _fontTitle;
@@ -41,31 +51,31 @@ namespace FlaxEngine.GUI
}
[Serialize]
private FontReference _fontMedium;
private FontReference[] _fontMedium;
/// <summary>
/// The font medium.
/// </summary>
[NoSerialize]
[EditorOrder(30)]
public Font FontMedium
public Font[] FontMedium
{
get => _fontMedium?.GetFont();
set => _fontMedium = new FontReference(value);
get => _fontMedium?.Select((x)=>x.GetFont()).ToArray();
set => _fontMedium = value.Select((x)=>new FontReference(x)).ToArray();
}
[Serialize]
private FontReference _fontSmall;
private FontReference[] _fontSmall;
/// <summary>
/// The font small.
/// </summary>
[NoSerialize]
[EditorOrder(40)]
public Font FontSmall
public Font[] FontSmall
{
get => _fontSmall?.GetFont();
set => _fontSmall = new FontReference(value);
get => _fontSmall?.Select((x) => x.GetFont()).ToArray();
set => _fontSmall = value.Select((x) => new FontReference(x)).ToArray();
}
/// <summary>

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Linq;
namespace FlaxEngine.GUI
{
@@ -255,14 +256,14 @@ namespace FlaxEngine.GUI
// Calculate size of the tooltip
var size = Float2.Zero;
if (style != null && style.FontMedium && !string.IsNullOrEmpty(_currentText))
if (style != null && style.FontMedium.First() && !string.IsNullOrEmpty(_currentText))
{
var layout = TextLayoutOptions.Default;
layout.Bounds = new Rectangle(0, 0, MaxWidth, 10000000);
layout.HorizontalAlignment = TextAlignment.Center;
layout.VerticalAlignment = TextAlignment.Center;
layout.TextWrapping = TextWrapping.WrapWords;
var items = style.FontMedium.ProcessText(_currentText, ref layout);
var items = style.FontMedium.First().ProcessText(_currentText, ref layout);
for (int i = 0; i < items.Length; i++)
{
ref var item = ref items[i];