#include #include #include #include #include #include #include #include namespace { int failures = 0; void expect(bool condition, const char* message) { if (!condition) { std::cerr << "FAIL: " << message << '\n'; ++failures; } } bool opaque_at(const gui::SvgIcon& icon, int x, int y) { return ((icon.pixels[y * icon.width + x] >> 24) & 0xff) != 0; } void test_transform_parser() { gui::SvgTransform transform; expect(gui::svg_parse_transform("translate(3 4) scale(2)", &transform), "transform list parses"); gui::fixed_t x, y; gui::svg_transform_point(transform, gui::int_to_fixed(1), gui::int_to_fixed(2), &x, &y); expect(gui::fixed_to_int(x) == 5 && gui::fixed_to_int(y) == 8, "transform list uses SVG composition order"); expect(gui::svg_parse_transform("matrix(2 0 0 3 4 5)", &transform), "matrix transform parses"); gui::svg_transform_point(transform, gui::int_to_fixed(2), gui::int_to_fixed(3), &x, &y); expect(gui::fixed_to_int(x) == 8 && gui::fixed_to_int(y) == 14, "matrix coefficients and translation apply"); expect(gui::svg_parse_transform("rotate(90 5 5)", &transform), "rotation around a center parses"); gui::svg_transform_point(transform, gui::int_to_fixed(7), gui::int_to_fixed(5), &x, &y); expect(gui::fixed_to_int(x) == 5 && gui::fixed_to_int(y) == 7, "rotation around a center applies"); } void test_natural_size_units() { const char svg[] = ""; int width = 0, height = 0; gui::svg_get_natural_size(svg, sizeof(svg) - 1, &width, &height); expect(width == 96 && height == 96, "absolute SVG units convert to CSS pixels"); const char percent_svg[] = ""; gui::svg_get_natural_size(percent_svg, sizeof(percent_svg) - 1, &width, &height); expect(width == 320 && height == 180, "percentage dimensions fall back to viewBox"); } void test_nested_group_transform() { const char svg[] = "" "" "" ""; gui::SvgIcon icon = gui::svg_render(svg, sizeof(svg) - 1, 20, 20, gui::Color::from_rgb(0, 0, 0)); expect(opaque_at(icon, 7, 6), "nested transformed rectangle is rendered at destination"); expect(!opaque_at(icon, 2, 2), "nested transformed rectangle is absent at source position"); gui::svg_free(icon); } void test_matrix_group_and_stroke() { const char svg[] = "" "" "" ""; gui::SvgIcon icon = gui::svg_render(svg, sizeof(svg) - 1, 20, 20, gui::Color::from_rgb(0, 0, 0)); expect(opaque_at(icon, 8, 8), "horizontal stroke in a matrix-transformed group renders"); expect(!opaque_at(icon, 8, 3), "matrix-transformed stroke is not left untransformed"); gui::svg_free(icon); } void test_translucent_stroke_is_composited_once() { const char svg[] = ""; gui::SvgIcon icon = gui::svg_render(svg, sizeof(svg) - 1, 20, 20, gui::Color::from_rgb(0, 0, 0)); int body_alpha = (icon.pixels[10 * icon.width + 5] >> 24) & 0xff; int join_alpha = (icon.pixels[10 * icon.width + 10] >> 24) & 0xff; expect(body_alpha >= 75 && body_alpha <= 77, "translucent stroke has expected opacity"); expect(join_alpha == body_alpha, "flattened stroke joins do not accumulate opacity"); gui::svg_free(icon); } void test_linear_gradient() { const char svg[] = "" "" "" ""; gui::SvgIcon icon = gui::svg_render(svg, sizeof(svg) - 1, 20, 10, gui::Color::from_rgb(0, 0, 0)); uint32_t left = icon.pixels[5 * icon.width + 1]; uint32_t right = icon.pixels[5 * icon.width + 18]; expect(((left >> 16) & 0xff) > (left & 0xff), "linear gradient starts at first stop"); expect((right & 0xff) > ((right >> 16) & 0xff), "linear gradient reaches final stop"); gui::svg_free(icon); } void test_ellipse_and_paint_inheritance() { const char svg[] = "" "" ""; gui::SvgIcon icon = gui::svg_render(svg, sizeof(svg) - 1, 20, 20, gui::Color::from_rgb(0, 0, 0)); uint32_t center = icon.pixels[10 * icon.width + 10]; int alpha = (center >> 24) & 0xff; expect(alpha >= 126 && alpha <= 128, "ellipse renders with explicit fill over inherited none"); expect(((center >> 8) & 0xff) == 255, "ellipse fill color is preserved"); gui::svg_free(icon); } void test_long_path_is_not_truncated() { std::string svg = ""; expect(svg.size() > 8192, "long-path regression fixture exceeds former limit"); gui::SvgIcon icon = gui::svg_render(svg.data(), (int)svg.size(), 20, 20, gui::Color::from_rgb(0, 0, 0)); expect(opaque_at(icon, 16, 16), "geometry after 8 KiB in a path still renders"); gui::svg_free(icon); } std::string read_test_asset(const char* name) { const char* prefixes[] = {"programs/bin/icons/", "../programs/bin/icons/"}; for (const char* prefix : prefixes) { std::ifstream input(std::string(prefix) + name, std::ios::binary); if (input) return std::string(std::istreambuf_iterator(input), {}); } return {}; } void test_bundled_system_icons() { std::string executable = read_test_asset("application-x-executable.svg"); expect(!executable.empty(), "bundled executable icon is available to regression test"); if (!executable.empty()) { gui::SvgIcon icon = gui::svg_render(executable.data(), (int)executable.size(), 36, 36, gui::Color::from_rgb(0, 0, 0)); int painted = 0; for (int i = 0; i < icon.width * icon.height; ++i) if ((icon.pixels[i] >> 24) != 0) ++painted; expect(painted > 350, "matrix-transformed executable icon renders substantial geometry"); gui::svg_free(icon); } std::string browser = read_test_asset("web-browser.svg"); expect(!browser.empty(), "bundled browser icon is available to regression test"); if (!browser.empty()) { gui::SvgIcon icon = gui::svg_render(browser.data(), (int)browser.size(), 64, 64, gui::Color::from_rgb(0, 0, 0)); uint32_t equator = icon.pixels[33 * icon.width + 32]; expect(((equator >> 16) & 0xff) > 180 && ((equator >> 8) & 0xff) > 180, "stroke-only globe geometry appears in bundled browser icon"); gui::svg_free(icon); } } void test_all_bundled_icons_render() { namespace fs = std::filesystem; fs::path directory = fs::path("programs/bin/icons"); if (!fs::exists(directory)) directory = fs::path("../programs/bin/icons"); expect(fs::exists(directory), "bundled icon directory is available"); if (!fs::exists(directory)) return; int checked = 0; for (const fs::directory_entry& entry : fs::directory_iterator(directory)) { if (entry.path().extension() != ".svg") continue; std::ifstream input(entry.path(), std::ios::binary); std::string source(std::istreambuf_iterator(input), {}); gui::SvgIcon icon = gui::svg_render(source.data(), (int)source.size(), 64, 64, gui::Color::from_rgb(92, 97, 108)); bool painted = false; if (icon.pixels) { for (int i = 0; i < icon.width * icon.height; ++i) { if ((icon.pixels[i] >> 24) != 0) { painted = true; break; } } } if (!painted) { std::string message = "bundled icon renders visible pixels: " + entry.path().filename().string(); expect(false, message.c_str()); } gui::svg_free(icon); ++checked; } expect(checked >= 100, "complete bundled system icon set was audited"); } } // namespace int main() { test_transform_parser(); test_natural_size_units(); test_nested_group_transform(); test_matrix_group_and_stroke(); test_translucent_stroke_is_composited_once(); test_linear_gradient(); test_ellipse_and_paint_inheritance(); test_long_path_is_not_truncated(); test_bundled_system_icons(); test_all_bundled_icons_render(); if (failures != 0) return 1; std::cout << "SVG renderer tests passed\n"; return 0; }