Merge branch 'master' of https://github.com/HydrogenC/FlaxEngine into HydrogenC-master

This commit is contained in:
Wojtek Figat
2024-02-18 16:11:31 +01:00
29 changed files with 1762 additions and 150 deletions

1
.gitignore vendored
View File

@@ -157,3 +157,4 @@ obj/
.idea/
*.code-workspace
omnisharp.json
Content/Editor/Fonts/NotoSansSC-Regular.flax

View File

@@ -54,6 +54,8 @@ namespace FlaxEditor
/// </summary>
public static string PrimaryFont = "Editor/Fonts/Roboto-Regular";
public static string CjkFont = "Editor/Fonts/NotoSansSC-Regular";
/// <summary>
/// The Inconsolata Regular font.
/// </summary>

View File

@@ -43,8 +43,9 @@ namespace FlaxEditor.GUI
{
Depth = -1;
if (Height < Style.Current.FontMedium.Height)
Height = Style.Current.FontMedium.Height + 4;
var mediumHeight = Style.Current.FontMedium.GetMaxHeight();
if (Height < mediumHeight)
Height = mediumHeight + 4;
}
/// <inheritdoc />

View File

@@ -36,16 +36,16 @@ namespace FlaxEditor.GUI.Timeline.GUI
string labelText;
switch (_timeline.TimeShowMode)
{
case Timeline.TimeShowModes.Frames:
labelText = _timeline.CurrentFrame.ToString("###0", CultureInfo.InvariantCulture);
break;
case Timeline.TimeShowModes.Seconds:
labelText = _timeline.CurrentTime.ToString("###0.##'s'", CultureInfo.InvariantCulture);
break;
case Timeline.TimeShowModes.Time:
labelText = TimeSpan.FromSeconds(_timeline.CurrentTime).ToString("g");
break;
default: throw new ArgumentOutOfRangeException();
case Timeline.TimeShowModes.Frames:
labelText = _timeline.CurrentFrame.ToString("###0", CultureInfo.InvariantCulture);
break;
case Timeline.TimeShowModes.Seconds:
labelText = _timeline.CurrentTime.ToString("###0.##'s'", CultureInfo.InvariantCulture);
break;
case Timeline.TimeShowModes.Time:
labelText = TimeSpan.FromSeconds(_timeline.CurrentTime).ToString("g");
break;
default: throw new ArgumentOutOfRangeException();
}
var color = (_timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground).AlphaMultiplied(0.6f);
Matrix3x3.RotationZ(Mathf.PiOverTwo, out var m1);

View File

@@ -237,11 +237,19 @@ namespace FlaxEditor.Options
public int NumberOfGameClientsToLaunch = 1;
private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.PrimaryFont);
private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.PrimaryFont);
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<FontAsset>(EditorAssets.InconsolataRegularFont), 10);
private FontReference _outputLogFont = new FontReference(ConsoleFont, 10);
/// <summary>
/// The fallback fonts.
/// </summary>
public FontAsset[] Fallbacks = [FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.CjkFont)];
/// <summary>
/// Gets or sets the title font for editor UI.

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using FlaxEditor.Content.Settings;
using FlaxEditor.Modules;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -217,12 +218,18 @@ namespace FlaxEditor.Options
if (styleName == ThemeOptions.LightDefault)
{
Style.Current = CreateLightStyle();
}
}
else
{
Style.Current = CreateDefaultStyle();
}
}
var graphicsSetttings = GameSettings.Load<GraphicsSettings>();
if (graphicsSetttings.EnableFontFallback && graphicsSetttings.FallbackFonts == null)
{
Render2D.FallbackFonts = graphicsSetttings.FallbackFonts = FontFallbackList.Create(Options.Interface.Fallbacks);
}
}
/// <summary>

View File

@@ -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);

View File

@@ -562,12 +562,12 @@ namespace FlaxEditor.Windows
{
switch (match.Groups["level"].Value)
{
case "error":
textBlock.Style = _output.ErrorStyle;
break;
case "warning":
textBlock.Style = _output.WarningStyle;
break;
case "error":
textBlock.Style = _output.ErrorStyle;
break;
case "warning":
textBlock.Style = _output.WarningStyle;
break;
}
textBlock.Tag = new TextBlockTag
{

View File

@@ -258,13 +258,13 @@ void SplashScreen::OnDraw()
return;
// Title
const auto titleLength = _titleFont->MeasureText(GetTitle());
const auto titleLength = _titleFont->MeasureTextInternal(GetTitle());
TextLayoutOptions layout;
layout.Bounds = Rectangle(10 * s, 10 * s, width - 10 * s, 50 * s);
layout.HorizontalAlignment = TextAlignment::Near;
layout.VerticalAlignment = TextAlignment::Near;
layout.Scale = Math::Min((width - 20 * s) / titleLength.X, 1.0f);
Render2D::DrawText(_titleFont, GetTitle(), Color::White, layout);
Render2D::DrawTextInternal(_titleFont, GetTitle(), Color::White, layout);
// Subtitle
String subtitle(_quote);
@@ -279,14 +279,14 @@ void SplashScreen::OnDraw()
layout.Scale = 1.0f;
layout.HorizontalAlignment = TextAlignment::Far;
layout.VerticalAlignment = TextAlignment::Far;
Render2D::DrawText(_subtitleFont, subtitle, Color::FromRGB(0x8C8C8C), layout);
Render2D::DrawTextInternal(_subtitleFont, subtitle, Color::FromRGB(0x8C8C8C), layout);
// Additional info
const float infoMargin = 6 * s;
layout.Bounds = Rectangle(infoMargin, lightBarHeight + infoMargin, width - (2 * infoMargin), height - lightBarHeight - (2 * infoMargin));
layout.HorizontalAlignment = TextAlignment::Near;
layout.VerticalAlignment = TextAlignment::Center;
Render2D::DrawText(_subtitleFont, _infoText, Color::FromRGB(0xFFFFFF) * 0.9f, layout);
Render2D::DrawTextInternal(_subtitleFont, _infoText, Color::FromRGB(0xFFFFFF) * 0.9f, layout);
}
bool SplashScreen::HasLoadedFonts() const

View File

@@ -119,7 +119,7 @@ namespace FlaxEditor.Content.Settings
/// <summary>
/// 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).
/// </summary>
[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<string, JsonAsset> CustomSettings;
#if FLAX_EDITOR || PLATFORM_WINDOWS

View File

@@ -7,6 +7,8 @@
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Collections/Dictionary.h"
class FontFallbackList;
/// <summary>
/// The main game engine configuration service. Loads and applies game configuration.
/// </summary>

View File

@@ -6,6 +6,8 @@
#include "Engine/Graphics/Enums.h"
#include "Engine/Graphics/PostProcessSettings.h"
class FontFallbackList;
/// <summary>
/// Graphics rendering settings.
/// </summary>
@@ -118,6 +120,18 @@ public:
API_FIELD(Attributes="EditorOrder(10000), EditorDisplay(\"Post Process Settings\", EditorDisplayAttribute.InlineStyle)")
PostProcessSettings PostProcessSettings;
/// <summary>
/// Whether to enable font fallbacking globally.
/// </summary>
API_FIELD(Attributes = "EditorOrder(12000), EditorDisplay(\"Text Render Settings\", EditorDisplayAttribute.InlineStyle)")
bool EnableFontFallback = true;
/// <summary>
/// The fallback fonts used for text rendering, ignored if null.
/// </summary>
API_FIELD(Attributes = "EditorOrder(12005), EditorDisplay(\"Text Render Settings\", EditorDisplayAttribute.InlineStyle)")
FontFallbackList* FallbackFonts;
private:
/// <summary>
/// Renamed UeeHDRProbes into UseHDRProbes

View File

@@ -8,6 +8,7 @@
#include "Engine/Core/Config/GraphicsSettings.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Render2D/Render2D.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);
Render2D::EnableFontFallback = EnableFontFallback;
Render2D::FallbackFonts = FallbackFonts;
}
void Graphics::DisposeDevice()

View File

@@ -0,0 +1,11 @@
#include "FallbackFonts.h"
#include "FontManager.h"
#include "Engine/Core/Math/Math.h"
FontFallbackList::FontFallbackList(const Array<FontAsset*>& fonts)
: ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer)),
_fontAssets(fonts)
{
}

View File

