CSSNumericType.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. /*
  2. * Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "CSSNumericType.h"
  7. #include <AK/HashMap.h>
  8. #include <LibWeb/CSS/Angle.h>
  9. #include <LibWeb/CSS/Frequency.h>
  10. #include <LibWeb/CSS/Length.h>
  11. #include <LibWeb/CSS/Percentage.h>
  12. #include <LibWeb/CSS/Resolution.h>
  13. #include <LibWeb/CSS/Time.h>
  14. namespace Web::CSS {
  15. Optional<CSSNumericType::BaseType> CSSNumericType::base_type_from_value_type(ValueType value_type)
  16. {
  17. switch (value_type) {
  18. case ValueType::Angle:
  19. return BaseType::Angle;
  20. case ValueType::Flex:
  21. return BaseType::Flex;
  22. case ValueType::Frequency:
  23. return BaseType::Frequency;
  24. case ValueType::Length:
  25. return BaseType::Length;
  26. case ValueType::Percentage:
  27. return BaseType::Percent;
  28. case ValueType::Resolution:
  29. return BaseType::Resolution;
  30. case ValueType::Time:
  31. return BaseType::Time;
  32. case ValueType::BackgroundPosition:
  33. case ValueType::BasicShape:
  34. case ValueType::Color:
  35. case ValueType::Counter:
  36. case ValueType::CustomIdent:
  37. case ValueType::EasingFunction:
  38. case ValueType::FilterValueList:
  39. case ValueType::Image:
  40. case ValueType::Integer:
  41. case ValueType::Number:
  42. case ValueType::OpenTypeTag:
  43. case ValueType::Paint:
  44. case ValueType::Position:
  45. case ValueType::Ratio:
  46. case ValueType::Rect:
  47. case ValueType::String:
  48. case ValueType::Url:
  49. return {};
  50. }
  51. VERIFY_NOT_REACHED();
  52. }
  53. // https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-create-a-type
  54. Optional<CSSNumericType> CSSNumericType::create_from_unit(StringView unit)
  55. {
  56. // To create a type from a string unit, follow the appropriate branch of the following:
  57. // unit is "number"
  58. if (unit == "number"sv) {
  59. // Return «[ ]» (empty map)
  60. return CSSNumericType {};
  61. }
  62. // unit is "percent"
  63. if (unit == "percent"sv) {
  64. // Return «[ "percent" → 1 ]»
  65. return CSSNumericType { BaseType::Percent, 1 };
  66. }
  67. // unit is a <length> unit
  68. if (Length::unit_from_name(unit).has_value()) {
  69. // Return «[ "length" → 1 ]»
  70. return CSSNumericType { BaseType::Length, 1 };
  71. }
  72. // unit is an <angle> unit
  73. if (Angle::unit_from_name(unit).has_value()) {
  74. // Return «[ "angle" → 1 ]»
  75. return CSSNumericType { BaseType::Angle, 1 };
  76. }
  77. // unit is a <time> unit
  78. if (Time::unit_from_name(unit).has_value()) {
  79. // Return «[ "time" → 1 ]»
  80. return CSSNumericType { BaseType::Time, 1 };
  81. }
  82. // unit is a <frequency> unit
  83. if (Frequency::unit_from_name(unit).has_value()) {
  84. // Return «[ "frequency" → 1 ]»
  85. return CSSNumericType { BaseType::Frequency, 1 };
  86. }
  87. // unit is a <resolution> unit
  88. if (Resolution::unit_from_name(unit).has_value()) {
  89. // Return «[ "resolution" → 1 ]»
  90. return CSSNumericType { BaseType::Resolution, 1 };
  91. }
  92. // unit is a <flex> unit
  93. // FIXME: We don't have <flex> as a type yet.
  94. // Return «[ "flex" → 1 ]»
  95. // anything else
  96. // Return failure.
  97. return {};
  98. // In all cases, the associated percent hint is null.
  99. }
  100. // https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-add-two-types
  101. Optional<CSSNumericType> CSSNumericType::added_to(CSSNumericType const& other) const
  102. {
  103. // To add two types type1 and type2, perform the following steps:
  104. // 1. Replace type1 with a fresh copy of type1, and type2 with a fresh copy of type2.
  105. // Let finalType be a new type with an initially empty ordered map and an initially null percent hint.
  106. CSSNumericType type1 = *this;
  107. CSSNumericType type2 = other;
  108. CSSNumericType final_type {};
  109. // 2. If both type1 and type2 have non-null percent hints with different values
  110. if (type1.percent_hint().has_value() && type2.percent_hint().has_value() && type1.percent_hint() != type2.percent_hint()) {
  111. // The types can’t be added. Return failure.
  112. return {};
  113. }
  114. // If type1 has a non-null percent hint hint and type2 doesn’t
  115. else if (type1.percent_hint().has_value() && !type2.percent_hint().has_value()) {
  116. // Apply the percent hint hint to type2.
  117. type2.apply_percent_hint(type1.percent_hint().value());
  118. }
  119. // Vice versa if type2 has a non-null percent hint and type1 doesn’t.
  120. else if (type2.percent_hint().has_value() && !type1.percent_hint().has_value()) {
  121. type1.apply_percent_hint(type2.percent_hint().value());
  122. }
  123. // Otherwise
  124. // Continue to the next step.
  125. // 3. If all the entries of type1 with non-zero values are contained in type2 with the same value, and vice-versa
  126. if (type2.contains_all_the_non_zero_entries_of_other_with_the_same_value(type1)
  127. && type1.contains_all_the_non_zero_entries_of_other_with_the_same_value(type2)) {
  128. // Copy all of type1’s entries to finalType, and then copy all of type2’s entries to finalType that
  129. // finalType doesn’t already contain. Set finalType’s percent hint to type1’s percent hint. Return finalType.
  130. final_type.copy_all_entries_from(type1, SkipIfAlreadyPresent::No);
  131. final_type.copy_all_entries_from(type2, SkipIfAlreadyPresent::Yes);
  132. final_type.set_percent_hint(type1.percent_hint());
  133. return final_type;
  134. }
  135. // If type1 and/or type2 contain "percent" with a non-zero value,
  136. // and type1 and/or type2 contain a key other than "percent" with a non-zero value
  137. else if ((type1.exponent(BaseType::Percent) != 0 || type2.exponent(BaseType::Percent) != 0)
  138. && (type1.contains_a_key_other_than_percent_with_a_non_zero_value() || type2.contains_a_key_other_than_percent_with_a_non_zero_value())) {
  139. // For each base type other than "percent" hint:
  140. for (auto hint_int = 0; hint_int < to_underlying(BaseType::__Count); ++hint_int) {
  141. auto hint = static_cast<BaseType>(hint_int);
  142. if (hint == BaseType::Percent)
  143. continue;
  144. // 1. Provisionally apply the percent hint hint to both type1 and type2.
  145. auto provisional_type1 = type1;
  146. provisional_type1.apply_percent_hint(hint);
  147. auto provisional_type2 = type2;
  148. provisional_type2.apply_percent_hint(hint);
  149. // 2. If, afterwards, all the entries of type1 with non-zero values are contained in type2
  150. // with the same value, and vice versa, then copy all of type1’s entries to finalType,
  151. // and then copy all of type2’s entries to finalType that finalType doesn’t already contain.
  152. // Set finalType’s percent hint to hint. Return finalType.
  153. if (provisional_type2.contains_all_the_non_zero_entries_of_other_with_the_same_value(provisional_type1)
  154. && provisional_type1.contains_all_the_non_zero_entries_of_other_with_the_same_value(provisional_type2)) {
  155. final_type.copy_all_entries_from(provisional_type1, SkipIfAlreadyPresent::No);
  156. final_type.copy_all_entries_from(provisional_type2, SkipIfAlreadyPresent::Yes);
  157. final_type.set_percent_hint(hint);
  158. return final_type;
  159. }
  160. // 3. Otherwise, revert type1 and type2 to their state at the start of this loop.
  161. // NOTE: We did the modifications to provisional_type1/2 so this is a no-op.
  162. }
  163. // If the loop finishes without returning finalType, then the types can’t be added. Return failure.
  164. return {};
  165. }
  166. // Otherwise
  167. // The types can’t be added. Return failure.
  168. return {};
  169. }
  170. // https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-multiply-two-types
  171. Optional<CSSNumericType> CSSNumericType::multiplied_by(CSSNumericType const& other) const
  172. {
  173. // To multiply two types type1 and type2, perform the following steps:
  174. // 1. Replace type1 with a fresh copy of type1, and type2 with a fresh copy of type2.
  175. // Let finalType be a new type with an initially empty ordered map and an initially null percent hint.
  176. CSSNumericType type1 = *this;
  177. CSSNumericType type2 = other;
  178. CSSNumericType final_type {};
  179. // 2. If both type1 and type2 have non-null percent hints with different values,
  180. // the types can’t be multiplied. Return failure.
  181. if (type1.percent_hint().has_value() && type2.percent_hint().has_value() && type1.percent_hint() != type2.percent_hint())
  182. return {};
  183. // 3. If type1 has a non-null percent hint hint and type2 doesn’t, apply the percent hint hint to type2.
  184. if (type1.percent_hint().has_value() && !type2.percent_hint().has_value()) {
  185. type2.apply_percent_hint(type1.percent_hint().value());
  186. }
  187. // Vice versa if type2 has a non-null percent hint and type1 doesn’t.
  188. else if (type2.percent_hint().has_value() && !type1.percent_hint().has_value()) {
  189. type1.apply_percent_hint(type2.percent_hint().value());
  190. }
  191. // 4. Copy all of type1’s entries to finalType, then for each baseType → power of type2:
  192. final_type.copy_all_entries_from(type1, SkipIfAlreadyPresent::No);
  193. for (auto i = 0; i < to_underlying(BaseType::__Count); ++i) {
  194. auto base_type = static_cast<BaseType>(i);
  195. if (!type2.exponent(base_type).has_value())
  196. continue;
  197. auto power = type2.exponent(base_type).value();
  198. // 1. If finalType[baseType] exists, increment its value by power.
  199. if (auto exponent = final_type.exponent(base_type); exponent.has_value()) {
  200. final_type.set_exponent(base_type, exponent.value() + power);
  201. }
  202. // 2. Otherwise, set finalType[baseType] to power.
  203. else {
  204. final_type.set_exponent(base_type, power);
  205. }
  206. }
  207. // Set finalType’s percent hint to type1’s percent hint.
  208. final_type.set_percent_hint(type1.percent_hint());
  209. // 5. Return finalType.
  210. return final_type;
  211. }
  212. // https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-invert-a-type
  213. CSSNumericType CSSNumericType::inverted() const
  214. {
  215. // To invert a type type, perform the following steps:
  216. // 1. Let result be a new type with an initially empty ordered map and an initially null percent hint
  217. CSSNumericType result;
  218. // 2. For each unit → exponent of type, set result[unit] to (-1 * exponent).
  219. for (auto i = 0; i < to_underlying(BaseType::__Count); ++i) {
  220. auto base_type = static_cast<BaseType>(i);
  221. if (!exponent(base_type).has_value())
  222. continue;
  223. auto power = exponent(base_type).value();
  224. result.set_exponent(base_type, -1 * power);
  225. }
  226. // 3. Return result.
  227. return result;
  228. }
  229. // https://drafts.css-houdini.org/css-typed-om-1/#apply-the-percent-hint
  230. void CSSNumericType::apply_percent_hint(BaseType hint)
  231. {
  232. // To apply the percent hint hint to a type, perform the following steps:
  233. // 1. If type doesn’t contain hint, set type[hint] to 0.
  234. if (!exponent(hint).has_value())
  235. set_exponent(hint, 0);
  236. // 2. If type contains "percent", add type["percent"] to type[hint], then set type["percent"] to 0.
  237. if (exponent(BaseType::Percent).has_value()) {
  238. set_exponent(hint, exponent(BaseType::Percent).value() + exponent(hint).value());
  239. set_exponent(BaseType::Percent, 0);
  240. }
  241. // 3. Set type’s percent hint to hint.
  242. set_percent_hint(hint);
  243. }
  244. bool CSSNumericType::contains_all_the_non_zero_entries_of_other_with_the_same_value(CSSNumericType const& other) const
  245. {
  246. for (auto i = 0; i < to_underlying(BaseType::__Count); ++i) {
  247. auto other_exponent = other.exponent(static_cast<BaseType>(i));
  248. if (other_exponent.has_value() && other_exponent != 0
  249. && this->exponent(static_cast<BaseType>(i)) != other_exponent) {
  250. return false;
  251. }
  252. }
  253. return true;
  254. }
  255. bool CSSNumericType::contains_a_key_other_than_percent_with_a_non_zero_value() const
  256. {
  257. for (auto i = 0; i < to_underlying(BaseType::__Count); ++i) {
  258. if (i == to_underlying(BaseType::Percent))
  259. continue;
  260. if (m_type_exponents[i].has_value() && m_type_exponents[i] != 0)
  261. return true;
  262. }
  263. return false;
  264. }
  265. void CSSNumericType::copy_all_entries_from(CSSNumericType const& other, SkipIfAlreadyPresent ignore_existing_values)
  266. {
  267. for (auto i = 0; i < to_underlying(BaseType::__Count); ++i) {
  268. auto base_type = static_cast<BaseType>(i);
  269. auto exponent = other.exponent(base_type);
  270. if (!exponent.has_value())
  271. continue;
  272. if (ignore_existing_values == SkipIfAlreadyPresent::Yes && this->exponent(base_type).has_value())
  273. continue;
  274. set_exponent(base_type, *exponent);
  275. }
  276. }
  277. // https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-match
  278. bool CSSNumericType::matches_dimension(BaseType type) const
  279. {
  280. // A type matches <length> if its only non-zero entry is «[ "length" → 1 ]» and its percent hint is null.
  281. // Similarly for <angle>, <time>, <frequency>, <resolution>, and <flex>.
  282. if (percent_hint().has_value())
  283. return false;
  284. for (auto i = 0; i < to_underlying(BaseType::__Count); ++i) {
  285. auto base_type = static_cast<BaseType>(i);
  286. auto type_exponent = exponent(base_type);
  287. if (base_type == type) {
  288. if (type_exponent != 1)
  289. return false;
  290. } else {
  291. if (type_exponent.has_value() && type_exponent != 0)
  292. return false;
  293. }
  294. }
  295. return true;
  296. }
  297. // https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-match
  298. bool CSSNumericType::matches_percentage() const
  299. {
  300. // A type matches <percentage> if its only non-zero entry is «[ "percent" → 1 ]».
  301. for (auto i = 0; i < to_underlying(BaseType::__Count); ++i) {
  302. auto base_type = static_cast<BaseType>(i);
  303. auto type_exponent = exponent(base_type);
  304. if (base_type == BaseType::Percent) {
  305. if (type_exponent != 1)
  306. return false;
  307. } else {
  308. if (type_exponent.has_value() && type_exponent != 0)
  309. return false;
  310. }
  311. }
  312. return true;
  313. }
  314. // https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-match
  315. bool CSSNumericType::matches_dimension_percentage(BaseType type) const
  316. {
  317. // A type matches <length-percentage> if its only non-zero entry is either «[ "length" → 1 ]»
  318. // or «[ "percent" → 1 ]» Same for <angle-percentage>, <time-percentage>, etc.
  319. // Check for percent -> 1 or type -> 1, but not both
  320. if ((exponent(type) == 1) == (exponent(BaseType::Percent) == 1))
  321. return false;
  322. // Ensure all other types are absent or 0
  323. for (auto i = 0; i < to_underlying(BaseType::__Count); ++i) {
  324. auto base_type = static_cast<BaseType>(i);
  325. auto type_exponent = exponent(base_type);
  326. if (base_type == type || base_type == BaseType::Percent)
  327. continue;
  328. if (type_exponent.has_value() && type_exponent != 0)
  329. return false;
  330. }
  331. return true;
  332. }
  333. // https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-match
  334. bool CSSNumericType::matches_number() const
  335. {
  336. // A type matches <number> if it has no non-zero entries and its percent hint is null.
  337. if (percent_hint().has_value())
  338. return false;
  339. for (auto i = 0; i < to_underlying(BaseType::__Count); ++i) {
  340. auto base_type = static_cast<BaseType>(i);
  341. auto type_exponent = exponent(base_type);
  342. if (type_exponent.has_value() && type_exponent != 0)
  343. return false;
  344. }
  345. return true;
  346. }
  347. // https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-match
  348. bool CSSNumericType::matches_number_percentage() const
  349. {
  350. // A type matches <number-percentage> if it has no non-zero entries, or its only non-zero entry is «[ "percent" → 1 ]».
  351. for (auto i = 0; i < to_underlying(BaseType::__Count); ++i) {
  352. auto base_type = static_cast<BaseType>(i);
  353. auto type_exponent = exponent(base_type);
  354. if (base_type == BaseType::Percent) {
  355. if (type_exponent.has_value() && type_exponent != 0 && type_exponent != 1)
  356. return false;
  357. } else if (type_exponent.has_value() && type_exponent != 0) {
  358. return false;
  359. }
  360. }
  361. return true;
  362. }
  363. bool CSSNumericType::matches_dimension() const
  364. {
  365. // This isn't a spec algorithm.
  366. // A type should match `<dimension>` if there are no non-zero entries,
  367. // or it has a single non-zero entry (other than percent) which is equal to 1.
  368. auto number_of_one_exponents = 0;
  369. for (auto i = 0; i < to_underlying(BaseType::__Count); ++i) {
  370. auto base_type = static_cast<BaseType>(i);
  371. auto type_exponent = exponent(base_type);
  372. if (!type_exponent.has_value())
  373. continue;
  374. if (type_exponent == 1) {
  375. if (base_type == BaseType::Percent)
  376. return false;
  377. number_of_one_exponents++;
  378. } else if (type_exponent != 0) {
  379. return false;
  380. }
  381. }
  382. return number_of_one_exponents == 0 || number_of_one_exponents == 1;
  383. }
  384. ErrorOr<String> CSSNumericType::dump() const
  385. {
  386. StringBuilder builder;
  387. TRY(builder.try_appendff("{{ hint: {}", m_percent_hint.map([](auto base_type) { return base_type_name(base_type); })));
  388. for (auto i = 0; i < to_underlying(BaseType::__Count); ++i) {
  389. auto base_type = static_cast<BaseType>(i);
  390. auto type_exponent = exponent(base_type);
  391. if (type_exponent.has_value())
  392. TRY(builder.try_appendff(", \"{}\" → {}", base_type_name(base_type), type_exponent.value()));
  393. }
  394. TRY(builder.try_append(" }"sv));
  395. return builder.to_string();
  396. }
  397. }