diff options
author | Sam Atkins <atkinssj@serenityos.org> | 2022-03-28 20:32:28 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-03-28 22:25:25 +0200 |
commit | 6672c19c93e94f1c223b8d72c2640e4a931da889 (patch) | |
tree | 9fdb476af455e193ef4b712894df76de085b281d /Userland/Libraries/LibWeb/CSS/Parser | |
parent | 804b8c85e8ff12ce8dd489b41d8626426c9f5488 (diff) | |
download | serenity-6672c19c93e94f1c223b8d72c2640e4a931da889.zip |
LibWeb: Parse `@font-face` rules
This is very limited for now, only caring about `font-family` and `src`.
Diffstat (limited to 'Userland/Libraries/LibWeb/CSS/Parser')
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp | 175 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.h | 3 |
2 files changed, 146 insertions, 32 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 4756203878..e85c4c2991 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -11,6 +11,7 @@ #include <AK/Debug.h> #include <AK/NonnullRefPtrVector.h> #include <AK/SourceLocation.h> +#include <LibWeb/CSS/CSSFontFaceRule.h> #include <LibWeb/CSS/CSSImportRule.h> #include <LibWeb/CSS/CSSMediaRule.h> #include <LibWeb/CSS/CSSStyleDeclaration.h> @@ -2054,23 +2055,13 @@ RefPtr<CSSRule> Parser::convert_to_rule(NonnullRefPtr<StyleRule> rule) if (rule->m_type == StyleRule::Type::At) { if (has_ignored_vendor_prefix(rule->m_name)) { return {}; - } else if (rule->m_name.equals_ignoring_case("media"sv)) { - - auto media_query_tokens = TokenStream { rule->prelude() }; - auto media_query_list = parse_a_media_query_list(media_query_tokens); - if (media_query_list.is_empty() || !rule->block()) + } else if (rule->m_name.equals_ignoring_case("font-face"sv)) { + if (rule->prelude().is_empty() || !rule->block()->is_curly()) { + dbgln_if(CSS_PARSER_DEBUG, "@font-face rule is malformed."); return {}; - - auto child_tokens = TokenStream { rule->block()->values() }; - auto parser_rules = consume_a_list_of_rules(child_tokens, false); - NonnullRefPtrVector<CSSRule> child_rules; - for (auto& raw_rule : parser_rules) { - if (auto child_rule = convert_to_rule(raw_rule)) - child_rules.append(*child_rule); } - - return CSSMediaRule::create(MediaList::create(move(media_query_list)), move(child_rules)); - + TokenStream tokens { rule->block()->values() }; + return parse_font_face_rule(tokens); } else if (rule->m_name.equals_ignoring_case("import"sv) && !rule->prelude().is_empty()) { Optional<AK::URL> url; @@ -2094,6 +2085,23 @@ RefPtr<CSSRule> Parser::convert_to_rule(NonnullRefPtr<StyleRule> rule) else dbgln_if(CSS_PARSER_DEBUG, "Unable to parse url from @import rule"); + } else if (rule->m_name.equals_ignoring_case("media"sv)) { + + auto media_query_tokens = TokenStream { rule->prelude() }; + auto media_query_list = parse_a_media_query_list(media_query_tokens); + if (media_query_list.is_empty() || !rule->block()) + return {}; + + auto child_tokens = TokenStream { rule->block()->values() }; + auto parser_rules = consume_a_list_of_rules(child_tokens, false); + NonnullRefPtrVector<CSSRule> child_rules; + for (auto& raw_rule : parser_rules) { + if (auto child_rule = convert_to_rule(raw_rule)) + child_rules.append(*child_rule); + } + + return CSSMediaRule::create(MediaList::create(move(media_query_list)), move(child_rules)); + } else if (rule->m_name.equals_ignoring_case("supports"sv)) { auto supports_tokens = TokenStream { rule->prelude() }; @@ -3610,6 +3618,24 @@ RefPtr<StyleValue> Parser::parse_flex_flow_value(Vector<StyleComponentValueRule> return FlexFlowStyleValue::create(flex_direction.release_nonnull(), flex_wrap.release_nonnull()); } +static bool is_generic_font_family(ValueID identifier) +{ + switch (identifier) { + case ValueID::Cursive: + case ValueID::Fantasy: + case ValueID::Monospace: + case ValueID::Serif: + case ValueID::SansSerif: + case ValueID::UiMonospace: + case ValueID::UiRounded: + case ValueID::UiSerif: + case ValueID::UiSansSerif: + return true; + default: + return false; + } +} + RefPtr<StyleValue> Parser::parse_font_value(Vector<StyleComponentValueRule> const& component_values) { RefPtr<StyleValue> font_style; @@ -3703,23 +3729,6 @@ RefPtr<StyleValue> Parser::parse_font_value(Vector<StyleComponentValueRule> cons RefPtr<StyleValue> Parser::parse_font_family_value(Vector<StyleComponentValueRule> const& component_values, size_t start_index) { - auto is_generic_font_family = [](ValueID identifier) -> bool { - switch (identifier) { - case ValueID::Cursive: - case ValueID::Fantasy: - case ValueID::Monospace: - case ValueID::Serif: - case ValueID::SansSerif: - case ValueID::UiMonospace: - case ValueID::UiRounded: - case ValueID::UiSerif: - case ValueID::UiSansSerif: - return true; - default: - return false; - } - }; - auto is_comma_or_eof = [&](size_t i) -> bool { if (i < component_values.size()) { auto& maybe_comma = component_values[i]; @@ -3791,6 +3800,101 @@ RefPtr<StyleValue> Parser::parse_font_family_value(Vector<StyleComponentValueRul return StyleValueList::create(move(font_families), StyleValueList::Separator::Comma); } +RefPtr<CSSRule> Parser::parse_font_face_rule(TokenStream<StyleComponentValueRule>& tokens) +{ + auto declarations_and_at_rules = parse_a_list_of_declarations(tokens); + + Optional<FlyString> font_family; + Vector<FontFace::Source> src; + + for (auto& declaration_or_at_rule : declarations_and_at_rules) { + if (declaration_or_at_rule.is_at_rule()) { + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: CSS at-rules are not allowed in @font-family; discarding."); + continue; + } + + auto& declaration = declaration_or_at_rule.declaration(); + if (declaration.m_name.equals_ignoring_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<String> font_family_parts; + bool had_syntax_error = false; + for (size_t i = 0; i < declaration.m_values.size(); ++i) { + auto& part = declaration.m_values[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)) { + auto value_id = value_id_from_string(part.token().ident()); + if (is_generic_font_family(value_id)) { + 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()) + continue; + + font_family = String::join(' ', font_family_parts); + continue; + } + if (declaration.m_name.equals_ignoring_case("src"sv)) { + Vector<FontFace::Source> supported_sources; + // FIXME: Implement `local()`. + // FIXME: Implement `format()`. + TokenStream token_stream { declaration.m_values }; + auto list_of_source_token_lists = parse_a_comma_separated_list_of_component_values(token_stream); + for (auto const& source_token_list : list_of_source_token_lists) { + Optional<AK::URL> url; + bool had_syntax_error = false; + for (auto const& source_token : source_token_list) { + // FIXME: Allow data urls for fonts. + if (auto maybe_url = parse_url_function(source_token); maybe_url.has_value()) { + if (url.has_value()) { + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src format invalid; discarding."); + had_syntax_error = true; + break; + } + url = maybe_url.release_value(); + } + } + if (had_syntax_error) + continue; + if (!url.has_value()) + continue; + supported_sources.empend(url.release_value()); + } + if (supported_sources.is_empty()) + continue; + src = move(supported_sources); + } + + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unrecognized descriptor '{}' in @font-family; discarding.", declaration.m_name); + } + + if (!font_family.has_value()) { + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face: no font-family!"); + return {}; + } + + return CSSFontFaceRule::create(FontFace { font_family.release_value(), move(src) }); +} + RefPtr<StyleValue> Parser::parse_list_style_value(Vector<StyleComponentValueRule> const& component_values) { if (component_values.size() > 3) @@ -4955,6 +5059,13 @@ bool Parser::has_ignored_vendor_prefix(StringView string) return true; } +bool Parser::is_builtin(StringView name) +{ + return name.equals_ignoring_case("inherit"sv) + || name.equals_ignoring_case("initial"sv) + || name.equals_ignoring_case("unset"sv); +} + RefPtr<StyleValue> Parser::parse_css_value(Badge<StyleComputer>, ParsingContext const& context, PropertyID property_id, Vector<StyleComponentValueRule> const& tokens) { if (tokens.is_empty() || property_id == CSS::PropertyID::Invalid || property_id == CSS::PropertyID::Custom) diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index c417bb6778..786619b029 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -188,6 +188,8 @@ private: [[nodiscard]] Optional<GeneralEnclosed> parse_general_enclosed(TokenStream<StyleComponentValueRule>&); + RefPtr<CSSRule> parse_font_face_rule(TokenStream<StyleComponentValueRule>&); + [[nodiscard]] RefPtr<CSSRule> convert_to_rule(NonnullRefPtr<StyleRule>); [[nodiscard]] RefPtr<PropertyOwningCSSStyleDeclaration> convert_to_style_declaration(Vector<DeclarationOrAtRule> declarations); [[nodiscard]] Optional<StyleProperty> convert_to_style_property(StyleDeclarationRule const&); @@ -339,6 +341,7 @@ private: Optional<Supports::Feature> parse_supports_feature(TokenStream<StyleComponentValueRule>&); static bool has_ignored_vendor_prefix(StringView); + static bool is_builtin(StringView); ParsingContext m_context; |