@@ -0,0 +1,119 @@
#pragma once
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Font.h"
#include "FontAsset.h"
struct TextRange;
class Font;
class FontAsset;
/// <summary>
/// Defines a list of fonts that can be used as a fallback, ordered by priority.
/// </summary>
API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FontFallbackList : public ManagedScriptingObject
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(FontFallbackList);
private:
Array<FontAsset*> _fontAssets;
// Cache fallback fonts of various sizes
Dictionary<float, Array<Font*>*> _cache;
public:
/// <summary>
/// Initializes a new instance of the <see cref="FontFallbackList"/> class.
/// </summary>
/// <param name="fonts">The fallback font assets.</param>
FontFallbackList(const Array<FontAsset*>& fonts);
/// <summary>
/// Initializes a new instance of the <see cref="FontFallbackList"/> class, exposed for C#.
/// </summary>
/// <param name="fonts">The fallback font assets.</param>
/// <returns>The new instance.</returns>
API_FUNCTION() FORCE_INLINE static FontFallbackList* Create(const Array<FontAsset*>& fonts) {
return New<FontFallbackList>(fonts);
}
/// <summary>
/// Get the parent assets of fallback fonts.
/// </summary>
/// <returns>The font assets.</returns>
API_PROPERTY() FORCE_INLINE Array<FontAsset*>& GetFonts() {
return _fontAssets;
}
/// <summary>
/// Set the fallback fonts.
/// </summary>
/// <param name="val">The parent assets of the new fonts.</param>
API_PROPERTY() FORCE_INLINE void SetFonts(const Array<FontAsset*>& val) {
_fontAssets = val;
}
/// <summary>
/// Gets the fallback fonts with the given size.
/// </summary>
/// <param name="size">The size.</param>
/// <returns>The generated fonts.</returns>
API_FUNCTION() FORCE_INLINE Array<Font*>& GetFontList(float size) {
Array<Font*>* result;
if (_cache.TryGet(size, result)) {
return *result;
}
result = New<Array<Font*>>(_fontAssets.Count());
auto& arr = *result;
for (int32 i = 0; i < _fontAssets.Count(); i++)
{
arr.Add(_fontAssets[i]->CreateFont(size));
}
_cache[size] = result;
return *result;
}
/// <summary>
/// Gets the index of the fallback font that should be used to render the char
/// </summary>
/// <param name="c">The char.</param>
/// <param name="primaryFont">The primary font.</param>
/// <param name="missing">The number to return if none of the fonts can render.</param>
/// <returns>-1 if char can be rendered with primary font, index if it matches a fallback font. </returns>
API_FUNCTION() FORCE_INLINE int32 GetCharFallbackIndex(Char c, Font* primaryFont = nullptr, int32 missing = -1) {
if (primaryFont && primaryFont->GetAsset()->ContainsChar(c)) {
return -1;
}
int32 fontIndex = 0;
while (fontIndex < _fontAssets.Count() && _fontAssets[fontIndex] && !_fontAssets[fontIndex]->ContainsChar(c))
{
fontIndex++;
}
if (fontIndex < _fontAssets.Count()) {
return fontIndex;
}
return missing;
}
/// <summary>
/// Checks if every font is properly loaded.
/// </summary>
/// <returns>True if every font asset is non-null, otherwise false.</returns>
API_FUNCTION() FORCE_INLINE bool Verify() {
for (int32 i = 0; i < _fontAssets.Count(); i++)
{
if (!_fontAssets[i]) {
return false;
}
}
return true;
}
};

View File

