1 /* Canvas for random-access procedural text art.
2 Copyright (C) 2023-2024 Free Software Foundation, Inc.
3 Contributed by David Malcolm <dmalcolm@redhat.com>.
5 This file is part of GCC.
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
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
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/>. */
22 #define INCLUDE_VECTOR
24 #include "coretypes.h"
25 #include "pretty-print.h"
27 #include "text-art/selftests.h"
28 #include "text-art/canvas.h"
30 using namespace text_art
;
32 canvas::canvas (size_t size
, const style_manager
&style_mgr
)
33 : m_cells (size_t (size
.w
, size
.h
)),
34 m_style_mgr (style_mgr
)
36 m_cells
.fill (cell_t (' '));
40 canvas::paint (coord_t coord
, styled_unichar ch
)
42 m_cells
.set (coord
, std::move (ch
));
46 canvas::paint_text (coord_t coord
, const styled_string
&text
)
51 if (ch
.double_width_p ())
59 canvas::fill (rect_t rect
, cell_t c
)
61 for (int y
= rect
.get_min_y (); y
< rect
.get_next_y (); y
++)
62 for (int x
= rect
.get_min_x (); x
< rect
.get_next_x (); x
++)
63 paint(coord_t (x
, y
), c
);
69 fill (rect_t (coord_t (0, 0), get_size ()), cell_t ('*'));
73 canvas::print_to_pp (pretty_printer
*pp
,
74 const char *per_line_prefix
) const
76 for (int y
= 0; y
< m_cells
.get_size ().h
; y
++)
78 style::id_t curr_style_id
= 0;
80 pp_string (pp
, per_line_prefix
);
82 pretty_printer line_pp
;
83 line_pp
.show_color
= pp
->show_color
;
84 line_pp
.url_format
= pp
->url_format
;
85 const int final_x_in_row
= get_final_x_in_row (y
);
86 for (int x
= 0; x
<= final_x_in_row
; x
++)
90 const cell_t prev_cell
= m_cells
.get (coord_t (x
- 1, y
));
91 if (prev_cell
.double_width_p ())
92 /* This cell is just a placeholder for the
93 2nd column of a double width cell; skip it. */
96 const cell_t cell
= m_cells
.get (coord_t (x
, y
));
97 if (cell
.get_style_id () != curr_style_id
)
99 m_style_mgr
.print_any_style_changes (&line_pp
,
101 cell
.get_style_id ());
102 curr_style_id
= cell
.get_style_id ();
104 pp_unicode_character (&line_pp
, cell
.get_code ());
105 if (cell
.emoji_variant_p ())
106 /* Append U+FE0F VARIATION SELECTOR-16 to select the emoji
107 variation of the char. */
108 pp_unicode_character (&line_pp
, 0xFE0F);
110 /* Reset the style at the end of each line. */
111 m_style_mgr
.print_any_style_changes (&line_pp
, curr_style_id
, 0);
113 /* Print from line_pp to pp, stripping trailing whitespace from
115 const char *line_buf
= pp_formatted_text (&line_pp
);
116 ::size_t len
= strlen (line_buf
);
119 if (line_buf
[len
- 1] == ' ')
124 pp_append_text (pp
, line_buf
, line_buf
+ len
);
130 canvas::debug (bool styled
) const
135 pp_show_color (&pp
) = true;
136 pp
.url_format
= determine_url_format (DIAGNOSTICS_URL_AUTO
);
139 fprintf (stderr
, "%s\n", pp_formatted_text (&pp
));
142 /* Find right-most non-default cell in this row,
143 or -1 if all are default. */
146 canvas::get_final_x_in_row (int y
) const
148 for (int x
= m_cells
.get_size ().w
- 1; x
>= 0; x
--)
150 cell_t cell
= m_cells
.get (coord_t (x
, y
));
151 if (cell
.get_code () != ' '
152 || cell
.get_style_id () != style::id_plain
)
166 canvas
c (canvas::size_t (5, 5), sm
);
167 ASSERT_CANVAS_STREQ (c
, false,
179 canvas
c (canvas::size_t (3, 3), sm
);
180 c
.paint (canvas::coord_t (0, 0), styled_unichar ('A'));
181 c
.paint (canvas::coord_t (1, 1), styled_unichar ('B'));
182 c
.paint (canvas::coord_t (2, 2), styled_unichar ('C'));
184 ASSERT_CANVAS_STREQ (c
, false,
192 canvas
c (canvas::size_t (5, 3), sm
);
194 ASSERT_CANVAS_STREQ (c
, false,
204 canvas
c (canvas::size_t (6, 1), sm
);
205 c
.paint_text (canvas::coord_t (0, 0), styled_string (sm
, "012345"));
206 ASSERT_CANVAS_STREQ (c
, false,
209 /* Paint an emoji character that should occupy two canvas columns when
211 c
.paint_text (canvas::coord_t (2, 0), styled_string ((cppchar_t
)0x1f642));
212 ASSERT_CANVAS_STREQ (c
, false,
219 canvas::size_t sz (30, 30);
221 canvas
canvas (sz
, sm
);
222 canvas::coord_t
center (sz
.w
/ 2, sz
.h
/ 2);
223 const int radius
= 12;
224 const int radius_squared
= radius
* radius
;
225 for (int x
= 0; x
< sz
.w
; x
++)
226 for (int y
= 0; y
< sz
.h
; y
++)
228 int dx
= x
- center
.x
;
229 int dy
= y
- center
.y
;
230 char ch
= "AB"[(x
+ y
) % 2];
231 if (dx
* dx
+ dy
* dy
< radius_squared
)
232 canvas
.paint (canvas::coord_t (x
, y
), styled_unichar (ch
));
243 " ABABABABABABABABA\n"
244 " ABABABABABABABABABA\n"
245 " ABABABABABABABABABABA\n"
246 " BABABABABABABABABABAB\n"
247 " BABABABABABABABABABABAB\n"
248 " ABABABABABABABABABABABA\n"
249 " BABABABABABABABABABABAB\n"
250 " ABABABABABABABABABABABA\n"
251 " BABABABABABABABABABABAB\n"
252 " ABABABABABABABABABABABA\n"
253 " BABABABABABABABABABABAB\n"
254 " ABABABABABABABABABABABA\n"
255 " BABABABABABABABABABABAB\n"
256 " BABABABABABABABABABAB\n"
257 " ABABABABABABABABABABA\n"
258 " ABABABABABABABABABA\n"
259 " ABABABABABABABABA\n"
271 const canvas::size_t sz (10, 10);
272 const canvas::coord_t
center (sz
.w
/ 2, sz
.h
/ 2);
273 const int outer_r2
= 25;
274 const int inner_r2
= 10;
277 for (int x
= 0; x
< sz
.w
; x
++)
278 for (int y
= 0; y
< sz
.h
; y
++)
280 const int dist_from_center_squared
281 = ((x
- center
.x
) * (x
- center
.x
) + (y
- center
.y
) * (y
- center
.y
));
282 if (dist_from_center_squared
< outer_r2
)
285 if (dist_from_center_squared
< inner_r2
)
286 s
.m_fg_color
= style::named_color::RED
;
288 s
.m_fg_color
= style::named_color::GREEN
;
289 c
.paint (canvas::coord_t (x
, y
),
290 styled_unichar ('*', false, sm
.get_or_create_id (s
)));
293 ASSERT_EQ (sm
.get_num_styles (), 3);
309 " \e[32m\e[K*****\e[m\e[K\n"
310 " \e[32m\e[K***\e[31m\e[K*\e[32m\e[K***\e[m\e[K\n"
311 " \e[32m\e[K**\e[31m\e[K*****\e[32m\e[K**\e[m\e[K\n"
312 " \e[32m\e[K**\e[31m\e[K*****\e[32m\e[K**\e[m\e[K\n"
313 " \e[32m\e[K*\e[31m\e[K*******\e[32m\e[K*\e[m\e[K\n"
314 " \e[32m\e[K**\e[31m\e[K*****\e[32m\e[K**\e[m\e[K\n"
315 " \e[32m\e[K**\e[31m\e[K*****\e[32m\e[K**\e[m\e[K\n"
316 " \e[32m\e[K***\e[31m\e[K*\e[32m\e[K***\e[m\e[K\n"
317 " \e[32m\e[K*****\e[m\e[K\n"));
323 auto_fix_quotes fix_quotes
;
325 styled_string
s (styled_string::from_fmt (sm
, nullptr,
326 "before %qs after", "foo"));
327 canvas
c (canvas::size_t (s
.calc_canvas_width (), 1), sm
);
328 c
.paint_text (canvas::coord_t (0, 0), s
);
329 ASSERT_CANVAS_STREQ (c
, false,
330 "before `foo' after\n");
331 ASSERT_CANVAS_STREQ (c
, true,
332 "before `\e[00;01m\e[Kfoo\e[00m\e[K' after\n");
339 styled_string
s (0x26A0, /* U+26A0 WARNING SIGN. */
341 canvas
c (canvas::size_t (s
.calc_canvas_width (), 1), sm
);
342 c
.paint_text (canvas::coord_t (0, 0), s
);
343 ASSERT_CANVAS_STREQ (c
, false, "⚠️\n");
344 ASSERT_CANVAS_STREQ (c
, true, "⚠️\n");
352 s
.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN. */
354 s
.append (styled_string (sm
, "test"));
355 ASSERT_EQ (s
.size (), 5);
356 ASSERT_EQ (s
.calc_canvas_width (), 5);
357 canvas
c (canvas::size_t (s
.calc_canvas_width (), 1), sm
);
358 c
.paint_text (canvas::coord_t (0, 0), s
);
359 ASSERT_CANVAS_STREQ (c
, false,
360 /* U+26A0 WARNING SIGN, as UTF-8: 0xE2 0x9A 0xA0. */
362 /* U+FE0F VARIATION SELECTOR-16, as UTF-8: 0xEF 0xB8 0x8F. */
371 canvas
canvas (canvas::size_t (9, 3), sm
);
372 styled_string
foo_ss (sm
, "foo");
373 foo_ss
.set_url (sm
, "https://www.example.com/foo");
374 styled_string
bar_ss (sm
, "bar");
375 bar_ss
.set_url (sm
, "https://www.example.com/bar");
376 canvas
.paint_text(canvas::coord_t (1, 1), foo_ss
);
377 canvas
.paint_text(canvas::coord_t (5, 1), bar_ss
);
379 ASSERT_CANVAS_STREQ (canvas
, false,
385 pp_show_color (&pp
) = true;
386 pp
.url_format
= URL_FORMAT_ST
;
387 assert_canvas_streq (SELFTEST_LOCATION
, canvas
, &pp
,
392 "\33]8;;https://www.example.com/foo\33\\foo\33]8;;\33\\"
394 "\33]8;;https://www.example.com/bar\33\\bar\33]8;;\33\\"
402 pp_show_color (&pp
) = true;
403 pp
.url_format
= URL_FORMAT_BEL
;
404 assert_canvas_streq (SELFTEST_LOCATION
, canvas
, &pp
,
409 "\33]8;;https://www.example.com/foo\afoo\33]8;;\a"
411 "\33]8;;https://www.example.com/bar\abar\33]8;;\a"
418 /* Run all selftests in this file. */
421 text_art_canvas_cc_tests ()
428 test_color_circle ();
435 } // namespace selftest
438 #endif /* #if CHECKING_P */