Add UI core color parser tests
This commit is contained in:
@@ -150,6 +150,7 @@ target_link_libraries(pp_paint_renderer
|
||||
pp_project_warnings)
|
||||
|
||||
add_library(pp_ui_core STATIC
|
||||
src/ui_core/color.cpp
|
||||
src/ui_core/layout_value.cpp
|
||||
src/ui_core/layout_xml.cpp)
|
||||
target_include_directories(pp_ui_core
|
||||
|
||||
@@ -81,7 +81,8 @@ Known local toolchain state:
|
||||
`pp_paint`, `pp_document`, `pp_renderer_api`, `pp_paint_renderer`,
|
||||
`pp_ui_core`, `pano_cli`, and their current headless test binaries,
|
||||
including foundation event/logging/task queue coverage, PPI header, settings
|
||||
document, paint stroke sampling, and layout XML parse coverage.
|
||||
document, paint stroke sampling, UI color parsing, and layout XML parse
|
||||
coverage.
|
||||
- `panopainter_validate_shaders` validates the current combined GLSL shader
|
||||
files for one vertex stage marker, one fragment stage marker, valid marker
|
||||
order, and existing relative includes.
|
||||
|
||||
@@ -317,7 +317,8 @@ layer/frame/undo-redo history invariant tests. `pp_renderer_api` has started wit
|
||||
texture/readback descriptors and validation tests. `pp_paint_renderer` has
|
||||
started with deterministic CPU layer compositing over renderer extents using
|
||||
the paint blend reference. `pp_ui_core` has started with XML-layout-facing
|
||||
length parsing, tinyxml-backed layout XML parsing, and invalid input tests.
|
||||
length parsing, color parsing, tinyxml-backed layout XML parsing, and invalid
|
||||
input tests.
|
||||
`pano_cli parse-layout` now exercises that path. Continue expanding document
|
||||
behavior toward legacy Canvas parity and then port OpenGL classes behind the
|
||||
renderer boundary.
|
||||
@@ -525,7 +526,7 @@ Last verified on 2026-06-01:
|
||||
|
||||
```powershell
|
||||
cmake --preset windows-msvc-default
|
||||
cmake --build --preset windows-msvc-default --config Debug --target pp_foundation_binary_stream_tests pp_foundation_event_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_task_queue_tests pp_foundation_trace_tests pp_assets_image_format_tests pp_assets_ppi_header_tests pp_assets_settings_document_tests pp_paint_blend_tests pp_paint_stroke_tests pp_document_tests pp_renderer_api_tests pp_paint_renderer_compositor_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests pano_cli PanoPainter
|
||||
cmake --build --preset windows-msvc-default --config Debug --target pp_foundation_binary_stream_tests pp_foundation_event_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_task_queue_tests pp_foundation_trace_tests pp_assets_image_format_tests pp_assets_ppi_header_tests pp_assets_settings_document_tests pp_paint_blend_tests pp_paint_stroke_tests pp_document_tests pp_renderer_api_tests pp_paint_renderer_compositor_tests pp_ui_core_color_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests pano_cli PanoPainter
|
||||
ctest --preset desktop-fast --build-config Debug
|
||||
powershell -ExecutionPolicy Bypass -File scripts\automation\test.ps1 -Preset desktop-fast -Configuration Debug
|
||||
powershell -ExecutionPolicy Bypass -File scripts\automation\build.ps1 -Preset windows-msvc-default -Configuration Debug -Target pano_cli
|
||||
@@ -552,6 +553,7 @@ Results:
|
||||
- `pp_document_tests` passed.
|
||||
- `pp_renderer_api_tests` passed.
|
||||
- `pp_paint_renderer_compositor_tests` passed.
|
||||
- `pp_ui_core_color_tests` passed.
|
||||
- `pp_ui_core_layout_value_tests` passed.
|
||||
- `pp_ui_core_layout_xml_tests` passed.
|
||||
- `pano_cli_create_document_smoke` passed.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string[]]$Presets = @("android-arm64"),
|
||||
[string[]]$Targets = @("pp_foundation", "pp_assets", "pp_paint", "pp_document", "pp_renderer_api", "pp_paint_renderer", "pp_ui_core", "pano_cli", "pp_foundation_binary_stream_tests", "pp_foundation_event_tests", "pp_foundation_log_tests", "pp_foundation_parse_tests", "pp_foundation_task_queue_tests", "pp_foundation_trace_tests", "pp_assets_image_format_tests", "pp_assets_ppi_header_tests", "pp_assets_settings_document_tests", "pp_paint_blend_tests", "pp_paint_stroke_tests", "pp_document_tests", "pp_renderer_api_tests", "pp_paint_renderer_compositor_tests", "pp_ui_core_layout_value_tests", "pp_ui_core_layout_xml_tests")
|
||||
[string[]]$Targets = @("pp_foundation", "pp_assets", "pp_paint", "pp_document", "pp_renderer_api", "pp_paint_renderer", "pp_ui_core", "pano_cli", "pp_foundation_binary_stream_tests", "pp_foundation_event_tests", "pp_foundation_log_tests", "pp_foundation_parse_tests", "pp_foundation_task_queue_tests", "pp_foundation_trace_tests", "pp_assets_image_format_tests", "pp_assets_ppi_header_tests", "pp_assets_settings_document_tests", "pp_paint_blend_tests", "pp_paint_stroke_tests", "pp_document_tests", "pp_renderer_api_tests", "pp_paint_renderer_compositor_tests", "pp_ui_core_color_tests", "pp_ui_core_layout_value_tests", "pp_ui_core_layout_xml_tests")
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
@@ -3,7 +3,7 @@ set -u
|
||||
|
||||
preset="${1:-android-arm64}"
|
||||
shift || true
|
||||
targets="${*:-pp_foundation pp_assets pp_paint pp_document pp_renderer_api pp_paint_renderer pp_ui_core pano_cli pp_foundation_binary_stream_tests pp_foundation_event_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_task_queue_tests pp_foundation_trace_tests pp_assets_image_format_tests pp_assets_ppi_header_tests pp_assets_settings_document_tests pp_paint_blend_tests pp_paint_stroke_tests pp_document_tests pp_renderer_api_tests pp_paint_renderer_compositor_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests}"
|
||||
targets="${*:-pp_foundation pp_assets pp_paint pp_document pp_renderer_api pp_paint_renderer pp_ui_core pano_cli pp_foundation_binary_stream_tests pp_foundation_event_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_task_queue_tests pp_foundation_trace_tests pp_assets_image_format_tests pp_assets_ppi_header_tests pp_assets_settings_document_tests pp_paint_blend_tests pp_paint_stroke_tests pp_document_tests pp_renderer_api_tests pp_paint_renderer_compositor_tests pp_ui_core_color_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests}"
|
||||
start="$(date +%s)"
|
||||
|
||||
cmake --preset "$preset"
|
||||
|
||||
95
src/ui_core/color.cpp
Normal file
95
src/ui_core/color.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#include "ui_core/color.h"
|
||||
|
||||
namespace pp::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] int hex_value(char ch) noexcept
|
||||
{
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
return ch - '0';
|
||||
}
|
||||
if (ch >= 'a' && ch <= 'f') {
|
||||
return 10 + (ch - 'a');
|
||||
}
|
||||
if (ch >= 'A' && ch <= 'F') {
|
||||
return 10 + (ch - 'A');
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<std::uint8_t> parse_hex_byte(std::string_view value) noexcept
|
||||
{
|
||||
const auto high = hex_value(value[0]);
|
||||
const auto low = hex_value(value[1]);
|
||||
if (high < 0 || low < 0) {
|
||||
return pp::foundation::Result<std::uint8_t>::failure(
|
||||
pp::foundation::Status::invalid_argument("color contains a non-hex character"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<std::uint8_t>::success(
|
||||
static_cast<std::uint8_t>((high << 4) | low));
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<std::uint8_t> parse_hex_nibble(char value) noexcept
|
||||
{
|
||||
const auto nibble = hex_value(value);
|
||||
if (nibble < 0) {
|
||||
return pp::foundation::Result<std::uint8_t>::failure(
|
||||
pp::foundation::Status::invalid_argument("color contains a non-hex character"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<std::uint8_t>::success(
|
||||
static_cast<std::uint8_t>((nibble << 4) | nibble));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pp::foundation::Result<ColorRgba8> parse_hex_color(std::string_view value) noexcept
|
||||
{
|
||||
if (value.empty()) {
|
||||
return pp::foundation::Result<ColorRgba8>::failure(
|
||||
pp::foundation::Status::invalid_argument("color must not be empty"));
|
||||
}
|
||||
|
||||
if (value.front() != '#') {
|
||||
return pp::foundation::Result<ColorRgba8>::failure(
|
||||
pp::foundation::Status::invalid_argument("color must start with #"));
|
||||
}
|
||||
|
||||
const auto hex = value.substr(1);
|
||||
if (hex.size() != 3U && hex.size() != 4U && hex.size() != 6U && hex.size() != 8U) {
|
||||
return pp::foundation::Result<ColorRgba8>::failure(
|
||||
pp::foundation::Status::invalid_argument("color must use #rgb, #rgba, #rrggbb, or #rrggbbaa"));
|
||||
}
|
||||
|
||||
ColorRgba8 color;
|
||||
if (hex.size() == 3U || hex.size() == 4U) {
|
||||
const auto r = parse_hex_nibble(hex[0]);
|
||||
const auto g = parse_hex_nibble(hex[1]);
|
||||
const auto b = parse_hex_nibble(hex[2]);
|
||||
const auto a = hex.size() == 4U ? parse_hex_nibble(hex[3])
|
||||
: pp::foundation::Result<std::uint8_t>::success(255);
|
||||
if (!r || !g || !b || !a) {
|
||||
return pp::foundation::Result<ColorRgba8>::failure(
|
||||
pp::foundation::Status::invalid_argument("color contains a non-hex character"));
|
||||
}
|
||||
color = ColorRgba8 { .r = r.value(), .g = g.value(), .b = b.value(), .a = a.value() };
|
||||
return pp::foundation::Result<ColorRgba8>::success(color);
|
||||
}
|
||||
|
||||
const auto r = parse_hex_byte(hex.substr(0, 2));
|
||||
const auto g = parse_hex_byte(hex.substr(2, 2));
|
||||
const auto b = parse_hex_byte(hex.substr(4, 2));
|
||||
const auto a = hex.size() == 8U ? parse_hex_byte(hex.substr(6, 2))
|
||||
: pp::foundation::Result<std::uint8_t>::success(255);
|
||||
if (!r || !g || !b || !a) {
|
||||
return pp::foundation::Result<ColorRgba8>::failure(
|
||||
pp::foundation::Status::invalid_argument("color contains a non-hex character"));
|
||||
}
|
||||
|
||||
color = ColorRgba8 { .r = r.value(), .g = g.value(), .b = b.value(), .a = a.value() };
|
||||
return pp::foundation::Result<ColorRgba8>::success(color);
|
||||
}
|
||||
|
||||
}
|
||||
19
src/ui_core/color.h
Normal file
19
src/ui_core/color.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::ui {
|
||||
|
||||
struct ColorRgba8 {
|
||||
std::uint8_t r = 0;
|
||||
std::uint8_t g = 0;
|
||||
std::uint8_t b = 0;
|
||||
std::uint8_t a = 255;
|
||||
};
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<ColorRgba8> parse_hex_color(std::string_view value) noexcept;
|
||||
|
||||
}
|
||||
@@ -146,6 +146,16 @@ add_test(NAME pp_paint_renderer_compositor_tests COMMAND pp_paint_renderer_compo
|
||||
set_tests_properties(pp_paint_renderer_compositor_tests PROPERTIES
|
||||
LABELS "renderer;paint;desktop-fast")
|
||||
|
||||
add_executable(pp_ui_core_color_tests
|
||||
ui_core/color_tests.cpp)
|
||||
target_link_libraries(pp_ui_core_color_tests PRIVATE
|
||||
pp_ui_core
|
||||
pp_test_harness)
|
||||
|
||||
add_test(NAME pp_ui_core_color_tests COMMAND pp_ui_core_color_tests)
|
||||
set_tests_properties(pp_ui_core_color_tests PROPERTIES
|
||||
LABELS "ui;desktop-fast")
|
||||
|
||||
add_executable(pp_ui_core_layout_value_tests
|
||||
ui_core/layout_value_tests.cpp)
|
||||
target_link_libraries(pp_ui_core_layout_value_tests PRIVATE
|
||||
|
||||
71
tests/ui_core/color_tests.cpp
Normal file
71
tests/ui_core/color_tests.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "test_harness.h"
|
||||
#include "ui_core/color.h"
|
||||
|
||||
using pp::foundation::StatusCode;
|
||||
using pp::ui::parse_hex_color;
|
||||
|
||||
namespace {
|
||||
|
||||
void parses_short_and_long_rgb_forms(pp::tests::Harness& h)
|
||||
{
|
||||
const auto short_rgb = parse_hex_color("#0f8");
|
||||
const auto long_rgb = parse_hex_color("#102aFF");
|
||||
|
||||
PP_EXPECT(h, short_rgb.ok());
|
||||
PP_EXPECT(h, short_rgb.value().r == 0x00);
|
||||
PP_EXPECT(h, short_rgb.value().g == 0xff);
|
||||
PP_EXPECT(h, short_rgb.value().b == 0x88);
|
||||
PP_EXPECT(h, short_rgb.value().a == 0xff);
|
||||
|
||||
PP_EXPECT(h, long_rgb.ok());
|
||||
PP_EXPECT(h, long_rgb.value().r == 0x10);
|
||||
PP_EXPECT(h, long_rgb.value().g == 0x2a);
|
||||
PP_EXPECT(h, long_rgb.value().b == 0xff);
|
||||
PP_EXPECT(h, long_rgb.value().a == 0xff);
|
||||
}
|
||||
|
||||
void parses_alpha_forms(pp::tests::Harness& h)
|
||||
{
|
||||
const auto short_rgba = parse_hex_color("#1234");
|
||||
const auto long_rgba = parse_hex_color("#11223344");
|
||||
|
||||
PP_EXPECT(h, short_rgba.ok());
|
||||
PP_EXPECT(h, short_rgba.value().r == 0x11);
|
||||
PP_EXPECT(h, short_rgba.value().g == 0x22);
|
||||
PP_EXPECT(h, short_rgba.value().b == 0x33);
|
||||
PP_EXPECT(h, short_rgba.value().a == 0x44);
|
||||
|
||||
PP_EXPECT(h, long_rgba.ok());
|
||||
PP_EXPECT(h, long_rgba.value().r == 0x11);
|
||||
PP_EXPECT(h, long_rgba.value().g == 0x22);
|
||||
PP_EXPECT(h, long_rgba.value().b == 0x33);
|
||||
PP_EXPECT(h, long_rgba.value().a == 0x44);
|
||||
}
|
||||
|
||||
void rejects_invalid_colors(pp::tests::Harness& h)
|
||||
{
|
||||
const auto empty = parse_hex_color("");
|
||||
const auto missing_hash = parse_hex_color("112233");
|
||||
const auto bad_length = parse_hex_color("#12");
|
||||
const auto bad_character = parse_hex_color("#12xz45");
|
||||
|
||||
PP_EXPECT(h, !empty.ok());
|
||||
PP_EXPECT(h, empty.status().code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !missing_hash.ok());
|
||||
PP_EXPECT(h, missing_hash.status().code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !bad_length.ok());
|
||||
PP_EXPECT(h, bad_length.status().code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !bad_character.ok());
|
||||
PP_EXPECT(h, bad_character.status().code == StatusCode::invalid_argument);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
pp::tests::Harness harness;
|
||||
harness.run("parses_short_and_long_rgb_forms", parses_short_and_long_rgb_forms);
|
||||
harness.run("parses_alpha_forms", parses_alpha_forms);
|
||||
harness.run("rejects_invalid_colors", rejects_invalid_colors);
|
||||
return harness.finish();
|
||||
}
|
||||
Reference in New Issue
Block a user