@@ -6,6 +6,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Threading/Threading.h"
#include "IncludeFreeType.h"
#include "FallbackFonts.h"
Font::Font(FontAsset* parentAsset, float size)
: ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer))
@@ -102,6 +103,18 @@ void Font::Invalidate()
_characters.Clear();
}
float Font::GetMaxHeight(FontFallbackList* fallbacks) const
{
float height = GetHeight();
auto& fallbackFonts = fallbacks->GetFontList(GetSize());
for (int32 i = 0; i < fallbackFonts.Count(); i++)
{
height = Math::Max(height, static_cast<float>(fallbackFonts[i]->GetHeight()));
}
return height;
}
void Font::ProcessText(const StringView& text, Array<FontLineCache>& outputLines, const TextLayoutOptions& layout)
{
float cursorX = 0;
@@ -118,6 +131,10 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache>& outputLines
tmpLine.FirstCharIndex = 0;
tmpLine.LastCharIndex = -1;
if (textLength == 0) {
return;
}
int32 lastWrapCharIndex = INVALID_INDEX;
float lastWrapCharX = 0;
bool lastMoveLine = false;
@@ -129,6 +146,11 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache>& outputLines
float xAdvance = 0;
int32 nextCharIndex = currentIndex + 1;
// Submit line if text ends
if (nextCharIndex == textLength) {
moveLine = true;
}
// Cache current character
const Char currentChar = text[currentIndex];
const bool isWhitespace = StringUtils::IsWhitespace(currentChar);
@@ -146,7 +168,6 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache>& outputLines
{
// Break line
moveLine = true;
currentIndex++;
tmpLine.LastCharIndex++;
}
else
@@ -178,8 +199,8 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache>& 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;
@@ -226,8 +247,8 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache>& outputLines
// Reset line
tmpLine.Location.Y += baseLinesDistance;
tmpLine.FirstCharIndex = currentIndex;
tmpLine.LastCharIndex = currentIndex - 1;
tmpLine.FirstCharIndex = nextCharIndex;
tmpLine.LastCharIndex = nextCharIndex - 1;
cursorX = 0;
lastWrapCharIndex = INVALID_INDEX;
lastWrapCharX = 0;
@@ -238,7 +259,8 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache>& outputLines
lastMoveLine = moveLine;
}
if (textLength != 0 && (tmpLine.LastCharIndex >= tmpLine.FirstCharIndex || text[textLength - 1] == '\n'))
// Check if an additional line should be created
if (text[textLength - 1] == '\n')
{
// Add line
tmpLine.Size.X = cursorX;
@@ -283,7 +305,262 @@ void Font::ProcessText(const StringView& text, Array<FontLineCache>& outputLines
}
}
Float2 Font::MeasureText(const StringView& text, const TextLayoutOptions& layout)
void Font::ProcessText(FontFallbackList* fallbacks, const StringView& text, Array<BlockedTextLineCache>& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout)
{
const Array<Font*>& fallbackFonts = fallbacks->GetFontList(GetSize());
float cursorX = 0;
int32 kerning;
BlockedTextLineCache tmpLine;
FontBlockCache tmpBlock;
FontCharacterEntry entry;
FontCharacterEntry previous;
int32 textLength = text.Length();
float scale = layout.Scale / FontManager::FontScale;
float boundsWidth = layout.Bounds.GetWidth();
float baseLinesDistanceScale = layout.BaseLinesGapScale * scale;
tmpBlock.Location = Float2::Zero;
tmpBlock.Size = Float2::Zero;
tmpBlock.FirstCharIndex = 0;
tmpBlock.LastCharIndex = -1;
tmpLine.Location = Float2::Zero;
tmpLine.Size = Float2::Zero;
tmpLine.Blocks = Array<FontBlockCache>();
if (textLength == 0) {
return;
}
int32 lastWrapCharIndex = INVALID_INDEX;
float lastWrapCharX = 0;
bool lastMoveLine = false;
// The index of the font used by the current block
int32 currentFontIndex = fallbacks->GetCharFallbackIndex(text[0], this);
// The maximum font height of the current line
float maxHeight = 0;
float maxAscender = 0;
float lastCursorX = 0;
auto getFont = [&](int32 index)->Font* {
return index >= 0 ? fallbackFonts[index] : this;
};
// Process each character to split text into single blocks
for (int32 currentIndex = 0; currentIndex < textLength;)
{
bool moveLine = false;
bool moveBlock = false;
float xAdvance = 0;
int32 nextCharIndex = currentIndex + 1;
// Submit line and block if text ends
if (nextCharIndex == textLength) {
moveLine = moveBlock = true;
}
// 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) || (currentChar >= 0x3040 && currentChar <= 0x9FFF);
if (isWrapChar && currentIndex != 0)
{
lastWrapCharIndex = currentIndex;
lastWrapCharX = cursorX;
}
int32 nextFontIndex = currentFontIndex;
// Check if it's a newline character
if (currentChar == '\n')
{
// Break line
moveLine = moveBlock = true;
tmpBlock.LastCharIndex++;
}
else
{
// Get character entry
if (nextCharIndex < textLength) {
nextFontIndex = fallbacks->GetCharFallbackIndex(text[nextCharIndex], this, currentFontIndex);
}
// Get character entry
getFont(currentFontIndex)->GetCharacter(currentChar, entry);
maxHeight = Math::Max(maxHeight,
static_cast<float>(getFont(currentFontIndex)->GetHeight()));
maxAscender = Math::Max(maxAscender,
static_cast<float>(getFont(currentFontIndex)->GetAscender()));
// Move block if the font changes or text ends
if (nextFontIndex != currentFontIndex || nextCharIndex == textLength) {
moveBlock = true;
}
// Get kerning, only when the font hasn't changed
if (!isWhitespace && previous.IsValid && !moveBlock)
{
kerning = getFont(currentFontIndex)->GetKerning(previous.Character, entry.Character);
}
else
{
kerning = 0;
}
previous = entry;
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;
tmpBlock.LastCharIndex++;
}
else if (layout.TextWrapping == TextWrapping::WrapWords)
{
if (lastWrapCharIndex != INVALID_INDEX)
{
// Skip moving twice for the same character
int32 lastLineLastCharIndex = outputLines.HasItems() && outputLines.Last().Blocks.HasItems() ? outputLines.Last().Blocks.Last().LastCharIndex : -10000;
if (lastLineLastCharIndex == lastWrapCharIndex || lastLineLastCharIndex == lastWrapCharIndex - 1 || lastLineLastCharIndex == lastWrapCharIndex - 2)
{
currentIndex = nextCharIndex;
lastMoveLine = moveLine;
continue;
}
// Move line
const Char wrapChar = text[lastWrapCharIndex];
moveLine = true;
moveBlock = tmpBlock.FirstCharIndex < lastWrapCharIndex;
cursorX = lastWrapCharX;
if (StringUtils::IsWhitespace(wrapChar))
{
// Skip whitespaces
tmpBlock.LastCharIndex = lastWrapCharIndex - 1;
nextCharIndex = currentIndex = lastWrapCharIndex + 1;
}
else
{
tmpBlock.LastCharIndex = lastWrapCharIndex - 1;
nextCharIndex = currentIndex = lastWrapCharIndex;
}
}
}
else if (layout.TextWrapping == TextWrapping::WrapChars)
{
// Move line
moveLine = true;
moveBlock = tmpBlock.FirstCharIndex < currentChar;
nextCharIndex = currentIndex;
// Skip moving twice for the same character
if (lastMoveLine)
break;
}
}
if (moveBlock) {
// Add block
tmpBlock.Size.X = lastCursorX - cursorX;
tmpBlock.Size.Y = baseLinesDistanceScale * getFont(currentFontIndex)->GetHeight();
tmpBlock.LastCharIndex = Math::Max(tmpBlock.LastCharIndex, tmpBlock.FirstCharIndex);
tmpBlock.FallbackFontIndex = currentFontIndex;
tmpLine.Blocks.Add(tmpBlock);
// Reset block
tmpBlock.Location.X = cursorX;
tmpBlock.FirstCharIndex = nextCharIndex;
tmpBlock.LastCharIndex = nextCharIndex - 1;
currentFontIndex = nextFontIndex;
lastCursorX = cursorX;
}
// Check if move to another line
if (moveLine)
{
// Add line
tmpLine.Size.X = cursorX;
tmpLine.Size.Y = baseLinesDistanceScale * maxHeight;
tmpLine.MaxAscender = maxAscender;
outputLines.Add(tmpLine);
// Reset line
tmpLine.Blocks.Clear();
tmpLine.Location.Y += baseLinesDistanceScale * maxHeight;
cursorX = 0;
tmpBlock.Location.X = cursorX;
lastWrapCharIndex = INVALID_INDEX;
lastWrapCharX = 0;
previous.IsValid = false;
// Reset max font height
maxHeight = 0;
maxAscender = 0;
lastCursorX = 0;
}
currentIndex = nextCharIndex;
lastMoveLine = moveLine;
}
// Check if an additional line should be created
if (text[textLength - 1] == '\n')
{
// Add line
tmpLine.Size.X = cursorX;
tmpLine.Size.Y = baseLinesDistanceScale * maxHeight;
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++)
{
BlockedTextLineCache& 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;
// Align all blocks to center in case they have different heights
for (int32 j = 0; j < line.Blocks.Count(); j++)
{
FontBlockCache& block = line.Blocks[j];
block.Location.Y += (line.MaxAscender - getFont(block.FallbackFontIndex)->GetAscender()) / 2;
}
}
}
Float2 Font::MeasureTextInternal(const StringView& text, const TextLayoutOptions& layout)
{
// Check if there is no need to do anything
if (text.IsEmpty())
@@ -304,7 +581,28 @@ Float2 Font::MeasureText(const StringView& text, const TextLayoutOptions& layout
return max;
}
int32 Font::HitTestText(const StringView& text, const Float2& location, const TextLayoutOptions& layout)
Float2 Font::MeasureTextInternal(FontFallbackList* fallbacks, const StringView& text, const TextLayoutOptions& layout)
{
// Check if there is no need to do anything
if (text.IsEmpty())
return Float2::Zero;
// Process text
Array<BlockedTextLineCache> lines;
ProcessText(fallbacks, text, lines, layout);
// Calculate bounds
Float2 max = Float2::Zero;
for (int32 i = 0; i < lines.Count(); i++)
{
const BlockedTextLineCache& line = lines[i];
max = Float2::Max(max, line.Location + line.Size);
}
return max;
}
int32 Font::HitTestTextInternal(const StringView& text, const Float2& location, const TextLayoutOptions& layout)
{
// Check if there is no need to do anything
if (text.Length() <= 0)
@@ -378,7 +676,107 @@ int32 Font::HitTestText(const StringView& text, const Float2& location, const Te
return smallestIndex;
}
Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayoutOptions& layout)
int32 Font::HitTestTextInternal(FontFallbackList* fallbacks, const StringView& text, const Float2& location, const TextLayoutOptions& layout)
{
// Check if there is no need to do anything
if (text.Length() <= 0)
return 0;
// Process text
const Array<Font*>& fallbackFonts = fallbacks->GetFontList(GetSize());
Array<BlockedTextLineCache> lines;
ProcessText(fallbacks, text, lines, layout);
ASSERT(lines.HasItems());
float scale = layout.Scale / FontManager::FontScale;
// Offset position to match lines origin space
Float2 rootOffset = layout.Bounds.Location + lines.First().Location;
Float2 testPoint = location - rootOffset;
// Get block which may intersect with the position (it's possible because lines have fixed height)
int32 lineIndex = 0;
while (lineIndex < lines.Count())
{
if (lines[lineIndex].Location.Y + lines[lineIndex].Size.Y >= location.Y) {
break;
}
lineIndex++;
}
lineIndex = Math::Clamp(lineIndex, 0, lines.Count() - 1);
const BlockedTextLineCache& line = lines[lineIndex];
int32 blockIndex = 0;
while (blockIndex < line.Blocks.Count() - 1)
{
if (line.Location.X + line.Blocks[blockIndex + 1].Location.X >= location.X) {
break;
}
blockIndex++;
}
const FontBlockCache& block = line.Blocks[blockIndex];
float x = line.Location.X;
// Check all characters in the line to find hit point
FontCharacterEntry previous;
FontCharacterEntry entry;
int32 smallestIndex = INVALID_INDEX;
float dst, smallestDst = MAX_float;
auto getFont = [&](int32 index)->Font* {
return index >= 0 ? fallbackFonts[index] : this;
};
for (int32 currentIndex = block.FirstCharIndex; currentIndex <= block.LastCharIndex; currentIndex++)
{
// Cache current character
const Char currentChar = text[currentIndex];
getFont(block.FallbackFontIndex)->GetCharacter(currentChar, entry);
const bool isWhitespace = StringUtils::IsWhitespace(currentChar);
// Apply kerning
if (!isWhitespace && previous.IsValid)
{
x += getFont(block.FallbackFontIndex)->GetKerning(previous.Character, entry.Character);
}
previous = entry;
// Test
dst = Math::Abs(testPoint.X - x);
if (dst < smallestDst)
{
// Found closer character
smallestIndex = currentIndex;
smallestDst = dst;
}
else if (dst > smallestDst)
{
// Current char is worse so return the best result
return smallestIndex;
}
// Move
x += entry.AdvanceX * scale;
}
// Test line end edge
dst = Math::Abs(testPoint.X - x);
if (dst < smallestDst)
{
// Pointer is behind the last character in the line
smallestIndex = block.LastCharIndex;
// Fix for last line
if (lineIndex == lines.Count() - 1)
smallestIndex++;
}
return smallestIndex;
}
Float2 Font::GetCharPositionInternal(const StringView& text, int32 index, const TextLayoutOptions& layout)
{
// Check if there is no need to do anything
if (text.IsEmpty())
@@ -390,7 +788,7 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo
ASSERT(lines.HasItems());
float scale = layout.Scale / FontManager::FontScale;
float baseLinesDistance = static_cast<float>(_height) * layout.BaseLinesGapScale * scale;
Float2 rootOffset = layout.Bounds.Location + lines.First().Location;
Float2 rootOffset = layout.Bounds.Location;
// Find line with that position
FontCharacterEntry previous;
@@ -429,7 +827,71 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo
}
// Position after last character in the last line
return rootOffset + Float2(lines.Last().Size.X, static_cast<float>((lines.Count() - 1) * baseLinesDistance));
return rootOffset + Float2(lines.Last().Location.X + lines.Last().Size.X, static_cast<float>((lines.Count() - 1) * baseLinesDistance));
}
Float2 Font::GetCharPositionInternal(FontFallbackList* fallbacks, const StringView& text, int32 index, const TextLayoutOptions& layout)
{
// Check if there is no need to do anything
if (text.IsEmpty())
return layout.Bounds.Location;
// Process text
const Array<Font*>& fallbackFonts = fallbacks->GetFontList(GetSize());
Array<BlockedTextLineCache> lines;
ProcessText(fallbacks, text, lines, layout);
ASSERT(lines.HasItems());
float scale = layout.Scale / FontManager::FontScale;
float baseLinesDistance = layout.BaseLinesGapScale * scale;
Float2 rootOffset = layout.Bounds.Location;
// Find line with that position
FontCharacterEntry previous;
FontCharacterEntry entry;
auto getFont = [&](int32 index)->Font* {
return index >= 0 ? fallbackFonts[index] : this;
};
for (int32 lineIndex = 0; lineIndex < lines.Count(); lineIndex++)
{
const BlockedTextLineCache& line = lines[lineIndex];
for (int32 blockIndex = 0; blockIndex < line.Blocks.Count(); blockIndex++)
{
const FontBlockCache& block = line.Blocks[blockIndex];
// Check if desire position is somewhere inside characters in line range
if (Math::IsInRange(index, block.FirstCharIndex, block.LastCharIndex))
{
float x = line.Location.X + block.Location.X;
float y = line.Location.Y + block.Location.Y;
// Check all characters in the line
for (int32 currentIndex = block.FirstCharIndex; currentIndex < index; currentIndex++)
{
// Cache current character
const Char currentChar = text[currentIndex];
getFont(block.FallbackFontIndex)->GetCharacter(currentChar, entry);
const bool isWhitespace = StringUtils::IsWhitespace(currentChar);
// Apply kerning
if (!isWhitespace && previous.IsValid)
{
x += getFont(block.FallbackFontIndex)->GetKerning(previous.Character, entry.Character);
}
previous = entry;
// Move
x += entry.AdvanceX * scale;
}
// Upper left corner of the character
return rootOffset + Float2(x, y);
}
}
}
// Position after last character in the last line
return rootOffset + Float2(lines.Last().Location.X + lines.Last().Size.X, lines.Last().Location.Y);
}
void Font::FlushFaceSize() const

