Skip to content

Commit 05e2575

Browse files
committed
Optimize size of text_style using bit packing
1 parent 78f8dda commit 05e2575

File tree

4 files changed

+130
-96
lines changed

4 files changed

+130
-96
lines changed

doc/api.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ performance bottleneck.
580580

581581
`fmt/color.h` provides support for terminal color and text style output.
582582

583-
::: print(const text_style&, format_string<T...>, T&&...)
583+
::: print(text_style, format_string<T...>, T&&...)
584584

585585
::: fg(detail::color_type)
586586

include/fmt/color.h

+112-85
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ enum class color : uint32_t {
155155
white_smoke = 0xF5F5F5, // rgb(245,245,245)
156156
yellow = 0xFFFF00, // rgb(255,255,0)
157157
yellow_green = 0x9ACD32 // rgb(154,205,50)
158-
}; // enum class color
158+
}; // enum class color
159159

160160
enum class terminal_color : uint8_t {
161161
black = 30,
@@ -205,121 +205,155 @@ struct rgb {
205205

206206
namespace detail {
207207

208-
// color is a struct of either a rgb color or a terminal color.
208+
// a bit-packed variant of either an rgb color or a terminal color.
209+
// see text_style for the bit-packing scheme.
209210
struct color_type {
210-
FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {}
211-
FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} {
212-
value.rgb_color = static_cast<uint32_t>(rgb_color);
213-
}
214-
FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} {
215-
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
216-
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
217-
}
211+
FMT_CONSTEXPR color_type() noexcept = default;
212+
FMT_CONSTEXPR color_type(color rgb_color) noexcept
213+
: value_(static_cast<uint32_t>(rgb_color) | (1 << 24)) {}
214+
FMT_CONSTEXPR color_type(rgb rgb_color) noexcept
215+
: color_type(static_cast<color>(
216+
(static_cast<uint32_t>(rgb_color.r) << 16) |
217+
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b)) {}
218218
FMT_CONSTEXPR color_type(terminal_color term_color) noexcept
219-
: is_rgb(), value{} {
220-
value.term_color = static_cast<uint8_t>(term_color);
219+
: value_(static_cast<uint32_t>(term_color) | (3 << 24)) {}
220+
221+
FMT_CONSTEXPR auto is_terminal_color() const noexcept -> bool {
222+
return (value_ & (1 << 25)) != 0;
223+
}
224+
225+
FMT_CONSTEXPR auto get_value() const noexcept -> uint32_t {
226+
return value_ & 0xFFFFFF;
221227
}
222-
bool is_rgb;
223-
union color_union {
224-
uint8_t term_color;
225-
uint32_t rgb_color;
226-
} value;
228+
229+
FMT_CONSTEXPR color_type(uint32_t value) noexcept : value_(value) {}
230+
231+
uint32_t value_{};
227232
};
228233
} // namespace detail
229234

230235
/// A text style consisting of foreground and background colors and emphasis.
231236
class text_style {
237+
// The information is packed as follows:
238+
// ┌──┐
239+
// │ 0│─┐
240+
// │..│ ├── foreground color value
241+
// │23│─┘
242+
// ├──┤
243+
// │24│─┬── discriminator for the above value. 00 if unset, 01 if it's
244+
// │25│─┘ an RGB color, or 11 if it's a terminal color (10 is unused)
245+
// ├──┤
246+
// │26│──── overflow bit, always zero (see below)
247+
// ├──┤
248+
// │27│─┐
249+
// │..│ │
250+
// │50│ │
251+
// ├──┤ │
252+
// │51│ ├── background color (same format as the foreground color)
253+
// │52│ │
254+
// ├──┤ │
255+
// │53│─┘
256+
// ├──┤
257+
// │54│─┐
258+
// │..│ ├── emphases
259+
// │61│─┘
260+
// ├──┤
261+
// │62│─┬── unused
262+
// │63│─┘
263+
// └──┘
264+
// The overflow bits are there to make operator|= efficient.
265+
// When ORing, we must throw if, for either the foreground or background,
266+
// one style specifies a terminal color and the other specifies any color
267+
// (terminal or RGB); in other words, if one discriminator is 11 and the
268+
// other is 11 or 01.
269+
//
270+
// We do that check by adding the styles. Consider what adding does to each
271+
// possible pair of discriminators:
272+
// 00 + 00 = 000
273+
// 01 + 00 = 001
274+
// 11 + 00 = 011
275+
// 01 + 01 = 010
276+
// 11 + 01 = 100 (!!)
277+
// 11 + 11 = 110 (!!)
278+
// In the last two cases, the ones we want to catch, the third bit——the
279+
// overflow bit——is set. Bingo.
280+
//
281+
// We must take into account the possible carry bit from the bits
282+
// before the discriminator. The only potentially problematic case is
283+
// 11 + 00 = 011 (a carry bit would make it 100, not good!), but a carry
284+
// bit is impossible in that case, because 00 (unset color) means the
285+
// 24 bits that precede the discriminator are all zero.
286+
//
287+
// This test can be applied to both colors simultaneously.
288+
232289
public:
233290
FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
234-
: set_foreground_color(), set_background_color(), ems(em) {}
235-
236-
FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& {
237-
if (!set_foreground_color) {
238-
set_foreground_color = rhs.set_foreground_color;
239-
foreground_color = rhs.foreground_color;
240-
} else if (rhs.set_foreground_color) {
241-
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
242-
report_error("can't OR a terminal color");
243-
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
244-
}
291+
: style_(static_cast<uint64_t>(em) << 54) {}
245292

246-
if (!set_background_color) {
247-
set_background_color = rhs.set_background_color;
248-
background_color = rhs.background_color;
249-
} else if (rhs.set_background_color) {
250-
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
251-
report_error("can't OR a terminal color");
252-
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
253-
}
254-
255-
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
256-
static_cast<uint8_t>(rhs.ems));
293+
FMT_CONSTEXPR auto operator|=(text_style rhs) -> text_style& {
294+
if (((style_ + rhs.style_) & ((1ULL << 26) | (1ULL << 53))) != 0)
295+
report_error("can't OR a terminal color");
296+
style_ |= rhs.style_;
257297
return *this;
258298
}
259299

260-
friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs)
300+
friend FMT_CONSTEXPR auto operator|(text_style lhs, text_style rhs)
261301
-> text_style {
262302
return lhs |= rhs;
263303
}
264304

305+
FMT_CONSTEXPR auto operator==(text_style rhs) const noexcept -> bool {
306+
return style_ == rhs.style_;
307+
}
308+
309+
FMT_CONSTEXPR auto operator!=(text_style rhs) const noexcept -> bool {
310+
return !(*this == rhs);
311+
}
312+
265313
FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
266-
return set_foreground_color;
314+
return (style_ & (1 << 24)) != 0;
267315
}
268316
FMT_CONSTEXPR auto has_background() const noexcept -> bool {
269-
return set_background_color;
317+
return (style_ & (1ULL << 51)) != 0;
270318
}
271319
FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
272-
return static_cast<uint8_t>(ems) != 0;
320+
return (style_ >> 54) != 0;
273321
}
274322
FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
275323
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
276-
return foreground_color;
324+
return style_ & 0x3FFFFFF;
277325
}
278326
FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
279327
FMT_ASSERT(has_background(), "no background specified for this style");
280-
return background_color;
328+
return (style_ >> 27) & 0x3FFFFFF;
281329
}
282330
FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
283331
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
284-
return ems;
332+
return static_cast<emphasis>(style_ >> 54);
285333
}
286334

