2021-10-06 14:27:13 +01:00
/*
2023-02-14 19:55:40 +00:00
* Copyright ( c ) 2021 - 2023 , Sam Atkins < atkinssj @ serenityos . org >
2021-10-06 14:27:13 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <AK/StringBuilder.h>
# include <AK/Utf8View.h>
# include <LibWeb/CSS/Serialize.h>
namespace Web : : CSS {
// https://www.w3.org/TR/cssom-1/#escape-a-character
2023-08-22 12:05:44 +01:00
void escape_a_character ( StringBuilder & builder , u32 character )
2021-10-06 14:27:13 +01:00
{
2023-08-22 12:05:44 +01:00
builder . append ( ' \\ ' ) ;
builder . append_code_point ( character ) ;
2021-10-06 14:27:13 +01:00
}
// https://www.w3.org/TR/cssom-1/#escape-a-character-as-code-point
2023-08-22 12:05:44 +01:00
void escape_a_character_as_code_point ( StringBuilder & builder , u32 character )
2021-10-06 14:27:13 +01:00
{
2023-08-22 12:05:44 +01:00
builder . appendff ( " \\ {:x} " , character ) ;
2021-10-06 14:27:13 +01:00
}
// https://www.w3.org/TR/cssom-1/#serialize-an-identifier
2023-08-22 12:05:44 +01:00
void serialize_an_identifier ( StringBuilder & builder , StringView ident )
2021-10-06 14:27:13 +01:00
{
Utf8View characters { ident } ;
auto first_character = characters . is_empty ( ) ? 0 : * characters . begin ( ) ;
// To serialize an identifier means to create a string represented by the concatenation of,
// for each character of the identifier:
for ( auto character : characters ) {
// If the character is NULL (U+0000), then the REPLACEMENT CHARACTER (U+FFFD).
if ( character = = 0 ) {
2023-08-22 12:05:44 +01:00
builder . append_code_point ( 0xFFFD ) ;
2021-10-06 14:27:13 +01:00
continue ;
}
// If the character is in the range [\1-\1f] (U+0001 to U+001F) or is U+007F,
// then the character escaped as code point.
if ( ( character > = 0x0001 & & character < = 0x001F ) | | ( character = = 0x007F ) ) {
2023-08-22 12:05:44 +01:00
escape_a_character_as_code_point ( builder , character ) ;
2021-10-06 14:27:13 +01:00
continue ;
}
// If the character is the first character and is in the range [0-9] (U+0030 to U+0039),
// then the character escaped as code point.
if ( builder . is_empty ( ) & & character > = ' 0 ' & & character < = ' 9 ' ) {
2023-08-22 12:05:44 +01:00
escape_a_character_as_code_point ( builder , character ) ;
2021-10-06 14:27:13 +01:00
continue ;
}
// If the character is the second character and is in the range [0-9] (U+0030 to U+0039)
// and the first character is a "-" (U+002D), then the character escaped as code point.
if ( builder . length ( ) = = 1 & & first_character = = ' - ' & & character > = ' 0 ' & & character < = ' 9 ' ) {
2023-08-22 12:05:44 +01:00
escape_a_character_as_code_point ( builder , character ) ;
2021-10-06 14:27:13 +01:00
continue ;
}
// If the character is the first character and is a "-" (U+002D), and there is no second
// character, then the escaped character.
if ( builder . is_empty ( ) & & character = = ' - ' & & characters . length ( ) = = 1 ) {
2023-08-22 12:05:44 +01:00
escape_a_character ( builder , character ) ;
2021-10-06 14:27:13 +01:00
continue ;
}
// 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.
if ( ( character > = 0x0080 )
| | ( character = = ' - ' ) | | ( character = = ' _ ' )
| | ( character > = ' 0 ' & & character < = ' 9 ' )
| | ( character > = ' A ' & & character < = ' Z ' )
| | ( character > = ' a ' & & character < = ' z ' ) ) {
2023-08-22 12:05:44 +01:00
builder . append_code_point ( character ) ;
2021-10-06 14:27:13 +01:00
continue ;
}
// Otherwise, the escaped character.
2023-08-22 12:05:44 +01:00
escape_a_character ( builder , character ) ;
2021-10-06 14:27:13 +01:00
}
2021-10-14 16:50:43 +01:00
}
// https://www.w3.org/TR/cssom-1/#serialize-a-string
2023-08-22 12:05:44 +01:00
void serialize_a_string ( StringBuilder & builder , StringView string )
2021-10-14 16:50:43 +01:00
{
Utf8View characters { string } ;
// To serialize a string means to create a string represented by '"' (U+0022), followed by the result
// of applying the rules below to each character of the given string, followed by '"' (U+0022):
2023-08-22 12:05:44 +01:00
builder . append ( ' " ' ) ;
2021-10-14 16:50:43 +01:00
for ( auto character : characters ) {
// If the character is NULL (U+0000), then the REPLACEMENT CHARACTER (U+FFFD).
if ( character = = 0 ) {
2023-08-22 12:05:44 +01:00
builder . append_code_point ( 0xFFFD ) ;
2021-10-14 16:50:43 +01:00
continue ;
}
// If the character is in the range [\1-\1f] (U+0001 to U+001F) or is U+007F, the character escaped as code point.
if ( ( character > = 0x0001 & & character < = 0x001F ) | | ( character = = 0x007F ) ) {
2023-08-22 12:05:44 +01:00
escape_a_character_as_code_point ( builder , character ) ;
2021-10-14 16:50:43 +01:00
continue ;
}
// If the character is '"' (U+0022) or "\" (U+005C), the escaped character.
if ( character = = 0x0022 | | character = = 0x005C ) {
2023-08-22 12:05:44 +01:00
escape_a_character ( builder , character ) ;
2021-10-14 16:50:43 +01:00
continue ;
}
// Otherwise, the character itself.
2023-08-22 12:05:44 +01:00
builder . append_code_point ( character ) ;
2021-10-14 16:50:43 +01:00
}
2023-08-22 12:05:44 +01:00
builder . append ( ' " ' ) ;
2021-10-14 16:50:43 +01:00
}
// https://www.w3.org/TR/cssom-1/#serialize-a-url
2023-08-22 12:05:44 +01:00
void serialize_a_url ( StringBuilder & builder , StringView url )
2021-10-14 16:50:43 +01:00
{
// To serialize a URL means to create a string represented by "url(",
// followed by the serialization of the URL as a string, followed by ")".
2023-08-22 12:05:44 +01:00
builder . append ( " url( " sv ) ;
2023-12-01 16:45:31 +00:00
serialize_a_string ( builder , url ) ;
2023-08-22 12:05:44 +01:00
builder . append ( ' ) ' ) ;
2022-09-11 12:20:16 -04:00
}
// NOTE: No spec currently exists for serializing a <'unicode-range'>.
2023-12-09 23:30:01 +01:00
void serialize_unicode_ranges ( StringBuilder & builder , Vector < Gfx : : UnicodeRange > const & unicode_ranges )
2022-09-11 12:20:16 -04:00
{
2023-12-09 23:30:01 +01:00
serialize_a_comma_separated_list ( builder , unicode_ranges , [ ] ( auto & builder , Gfx : : UnicodeRange unicode_range ) - > void {
2023-08-22 12:25:30 +01:00
return serialize_a_string ( builder , unicode_range . to_string ( ) ) ;
2023-08-22 12:05:44 +01:00
} ) ;
2022-09-11 12:20:16 -04:00
}
2024-11-23 17:15:36 -05:00
namespace {
char nth_digit ( u32 value , u8 digit )
{
// This helper is used to format integers.
// nth_digit(745, 1) -> '5'
// nth_digit(745, 2) -> '4'
// nth_digit(745, 3) -> '7'
VERIFY ( value < 1000 ) ;
VERIFY ( digit < = 3 ) ;
VERIFY ( digit > 0 ) ;
while ( digit > 1 ) {
value / = 10 ;
digit - - ;
}
return ' 0 ' + value % 10 ;
}
Array < char , 4 > format_to_8bit_compatible ( u8 value )
{
// This function formats to the shortest string that roundtrips at 8 bits.
// As an example:
// 127 / 255 = 0.498 ± 0.001
// 128 / 255 = 0.502 ± 0.001
// But round(.5 * 255) == 128, so this function returns (note that it's only the fractional part):
// 127 -> "498"
// 128 -> "5"
u32 const three_digits = ( value * 1000u + 127 ) / 255 ;
u32 const rounded_to_two_digits = ( three_digits + 5 ) / 10 * 10 ;
if ( ( rounded_to_two_digits * 255 / 100 + 5 ) / 10 ! = value )
return { nth_digit ( three_digits , 3 ) , nth_digit ( three_digits , 2 ) , nth_digit ( three_digits , 1 ) , ' \0 ' } ;
u32 const rounded_to_one_digit = ( three_digits + 50 ) / 100 * 100 ;
if ( ( rounded_to_one_digit * 255 / 100 + 5 ) / 10 ! = value )
return { nth_digit ( rounded_to_two_digits , 3 ) , nth_digit ( rounded_to_two_digits , 2 ) , ' \0 ' , ' \0 ' } ;
return { nth_digit ( rounded_to_one_digit , 3 ) , ' \0 ' , ' \0 ' , ' \0 ' } ;
}
}
2022-07-12 15:30:13 +01:00
// https://www.w3.org/TR/css-color-4/#serializing-sRGB-values
2023-08-22 12:05:44 +01:00
void serialize_a_srgb_value ( StringBuilder & builder , Color color )
2022-07-12 15:30:13 +01:00
{
// The serialized form is derived from the computed value and thus, uses either the rgb() or rgba() form
// (depending on whether the alpha is exactly 1, or not), with lowercase letters for the function name.
// NOTE: Since we use Gfx::Color, having an "alpha of 1" means its value is 255.
2024-11-23 17:15:36 -05:00
if ( color . alpha ( ) = = 0 )
builder . appendff ( " rgba({}, {}, {}, 0) " sv , color . red ( ) , color . green ( ) , color . blue ( ) ) ;
else if ( color . alpha ( ) = = 255 )
2023-08-22 12:05:44 +01:00
builder . appendff ( " rgb({}, {}, {}) " sv , color . red ( ) , color . green ( ) , color . blue ( ) ) ;
2022-07-12 15:30:13 +01:00
else
2024-11-23 17:15:36 -05:00
builder . appendff ( " rgba({}, {}, {}, 0.{}) " sv , color . red ( ) , color . green ( ) , color . blue ( ) , format_to_8bit_compatible ( color . alpha ( ) ) . data ( ) ) ;
2022-07-12 15:30:13 +01:00
}
2023-08-22 12:05:44 +01:00
String serialize_an_identifier ( StringView ident )
2021-10-14 16:50:43 +01:00
{
StringBuilder builder ;
2023-08-22 12:05:44 +01:00
serialize_an_identifier ( builder , ident ) ;
return MUST ( builder . to_string ( ) ) ;
2021-10-14 16:50:43 +01:00
}
2023-08-22 12:05:44 +01:00
String serialize_a_string ( StringView string )
2021-10-14 16:50:43 +01:00
{
StringBuilder builder ;
2023-08-22 12:05:44 +01:00
serialize_a_string ( builder , string ) ;
return MUST ( builder . to_string ( ) ) ;
2021-10-14 16:50:43 +01:00
}
2023-08-22 12:05:44 +01:00
String serialize_a_url ( StringView url )
2021-10-14 16:50:43 +01:00
{
StringBuilder builder ;
2023-08-22 12:05:44 +01:00
serialize_a_url ( builder , url ) ;
return MUST ( builder . to_string ( ) ) ;
2021-10-06 14:27:13 +01:00
}
2023-08-22 12:05:44 +01:00
String serialize_a_srgb_value ( Color color )
2022-07-12 15:30:13 +01:00
{
StringBuilder builder ;
2023-08-22 12:05:44 +01:00
serialize_a_srgb_value ( builder , color ) ;
return MUST ( builder . to_string ( ) ) ;
2022-07-12 15:30:13 +01:00
}
2025-03-26 16:20:55 +00:00
// https://drafts.csswg.org/cssom/#serialize-a-css-declaration
String serialize_a_css_declaration ( StringView property , StringView value , Important important )
{
// 1. Let s be the empty string.
StringBuilder builder ;
// 2. Append property to s.
builder . append ( property ) ;
// 3. Append ": " (U+003A U+0020) to s.
builder . append ( " : " sv ) ;
// 4. If value contains any non-whitespace characters, append value to s.
if ( ! value . is_whitespace ( ) )
builder . append ( value ) ;
// 5. If the important flag is set, append " !important" (U+0020 U+0021 U+0069 U+006D U+0070 U+006F U+0072 U+0074
// U+0061 U+006E U+0074) to s.
if ( important = = Important : : Yes )
builder . append ( " !important " sv ) ;
// 6. Append ";" (U+003B) to s.
builder . append ( ' ; ' ) ;
// 7. Return s.
return MUST ( builder . to_string ( ) ) ;
}
2021-10-06 14:27:13 +01:00
}