LCOV - code coverage report
Current view: top level - gcc/text-art - table.cc (source / functions) Coverage Total Hit
Test: gcc.info Lines: 98.0 % 599 587
Test Date: 2026-02-28 14:20:25 Functions: 97.4 % 39 38
Legend: Lines:     hit not hit

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

Generated by: LCOV version 2.4-beta

LCOV profile is generated on x86_64 machine using following configure options: configure --disable-bootstrap --enable-coverage=opt --enable-languages=c,c++,fortran,go,jit,lto,rust,m2 --enable-host-shared. GCC test suite is run with the built compiler.