aboutsummaryrefslogtreecommitdiff
path: root/gcc/text-art/ruler.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/text-art/ruler.cc')
-rw-r--r--gcc/text-art/ruler.cc724
1 files changed, 724 insertions, 0 deletions
diff --git a/gcc/text-art/ruler.cc b/gcc/text-art/ruler.cc
new file mode 100644
index 0000000..3323a05
--- /dev/null
+++ b/gcc/text-art/ruler.cc
@@ -0,0 +1,724 @@
+/* Classes for printing labelled rulers.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#define INCLUDE_ALGORITHM
+#define INCLUDE_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "pretty-print.h"
+#include "selftest.h"
+#include "text-art/selftests.h"
+#include "text-art/ruler.h"
+#include "text-art/theme.h"
+
+using namespace text_art;
+
+void
+x_ruler::add_label (const canvas::range_t &r,
+ styled_string text,
+ style::id_t style_id,
+ label_kind kind)
+{
+ m_labels.push_back (label (r, std::move (text), style_id, kind));
+ m_has_layout = false;
+}
+
+int
+x_ruler::get_canvas_y (int rel_y) const
+{
+ gcc_assert (rel_y >= 0);
+ gcc_assert (rel_y < m_size.h);
+ switch (m_label_dir)
+ {
+ default:
+ gcc_unreachable ();
+ case label_dir::ABOVE:
+ return m_size.h - (rel_y + 1);
+ case label_dir::BELOW:
+ return rel_y;
+ }
+}
+
+void
+x_ruler::paint_to_canvas (canvas &canvas,
+ canvas::coord_t offset,
+ const theme &theme)
+{
+ ensure_layout ();
+
+ if (0)
+ canvas.fill (canvas::rect_t (offset, m_size),
+ canvas::cell_t ('*'));
+
+ for (size_t idx = 0; idx < m_labels.size (); idx++)
+ {
+ const label &iter_label = m_labels[idx];
+
+ /* Paint the ruler itself. */
+ const int ruler_rel_y = get_canvas_y (0);
+ for (int rel_x = iter_label.m_range.start;
+ rel_x < iter_label.m_range.next;
+ rel_x++)
+ {
+ enum theme::cell_kind kind = theme::cell_kind::X_RULER_MIDDLE;
+
+ if (rel_x == iter_label.m_range.start)
+ {
+ kind = theme::cell_kind::X_RULER_LEFT_EDGE;
+ if (idx > 0)
+ {
+ const label &prev_label = m_labels[idx - 1];
+ if (prev_label.m_range.get_max () == iter_label.m_range.start)
+ kind = theme::cell_kind::X_RULER_INTERNAL_EDGE;
+ }
+ }
+ else if (rel_x == iter_label.m_range.get_max ())
+ kind = theme::cell_kind::X_RULER_RIGHT_EDGE;
+ else if (rel_x == iter_label.m_connector_x)
+ {
+ switch (m_label_dir)
+ {
+ default:
+ gcc_unreachable ();
+ case label_dir::ABOVE:
+ kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE;
+ break;
+ case label_dir::BELOW:
+ kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW;
+ break;
+ }
+ }
+ canvas.paint (canvas::coord_t (rel_x, ruler_rel_y) + offset,
+ theme.get_cell (kind, iter_label.m_style_id));
+ }
+
+ /* Paint the connector to the text. */
+ for (int connector_rel_y = 1;
+ connector_rel_y < iter_label.m_text_rect.get_min_y ();
+ connector_rel_y++)
+ {
+ canvas.paint
+ ((canvas::coord_t (iter_label.m_connector_x,
+ get_canvas_y (connector_rel_y))
+ + offset),
+ theme.get_cell (theme::cell_kind::X_RULER_VERTICAL_CONNECTOR,
+ iter_label.m_style_id));
+ }
+
+ /* Paint the text. */
+ switch (iter_label.m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case x_ruler::label_kind::TEXT:
+ canvas.paint_text
+ ((canvas::coord_t (iter_label.m_text_rect.get_min_x (),
+ get_canvas_y (iter_label.m_text_rect.get_min_y ()))
+ + offset),
+ iter_label.m_text);
+ break;
+
+ case x_ruler::label_kind::TEXT_WITH_BORDER:
+ {
+ const canvas::range_t rel_x_range
+ (iter_label.m_text_rect.get_x_range ());
+
+ enum theme::cell_kind inner_left_kind;
+ enum theme::cell_kind inner_connector_kind;
+ enum theme::cell_kind inner_right_kind;
+ enum theme::cell_kind outer_left_kind;
+ enum theme::cell_kind outer_right_kind;
+
+ switch (m_label_dir)
+ {
+ default:
+ gcc_unreachable ();
+ case label_dir::ABOVE:
+ outer_left_kind = theme::cell_kind::TEXT_BORDER_TOP_LEFT;
+ outer_right_kind = theme::cell_kind::TEXT_BORDER_TOP_RIGHT;
+ inner_left_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_LEFT;
+ inner_connector_kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW;
+ inner_right_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_RIGHT;
+ break;
+ case label_dir::BELOW:
+ inner_left_kind = theme::cell_kind::TEXT_BORDER_TOP_LEFT;
+ inner_connector_kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE;
+ inner_right_kind = theme::cell_kind::TEXT_BORDER_TOP_RIGHT;
+ outer_left_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_LEFT;
+ outer_right_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_RIGHT;
+ break;
+ }
+ /* Inner border. */
+ {
+ const int rel_canvas_y
+ = get_canvas_y (iter_label.m_text_rect.get_min_y ());
+ /* Left corner. */
+ canvas.paint ((canvas::coord_t (rel_x_range.get_min (),
+ rel_canvas_y)
+ + offset),
+ theme.get_cell (inner_left_kind,
+ iter_label.m_style_id));
+ /* Edge. */
+ const canvas::cell_t edge_border_cell
+ = theme.get_cell (theme::cell_kind::TEXT_BORDER_HORIZONTAL,
+ iter_label.m_style_id);
+ const canvas::cell_t connector_border_cell
+ = theme.get_cell (inner_connector_kind,
+ iter_label.m_style_id);
+ for (int rel_x = rel_x_range.get_min () + 1;
+ rel_x < rel_x_range.get_max ();
+ rel_x++)
+ if (rel_x == iter_label.m_connector_x)
+ canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y)
+ + offset),
+ connector_border_cell);
+ else
+ canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y)
+ + offset),
+ edge_border_cell);
+
+ /* Right corner. */
+ canvas.paint ((canvas::coord_t (rel_x_range.get_max (),
+ rel_canvas_y)
+ + offset),
+ theme.get_cell (inner_right_kind,
+ iter_label.m_style_id));
+ }
+
+ {
+ const int rel_canvas_y
+ = get_canvas_y (iter_label.m_text_rect.get_min_y () + 1);
+ const canvas::cell_t border_cell
+ = theme.get_cell (theme::cell_kind::TEXT_BORDER_VERTICAL,
+ iter_label.m_style_id);
+
+ /* Left border. */
+ canvas.paint ((canvas::coord_t (rel_x_range.get_min (),
+ rel_canvas_y)
+ + offset),
+ border_cell);
+ /* Text. */
+ canvas.paint_text ((canvas::coord_t (rel_x_range.get_min () + 1,
+ rel_canvas_y)
+ + offset),
+ iter_label.m_text);
+ /* Right border. */
+ canvas.paint ((canvas::coord_t (rel_x_range.get_max (),
+ rel_canvas_y)
+ + offset),
+ border_cell);
+ }
+
+ /* Outer border. */
+ {
+ const int rel_canvas_y
+ = get_canvas_y (iter_label.m_text_rect.get_max_y ());
+ /* Left corner. */
+ canvas.paint ((canvas::coord_t (rel_x_range.get_min (),
+ rel_canvas_y)
+ + offset),
+ theme.get_cell (outer_left_kind,
+ iter_label.m_style_id));
+ /* Edge. */
+ const canvas::cell_t border_cell
+ = theme.get_cell (theme::cell_kind::TEXT_BORDER_HORIZONTAL,
+ iter_label.m_style_id);
+ for (int rel_x = rel_x_range.get_min () + 1;
+ rel_x < rel_x_range.get_max ();
+ rel_x++)
+ canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y)
+ + offset),
+ border_cell);
+
+ /* Right corner. */
+ canvas.paint ((canvas::coord_t (rel_x_range.get_max (),
+ rel_canvas_y)
+ + offset),
+ theme.get_cell (outer_right_kind,
+ iter_label.m_style_id));
+ }
+ }
+ break;
+ }
+ }
+}
+
+DEBUG_FUNCTION void
+x_ruler::debug (const style_manager &sm)
+{
+ canvas c (get_size (), sm);
+ paint_to_canvas (c, canvas::coord_t (0, 0), unicode_theme ());
+ c.debug (true);
+}
+
+x_ruler::label::label (const canvas::range_t &range,
+ styled_string text,
+ style::id_t style_id,
+ label_kind kind)
+: m_range (range),
+ m_text (std::move (text)),
+ m_style_id (style_id),
+ m_kind (kind),
+ m_text_rect (canvas::coord_t (0, 0),
+ canvas::size_t (m_text.calc_canvas_width (), 1)),
+ m_connector_x ((m_range.get_min () + m_range.get_max ()) / 2)
+{
+ if (kind == label_kind::TEXT_WITH_BORDER)
+ {
+ m_text_rect.m_size.w += 2;
+ m_text_rect.m_size.h += 2;
+ }
+}
+
+bool
+x_ruler::label::operator< (const label &other) const
+{
+ int cmp = m_range.start - other.m_range.start;
+ if (cmp)
+ return cmp < 0;
+ return m_range.next < other.m_range.next;
+}
+
+void
+x_ruler::ensure_layout ()
+{
+ if (m_has_layout)
+ return;
+ update_layout ();
+ m_has_layout = true;
+}
+
+void
+x_ruler::update_layout ()
+{
+ if (m_labels.empty ())
+ return;
+
+ std::sort (m_labels.begin (), m_labels.end ());
+
+ /* Place labels. */
+ int ruler_width = m_labels.back ().m_range.get_next ();
+ int width_with_labels = ruler_width;
+
+ /* Get x coordinates of text parts of each label
+ (m_text_rect.m_top_left.x for each label). */
+ for (size_t idx = 0; idx < m_labels.size (); idx++)
+ {
+ label &iter_label = m_labels[idx];
+ /* Attempt to center the text label. */
+ int min_x;
+ if (idx > 0)
+ {
+ /* ...but don't overlap with the connector to the left. */
+ int left_neighbor_connector_x = m_labels[idx - 1].m_connector_x;
+ min_x = left_neighbor_connector_x + 1;
+ }
+ else
+ {
+ /* ...or go beyond the leftmost column. */
+ min_x = 0;
+ }
+ int connector_x = iter_label.m_connector_x;
+ int centered_x
+ = connector_x - ((int)iter_label.m_text_rect.get_width () / 2);
+ int text_x = std::max (min_x, centered_x);
+ iter_label.m_text_rect.m_top_left.x = text_x;
+ }
+
+ /* Now walk backwards trying to place them vertically,
+ setting m_text_rect.m_top_left.y for each label,
+ consolidating the rows where possible.
+ The y cooordinates are stored with respect to label_dir::BELOW. */
+ int label_y = 2;
+ for (int idx = m_labels.size () - 1; idx >= 0; idx--)
+ {
+ label &iter_label = m_labels[idx];
+ /* Does it fit on the same row as the text label to the right? */
+ size_t text_len = iter_label.m_text_rect.get_width ();
+ /* Get the x-coord of immediately beyond iter_label's text. */
+ int next_x = iter_label.m_text_rect.get_min_x () + text_len;
+ if (idx < (int)m_labels.size () - 1)
+ {
+ if (next_x >= m_labels[idx + 1].m_text_rect.get_min_x ())
+ {
+ /* If not, start a new row. */
+ label_y += m_labels[idx + 1].m_text_rect.get_height ();
+ }
+ }
+ iter_label.m_text_rect.m_top_left.y = label_y;
+ width_with_labels = std::max (width_with_labels, next_x);
+ }
+
+ m_size = canvas::size_t (width_with_labels,
+ label_y + m_labels[0].m_text_rect.get_height ());
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+assert_x_ruler_streq (const location &loc,
+ x_ruler &ruler,
+ const theme &theme,
+ const style_manager &sm,
+ bool styled,
+ const char *expected_str)
+{
+ canvas c (ruler.get_size (), sm);
+ ruler.paint_to_canvas (c, canvas::coord_t (0, 0), theme);
+ if (0)
+ c.debug (styled);
+ assert_canvas_streq (loc, c, styled, expected_str);
+}
+
+#define ASSERT_X_RULER_STREQ(RULER, THEME, SM, STYLED, EXPECTED_STR) \
+ SELFTEST_BEGIN_STMT \
+ assert_x_ruler_streq ((SELFTEST_LOCATION), \
+ (RULER), \
+ (THEME), \
+ (SM), \
+ (STYLED), \
+ (EXPECTED_STR)); \
+ SELFTEST_END_STMT
+
+static void
+test_single ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"),
+ style::id_plain, x_ruler::label_kind::TEXT);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ ("|~~~~+~~~~|\n"
+ " |\n"
+ " foo\n"));
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("├────┬────┤\n"
+ " │\n"
+ " foo\n"));
+}
+
+static void
+test_single_above ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::ABOVE);
+ r.add_label (canvas::range_t (0, 11), styled_string (sm, "hello world"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ ("hello world\n"
+ " |\n"
+ "|~~~~+~~~~|\n"));
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("hello world\n"
+ " │\n"
+ "├────┴────┤\n"));
+}
+
+static void
+test_multiple_contiguous ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"),
+ style::id_plain);
+ r.add_label (canvas::range_t (10, 16), styled_string (sm, "bar"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ ("|~~~~+~~~~|~+~~|\n"
+ " | |\n"
+ " foo bar\n"));
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("├────┬────┼─┬──┤\n"
+ " │ │\n"
+ " foo bar\n"));
+}
+
+static void
+test_multiple_contiguous_above ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::ABOVE);
+ r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"),
+ style::id_plain);
+ r.add_label (canvas::range_t (10, 16), styled_string (sm, "bar"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ (" foo bar\n"
+ " | |\n"
+ "|~~~~+~~~~|~+~~|\n"));
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ (" foo bar\n"
+ " │ │\n"
+ "├────┴────┼─┴──┤\n"));
+}
+
+static void
+test_multiple_contiguous_abutting_labels ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 11), styled_string (sm, "12345678"),
+ style::id_plain);
+ r.add_label (canvas::range_t (10, 16), styled_string (sm, "1234678"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("├────┬────┼─┬──┤\n"
+ " │ │\n"
+ " │ 1234678\n"
+ " 12345678\n"));
+}
+
+static void
+test_multiple_contiguous_overlapping_labels ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 11), styled_string (sm, "123456789"),
+ style::id_plain);
+ r.add_label (canvas::range_t (10, 16), styled_string (sm, "12346789"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("├────┬────┼─┬──┤\n"
+ " │ │\n"
+ " │ 12346789\n"
+ " 123456789\n"));
+}
+static void
+test_abutting_left_border ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 6),
+ styled_string (sm, "this is a long label"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("├─┬──┤\n"
+ " │\n"
+ "this is a long label\n"));
+}
+
+static void
+test_too_long_to_consolidate_vertically ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 11),
+ styled_string (sm, "long string A"),
+ style::id_plain);
+ r.add_label (canvas::range_t (10, 16),
+ styled_string (sm, "long string B"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("├────┬────┼─┬──┤\n"
+ " │ │\n"
+ " │long string B\n"
+ "long string A\n"));
+}
+
+static void
+test_abutting_neighbor ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 11),
+ styled_string (sm, "very long string A"),
+ style::id_plain);
+ r.add_label (canvas::range_t (10, 16),
+ styled_string (sm, "very long string B"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("├────┬────┼─┬──┤\n"
+ " │ │\n"
+ " │very long string B\n"
+ "very long string A\n"));
+}
+
+static void
+test_gaps ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 5),
+ styled_string (sm, "foo"),
+ style::id_plain);
+ r.add_label (canvas::range_t (10, 15),
+ styled_string (sm, "bar"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ ("|~+~| |~+~|\n"
+ " | |\n"
+ " foo bar\n"));
+}
+
+static void
+test_styled ()
+{
+ style_manager sm;
+ style s1, s2;
+ s1.m_bold = true;
+ s1.m_fg_color = style::named_color::YELLOW;
+ s2.m_bold = true;
+ s2.m_fg_color = style::named_color::BLUE;
+ style::id_t sid1 = sm.get_or_create_id (s1);
+ style::id_t sid2 = sm.get_or_create_id (s2);
+
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 5), styled_string (sm, "foo"), sid1);
+ r.add_label (canvas::range_t (10, 15), styled_string (sm, "bar"), sid2);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ ("|~+~| |~+~|\n"
+ " | |\n"
+ " foo bar\n"));
+}
+
+static void
+test_borders ()
+{
+ style_manager sm;
+ {
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 5),
+ styled_string (sm, "label 1"),
+ style::id_plain,
+ x_ruler::label_kind::TEXT_WITH_BORDER);
+ r.add_label (canvas::range_t (10, 15),
+ styled_string (sm, "label 2"),
+ style::id_plain);
+ r.add_label (canvas::range_t (20, 25),
+ styled_string (sm, "label 3"),
+ style::id_plain,
+ x_ruler::label_kind::TEXT_WITH_BORDER);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ "|~+~| |~+~| |~+~|\n"
+ " | | |\n"
+ " | label 2 +---+---+\n"
+ "+-+-----+ |label 3|\n"
+ "|label 1| +-------+\n"
+ "+-------+\n");
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ "├─┬─┤ ├─┬─┤ ├─┬─┤\n"
+ " │ │ │\n"
+ " │ label 2 ╭───┴───╮\n"
+ "╭─┴─────╮ │label 3│\n"
+ "│label 1│ ╰───────╯\n"
+ "╰───────╯\n");
+ }
+ {
+ x_ruler r (x_ruler::label_dir::ABOVE);
+ r.add_label (canvas::range_t (0, 5),
+ styled_string (sm, "label 1"),
+ style::id_plain,
+ x_ruler::label_kind::TEXT_WITH_BORDER);
+ r.add_label (canvas::range_t (10, 15),
+ styled_string (sm, "label 2"),
+ style::id_plain);
+ r.add_label (canvas::range_t (20, 25),
+ styled_string (sm, "label 3"),
+ style::id_plain,
+ x_ruler::label_kind::TEXT_WITH_BORDER);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ "+-------+\n"
+ "|label 1| +-------+\n"
+ "+-+-----+ |label 3|\n"
+ " | label 2 +---+---+\n"
+ " | | |\n"
+ "|~+~| |~+~| |~+~|\n");
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ "╭───────╮\n"
+ "│label 1│ ╭───────╮\n"
+ "╰─┬─────╯ │label 3│\n"
+ " │ label 2 ╰───┬───╯\n"
+ " │ │ │\n"
+ "├─┴─┤ ├─┴─┤ ├─┴─┤\n");
+ }
+}
+
+static void
+test_emoji ()
+{
+ style_manager sm;
+
+ styled_string s;
+ s.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN. */
+ true));
+ s.append (styled_string (sm, " "));
+ s.append (styled_string (sm, "this is a warning"));
+
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 5),
+ std::move (s),
+ style::id_plain,
+ x_ruler::label_kind::TEXT_WITH_BORDER);
+
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ "|~+~|\n"
+ " |\n"
+ "+-+------------------+\n"
+ "|⚠️ this is a warning|\n"
+ "+--------------------+\n");
+}
+
+/* Run all selftests in this file. */
+
+void
+text_art_ruler_cc_tests ()
+{
+ test_single ();
+ test_single_above ();
+ test_multiple_contiguous ();
+ test_multiple_contiguous_above ();
+ test_multiple_contiguous_abutting_labels ();
+ test_multiple_contiguous_overlapping_labels ();
+ test_abutting_left_border ();
+ test_too_long_to_consolidate_vertically ();
+ test_abutting_neighbor ();
+ test_gaps ();
+ test_styled ();
+ test_borders ();
+ test_emoji ();
+}
+
+} // namespace selftest
+
+
+#endif /* #if CHECKING_P */