Porytiles
Loading...
Searching...
No Matches
ansi_styled_text_formatter.cpp
Go to the documentation of this file.
2
3#include <array>
4#include <cmath>
5#include <limits>
6#include <string>
7
8namespace {
9
10using namespace porytiles;
11
25int rgb_to_ansi256(std::uint8_t r, std::uint8_t g, std::uint8_t b)
26{
27 // Handle grayscale (colors 232-255)
28 if (r == g && g == b) {
29 if (r < 8) {
30 return 16;
31 }
32 if (r > 248) {
33 return 231;
34 }
35 return static_cast<int>(std::lround((r - 8) / 247.0 * 24)) + 232;
36 }
37
38 // Convert to 6x6x6 color cube (colors 16-231)
39 // Map each RGB component from 0-255 to 0-5
40 auto to_6 = [](const std::uint8_t val) -> int {
41 if (val < 48) {
42 return 0;
43 }
44 if (val < 115) {
45 return 1;
46 }
47 return (val - 35) / 40;
48 };
49
50 const int r_idx = to_6(r);
51 const int g_idx = to_6(g);
52 const int b_idx = to_6(b);
53
54 return 16 + 36 * r_idx + 6 * g_idx + b_idx;
55}
56
70PredefinedColor find_closest_plain_color(std::uint8_t r, std::uint8_t g, std::uint8_t b)
71{
72 // Standard ANSI color RGB values
73 const std::array<std::pair<PredefinedColor, std::array<int, 3>>, 8> plain_colors = {{
74 {PredefinedColor::black, {0, 0, 0}},
75 {PredefinedColor::red, {255, 0, 0}},
76 {PredefinedColor::green, {0, 255, 0}},
77 {PredefinedColor::yellow, {255, 255, 0}},
78 {PredefinedColor::blue, {0, 0, 255}},
79 {PredefinedColor::magenta, {255, 0, 255}},
80 {PredefinedColor::cyan, {0, 255, 255}},
81 {PredefinedColor::white, {255, 255, 255}},
82 }};
83
84 PredefinedColor closest = PredefinedColor::white;
85 double min_distance = std::numeric_limits<double>::max();
86
87 for (const auto &[color, rgb] : plain_colors) {
88 const int dr = r - rgb[0];
89 const int dg = g - rgb[1];
90 const int db = b - rgb[2];
91
92 // Weighted perceptual distance: 2*ΔR² + 4*ΔG² + 3*ΔB²
93 const double distance = 2.0 * dr * dr + 4.0 * dg * dg + 3.0 * db * db;
94
95 if (distance < min_distance) {
96 min_distance = distance;
97 closest = color;
98 }
99 }
100
101 return closest;
102}
103
104// ANSI reset code
105const std::string ansi_reset = "\033[0m";
106
107// Mapping from PredefinedColor to ANSI foreground codes
108const std::array<std::pair<PredefinedColor, std::string>, 8> fg_color_mappings = {{
109 {PredefinedColor::black, "\033[30m"},
110 {PredefinedColor::red, "\033[31m"},
111 {PredefinedColor::green, "\033[32m"},
112 {PredefinedColor::yellow, "\033[33m"},
113 {PredefinedColor::blue, "\033[34m"},
114 {PredefinedColor::magenta, "\033[35m"},
115 {PredefinedColor::cyan, "\033[36m"},
116 {PredefinedColor::white, "\033[37m"},
117}};
118
119// Mapping from PredefinedColor to ANSI background codes
120const std::array<std::pair<PredefinedColor, std::string>, 8> bg_color_mappings = {{
121 {PredefinedColor::black, "\033[40m"},
122 {PredefinedColor::red, "\033[41m"},
123 {PredefinedColor::green, "\033[42m"},
124 {PredefinedColor::yellow, "\033[43m"},
125 {PredefinedColor::blue, "\033[44m"},
126 {PredefinedColor::magenta, "\033[45m"},
127 {PredefinedColor::cyan, "\033[46m"},
128 {PredefinedColor::white, "\033[47m"},
129}};
130
131} // namespace
132
133namespace porytiles {
134
135std::string AnsiStyledTextFormatter::style(const std::string &text, Style styles) const
136{
137 std::string prefix;
138
139 // Handle foreground color
140 if (styles.has_fg_color()) {
141 if (styles.is_fg_rgb()) {
142 const RgbColor rgb = styles.fg_rgb();
143
144 switch (mode_) {
146 // Find closest plain color and use its standard ANSI code
147 const PredefinedColor closest = find_closest_plain_color(rgb.r, rgb.g, rgb.b);
148 for (const auto &[color, ansi_code] : fg_color_mappings) {
149 if (color == closest) {
150 prefix += ansi_code;
151 break;
152 }
153 }
154 break;
155 }
157 // Convert to ANSI-256 and emit \033[38;5;<code>m
158 const int color_code = rgb_to_ansi256(rgb.r, rgb.g, rgb.b);
159 prefix += "\033[38;5;" + std::to_string(color_code) + "m";
160 break;
161 }
163 // Emit 24-bit RGB ANSI code \033[38;2;<r>;<g>;<b>m
164 prefix += "\033[38;2;" + std::to_string(rgb.r) + ";" + std::to_string(rgb.g) + ";" +
165 std::to_string(rgb.b) + "m";
166 break;
167 }
168 }
169 }
170 else {
171 // Handle predefined foreground colors using standard ANSI codes
172 const PredefinedColor fg = styles.fg_predefined();
173 for (const auto &[color, ansi_code] : fg_color_mappings) {
174 if (color == fg) {
175 prefix += ansi_code;
176 break;
177 }
178 }
179 }
180 }
181
182 // Handle background color
183 if (styles.has_bg_color()) {
184 if (styles.is_bg_rgb()) {
185 const RgbColor rgb = styles.bg_rgb();
186
187 switch (mode_) {
189 // Find closest plain color and use its standard ANSI code
190 const PredefinedColor closest = find_closest_plain_color(rgb.r, rgb.g, rgb.b);
191 for (const auto &[color, ansi_code] : bg_color_mappings) {
192 if (color == closest) {
193 prefix += ansi_code;
194 break;
195 }
196 }
197 break;
198 }
200 // Convert to ANSI-256 and emit \033[48;5;<code>m
201 const int color_code = rgb_to_ansi256(rgb.r, rgb.g, rgb.b);
202 prefix += "\033[48;5;" + std::to_string(color_code) + "m";
203 break;
204 }
206 // Emit 24-bit RGB ANSI code \033[48;2;<r>;<g>;<b>m
207 prefix += "\033[48;2;" + std::to_string(rgb.r) + ";" + std::to_string(rgb.g) + ";" +
208 std::to_string(rgb.b) + "m";
209 break;
210 }
211 }
212 }
213 else {
214 // Handle predefined background colors using standard ANSI codes
215 const PredefinedColor bg = styles.bg_predefined();
216 for (const auto &[color, ansi_code] : bg_color_mappings) {
217 if (color == bg) {
218 prefix += ansi_code;
219 break;
220 }
221 }
222 }
223 }
224
225 // Apply formatting flags (bold, italic) - these work independently of color mode
226 if (styles.has_bold()) {
227 prefix += "\033[1m";
228 }
229 if (styles.has_faint()) {
230 prefix += "\033[2m";
231 }
232 if (styles.has_italic()) {
233 prefix += "\033[3m";
234 }
235 if (styles.has_underline()) {
236 prefix += "\033[4m";
237 }
238 if (styles.has_blink()) {
239 prefix += "\033[5m";
240 }
241
242 // Only add prefix and reset if we actually have styling to apply
243 if (prefix.empty()) {
244 return text;
245 }
246
247 return prefix + text + ansi_reset;
248}
249
250} // namespace porytiles
std::string style(const std::string &text, Style styles) const override
Applies ANSI escape codes to style text according to the specified Style flags.
Text styling class supporting formatting, foreground colors, and background colors.
constexpr bool is_bg_rgb() const
Checks if the background color is in RGB mode.
constexpr bool has_faint() const
Checks if this Style has faint formatting.
constexpr bool has_fg_color() const
Checks if this Style has a foreground color set.
constexpr bool has_blink() const
Checks if this Style has blink formatting.
constexpr RgbColor fg_rgb() const
Returns the foreground RGB color.
constexpr bool has_underline() const
Checks if this Style has underline formatting.
constexpr bool has_italic() const
Checks if this Style has italic formatting.
constexpr PredefinedColor bg_predefined() const
Returns the predefined background color.
constexpr PredefinedColor fg_predefined() const
Returns the predefined foreground color.
constexpr bool has_bold() const
Checks if this Style has bold formatting.
constexpr RgbColor bg_rgb() const
Returns the background RGB color.
constexpr bool is_fg_rgb() const
Checks if the foreground color is in RGB mode.
constexpr bool has_bg_color() const
Checks if this Style has a background color set.
PredefinedColor
Predefined color options for text styling.
RGB color representation for extracting color components from Style values.
std::uint8_t b
Blue channel (0-255)
std::uint8_t r
Red channel (0-255)
std::uint8_t g
Green channel (0-255)