Branch data Line data Source code
1 : : /* Support for tabular/grid-based content.
2 : : Copyright (C) 2023-2025 Free Software Foundation, Inc.
3 : : Contributed by David Malcolm <dmalcolm@redhat.com>.
4 : :
5 : : This file is part of GCC.
6 : :
7 : : GCC is free software; you can redistribute it and/or modify it under
8 : : the terms of the GNU General Public License as published by the Free
9 : : Software Foundation; either version 3, or (at your option) any later
10 : : version.
11 : :
12 : : GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13 : : WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 : : FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 : : for more details.
16 : :
17 : : You should have received a copy of the GNU General Public License
18 : : along with GCC; see the file COPYING3. If not see
19 : : <http://www.gnu.org/licenses/>. */
20 : :
21 : : #include "config.h"
22 : : #define INCLUDE_VECTOR
23 : : #include "system.h"
24 : : #include "coretypes.h"
25 : : #include "pretty-print.h"
26 : : #include "diagnostic.h"
27 : : #include "selftest.h"
28 : : #include "text-art/selftests.h"
29 : : #include "text-art/table.h"
30 : :
31 : : using namespace text_art;
32 : :
33 : : /* class text_art::table_cell_content. */
34 : :
35 : 2008 : table_cell_content::table_cell_content (styled_string &&s)
36 : 2008 : : m_str (std::move (s)),
37 : : /* We assume here that the content occupies a single canvas row. */
38 : 2008 : m_size (m_str.calc_canvas_width (), 1)
39 : : {
40 : 2008 : }
41 : :
42 : : void
43 : 2327 : table_cell_content::paint_to_canvas (canvas &canvas,
44 : : canvas::coord_t top_left) const
45 : : {
46 : 2327 : canvas.paint_text (top_left, m_str);
47 : 2327 : }
48 : :
49 : : /* struct text_art::table_dimension_sizes. */
50 : :
51 : 365 : table_dimension_sizes::table_dimension_sizes (unsigned num)
52 : 365 : : m_requirements (num, 0)
53 : : {
54 : 365 : }
55 : :
56 : : /* class text_art::table::cell_placement. */
57 : :
58 : : void
59 : 2327 : table::cell_placement::paint_cell_contents_to_canvas(canvas &canvas,
60 : : canvas::coord_t offset,
61 : : const table_geometry &tg) const
62 : : {
63 : 2327 : const canvas::size_t req_canvas_size = get_min_canvas_size ();
64 : 2327 : const canvas::size_t alloc_canvas_size = tg.get_canvas_size (m_rect);
65 : 2327 : gcc_assert (req_canvas_size.w <= alloc_canvas_size.w);
66 : 2327 : gcc_assert (req_canvas_size.h <= alloc_canvas_size.h);
67 : 2327 : const int x_padding = alloc_canvas_size.w - req_canvas_size.w;
68 : 2327 : const int y_padding = alloc_canvas_size.h - req_canvas_size.h;
69 : 2327 : const table::coord_t table_top_left = m_rect.m_top_left;
70 : 2327 : const canvas::coord_t canvas_top_left = tg.table_to_canvas (table_top_left);
71 : :
72 : 2327 : gcc_assert (x_padding >= 0);
73 : 2327 : int x_align_offset;
74 : 2327 : switch (m_x_align)
75 : : {
76 : 0 : default:
77 : 0 : gcc_unreachable ();
78 : : case x_align::LEFT:
79 : : x_align_offset = 0;
80 : : break;
81 : 2303 : case x_align::CENTER:
82 : 2303 : x_align_offset = x_padding / 2;
83 : 2303 : break;
84 : 12 : case x_align::RIGHT:
85 : 12 : x_align_offset = x_padding;
86 : 12 : break;
87 : : }
88 : :
89 : 2327 : gcc_assert (y_padding >= 0);
90 : 2327 : int y_align_offset;
91 : 2327 : switch (m_y_align)
92 : : {
93 : 0 : default:
94 : 0 : gcc_unreachable ();
95 : : case y_align::TOP:
96 : : y_align_offset = 0;
97 : : break;
98 : 2303 : case y_align::CENTER:
99 : 2303 : y_align_offset = y_padding / 2;
100 : 2303 : break;
101 : 12 : case y_align::BOTTOM:
102 : 12 : y_align_offset = y_padding;
103 : 12 : break;
104 : : }
105 : 2327 : const canvas::coord_t content_rel_coord
106 : 2327 : (canvas_top_left.x + 1 + x_align_offset,
107 : 2327 : canvas_top_left.y + 1 + y_align_offset);
108 : 2327 : m_content.paint_to_canvas (canvas, offset + content_rel_coord);
109 : 2327 : }
110 : :
111 : : /* class text_art::table. */
112 : :
113 : :
114 : 215 : table::table (size_t size)
115 : 215 : : m_size (size),
116 : 215 : m_placements (),
117 : 215 : m_occupancy (size)
118 : : {
119 : 215 : m_occupancy.fill (-1);
120 : 215 : }
121 : :
122 : : void
123 : 738 : table::set_cell (coord_t coord,
124 : : table_cell_content &&content,
125 : : enum x_align x_align,
126 : : enum y_align y_align)
127 : : {
128 : 738 : set_cell_span (rect_t (coord, table::size_t (1, 1)),
129 : : std::move (content), x_align, y_align);
130 : 738 : }
131 : :
132 : : void
133 : 2090 : table::set_cell_span (rect_t span,
134 : : table_cell_content &&content,
135 : : enum x_align x_align,
136 : : enum y_align y_align)
137 : : {
138 : 2090 : gcc_assert (span.m_size.w > 0);
139 : 2090 : gcc_assert (span.m_size.h > 0);
140 : 2090 : int placement_idx = m_placements.size ();
141 : 2090 : m_placements.emplace_back (cell_placement (span, std::move (content),
142 : 2090 : x_align, y_align));
143 : 4355 : for (int y = span.get_min_y (); y < span.get_next_y (); y++)
144 : 8249 : for (int x = span.get_min_x (); x < span.get_next_x (); x++)
145 : : {
146 : 5984 : gcc_assert (m_occupancy.get (coord_t (x, y)) == -1);
147 : 5984 : m_occupancy.set (coord_t (x, y), placement_idx);
148 : : }
149 : 2090 : }
150 : :
151 : : /* If SPAN is unoccuped, set it to CONTENT.
152 : : Otherwise, discard CONTENT. */
153 : :
154 : : void
155 : 108 : table::maybe_set_cell_span (rect_t span,
156 : : table_cell_content &&content,
157 : : enum x_align x_align,
158 : : enum y_align y_align)
159 : : {
160 : 108 : gcc_assert (span.m_size.w > 0);
161 : 108 : gcc_assert (span.m_size.h > 0);
162 : 194 : for (int y = span.get_min_y (); y < span.get_next_y (); y++)
163 : 194 : for (int x = span.get_min_x (); x < span.get_next_x (); x++)
164 : : {
165 : 108 : if (m_occupancy.get (coord_t (x, y)) != -1)
166 : : return;
167 : : }
168 : 86 : set_cell_span (span, std::move (content), x_align, y_align);
169 : : }
170 : :
171 : : canvas
172 : 76 : table::to_canvas (const theme &theme, const style_manager &sm) const
173 : : {
174 : 76 : table_dimension_sizes col_widths (m_size.w);
175 : 76 : table_dimension_sizes row_heights (m_size.h);
176 : 76 : table_cell_sizes cell_sizes (col_widths, row_heights);
177 : 76 : cell_sizes.pass_1 (*this);
178 : 76 : cell_sizes.pass_2 (*this);
179 : 76 : table_geometry tg (*this, cell_sizes);
180 : 76 : canvas canvas (tg.get_canvas_size (), sm);
181 : 76 : paint_to_canvas (canvas, canvas::coord_t (0, 0), tg, theme);
182 : 76 : return canvas;
183 : 76 : }
184 : :
185 : : void
186 : 221 : table::paint_to_canvas (canvas &canvas,
187 : : canvas::coord_t offset,
188 : : const table_geometry &tg,
189 : : const theme &theme) const
190 : : {
191 : 221 : canvas.fill (canvas::rect_t (offset, tg.get_canvas_size ()),
192 : 221 : styled_unichar (' '));
193 : 221 : paint_cell_borders_to_canvas (canvas, offset, tg, theme);
194 : 221 : paint_cell_contents_to_canvas (canvas, offset, tg);
195 : 221 : }
196 : :
197 : : /* Print this table to stderr. */
198 : :
199 : : DEBUG_FUNCTION void
200 : 0 : table::debug () const
201 : : {
202 : : /* Use a temporary style manager.
203 : : Styles in the table will be meaningless, so
204 : : print the canvas with styling disabled. */
205 : 0 : style_manager sm;
206 : 0 : canvas canvas (to_canvas (unicode_theme (), sm));
207 : 0 : canvas.debug (false);
208 : 0 : }
209 : :
210 : : /* Move OTHER's content this table, starting at OFFSET. */
211 : :
212 : : void
213 : 24 : table::add_other_table (table &&other,
214 : : table::coord_t offset)
215 : : {
216 : 252 : for (auto &&placement : other.m_placements)
217 : : {
218 : 228 : set_cell_span (placement.m_rect + offset,
219 : 228 : std::move (placement.m_content),
220 : : placement.m_x_align,
221 : : placement.m_y_align);
222 : : }
223 : 24 : }
224 : :
225 : : const table::cell_placement *
226 : 362 : table::get_placement_at (coord_t coord) const
227 : : {
228 : 362 : const int placement_idx = m_occupancy.get (coord);
229 : 362 : if (placement_idx == -1)
230 : : return nullptr;
231 : 142 : return &m_placements[placement_idx];
232 : : }
233 : :
234 : : int
235 : 42528 : table::get_occupancy_safe (coord_t coord) const
236 : : {
237 : 42528 : if (coord.x < 0)
238 : : return -1;
239 : 40882 : if (coord.x >= m_size.w)
240 : : return -1;
241 : 39236 : if (coord.y < 0)
242 : : return -1;
243 : 35530 : if (coord.y >= m_size.h)
244 : : return -1;
245 : 31824 : return m_occupancy.get (coord);
246 : : }
247 : :
248 : : /* Determine if the "?" edges need borders for table cell D
249 : : in the following, for the directions relative to "X", based
250 : : on whether each of table cell boundaries AB, CD, AC, and BD
251 : : are boundaries between cell spans:
252 : :
253 : : # up?
254 : : # +-----+-----+
255 : : # | |
256 : : # | ? |
257 : : # | A ? B |
258 : : # | ? |
259 : : # | |
260 : : # left?+ ??? X ??? + right?
261 : : # | |
262 : : # | ? |
263 : : # | C ? D |
264 : : # | ? |
265 : : # | |
266 : : # +-----+-----+
267 : : # down?
268 : : */
269 : :
270 : : directions
271 : 10632 : table::get_connections (int table_x, int table_y) const
272 : : {
273 : 10632 : int cell_a = get_occupancy_safe (coord_t (table_x - 1, table_y - 1));
274 : 10632 : int cell_b = get_occupancy_safe (coord_t (table_x, table_y - 1));
275 : 10632 : int cell_c = get_occupancy_safe (coord_t (table_x - 1, table_y));
276 : 10632 : int cell_d = get_occupancy_safe (coord_t (table_x, table_y));
277 : 10632 : const bool up = (cell_a != cell_b);
278 : 10632 : const bool down = (cell_c != cell_d);
279 : 10632 : const bool left = (cell_a != cell_c);
280 : 10632 : const bool right = (cell_b != cell_d);
281 : 10632 : return directions (up, down, left, right);
282 : : }
283 : :
284 : : /* Paint the grid lines.
285 : :
286 : : Consider painting
287 : : - a grid of cells,
288 : : - plus a right-hand border
289 : : - and a bottom border
290 : :
291 : : Then we need to paint to the canvas like this:
292 : :
293 : : # PER-TABLE-COLUMN R BORDER
294 : : # +-------------------+ +-----+
295 : : #
296 : : # TABLE CELL WIDTH (in canvas units)
297 : : # +-------------+
298 : : # . . . . . . .
299 : : # ...+-----+-----+.+-----+...+-----+ +
300 : : # | U | |.| | | U | |
301 : : # | U | |.| | | U | |
302 : : # |LL+RR|RRRRR|.|RRRRR| |LL+ | |
303 : : # | D | |.| | | D | |
304 : : # | D | |.| | | D | |
305 : : # ...+-----+-----+.+-----+...+-----+ |
306 : : # ..................... ...... +-- PER-TABLE-ROW
307 : : # ...+-----+-----+.+-----+...+-----+ | +
308 : : # | D | |.| | | D | | |
309 : : # | D | |.| | | D | | |
310 : : # | D | |.| | | D | | +---- TABLE CELL HEIGHT (in canvas units)
311 : : # | D | |.| | | D | | |
312 : : # | D | |.| | | D | | |
313 : : # ...+-----+-----+.+-----+...+-----+ + +
314 : : # . . . . . .
315 : : # ...+-----+-----+.+-----+...+-----+ +
316 : : # | D | |.| | | U | |
317 : : # | D | |.| | | U | |
318 : : # |LL+RR|RRRRR|.|RRRRR| |LL+ | | BOTTOM BORDER
319 : : # | | |.| | | | |
320 : : # | | |.| | | | |
321 : : # ...+-----+-----+.+-----+...+-----+ +
322 : :
323 : : where each:
324 : :
325 : : # +-----+
326 : : # | |
327 : : # | |
328 : : # | |
329 : : # | |
330 : : # | |
331 : : # +-----+
332 : :
333 : : is a canvas cell, and the U, L, R, D express the connections
334 : : that are present with neighboring table cells. These affect
335 : : the kinds of borders that we draw for a particular table cell. */
336 : :
337 : : void
338 : 221 : table::paint_cell_borders_to_canvas (canvas &canvas,
339 : : canvas::coord_t offset,
340 : : const table_geometry &tg,
341 : : const theme &theme) const
342 : : {
343 : : /* The per-table-cell left and top borders are either drawn or not,
344 : : but if they are, they aren't affected by per-table-cell connections. */
345 : 221 : const canvas::cell_t left_border
346 : 221 : = theme.get_line_art (directions (true, /* up */
347 : : true, /* down */
348 : : false, /* left */
349 : 221 : false /* right */));
350 : 221 : const canvas::cell_t top_border
351 : 221 : = theme.get_line_art (directions (false, /* up */
352 : : false, /* down */
353 : : true, /* left */
354 : 221 : true)); /* right */
355 : 823 : for (int table_y = 0; table_y < m_size.h; table_y++)
356 : : {
357 : 602 : const int canvas_y = tg.table_y_to_canvas_y (table_y);
358 : 8558 : for (int table_x = 0; table_x < m_size.w; table_x++)
359 : : {
360 : 7956 : canvas::coord_t canvas_top_left
361 : 7956 : = tg.table_to_canvas(table::coord_t (table_x, table_y));
362 : :
363 : 7956 : const directions c (get_connections (table_x, table_y));
364 : :
365 : : /* Paint top-left corner of border, if any. */
366 : 7956 : canvas.paint (offset + canvas_top_left,
367 : 7956 : theme.get_line_art (c));
368 : :
369 : : /* Paint remainder of left border of cell, if any.
370 : : We assume here that the content occupies a single canvas row. */
371 : 7956 : if (c.m_down)
372 : 5484 : canvas.paint (offset + canvas::coord_t (canvas_top_left.x,
373 : 2742 : canvas_y + 1),
374 : : left_border);
375 : :
376 : : /* Paint remainder of top border of cell, if any. */
377 : 7956 : if (c.m_right)
378 : : {
379 : 6002 : const int col_width = tg.get_col_width (table_x);
380 : 27147 : for (int x_offset = 0; x_offset < col_width; x_offset++)
381 : : {
382 : 21145 : const int canvas_x = canvas_top_left.x + 1 + x_offset;
383 : 21145 : canvas.paint (offset + canvas::coord_t (canvas_x, canvas_y),
384 : : top_border);
385 : : }
386 : : }
387 : : }
388 : :
389 : : /* Paint right-hand border of row. */
390 : 602 : const int table_x = m_size.w;
391 : 602 : const int canvas_x = tg.table_x_to_canvas_x (table_x);
392 : 602 : const directions c (get_connections (m_size.w, table_y));
393 : 602 : canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y),
394 : 602 : theme.get_line_art (directions (c.m_up,
395 : 602 : c.m_down,
396 : 602 : c.m_left,
397 : 602 : false))); /* right */
398 : : /* We assume here that the content occupies a single canvas row. */
399 : 602 : canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y + 1),
400 : 602 : theme.get_line_art (directions (c.m_down, /* up */
401 : : c.m_down, /* down */
402 : : false, /* left */
403 : 602 : false))); /* right */
404 : : }
405 : :
406 : : /* Draw bottom border of table. */
407 : 221 : {
408 : 221 : const int canvas_y = tg.get_canvas_size ().h - 1;
409 : 2074 : for (int table_x = 0; table_x < m_size.w; table_x++)
410 : : {
411 : 1853 : const directions c (get_connections (table_x, m_size.h));
412 : 1853 : const int left_canvas_x = tg.table_x_to_canvas_x (table_x);
413 : 1853 : canvas.paint (offset + canvas::coord_t (left_canvas_x, canvas_y),
414 : 1853 : theme.get_line_art (directions (c.m_up,
415 : : false, /* down */
416 : 1853 : c.m_left,
417 : 1853 : c.m_right)));
418 : 1853 : const int col_width = tg.get_col_width (table_x);
419 : 12917 : for (int x_offset = 0; x_offset < col_width; x_offset++)
420 : : {
421 : 11064 : const int canvas_x = left_canvas_x + 1 + x_offset;
422 : 11064 : canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y),
423 : 22128 : theme.get_line_art (directions (false, // up
424 : : false, // down
425 : : c.m_right, // left
426 : 11064 : c.m_right))); // right
427 : : }
428 : : }
429 : :
430 : : /* Bottom-right corner of table. */
431 : 221 : const int table_x = m_size.w;
432 : 221 : const int canvas_x = tg.table_x_to_canvas_x (table_x);
433 : 221 : const directions c (get_connections (m_size.w, m_size.h));
434 : 221 : canvas.paint (offset + canvas::coord_t (canvas_x, canvas_y),
435 : 442 : theme.get_line_art (directions (c.m_up, // up
436 : : false, // down
437 : 221 : c.m_left, // left
438 : 221 : false))); // right
439 : : }
440 : 221 : }
441 : :
442 : : void
443 : 221 : table::paint_cell_contents_to_canvas(canvas &canvas,
444 : : canvas::coord_t offset,
445 : : const table_geometry &tg) const
446 : : {
447 : 2548 : for (auto &placement : m_placements)
448 : 2327 : placement.paint_cell_contents_to_canvas (canvas, offset, tg);
449 : 221 : }
450 : :
451 : : /* class table_cell_sizes. */
452 : :
453 : : /* Consider 1x1 cells. */
454 : :
455 : : void
456 : 217 : table_cell_sizes::pass_1 (const table &table)
457 : : {
458 : 2508 : for (auto &placement : table.m_placements)
459 : 4117 : if (placement.one_by_one_p ())
460 : : {
461 : 1783 : canvas::size_t canvas_size (placement.get_min_canvas_size ());
462 : 1783 : table::coord_t table_coord (placement.m_rect.m_top_left);
463 : 1783 : m_col_widths.require (table_coord.x, canvas_size.w);
464 : 2232 : m_row_heights.require (table_coord.y, canvas_size.h);
465 : : }
466 : 217 : }
467 : :
468 : : /* Consider cells that span more than one row or column. */
469 : :
470 : : void
471 : 217 : table_cell_sizes::pass_2 (const table &table)
472 : : {
473 : 2508 : for (auto &placement : table.m_placements)
474 : 4582 : if (!placement.one_by_one_p ())
475 : : {
476 : 508 : const canvas::size_t req_canvas_size (placement.get_min_canvas_size ());
477 : 508 : const canvas::size_t current_canvas_size
478 : 508 : = get_canvas_size (placement.m_rect);
479 : : /* Grow columns as necessary. */
480 : 508 : if (req_canvas_size.w > current_canvas_size.w)
481 : : {
482 : : /* Spread the deficit amongst the columns. */
483 : 88 : int deficit = req_canvas_size.w - current_canvas_size.w;
484 : 88 : const int per_col = deficit / placement.m_rect.m_size.w;
485 : 88 : for (int table_x = placement.m_rect.get_min_x ();
486 : 327 : table_x < placement.m_rect.get_next_x ();
487 : : table_x++)
488 : : {
489 : 239 : m_col_widths.m_requirements[table_x] += per_col;
490 : 239 : deficit -= per_col;
491 : : }
492 : : /* Make sure we allocate all of the deficit. */
493 : 88 : if (deficit > 0)
494 : : {
495 : 45 : const int table_x = placement.m_rect.get_max_x ();
496 : 45 : m_col_widths.m_requirements[table_x] += deficit;
497 : : }
498 : : }
499 : : /* Grow rows as necessary. */
500 : 508 : if (req_canvas_size.h > current_canvas_size.h)
501 : : {
502 : : /* Spread the deficit amongst the rows. */
503 : 105 : int deficit = req_canvas_size.h - current_canvas_size.h;
504 : 105 : const int per_row = deficit / placement.m_rect.m_size.h;
505 : 105 : for (int table_y = placement.m_rect.get_min_y ();
506 : 210 : table_y < placement.m_rect.get_next_y ();
507 : : table_y++)
508 : : {
509 : 105 : m_row_heights.m_requirements[table_y] += per_row;
510 : 105 : deficit -= per_row;
511 : : }
512 : : /* Make sure we allocate all of the deficit. */
513 : 105 : if (deficit > 0)
514 : : {
515 : 0 : const int table_y = placement.m_rect.get_max_y ();
516 : 0 : m_row_heights.m_requirements[table_y] += deficit;
517 : : }
518 : : }
519 : : }
520 : 217 : }
521 : :
522 : : canvas::size_t
523 : 2835 : table_cell_sizes::get_canvas_size (const table::rect_t &rect) const
524 : : {
525 : 2835 : canvas::size_t result (0, 0);
526 : 12836 : for (int table_x = rect.get_min_x ();
527 : 12836 : table_x < rect.get_next_x ();
528 : : table_x ++)
529 : 10001 : result.w += m_col_widths.m_requirements[table_x];
530 : 6072 : for (int table_y = rect.get_min_y ();
531 : 6072 : table_y < rect.get_next_y ();
532 : : table_y ++)
533 : 3237 : result.h += m_row_heights.m_requirements[table_y];
534 : : /* Allow space for the borders. */
535 : 2835 : result.w += rect.m_size.w - 1;
536 : 2835 : result.h += rect.m_size.h - 1;
537 : 2835 : return result;
538 : : }
539 : :
540 : : /* class text_art::table_geometry. */
541 : :
542 : 217 : table_geometry::table_geometry (const table &table, table_cell_sizes &cell_sizes)
543 : 217 : : m_cell_sizes (cell_sizes),
544 : 217 : m_canvas_size (canvas::size_t (0, 0)),
545 : 217 : m_col_start_x (table.get_size ().w),
546 : 217 : m_row_start_y (table.get_size ().h)
547 : : {
548 : 217 : recalc_coords ();
549 : 217 : }
550 : :
551 : : void
552 : 358 : table_geometry::recalc_coords ()
553 : : {
554 : : /* Start canvas column of table cell, including leading border. */
555 : 358 : m_col_start_x.clear ();
556 : 358 : int iter_canvas_x = 0;
557 : 3328 : for (auto w : m_cell_sizes.m_col_widths.m_requirements)
558 : : {
559 : 2970 : m_col_start_x.push_back (iter_canvas_x);
560 : 2970 : iter_canvas_x += w + 1;
561 : : }
562 : :
563 : : /* Start canvas row of table cell, including leading border. */
564 : 358 : m_row_start_y.clear ();
565 : 358 : int iter_canvas_y = 0;
566 : 1194 : for (auto h : m_cell_sizes.m_row_heights.m_requirements)
567 : : {
568 : 836 : m_row_start_y.push_back (iter_canvas_y);
569 : 836 : iter_canvas_y += h + 1;
570 : : }
571 : :
572 : 358 : m_canvas_size = canvas::size_t (iter_canvas_x + 1,
573 : 358 : iter_canvas_y + 1);
574 : 358 : }
575 : :
576 : : /* Get the TL corner of the table cell at TABLE_COORD
577 : : in canvas coords (including the border). */
578 : :
579 : : canvas::coord_t
580 : 10283 : table_geometry::table_to_canvas (table::coord_t table_coord) const
581 : : {
582 : 10283 : return canvas::coord_t (table_x_to_canvas_x (table_coord.x),
583 : 10283 : table_y_to_canvas_y (table_coord.y));
584 : : }
585 : :
586 : : /* Get the left border of the table cell at column TABLE_X
587 : : in canvas coords (including the border). */
588 : :
589 : : int
590 : 12959 : table_geometry::table_x_to_canvas_x (int table_x) const
591 : : {
592 : : /* Allow one beyond the end, for the right-hand border of the table. */
593 : 12959 : if (table_x == (int)m_col_start_x.size ())
594 : 823 : return m_canvas_size.w - 1;
595 : 12136 : return m_col_start_x[table_x];
596 : : }
597 : :
598 : : /* Get the top border of the table cell at column TABLE_Y
599 : : in canvas coords (including the border). */
600 : :
601 : : int
602 : 10885 : table_geometry::table_y_to_canvas_y (int table_y) const
603 : : {
604 : : /* Allow one beyond the end, for the right-hand border of the table. */
605 : 10885 : if (table_y == (int)m_row_start_y.size ())
606 : 0 : return m_canvas_size.h - 1;
607 : 10885 : return m_row_start_y[table_y];
608 : : }
609 : :
610 : : /* class text_art::simple_table_geometry. */
611 : :
612 : 4 : simple_table_geometry::simple_table_geometry (const table &table)
613 : 4 : : m_col_widths (table.get_size ().w),
614 : 4 : m_row_heights (table.get_size ().h),
615 : 4 : m_cell_sizes (m_col_widths, m_row_heights),
616 : 4 : m_tg (table, m_cell_sizes)
617 : : {
618 : 4 : m_cell_sizes.pass_1 (table);
619 : 4 : m_cell_sizes.pass_2 (table);
620 : 4 : m_tg.recalc_coords ();
621 : 4 : }
622 : :
623 : : #if CHECKING_P
624 : :
625 : : namespace selftest {
626 : :
627 : : static void
628 : 4 : test_tic_tac_toe ()
629 : : {
630 : 4 : style_manager sm;
631 : 4 : table t (table::size_t (3, 3));
632 : 4 : t.set_cell (table::coord_t (0, 0), styled_string (sm, "X"));
633 : 4 : t.set_cell (table::coord_t (1, 0), styled_string (sm, ""));
634 : 4 : t.set_cell (table::coord_t (2, 0), styled_string (sm, ""));
635 : 4 : t.set_cell (table::coord_t (0, 1), styled_string (sm, "O"));
636 : 4 : t.set_cell (table::coord_t (1, 1), styled_string (sm, "O"));
637 : 4 : t.set_cell (table::coord_t (2, 1), styled_string (sm, ""));
638 : 4 : t.set_cell (table::coord_t (0, 2), styled_string (sm, "X"));
639 : 4 : t.set_cell (table::coord_t (1, 2), styled_string (sm, ""));
640 : 4 : t.set_cell (table::coord_t (2, 2), styled_string (sm, "O"));
641 : :
642 : 4 : {
643 : 4 : canvas canvas (t.to_canvas (ascii_theme (), sm));
644 : 4 : ASSERT_CANVAS_STREQ
645 : : (canvas, false,
646 : : ("+-+-+-+\n"
647 : : "|X| | |\n"
648 : : "+-+-+-+\n"
649 : : "|O|O| |\n"
650 : : "+-+-+-+\n"
651 : : "|X| |O|\n"
652 : : "+-+-+-+\n"));
653 : 4 : }
654 : :
655 : 4 : {
656 : 4 : canvas canvas (t.to_canvas (unicode_theme (), sm));
657 : 4 : ASSERT_CANVAS_STREQ
658 : : (canvas, false,
659 : : // FIXME: are we allowed unicode chars in string literals in our source?
660 : : ("┌─┬─┬─┐\n"
661 : : "│X│ │ │\n"
662 : : "├─┼─┼─┤\n"
663 : : "│O│O│ │\n"
664 : : "├─┼─┼─┤\n"
665 : : "│X│ │O│\n"
666 : : "└─┴─┴─┘\n"));
667 : 4 : }
668 : 4 : }
669 : :
670 : : static table
671 : 8 : make_text_table ()
672 : : {
673 : 8 : style_manager sm;
674 : 8 : table t (table::size_t (3, 3));
675 : 8 : t.set_cell (table::coord_t (0, 0), styled_string (sm, "top left"));
676 : 8 : t.set_cell (table::coord_t (1, 0), styled_string (sm, "top middle"));
677 : 8 : t.set_cell (table::coord_t (2, 0), styled_string (sm, "top right"));
678 : 8 : t.set_cell (table::coord_t (0, 1), styled_string (sm, "middle left"));
679 : 8 : t.set_cell (table::coord_t (1, 1), styled_string (sm, "middle middle"));
680 : 8 : t.set_cell (table::coord_t (2, 1), styled_string (sm, "middle right"));
681 : 8 : t.set_cell (table::coord_t (0, 2), styled_string (sm, "bottom left"));
682 : 8 : t.set_cell (table::coord_t (1, 2), styled_string (sm, "bottom middle"));
683 : 8 : t.set_cell (table::coord_t (2, 2), styled_string (sm, "bottom right"));
684 : 8 : return t;
685 : 8 : }
686 : :
687 : : static void
688 : 4 : test_text_table ()
689 : : {
690 : 4 : style_manager sm;
691 : 4 : table table = make_text_table ();
692 : 4 : {
693 : 4 : canvas canvas (table.to_canvas (ascii_theme(), sm));
694 : 4 : ASSERT_CANVAS_STREQ
695 : : (canvas, false,
696 : : ("+-----------+-------------+------------+\n"
697 : : "| top left | top middle | top right |\n"
698 : : "+-----------+-------------+------------+\n"
699 : : "|middle left|middle middle|middle right|\n"
700 : : "+-----------+-------------+------------+\n"
701 : : "|bottom left|bottom middle|bottom right|\n"
702 : : "+-----------+-------------+------------+\n"));
703 : 4 : }
704 : 4 : {
705 : 4 : canvas canvas (table.to_canvas (unicode_theme(), sm));
706 : 4 : ASSERT_CANVAS_STREQ
707 : : (canvas, false,
708 : : // FIXME: are we allowed unicode chars in string literals in our source?
709 : : ("┌───────────┬─────────────┬────────────┐\n"
710 : : "│ top left │ top middle │ top right │\n"
711 : : "├───────────┼─────────────┼────────────┤\n"
712 : : "│middle left│middle middle│middle right│\n"
713 : : "├───────────┼─────────────┼────────────┤\n"
714 : : "│bottom left│bottom middle│bottom right│\n"
715 : : "└───────────┴─────────────┴────────────┘\n"));
716 : 4 : }
717 : 4 : }
718 : :
719 : : static void
720 : 4 : test_offset_table ()
721 : : {
722 : 4 : style_manager sm;
723 : 4 : table table = make_text_table ();
724 : 4 : simple_table_geometry stg (table);
725 : 4 : const canvas::size_t tcs = stg.m_tg.get_canvas_size();
726 : 4 : {
727 : 4 : canvas canvas (canvas::size_t (tcs.w + 5, tcs.h + 5), sm);
728 : 4 : canvas.debug_fill ();
729 : 4 : table.paint_to_canvas (canvas, canvas::coord_t (3, 3),
730 : : stg.m_tg,
731 : 4 : ascii_theme());
732 : 4 : ASSERT_CANVAS_STREQ
733 : : (canvas, false,
734 : : ("*********************************************\n"
735 : : "*********************************************\n"
736 : : "*********************************************\n"
737 : : "***+-----------+-------------+------------+**\n"
738 : : "***| top left | top middle | top right |**\n"
739 : : "***+-----------+-------------+------------+**\n"
740 : : "***|middle left|middle middle|middle right|**\n"
741 : : "***+-----------+-------------+------------+**\n"
742 : : "***|bottom left|bottom middle|bottom right|**\n"
743 : : "***+-----------+-------------+------------+**\n"
744 : : "*********************************************\n"
745 : : "*********************************************\n"));
746 : 4 : }
747 : 4 : {
748 : 4 : canvas canvas (canvas::size_t (tcs.w + 5, tcs.h + 5), sm);
749 : 4 : canvas.debug_fill ();
750 : 4 : table.paint_to_canvas (canvas, canvas::coord_t (3, 3),
751 : : stg.m_tg,
752 : 4 : unicode_theme());
753 : 4 : ASSERT_CANVAS_STREQ
754 : : (canvas, false,
755 : : // FIXME: are we allowed unicode chars in string literals in our source?
756 : : ("*********************************************\n"
757 : : "*********************************************\n"
758 : : "*********************************************\n"
759 : : "***┌───────────┬─────────────┬────────────┐**\n"
760 : : "***│ top left │ top middle │ top right │**\n"
761 : : "***├───────────┼─────────────┼────────────┤**\n"
762 : : "***│middle left│middle middle│middle right│**\n"
763 : : "***├───────────┼─────────────┼────────────┤**\n"
764 : : "***│bottom left│bottom middle│bottom right│**\n"
765 : : "***└───────────┴─────────────┴────────────┘**\n"
766 : : "*********************************************\n"
767 : : "*********************************************\n"));
768 : 4 : }
769 : 8 : }
770 : :
771 : : #define ASSERT_TABLE_CELL_STREQ(TABLE, TABLE_X, TABLE_Y, EXPECTED_STR) \
772 : : SELFTEST_BEGIN_STMT \
773 : : table::coord_t coord ((TABLE_X), (TABLE_Y)); \
774 : : const table::cell_placement *cp = (TABLE).get_placement_at (coord); \
775 : : ASSERT_NE (cp, nullptr); \
776 : : ASSERT_EQ (cp->get_content (), styled_string (sm, EXPECTED_STR)); \
777 : : SELFTEST_END_STMT
778 : :
779 : : #define ASSERT_TABLE_NULL_CELL(TABLE, TABLE_X, TABLE_Y) \
780 : : SELFTEST_BEGIN_STMT \
781 : : table::coord_t coord ((TABLE_X), (TABLE_Y)); \
782 : : const table::cell_placement *cp = (TABLE).get_placement_at (coord); \
783 : : ASSERT_EQ (cp, nullptr); \
784 : : SELFTEST_END_STMT
785 : :
786 : : static void
787 : 4 : test_spans ()
788 : : {
789 : 4 : style_manager sm;
790 : 4 : table table (table::size_t (3, 3));
791 : 4 : table.set_cell_span (table::rect_t (table::coord_t (0, 0),
792 : 4 : table::size_t (3, 1)),
793 : 8 : styled_string (sm, "ABC"));
794 : 4 : table.set_cell_span (table::rect_t (table::coord_t (0, 1),
795 : 4 : table::size_t (2, 1)),
796 : 8 : styled_string (sm, "DE"));
797 : 4 : table.set_cell_span (table::rect_t (table::coord_t (2, 1),
798 : 4 : table::size_t (1, 1)),
799 : 8 : styled_string (sm, "F"));
800 : 4 : table.set_cell (table::coord_t (0, 2), styled_string (sm, "G"));
801 : 4 : table.set_cell (table::coord_t (1, 2), styled_string (sm, "H"));
802 : 4 : table.set_cell (table::coord_t (2, 2), styled_string (sm, "I"));
803 : 4 : {
804 : 4 : canvas canvas (table.to_canvas (ascii_theme(), sm));
805 : 4 : ASSERT_CANVAS_STREQ
806 : : (canvas, false,
807 : : ("+-----+\n"
808 : : "| ABC |\n"
809 : : "+---+-+\n"
810 : : "|DE |F|\n"
811 : : "+-+-+-+\n"
812 : : "|G|H|I|\n"
813 : : "+-+-+-+\n"));
814 : 4 : }
815 : 4 : {
816 : 4 : canvas canvas (table.to_canvas (unicode_theme(), sm));
817 : 4 : ASSERT_CANVAS_STREQ
818 : : (canvas, false,
819 : : // FIXME: are we allowed unicode chars in string literals in our source?
820 : : ("┌─────┐\n"
821 : : "│ ABC │\n"
822 : : "├───┬─┤\n"
823 : : "│DE │F│\n"
824 : : "├─┬─┼─┤\n"
825 : : "│G│H│I│\n"
826 : : "└─┴─┴─┘\n"));
827 : 4 : }
828 : 4 : }
829 : :
830 : : /* Verify building this 5x5 table with spans:
831 : : |0|1|2|3|4|
832 : : +-+-+-+-+-+
833 : : 0|A A A|B|C|0
834 : : + +-+ +
835 : : 1|A A A|D|C|1
836 : : + +-+-+
837 : : 2|A A A|E|F|2
838 : : +-+-+-+-+-+
839 : : 3|G G|H|I I|3
840 : : | | +-+-+
841 : : 4|G G|H|J J|4
842 : : +-+-+-+-+-+
843 : : |0|1|2|3|4|
844 : : */
845 : :
846 : : static void
847 : 4 : test_spans_2 ()
848 : : {
849 : 4 : style_manager sm;
850 : 4 : table table (table::size_t (5, 5));
851 : 4 : table.set_cell_span (table::rect_t (table::coord_t (0, 0),
852 : 4 : table::size_t (3, 3)),
853 : 8 : styled_string (sm, "A"));
854 : 4 : table.set_cell_span (table::rect_t (table::coord_t (3, 0),
855 : 4 : table::size_t (1, 1)),
856 : 8 : styled_string (sm, "B"));
857 : 4 : table.set_cell_span (table::rect_t (table::coord_t (4, 0),
858 : 4 : table::size_t (1, 2)),
859 : 8 : styled_string (sm, "C"));
860 : 4 : table.set_cell_span (table::rect_t (table::coord_t (3, 1),
861 : 4 : table::size_t (1, 1)),
862 : 8 : styled_string (sm, "D"));
863 : 4 : table.set_cell_span (table::rect_t (table::coord_t (3, 2),
864 : 4 : table::size_t (1, 1)),
865 : 8 : styled_string (sm, "E"));
866 : 4 : table.set_cell_span (table::rect_t (table::coord_t (4, 2),
867 : 4 : table::size_t (1, 1)),
868 : 8 : styled_string (sm, "F"));
869 : 4 : table.set_cell_span (table::rect_t (table::coord_t (0, 3),
870 : 4 : table::size_t (2, 2)),
871 : 8 : styled_string (sm, "G"));
872 : 4 : table.set_cell_span (table::rect_t (table::coord_t (2, 3),
873 : 4 : table::size_t (1, 2)),
874 : 8 : styled_string (sm, "H"));
875 : 4 : table.set_cell_span (table::rect_t (table::coord_t (3, 3),
876 : 4 : table::size_t (2, 1)),
877 : 8 : styled_string (sm, "I"));
878 : 4 : table.set_cell_span (table::rect_t (table::coord_t (3, 4),
879 : 4 : table::size_t (2, 1)),
880 : 8 : styled_string (sm, "J"));
881 : :
882 : : /* Check occupancy at each table coordinate. */
883 : 4 : ASSERT_TABLE_CELL_STREQ (table, 0, 0, "A");
884 : 4 : ASSERT_TABLE_CELL_STREQ (table, 1, 0, "A");
885 : 4 : ASSERT_TABLE_CELL_STREQ (table, 2, 0, "A");
886 : 4 : ASSERT_TABLE_CELL_STREQ (table, 3, 0, "B");
887 : 4 : ASSERT_TABLE_CELL_STREQ (table, 4, 0, "C");
888 : :
889 : 4 : ASSERT_TABLE_CELL_STREQ (table, 0, 1, "A");
890 : 4 : ASSERT_TABLE_CELL_STREQ (table, 1, 1, "A");
891 : 4 : ASSERT_TABLE_CELL_STREQ (table, 2, 1, "A");
892 : 4 : ASSERT_TABLE_CELL_STREQ (table, 3, 1, "D");
893 : 4 : ASSERT_TABLE_CELL_STREQ (table, 4, 1, "C");
894 : :
895 : 4 : ASSERT_TABLE_CELL_STREQ (table, 0, 2, "A");
896 : 4 : ASSERT_TABLE_CELL_STREQ (table, 1, 2, "A");
897 : 4 : ASSERT_TABLE_CELL_STREQ (table, 2, 2, "A");
898 : 4 : ASSERT_TABLE_CELL_STREQ (table, 3, 2, "E");
899 : 4 : ASSERT_TABLE_CELL_STREQ (table, 4, 2, "F");
900 : :
901 : 4 : ASSERT_TABLE_CELL_STREQ (table, 0, 3, "G");
902 : 4 : ASSERT_TABLE_CELL_STREQ (table, 1, 3, "G");
903 : 4 : ASSERT_TABLE_CELL_STREQ (table, 2, 3, "H");
904 : 4 : ASSERT_TABLE_CELL_STREQ (table, 3, 3, "I");
905 : 4 : ASSERT_TABLE_CELL_STREQ (table, 4, 3, "I");
906 : :
907 : 4 : ASSERT_TABLE_CELL_STREQ (table, 0, 4, "G");
908 : 4 : ASSERT_TABLE_CELL_STREQ (table, 1, 4, "G");
909 : 4 : ASSERT_TABLE_CELL_STREQ (table, 2, 4, "H");
910 : 4 : ASSERT_TABLE_CELL_STREQ (table, 3, 4, "J");
911 : 4 : ASSERT_TABLE_CELL_STREQ (table, 4, 4, "J");
912 : :
913 : 4 : {
914 : 4 : canvas canvas (table.to_canvas (ascii_theme(), sm));
915 : 4 : ASSERT_CANVAS_STREQ
916 : : (canvas, false,
917 : : ("+---+-+-+\n"
918 : : "| |B| |\n"
919 : : "| +-+C|\n"
920 : : "| A |D| |\n"
921 : : "| +-+-+\n"
922 : : "| |E|F|\n"
923 : : "+-+-+-+-+\n"
924 : : "| | | I |\n"
925 : : "|G|H+---+\n"
926 : : "| | | J |\n"
927 : : "+-+-+---+\n"));
928 : 4 : }
929 : 4 : {
930 : 4 : canvas canvas (table.to_canvas (unicode_theme(), sm));
931 : 4 : ASSERT_CANVAS_STREQ
932 : : (canvas, false,
933 : : // FIXME: are we allowed unicode chars in string literals in our source?
934 : : ("┌───┬─┬─┐\n"
935 : : "│ │B│ │\n"
936 : : "│ ├─┤C│\n"
937 : : "│ A │D│ │\n"
938 : : "│ ├─┼─┤\n"
939 : : "│ │E│F│\n"
940 : : "├─┬─┼─┴─┤\n"
941 : : "│ │ │ I │\n"
942 : : "│G│H├───┤\n"
943 : : "│ │ │ J │\n"
944 : : "└─┴─┴───┘\n"));
945 : 4 : }
946 : 4 : }
947 : :
948 : : /* Experiment with adding a 1-table-column gap at the boundary between
949 : : valid vs invalid for visualizing a buffer overflow. */
950 : : static void
951 : 4 : test_spans_3 ()
952 : : {
953 : 4 : const char * const str = "hello world!";
954 : 4 : const size_t buf_size = 10;
955 : 4 : const size_t str_size = strlen (str) + 1;
956 : :
957 : 4 : style_manager sm;
958 : 4 : table table (table::size_t (str_size + 1, 3));
959 : :
960 : 4 : table.set_cell_span (table::rect_t (table::coord_t (0, 0),
961 : 4 : table::size_t (str_size + 1, 1)),
962 : 8 : styled_string (sm, "String literal"));
963 : :
964 : 56 : for (size_t i = 0; i < str_size; i++)
965 : : {
966 : 52 : table::coord_t c (i, 1);
967 : 52 : if (i >= buf_size)
968 : 12 : c.x++;
969 : 52 : if (str[i] == '\0')
970 : 4 : table.set_cell (c, styled_string (sm, "NUL"));
971 : : else
972 : 48 : table.set_cell (c, styled_string ((cppchar_t)str[i]));
973 : : }
974 : :
975 : 4 : table.set_cell_span (table::rect_t (table::coord_t (0, 2),
976 : 4 : table::size_t (buf_size, 1)),
977 : 8 : styled_string::from_fmt (sm,
978 : : nullptr,
979 : : "'buf' (char[%i])",
980 : : (int)buf_size));
981 : 4 : table.set_cell_span (table::rect_t (table::coord_t (buf_size + 1, 2),
982 : 4 : table::size_t (str_size - buf_size, 1)),
983 : 8 : styled_string (sm, "overflow"));
984 : :
985 : 4 : {
986 : 4 : canvas canvas (table.to_canvas (ascii_theme (), sm));
987 : 4 : ASSERT_CANVAS_STREQ
988 : : (canvas, false,
989 : : "+-----------------------------+\n"
990 : : "| String literal |\n"
991 : : "+-+-+-+-+-+-+-+-+-+-++-+-+----+\n"
992 : : "|h|e|l|l|o| |w|o|r|l||d|!|NUL |\n"
993 : : "+-+-+-+-+-+-+-+-+-+-++-+-+----+\n"
994 : : "| 'buf' (char[10]) ||overflow|\n"
995 : : "+-------------------++--------+\n");
996 : 4 : }
997 : 4 : {
998 : 4 : canvas canvas (table.to_canvas (unicode_theme (), sm));
999 : 4 : ASSERT_CANVAS_STREQ
1000 : : (canvas, false,
1001 : : // FIXME: are we allowed unicode chars in string literals in our source?
1002 : : ("┌─────────────────────────────┐\n"
1003 : : "│ String literal │\n"
1004 : : "├─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬┬─┬─┬────┤\n"
1005 : : "│h│e│l│l│o│ │w│o│r│l││d│!│NUL │\n"
1006 : : "├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤├─┴─┴────┤\n"
1007 : : "│ 'buf' (char[10]) ││overflow│\n"
1008 : : "└───────────────────┘└────────┘\n"));
1009 : 4 : }
1010 : 4 : }
1011 : :
1012 : : static void
1013 : 4 : test_double_width_chars ()
1014 : : {
1015 : 4 : table_cell_content tcc (styled_string ((cppchar_t)0x1f642));
1016 : 4 : ASSERT_EQ (tcc.get_canvas_size ().w, 2);
1017 : 4 : ASSERT_EQ (tcc.get_canvas_size ().h, 1);
1018 : :
1019 : 4 : style_manager sm;
1020 : 4 : table table (table::size_t (1, 1));
1021 : 4 : table.set_cell (table::coord_t (0,0),
1022 : 8 : styled_string ((cppchar_t)0x1f642));
1023 : :
1024 : 4 : canvas canvas (table.to_canvas (unicode_theme(), sm));
1025 : 4 : ASSERT_CANVAS_STREQ
1026 : : (canvas, false,
1027 : : // FIXME: are we allowed unicode chars in string literals in our source?
1028 : : ("┌──┐\n"
1029 : : "│🙂│\n"
1030 : : "└──┘\n"));
1031 : 8 : }
1032 : :
1033 : : static void
1034 : 4 : test_ipv4_header ()
1035 : : {
1036 : 4 : style_manager sm;
1037 : 4 : table table (table::size_t (34, 10));
1038 : 4 : table.set_cell (table::coord_t (0, 0), styled_string (sm, "Offsets"));
1039 : 4 : table.set_cell (table::coord_t (1, 0), styled_string (sm, "Octet"));
1040 : 4 : table.set_cell (table::coord_t (0, 1), styled_string (sm, "Octet"));
1041 : 20 : for (int octet = 0; octet < 4; octet++)
1042 : 16 : table.set_cell_span (table::rect_t (table::coord_t (2 + (octet * 8), 0),
1043 : 16 : table::size_t (8, 1)),
1044 : 32 : styled_string::from_fmt (sm, nullptr, "%i", octet));
1045 : 4 : table.set_cell (table::coord_t (1, 1), styled_string (sm, "Bit"));
1046 : 132 : for (int bit = 0; bit < 32; bit++)
1047 : 128 : table.set_cell (table::coord_t (bit + 2, 1),
1048 : 256 : styled_string::from_fmt (sm, nullptr, "%i", bit));
1049 : 28 : for (int word = 0; word < 6; word++)
1050 : : {
1051 : 24 : table.set_cell (table::coord_t (0, word + 2),
1052 : 48 : styled_string::from_fmt (sm, nullptr, "%i", word * 4));
1053 : 24 : table.set_cell (table::coord_t (1, word + 2),
1054 : 48 : styled_string::from_fmt (sm, nullptr, "%i", word * 32));
1055 : : }
1056 : :
1057 : 4 : table.set_cell (table::coord_t (0, 8), styled_string (sm, "..."));
1058 : 4 : table.set_cell (table::coord_t (1, 8), styled_string (sm, "..."));
1059 : 4 : table.set_cell (table::coord_t (0, 9), styled_string (sm, "56"));
1060 : 4 : table.set_cell (table::coord_t (1, 9), styled_string (sm, "448"));
1061 : :
1062 : : #define SET_BITS(FIRST, LAST, NAME) \
1063 : : do { \
1064 : : const int first = (FIRST); \
1065 : : const int last = (LAST); \
1066 : : const char *name = (NAME); \
1067 : : const int row = first / 32; \
1068 : : gcc_assert (last / 32 == row); \
1069 : : table::rect_t rect (table::coord_t ((first % 32) + 2, row + 2), \
1070 : : table::size_t (last + 1 - first , 1)); \
1071 : : table.set_cell_span (rect, styled_string (sm, name)); \
1072 : : } while (0)
1073 : :
1074 : 4 : SET_BITS (0, 3, "Version");
1075 : 4 : SET_BITS (4, 7, "IHL");
1076 : 4 : SET_BITS (8, 13, "DSCP");
1077 : 4 : SET_BITS (14, 15, "ECN");
1078 : 4 : SET_BITS (16, 31, "Total Length");
1079 : :
1080 : 4 : SET_BITS (32 + 0, 32 + 15, "Identification");
1081 : 4 : SET_BITS (32 + 16, 32 + 18, "Flags");
1082 : 4 : SET_BITS (32 + 19, 32 + 31, "Fragment Offset");
1083 : :
1084 : 4 : SET_BITS (64 + 0, 64 + 7, "Time To Live");
1085 : 4 : SET_BITS (64 + 8, 64 + 15, "Protocol");
1086 : 4 : SET_BITS (64 + 16, 64 + 31, "Header Checksum");
1087 : :
1088 : 4 : SET_BITS (96 + 0, 96 + 31, "Source IP Address");
1089 : 4 : SET_BITS (128 + 0, 128 + 31, "Destination IP Address");
1090 : :
1091 : 4 : table.set_cell_span(table::rect_t (table::coord_t (2, 7),
1092 : 4 : table::size_t (32, 3)),
1093 : 8 : styled_string (sm, "Options"));
1094 : 4 : {
1095 : 4 : canvas canvas (table.to_canvas (ascii_theme(), sm));
1096 : 4 : ASSERT_CANVAS_STREQ
1097 : : (canvas, false,
1098 : : ("+-------+-----+---------------+---------------------+-----------------------+-----------------------+\n"
1099 : : "|Offsets|Octet| 0 | 1 | 2 | 3 |\n"
1100 : : "+-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n"
1101 : : "| Octet | Bit |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|\n"
1102 : : "+-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n"
1103 : : "| 0 | 0 |Version| IHL | DSCP | ECN | Total Length |\n"
1104 : : "+-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+\n"
1105 : : "| 4 | 32 | Identification | Flags | Fragment Offset |\n"
1106 : : "+-------+-----+---------------+---------------------+--------+--------------------------------------+\n"
1107 : : "| 8 | 64 | Time To Live | Protocol | Header Checksum |\n"
1108 : : "+-------+-----+---------------+---------------------+-----------------------------------------------+\n"
1109 : : "| 12 | 96 | Source IP Address |\n"
1110 : : "+-------+-----+-------------------------------------------------------------------------------------+\n"
1111 : : "| 16 | 128 | Destination IP Address |\n"
1112 : : "+-------+-----+-------------------------------------------------------------------------------------+\n"
1113 : : "| 20 | 160 | |\n"
1114 : : "+-------+-----+ |\n"
1115 : : "| ... | ... | Options |\n"
1116 : : "+-------+-----+ |\n"
1117 : : "| 56 | 448 | |\n"
1118 : : "+-------+-----+-------------------------------------------------------------------------------------+\n"));
1119 : 4 : }
1120 : 4 : {
1121 : 4 : canvas canvas (table.to_canvas (unicode_theme(), sm));
1122 : 4 : ASSERT_CANVAS_STREQ
1123 : : (canvas, false,
1124 : : // FIXME: are we allowed unicode chars in string literals in our source?
1125 : : ("┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐\n"
1126 : : "│Offsets│Octet│ 0 │ 1 │ 2 │ 3 │\n"
1127 : : "├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤\n"
1128 : : "│ Octet │ Bit │0│1│2│3│4│5│6│7│8│9│10│11│12│13│14│15│16│17│18│19│20│21│22│23│24│25│26│27│28│29│30│31│\n"
1129 : : "├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤\n"
1130 : : "│ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │\n"
1131 : : "├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤\n"
1132 : : "│ 4 │ 32 │ Identification │ Flags │ Fragment Offset │\n"
1133 : : "├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤\n"
1134 : : "│ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │\n"
1135 : : "├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤\n"
1136 : : "│ 12 │ 96 │ Source IP Address │\n"
1137 : : "├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤\n"
1138 : : "│ 16 │ 128 │ Destination IP Address │\n"
1139 : : "├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤\n"
1140 : : "│ 20 │ 160 │ │\n"
1141 : : "├───────┼─────┤ │\n"
1142 : : "│ ... │ ... │ Options │\n"
1143 : : "├───────┼─────┤ │\n"
1144 : : "│ 56 │ 448 │ │\n"
1145 : : "└───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘\n"));
1146 : 4 : }
1147 : 4 : }
1148 : :
1149 : : static void
1150 : 4 : test_missing_cells ()
1151 : : {
1152 : 4 : style_manager sm;
1153 : 4 : table table (table::size_t (3, 3));
1154 : 4 : table.set_cell (table::coord_t (1, 0), styled_string (sm, "A"));
1155 : 4 : table.set_cell (table::coord_t (0, 1), styled_string (sm, "B"));
1156 : 4 : table.set_cell (table::coord_t (1, 1), styled_string (sm, "C"));
1157 : 4 : table.set_cell (table::coord_t (2, 1), styled_string (sm, "D"));
1158 : 4 : table.set_cell (table::coord_t (1, 2), styled_string (sm, "E"));
1159 : :
1160 : 4 : ASSERT_TABLE_NULL_CELL (table, 0, 0);
1161 : 4 : ASSERT_TABLE_CELL_STREQ (table, 1, 0, "A");
1162 : 4 : ASSERT_TABLE_NULL_CELL (table, 2, 0);
1163 : :
1164 : 4 : ASSERT_TABLE_CELL_STREQ (table, 0, 1, "B");
1165 : 4 : ASSERT_TABLE_CELL_STREQ (table, 1, 1, "C");
1166 : 4 : ASSERT_TABLE_CELL_STREQ (table, 2, 1, "D");
1167 : :
1168 : 4 : ASSERT_TABLE_NULL_CELL (table, 0, 2);
1169 : 4 : ASSERT_TABLE_CELL_STREQ (table, 1, 2, "E");
1170 : 4 : ASSERT_TABLE_NULL_CELL (table, 2, 2);
1171 : :
1172 : 4 : {
1173 : 4 : canvas canvas (table.to_canvas (ascii_theme(), sm));
1174 : 4 : ASSERT_CANVAS_STREQ
1175 : : (canvas, false,
1176 : : (" +-+\n"
1177 : : " |A|\n"
1178 : : "+-+-+-+\n"
1179 : : "|B|C|D|\n"
1180 : : "+-+-+-+\n"
1181 : : " |E|\n"
1182 : : " +-+\n"));
1183 : 4 : }
1184 : 4 : {
1185 : 4 : canvas canvas (table.to_canvas (unicode_theme(), sm));
1186 : 4 : ASSERT_CANVAS_STREQ
1187 : : (canvas, false,
1188 : : (" ┌─┐\n"
1189 : : " │A│\n"
1190 : : "┌─┼─┼─┐\n"
1191 : : "│B│C│D│\n"
1192 : : "└─┼─┼─┘\n"
1193 : : " │E│\n"
1194 : : " └─┘\n"));
1195 : 4 : }
1196 : 4 : }
1197 : :
1198 : : static void
1199 : 4 : test_add_row ()
1200 : : {
1201 : 4 : style_manager sm;
1202 : 4 : table table (table::size_t (3, 0));
1203 : 24 : for (int i = 0; i < 5; i++)
1204 : : {
1205 : 20 : const int y = table.add_row ();
1206 : 80 : for (int x = 0; x < 3; x++)
1207 : 60 : table.set_cell (table::coord_t (x, y),
1208 : 120 : styled_string::from_fmt (sm, nullptr,
1209 : : "%i, %i", x, y));
1210 : : }
1211 : 4 : canvas canvas (table.to_canvas (ascii_theme(), sm));
1212 : 4 : ASSERT_CANVAS_STREQ
1213 : : (canvas, false,
1214 : : ("+----+----+----+\n"
1215 : : "|0, 0|1, 0|2, 0|\n"
1216 : : "+----+----+----+\n"
1217 : : "|0, 1|1, 1|2, 1|\n"
1218 : : "+----+----+----+\n"
1219 : : "|0, 2|1, 2|2, 2|\n"
1220 : : "+----+----+----+\n"
1221 : : "|0, 3|1, 3|2, 3|\n"
1222 : : "+----+----+----+\n"
1223 : : "|0, 4|1, 4|2, 4|\n"
1224 : : "+----+----+----+\n"));
1225 : 8 : }
1226 : :
1227 : : static void
1228 : 4 : test_alignment ()
1229 : : {
1230 : 4 : style_manager sm;
1231 : 4 : table table (table::size_t (9, 9));
1232 : 4 : table.set_cell_span (table::rect_t (table::coord_t (0, 0),
1233 : 4 : table::size_t (3, 3)),
1234 : 8 : styled_string (sm, "left top"),
1235 : : x_align::LEFT, y_align::TOP);
1236 : 4 : table.set_cell_span (table::rect_t (table::coord_t (3, 0),
1237 : 4 : table::size_t (3, 3)),
1238 : 8 : styled_string (sm, "center top"),
1239 : : x_align::CENTER, y_align::TOP);
1240 : 4 : table.set_cell_span (table::rect_t (table::coord_t (6, 0),
1241 : 4 : table::size_t (3, 3)),
1242 : 8 : styled_string (sm, "right top"),
1243 : : x_align::RIGHT, y_align::TOP);
1244 : 4 : table.set_cell_span (table::rect_t (table::coord_t (0, 3),
1245 : 4 : table::size_t (3, 3)),
1246 : 8 : styled_string (sm, "left center"),
1247 : : x_align::LEFT, y_align::CENTER);
1248 : 4 : table.set_cell_span (table::rect_t (table::coord_t (3, 3),
1249 : 4 : table::size_t (3, 3)),
1250 : 8 : styled_string (sm, "center center"),
1251 : : x_align::CENTER, y_align::CENTER);
1252 : 4 : table.set_cell_span (table::rect_t (table::coord_t (6, 3),
1253 : 4 : table::size_t (3, 3)),
1254 : 8 : styled_string (sm, "right center"),
1255 : : x_align::RIGHT, y_align::CENTER);
1256 : 4 : table.set_cell_span (table::rect_t (table::coord_t (0, 6),
1257 : 4 : table::size_t (3, 3)),
1258 : 8 : styled_string (sm, "left bottom"),
1259 : : x_align::LEFT, y_align::BOTTOM);
1260 : 4 : table.set_cell_span (table::rect_t (table::coord_t (3, 6),
1261 : 4 : table::size_t (3, 3)),
1262 : 8 : styled_string (sm, "center bottom"),
1263 : : x_align::CENTER, y_align::BOTTOM);
1264 : 4 : table.set_cell_span (table::rect_t (table::coord_t (6, 6),
1265 : 4 : table::size_t (3, 3)),
1266 : 8 : styled_string (sm, "right bottom"),
1267 : : x_align::RIGHT, y_align::BOTTOM);
1268 : :
1269 : 4 : canvas canvas (table.to_canvas (ascii_theme(), sm));
1270 : 4 : ASSERT_CANVAS_STREQ
1271 : : (canvas, false,
1272 : : ("+-----------+-------------+------------+\n"
1273 : : "|left top | center top | right top|\n"
1274 : : "| | | |\n"
1275 : : "+-----------+-------------+------------+\n"
1276 : : "|left center|center center|right center|\n"
1277 : : "| | | |\n"
1278 : : "+-----------+-------------+------------+\n"
1279 : : "| | | |\n"
1280 : : "|left bottom|center bottom|right bottom|\n"
1281 : : "+-----------+-------------+------------+\n"));
1282 : 8 : }
1283 : :
1284 : : /* Run all selftests in this file. */
1285 : :
1286 : : void
1287 : 4 : text_art_table_cc_tests ()
1288 : : {
1289 : 4 : test_tic_tac_toe ();
1290 : 4 : test_text_table ();
1291 : 4 : test_offset_table ();
1292 : 4 : test_spans ();
1293 : 4 : test_spans_2 ();
1294 : 4 : test_spans_3 ();
1295 : 4 : test_double_width_chars ();
1296 : 4 : test_ipv4_header ();
1297 : 4 : test_missing_cells ();
1298 : 4 : test_add_row ();
1299 : 4 : test_alignment ();
1300 : 4 : }
1301 : :
1302 : : } // namespace selftest
1303 : :
1304 : :
1305 : : #endif /* #if CHECKING_P */
|