Branch data Line data Source code
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>.
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 : 315 : canvas::canvas (size_t size, const style_manager &style_mgr)
33 : 315 : : m_cells (size_t (size.w, size.h)),
34 : 315 : m_style_mgr (style_mgr)
35 : : {
36 : 315 : m_cells.fill (cell_t (' '));
37 : 315 : }
38 : :
39 : : void
40 : 174552 : canvas::paint (coord_t coord, styled_unichar ch)
41 : : {
42 : 174552 : m_cells.set (coord, std::move (ch));
43 : 174552 : }
44 : :
45 : : void
46 : 2757 : canvas::paint_text (coord_t coord, const styled_string &text)
47 : : {
48 : 21081 : for (auto ch : text)
49 : : {
50 : 18324 : paint (coord, ch);
51 : 18324 : if (ch.double_width_p ())
52 : 21 : coord.x += 2;
53 : : else
54 : 18303 : coord.x++;
55 : 18324 : }
56 : 2757 : }
57 : :
58 : : void
59 : 257 : canvas::fill (rect_t rect, cell_t c)
60 : : {
61 : 1834 : for (int y = rect.get_min_y (); y < rect.get_next_y (); y++)
62 : 92345 : for (int x = rect.get_min_x (); x < rect.get_next_x (); x++)
63 : 90768 : 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 : 328 : canvas::print_to_pp (pretty_printer *pp,
74 : : const char *per_line_prefix) const
75 : : {
76 : 3120 : for (int y = 0; y < m_cells.get_size ().h; y++)
77 : : {
78 : 2792 : style::id_t curr_style_id = 0;
79 : 2792 : if (per_line_prefix)
80 : 1360 : pp_string (pp, per_line_prefix);
81 : :
82 : 2792 : pretty_printer line_pp;
83 : 2792 : line_pp.show_color = pp->show_color;
84 : 2792 : line_pp.url_format = pp->url_format;
85 : 2792 : const int final_x_in_row = get_final_x_in_row (y);
86 : 140172 : for (int x = 0; x <= final_x_in_row; x++)
87 : : {
88 : 137380 : if (x > 0)
89 : : {
90 : 134668 : const cell_t prev_cell = m_cells.get (coord_t (x - 1, y));
91 : 134668 : 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 : 134668 : }
96 : 137359 : const cell_t cell = m_cells.get (coord_t (x, y));
97 : 137359 : if (cell.get_style_id () != curr_style_id)
98 : : {
99 : 4339 : m_style_mgr.print_any_style_changes (&line_pp,
100 : : curr_style_id,
101 : 4339 : cell.get_style_id ());
102 : 4339 : curr_style_id = cell.get_style_id ();
103 : : }
104 : 137359 : pp_unicode_character (&line_pp, cell.get_code ());
105 : 137359 : 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 : 137359 : }
110 : : /* Reset the style at the end of each line. */
111 : 2792 : 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 : 2792 : const char *line_buf = pp_formatted_text (&line_pp);
116 : 2792 : ::size_t len = strlen (line_buf);
117 : 2928 : while (len > 0)
118 : : {
119 : 2840 : if (line_buf[len - 1] == ' ')
120 : : len--;
121 : : else
122 : : break;
123 : : }
124 : 2792 : pp_append_text (pp, line_buf, line_buf + len);
125 : 2792 : pp_newline (pp);
126 : 2792 : }
127 : 328 : }
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.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 : 2792 : canvas::get_final_x_in_row (int y) const
147 : : {
148 : 12590 : for (int x = m_cells.get_size ().w - 1; x >= 0; x--)
149 : : {
150 : 12510 : cell_t cell = m_cells.get (coord_t (x, y));
151 : 12510 : if (cell.get_code () != ' '
152 : 12510 : || cell.get_style_id () != style::id_plain)
153 : 2712 : return x;
154 : 12510 : }
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 : : 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.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.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 */
|