Line data Source code
1 : /* Classes for printing labelled rulers.
2 : Copyright (C) 2023-2026 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 */
|