Branch data Line data Source code
1 : : /* Classes for printing labelled rulers.
2 : : Copyright (C) 2023-2025 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_ALGORITHM
23 : : #define INCLUDE_VECTOR
24 : : #include "system.h"
25 : : #include "coretypes.h"
26 : : #include "pretty-print.h"
27 : : #include "selftest.h"
28 : : #include "text-art/selftests.h"
29 : : #include "text-art/ruler.h"
30 : : #include "text-art/theme.h"
31 : :
32 : : using namespace text_art;
33 : :
34 : : void
35 : 410 : x_ruler::add_label (const canvas::range_t &r,
36 : : styled_string text,
37 : : style::id_t style_id,
38 : : label_kind kind)
39 : : {
40 : 410 : m_labels.push_back (label (r, std::move (text), style_id, kind));
41 : 410 : m_has_layout = false;
42 : 410 : }
43 : :
44 : : int
45 : 1391 : x_ruler::get_canvas_y (int rel_y) const
46 : : {
47 : 1391 : gcc_assert (rel_y >= 0);
48 : 1391 : gcc_assert (rel_y < m_size.h);
49 : 1391 : switch (m_label_dir)
50 : : {
51 : 0 : default:
52 : 0 : gcc_unreachable ();
53 : 184 : case label_dir::ABOVE:
54 : 184 : return m_size.h - (rel_y + 1);
55 : : case label_dir::BELOW:
56 : : return rel_y;
57 : : }
58 : : }
59 : :
60 : : void
61 : 148 : x_ruler::paint_to_canvas (canvas &canvas,
62 : : canvas::coord_t offset,
63 : : const theme &theme)
64 : : {
65 : 148 : ensure_layout ();
66 : :
67 : 148 : if (0)
68 : : canvas.fill (canvas::rect_t (offset, m_size),
69 : : canvas::cell_t ('*'));
70 : :
71 : 453 : for (size_t idx = 0; idx < m_labels.size (); idx++)
72 : : {
73 : 305 : const label &iter_label = m_labels[idx];
74 : :
75 : : /* Paint the ruler itself. */
76 : 305 : const int ruler_rel_y = get_canvas_y (0);
77 : 305 : for (int rel_x = iter_label.m_range.start;
78 : 6547 : rel_x < iter_label.m_range.next;
79 : : rel_x++)
80 : : {
81 : 6242 : enum theme::cell_kind kind = theme::cell_kind::X_RULER_MIDDLE;
82 : :
83 : 6242 : if (rel_x == iter_label.m_range.start)
84 : : {
85 : 305 : kind = theme::cell_kind::X_RULER_LEFT_EDGE;
86 : 305 : if (idx > 0)
87 : : {
88 : 157 : const label &prev_label = m_labels[idx - 1];
89 : 157 : if (prev_label.m_range.get_max () == iter_label.m_range.start)
90 : 6242 : kind = theme::cell_kind::X_RULER_INTERNAL_EDGE;
91 : : }
92 : : }
93 : 5937 : else if (rel_x == iter_label.m_range.get_max ())
94 : : kind = theme::cell_kind::X_RULER_RIGHT_EDGE;
95 : 5632 : else if (rel_x == iter_label.m_connector_x)
96 : : {
97 : 305 : switch (m_label_dir)
98 : : {
99 : 0 : default:
100 : 0 : gcc_unreachable ();
101 : : case label_dir::ABOVE:
102 : : kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE;
103 : : break;
104 : 257 : case label_dir::BELOW:
105 : 257 : kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW;
106 : 257 : break;
107 : : }
108 : : }
109 : 6242 : canvas.paint (canvas::coord_t (rel_x, ruler_rel_y) + offset,
110 : 12484 : theme.get_cell (kind, iter_label.m_style_id));
111 : : }
112 : :
113 : : /* Paint the connector to the text. */
114 : 403 : for (int connector_rel_y = 1;
115 : 708 : connector_rel_y < iter_label.m_text_rect.get_min_y ();
116 : : connector_rel_y++)
117 : : {
118 : 403 : canvas.paint
119 : 403 : ((canvas::coord_t (iter_label.m_connector_x,
120 : 403 : get_canvas_y (connector_rel_y))
121 : : + offset),
122 : 403 : theme.get_cell (theme::cell_kind::X_RULER_VERTICAL_CONNECTOR,
123 : 403 : iter_label.m_style_id));
124 : : }
125 : :
126 : : /* Paint the text. */
127 : 305 : switch (iter_label.m_kind)
128 : : {
129 : 0 : default:
130 : 0 : gcc_unreachable ();
131 : 116 : case x_ruler::label_kind::TEXT:
132 : 116 : canvas.paint_text
133 : 116 : ((canvas::coord_t (iter_label.m_text_rect.get_min_x (),
134 : 116 : get_canvas_y (iter_label.m_text_rect.get_min_y ()))
135 : : + offset),
136 : 116 : iter_label.m_text);
137 : 116 : break;
138 : :
139 : 189 : case x_ruler::label_kind::TEXT_WITH_BORDER:
140 : 189 : {
141 : 189 : const canvas::range_t rel_x_range
142 : 189 : (iter_label.m_text_rect.get_x_range ());
143 : :
144 : 189 : enum theme::cell_kind inner_left_kind;
145 : 189 : enum theme::cell_kind inner_connector_kind;
146 : 189 : enum theme::cell_kind inner_right_kind;
147 : 189 : enum theme::cell_kind outer_left_kind;
148 : 189 : enum theme::cell_kind outer_right_kind;
149 : :
150 : 189 : switch (m_label_dir)
151 : : {
152 : 0 : default:
153 : 0 : gcc_unreachable ();
154 : : case label_dir::ABOVE:
155 : : outer_left_kind = theme::cell_kind::TEXT_BORDER_TOP_LEFT;
156 : : outer_right_kind = theme::cell_kind::TEXT_BORDER_TOP_RIGHT;
157 : : inner_left_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_LEFT;
158 : : inner_connector_kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW;
159 : : inner_right_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_RIGHT;
160 : : break;
161 : 173 : case label_dir::BELOW:
162 : 173 : inner_left_kind = theme::cell_kind::TEXT_BORDER_TOP_LEFT;
163 : 173 : inner_connector_kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE;
164 : 173 : inner_right_kind = theme::cell_kind::TEXT_BORDER_TOP_RIGHT;
165 : 173 : outer_left_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_LEFT;
166 : 173 : outer_right_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_RIGHT;
167 : 173 : break;
168 : : }
169 : : /* Inner border. */
170 : 189 : {
171 : 189 : const int rel_canvas_y
172 : 189 : = get_canvas_y (iter_label.m_text_rect.get_min_y ());
173 : : /* Left corner. */
174 : 189 : canvas.paint ((canvas::coord_t (rel_x_range.get_min (),
175 : 189 : rel_canvas_y)
176 : : + offset),
177 : 189 : theme.get_cell (inner_left_kind,
178 : 189 : iter_label.m_style_id));
179 : : /* Edge. */
180 : 189 : const canvas::cell_t edge_border_cell
181 : : = theme.get_cell (theme::cell_kind::TEXT_BORDER_HORIZONTAL,
182 : 189 : iter_label.m_style_id);
183 : 189 : const canvas::cell_t connector_border_cell
184 : : = theme.get_cell (inner_connector_kind,
185 : 189 : iter_label.m_style_id);
186 : 3522 : for (int rel_x = rel_x_range.get_min () + 1;
187 : 3522 : rel_x < rel_x_range.get_max ();
188 : : rel_x++)
189 : 3333 : if (rel_x == iter_label.m_connector_x)
190 : 189 : canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y)
191 : : + offset),
192 : : connector_border_cell);
193 : : else
194 : 3144 : canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y)
195 : : + offset),
196 : : edge_border_cell);
197 : :
198 : : /* Right corner. */
199 : 189 : canvas.paint ((canvas::coord_t (rel_x_range.get_max (),
200 : 189 : rel_canvas_y)
201 : : + offset),
202 : 378 : theme.get_cell (inner_right_kind,
203 : 189 : iter_label.m_style_id));
204 : 189 : }
205 : :
206 : 189 : {
207 : 189 : const int rel_canvas_y
208 : 189 : = get_canvas_y (iter_label.m_text_rect.get_min_y () + 1);
209 : 189 : const canvas::cell_t border_cell
210 : : = theme.get_cell (theme::cell_kind::TEXT_BORDER_VERTICAL,
211 : 189 : iter_label.m_style_id);
212 : :
213 : : /* Left border. */
214 : 378 : canvas.paint ((canvas::coord_t (rel_x_range.get_min (),
215 : 189 : rel_canvas_y)
216 : : + offset),
217 : : border_cell);
218 : : /* Text. */
219 : 189 : canvas.paint_text ((canvas::coord_t (rel_x_range.get_min () + 1,
220 : 189 : rel_canvas_y)
221 : : + offset),
222 : 189 : iter_label.m_text);
223 : : /* Right border. */
224 : 378 : canvas.paint ((canvas::coord_t (rel_x_range.get_max (),
225 : 189 : rel_canvas_y)
226 : : + offset),
227 : : border_cell);
228 : 189 : }
229 : :
230 : : /* Outer border. */
231 : 189 : {
232 : 189 : const int rel_canvas_y
233 : 189 : = get_canvas_y (iter_label.m_text_rect.get_max_y ());
234 : : /* Left corner. */
235 : 189 : canvas.paint ((canvas::coord_t (rel_x_range.get_min (),
236 : 189 : rel_canvas_y)
237 : : + offset),
238 : 189 : theme.get_cell (outer_left_kind,
239 : 189 : iter_label.m_style_id));
240 : : /* Edge. */
241 : 189 : const canvas::cell_t border_cell
242 : : = theme.get_cell (theme::cell_kind::TEXT_BORDER_HORIZONTAL,
243 : 189 : iter_label.m_style_id);
244 : 189 : for (int rel_x = rel_x_range.get_min () + 1;
245 : 3522 : rel_x < rel_x_range.get_max ();
246 : : rel_x++)
247 : 3333 : canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y)
248 : : + offset),
249 : : border_cell);
250 : :
251 : : /* Right corner. */
252 : 189 : canvas.paint ((canvas::coord_t (rel_x_range.get_max (),
253 : 189 : rel_canvas_y)
254 : : + offset),
255 : 378 : theme.get_cell (outer_right_kind,
256 : 189 : iter_label.m_style_id));
257 : 189 : }
258 : : }
259 : 189 : break;
260 : : }
261 : : }
262 : 148 : }
263 : :
264 : : DEBUG_FUNCTION void
265 : 0 : x_ruler::debug (const style_manager &sm)
266 : : {
267 : 0 : canvas c (get_size (), sm);
268 : 0 : paint_to_canvas (c, canvas::coord_t (0, 0), unicode_theme ());
269 : 0 : c.debug (true);
270 : 0 : }
271 : :
272 : 410 : x_ruler::label::label (const canvas::range_t &range,
273 : : styled_string text,
274 : : style::id_t style_id,
275 : 410 : label_kind kind)
276 : 410 : : m_range (range),
277 : 410 : m_text (std::move (text)),
278 : 410 : m_style_id (style_id),
279 : 410 : m_kind (kind),
280 : 410 : m_text_rect (canvas::coord_t (0, 0),
281 : 410 : canvas::size_t (m_text.calc_canvas_width (), 1)),
282 : 410 : m_connector_x ((m_range.get_min () + m_range.get_max ()) / 2)
283 : : {
284 : 410 : if (kind == label_kind::TEXT_WITH_BORDER)
285 : : {
286 : 326 : m_text_rect.m_size.w += 2;
287 : 326 : m_text_rect.m_size.h += 2;
288 : : }
289 : 410 : }
290 : :
291 : : bool
292 : 436 : x_ruler::label::operator< (const label &other) const
293 : : {
294 : 436 : int cmp = m_range.start - other.m_range.start;
295 : 436 : if (cmp)
296 : 436 : return cmp < 0;
297 : 0 : return m_range.next < other.m_range.next;
298 : : }
299 : :
300 : : void
301 : 296 : x_ruler::ensure_layout ()
302 : : {
303 : 296 : if (m_has_layout)
304 : : return;
305 : 192 : update_layout ();
306 : 192 : m_has_layout = true;
307 : : }
308 : :
309 : : void
310 : 192 : x_ruler::update_layout ()
311 : : {
312 : 192 : if (m_labels.empty ())
313 : 0 : return;
314 : :
315 : 192 : std::sort (m_labels.begin (), m_labels.end ());
316 : :
317 : : /* Place labels. */
318 : 192 : int ruler_width = m_labels.back ().m_range.get_next ();
319 : 192 : int width_with_labels = ruler_width;
320 : :
321 : : /* Get x coordinates of text parts of each label
322 : : (m_text_rect.m_top_left.x for each label). */
323 : 602 : for (size_t idx = 0; idx < m_labels.size (); idx++)
324 : : {
325 : 410 : label &iter_label = m_labels[idx];
326 : : /* Attempt to center the text label. */
327 : 410 : int min_x;
328 : 410 : if (idx > 0)
329 : : {
330 : : /* ...but don't overlap with the connector to the left. */
331 : 218 : int left_neighbor_connector_x = m_labels[idx - 1].m_connector_x;
332 : 218 : min_x = left_neighbor_connector_x + 1;
333 : : }
334 : : else
335 : : {
336 : : /* ...or go beyond the leftmost column. */
337 : 192 : min_x = 0;
338 : : }
339 : 410 : int connector_x = iter_label.m_connector_x;
340 : 410 : int centered_x
341 : 410 : = connector_x - ((int)iter_label.m_text_rect.get_width () / 2);
342 : 410 : int text_x = std::max (min_x, centered_x);
343 : 410 : iter_label.m_text_rect.m_top_left.x = text_x;
344 : : }
345 : :
346 : : /* Now walk backwards trying to place them vertically,
347 : : setting m_text_rect.m_top_left.y for each label,
348 : : consolidating the rows where possible.
349 : : The y cooordinates are stored with respect to label_dir::BELOW. */
350 : 192 : int label_y = 2;
351 : 602 : for (int idx = m_labels.size () - 1; idx >= 0; idx--)
352 : : {
353 : 410 : label &iter_label = m_labels[idx];
354 : : /* Does it fit on the same row as the text label to the right? */
355 : 410 : size_t text_len = iter_label.m_text_rect.get_width ();
356 : : /* Get the x-coord of immediately beyond iter_label's text. */
357 : 410 : int next_x = iter_label.m_text_rect.get_min_x () + text_len;
358 : 410 : if (idx < (int)m_labels.size () - 1)
359 : : {
360 : 218 : if (next_x >= m_labels[idx + 1].m_text_rect.get_min_x ())
361 : : {
362 : : /* If not, start a new row. */
363 : 52 : label_y += m_labels[idx + 1].m_text_rect.get_height ();
364 : : }
365 : : }
366 : 410 : iter_label.m_text_rect.m_top_left.y = label_y;
367 : 410 : width_with_labels = std::max (width_with_labels, next_x);
368 : : }
369 : :
370 : 192 : m_size = canvas::size_t (width_with_labels,
371 : 192 : label_y + m_labels[0].m_text_rect.get_height ());
372 : : }
373 : :
374 : : #if CHECKING_P
375 : :
376 : : namespace selftest {
377 : :
378 : : static void
379 : 80 : assert_x_ruler_streq (const location &loc,
380 : : x_ruler &ruler,
381 : : const theme &theme,
382 : : const style_manager &sm,
383 : : bool styled,
384 : : const char *expected_str)
385 : : {
386 : 80 : canvas c (ruler.get_size (), sm);
387 : 80 : ruler.paint_to_canvas (c, canvas::coord_t (0, 0), theme);
388 : 80 : if (0)
389 : : c.debug (styled);
390 : 80 : assert_canvas_streq (loc, c, styled, expected_str);
391 : 80 : }
392 : :
393 : : #define ASSERT_X_RULER_STREQ(RULER, THEME, SM, STYLED, EXPECTED_STR) \
394 : : SELFTEST_BEGIN_STMT \
395 : : assert_x_ruler_streq ((SELFTEST_LOCATION), \
396 : : (RULER), \
397 : : (THEME), \
398 : : (SM), \
399 : : (STYLED), \
400 : : (EXPECTED_STR)); \
401 : : SELFTEST_END_STMT
402 : :
403 : : static void
404 : 4 : test_single ()
405 : : {
406 : 4 : style_manager sm;
407 : 4 : x_ruler r (x_ruler::label_dir::BELOW);
408 : 4 : r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"),
409 : : style::id_plain, x_ruler::label_kind::TEXT);
410 : 4 : ASSERT_X_RULER_STREQ
411 : : (r, ascii_theme (), sm, true,
412 : : ("|~~~~+~~~~|\n"
413 : : " |\n"
414 : : " foo\n"));
415 : 4 : ASSERT_X_RULER_STREQ
416 : : (r, unicode_theme (), sm, true,
417 : : ("├────┬────┤\n"
418 : : " │\n"
419 : : " foo\n"));
420 : 4 : }
421 : :
422 : : static void
423 : 4 : test_single_above ()
424 : : {
425 : 4 : style_manager sm;
426 : 4 : x_ruler r (x_ruler::label_dir::ABOVE);
427 : 4 : r.add_label (canvas::range_t (0, 11), styled_string (sm, "hello world"),
428 : : style::id_plain);
429 : 4 : ASSERT_X_RULER_STREQ
430 : : (r, ascii_theme (), sm, true,
431 : : ("hello world\n"
432 : : " |\n"
433 : : "|~~~~+~~~~|\n"));
434 : 4 : ASSERT_X_RULER_STREQ
435 : : (r, unicode_theme (), sm, true,
436 : : ("hello world\n"
437 : : " │\n"
438 : : "├────┴────┤\n"));
439 : 4 : }
440 : :
441 : : static void
442 : 4 : test_multiple_contiguous ()
443 : : {
444 : 4 : style_manager sm;
445 : 4 : x_ruler r (x_ruler::label_dir::BELOW);
446 : 4 : r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"),
447 : : style::id_plain);
448 : 4 : r.add_label (canvas::range_t (10, 16), styled_string (sm, "bar"),
449 : : style::id_plain);
450 : 4 : ASSERT_X_RULER_STREQ
451 : : (r, ascii_theme (), sm, true,
452 : : ("|~~~~+~~~~|~+~~|\n"
453 : : " | |\n"
454 : : " foo bar\n"));
455 : 4 : ASSERT_X_RULER_STREQ
456 : : (r, unicode_theme (), sm, true,
457 : : ("├────┬────┼─┬──┤\n"
458 : : " │ │\n"
459 : : " foo bar\n"));
460 : 4 : }
461 : :
462 : : static void
463 : 4 : test_multiple_contiguous_above ()
464 : : {
465 : 4 : style_manager sm;
466 : 4 : x_ruler r (x_ruler::label_dir::ABOVE);
467 : 4 : r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"),
468 : : style::id_plain);
469 : 4 : r.add_label (canvas::range_t (10, 16), styled_string (sm, "bar"),
470 : : style::id_plain);
471 : 4 : ASSERT_X_RULER_STREQ
472 : : (r, ascii_theme (), sm, true,
473 : : (" foo bar\n"
474 : : " | |\n"
475 : : "|~~~~+~~~~|~+~~|\n"));
476 : 4 : ASSERT_X_RULER_STREQ
477 : : (r, unicode_theme (), sm, true,
478 : : (" foo bar\n"
479 : : " │ │\n"
480 : : "├────┴────┼─┴──┤\n"));
481 : 4 : }
482 : :
483 : : static void
484 : 4 : test_multiple_contiguous_abutting_labels ()
485 : : {
486 : 4 : style_manager sm;
487 : 4 : x_ruler r (x_ruler::label_dir::BELOW);
488 : 4 : r.add_label (canvas::range_t (0, 11), styled_string (sm, "12345678"),
489 : : style::id_plain);
490 : 4 : r.add_label (canvas::range_t (10, 16), styled_string (sm, "1234678"),
491 : : style::id_plain);
492 : 4 : ASSERT_X_RULER_STREQ
493 : : (r, unicode_theme (), sm, true,
494 : : ("├────┬────┼─┬──┤\n"
495 : : " │ │\n"
496 : : " │ 1234678\n"
497 : : " 12345678\n"));
498 : 4 : }
499 : :
500 : : static void
501 : 4 : test_multiple_contiguous_overlapping_labels ()
502 : : {
503 : 4 : style_manager sm;
504 : 4 : x_ruler r (x_ruler::label_dir::BELOW);
505 : 4 : r.add_label (canvas::range_t (0, 11), styled_string (sm, "123456789"),
506 : : style::id_plain);
507 : 4 : r.add_label (canvas::range_t (10, 16), styled_string (sm, "12346789"),
508 : : style::id_plain);
509 : 4 : ASSERT_X_RULER_STREQ
510 : : (r, unicode_theme (), sm, true,
511 : : ("├────┬────┼─┬──┤\n"
512 : : " │ │\n"
513 : : " │ 12346789\n"
514 : : " 123456789\n"));
515 : 4 : }
516 : : static void
517 : 4 : test_abutting_left_border ()
518 : : {
519 : 4 : style_manager sm;
520 : 4 : x_ruler r (x_ruler::label_dir::BELOW);
521 : 4 : r.add_label (canvas::range_t (0, 6),
522 : 4 : styled_string (sm, "this is a long label"),
523 : : style::id_plain);
524 : 4 : ASSERT_X_RULER_STREQ
525 : : (r, unicode_theme (), sm, true,
526 : : ("├─┬──┤\n"
527 : : " │\n"
528 : : "this is a long label\n"));
529 : 4 : }
530 : :
531 : : static void
532 : 4 : test_too_long_to_consolidate_vertically ()
533 : : {
534 : 4 : style_manager sm;
535 : 4 : x_ruler r (x_ruler::label_dir::BELOW);
536 : 4 : r.add_label (canvas::range_t (0, 11),
537 : 4 : styled_string (sm, "long string A"),
538 : : style::id_plain);
539 : 4 : r.add_label (canvas::range_t (10, 16),
540 : 4 : styled_string (sm, "long string B"),
541 : : style::id_plain);
542 : 4 : ASSERT_X_RULER_STREQ
543 : : (r, unicode_theme (), sm, true,
544 : : ("├────┬────┼─┬──┤\n"
545 : : " │ │\n"
546 : : " │long string B\n"
547 : : "long string A\n"));
548 : 4 : }
549 : :
550 : : static void
551 : 4 : test_abutting_neighbor ()
552 : : {
553 : 4 : style_manager sm;
554 : 4 : x_ruler r (x_ruler::label_dir::BELOW);
555 : 4 : r.add_label (canvas::range_t (0, 11),
556 : 4 : styled_string (sm, "very long string A"),
557 : : style::id_plain);
558 : 4 : r.add_label (canvas::range_t (10, 16),
559 : 4 : styled_string (sm, "very long string B"),
560 : : style::id_plain);
561 : 4 : ASSERT_X_RULER_STREQ
562 : : (r, unicode_theme (), sm, true,
563 : : ("├────┬────┼─┬──┤\n"
564 : : " │ │\n"
565 : : " │very long string B\n"
566 : : "very long string A\n"));
567 : 4 : }
568 : :
569 : : static void
570 : 4 : test_gaps ()
571 : : {
572 : 4 : style_manager sm;
573 : 4 : x_ruler r (x_ruler::label_dir::BELOW);
574 : 4 : r.add_label (canvas::range_t (0, 5),
575 : 4 : styled_string (sm, "foo"),
576 : : style::id_plain);
577 : 4 : r.add_label (canvas::range_t (10, 15),
578 : 4 : styled_string (sm, "bar"),
579 : : style::id_plain);
580 : 4 : ASSERT_X_RULER_STREQ
581 : : (r, ascii_theme (), sm, true,
582 : : ("|~+~| |~+~|\n"
583 : : " | |\n"
584 : : " foo bar\n"));
585 : 4 : }
586 : :
587 : : static void
588 : 4 : test_styled ()
589 : : {
590 : 4 : style_manager sm;
591 : 4 : style s1, s2;
592 : 4 : s1.m_bold = true;
593 : 4 : s1.m_fg_color = style::named_color::YELLOW;
594 : 4 : s2.m_bold = true;
595 : 4 : s2.m_fg_color = style::named_color::BLUE;
596 : 4 : style::id_t sid1 = sm.get_or_create_id (s1);
597 : 4 : style::id_t sid2 = sm.get_or_create_id (s2);
598 : :
599 : 4 : x_ruler r (x_ruler::label_dir::BELOW);
600 : 4 : r.add_label (canvas::range_t (0, 5), styled_string (sm, "foo"), sid1);
601 : 4 : r.add_label (canvas::range_t (10, 15), styled_string (sm, "bar"), sid2);
602 : 4 : ASSERT_X_RULER_STREQ
603 : : (r, ascii_theme (), sm, true,
604 : : ("[00;01;33m[K|~+~|[00m[K [00;01;34m[K|~+~|[00m[K\n"
605 : : " [00;01;33m[K|[00m[K [00;01;34m[K|[00m[K\n"
606 : : " foo bar\n"));
607 : 4 : }
608 : :
609 : : static void
610 : 4 : test_borders ()
611 : : {
612 : 4 : style_manager sm;
613 : 4 : {
614 : 4 : x_ruler r (x_ruler::label_dir::BELOW);
615 : 4 : r.add_label (canvas::range_t (0, 5),
616 : 4 : styled_string (sm, "label 1"),
617 : : style::id_plain,
618 : : x_ruler::label_kind::TEXT_WITH_BORDER);
619 : 4 : r.add_label (canvas::range_t (10, 15),
620 : 4 : styled_string (sm, "label 2"),
621 : : style::id_plain);
622 : 4 : r.add_label (canvas::range_t (20, 25),
623 : 4 : styled_string (sm, "label 3"),
624 : : style::id_plain,
625 : : x_ruler::label_kind::TEXT_WITH_BORDER);
626 : 4 : ASSERT_X_RULER_STREQ
627 : : (r, ascii_theme (), sm, true,
628 : : "|~+~| |~+~| |~+~|\n"
629 : : " | | |\n"
630 : : " | label 2 +---+---+\n"
631 : : "+-+-----+ |label 3|\n"
632 : : "|label 1| +-------+\n"
633 : : "+-------+\n");
634 : 4 : ASSERT_X_RULER_STREQ
635 : : (r, unicode_theme (), sm, true,
636 : : "├─┬─┤ ├─┬─┤ ├─┬─┤\n"
637 : : " │ │ │\n"
638 : : " │ label 2 ╭───┴───╮\n"
639 : : "╭─┴─────╮ │label 3│\n"
640 : : "│label 1│ ╰───────╯\n"
641 : : "╰───────╯\n");
642 : 4 : }
643 : 4 : {
644 : 4 : x_ruler r (x_ruler::label_dir::ABOVE);
645 : 4 : r.add_label (canvas::range_t (0, 5),
646 : 4 : styled_string (sm, "label 1"),
647 : : style::id_plain,
648 : : x_ruler::label_kind::TEXT_WITH_BORDER);
649 : 4 : r.add_label (canvas::range_t (10, 15),
650 : 4 : styled_string (sm, "label 2"),
651 : : style::id_plain);
652 : 4 : r.add_label (canvas::range_t (20, 25),
653 : 4 : styled_string (sm, "label 3"),
654 : : style::id_plain,
655 : : x_ruler::label_kind::TEXT_WITH_BORDER);
656 : 4 : ASSERT_X_RULER_STREQ
657 : : (r, ascii_theme (), sm, true,
658 : : "+-------+\n"
659 : : "|label 1| +-------+\n"
660 : : "+-+-----+ |label 3|\n"
661 : : " | label 2 +---+---+\n"
662 : : " | | |\n"
663 : : "|~+~| |~+~| |~+~|\n");
664 : 4 : ASSERT_X_RULER_STREQ
665 : : (r, unicode_theme (), sm, true,
666 : : "╭───────╮\n"
667 : : "│label 1│ ╭───────╮\n"
668 : : "╰─┬─────╯ │label 3│\n"
669 : : " │ label 2 ╰───┬───╯\n"
670 : : " │ │ │\n"
671 : : "├─┴─┤ ├─┴─┤ ├─┴─┤\n");
672 : 4 : }
673 : 4 : }
674 : :
675 : : static void
676 : 4 : test_emoji ()
677 : : {
678 : 4 : style_manager sm;
679 : :
680 : 4 : styled_string s;
681 : 4 : s.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN. */
682 : 4 : true));
683 : 4 : s.append (styled_string (sm, " "));
684 : 4 : s.append (styled_string (sm, "this is a warning"));
685 : :
686 : 4 : x_ruler r (x_ruler::label_dir::BELOW);
687 : 4 : r.add_label (canvas::range_t (0, 5),
688 : : std::move (s),
689 : : style::id_plain,
690 : : x_ruler::label_kind::TEXT_WITH_BORDER);
691 : :
692 : 4 : ASSERT_X_RULER_STREQ
693 : : (r, ascii_theme (), sm, true,
694 : : "|~+~|\n"
695 : : " |\n"
696 : : "+-+------------------+\n"
697 : : "|⚠️ this is a warning|\n"
698 : : "+--------------------+\n");
699 : 4 : }
700 : :
701 : : /* Run all selftests in this file. */
702 : :
703 : : void
704 : 4 : text_art_ruler_cc_tests ()
705 : : {
706 : 4 : test_single ();
707 : 4 : test_single_above ();
708 : 4 : test_multiple_contiguous ();
709 : 4 : test_multiple_contiguous_above ();
710 : 4 : test_multiple_contiguous_abutting_labels ();
711 : 4 : test_multiple_contiguous_overlapping_labels ();
712 : 4 : test_abutting_left_border ();
713 : 4 : test_too_long_to_consolidate_vertically ();
714 : 4 : test_abutting_neighbor ();
715 : 4 : test_gaps ();
716 : 4 : test_styled ();
717 : 4 : test_borders ();
718 : 4 : test_emoji ();
719 : 4 : }
720 : :
721 : : } // namespace selftest
722 : :
723 : :
724 : : #endif /* #if CHECKING_P */
|