Line data Source code
1 : /* Canvas for random-access procedural text art.
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_VECTOR
23 : #include "system.h"
24 : #include "coretypes.h"
25 : #include "pretty-print.h"
26 : #include "selftest.h"
27 : #include "text-art/selftests.h"
28 : #include "text-art/canvas.h"
29 :
30 : using namespace text_art;
31 :
32 330 : canvas::canvas (size_t size, const style_manager &style_mgr)
33 330 : : m_cells (size_t (size.w, size.h)),
34 330 : m_style_mgr (style_mgr)
35 : {
36 330 : m_cells.fill (cell_t (' '));
37 330 : }
38 :
39 : void
40 172390 : canvas::paint (coord_t coord, styled_unichar ch)
41 : {
42 172390 : m_cells.set (coord, std::move (ch));
43 172390 : }
44 :
45 : void
46 2800 : canvas::paint_text (coord_t coord, const styled_string &text)
47 : {
48 22167 : for (auto ch : text)
49 : {
50 19367 : paint (coord, ch);
51 19367 : if (ch.double_width_p ())
52 21 : coord.x += 2;
53 : else
54 19346 : coord.x++;
55 19367 : }
56 2800 : }
57 :
58 : void
59 257 : canvas::fill (rect_t rect, cell_t c)
60 : {
61 1814 : for (int y = rect.get_min_y (); y < rect.get_next_y (); y++)
62 90103 : for (int x = rect.get_min_x (); x < rect.get_next_x (); x++)
63 88546 : paint(coord_t (x, y), c);
64 257 : }
65 :
66 : void
67 12 : canvas::debug_fill ()
68 : {
69 12 : fill (rect_t (coord_t (0, 0), get_size ()), cell_t ('*'));
70 12 : }
71 :
72 : void
73 344 : canvas::print_to_pp (pretty_printer *pp,
74 : const char *per_line_prefix) const
75 : {
76 3223 : for (int y = 0; y < m_cells.get_size ().h; y++)
77 : {
78 2879 : style::id_t curr_style_id = 0;
79 2879 : if (per_line_prefix)
80 1331 : pp_string (pp, per_line_prefix);
81 :
82 2879 : pretty_printer line_pp;
83 2879 : pp_show_color (&line_pp) = pp_show_color (pp);
84 2879 : line_pp.set_url_format (pp->get_url_format ());
85 2879 : const int final_x_in_row = get_final_x_in_row (y);
86 136854 : for (int x = 0; x <= final_x_in_row; x++)
87 : {
88 133975 : if (x > 0)
89 : {
90 131176 : const cell_t prev_cell = m_cells.get (coord_t (x - 1, y));
91 131176 : 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. */
94 21 : continue;
95 131176 : }
96 133954 : const cell_t cell = m_cells.get (coord_t (x, y));
97 133954 : if (cell.get_style_id () != curr_style_id)
98 : {
99 4140 : m_style_mgr.print_any_style_changes (&line_pp,
100 : curr_style_id,
101 4140 : cell.get_style_id ());
102 4140 : curr_style_id = cell.get_style_id ();
103 : }
104 133954 : pp_unicode_character (&line_pp, cell.get_code ());
105 133954 : if (cell.emoji_variant_p ())
106 : /* Append U+FE0F VARIATION SELECTOR-16 to select the emoji
107 : variation of the char. */
108 17 : pp_unicode_character (&line_pp, 0xFE0F);
109 133954 : }
110 : /* Reset the style at the end of each line. */
111 2879 : m_style_mgr.print_any_style_changes (&line_pp, curr_style_id, 0);
112 :
113 : /* Print from line_pp to pp, stripping trailing whitespace from
114 : the line. */
115 2879 : const char *line_buf = pp_formatted_text (&line_pp);
116 2879 : ::size_t len = strlen (line_buf);
117 3015 : while (len > 0)
118 : {
119 2927 : if (line_buf[len - 1] == ' ')
120 : len--;
121 : else
122 : break;
123 : }
124 2879 : pp_append_text (pp, line_buf, line_buf + len);
125 2879 : pp_newline (pp);
126 2879 : }
127 344 : }
128 :
129 : DEBUG_FUNCTION void
130 0 : canvas::debug (bool styled) const
131 : {
132 0 : pretty_printer pp;
133 0 : if (styled)
134 : {
135 0 : pp_show_color (&pp) = true;
136 0 : pp.set_url_format (determine_url_format (DIAGNOSTICS_URL_AUTO));
137 : }
138 0 : print_to_pp (&pp);
139 0 : fprintf (stderr, "%s\n", pp_formatted_text (&pp));
140 0 : }
141 :
142 : /* Find right-most non-default cell in this row,
143 : or -1 if all are default. */
144 :
145 : int
146 2879 : canvas::get_final_x_in_row (int y) const
147 : {
148 15108 : for (int x = m_cells.get_size ().w - 1; x >= 0; x--)
149 : {
150 15028 : cell_t cell = m_cells.get (coord_t (x, y));
151 15028 : if (cell.get_code () != ' '
152 15028 : || cell.get_style_id () != style::id_plain)
153 2799 : return x;
154 15028 : }
155 : return -1;
156 : }
157 :
158 : #if CHECKING_P
159 :
160 : namespace selftest {
161 :
162 : static void
163 4 : test_blank ()
164 : {
165 4 : style_manager sm;
166 4 : canvas c (canvas::size_t (5, 5), sm);
167 4 : ASSERT_CANVAS_STREQ (c, false,
168 : ("\n"
169 : "\n"
170 : "\n"
171 : "\n"
172 : "\n"));
173 4 : }
174 :
175 : static void
176 4 : test_abc ()
177 : {
178 4 : style_manager sm;
179 4 : canvas c (canvas::size_t (3, 3), sm);
180 4 : c.paint (canvas::coord_t (0, 0), styled_unichar ('A'));
181 4 : c.paint (canvas::coord_t (1, 1), styled_unichar ('B'));
182 4 : c.paint (canvas::coord_t (2, 2), styled_unichar ('C'));
183 :
184 4 : ASSERT_CANVAS_STREQ (c, false,
185 : "A\n B\n C\n");
186 4 : }
187 :
188 : static void
189 4 : test_debug_fill ()
190 : {
191 4 : style_manager sm;
192 4 : canvas c (canvas::size_t (5, 3), sm);
193 4 : c.debug_fill();
194 4 : ASSERT_CANVAS_STREQ (c, false,
195 : ("*****\n"
196 : "*****\n"
197 : "*****\n"));
198 4 : }
199 :
200 : static void
201 4 : test_text ()
202 : {
203 4 : style_manager sm;
204 4 : canvas c (canvas::size_t (6, 1), sm);
205 4 : c.paint_text (canvas::coord_t (0, 0), styled_string (sm, "012345"));
206 4 : ASSERT_CANVAS_STREQ (c, false,
207 : ("012345\n"));
208 :
209 : /* Paint an emoji character that should occupy two canvas columns when
210 : printed. */
211 4 : c.paint_text (canvas::coord_t (2, 0), styled_string ((cppchar_t)0x1f642));
212 4 : ASSERT_CANVAS_STREQ (c, false,
213 : ("01🙂45\n"));
214 4 : }
215 :
216 : static void
217 4 : test_circle ()
218 : {
219 4 : canvas::size_t sz (30, 30);
220 4 : style_manager sm;
221 4 : canvas canvas (sz, sm);
222 4 : canvas::coord_t center (sz.w / 2, sz.h / 2);
223 4 : const int radius = 12;
224 4 : const int radius_squared = radius * radius;
225 124 : for (int x = 0; x < sz.w; x++)
226 3720 : for (int y = 0; y < sz.h; y++)
227 : {
228 3600 : int dx = x - center.x;
229 3600 : int dy = y - center.y;
230 3600 : char ch = "AB"[(x + y) % 2];
231 3600 : if (dx * dx + dy * dy < radius_squared)
232 1748 : canvas.paint (canvas::coord_t (x, y), styled_unichar (ch));
233 : }
234 4 : ASSERT_CANVAS_STREQ
235 : (canvas, false,
236 : ("\n"
237 : "\n"
238 : "\n"
239 : "\n"
240 : " BABABABAB\n"
241 : " ABABABABABABA\n"
242 : " ABABABABABABABA\n"
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"
260 : " ABABABABABABABA\n"
261 : " ABABABABABABA\n"
262 : " BABABABAB\n"
263 : "\n"
264 : "\n"
265 : "\n"));
266 4 : }
267 :
268 : static void
269 4 : test_color_circle ()
270 : {
271 4 : const canvas::size_t sz (10, 10);
272 4 : const canvas::coord_t center (sz.w / 2, sz.h / 2);
273 4 : const int outer_r2 = 25;
274 4 : const int inner_r2 = 10;
275 4 : style_manager sm;
276 4 : canvas c (sz, sm);
277 44 : for (int x = 0; x < sz.w; x++)
278 440 : for (int y = 0; y < sz.h; y++)
279 : {
280 400 : const int dist_from_center_squared
281 400 : = ((x - center.x) * (x - center.x) + (y - center.y) * (y - center.y));
282 400 : if (dist_from_center_squared < outer_r2)
283 : {
284 276 : style s;
285 276 : if (dist_from_center_squared < inner_r2)
286 116 : s.m_fg_color = style::named_color::RED;
287 : else
288 160 : s.m_fg_color = style::named_color::GREEN;
289 276 : c.paint (canvas::coord_t (x, y),
290 552 : styled_unichar ('*', false, sm.get_or_create_id (s)));
291 276 : }
292 : }
293 4 : ASSERT_EQ (sm.get_num_styles (), 3);
294 4 : ASSERT_CANVAS_STREQ
295 : (c, false,
296 : ("\n"
297 : " *****\n"
298 : " *******\n"
299 : " *********\n"
300 : " *********\n"
301 : " *********\n"
302 : " *********\n"
303 : " *********\n"
304 : " *******\n"
305 : " *****\n"));
306 4 : ASSERT_CANVAS_STREQ
307 : (c, true,
308 : ("\n"
309 : " [32m[K*****[m[K\n"
310 : " [32m[K***[31m[K*[32m[K***[m[K\n"
311 : " [32m[K**[31m[K*****[32m[K**[m[K\n"
312 : " [32m[K**[31m[K*****[32m[K**[m[K\n"
313 : " [32m[K*[31m[K*******[32m[K*[m[K\n"
314 : " [32m[K**[31m[K*****[32m[K**[m[K\n"
315 : " [32m[K**[31m[K*****[32m[K**[m[K\n"
316 : " [32m[K***[31m[K*[32m[K***[m[K\n"
317 : " [32m[K*****[m[K\n"));
318 4 : }
319 :
320 : static void
321 4 : test_bold ()
322 : {
323 4 : auto_fix_quotes fix_quotes;
324 4 : style_manager sm;
325 4 : styled_string s (styled_string::from_fmt (sm, nullptr,
326 4 : "before %qs after", "foo"));
327 4 : canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm);
328 4 : c.paint_text (canvas::coord_t (0, 0), s);
329 4 : ASSERT_CANVAS_STREQ (c, false,
330 : "before `foo' after\n");
331 4 : ASSERT_CANVAS_STREQ (c, true,
332 : "before `[00;01m[Kfoo[00m[K' after\n");
333 4 : }
334 :
335 : static void
336 4 : test_emoji ()
337 : {
338 4 : style_manager sm;
339 4 : styled_string s (0x26A0, /* U+26A0 WARNING SIGN. */
340 4 : true);
341 4 : canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm);
342 4 : c.paint_text (canvas::coord_t (0, 0), s);
343 4 : ASSERT_CANVAS_STREQ (c, false, "⚠️\n");
344 4 : ASSERT_CANVAS_STREQ (c, true, "⚠️\n");
345 4 : }
346 :
347 : static void
348 4 : test_emoji_2 ()
349 : {
350 4 : style_manager sm;
351 4 : styled_string s;
352 4 : s.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN. */
353 4 : true));
354 4 : s.append (styled_string (sm, "test"));
355 4 : ASSERT_EQ (s.size (), 5);
356 4 : ASSERT_EQ (s.calc_canvas_width (), 5);
357 4 : canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm);
358 4 : c.paint_text (canvas::coord_t (0, 0), s);
359 4 : ASSERT_CANVAS_STREQ (c, false,
360 : /* U+26A0 WARNING SIGN, as UTF-8: 0xE2 0x9A 0xA0. */
361 : "\xE2\x9A\xA0"
362 : /* U+FE0F VARIATION SELECTOR-16, as UTF-8: 0xEF 0xB8 0x8F. */
363 : "\xEF\xB8\x8F"
364 : "test\n");
365 4 : }
366 :
367 : static void
368 4 : test_canvas_urls ()
369 : {
370 4 : style_manager sm;
371 4 : canvas canvas (canvas::size_t (9, 3), sm);
372 4 : styled_string foo_ss (sm, "foo");
373 4 : foo_ss.set_url (sm, "https://www.example.com/foo");
374 4 : styled_string bar_ss (sm, "bar");
375 4 : bar_ss.set_url (sm, "https://www.example.com/bar");
376 4 : canvas.paint_text(canvas::coord_t (1, 1), foo_ss);
377 4 : canvas.paint_text(canvas::coord_t (5, 1), bar_ss);
378 :
379 4 : ASSERT_CANVAS_STREQ (canvas, false,
380 : ("\n"
381 : " foo bar\n"
382 : "\n"));
383 4 : {
384 4 : pretty_printer pp;
385 4 : pp_show_color (&pp) = true;
386 4 : pp.set_url_format (URL_FORMAT_ST);
387 4 : assert_canvas_streq (SELFTEST_LOCATION, canvas, &pp,
388 : (/* Line 1. */
389 : "\n"
390 : /* Line 2. */
391 : " "
392 : "\33]8;;https://www.example.com/foo\33\\foo\33]8;;\33\\"
393 : " "
394 : "\33]8;;https://www.example.com/bar\33\\bar\33]8;;\33\\"
395 : "\n"
396 : /* Line 3. */
397 : "\n"));
398 4 : }
399 :
400 4 : {
401 4 : pretty_printer pp;
402 4 : pp_show_color (&pp) = true;
403 4 : pp.set_url_format (URL_FORMAT_BEL);
404 4 : assert_canvas_streq (SELFTEST_LOCATION, canvas, &pp,
405 : (/* Line 1. */
406 : "\n"
407 : /* Line 2. */
408 : " "
409 : "\33]8;;https://www.example.com/foo\afoo\33]8;;\a"
410 : " "
411 : "\33]8;;https://www.example.com/bar\abar\33]8;;\a"
412 : "\n"
413 : /* Line 3. */
414 : "\n"));
415 4 : }
416 4 : }
417 :
418 : /* Run all selftests in this file. */
419 :
420 : void
421 4 : text_art_canvas_cc_tests ()
422 : {
423 4 : test_blank ();
424 4 : test_abc ();
425 4 : test_debug_fill ();
426 4 : test_text ();
427 4 : test_circle ();
428 4 : test_color_circle ();
429 4 : test_bold ();
430 4 : test_emoji ();
431 4 : test_emoji_2 ();
432 4 : test_canvas_urls ();
433 4 : }
434 :
435 : } // namespace selftest
436 :
437 :
438 : #endif /* #if CHECKING_P */
|