feat: add text rendering with SDF's
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -33,6 +34,7 @@
|
||||
#include <destrum/Components/CameraComponent.h>
|
||||
#include <destrum/Graphics/Resources/PrimitiveMeshes.h>
|
||||
#include <destrum/Graphics/Managers/Renderer2D.h>
|
||||
#include <destrum/Graphics/Resources/Font.h>
|
||||
#include <destrum/Components/LightComponent.h>
|
||||
#include <destrum/Components/Physics/Rigidbody.h>
|
||||
#include <destrum/Graphics/Material.h>
|
||||
@@ -478,6 +480,52 @@ namespace {
|
||||
check(r2d.GetShapes().empty(), "clear must drop all submitted shapes");
|
||||
}
|
||||
|
||||
void testFontAtlasAndLayout()
|
||||
{
|
||||
const std::filesystem::path fontPath =
|
||||
std::filesystem::path{DESTRUM_ENGINE_ASSETS_SRC_DIR} / "fonts" / "Roboto-Medium.ttf";
|
||||
const Font font = Font::LoadFromFile(fontPath, 32.0f);
|
||||
|
||||
const FontGlyph* a = font.GetGlyph(U'A');
|
||||
const FontGlyph* space = font.GetGlyph(U' ');
|
||||
check(a != nullptr && a->size.x > 0.0f && a->size.y > 0.0f && a->advance > 0.0f &&
|
||||
a->uvMax.x > a->uvMin.x && a->uvMax.y > a->uvMin.y,
|
||||
"printable glyphs must be rasterized into the atlas");
|
||||
check(space != nullptr && space->size == glm::vec2{0.0f} && space->advance > 0.0f,
|
||||
"spaces must only advance the pen");
|
||||
check(font.GetGlyph(U'中') == font.GetGlyph(U'?'), "missing glyphs must fall back to '?'");
|
||||
|
||||
const auto& pixels = font.GetAtlasPixels();
|
||||
const auto [minIt, maxIt] = std::minmax_element(pixels.begin(), pixels.end());
|
||||
check(!pixels.empty() && *minIt < 128 && *maxIt > 128,
|
||||
"the atlas must hold a signed distance field with the edge at 128");
|
||||
|
||||
const glm::vec2 one = font.MeasureText("A", 64.0f);
|
||||
const glm::vec2 two = font.MeasureText("A\nAA", 64.0f);
|
||||
check(close(one.x, a->advance * 2.0f) && close(one.y, font.GetLineHeight() * 2.0f),
|
||||
"text must be measured at the requested size");
|
||||
check(close(two.x, (a->advance * 2.0f + font.GetKerning(U'A', U'A')) * 2.0f) &&
|
||||
close(two.y, one.y * 2.0f),
|
||||
"multi line text must measure its widest line and every line");
|
||||
|
||||
std::vector<PlacedGlyph> placed;
|
||||
font.Layout("A b", {100.0f, 50.0f}, 32.0f, [&](const PlacedGlyph& glyph) { placed.push_back(glyph); });
|
||||
check(placed.size() == 2 && placed[0].glyph == a &&
|
||||
close(placed[0].topLeft.x, 100.0f + a->offset.x) &&
|
||||
close(placed[0].topLeft.y, 50.0f + font.GetAscender() + a->offset.y),
|
||||
"layout must skip spaces and start on the baseline below the top left");
|
||||
|
||||
const std::string_view utf8 = "\xC3\xA9\xFF";
|
||||
std::size_t index = 0;
|
||||
check(Font::DecodeUtf8(utf8, index) == U'é' && index == 2, "utf8 must decode two byte sequences");
|
||||
check(Font::DecodeUtf8(utf8, index) == 0xFFFD && index == 3, "invalid utf8 must decode to U+FFFD");
|
||||
|
||||
auto& r2d = Renderer2D::GetInstance();
|
||||
r2d.Clear();
|
||||
r2d.DrawText(font, "not uploaded", {0.0f, 0.0f}, 16.0f, glm::vec4{1.0f});
|
||||
check(r2d.GetShapes().empty(), "text must not be drawn before the font atlas is uploaded");
|
||||
}
|
||||
|
||||
void testCapsuleColliderDebugLines()
|
||||
{
|
||||
GameObject object{"capsule"};
|
||||
@@ -1179,6 +1227,7 @@ int main()
|
||||
testColliderCenterOffset();
|
||||
testCapsuleColliderDebugLines();
|
||||
testRenderer2DShapes();
|
||||
testFontAtlasAndLayout();
|
||||
std::cout << "destrum tests passed\n";
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user