View File

@@ -8,9 +8,12 @@
#include "Engine/Content/AssetReference.h"
#include "Engine/Scripting/ScriptingObject.h"
#include "TextLayoutOptions.h"
#include "Render2D.h"
class FontAsset;
class FontFallbackList;
struct FontTextureAtlasSlot;
struct BlockedTextLineCache;
// The default DPI that engine is using
#define DefaultDPI 96
@@ -20,7 +23,7 @@ struct FontTextureAtlasSlot;
/// </summary>
API_STRUCT(NoDefault) struct TextRange
{
DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange);
DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange);
/// <summary>
/// The start index (inclusive).
@@ -90,7 +93,7 @@ struct TIsPODType<TextRange>
/// </summary>
API_STRUCT(NoDefault) struct FontLineCache
{
DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache);
DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache);
/// <summary>
/// The root position of the line (upper left corner).
@@ -108,7 +111,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache);
API_FIELD() int32 FirstCharIndex;
/// <summary>
/// The last character index (from the input text).
/// The last character index (from the input text), inclusive.
/// </summary>
API_FIELD() int32 LastCharIndex;
};
@@ -119,6 +122,74 @@ struct TIsPODType<FontLineCache>
enum { Value = true };
};
/// <summary>
/// The font block info generated during text processing.
/// A block means a range of text that belongs to the same line and can be rendered with the same font.
/// </summary>
API_STRUCT(NoDefault) struct FontBlockCache
{
DECLARE_SCRIPTING_TYPE_MINIMAL(FontBlockCache);
/// <summary>
/// The root position of the block (upper left corner), relative to line.
/// </summary>
API_FIELD() Float2 Location;
/// <summary>
/// The size of the current block
/// </summary>
API_FIELD() Float2 Size;
/// <summary>
/// The first character index (from the input text).
/// </summary>
API_FIELD() int32 FirstCharIndex;
/// <summary>
/// The last character index (from the input text), inclusive.
/// </summary>
API_FIELD() int32 LastCharIndex;
/// <summary>
/// Indicates the fallback font to render this block with, -1 if doesn't require fallback.
/// </summary>
API_FIELD() int32 FallbackFontIndex;
};
template<>
struct TIsPODType<FontBlockCache>
{
enum { Value = true };
};
/// <summary>
/// Line of font blocks info generated during text processing.
/// </summary>
API_STRUCT(NoDefault) struct BlockedTextLineCache
{
DECLARE_SCRIPTING_TYPE_MINIMAL(BlockedTextLineCache);
/// <summary>
/// The root position of the line (upper left corner).
/// </summary>
API_FIELD() Float2 Location;
/// <summary>
/// The line bounds (width and height).
/// </summary>
API_FIELD() Float2 Size;
/// <summary>
/// The maximum ascender of the line.
/// </summary>
API_FIELD() float MaxAscender;
/// <summary>
/// The blocks that belongs to this line
/// </summary>
API_FIELD() Array<FontBlockCache> Blocks;
};
// Font glyph metrics:
//
// xmin xmax
@@ -154,7 +225,7 @@ struct TIsPODType<FontLineCache>
/// </summary>
API_STRUCT(NoDefault) struct FontCharacterEntry
{
DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry);
DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry);
/// <summary>
/// The character represented by this entry.
@@ -223,7 +294,7 @@ struct TIsPODType<FontCharacterEntry>
/// </summary>
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:
@@ -332,7 +403,31 @@ public:
public:
/// <summary>
/// Processes text to get cached lines for rendering.
/// Gets the maximum height among the font and the fallback fonts.
/// </summary>
/// <param name="fallbacks">The fallback fonts.</param>
/// <returns>The maximum height.</returns>
API_FUNCTION() float GetMaxHeight(FontFallbackList* fallbacks) const;
/// <summary>
/// Gets the maximum height among the font and the fallback fonts, uses the default font defined in <see cref="Render2D"/>.
/// </summary>
/// <param name="fallbacks">The fallback fonts.</param>
/// <returns>The maximum height.</returns>
API_FUNCTION() FORCE_INLINE float GetMaxHeight() const
{
if (Render2D::EnableFontFallback && Render2D::FallbackFonts)
{
return GetMaxHeight(Render2D::FallbackFonts);
}
else
{
return GetHeight();
}
}
/// <summary>
/// Processes text to get cached lines for rendering, with font fallbacking disabled.
/// </summary>
/// <param name="text">The input text.</param>
/// <param name="layout">The layout properties.</param>
@@ -340,12 +435,12 @@ public:
void ProcessText(const StringView& text, Array<FontLineCache>& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
/// Processes text to get cached lines for rendering.
/// Processes text to get cached lines for rendering, with font fallbacking disabled.
/// </summary>
/// <param name="text">The input text.</param>
/// <param name="layout">The layout properties.</param>
/// <returns>The output lines list.</returns>
API_FUNCTION() Array<FontLineCache> ProcessText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout)
API_FUNCTION() FORCE_INLINE Array<FontLineCache> ProcessText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout)
{
Array<FontLineCache> lines;
ProcessText(text, lines, layout);
@@ -353,13 +448,13 @@ public:
}
/// <summary>
/// Processes text to get cached lines for rendering.
/// Processes text to get cached lines for rendering, with font fallbacking disabled.
/// </summary>
/// <param name="text">The input text.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="layout">The layout properties.</param>
/// <returns>The output lines list.</returns>
API_FUNCTION() Array<FontLineCache> ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout)
API_FUNCTION() FORCE_INLINE Array<FontLineCache> ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout)
{
Array<FontLineCache> lines;
ProcessText(textRange.Substring(text), lines, layout);
@@ -367,7 +462,7 @@ public:
}
/// <summary>
/// Processes text to get cached lines for rendering.
/// Processes text to get cached lines for rendering, with font fallbacking disabled.
/// </summary>
/// <param name="text">The input text.</param>
/// <returns>The output lines list.</returns>
@@ -377,7 +472,7 @@ public:
}
/// <summary>
/// Processes text to get cached lines for rendering.
/// Processes text to get cached lines for rendering, with font fallbacking disabled.
/// </summary>
/// <param name="text">The input text.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
@@ -388,81 +483,349 @@ public:
}
/// <summary>
/// Measures minimum size of the rectangle that will be needed to draw given text.
/// Processes text to get cached lines for rendering, using custom fallback options.
/// </summary>
/// <param name="text">The input text.</param>
/// <param name="layout">The layout properties.</param>
/// <param name="outputLines">The output lines list.</param>
void ProcessText(FontFallbackList* fallbacks, const StringView& text, Array<BlockedTextLineCache>& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
/// Processes text to get cached lines for rendering, using custom fallback options.
/// </summary>
/// <param name="text">The input text.</param>
/// <param name="layout">The layout properties.</param>
/// <returns>The output lines list.</returns>
API_FUNCTION() FORCE_INLINE Array<BlockedTextLineCache> ProcessText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout)
{
Array<BlockedTextLineCache> lines;
ProcessText(fallbacks, text, lines, layout);
return lines;
}
/// <summary>
/// Processes text to get cached lines for rendering, using custom fallback options.
/// </summary>
/// <param name="text">The input text.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="layout">The layout properties.</param>
/// <returns>The output lines list.</returns>
API_FUNCTION() FORCE_INLINE Array<BlockedTextLineCache> ProcessText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout)
{
Array<BlockedTextLineCache> lines;
ProcessText(fallbacks, textRange.Substring(text), lines, layout);
return lines;
}
/// <summary>
/// Processes text to get cached lines for rendering, using custom fallback options.
/// </summary>
/// <param name="text">The input text.</param>
/// <returns>The output lines list.</returns>
API_FUNCTION() FORCE_INLINE Array<BlockedTextLineCache> ProcessText(FontFallbackList* fallbacks, const StringView& text)
{
return ProcessText(fallbacks, text, TextLayoutOptions());
}
/// <summary>
/// Processes text to get cached lines for rendering, using custom fallback options.
/// </summary>
/// <param name="text">The input text.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <returns>The output lines list.</returns>
API_FUNCTION() FORCE_INLINE Array<BlockedTextLineCache> ProcessText(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange)
{
return ProcessText(fallbacks, textRange.Substring(text), TextLayoutOptions());
}
/// <summary>
/// Measures minimum size of the rectangle that will be needed to draw given text, with font fallbacking disabled.
/// </summary>
/// <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() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout);
API_FUNCTION() Float2 MeasureTextInternal(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
/// Measures minimum size of the rectangle that will be needed to draw given text.
/// Measures minimum size of the rectangle that will be needed to draw given text, with font fallbacking disabled.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="layout">The layout properties.</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout)
API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout)
{
return MeasureText(textRange.Substring(text), layout);
return MeasureTextInternal(textRange.Substring(text), layout);
}
/// <summary>
/// Measures minimum size of the rectangle that will be needed to draw given text
/// Measures minimum size of the rectangle that will be needed to draw given text, with font fallbacking disabled.
/// </summary>.
/// <param name="text">The input text to test.</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(const StringView& text)
{
return MeasureTextInternal(text, TextLayoutOptions());
}
/// <summary>
/// Measures minimum size of the rectangle that will be needed to draw given text, with font fallbacking disabled.
/// </summary>.
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange)
{
return MeasureTextInternal(textRange.Substring(text), TextLayoutOptions());
}
/// <summary>
/// Measures minimum size of the rectangle that will be needed to draw given text, using custom fallback options.
/// </summary>
/// <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() Float2 MeasureTextInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
/// Measures minimum size of the rectangle that will be needed to draw given text, using custom fallback options.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="layout">The layout properties.</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout)
{
return MeasureTextInternal(fallbacks, textRange.Substring(text), layout);
}
/// <summary>
/// Measures minimum size of the rectangle that will be needed to draw given text, using custom fallback options.
/// </summary>.
/// <param name="text">The input text to test.</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(FontFallbackList* fallbacks, const StringView& text)
{
return MeasureTextInternal(fallbacks, text, TextLayoutOptions());
}
/// <summary>
/// Measures minimum size of the rectangle that will be needed to draw given text, using custom fallback options.
/// </summary>.
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
API_FUNCTION() FORCE_INLINE Float2 MeasureTextInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange)
{
return MeasureTextInternal(fallbacks, textRange.Substring(text), TextLayoutOptions());
}
/// <summary>
/// Measures minimum size of the rectangle that will be needed to draw given text, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>
/// <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() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) {
if (Render2D::EnableFontFallback && Render2D::FallbackFonts) {
return MeasureTextInternal(Render2D::FallbackFonts, text, layout);
}
else {
return MeasureTextInternal(text, layout);
}
}
/// <summary>
/// Measures minimum size of the rectangle that will be needed to draw given text, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="layout">The layout properties.</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout)
{
if (Render2D::EnableFontFallback && Render2D::FallbackFonts) {
return MeasureTextInternal(Render2D::FallbackFonts, textRange.Substring(text), layout);
}
else {
return MeasureTextInternal(textRange.Substring(text), layout);
}
}
/// <summary>
/// Measures minimum size of the rectangle that will be needed to draw given text, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>.
/// <param name="text">The input text to test.</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text)
{
return MeasureText(text, TextLayoutOptions());
if (Render2D::EnableFontFallback && Render2D::FallbackFonts) {
return MeasureTextInternal(Render2D::FallbackFonts, text, TextLayoutOptions());
}
else {
return MeasureTextInternal(text, TextLayoutOptions());
}
}
/// <summary>
/// Measures minimum size of the rectangle that will be needed to draw given text
/// Measures minimum size of the rectangle that will be needed to draw given text, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>.
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange)
{
return MeasureText(textRange.Substring(text), TextLayoutOptions());
if (Render2D::EnableFontFallback && Render2D::FallbackFonts) {
return MeasureTextInternal(Render2D::FallbackFonts, textRange.Substring(text), TextLayoutOptions());
}
else {
return MeasureTextInternal(textRange.Substring(text), TextLayoutOptions());
}
}
/// <summary>
/// Calculates hit character index at given location.
/// Calculates hit character index at given location, with font fallbacking disabled.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="location">The input location to test.</param>
/// <param name="layout">The text layout properties.</param>
/// <returns>The selected character position index (can be equal to text length if location is outside of the layout rectangle).</returns>
API_FUNCTION() int32 HitTestTextInternal(const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
/// Calculates hit character index at given location, with font fallbacking disabled.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="location">The input location to test.</param>
/// <param name="layout">The text layout properties.</param>
/// <returns>The selected character position index (can be equal to text length if location is outside of the layout rectangle).</returns>
API_FUNCTION() int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout)
API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(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);
return HitTestTextInternal(textRange.Substring(text), location, layout);
}
/// <summary>
/// Calculates hit character index at given location.
/// Calculates hit character index at given location, with font fallbacking disabled.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="location">The input location to test.</param>
/// <returns>The selected character position index (can be equal to text length if location is outside of the layout rectangle).</returns>
API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(const StringView& text, const Float2& location)
{
return HitTestTextInternal(text, location, TextLayoutOptions());
}
/// <summary>
/// Calculates hit character index at given location, with font fallbacking disabled.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="location">The input location to test.</param>
/// <returns>The selected character position index (can be equal to text length if location is outside of the layout rectangle).</returns>
API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location)
{
return HitTestTextInternal(textRange.Substring(text), location, TextLayoutOptions());
}
/// <summary>
/// Calculates hit character index at given location, using custom fallback options.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="location">The input location to test.</param>
/// <param name="layout">The text layout properties.</param>
/// <returns>The selected character position index (can be equal to text length if location is outside of the layout rectangle).</returns>
API_FUNCTION() int32 HitTestText(const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout);
API_FUNCTION() int32 HitTestTextInternal(FontFallbackList* fallbacks, const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
/// Calculates hit character index at given location.
/// Calculates hit character index at given location, using custom fallback options.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="location">The input location to test.</param>
/// <param name="layout">The text layout properties.</param>
/// <returns>The selected character position index (can be equal to text length if location is outside of the layout rectangle).</returns>
API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout)
{
return HitTestTextInternal(fallbacks, textRange.Substring(text), location, layout);
}
/// <summary>
/// Calculates hit character index at given location, using custom fallback options.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="location">The input location to test.</param>
/// <returns>The selected character position index (can be equal to text length if location is outside of the layout rectangle).</returns>
API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(FontFallbackList* fallbacks, const StringView& text, const Float2& location)
{
return HitTestTextInternal(fallbacks, text, location, TextLayoutOptions());
}
/// <summary>
/// Calculates hit character index at given location, using custom fallback options.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="location">The input location to test.</param>
/// <returns>The selected character position index (can be equal to text length if location is outside of the layout rectangle).</returns>
API_FUNCTION() FORCE_INLINE int32 HitTestTextInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location)
{
return HitTestTextInternal(fallbacks, textRange.Substring(text), location, TextLayoutOptions());
}
/// <summary>
/// Calculates hit character index at given location, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="location">The input location to test.</param>
/// <param name="layout">The text layout properties.</param>
/// <returns>The selected character position index (can be equal to text length if location is outside of the layout rectangle).</returns>
API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) {
if (Render2D::EnableFontFallback && Render2D::FallbackFonts) {
return HitTestTextInternal(Render2D::FallbackFonts, text, location, layout);
}
else {
return HitTestTextInternal(text, location, layout);
}
}
/// <summary>
/// Calculates hit character index at given location, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="location">The input location to test.</param>
/// <param name="layout">The text layout properties.</param>
/// <returns>The selected character position index (can be equal to text length if location is outside of the layout rectangle).</returns>
API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout)
{
if (Render2D::EnableFontFallback && Render2D::FallbackFonts) {
return HitTestTextInternal(Render2D::FallbackFonts, textRange.Substring(text), location, layout);
}
else {
return HitTestTextInternal(textRange.Substring(text), location, layout);
}
}
/// <summary>
/// Calculates hit character index at given location, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="location">The input location to test.</param>
/// <returns>The selected character position index (can be equal to text length if location is outside of the layout rectangle).</returns>
API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, const Float2& location)
{
return HitTestText(text, location, TextLayoutOptions());
if (Render2D::EnableFontFallback && Render2D::FallbackFonts) {
return HitTestTextInternal(Render2D::FallbackFonts, text, location, TextLayoutOptions());
}
else {
return HitTestTextInternal(text, location, TextLayoutOptions());
}
}
/// <summary>
/// Calculates hit character index at given location.
/// Calculates hit character index at given location, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
@@ -470,44 +833,156 @@ public:
/// <returns>The selected character position index (can be equal to text length if location is outside of the layout rectangle).</returns>
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());
if (Render2D::EnableFontFallback && Render2D::FallbackFonts) {
return HitTestTextInternal(Render2D::FallbackFonts, textRange.Substring(text), location, TextLayoutOptions());
}
else {
return HitTestTextInternal(textRange.Substring(text), location, TextLayoutOptions());
}
}
/// <summary>
/// Calculates character position for given text and character index.
/// Calculates character position for given text and character index, with font fallbacking disabled.
/// </summary>
/// <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() Float2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout);
API_FUNCTION() Float2 GetCharPositionInternal(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
/// Calculates character position for given text and character index.
/// Calculates character position for given text and character index, with font fallbacking disabled.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</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() Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout)
API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout)
{
return GetCharPosition(textRange.Substring(text), index, layout);
return GetCharPositionInternal(textRange.Substring(text), index, layout);
}
/// <summary>
/// Calculates character position for given text and character index
/// Calculates character position for given text and character index, with font fallbacking disabled.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="index">The text position to get coordinates of.</param>
/// <returns>The character position (upper left corner which can be used for a caret position).</returns>
API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(const StringView& text, int32 index)
{
return GetCharPositionInternal(text, index, TextLayoutOptions());
}
/// <summary>
/// Calculates character position for given text and character index, with font fallbacking disabled.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="index">The text position to get coordinates of.</param>
/// <returns>The character position (upper left corner which can be used for a caret position).</returns>
API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index)
{
return GetCharPositionInternal(textRange.Substring(text), index, TextLayoutOptions());
}
/// <summary>
/// Calculates character position for given text and character index, using custom fallback options.
/// </summary>
/// <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() Float2 GetCharPositionInternal(FontFallbackList* fallbacks, const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
/// Calculates character position for given text and character index, using custom fallback options.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</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() FORCE_INLINE Float2 GetCharPositionInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout)
{
return GetCharPositionInternal(fallbacks, textRange.Substring(text), index, layout);
}
/// <summary>
/// Calculates character position for given text and character index, using custom fallback options.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="index">The text position to get coordinates of.</param>
/// <returns>The character position (upper left corner which can be used for a caret position).</returns>
API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(FontFallbackList* fallbacks, const StringView& text, int32 index)
{
return GetCharPositionInternal(fallbacks, text, index, TextLayoutOptions());
}
/// <summary>
/// Calculates character position for given text and character index, using custom fallback options.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="index">The text position to get coordinates of.</param>
/// <returns>The character position (upper left corner which can be used for a caret position).</returns>
API_FUNCTION() FORCE_INLINE Float2 GetCharPositionInternal(FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index)
{
return GetCharPositionInternal(fallbacks, textRange.Substring(text), index, TextLayoutOptions());
}
/// <summary>
/// Calculates character position for given text and character index, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>
/// <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() FORCE_INLINE Float2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) {
if (Render2D::EnableFontFallback && Render2D::FallbackFonts) {
return GetCharPositionInternal(Render2D::FallbackFonts, text, index, layout);
}
else {
return GetCharPositionInternal(text, index, layout);
}
}
/// <summary>
/// Calculates character position for given text and character index, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</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() FORCE_INLINE Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout)
{
if (Render2D::EnableFontFallback && Render2D::FallbackFonts) {
return GetCharPositionInternal(Render2D::FallbackFonts, textRange.Substring(text), index, layout);
}
else {
return GetCharPositionInternal(textRange.Substring(text), index, layout);
}
}
/// <summary>
/// Calculates character position for given text and character index, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="index">The text position to get coordinates of.</param>
/// <returns>The character position (upper left corner which can be used for a caret position).</returns>
API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, int32 index)
{
return GetCharPosition(text, index, TextLayoutOptions());
if (Render2D::EnableFontFallback && Render2D::FallbackFonts) {
return GetCharPositionInternal(Render2D::FallbackFonts, text, index, TextLayoutOptions());
}
else {
return GetCharPositionInternal(text, index, TextLayoutOptions());
}
}
/// <summary>
/// Calculates character position for given text and character index
/// Calculates character position for given text and character index, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
@@ -515,7 +990,12 @@ public:
/// <returns>The character position (upper left corner which can be used for a caret position).</returns>
API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index)
{
return GetCharPosition(textRange.Substring(text), index, TextLayoutOptions());
if (Render2D::EnableFontFallback && Render2D::FallbackFonts) {
return GetCharPositionInternal(Render2D::FallbackFonts, textRange.Substring(text), index, TextLayoutOptions());
}
else {
return GetCharPositionInternal(textRange.Substring(text), index, TextLayoutOptions());
}
}
/// <summary>

