LCOV - code coverage report
Current view: top level - src/ascii - TextRenderer.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 92.1 % 228 210
Test Date: 2026-04-10 19:03:25 Functions: 94.4 % 18 17

            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              : }
        

Generated by: LCOV version 2.0-1