aboutsummaryrefslogtreecommitdiff
path: root/gcc/text-art/canvas.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/text-art/canvas.cc')
-rw-r--r--gcc/text-art/canvas.cc438
1 files changed, 438 insertions, 0 deletions
diff --git a/gcc/text-art/canvas.cc b/gcc/text-art/canvas.cc
new file mode 100644
index 0000000..26ea051
--- /dev/null
+++ b/gcc/text-art/canvas.cc
@@ -0,0 +1,438 @@
+/* Canvas for random-access procedural text art.
+ 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_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "pretty-print.h"
+#include "selftest.h"
+#include "text-art/selftests.h"
+#include "text-art/canvas.h"
+
+using namespace text_art;
+
+canvas::canvas (size_t size, const style_manager &style_mgr)
+: m_cells (size_t (size.w, size.h)),
+ m_style_mgr (style_mgr)
+{
+ m_cells.fill (cell_t (' '));
+}
+
+void
+canvas::paint (coord_t coord, styled_unichar ch)
+{
+ m_cells.set (coord, std::move (ch));
+}
+
+void
+canvas::paint_text (coord_t coord, const styled_string &text)
+{
+ for (auto ch : text)
+ {
+ paint (coord, ch);
+ if (ch.double_width_p ())
+ coord.x += 2;
+ else
+ coord.x++;
+ }
+}
+
+void
+canvas::fill (rect_t rect, cell_t c)
+{
+ for (int y = rect.get_min_y (); y < rect.get_next_y (); y++)
+ for (int x = rect.get_min_x (); x < rect.get_next_x (); x++)
+ paint(coord_t (x, y), c);
+}
+
+void
+canvas::debug_fill ()
+{
+ fill (rect_t (coord_t (0, 0), get_size ()), cell_t ('*'));
+}
+
+void
+canvas::print_to_pp (pretty_printer *pp,
+ const char *per_line_prefix) const
+{
+ for (int y = 0; y < m_cells.get_size ().h; y++)
+ {
+ style::id_t curr_style_id = 0;
+ if (per_line_prefix)
+ pp_string (pp, per_line_prefix);
+
+ pretty_printer line_pp;
+ line_pp.show_color = pp->show_color;
+ line_pp.url_format = pp->url_format;
+ const int final_x_in_row = get_final_x_in_row (y);
+ for (int x = 0; x <= final_x_in_row; x++)
+ {
+ if (x > 0)
+ {
+ const cell_t prev_cell = m_cells.get (coord_t (x - 1, y));
+ if (prev_cell.double_width_p ())
+ /* This cell is just a placeholder for the
+ 2nd column of a double width cell; skip it. */
+ continue;
+ }
+ const cell_t cell = m_cells.get (coord_t (x, y));
+ if (cell.get_style_id () != curr_style_id)
+ {
+ m_style_mgr.print_any_style_changes (&line_pp,
+ curr_style_id,
+ cell.get_style_id ());
+ curr_style_id = cell.get_style_id ();
+ }
+ pp_unicode_character (&line_pp, cell.get_code ());
+ if (cell.emoji_variant_p ())
+ /* Append U+FE0F VARIATION SELECTOR-16 to select the emoji
+ variation of the char. */
+ pp_unicode_character (&line_pp, 0xFE0F);
+ }
+ /* Reset the style at the end of each line. */
+ m_style_mgr.print_any_style_changes (&line_pp, curr_style_id, 0);
+
+ /* Print from line_pp to pp, stripping trailing whitespace from
+ the line. */
+ const char *line_buf = pp_formatted_text (&line_pp);
+ ::size_t len = strlen (line_buf);
+ while (len > 0)
+ {
+ if (line_buf[len - 1] == ' ')
+ len--;
+ else
+ break;
+ }
+ pp_append_text (pp, line_buf, line_buf + len);
+ pp_newline (pp);
+ }
+}
+
+DEBUG_FUNCTION void
+canvas::debug (bool styled) const
+{
+ pretty_printer pp;
+ if (styled)
+ {
+ pp_show_color (&pp) = true;
+ pp.url_format = determine_url_format (DIAGNOSTICS_URL_AUTO);
+ }
+ print_to_pp (&pp);
+ fprintf (stderr, "%s\n", pp_formatted_text (&pp));
+}
+
+/* Find right-most non-default cell in this row,
+ or -1 if all are default. */
+
+int
+canvas::get_final_x_in_row (int y) const
+{
+ for (int x = m_cells.get_size ().w - 1; x >= 0; x--)
+ {
+ cell_t cell = m_cells.get (coord_t (x, y));
+ if (cell.get_code () != ' '
+ || cell.get_style_id () != style::id_plain)
+ return x;
+ }
+ return -1;
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+test_blank ()
+{
+ style_manager sm;
+ canvas c (canvas::size_t (5, 5), sm);
+ ASSERT_CANVAS_STREQ (c, false,
+ ("\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"));
+}
+
+static void
+test_abc ()
+{
+ style_manager sm;
+ canvas c (canvas::size_t (3, 3), sm);
+ c.paint (canvas::coord_t (0, 0), styled_unichar ('A'));
+ c.paint (canvas::coord_t (1, 1), styled_unichar ('B'));
+ c.paint (canvas::coord_t (2, 2), styled_unichar ('C'));
+
+ ASSERT_CANVAS_STREQ (c, false,
+ "A\n B\n C\n");
+}
+
+static void
+test_debug_fill ()
+{
+ style_manager sm;
+ canvas c (canvas::size_t (5, 3), sm);
+ c.debug_fill();
+ ASSERT_CANVAS_STREQ (c, false,
+ ("*****\n"
+ "*****\n"
+ "*****\n"));
+}
+
+static void
+test_text ()
+{
+ style_manager sm;
+ canvas c (canvas::size_t (6, 1), sm);
+ c.paint_text (canvas::coord_t (0, 0), styled_string (sm, "012345"));
+ ASSERT_CANVAS_STREQ (c, false,
+ ("012345\n"));
+
+ /* Paint an emoji character that should occupy two canvas columns when
+ printed. */
+ c.paint_text (canvas::coord_t (2, 0), styled_string ((cppchar_t)0x1f642));
+ ASSERT_CANVAS_STREQ (c, false,
+ ("01🙂45\n"));
+}
+
+static void
+test_circle ()
+{
+ canvas::size_t sz (30, 30);
+ style_manager sm;
+ canvas canvas (sz, sm);
+ canvas::coord_t center (sz.w / 2, sz.h / 2);
+ const int radius = 12;
+ const int radius_squared = radius * radius;
+ for (int x = 0; x < sz.w; x++)
+ for (int y = 0; y < sz.h; y++)
+ {
+ int dx = x - center.x;
+ int dy = y - center.y;
+ char ch = "AB"[(x + y) % 2];
+ if (dx * dx + dy * dy < radius_squared)
+ canvas.paint (canvas::coord_t (x, y), styled_unichar (ch));
+ }
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("\n"
+ "\n"
+ "\n"
+ "\n"
+ " BABABABAB\n"
+ " ABABABABABABA\n"
+ " ABABABABABABABA\n"
+ " ABABABABABABABABA\n"
+ " ABABABABABABABABABA\n"
+ " ABABABABABABABABABABA\n"
+ " BABABABABABABABABABAB\n"
+ " BABABABABABABABABABABAB\n"
+ " ABABABABABABABABABABABA\n"
+ " BABABABABABABABABABABAB\n"
+ " ABABABABABABABABABABABA\n"
+ " BABABABABABABABABABABAB\n"
+ " ABABABABABABABABABABABA\n"
+ " BABABABABABABABABABABAB\n"
+ " ABABABABABABABABABABABA\n"
+ " BABABABABABABABABABABAB\n"
+ " BABABABABABABABABABAB\n"
+ " ABABABABABABABABABABA\n"
+ " ABABABABABABABABABA\n"
+ " ABABABABABABABABA\n"
+ " ABABABABABABABA\n"
+ " ABABABABABABA\n"
+ " BABABABAB\n"
+ "\n"
+ "\n"
+ "\n"));
+}
+
+static void
+test_color_circle ()
+{
+ const canvas::size_t sz (10, 10);
+ const canvas::coord_t center (sz.w / 2, sz.h / 2);
+ const int outer_r2 = 25;
+ const int inner_r2 = 10;
+ style_manager sm;
+ canvas c (sz, sm);
+ for (int x = 0; x < sz.w; x++)
+ for (int y = 0; y < sz.h; y++)
+ {
+ const int dist_from_center_squared
+ = ((x - center.x) * (x - center.x) + (y - center.y) * (y - center.y));
+ if (dist_from_center_squared < outer_r2)
+ {
+ style s;
+ if (dist_from_center_squared < inner_r2)
+ s.m_fg_color = style::named_color::RED;
+ else
+ s.m_fg_color = style::named_color::GREEN;
+ c.paint (canvas::coord_t (x, y),
+ styled_unichar ('*', false, sm.get_or_create_id (s)));
+ }
+ }
+ ASSERT_EQ (sm.get_num_styles (), 3);
+ ASSERT_CANVAS_STREQ
+ (c, false,
+ ("\n"
+ " *****\n"
+ " *******\n"
+ " *********\n"
+ " *********\n"
+ " *********\n"
+ " *********\n"
+ " *********\n"
+ " *******\n"
+ " *****\n"));
+ ASSERT_CANVAS_STREQ
+ (c, true,
+ ("\n"
+ " *****\n"
+ " *******\n"
+ " *********\n"
+ " *********\n"
+ " *********\n"
+ " *********\n"
+ " *********\n"
+ " *******\n"
+ " *****\n"));
+}
+
+static void
+test_bold ()
+{
+ auto_fix_quotes fix_quotes;
+ style_manager sm;
+ styled_string s (styled_string::from_fmt (sm, nullptr,
+ "before %qs after", "foo"));
+ canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm);
+ c.paint_text (canvas::coord_t (0, 0), s);
+ ASSERT_CANVAS_STREQ (c, false,
+ "before `foo' after\n");
+ ASSERT_CANVAS_STREQ (c, true,
+ "before `foo' after\n");
+}
+
+static void
+test_emoji ()
+{
+ style_manager sm;
+ styled_string s (0x26A0, /* U+26A0 WARNING SIGN. */
+ true);
+ canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm);
+ c.paint_text (canvas::coord_t (0, 0), s);
+ ASSERT_CANVAS_STREQ (c, false, "⚠️\n");
+ ASSERT_CANVAS_STREQ (c, true, "⚠️\n");
+}
+
+static void
+test_emoji_2 ()
+{
+ style_manager sm;
+ styled_string s;
+ s.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN. */
+ true));
+ s.append (styled_string (sm, "test"));
+ ASSERT_EQ (s.size (), 5);
+ ASSERT_EQ (s.calc_canvas_width (), 5);
+ canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm);
+ c.paint_text (canvas::coord_t (0, 0), s);
+ ASSERT_CANVAS_STREQ (c, false,
+ /* U+26A0 WARNING SIGN, as UTF-8: 0xE2 0x9A 0xA0. */
+ "\xE2\x9A\xA0"
+ /* U+FE0F VARIATION SELECTOR-16, as UTF-8: 0xEF 0xB8 0x8F. */
+ "\xEF\xB8\x8F"
+ "test\n");
+}
+
+static void
+test_canvas_urls ()
+{
+ style_manager sm;
+ canvas canvas (canvas::size_t (9, 3), sm);
+ styled_string foo_ss (sm, "foo");
+ foo_ss.set_url (sm, "https://www.example.com/foo");
+ styled_string bar_ss (sm, "bar");
+ bar_ss.set_url (sm, "https://www.example.com/bar");
+ canvas.paint_text(canvas::coord_t (1, 1), foo_ss);
+ canvas.paint_text(canvas::coord_t (5, 1), bar_ss);
+
+ ASSERT_CANVAS_STREQ (canvas, false,
+ ("\n"
+ " foo bar\n"
+ "\n"));
+ {
+ pretty_printer pp;
+ pp_show_color (&pp) = true;
+ pp.url_format = URL_FORMAT_ST;
+ assert_canvas_streq (SELFTEST_LOCATION, canvas, &pp,
+ (/* Line 1. */
+ "\n"
+ /* Line 2. */
+ " "
+ "\33]8;;https://www.example.com/foo\33\\foo\33]8;;\33\\"
+ " "
+ "\33]8;;https://www.example.com/bar\33\\bar\33]8;;\33\\"
+ "\n"
+ /* Line 3. */
+ "\n"));
+ }
+
+ {
+ pretty_printer pp;
+ pp_show_color (&pp) = true;
+ pp.url_format = URL_FORMAT_BEL;
+ assert_canvas_streq (SELFTEST_LOCATION, canvas, &pp,
+ (/* Line 1. */
+ "\n"
+ /* Line 2. */
+ " "
+ "\33]8;;https://www.example.com/foo\afoo\33]8;;\a"
+ " "
+ "\33]8;;https://www.example.com/bar\abar\33]8;;\a"
+ "\n"
+ /* Line 3. */
+ "\n"));
+ }
+}
+
+/* Run all selftests in this file. */
+
+void
+text_art_canvas_cc_tests ()
+{
+ test_blank ();
+ test_abc ();
+ test_debug_fill ();
+ test_text ();
+ test_circle ();
+ test_color_circle ();
+ test_bold ();
+ test_emoji ();
+ test_emoji_2 ();
+ test_canvas_urls ();
+}
+
+} // namespace selftest
+
+
+#endif /* #if CHECKING_P */