Files
fn_registry/cpp/tests/test_graph_icons.cpp
T
egutierrez ac11300335 feat(viz): renderer shapes/iconos/flechas/edge-styles (issue 0049f)
graph_renderer 1.5.0:
- 6 shapes SDF (circle, square, diamond, hex, triangle, rounded square)
  con dispatch en fragment shader y AA via fwidth.
- Atlas opcional de iconos Tabler bakeado por graph_icons; el shader
  compone overlay desde un uniform vec4 u_icon_uvs[256]. Setter publico
  graph_renderer_set_icon_atlas(r, tex, uv_table, count).
- Aristas direccionales: 6 vertices por arista (line + chevron de la
  flecha) en una sola draw call; segmento principal acortado por el
  radio del nodo target.
- Edge styles solid/dashed/dotted via descarte por arc_length en el
  fragment shader; las lineas del chevron son siempre solidas.

graph_icons 1.0.0 (nuevo):
- Atlas RGBA8 512x512 = grid 16x16 (256 iconos max) bakeado con
  stb_truetype desde tabler-icons.ttf.
- API: graph_icons_build/texture/region/uv_table/destroy. icon_id es
  1-based; 0 reservado para "sin icono".
- Hook FN_GRAPH_ICONS_SKIP_GL=1 para tests sin contexto GL.

Demo demos_graph_styles en primitives_gallery: 6 EntityTypes (uno por
shape) con icono Tabler representativo + 3 RelationTypes (knows/uses/
owns) con flechas direccionales y los 3 estilos.

test_graph_icons: 6 casos cubriendo bake, regiones 1-indexed, uv_table
consistente, layout en grid 16x16, validacion de count fuera de rango,
y verificacion de alpha != 0 en las celdas tras bake.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:01:49 +02:00

154 lines
5.1 KiB
C++

