Serialize.cpp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. /*
  2. * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/StringBuilder.h>
  7. #include <AK/Utf8View.h>
  8. #include <LibWeb/CSS/Serialize.h>
  9. namespace Web::CSS {
  10. // https://www.w3.org/TR/cssom-1/#escape-a-character
  11. void escape_a_character(StringBuilder& builder, u32 character)
  12. {
  13. builder.append('\\');
  14. builder.append_code_point(character);
  15. }
  16. // https://www.w3.org/TR/cssom-1/#escape-a-character-as-code-point
  17. void escape_a_character_as_code_point(StringBuilder& builder, u32 character)
  18. {
  19. builder.appendff("\\{:x} ", character);
  20. }
  21. // https://www.w3.org/TR/cssom-1/#serialize-an-identifier
  22. void serialize_an_identifier(StringBuilder& builder, StringView ident)
  23. {
  24. Utf8View characters { ident };
  25. auto first_character = characters.is_empty() ? 0 : *characters.begin();
  26. // To serialize an identifier means to create a string represented by the concatenation of,
  27. // for each character of the identifier:
  28. for (auto character : characters) {
  29. // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER (U+FFFD).
  30. if (character == 0) {
  31. builder.append_code_point(0xFFFD);
  32. continue;
  33. }
  34. // If the character is in the range [\1-\1f] (U+0001 to U+001F) or is U+007F,
  35. // then the character escaped as code point.
  36. if ((character >= 0x0001 && character <= 0x001F) || (character == 0x007F)) {
  37. escape_a_character_as_code_point(builder, character);
  38. continue;
  39. }
  40. // If the character is the first character and is in the range [0-9] (U+0030 to U+0039),
  41. // then the character escaped as code point.
  42. if (builder.is_empty() && character >= '0' && character <= '9') {
  43. escape_a_character_as_code_point(builder, character);
  44. continue;
  45. }
  46. // If the character is the second character and is in the range [0-9] (U+0030 to U+0039)
  47. // and the first character is a "-" (U+002D), then the character escaped as code point.
  48. if (builder.length() == 1 && first_character == '-' && character >= '0' && character <= '9') {
  49. escape_a_character_as_code_point(builder, character);
  50. continue;
  51. }
  52. // If the character is the first character and is a "-" (U+002D), and there is no second
  53. // character, then the escaped character.
  54. if (builder.is_empty() && character == '-' && characters.length() == 1) {
  55. escape_a_character(builder, character);
  56. continue;
  57. }
  58. // If the character is not handled by one of the above rules and is greater than or equal to U+0080, is "-" (U+002D) or "_" (U+005F), or is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to U+005A), or \[a-z] (U+0061 to U+007A), then the character itself.
  59. if ((character >= 0x0080)
  60. || (character == '-') || (character == '_')
  61. || (character >= '0' && character <= '9')
  62. || (character >= 'A' && character <= 'Z')
  63. || (character >= 'a' && character <= 'z')) {
  64. builder.append_code_point(character);
  65. continue;
  66. }
  67. // Otherwise, the escaped character.
  68. escape_a_character(builder, character);
  69. }
  70. }
  71. // https://www.w3.org/TR/cssom-1/#serialize-a-string
  72. void serialize_a_string(StringBuilder& builder, StringView string)
  73. {
  74. Utf8View characters { string };
  75. // To serialize a string means to create a string represented by '"' (U+0022), followed by the result
  76. // of applying the rules below to each character of the given string, followed by '"' (U+0022):
  77. builder.append('"');
  78. for (auto character : characters) {
  79. // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER (U+FFFD).
  80. if (character == 0) {
  81. builder.append_code_point(0xFFFD);
  82. continue;
  83. }
  84. // If the character is in the range [\1-\1f] (U+0001 to U+001F) or is U+007F, the character escaped as code point.
  85. if ((character >= 0x0001 && character <= 0x001F) || (character == 0x007F)) {
  86. escape_a_character_as_code_point(builder, character);
  87. continue;
  88. }
  89. // If the character is '"' (U+0022) or "\" (U+005C), the escaped character.
  90. if (character == 0x0022 || character == 0x005C) {
  91. escape_a_character(builder, character);
  92. continue;
  93. }
  94. // Otherwise, the character itself.
  95. builder.append_code_point(character);
  96. }
  97. builder.append('"');
  98. }
  99. // https://www.w3.org/TR/cssom-1/#serialize-a-url
  100. void serialize_a_url(StringBuilder& builder, StringView url)
  101. {
  102. // To serialize a URL means to create a string represented by "url(",
  103. // followed by the serialization of the URL as a string, followed by ")".
  104. builder.append("url("sv);
  105. serialize_a_string(builder, url);
  106. builder.append(')');
  107. }
  108. // https://www.w3.org/TR/cssom-1/#serialize-a-local
  109. void serialize_a_local(StringBuilder& builder, StringView path)
  110. {
  111. // To serialize a LOCAL means to create a string represented by "local(",
  112. // followed by the serialization of the LOCAL as a string, followed by ")".
  113. builder.append("local("sv);
  114. serialize_a_string(builder, path);
  115. builder.append(')');
  116. }
  117. // NOTE: No spec currently exists for serializing a <'unicode-range'>.
  118. void serialize_unicode_ranges(StringBuilder& builder, Vector<Gfx::UnicodeRange> const& unicode_ranges)
  119. {
  120. serialize_a_comma_separated_list(builder, unicode_ranges, [](auto& builder, Gfx::UnicodeRange unicode_range) -> void {
  121. return serialize_a_string(builder, unicode_range.to_string());
  122. });
  123. }
  124. namespace {
  125. char nth_digit(u32 value, u8 digit)
  126. {
  127. // This helper is used to format integers.
  128. // nth_digit(745, 1) -> '5'
  129. // nth_digit(745, 2) -> '4'
  130. // nth_digit(745, 3) -> '7'
  131. VERIFY(value < 1000);
  132. VERIFY(digit <= 3);
  133. VERIFY(digit > 0);
  134. while (digit > 1) {
  135. value /= 10;
  136. digit--;
  137. }
  138. return '0' + value % 10;
  139. }
  140. Array<char, 4> format_to_8bit_compatible(u8 value)
  141. {
  142. // This function formats to the shortest string that roundtrips at 8 bits.
  143. // As an example:
  144. // 127 / 255 = 0.498 ± 0.001
  145. // 128 / 255 = 0.502 ± 0.001
  146. // But round(.5 * 255) == 128, so this function returns (note that it's only the fractional part):
  147. // 127 -> "498"
  148. // 128 -> "5"
  149. u32 const three_digits = (value * 1000u + 127) / 255;
  150. u32 const rounded_to_two_digits = (three_digits + 5) / 10 * 10;
  151. if ((rounded_to_two_digits * 255 / 100 + 5) / 10 != value)
  152. return { nth_digit(three_digits, 3), nth_digit(three_digits, 2), nth_digit(three_digits, 1), '\0' };
  153. u32 const rounded_to_one_digit = (three_digits + 50) / 100 * 100;
  154. if ((rounded_to_one_digit * 255 / 100 + 5) / 10 != value)
  155. return { nth_digit(rounded_to_two_digits, 3), nth_digit(rounded_to_two_digits, 2), '\0', '\0' };
  156. return { nth_digit(rounded_to_one_digit, 3), '\0', '\0', '\0' };
  157. }
  158. }
  159. // https://www.w3.org/TR/css-color-4/#serializing-sRGB-values
  160. void serialize_a_srgb_value(StringBuilder& builder, Color color)
  161. {
  162. // The serialized form is derived from the computed value and thus, uses either the rgb() or rgba() form
  163. // (depending on whether the alpha is exactly 1, or not), with lowercase letters for the function name.
  164. // NOTE: Since we use Gfx::Color, having an "alpha of 1" means its value is 255.
  165. if (color.alpha() == 0)
  166. builder.appendff("rgba({}, {}, {}, 0)"sv, color.red(), color.green(), color.blue());
  167. else if (color.alpha() == 255)
  168. builder.appendff("rgb({}, {}, {})"sv, color.red(), color.green(), color.blue());
  169. else
  170. builder.appendff("rgba({}, {}, {}, 0.{})"sv, color.red(), color.green(), color.blue(), format_to_8bit_compatible(color.alpha()).data());
  171. }
  172. String serialize_an_identifier(StringView ident)
  173. {
  174. StringBuilder builder;
  175. serialize_an_identifier(builder, ident);
  176. return MUST(builder.to_string());
  177. }
  178. String serialize_a_string(StringView string)
  179. {
  180. StringBuilder builder;
  181. serialize_a_string(builder, string);
  182. return MUST(builder.to_string());
  183. }
  184. String serialize_a_url(StringView url)
  185. {
  186. StringBuilder builder;
  187. serialize_a_url(builder, url);
  188. return MUST(builder.to_string());
  189. }
  190. String serialize_a_srgb_value(Color color)
  191. {
  192. StringBuilder builder;
  193. serialize_a_srgb_value(builder, color);
  194. return MUST(builder.to_string());
  195. }
  196. }