You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

css_value.cxx 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. /*-
  2. * Copyright 2021 Vsevolod Stakhov
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include "css_value.hxx"
  17. #include "css_colors_list.hxx"
  18. #include "frozen/unordered_map.h"
  19. #include "frozen/string.h"
  20. #include "contrib/robin-hood/robin_hood.h"
  21. #include "fmt/core.h"
  22. #define DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
  23. #include "doctest/doctest.h"
  24. /* Helper for unit test stringification */
  25. namespace doctest {
  26. template<> struct StringMaker<rspamd::css::css_color> {
  27. static String convert(const rspamd::css::css_color& value) {
  28. return fmt::format("r={};g={};b={};alpha={}",
  29. value.r, value.g, value.b, value.alpha).c_str();
  30. }
  31. };
  32. }
  33. namespace rspamd::css {
  34. auto css_value::maybe_color_from_string(const std::string_view &input)
  35. -> std::optional<css_value> {
  36. auto found_it = css_colors_map.find(input);
  37. if (found_it != css_colors_map.end()) {
  38. return css_value{found_it->second};
  39. }
  40. return std::nullopt;
  41. }
  42. constexpr static inline auto hexpair_decode(char c1, char c2) -> std::uint8_t {
  43. std::uint8_t ret = 0;
  44. if (c1 >= '0' && c1 <= '9') ret = c1 - '0';
  45. else if (c1 >= 'A' && c1 <= 'F') ret = c1 - 'A' + 10;
  46. else if (c1 >= 'a' && c1 <= 'f') ret = c1 - 'a' + 10;
  47. ret *= 16;
  48. if (c2 >= '0' && c2 <= '9') ret += c2 - '0';
  49. else if (c2 >= 'A' && c2 <= 'F') ret += c2 - 'A' + 10;
  50. else if (c2 >= 'a' && c2 <= 'f') ret += c2 - 'a' + 10;
  51. return ret;
  52. }
  53. auto css_value::maybe_color_from_hex(const std::string_view &input)
  54. -> std::optional<css_value> {
  55. if (input.length() == 6) {
  56. /* Plain RGB */
  57. css_color col(hexpair_decode(input[0], input[1]),
  58. hexpair_decode(input[2], input[3]),
  59. hexpair_decode(input[4], input[5]));
  60. return css_value(col);
  61. }
  62. else if (input.length() == 3) {
  63. /* Rgb as 3 hex digests */
  64. css_color col(hexpair_decode(input[0], input[0]),
  65. hexpair_decode(input[1], input[1]),
  66. hexpair_decode(input[2], input[2]));
  67. return css_value(col);
  68. }
  69. else if (input.length() == 8) {
  70. /* RGBA */
  71. css_color col(hexpair_decode(input[0], input[1]),
  72. hexpair_decode(input[2], input[3]),
  73. hexpair_decode(input[4], input[5]),
  74. hexpair_decode(input[6], input[7]));
  75. return css_value(col);
  76. }
  77. return std::nullopt;
  78. }
  79. constexpr static inline auto rgb_color_component_convert(const css_parser_token &tok)
  80. -> std::uint8_t {
  81. std::uint8_t ret = 0;
  82. if (tok.type == css_parser_token::token_type::number_token) {
  83. auto dbl = std::get<double>(tok.value);
  84. if (tok.flags & css_parser_token::number_percent) {
  85. if (dbl > 100) {
  86. dbl = 100;
  87. }
  88. else if (dbl < 0) {
  89. dbl = 0;
  90. }
  91. ret = (std::uint8_t) (dbl / 100.0 * 255.0);
  92. }
  93. else {
  94. if (dbl > 1) {
  95. dbl = 1;
  96. }
  97. else if (dbl < 0) {
  98. dbl = 0;
  99. }
  100. ret = (std::uint8_t) (dbl * 255.0);
  101. }
  102. }
  103. return ret;
  104. }
  105. constexpr static inline auto alpha_component_convert(const css_parser_token &tok)
  106. -> std::uint8_t {
  107. double ret = 1.0;
  108. if (tok.type == css_parser_token::token_type::number_token) {
  109. auto dbl = std::get<double>(tok.value);
  110. if (tok.flags & css_parser_token::number_percent) {
  111. if (dbl > 100) {
  112. dbl = 100;
  113. }
  114. else if (dbl < 0) {
  115. dbl = 0;
  116. }
  117. ret = (dbl / 100.0);
  118. }
  119. else {
  120. if (dbl > 255) {
  121. dbl = 255;
  122. }
  123. else if (dbl < 0) {
  124. dbl = 0;
  125. }
  126. ret = dbl / 255.0;
  127. }
  128. }
  129. return (std::uint8_t) (ret * 255.0);
  130. }
  131. constexpr static inline auto h_component_convert(const css_parser_token &tok)
  132. -> double {
  133. double ret = 0.0;
  134. if (tok.type == css_parser_token::token_type::number_token) {
  135. auto dbl = std::get<double>(tok.value);
  136. if (tok.flags & css_parser_token::number_percent) {
  137. if (dbl > 100) {
  138. dbl = 100;
  139. }
  140. else if (dbl < 0) {
  141. dbl = 0;
  142. }
  143. ret = (dbl / 100.0);
  144. }
  145. else {
  146. dbl = ((((int) dbl % 360) + 360) % 360); /* Deal with rotations */
  147. ret = dbl / 360.0; /* Normalize to 0..1 */
  148. }
  149. }
  150. return ret;
  151. }
  152. constexpr static inline auto sl_component_convert(const css_parser_token &tok)
  153. -> double {
  154. double ret = 0.0;
  155. if (tok.type == css_parser_token::token_type::number_token) {
  156. ret = tok.get_normal_number_or_default(ret);
  157. }
  158. return ret;
  159. }
  160. static inline auto hsl_to_rgb(double h, double s, double l)
  161. -> css_color {
  162. css_color ret;
  163. constexpr auto hue2rgb = [](auto p, auto q, auto t) -> auto {
  164. if (t < 0.0) {
  165. t += 1.0;
  166. }
  167. if (t > 1.0) {
  168. t -= 1.0;
  169. }
  170. if (t * 6. < 1.0) {
  171. return p + (q - p) * 6.0 * t;
  172. }
  173. if (t * 2. < 1) {
  174. return q;
  175. }
  176. if (t * 3. < 2.) {
  177. return p + (q - p) * (2.0 / 3.0 - t) * 6.0;
  178. }
  179. return p;
  180. };
  181. if (s == 0) {
  182. /* Achromatic */
  183. ret.r = l;
  184. ret.g = l;
  185. ret.b = l;
  186. }
  187. else {
  188. auto q = l <= 0.5 ? l * (1.0 + s) : l + s - l * s;
  189. auto p = 2.0 * l - q;
  190. ret.r = (std::uint8_t) (hue2rgb(p, q, h + 1.0 / 3.0) * 255);
  191. ret.g = (std::uint8_t) (hue2rgb(p, q, h) * 255);
  192. ret.b = (std::uint8_t) (hue2rgb(p, q, h - 1.0 / 3.0) * 255);
  193. }
  194. ret.alpha = 255;
  195. return ret;
  196. }
  197. auto css_value::maybe_color_from_function(const css_consumed_block::css_function_block &func)
  198. -> std::optional<css_value> {
  199. if (func.as_string() == "rgb" && func.args.size() == 3) {
  200. css_color col{rgb_color_component_convert(func.args[0]->get_token_or_empty()),
  201. rgb_color_component_convert(func.args[1]->get_token_or_empty()),
  202. rgb_color_component_convert(func.args[2]->get_token_or_empty())};
  203. return css_value(col);
  204. }
  205. else if (func.as_string() == "rgba" && func.args.size() == 4) {
  206. css_color col{rgb_color_component_convert(func.args[0]->get_token_or_empty()),
  207. rgb_color_component_convert(func.args[1]->get_token_or_empty()),
  208. rgb_color_component_convert(func.args[2]->get_token_or_empty()),
  209. alpha_component_convert(func.args[3]->get_token_or_empty())};
  210. return css_value(col);
  211. }
  212. else if (func.as_string() == "hsl" && func.args.size() == 3) {
  213. auto h = h_component_convert(func.args[0]->get_token_or_empty());
  214. auto s = sl_component_convert(func.args[1]->get_token_or_empty());
  215. auto l = sl_component_convert(func.args[2]->get_token_or_empty());
  216. auto col = hsl_to_rgb(h, s, l);
  217. return css_value(col);
  218. }
  219. else if (func.as_string() == "hsla" && func.args.size() == 4) {
  220. auto h = h_component_convert(func.args[0]->get_token_or_empty());
  221. auto s = sl_component_convert(func.args[1]->get_token_or_empty());
  222. auto l = sl_component_convert(func.args[2]->get_token_or_empty());
  223. auto col = hsl_to_rgb(h, s, l);
  224. col.alpha = alpha_component_convert(func.args[3]->get_token_or_empty());
  225. return css_value(col);
  226. }
  227. return std::nullopt;
  228. }
  229. auto css_value::maybe_dimension_from_number(const css_parser_token &tok)
  230. -> std::optional<css_value> {
  231. if (std::holds_alternative<double>(tok.value)) {
  232. auto dbl = std::get<double>(tok.value);
  233. css_dimension dim;
  234. dim.dim = dbl;
  235. if (tok.flags & css_parser_token::number_percent) {
  236. dim.is_percent = true;
  237. }
  238. else {
  239. dim.is_percent = false;
  240. }
  241. return css_value{dim};
  242. }
  243. return std::nullopt;
  244. }
  245. constexpr const auto display_names_map = frozen::make_unordered_map<frozen::string, css_display_value>({
  246. {"hidden", css_display_value::DISPLAY_HIDDEN},
  247. {"none", css_display_value::DISPLAY_HIDDEN},
  248. {"inline", css_display_value::DISPLAY_NORMAL},
  249. {"block", css_display_value::DISPLAY_NORMAL},
  250. {"content", css_display_value::DISPLAY_NORMAL},
  251. {"flex", css_display_value::DISPLAY_NORMAL},
  252. {"grid", css_display_value::DISPLAY_NORMAL},
  253. {"inline-block", css_display_value::DISPLAY_NORMAL},
  254. {"inline-flex", css_display_value::DISPLAY_NORMAL},
  255. {"inline-grid", css_display_value::DISPLAY_NORMAL},
  256. {"inline-table", css_display_value::DISPLAY_NORMAL},
  257. {"list-item", css_display_value::DISPLAY_NORMAL},
  258. {"run-in", css_display_value::DISPLAY_NORMAL},
  259. {"table", css_display_value::DISPLAY_NORMAL},
  260. {"table-caption", css_display_value::DISPLAY_NORMAL},
  261. {"table-column-group", css_display_value::DISPLAY_NORMAL},
  262. {"table-header-group", css_display_value::DISPLAY_NORMAL},
  263. {"table-footer-group", css_display_value::DISPLAY_NORMAL},
  264. {"table-row-group", css_display_value::DISPLAY_NORMAL},
  265. {"table-cell", css_display_value::DISPLAY_NORMAL},
  266. {"table-column", css_display_value::DISPLAY_NORMAL},
  267. {"table-row", css_display_value::DISPLAY_NORMAL},
  268. {"initial", css_display_value::DISPLAY_NORMAL},
  269. });
  270. auto css_value::maybe_display_from_string(const std::string_view &input)
  271. -> std::optional<css_value> {
  272. auto f = display_names_map.find(input);
  273. if (f != display_names_map.end()) {
  274. return css_value{f->second};
  275. }
  276. return std::nullopt;
  277. }
  278. auto css_value::debug_str() const -> std::string {
  279. std::string ret;
  280. std::visit([&](const auto &arg) {
  281. using T = std::decay_t<decltype(arg)>;
  282. if constexpr (std::is_same_v<T, css_color>) {
  283. ret += "color: r=" + std::to_string(arg.r) +
  284. "; g=" + std::to_string(arg.g) +
  285. "; b=" + std::to_string(arg.b) +
  286. "; a=" + std::to_string(arg.alpha);
  287. }
  288. else if constexpr (std::is_same_v<T, double>) {
  289. ret += "size: " + std::to_string(arg);
  290. }
  291. else if constexpr (std::is_same_v<T, css_dimension>) {
  292. ret += "dimension: " + std::to_string(arg.dim);
  293. if (arg.is_percent) {
  294. ret += "%";
  295. }
  296. }
  297. else if constexpr (std::is_same_v<T, css_display_value>) {
  298. ret += "display: ";
  299. ret += (arg == css_display_value::DISPLAY_HIDDEN ? "hidden" : "normal");
  300. }
  301. else if constexpr (std::is_integral_v<T>) {
  302. ret += "integral: " + std::to_string(static_cast<int>(arg));
  303. }
  304. else {
  305. ret += "nyi";
  306. }
  307. }, value);
  308. return ret;
  309. }
  310. TEST_SUITE("css values") {
  311. TEST_CASE("css hex colors") {
  312. const std::pair<const char*, css_color> hex_tests[] = {
  313. {"000", css_color(0, 0, 0)},
  314. {"000000", css_color(0, 0, 0)},
  315. {"f00", css_color(255, 0, 0)},
  316. {"FEDCBA", css_color(254, 220, 186)},
  317. {"234", css_color(34, 51, 68)},
  318. };
  319. for (const auto &p : hex_tests) {
  320. auto col_parsed = css_value::maybe_color_from_hex(p.first);
  321. //CHECK_UNARY(col_parsed);
  322. //CHECK_UNARY(col_parsed.value().to_color());
  323. auto final_col = col_parsed.value().to_color().value();
  324. CHECK(final_col == p.second);
  325. }
  326. }
  327. };
  328. }