diff options
Diffstat (limited to 'gcc/text-art/table.cc')
-rw-r--r-- | gcc/text-art/table.cc | 1273 |
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 */ |