View File

@@ -199,6 +199,17 @@ bool FontAsset::Save(const StringView& path)
#endif
/// <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>
bool FontAsset::ContainsChar(Char c) const {
return FT_Get_Char_Index(GetFTFace(), c) > 0;
}
void FontAsset::Invalidate()
{
ScopeLock lock(Locker);

View File

@@ -174,6 +174,13 @@ public:
API_FUNCTION() bool Save(const StringView& path = StringView::Empty);
#endif
/// <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() bool ContainsChar(Char c) const;
/// <summary>
/// Invalidates all cached dynamic font atlases using this font. Can be used to reload font characters after changing font asset options.
/// </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

@@ -3,6 +3,7 @@
#include "Render2D.h"
#include "Font.h"
#include "FontManager.h"
#include "FallbackFonts.h"
#include "FontTextureAtlas.h"
#include "RotatedRectangle.h"
#include "SpriteAtlas.h"
@@ -27,11 +28,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 +55,7 @@ const bool DownsampleForBlur = false;
PACK_STRUCT(struct Data {
Matrix ViewProjection;
});
});
PACK_STRUCT(struct BlurData {
Float2 InvBufferSize;
@@ -62,7 +63,7 @@ PACK_STRUCT(struct BlurData {
float Dummy0;
Float4 Bounds;
Float4 WeightAndOffsets[RENDER2D_BLUR_MAX_SAMPLES / 2];
});
});
enum class DrawCallType : byte
{
@@ -181,6 +182,8 @@ struct ClipMask
};
Render2D::RenderingFeatures Render2D::Features = RenderingFeatures::VertexSnapping;
bool Render2D::EnableFontFallback = true;
FontFallbackList* Render2D::FallbackFonts = nullptr;
namespace
{
@@ -194,6 +197,7 @@ namespace
// Drawing
Array<Render2DDrawCall> DrawCalls;
Array<FontLineCache> Lines;
Array<BlockedTextLineCache> BlockedTextLines;
Array<Float2> Lines2;
bool IsScissorsRectEmpty;
bool IsScissorsRectEnabled;
@@ -1142,7 +1146,7 @@ void DrawBatch(int32 startIndex, int32 count)
Context->DrawIndexed(countIb, 0, d.StartIB);
}
void Render2D::DrawText(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial)
void Render2D::DrawTextInternal(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial)
{
RENDER2D_CHECK_RENDERING_STATE;
@@ -1174,7 +1178,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];
@@ -1250,12 +1254,12 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
}
}
void Render2D::DrawText(Font* font, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial)
void Render2D::DrawTextInternal(Font* font, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial)
{
DrawText(font, textRange.Substring(text), color, location, customMaterial);
DrawTextInternal(font, textRange.Substring(text), color, location, customMaterial);
}
void Render2D::DrawText(Font* font, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial)
void Render2D::DrawTextInternal(Font* font, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial)
{
RENDER2D_CHECK_RENDERING_STATE;
@@ -1363,9 +1367,317 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
}
}
void Render2D::DrawText(Font* font, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial)
void Render2D::DrawTextInternal(Font* font, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial)
{
DrawText(font, textRange.Substring(text), color, layout, customMaterial);
DrawTextInternal(font, textRange.Substring(text), color, layout, customMaterial);
}
void Render2D::DrawTextInternal(Font* font, FontFallbackList* fallbacks, 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 (font == nullptr || text.Length() < 0)
return;
// Temporary data
const Array<Font*>& fallbackFonts = fallbacks->GetFontList(font->GetSize());
uint32 fontAtlasIndex = 0;
FontTextureAtlas* fontAtlas = nullptr;
Float2 invAtlasSize = Float2::One;
FontCharacterEntry previous;
int32 kerning;
float scale = 1.0f / FontManager::FontScale;
// Process text to get lines
Array<float> maxAscenders;
// 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;
}
int32 lineIndex = 0;
maxAscenders.Add(0);
auto getFont = [&](int32 index)->Font* {
return index >= 0 ? fallbackFonts[index] : font;
};
// Preprocess the text to determine vertical offset of blocks
for (int32 currentIndex = 0; currentIndex < text.Length(); currentIndex++)
{
const Char c = text[currentIndex];
if (c != '\n') {
int32 fontIndex = fallbacks->GetCharFallbackIndex(c, font);
maxAscenders[lineIndex] = Math::Max(maxAscenders[lineIndex],
static_cast<float>(getFont(fontIndex)->GetAscender()));
}
else {
lineIndex++;
maxAscenders.Add(0);
}
}
lineIndex = 0;
// The following code cut the text into blocks, according to the font used to render
Float2 pointer = location;
// The starting index of the current block
int32 startIndex = 0;
// The index of the font used by the current block
int32 currentFontIndex = fallbacks->GetCharFallbackIndex(text[0], font);
// 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 = text[currentIndex];
int32 nextCharIndex = currentIndex + 1;
bool moveBlock = false;
bool moveLine = false;
int32 nextFontIndex = currentFontIndex;
// Submit block if text ends
if (nextCharIndex == text.Length()) {
moveBlock = true;
}
// Check if it isn't a newline character
if (currentChar != '\n')
{
// Get character entry
if (nextCharIndex < text.Length()) {
nextFontIndex = fallbacks->GetCharFallbackIndex(text[nextCharIndex], font);
}
if (nextFontIndex != currentFontIndex) {
moveBlock = true;
}
}
else
{
// Move
moveLine = moveBlock = true;
}
if (moveBlock) {
// Render the pending block before beginning the new block
auto fontHeight = getFont(currentFontIndex)->GetHeight();
maxHeight = Math::Max(maxHeight, static_cast<float>(fontHeight));
auto fontDescender = getFont(currentFontIndex)->GetDescender();
for (int32 renderIndex = startIndex; renderIndex <= currentIndex; renderIndex++)
{
// Get character entry
getFont(currentFontIndex)->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 = getFont(currentFontIndex)->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 + (maxAscenders[lineIndex] - getFont(currentFontIndex)->GetAscender()) / 2, 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;
}
if (moveLine) {
pointer.X = location.X;
pointer.Y += maxHeight * scale;
// Clear max height
maxHeight = 0;
lineIndex++;
}
// Start new block
startIndex = nextCharIndex;
currentFontIndex = nextFontIndex;
}
}
}
void Render2D::DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial)
{
DrawTextInternal(font, fallbacks, textRange.Substring(text), color, location, customMaterial);
}
void Render2D::DrawTextInternal(Font* font, FontFallbackList* fallbacks, 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 (font == nullptr || text.IsEmpty() || layout.Scale <= ZeroTolerance)
return;
// Temporary data
const Array<Font*>& fallbackFonts = fallbacks->GetFontList(font->GetSize());
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
BlockedTextLines.Clear();
font->ProcessText(fallbacks, text, BlockedTextLines, 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;
}
auto getFont = [&](int32 index)->Font* {
return index >= 0 ? fallbackFonts[index] : font;
};
for (int32 lineIndex = 0; lineIndex < BlockedTextLines.Count(); lineIndex++)
{
const BlockedTextLineCache& line = BlockedTextLines[lineIndex];
for (int32 blockIndex = 0; blockIndex < line.Blocks.Count(); blockIndex++)
{
const FontBlockCache& block = BlockedTextLines[lineIndex].Blocks[blockIndex];
auto fontHeight = getFont(block.FallbackFontIndex)->GetHeight();
auto fontDescender = getFont(block.FallbackFontIndex)->GetDescender();
Float2 pointer = line.Location + block.Location;
for (int32 charIndex = block.FirstCharIndex; charIndex <= block.LastCharIndex; charIndex++)
{
Char c = text[charIndex];
if (c == '\n')
{
continue;
}
// Get character entry
getFont(block.FallbackFontIndex)->GetCharacter(c, 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(c);
if (!isWhitespace && previous.IsValid)
{
kerning = getFont(block.FallbackFontIndex)->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;
}
}
}
}
void Render2D::DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial)
{
DrawTextInternal(font, fallbacks, textRange.Substring(text), color, layout, customMaterial);
}
FORCE_INLINE bool NeedAlphaWithTint(const Color& color)
@@ -1869,22 +2181,22 @@ void Render2D::DrawBezier(const Float2& p1, const Float2& p2, const Float2& p3,
{
RENDER2D_CHECK_RENDERING_STATE;
// Find amount of segments to use
// Find amount of blocks to use
const Float2 d1 = p2 - p1;
const Float2 d2 = p3 - p2;
const Float2 d3 = p4 - p3;
const float len = d1.Length() + d2.Length() + d3.Length();
const int32 segmentCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100);
const float segmentCountInv = 1.0f / segmentCount;
const int32 blockCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100);
const float blockCountInv = 1.0f / blockCount;
// Draw segmented curve
// Draw blocked curve
Float2 p;
AnimationUtils::Bezier(p1, p2, p3, p4, 0, p);
Lines2.Clear();
Lines2.Add(p);
for (int32 i = 1; i <= segmentCount; i++)
for (int32 i = 1; i <= blockCount; i++)
{
const float t = i * segmentCountInv;
const float t = i * blockCountInv;
AnimationUtils::Bezier(p1, p2, p3, p4, t, p);
Lines2.Add(p);
}
@@ -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

@@ -1,11 +1,13 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEngine.GUI;
using System;
namespace FlaxEngine
{
partial class Render2D
{
/// <summary>
/// Pushes transformation layer.
/// </summary>
@@ -100,7 +102,7 @@ namespace FlaxEngine
}
/// <summary>
/// Draws a text.
/// Draws a text, follows the font fallback settings defined in <see cref="Render2D"/>.
/// </summary>
/// <param name="font">The font to use.</param>
/// <param name="text">The text to render.</param>
@@ -126,7 +128,7 @@ namespace FlaxEngine
}
/// <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).
/// 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). Follows the font fallback settings defined in <see cref="Render2D"/>.
/// </summary>
/// <param name="font">The font to use.</param>
/// <param name="customMaterial">Custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture.</param>

View File

@@ -15,6 +15,7 @@ struct Matrix3x3;
struct Viewport;
struct TextRange;
class Font;
class FontFallbackList;
class GPUPipelineState;
class GPUTexture;
class GPUTextureView;
@@ -33,7 +34,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.
@@ -53,6 +54,7 @@ API_CLASS(Static) class FLAXENGINE_API Render2D
};
public:
/// <summary>
/// Checks if interface is during rendering phrase (Draw calls may be performed without failing).
/// </summary>
@@ -68,6 +70,10 @@ public:
/// </summary>
API_FIELD() static RenderingFeatures Features;
API_FIELD() static bool EnableFontFallback;
API_FIELD() static FontFallbackList* FallbackFonts;
/// <summary>
/// Called when frame rendering begins by the graphics device.
/// </summary>
@@ -174,17 +180,17 @@ public:
public:
/// <summary>
/// Draws a text.
/// Draws a text, with font fallbacking disabled.
/// </summary>
/// <param name="font">The font to use.</param>
/// <param name="text">The text to render.</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(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr);
API_FUNCTION() static void DrawTextInternal(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr);
/// <summary>
/// Draws a text.
/// Draws a text, with font fallbacking disabled.
/// </summary>
/// <param name="font">The font to use.</param>
/// <param name="text">The text to render.</param>
@@ -192,20 +198,20 @@ public:
/// <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(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr);
API_FUNCTION() static void DrawTextInternal(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr);
/// <summary>
/// Draws a text with formatting.
/// Draws a text with formatting, with font fallbacking disabled.
/// </summary>
/// <param name="font">The font to use.</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(Font* font, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr);
API_FUNCTION() static void DrawTextInternal(Font* font, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr);
/// <summary>
/// Draws a text with formatting.
/// Draws a text with formatting, with font fallbacking disabled.
/// </summary>
/// <param name="font">The font to use.</param>
/// <param name="text">The text to render.</param>
@@ -213,7 +219,120 @@ public:
/// <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(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr);
API_FUNCTION() static void DrawTextInternal(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, using custom fallback options.
/// </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 DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr);
/// <summary>
/// Draws a text with formatting, using custom fallback options.
/// </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 DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr);
/// <summary>
/// Draws a text with formatting, using custom fallback options.
/// </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 DrawTextInternal(Font* font, FontFallbackList* fallbacks, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr);
/// <summary>
/// Draws a text with formatting, using custom fallback options.
/// </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 DrawTextInternal(Font* font, FontFallbackList* fallbacks, 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, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>
/// <param name="font">The font to use.</param>
/// <param name="text">The text to render.</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() FORCE_INLINE static void DrawText(Font* font, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr) {
if (EnableFontFallback && FallbackFonts) {
DrawTextInternal(font, FallbackFonts, text, color, location, customMaterial);
}
else {
DrawTextInternal(font, text, color, location, customMaterial);
}
}
/// <summary>
/// Draws a text, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>
/// <param name="font">The font to use.</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() FORCE_INLINE static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr) {
if (EnableFontFallback && FallbackFonts) {
DrawTextInternal(font, FallbackFonts, text, textRange, color, location, customMaterial);
}
else {
DrawTextInternal(font, text, textRange, color, location, customMaterial);
}
}
/// <summary>
/// Draws a text with formatting, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>
/// <param name="font">The font to use.</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() FORCE_INLINE static void DrawText(Font* font, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr) {
if (EnableFontFallback && FallbackFonts) {
DrawTextInternal(font, FallbackFonts, text, color, layout, customMaterial);
}
else {
DrawTextInternal(font, text, color, layout, customMaterial);
}
}
/// <summary>
/// Draws a text with formatting, follows the fallback settings defined in <see cref="Render2D" />.
/// </summary>
/// <param name="font">The font to use.</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() FORCE_INLINE 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) {
if (EnableFontFallback && FallbackFonts) {
DrawTextInternal(font, FallbackFonts, text, textRange, color, layout, customMaterial);
}
else {
DrawTextInternal(font, text, textRange, color, layout, customMaterial);
}
}
/// <summary>
/// Fills a rectangle area.

