Skip to content

Commit fe96c5e

Browse files
authored
Merge pull request #589 from OpenVicProject/add/colour-fmt-parse
Add fmt specifier parsing to basic_colour_t
2 parents 0661bae + 515af67 commit fe96c5e

File tree

2 files changed

+328
-9
lines changed

2 files changed

+328
-9
lines changed

src/openvic-simulation/types/Colour.hpp

Lines changed: 248 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
#include <type_traits>
2020
#include <utility>
2121

22-
#include <fmt/core.h>
22+
#include <fmt/base.h>
23+
#include <fmt/format.h>
24+
#include <fmt/ranges.h>
2325

2426
#include <range/v3/algorithm/rotate.hpp>
2527

@@ -679,17 +681,254 @@ namespace OpenVic {
679681
}
680682
}
681683

682-
template<typename T>
683-
struct fmt::formatter<T, std::enable_if_t<OpenVic::utility::specialization_of<T, OpenVic::basic_colour_t>, char>>
684-
: formatter<string_view> {
685-
auto format(T const& c, format_context& ctx) const {
686-
typename T::stack_string result = c.to_hex_array();
687-
if (OV_unlikely(result.empty())) {
688-
return formatter<string_view>::format(string_view {}, ctx);
684+
template<OpenVic::utility::specialization_of<OpenVic::basic_colour_t> T>
685+
class fmt::detail::is_tuple_like_<T> {
686+
public:
687+
static constexpr const bool value = false;
688+
};
689+
690+
template<OpenVic::utility::specialization_of<OpenVic::basic_colour_t> T>
691+
struct fmt::formatter<T> {
692+
constexpr void set_brackets(basic_string_view<char> open, basic_string_view<char> close) {
693+
_opening_bracket = open;
694+
_closing_bracket = close;
695+
}
696+
697+
constexpr void set_separator(basic_string_view<char> sep) {
698+
_separator = sep;
699+
}
700+
701+
constexpr format_parse_context::iterator parse(format_parse_context& ctx) {
702+
format_parse_context::iterator it = ctx.begin(), end = ctx.end();
703+
if (it == end || *it == '}') {
704+
return it;
705+
}
706+
707+
it = detail::parse_align(it, end, _specs);
708+
if (it == end) {
709+
return it;
710+
}
711+
712+
if (*it == '#') {
713+
_specs.set_alt();
714+
++it;
715+
if (it == end) {
716+
return it;
717+
}
718+
}
719+
720+
char c = *it;
721+
if ((c >= '0' && c <= '9') || c == '{') {
722+
it = detail::parse_width(it, end, _specs, _specs.width_ref, ctx);
723+
if (it == end) {
724+
return it;
725+
}
726+
}
727+
728+
switch (*it) {
729+
case 'X': _specs.set_upper(); [[fallthrough]];
730+
case 'x':
731+
_specs.set_type(presentation_type::hex);
732+
++it;
733+
break;
734+
}
735+
if (it == end) {
736+
return it;
737+
}
738+
739+
switch (*it) {
740+
case 'n':
741+
set_brackets({}, {});
742+
_value_format_type = value_format_type::value;
743+
++it;
744+
break;
745+
}
746+
if (it == end) {
747+
return it;
748+
}
749+
750+
switch (*it) {
751+
case 'v':
752+
_value_format_type = value_format_type::value;
753+
++it;
754+
break;
755+
case 'i':
756+
_value_format_type = value_format_type::integer;
757+
++it;
758+
break;
759+
case 'f':
760+
_value_format_type = value_format_type::floating;
761+
++it;
762+
break;
763+
}
764+
765+
if (it != end) {
766+
switch (*it) {
767+
case 't':
768+
_alpha_handle_type = alpha_handle_type::follow_type;
769+
++it;
770+
break;
771+
case 's':
772+
_alpha_handle_type = alpha_handle_type::no_alpha;
773+
++it;
774+
break;
775+
case 'a':
776+
_alpha_handle_type = alpha_handle_type::argb;
777+
++it;
778+
break;
779+
case 'r':
780+
_alpha_handle_type = alpha_handle_type::rgba;
781+
++it;
782+
break;
783+
}
784+
}
785+
786+
if (it != end && (*it == ':' || _value_format_type != value_format_type::none)) {
787+
if (_specs.align() != align::none) {
788+
report_error("invalid format specifier");
789+
}
790+
791+
if (_specs.alt()) {
792+
report_error("invalid format specifier");
793+
}
794+
795+
if (_specs.width != 0 || _specs.dynamic_width() != arg_id_kind::none) {
796+
report_error("invalid format specifier");
797+
}
798+
799+
if (_specs.upper()) {
800+
report_error("invalid format specifier");
801+
}
802+
803+
if (_specs.type() == presentation_type::hex) {
804+
report_error("invalid format specifier");
805+
}
806+
807+
if (*it == ':') {
808+
++it;
809+
}
810+
ctx.advance_to(it);
811+
812+
switch (_value_format_type) {
813+
case value_format_type::none: _value_format_type = value_format_type::value; [[fallthrough]];
814+
case value_format_type::value: return _colour_value.parse(ctx);
815+
case value_format_type::integer: return _integer.parse(ctx);
816+
case value_format_type::floating: return _floating.parse(ctx);
817+
}
689818
}
690819

691-
return formatter<string_view>::format(string_view { result.data(), result.size() }, ctx);
820+
if (_value_format_type != value_format_type::none) {
821+
report_error("invalid format specifier");
822+
}
823+
824+
return it;
692825
}
826+
827+
format_context::iterator format(T const& colour, format_context& ctx) const {
828+
format_specs specs { _specs };
829+
if (_specs.dynamic()) {
830+
detail::handle_dynamic_spec(_specs.dynamic_width(), specs.width, _specs.width_ref, ctx);
831+
}
832+
833+
format_context::iterator out = ctx.out();
834+
835+
if (_value_format_type == value_format_type::none) {
836+
typename T::stack_string result = [&]() -> typename T::stack_string {
837+
switch (_alpha_handle_type) {
838+
default:
839+
case alpha_handle_type::follow_type: return colour.to_hex_array();
840+
case alpha_handle_type::no_alpha: return colour.to_hex_array(false);
841+
case alpha_handle_type::argb: return colour.to_argb_hex_array();
842+
case alpha_handle_type::rgba: return colour.to_hex_array(true);
843+
}
844+
}();
845+
846+
if (result.empty()) {
847+
return out;
848+
}
849+
850+
if (_specs.type() == presentation_type::hex) {
851+
bool upper = _specs.upper();
852+
if (_specs.alt()) {
853+
if (upper) {
854+
out = detail::write(out, "0X");
855+
} else {
856+
out = detail::write(out, "0x");
857+
}
858+
}
859+
860+
if (!upper) {
861+
std::array<char, T::stack_string::array_length> lower;
862+
size_t i = 0;
863+
for (char& c : lower) {
864+
c = std::tolower(result[i]);
865+
if (c == '\0') {
866+
break;
867+
}
868+
++i;
869+
}
870+
return detail::write(out, string_view { lower.data(), i }, specs, ctx.locale());
871+
}
872+
} else if (_specs.alt()) {
873+
*out++ = '#';
874+
}
875+
876+
return detail::write(out, string_view { result.data(), result.size() }, specs, ctx.locale());
877+
}
878+
879+
auto write_value = [&](T::value_type const& comp) {
880+
ctx.advance_to(out);
881+
switch (_value_format_type) {
882+
case value_format_type::value: _colour_value.format(comp, ctx); break;
883+
case value_format_type::integer: _integer.format(comp, ctx); break;
884+
case value_format_type::floating: _floating.format(double(comp) / T::max_value, ctx); break;
885+
}
886+
};
887+
888+
out = detail::copy<char>(_opening_bracket, out);
889+
size_t size = colour.size();
890+
if (size == 4) {
891+
switch (_alpha_handle_type) {
892+
case alpha_handle_type::argb:
893+
write_value(colour[--size]);
894+
out = detail::copy<char>(_separator, out);
895+
break;
896+
case alpha_handle_type::no_alpha: --size; break;
897+
default: break;
898+
}
899+
} else if (_alpha_handle_type == alpha_handle_type::argb) {
900+
write_value(T::max_value);
901+
out = detail::copy<char>(_separator, out);
902+
}
903+
904+
size_t i = 0;
905+
for (; i < size; ++i) {
906+
if (i > 0) {
907+
out = detail::copy<char>(_separator, out);
908+
}
909+
write_value(colour[i]);
910+
}
911+
912+
if (i == 3 && _alpha_handle_type == alpha_handle_type::rgba) {
913+
out = detail::copy<char>(_separator, out);
914+
write_value(T::max_value);
915+
}
916+
917+
return detail::copy<char>(_closing_bracket, out);
918+
}
919+
920+
private:
921+
fmt::detail::dynamic_format_specs<char> _specs;
922+
fmt::formatter<typename T::value_type> _colour_value;
923+
fmt::formatter<unsigned long long> _integer;
924+
fmt::formatter<double> _floating;
925+
926+
enum value_format_type { none, value, integer, floating } _value_format_type = value_format_type::none;
927+
enum alpha_handle_type { follow_type, no_alpha, argb, rgba } _alpha_handle_type = alpha_handle_type::follow_type;
928+
929+
basic_string_view<char> _separator = detail::string_literal<char, ',', ' '> {};
930+
basic_string_view<char> _opening_bracket = detail::string_literal<char, '('> {};
931+
basic_string_view<char> _closing_bracket = detail::string_literal<char, ')'> {};
693932
};
694933

695934
namespace std {

tests/src/types/Colour.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,44 @@ TEMPLATE_LIST_TEST_CASE("basic_colour_t Other methods", "[basic_colour_t][basic_
8888
CONSTEXPR_CHECK(blue.with_blue(0) == TestType::from_floats(0.2, 0.2, 0));
8989
}
9090

91+
TEMPLATE_LIST_TEST_CASE("basic_colour_t Formatting", "[basic_colour_t][basic_colour_t-formatting]", ColourTypes) {
92+
static constexpr TestType blue = TestType::from_floats(0.2, 0.2, 1);
93+
94+
CHECK(fmt::format("{:s}", blue) == "3333FF"sv);
95+
CHECK(fmt::format("{:a}", blue) == "FF3333FF"sv);
96+
CHECK(fmt::format("{:r}", blue) == "3333FFFF"sv);
97+
98+
CHECK(fmt::format("{:Q<16r}", blue) == "3333FFFFQQQQQQQQ"sv);
99+
CHECK(fmt::format("{:Q^16r}", blue) == "QQQQ3333FFFFQQQQ"sv);
100+
CHECK(fmt::format("{:Q>16r}", blue) == "QQQQQQQQ3333FFFF"sv);
101+
102+
int width = 16;
103+
CHECK(fmt::format("{:Q>{}r}", blue, width) == "QQQQQQQQ3333FFFF"sv);
104+
width -= 2;
105+
CHECK(fmt::format("{:Q>{}r}", blue, width) == "QQQQQQ3333FFFF"sv);
106+
width += 6;
107+
CHECK(fmt::format("{:Q>{}r}", blue, width) == "QQQQQQQQQQQQ3333FFFF"sv);
108+
CHECK(fmt::format("{1:Q>{0}r}", width, blue) == "QQQQQQQQQQQQ3333FFFF"sv);
109+
110+
CHECK(fmt::format("{:vs}", blue) == "(51, 51, 255)"sv);
111+
CHECK(fmt::format("{:va}", blue) == "(255, 51, 51, 255)"sv);
112+
CHECK(fmt::format("{:vr}", blue) == "(51, 51, 255, 255)"sv);
113+
114+
CHECK(fmt::format("{:nvs}", blue) == "51, 51, 255"sv);
115+
CHECK(fmt::format("{:nva}", blue) == "255, 51, 51, 255"sv);
116+
CHECK(fmt::format("{:nvr}", blue) == "51, 51, 255, 255"sv);
117+
118+
CHECK(fmt::format("{:ns}", blue) == "51, 51, 255"sv);
119+
CHECK(fmt::format("{:na}", blue) == "255, 51, 51, 255"sv);
120+
CHECK(fmt::format("{:nr}", blue) == "51, 51, 255, 255"sv);
121+
122+
CHECK(fmt::format("{:#s}", blue) == "#3333FF"sv);
123+
CHECK(fmt::format("{:xa}", blue) == "ff3333ff"sv);
124+
CHECK(fmt::format("{:Xr}", blue) == "3333FFFF"sv);
125+
CHECK(fmt::format("{:#xs}", blue) == "0x3333ff"sv);
126+
CHECK(fmt::format("{:#Xa}", blue) == "0XFF3333FF"sv);
127+
}
128+
91129
TEST_CASE("colour_rgb_t Constructor methods", "[basic_colour_t][colour_rgb_t][colour_rgb_t-constructor]") {
92130
static constexpr colour_rgb_t blue_rgb = colour_rgb_t(
93131
63.0 / 255 * 255, //
@@ -170,6 +208,27 @@ TEST_CASE("colour_rgb_t Parse methods", "[basic_colour_t][colour_rgb_t][colour_r
170208
CONSTEXPR_CHECK(colour_rgb_t::from_string(hex_yellow_rgb) == yellow);
171209
}
172210

211+
TEST_CASE("colour_rgb_t Formatting", "[basic_colour_t][colour_rgb_t][colour_rgb_t-formatting]") {
212+
static constexpr colour_rgb_t blue = colour_rgb_t::from_floats(0.2, 0.2, 1);
213+
214+
CHECK(fmt::format("{}", blue) == "3333FF"sv);
215+
CHECK(fmt::format("{:n}", blue) == "51, 51, 255"sv);
216+
CHECK(fmt::format("{::}", blue) == "(51, 51, 255)"sv);
217+
CHECK(fmt::format("{:i}", blue) == "(51, 51, 255)"sv);
218+
CHECK(fmt::format("{:f}", blue) == "(0.2, 0.2, 1)"sv);
219+
220+
CHECK(fmt::format("{:t}", blue) == "3333FF"sv);
221+
CHECK(fmt::format("{:vt}", blue) == "(51, 51, 255)"sv);
222+
CHECK(fmt::format("{:nvt}", blue) == "51, 51, 255"sv);
223+
CHECK(fmt::format("{:nt}", blue) == "51, 51, 255"sv);
224+
225+
CHECK(fmt::format("{:#}", blue) == "#3333FF"sv);
226+
CHECK(fmt::format("{:x}", blue) == "3333ff"sv);
227+
CHECK(fmt::format("{:X}", blue) == "3333FF"sv);
228+
CHECK(fmt::format("{:#x}", blue) == "0x3333ff"sv);
229+
CHECK(fmt::format("{:#X}", blue) == "0X3333FF"sv);
230+
}
231+
173232
TEST_CASE("colour_argb_t Operators", "[basic_colour_t][colour_argb_t][colour_argb_t-operators]") {
174233
static constexpr colour_argb_t blue = colour_argb_t::from_floats(0.2, 0.2, 1);
175234
static constexpr colour_argb_t transparent = colour_argb_t::from_floats(0.2, 0.2, 1, 0);
@@ -196,3 +255,24 @@ TEST_CASE("colour_argb_t Other methods", "[basic_colour_t][colour_argb_t][colour
196255

197256
CONSTEXPR_CHECK(blue.with_alpha(1) == colour_argb_t::from_floats(0.2, 0.2, 1, 1.0 / 255));
198257
}
258+
259+
TEST_CASE("colour_argb_t Formatting", "[basic_colour_t][colour_argb_t][colour_argb_t-formatting]") {
260+
static constexpr colour_argb_t blue = colour_argb_t::from_floats(0.2, 0.2, 1);
261+
262+
CHECK(fmt::format("{}", blue) == "3333FFFF"sv);
263+
CHECK(fmt::format("{:n}", blue) == "51, 51, 255, 255"sv);
264+
CHECK(fmt::format("{::}", blue) == "(51, 51, 255, 255)"sv);
265+
CHECK(fmt::format("{:i}", blue) == "(51, 51, 255, 255)"sv);
266+
CHECK(fmt::format("{:f}", blue) == "(0.2, 0.2, 1, 1)"sv);
267+
268+
CHECK(fmt::format("{:t}", blue) == "3333FFFF"sv);
269+
CHECK(fmt::format("{:vt}", blue) == "(51, 51, 255, 255)"sv);
270+
CHECK(fmt::format("{:nvt}", blue) == "51, 51, 255, 255"sv);
271+
CHECK(fmt::format("{:nt}", blue) == "51, 51, 255, 255"sv);
272+
273+
CHECK(fmt::format("{:#}", blue) == "#3333FFFF"sv);
274+
CHECK(fmt::format("{:x}", blue) == "3333ffff"sv);
275+
CHECK(fmt::format("{:X}", blue) == "3333FFFF"sv);
276+
CHECK(fmt::format("{:#x}", blue) == "0x3333ffff"sv);
277+
CHECK(fmt::format("{:#X}", blue) == "0X3333FFFF"sv);
278+
}

0 commit comments

Comments
 (0)