aboutsummaryrefslogtreecommitdiff
path: root/gcc/text-art/table.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/text-art/table.cc')
-rw-r--r--gcc/text-art/table.cc1273
1 files changed, 1273 insertions, 0 deletions
diff --git a/gcc/text-art/table.cc b/gcc/text-art/table.cc
new file mode 100644
index 0000000..71a1024
--- /dev/null
+++ b/gcc/text-art/table.cc
@@ -0,0 +1,1273 @@
+/* Support for tabular/grid-based content.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "make-unique.h"
+#include "pretty-print.h"
+#include "diagnostic.h"
+#include "selftest.h"
+#include "text-art/selftests.h"
+#include "text-art/table.h"
+
+using namespace text_art;
+
+/* class text_art::table_cell_content. */
+
+table_cell_content::table_cell_content (styled_string &&s)
+: m_str (std::move (s)),
+ /* We assume here that the content occupies a single canvas row. */
+ m_size (m_str.calc_canvas_width (), 1)
+{
+}
+
+void
+table_cell_content::paint_to_canvas (canvas &canvas,
+ canvas::coord_t top_left) const
+{
+ canvas.paint_text (top_left, m_str);
+}
+
+/* struct text_art::table_dimension_sizes. */
+
+table_dimension_sizes::table_dimension_sizes (unsigned num)
+: m_requirements (num, 0)
+{
+}
+
+/* class text_art::table::cell_placement. */
+
+void
+table::cell_placement::paint_cell_contents_to_canvas(canvas &canvas,
+ canvas::coord_t offset,
+ const table_geometry &tg) const
+{
+ const canvas::size_t req_canvas_size = get_min_canvas_size ();
+ const canvas::size_t alloc_canvas_size = tg.get_canvas_size (m_rect);
+ gcc_assert (req_canvas_size.w <= alloc_canvas_size.w);
+ gcc_assert (req_canvas_size.h <= alloc_canvas_size.h);
+ const int x_padding = alloc_canvas_size.w - req_canvas_size.w;
+ const int y_padding = alloc_canvas_size.h - req_canvas_size.h;
+ const table::coord_t table_top_left = m_rect.m_top_left;
+ const canvas::coord_t canvas_top_left = tg.table_to_canvas (table_top_left);
+
+ gcc_assert (x_padding >= 0);
+ int x_align_offset;
+ switch (m_x_align)
+ {
+ default:
+ gcc_unreachable ();
+ case x_align::LEFT:
+ x_align_offset = 0;
+ break;
+ case x_align::CENTER:
+ x_align_offset = x_padding / 2;
+ break;
+ case x_align::RIGHT:
+ x_align_offset = x_padding;
+ break;
+ }
+
+ gcc_assert (y_padding >= 0);
+ int y_align_offset;
+ switch (m_y_align)
+ {
+ default:
+ gcc_unreachable ();
+ case y_align::TOP:
+ y_align_offset = 0;
+ break;
+ case y_align::CENTER:
+ y_align_offset = y_padding / 2;
+ break;
+ case y_align::BOTTOM:
+ y_align_offset = y_padding;
+ break;
+ }
+ const canvas::coord_t content_rel_coord
+ (canvas_top_left.x + 1 + x_align_offset,
+ canvas_top_left.y + 1 + y_align_offset);
+ m_content.paint_to_canvas (canvas, offset + content_rel_coord);
+}
+
+/* class text_art::table. */
+
+
+table::table (size_t size)
+: m_size (size),
+ m_placements (),
+ m_occupancy (size)
+{
+ m_occupancy.fill (-1);
+}
+
+void
+table::set_cell (coord_t coord,
+ table_cell_content &&content,
+ enum x_align x_align,
+ enum y_align y_align)
+{
+ set_cell_span (rect_t (coord, table::size_t (1, 1)),
+ std::move (content), x_align, y_align);
+}
+
+void
+table::set_cell_span (rect_t span,
+ table_cell_content &&content,
+ enum x_align x_align,
+ enum y_align y_align)
+{
+ gcc_assert (span.m_size.w > 0);
+ gcc_assert (span.m_size.h > 0);
+ int placement_idx = m_placements.size ();
+ m_placements.emplace_back (cell_placement (span, std::move (content),
+ x_align, y_align));
+ for (int y = span.get_min_y (); y < span.get_next_y (); y++)
+ for (int x = span.get_min_x (); x < span.get_next_x (); x++)
+ {
+ gcc_assert (m_occupancy.get (coord_t (x, y)) == -1);
+ m_occupancy.set (coord_t (x, y), placement_idx);
+ }
+}
+
+canvas
+table::to_canvas (const theme &theme, const style_manager &sm) const
+{
+ table_dimension_sizes col_widths (m_size.w);
+ table_dimension_sizes row_heights (m_size.h);
+ table_cell_sizes cell_sizes (col_widths, row_heights);
+ cell_sizes.pass_1 (*this);
+ cell_sizes.pass_2 (*this);
+ table_geometry tg (*this, cell_sizes);
+ canvas canvas (tg.get_canvas_size (), sm);
+ paint_to_canvas (canvas, canvas::coord_t (0, 0), tg, theme);
+ return canvas;
+}
+
+void
+table::paint_to_canvas (canvas &canvas,
+ canvas::coord_t offset,
+ const table_geometry &tg,
+ const theme &theme) const
+{
+ canvas.fill (canvas::rect_t (offset, tg.get_canvas_size ()),
+ styled_unichar (' '));
+ paint_cell_borders_to_canvas (canvas, offset, tg, theme);
+ paint_cell_contents_to_canvas (canvas, offset, tg);
+}
+
+/* Print this table to stderr. */
+
+DEBUG_FUNCTION void
+table::debug () const
+{
+ /* Use a temporary style manager.
+ Styles in the table will be meaningless, so
+ print the canvas with styling disabled. */
+ style_manager sm;
+ canvas canvas (to_canvas (unicode_theme (), sm));
+ canvas.debug (false);
+}
+
+const table::cell_placement *
+table::get_placement_at (coord_t coord) const
+{
+ const int placement_idx = m_occupancy.get (coord);
+ if (placement_idx == -1)
+ return nullptr;
+ return &m_placements[placement_idx];
+}
+
+int
+table::get_occupancy_safe (coord_t coord) const
+{
+ if (coord.x < 0)
+ return -1;
+ if (coord.x >= m_size.w)
+ return -1;
+ if (coord.y < 0)
+ return -1;
+ if (coord.y >= m_size.h)
+ return -1;
+ return m_occupancy.get (coord);
+}
+
+/* Determine if the "?" edges need borders for table cell D
+ in the following, for the directions relative to "X", based
+ on whether each of table cell boundaries AB, CD, AC, and BD
+ are boundaries between cell spans:
+
+ # up?
+ # +-----+-----+
+ # | |
+ # | ? |
+ # | A ? B |
+ # | ? |
+ # | |
+ # left?+ ??? X ??? + right?
+ # | |
+ # | ? |
+ # | C ? D |
+ # | ? |
+ # | |
+ # +-----+-----+
+ # down?
+*/
+
+directions
+table::get_connections (int table_x, int table_y) const
+{
+ int cell_a = get_occupancy_safe (coord_t (table_x - 1, table_y - 1));
+ int cell_b = get_occupancy_safe (coord_t (table_x, table_y - 1));
+ int cell_c = get_occupancy_safe (coord_t (table_x - 1, table_y));
+ int cell_d = get_occupancy_safe (coord_t (table_x, table_y));
+ const bool up = (cell_a != cell_b);
+ const bool down = (cell_c != cell_d);
+ const bool left = (cell_a != cell_c);
+ const bool right = (cell_b != cell_d);
+ return directions (up, down, left, right);
+}
+
+/* Paint the grid lines.
+
+ Consider painting
+ - a grid of cells,
+ - plus a right-hand border
+ - and a bottom border
+
+ Then we need to paint to the canvas like this:
+
+ # PER-TABLE-COLUMN R BORDER
+ # +-------------------+ +-----+
+ #
+ # TABLE CELL WIDTH (in canvas units)
+ # +-------------+
+ # . . . . . . .
+ # ...+-----+-----+.+-----+...+-----+ +
+ # | U | |.| | | U | |
+ # | U | |.| | | U | |
+ # |LL+RR|RRRRR|.|RRRRR| |LL+ | |
+ # | D | |.| | | D | |
+ # | D | |.| | | D | |
+ # ...+-----+-----+.+-----+...+-----+ |
+ # ..................... ...... +-- PER-TABLE-ROW
+ # ...+-----+-----+.+-----+...+-----+ | +
+ # | D | |.| | | D | | |
+ # | D | |.| | | D | | |
+ # | D | |.| | | D | | +---- TABLE CELL HEIGHT (in canvas units)
+ # | D | |.| | | D | | |
+ # | D | |.| | | D | | |
+ # ...+-----+-----+.+-----+...+-----+ + +
+ # . . . . . .
+ # ...+-----+-----+.+-----+...+-----+ +
+ # | D | |.| | | U | |
+ # | D | |.| | | U | |
+ # |LL+RR|RRRRR|.|RRRRR| |LL+ | | BOTTOM BORDER
+ # | | |.| | | | |
+ # | | |.| | | | |
+ # ...+-----+-----+.+-----+...+-----+ +
+
+ where each:
+
+ # +-----+
+ # | |
+ # | |
+ # | |
+ # | |
+ # | |
+ # +-----+
+
+ is a canvas cell, and the U, L, R, D express the connections
+ that are present with neighboring table cells. These affect
+ the kinds of borders that we draw for a particular table cell. */
+
+void
+table::paint_cell_borders_to_canvas (canvas &canvas,
+ canvas::coord_t offset,
+ const table_geometry &tg,
+ const theme &theme) const
+{
+ /* The per-table-cell left and top borders are either drawn or not,
+ but if they are, they aren't affected by per-table-cell connections. */
+ const canvas::cell_t left_border
+ = theme.get_line_art (directions (true, /* up */
+ true, /* down */
+ false, /* left */
+ false /* right */));
+ const canvas::cell_t top_border
+ = theme.get_line_art (directions (false, /* up */
+ false, /* down */
+ true, /* left */
+ true)); /* right */
+ for (int table_y = 0; table_y < m_size.h; table_y++)
+ {
+ const int canvas_y = tg.table_y_to_canvas_y (table_y);
+ for (int table_x = 0; table_x < m_size.w; table_x++)
+ {
+ canvas::coord_t canvas_top_left
+ = tg.table_to_canvas(table::coord_t (table_x, table_y));
+
+ const directions c (get_connections (table_x, table_y));
+
+ /* Paint top-left corner of border, if any. */
+ canvas.paint (offset + canvas_top_left,
+ theme.get_line_art (c));
+
+ /* Paint remainder of left border of cell, if any.
+ We assume here that the content occupies a single canvas row. */
+ if (c.m_down)
+ canvas.paint (offset + canvas::coord_t (canvas_top_left.x,
+ canvas_y + 1),
+ left_border);
+
+ /* Paint remainder of top border of cell, if any. */
+ if (c.m_right)
+ {
+ const int col_width = tg.get_col_width (table_x);
+ for (int x_offset = 0; x_offset < col_width; x_offset++)
+ {
+ const int canvas_x = canvas_top_left.x + 1 + x_offset;
+ canvas.paint (offset + canvas::coord_t (canvas_x, canvas_y),
+ top_border);
+ }
+ }
+ }
+
+ /* Paint right-hand border of row. */
+ const int table_x = m_size.w;
+ const int canvas_x = tg.table_x_to_canvas_x (table_x);
+ const directions c (get_connections (m_size.w, table_y));
+ canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y),
+ theme.get_line_art (directions (c.m_up,
+ c.m_down,
+ c.m_left,
+ false))); /* right */
+ /* We assume here that the content occupies a single canvas row. */
+ canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y + 1),
+ theme.get_line_art (directions (c.m_down, /* up */
+ c.m_down, /* down */
+ false, /* left */
+ false))); /* right */
+ }
+
+ /* Draw bottom border of table. */
+ {
+ const int canvas_y = tg.get_canvas_size ().h - 1;
+ for (int table_x = 0; table_x < m_size.w; table_x++)
+ {
+ const directions c (get_connections (table_x, m_size.h));
+ const int left_canvas_x = tg.table_x_to_canvas_x (table_x);
+ canvas.paint (offset + canvas::coord_t (left_canvas_x, canvas_y),
+ theme.get_line_art (directions (c.m_up,
+ false, /* down */
+ c.m_left,
+ c.m_right)));
+ const int col_width = tg.get_col_width (table_x);
+ for (int x_offset = 0; x_offset < col_width; x_offset++)
+ {
+ const int canvas_x = left_canvas_x + 1 + x_offset;
+ canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y),
+ theme.get_line_art (directions (false, // up
+ false, // down
+ c.m_right, // left
+ c.m_right))); // right
+ }
+ }
+
+ /* Bottom-right corner of table. */
+ const int table_x = m_size.w;
+ const int canvas_x = tg.table_x_to_canvas_x (table_x);
+ const directions c (get_connections (m_size.w, m_size.h));
+ canvas.paint (offset + canvas::coord_t (canvas_x, canvas_y),
+ theme.get_line_art (directions (c.m_up, // up
+ false, // down
+ c.m_left, // left
+ false))); // right
+ }
+}
+
+void
+table::paint_cell_contents_to_canvas(canvas &canvas,
+ canvas::coord_t offset,
+ const table_geometry &tg) const
+{
+ for (auto &placement : m_placements)
+ placement.paint_cell_contents_to_canvas (canvas, offset, tg);
+}
+
+/* class table_cell_sizes. */
+
+/* Consider 1x1 cells. */
+
+void
+table_cell_sizes::pass_1 (const table &table)
+{
+ for (auto &placement : table.m_placements)
+ if (placement.one_by_one_p ())
+ {
+ canvas::size_t canvas_size (placement.get_min_canvas_size ());
+ table::coord_t table_coord (placement.m_rect.m_top_left);
+ m_col_widths.require (table_coord.x, canvas_size.w);
+ m_row_heights.require (table_coord.y, canvas_size.h);
+ }
+}
+
+/* Consider cells that span more than one row or column. */
+
+void
+table_cell_sizes::pass_2 (const table &table)
+{
+ for (auto &placement : table.m_placements)
+ if (!placement.one_by_one_p ())
+ {
+ const canvas::size_t req_canvas_size (placement.get_min_canvas_size ());
+ const canvas::size_t current_canvas_size
+ = get_canvas_size (placement.m_rect);
+ /* Grow columns as necessary. */
+ if (req_canvas_size.w > current_canvas_size.w)
+ {
+ /* Spread the deficit amongst the columns. */
+ int deficit = req_canvas_size.w - current_canvas_size.w;
+ const int per_col = deficit / placement.m_rect.m_size.w;
+ for (int table_x = placement.m_rect.get_min_x ();
+ table_x < placement.m_rect.get_next_x ();
+ table_x++)
+ {
+ m_col_widths.m_requirements[table_x] += per_col;
+ deficit -= per_col;
+ }
+ /* Make sure we allocate all of the deficit. */
+ if (deficit > 0)
+ {
+ const int table_x = placement.m_rect.get_max_x ();
+ m_col_widths.m_requirements[table_x] += deficit;
+ }
+ }
+ /* Grow rows as necessary. */
+ if (req_canvas_size.h > current_canvas_size.h)
+ {
+ /* Spread the deficit amongst the rows. */
+ int deficit = req_canvas_size.h - current_canvas_size.h;
+ const int per_row = deficit / placement.m_rect.m_size.h;
+ for (int table_y = placement.m_rect.get_min_y ();
+ table_y < placement.m_rect.get_next_y ();
+ table_y++)
+ {
+ m_row_heights.m_requirements[table_y] += per_row;
+ deficit -= per_row;
+ }
+ /* Make sure we allocate all of the deficit. */
+ if (deficit > 0)
+ {
+ const int table_y = placement.m_rect.get_max_y ();
+ m_row_heights.m_requirements[table_y] += deficit;
+ }
+ }
+ }
+}
+
+canvas::size_t
+table_cell_sizes::get_canvas_size (const table::rect_t &rect) const
+{
+ canvas::size_t result (0, 0);
+ for (int table_x = rect.get_min_x ();
+ table_x < rect.get_next_x ();
+ table_x ++)
+ result.w += m_col_widths.m_requirements[table_x];
+ for (int table_y = rect.get_min_y ();
+ table_y < rect.get_next_y ();
+ table_y ++)
+ result.h += m_row_heights.m_requirements[table_y];
+ /* Allow space for the borders. */
+ result.w += rect.m_size.w - 1;
+ result.h += rect.m_size.h - 1;
+ return result;
+}
+
+/* class text_art::table_geometry. */
+
+table_geometry::table_geometry (const table &table, table_cell_sizes &cell_sizes)
+: m_table (table),
+ m_cell_sizes (cell_sizes),
+ m_canvas_size (canvas::size_t (0, 0)),
+ m_col_start_x (table.get_size ().w),
+ m_row_start_y (table.get_size ().h)
+{
+ recalc_coords ();
+}
+
+void
+table_geometry::recalc_coords ()
+{
+ /* Start canvas column of table cell, including leading border. */
+ m_col_start_x.clear ();
+ int iter_canvas_x = 0;
+ for (auto w : m_cell_sizes.m_col_widths.m_requirements)
+ {
+ m_col_start_x.push_back (iter_canvas_x);
+ iter_canvas_x += w + 1;
+ }
+
+ /* Start canvas row of table cell, including leading border. */
+ m_row_start_y.clear ();
+ int iter_canvas_y = 0;
+ for (auto h : m_cell_sizes.m_row_heights.m_requirements)
+ {
+ m_row_start_y.push_back (iter_canvas_y);
+ iter_canvas_y += h + 1;
+ }
+
+ m_canvas_size = canvas::size_t (iter_canvas_x + 1,
+ iter_canvas_y + 1);
+}
+
+/* Get the TL corner of the table cell at TABLE_COORD
+ in canvas coords (including the border). */
+
+canvas::coord_t
+table_geometry::table_to_canvas (table::coord_t table_coord) const
+{
+ return canvas::coord_t (table_x_to_canvas_x (table_coord.x),
+ table_y_to_canvas_y (table_coord.y));
+}
+
+/* Get the left border of the table cell at column TABLE_X
+ in canvas coords (including the border). */
+
+int
+table_geometry::table_x_to_canvas_x (int table_x) const
+{
+ /* Allow one beyond the end, for the right-hand border of the table. */
+ if (table_x == m_col_start_x.size ())
+ return m_canvas_size.w - 1;
+ return m_col_start_x[table_x];
+}
+
+/* Get the top border of the table cell at column TABLE_Y
+ in canvas coords (including the border). */
+
+int
+table_geometry::table_y_to_canvas_y (int table_y) const
+{
+ /* Allow one beyond the end, for the right-hand border of the table. */
+ if (table_y == m_row_start_y.size ())
+ return m_canvas_size.h - 1;
+ return m_row_start_y[table_y];
+}
+
+/* class text_art::simple_table_geometry. */
+
+simple_table_geometry::simple_table_geometry (const table &table)
+: m_col_widths (table.get_size ().w),
+ m_row_heights (table.get_size ().h),
+ m_cell_sizes (m_col_widths, m_row_heights),
+ m_tg (table, m_cell_sizes)
+{
+ m_cell_sizes.pass_1 (table);
+ m_cell_sizes.pass_2 (table);
+ m_tg.recalc_coords ();
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+test_tic_tac_toe ()
+{
+ style_manager sm;
+ table t (table::size_t (3, 3));
+ t.set_cell (table::coord_t (0, 0), styled_string (sm, "X"));
+ t.set_cell (table::coord_t (1, 0), styled_string (sm, ""));
+ t.set_cell (table::coord_t (2, 0), styled_string (sm, ""));
+ t.set_cell (table::coord_t (0, 1), styled_string (sm, "O"));
+ t.set_cell (table::coord_t (1, 1), styled_string (sm, "O"));
+ t.set_cell (table::coord_t (2, 1), styled_string (sm, ""));
+ t.set_cell (table::coord_t (0, 2), styled_string (sm, "X"));
+ t.set_cell (table::coord_t (1, 2), styled_string (sm, ""));
+ t.set_cell (table::coord_t (2, 2), styled_string (sm, "O"));
+
+ {
+ canvas canvas (t.to_canvas (ascii_theme (), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("+-+-+-+\n"
+ "|X| | |\n"
+ "+-+-+-+\n"
+ "|O|O| |\n"
+ "+-+-+-+\n"
+ "|X| |O|\n"
+ "+-+-+-+\n"));
+ }
+
+ {
+ canvas canvas (t.to_canvas (unicode_theme (), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("┌─┬─┬─┐\n"
+ "│X│ │ │\n"
+ "├─┼─┼─┤\n"
+ "│O│O│ │\n"
+ "├─┼─┼─┤\n"
+ "│X│ │O│\n"
+ "└─┴─┴─┘\n"));
+ }
+}
+
+static table
+make_text_table ()
+{
+ style_manager sm;
+ table t (table::size_t (3, 3));
+ t.set_cell (table::coord_t (0, 0), styled_string (sm, "top left"));
+ t.set_cell (table::coord_t (1, 0), styled_string (sm, "top middle"));
+ t.set_cell (table::coord_t (2, 0), styled_string (sm, "top right"));
+ t.set_cell (table::coord_t (0, 1), styled_string (sm, "middle left"));
+ t.set_cell (table::coord_t (1, 1), styled_string (sm, "middle middle"));
+ t.set_cell (table::coord_t (2, 1), styled_string (sm, "middle right"));
+ t.set_cell (table::coord_t (0, 2), styled_string (sm, "bottom left"));
+ t.set_cell (table::coord_t (1, 2), styled_string (sm, "bottom middle"));
+ t.set_cell (table::coord_t (2, 2), styled_string (sm, "bottom right"));
+ return t;
+}
+
+static void
+test_text_table ()
+{
+ style_manager sm;
+ table table = make_text_table ();
+ {
+ canvas canvas (table.to_canvas (ascii_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("+-----------+-------------+------------+\n"
+ "| top left | top middle | top right |\n"
+ "+-----------+-------------+------------+\n"
+ "|middle left|middle middle|middle right|\n"
+ "+-----------+-------------+------------+\n"
+ "|bottom left|bottom middle|bottom right|\n"
+ "+-----------+-------------+------------+\n"));
+ }
+ {
+ canvas canvas (table.to_canvas (unicode_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("┌───────────┬─────────────┬────────────┐\n"
+ "│ top left │ top middle │ top right │\n"
+ "├───────────┼─────────────┼────────────┤\n"
+ "│middle left│middle middle│middle right│\n"
+ "├───────────┼─────────────┼────────────┤\n"
+ "│bottom left│bottom middle│bottom right│\n"
+ "└───────────┴─────────────┴────────────┘\n"));
+ }
+}
+
+static void
+test_offset_table ()
+{
+ style_manager sm;
+ table table = make_text_table ();
+ simple_table_geometry stg (table);
+ const canvas::size_t tcs = stg.m_tg.get_canvas_size();
+ {
+ canvas canvas (canvas::size_t (tcs.w + 5, tcs.h + 5), sm);
+ canvas.debug_fill ();
+ table.paint_to_canvas (canvas, canvas::coord_t (3, 3),
+ stg.m_tg,
+ ascii_theme());
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("*********************************************\n"
+ "*********************************************\n"
+ "*********************************************\n"
+ "***+-----------+-------------+------------+**\n"
+ "***| top left | top middle | top right |**\n"
+ "***+-----------+-------------+------------+**\n"
+ "***|middle left|middle middle|middle right|**\n"
+ "***+-----------+-------------+------------+**\n"
+ "***|bottom left|bottom middle|bottom right|**\n"
+ "***+-----------+-------------+------------+**\n"
+ "*********************************************\n"
+ "*********************************************\n"));
+ }
+ {
+ canvas canvas (canvas::size_t (tcs.w + 5, tcs.h + 5), sm);
+ canvas.debug_fill ();
+ table.paint_to_canvas (canvas, canvas::coord_t (3, 3),
+ stg.m_tg,
+ unicode_theme());
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("*********************************************\n"
+ "*********************************************\n"
+ "*********************************************\n"
+ "***┌───────────┬─────────────┬────────────┐**\n"
+ "***│ top left │ top middle │ top right │**\n"
+ "***├───────────┼─────────────┼────────────┤**\n"
+ "***│middle left│middle middle│middle right│**\n"
+ "***├───────────┼─────────────┼────────────┤**\n"
+ "***│bottom left│bottom middle│bottom right│**\n"
+ "***└───────────┴─────────────┴────────────┘**\n"
+ "*********************************************\n"
+ "*********************************************\n"));
+ }
+}
+
+#define ASSERT_TABLE_CELL_STREQ(TABLE, TABLE_X, TABLE_Y, EXPECTED_STR) \
+ SELFTEST_BEGIN_STMT \
+ table::coord_t coord ((TABLE_X), (TABLE_Y)); \
+ const table::cell_placement *cp = (TABLE).get_placement_at (coord); \
+ ASSERT_NE (cp, nullptr); \
+ ASSERT_EQ (cp->get_content (), styled_string (sm, EXPECTED_STR)); \
+ SELFTEST_END_STMT
+
+#define ASSERT_TABLE_NULL_CELL(TABLE, TABLE_X, TABLE_Y) \
+ SELFTEST_BEGIN_STMT \
+ table::coord_t coord ((TABLE_X), (TABLE_Y)); \
+ const table::cell_placement *cp = (TABLE).get_placement_at (coord); \
+ ASSERT_EQ (cp, nullptr); \
+ SELFTEST_END_STMT
+
+static void
+test_spans ()
+{
+ style_manager sm;
+ table table (table::size_t (3, 3));
+ table.set_cell_span (table::rect_t (table::coord_t (0, 0),
+ table::size_t (3, 1)),
+ styled_string (sm, "ABC"));
+ table.set_cell_span (table::rect_t (table::coord_t (0, 1),
+ table::size_t (2, 1)),
+ styled_string (sm, "DE"));
+ table.set_cell_span (table::rect_t (table::coord_t (2, 1),
+ table::size_t (1, 1)),
+ styled_string (sm, "F"));
+ table.set_cell (table::coord_t (0, 2), styled_string (sm, "G"));
+ table.set_cell (table::coord_t (1, 2), styled_string (sm, "H"));
+ table.set_cell (table::coord_t (2, 2), styled_string (sm, "I"));
+ {
+ canvas canvas (table.to_canvas (ascii_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("+-----+\n"
+ "| ABC |\n"
+ "+---+-+\n"
+ "|DE |F|\n"
+ "+-+-+-+\n"
+ "|G|H|I|\n"
+ "+-+-+-+\n"));
+ }
+ {
+ canvas canvas (table.to_canvas (unicode_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("┌─────┐\n"
+ "│ ABC │\n"
+ "├───┬─┤\n"
+ "│DE │F│\n"
+ "├─┬─┼─┤\n"
+ "│G│H│I│\n"
+ "└─┴─┴─┘\n"));
+ }
+}
+
+/* Verify building this 5x5 table with spans:
+ |0|1|2|3|4|
+ +-+-+-+-+-+
+ 0|A A A|B|C|0
+ + +-+ +
+ 1|A A A|D|C|1
+ + +-+-+
+ 2|A A A|E|F|2
+ +-+-+-+-+-+
+ 3|G G|H|I I|3
+ | | +-+-+
+ 4|G G|H|J J|4
+ +-+-+-+-+-+
+ |0|1|2|3|4|
+*/
+
+static void
+test_spans_2 ()
+{
+ style_manager sm;
+ table table (table::size_t (5, 5));
+ table.set_cell_span (table::rect_t (table::coord_t (0, 0),
+ table::size_t (3, 3)),
+ styled_string (sm, "A"));
+ table.set_cell_span (table::rect_t (table::coord_t (3, 0),
+ table::size_t (1, 1)),
+ styled_string (sm, "B"));
+ table.set_cell_span (table::rect_t (table::coord_t (4, 0),
+ table::size_t (1, 2)),
+ styled_string (sm, "C"));
+ table.set_cell_span (table::rect_t (table::coord_t (3, 1),
+ table::size_t (1, 1)),
+ styled_string (sm, "D"));
+ table.set_cell_span (table::rect_t (table::coord_t (3, 2),
+ table::size_t (1, 1)),
+ styled_string (sm, "E"));
+ table.set_cell_span (table::rect_t (table::coord_t (4, 2),
+ table::size_t (1, 1)),
+ styled_string (sm, "F"));
+ table.set_cell_span (table::rect_t (table::coord_t (0, 3),
+ table::size_t (2, 2)),
+ styled_string (sm, "G"));
+ table.set_cell_span (table::rect_t (table::coord_t (2, 3),
+ table::size_t (1, 2)),
+ styled_string (sm, "H"));
+ table.set_cell_span (table::rect_t (table::coord_t (3, 3),
+ table::size_t (2, 1)),
+ styled_string (sm, "I"));
+ table.set_cell_span (table::rect_t (table::coord_t (3, 4),
+ table::size_t (2, 1)),
+ styled_string (sm, "J"));
+
+ /* Check occupancy at each table coordinate. */
+ ASSERT_TABLE_CELL_STREQ (table, 0, 0, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 1, 0, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 2, 0, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 3, 0, "B");
+ ASSERT_TABLE_CELL_STREQ (table, 4, 0, "C");
+
+ ASSERT_TABLE_CELL_STREQ (table, 0, 1, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 1, 1, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 2, 1, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 3, 1, "D");
+ ASSERT_TABLE_CELL_STREQ (table, 4, 1, "C");
+
+ ASSERT_TABLE_CELL_STREQ (table, 0, 2, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 1, 2, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 2, 2, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 3, 2, "E");
+ ASSERT_TABLE_CELL_STREQ (table, 4, 2, "F");
+
+ ASSERT_TABLE_CELL_STREQ (table, 0, 3, "G");
+ ASSERT_TABLE_CELL_STREQ (table, 1, 3, "G");
+ ASSERT_TABLE_CELL_STREQ (table, 2, 3, "H");
+ ASSERT_TABLE_CELL_STREQ (table, 3, 3, "I");
+ ASSERT_TABLE_CELL_STREQ (table, 4, 3, "I");
+
+ ASSERT_TABLE_CELL_STREQ (table, 0, 4, "G");
+ ASSERT_TABLE_CELL_STREQ (table, 1, 4, "G");
+ ASSERT_TABLE_CELL_STREQ (table, 2, 4, "H");
+ ASSERT_TABLE_CELL_STREQ (table, 3, 4, "J");
+ ASSERT_TABLE_CELL_STREQ (table, 4, 4, "J");
+
+ {
+ canvas canvas (table.to_canvas (ascii_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("+---+-+-+\n"
+ "| |B| |\n"
+ "| +-+C|\n"
+ "| A |D| |\n"
+ "| +-+-+\n"
+ "| |E|F|\n"
+ "+-+-+-+-+\n"
+ "| | | I |\n"
+ "|G|H+---+\n"
+ "| | | J |\n"
+ "+-+-+---+\n"));
+ }
+ {
+ canvas canvas (table.to_canvas (unicode_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("┌───┬─┬─┐\n"
+ "│ │B│ │\n"
+ "│ ├─┤C│\n"
+ "│ A │D│ │\n"
+ "│ ├─┼─┤\n"
+ "│ │E│F│\n"
+ "├─┬─┼─┴─┤\n"
+ "│ │ │ I │\n"
+ "│G│H├───┤\n"
+ "│ │ │ J │\n"
+ "└─┴─┴───┘\n"));
+ }
+}
+
+/* Experiment with adding a 1-table-column gap at the boundary between
+ valid vs invalid for visualizing a buffer overflow. */
+static void
+test_spans_3 ()
+{
+ const char * const str = "hello world!";
+ const size_t buf_size = 10;
+ const size_t str_size = strlen (str) + 1;
+
+ style_manager sm;
+ table table (table::size_t (str_size + 1, 3));
+
+ table.set_cell_span (table::rect_t (table::coord_t (0, 0),
+ table::size_t (str_size + 1, 1)),
+ styled_string (sm, "String literal"));
+
+ for (size_t i = 0; i < str_size; i++)
+ {
+ table::coord_t c (i, 1);
+ if (i >= buf_size)
+ c.x++;
+ if (str[i] == '\0')
+ table.set_cell (c, styled_string (sm, "NUL"));
+ else
+ table.set_cell (c, styled_string ((cppchar_t)str[i]));
+ }
+
+ table.set_cell_span (table::rect_t (table::coord_t (0, 2),
+ table::size_t (buf_size, 1)),
+ styled_string::from_fmt (sm,
+ nullptr,
+ "'buf' (char[%i])",
+ (int)buf_size));
+ table.set_cell_span (table::rect_t (table::coord_t (buf_size + 1, 2),
+ table::size_t (str_size - buf_size, 1)),
+ styled_string (sm, "overflow"));
+
+ {
+ canvas canvas (table.to_canvas (ascii_theme (), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ "+-----------------------------+\n"
+ "| String literal |\n"
+ "+-+-+-+-+-+-+-+-+-+-++-+-+----+\n"
+ "|h|e|l|l|o| |w|o|r|l||d|!|NUL |\n"
+ "+-+-+-+-+-+-+-+-+-+-++-+-+----+\n"
+ "| 'buf' (char[10]) ||overflow|\n"
+ "+-------------------++--------+\n");
+ }
+ {
+ canvas canvas (table.to_canvas (unicode_theme (), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("┌─────────────────────────────┐\n"
+ "│ String literal │\n"
+ "├─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬┬─┬─┬────┤\n"
+ "│h│e│l│l│o│ │w│o│r│l││d│!│NUL │\n"
+ "├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤├─┴─┴────┤\n"
+ "│ 'buf' (char[10]) ││overflow│\n"
+ "└───────────────────┘└────────┘\n"));
+ }
+}
+
+static void
+test_double_width_chars ()
+{
+ table_cell_content tcc (styled_string ((cppchar_t)0x1f642));
+ ASSERT_EQ (tcc.get_canvas_size ().w, 2);
+ ASSERT_EQ (tcc.get_canvas_size ().h, 1);
+
+ style_manager sm;
+ table table (table::size_t (1, 1));
+ table.set_cell (table::coord_t (0,0),
+ styled_string ((cppchar_t)0x1f642));
+
+ canvas canvas (table.to_canvas (unicode_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("┌──┐\n"
+ "│🙂│\n"
+ "└──┘\n"));
+}
+
+static void
+test_ipv4_header ()
+{
+ style_manager sm;
+ table table (table::size_t (34, 10));
+ table.set_cell (table::coord_t (0, 0), styled_string (sm, "Offsets"));
+ table.set_cell (table::coord_t (1, 0), styled_string (sm, "Octet"));
+ table.set_cell (table::coord_t (0, 1), styled_string (sm, "Octet"));
+ for (int octet = 0; octet < 4; octet++)
+ table.set_cell_span (table::rect_t (table::coord_t (2 + (octet * 8), 0),
+ table::size_t (8, 1)),
+ styled_string::from_fmt (sm, nullptr, "%i", octet));
+ table.set_cell (table::coord_t (1, 1), styled_string (sm, "Bit"));
+ for (int bit = 0; bit < 32; bit++)
+ table.set_cell (table::coord_t (bit + 2, 1),
+ styled_string::from_fmt (sm, nullptr, "%i", bit));
+ for (int word = 0; word < 6; word++)
+ {
+ table.set_cell (table::coord_t (0, word + 2),
+ styled_string::from_fmt (sm, nullptr, "%i", word * 4));
+ table.set_cell (table::coord_t (1, word + 2),
+ styled_string::from_fmt (sm, nullptr, "%i", word * 32));
+ }
+
+ table.set_cell (table::coord_t (0, 8), styled_string (sm, "..."));
+ table.set_cell (table::coord_t (1, 8), styled_string (sm, "..."));
+ table.set_cell (table::coord_t (0, 9), styled_string (sm, "56"));
+ table.set_cell (table::coord_t (1, 9), styled_string (sm, "448"));
+
+#define SET_BITS(FIRST, LAST, NAME) \
+ do { \
+ const int first = (FIRST); \
+ const int last = (LAST); \
+ const char *name = (NAME); \
+ const int row = first / 32; \
+ gcc_assert (last / 32 == row); \
+ table::rect_t rect (table::coord_t ((first % 32) + 2, row + 2), \
+ table::size_t (last + 1 - first , 1)); \
+ table.set_cell_span (rect, styled_string (sm, name)); \
+ } while (0)
+
+ SET_BITS (0, 3, "Version");
+ SET_BITS (4, 7, "IHL");
+ SET_BITS (8, 13, "DSCP");
+ SET_BITS (14, 15, "ECN");
+ SET_BITS (16, 31, "Total Length");
+
+ SET_BITS (32 + 0, 32 + 15, "Identification");
+ SET_BITS (32 + 16, 32 + 18, "Flags");
+ SET_BITS (32 + 19, 32 + 31, "Fragment Offset");
+
+ SET_BITS (64 + 0, 64 + 7, "Time To Live");
+ SET_BITS (64 + 8, 64 + 15, "Protocol");
+ SET_BITS (64 + 16, 64 + 31, "Header Checksum");
+
+ SET_BITS (96 + 0, 96 + 31, "Source IP Address");
+ SET_BITS (128 + 0, 128 + 31, "Destination IP Address");
+
+ table.set_cell_span(table::rect_t (table::coord_t (2, 7),
+ table::size_t (32, 3)),
+ styled_string (sm, "Options"));
+ {
+ canvas canvas (table.to_canvas (ascii_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("+-------+-----+---------------+---------------------+-----------------------+-----------------------+\n"
+ "|Offsets|Octet| 0 | 1 | 2 | 3 |\n"
+ "+-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n"
+ "| 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"
+ "+-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n"
+ "| 0 | 0 |Version| IHL | DSCP | ECN | Total Length |\n"
+ "+-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+\n"
+ "| 4 | 32 | Identification | Flags | Fragment Offset |\n"
+ "+-------+-----+---------------+---------------------+--------+--------------------------------------+\n"
+ "| 8 | 64 | Time To Live | Protocol | Header Checksum |\n"
+ "+-------+-----+---------------+---------------------+-----------------------------------------------+\n"
+ "| 12 | 96 | Source IP Address |\n"
+ "+-------+-----+-------------------------------------------------------------------------------------+\n"
+ "| 16 | 128 | Destination IP Address |\n"
+ "+-------+-----+-------------------------------------------------------------------------------------+\n"
+ "| 20 | 160 | |\n"
+ "+-------+-----+ |\n"
+ "| ... | ... | Options |\n"
+ "+-------+-----+ |\n"
+ "| 56 | 448 | |\n"
+ "+-------+-----+-------------------------------------------------------------------------------------+\n"));
+ }
+ {
+ canvas canvas (table.to_canvas (unicode_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐\n"
+ "│Offsets│Octet│ 0 │ 1 │ 2 │ 3 │\n"
+ "├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤\n"
+ "│ 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"
+ "├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤\n"
+ "│ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │\n"
+ "├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤\n"
+ "│ 4 │ 32 │ Identification │ Flags │ Fragment Offset │\n"
+ "├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤\n"
+ "│ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │\n"
+ "├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤\n"
+ "│ 12 │ 96 │ Source IP Address │\n"
+ "├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤\n"
+ "│ 16 │ 128 │ Destination IP Address │\n"
+ "├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤\n"
+ "│ 20 │ 160 │ │\n"
+ "├───────┼─────┤ │\n"
+ "│ ... │ ... │ Options │\n"
+ "├───────┼─────┤ │\n"
+ "│ 56 │ 448 │ │\n"
+ "└───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘\n"));
+ }
+}
+
+static void
+test_missing_cells ()
+{
+ style_manager sm;
+ table table (table::size_t (3, 3));
+ table.set_cell (table::coord_t (1, 0), styled_string (sm, "A"));
+ table.set_cell (table::coord_t (0, 1), styled_string (sm, "B"));
+ table.set_cell (table::coord_t (1, 1), styled_string (sm, "C"));
+ table.set_cell (table::coord_t (2, 1), styled_string (sm, "D"));
+ table.set_cell (table::coord_t (1, 2), styled_string (sm, "E"));
+
+ ASSERT_TABLE_NULL_CELL (table, 0, 0);
+ ASSERT_TABLE_CELL_STREQ (table, 1, 0, "A");
+ ASSERT_TABLE_NULL_CELL (table, 2, 0);
+
+ ASSERT_TABLE_CELL_STREQ (table, 0, 1, "B");
+ ASSERT_TABLE_CELL_STREQ (table, 1, 1, "C");
+ ASSERT_TABLE_CELL_STREQ (table, 2, 1, "D");
+
+ ASSERT_TABLE_NULL_CELL (table, 0, 2);
+ ASSERT_TABLE_CELL_STREQ (table, 1, 2, "E");
+ ASSERT_TABLE_NULL_CELL (table, 2, 2);
+
+ {
+ canvas canvas (table.to_canvas (ascii_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ (" +-+\n"
+ " |A|\n"
+ "+-+-+-+\n"
+ "|B|C|D|\n"
+ "+-+-+-+\n"
+ " |E|\n"
+ " +-+\n"));
+ }
+ {
+ canvas canvas (table.to_canvas (unicode_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ (" ┌─┐\n"
+ " │A│\n"
+ "┌─┼─┼─┐\n"
+ "│B│C│D│\n"
+ "└─┼─┼─┘\n"
+ " │E│\n"
+ " └─┘\n"));
+ }
+}
+
+static void
+test_add_row ()
+{
+ style_manager sm;
+ table table (table::size_t (3, 0));
+ for (int i = 0; i < 5; i++)
+ {
+ const int y = table.add_row ();
+ for (int x = 0; x < 3; x++)
+ table.set_cell (table::coord_t (x, y),
+ styled_string::from_fmt (sm, nullptr,
+ "%i, %i", x, y));
+ }
+ canvas canvas (table.to_canvas (ascii_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("+----+----+----+\n"
+ "|0, 0|1, 0|2, 0|\n"
+ "+----+----+----+\n"
+ "|0, 1|1, 1|2, 1|\n"
+ "+----+----+----+\n"
+ "|0, 2|1, 2|2, 2|\n"
+ "+----+----+----+\n"
+ "|0, 3|1, 3|2, 3|\n"
+ "+----+----+----+\n"
+ "|0, 4|1, 4|2, 4|\n"
+ "+----+----+----+\n"));
+}
+
+static void
+test_alignment ()
+{
+ style_manager sm;
+ table table (table::size_t (9, 9));
+ table.set_cell_span (table::rect_t (table::coord_t (0, 0),
+ table::size_t (3, 3)),
+ styled_string (sm, "left top"),
+ x_align::LEFT, y_align::TOP);
+ table.set_cell_span (table::rect_t (table::coord_t (3, 0),
+ table::size_t (3, 3)),
+ styled_string (sm, "center top"),
+ x_align::CENTER, y_align::TOP);
+ table.set_cell_span (table::rect_t (table::coord_t (6, 0),
+ table::size_t (3, 3)),
+ styled_string (sm, "right top"),
+ x_align::RIGHT, y_align::TOP);
+ table.set_cell_span (table::rect_t (table::coord_t (0, 3),
+ table::size_t (3, 3)),
+ styled_string (sm, "left center"),
+ x_align::LEFT, y_align::CENTER);
+ table.set_cell_span (table::rect_t (table::coord_t (3, 3),
+ table::size_t (3, 3)),
+ styled_string (sm, "center center"),
+ x_align::CENTER, y_align::CENTER);
+ table.set_cell_span (table::rect_t (table::coord_t (6, 3),
+ table::size_t (3, 3)),
+ styled_string (sm, "right center"),
+ x_align::RIGHT, y_align::CENTER);
+ table.set_cell_span (table::rect_t (table::coord_t (0, 6),
+ table::size_t (3, 3)),
+ styled_string (sm, "left bottom"),
+ x_align::LEFT, y_align::BOTTOM);
+ table.set_cell_span (table::rect_t (table::coord_t (3, 6),
+ table::size_t (3, 3)),
+ styled_string (sm, "center bottom"),
+ x_align::CENTER, y_align::BOTTOM);
+ table.set_cell_span (table::rect_t (table::coord_t (6, 6),
+ table::size_t (3, 3)),
+ styled_string (sm, "right bottom"),
+ x_align::RIGHT, y_align::BOTTOM);
+
+ canvas canvas (table.to_canvas (ascii_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("+-----------+-------------+------------+\n"
+ "|left top | center top | right top|\n"
+ "| | | |\n"
+ "+-----------+-------------+------------+\n"
+ "|left center|center center|right center|\n"
+ "| | | |\n"
+ "+-----------+-------------+------------+\n"
+ "| | | |\n"
+ "|left bottom|center bottom|right bottom|\n"
+ "+-----------+-------------+------------+\n"));
+}
+
+/* Run all selftests in this file. */
+
+void
+text_art_table_cc_tests ()
+{
+ test_tic_tac_toe ();
+ test_text_table ();
+ test_offset_table ();
+ test_spans ();
+ test_spans_2 ();
+ test_spans_3 ();
+ test_double_width_chars ();
+ test_ipv4_header ();
+ test_missing_cells ();
+ test_add_row ();
+ test_alignment ();
+}
+
+} // namespace selftest
+
+
+#endif /* #if CHECKING_P */