View File

@@ -294,14 +294,12 @@ namespace FlaxEngine
style.DragWindow = style.BackgroundSelected * 0.7f;
// Use optionally bundled default font (matches Editor)
var defaultFont = Content.LoadAsyncInternal<FontAsset>("Editor/Fonts/Roboto-Regular");
if (defaultFont)
{
style.FontTitle = defaultFont.CreateFont(18);
style.FontLarge = defaultFont.CreateFont(14);
style.FontMedium = defaultFont.CreateFont(9);
style.FontSmall = defaultFont.CreateFont(9);
}
FontAsset defaultFont = Content.LoadAsyncInternal<FontAsset>("Editor/Fonts/Roboto-Regular");
style.FontTitle = new FontReference(defaultFont, 18).GetFont();
style.FontLarge = new FontReference(defaultFont, 14).GetFont();
style.FontMedium = new FontReference(defaultFont, 9).GetFont();
style.FontSmall = new FontReference(defaultFont, 9).GetFont();
Style.Current = style;
}

View File

@@ -182,6 +182,12 @@ namespace FlaxEngine.GUI
set => _autoFitTextRange = value;
}
/// <summary>
/// Gets or sets whether to fallback when the primary font cannot render a char.
/// </summary>
[EditorOrder(120), DefaultValue(true), Tooltip("Whether to fallback when the font cannot render a char.")]
public bool EnableFontFallback { get; set; } = true;
/// <summary>
/// Initializes a new instance of the <see cref="Label"/> class.
/// </summary>
@@ -233,7 +239,23 @@ namespace FlaxEngine.GUI
}
}
Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale);
if (EnableFontFallback)
{
Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale);
}
else
{
var layout = new TextLayoutOptions
{
Bounds = rect,
HorizontalAlignment = hAlignment,
VerticalAlignment = wAlignment,
TextWrapping = Wrapping,
Scale = scale,
BaseLinesGapScale = BaseLinesGapScale,
};
Render2D.DrawTextInternal(_font.GetFont(), _text, color, ref layout, Material);
}
if (ClipText)
Render2D.PopClip();
@@ -254,7 +276,8 @@ namespace FlaxEngine.GUI
layout.Bounds.Size.X = Width - Margin.Width;
else if (_autoWidth && !_autoHeight)
layout.Bounds.Size.Y = Height - Margin.Height;
_textSize = font.MeasureText(_text, ref layout);
_textSize = EnableFontFallback ?
font.MeasureText(_text, ref layout) : font.MeasureTextInternal(_text, ref layout);
_textSize.Y *= BaseLinesGapScale;
// Check if size is controlled via text

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using FlaxEngine.Utilities;