287335
private:
288-
FMT_CONSTEXPR text_style(bool is_foreground,
289-
detail::color_type text_color) noexcept
290-
: set_foreground_color(), set_background_color(), ems() {
291-
if (is_foreground) {
292-
foreground_color = text_color;
293-
set_foreground_color = true;
294-
} else {
295-
background_color = text_color;
296-
set_background_color = true;
297-
}
298-
}
336+
FMT_CONSTEXPR text_style(uint64_t style) noexcept : style_(style) {}
299337

300338
friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
301339
-> text_style;
302340

303341
friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
304342
-> text_style;
305343

306-
detail::color_type foreground_color;
307-
detail::color_type background_color;
308-
bool set_foreground_color;
309-
bool set_background_color;
310-
emphasis ems;
344+
uint64_t style_{};
311345
};
312346

313347
/// Creates a text style from the foreground (text) color.
314348
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
315349
-> text_style {
316-
return text_style(true, foreground);
350+
return foreground.value_ | (1ULL << 24);
317351
}
318352

319353
/// Creates a text style from the background color.
320354
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
321355
-> text_style {
322-
return text_style(false, background);
356+
return (background.value_ | (1ULL << 24)) << 27;
323357
}
324358

325359
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
@@ -334,9 +368,9 @@ template <typename Char> struct ansi_color_escape {
334368
const char* esc) noexcept {
335369
// If we have a terminal color, we need to output another escape code
336370
// sequence.
337-
if (!text_color.is_rgb) {
371+
if (text_color.is_terminal_color()) {
338372
bool is_background = esc == string_view("\x1b[48;2;");
339-
uint32_t value = text_color.value.term_color;
373+
uint32_t value = text_color.get_value();
340374
// Background ASCII codes are the same as the foreground ones but with
341375
// 10 more.
342376
if (is_background) value += 10u;
@@ -360,7 +394,7 @@ template <typename Char> struct ansi_color_escape {
360394
for (int i = 0; i < 7; i++) {
361395
buffer[i] = static_cast<Char>(esc[i]);
362396
}
363-
rgb color(text_color.value.rgb_color);
397+
rgb color(text_color.get_value());
364398
to_esc(color.r, buffer + 7, ';');
365399
to_esc(color.g, buffer + 11, ';');
366400
to_esc(color.b, buffer + 15, 'm');
@@ -441,32 +475,26 @@ template <typename T> struct styled_arg : view {
441475
};
442476

443477
template <typename Char>
444-
void vformat_to(buffer<Char>& buf, const text_style& ts,
445-
basic_string_view<Char> fmt,
478+
void vformat_to(buffer<Char>& buf, text_style ts, basic_string_view<Char> fmt,
446479
basic_format_args<buffered_context<Char>> args) {
447-
bool has_style = false;
448480
if (ts.has_emphasis()) {
449-
has_style = true;
450481
auto emphasis = make_emphasis<Char>(ts.get_emphasis());
451482
buf.append(emphasis.begin(), emphasis.end());
452483
}
453484
if (ts.has_foreground()) {
454-
has_style = true;
455485
auto foreground = make_foreground_color<Char>(ts.get_foreground());
456486
buf.append(foreground.begin(), foreground.end());
457487
}
458488
if (ts.has_background()) {
459-
has_style = true;
460489
auto background = make_background_color<Char>(ts.get_background());
461490
buf.append(background.begin(), background.end());
462491
}
463492
vformat_to(buf, fmt, args);
464-
if (has_style) reset_color<Char>(buf);
493+
if (ts != text_style{}) reset_color<Char>(buf);
465494
}
466495
} // namespace detail
467496

468-
inline void vprint(FILE* f, const text_style& ts, string_view fmt,
469-
format_args args) {
497+
inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) {
470498
auto buf = memory_buffer();
471499
detail::vformat_to(buf, ts, fmt, args);
472500
print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size()));
@@ -482,8 +510,7 @@ inline void vprint(FILE* f, const text_style& ts, string_view fmt,
482510
* "Elapsed time: {0:.2f} seconds", 1.23);
483511
*/
484512
template <typename... T>
485-
void print(FILE* f, const text_style& ts, format_string<T...> fmt,
486-
T&&... args) {
513+
void print(FILE* f, text_style ts, format_string<T...> fmt, T&&... args) {
487514
vprint(f, ts, fmt.str, vargs<T...>{{args...}});
488515
}
489516

@@ -497,11 +524,11 @@ void print(FILE* f, const text_style& ts, format_string<T...> fmt,
497524
* "Elapsed time: {0:.2f} seconds", 1.23);
498525
*/
499526
template <typename... T>
500-
void print(const text_style& ts, format_string<T...> fmt, T&&... args) {
527+
void print(text_style ts, format_string<T...> fmt, T&&... args) {
501528
return print(stdout, ts, fmt, std::forward<T>(args)...);
502529
}
503530

504-
inline auto vformat(const text_style& ts, string_view fmt, format_args args)
531+
inline auto vformat(text_style ts, string_view fmt, format_args args)
505532
-> std::string {
506533
auto buf = memory_buffer();
507534
detail::vformat_to(buf, ts, fmt, args);
@@ -521,16 +548,16 @@ inline auto vformat(const text_style& ts, string_view fmt, format_args args)
521548
* ```
522549
*/
523550
template <typename... T>
524-
inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args)
551+
inline auto format(text_style ts, format_string<T...> fmt, T&&... args)
525552
-> std::string {
526553
return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}});
527554
}
528555

529556
/// Formats a string with the given text_style and writes the output to `out`.
530557
template <typename OutputIt,
531558
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
532-
auto vformat_to(OutputIt out, const text_style& ts, string_view fmt,
533-
format_args args) -> OutputIt {
559+
auto vformat_to(OutputIt out, text_style ts, string_view fmt, format_args args)
560+
-> OutputIt {
534561
auto&& buf = detail::get_buffer<char>(out);
535562
detail::vformat_to(buf, ts, fmt, args);
536563
return detail::get_iterator(buf, out);
@@ -548,8 +575,8 @@ auto vformat_to(OutputIt out, const text_style& ts, string_view fmt,
548575
*/
549576
template <typename OutputIt, typename... T,
550577
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
551-
inline auto format_to(OutputIt out, const text_style& ts,
552-
format_string<T...> fmt, T&&... args) -> OutputIt {
578+
inline auto format_to(OutputIt out, text_style ts, format_string<T...> fmt,
579+
T&&... args) -> OutputIt {
553580
return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}});
554581
}
555582

include/fmt/xchar.h

+5-5
Original file line numberDiff line numberDiff line change
@@ -322,27 +322,27 @@ template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
322322
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
323323
}
324324

325-
inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args)
325+
inline auto vformat(text_style ts, wstring_view fmt, wformat_args args)
326326
-> std::wstring {
327327
auto buf = wmemory_buffer();
328328
detail::vformat_to(buf, ts, fmt, args);
329329
return {buf.data(), buf.size()};
330330
}
331331

332332
template <typename... T>
333-
inline auto format(const text_style& ts, wformat_string<T...> fmt, T&&... args)
333+
inline auto format(text_style ts, wformat_string<T...> fmt, T&&... args)
334334
-> std::wstring {
335335
return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
336336
}
337337

338338
template <typename... T>
339-
FMT_DEPRECATED void print(std::FILE* f, const text_style& ts,
340-
wformat_string<T...> fmt, const T&... args) {
339+
FMT_DEPRECATED void print(std::FILE* f, text_style ts, wformat_string<T...> fmt,
340+
const T&... args) {
341341
vprint(f, ts, fmt, fmt::make_wformat_args(args...));
342342
}
343343

344344
template <typename... T>
345-
FMT_DEPRECATED void print(const text_style& ts, wformat_string<T...> fmt,
345+
FMT_DEPRECATED void print(text_style ts, wformat_string<T...> fmt,
346346
const T&... args) {
347347
return print(stdout, ts, fmt, args...);
348348
}

0 commit comments

Comments
 (0)