Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
geometry_visual_golden.h
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
31#ifndef ALEPH_TEST_GEOMETRY_VISUAL_GOLDEN_H
32#define ALEPH_TEST_GEOMETRY_VISUAL_GOLDEN_H
33
34#include <algorithm>
35#include <array>
36#include <cctype>
37#include <cstdlib>
38#include <filesystem>
39#include <fstream>
40#include <iomanip>
41#include <sstream>
42#include <string>
43
44#include <point.H>
45#include <polygon.H>
46#include <tpl_array.H>
47
49{
50
58
59inline std::string sanitize_filename(const std::string & raw)
60{
61 std::string out;
62 out.reserve(raw.size());
63 for (const char ch : raw)
64 if (std::isalnum(static_cast<unsigned char>(ch)) or ch == '-' or ch == '_')
65 out.push_back(ch);
66 else
67 out.push_back('_');
68
69 return out.empty() ? std::string("case") : out;
70}
71
72inline std::string xml_escape(const std::string & s)
73{
74 std::string out;
75 out.reserve(s.size());
76 for (const char ch : s)
77 {
78 switch (ch)
79 {
80 case '&': out += "&amp;"; break;
81 case '<': out += "&lt;"; break;
82 case '>': out += "&gt;"; break;
83 case '"': out += "&quot;"; break;
84 case '\'': out += "&apos;"; break;
85 default: out.push_back(ch); break;
86 }
87 }
88
89 return out;
90}
91
92inline std::filesystem::path golden_output_dir()
93{
94 if (const char * env_dir = std::getenv("ALEPH_TEST_GOLDEN_DIR"); env_dir != nullptr and env_dir[0] != '\0')
95 return std::filesystem::path(env_dir);
96
97 return std::filesystem::path("test_artifacts/golden_svg");
98}
99
100inline void add_polygon_vertices(SvgScene & scene, const ::Polygon & poly,
101 const bool as_highlight = false)
102{
103 if (poly.size() == 0)
104 return;
105
106 for (::Polygon::Vertex_Iterator it(poly); it.has_curr(); it.next_ne())
107 {
108 if (as_highlight)
109 scene.highlighted_points.append(it.get_current_vertex());
110 else
111 scene.points.append(it.get_current_vertex());
112 }
113}
114
116{
117 for (size_t i = 0; i < scene.points.size(); ++i)
118 all_points.append(scene.points(i));
119 for (size_t i = 0; i < scene.highlighted_points.size(); ++i)
120 all_points.append(scene.highlighted_points(i));
121
122 for (size_t i = 0; i < scene.segments.size(); ++i)
123 {
124 all_points.append(scene.segments(i).get_src_point());
125 all_points.append(scene.segments(i).get_tgt_point());
126 }
127
128 for (size_t i = 0; i < scene.polygons.size(); ++i)
129 {
130 if (scene.polygons(i).size() == 0)
131 continue;
132 for (::Polygon::Vertex_Iterator it(scene.polygons(i)); it.has_curr(); it.next_ne())
133 all_points.append(it.get_current_vertex());
134 }
135}
136
137inline std::filesystem::path emit_case_svg(const std::string & case_id,
138 const SvgScene & scene,
139 const std::string & note = "")
140{
141 const std::filesystem::path out_dir = golden_output_dir();
142 std::error_code ec;
143 std::filesystem::create_directories(out_dir, ec);
144
145 const std::filesystem::path out_file =
146 out_dir / (sanitize_filename(case_id) + ".svg");
147
148 std::ofstream out(out_file);
149 if (not out)
150 return out_file;
151
154
155 double xmin = 0.0;
156 double xmax = 1.0;
157 double ymin = 0.0;
158 double ymax = 1.0;
159 bool initialized = false;
160
161 for (size_t i = 0; i < all_points.size(); ++i)
162 {
163 const double x = all_points(i).get_x().get_d();
164 const double y = all_points(i).get_y().get_d();
165 if (not initialized)
166 {
167 xmin = xmax = x;
168 ymin = ymax = y;
169 initialized = true;
170 }
171 else
172 {
173 xmin = std::min(xmin, x);
174 xmax = std::max(xmax, x);
175 ymin = std::min(ymin, y);
176 ymax = std::max(ymax, y);
177 }
178 }
179
180 if (not initialized)
181 {
182 xmin = ymin = -1.0;
183 xmax = ymax = 1.0;
184 }
185
186 double dx = xmax - xmin;
187 double dy = ymax - ymin;
188 if (dx < 1e-9)
189 {
190 xmin -= 1.0;
191 xmax += 1.0;
192 dx = xmax - xmin;
193 }
194 if (dy < 1e-9)
195 {
196 ymin -= 1.0;
197 ymax += 1.0;
198 dy = ymax - ymin;
199 }
200
201 double pad = std::max(dx, dy) * 0.08;
202 if (pad < 1e-6)
203 pad = 1.0;
204 xmin -= pad;
205 xmax += pad;
206 ymin -= pad;
207 ymax += pad;
208 dx = xmax - xmin;
209 dy = ymax - ymin;
210
211 constexpr double width = 960.0;
212 constexpr double height = 720.0;
213 constexpr double margin = 32.0;
214 constexpr double drawable_w = width - 2.0 * margin;
215 constexpr double drawable_h = height - 2.0 * margin;
216 const double scale = std::min(drawable_w / dx, drawable_h / dy);
217 const double xoff = margin + (drawable_w - scale * dx) * 0.5;
218 const double yoff = margin + (drawable_h - scale * dy) * 0.5;
219
220 const auto map_point = [=](const ::Point & p)
221 {
222 const double x = xoff + (p.get_x().get_d() - xmin) * scale;
223 const double y = height - (yoff + (p.get_y().get_d() - ymin) * scale);
224 return std::pair<double, double>{x, y};
225 };
226
227 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
228 out << "<svg xmlns=\"http://www.w3.org/2000/svg\" "
229 << "width=\"" << static_cast<int>(width) << "\" "
230 << "height=\"" << static_cast<int>(height) << "\" "
231 << "viewBox=\"0 0 " << static_cast<int>(width) << " "
232 << static_cast<int>(height) << "\">\n";
233 out << " <rect x=\"0\" y=\"0\" width=\"" << static_cast<int>(width)
234 << "\" height=\"" << static_cast<int>(height)
235 << "\" fill=\"#ffffff\"/>\n";
236 out << " <rect x=\"1\" y=\"1\" width=\"" << static_cast<int>(width - 2)
237 << "\" height=\"" << static_cast<int>(height - 2)
238 << "\" fill=\"none\" stroke=\"#e5e7eb\" stroke-width=\"2\"/>\n";
239
240 if (not note.empty())
241 out << " <text x=\"16\" y=\"22\" font-size=\"14\" "
242 << "font-family=\"monospace\" fill=\"#111827\">"
243 << xml_escape(note) << "</text>\n";
244
245 static constexpr std::array<const char *, 8> stroke_colors = {
246 "#2563eb", "#16a34a", "#ea580c", "#7c3aed",
247 "#0f766e", "#b91c1c", "#1d4ed8", "#0369a1"
248 };
249 static constexpr std::array<const char *, 8> fill_colors = {
250 "#bfdbfe", "#bbf7d0", "#fed7aa", "#ddd6fe",
251 "#99f6e4", "#fecaca", "#c7d2fe", "#bae6fd"
252 };
253
254 out << std::fixed << std::setprecision(3);
255
256 for (size_t pi = 0; pi < scene.polygons.size(); ++pi)
257 {
258 const ::Polygon & poly = scene.polygons(pi);
259 if (poly.size() == 0)
260 continue;
261 std::ostringstream pts_stream;
262 size_t count = 0;
263 for (::Polygon::Vertex_Iterator it(poly); it.has_curr(); it.next_ne())
264 {
265 const auto [x, y] = map_point(it.get_current_vertex());
266 if (count != 0)
267 pts_stream << ' ';
268 pts_stream << x << ',' << y;
269 ++count;
270 }
271
272 if (count == 0)
273 continue;
274
275 const char * stroke = stroke_colors[pi % stroke_colors.size()];
276 const char * fill = fill_colors[pi % fill_colors.size()];
277 if (poly.is_closed() and count >= 3)
278 out << " <polygon points=\"" << pts_stream.str()
279 << "\" fill=\"" << fill
280 << "\" fill-opacity=\"0.22\" stroke=\"" << stroke
281 << "\" stroke-width=\"2\"/>\n";
282 else
283 out << " <polyline points=\"" << pts_stream.str()
284 << "\" fill=\"none\" stroke=\"" << stroke
285 << "\" stroke-width=\"2\"/>\n";
286
287 for (::Polygon::Vertex_Iterator it(poly); it.has_curr(); it.next_ne())
288 {
289 const auto [vx, vy] = map_point(it.get_current_vertex());
290 out << " <circle cx=\"" << vx << "\" cy=\"" << vy
291 << "\" r=\"3.0\" fill=\"" << stroke
292 << "\" stroke=\"#ffffff\" stroke-width=\"1\"/>\n";
293 }
294 }
295
296 for (size_t si = 0; si < scene.segments.size(); ++si)
297 {
298 const ::Segment & seg = scene.segments(si);
299 const auto [x1, y1] = map_point(seg.get_src_point());
300 const auto [x2, y2] = map_point(seg.get_tgt_point());
301 out << " <line x1=\"" << x1 << "\" y1=\"" << y1
302 << "\" x2=\"" << x2 << "\" y2=\"" << y2
303 << "\" stroke=\"#4b5563\" stroke-width=\"2.0\"/>\n";
304 }
305
306 for (size_t i = 0; i < scene.points.size(); ++i)
307 {
308 const auto [x, y] = map_point(scene.points(i));
309 out << " <circle cx=\"" << x << "\" cy=\"" << y
310 << "\" r=\"3.2\" fill=\"#111827\" stroke=\"#ffffff\" "
311 << "stroke-width=\"0.9\"/>\n";
312 }
313
314 for (size_t i = 0; i < scene.highlighted_points.size(); ++i)
315 {
316 const auto [x, y] = map_point(scene.highlighted_points(i));
317 out << " <circle cx=\"" << x << "\" cy=\"" << y
318 << "\" r=\"4.3\" fill=\"#dc2626\" stroke=\"#ffffff\" "
319 << "stroke-width=\"1.2\"/>\n";
320 }
321
322 out << " <text x=\"16\" y=\"" << static_cast<int>(height - 14)
323 << "\" font-size=\"12\" font-family=\"monospace\" fill=\"#4b5563\">"
324 << xml_escape(case_id) << "</text>\n";
325 out << "</svg>\n";
326
327 return out_file;
328}
329
330} // namespace Aleph::TestVisual
331
332#endif // ALEPH_TEST_GEOMETRY_VISUAL_GOLDEN_H
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:315
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_y1_function > > y1(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4103
static int initialized
Definition mpfr_mul_d.c:4
static mpfr_t y
Definition mpfr_mul_d.c:3
void add_polygon_vertices(SvgScene &scene, const ::Polygon &poly, const bool as_highlight=false)
std::filesystem::path emit_case_svg(const std::string &case_id, const SvgScene &scene, const std::string &note="")
std::string sanitize_filename(const std::string &raw)
std::string xml_escape(const std::string &s)
std::filesystem::path golden_output_dir()
void collect_scene_points(const SvgScene &scene, ::Array<::Point > &all_points)
and
Check uniqueness with explicit hash + equality functors.
void fill(Itor beg, const Itor &end, const T &value)
Fill a range with a value.
Definition ahAlgo.H:707
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
2D point and geometric utilities.
2D polygon representation and geometric operations.
Iterator over the vertices of a polygon.
Definition polygon.H:489
Dynamic array container with automatic resizing.