mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-23 21:15:14 +00:00
LibWeb/CSS: Parse @font-face
descriptors as style values
CSSFontFaceRule now stores its values as a CSSFontFaceDescriptors, with a ParsedFontFace produced on request. This is exposed via the `style` attribute, so we pass a lot of tests that try to read values from that. We have one test regression, which we passed by mistake before: The test wanted to ensure we don't allow `@font-face` nested inside other rules. We passed it just because we discarded any `@font-face` without a `font-family`. What we're supposed to do is 1) keep at-rules with missing required descriptors and just not use them, and 2) reject certain ones when nested. We may want to cache the ParsedFontFace in the future, but I didn't here because 1) it's called rarely, and 2) that would mean knowing to invalidate it when the CSSFontFaceDescriptors changes, which isn't obvious to me right now.
This commit is contained in:
parent
949dbef27d
commit
d42a5494d7
12 changed files with 118 additions and 378 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
|
||||
* Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
|
||||
* Copyright (c) 2022-2023, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
|
@ -17,14 +17,14 @@ namespace Web::CSS {
|
|||
|
||||
GC_DEFINE_ALLOCATOR(CSSFontFaceRule);
|
||||
|
||||
GC::Ref<CSSFontFaceRule> CSSFontFaceRule::create(JS::Realm& realm, ParsedFontFace&& font_face)
|
||||
GC::Ref<CSSFontFaceRule> CSSFontFaceRule::create(JS::Realm& realm, GC::Ref<CSSFontFaceDescriptors> style)
|
||||
{
|
||||
return realm.create<CSSFontFaceRule>(realm, move(font_face));
|
||||
return realm.create<CSSFontFaceRule>(realm, style);
|
||||
}
|
||||
|
||||
CSSFontFaceRule::CSSFontFaceRule(JS::Realm& realm, ParsedFontFace&& font_face)
|
||||
CSSFontFaceRule::CSSFontFaceRule(JS::Realm& realm, GC::Ref<CSSFontFaceDescriptors> style)
|
||||
: CSSRule(realm, Type::FontFace)
|
||||
, m_font_face(move(font_face))
|
||||
, m_style(style)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -34,15 +34,15 @@ void CSSFontFaceRule::initialize(JS::Realm& realm)
|
|||
WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSFontFaceRule);
|
||||
}
|
||||
|
||||
CSSStyleDeclaration* CSSFontFaceRule::style()
|
||||
ParsedFontFace CSSFontFaceRule::font_face() const
|
||||
{
|
||||
// FIXME: Return a CSSStyleDeclaration subclass that directs changes to the ParsedFontFace.
|
||||
return nullptr;
|
||||
return ParsedFontFace::from_descriptors(m_style);
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/cssom/#ref-for-cssfontfacerule
|
||||
String CSSFontFaceRule::serialized() const
|
||||
{
|
||||
auto font_face = this->font_face();
|
||||
StringBuilder builder;
|
||||
// The result of concatenating the following:
|
||||
|
||||
|
@ -53,18 +53,18 @@ String CSSFontFaceRule::serialized() const
|
|||
builder.append("font-family: "sv);
|
||||
|
||||
// 3. The result of performing serialize a string on the rule’s font family name.
|
||||
serialize_a_string(builder, m_font_face.font_family());
|
||||
serialize_a_string(builder, font_face.font_family());
|
||||
|
||||
// 4. The string ";", i.e., SEMICOLON (U+003B).
|
||||
builder.append(';');
|
||||
|
||||
// 5. If the rule’s associated source list is not empty, follow these substeps:
|
||||
if (!m_font_face.sources().is_empty()) {
|
||||
if (!font_face.sources().is_empty()) {
|
||||
// 1. A single SPACE (U+0020), followed by the string "src:", followed by a single SPACE (U+0020).
|
||||
builder.append(" src: "sv);
|
||||
|
||||
// 2. The result of invoking serialize a comma-separated list on performing serialize a URL or serialize a LOCAL for each source on the source list.
|
||||
serialize_a_comma_separated_list(builder, m_font_face.sources(), [&](StringBuilder& builder, ParsedFontFace::Source source) -> void {
|
||||
serialize_a_comma_separated_list(builder, font_face.sources(), [&](StringBuilder& builder, ParsedFontFace::Source source) -> void {
|
||||
source.local_or_url.visit(
|
||||
[&builder](URL::URL const& url) {
|
||||
serialize_a_url(builder, url.to_string());
|
||||
|
@ -89,7 +89,7 @@ String CSSFontFaceRule::serialized() const
|
|||
|
||||
// 6. If rule’s associated unicode-range descriptor is present, a single SPACE (U+0020), followed by the string "unicode-range:", followed by a single SPACE (U+0020), followed by the result of performing serialize a <'unicode-range'>, followed by the string ";", i.e., SEMICOLON (U+003B).
|
||||
builder.append(" unicode-range: "sv);
|
||||
serialize_unicode_ranges(builder, m_font_face.unicode_ranges());
|
||||
serialize_unicode_ranges(builder, font_face.unicode_ranges());
|
||||
builder.append(';');
|
||||
|
||||
// FIXME: 7. If rule’s associated font-variant descriptor is present, a single SPACE (U+0020),
|
||||
|
@ -101,8 +101,8 @@ String CSSFontFaceRule::serialized() const
|
|||
// followed by the string "font-feature-settings:", followed by a single SPACE (U+0020),
|
||||
// followed by the result of performing serialize a <'font-feature-settings'>,
|
||||
// followed by the string ";", i.e., SEMICOLON (U+003B).
|
||||
if (m_font_face.font_feature_settings().has_value()) {
|
||||
auto const& feature_settings = m_font_face.font_feature_settings().value();
|
||||
if (font_face.font_feature_settings().has_value()) {
|
||||
auto const& feature_settings = font_face.font_feature_settings().value();
|
||||
builder.append(" font-feature-settings: "sv);
|
||||
// NOTE: We sort the tags during parsing, so they're already in the correct order.
|
||||
bool first = true;
|
||||
|
@ -126,12 +126,12 @@ String CSSFontFaceRule::serialized() const
|
|||
// followed by the result of performing serialize a <'font-stretch'>,
|
||||
// followed by the string ";", i.e., SEMICOLON (U+003B).
|
||||
// NOTE: font-stretch is now an alias for font-width, so we use that instead.
|
||||
if (m_font_face.width().has_value()) {
|
||||
if (font_face.width().has_value()) {
|
||||
builder.append(" font-width: "sv);
|
||||
// NOTE: font-width is supposed to always be serialized as a percentage.
|
||||
// Right now, it's stored as a Gfx::FontWidth value, so we have to lossily convert it back.
|
||||
float percentage = 100.0f;
|
||||
switch (m_font_face.width().value()) {
|
||||
switch (font_face.width().value()) {
|
||||
case Gfx::FontWidth::UltraCondensed:
|
||||
percentage = 50.0f;
|
||||
break;
|
||||
|
@ -170,8 +170,8 @@ String CSSFontFaceRule::serialized() const
|
|||
// followed by the string "font-weight:", followed by a single SPACE (U+0020),
|
||||
// followed by the result of performing serialize a <'font-weight'>,
|
||||
// followed by the string ";", i.e., SEMICOLON (U+003B).
|
||||
if (m_font_face.weight().has_value()) {
|
||||
auto weight = m_font_face.weight().value();
|
||||
if (font_face.weight().has_value()) {
|
||||
auto weight = font_face.weight().value();
|
||||
builder.append(" font-weight: "sv);
|
||||
if (weight == 400)
|
||||
builder.append("normal"sv);
|
||||
|
@ -186,8 +186,8 @@ String CSSFontFaceRule::serialized() const
|
|||
// followed by the string "font-style:", followed by a single SPACE (U+0020),
|
||||
// followed by the result of performing serialize a <'font-style'>,
|
||||
// followed by the string ";", i.e., SEMICOLON (U+003B).
|
||||
if (m_font_face.slope().has_value()) {
|
||||
auto slope = m_font_face.slope().value();
|
||||
if (font_face.slope().has_value()) {
|
||||
auto slope = font_face.slope().value();
|
||||
builder.append(" font-style: "sv);
|
||||
if (slope == Gfx::name_to_slope("Normal"sv))
|
||||
builder.append("normal"sv);
|
||||
|
@ -206,4 +206,10 @@ String CSSFontFaceRule::serialized() const
|
|||
return MUST(builder.to_string());
|
||||
}
|
||||
|
||||
void CSSFontFaceRule::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_style);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
* Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
|
||||
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
|
@ -7,6 +7,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/CSS/CSSFontFaceDescriptors.h>
|
||||
#include <LibWeb/CSS/CSSRule.h>
|
||||
#include <LibWeb/CSS/ParsedFontFace.h>
|
||||
|
||||
|
@ -17,20 +18,21 @@ class CSSFontFaceRule final : public CSSRule {
|
|||
GC_DECLARE_ALLOCATOR(CSSFontFaceRule);
|
||||
|
||||
public:
|
||||
[[nodiscard]] static GC::Ref<CSSFontFaceRule> create(JS::Realm&, ParsedFontFace&&);
|
||||
[[nodiscard]] static GC::Ref<CSSFontFaceRule> create(JS::Realm&, GC::Ref<CSSFontFaceDescriptors>);
|
||||
|
||||
virtual ~CSSFontFaceRule() override = default;
|
||||
|
||||
ParsedFontFace const& font_face() const { return m_font_face; }
|
||||
CSSStyleDeclaration* style();
|
||||
ParsedFontFace font_face() const;
|
||||
CSSStyleDeclaration* style() { return m_style; }
|
||||
|
||||
private:
|
||||
CSSFontFaceRule(JS::Realm&, ParsedFontFace&&);
|
||||
CSSFontFaceRule(JS::Realm&, GC::Ref<CSSFontFaceDescriptors>);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual String serialized() const override;
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
ParsedFontFace m_font_face;
|
||||
GC::Ref<CSSFontFaceDescriptors> m_style;
|
||||
};
|
||||
|
||||
template<>
|
||||
|
|
|
@ -45,16 +45,17 @@ Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue>> Parser::parse_descriptor_valu
|
|||
return parse_all_as_single_keyword_value(tokens, keyword);
|
||||
},
|
||||
[&](PropertyID property_id) -> RefPtr<CSSStyleValue> {
|
||||
auto value_for_property = parse_css_value_for_property(property_id, tokens);
|
||||
if (!value_for_property)
|
||||
auto value_or_error = parse_css_value(property_id, tokens);
|
||||
if (value_or_error.is_error())
|
||||
return nullptr;
|
||||
auto value_for_property = value_or_error.release_value();
|
||||
// Descriptors don't accept the following, which properties do:
|
||||
// - CSS-wide keywords
|
||||
// - Shorthands
|
||||
// - Arbitrary substitution functions (so, UnresolvedStyleValue)
|
||||
if (value_for_property->is_css_wide_keyword() || value_for_property->is_shorthand() || value_for_property->is_unresolved())
|
||||
return nullptr;
|
||||
return value_for_property.release_nonnull();
|
||||
return value_for_property;
|
||||
},
|
||||
[&](DescriptorMetadata::ValueType value_type) -> RefPtr<CSSStyleValue> {
|
||||
switch (value_type) {
|
||||
|
|
|
@ -699,296 +699,22 @@ template Vector<ParsedFontFace::Source> Parser::parse_font_face_src(TokenStream<
|
|||
GC::Ptr<CSSFontFaceRule> Parser::convert_to_font_face_rule(AtRule const& rule)
|
||||
{
|
||||
// https://drafts.csswg.org/css-fonts/#font-face-rule
|
||||
|
||||
Optional<FlyString> font_family;
|
||||
Optional<FlyString> font_named_instance;
|
||||
Vector<ParsedFontFace::Source> src;
|
||||
Vector<Gfx::UnicodeRange> unicode_range;
|
||||
Optional<int> weight;
|
||||
Optional<int> slope;
|
||||
Optional<int> width;
|
||||
Optional<Percentage> ascent_override;
|
||||
Optional<Percentage> descent_override;
|
||||
Optional<Percentage> line_gap_override;
|
||||
FontDisplay font_display = FontDisplay::Auto;
|
||||
Optional<FlyString> language_override;
|
||||
Optional<OrderedHashMap<FlyString, i64>> font_feature_settings;
|
||||
Optional<OrderedHashMap<FlyString, double>> font_variation_settings;
|
||||
|
||||
// "normal" is returned as nullptr
|
||||
auto parse_as_percentage_or_normal = [&](Vector<ComponentValue> const& values) -> ErrorOr<Optional<Percentage>> {
|
||||
// normal | <percentage [0,∞]>
|
||||
TokenStream tokens { values };
|
||||
if (auto percentage_value = parse_percentage_value(tokens)) {
|
||||
tokens.discard_whitespace();
|
||||
if (tokens.has_next_token())
|
||||
return Error::from_string_literal("Unexpected trailing tokens");
|
||||
|
||||
if (percentage_value->is_percentage() && percentage_value->as_percentage().percentage().value() >= 0)
|
||||
return percentage_value->as_percentage().percentage();
|
||||
|
||||
// TODO: Once we implement calc-simplification in the parser, we should no longer see math values here,
|
||||
// unless they're impossible to resolve and thus invalid.
|
||||
if (percentage_value->is_calculated()) {
|
||||
if (auto result = percentage_value->as_calculated().resolve_percentage({}); result.has_value())
|
||||
return result.value();
|
||||
}
|
||||
|
||||
return Error::from_string_literal("Invalid percentage");
|
||||
}
|
||||
|
||||
tokens.discard_whitespace();
|
||||
if (!tokens.consume_a_token().is_ident("normal"sv))
|
||||
return Error::from_string_literal("Expected `normal | <percentage [0,∞]>`");
|
||||
tokens.discard_whitespace();
|
||||
if (tokens.has_next_token())
|
||||
return Error::from_string_literal("Unexpected trailing tokens");
|
||||
|
||||
return OptionalNone {};
|
||||
};
|
||||
|
||||
Vector<Descriptor> descriptors;
|
||||
HashTable<DescriptorID> seen_descriptor_ids;
|
||||
rule.for_each_as_declaration_list([&](auto& declaration) {
|
||||
if (declaration.name.equals_ignoring_ascii_case("ascent-override"sv)) {
|
||||
auto value = parse_as_percentage_or_normal(declaration.value);
|
||||
if (value.is_error()) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face ascent-override: {}", value.error());
|
||||
if (auto descriptor = convert_to_descriptor(AtRuleID::FontFace, declaration); descriptor.has_value()) {
|
||||
if (seen_descriptor_ids.contains(descriptor->descriptor_id)) {
|
||||
descriptors.remove_first_matching([&descriptor](Descriptor const& existing) {
|
||||
return existing.descriptor_id == descriptor->descriptor_id;
|
||||
});
|
||||
} else {
|
||||
ascent_override = value.release_value();
|
||||
seen_descriptor_ids.set(descriptor->descriptor_id);
|
||||
}
|
||||
return;
|
||||
descriptors.append(descriptor.release_value());
|
||||
}
|
||||
if (declaration.name.equals_ignoring_ascii_case("descent-override"sv)) {
|
||||
auto value = parse_as_percentage_or_normal(declaration.value);
|
||||
if (value.is_error()) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face descent-override: {}", value.error());
|
||||
} else {
|
||||
descent_override = value.release_value();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (declaration.name.equals_ignoring_ascii_case("font-display"sv)) {
|
||||
TokenStream token_stream { declaration.value };
|
||||
if (auto keyword_value = parse_keyword_value(token_stream)) {
|
||||
token_stream.discard_whitespace();
|
||||
if (token_stream.has_next_token()) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in font-display");
|
||||
} else {
|
||||
auto value = keyword_to_font_display(keyword_value->to_keyword());
|
||||
if (value.has_value()) {
|
||||
font_display = *value;
|
||||
} else {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: `{}` is not a valid value for font-display", keyword_value->to_string(CSSStyleValue::SerializationMode::Normal));
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (declaration.name.equals_ignoring_ascii_case("font-family"sv)) {
|
||||
// FIXME: This is very similar to, but different from, the logic in parse_font_family_value().
|
||||
// Ideally they could share code.
|
||||
Vector<FlyString> font_family_parts;
|
||||
bool had_syntax_error = false;
|
||||
for (size_t i = 0; i < declaration.value.size(); ++i) {
|
||||
auto const& part = declaration.value[i];
|
||||
if (part.is(Token::Type::Whitespace))
|
||||
continue;
|
||||
if (part.is(Token::Type::String)) {
|
||||
if (!font_family_parts.is_empty()) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
|
||||
had_syntax_error = true;
|
||||
break;
|
||||
}
|
||||
font_family_parts.append(part.token().string());
|
||||
continue;
|
||||
}
|
||||
if (part.is(Token::Type::Ident)) {
|
||||
if (is_css_wide_keyword(part.token().ident())) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
|
||||
had_syntax_error = true;
|
||||
break;
|
||||
}
|
||||
auto keyword = keyword_from_string(part.token().ident());
|
||||
if (keyword.has_value() && keyword_to_generic_font_family(keyword.value()).has_value()) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
|
||||
had_syntax_error = true;
|
||||
break;
|
||||
}
|
||||
font_family_parts.append(part.token().ident());
|
||||
continue;
|
||||
}
|
||||
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
|
||||
had_syntax_error = true;
|
||||
break;
|
||||
}
|
||||
if (had_syntax_error || font_family_parts.is_empty())
|
||||
return;
|
||||
|
||||
font_family = String::join(' ', font_family_parts).release_value_but_fixme_should_propagate_errors();
|
||||
return;
|
||||
}
|
||||
if (declaration.name.equals_ignoring_ascii_case("font-feature-settings"sv)) {
|
||||
TokenStream token_stream { declaration.value };
|
||||
if (auto value = parse_css_value(CSS::PropertyID::FontFeatureSettings, token_stream); !value.is_error()) {
|
||||
if (value.value()->to_keyword() == Keyword::Normal) {
|
||||
font_feature_settings.clear();
|
||||
} else if (value.value()->is_value_list()) {
|
||||
auto const& feature_tags = value.value()->as_value_list().values();
|
||||
OrderedHashMap<FlyString, i64> settings;
|
||||
settings.ensure_capacity(feature_tags.size());
|
||||
for (auto const& feature_tag : feature_tags) {
|
||||
if (!feature_tag->is_open_type_tagged()) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-feature-settings descriptor is not an OpenTypeTaggedStyleValue; skipping");
|
||||
continue;
|
||||
}
|
||||
auto const& setting_value = feature_tag->as_open_type_tagged().value();
|
||||
if (setting_value->is_integer()) {
|
||||
settings.set(feature_tag->as_open_type_tagged().tag(), setting_value->as_integer().integer());
|
||||
} else if (setting_value->is_calculated() && setting_value->as_calculated().resolves_to_number()) {
|
||||
if (auto integer = setting_value->as_calculated().resolve_integer({}); integer.has_value()) {
|
||||
settings.set(feature_tag->as_open_type_tagged().tag(), *integer);
|
||||
} else {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Calculated value in font-feature-settings descriptor cannot be resolved at parse time; skipping");
|
||||
}
|
||||
} else {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-feature-settings descriptor is not an OpenTypeTaggedStyleValue holding a <integer>; skipping");
|
||||
}
|
||||
}
|
||||
font_feature_settings = move(settings);
|
||||
} else {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-feature-settings descriptor, not compatible with value returned from parsing font-feature-settings property: {}", value.value()->to_string(CSSStyleValue::SerializationMode::Normal));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (declaration.name.equals_ignoring_ascii_case("font-language-override"sv)) {
|
||||
TokenStream token_stream { declaration.value };
|
||||
if (auto maybe_value = parse_css_value(CSS::PropertyID::FontLanguageOverride, token_stream); !maybe_value.is_error()) {
|
||||
auto& value = maybe_value.value();
|
||||
if (value->is_string()) {
|
||||
language_override = value->as_string().string_value();
|
||||
} else {
|
||||
language_override.clear();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (declaration.name.equals_ignoring_ascii_case("font-named-instance"sv)) {
|
||||
// auto | <string>
|
||||
TokenStream token_stream { declaration.value };
|
||||
token_stream.discard_whitespace();
|
||||
auto& token = token_stream.consume_a_token();
|
||||
token_stream.discard_whitespace();
|
||||
if (token_stream.has_next_token()) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in font-named-instance");
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.is_ident("auto"sv)) {
|
||||
font_named_instance.clear();
|
||||
} else if (token.is(Token::Type::String)) {
|
||||
font_named_instance = token.token().string();
|
||||
} else {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-named-instance from {}", token.to_debug_string());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if (declaration.name.equals_ignoring_ascii_case("font-style"sv)) {
|
||||
TokenStream token_stream { declaration.value };
|
||||
if (auto value = parse_css_value(CSS::PropertyID::FontStyle, token_stream); !value.is_error()) {
|
||||
slope = value.value()->to_font_slope();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (declaration.name.equals_ignoring_ascii_case("font-variation-settings"sv)) {
|
||||
TokenStream token_stream { declaration.value };
|
||||
if (auto value = parse_css_value(CSS::PropertyID::FontVariationSettings, token_stream); !value.is_error()) {
|
||||
if (value.value()->to_keyword() == Keyword::Normal) {
|
||||
font_variation_settings.clear();
|
||||
} else if (value.value()->is_value_list()) {
|
||||
auto const& variation_tags = value.value()->as_value_list().values();
|
||||
OrderedHashMap<FlyString, double> settings;
|
||||
settings.ensure_capacity(variation_tags.size());
|
||||
for (auto const& variation_tag : variation_tags) {
|
||||
if (!variation_tag->is_open_type_tagged()) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-variation-settings descriptor is not an OpenTypeTaggedStyleValue; skipping");
|
||||
continue;
|
||||
}
|
||||
auto const& setting_value = variation_tag->as_open_type_tagged().value();
|
||||
if (setting_value->is_number()) {
|
||||
settings.set(variation_tag->as_open_type_tagged().tag(), setting_value->as_number().number());
|
||||
} else if (setting_value->is_calculated() && setting_value->as_calculated().resolves_to_number()) {
|
||||
if (auto number = setting_value->as_calculated().resolve_number({}); number.has_value()) {
|
||||
settings.set(variation_tag->as_open_type_tagged().tag(), *number);
|
||||
} else {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Calculated value in font-variation-settings descriptor cannot be resolved at parse time; skipping");
|
||||
}
|
||||
} else {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-variation-settings descriptor is not an OpenTypeTaggedStyleValue holding a <number>; skipping");
|
||||
}
|
||||
}
|
||||
font_variation_settings = move(settings);
|
||||
} else {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-variation-settings descriptor, not compatible with value returned from parsing font-variation-settings property: {}", value.value()->to_string(CSSStyleValue::SerializationMode::Normal));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (declaration.name.equals_ignoring_ascii_case("font-weight"sv)) {
|
||||
TokenStream token_stream { declaration.value };
|
||||
if (auto value = parse_css_value(CSS::PropertyID::FontWeight, token_stream); !value.is_error()) {
|
||||
weight = value.value()->to_font_weight();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (declaration.name.equals_ignoring_ascii_case("font-width"sv)
|
||||
|| declaration.name.equals_ignoring_ascii_case("font-stretch"sv)) {
|
||||
TokenStream token_stream { declaration.value };
|
||||
if (auto value = parse_css_value(CSS::PropertyID::FontWidth, token_stream); !value.is_error()) {
|
||||
width = value.value()->to_font_width();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (declaration.name.equals_ignoring_ascii_case("line-gap-override"sv)) {
|
||||
auto value = parse_as_percentage_or_normal(declaration.value);
|
||||
if (value.is_error()) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face line-gap-override: {}", value.error());
|
||||
} else {
|
||||
line_gap_override = value.release_value();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (declaration.name.equals_ignoring_ascii_case("src"sv)) {
|
||||
TokenStream token_stream { declaration.value };
|
||||
Vector<ParsedFontFace::Source> supported_sources = parse_font_face_src(token_stream);
|
||||
if (!supported_sources.is_empty())
|
||||
src = move(supported_sources);
|
||||
return;
|
||||
}
|
||||
if (declaration.name.equals_ignoring_ascii_case("unicode-range"sv)) {
|
||||
TokenStream token_stream { declaration.value };
|
||||
auto unicode_ranges = parse_unicode_ranges(token_stream);
|
||||
if (unicode_ranges.is_empty())
|
||||
return;
|
||||
|
||||
unicode_range = move(unicode_ranges);
|
||||
return;
|
||||
}
|
||||
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unrecognized descriptor '{}' in @font-face; discarding.", declaration.name);
|
||||
});
|
||||
|
||||
if (!font_family.has_value()) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face: no font-family!");
|
||||
return {};
|
||||
}
|
||||
|
||||
if (unicode_range.is_empty()) {
|
||||
unicode_range.empend(0x0u, 0x10FFFFu);
|
||||
}
|
||||
|
||||
return CSSFontFaceRule::create(realm(), ParsedFontFace { font_family.release_value(), move(weight), move(slope), move(width), move(src), move(unicode_range), move(ascent_override), move(descent_override), move(line_gap_override), font_display, move(font_named_instance), move(language_override), move(font_feature_settings), move(font_variation_settings) });
|
||||
return CSSFontFaceRule::create(realm(), CSSFontFaceDescriptors::create(realm(), move(descriptors)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -701,7 +701,7 @@ void dump_rule(StringBuilder& builder, CSS::CSSRule const& rule, int indent_leve
|
|||
|
||||
void dump_font_face_rule(StringBuilder& builder, CSS::CSSFontFaceRule const& rule, int indent_levels)
|
||||
{
|
||||
auto& font_face = rule.font_face();
|
||||
auto const font_face = rule.font_face();
|
||||
indent(builder, indent_levels + 1);
|
||||
builder.appendff("font-family: {}\n", font_face.font_family());
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ CSSImportRule type = 3
|
|||
CSSNamespaceRule type = 10
|
||||
CSSStyleRule type = 1
|
||||
CSSMediaRule type = 4
|
||||
CSSFontFaceRule type = 5
|
||||
CSSKeyframesRule type = 7
|
||||
CSSSupportsRule type = 12
|
||||
CSSLayerStatementRule type = 0
|
||||
|
|
|
@ -2,10 +2,11 @@ Harness status: OK
|
|||
|
||||
Found 6 tests
|
||||
|
||||
6 Fail
|
||||
2 Pass
|
||||
4 Fail
|
||||
Fail Check that size-adjust: 100% is valid
|
||||
Fail Check that size-adjust: 0% is valid
|
||||
Fail Check that size-adjust: 110% is valid
|
||||
Fail Check that size-adjust: 100000000000% is valid
|
||||
Fail Check that size-adjust: -100% is invalid
|
||||
Fail Check that size-adjust: -1% is invalid
|
||||
Pass Check that size-adjust: -100% is invalid
|
||||
Pass Check that size-adjust: -1% is invalid
|
|
@ -2,33 +2,34 @@ Harness status: OK
|
|||
|
||||
Found 35 tests
|
||||
|
||||
35 Fail
|
||||
Fail Check that src: url("foo.ttf") is valid
|
||||
Fail Check that src: url("foo.ttf"), url("bar.ttf") is valid
|
||||
Fail Check that src: url("foo.ttf") format() is invalid
|
||||
Fail Check that src: url("foo.ttf") dummy() is invalid
|
||||
Fail Check that src: url("foo.ttf") format("woff") dummy() is invalid
|
||||
Fail Check that src: url("foo.ttf") dummy() format("woff") is invalid
|
||||
21 Pass
|
||||
14 Fail
|
||||
Pass Check that src: url("foo.ttf") is valid
|
||||
Pass Check that src: url("foo.ttf"), url("bar.ttf") is valid
|
||||
Pass Check that src: url("foo.ttf") format() is invalid
|
||||
Pass Check that src: url("foo.ttf") dummy() is invalid
|
||||
Pass Check that src: url("foo.ttf") format("woff") dummy() is invalid
|
||||
Pass Check that src: url("foo.ttf") dummy() format("woff") is invalid
|
||||
Fail Check that src: url("foo.ttf") format("collection") is valid
|
||||
Fail Check that src: url("foo.ttf") format("opentype") is valid
|
||||
Fail Check that src: url("foo.ttf") format("truetype") is valid
|
||||
Fail Check that src: url("foo.ttf") format("woff") is valid
|
||||
Fail Check that src: url("foo.ttf") format("woff2") is valid
|
||||
Pass Check that src: url("foo.ttf") format("opentype") is valid
|
||||
Pass Check that src: url("foo.ttf") format("truetype") is valid
|
||||
Pass Check that src: url("foo.ttf") format("woff") is valid
|
||||
Pass Check that src: url("foo.ttf") format("woff2") is valid
|
||||
Fail Check that src: url("foo.ttf") format("opentype", "truetype") is invalid
|
||||
Fail Check that src: url("foo.ttf") format(collection) is valid
|
||||
Fail Check that src: url("foo.ttf") format(opentype) is valid
|
||||
Fail Check that src: url("foo.ttf") format(truetype) is valid
|
||||
Fail Check that src: url("foo.ttf") format(woff) is valid
|
||||
Fail Check that src: url("foo.ttf") format(woff2) is valid
|
||||
Pass Check that src: url("foo.ttf") format(opentype) is valid
|
||||
Pass Check that src: url("foo.ttf") format(truetype) is valid
|
||||
Pass Check that src: url("foo.ttf") format(woff) is valid
|
||||
Pass Check that src: url("foo.ttf") format(woff2) is valid
|
||||
Fail Check that src: url("foo.ttf") format(opentype, truetype) is invalid
|
||||
Fail Check that src: url("foo.ttf") format(opentype truetype) is invalid
|
||||
Fail Check that src: url("foo.ttf") format(auto) is invalid
|
||||
Fail Check that src: url("foo.ttf") format(default) is invalid
|
||||
Fail Check that src: url("foo.ttf") format(inherit) is invalid
|
||||
Fail Check that src: url("foo.ttf") format(initial) is invalid
|
||||
Fail Check that src: url("foo.ttf") format(none) is invalid
|
||||
Fail Check that src: url("foo.ttf") format(normal) is invalid
|
||||
Fail Check that src: url("foo.ttf") format(xyzzy) is invalid
|
||||
Pass Check that src: url("foo.ttf") format(auto) is invalid
|
||||
Pass Check that src: url("foo.ttf") format(default) is invalid
|
||||
Pass Check that src: url("foo.ttf") format(inherit) is invalid
|
||||
Pass Check that src: url("foo.ttf") format(initial) is invalid
|
||||
Pass Check that src: url("foo.ttf") format(none) is invalid
|
||||
Pass Check that src: url("foo.ttf") format(normal) is invalid
|
||||
Pass Check that src: url("foo.ttf") format(xyzzy) is invalid
|
||||
Fail Check that src: url("foo.ttf") format("embedded-opentype"), url("bar.html") is valid
|
||||
Fail Check that src: url("foo.ttf") format(embedded-opentype), url("bar.html") is valid
|
||||
Fail Check that src: url("foo.ttf") format("svg"), url("bar.html") is valid
|
||||
|
|
|
@ -2,7 +2,8 @@ Harness status: OK
|
|||
|
||||
Found 17 tests
|
||||
|
||||
17 Fail
|
||||
5 Pass
|
||||
12 Fail
|
||||
Fail Check that src: local(inherit), url(foo.ttf) is valid
|
||||
Fail Check that src: local("myfont"), local(unset) is valid
|
||||
Fail Check that src: local(), url(foo.ttf) is valid
|
||||
|
@ -15,8 +16,8 @@ Fail Check that src: url(foo.ttf) tech(color-COLRv0) otherfunc(othervalue), url(
|
|||
Fail Check that src: url(foo.ttf), url(something.ttf) format(broken) is valid
|
||||
Fail Check that src: /* an empty component */, url(foo.ttf) is valid
|
||||
Fail Check that src: local(""), url(foo.ttf), unparseable-garbage, local("another font name") is valid
|
||||
Fail Check that src: local(), local(initial) is invalid
|
||||
Fail Check that src: local("textfont") format(opentype), local("emoji") tech(color-COLRv0) is invalid
|
||||
Fail Check that src: local(), /*empty*/, url(should be quoted.ttf), junk is invalid
|
||||
Fail Check that src: url(foo.ttf) format(unknown), url(bar.ttf) tech(broken) is invalid
|
||||
Fail Check that src: url(foo.ttf) tech(color-COLRv0) otherfunc(othervalue), junk is invalid
|
||||
Pass Check that src: local(), local(initial) is invalid
|
||||
Pass Check that src: local("textfont") format(opentype), local("emoji") tech(color-COLRv0) is invalid
|
||||
Pass Check that src: local(), /*empty*/, url(should be quoted.ttf), junk is invalid
|
||||
Pass Check that src: url(foo.ttf) format(unknown), url(bar.ttf) tech(broken) is invalid
|
||||
Pass Check that src: url(foo.ttf) tech(color-COLRv0) otherfunc(othervalue), junk is invalid
|
|
@ -2,13 +2,14 @@ Harness status: OK
|
|||
|
||||
Found 18 tests
|
||||
|
||||
18 Fail
|
||||
Fail Check that src: local(A) dummy() is invalid
|
||||
Fail Check that src: dummy() local(A) is invalid
|
||||
Fail Check that src: local( A ) is valid
|
||||
Fail Check that src: local(A B) is valid
|
||||
Fail Check that src: local(A B) is valid
|
||||
Fail Check that src: local( A B ) is valid
|
||||
6 Pass
|
||||
12 Fail
|
||||
Pass Check that src: local(A) dummy() is invalid
|
||||
Pass Check that src: dummy() local(A) is invalid
|
||||
Pass Check that src: local( A ) is valid
|
||||
Pass Check that src: local(A B) is valid
|
||||
Pass Check that src: local(A B) is valid
|
||||
Pass Check that src: local( A B ) is valid
|
||||
Fail Check that src: local(default) is invalid
|
||||
Fail Check that src: local(inherit) is invalid
|
||||
Fail Check that src: local(revert) is invalid
|
||||
|
|
|
@ -2,9 +2,10 @@ Harness status: OK
|
|||
|
||||
Found 39 tests
|
||||
|
||||
39 Fail
|
||||
Fail Check that src: url("foo.ttf") is valid
|
||||
Fail Check that src: url("foo.ttf") tech() is invalid
|
||||
22 Pass
|
||||
17 Fail
|
||||
Pass Check that src: url("foo.ttf") is valid
|
||||
Pass Check that src: url("foo.ttf") tech() is invalid
|
||||
Fail Check that src: url("foo.ttf") tech(features-opentype) is valid
|
||||
Fail Check that src: url("foo.ttf") tech(features-aat) is valid
|
||||
Fail Check that src: url("foo.ttf") tech(color-COLRv0) is valid
|
||||
|
@ -13,32 +14,32 @@ Fail Check that src: url("foo.ttf") tech(color-sbix) is valid
|
|||
Fail Check that src: url("foo.ttf") tech(color-CBDT) is valid
|
||||
Fail Check that src: url("foo.ttf") tech(variations) is valid
|
||||
Fail Check that src: url("foo.ttf") tech(palettes) is valid
|
||||
Fail Check that src: url("foo.ttf") tech("features-opentype") is invalid
|
||||
Fail Check that src: url("foo.ttf") tech("color-COLRv0") is invalid
|
||||
Fail Check that src: url("foo.ttf") tech("variations") is invalid
|
||||
Pass Check that src: url("foo.ttf") tech("features-opentype") is invalid
|
||||
Pass Check that src: url("foo.ttf") tech("color-COLRv0") is invalid
|
||||
Pass Check that src: url("foo.ttf") tech("variations") is invalid
|
||||
Fail Check that src: url("foo.ttf") tech(features-opentype, color-COLRv0, variations, palettes) is valid
|
||||
Fail Check that src: url("foo.ttf") tech(features-opentype color-COLRv0 variations palettes) is invalid
|
||||
Fail Check that src: url("foo.ttf") tech(feature-opentype) is invalid
|
||||
Fail Check that src: url("foo.ttf") tech(feature-aat) is invalid
|
||||
Fail Check that src: url("foo.ttf") tech(feature-graphite) is invalid
|
||||
Fail Check that src: url("foo.ttf") tech(auto) is invalid
|
||||
Fail Check that src: url("foo.ttf") tech(default) is invalid
|
||||
Fail Check that src: url("foo.ttf") tech(inherit) is invalid
|
||||
Fail Check that src: url("foo.ttf") tech(initial) is invalid
|
||||
Fail Check that src: url("foo.ttf") tech(none) is invalid
|
||||
Fail Check that src: url("foo.ttf") tech(normal) is invalid
|
||||
Fail Check that src: url("foo.ttf") tech(xyzzy) is invalid
|
||||
Fail Check that src: url("foo.ttf") tech(xyzzy, features-opentype) is invalid
|
||||
Fail Check that src: url("foo.ttf") tech(features-opentype, xyzzy) is invalid
|
||||
Pass Check that src: url("foo.ttf") tech(features-opentype color-COLRv0 variations palettes) is invalid
|
||||
Pass Check that src: url("foo.ttf") tech(feature-opentype) is invalid
|
||||
Pass Check that src: url("foo.ttf") tech(feature-aat) is invalid
|
||||
Pass Check that src: url("foo.ttf") tech(feature-graphite) is invalid
|
||||
Pass Check that src: url("foo.ttf") tech(auto) is invalid
|
||||
Pass Check that src: url("foo.ttf") tech(default) is invalid
|
||||
Pass Check that src: url("foo.ttf") tech(inherit) is invalid
|
||||
Pass Check that src: url("foo.ttf") tech(initial) is invalid
|
||||
Pass Check that src: url("foo.ttf") tech(none) is invalid
|
||||
Pass Check that src: url("foo.ttf") tech(normal) is invalid
|
||||
Pass Check that src: url("foo.ttf") tech(xyzzy) is invalid
|
||||
Pass Check that src: url("foo.ttf") tech(xyzzy, features-opentype) is invalid
|
||||
Pass Check that src: url("foo.ttf") tech(features-opentype, xyzzy) is invalid
|
||||
Fail Check that src: url("foo.ttf") format(opentype) tech(features-opentype) is valid
|
||||
Fail Check that src: url("foo.ttf") tech(features-opentype) format(opentype) is invalid
|
||||
Pass Check that src: url("foo.ttf") tech(features-opentype) format(opentype) is invalid
|
||||
Fail Check that src: url("foo.ttf") tech(incremental), url("bar.html") is valid
|
||||
Fail Check that src: url("foo.ttf") tech(incremental, color-SVG, features-graphite, features-aat), url("bar.html") is valid
|
||||
Fail Check that src: url("foo.ttf") tech(color-SVG, features-graphite), url("bar.html") is valid
|
||||
Fail Check that src: url("foo.ttf") tech(color-SVG), url("bar.html") is valid
|
||||
Fail Check that src: url("foo.ttf") tech(features-graphite), url("bar.html") is valid
|
||||
Fail Check that src: url("foo.ttf") dummy("opentype") tech(variations) is invalid
|
||||
Fail Check that src: url("foo.ttf") dummy("opentype") dummy(variations) is invalid
|
||||
Fail Check that src: url("foo.ttf") format(opentype) tech(features-opentype) dummy(something) is invalid
|
||||
Pass Check that src: url("foo.ttf") dummy("opentype") tech(variations) is invalid
|
||||
Pass Check that src: url("foo.ttf") dummy("opentype") dummy(variations) is invalid
|
||||
Pass Check that src: url("foo.ttf") format(opentype) tech(features-opentype) dummy(something) is invalid
|
||||
Fail Check that src: url("foo.ttf") format(dummy), url("foo.ttf") tech(variations) is valid
|
||||
Fail Check that src: url("foo.ttf") tech(color), url("bar.html") is valid
|
|
@ -2,7 +2,6 @@ Harness status: OK
|
|||
|
||||
Found 2 tests
|
||||
|
||||
1 Pass
|
||||
1 Fail
|
||||
Pass Simple CSSOM manipulation of subrules
|
||||
2 Fail
|
||||
Fail Simple CSSOM manipulation of subrules
|
||||
Fail Simple CSSOM manipulation of subrules 1
|
Loading…
Add table
Reference in a new issue