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