View File

@@ -213,18 +213,18 @@ namespace FlaxEngine.GUI
style.Alignment &= ~TextBlockStyle.Alignments.VerticalMask;
switch (valign)
{
case "top":
style.Alignment = TextBlockStyle.Alignments.Top;
break;
case "bottom":
style.Alignment = TextBlockStyle.Alignments.Bottom;
break;
case "middle":
style.Alignment = TextBlockStyle.Alignments.Middle;
break;
case "baseline":
style.Alignment = TextBlockStyle.Alignments.Baseline;
break;
case "top":
style.Alignment = TextBlockStyle.Alignments.Top;
break;
case "bottom":
style.Alignment = TextBlockStyle.Alignments.Bottom;
break;
case "middle":
style.Alignment = TextBlockStyle.Alignments.Middle;
break;
case "baseline":
style.Alignment = TextBlockStyle.Alignments.Baseline;
break;
}
}
context.StyleStack.Push(style);
@@ -245,15 +245,15 @@ namespace FlaxEngine.GUI
style.Alignment &= ~TextBlockStyle.Alignments.VerticalMask;
switch (valign)
{
case "left":
style.Alignment = TextBlockStyle.Alignments.Left;
break;
case "right":
style.Alignment = TextBlockStyle.Alignments.Right;
break;
case "center":
style.Alignment = TextBlockStyle.Alignments.Center;
break;
case "left":
style.Alignment = TextBlockStyle.Alignments.Left;
break;
case "right":
style.Alignment = TextBlockStyle.Alignments.Right;
break;
case "center":
style.Alignment = TextBlockStyle.Alignments.Center;
break;
}
}
context.StyleStack.Push(style);
@@ -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);

