From 8dc0f039f94be74a48cf08f5c16e78af8a00d18f Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 1 Mar 2025 09:15:18 +0900 Subject: [PATCH] Add a text rendering integration test This shows that we still have some issues with draw string with colors + kerning fit but that's OK as we don't use that currently. --- .gitignore | 3 + test/CMakeLists.txt | 3 + .../text_render_integration_test/expected.png | Bin 0 -> 4230 bytes test/text_render_integration_test.cpp | 136 ++++++++++++++++++ 4 files changed, 142 insertions(+) create mode 100644 test/fixtures/text_render_integration_test/expected.png create mode 100644 test/text_render_integration_test.cpp diff --git a/.gitignore b/.gitignore index 558dafacc..1c90cb068 100644 --- a/.gitignore +++ b/.gitignore @@ -465,3 +465,6 @@ uwp-project/Assets/gendata uwp-project/Assets/ui_art !uwp-project/devilutionX_TemporaryKey.pfx /.s390x-ccache/ + +# Test fixtures +/test/fixtures/text_render_integration_test/actual.png diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 544cb8415..17d1a430e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -37,6 +37,9 @@ set(tests timedemo_test writehero_test ) +if(NOT USE_SDL1) + list(APPEND tests text_render_integration_test) +endif() set(standalone_tests codec_test crawl_test diff --git a/test/fixtures/text_render_integration_test/expected.png b/test/fixtures/text_render_integration_test/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..85648074c275fa4b14fc374c2df5fc6435131b79 GIT binary patch literal 4230 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 literal 0 HcmV?d00001 diff --git a/test/text_render_integration_test.cpp b/test/text_render_integration_test.cpp new file mode 100644 index 000000000..1cd25e20f --- /dev/null +++ b/test/text_render_integration_test.cpp @@ -0,0 +1,136 @@ +#include + +#include +#include + +#include +#include +#include + +#include "engine/load_file.hpp" +#include "engine/palette.h" +#include "engine/point.hpp" +#include "engine/rectangle.hpp" +#include "engine/render/primitive_render.hpp" +#include "engine/render/text_render.hpp" +#include "engine/size.hpp" +#include "engine/surface.hpp" +#include "utils/paths.h" +#include "utils/sdl_wrap.h" +#include "utils/surface_to_png.hpp" + +namespace devilution { +namespace { + +constexpr char FixturesPath[] = "../test/fixtures/text_render_integration_test/"; + +SDLPaletteUniquePtr LoadPalette() +{ + struct Color { + uint8_t r, g, b; + }; + std::array palData; + LoadFileInMem("ui_art\\diablo.pal", palData); + SDLPaletteUniquePtr palette = SDLWrap::AllocPalette(256); + for (unsigned i = 0; i < palData.size(); i++) { + palette->colors[i] = SDL_Color { + palData[i].r, palData[i].g, palData[i].b, SDL_ALPHA_OPAQUE + }; + } + return palette; +} + +std::vector ReadFile(const std::string &path) +{ + SDL_RWops *rwops = SDL_RWFromFile(path.c_str(), "rb"); + std::vector result; + if (rwops == nullptr) return result; + const size_t size = SDL_RWsize(rwops); + result.resize(size); + SDL_RWread(rwops, result.data(), size, 1); + SDL_RWclose(rwops); + return result; +} + +void DrawWithBorder(const Surface &out, const Rectangle &area, tl::function_ref fn) +{ + const uint8_t debugColor = PAL8_RED; + DrawHorizontalLine(out, area.position, area.size.width, debugColor); + DrawHorizontalLine(out, area.position + Displacement { 0, area.size.height - 1 }, area.size.width, debugColor); + DrawVerticalLine(out, area.position, area.size.height, debugColor); + DrawVerticalLine(out, area.position + Displacement { area.size.width - 1, 0 }, area.size.height, debugColor); + fn(Rectangle { + Point { area.position.x + 1, area.position.y + 1 }, + Size { area.size.width - 2, area.size.height - 2 } }); +} + +TEST(TextRenderIntegrationTest, GoldenTest) +{ + SDLPaletteUniquePtr palette = LoadPalette(); + OwnedSurface out { Size { 200, 140 } }; + SDL_SetSurfacePalette(out.surface, palette.get()); + ASSERT_NE(out.surface, nullptr); + + int y = -15; + DrawWithBorder(out, Rectangle { Point { 0, y += 15 }, Size { 96, 15 } }, [&](const Rectangle &rect) { + DrawString(out, "DrawString", rect, + TextRenderOptions { .flags = UiFlags::ColorUiGold }); + }); + DrawWithBorder(out, Rectangle { Point { 0, y += 15 }, Size { 120, 15 } }, [&](const Rectangle &rect) { + DrawString(out, "KerningFitSpacing", rect, + TextRenderOptions { .flags = UiFlags::KerningFitSpacing | UiFlags::ColorUiSilver }); + }); + DrawWithBorder(out, Rectangle { Point { 0, y += 15 }, Size { 170, 15 } }, [&](const Rectangle &rect) { + DrawString(out, "KerningFitSpacing | AlignCenter", rect, + TextRenderOptions { .flags = UiFlags::KerningFitSpacing | UiFlags::AlignCenter | UiFlags::ColorUiSilver }); + }); + DrawWithBorder(out, Rectangle { Point { 0, y += 15 }, Size { 170, 15 } }, [&](const Rectangle &rect) { + DrawString(out, "KerningFitSpacing | AlignRight", rect, + TextRenderOptions { .flags = UiFlags::KerningFitSpacing | UiFlags::AlignRight | UiFlags::ColorUiSilver }); + }); + y += 4; + DrawWithBorder(out, Rectangle { Point { 0, y += 15 }, Size { 186, 15 } }, [&](const Rectangle &rect) { + DrawStringWithColors(out, "{}{}{}{}", + { { "Draw", UiFlags::ColorUiSilver }, + { "String", UiFlags::ColorUiGold }, + { "With", UiFlags::ColorUiSilverDark }, + { "Colors", UiFlags::ColorUiGoldDark } }, + rect); + }); + DrawWithBorder(out, Rectangle { Point { 0, y += 15 }, Size { 120, 15 } }, [&](const Rectangle &rect) { + DrawStringWithColors(out, "{}{}{}", + { { "Kerning", UiFlags::ColorUiSilver }, + { "Fit", UiFlags::ColorUiGold }, + { "Spacing", UiFlags::ColorUiSilverDark } }, + rect, + TextRenderOptions { .flags = UiFlags::KerningFitSpacing }); + }); + DrawWithBorder(out, Rectangle { Point { 0, y += 15 }, Size { 170, 15 } }, [&](const Rectangle &rect) { + DrawStringWithColors(out, "{}{}{}", + { { "KerningFitSpacing", UiFlags::ColorUiSilver }, + { " | ", UiFlags::ColorUiGold }, + { "AlignCenter", UiFlags::ColorUiSilverDark } }, + rect, + TextRenderOptions { .flags = UiFlags::KerningFitSpacing | UiFlags::AlignCenter | UiFlags::ColorUiSilver }); + }); + DrawWithBorder(out, Rectangle { Point { 0, y += 15 }, Size { 170, 15 } }, [&](const Rectangle &rect) { + DrawStringWithColors(out, "{}{}{}", + { { "KerningFitSpacing", UiFlags::ColorUiSilver }, + { " | ", UiFlags::ColorUiGold }, + { "AlignRight", UiFlags::ColorUiSilverDark } }, + rect, + TextRenderOptions { .flags = UiFlags::KerningFitSpacing | UiFlags::AlignRight | UiFlags::ColorUiSilver }); + }); + + const std::string actualPath = paths::BasePath() + FixturesPath + "actual.png"; + const std::string expectedPath = paths::BasePath() + FixturesPath + "expected.png"; + SDL_RWops *actual = SDL_RWFromFile(actualPath.c_str(), "wb"); + ASSERT_NE(actual, nullptr) << SDL_GetError(); + ASSERT_TRUE(WriteSurfaceToFilePng(out, actual).has_value()); + EXPECT_EQ(ReadFile(actualPath), ReadFile(expectedPath)) << "\n" + << expectedPath << "\n" + << actualPath; +} + +} // namespace +} // namespace devilution