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 | table_cell_content::table_cell_content (styled_string &&s) |
38 | : m_str (std::move (s)), |
39 | /* We assume here that the content occupies a single canvas row. */ |
40 | m_size (m_str.calc_canvas_width (), 1) |
41 | { |
42 | } |
43 | |
44 | void |
45 | table_cell_content::paint_to_canvas (canvas &canvas, |
46 | canvas::coord_t top_left) const |
47 | { |
48 | canvas.paint_text (coord: top_left, text: m_str); |
49 | } |
50 | |
51 | /* struct text_art::table_dimension_sizes. */ |
52 | |
53 | table_dimension_sizes::table_dimension_sizes (unsigned num) |
54 | : m_requirements (num, 0) |
55 | { |
56 | } |
57 | |
58 | /* class text_art::table::cell_placement. */ |
59 | |
60 | void |
61 | table::cell_placement::paint_cell_contents_to_canvas(canvas &canvas, |
62 | canvas::coord_t offset, |
63 | const table_geometry &tg) const |
64 | { |
65 | const canvas::size_t req_canvas_size = get_min_canvas_size (); |
66 | const canvas::size_t alloc_canvas_size = tg.get_canvas_size (rect: m_rect); |
67 | gcc_assert (req_canvas_size.w <= alloc_canvas_size.w); |
68 | gcc_assert (req_canvas_size.h <= alloc_canvas_size.h); |
69 | const int x_padding = alloc_canvas_size.w - req_canvas_size.w; |
70 | const int y_padding = alloc_canvas_size.h - req_canvas_size.h; |
71 | const table::coord_t table_top_left = m_rect.m_top_left; |
72 | const canvas::coord_t canvas_top_left = tg.table_to_canvas (table_coord: table_top_left); |
73 | |
74 | gcc_assert (x_padding >= 0); |
75 | int x_align_offset; |
76 | switch (m_x_align) |
77 | { |
78 | default: |
79 | gcc_unreachable (); |
80 | case x_align::LEFT: |
81 | x_align_offset = 0; |
82 | break; |
83 | case x_align::CENTER: |
84 | x_align_offset = x_padding / 2; |
85 | break; |
86 | case x_align::RIGHT: |
87 | x_align_offset = x_padding; |
88 | break; |
89 | } |
90 | |
91 | gcc_assert (y_padding >= 0); |
92 | int y_align_offset; |
93 | switch (m_y_align) |
94 | { |
95 | default: |
96 | gcc_unreachable (); |
97 | case y_align::TOP: |
98 | y_align_offset = 0; |
99 | break; |
100 | case y_align::CENTER: |
101 | y_align_offset = y_padding / 2; |
102 | break; |
103 | case y_align::BOTTOM: |
104 | y_align_offset = y_padding; |
105 | break; |
106 | } |
107 | const canvas::coord_t content_rel_coord |
108 | (canvas_top_left.x + 1 + x_align_offset, |
109 | canvas_top_left.y + 1 + y_align_offset); |
110 | m_content.paint_to_canvas (canvas, top_left: offset + content_rel_coord); |
111 | } |
112 | |
113 | /* class text_art::table. */ |
114 | |
115 | |
116 | table::table (size_t size) |
117 | : m_size (size), |
118 | m_placements (), |
119 | m_occupancy (size) |
120 | { |
121 | m_occupancy.fill (element: -1); |
122 | } |
123 | |
124 | void |
125 | 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 | set_cell_span (span: rect_t (coord, table::size_t (1, 1)), |
131 | content: std::move (content), x_align, y_align); |
132 | } |
133 | |
134 | void |
135 | 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 | gcc_assert (span.m_size.w > 0); |
141 | gcc_assert (span.m_size.h > 0); |
142 | int placement_idx = m_placements.size (); |
143 | m_placements.emplace_back (args: cell_placement (span, std::move (content), |
144 | x_align, y_align)); |
145 | for (int y = span.get_min_y (); y < span.get_next_y (); y++) |
146 | for (int x = span.get_min_x (); x < span.get_next_x (); x++) |
147 | { |
148 | gcc_assert (m_occupancy.get (coord_t (x, y)) == -1); |
149 | m_occupancy.set (coord: coord_t (x, y), element: placement_idx); |
150 | } |
151 | } |
152 | |
153 | /* If SPAN is unoccuped, set it to CONTENT. |
154 | Otherwise, discard CONTENT. */ |
155 | |
156 | void |
157 | 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 | gcc_assert (span.m_size.w > 0); |
163 | gcc_assert (span.m_size.h > 0); |
164 | for (int y = span.get_min_y (); y < span.get_next_y (); y++) |
165 | for (int x = span.get_min_x (); x < span.get_next_x (); x++) |
166 | { |
167 | if (m_occupancy.get (coord: coord_t (x, y)) != -1) |
168 | return; |
169 | } |
170 | set_cell_span (span, content: std::move (content), x_align, y_align); |
171 | } |
172 | |
173 | canvas |
174 | table::to_canvas (const theme &theme, const style_manager &sm) const |
175 | { |
176 | table_dimension_sizes col_widths (m_size.w); |
177 | table_dimension_sizes row_heights (m_size.h); |
178 | table_cell_sizes cell_sizes (col_widths, row_heights); |
179 | cell_sizes.pass_1 (table: *this); |
180 | cell_sizes.pass_2 (table: *this); |
181 | table_geometry tg (*this, cell_sizes); |
182 | canvas canvas (tg.get_canvas_size (), sm); |
183 | paint_to_canvas (canvas, offset: canvas::coord_t (0, 0), tg, theme); |
184 | return canvas; |
185 | } |
186 | |
187 | void |
188 | table::paint_to_canvas (canvas &canvas, |
189 | canvas::coord_t offset, |
190 | const table_geometry &tg, |
191 | const theme &theme) const |
192 | { |
193 | canvas.fill (rect: canvas::rect_t (offset, tg.get_canvas_size ()), |
194 | c: styled_unichar (' ')); |
195 | paint_cell_borders_to_canvas (canvas, offset, tg, theme); |
196 | paint_cell_contents_to_canvas (canvas, offset, tg); |
197 | } |
198 | |
199 | /* Print this table to stderr. */ |
200 | |
201 | DEBUG_FUNCTION void |
202 | 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 | style_manager sm; |
208 | canvas canvas (to_canvas (theme: unicode_theme (), sm)); |
209 | canvas.debug (styled: false); |
210 | } |
211 | |
212 | /* Move OTHER's content this table, starting at OFFSET. */ |
213 | |
214 | void |
215 | table::add_other_table (table &&other, |
216 | table::coord_t offset) |
217 | { |
218 | for (auto &&placement : other.m_placements) |
219 | { |
220 | set_cell_span (span: placement.m_rect + offset, |
221 | content: std::move (placement.m_content), |
222 | x_align: placement.m_x_align, |
223 | y_align: placement.m_y_align); |
224 | } |
225 | } |
226 | |
227 | const table::cell_placement * |
228 | table::get_placement_at (coord_t coord) const |
229 | { |
230 | const int placement_idx = m_occupancy.get (coord); |
231 | if (placement_idx == -1) |
232 | return nullptr; |
233 | return &m_placements[placement_idx]; |
234 | } |
235 | |
236 | int |
237 | table::get_occupancy_safe (coord_t coord) const |
238 | { |
239 | if (coord.x < 0) |
240 | return -1; |
241 | if (coord.x >= m_size.w) |
242 | return -1; |
243 | if (coord.y < 0) |
244 | return -1; |
245 | if (coord.y >= m_size.h) |
246 | return -1; |
247 | 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 | table::get_connections (int table_x, int table_y) const |
274 | { |
275 | int cell_a = get_occupancy_safe (coord: coord_t (table_x - 1, table_y - 1)); |
276 | int cell_b = get_occupancy_safe (coord: coord_t (table_x, table_y - 1)); |
277 | int cell_c = get_occupancy_safe (coord: coord_t (table_x - 1, table_y)); |
278 | int cell_d = get_occupancy_safe (coord: coord_t (table_x, table_y)); |
279 | const bool up = (cell_a != cell_b); |
280 | const bool down = (cell_c != cell_d); |
281 | const bool left = (cell_a != cell_c); |
282 | const bool right = (cell_b != cell_d); |
283 | 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 | 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 | const canvas::cell_t left_border |
348 | = theme.get_line_art (line_dirs: directions (true, /* up */ |
349 | true, /* down */ |
350 | false, /* left */ |
351 | false /* right */)); |
352 | const canvas::cell_t top_border |
353 | = theme.get_line_art (line_dirs: directions (false, /* up */ |
354 | false, /* down */ |
355 | true, /* left */ |
356 | true)); /* right */ |
357 | for (int table_y = 0; table_y < m_size.h; table_y++) |
358 | { |
359 | const int canvas_y = tg.table_y_to_canvas_y (table_y); |
360 | for (int table_x = 0; table_x < m_size.w; table_x++) |
361 | { |
362 | canvas::coord_t canvas_top_left |
363 | = tg.table_to_canvas(table_coord: table::coord_t (table_x, table_y)); |
364 | |
365 | const directions c (get_connections (table_x, table_y)); |
366 | |
367 | /* Paint top-left corner of border, if any. */ |
368 | canvas.paint (coord: offset + canvas_top_left, |
369 | c: theme.get_line_art (line_dirs: 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 | if (c.m_down) |
374 | canvas.paint (coord: offset + canvas::coord_t (canvas_top_left.x, |
375 | canvas_y + 1), |
376 | c: left_border); |
377 | |
378 | /* Paint remainder of top border of cell, if any. */ |
379 | if (c.m_right) |
380 | { |
381 | const int col_width = tg.get_col_width (table_x); |
382 | for (int x_offset = 0; x_offset < col_width; x_offset++) |
383 | { |
384 | const int canvas_x = canvas_top_left.x + 1 + x_offset; |
385 | canvas.paint (coord: offset + canvas::coord_t (canvas_x, canvas_y), |
386 | c: top_border); |
387 | } |
388 | } |
389 | } |
390 | |
391 | /* Paint right-hand border of row. */ |
392 | const int table_x = m_size.w; |
393 | const int canvas_x = tg.table_x_to_canvas_x (table_x); |
394 | const directions c (get_connections (table_x: m_size.w, table_y)); |
395 | canvas.paint(coord: offset + canvas::coord_t (canvas_x, canvas_y), |
396 | c: theme.get_line_art (line_dirs: directions (c.m_up, |
397 | c.m_down, |
398 | c.m_left, |
399 | false))); /* right */ |
400 | /* We assume here that the content occupies a single canvas row. */ |
401 | canvas.paint(coord: offset + canvas::coord_t (canvas_x, canvas_y + 1), |
402 | c: theme.get_line_art (line_dirs: 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 | { |
410 | const int canvas_y = tg.get_canvas_size ().h - 1; |
411 | for (int table_x = 0; table_x < m_size.w; table_x++) |
412 | { |
413 | const directions c (get_connections (table_x, table_y: m_size.h)); |
414 | const int left_canvas_x = tg.table_x_to_canvas_x (table_x); |
415 | canvas.paint (coord: offset + canvas::coord_t (left_canvas_x, canvas_y), |
416 | c: theme.get_line_art (line_dirs: directions (c.m_up, |
417 | false, /* down */ |
418 | c.m_left, |
419 | c.m_right))); |
420 | const int col_width = tg.get_col_width (table_x); |
421 | for (int x_offset = 0; x_offset < col_width; x_offset++) |
422 | { |
423 | const int canvas_x = left_canvas_x + 1 + x_offset; |
424 | canvas.paint(coord: offset + canvas::coord_t (canvas_x, canvas_y), |
425 | c: theme.get_line_art (line_dirs: 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 | const int table_x = m_size.w; |
434 | const int canvas_x = tg.table_x_to_canvas_x (table_x); |
435 | const directions c (get_connections (table_x: m_size.w, table_y: m_size.h)); |
436 | canvas.paint (coord: offset + canvas::coord_t (canvas_x, canvas_y), |
437 | c: theme.get_line_art (line_dirs: directions (c.m_up, // up |
438 | false, // down |
439 | c.m_left, // left |
440 | false))); // right |
441 | } |
442 | } |
443 | |
444 | void |
445 | table::paint_cell_contents_to_canvas(canvas &canvas, |
446 | canvas::coord_t offset, |
447 | const table_geometry &tg) const |
448 | { |
449 | for (auto &placement : m_placements) |
450 | placement.paint_cell_contents_to_canvas (canvas, offset, tg); |
451 | } |
452 | |
453 | /* class table_cell_sizes. */ |
454 | |
455 | /* Consider 1x1 cells. */ |
456 | |
457 | void |
458 | table_cell_sizes::pass_1 (const table &table) |
459 | { |
460 | for (auto &placement : table.m_placements) |
461 | if (placement.one_by_one_p ()) |
462 | { |
463 | canvas::size_t canvas_size (placement.get_min_canvas_size ()); |
464 | table::coord_t table_coord (placement.m_rect.m_top_left); |
465 | m_col_widths.require (idx: table_coord.x, amount: canvas_size.w); |
466 | m_row_heights.require (idx: table_coord.y, amount: canvas_size.h); |
467 | } |
468 | } |
469 | |
470 | /* Consider cells that span more than one row or column. */ |
471 | |
472 | void |
473 | table_cell_sizes::pass_2 (const table &table) |
474 | { |
475 | for (auto &placement : table.m_placements) |
476 | if (!placement.one_by_one_p ()) |
477 | { |
478 | const canvas::size_t req_canvas_size (placement.get_min_canvas_size ()); |
479 | const canvas::size_t current_canvas_size |
480 | = get_canvas_size (rect: placement.m_rect); |
481 | /* Grow columns as necessary. */ |
482 | if (req_canvas_size.w > current_canvas_size.w) |
483 | { |
484 | /* Spread the deficit amongst the columns. */ |
485 | int deficit = req_canvas_size.w - current_canvas_size.w; |
486 | const int per_col = deficit / placement.m_rect.m_size.w; |
487 | for (int table_x = placement.m_rect.get_min_x (); |
488 | table_x < placement.m_rect.get_next_x (); |
489 | table_x++) |
490 | { |
491 | m_col_widths.m_requirements[table_x] += per_col; |
492 | deficit -= per_col; |
493 | } |
494 | /* Make sure we allocate all of the deficit. */ |
495 | if (deficit > 0) |
496 | { |
497 | const int table_x = placement.m_rect.get_max_x (); |
498 | m_col_widths.m_requirements[table_x] += deficit; |
499 | } |
500 | } |
501 | /* Grow rows as necessary. */ |
502 | if (req_canvas_size.h > current_canvas_size.h) |
503 | { |
504 | /* Spread the deficit amongst the rows. */ |
505 | int deficit = req_canvas_size.h - current_canvas_size.h; |
506 | const int per_row = deficit / placement.m_rect.m_size.h; |
507 | for (int table_y = placement.m_rect.get_min_y (); |
508 | table_y < placement.m_rect.get_next_y (); |
509 | table_y++) |
510 | { |
511 | m_row_heights.m_requirements[table_y] += per_row; |
512 | deficit -= per_row; |
513 | } |
514 | /* Make sure we allocate all of the deficit. */ |
515 | if (deficit > 0) |
516 | { |
517 | const int table_y = placement.m_rect.get_max_y (); |
518 | m_row_heights.m_requirements[table_y] += deficit; |
519 | } |
520 | } |
521 | } |
522 | } |
523 | |
524 | canvas::size_t |
525 | table_cell_sizes::get_canvas_size (const table::rect_t &rect) const |
526 | { |
527 | canvas::size_t result (0, 0); |
528 | for (int table_x = rect.get_min_x (); |
529 | table_x < rect.get_next_x (); |
530 | table_x ++) |
531 | result.w += m_col_widths.m_requirements[table_x]; |
532 | for (int table_y = rect.get_min_y (); |
533 | table_y < rect.get_next_y (); |
534 | table_y ++) |
535 | result.h += m_row_heights.m_requirements[table_y]; |
536 | /* Allow space for the borders. */ |
537 | result.w += rect.m_size.w - 1; |
538 | result.h += rect.m_size.h - 1; |
539 | return result; |
540 | } |
541 | |
542 | /* class text_art::table_geometry. */ |
543 | |
544 | table_geometry::table_geometry (const table &table, table_cell_sizes &cell_sizes) |
545 | : m_cell_sizes (cell_sizes), |
546 | m_canvas_size (canvas::size_t (0, 0)), |
547 | m_col_start_x (table.get_size ().w), |
548 | m_row_start_y (table.get_size ().h) |
549 | { |
550 | recalc_coords (); |
551 | } |
552 | |
553 | void |
554 | table_geometry::recalc_coords () |
555 | { |
556 | /* Start canvas column of table cell, including leading border. */ |
557 | m_col_start_x.clear (); |
558 | int iter_canvas_x = 0; |
559 | for (auto w : m_cell_sizes.m_col_widths.m_requirements) |
560 | { |
561 | m_col_start_x.push_back (x: iter_canvas_x); |
562 | iter_canvas_x += w + 1; |
563 | } |
564 | |
565 | /* Start canvas row of table cell, including leading border. */ |
566 | m_row_start_y.clear (); |
567 | int iter_canvas_y = 0; |
568 | for (auto h : m_cell_sizes.m_row_heights.m_requirements) |
569 | { |
570 | m_row_start_y.push_back (x: iter_canvas_y); |
571 | iter_canvas_y += h + 1; |
572 | } |
573 | |
574 | m_canvas_size = canvas::size_t (iter_canvas_x + 1, |
575 | iter_canvas_y + 1); |
576 | } |
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 | table_geometry::table_to_canvas (table::coord_t table_coord) const |
583 | { |
584 | return canvas::coord_t (table_x_to_canvas_x (table_x: table_coord.x), |
585 | table_y_to_canvas_y (table_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 | 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 | if (table_x == (int)m_col_start_x.size ()) |
596 | return m_canvas_size.w - 1; |
597 | 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 | 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 | if (table_y == (int)m_row_start_y.size ()) |
608 | return m_canvas_size.h - 1; |
609 | return m_row_start_y[table_y]; |
610 | } |
611 | |
612 | /* class text_art::simple_table_geometry. */ |
613 | |
614 | simple_table_geometry::simple_table_geometry (const table &table) |
615 | : m_col_widths (table.get_size ().w), |
616 | m_row_heights (table.get_size ().h), |
617 | m_cell_sizes (m_col_widths, m_row_heights), |
618 | m_tg (table, m_cell_sizes) |
619 | { |
620 | m_cell_sizes.pass_1 (table); |
621 | m_cell_sizes.pass_2 (table); |
622 | m_tg.recalc_coords (); |
623 | } |
624 | |
625 | #if CHECKING_P |
626 | |
627 | namespace selftest { |
628 | |
629 | static void |
630 | test_tic_tac_toe () |
631 | { |
632 | style_manager sm; |
633 | table t (table::size_t (3, 3)); |
634 | t.set_cell (coord: table::coord_t (0, 0), content: styled_string (sm, "X" )); |
635 | t.set_cell (coord: table::coord_t (1, 0), content: styled_string (sm, "" )); |
636 | t.set_cell (coord: table::coord_t (2, 0), content: styled_string (sm, "" )); |
637 | t.set_cell (coord: table::coord_t (0, 1), content: styled_string (sm, "O" )); |
638 | t.set_cell (coord: table::coord_t (1, 1), content: styled_string (sm, "O" )); |
639 | t.set_cell (coord: table::coord_t (2, 1), content: styled_string (sm, "" )); |
640 | t.set_cell (coord: table::coord_t (0, 2), content: styled_string (sm, "X" )); |
641 | t.set_cell (coord: table::coord_t (1, 2), content: styled_string (sm, "" )); |
642 | t.set_cell (coord: table::coord_t (2, 2), content: styled_string (sm, "O" )); |
643 | |
644 | { |
645 | canvas canvas (t.to_canvas (theme: ascii_theme (), sm)); |
646 | 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 | } |
656 | |
657 | { |
658 | canvas canvas (t.to_canvas (theme: unicode_theme (), sm)); |
659 | 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 | } |
670 | } |
671 | |
672 | static table |
673 | make_text_table () |
674 | { |
675 | style_manager sm; |
676 | table t (table::size_t (3, 3)); |
677 | t.set_cell (coord: table::coord_t (0, 0), content: styled_string (sm, "top left" )); |
678 | t.set_cell (coord: table::coord_t (1, 0), content: styled_string (sm, "top middle" )); |
679 | t.set_cell (coord: table::coord_t (2, 0), content: styled_string (sm, "top right" )); |
680 | t.set_cell (coord: table::coord_t (0, 1), content: styled_string (sm, "middle left" )); |
681 | t.set_cell (coord: table::coord_t (1, 1), content: styled_string (sm, "middle middle" )); |
682 | t.set_cell (coord: table::coord_t (2, 1), content: styled_string (sm, "middle right" )); |
683 | t.set_cell (coord: table::coord_t (0, 2), content: styled_string (sm, "bottom left" )); |
684 | t.set_cell (coord: table::coord_t (1, 2), content: styled_string (sm, "bottom middle" )); |
685 | t.set_cell (coord: table::coord_t (2, 2), content: styled_string (sm, "bottom right" )); |
686 | return t; |
687 | } |
688 | |
689 | static void |
690 | test_text_table () |
691 | { |
692 | style_manager sm; |
693 | table table = make_text_table (); |
694 | { |
695 | canvas canvas (table.to_canvas (theme: ascii_theme(), sm)); |
696 | 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 | } |
706 | { |
707 | canvas canvas (table.to_canvas (theme: unicode_theme(), sm)); |
708 | 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 | } |
719 | } |
720 | |
721 | static void |
722 | test_offset_table () |
723 | { |
724 | style_manager sm; |
725 | table table = make_text_table (); |
726 | simple_table_geometry stg (table); |
727 | const canvas::size_t tcs = stg.m_tg.get_canvas_size(); |
728 | { |
729 | canvas canvas (canvas::size_t (tcs.w + 5, tcs.h + 5), sm); |
730 | canvas.debug_fill (); |
731 | table.paint_to_canvas (canvas, offset: canvas::coord_t (3, 3), |
732 | tg: stg.m_tg, |
733 | theme: ascii_theme()); |
734 | 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 | } |
749 | { |
750 | canvas canvas (canvas::size_t (tcs.w + 5, tcs.h + 5), sm); |
751 | canvas.debug_fill (); |
752 | table.paint_to_canvas (canvas, offset: canvas::coord_t (3, 3), |
753 | tg: stg.m_tg, |
754 | theme: unicode_theme()); |
755 | 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 | } |
771 | } |
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 | test_spans () |
790 | { |
791 | style_manager sm; |
792 | table table (table::size_t (3, 3)); |
793 | table.set_cell_span (span: table::rect_t (table::coord_t (0, 0), |
794 | table::size_t (3, 1)), |
795 | content: styled_string (sm, "ABC" )); |
796 | table.set_cell_span (span: table::rect_t (table::coord_t (0, 1), |
797 | table::size_t (2, 1)), |
798 | content: styled_string (sm, "DE" )); |
799 | table.set_cell_span (span: table::rect_t (table::coord_t (2, 1), |
800 | table::size_t (1, 1)), |
801 | content: styled_string (sm, "F" )); |
802 | table.set_cell (coord: table::coord_t (0, 2), content: styled_string (sm, "G" )); |
803 | table.set_cell (coord: table::coord_t (1, 2), content: styled_string (sm, "H" )); |
804 | table.set_cell (coord: table::coord_t (2, 2), content: styled_string (sm, "I" )); |
805 | { |
806 | canvas canvas (table.to_canvas (theme: ascii_theme(), sm)); |
807 | 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 | } |
817 | { |
818 | canvas canvas (table.to_canvas (theme: unicode_theme(), sm)); |
819 | 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 | } |
830 | } |
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 | test_spans_2 () |
850 | { |
851 | style_manager sm; |
852 | table table (table::size_t (5, 5)); |
853 | table.set_cell_span (span: table::rect_t (table::coord_t (0, 0), |
854 | table::size_t (3, 3)), |
855 | content: styled_string (sm, "A" )); |
856 | table.set_cell_span (span: table::rect_t (table::coord_t (3, 0), |
857 | table::size_t (1, 1)), |
858 | content: styled_string (sm, "B" )); |
859 | table.set_cell_span (span: table::rect_t (table::coord_t (4, 0), |
860 | table::size_t (1, 2)), |
861 | content: styled_string (sm, "C" )); |
862 | table.set_cell_span (span: table::rect_t (table::coord_t (3, 1), |
863 | table::size_t (1, 1)), |
864 | content: styled_string (sm, "D" )); |
865 | table.set_cell_span (span: table::rect_t (table::coord_t (3, 2), |
866 | table::size_t (1, 1)), |
867 | content: styled_string (sm, "E" )); |
868 | table.set_cell_span (span: table::rect_t (table::coord_t (4, 2), |
869 | table::size_t (1, 1)), |
870 | content: styled_string (sm, "F" )); |
871 | table.set_cell_span (span: table::rect_t (table::coord_t (0, 3), |
872 | table::size_t (2, 2)), |
873 | content: styled_string (sm, "G" )); |
874 | table.set_cell_span (span: table::rect_t (table::coord_t (2, 3), |
875 | table::size_t (1, 2)), |
876 | content: styled_string (sm, "H" )); |
877 | table.set_cell_span (span: table::rect_t (table::coord_t (3, 3), |
878 | table::size_t (2, 1)), |
879 | content: styled_string (sm, "I" )); |
880 | table.set_cell_span (span: table::rect_t (table::coord_t (3, 4), |
881 | table::size_t (2, 1)), |
882 | content: styled_string (sm, "J" )); |
883 | |
884 | /* Check occupancy at each table coordinate. */ |
885 | ASSERT_TABLE_CELL_STREQ (table, 0, 0, "A" ); |
886 | ASSERT_TABLE_CELL_STREQ (table, 1, 0, "A" ); |
887 | ASSERT_TABLE_CELL_STREQ (table, 2, 0, "A" ); |
888 | ASSERT_TABLE_CELL_STREQ (table, 3, 0, "B" ); |
889 | ASSERT_TABLE_CELL_STREQ (table, 4, 0, "C" ); |
890 | |
891 | ASSERT_TABLE_CELL_STREQ (table, 0, 1, "A" ); |
892 | ASSERT_TABLE_CELL_STREQ (table, 1, 1, "A" ); |
893 | ASSERT_TABLE_CELL_STREQ (table, 2, 1, "A" ); |
894 | ASSERT_TABLE_CELL_STREQ (table, 3, 1, "D" ); |
895 | ASSERT_TABLE_CELL_STREQ (table, 4, 1, "C" ); |
896 | |
897 | ASSERT_TABLE_CELL_STREQ (table, 0, 2, "A" ); |
898 | ASSERT_TABLE_CELL_STREQ (table, 1, 2, "A" ); |
899 | ASSERT_TABLE_CELL_STREQ (table, 2, 2, "A" ); |
900 | ASSERT_TABLE_CELL_STREQ (table, 3, 2, "E" ); |
901 | ASSERT_TABLE_CELL_STREQ (table, 4, 2, "F" ); |
902 | |
903 | ASSERT_TABLE_CELL_STREQ (table, 0, 3, "G" ); |
904 | ASSERT_TABLE_CELL_STREQ (table, 1, 3, "G" ); |
905 | ASSERT_TABLE_CELL_STREQ (table, 2, 3, "H" ); |
906 | ASSERT_TABLE_CELL_STREQ (table, 3, 3, "I" ); |
907 | ASSERT_TABLE_CELL_STREQ (table, 4, 3, "I" ); |
908 | |
909 | ASSERT_TABLE_CELL_STREQ (table, 0, 4, "G" ); |
910 | ASSERT_TABLE_CELL_STREQ (table, 1, 4, "G" ); |
911 | ASSERT_TABLE_CELL_STREQ (table, 2, 4, "H" ); |
912 | ASSERT_TABLE_CELL_STREQ (table, 3, 4, "J" ); |
913 | ASSERT_TABLE_CELL_STREQ (table, 4, 4, "J" ); |
914 | |
915 | { |
916 | canvas canvas (table.to_canvas (theme: ascii_theme(), sm)); |
917 | 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 | } |
931 | { |
932 | canvas canvas (table.to_canvas (theme: unicode_theme(), sm)); |
933 | 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 | } |
948 | } |
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 | test_spans_3 () |
954 | { |
955 | const char * const str = "hello world!" ; |
956 | const size_t buf_size = 10; |
957 | const size_t str_size = strlen (s: str) + 1; |
958 | |
959 | style_manager sm; |
960 | table table (table::size_t (str_size + 1, 3)); |
961 | |
962 | table.set_cell_span (span: table::rect_t (table::coord_t (0, 0), |
963 | table::size_t (str_size + 1, 1)), |
964 | content: styled_string (sm, "String literal" )); |
965 | |
966 | for (size_t i = 0; i < str_size; i++) |
967 | { |
968 | table::coord_t c (i, 1); |
969 | if (i >= buf_size) |
970 | c.x++; |
971 | if (str[i] == '\0') |
972 | table.set_cell (coord: c, content: styled_string (sm, "NUL" )); |
973 | else |
974 | table.set_cell (coord: c, content: styled_string ((cppchar_t)str[i])); |
975 | } |
976 | |
977 | table.set_cell_span (span: table::rect_t (table::coord_t (0, 2), |
978 | table::size_t (buf_size, 1)), |
979 | content: styled_string::from_fmt (sm, |
980 | format_decoder: nullptr, |
981 | fmt: "'buf' (char[%i])" , |
982 | (int)buf_size)); |
983 | table.set_cell_span (span: table::rect_t (table::coord_t (buf_size + 1, 2), |
984 | table::size_t (str_size - buf_size, 1)), |
985 | content: styled_string (sm, "overflow" )); |
986 | |
987 | { |
988 | canvas canvas (table.to_canvas (theme: ascii_theme (), sm)); |
989 | 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 | } |
999 | { |
1000 | canvas canvas (table.to_canvas (theme: unicode_theme (), sm)); |
1001 | 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 | } |
1012 | } |
1013 | |
1014 | static void |
1015 | test_double_width_chars () |
1016 | { |
1017 | table_cell_content tcc (styled_string ((cppchar_t)0x1f642)); |
1018 | ASSERT_EQ (tcc.get_canvas_size ().w, 2); |
1019 | ASSERT_EQ (tcc.get_canvas_size ().h, 1); |
1020 | |
1021 | style_manager sm; |
1022 | table table (table::size_t (1, 1)); |
1023 | table.set_cell (coord: table::coord_t (0,0), |
1024 | content: styled_string ((cppchar_t)0x1f642)); |
1025 | |
1026 | canvas canvas (table.to_canvas (theme: unicode_theme(), sm)); |
1027 | 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 | } |
1034 | |
1035 | static void |
1036 | () |
1037 | { |
1038 | style_manager sm; |
1039 | table table (table::size_t (34, 10)); |
1040 | table.set_cell (coord: table::coord_t (0, 0), content: styled_string (sm, "Offsets" )); |
1041 | table.set_cell (coord: table::coord_t (1, 0), content: styled_string (sm, "Octet" )); |
1042 | table.set_cell (coord: table::coord_t (0, 1), content: styled_string (sm, "Octet" )); |
1043 | for (int octet = 0; octet < 4; octet++) |
1044 | table.set_cell_span (span: table::rect_t (table::coord_t (2 + (octet * 8), 0), |
1045 | table::size_t (8, 1)), |
1046 | content: styled_string::from_fmt (sm, format_decoder: nullptr, fmt: "%i" , octet)); |
1047 | table.set_cell (coord: table::coord_t (1, 1), content: styled_string (sm, "Bit" )); |
1048 | for (int bit = 0; bit < 32; bit++) |
1049 | table.set_cell (coord: table::coord_t (bit + 2, 1), |
1050 | content: styled_string::from_fmt (sm, format_decoder: nullptr, fmt: "%i" , bit)); |
1051 | for (int word = 0; word < 6; word++) |
1052 | { |
1053 | table.set_cell (coord: table::coord_t (0, word + 2), |
1054 | content: styled_string::from_fmt (sm, format_decoder: nullptr, fmt: "%i" , word * 4)); |
1055 | table.set_cell (coord: table::coord_t (1, word + 2), |
1056 | content: styled_string::from_fmt (sm, format_decoder: nullptr, fmt: "%i" , word * 32)); |
1057 | } |
1058 | |
1059 | table.set_cell (coord: table::coord_t (0, 8), content: styled_string (sm, "..." )); |
1060 | table.set_cell (coord: table::coord_t (1, 8), content: styled_string (sm, "..." )); |
1061 | table.set_cell (coord: table::coord_t (0, 9), content: styled_string (sm, "56" )); |
1062 | table.set_cell (coord: table::coord_t (1, 9), content: 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 | SET_BITS (0, 3, "Version" ); |
1077 | SET_BITS (4, 7, "IHL" ); |
1078 | SET_BITS (8, 13, "DSCP" ); |
1079 | SET_BITS (14, 15, "ECN" ); |
1080 | SET_BITS (16, 31, "Total Length" ); |
1081 | |
1082 | SET_BITS (32 + 0, 32 + 15, "Identification" ); |
1083 | SET_BITS (32 + 16, 32 + 18, "Flags" ); |
1084 | SET_BITS (32 + 19, 32 + 31, "Fragment Offset" ); |
1085 | |
1086 | SET_BITS (64 + 0, 64 + 7, "Time To Live" ); |
1087 | SET_BITS (64 + 8, 64 + 15, "Protocol" ); |
1088 | SET_BITS (64 + 16, 64 + 31, "Header Checksum" ); |
1089 | |
1090 | SET_BITS (96 + 0, 96 + 31, "Source IP Address" ); |
1091 | SET_BITS (128 + 0, 128 + 31, "Destination IP Address" ); |
1092 | |
1093 | table.set_cell_span(span: table::rect_t (table::coord_t (2, 7), |
1094 | table::size_t (32, 3)), |
1095 | content: styled_string (sm, "Options" )); |
1096 | { |
1097 | canvas canvas (table.to_canvas (theme: ascii_theme(), sm)); |
1098 | 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 | } |
1122 | { |
1123 | canvas canvas (table.to_canvas (theme: unicode_theme(), sm)); |
1124 | 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 | } |
1149 | } |
1150 | |
1151 | static void |
1152 | test_missing_cells () |
1153 | { |
1154 | style_manager sm; |
1155 | table table (table::size_t (3, 3)); |
1156 | table.set_cell (coord: table::coord_t (1, 0), content: styled_string (sm, "A" )); |
1157 | table.set_cell (coord: table::coord_t (0, 1), content: styled_string (sm, "B" )); |
1158 | table.set_cell (coord: table::coord_t (1, 1), content: styled_string (sm, "C" )); |
1159 | table.set_cell (coord: table::coord_t (2, 1), content: styled_string (sm, "D" )); |
1160 | table.set_cell (coord: table::coord_t (1, 2), content: styled_string (sm, "E" )); |
1161 | |
1162 | ASSERT_TABLE_NULL_CELL (table, 0, 0); |
1163 | ASSERT_TABLE_CELL_STREQ (table, 1, 0, "A" ); |
1164 | ASSERT_TABLE_NULL_CELL (table, 2, 0); |
1165 | |
1166 | ASSERT_TABLE_CELL_STREQ (table, 0, 1, "B" ); |
1167 | ASSERT_TABLE_CELL_STREQ (table, 1, 1, "C" ); |
1168 | ASSERT_TABLE_CELL_STREQ (table, 2, 1, "D" ); |
1169 | |
1170 | ASSERT_TABLE_NULL_CELL (table, 0, 2); |
1171 | ASSERT_TABLE_CELL_STREQ (table, 1, 2, "E" ); |
1172 | ASSERT_TABLE_NULL_CELL (table, 2, 2); |
1173 | |
1174 | { |
1175 | canvas canvas (table.to_canvas (theme: ascii_theme(), sm)); |
1176 | 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 | } |
1186 | { |
1187 | canvas canvas (table.to_canvas (theme: unicode_theme(), sm)); |
1188 | 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 | } |
1198 | } |
1199 | |
1200 | static void |
1201 | test_add_row () |
1202 | { |
1203 | style_manager sm; |
1204 | table table (table::size_t (3, 0)); |
1205 | for (int i = 0; i < 5; i++) |
1206 | { |
1207 | const int y = table.add_row (); |
1208 | for (int x = 0; x < 3; x++) |
1209 | table.set_cell (coord: table::coord_t (x, y), |
1210 | content: styled_string::from_fmt (sm, format_decoder: nullptr, |
1211 | fmt: "%i, %i" , x, y)); |
1212 | } |
1213 | canvas canvas (table.to_canvas (theme: ascii_theme(), sm)); |
1214 | 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 | } |
1228 | |
1229 | static void |
1230 | test_alignment () |
1231 | { |
1232 | style_manager sm; |
1233 | table table (table::size_t (9, 9)); |
1234 | table.set_cell_span (span: table::rect_t (table::coord_t (0, 0), |
1235 | table::size_t (3, 3)), |
1236 | content: styled_string (sm, "left top" ), |
1237 | x_align: x_align::LEFT, y_align: y_align::TOP); |
1238 | table.set_cell_span (span: table::rect_t (table::coord_t (3, 0), |
1239 | table::size_t (3, 3)), |
1240 | content: styled_string (sm, "center top" ), |
1241 | x_align: x_align::CENTER, y_align: y_align::TOP); |
1242 | table.set_cell_span (span: table::rect_t (table::coord_t (6, 0), |
1243 | table::size_t (3, 3)), |
1244 | content: styled_string (sm, "right top" ), |
1245 | x_align: x_align::RIGHT, y_align: y_align::TOP); |
1246 | table.set_cell_span (span: table::rect_t (table::coord_t (0, 3), |
1247 | table::size_t (3, 3)), |
1248 | content: styled_string (sm, "left center" ), |
1249 | x_align: x_align::LEFT, y_align: y_align::CENTER); |
1250 | table.set_cell_span (span: table::rect_t (table::coord_t (3, 3), |
1251 | table::size_t (3, 3)), |
1252 | content: styled_string (sm, "center center" ), |
1253 | x_align: x_align::CENTER, y_align: y_align::CENTER); |
1254 | table.set_cell_span (span: table::rect_t (table::coord_t (6, 3), |
1255 | table::size_t (3, 3)), |
1256 | content: styled_string (sm, "right center" ), |
1257 | x_align: x_align::RIGHT, y_align: y_align::CENTER); |
1258 | table.set_cell_span (span: table::rect_t (table::coord_t (0, 6), |
1259 | table::size_t (3, 3)), |
1260 | content: styled_string (sm, "left bottom" ), |
1261 | x_align: x_align::LEFT, y_align: y_align::BOTTOM); |
1262 | table.set_cell_span (span: table::rect_t (table::coord_t (3, 6), |
1263 | table::size_t (3, 3)), |
1264 | content: styled_string (sm, "center bottom" ), |
1265 | x_align: x_align::CENTER, y_align: y_align::BOTTOM); |
1266 | table.set_cell_span (span: table::rect_t (table::coord_t (6, 6), |
1267 | table::size_t (3, 3)), |
1268 | content: styled_string (sm, "right bottom" ), |
1269 | x_align: x_align::RIGHT, y_align: y_align::BOTTOM); |
1270 | |
1271 | canvas canvas (table.to_canvas (theme: ascii_theme(), sm)); |
1272 | 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 | } |
1285 | |
1286 | /* Run all selftests in this file. */ |
1287 | |
1288 | void |
1289 | text_art_table_cc_tests () |
1290 | { |
1291 | test_tic_tac_toe (); |
1292 | test_text_table (); |
1293 | test_offset_table (); |
1294 | test_spans (); |
1295 | test_spans_2 (); |
1296 | test_spans_3 (); |
1297 | test_double_width_chars (); |
1298 | test_ipv4_header (); |
1299 | test_missing_cells (); |
1300 | test_add_row (); |
1301 | test_alignment (); |
1302 | } |
1303 | |
1304 | } // namespace selftest |
1305 | |
1306 | |
1307 | #endif /* #if CHECKING_P */ |
1308 | |