// Unit tests para `graph_icons` (issue 0049f).
// Cubre la parte CPU del builder — bake del atlas, layout en grid 16x16,
// regiones 1-indexed, uv_table consistente, y comportamiento ante codepoints
// inexistentes en la fuente. La subida a GPU se desactiva con la env
// `FN_GRAPH_ICONS_SKIP_GL=1` (set en el setup) para que el test corra sin
// contexto GL en CI.
#define CATCH_CONFIG_MAIN
#include "catch_amalgamated.hpp"
#include "viz/graph_icons.h"
#include <cstdint>
#include <cstdlib>
namespace {
// Tabler 3.41: codepoints que sabemos que existen en la TTF embebida en el
// repo (ver cpp/functions/core/icons_tabler.h).
const uint16_t k_known[] = {
0xEB4Du, // TI_USER
0xEAE5u, // TI_MAIL
0xEAB9u, // TI_GLOBE
0xEB09u, // TI_PHONE
0xEA4Fu, // TI_BUILDING
0xEA88u, // TI_DATABASE
};
constexpr int k_known_count = sizeof(k_known) / sizeof(k_known[0]);
bool any_alpha_in_region(const unsigned char* pixels, int W,
float u0, float v0, float u1, float v1)
{
int x0 = (int)(u0 * W);
int y0 = (int)(v0 * W);
int x1 = (int)(u1 * W);
int y1 = (int)(v1 * W);
for (int y = y0; y < y1; ++y) {
for (int x = x0; x < x1; ++x) {
if (pixels[(y * W + x) * 4 + 3] > 0) return true;
}
}
return false;
}
struct EnvSetup {
EnvSetup() { setenv("FN_GRAPH_ICONS_SKIP_GL", "1", /*overwrite=*/1); }
};
EnvSetup _env_setup;
} // namespace
TEST_CASE("graph_icons_build con 6 codepoints conocidos", "[viz][graph_icons]") {
IconAtlas* a = graph_icons_build(k_known, k_known_count, 32);
REQUIRE(a != nullptr);
REQUIRE(graph_icons_count(a) == k_known_count);
REQUIRE(graph_icons_width(a) == 512);
REQUIRE(graph_icons_height(a) == 512);
graph_icons_destroy(a);
}
TEST_CASE("regiones 1-indexed y orden conservado", "[viz][graph_icons]") {
IconAtlas* a = graph_icons_build(k_known, k_known_count, 32);
REQUIRE(a != nullptr);
// icon_id = 0 -> nullptr (reservado para "sin icono")
REQUIRE(graph_icons_region(a, 0) == nullptr);
// icon_id = i+1 -> region con codepoint en posicion i del array
for (int i = 0; i < k_known_count; ++i) {
const IconRegion* r = graph_icons_region(a, (uint16_t)(i + 1));
REQUIRE(r != nullptr);
REQUIRE(r->id == (uint16_t)(i + 1));
REQUIRE(r->codepoint == k_known[i]);
REQUIRE(r->u0 < r->u1);
REQUIRE(r->v0 < r->v1);
REQUIRE(r->u0 >= 0.0f);
REQUIRE(r->v1 <= 1.0f);
}
// icon_id fuera de rango -> nullptr
REQUIRE(graph_icons_region(a, (uint16_t)(k_known_count + 1)) == nullptr);
REQUIRE(graph_icons_region(a, 9999) == nullptr);
graph_icons_destroy(a);
}
TEST_CASE("uv_table coincide con regions", "[viz][graph_icons]") {
IconAtlas* a = graph_icons_build(k_known, k_known_count, 32);
REQUIRE(a != nullptr);
const float* uv = graph_icons_uv_table(a);
REQUIRE(uv != nullptr);
for (int i = 0; i < k_known_count; ++i) {
const IconRegion* r = graph_icons_region(a, (uint16_t)(i + 1));
REQUIRE(uv[i * 4 + 0] == r->u0);
REQUIRE(uv[i * 4 + 1] == r->v0);
REQUIRE(uv[i * 4 + 2] == r->u1);
REQUIRE(uv[i * 4 + 3] == r->v1);
}
graph_icons_destroy(a);
}
TEST_CASE("regiones tienen contenido (alpha != 0)", "[viz][graph_icons]") {
// El test no se puede correr si el TTF no esta accesible — en ese caso
// graph_icons_build devuelve nullptr y skipeamos.
IconAtlas* a = graph_icons_build(k_known, k_known_count, 32);
if (!a) {
WARN("tabler-icons.ttf no encontrado — test de contenido skipped. "
"Defina FN_CPP_ROOT o copie el TTF a ./assets/.");
return;
}
const unsigned char* px = graph_icons_pixels(a);
REQUIRE(px != nullptr);
for (int i = 0; i < k_known_count; ++i) {
const IconRegion* r = graph_icons_region(a, (uint16_t)(i + 1));
REQUIRE(any_alpha_in_region(px, graph_icons_width(a),
r->u0, r->v0, r->u1, r->v1));
}
graph_icons_destroy(a);
}
TEST_CASE("count fuera de rango devuelve nullptr", "[viz][graph_icons]") {
REQUIRE(graph_icons_build(k_known, 0, 32) == nullptr);
REQUIRE(graph_icons_build(k_known, -1, 32) == nullptr);
REQUIRE(graph_icons_build(k_known, 257, 32) == nullptr); // max 256
REQUIRE(graph_icons_build(nullptr, k_known_count, 32) == nullptr);
}
TEST_CASE("layout en grid 16x16", "[viz][graph_icons]") {
IconAtlas* a = graph_icons_build(k_known, k_known_count, 32);
REQUIRE(a != nullptr);
// Cada celda es 32px = 32/512 = 0.0625 en UV. Con 1px padding, las UVs
// van de 1/512 a 31/512 dentro de la celda.
const float cell_uv = 32.0f / 512.0f;
for (int i = 0; i < k_known_count; ++i) {
const IconRegion* r = graph_icons_region(a, (uint16_t)(i + 1));
int row = i / 16;
int col = i % 16;
// u0 esta dentro de [col*cell, (col+1)*cell]
REQUIRE(r->u0 >= col * cell_uv);
REQUIRE(r->u1 <= (col + 1) * cell_uv);
REQUIRE(r->v0 >= row * cell_uv);
REQUIRE(r->v1 <= (row + 1) * cell_uv);
}
graph_icons_destroy(a);
}