1 /* Support for tabular/grid-based content.
2 Copyright (C) 2023-2024 Free Software Foundation, Inc.
3 Contributed by David Malcolm <dmalcolm@redhat.com>.
5 This file is part of GCC.
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
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
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/>. */
22 #define INCLUDE_MEMORY
23 #define INCLUDE_VECTOR
25 #include "coretypes.h"
26 #include "make-unique.h"
27 #include "pretty-print.h"
28 #include "diagnostic.h"
30 #include "text-art/selftests.h"
31 #include "text-art/table.h"
33 using namespace text_art
;
35 /* class text_art::table_cell_content. */
37 table_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)
45 table_cell_content::paint_to_canvas (canvas
&canvas
,
46 canvas::coord_t top_left
) const
48 canvas
.paint_text (top_left
, m_str
);
51 /* struct text_art::table_dimension_sizes. */
53 table_dimension_sizes::table_dimension_sizes (unsigned num
)
54 : m_requirements (num
, 0)
58 /* class text_art::table::cell_placement. */
61 table::cell_placement::paint_cell_contents_to_canvas(canvas
&canvas
,
62 canvas::coord_t offset
,
63 const table_geometry
&tg
) const
65 const canvas::size_t req_canvas_size
= get_min_canvas_size ();
66 const canvas::size_t alloc_canvas_size
= tg
.get_canvas_size (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_top_left
);
74 gcc_assert (x_padding
>= 0);
84 x_align_offset
= x_padding
/ 2;
87 x_align_offset
= x_padding
;
91 gcc_assert (y_padding
>= 0);
100 case y_align::CENTER
:
101 y_align_offset
= y_padding
/ 2;
103 case y_align::BOTTOM
:
104 y_align_offset
= y_padding
;
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
, offset
+ content_rel_coord
);
113 /* class text_art::table. */
116 table::table (size_t size
)
121 m_occupancy
.fill (-1);
125 table::set_cell (coord_t coord
,
126 table_cell_content
&&content
,
127 enum x_align x_align
,
128 enum y_align y_align
)
130 set_cell_span (rect_t (coord
, table::size_t (1, 1)),
131 std::move (content
), x_align
, y_align
);
135 table::set_cell_span (rect_t span
,
136 table_cell_content
&&content
,
137 enum x_align x_align
,
138 enum y_align y_align
)
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 (cell_placement (span
, std::move (content
),
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
++)
148 gcc_assert (m_occupancy
.get (coord_t (x
, y
)) == -1);
149 m_occupancy
.set (coord_t (x
, y
), placement_idx
);
153 /* If SPAN is unoccuped, set it to CONTENT.
154 Otherwise, discard CONTENT. */
157 table::maybe_set_cell_span (rect_t span
,
158 table_cell_content
&&content
,
159 enum x_align x_align
,
160 enum y_align y_align
)
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
++)
167 if (m_occupancy
.get (coord_t (x
, y
)) != -1)
170 set_cell_span (span
, std::move (content
), x_align
, y_align
);
174 table::to_canvas (const theme
&theme
, const style_manager
&sm
) const
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 (*this);
180 cell_sizes
.pass_2 (*this);
181 table_geometry
tg (*this, cell_sizes
);
182 canvas
canvas (tg
.get_canvas_size (), sm
);
183 paint_to_canvas (canvas
, canvas::coord_t (0, 0), tg
, theme
);
188 table::paint_to_canvas (canvas
&canvas
,
189 canvas::coord_t offset
,
190 const table_geometry
&tg
,
191 const theme
&theme
) const
193 canvas
.fill (canvas::rect_t (offset
, tg
.get_canvas_size ()),
194 styled_unichar (' '));
195 paint_cell_borders_to_canvas (canvas
, offset
, tg
, theme
);
196 paint_cell_contents_to_canvas (canvas
, offset
, tg
);
199 /* Print this table to stderr. */
202 table::debug () const
204 /* Use a temporary style manager.
205 Styles in the table will be meaningless, so
206 print the canvas with styling disabled. */
208 canvas
canvas (to_canvas (unicode_theme (), sm
));
209 canvas
.debug (false);
212 /* Move OTHER's content this table, starting at OFFSET. */
215 table::add_other_table (table
&&other
,
216 table::coord_t offset
)
218 for (auto &&placement
: other
.m_placements
)
220 set_cell_span (placement
.m_rect
+ offset
,
221 std::move (placement
.m_content
),
223 placement
.m_y_align
);
227 const table::cell_placement
*
228 table::get_placement_at (coord_t coord
) const
230 const int placement_idx
= m_occupancy
.get (coord
);
231 if (placement_idx
== -1)
233 return &m_placements
[placement_idx
];
237 table::get_occupancy_safe (coord_t coord
) const
241 if (coord
.x
>= m_size
.w
)
245 if (coord
.y
>= m_size
.h
)
247 return m_occupancy
.get (coord
);
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:
262 # left?+ ??? X ??? + right?
273 table::get_connections (int table_x
, int table_y
) const
275 int cell_a
= get_occupancy_safe (coord_t (table_x
- 1, table_y
- 1));
276 int cell_b
= get_occupancy_safe (coord_t (table_x
, table_y
- 1));
277 int cell_c
= get_occupancy_safe (coord_t (table_x
- 1, table_y
));
278 int cell_d
= get_occupancy_safe (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
);
286 /* Paint the grid lines.
290 - plus a right-hand border
291 - and a bottom border
293 Then we need to paint to the canvas like this:
295 # PER-TABLE-COLUMN R BORDER
296 # +-------------------+ +-----+
298 # TABLE CELL WIDTH (in canvas units)
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 # ...+-----+-----+.+-----+...+-----+ + +
317 # ...+-----+-----+.+-----+...+-----+ +
318 # | D | |.| | | U | |
319 # | D | |.| | | U | |
320 # |LL+RR|RRRRR|.|RRRRR| |LL+ | | BOTTOM BORDER
323 # ...+-----+-----+.+-----+...+-----+ +
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. */
340 table::paint_cell_borders_to_canvas (canvas
&canvas
,
341 canvas::coord_t offset
,
342 const table_geometry
&tg
,
343 const theme
&theme
) const
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 (directions (true, /* up */
352 const canvas::cell_t top_border
353 = theme
.get_line_art (directions (false, /* up */
357 for (int table_y
= 0; table_y
< m_size
.h
; table_y
++)
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
++)
362 canvas::coord_t canvas_top_left
363 = tg
.table_to_canvas(table::coord_t (table_x
, table_y
));
365 const directions
c (get_connections (table_x
, table_y
));
367 /* Paint top-left corner of border, if any. */
368 canvas
.paint (offset
+ canvas_top_left
,
369 theme
.get_line_art (c
));
371 /* Paint remainder of left border of cell, if any.
372 We assume here that the content occupies a single canvas row. */
374 canvas
.paint (offset
+ canvas::coord_t (canvas_top_left
.x
,
378 /* Paint remainder of top border of cell, if any. */
381 const int col_width
= tg
.get_col_width (table_x
);
382 for (int x_offset
= 0; x_offset
< col_width
; x_offset
++)
384 const int canvas_x
= canvas_top_left
.x
+ 1 + x_offset
;
385 canvas
.paint (offset
+ canvas::coord_t (canvas_x
, canvas_y
),
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 (m_size
.w
, table_y
));
395 canvas
.paint(offset
+ canvas::coord_t (canvas_x
, canvas_y
),
396 theme
.get_line_art (directions (c
.m_up
,
399 false))); /* right */
400 /* We assume here that the content occupies a single canvas row. */
401 canvas
.paint(offset
+ canvas::coord_t (canvas_x
, canvas_y
+ 1),
402 theme
.get_line_art (directions (c
.m_down
, /* up */
405 false))); /* right */
408 /* Draw bottom border of table. */
410 const int canvas_y
= tg
.get_canvas_size ().h
- 1;
411 for (int table_x
= 0; table_x
< m_size
.w
; table_x
++)
413 const directions
c (get_connections (table_x
, m_size
.h
));
414 const int left_canvas_x
= tg
.table_x_to_canvas_x (table_x
);
415 canvas
.paint (offset
+ canvas::coord_t (left_canvas_x
, canvas_y
),
416 theme
.get_line_art (directions (c
.m_up
,
420 const int col_width
= tg
.get_col_width (table_x
);
421 for (int x_offset
= 0; x_offset
< col_width
; x_offset
++)
423 const int canvas_x
= left_canvas_x
+ 1 + x_offset
;
424 canvas
.paint(offset
+ canvas::coord_t (canvas_x
, canvas_y
),
425 theme
.get_line_art (directions (false, // up
428 c
.m_right
))); // right
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 (m_size
.w
, m_size
.h
));
436 canvas
.paint (offset
+ canvas::coord_t (canvas_x
, canvas_y
),
437 theme
.get_line_art (directions (c
.m_up
, // up
445 table::paint_cell_contents_to_canvas(canvas
&canvas
,
446 canvas::coord_t offset
,
447 const table_geometry
&tg
) const
449 for (auto &placement
: m_placements
)
450 placement
.paint_cell_contents_to_canvas (canvas
, offset
, tg
);
453 /* class table_cell_sizes. */
455 /* Consider 1x1 cells. */
458 table_cell_sizes::pass_1 (const table
&table
)
460 for (auto &placement
: table
.m_placements
)
461 if (placement
.one_by_one_p ())
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 (table_coord
.x
, canvas_size
.w
);
466 m_row_heights
.require (table_coord
.y
, canvas_size
.h
);
470 /* Consider cells that span more than one row or column. */
473 table_cell_sizes::pass_2 (const table
&table
)
475 for (auto &placement
: table
.m_placements
)
476 if (!placement
.one_by_one_p ())
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 (placement
.m_rect
);
481 /* Grow columns as necessary. */
482 if (req_canvas_size
.w
> current_canvas_size
.w
)
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 ();
491 m_col_widths
.m_requirements
[table_x
] += per_col
;
494 /* Make sure we allocate all of the deficit. */
497 const int table_x
= placement
.m_rect
.get_max_x ();
498 m_col_widths
.m_requirements
[table_x
] += deficit
;
501 /* Grow rows as necessary. */
502 if (req_canvas_size
.h
> current_canvas_size
.h
)
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 ();
511 m_row_heights
.m_requirements
[table_y
] += per_row
;
514 /* Make sure we allocate all of the deficit. */
517 const int table_y
= placement
.m_rect
.get_max_y ();
518 m_row_heights
.m_requirements
[table_y
] += deficit
;
525 table_cell_sizes::get_canvas_size (const table::rect_t
&rect
) const
527 canvas::size_t result (0, 0);
528 for (int table_x
= rect
.get_min_x ();
529 table_x
< rect
.get_next_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 ();
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;
542 /* class text_art::table_geometry. */
544 table_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
)
554 table_geometry::recalc_coords ()
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
)
561 m_col_start_x
.push_back (iter_canvas_x
);
562 iter_canvas_x
+= w
+ 1;
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
)
570 m_row_start_y
.push_back (iter_canvas_y
);
571 iter_canvas_y
+= h
+ 1;
574 m_canvas_size
= canvas::size_t (iter_canvas_x
+ 1,
578 /* Get the TL corner of the table cell at TABLE_COORD
579 in canvas coords (including the border). */
582 table_geometry::table_to_canvas (table::coord_t table_coord
) const
584 return canvas::coord_t (table_x_to_canvas_x (table_coord
.x
),
585 table_y_to_canvas_y (table_coord
.y
));
588 /* Get the left border of the table cell at column TABLE_X
589 in canvas coords (including the border). */
592 table_geometry::table_x_to_canvas_x (int table_x
) const
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
];
600 /* Get the top border of the table cell at column TABLE_Y
601 in canvas coords (including the border). */
604 table_geometry::table_y_to_canvas_y (int table_y
) const
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
];
612 /* class text_art::simple_table_geometry. */
614 simple_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
)
620 m_cell_sizes
.pass_1 (table
);
621 m_cell_sizes
.pass_2 (table
);
622 m_tg
.recalc_coords ();
633 table
t (table::size_t (3, 3));
634 t
.set_cell (table::coord_t (0, 0), styled_string (sm
, "X"));
635 t
.set_cell (table::coord_t (1, 0), styled_string (sm
, ""));
636 t
.set_cell (table::coord_t (2, 0), styled_string (sm
, ""));
637 t
.set_cell (table::coord_t (0, 1), styled_string (sm
, "O"));
638 t
.set_cell (table::coord_t (1, 1), styled_string (sm
, "O"));
639 t
.set_cell (table::coord_t (2, 1), styled_string (sm
, ""));
640 t
.set_cell (table::coord_t (0, 2), styled_string (sm
, "X"));
641 t
.set_cell (table::coord_t (1, 2), styled_string (sm
, ""));
642 t
.set_cell (table::coord_t (2, 2), styled_string (sm
, "O"));
645 canvas
canvas (t
.to_canvas (ascii_theme (), sm
));
658 canvas
canvas (t
.to_canvas (unicode_theme (), sm
));
661 // FIXME: are we allowed unicode chars in string literals in our source?
676 table
t (table::size_t (3, 3));
677 t
.set_cell (table::coord_t (0, 0), styled_string (sm
, "top left"));
678 t
.set_cell (table::coord_t (1, 0), styled_string (sm
, "top middle"));
679 t
.set_cell (table::coord_t (2, 0), styled_string (sm
, "top right"));
680 t
.set_cell (table::coord_t (0, 1), styled_string (sm
, "middle left"));
681 t
.set_cell (table::coord_t (1, 1), styled_string (sm
, "middle middle"));
682 t
.set_cell (table::coord_t (2, 1), styled_string (sm
, "middle right"));
683 t
.set_cell (table::coord_t (0, 2), styled_string (sm
, "bottom left"));
684 t
.set_cell (table::coord_t (1, 2), styled_string (sm
, "bottom middle"));
685 t
.set_cell (table::coord_t (2, 2), styled_string (sm
, "bottom right"));
693 table table
= make_text_table ();
695 canvas
canvas (table
.to_canvas (ascii_theme(), sm
));
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"));
707 canvas
canvas (table
.to_canvas (unicode_theme(), sm
));
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"));
725 table table
= make_text_table ();
726 simple_table_geometry
stg (table
);
727 const canvas::size_t tcs
= stg
.m_tg
.get_canvas_size();
729 canvas
canvas (canvas::size_t (tcs
.w
+ 5, tcs
.h
+ 5), sm
);
730 canvas
.debug_fill ();
731 table
.paint_to_canvas (canvas
, canvas::coord_t (3, 3),
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"));
750 canvas
canvas (canvas::size_t (tcs
.w
+ 5, tcs
.h
+ 5), sm
);
751 canvas
.debug_fill ();
752 table
.paint_to_canvas (canvas
, canvas::coord_t (3, 3),
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"));
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)); \
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); \
792 table
table (table::size_t (3, 3));
793 table
.set_cell_span (table::rect_t (table::coord_t (0, 0),
794 table::size_t (3, 1)),
795 styled_string (sm
, "ABC"));
796 table
.set_cell_span (table::rect_t (table::coord_t (0, 1),
797 table::size_t (2, 1)),
798 styled_string (sm
, "DE"));
799 table
.set_cell_span (table::rect_t (table::coord_t (2, 1),
800 table::size_t (1, 1)),
801 styled_string (sm
, "F"));
802 table
.set_cell (table::coord_t (0, 2), styled_string (sm
, "G"));
803 table
.set_cell (table::coord_t (1, 2), styled_string (sm
, "H"));
804 table
.set_cell (table::coord_t (2, 2), styled_string (sm
, "I"));
806 canvas
canvas (table
.to_canvas (ascii_theme(), sm
));
818 canvas
canvas (table
.to_canvas (unicode_theme(), sm
));
821 // FIXME: are we allowed unicode chars in string literals in our source?
832 /* Verify building this 5x5 table with spans:
852 table
table (table::size_t (5, 5));
853 table
.set_cell_span (table::rect_t (table::coord_t (0, 0),
854 table::size_t (3, 3)),
855 styled_string (sm
, "A"));
856 table
.set_cell_span (table::rect_t (table::coord_t (3, 0),
857 table::size_t (1, 1)),
858 styled_string (sm
, "B"));
859 table
.set_cell_span (table::rect_t (table::coord_t (4, 0),
860 table::size_t (1, 2)),
861 styled_string (sm
, "C"));
862 table
.set_cell_span (table::rect_t (table::coord_t (3, 1),
863 table::size_t (1, 1)),
864 styled_string (sm
, "D"));
865 table
.set_cell_span (table::rect_t (table::coord_t (3, 2),
866 table::size_t (1, 1)),
867 styled_string (sm
, "E"));
868 table
.set_cell_span (table::rect_t (table::coord_t (4, 2),
869 table::size_t (1, 1)),
870 styled_string (sm
, "F"));
871 table
.set_cell_span (table::rect_t (table::coord_t (0, 3),
872 table::size_t (2, 2)),
873 styled_string (sm
, "G"));
874 table
.set_cell_span (table::rect_t (table::coord_t (2, 3),
875 table::size_t (1, 2)),
876 styled_string (sm
, "H"));
877 table
.set_cell_span (table::rect_t (table::coord_t (3, 3),
878 table::size_t (2, 1)),
879 styled_string (sm
, "I"));
880 table
.set_cell_span (table::rect_t (table::coord_t (3, 4),
881 table::size_t (2, 1)),
882 styled_string (sm
, "J"));
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");
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");
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");
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");
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");
916 canvas
canvas (table
.to_canvas (ascii_theme(), sm
));
932 canvas
canvas (table
.to_canvas (unicode_theme(), sm
));
935 // FIXME: are we allowed unicode chars in string literals in our source?
950 /* Experiment with adding a 1-table-column gap at the boundary between
951 valid vs invalid for visualizing a buffer overflow. */
955 const char * const str
= "hello world!";
956 const size_t buf_size
= 10;
957 const size_t str_size
= strlen (str
) + 1;
960 table
table (table::size_t (str_size
+ 1, 3));
962 table
.set_cell_span (table::rect_t (table::coord_t (0, 0),
963 table::size_t (str_size
+ 1, 1)),
964 styled_string (sm
, "String literal"));
966 for (size_t i
= 0; i
< str_size
; i
++)
968 table::coord_t
c (i
, 1);
972 table
.set_cell (c
, styled_string (sm
, "NUL"));
974 table
.set_cell (c
, styled_string ((cppchar_t
)str
[i
]));
977 table
.set_cell_span (table::rect_t (table::coord_t (0, 2),
978 table::size_t (buf_size
, 1)),
979 styled_string::from_fmt (sm
,
983 table
.set_cell_span (table::rect_t (table::coord_t (buf_size
+ 1, 2),
984 table::size_t (str_size
- buf_size
, 1)),
985 styled_string (sm
, "overflow"));
988 canvas
canvas (table
.to_canvas (ascii_theme (), sm
));
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");
1000 canvas
canvas (table
.to_canvas (unicode_theme (), sm
));
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"));
1015 test_double_width_chars ()
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);
1022 table
table (table::size_t (1, 1));
1023 table
.set_cell (table::coord_t (0,0),
1024 styled_string ((cppchar_t
)0x1f642));
1026 canvas
canvas (table
.to_canvas (unicode_theme(), sm
));
1029 // FIXME: are we allowed unicode chars in string literals in our source?
1039 table
table (table::size_t (34, 10));
1040 table
.set_cell (table::coord_t (0, 0), styled_string (sm
, "Offsets"));
1041 table
.set_cell (table::coord_t (1, 0), styled_string (sm
, "Octet"));
1042 table
.set_cell (table::coord_t (0, 1), styled_string (sm
, "Octet"));
1043 for (int octet
= 0; octet
< 4; octet
++)
1044 table
.set_cell_span (table::rect_t (table::coord_t (2 + (octet
* 8), 0),
1045 table::size_t (8, 1)),
1046 styled_string::from_fmt (sm
, nullptr, "%i", octet
));
1047 table
.set_cell (table::coord_t (1, 1), styled_string (sm
, "Bit"));
1048 for (int bit
= 0; bit
< 32; bit
++)
1049 table
.set_cell (table::coord_t (bit
+ 2, 1),
1050 styled_string::from_fmt (sm
, nullptr, "%i", bit
));
1051 for (int word
= 0; word
< 6; word
++)
1053 table
.set_cell (table::coord_t (0, word
+ 2),
1054 styled_string::from_fmt (sm
, nullptr, "%i", word
* 4));
1055 table
.set_cell (table::coord_t (1, word
+ 2),
1056 styled_string::from_fmt (sm
, nullptr, "%i", word
* 32));
1059 table
.set_cell (table::coord_t (0, 8), styled_string (sm
, "..."));
1060 table
.set_cell (table::coord_t (1, 8), styled_string (sm
, "..."));
1061 table
.set_cell (table::coord_t (0, 9), styled_string (sm
, "56"));
1062 table
.set_cell (table::coord_t (1, 9), styled_string (sm
, "448"));
1064 #define SET_BITS(FIRST, LAST, NAME) \
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)); \
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");
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");
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");
1090 SET_BITS (96 + 0, 96 + 31, "Source IP Address");
1091 SET_BITS (128 + 0, 128 + 31, "Destination IP Address");
1093 table
.set_cell_span(table::rect_t (table::coord_t (2, 7),
1094 table::size_t (32, 3)),
1095 styled_string (sm
, "Options"));
1097 canvas
canvas (table
.to_canvas (ascii_theme(), sm
));
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"
1116 "+-------+-----+ |\n"
1117 "| ... | ... | Options |\n"
1118 "+-------+-----+ |\n"
1120 "+-------+-----+-------------------------------------------------------------------------------------+\n"));
1123 canvas
canvas (table
.to_canvas (unicode_theme(), sm
));
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"
1143 "├───────┼─────┤ │\n"
1144 "│ ... │ ... │ Options │\n"
1145 "├───────┼─────┤ │\n"
1147 "└───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘\n"));
1152 test_missing_cells ()
1155 table
table (table::size_t (3, 3));
1156 table
.set_cell (table::coord_t (1, 0), styled_string (sm
, "A"));
1157 table
.set_cell (table::coord_t (0, 1), styled_string (sm
, "B"));
1158 table
.set_cell (table::coord_t (1, 1), styled_string (sm
, "C"));
1159 table
.set_cell (table::coord_t (2, 1), styled_string (sm
, "D"));
1160 table
.set_cell (table::coord_t (1, 2), styled_string (sm
, "E"));
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);
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");
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);
1175 canvas
canvas (table
.to_canvas (ascii_theme(), sm
));
1187 canvas
canvas (table
.to_canvas (unicode_theme(), sm
));
1204 table
table (table::size_t (3, 0));
1205 for (int i
= 0; i
< 5; i
++)
1207 const int y
= table
.add_row ();
1208 for (int x
= 0; x
< 3; x
++)
1209 table
.set_cell (table::coord_t (x
, y
),
1210 styled_string::from_fmt (sm
, nullptr,
1213 canvas
canvas (table
.to_canvas (ascii_theme(), sm
));
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"));
1233 table
table (table::size_t (9, 9));
1234 table
.set_cell_span (table::rect_t (table::coord_t (0, 0),
1235 table::size_t (3, 3)),
1236 styled_string (sm
, "left top"),
1237 x_align::LEFT
, y_align::TOP
);
1238 table
.set_cell_span (table::rect_t (table::coord_t (3, 0),
1239 table::size_t (3, 3)),
1240 styled_string (sm
, "center top"),
1241 x_align::CENTER
, y_align::TOP
);
1242 table
.set_cell_span (table::rect_t (table::coord_t (6, 0),
1243 table::size_t (3, 3)),
1244 styled_string (sm
, "right top"),
1245 x_align::RIGHT
, y_align::TOP
);
1246 table
.set_cell_span (table::rect_t (table::coord_t (0, 3),
1247 table::size_t (3, 3)),
1248 styled_string (sm
, "left center"),
1249 x_align::LEFT
, y_align::CENTER
);
1250 table
.set_cell_span (table::rect_t (table::coord_t (3, 3),
1251 table::size_t (3, 3)),
1252 styled_string (sm
, "center center"),
1253 x_align::CENTER
, y_align::CENTER
);
1254 table
.set_cell_span (table::rect_t (table::coord_t (6, 3),
1255 table::size_t (3, 3)),
1256 styled_string (sm
, "right center"),
1257 x_align::RIGHT
, y_align::CENTER
);
1258 table
.set_cell_span (table::rect_t (table::coord_t (0, 6),
1259 table::size_t (3, 3)),
1260 styled_string (sm
, "left bottom"),
1261 x_align::LEFT
, y_align::BOTTOM
);
1262 table
.set_cell_span (table::rect_t (table::coord_t (3, 6),
1263 table::size_t (3, 3)),
1264 styled_string (sm
, "center bottom"),
1265 x_align::CENTER
, y_align::BOTTOM
);
1266 table
.set_cell_span (table::rect_t (table::coord_t (6, 6),
1267 table::size_t (3, 3)),
1268 styled_string (sm
, "right bottom"),
1269 x_align::RIGHT
, y_align::BOTTOM
);
1271 canvas
canvas (table
.to_canvas (ascii_theme(), sm
));
1274 ("+-----------+-------------+------------+\n"
1275 "|left top | center top | right top|\n"
1277 "+-----------+-------------+------------+\n"
1278 "|left center|center center|right center|\n"
1280 "+-----------+-------------+------------+\n"
1282 "|left bottom|center bottom|right bottom|\n"
1283 "+-----------+-------------+------------+\n"));
1286 /* Run all selftests in this file. */
1289 text_art_table_cc_tests ()
1291 test_tic_tac_toe ();
1293 test_offset_table ();
1297 test_double_width_chars ();
1298 test_ipv4_header ();
1299 test_missing_cells ();
1304 } // namespace selftest
1307 #endif /* #if CHECKING_P */