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