Line data Source code
1 : /*
2 : * Copyright (C) 2025 aeml
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "ascii/TextRenderer.hpp"
19 : #include <iostream>
20 : #include <cmath>
21 :
22 : namespace ascii
23 : {
24 : namespace
25 : {
26 4030 : const char* GetColorCode(Color c)
27 : {
28 4030 : switch (c)
29 : {
30 417 : case Color::Red: return "\x1b[31m";
31 907 : case Color::Green: return "\x1b[32m";
32 150 : case Color::Yellow: return "\x1b[33m";
33 7 : case Color::Blue: return "\x1b[34m";
34 2 : case Color::Magenta: return "\x1b[35m";
35 235 : case Color::Cyan: return "\x1b[36m";
36 196 : case Color::White: return "\x1b[37m";
37 2116 : case Color::Default:
38 2116 : default: return "\x1b[39m";
39 : }
40 : }
41 : }
42 :
43 68 : TextSurface::TextSurface(int width, int height)
44 136 : : m_width(width), m_height(height), m_cells(static_cast<std::size_t>(width * height))
45 : {
46 68 : Clear();
47 68 : }
48 :
49 851 : void TextSurface::Clear(char fill, Color color)
50 : {
51 2540979 : for (auto &cell : m_cells)
52 : {
53 2540128 : cell.ch = fill;
54 2540128 : cell.color = color;
55 : }
56 851 : }
57 :
58 155348 : void TextSurface::Put(int x, int y, char ch, Color color)
59 : {
60 155348 : if (x < 0 || y < 0 || x >= m_width || y >= m_height) return;
61 147203 : auto& cell = m_cells[static_cast<std::size_t>(y * m_width + x)];
62 147203 : cell.ch = ch;
63 147203 : cell.color = color;
64 : }
65 :
66 34 : TextRenderer::TextRenderer(int width, int height)
67 34 : : m_current(width, height), m_previous(width, height)
68 : {
69 : // ensure first diff treats all cells as changed
70 34 : m_previous.Clear('\0', Color::Default);
71 34 : }
72 :
73 749 : void TextRenderer::Clear(char fill, Color color)
74 : {
75 749 : m_current.Clear(fill, color);
76 749 : }
77 :
78 155348 : void TextRenderer::Put(int x, int y, char ch, Color color)
79 : {
80 155348 : m_current.Put(x, y, ch, color);
81 155348 : }
82 :
83 3420 : void TextRenderer::DrawLine(int x0, int y0, int x1, int y1, char ch, Color color)
84 : {
85 3420 : int dx = std::abs(x1 - x0);
86 3420 : int dy = std::abs(y1 - y0);
87 3420 : int sx = (x0 < x1) ? 1 : -1;
88 3420 : int sy = (y0 < y1) ? 1 : -1;
89 3420 : int err = dx - dy;
90 :
91 : while (true)
92 : {
93 55668 : Put(x0, y0, ch, color);
94 55668 : if (x0 == x1 && y0 == y1) break;
95 52248 : int e2 = 2 * err;
96 52248 : if (e2 > -dy)
97 : {
98 38399 : err -= dy;
99 38399 : x0 += sx;
100 : }
101 52248 : if (e2 < dx)
102 : {
103 14824 : err += dx;
104 14824 : y0 += sy;
105 : }
106 52248 : }
107 3420 : }
108 :
109 180 : void TextRenderer::DrawRect(int x, int y, int w, int h, char ch, Color color)
110 : {
111 180 : if (w <= 0 || h <= 0) return;
112 180 : DrawLine(x, y, x + w - 1, y, ch, color);
113 180 : DrawLine(x, y + h - 1, x + w - 1, y + h - 1, ch, color);
114 180 : DrawLine(x, y, x, y + h - 1, ch, color);
115 180 : DrawLine(x + w - 1, y, x + w - 1, y + h - 1, ch, color);
116 : }
117 :
118 384 : void TextRenderer::DrawCircle(int xc, int yc, int r, char ch, Color color)
119 : {
120 384 : int x = 0, y = r;
121 384 : int d = 3 - 2 * r;
122 1356 : auto draw8 = [&](int xc, int yc, int x, int y) {
123 1356 : Put(xc+x, yc+y, ch, color);
124 1356 : Put(xc-x, yc+y, ch, color);
125 1356 : Put(xc+x, yc-y, ch, color);
126 1356 : Put(xc-x, yc-y, ch, color);
127 1356 : Put(xc+y, yc+x, ch, color);
128 1356 : Put(xc-y, yc+x, ch, color);
129 1356 : Put(xc+y, yc-x, ch, color);
130 1356 : Put(xc-y, yc-x, ch, color);
131 1740 : };
132 384 : draw8(xc, yc, x, y);
133 1356 : while (y >= x)
134 : {
135 972 : x++;
136 972 : if (d > 0)
137 : {
138 588 : y--;
139 588 : d = d + 4 * (x - y) + 10;
140 : }
141 : else
142 384 : d = d + 4 * x + 6;
143 972 : draw8(xc, yc, x, y);
144 : }
145 384 : }
146 :
147 180 : void TextRenderer::DrawEllipse(int xc, int yc, int rx, int ry, char ch, Color color)
148 : {
149 180 : long rx2 = (long)rx * rx;
150 180 : long ry2 = (long)ry * ry;
151 180 : long twoRx2 = 2 * rx2;
152 180 : long twoRy2 = 2 * ry2;
153 : long p;
154 180 : long x = 0;
155 180 : long y = ry;
156 180 : long px = 0;
157 180 : long py = twoRx2 * y;
158 :
159 1260 : auto draw4 = [&](int xc, int yc, int x, int y) {
160 1260 : Put(xc + x, yc + y, ch, color);
161 1260 : Put(xc - x, yc + y, ch, color);
162 1260 : Put(xc + x, yc - y, ch, color);
163 1260 : Put(xc - x, yc - y, ch, color);
164 1440 : };
165 :
166 : // Region 1
167 180 : draw4(xc, yc, x, y);
168 180 : p = (long)(ry2 - (rx2 * ry) + (0.25 * rx2));
169 720 : while (px < py) {
170 540 : x++;
171 540 : px += twoRy2;
172 540 : if (p < 0) {
173 360 : p += ry2 + px;
174 : } else {
175 180 : y--;
176 180 : py -= twoRx2;
177 180 : p += ry2 + px - py;
178 : }
179 540 : draw4(xc, yc, x, y);
180 : }
181 :
182 : // Region 2
183 180 : p = (long)(ry2 * (x + 0.5) * (x + 0.5) + rx2 * (y - 1) * (y - 1) - rx2 * ry2);
184 720 : while (y > 0) {
185 540 : y--;
186 540 : py -= twoRx2;
187 540 : if (p > 0) {
188 360 : p += rx2 - py;
189 : } else {
190 180 : x++;
191 180 : px += twoRy2;
192 180 : p += rx2 - py + px;
193 : }
194 540 : draw4(xc, yc, x, y);
195 : }
196 180 : }
197 :
198 180 : void TextRenderer::FillEllipse(int xc, int yc, int rx, int ry, char ch, Color color)
199 : {
200 180 : long rx2 = (long)rx * rx;
201 180 : long ry2 = (long)ry * ry;
202 180 : long twoRx2 = 2 * rx2;
203 180 : long twoRy2 = 2 * ry2;
204 : long p;
205 180 : long x = 0;
206 180 : long y = ry;
207 180 : long px = 0;
208 180 : long py = twoRx2 * y;
209 :
210 900 : auto drawLine = [&](int xc, int yc, int x, int y) {
211 900 : DrawLine(xc - x, yc + y, xc + x, yc + y, ch, color);
212 900 : DrawLine(xc - x, yc - y, xc + x, yc - y, ch, color);
213 1080 : };
214 :
215 : // Region 1
216 180 : drawLine(xc, yc, x, y);
217 180 : p = (long)(ry2 - (rx2 * ry) + (0.25 * rx2));
218 540 : while (px < py) {
219 360 : x++;
220 360 : px += twoRy2;
221 360 : if (p < 0) {
222 180 : p += ry2 + px;
223 : } else {
224 180 : y--;
225 180 : py -= twoRx2;
226 180 : p += ry2 + px - py;
227 : }
228 360 : drawLine(xc, yc, x, y);
229 : }
230 :
231 : // Region 2
232 180 : p = (long)(ry2 * (x + 0.5) * (x + 0.5) + rx2 * (y - 1) * (y - 1) - rx2 * ry2);
233 540 : while (y > 0) {
234 360 : y--;
235 360 : py -= twoRx2;
236 360 : if (p > 0) {
237 180 : p += rx2 - py;
238 : } else {
239 180 : x++;
240 180 : px += twoRy2;
241 180 : p += rx2 - py + px;
242 : }
243 360 : drawLine(xc, yc, x, y);
244 : }
245 180 : }
246 :
247 7 : std::size_t TextRenderer::ComputeDiff() const
248 : {
249 7 : const Cell* cur = m_current.Data();
250 7 : const Cell* prev = m_previous.Data();
251 7 : std::size_t changed = 0;
252 7 : const std::size_t total = static_cast<std::size_t>(m_current.Width() * m_current.Height());
253 135 : for (std::size_t i = 0; i < total; ++i)
254 : {
255 128 : if (cur[i] != prev[i]) ++changed;
256 : }
257 7 : return changed;
258 : }
259 :
260 754 : std::size_t TextRenderer::PresentDiff(std::ostream& out)
261 : {
262 754 : if (m_headless)
263 : {
264 21 : const Cell* cur = m_current.Data();
265 21 : Cell* prev = m_previous.Data();
266 21 : const int w = m_current.Width();
267 21 : const int h = m_current.Height();
268 21 : const std::size_t total = static_cast<std::size_t>(w * h);
269 21 : std::size_t changed = 0;
270 :
271 : // In headless mode, dump the full frame to the stream
272 21 : out << "--- FRAME START ---\n";
273 841 : for (int y = 0; y < h; ++y)
274 : {
275 66420 : for (int x = 0; x < w; ++x)
276 : {
277 65600 : out << cur[y * w + x].ch;
278 : }
279 820 : out << '\n';
280 : }
281 21 : out << "--- FRAME END ---\n";
282 21 : out.flush();
283 :
284 65621 : for (std::size_t i = 0; i < total; ++i)
285 : {
286 65600 : if (cur[i] != prev[i])
287 : {
288 32498 : ++changed;
289 32498 : prev[i] = cur[i];
290 : }
291 : }
292 21 : return changed;
293 : }
294 :
295 733 : const int w = m_current.Width();
296 733 : const int h = m_current.Height();
297 733 : const Cell* cur = m_current.Data();
298 733 : Cell* prev = m_previous.Data();
299 733 : std::size_t changed = 0;
300 :
301 : // Save cursor position
302 733 : out << "\x1b[s";
303 : // Hide cursor
304 733 : out << "\x1b[?25l";
305 :
306 733 : Color lastColor = Color::Default;
307 : // Ensure we start with default color
308 733 : out << GetColorCode(lastColor);
309 :
310 733 : int cursorX = -1;
311 733 : int cursorY = -1;
312 :
313 28021 : for (int y = 0; y < h; ++y)
314 : {
315 2209008 : for (int x = 0; x < w; ++x)
316 : {
317 2181720 : const std::size_t idx = static_cast<std::size_t>(y * w + x);
318 2181720 : if (cur[idx] != prev[idx])
319 : {
320 54620 : ++changed;
321 :
322 : // Move cursor if not already at the right spot
323 54620 : if (cursorX != x || cursorY != y)
324 : {
325 : // ANSI uses 1-based coordinates
326 23412 : out << "\x1b[" << (y + 1) << ";" << (x + 1) << "H";
327 : }
328 :
329 : // Change color if needed
330 54620 : if (cur[idx].color != lastColor)
331 : {
332 3297 : out << GetColorCode(cur[idx].color);
333 3297 : lastColor = cur[idx].color;
334 : }
335 :
336 54620 : out << cur[idx].ch;
337 :
338 : // Update cursor position (it advanced by 1)
339 54620 : cursorX = x + 1;
340 54620 : cursorY = y;
341 :
342 : // Update previous buffer
343 54620 : prev[idx] = cur[idx];
344 : }
345 : }
346 : }
347 :
348 : // Reset color
349 733 : out << "\x1b[0m";
350 : // Restore cursor
351 733 : out << "\x1b[u";
352 : // Show cursor
353 733 : out << "\x1b[?25h";
354 :
355 733 : out.flush(); // Ensure output is sent
356 733 : return changed;
357 : }
358 :
359 0 : std::size_t TextRenderer::PresentFull(std::ostream& out)
360 : {
361 0 : const Cell* cur = m_current.Data();
362 0 : Cell* prev = m_previous.Data();
363 0 : const int w = m_current.Width();
364 0 : const int h = m_current.Height();
365 0 : const std::size_t total = static_cast<std::size_t>(w * h);
366 0 : std::size_t changed = 0;
367 :
368 0 : out << "--- FRAME START ---\n";
369 0 : for (int y = 0; y < h; ++y)
370 : {
371 0 : for (int x = 0; x < w; ++x)
372 : {
373 0 : const std::size_t idx = static_cast<std::size_t>(y * w + x);
374 0 : if (cur[idx] != prev[idx]) ++changed;
375 0 : out << cur[idx].ch;
376 : }
377 0 : out << '\n';
378 : }
379 0 : out << "--- FRAME END ---\n";
380 0 : out.flush();
381 :
382 : // Update previous buffer to current state
383 0 : for (std::size_t i = 0; i < total; ++i) prev[i] = cur[i];
384 0 : return changed;
385 : }
386 : }
|