You're breathtaking!
This commit is contained in:
442
Source/Engine/Render2D/Font.cpp
Normal file
442
Source/Engine/Render2D/Font.cpp
Normal file
@@ -0,0 +1,442 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "Font.h"
|
||||
#include "FontAsset.h"
|
||||
#include "FontManager.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "IncludeFreeType.h"
|
||||
|
||||
Font::Font(FontAsset* parentAsset, int32 size)
|
||||
: ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer))
|
||||
, _asset(parentAsset)
|
||||
, _size(size)
|
||||
, _characters(512)
|
||||
{
|
||||
_asset->_fonts.Add(this);
|
||||
|
||||
// Cache data
|
||||
FlushFaceSize();
|
||||
const FT_Face face = parentAsset->GetFTFace();
|
||||
ASSERT(face != nullptr);
|
||||
_height = Convert26Dot6ToRoundedPixel<int32>(FT_MulFix(face->height, face->size->metrics.y_scale));
|
||||
_hasKerning = FT_HAS_KERNING(face) != 0;
|
||||
_ascender = Convert26Dot6ToRoundedPixel<int16>(face->size->metrics.ascender);
|
||||
_descender = Convert26Dot6ToRoundedPixel<int16>(face->size->metrics.descender);
|
||||
_lineGap = _height - _ascender + _descender;
|
||||
}
|
||||
|
||||
Font::~Font()
|
||||
{
|
||||
if (_asset)
|
||||
_asset->_fonts.Remove(this);
|
||||
}
|
||||
|
||||
void Font::GetCharacter(Char c, FontCharacterEntry& result)
|
||||
{
|
||||
// Try get character or cache it if cannot find
|
||||
if (!_characters.TryGet(c, result))
|
||||
{
|
||||
// This thread race condition may happen in editor but in game we usually do all stuff with fonts on main thread (chars caching)
|
||||
ScopeLock lock(_asset->Locker);
|
||||
|
||||
// Handle situation when more than one thread wants to get the same character
|
||||
if (_characters.TryGet(c, result))
|
||||
return;
|
||||
|
||||
// Create character cache
|
||||
FontManager::AddNewEntry(this, c, result);
|
||||
|
||||
// Add to the dictionary
|
||||
_characters.Add(c, result);
|
||||
}
|
||||
}
|
||||
|
||||
int32 Font::GetKerning(Char first, Char second) const
|
||||
{
|
||||
int32 kerning = 0;
|
||||
const uint32 key = (uint32)first << 16 | second;
|
||||
if (_hasKerning && !_kerningTable.TryGet(key, kerning))
|
||||
{
|
||||
// This thread race condition may happen in editor but in game we usually do all stuff with fonts on main thread (chars caching)
|
||||
ScopeLock lock(_asset->Locker);
|
||||
|
||||
// Handle situation when more than one thread wants to get the same character
|
||||
if (!_kerningTable.TryGet(key, kerning))
|
||||
{
|
||||
const FT_Face face = _asset->GetFTFace();
|
||||
ASSERT(face);
|
||||
|
||||
FlushFaceSize();
|
||||
|
||||
FT_Vector vec;
|
||||
const FT_UInt firstIndex = FT_Get_Char_Index(face, first);
|
||||
const FT_UInt secondIndex = FT_Get_Char_Index(face, second);
|
||||
FT_Get_Kerning(face, firstIndex, secondIndex, FT_KERNING_DEFAULT, &vec);
|
||||
kerning = vec.x >> 6;
|
||||
|
||||
_kerningTable.Add(key, kerning);
|
||||
}
|
||||
}
|
||||
|
||||
return kerning;
|
||||
}
|
||||
|
||||
void Font::CacheText(const StringView& text)
|
||||
{
|
||||
FontCharacterEntry entry;
|
||||
for (int32 i = 0; i < text.Length(); i++)
|
||||
{
|
||||
GetCharacter(text[i], entry);
|
||||
}
|
||||
}
|
||||
|
||||
void Font::Invalidate()
|
||||
{
|
||||
ScopeLock lock(_asset->Locker);
|
||||
|
||||
for (auto i = _characters.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
FontManager::Invalidate(i->Value);
|
||||
}
|
||||
_characters.Clear();
|
||||
}
|
||||
|
||||
void Font::ProcessText(const StringView& text, Array<FontLineCache>& outputLines, const TextLayoutOptions& layout)
|
||||
{
|
||||
float cursorX = 0;
|
||||
int32 kerning;
|
||||
FontLineCache tmpLine;
|
||||
FontCharacterEntry entry;
|
||||
FontCharacterEntry previous;
|
||||
int32 textLength = text.Length();
|
||||
float scale = layout.Scale / FontManager::FontScale;
|
||||
float boundsWidth = layout.Bounds.GetWidth();
|
||||
float baseLinesDistance = static_cast<float>(_height) * layout.BaseLinesGapScale * scale;
|
||||
tmpLine.Location = Vector2::Zero;
|
||||
tmpLine.Size = Vector2::Zero;
|
||||
tmpLine.FirstCharIndex = 0;
|
||||
tmpLine.LastCharIndex = -1;
|
||||
|
||||
int32 lastWhitespaceIndex = INVALID_INDEX;
|
||||
float lastWhitespaceX = 0;
|
||||
bool lastMoveLine = false;
|
||||
|
||||
// Process each character to split text into single lines
|
||||
for (int32 currentIndex = 0; currentIndex < textLength;)
|
||||
{
|
||||
bool moveLine = false;
|
||||
float xAdvance = 0;
|
||||
int32 nextCharIndex = currentIndex + 1;
|
||||
|
||||
// Cache current character
|
||||
const Char currentChar = text[currentIndex];
|
||||
|
||||
// Check if character is a whitespace
|
||||
const bool isWhitespace = StringUtils::IsWhitespace(currentChar);
|
||||
if (isWhitespace)
|
||||
{
|
||||
// Cache line break point
|
||||
lastWhitespaceIndex = currentIndex;
|
||||
lastWhitespaceX = cursorX;
|
||||
}
|
||||
|
||||
// Check if it's a newline character
|
||||
if (currentChar == '\n')
|
||||
{
|
||||
// Break line
|
||||
moveLine = true;
|
||||
currentIndex++;
|
||||
tmpLine.LastCharIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get character entry
|
||||
GetCharacter(currentChar, entry);
|
||||
|
||||
// Get kerning
|
||||
if (!isWhitespace && previous.IsValid)
|
||||
{
|
||||
kerning = 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;
|
||||
tmpLine.LastCharIndex++;
|
||||
}
|
||||
else if (layout.TextWrapping == TextWrapping::WrapWords)
|
||||
{
|
||||
// Move line but back to the last after-whitespace character
|
||||
moveLine = true;
|
||||
if (lastWhitespaceIndex != INVALID_INDEX)
|
||||
{
|
||||
// Back
|
||||
cursorX = lastWhitespaceX;
|
||||
tmpLine.LastCharIndex = lastWhitespaceIndex - 1;
|
||||
currentIndex = lastWhitespaceIndex + 1;
|
||||
nextCharIndex = currentIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextCharIndex = currentIndex;
|
||||
|
||||
// Skip moving twice for the same character
|
||||
if (lastMoveLine)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (layout.TextWrapping == TextWrapping::WrapChars)
|
||||
{
|
||||
// Move line
|
||||
moveLine = true;
|
||||
nextCharIndex = currentIndex;
|
||||
|
||||
// Skip moving twice for the same character
|
||||
if (lastMoveLine)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if move to another line
|
||||
if (moveLine)
|
||||
{
|
||||
// Add line
|
||||
tmpLine.Size.X = cursorX;
|
||||
tmpLine.Size.Y = baseLinesDistance;
|
||||
tmpLine.LastCharIndex = Math::Max(tmpLine.LastCharIndex, tmpLine.FirstCharIndex);
|
||||
outputLines.Add(tmpLine);
|
||||
|
||||
// Reset line
|
||||
tmpLine.Location.Y += baseLinesDistance;
|
||||
tmpLine.FirstCharIndex = currentIndex;
|
||||
tmpLine.LastCharIndex = currentIndex - 1;
|
||||
cursorX = 0;
|
||||
|
||||
lastWhitespaceIndex = INVALID_INDEX;
|
||||
lastWhitespaceX = 0;
|
||||
|
||||
previous.IsValid = false;
|
||||
}
|
||||
|
||||
currentIndex = nextCharIndex;
|
||||
lastMoveLine = moveLine;
|
||||
}
|
||||
|
||||
if (tmpLine.LastCharIndex >= tmpLine.FirstCharIndex || text[textLength - 1] == '\n')
|
||||
{
|
||||
// Add line
|
||||
tmpLine.Size.X = cursorX;
|
||||
tmpLine.Size.Y = baseLinesDistance;
|
||||
tmpLine.LastCharIndex = textLength - 1;
|
||||
outputLines.Add(tmpLine);
|
||||
|
||||
tmpLine.Location.Y += baseLinesDistance;
|
||||
}
|
||||
|
||||
// Check amount of lines
|
||||
if (outputLines.IsEmpty())
|
||||
return;
|
||||
|
||||
float totalHeight = tmpLine.Location.Y;
|
||||
|
||||
Vector2 offset = Vector2::Zero;
|
||||
if (layout.VerticalAlignment == TextAlignment::Center)
|
||||
{
|
||||
offset.Y += (layout.Bounds.GetHeight() - totalHeight) * 0.5f;
|
||||
}
|
||||
else if (layout.VerticalAlignment == TextAlignment::Far)
|
||||
{
|
||||
offset.Y += layout.Bounds.GetHeight() - totalHeight;
|
||||
}
|
||||
for (int32 i = 0; i < outputLines.Count(); i++)
|
||||
{
|
||||
FontLineCache& line = outputLines[i];
|
||||
Vector2 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;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 Font::MeasureText(const StringView& text, const TextLayoutOptions& layout)
|
||||
{
|
||||
// Check if there is no need to do anything
|
||||
if (text.IsEmpty())
|
||||
return Vector2::Zero;
|
||||
|
||||
// Process text
|
||||
Array<FontLineCache> lines;
|
||||
ProcessText(text, lines, layout);
|
||||
|
||||
// Calculate bounds
|
||||
Vector2 max = Vector2::Zero;
|
||||
for (int32 i = 0; i < lines.Count(); i++)
|
||||
{
|
||||
const FontLineCache& line = lines[i];
|
||||
max = Vector2::Max(max, line.Location + line.Size);
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
int32 Font::HitTestText(const StringView& text, const Vector2& location, const TextLayoutOptions& layout)
|
||||
{
|
||||
// Check if there is no need to do anything
|
||||
if (text.Length() <= 0)
|
||||
return 0;
|
||||
|
||||
// Process text
|
||||
Array<FontLineCache> lines;
|
||||
ProcessText(text, lines, layout);
|
||||
ASSERT(lines.HasItems());
|
||||
float scale = layout.Scale / FontManager::FontScale;
|
||||
|
||||
// Offset position to match lines origin space
|
||||
Vector2 rootOffset = layout.Bounds.Location + lines.First().Location;
|
||||
Vector2 testPoint = location - rootOffset;
|
||||
|
||||
// Get line which may intersect with the position (it's possible because lines have fixed height)
|
||||
int32 lineIndex = Math::Clamp(Math::FloorToInt(testPoint.Y / GetHeight()), 0, lines.Count() - 1);
|
||||
const FontLineCache& line = lines[lineIndex];
|
||||
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;
|
||||
for (int32 currentIndex = line.FirstCharIndex; currentIndex <= line.LastCharIndex; currentIndex++)
|
||||
{
|
||||
// Cache current character
|
||||
const Char currentChar = text[currentIndex];
|
||||
GetCharacter(currentChar, entry);
|
||||
const bool isWhitespace = StringUtils::IsWhitespace(currentChar);
|
||||
|
||||
// Apply kerning
|
||||
if (!isWhitespace && previous.IsValid)
|
||||
{
|
||||
x += 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 = line.LastCharIndex;
|
||||
|
||||
// Fix for last line
|
||||
if (lineIndex == lines.Count() - 1)
|
||||
smallestIndex++;
|
||||
}
|
||||
|
||||
return smallestIndex;
|
||||
}
|
||||
|
||||
Vector2 Font::GetCharPosition(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
|
||||
Array<FontLineCache> lines;
|
||||
ProcessText(text, lines, layout);
|
||||
ASSERT(lines.HasItems());
|
||||
float scale = layout.Scale / FontManager::FontScale;
|
||||
Vector2 rootOffset = layout.Bounds.Location + lines.First().Location;
|
||||
|
||||
// Find line with that position
|
||||
FontCharacterEntry previous;
|
||||
FontCharacterEntry entry;
|
||||
for (int32 lineIndex = 0; lineIndex < lines.Count(); lineIndex++)
|
||||
{
|
||||
const FontLineCache& line = lines[lineIndex];
|
||||
|
||||
// Check if desire position is somewhere inside characters in line range
|
||||
if (Math::IsInRange(index, line.FirstCharIndex, line.LastCharIndex))
|
||||
{
|
||||
float x = line.Location.X;
|
||||
|
||||
// Check all characters in the line
|
||||
for (int32 currentIndex = line.FirstCharIndex; currentIndex < index; currentIndex++)
|
||||
{
|
||||
// Cache current character
|
||||
const Char currentChar = text[currentIndex];
|
||||
GetCharacter(currentChar, entry);
|
||||
const bool isWhitespace = StringUtils::IsWhitespace(currentChar);
|
||||
|
||||
// Apply kerning
|
||||
if (!isWhitespace && previous.IsValid)
|
||||
{
|
||||
x += GetKerning(previous.Character, entry.Character);
|
||||
}
|
||||
previous = entry;
|
||||
|
||||
// Move
|
||||
x += entry.AdvanceX * scale;
|
||||
}
|
||||
|
||||
// Upper left corner of the character
|
||||
return rootOffset + Vector2(x, static_cast<float>(lineIndex * GetHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
// Position after last character in the last line
|
||||
return rootOffset + Vector2(lines.Last().Size.X, static_cast<float>((lines.Count() - 1) * GetHeight()));
|
||||
}
|
||||
|
||||
void Font::FlushFaceSize() const
|
||||
{
|
||||
// Set the character size
|
||||
const FT_Face face = _asset->GetFTFace();
|
||||
const FT_Error error = FT_Set_Char_Size(face, 0, ConvertPixelTo26Dot6<FT_F26Dot6>((float)_size * FontManager::FontScale), DefaultDPI, DefaultDPI);
|
||||
if (error)
|
||||
{
|
||||
LOG_FT_ERROR(error);
|
||||
}
|
||||
|
||||
// Clear transform
|
||||
FT_Set_Transform(face, nullptr, nullptr);
|
||||
}
|
||||
|
||||
String Font::ToString() const
|
||||
{
|
||||
return String::Format(TEXT("Font {0} {1}"), _asset->GetFamilyName(), _size);
|
||||
}
|
||||
466
Source/Engine/Render2D/Font.h
Normal file
466
Source/Engine/Render2D/Font.h
Normal file
@@ -0,0 +1,466 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
#include "TextLayoutOptions.h"
|
||||
|
||||
class FontAsset;
|
||||
|
||||
// The default DPI that engine is using
|
||||
#define DefaultDPI 96
|
||||
|
||||
/// <summary>
|
||||
/// The text range.
|
||||
/// </summary>
|
||||
API_STRUCT() struct TextRange
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange);
|
||||
|
||||
/// <summary>
|
||||
/// The start index.
|
||||
/// </summary>
|
||||
API_FIELD() int32 StartIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The end index.
|
||||
/// </summary>
|
||||
API_FIELD() int32 EndIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the range length.
|
||||
/// </summary>
|
||||
int32 Length() const
|
||||
{
|
||||
return EndIndex - StartIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether range is empty.
|
||||
/// </summary>
|
||||
bool IsEmpty() const
|
||||
{
|
||||
return (EndIndex - StartIndex) <= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this range contains the character index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns><c>true</c> if range contains the specified character index; otherwise, <c>false</c>.</returns>
|
||||
bool Contains(int32 index) const
|
||||
{
|
||||
return index >= StartIndex && index < EndIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this range intersects with the other range.
|
||||
/// </summary>
|
||||
/// <param name="other">The other text range.</param>
|
||||
/// <returns><c>true</c> if range intersects with the specified range index;, <c>false</c>.</returns>
|
||||
bool Intersect(const TextRange& other) const
|
||||
{
|
||||
return Math::Min(EndIndex, other.EndIndex) > Math::Max(StartIndex, other.StartIndex);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TIsPODType<TextRange>
|
||||
{
|
||||
enum { Value = true };
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The font line info generated during text processing.
|
||||
/// </summary>
|
||||
API_STRUCT() struct FontLineCache
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache);
|
||||
|
||||
/// <summary>
|
||||
/// The root position of the line (upper left corner).
|
||||
/// </summary>
|
||||
API_FIELD() Vector2 Location;
|
||||
|
||||
/// <summary>
|
||||
/// The line bounds (width and height).
|
||||
/// </summary>
|
||||
API_FIELD() Vector2 Size;
|
||||
|
||||
/// <summary>
|
||||
/// The first character index (from the input text).
|
||||
/// </summary>
|
||||
API_FIELD() int32 FirstCharIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The last character index (from the input text).
|
||||
/// </summary>
|
||||
API_FIELD() int32 LastCharIndex;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TIsPODType<FontLineCache>
|
||||
{
|
||||
enum { Value = true };
|
||||
};
|
||||
|
||||
// Font glyph metrics:
|
||||
//
|
||||
// xmin xmax
|
||||
// | |
|
||||
// |<-------- width -------->|
|
||||
// | |
|
||||
// | +-------------------------+----------------- ymax
|
||||
// | | ggggggggg ggggg | ^ ^
|
||||
// | | g:::::::::ggg::::g | | |
|
||||
// | | g:::::::::::::::::g | | |
|
||||
// | | g::::::ggggg::::::gg | | |
|
||||
// | | g:::::g g:::::g | | |
|
||||
// offsetX -|-------->| g:::::g g:::::g | offsetY |
|
||||
// | | g:::::g g:::::g | | |
|
||||
// | | g::::::g g:::::g | | |
|
||||
// | | g:::::::ggggg:::::g | | |
|
||||
// | | g::::::::::::::::g | | height
|
||||
// | | gg::::::::::::::g | | |
|
||||
// baseline ---*---------|---- gggggggg::::::g-----*-------- |
|
||||
// / | | g:::::g | |
|
||||
// origin | | gggggg g:::::g | |
|
||||
// | | g:::::gg gg:::::g | |
|
||||
// | | g::::::ggg:::::::g | |
|
||||
// | | gg:::::::::::::g | |
|
||||
// | | ggg::::::ggg | |
|
||||
// | | gggggg | v
|
||||
// | +-------------------------+----------------- ymin
|
||||
// | |
|
||||
// |------------- advanceX ----------->|
|
||||
|
||||
/// <summary>
|
||||
/// The cached font character entry (read for rendering and further processing).
|
||||
/// </summary>
|
||||
API_STRUCT() struct FontCharacterEntry
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry);
|
||||
|
||||
/// <summary>
|
||||
/// The character represented by this entry.
|
||||
/// </summary>
|
||||
API_FIELD() Char Character;
|
||||
|
||||
/// <summary>
|
||||
/// True if entry is valid, otherwise false.
|
||||
/// </summary>
|
||||
API_FIELD() bool IsValid = false;
|
||||
|
||||
/// <summary>
|
||||
/// The index to a specific texture in the font cache.
|
||||
/// </summary>
|
||||
API_FIELD() byte TextureIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The left bearing expressed in integer pixels.
|
||||
/// </summary>
|
||||
API_FIELD() int16 OffsetX;
|
||||
|
||||
/// <summary>
|
||||
/// The top bearing expressed in integer pixels.
|
||||
/// </summary>
|
||||
API_FIELD() int16 OffsetY;
|
||||
|
||||
/// <summary>
|
||||
/// The amount to advance in X before drawing the next character in a string.
|
||||
/// </summary>
|
||||
API_FIELD() int16 AdvanceX;
|
||||
|
||||
/// <summary>
|
||||
/// The distance from baseline to glyph top most point.
|
||||
/// </summary>
|
||||
API_FIELD() int16 BearingY;
|
||||
|
||||
/// <summary>
|
||||
/// The height in pixels of the glyph.
|
||||
/// </summary>
|
||||
API_FIELD() int16 Height;
|
||||
|
||||
/// <summary>
|
||||
/// The start location of the character in the texture (in texture coordinates space).
|
||||
/// </summary>
|
||||
API_FIELD() Vector2 UV;
|
||||
|
||||
/// <summary>
|
||||
/// The size the character in the texture (in texture coordinates space).
|
||||
/// </summary>
|
||||
API_FIELD() Vector2 UVSize;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TIsPODType<FontCharacterEntry>
|
||||
{
|
||||
enum { Value = true };
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Represents font object that can be using during text rendering (it uses Font Asset but with pre-cached data for chosen font properties).
|
||||
/// </summary>
|
||||
API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API Font : public ManagedScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(Font);
|
||||
friend FontAsset;
|
||||
private:
|
||||
|
||||
FontAsset* _asset;
|
||||
int32 _size;
|
||||
int32 _height;
|
||||
int32 _ascender;
|
||||
int32 _descender;
|
||||
int32 _lineGap;
|
||||
bool _hasKerning;
|
||||
Dictionary<Char, FontCharacterEntry> _characters;
|
||||
mutable Dictionary<uint32, int32> _kerningTable;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Font"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parentAsset">The parent asset.</param>
|
||||
/// <param name="size">The size.</param>
|
||||
Font(FontAsset* parentAsset, int32 size);
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="Font"/> class.
|
||||
/// </summary>
|
||||
~Font();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets parent font asset that contains font family used by this font.
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE FontAsset* GetAsset() const
|
||||
{
|
||||
return _asset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets font size.
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE int32 GetSize() const
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets characters height.
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE int32 GetHeight() const
|
||||
{
|
||||
return _height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the largest vertical distance above the baseline for any character in the font.
|
||||
/// </summary>
|
||||
FORCE_INLINE int32 GetAscender() const
|
||||
{
|
||||
return _ascender;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the largest vertical distance below the baseline for any character in the font.
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE int32 GetDescender() const
|
||||
{
|
||||
return _descender;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the line gap property.
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE int32 GetLineGap() const
|
||||
{
|
||||
return _lineGap;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets character entry.
|
||||
/// </summary>
|
||||
/// <param name="c">The character.</param>
|
||||
/// <param name="result">The output character entry.</param>
|
||||
void GetCharacter(Char c, FontCharacterEntry& result);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the kerning amount for a pair of characters.
|
||||
/// </summary>
|
||||
/// <param name="first">The first character in the pair.</param>
|
||||
/// <param name="second">The second character in the pair.</param>
|
||||
/// <returns>The kerning amount or 0 if no kerning.</returns>
|
||||
API_FUNCTION() int32 GetKerning(Char first, Char second) const;
|
||||
|
||||
/// <summary>
|
||||
/// Caches the given text to prepared for the rendering.
|
||||
/// </summary>
|
||||
/// <param name="text">The text witch characters to cache.</param>
|
||||
API_FUNCTION() void CacheText(const StringView& text);
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates all cached dynamic font atlases using this font. Can be used to reload font characters after changing font asset options.
|
||||
/// </summary>
|
||||
API_FUNCTION() void Invalidate();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Processes text to get cached lines for rendering.
|
||||
/// </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(const StringView& text, Array<FontLineCache>& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout);
|
||||
|
||||
/// <summary>
|
||||
/// Processes text to get cached lines for rendering.
|
||||
/// </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)
|
||||
{
|
||||
Array<FontLineCache> lines;
|
||||
ProcessText(text, lines, layout);
|
||||
return lines;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes text to get cached lines for rendering.
|
||||
/// </summary>
|
||||
/// <param name="text">The input text.</param>
|
||||
/// <returns>The output lines list.</returns>
|
||||
API_FUNCTION() FORCE_INLINE Array<FontLineCache> ProcessText(const StringView& text)
|
||||
{
|
||||
return ProcessText(text, TextLayoutOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Measures minimum size of the rectangle that will be needed to draw given text.
|
||||
/// </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() Vector2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout);
|
||||
|
||||
/// <summary>
|
||||
/// Measures minimum size of the rectangle that will be needed to draw given text
|
||||
/// </summary>.
|
||||
/// <param name="text">The input text to test.</param>
|
||||
/// <returns>The minimum size for that text and fot to render properly.</returns>
|
||||
API_FUNCTION() FORCE_INLINE Vector2 MeasureText(const StringView& text)
|
||||
{
|
||||
return MeasureText(text, TextLayoutOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates hit character index at given location.
|
||||
/// </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 Vector2& location, API_PARAM(Ref) const TextLayoutOptions& layout)
|
||||
{
|
||||
return HitTestText(StringView(text.Get() + textRange.StartIndex, textRange.Length()), location, layout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates hit character index at given location.
|
||||
/// </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 Vector2& location, API_PARAM(Ref) const TextLayoutOptions& layout);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates hit character index at given location.
|
||||
/// </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 Vector2& location)
|
||||
{
|
||||
return HitTestText(text, location, TextLayoutOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates hit character index at given location.
|
||||
/// </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 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Vector2& location)
|
||||
{
|
||||
return HitTestText(StringView(text.Get() + textRange.StartIndex, textRange.Length()), location, TextLayoutOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates character position for given text and character index.
|
||||
/// </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() Vector2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates character position for given text and character index.
|
||||
/// </summary>
|
||||
/// <param name="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() Vector2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout)
|
||||
{
|
||||
return GetCharPosition(StringView(text.Get() + textRange.StartIndex, textRange.Length()), index, layout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates character position for given text and character index
|
||||
/// </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 Vector2 GetCharPosition(const StringView& text, int32 index)
|
||||
{
|
||||
return GetCharPosition(text, index, TextLayoutOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates character position for given text and character index
|
||||
/// </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 Vector2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index)
|
||||
{
|
||||
return GetCharPosition(StringView(text.Get() + textRange.StartIndex, textRange.Length()), index, TextLayoutOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the size of the face with the Free Type library backend.
|
||||
/// </summary>
|
||||
void FlushFaceSize() const;
|
||||
|
||||
public:
|
||||
|
||||
// [Object]
|
||||
String ToString() const override;
|
||||
};
|
||||
166
Source/Engine/Render2D/FontAsset.cpp
Normal file
166
Source/Engine/Render2D/FontAsset.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "FontAsset.h"
|
||||
#include "Font.h"
|
||||
#include "FontManager.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||
#include "Engine/Content/Upgraders/FontAssetUpgrader.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "IncludeFreeType.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#endif
|
||||
|
||||
REGISTER_BINARY_ASSET(FontAsset, "FlaxEngine.FontAsset", ::New<FontAssetUpgrader>(), false);
|
||||
|
||||
FontAsset::FontAsset(const SpawnParams& params, const AssetInfo* info)
|
||||
: BinaryAsset(params, info)
|
||||
, _face(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
String FontAsset::GetFamilyName() const
|
||||
{
|
||||
return _face ? String(_face->family_name) : String::Empty;
|
||||
}
|
||||
|
||||
String FontAsset::GetStyleName() const
|
||||
{
|
||||
return _face ? String(_face->style_name) : String::Empty;
|
||||
}
|
||||
|
||||
Asset::LoadResult FontAsset::load()
|
||||
{
|
||||
// Load font data
|
||||
auto chunk0 = GetChunk(0);
|
||||
if (chunk0 == nullptr || chunk0->IsMissing())
|
||||
return LoadResult::MissingDataChunk;
|
||||
_fontFile.Swap(chunk0->Data);
|
||||
|
||||
// Create font face
|
||||
const FT_Error error = FT_New_Memory_Face(FontManager::GetLibrary(), _fontFile.Get(), static_cast<FT_Long>(_fontFile.Length()), 0, &_face);
|
||||
if (error)
|
||||
{
|
||||
_face = nullptr;
|
||||
LOG_FT_ERROR(error);
|
||||
return LoadResult::Failed;
|
||||
}
|
||||
|
||||
return LoadResult::Ok;
|
||||
}
|
||||
|
||||
void FontAsset::unload(bool isReloading)
|
||||
{
|
||||
// Ensure to cleanup child font objects
|
||||
if (_fonts.HasItems())
|
||||
{
|
||||
LOG(Warning, "Font asset {0} is unloading but has {1} reaming font objects created", ToString(), _fonts.Count());
|
||||
for (auto font : _fonts)
|
||||
{
|
||||
font->_asset = nullptr;
|
||||
font->DeleteObject();
|
||||
}
|
||||
_fonts.Clear();
|
||||
}
|
||||
|
||||
// Unload face
|
||||
if (_face)
|
||||
{
|
||||
FT_Done_Face(_face);
|
||||
_face = nullptr;
|
||||
}
|
||||
|
||||
// Cleanup data
|
||||
_fontFile.Release();
|
||||
}
|
||||
|
||||
AssetChunksFlag FontAsset::getChunksToPreload() const
|
||||
{
|
||||
return GET_CHUNK_FLAG(0);
|
||||
}
|
||||
|
||||
Font* FontAsset::CreateFont(int32 size)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
if (WaitForLoaded())
|
||||
return nullptr;
|
||||
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Check if font with that size has already been created
|
||||
for (auto font : _fonts)
|
||||
{
|
||||
if (font->GetAsset() == this && font->GetSize() == size)
|
||||
return font;
|
||||
}
|
||||
|
||||
return New<Font>(this, size);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool FontAsset::Save(const StringView& path)
|
||||
{
|
||||
// Validate state
|
||||
if (WaitForLoaded())
|
||||
{
|
||||
LOG(Error, "Asset loading failed. Cannot save it.");
|
||||
return true;
|
||||
}
|
||||
if (IsVirtual() && path.IsEmpty())
|
||||
{
|
||||
LOG(Error, "To save virtual asset asset you need to specify the target asset path location.");
|
||||
return true;
|
||||
}
|
||||
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
AssetInitData data;
|
||||
data.SerializedVersion = SerializedVersion;
|
||||
data.CustomData.Copy(&_options);
|
||||
auto chunk0 = GetChunk(0);
|
||||
_fontFile.Swap(chunk0->Data);
|
||||
const bool saveResult = path.HasChars() ? SaveAsset(path, data) : SaveAsset(data, true);
|
||||
_fontFile.Swap(chunk0->Data);
|
||||
if (saveResult)
|
||||
{
|
||||
LOG(Error, "Cannot save '{0}'", ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void FontAsset::Invalidate()
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
for (auto font : _fonts)
|
||||
{
|
||||
font->Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
bool FontAsset::init(AssetInitData& initData)
|
||||
{
|
||||
// Validate
|
||||
if (initData.SerializedVersion != SerializedVersion)
|
||||
{
|
||||
LOG(Error, "Invalid serialized font asset version.");
|
||||
return true;
|
||||
}
|
||||
if (initData.CustomData.Length() != sizeof(_options))
|
||||
{
|
||||
LOG(Error, "Missing font asset header.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Copy header
|
||||
Platform::MemoryCopy(&_options, initData.CustomData.Get(), sizeof(_options));
|
||||
|
||||
return false;
|
||||
}
|
||||
54
Source/Engine/Render2D/FontAsset.cs
Normal file
54
Source/Engine/Render2D/FontAsset.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
partial struct FontOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for equality between two objects.
|
||||
/// </summary>
|
||||
/// <param name="other">The other object to compare.</param>
|
||||
/// <returns><c>true</c> if this object has the same value as <paramref name="other" />; otherwise, <c>false</c> </returns>
|
||||
public bool Equals(FontOptions other)
|
||||
{
|
||||
return Hinting == other.Hinting && Flags == other.Flags;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is FontOptions other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return ((int)Hinting * 397) ^ (int)Flags;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests for equality between two objects.
|
||||
/// </summary>
|
||||
/// <param name="left">The first value to compare.</param>
|
||||
/// <param name="right">The second value to compare.</param>
|
||||
/// <returns><c>true</c> if <paramref name="left" /> has the same value as <paramref name="right" />; otherwise, <c>false</c>.</returns>
|
||||
public static bool operator ==(FontOptions left, FontOptions right)
|
||||
{
|
||||
return left.Hinting == right.Hinting && left.Flags == right.Flags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests for inequality between two objects.
|
||||
/// </summary>
|
||||
/// <param name="left">The first value to compare.</param>
|
||||
/// <param name="right">The second value to compare.</param>
|
||||
/// <returns><c>true</c> if <paramref name="left" /> has a different value than <paramref name="right" />; otherwise,<c>false</c>.</returns>
|
||||
public static bool operator !=(FontOptions left, FontOptions right)
|
||||
{
|
||||
return left.Hinting != right.Hinting || left.Flags != right.Flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
170
Source/Engine/Render2D/FontAsset.h
Normal file
170
Source/Engine/Render2D/FontAsset.h
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Content/BinaryAsset.h"
|
||||
|
||||
class Font;
|
||||
class FontManager;
|
||||
typedef struct FT_FaceRec_* FT_Face;
|
||||
|
||||
/// <summary>
|
||||
/// The font hinting used when rendering characters.
|
||||
/// </summary>
|
||||
API_ENUM() enum class FontHinting : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Use the default hinting specified in the font.
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// Force the use of an automatic hinting algorithm (over the font's native hinter).
|
||||
/// </summary>
|
||||
Auto,
|
||||
|
||||
/// <summary>
|
||||
/// Force the use of an automatic light hinting algorithm, optimized for non-monochrome displays.
|
||||
/// </summary>
|
||||
AutoLight,
|
||||
|
||||
/// <summary>
|
||||
/// Force the use of an automatic hinting algorithm optimized for monochrome displays.
|
||||
/// </summary>
|
||||
Monochrome,
|
||||
|
||||
/// <summary>
|
||||
/// Do not use hinting. This generally generates 'blurrier' bitmap glyphs when the glyph are rendered in any of the anti-aliased modes.
|
||||
/// </summary>
|
||||
None,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The font flags used when rendering characters.
|
||||
/// </summary>
|
||||
API_ENUM(Attributes="Flags") enum class FontFlags : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// No options.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Enables using anti-aliasing for font characters. Otherwise font will use monochrome data.
|
||||
/// </summary>
|
||||
AntiAliasing = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Enables artificial embolden effect.
|
||||
/// </summary>
|
||||
Bold = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Enables slant effect, emulating italic style.
|
||||
/// </summary>
|
||||
Italic = 4,
|
||||
};
|
||||
|
||||
DECLARE_ENUM_OPERATORS(FontFlags);
|
||||
|
||||
/// <summary>
|
||||
/// The font asset options.
|
||||
/// </summary>
|
||||
API_STRUCT() struct FontOptions
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(FontOptions);
|
||||
|
||||
/// <summary>
|
||||
/// The hinting.
|
||||
/// </summary>
|
||||
API_FIELD() FontHinting Hinting;
|
||||
|
||||
/// <summary>
|
||||
/// The flags.
|
||||
/// </summary>
|
||||
API_FIELD() FontFlags Flags;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Font asset contains glyph collection and cached data used to render text.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API FontAsset : public BinaryAsset
|
||||
{
|
||||
DECLARE_BINARY_ASSET_HEADER(FontAsset, 3);
|
||||
friend Font;
|
||||
private:
|
||||
|
||||
FT_Face _face;
|
||||
FontOptions _options;
|
||||
BytesContainer _fontFile;
|
||||
Array<Font*, InlinedAllocation<32>> _fonts;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font family name.
|
||||
/// </summary>
|
||||
API_PROPERTY() String GetFamilyName() const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font style name.
|
||||
/// </summary>
|
||||
API_PROPERTY() String GetStyleName() const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets FreeType face handle.
|
||||
/// </summary>
|
||||
FORCE_INLINE FT_Face GetFTFace() const
|
||||
{
|
||||
return _face;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font options.
|
||||
/// </summary>
|
||||
API_PROPERTY() const FontOptions& GetOptions() const
|
||||
{
|
||||
return _options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the font options.
|
||||
/// </summary>
|
||||
API_PROPERTY() void SetOptions(const FontOptions& value)
|
||||
{
|
||||
_options = value;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Creates the font object of given characters size.
|
||||
/// </summary>
|
||||
/// <param name="size">The font characters size.</param>
|
||||
/// <returns>The created font object.</returns>
|
||||
API_FUNCTION() Font* CreateFont(int32 size);
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
/// <summary>
|
||||
/// Saves this asset to the file. Supported only in Editor.
|
||||
/// </summary>
|
||||
/// <param name="path">The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset.</param>
|
||||
/// <returns>True if cannot save data, otherwise false.</returns>
|
||||
API_FUNCTION() bool Save(const StringView& path = StringView::Empty);
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates all cached dynamic font atlases using this font. Can be used to reload font characters after changing font asset options.
|
||||
/// </summary>
|
||||
API_FUNCTION() void Invalidate();
|
||||
|
||||
protected:
|
||||
|
||||
// [BinaryAsset]
|
||||
bool init(AssetInitData& initData) override;
|
||||
LoadResult load() override;
|
||||
void unload(bool isReloading) override;
|
||||
AssetChunksFlag getChunksToPreload() const override;
|
||||
};
|
||||
333
Source/Engine/Render2D/FontManager.cpp
Normal file
333
Source/Engine/Render2D/FontManager.cpp
Normal file
@@ -0,0 +1,333 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "FontManager.h"
|
||||
#include "FontTextureAtlas.h"
|
||||
#include "FontAsset.h"
|
||||
#include "Font.h"
|
||||
#include "Engine/Platform/Platform.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "IncludeFreeType.h"
|
||||
#include <ThirdParty/freetype/ftsynth.h>
|
||||
#include <ThirdParty/freetype/ftbitmap.h>
|
||||
#include <ThirdParty/freetype/internal/ftdrv.h>
|
||||
|
||||
namespace FontManagerImpl
|
||||
{
|
||||
FT_Library Library;
|
||||
CriticalSection Locker;
|
||||
Array<AssetReference<FontTextureAtlas>> Atlases;
|
||||
Array<byte> GlyphImageData;
|
||||
}
|
||||
|
||||
using namespace FontManagerImpl;
|
||||
|
||||
class FontManagerService : public EngineService
|
||||
{
|
||||
public:
|
||||
|
||||
FontManagerService()
|
||||
: EngineService(TEXT("Font Manager"), -700)
|
||||
{
|
||||
}
|
||||
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
};
|
||||
|
||||
FontManagerService FontManagerServiceInstance;
|
||||
|
||||
float FontManager::FontScale = 1.0f;
|
||||
|
||||
FT_Library FontManager::GetLibrary()
|
||||
{
|
||||
return Library;
|
||||
}
|
||||
|
||||
FT_MemoryRec_ FreeTypeMemory;
|
||||
|
||||
static void* FreeTypeAlloc(FT_Memory memory, long size)
|
||||
{
|
||||
return Allocator::Allocate(size);
|
||||
}
|
||||
|
||||
void* FreeTypeRealloc(FT_Memory memory, long oldSize, long newSize, void* ptr)
|
||||
{
|
||||
return AllocatorExt::Realloc(ptr, oldSize, newSize);
|
||||
}
|
||||
|
||||
void FreeTypeFree(FT_Memory memory, void* ptr)
|
||||
{
|
||||
return Allocator::Free(ptr);
|
||||
}
|
||||
|
||||
bool FontManagerService::Init()
|
||||
{
|
||||
ASSERT(Library == nullptr);
|
||||
|
||||
// Scale UI fonts to match the monitor DPI
|
||||
FontManager::FontScale = (float)Platform::GetDpi() / (float)DefaultDPI;
|
||||
|
||||
// Init Free Type
|
||||
FreeTypeMemory.user = nullptr;
|
||||
FreeTypeMemory.alloc = &FreeTypeAlloc;
|
||||
FreeTypeMemory.realloc = &FreeTypeRealloc;
|
||||
FreeTypeMemory.free = &FreeTypeFree;
|
||||
const FT_Error error = FT_New_Library(&FreeTypeMemory, &Library);
|
||||
if (error)
|
||||
{
|
||||
LOG_FT_ERROR(error);
|
||||
return true;
|
||||
}
|
||||
FT_Add_Default_Modules(Library);
|
||||
|
||||
// Log version info
|
||||
FT_Int major, minor, patch;
|
||||
FT_Library_Version(Library, &major, &minor, &patch);
|
||||
LOG(Info, "FreeType initialized, version: {0}.{1}.{2}", major, minor, patch);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FontManagerService::Dispose()
|
||||
{
|
||||
// Release font atlases
|
||||
Atlases.Resize(0);
|
||||
|
||||
// Clean library
|
||||
if (Library)
|
||||
{
|
||||
const FT_Error error = FT_Done_Library(Library);
|
||||
Library = nullptr;
|
||||
if (error)
|
||||
{
|
||||
LOG_FT_ERROR(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FontTextureAtlas* FontManager::GetAtlas(int32 index)
|
||||
{
|
||||
return Atlases[index];
|
||||
}
|
||||
|
||||
bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry)
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
const FontAsset* asset = font->GetAsset();
|
||||
const FontOptions& options = asset->GetOptions();
|
||||
const FT_Face face = asset->GetFTFace();
|
||||
ASSERT(face != nullptr);
|
||||
font->FlushFaceSize();
|
||||
|
||||
// Set load flags
|
||||
uint32 glyphFlags = FT_LOAD_NO_BITMAP;
|
||||
if (options.Flags & FontFlags::AntiAliasing)
|
||||
{
|
||||
switch (options.Hinting)
|
||||
{
|
||||
case FontHinting::Auto:
|
||||
glyphFlags |= FT_LOAD_FORCE_AUTOHINT;
|
||||
break;
|
||||
case FontHinting::AutoLight:
|
||||
glyphFlags |= FT_LOAD_TARGET_LIGHT;
|
||||
break;
|
||||
case FontHinting::Monochrome:
|
||||
glyphFlags |= FT_LOAD_TARGET_MONO;
|
||||
break;
|
||||
case FontHinting::None:
|
||||
glyphFlags |= FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING;
|
||||
break;
|
||||
case FontHinting::Default:
|
||||
default:
|
||||
glyphFlags |= FT_LOAD_TARGET_NORMAL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
glyphFlags |= FT_LOAD_TARGET_MONO | FT_LOAD_FORCE_AUTOHINT;
|
||||
}
|
||||
|
||||
// Get the index to the glyph in the font face
|
||||
const FT_UInt glyphIndex = FT_Get_Char_Index(face, c);
|
||||
|
||||
// Load the glyph
|
||||
const FT_Error error = FT_Load_Glyph(face, glyphIndex, glyphFlags);
|
||||
if (error)
|
||||
{
|
||||
LOG_FT_ERROR(error);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle special effects
|
||||
if (options.Flags & FontFlags::Bold)
|
||||
{
|
||||
FT_GlyphSlot_Embolden(face->glyph);
|
||||
}
|
||||
if (options.Flags & FontFlags::Italic)
|
||||
{
|
||||
FT_GlyphSlot_Oblique(face->glyph);
|
||||
}
|
||||
|
||||
// Render glyph to the bitmap
|
||||
FT_GlyphSlot glyph = face->glyph;
|
||||
FT_Render_Glyph(glyph, options.Flags & FontFlags::AntiAliasing ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO);
|
||||
|
||||
FT_Bitmap* bitmap = &glyph->bitmap;
|
||||
FT_Bitmap tmpBitmap;
|
||||
if (bitmap->pixel_mode != FT_PIXEL_MODE_GRAY)
|
||||
{
|
||||
// Convert the bitmap to 8bpp grayscale
|
||||
FT_Bitmap_New(&tmpBitmap);
|
||||
FT_Bitmap_Convert(Library, bitmap, &tmpBitmap, 4);
|
||||
bitmap = &tmpBitmap;
|
||||
}
|
||||
ASSERT(bitmap && bitmap->pixel_mode == FT_PIXEL_MODE_GRAY);
|
||||
|
||||
// Fill the character data
|
||||
Platform::MemoryClear(&entry, sizeof(entry));
|
||||
entry.Character = c;
|
||||
entry.AdvanceX = Convert26Dot6ToRoundedPixel<int16>(glyph->advance.x);
|
||||
entry.OffsetY = glyph->bitmap_top;
|
||||
entry.OffsetX = glyph->bitmap_left;
|
||||
entry.IsValid = true;
|
||||
entry.BearingY = Convert26Dot6ToRoundedPixel<int16>(glyph->metrics.horiBearingY);
|
||||
entry.Height = Convert26Dot6ToRoundedPixel<int16>(glyph->metrics.height);
|
||||
|
||||
// Allocate memory
|
||||
const int32 glyphWidth = bitmap->width;
|
||||
const int32 glyphHeight = bitmap->rows;
|
||||
GlyphImageData.Clear();
|
||||
GlyphImageData.Resize(glyphWidth * glyphHeight);
|
||||
|
||||
// End for empty glyphs
|
||||
if (GlyphImageData.IsEmpty())
|
||||
{
|
||||
if (bitmap == &tmpBitmap)
|
||||
{
|
||||
FT_Bitmap_Done(Library, bitmap);
|
||||
bitmap = nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy glyph data after rasterize (row by row)
|
||||
for (int32 row = 0; row < glyphHeight; row++)
|
||||
{
|
||||
Platform::MemoryCopy(&GlyphImageData[row * glyphWidth], &bitmap->buffer[row * bitmap->pitch], glyphWidth);
|
||||
}
|
||||
|
||||
// Normalize gray scale images not using 256 colors
|
||||
if (bitmap->num_grays != 256)
|
||||
{
|
||||
const int32 scale = 255 / (bitmap->num_grays - 1);
|
||||
for (byte& pixel : GlyphImageData)
|
||||
{
|
||||
pixel *= scale;
|
||||
}
|
||||
}
|
||||
|
||||
// Free temporary bitmap if used
|
||||
if (bitmap == &tmpBitmap)
|
||||
{
|
||||
FT_Bitmap_Done(Library, bitmap);
|
||||
bitmap = nullptr;
|
||||
}
|
||||
|
||||
// Find atlas for the character texture
|
||||
int32 atlasIndex = 0;
|
||||
const FontTextureAtlas::Slot* slot = nullptr;
|
||||
for (; atlasIndex < Atlases.Count(); atlasIndex++)
|
||||
{
|
||||
// Add the character to the texture
|
||||
slot = Atlases[atlasIndex]->AddEntry(glyphWidth, glyphHeight, GlyphImageData);
|
||||
|
||||
// Check result, if not null char has been added
|
||||
if (slot)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there is no atlas for this character
|
||||
if (!slot)
|
||||
{
|
||||
// Create new atlas
|
||||
auto atlas = Content::CreateVirtualAsset<FontTextureAtlas>();
|
||||
atlas->Setup(PixelFormat::R8_UNorm, FontTextureAtlas::PaddingStyle::PadWithZero);
|
||||
Atlases.Add(atlas);
|
||||
|
||||
// Init atlas
|
||||
const int32 fontAtlasSize = 512; // TODO: make it a configuration variable
|
||||
atlas->Init(fontAtlasSize, fontAtlasSize);
|
||||
|
||||
// Add the character to the texture
|
||||
slot = atlas->AddEntry(glyphWidth, glyphHeight, GlyphImageData);
|
||||
}
|
||||
if (slot == nullptr)
|
||||
{
|
||||
LOG(Error, "Cannot find free space in texture atlases for character '{0}' from font {1} {2}. Size: {3}x{4}", c, String(face->family_name), String(face->style_name), glyphWidth, glyphHeight);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fill with atlas dependant data
|
||||
const uint32 padding = Atlases[atlasIndex]->GetPaddingAmount();
|
||||
entry.TextureIndex = atlasIndex;
|
||||
entry.UV.X = static_cast<float>(slot->X + padding);
|
||||
entry.UV.Y = static_cast<float>(slot->Y + padding);
|
||||
entry.UVSize.X = static_cast<float>(slot->Width - 2 * padding);
|
||||
entry.UVSize.Y = static_cast<float>(slot->Height - 2 * padding);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FontManager::Invalidate(FontCharacterEntry& entry)
|
||||
{
|
||||
auto atlas = Atlases[entry.TextureIndex];
|
||||
const uint32 padding = atlas->GetPaddingAmount();
|
||||
const uint32 slotX = static_cast<uint32>(entry.UV.X - padding);
|
||||
const uint32 slotY = static_cast<uint32>(entry.UV.Y - padding);
|
||||
const uint32 slotSizeX = static_cast<uint32>(entry.UVSize.X + 2 * padding);
|
||||
const uint32 slotSizeY = static_cast<uint32>(entry.UVSize.Y + 2 * padding);
|
||||
atlas->Invalidate(slotX, slotY, slotSizeX, slotSizeY);
|
||||
}
|
||||
|
||||
void FontManager::Flush()
|
||||
{
|
||||
for (const auto& atlas : Atlases)
|
||||
{
|
||||
atlas->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
void FontManager::EnsureAtlasCreated(int32 index)
|
||||
{
|
||||
Atlases[index]->EnsureTextureCreated();
|
||||
}
|
||||
|
||||
bool FontManager::IsDirty()
|
||||
{
|
||||
for (const auto atlas : Atlases)
|
||||
{
|
||||
if (atlas->IsDirty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FontManager::HasDataSyncWithGPU()
|
||||
{
|
||||
for (const auto atlas : Atlases)
|
||||
{
|
||||
if (atlas->HasDataSyncWithGPU() == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
72
Source/Engine/Render2D/FontManager.h
Normal file
72
Source/Engine/Render2D/FontManager.h
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Types/BaseTypes.h"
|
||||
|
||||
class Font;
|
||||
class FontAsset;
|
||||
class FontTextureAtlas;
|
||||
struct FontCharacterEntry;
|
||||
typedef struct FT_LibraryRec_* FT_Library;
|
||||
|
||||
/// <summary>
|
||||
/// Fonts management and character atlases management utility service.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API FontManager
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The global characters font scale factor. Used to upscale characters on high-DPI monitors.
|
||||
/// </summary>
|
||||
static float FontScale;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the FreeType library.
|
||||
/// </summary>
|
||||
/// <returns>The library.</returns>
|
||||
static FT_Library GetLibrary();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture atlas.
|
||||
/// </summary>
|
||||
/// <param name="index">The atlas index.</param>
|
||||
/// <returns>The texture atlas.</returns>
|
||||
static FontTextureAtlas* GetAtlas(int32 index);
|
||||
|
||||
/// <summary>
|
||||
/// Adds character from given font to the cache.
|
||||
/// </summary>
|
||||
/// <param name="font">The font to create character entry for it.</param>
|
||||
/// <param name="c">The character to add.</param>
|
||||
/// <param name="entry">The created character entry.</param>
|
||||
/// <returns>True if cannot add new character entry to the font cache, otherwise false.</returns>
|
||||
static bool AddNewEntry(Font* font, Char c, FontCharacterEntry& entry);
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates the cached dynamic font character. Can be used to reload font characters after changing font asset options.
|
||||
/// </summary>
|
||||
/// <param name="entry">The font character entry.</param>
|
||||
static void Invalidate(FontCharacterEntry& entry);
|
||||
|
||||
/// <summary>
|
||||
/// Flushes all font atlases.
|
||||
/// </summary>
|
||||
static void Flush();
|
||||
|
||||
// Ensure that atlas with given index has been created
|
||||
static void EnsureAtlasCreated(int32 index);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether one or more font atlases is dirty.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if one or more font atlases is dirty; otherwise, <c>false</c>.</returns>
|
||||
static bool IsDirty();
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether all atlases has been synced with the GPU memory and data is up to date.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if all atlases has been synced with the GPU memory and data is up to date; otherwise, <c>false</c>.</returns>
|
||||
static bool HasDataSyncWithGPU();
|
||||
};
|
||||
152
Source/Engine/Render2D/FontReference.cs
Normal file
152
Source/Engine/Render2D/FontReference.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Font reference that defines the font asset and font size to use.
|
||||
/// </summary>
|
||||
public struct FontReference
|
||||
{
|
||||
[NoSerialize]
|
||||
private FontAsset _font;
|
||||
|
||||
[NoSerialize]
|
||||
private int _size;
|
||||
|
||||
[NoSerialize]
|
||||
private Font _cachedFont;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FontReference"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="font">The font.</param>
|
||||
/// <param name="size">The font size.</param>
|
||||
public FontReference(FontAsset font, int size)
|
||||
{
|
||||
_font = font;
|
||||
_size = size;
|
||||
_cachedFont = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FontReference"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="font">The font.</param>
|
||||
public FontReference(Font font)
|
||||
{
|
||||
_font = font?.Asset;
|
||||
_size = font?.Size ?? 30;
|
||||
_cachedFont = font;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The font asset.
|
||||
/// </summary>
|
||||
[EditorOrder(0), Tooltip("The font asset to use as characters source.")]
|
||||
public FontAsset Font
|
||||
{
|
||||
get => _font;
|
||||
set
|
||||
{
|
||||
if (_font != value)
|
||||
{
|
||||
_font = value;
|
||||
|
||||
if (_cachedFont)
|
||||
_cachedFont = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The size of the font characters.
|
||||
/// </summary>
|
||||
[EditorOrder(10), Limit(1, 500, 0.1f), Tooltip("The size of the font characters.")]
|
||||
public int Size
|
||||
{
|
||||
get => _size;
|
||||
set
|
||||
{
|
||||
if (_size != value)
|
||||
{
|
||||
_size = value;
|
||||
|
||||
if (_cachedFont)
|
||||
_cachedFont = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the font object described by the structure.
|
||||
/// </summary>
|
||||
/// <returns>Th font or null if descriptor is invalid.</returns>
|
||||
public Font GetFont()
|
||||
{
|
||||
return _cachedFont ?? (_cachedFont = _font?.CreateFont(_size));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="FontReference" /> is equal to this instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The <see cref="FontReference" /> to compare with this instance.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified <see cref="FontReference" /> is equal to this instance; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ref FontReference other)
|
||||
{
|
||||
return _font == other._font && _size == other._size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two font references.
|
||||
/// </summary>
|
||||
/// <param name="lhs">The left.</param>
|
||||
/// <param name="rhs">The right.</param>
|
||||
/// <returns>True if font references are equal, otherwise false.</returns>
|
||||
public static bool operator ==(FontReference lhs, FontReference rhs)
|
||||
{
|
||||
return lhs.Equals(ref rhs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two font references.
|
||||
/// </summary>
|
||||
/// <param name="lhs">The left.</param>
|
||||
/// <param name="rhs">The right.</param>
|
||||
/// <returns>True if font references are not equal, otherwise false.</returns>
|
||||
public static bool operator !=(FontReference lhs, FontReference rhs)
|
||||
{
|
||||
return !lhs.Equals(ref rhs);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
if (!(other is FontReference))
|
||||
return false;
|
||||
var fontReference = (FontReference)other;
|
||||
return Equals(ref fontReference);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hashCode = _font.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ _size;
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}, size {1}", _font ? _font.ToString() : string.Empty, _size);
|
||||
}
|
||||
}
|
||||
}
|
||||
265
Source/Engine/Render2D/FontTextureAtlas.cpp
Normal file
265
Source/Engine/Render2D/FontTextureAtlas.cpp
Normal file
@@ -0,0 +1,265 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "FontTextureAtlas.h"
|
||||
#include "Engine/Core/Types/DataContainer.h"
|
||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||
#include "Engine/Graphics/PixelFormat.h"
|
||||
#include "Engine/Graphics/PixelFormatExtensions.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
|
||||
REGISTER_BINARY_ASSET(FontTextureAtlas, "FlaxEngine.FontTextureAtlas", nullptr, true);
|
||||
|
||||
FontTextureAtlas::FontTextureAtlas(const SpawnParams& params, const AssetInfo* info)
|
||||
: Texture(params, info)
|
||||
, _width(0)
|
||||
, _height(0)
|
||||
, _isDirty(true)
|
||||
, _root(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
uint32 FontTextureAtlas::GetPaddingAmount() const
|
||||
{
|
||||
return (_paddingStyle == NoPadding ? 0 : 1);
|
||||
}
|
||||
|
||||
void FontTextureAtlas::Setup(PixelFormat format, PaddingStyle paddingStyle)
|
||||
{
|
||||
_format = format;
|
||||
_bytesPerPixel = PixelFormatExtensions::SizeInBytes(format);
|
||||
_paddingStyle = paddingStyle;
|
||||
}
|
||||
|
||||
void FontTextureAtlas::Init(uint32 width, uint32 height)
|
||||
{
|
||||
ASSERT(_root == nullptr);
|
||||
|
||||
// Setup
|
||||
uint32 padding = GetPaddingAmount();
|
||||
_width = width;
|
||||
_height = height;
|
||||
_root = New<Slot>(padding, padding, _width - padding, _height - padding);
|
||||
_isDirty = false;
|
||||
|
||||
// Reserve upload data memory
|
||||
_data.Resize(_width * _height * _bytesPerPixel, false);
|
||||
Platform::MemoryClear(_data.Get(), _data.Capacity());
|
||||
}
|
||||
|
||||
FontTextureAtlas::Slot* FontTextureAtlas::AddEntry(uint32 targetWidth, uint32 targetHeight, const Array<byte>& data)
|
||||
{
|
||||
// Check for invalid size
|
||||
if (targetWidth == 0 || targetHeight == 0)
|
||||
return nullptr;
|
||||
|
||||
// Try to find slot for the texture
|
||||
Slot* slot = nullptr;
|
||||
const uint32 padding = GetPaddingAmount();
|
||||
const uint32 allPadding = padding * 2;
|
||||
for (int32 i = 0; i < _freeSlots.Count(); i++)
|
||||
{
|
||||
Slot* e = _freeSlots[i];
|
||||
if (e->Width == targetWidth + allPadding && e->Height == targetHeight + allPadding)
|
||||
{
|
||||
slot = e;
|
||||
_freeSlots.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!slot)
|
||||
{
|
||||
slot = _root->Insert(targetWidth, targetHeight, GetPaddingAmount() * 2);
|
||||
}
|
||||
|
||||
// Check if can fit it
|
||||
if (slot)
|
||||
{
|
||||
// Copy data to into the atlas memory
|
||||
CopyDataIntoSlot(slot, data);
|
||||
|
||||
// Set dirty state
|
||||
markAsDirty();
|
||||
}
|
||||
|
||||
// Returns result
|
||||
return slot;
|
||||
}
|
||||
|
||||
bool FontTextureAtlas::Invalidate(uint32 x, uint32 y, uint32 width, uint32 height)
|
||||
{
|
||||
Slot* slot = invalidate(_root, x, y, width, height);
|
||||
if (slot)
|
||||
{
|
||||
_freeSlots.Add(slot);
|
||||
}
|
||||
return slot != nullptr;
|
||||
}
|
||||
|
||||
void FontTextureAtlas::CopyDataIntoSlot(const Slot* slot, const Array<byte>& data)
|
||||
{
|
||||
// Copy pixel data to the texture
|
||||
uint8* start = &_data[slot->Y * _width * _bytesPerPixel + slot->X * _bytesPerPixel];
|
||||
|
||||
// Account for same padding on each sides
|
||||
const uint32 padding = GetPaddingAmount();
|
||||
const uint32 allPadding = padding * 2;
|
||||
|
||||
// The width of the source texture without padding (actual width)
|
||||
const uint32 sourceWidth = slot->Width - allPadding;
|
||||
const uint32 sourceHeight = slot->Height - allPadding;
|
||||
|
||||
CopyRowData copyRowData;
|
||||
copyRowData.DestData = start;
|
||||
copyRowData.SrcData = data.Get();
|
||||
copyRowData.DestTextureWidth = _width;
|
||||
copyRowData.SrcTextureWidth = sourceWidth;
|
||||
copyRowData.RowWidth = slot->Width;
|
||||
|
||||
// Apply the padding for bilinear filtering
|
||||
// Not used if no padding (assumes sampling outside boundaries of the sub texture is not possible)
|
||||
if (padding > 0)
|
||||
{
|
||||
// Copy first color row into padding.
|
||||
copyRowData.SrcRow = 0;
|
||||
copyRowData.DestRow = 0;
|
||||
|
||||
if (_paddingStyle == DilateBorder)
|
||||
{
|
||||
copyRow(copyRowData);
|
||||
}
|
||||
else
|
||||
{
|
||||
zeroRow(copyRowData);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy each row of the texture
|
||||
for (uint32 row = padding; row < slot->Height - padding; row++)
|
||||
{
|
||||
copyRowData.SrcRow = row - padding;
|
||||
copyRowData.DestRow = row;
|
||||
|
||||
copyRow(copyRowData);
|
||||
}
|
||||
|
||||
if (padding > 0)
|
||||
{
|
||||
// Copy last color row into padding row for bilinear filtering
|
||||
copyRowData.SrcRow = sourceHeight - 1;
|
||||
copyRowData.DestRow = slot->Height - padding;
|
||||
|
||||
if (_paddingStyle == DilateBorder)
|
||||
{
|
||||
copyRow(copyRowData);
|
||||
}
|
||||
else
|
||||
{
|
||||
zeroRow(copyRowData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FontTextureAtlas::copyRow(const CopyRowData& copyRowData) const
|
||||
{
|
||||
const byte* data = copyRowData.SrcData;
|
||||
byte* start = copyRowData.DestData;
|
||||
const uint32 sourceWidth = copyRowData.SrcTextureWidth;
|
||||
const uint32 destWidth = copyRowData.DestTextureWidth;
|
||||
const uint32 srcRow = copyRowData.SrcRow;
|
||||
const uint32 destRow = copyRowData.DestRow;
|
||||
const uint32 padding = GetPaddingAmount();
|
||||
|
||||
const byte* sourceDataAddr = &data[(srcRow * sourceWidth) * _bytesPerPixel];
|
||||
byte* destDataAddr = &start[(destRow * destWidth + padding) * _bytesPerPixel];
|
||||
Platform::MemoryCopy(destDataAddr, sourceDataAddr, sourceWidth * _bytesPerPixel);
|
||||
|
||||
if (padding > 0)
|
||||
{
|
||||
byte* destPaddingPixelLeft = &start[(destRow * destWidth) * _bytesPerPixel];
|
||||
byte* destPaddingPixelRight = destPaddingPixelLeft + ((copyRowData.RowWidth - 1) * _bytesPerPixel);
|
||||
if (_paddingStyle == DilateBorder)
|
||||
{
|
||||
const byte* firstPixel = sourceDataAddr;
|
||||
const byte* lastPixel = sourceDataAddr + ((sourceWidth - 1) * _bytesPerPixel);
|
||||
Platform::MemoryCopy(destPaddingPixelLeft, firstPixel, _bytesPerPixel);
|
||||
Platform::MemoryCopy(destPaddingPixelRight, lastPixel, _bytesPerPixel);
|
||||
}
|
||||
else
|
||||
{
|
||||
Platform::MemoryClear(destPaddingPixelLeft, _bytesPerPixel);
|
||||
Platform::MemoryClear(destPaddingPixelRight, _bytesPerPixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FontTextureAtlas::zeroRow(const CopyRowData& copyRowData) const
|
||||
{
|
||||
const uint32 destWidth = copyRowData.DestTextureWidth;
|
||||
const uint32 destRow = copyRowData.DestRow;
|
||||
byte* destData = ©RowData.DestData[destRow * destWidth * _bytesPerPixel];
|
||||
Platform::MemoryClear(destData, copyRowData.RowWidth * _bytesPerPixel);
|
||||
}
|
||||
|
||||
void FontTextureAtlas::unload(bool isReloading)
|
||||
{
|
||||
Texture::unload(isReloading);
|
||||
|
||||
Clear();
|
||||
_data.Clear();
|
||||
}
|
||||
|
||||
void FontTextureAtlas::Clear()
|
||||
{
|
||||
SAFE_DELETE(_root);
|
||||
_freeSlots.Clear();
|
||||
}
|
||||
|
||||
void FontTextureAtlas::Flush()
|
||||
{
|
||||
if (_isDirty)
|
||||
{
|
||||
EnsureTextureCreated();
|
||||
|
||||
// Upload data to the GPU
|
||||
BytesContainer data;
|
||||
data.Link(_data);
|
||||
_texture->UploadMipMapAsync(data, 0)->Start();
|
||||
|
||||
// Clear dirty flag
|
||||
_isDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FontTextureAtlas::EnsureTextureCreated() const
|
||||
{
|
||||
if (_texture->IsAllocated() == false)
|
||||
{
|
||||
// Initialize atlas texture
|
||||
if (_texture->Init(GPUTextureDescription::New2D(_width, _height, 1, _format, GPUTextureFlags::ShaderResource)))
|
||||
{
|
||||
LOG(Warning, "Cannot initialize font atlas texture.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FontTextureAtlas::HasDataSyncWithGPU() const
|
||||
{
|
||||
return _isDirty == false;
|
||||
}
|
||||
|
||||
FontTextureAtlas::Slot* FontTextureAtlas::invalidate(Slot* parent, uint32 x, uint32 y, uint32 width, uint32 height)
|
||||
{
|
||||
if (parent->X == x && parent->Y == y && parent->Width == width && parent->Height == height)
|
||||
{
|
||||
return parent;
|
||||
}
|
||||
Slot* result = parent->Left ? invalidate(parent->Left, x, y, width, height) : nullptr;
|
||||
if (result)
|
||||
return result;
|
||||
return parent->Right ? invalidate(parent->Right, x, y, width, height) : nullptr;
|
||||
}
|
||||
|
||||
void FontTextureAtlas::markAsDirty()
|
||||
{
|
||||
_isDirty = true;
|
||||
}
|
||||
236
Source/Engine/Render2D/FontTextureAtlas.h
Normal file
236
Source/Engine/Render2D/FontTextureAtlas.h
Normal file
@@ -0,0 +1,236 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Content/Assets/Texture.h"
|
||||
#include "Engine/Graphics/Textures/GPUTexture.h"
|
||||
#include "Engine/Utilities/RectPack.h"
|
||||
|
||||
/// <summary>
|
||||
/// Texture resource that contains an atlas of cached font glyphs.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API FontTextureAtlas : public Texture
|
||||
{
|
||||
DECLARE_BINARY_ASSET_HEADER(FontTextureAtlas, TexturesSerializedVersion);
|
||||
private:
|
||||
|
||||
struct CopyRowData
|
||||
{
|
||||
// Source data to copy
|
||||
const byte* SrcData;
|
||||
|
||||
// Place to copy data to
|
||||
uint8* DestData;
|
||||
|
||||
// The row number to copy
|
||||
uint32 SrcRow;
|
||||
|
||||
// The row number to copy to
|
||||
uint32 DestRow;
|
||||
|
||||
// The width of a source row
|
||||
uint32 RowWidth;
|
||||
|
||||
// The width of the source texture
|
||||
uint32 SrcTextureWidth;
|
||||
|
||||
// The width of the dest texture
|
||||
uint32 DestTextureWidth;
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about single texture atlas slot.
|
||||
/// </summary>
|
||||
struct Slot : RectPack<Slot>
|
||||
{
|
||||
Slot(uint32 x, uint32 y, uint32 width, uint32 height)
|
||||
: RectPack<Slot>(x, y, width, height)
|
||||
{
|
||||
}
|
||||
|
||||
void OnInsert()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Describes how to handle texture atlas padding
|
||||
/// </summary>
|
||||
enum PaddingStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't pad the atlas.
|
||||
/// </summary>
|
||||
NoPadding,
|
||||
|
||||
/// <summary>
|
||||
/// Dilate the texture by one pixel to pad the atlas.
|
||||
/// </summary>
|
||||
DilateBorder,
|
||||
|
||||
/// <summary>
|
||||
/// One pixel uniform padding border filled with zeros.
|
||||
/// </summary>
|
||||
PadWithZero,
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
Array<byte> _data;
|
||||
uint32 _width;
|
||||
uint32 _height;
|
||||
PixelFormat _format;
|
||||
uint32 _bytesPerPixel;
|
||||
PaddingStyle _paddingStyle;
|
||||
bool _isDirty;
|
||||
Slot* _root;
|
||||
Array<Slot*> _freeSlots;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FontTextureAtlas"/> class.
|
||||
/// </summary>
|
||||
/// <param name="format">The texture pixels format.</param>
|
||||
/// <param name="paddingStyle">The texture entries padding style.</param>
|
||||
/// <param name="index">The atlas index.</param>
|
||||
FontTextureAtlas(PixelFormat format, PaddingStyle paddingStyle, int32 index);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the atlas width.
|
||||
/// </summary>
|
||||
/// <returns>The width.</returns>
|
||||
FORCE_INLINE uint32 GetWidth() const
|
||||
{
|
||||
return _width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the atlas height.
|
||||
/// </summary>
|
||||
/// <returns>The height.</returns>
|
||||
FORCE_INLINE uint32 GetHeight() const
|
||||
{
|
||||
return _height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the atlas size.
|
||||
/// </summary>
|
||||
/// <returns>The size.</returns>
|
||||
FORCE_INLINE Vector2 GetSize() const
|
||||
{
|
||||
return Vector2(static_cast<float>(_width), static_cast<float>(_height));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this atlas is dirty and data need to be flushed.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if this atlas is dirty; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
FORCE_INLINE bool IsDirty() const
|
||||
{
|
||||
return _isDirty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets padding style for textures in the atlas.
|
||||
/// </summary>
|
||||
/// <returns>The padding style.</returns>
|
||||
FORCE_INLINE PaddingStyle GetPaddingStyle() const
|
||||
{
|
||||
return _paddingStyle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets amount of pixels to pad textures inside an atlas.
|
||||
/// </summary>
|
||||
/// <returns>The padding amount.</returns>
|
||||
uint32 GetPaddingAmount() const;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Setups the atlas after creation.
|
||||
/// </summary>
|
||||
/// <param name="format">The pixel format.</param>
|
||||
/// <param name="paddingStyle">The padding style.</param>
|
||||
void Setup(PixelFormat format, PaddingStyle paddingStyle);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the atlas.
|
||||
/// </summary>
|
||||
/// <param name="width">The width.</param>
|
||||
/// <param name="height">The height.</param>
|
||||
void Init(uint32 width, uint32 height);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the new entry to the atlas
|
||||
/// </summary>
|
||||
/// <param name="targetWidth">Width of the entry.</param>
|
||||
/// <param name="targetHeight">Height of the entry.</param>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>The atlas slot occupied by the new entry.</returns>
|
||||
Slot* AddEntry(uint32 targetWidth, uint32 targetHeight, const Array<byte>& data);
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates the cached dynamic entry from the atlas.
|
||||
/// </summary>
|
||||
/// <param name="x">The slot location (X coordinate in atlas pixels).</param>
|
||||
/// <param name="y">The slot location (Y coordinate in atlas pixels).</param>
|
||||
/// <param name="width">The slot width (size in atlas pixels).</param>
|
||||
/// <param name="height">The slot height (size in atlas pixels).</param>
|
||||
/// <returns>True if slot has been freed, otherwise false.</returns>
|
||||
bool Invalidate(uint32 x, uint32 y, uint32 width, uint32 height);
|
||||
|
||||
/// <summary>
|
||||
/// Copies the data into the slot.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot.</param>
|
||||
/// <param name="data">The data.</param>
|
||||
void CopyDataIntoSlot(const Slot* slot, const Array<byte>& data);
|
||||
|
||||
/// <summary>
|
||||
/// Clears this atlas entries data (doesn't change size/texture etc.).
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Disposed whole atlas data (texture, nodes etc.).
|
||||
/// </summary>
|
||||
void Dispose();
|
||||
|
||||
/// <summary>
|
||||
/// Flushes this atlas data to the GPU
|
||||
/// </summary>
|
||||
void Flush();
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that texture has been created for that atlas.
|
||||
/// </summary>
|
||||
void EnsureTextureCreated() const;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether atlas has data synchronized with the GPU.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if atlas has data synchronized with the GPU; otherwise, <c>false</c>.</returns>
|
||||
bool HasDataSyncWithGPU() const;
|
||||
|
||||
private:
|
||||
|
||||
Slot* invalidate(Slot* parent, uint32 x, uint32 y, uint32 width, uint32 height);
|
||||
void markAsDirty();
|
||||
void copyRow(const CopyRowData& copyRowData) const;
|
||||
void zeroRow(const CopyRowData& copyRowData) const;
|
||||
|
||||
protected:
|
||||
|
||||
// [Texture]
|
||||
void unload(bool isReloading) override;
|
||||
};
|
||||
34
Source/Engine/Render2D/IncludeFreeType.h
Normal file
34
Source/Engine/Render2D/IncludeFreeType.h
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Include Free Type library
|
||||
// Source: https://www.freetype.org
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
// Logs the free type error
|
||||
#define LOG_FT_ERROR(error) LOG(Error, "FreeType error '{0:#x}' at {1}:{2}", error, TEXT(__FILE__), __LINE__)
|
||||
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
|
||||
// Convert the given value from 26.6 space into rounded pixel space
|
||||
template<typename ReturnType, typename InputType>
|
||||
inline ReturnType Convert26Dot6ToRoundedPixel(InputType value)
|
||||
{
|
||||
return static_cast<ReturnType>(Math::RoundToInt(value / 64.0f));
|
||||
}
|
||||
|
||||
// Convert the given value from pixel space into 26.6 space
|
||||
template<typename ReturnType, typename InputType>
|
||||
inline ReturnType ConvertPixelTo26Dot6(InputType value)
|
||||
{
|
||||
return static_cast<ReturnType>(value * 64);
|
||||
}
|
||||
|
||||
// Convert the given value from pixel space into 16.16 space
|
||||
template<typename ReturnType, typename InputType>
|
||||
inline ReturnType ConvertPixelTo16Dot16(InputType value)
|
||||
{
|
||||
return static_cast<ReturnType>(value * 65536);
|
||||
}
|
||||
20
Source/Engine/Render2D/Render2D.Build.cs
Normal file
20
Source/Engine/Render2D/Render2D.Build.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using Flax.Build;
|
||||
using Flax.Build.NativeCpp;
|
||||
|
||||
/// <summary>
|
||||
/// 2D graphics rendering module.
|
||||
/// </summary>
|
||||
public class Render2D : EngineModule
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Setup(BuildOptions options)
|
||||
{
|
||||
base.Setup(options);
|
||||
|
||||
options.PrivateDependencies.Add("freetype");
|
||||
|
||||
options.PrivateDefinitions.Add("RENDER2D_USE_LINE_AA");
|
||||
}
|
||||
}
|
||||
1734
Source/Engine/Render2D/Render2D.cpp
Normal file
1734
Source/Engine/Render2D/Render2D.cpp
Normal file
File diff suppressed because it is too large
Load Diff
211
Source/Engine/Render2D/Render2D.cs
Normal file
211
Source/Engine/Render2D/Render2D.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
partial class Render2D
|
||||
{
|
||||
/// <summary>
|
||||
/// Pushes transformation layer.
|
||||
/// </summary>
|
||||
/// <param name="transform">The transformation to apply.</param>
|
||||
public static void PushTransform(Matrix3x3 transform)
|
||||
{
|
||||
Internal_PushTransform(ref transform);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes clipping rectangle mask.
|
||||
/// </summary>
|
||||
/// <param name="clipRect">The axis aligned clipping mask rectangle.</param>
|
||||
public static void PushClip(Rectangle clipRect)
|
||||
{
|
||||
Internal_PushClip(ref clipRect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the render target.
|
||||
/// </summary>
|
||||
/// <param name="rt">The render target handle to draw.</param>
|
||||
/// <param name="rect">The rectangle to draw.</param>
|
||||
public static void DrawTexture(GPUTextureView rt, Rectangle rect)
|
||||
{
|
||||
var color = Color.White;
|
||||
Internal_DrawTexture(FlaxEngine.Object.GetUnmanagedPtr(rt), ref rect, ref color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the texture.
|
||||
/// </summary>
|
||||
/// <param name="t">The texture to draw.</param>
|
||||
/// <param name="rect">The rectangle to draw.</param>
|
||||
public static void DrawTexture(GPUTexture t, Rectangle rect)
|
||||
{
|
||||
var color = Color.White;
|
||||
Internal_DrawTexture1(FlaxEngine.Object.GetUnmanagedPtr(t), ref rect, ref color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the texture.
|
||||
/// </summary>
|
||||
/// <param name="t">The texture to draw.</param>
|
||||
/// <param name="rect">The rectangle to draw.</param>
|
||||
public static void DrawTexture(TextureBase t, Rectangle rect)
|
||||
{
|
||||
var color = Color.White;
|
||||
Internal_DrawTexture2(FlaxEngine.Object.GetUnmanagedPtr(t), ref rect, ref color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a sprite.
|
||||
/// </summary>
|
||||
/// <param name="spriteHandle">The sprite to draw.</param>
|
||||
/// <param name="rect">The rectangle to draw.</param>
|
||||
public static void DrawSprite(SpriteHandle spriteHandle, Rectangle rect)
|
||||
{
|
||||
var color = Color.White;
|
||||
Internal_DrawSprite(ref spriteHandle, ref rect, ref color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the texture (uses point sampler).
|
||||
/// </summary>
|
||||
/// <param name="t">The texture to draw.</param>
|
||||
/// <param name="rect">The rectangle to draw.</param>
|
||||
public static void DrawTexturePoint(GPUTexture t, Rectangle rect)
|
||||
{
|
||||
var color = Color.White;
|
||||
Internal_DrawTexturePoint(FlaxEngine.Object.GetUnmanagedPtr(t), ref rect, ref color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a sprite (uses point sampler).
|
||||
/// </summary>
|
||||
/// <param name="spriteHandle">The sprite to draw.</param>
|
||||
/// <param name="rect">The rectangle to draw.</param>
|
||||
public static void DrawSpritePoint(SpriteHandle spriteHandle, Rectangle rect)
|
||||
{
|
||||
var color = Color.White;
|
||||
Internal_DrawSpritePoint(ref spriteHandle, ref rect, ref color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the GUI material.
|
||||
/// </summary>
|
||||
/// <param name="material">The material to render. Must be a GUI material type.</param>
|
||||
/// <param name="rect">The target rectangle to draw.</param>
|
||||
public static void DrawMaterial(MaterialBase material, Rectangle rect)
|
||||
{
|
||||
var color = Color.White;
|
||||
Internal_DrawMaterial(FlaxEngine.Object.GetUnmanagedPtr(material), ref rect, ref color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a text.
|
||||
/// </summary>
|
||||
/// <param name="font">The font to use.</param>
|
||||
/// <param name="text">The text to render.</param>
|
||||
/// <param name="layoutRect">The size and position of the area in which the text is drawn.</param>
|
||||
/// <param name="color">The text color.</param>
|
||||
/// <param name="horizontalAlignment">The horizontal alignment of the text in a layout rectangle.</param>
|
||||
/// <param name="verticalAlignment">The vertical alignment of the text in a layout rectangle.</param>
|
||||
/// <param name="textWrapping">Describes how wrap text inside a layout rectangle.</param>
|
||||
/// <param name="baseLinesGapScale">The scale for distance one baseline from another. Default is 1.</param>
|
||||
/// <param name="scale">The text drawing scale. Default is 1.</param>
|
||||
public static void DrawText(Font font, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f)
|
||||
{
|
||||
var layout = new TextLayoutOptions
|
||||
{
|
||||
Bounds = layoutRect,
|
||||
HorizontalAlignment = horizontalAlignment,
|
||||
VerticalAlignment = verticalAlignment,
|
||||
TextWrapping = textWrapping,
|
||||
Scale = scale,
|
||||
BaseLinesGapScale = baseLinesGapScale,
|
||||
};
|
||||
DrawText(font, text, color, ref layout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a text using a custom material shader. Given material must have GUI domain and a public parameter named Font (texture parameter used for a font atlas sampling).
|
||||
/// </summary>
|
||||
/// <param name="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>
|
||||
/// <param name="text">The text to render.</param>
|
||||
/// <param name="layoutRect">The size and position of the area in which the text is drawn.</param>
|
||||
/// <param name="color">The text color.</param>
|
||||
/// <param name="horizontalAlignment">The horizontal alignment of the text in a layout rectangle.</param>
|
||||
/// <param name="verticalAlignment">The vertical alignment of the text in a layout rectangle.</param>
|
||||
/// <param name="textWrapping">Describes how wrap text inside a layout rectangle.</param>
|
||||
/// <param name="baseLinesGapScale">The scale for distance one baseline from another. Default is 1.</param>
|
||||
/// <param name="scale">The text drawing scale. Default is 1.</param>
|
||||
public static void DrawText(Font font, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f)
|
||||
{
|
||||
var layout = new TextLayoutOptions
|
||||
{
|
||||
Bounds = layoutRect,
|
||||
HorizontalAlignment = horizontalAlignment,
|
||||
VerticalAlignment = verticalAlignment,
|
||||
TextWrapping = textWrapping,
|
||||
Scale = scale,
|
||||
BaseLinesGapScale = baseLinesGapScale,
|
||||
};
|
||||
DrawText(font, text, color, ref layout, customMaterial);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls drawing GUI to the texture.
|
||||
/// </summary>
|
||||
/// <param name="drawableElement">The root container for Draw methods.</param>
|
||||
/// <param name="context">The GPU context to handle graphics commands.</param>
|
||||
/// <param name="output">The output render target.</param>
|
||||
public static void CallDrawing(IDrawable drawableElement, GPUContext context, GPUTexture output)
|
||||
{
|
||||
if (context == null || output == null || drawableElement == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
Begin(context, output);
|
||||
try
|
||||
{
|
||||
drawableElement.Draw();
|
||||
}
|
||||
finally
|
||||
{
|
||||
End();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls drawing GUI to the texture using custom View*Projection matrix.
|
||||
/// If depth buffer texture is provided there will be depth test performed during rendering.
|
||||
/// </summary>
|
||||
/// <param name="drawableElement">The root container for Draw methods.</param>
|
||||
/// <param name="context">The GPU context to handle graphics commands.</param>
|
||||
/// <param name="output">The output render target.</param>
|
||||
/// <param name="depthBuffer">The depth buffer render target. It's optional parameter but if provided must match output texture.</param>
|
||||
/// <param name="viewProjection">The View*Projection matrix used to transform all rendered vertices.</param>
|
||||
public static void CallDrawing(IDrawable drawableElement, GPUContext context, GPUTexture output, GPUTexture depthBuffer, ref Matrix viewProjection)
|
||||
{
|
||||
if (context == null || output == null || drawableElement == null)
|
||||
throw new ArgumentNullException();
|
||||
if (depthBuffer != null)
|
||||
{
|
||||
if (!depthBuffer.IsAllocated)
|
||||
throw new InvalidOperationException("Depth buffer is not allocated. Use GPUTexture.Init before rendering.");
|
||||
if (output.Size != depthBuffer.Size)
|
||||
throw new InvalidOperationException("Output buffer and depth buffer dimensions must be equal.");
|
||||
}
|
||||
|
||||
Begin(context, output, depthBuffer, ref viewProjection);
|
||||
try
|
||||
{
|
||||
drawableElement.Draw();
|
||||
}
|
||||
finally
|
||||
{
|
||||
End();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
342
Source/Engine/Render2D/Render2D.h
Normal file
342
Source/Engine/Render2D/Render2D.h
Normal file
@@ -0,0 +1,342 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Math/Color.h"
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
|
||||
struct SpriteHandle;
|
||||
struct TextLayoutOptions;
|
||||
struct Matrix;
|
||||
struct Matrix3x3;
|
||||
struct Viewport;
|
||||
struct TextRange;
|
||||
class Font;
|
||||
class GPUPipelineState;
|
||||
class GPUTexture;
|
||||
class GPUTextureView;
|
||||
class GPUContext;
|
||||
class RenderTask;
|
||||
class MaterialBase;
|
||||
class TextureBase;
|
||||
|
||||
/// <summary>
|
||||
/// Rendering 2D shapes and text using Graphics Device.
|
||||
/// </summary>
|
||||
API_CLASS(Static) class FLAXENGINE_API Render2D
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(Render2D);
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The rendering features and options flags.
|
||||
/// </summary>
|
||||
API_ENUM(Attributes="Flags") enum class RenderingFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// The none.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Enables automatic geometry vertices snapping to integer coordinates in screen space. Reduces aliasing and sampling artifacts. Might be disabled for 3D projection viewport or for complex UI transformations.
|
||||
/// </summary>
|
||||
VertexSnapping = 1,
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Checks if interface is during rendering phrase (Draw calls may be performed without failing).
|
||||
/// </summary>
|
||||
/// <returns>True if is during rendering, otherwise false/</returns>
|
||||
static bool IsRendering();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current rendering viewport.
|
||||
/// </summary>
|
||||
/// <returns>The viewport </returns>
|
||||
static const Viewport& GetViewport();
|
||||
|
||||
/// <summary>
|
||||
/// The active rendering features flags.
|
||||
/// </summary>
|
||||
API_FIELD() static RenderingFeatures Features;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Called when frame rendering begins by the graphics device.
|
||||
/// </summary>
|
||||
static void BeginFrame();
|
||||
|
||||
/// <summary>
|
||||
/// Begins the rendering phrase.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU commands context to use.</param>
|
||||
/// <param name="output">The output target.</param>
|
||||
/// <param name="depthBuffer">The depth buffer.</param>
|
||||
API_FUNCTION() static void Begin(GPUContext* context, GPUTexture* output, GPUTexture* depthBuffer = nullptr);
|
||||
|
||||
/// <summary>
|
||||
/// Begins the rendering phrase.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU commands context to use.</param>
|
||||
/// <param name="output">The output target.</param>
|
||||
/// <param name="depthBuffer">The depth buffer.</param>
|
||||
/// <param name="viewProjection">The View*Projection matrix. Allows to render GUI in 3D or with custom transformations.</param>
|
||||
API_FUNCTION() static void Begin(GPUContext* context, GPUTexture* output, GPUTexture* depthBuffer, API_PARAM(Ref) const Matrix& viewProjection);
|
||||
|
||||
/// <summary>
|
||||
/// Begins the rendering phrase.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU commands context to use.</param>
|
||||
/// <param name="output">The output target.</param>
|
||||
/// <param name="depthBuffer">The depth buffer.</param>
|
||||
/// <param name="viewport">The output viewport.</param>
|
||||
API_FUNCTION() static void Begin(GPUContext* context, GPUTextureView* output, GPUTextureView* depthBuffer, API_PARAM(Ref) const Viewport& viewport);
|
||||
|
||||
/// <summary>
|
||||
/// Begins the rendering phrase.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU commands context to use.</param>
|
||||
/// <param name="output">The output target.</param>
|
||||
/// <param name="depthBuffer">The depth buffer.</param>
|
||||
/// <param name="viewport">The output viewport.</param>
|
||||
/// <param name="viewProjection">The View*Projection matrix. Allows to render GUI in 3D or with custom transformations.</param>
|
||||
API_FUNCTION() static void Begin(GPUContext* context, GPUTextureView* output, GPUTextureView* depthBuffer, API_PARAM(Ref) const Viewport& viewport, API_PARAM(Ref) const Matrix& viewProjection);
|
||||
|
||||
/// <summary>
|
||||
/// Ends the rendering phrase.
|
||||
/// </summary>
|
||||
API_FUNCTION() static void End();
|
||||
|
||||
/// <summary>
|
||||
/// Called when frame rendering ends by the graphics device.
|
||||
/// </summary>
|
||||
static void EndFrame();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Pushes transformation layer.
|
||||
/// </summary>
|
||||
/// <param name="transform">The transformation to apply.</param>
|
||||
API_FUNCTION() static void PushTransform(API_PARAM(Ref) const Matrix3x3& transform);
|
||||
|
||||
/// <summary>
|
||||
/// Peeks the current transformation layer.
|
||||
/// </summary>
|
||||
/// <param name="transform">The output transformation to apply combined from all the transformations together (pushed into the transformation stack).</param>
|
||||
API_FUNCTION() static void PeekTransform(API_PARAM(Out) Matrix3x3& transform);
|
||||
|
||||
/// <summary>
|
||||
/// Pops transformation layer.
|
||||
/// </summary>
|
||||
API_FUNCTION() static void PopTransform();
|
||||
|
||||
/// <summary>
|
||||
/// Pushes clipping rectangle mask.
|
||||
/// </summary>
|
||||
/// <param name="clipRect">The axis aligned clipping mask rectangle.</param>
|
||||
API_FUNCTION() static void PushClip(API_PARAM(Ref) const Rectangle& clipRect);
|
||||
|
||||
/// <summary>
|
||||
/// Peeks the current clipping rectangle mask.
|
||||
/// </summary>
|
||||
/// <param name="clipRect">The output axis aligned clipping mask rectangle combined from all the masks together (pushed into the masking stack).</param>
|
||||
API_FUNCTION() static void PeekClip(API_PARAM(Out) Rectangle& clipRect);
|
||||
|
||||
/// <summary>
|
||||
/// Pops clipping rectangle mask.
|
||||
/// </summary>
|
||||
API_FUNCTION() static void PopClip();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Draws a text.
|
||||
/// </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 Vector2& location, MaterialBase* customMaterial = nullptr);
|
||||
|
||||
/// <summary>
|
||||
/// Draws a text.
|
||||
/// </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() static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Vector2& location, MaterialBase* customMaterial = nullptr);
|
||||
|
||||
/// <summary>
|
||||
/// Draws a text with formatting.
|
||||
/// </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);
|
||||
|
||||
/// <summary>
|
||||
/// Draws a text with formatting.
|
||||
/// </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() static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr);
|
||||
|
||||
/// <summary>
|
||||
/// Fills a rectangle area.
|
||||
/// </summary>
|
||||
/// <param name="rect">The rectangle to fill.</param>
|
||||
/// <param name="color">The color to use.</param>
|
||||
API_FUNCTION() static void FillRectangle(const Rectangle& rect, const Color& color);
|
||||
|
||||
/// <summary>
|
||||
/// Fills a rectangle area.
|
||||
/// </summary>
|
||||
/// <param name="rect">The rectangle to fill.</param>
|
||||
/// <param name="color1">The color to use for upper left vertex.</param>
|
||||
/// <param name="color2">The color to use for upper right vertex.</param>
|
||||
/// <param name="color3">The color to use for bottom right vertex.</param>
|
||||
/// <param name="color4">The color to use for bottom left vertex.</param>
|
||||
API_FUNCTION() static void FillRectangle(const Rectangle& rect, const Color& color1, const Color& color2, const Color& color3, const Color& color4);
|
||||
|
||||
/// <summary>
|
||||
/// Draws a rectangle borders.
|
||||
/// </summary>
|
||||
/// <param name="rect">The rectangle to draw.</param>
|
||||
/// <param name="color">The color to use.</param>
|
||||
/// <param name="thickness">The line thickness.</param>
|
||||
API_FUNCTION() FORCE_INLINE static void DrawRectangle(const Rectangle& rect, const Color& color, float thickness = 1.0f)
|
||||
{
|
||||
DrawRectangle(rect, color, color, color, color, thickness);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a rectangle borders.
|
||||
/// </summary>
|
||||
/// <param name="rect">The rectangle to fill.</param>
|
||||
/// <param name="color1">The color to use for upper left vertex.</param>
|
||||
/// <param name="color2">The color to use for upper right vertex.</param>
|
||||
/// <param name="color3">The color to use for bottom right vertex.</param>
|
||||
/// <param name="color4">The color to use for bottom left vertex.</param>
|
||||
/// <param name="thickness">The line thickness.</param>
|
||||
API_FUNCTION() static void DrawRectangle(const Rectangle& rect, const Color& color1, const Color& color2, const Color& color3, const Color& color4, float thickness = 1.0f);
|
||||
|
||||
/// <summary>
|
||||
/// Draws the render target.
|
||||
/// </summary>
|
||||
/// <param name="rt">The render target handle to draw.</param>
|
||||
/// <param name="rect">The rectangle to draw.</param>
|
||||
/// <param name="color">The color to multiply all texture pixels.</param>
|
||||
API_FUNCTION() static void DrawTexture(GPUTextureView* rt, const Rectangle& rect, const Color& color = Color::White);
|
||||
|
||||
/// <summary>
|
||||
/// Draws the texture.
|
||||
/// </summary>
|
||||
/// <param name="t">The texture to draw.</param>
|
||||
/// <param name="rect">The rectangle to draw.</param>
|
||||
/// <param name="color">The color to multiply all texture pixels.</param>
|
||||
API_FUNCTION() static void DrawTexture(GPUTexture* t, const Rectangle& rect, const Color& color = Color::White);
|
||||
|
||||
/// <summary>
|
||||
/// Draws the texture.
|
||||
/// </summary>
|
||||
/// <param name="t">The texture to draw.</param>
|
||||
/// <param name="rect">The rectangle to draw.</param>
|
||||
/// <param name="color">The color to multiply all texture pixels.</param>
|
||||
API_FUNCTION() static void DrawTexture(TextureBase* t, const Rectangle& rect, const Color& color = Color::White);
|
||||
|
||||
/// <summary>
|
||||
/// Draws a sprite.
|
||||
/// </summary>
|
||||
/// <param name="spriteHandle">The sprite to draw.</param>
|
||||
/// <param name="rect">The rectangle to draw.</param>
|
||||
/// <param name="color">The color to multiply all texture pixels.</param>
|
||||
API_FUNCTION() static void DrawSprite(const SpriteHandle& spriteHandle, const Rectangle& rect, const Color& color = Color::White);
|
||||
|
||||
/// <summary>
|
||||
/// Draws the texture (uses point sampler).
|
||||
/// </summary>
|
||||
/// <param name="t">The texture to draw.</param>
|
||||
/// <param name="rect">The rectangle to draw.</param>
|
||||
/// <param name="color">The color to multiply all texture pixels.</param>
|
||||
API_FUNCTION() static void DrawTexturePoint(GPUTexture* t, const Rectangle& rect, const Color& color = Color::White);
|
||||
|
||||
/// <summary>
|
||||
/// Draws a sprite (uses point sampler).
|
||||
/// </summary>
|
||||
/// <param name="spriteHandle">The sprite to draw.</param>
|
||||
/// <param name="rect">The rectangle to draw.</param>
|
||||
/// <param name="color">The color to multiply all texture pixels.</param>
|
||||
API_FUNCTION() static void DrawSpritePoint(const SpriteHandle& spriteHandle, const Rectangle& rect, const Color& color = Color::White);
|
||||
|
||||
/// <summary>
|
||||
/// Performs custom rendering.
|
||||
/// </summary>
|
||||
/// <param name="t">The texture to use.</param>
|
||||
/// <param name="rect">The rectangle area to draw.</param>
|
||||
/// <param name="ps">The custom pipeline state to use (input must match default Render2D vertex shader and can use single texture).</param>
|
||||
/// <param name="color">The color to multiply all texture pixels.</param>
|
||||
API_FUNCTION() static void DrawCustom(GPUTexture* t, const Rectangle& rect, GPUPipelineState* ps, const Color& color = Color::White);
|
||||
|
||||
/// <summary>
|
||||
/// Draws a line.
|
||||
/// </summary>
|
||||
/// <param name="p1">The start point.</param>
|
||||
/// <param name="p2">The end point.</param>
|
||||
/// <param name="color">The line color.</param>
|
||||
/// <param name="thickness">The line thickness.</param>
|
||||
API_FUNCTION() FORCE_INLINE static void DrawLine(const Vector2& p1, const Vector2& p2, const Color& color, float thickness = 1.0f)
|
||||
{
|
||||
DrawLine(p1, p2, color, color, thickness);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a line.
|
||||
/// </summary>
|
||||
/// <param name="p1">The start point.</param>
|
||||
/// <param name="p2">The end point.</param>
|
||||
/// <param name="color1">The line start color.</param>
|
||||
/// <param name="color2">The line end color.</param>
|
||||
/// <param name="thickness">The line thickness.</param>
|
||||
API_FUNCTION() static void DrawLine(const Vector2& p1, const Vector2& p2, const Color& color1, const Color& color2, float thickness = 1.0f);
|
||||
|
||||
/// <summary>
|
||||
/// Draws a Bezier curve.
|
||||
/// </summary>
|
||||
/// <param name="p1">The start point.</param>
|
||||
/// <param name="p2">The first control point.</param>
|
||||
/// <param name="p3">The second control point.</param>
|
||||
/// <param name="p4">The end point.</param>
|
||||
/// <param name="color">The line color</param>
|
||||
/// <param name="thickness">The line thickness.</param>
|
||||
API_FUNCTION() static void DrawBezier(const Vector2& p1, const Vector2& p2, const Vector2& p3, const Vector2& p4, const Color& color, float thickness = 1.0f);
|
||||
|
||||
/// <summary>
|
||||
/// Draws the GUI material.
|
||||
/// </summary>
|
||||
/// <param name="material">The material to render. Must be a GUI material type.</param>
|
||||
/// <param name="rect">The target rectangle to draw.</param>
|
||||
/// <param name="color">The color to use.</param>
|
||||
API_FUNCTION() static void DrawMaterial(MaterialBase* material, const Rectangle& rect, const Color& color);
|
||||
|
||||
/// <summary>
|
||||
/// Draws the background blur.
|
||||
/// </summary>
|
||||
/// <param name="rect">The target rectangle to draw (blurs its background).</param>
|
||||
/// <param name="blurStrength">The blur strength defines how blurry the background is. Larger numbers increase blur, resulting in a larger runtime cost on the GPU.</param>
|
||||
API_FUNCTION() static void DrawBlur(const Rectangle& rect, float blurStrength);
|
||||
};
|
||||
113
Source/Engine/Render2D/RotatedRectangle.h
Normal file
113
Source/Engine/Render2D/RotatedRectangle.h
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Math/Rectangle.h"
|
||||
|
||||
/// <summary>
|
||||
/// Represents a rectangle that has been transformed by an arbitrary render transform.
|
||||
/// </summary>
|
||||
struct RotatedRectangle
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The transformed top left corner.
|
||||
/// </summary>
|
||||
Vector2 TopLeft;
|
||||
|
||||
/// <summary>
|
||||
/// The transformed X extent (right-left).
|
||||
/// </summary>
|
||||
Vector2 ExtentX;
|
||||
|
||||
/// <summary>
|
||||
/// The transformed Y extent (bottom-top).
|
||||
/// </summary>
|
||||
Vector2 ExtentY;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RotatedRectangle"/> struct.
|
||||
/// </summary>
|
||||
RotatedRectangle()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RotatedRectangle"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="rect">The aligned rectangle.</param>
|
||||
explicit RotatedRectangle(const Rectangle& rect)
|
||||
: TopLeft(rect.GetUpperLeft())
|
||||
, ExtentX(rect.GetWidth(), 0.0f)
|
||||
, ExtentY(0.0f, rect.GetHeight())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RotatedRectangle"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="topLeft">The top left corner.</param>
|
||||
/// <param name="extentX">The extent on X axis.</param>
|
||||
/// <param name="extentY">The extent on Y axis.</param>
|
||||
RotatedRectangle(const Vector2& topLeft, const Vector2& extentX, const Vector2& extentY)
|
||||
: TopLeft(topLeft)
|
||||
, ExtentX(extentX)
|
||||
, ExtentY(extentY)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Convert rotated rectangle to the axis-aligned rectangle that builds rotated rectangle bounding box.
|
||||
/// </summary>
|
||||
/// <returns>The bounds rectangle.</returns>
|
||||
Rectangle ToBoundingRect() const
|
||||
{
|
||||
Vector2 points[4] =
|
||||
{
|
||||
TopLeft,
|
||||
TopLeft + ExtentX,
|
||||
TopLeft + ExtentY,
|
||||
TopLeft + ExtentX + ExtentY
|
||||
};
|
||||
return Rectangle::FromPoints(points, 4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified location is contained within this rotated rectangle.
|
||||
/// </summary>
|
||||
/// <param name="location">The location to test.</param>
|
||||
/// <returns><c>true</c> if the specified location is contained by this rotated rectangle; otherwise, <c>false</c>.</returns>
|
||||
bool ContainsPoint(const Vector2& location) const
|
||||
{
|
||||
const Vector2 offset = location - TopLeft;
|
||||
const float det = Vector2::Cross(ExtentX, ExtentY);
|
||||
const float s = Vector2::Cross(offset, ExtentX) / -det;
|
||||
if (Math::IsInRange(s, 0.0f, 1.0f))
|
||||
{
|
||||
const float t = Vector2::Cross(offset, ExtentY) / det;
|
||||
return Math::IsInRange(t, 0.0f, 1.0f);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
bool operator ==(const RotatedRectangle& other) const
|
||||
{
|
||||
return
|
||||
TopLeft == other.TopLeft &&
|
||||
ExtentX == other.ExtentX &&
|
||||
ExtentY == other.ExtentY;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TIsPODType<RotatedRectangle>
|
||||
{
|
||||
enum { Value = true };
|
||||
};
|
||||
191
Source/Engine/Render2D/SpriteAtlas.cpp
Normal file
191
Source/Engine/Render2D/SpriteAtlas.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "SpriteAtlas.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Content/Loading/Tasks/LoadAssetDataTask.h"
|
||||
#include "Engine/Content/Upgraders/TextureAssetUpgrader.h"
|
||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
|
||||
const SpriteHandle SpriteHandle::Invalid = { nullptr, INVALID_INDEX };
|
||||
|
||||
REGISTER_BINARY_ASSET(SpriteAtlas, "FlaxEngine.SpriteAtlas", ::New<TextureAssetUpgrader>(), true);
|
||||
|
||||
bool SpriteHandle::GetSprite(Sprite* result) const
|
||||
{
|
||||
if (IsValid())
|
||||
{
|
||||
*result = Atlas->Sprites[Index];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SpriteHandle::IsValid() const
|
||||
{
|
||||
return Atlas && Index != INVALID_INDEX && Atlas->Sprites.Count() > Index;
|
||||
}
|
||||
|
||||
GPUTexture* SpriteHandle::GetAtlasTexture() const
|
||||
{
|
||||
ASSERT(Atlas);
|
||||
return Atlas->GetTexture();
|
||||
}
|
||||
|
||||
SpriteAtlas::SpriteAtlas(const SpawnParams& params, const AssetInfo* info)
|
||||
: TextureBase(params, info)
|
||||
{
|
||||
}
|
||||
|
||||
SpriteHandle SpriteAtlas::FindSprite(const StringView& name) const
|
||||
{
|
||||
SpriteHandle result(const_cast<SpriteAtlas*>(this), -1);
|
||||
|
||||
for (int32 i = 0; i < Sprites.Count(); i++)
|
||||
{
|
||||
if (name == Sprites[i].Name)
|
||||
{
|
||||
result.Index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SpriteHandle SpriteAtlas::AddSprite(const Sprite& sprite)
|
||||
{
|
||||
const int32 index = Sprites.Count();
|
||||
|
||||
Sprites.Add(sprite);
|
||||
|
||||
return SpriteHandle(this, index);
|
||||
}
|
||||
|
||||
void SpriteAtlas::RemoveSprite(int32 index)
|
||||
{
|
||||
Sprites.RemoveAt(index);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool SpriteAtlas::SaveSprites()
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Load whole asset
|
||||
if (LoadChunks(ALL_ASSET_CHUNKS))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prepare asset data
|
||||
AssetInitData data;
|
||||
data.SerializedVersion = 4;
|
||||
data.CustomData.Copy(_texture.GetHeader());
|
||||
|
||||
// Write sprites data
|
||||
MemoryWriteStream stream(1024);
|
||||
stream.WriteInt32(1); // Version
|
||||
stream.WriteInt32(Sprites.Count()); // Sprites Count
|
||||
for (int32 i = 0; i < Sprites.Count(); i++)
|
||||
{
|
||||
// Save sprite
|
||||
Sprite t = Sprites[i];
|
||||
stream.Write(&t.Area);
|
||||
stream.WriteString(t.Name, 49);
|
||||
}
|
||||
|
||||
// Link sprites data (unlink after safe)
|
||||
auto dataChunk = GetOrCreateChunk(15);
|
||||
dataChunk->Data.Link(stream.GetHandle(), stream.GetLength());
|
||||
|
||||
// Save (use silent mode to prevent asset reloading)
|
||||
bool saveResult = SaveAsset(data, true);
|
||||
dataChunk->Data.Release();
|
||||
if (saveResult)
|
||||
{
|
||||
LOG(Warning, "Failed to save sprite atlas \'{0}\'.", GetPath());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool SpriteAtlas::LoadSprites(ReadStream& stream)
|
||||
{
|
||||
#if USE_EDITOR
|
||||
// Sprites may be used on rendering thread so lock drawing for a while
|
||||
if (GPUDevice::Instance)
|
||||
GPUDevice::Instance->Locker.Lock();
|
||||
#endif
|
||||
|
||||
// Cleanup first
|
||||
Sprites.Clear();
|
||||
|
||||
// Load tiles data
|
||||
int32 tilesVersion, tilesCount;
|
||||
stream.ReadInt32(&tilesVersion);
|
||||
if (tilesVersion != 1)
|
||||
{
|
||||
#if USE_EDITOR
|
||||
if (GPUDevice::Instance)
|
||||
GPUDevice::Instance->Locker.Unlock();
|
||||
#endif
|
||||
LOG(Warning, "Invalid tiles version.");
|
||||
return true;
|
||||
}
|
||||
stream.ReadInt32(&tilesCount);
|
||||
Sprites.Resize(tilesCount);
|
||||
for (int32 i = 0; i < tilesCount; i++)
|
||||
{
|
||||
// Load sprite
|
||||
Sprite& t = Sprites[i];
|
||||
stream.Read(&t.Area);
|
||||
stream.ReadString(&t.Name, 49);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
if (GPUDevice::Instance)
|
||||
GPUDevice::Instance->Locker.Unlock();
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
Asset::LoadResult SpriteAtlas::load()
|
||||
{
|
||||
// Get sprites data
|
||||
auto spritesDataChunk = GetChunk(15);
|
||||
if (spritesDataChunk == nullptr || spritesDataChunk->IsMissing())
|
||||
return LoadResult::MissingDataChunk;
|
||||
MemoryReadStream spritesData(spritesDataChunk->Get(), spritesDataChunk->Size());
|
||||
|
||||
// Load sprites
|
||||
if (LoadSprites(spritesData))
|
||||
{
|
||||
LOG(Warning, "Cannot load sprites atlas data.");
|
||||
return LoadResult::Failed;
|
||||
}
|
||||
|
||||
return TextureBase::load();
|
||||
}
|
||||
|
||||
void SpriteAtlas::unload(bool isReloading)
|
||||
{
|
||||
// Release sprites
|
||||
Sprites.Resize(0);
|
||||
|
||||
// Base
|
||||
TextureBase::unload(isReloading);
|
||||
}
|
||||
|
||||
AssetChunksFlag SpriteAtlas::getChunksToPreload() const
|
||||
{
|
||||
return GET_CHUNK_FLAG(15);
|
||||
}
|
||||
104
Source/Engine/Render2D/SpriteAtlas.cs
Normal file
104
Source/Engine/Render2D/SpriteAtlas.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
partial struct SpriteHandle
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid sprite handle.
|
||||
/// </summary>
|
||||
public static SpriteHandle Invalid;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Sprite"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="atlas">The atlas.</param>
|
||||
/// <param name="index">The index.</param>
|
||||
public SpriteHandle(SpriteAtlas atlas, int index)
|
||||
{
|
||||
Atlas = atlas;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if sprite is valid.
|
||||
/// </summary>
|
||||
public bool IsValid => Atlas != null && Index != -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sprite name.
|
||||
/// </summary>
|
||||
[NoSerialize]
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Atlas == null)
|
||||
throw new InvalidOperationException("Cannot use invalid sprite.");
|
||||
return Atlas.GetSprite(Index).Name;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (Atlas == null)
|
||||
throw new InvalidOperationException("Cannot use invalid sprite.");
|
||||
var sprite = Atlas.GetSprite(Index);
|
||||
sprite.Name = value;
|
||||
Atlas.SetSprite(Index, ref sprite);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sprite location (in pixels).
|
||||
/// </summary>
|
||||
[NoSerialize]
|
||||
public Vector2 Location
|
||||
{
|
||||
get => Area.Location * Atlas.Size;
|
||||
set
|
||||
{
|
||||
var area = Area;
|
||||
area.Location = value / Atlas.Size;
|
||||
Area = area;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sprite size (in pixels).
|
||||
/// </summary>
|
||||
[NoSerialize]
|
||||
public Vector2 Size
|
||||
{
|
||||
get => Area.Size * Atlas.Size;
|
||||
set
|
||||
{
|
||||
var area = Area;
|
||||
area.Size = value / Atlas.Size;
|
||||
Area = area;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sprite area in atlas (in normalized atlas coordinates [0;1]).
|
||||
/// </summary>
|
||||
[NoSerialize]
|
||||
public Rectangle Area
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Atlas == null)
|
||||
throw new InvalidOperationException("Cannot use invalid sprite.");
|
||||
return Atlas.GetSprite(Index).Area;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (Atlas == null)
|
||||
throw new InvalidOperationException("Cannot use invalid sprite.");
|
||||
var sprite = Atlas.GetSprite(Index);
|
||||
sprite.Area = value;
|
||||
Atlas.SetSprite(Index, ref sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
179
Source/Engine/Render2D/SpriteAtlas.h
Normal file
179
Source/Engine/Render2D/SpriteAtlas.h
Normal file
@@ -0,0 +1,179 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Math/Rectangle.h"
|
||||
#include "Engine/Content/BinaryAsset.h"
|
||||
#include "Engine/Graphics/Textures/TextureBase.h"
|
||||
|
||||
class SpriteAtlas;
|
||||
class GPUTexture;
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about single atlas slot with sprite texture.
|
||||
/// </summary>
|
||||
API_STRUCT() struct Sprite
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(Sprite);
|
||||
|
||||
/// <summary>
|
||||
/// The normalized area of the sprite in the atlas (in range [0;1]).
|
||||
/// </summary>
|
||||
API_FIELD() Rectangle Area;
|
||||
|
||||
/// <summary>
|
||||
/// The sprite name.
|
||||
/// </summary>
|
||||
API_FIELD() String Name;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handle to sprite atlas slot with a single sprite texture.
|
||||
/// </summary>
|
||||
API_STRUCT() struct FLAXENGINE_API SpriteHandle
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(SpriteHandle);
|
||||
|
||||
/// <summary>
|
||||
/// Invalid sprite handle.
|
||||
/// </summary>
|
||||
static const SpriteHandle Invalid;
|
||||
|
||||
/// <summary>
|
||||
/// The parent atlas.
|
||||
/// </summary>
|
||||
API_FIELD() SpriteAtlas* Atlas;
|
||||
|
||||
/// <summary>
|
||||
/// The atlas sprites array index.
|
||||
/// </summary>
|
||||
API_FIELD() int32 Index;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SpriteHandle"/> struct.
|
||||
/// </summary>
|
||||
SpriteHandle()
|
||||
{
|
||||
Atlas = nullptr;
|
||||
Index = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SpriteHandle"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="atlas">The sprite atlas.</param>
|
||||
/// <param name="index">The sprite slot index.</param>
|
||||
SpriteHandle(SpriteAtlas* atlas, int32 index)
|
||||
{
|
||||
Atlas = atlas;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get sprite info.
|
||||
/// </summary>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <returns>True if data is valid, otherwise false.</returns>
|
||||
bool GetSprite(Sprite* result) const;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if sprite is valid.
|
||||
/// </summary>
|
||||
/// <returns>True if this sprite handle is valid, otherwise false.</returns>
|
||||
bool IsValid() const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite atlas texture.
|
||||
/// </summary>
|
||||
/// <returns>The texture object.</returns>
|
||||
GPUTexture* GetAtlasTexture() const;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Sprite atlas asset that contains collection of sprites combined into a single texture.
|
||||
/// </summary>
|
||||
/// <seealso cref="TextureBase" />
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API SpriteAtlas : public TextureBase
|
||||
{
|
||||
DECLARE_BINARY_ASSET_HEADER(SpriteAtlas, TexturesSerializedVersion);
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// List with all tiles in the sprite atlas.
|
||||
/// </summary>
|
||||
API_FIELD() Array<Sprite> Sprites;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprites count.
|
||||
/// </summary>
|
||||
/// <returns>The sprites count.</returns>
|
||||
API_PROPERTY() int32 GetSpritesCount() const
|
||||
{
|
||||
return Sprites.Count();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite data.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>The sprite data.</returns>
|
||||
API_FUNCTION() Sprite GetSprite(const int32 index) const
|
||||
{
|
||||
return Sprites[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the sprite data.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <param name="value">The sprite data.</param>
|
||||
/// <returns>The sprite handle.</returns>
|
||||
API_FUNCTION() void SetSprite(const int32 index, API_PARAM(Ref) const Sprite& value)
|
||||
{
|
||||
Sprites[index] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the sprite by the name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>The sprite handle.</returns>
|
||||
API_FUNCTION() SpriteHandle FindSprite(const StringView& name) const;
|
||||
|
||||
/// <summary>
|
||||
/// Adds the sprite.
|
||||
/// </summary>
|
||||
/// <param name="sprite">The sprite.</param>
|
||||
/// <returns>The sprite handle.</returns>
|
||||
API_FUNCTION() SpriteHandle AddSprite(const Sprite& sprite);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the sprite.
|
||||
/// </summary>
|
||||
/// <param name="index">The sprite index.</param>
|
||||
API_FUNCTION() void RemoveSprite(int32 index);
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
/// <summary>
|
||||
/// Save the sprites (texture content won't be modified).
|
||||
/// </summary>
|
||||
/// <returns>True if cannot save, otherwise false.</returns>
|
||||
API_FUNCTION() bool SaveSprites();
|
||||
|
||||
#endif
|
||||
|
||||
protected:
|
||||
|
||||
bool LoadSprites(ReadStream& stream);
|
||||
|
||||
protected:
|
||||
|
||||
// [BinaryAsset]
|
||||
LoadResult load() override;
|
||||
void unload(bool isReloading) override;
|
||||
AssetChunksFlag getChunksToPreload() const override;
|
||||
};
|
||||
17
Source/Engine/Render2D/TextLayoutOptions.cs
Normal file
17
Source/Engine/Render2D/TextLayoutOptions.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
partial struct TextLayoutOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the default layout.
|
||||
/// </summary>
|
||||
public static TextLayoutOptions Default => new TextLayoutOptions
|
||||
{
|
||||
Bounds = new Rectangle(0, 0, float.MaxValue, float.MaxValue),
|
||||
Scale = 1.0f,
|
||||
BaseLinesGapScale = 1.0f,
|
||||
};
|
||||
}
|
||||
}
|
||||
119
Source/Engine/Render2D/TextLayoutOptions.h
Normal file
119
Source/Engine/Render2D/TextLayoutOptions.h
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Math/Rectangle.h"
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the alignment of the text along horizontal or vertical direction in the layout box.
|
||||
/// </summary>
|
||||
API_ENUM() enum class TextAlignment
|
||||
{
|
||||
/// <summary>
|
||||
/// Align text near the edge.
|
||||
/// </summary>
|
||||
Near = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Align text to the center.
|
||||
/// </summary>
|
||||
Center,
|
||||
|
||||
/// <summary>
|
||||
/// Align text to the far edge.
|
||||
/// </summary>
|
||||
Far,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Specifies text wrapping to be used in a particular multiline paragraph.
|
||||
/// </summary>
|
||||
API_ENUM() enum class TextWrapping
|
||||
{
|
||||
/// <summary>
|
||||
/// No text wrapping.
|
||||
/// </summary>
|
||||
NoWrap = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Wrap only whole words that overflow.
|
||||
/// </summary>
|
||||
WrapWords,
|
||||
|
||||
/// <summary>
|
||||
/// Wrap single characters that overflow.
|
||||
/// </summary>
|
||||
WrapChars,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Structure which describes text layout properties.
|
||||
/// </summary>
|
||||
API_STRUCT() struct TextLayoutOptions
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(TextLayoutOptions);
|
||||
|
||||
/// <summary>
|
||||
/// The layout rectangle (text bounds).
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(0)")
|
||||
Rectangle Bounds;
|
||||
|
||||
/// <summary>
|
||||
/// The horizontal alignment mode.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(10)")
|
||||
TextAlignment HorizontalAlignment;
|
||||
|
||||
/// <summary>
|
||||
/// The vertical alignment mode.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(20)")
|
||||
TextAlignment VerticalAlignment;
|
||||
|
||||
/// <summary>
|
||||
/// The text wrapping mode.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(30), DefaultValue(TextWrapping.NoWrap)")
|
||||
TextWrapping TextWrapping;
|
||||
|
||||
/// <summary>
|
||||
/// The text scale factor. Default is 1.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(40), DefaultValue(1.0f), Limit(-1000, 1000, 0.01f)")
|
||||
float Scale;
|
||||
|
||||
/// <summary>
|
||||
/// Base line gap scale. Default is 1.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(50), DefaultValue(1.0f), Limit(-1000, 1000, 0.01f)")
|
||||
float BaseLinesGapScale;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextLayoutOptions"/> struct.
|
||||
/// </summary>
|
||||
TextLayoutOptions()
|
||||
{
|
||||
Bounds = Rectangle(0, 0, MAX_float, MAX_float);
|
||||
HorizontalAlignment = TextAlignment::Near;
|
||||
VerticalAlignment = TextAlignment::Near;
|
||||
TextWrapping = TextWrapping::NoWrap;
|
||||
Scale = 1.0f;
|
||||
BaseLinesGapScale = 1.0f;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator==(const TextLayoutOptions& other) const
|
||||
{
|
||||
return Bounds == other.Bounds
|
||||
&& HorizontalAlignment == other.HorizontalAlignment
|
||||
&& VerticalAlignment == other.VerticalAlignment
|
||||
&& TextWrapping == other.TextWrapping
|
||||
&& Math::NearEqual(Scale, other.Scale)
|
||||
&& Math::NearEqual(BaseLinesGapScale, other.BaseLinesGapScale);
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator!=(const TextLayoutOptions& other) const
|
||||
{
|
||||
return !operator==(other);
|
||||
}
|
||||
};
|
||||
50
Source/Engine/Render2D/TextRange.cs
Normal file
50
Source/Engine/Render2D/TextRange.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
partial struct TextRange
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the range length.
|
||||
/// </summary>
|
||||
public int Length => EndIndex - StartIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextRange"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="startIndex">The start index.</param>
|
||||
/// <param name="endIndex">The end index.</param>
|
||||
public TextRange(int startIndex, int endIndex)
|
||||
{
|
||||
StartIndex = startIndex;
|
||||
EndIndex = endIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether range is empty.
|
||||
/// </summary>
|
||||
public bool IsEmpty => (EndIndex - StartIndex) <= 0;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this range contains the character index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns><c>true</c> if range contains the specified character index; otherwise, <c>false</c>.</returns>
|
||||
public bool Contains(int index) => index >= StartIndex && index < EndIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this range intersects with the other range.
|
||||
/// </summary>
|
||||
/// <param name="other">The other text range.</param>
|
||||
/// <returns><c>true</c> if range intersects with the specified range index;, <c>false</c>.</returns>
|
||||
public bool Intersect(ref TextRange other) => Math.Min(EndIndex, other.EndIndex) > Math.Max(StartIndex, other.StartIndex);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Range: [{0}, {1}), Length: {2}", StartIndex, EndIndex, Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user