1/* Classes for printing labelled rulers.
2 Copyright (C) 2023-2024 Free Software Foundation, Inc.
3 Contributed by David Malcolm <dmalcolm@redhat.com>.
4
5This file is part of GCC.
6
7GCC is free software; you can redistribute it and/or modify it under
8the terms of the GNU General Public License as published by the Free
9Software Foundation; either version 3, or (at your option) any later
10version.
11
12GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13WARRANTY; without even the implied warranty of MERCHANTABILITY or
14FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15for more details.
16
17You should have received a copy of the GNU General Public License
18along 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
32using namespace text_art;
33
34void
35x_ruler::add_label (const canvas::range_t &r,
36 styled_string text,
37 style::id_t style_id,
38 label_kind kind)
39{
40 m_labels.push_back (x: label (r, std::move (text), style_id, kind));
41 m_has_layout = false;
42}
43
44int
45x_ruler::get_canvas_y (int rel_y) const
46{
47 gcc_assert (rel_y >= 0);
48 gcc_assert (rel_y < m_size.h);
49 switch (m_label_dir)
50 {
51 default:
52 gcc_unreachable ();
53 case label_dir::ABOVE:
54 return m_size.h - (rel_y + 1);
55 case label_dir::BELOW:
56 return rel_y;
57 }
58}
59
60void
61x_ruler::paint_to_canvas (canvas &canvas,
62 canvas::coord_t offset,
63 const theme &theme)
64{
65 ensure_layout ();
66
67 if (0)
68 canvas.fill (rect: canvas::rect_t (offset, m_size),
69 c: canvas::cell_t ('*'));
70
71 for (size_t idx = 0; idx < m_labels.size (); idx++)
72 {
73 const label &iter_label = m_labels[idx];
74
75 /* Paint the ruler itself. */
76 const int ruler_rel_y = get_canvas_y (rel_y: 0);
77 for (int rel_x = iter_label.m_range.start;
78 rel_x < iter_label.m_range.next;
79 rel_x++)
80 {
81 enum theme::cell_kind kind = theme::cell_kind::X_RULER_MIDDLE;
82
83 if (rel_x == iter_label.m_range.start)
84 {
85 kind = theme::cell_kind::X_RULER_LEFT_EDGE;
86 if (idx > 0)
87 {
88 const label &prev_label = m_labels[idx - 1];
89 if (prev_label.m_range.get_max () == iter_label.m_range.start)
90 kind = theme::cell_kind::X_RULER_INTERNAL_EDGE;
91 }
92 }
93 else if (rel_x == iter_label.m_range.get_max ())
94 kind = theme::cell_kind::X_RULER_RIGHT_EDGE;
95 else if (rel_x == iter_label.m_connector_x)
96 {
97 switch (m_label_dir)
98 {
99 default:
100 gcc_unreachable ();
101 case label_dir::ABOVE:
102 kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE;
103 break;
104 case label_dir::BELOW:
105 kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW;
106 break;
107 }
108 }
109 canvas.paint (coord: canvas::coord_t (rel_x, ruler_rel_y) + offset,
110 c: theme.get_cell (kind, style_idx: iter_label.m_style_id));
111 }
112
113 /* Paint the connector to the text. */
114 for (int connector_rel_y = 1;
115 connector_rel_y < iter_label.m_text_rect.get_min_y ();
116 connector_rel_y++)
117 {
118 canvas.paint
119 (coord: (canvas::coord_t (iter_label.m_connector_x,
120 get_canvas_y (rel_y: connector_rel_y))
121 + offset),
122 c: theme.get_cell (kind: theme::cell_kind::X_RULER_VERTICAL_CONNECTOR,
123 style_idx: iter_label.m_style_id));
124 }
125
126 /* Paint the text. */
127 switch (iter_label.m_kind)
128 {
129 default:
130 gcc_unreachable ();
131 case x_ruler::label_kind::TEXT:
132 canvas.paint_text
133 (coord: (canvas::coord_t (iter_label.m_text_rect.get_min_x (),
134 get_canvas_y (rel_y: iter_label.m_text_rect.get_min_y ()))
135 + offset),
136 text: iter_label.m_text);
137 break;
138
139 case x_ruler::label_kind::TEXT_WITH_BORDER:
140 {
141 const canvas::range_t rel_x_range
142 (iter_label.m_text_rect.get_x_range ());
143
144 enum theme::cell_kind inner_left_kind;
145 enum theme::cell_kind inner_connector_kind;
146 enum theme::cell_kind inner_right_kind;
147 enum theme::cell_kind outer_left_kind;
148 enum theme::cell_kind outer_right_kind;
149
150 switch (m_label_dir)
151 {
152 default:
153 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 case label_dir::BELOW:
162 inner_left_kind = theme::cell_kind::TEXT_BORDER_TOP_LEFT;
163 inner_connector_kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE;
164 inner_right_kind = theme::cell_kind::TEXT_BORDER_TOP_RIGHT;
165 outer_left_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_LEFT;
166 outer_right_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_RIGHT;
167 break;
168 }
169 /* Inner border. */
170 {
171 const int rel_canvas_y
172 = get_canvas_y (rel_y: iter_label.m_text_rect.get_min_y ());
173 /* Left corner. */
174 canvas.paint (coord: (canvas::coord_t (rel_x_range.get_min (),
175 rel_canvas_y)
176 + offset),
177 c: theme.get_cell (kind: inner_left_kind,
178 style_idx: iter_label.m_style_id));
179 /* Edge. */
180 const canvas::cell_t edge_border_cell
181 = theme.get_cell (kind: theme::cell_kind::TEXT_BORDER_HORIZONTAL,
182 style_idx: iter_label.m_style_id);
183 const canvas::cell_t connector_border_cell
184 = theme.get_cell (kind: inner_connector_kind,
185 style_idx: iter_label.m_style_id);
186 for (int rel_x = rel_x_range.get_min () + 1;
187 rel_x < rel_x_range.get_max ();
188 rel_x++)
189 if (rel_x == iter_label.m_connector_x)
190 canvas.paint (coord: (canvas::coord_t (rel_x, rel_canvas_y)
191 + offset),
192 c: connector_border_cell);
193 else
194 canvas.paint (coord: (canvas::coord_t (rel_x, rel_canvas_y)
195 + offset),
196 c: edge_border_cell);
197
198 /* Right corner. */
199 canvas.paint (coord: (canvas::coord_t (rel_x_range.get_max (),
200 rel_canvas_y)
201 + offset),
202 c: theme.get_cell (kind: inner_right_kind,
203 style_idx: iter_label.m_style_id));
204 }
205
206 {
207 const int rel_canvas_y
208 = get_canvas_y (rel_y: iter_label.m_text_rect.get_min_y () + 1);
209 const canvas::cell_t border_cell
210 = theme.get_cell (kind: theme::cell_kind::TEXT_BORDER_VERTICAL,
211 style_idx: iter_label.m_style_id);
212
213 /* Left border. */
214 canvas.paint (coord: (canvas::coord_t (rel_x_range.get_min (),
215 rel_canvas_y)
216 + offset),
217 c: border_cell);
218 /* Text. */
219 canvas.paint_text (coord: (canvas::coord_t (rel_x_range.get_min () + 1,
220 rel_canvas_y)
221 + offset),
222 text: iter_label.m_text);
223 /* Right border. */
224 canvas.paint (coord: (canvas::coord_t (rel_x_range.get_max (),
225 rel_canvas_y)
226 + offset),
227 c: border_cell);
228 }
229
230 /* Outer border. */
231 {
232 const int rel_canvas_y
233 = get_canvas_y (rel_y: iter_label.m_text_rect.get_max_y ());
234 /* Left corner. */
235 canvas.paint (coord: (canvas::coord_t (rel_x_range.get_min (),
236 rel_canvas_y)
237 + offset),
238 c: theme.get_cell (kind: outer_left_kind,
239 style_idx: iter_label.m_style_id));
240 /* Edge. */
241 const canvas::cell_t border_cell
242 = theme.get_cell (kind: theme::cell_kind::TEXT_BORDER_HORIZONTAL,
243 style_idx: iter_label.m_style_id);
244 for (int rel_x = rel_x_range.get_min () + 1;
245 rel_x < rel_x_range.get_max ();
246 rel_x++)
247 canvas.paint (coord: (canvas::coord_t (rel_x, rel_canvas_y)
248 + offset),
249 c: border_cell);
250
251 /* Right corner. */
252 canvas.paint (coord: (canvas::coord_t (rel_x_range.get_max (),
253 rel_canvas_y)
254 + offset),
255 c: theme.get_cell (kind: outer_right_kind,
256 style_idx: iter_label.m_style_id));
257 }
258 }
259 break;
260 }
261 }
262}
263
264DEBUG_FUNCTION void
265x_ruler::debug (const style_manager &sm)
266{
267 canvas c (get_size (), sm);
268 paint_to_canvas (canvas&: c, offset: canvas::coord_t (0, 0), theme: unicode_theme ());
269 c.debug (styled: true);
270}
271
272x_ruler::label::label (const canvas::range_t &range,
273 styled_string text,
274 style::id_t style_id,
275 label_kind kind)
276: m_range (range),
277 m_text (std::move (text)),
278 m_style_id (style_id),
279 m_kind (kind),
280 m_text_rect (canvas::coord_t (0, 0),
281 canvas::size_t (m_text.calc_canvas_width (), 1)),
282 m_connector_x ((m_range.get_min () + m_range.get_max ()) / 2)
283{
284 if (kind == label_kind::TEXT_WITH_BORDER)
285 {
286 m_text_rect.m_size.w += 2;
287 m_text_rect.m_size.h += 2;
288 }
289}
290
291bool
292x_ruler::label::operator< (const label &other) const
293{
294 int cmp = m_range.start - other.m_range.start;
295 if (cmp)
296 return cmp < 0;
297 return m_range.next < other.m_range.next;
298}
299
300void
301x_ruler::ensure_layout ()
302{
303 if (m_has_layout)
304 return;
305 update_layout ();
306 m_has_layout = true;
307}
308
309void
310x_ruler::update_layout ()
311{
312 if (m_labels.empty ())
313 return;
314
315 std::sort (first: m_labels.begin (), last: m_labels.end ());
316
317 /* Place labels. */
318 int ruler_width = m_labels.back ().m_range.get_next ();
319 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 for (size_t idx = 0; idx < m_labels.size (); idx++)
324 {
325 label &iter_label = m_labels[idx];
326 /* Attempt to center the text label. */
327 int min_x;
328 if (idx > 0)
329 {
330 /* ...but don't overlap with the connector to the left. */
331 int left_neighbor_connector_x = m_labels[idx - 1].m_connector_x;
332 min_x = left_neighbor_connector_x + 1;
333 }
334 else
335 {
336 /* ...or go beyond the leftmost column. */
337 min_x = 0;
338 }
339 int connector_x = iter_label.m_connector_x;
340 int centered_x
341 = connector_x - ((int)iter_label.m_text_rect.get_width () / 2);
342 int text_x = std::max (a: min_x, b: centered_x);
343 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 int label_y = 2;
351 for (int idx = m_labels.size () - 1; idx >= 0; idx--)
352 {
353 label &iter_label = m_labels[idx];
354 /* Does it fit on the same row as the text label to the right? */
355 size_t text_len = iter_label.m_text_rect.get_width ();
356 /* Get the x-coord of immediately beyond iter_label's text. */
357 int next_x = iter_label.m_text_rect.get_min_x () + text_len;
358 if (idx < (int)m_labels.size () - 1)
359 {
360 if (next_x >= m_labels[idx + 1].m_text_rect.get_min_x ())
361 {
362 /* If not, start a new row. */
363 label_y += m_labels[idx + 1].m_text_rect.get_height ();
364 }
365 }
366 iter_label.m_text_rect.m_top_left.y = label_y;
367 width_with_labels = std::max (a: width_with_labels, b: next_x);
368 }
369
370 m_size = canvas::size_t (width_with_labels,
371 label_y + m_labels[0].m_text_rect.get_height ());
372}
373
374#if CHECKING_P
375
376namespace selftest {
377
378static void
379assert_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 canvas c (ruler.get_size (), sm);
387 ruler.paint_to_canvas (canvas&: c, offset: canvas::coord_t (0, 0), theme);
388 if (0)
389 c.debug (styled);
390 assert_canvas_streq (loc, canvas: c, styled, expected_str);
391}
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
403static void
404test_single ()
405{
406 style_manager sm;
407 x_ruler r (x_ruler::label_dir::BELOW);
408 r.add_label (r: canvas::range_t (0, 11), text: styled_string (sm, "foo"),
409 style_id: style::id_plain, kind: x_ruler::label_kind::TEXT);
410 ASSERT_X_RULER_STREQ
411 (r, ascii_theme (), sm, true,
412 ("|~~~~+~~~~|\n"
413 " |\n"
414 " foo\n"));
415 ASSERT_X_RULER_STREQ
416 (r, unicode_theme (), sm, true,
417 ("├────┬────┤\n"
418 " │\n"
419 " foo\n"));
420}
421
422static void
423test_single_above ()
424{
425 style_manager sm;
426 x_ruler r (x_ruler::label_dir::ABOVE);
427 r.add_label (r: canvas::range_t (0, 11), text: styled_string (sm, "hello world"),
428 style_id: style::id_plain);
429 ASSERT_X_RULER_STREQ
430 (r, ascii_theme (), sm, true,
431 ("hello world\n"
432 " |\n"
433 "|~~~~+~~~~|\n"));
434 ASSERT_X_RULER_STREQ
435 (r, unicode_theme (), sm, true,
436 ("hello world\n"
437 " │\n"
438 "├────┴────┤\n"));
439}
440
441static void
442test_multiple_contiguous ()
443{
444 style_manager sm;
445 x_ruler r (x_ruler::label_dir::BELOW);
446 r.add_label (r: canvas::range_t (0, 11), text: styled_string (sm, "foo"),
447 style_id: style::id_plain);
448 r.add_label (r: canvas::range_t (10, 16), text: styled_string (sm, "bar"),
449 style_id: style::id_plain);
450 ASSERT_X_RULER_STREQ
451 (r, ascii_theme (), sm, true,
452 ("|~~~~+~~~~|~+~~|\n"
453 " | |\n"
454 " foo bar\n"));
455 ASSERT_X_RULER_STREQ
456 (r, unicode_theme (), sm, true,
457 ("├────┬────┼─┬──┤\n"
458 " │ │\n"
459 " foo bar\n"));
460}
461
462static void
463test_multiple_contiguous_above ()
464{
465 style_manager sm;
466 x_ruler r (x_ruler::label_dir::ABOVE);
467 r.add_label (r: canvas::range_t (0, 11), text: styled_string (sm, "foo"),
468 style_id: style::id_plain);
469 r.add_label (r: canvas::range_t (10, 16), text: styled_string (sm, "bar"),
470 style_id: style::id_plain);
471 ASSERT_X_RULER_STREQ
472 (r, ascii_theme (), sm, true,
473 (" foo bar\n"
474 " | |\n"
475 "|~~~~+~~~~|~+~~|\n"));
476 ASSERT_X_RULER_STREQ
477 (r, unicode_theme (), sm, true,
478 (" foo bar\n"
479 " │ │\n"
480 "├────┴────┼─┴──┤\n"));
481}
482
483static void
484test_multiple_contiguous_abutting_labels ()
485{
486 style_manager sm;
487 x_ruler r (x_ruler::label_dir::BELOW);
488 r.add_label (r: canvas::range_t (0, 11), text: styled_string (sm, "12345678"),
489 style_id: style::id_plain);
490 r.add_label (r: canvas::range_t (10, 16), text: styled_string (sm, "1234678"),
491 style_id: style::id_plain);
492 ASSERT_X_RULER_STREQ
493 (r, unicode_theme (), sm, true,
494 ("├────┬────┼─┬──┤\n"
495 " │ │\n"
496 " │ 1234678\n"
497 " 12345678\n"));
498}
499
500static void
501test_multiple_contiguous_overlapping_labels ()
502{
503 style_manager sm;
504 x_ruler r (x_ruler::label_dir::BELOW);
505 r.add_label (r: canvas::range_t (0, 11), text: styled_string (sm, "123456789"),
506 style_id: style::id_plain);
507 r.add_label (r: canvas::range_t (10, 16), text: styled_string (sm, "12346789"),
508 style_id: style::id_plain);
509 ASSERT_X_RULER_STREQ
510 (r, unicode_theme (), sm, true,
511 ("├────┬────┼─┬──┤\n"
512 " │ │\n"
513 " │ 12346789\n"
514 " 123456789\n"));
515}
516static void
517test_abutting_left_border ()
518{
519 style_manager sm;
520 x_ruler r (x_ruler::label_dir::BELOW);
521 r.add_label (r: canvas::range_t (0, 6),
522 text: styled_string (sm, "this is a long label"),
523 style_id: style::id_plain);
524 ASSERT_X_RULER_STREQ
525 (r, unicode_theme (), sm, true,
526 ("├─┬──┤\n"
527 " │\n"
528 "this is a long label\n"));
529}
530
531static void
532test_too_long_to_consolidate_vertically ()
533{
534 style_manager sm;
535 x_ruler r (x_ruler::label_dir::BELOW);
536 r.add_label (r: canvas::range_t (0, 11),
537 text: styled_string (sm, "long string A"),
538 style_id: style::id_plain);
539 r.add_label (r: canvas::range_t (10, 16),
540 text: styled_string (sm, "long string B"),
541 style_id: style::id_plain);
542 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}
549
550static void
551test_abutting_neighbor ()
552{
553 style_manager sm;
554 x_ruler r (x_ruler::label_dir::BELOW);
555 r.add_label (r: canvas::range_t (0, 11),
556 text: styled_string (sm, "very long string A"),
557 style_id: style::id_plain);
558 r.add_label (r: canvas::range_t (10, 16),
559 text: styled_string (sm, "very long string B"),
560 style_id: style::id_plain);
561 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}
568
569static void
570test_gaps ()
571{
572 style_manager sm;
573 x_ruler r (x_ruler::label_dir::BELOW);
574 r.add_label (r: canvas::range_t (0, 5),
575 text: styled_string (sm, "foo"),
576 style_id: style::id_plain);
577 r.add_label (r: canvas::range_t (10, 15),
578 text: styled_string (sm, "bar"),
579 style_id: style::id_plain);
580 ASSERT_X_RULER_STREQ
581 (r, ascii_theme (), sm, true,
582 ("|~+~| |~+~|\n"
583 " | |\n"
584 " foo bar\n"));
585}
586
587static void
588test_styled ()
589{
590 style_manager sm;
591 style s1, s2;
592 s1.m_bold = true;
593 s1.m_fg_color = style::named_color::YELLOW;
594 s2.m_bold = true;
595 s2.m_fg_color = style::named_color::BLUE;
596 style::id_t sid1 = sm.get_or_create_id (style: s1);
597 style::id_t sid2 = sm.get_or_create_id (style: s2);
598
599 x_ruler r (x_ruler::label_dir::BELOW);
600 r.add_label (r: canvas::range_t (0, 5), text: styled_string (sm, "foo"), style_id: sid1);
601 r.add_label (r: canvas::range_t (10, 15), text: styled_string (sm, "bar"), style_id: sid2);
602 ASSERT_X_RULER_STREQ
603 (r, ascii_theme (), sm, true,
604 ("|~+~| |~+~|\n"
605 " | |\n"
606 " foo bar\n"));
607}
608
609static void
610test_borders ()
611{
612 style_manager sm;
613 {
614 x_ruler r (x_ruler::label_dir::BELOW);
615 r.add_label (r: canvas::range_t (0, 5),
616 text: styled_string (sm, "label 1"),
617 style_id: style::id_plain,
618 kind: x_ruler::label_kind::TEXT_WITH_BORDER);
619 r.add_label (r: canvas::range_t (10, 15),
620 text: styled_string (sm, "label 2"),
621 style_id: style::id_plain);
622 r.add_label (r: canvas::range_t (20, 25),
623 text: styled_string (sm, "label 3"),
624 style_id: style::id_plain,
625 kind: x_ruler::label_kind::TEXT_WITH_BORDER);
626 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 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 }
643 {
644 x_ruler r (x_ruler::label_dir::ABOVE);
645 r.add_label (r: canvas::range_t (0, 5),
646 text: styled_string (sm, "label 1"),
647 style_id: style::id_plain,
648 kind: x_ruler::label_kind::TEXT_WITH_BORDER);
649 r.add_label (r: canvas::range_t (10, 15),
650 text: styled_string (sm, "label 2"),
651 style_id: style::id_plain);
652 r.add_label (r: canvas::range_t (20, 25),
653 text: styled_string (sm, "label 3"),
654 style_id: style::id_plain,
655 kind: x_ruler::label_kind::TEXT_WITH_BORDER);
656 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 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 }
673}
674
675static void
676test_emoji ()
677{
678 style_manager sm;
679
680 styled_string s;
681 s.append (suffix: styled_string (0x26A0, /* U+26A0 WARNING SIGN. */
682 true));
683 s.append (suffix: styled_string (sm, " "));
684 s.append (suffix: styled_string (sm, "this is a warning"));
685
686 x_ruler r (x_ruler::label_dir::BELOW);
687 r.add_label (r: canvas::range_t (0, 5),
688 text: std::move (s),
689 style_id: style::id_plain,
690 kind: x_ruler::label_kind::TEXT_WITH_BORDER);
691
692 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}
700
701/* Run all selftests in this file. */
702
703void
704text_art_ruler_cc_tests ()
705{
706 test_single ();
707 test_single_above ();
708 test_multiple_contiguous ();
709 test_multiple_contiguous_above ();
710 test_multiple_contiguous_abutting_labels ();
711 test_multiple_contiguous_overlapping_labels ();
712 test_abutting_left_border ();
713 test_too_long_to_consolidate_vertically ();
714 test_abutting_neighbor ();
715 test_gaps ();
716 test_styled ();
717 test_borders ();
718 test_emoji ();
719}
720
721} // namespace selftest
722
723
724#endif /* #if CHECKING_P */
725

source code of gcc/text-art/ruler.cc