-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimize text_style
using bit packing
#4363
base: master
Are you sure you want to change the base?
Changes from all commits
78f8dda
05e2575
90d783a
52afc1c
0271475
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -205,121 +205,155 @@ struct rgb { | |
|
||
namespace detail { | ||
|
||
// color is a struct of either a rgb color or a terminal color. | ||
// a bit-packed variant of an RGB color, a terminal color, or unset color. | ||
// see text_style for the bit-packing scheme. | ||
struct color_type { | ||
FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {} | ||
FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} { | ||
value.rgb_color = static_cast<uint32_t>(rgb_color); | ||
} | ||
FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} { | ||
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) | | ||
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b; | ||
} | ||
FMT_CONSTEXPR color_type() noexcept = default; | ||
FMT_CONSTEXPR color_type(color rgb_color) noexcept | ||
: value_(static_cast<uint32_t>(rgb_color) | (1 << 24)) {} | ||
FMT_CONSTEXPR color_type(rgb rgb_color) noexcept | ||
: color_type(static_cast<color>( | ||
(static_cast<uint32_t>(rgb_color.r) << 16) | | ||
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b)) {} | ||
FMT_CONSTEXPR color_type(terminal_color term_color) noexcept | ||
: is_rgb(), value{} { | ||
value.term_color = static_cast<uint8_t>(term_color); | ||
: value_(static_cast<uint32_t>(term_color) | (3 << 24)) {} | ||
|
||
FMT_CONSTEXPR auto is_terminal_color() const noexcept -> bool { | ||
return (value_ & (1 << 25)) != 0; | ||
} | ||
|
||
FMT_CONSTEXPR auto value() const noexcept -> uint32_t { | ||
return value_ & 0xFFFFFF; | ||
} | ||
bool is_rgb; | ||
union color_union { | ||
uint8_t term_color; | ||
uint32_t rgb_color; | ||
} value; | ||
|
||
FMT_CONSTEXPR color_type(uint32_t value) noexcept : value_(value) {} | ||
|
||
uint32_t value_{}; | ||
}; | ||
} // namespace detail | ||
|
||
/// A text style consisting of foreground and background colors and emphasis. | ||
class text_style { | ||
// The information is packed as follows: | ||
// ┌──┐ | ||
// │ 0│─┐ | ||
// │..│ ├── foreground color value | ||
// │23│─┘ | ||
// ├──┤ | ||
// │24│─┬── discriminator for the above value. 00 if unset, 01 if it's | ||
// │25│─┘ an RGB color, or 11 if it's a terminal color (10 is unused) | ||
// ├──┤ | ||
// │26│──── overflow bit, always zero (see below) | ||
// ├──┤ | ||
// │27│─┐ | ||
// │..│ │ | ||
// │50│ │ | ||
// ├──┤ │ | ||
// │51│ ├── background color (same format as the foreground color) | ||
// │52│ │ | ||
// ├──┤ │ | ||
// │53│─┘ | ||
// ├──┤ | ||
// │54│─┐ | ||
// │..│ ├── emphases | ||
// │61│─┘ | ||
// ├──┤ | ||
// │62│─┬── unused | ||
// │63│─┘ | ||
// └──┘ | ||
// The overflow bits are there to make operator|= efficient. | ||
// When ORing, we must throw if, for either the foreground or background, | ||
// one style specifies a terminal color and the other specifies any color | ||
// (terminal or RGB); in other words, if one discriminator is 11 and the | ||
// other is 11 or 01. | ||
// | ||
// We do that check by adding the styles. Consider what adding does to each | ||
// possible pair of discriminators: | ||
// 00 + 00 = 000 | ||
// 01 + 00 = 001 | ||
// 11 + 00 = 011 | ||
// 01 + 01 = 010 | ||
// 11 + 01 = 100 (!!) | ||
// 11 + 11 = 110 (!!) | ||
// In the last two cases, the ones we want to catch, the third bit——the | ||
// overflow bit——is set. Bingo. | ||
// | ||
// We must take into account the possible carry bit from the bits | ||
// before the discriminator. The only potentially problematic case is | ||
// 11 + 00 = 011 (a carry bit would make it 100, not good!), but a carry | ||
// bit is impossible in that case, because 00 (unset color) means the | ||
// 24 bits that precede the discriminator are all zero. | ||
// | ||
// This test can be applied to both colors simultaneously. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a very bit hacky algorithm; I hope the detailed explanation compensates for that. |
||
|
||
public: | ||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept | ||
: set_foreground_color(), set_background_color(), ems(em) {} | ||
|
||
FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& { | ||
if (!set_foreground_color) { | ||
set_foreground_color = rhs.set_foreground_color; | ||
foreground_color = rhs.foreground_color; | ||
} else if (rhs.set_foreground_color) { | ||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) | ||
report_error("can't OR a terminal color"); | ||
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color; | ||
} | ||
: style_(static_cast<uint64_t>(em) << 54) {} | ||
|
||
if (!set_background_color) { | ||
set_background_color = rhs.set_background_color; | ||
background_color = rhs.background_color; | ||
} else if (rhs.set_background_color) { | ||
if (!background_color.is_rgb || !rhs.background_color.is_rgb) | ||
report_error("can't OR a terminal color"); | ||
background_color.value.rgb_color |= rhs.background_color.value.rgb_color; | ||
} | ||
|
||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) | | ||
static_cast<uint8_t>(rhs.ems)); | ||
FMT_CONSTEXPR auto operator|=(text_style rhs) -> text_style& { | ||
if (((style_ + rhs.style_) & ((1ULL << 26) | (1ULL << 53))) != 0) | ||
report_error("can't OR a terminal color"); | ||
style_ |= rhs.style_; | ||
return *this; | ||
} | ||
|
||
friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs) | ||
friend FMT_CONSTEXPR auto operator|(text_style lhs, text_style rhs) | ||
-> text_style { | ||
return lhs |= rhs; | ||
} | ||
|
||
FMT_CONSTEXPR auto operator==(text_style rhs) const noexcept -> bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
return style_ == rhs.style_; | ||
} | ||
|
||
FMT_CONSTEXPR auto operator!=(text_style rhs) const noexcept -> bool { | ||
return !(*this == rhs); | ||
} | ||
|
||
FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { | ||
return set_foreground_color; | ||
return (style_ & (1 << 24)) != 0; | ||
} | ||
FMT_CONSTEXPR auto has_background() const noexcept -> bool { | ||
return set_background_color; | ||
return (style_ & (1ULL << 51)) != 0; | ||
} | ||
FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { | ||
return static_cast<uint8_t>(ems) != 0; | ||
return (style_ >> 54) != 0; | ||
} | ||
FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { | ||
FMT_ASSERT(has_foreground(), "no foreground specified for this style"); | ||
return foreground_color; | ||
return style_ & 0x3FFFFFF; | ||
} | ||
FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { | ||
FMT_ASSERT(has_background(), "no background specified for this style"); | ||
return background_color; | ||
return (style_ >> 27) & 0x3FFFFFF; | ||
} | ||
FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { | ||
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); | ||
return ems; | ||
return static_cast<emphasis>(style_ >> 54); | ||
} | ||
|
||
private: | ||
FMT_CONSTEXPR text_style(bool is_foreground, | ||
detail::color_type text_color) noexcept | ||
: set_foreground_color(), set_background_color(), ems() { | ||
if (is_foreground) { | ||
foreground_color = text_color; | ||
set_foreground_color = true; | ||
} else { | ||
background_color = text_color; | ||
set_background_color = true; | ||
} | ||
} | ||
FMT_CONSTEXPR text_style(uint64_t style) noexcept : style_(style) {} | ||
|
||
friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept | ||
-> text_style; | ||
|
||
friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept | ||
-> text_style; | ||
|
||
detail::color_type foreground_color; | ||
detail::color_type background_color; | ||
bool set_foreground_color; | ||
bool set_background_color; | ||
emphasis ems; | ||
uint64_t style_{}; | ||
}; | ||
|
||
/// Creates a text style from the foreground (text) color. | ||
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept | ||
-> text_style { | ||
return text_style(true, foreground); | ||
return foreground.value_; | ||
} | ||
|
||
/// Creates a text style from the background color. | ||
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept | ||
-> text_style { | ||
return text_style(false, background); | ||
return static_cast<uint64_t>(background.value_) << 27; | ||
} | ||
|
||
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept | ||
|
@@ -334,9 +368,9 @@ template <typename Char> struct ansi_color_escape { | |
const char* esc) noexcept { | ||
// If we have a terminal color, we need to output another escape code | ||
// sequence. | ||
if (!text_color.is_rgb) { | ||
if (text_color.is_terminal_color()) { | ||
bool is_background = esc == string_view("\x1b[48;2;"); | ||
uint32_t value = text_color.value.term_color; | ||
uint32_t value = text_color.value(); | ||
// Background ASCII codes are the same as the foreground ones but with | ||
// 10 more. | ||
if (is_background) value += 10u; | ||
|
@@ -360,7 +394,7 @@ template <typename Char> struct ansi_color_escape { | |
for (int i = 0; i < 7; i++) { | ||
buffer[i] = static_cast<Char>(esc[i]); | ||
} | ||
rgb color(text_color.value.rgb_color); | ||
rgb color(text_color.value()); | ||
to_esc(color.r, buffer + 7, ';'); | ||
to_esc(color.g, buffer + 11, ';'); | ||
to_esc(color.b, buffer + 15, 'm'); | ||
|
@@ -441,32 +475,26 @@ template <typename T> struct styled_arg : view { | |
}; | ||
|
||
template <typename Char> | ||
void vformat_to(buffer<Char>& buf, const text_style& ts, | ||
basic_string_view<Char> fmt, | ||
void vformat_to(buffer<Char>& buf, text_style ts, basic_string_view<Char> fmt, | ||
basic_format_args<buffered_context<Char>> args) { | ||
bool has_style = false; | ||
if (ts.has_emphasis()) { | ||
has_style = true; | ||
auto emphasis = make_emphasis<Char>(ts.get_emphasis()); | ||
buf.append(emphasis.begin(), emphasis.end()); | ||
} | ||
if (ts.has_foreground()) { | ||
has_style = true; | ||
auto foreground = make_foreground_color<Char>(ts.get_foreground()); | ||
buf.append(foreground.begin(), foreground.end()); | ||
} | ||
if (ts.has_background()) { | ||
has_style = true; | ||
auto background = make_background_color<Char>(ts.get_background()); | ||
buf.append(background.begin(), background.end()); | ||
} | ||
vformat_to(buf, fmt, args); | ||
if (has_style) reset_color<Char>(buf); | ||
if (ts != text_style{}) reset_color<Char>(buf); | ||
} | ||
} // namespace detail | ||
|
||
inline void vprint(FILE* f, const text_style& ts, string_view fmt, | ||
format_args args) { | ||
inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) { | ||
auto buf = memory_buffer(); | ||
detail::vformat_to(buf, ts, fmt, args); | ||
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, | |
* "Elapsed time: {0:.2f} seconds", 1.23); | ||
*/ | ||
template <typename... T> | ||
void print(FILE* f, const text_style& ts, format_string<T...> fmt, | ||
T&&... args) { | ||
void print(FILE* f, text_style ts, format_string<T...> fmt, T&&... args) { | ||
vprint(f, ts, fmt.str, vargs<T...>{{args...}}); | ||
} | ||
|
||
|
@@ -497,11 +524,11 @@ void print(FILE* f, const text_style& ts, format_string<T...> fmt, | |
* "Elapsed time: {0:.2f} seconds", 1.23); | ||
*/ | ||
template <typename... T> | ||
void print(const text_style& ts, format_string<T...> fmt, T&&... args) { | ||
void print(text_style ts, format_string<T...> fmt, T&&... args) { | ||
return print(stdout, ts, fmt, std::forward<T>(args)...); | ||
} | ||
|
||
inline auto vformat(const text_style& ts, string_view fmt, format_args args) | ||
inline auto vformat(text_style ts, string_view fmt, format_args args) | ||
-> std::string { | ||
auto buf = memory_buffer(); | ||
detail::vformat_to(buf, ts, fmt, args); | ||
|
@@ -521,16 +548,16 @@ inline auto vformat(const text_style& ts, string_view fmt, format_args args) | |
* ``` | ||
*/ | ||
template <typename... T> | ||
inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args) | ||
inline auto format(text_style ts, format_string<T...> fmt, T&&... args) | ||
-> std::string { | ||
return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}}); | ||
} | ||
|
||
/// Formats a string with the given text_style and writes the output to `out`. | ||
template <typename OutputIt, | ||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)> | ||
auto vformat_to(OutputIt out, const text_style& ts, string_view fmt, | ||
format_args args) -> OutputIt { | ||
auto vformat_to(OutputIt out, text_style ts, string_view fmt, format_args args) | ||
-> OutputIt { | ||
auto&& buf = detail::get_buffer<char>(out); | ||
detail::vformat_to(buf, ts, fmt, args); | ||
return detail::get_iterator(buf, out); | ||
|
@@ -548,8 +575,8 @@ auto vformat_to(OutputIt out, const text_style& ts, string_view fmt, | |
*/ | ||
template <typename OutputIt, typename... T, | ||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)> | ||
inline auto format_to(OutputIt out, const text_style& ts, | ||
format_string<T...> fmt, T&&... args) -> OutputIt { | ||
inline auto format_to(OutputIt out, text_style ts, format_string<T...> fmt, | ||
T&&... args) -> OutputIt { | ||
return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}}); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clever =)