View File

@@ -316,6 +316,7 @@ namespace FlaxEngine.GUI
color = textBlock.Style.ShadowColor;
if (!enabled)
color *= 0.6f;
// We don't need font fallbacks for rich text since the font is user-selected
Render2D.DrawText(font, _text, ref textBlock.Range, color, textBlock.Bounds.Location + textBlock.Style.ShadowOffset, textBlock.Style.CustomMaterial);
}

View File

@@ -1,5 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.ComponentModel;
namespace FlaxEngine.GUI
{
/// <summary>
@@ -64,6 +67,12 @@ namespace FlaxEngine.GUI
[EditorDisplay("Text Style"), EditorOrder(2022), Tooltip("The color of the selection (Transparent if not used).")]
public Color SelectionColor { get; set; }
/// <summary>
/// Gets or sets whether to fallback when the primary font cannot render a char.
/// </summary>
[EditorOrder(120), DefaultValue(true), Tooltip("Whether to fallback when the font cannot render a char.")]
public bool EnableFontFallback { get; set; } = true;
/// <summary>
/// Initializes a new instance of the <see cref="TextBox"/> class.
/// </summary>
@@ -103,7 +112,8 @@ namespace FlaxEngine.GUI
return Float2.Zero;
}
return font.MeasureText(_text, ref _layout);
return EnableFontFallback ? font.MeasureText(_text, ref _layout) :
font.MeasureTextInternal(_text, ref _layout);
}
/// <inheritdoc />
@@ -116,8 +126,9 @@ namespace FlaxEngine.GUI
return Float2.Zero;
}
height = font.Height / DpiScale;
return font.GetCharPosition(_text, index, ref _layout);
height = (EnableFontFallback ? font.GetMaxHeight() : font.Height) / DpiScale;
return EnableFontFallback ? font.GetCharPosition(_text, index, ref _layout) :
font.GetCharPositionInternal(_text, index, ref _layout);
}
/// <inheritdoc />
@@ -129,7 +140,8 @@ namespace FlaxEngine.GUI
return 0;
}
return font.HitTestText(_text, location, ref _layout);
return EnableFontFallback ? font.HitTestText(_text, location, ref _layout) :
font.HitTestTextInternal(_text, location, ref _layout);
}
/// <inheritdoc />
@@ -168,9 +180,13 @@ namespace FlaxEngine.GUI
// Check if sth is selected to draw selection
if (HasSelection)
{
var leftEdge = font.GetCharPosition(_text, SelectionLeft, ref _layout);
var rightEdge = font.GetCharPosition(_text, SelectionRight, ref _layout);
float fontHeight = font.Height / DpiScale;
var leftEdge = EnableFontFallback ?
font.GetCharPosition(_text, SelectionLeft, ref _layout) :
font.GetCharPositionInternal(_text, SelectionLeft, ref _layout);
var rightEdge = EnableFontFallback ?
font.GetCharPosition(_text, SelectionRight, ref _layout) :
font.GetCharPositionInternal(_text, SelectionRight, ref _layout);
float fontHeight = font.GetMaxHeight() / DpiScale;
// Draw selection background
float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f);
@@ -210,11 +226,19 @@ namespace FlaxEngine.GUI
var color = TextColor;
if (!enabled)
color *= 0.6f;
Render2D.DrawText(font, _text, color, ref _layout, TextMaterial);
if (EnableFontFallback)
Render2D.DrawText(font, _text, color, ref _layout, TextMaterial);
else
// Draw without fallback
Render2D.DrawTextInternal(font, _text, color, ref _layout, TextMaterial);
}
else if (!string.IsNullOrEmpty(_watermarkText) && !IsFocused)
{
Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial);
if (EnableFontFallback)
Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial);
else
// Draw without fallback
Render2D.DrawTextInternal(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial);
}
// Caret