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

source code of gcc/text-art/table.cc