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