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