mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 15:17:36 +00:00
LibWeb: Understand the format()
part of a @font-face
's src
This is used to skip downloading fonts in formats that we don't support. Currently we only support TTF as far as I am aware. The parts of a `src` are in a fixed order, unusually, which makes the parsing more nesty instead of loopy.
This commit is contained in:
parent
dbb0b68175
commit
12b8570ce3
4 changed files with 91 additions and 28 deletions
|
@ -16,6 +16,8 @@ class FontFace {
|
||||||
public:
|
public:
|
||||||
struct Source {
|
struct Source {
|
||||||
AK::URL url;
|
AK::URL url;
|
||||||
|
// FIXME: Do we need to keep this around, or is it only needed to discard unwanted formats during parsing?
|
||||||
|
Optional<FlyString> format;
|
||||||
};
|
};
|
||||||
|
|
||||||
FontFace(FlyString font_family, Vector<Source> sources, Vector<UnicodeRange> unicode_ranges);
|
FontFace(FlyString font_family, Vector<Source> sources, Vector<UnicodeRange> unicode_ranges);
|
||||||
|
|
|
@ -4438,34 +4438,10 @@ RefPtr<CSSRule> Parser::parse_font_face_rule(TokenStream<ComponentValue>& tokens
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (declaration.name().equals_ignoring_case("src"sv)) {
|
if (declaration.name().equals_ignoring_case("src"sv)) {
|
||||||
Vector<FontFace::Source> supported_sources;
|
|
||||||
// FIXME: Implement `local()`.
|
|
||||||
// FIXME: Implement `format()`.
|
|
||||||
TokenStream token_stream { declaration.values() };
|
TokenStream token_stream { declaration.values() };
|
||||||
auto list_of_source_token_lists = parse_a_comma_separated_list_of_component_values(token_stream);
|
Vector<FontFace::Source> supported_sources = parse_font_face_src(token_stream);
|
||||||
for (auto const& source_token_list : list_of_source_token_lists) {
|
if (!supported_sources.is_empty())
|
||||||
Optional<AK::URL> url;
|
src = move(supported_sources);
|
||||||
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);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (declaration.name().equals_ignoring_case("unicode-range"sv)) {
|
if (declaration.name().equals_ignoring_case("unicode-range"sv)) {
|
||||||
|
@ -4506,6 +4482,89 @@ RefPtr<CSSRule> Parser::parse_font_face_rule(TokenStream<ComponentValue>& tokens
|
||||||
return CSSFontFaceRule::create(FontFace { font_family.release_value(), move(src), move(unicode_range) });
|
return CSSFontFaceRule::create(FontFace { font_family.release_value(), move(src), move(unicode_range) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector<FontFace::Source> Parser::parse_font_face_src(TokenStream<ComponentValue>& component_values)
|
||||||
|
{
|
||||||
|
// FIXME: Get this information from the system somehow?
|
||||||
|
// Format-name table: https://www.w3.org/TR/css-fonts-4/#font-format-definitions
|
||||||
|
auto font_format_is_supported = [](StringView name) {
|
||||||
|
// The spec requires us to treat opentype and truetype as synonymous.
|
||||||
|
if (name.is_one_of_ignoring_case("opentype"sv, "truetype"sv))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<FontFace::Source> supported_sources;
|
||||||
|
|
||||||
|
auto list_of_source_token_lists = parse_a_comma_separated_list_of_component_values(component_values);
|
||||||
|
for (auto const& source_token_list : list_of_source_token_lists) {
|
||||||
|
TokenStream source_tokens { source_token_list };
|
||||||
|
source_tokens.skip_whitespace();
|
||||||
|
auto& first = source_tokens.next_token();
|
||||||
|
|
||||||
|
// <url> [ format(<font-format>)]?
|
||||||
|
// FIXME: Allow data urls for fonts.
|
||||||
|
// FIXME: Implement optional tech() function from CSS-Fonts-4.
|
||||||
|
if (auto maybe_url = parse_url_function(first); maybe_url.has_value()) {
|
||||||
|
auto url = maybe_url.release_value();
|
||||||
|
Optional<FlyString> format;
|
||||||
|
|
||||||
|
source_tokens.skip_whitespace();
|
||||||
|
if (!source_tokens.has_next_token()) {
|
||||||
|
supported_sources.empend(move(url), format);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto maybe_function = source_tokens.next_token();
|
||||||
|
if (!maybe_function.is_function()) {
|
||||||
|
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (token after `url()` that isn't a function: {}); discarding.", maybe_function.to_debug_string());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& function = maybe_function.function();
|
||||||
|
if (function.name().equals_ignoring_case("format"sv)) {
|
||||||
|
TokenStream format_tokens { function.values() };
|
||||||
|
format_tokens.skip_whitespace();
|
||||||
|
auto& format_name_token = format_tokens.next_token();
|
||||||
|
StringView format_name;
|
||||||
|
if (format_name_token.is(Token::Type::Ident)) {
|
||||||
|
format_name = format_name_token.token().ident();
|
||||||
|
} else if (format_name_token.is(Token::Type::String)) {
|
||||||
|
format_name = format_name_token.token().string();
|
||||||
|
} else {
|
||||||
|
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (`format()` parameter not an ident or string; is: {}); discarding.", format_name_token.to_debug_string());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!font_format_is_supported(format_name)) {
|
||||||
|
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src format({}) not supported; skipping.", format_name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
format = format_name;
|
||||||
|
} else {
|
||||||
|
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (unrecognized function token `{}`); discarding.", function.name());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
source_tokens.skip_whitespace();
|
||||||
|
if (source_tokens.has_next_token()) {
|
||||||
|
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (extra token `{}`); discarding.", source_tokens.peek_token().to_debug_string());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
supported_sources.empend(move(url), format);
|
||||||
|
continue;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// FIXME: Implement `local()`.
|
||||||
|
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face src invalid (failed to parse url from: {}); discarding.", first.to_debug_string());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return supported_sources;
|
||||||
|
}
|
||||||
|
|
||||||
RefPtr<StyleValue> Parser::parse_list_style_value(Vector<ComponentValue> const& component_values)
|
RefPtr<StyleValue> Parser::parse_list_style_value(Vector<ComponentValue> const& component_values)
|
||||||
{
|
{
|
||||||
if (component_values.size() > 3)
|
if (component_values.size() > 3)
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <AK/Result.h>
|
#include <AK/Result.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
#include <LibWeb/CSS/CSSStyleDeclaration.h>
|
#include <LibWeb/CSS/CSSStyleDeclaration.h>
|
||||||
|
#include <LibWeb/CSS/FontFace.h>
|
||||||
#include <LibWeb/CSS/GeneralEnclosed.h>
|
#include <LibWeb/CSS/GeneralEnclosed.h>
|
||||||
#include <LibWeb/CSS/MediaQuery.h>
|
#include <LibWeb/CSS/MediaQuery.h>
|
||||||
#include <LibWeb/CSS/Parser/ComponentValue.h>
|
#include <LibWeb/CSS/Parser/ComponentValue.h>
|
||||||
|
@ -204,6 +205,7 @@ private:
|
||||||
Optional<GeneralEnclosed> parse_general_enclosed(TokenStream<ComponentValue>&);
|
Optional<GeneralEnclosed> parse_general_enclosed(TokenStream<ComponentValue>&);
|
||||||
|
|
||||||
RefPtr<CSSRule> parse_font_face_rule(TokenStream<ComponentValue>&);
|
RefPtr<CSSRule> parse_font_face_rule(TokenStream<ComponentValue>&);
|
||||||
|
Vector<FontFace::Source> parse_font_face_src(TokenStream<ComponentValue>&);
|
||||||
|
|
||||||
RefPtr<CSSRule> convert_to_rule(NonnullRefPtr<StyleRule>);
|
RefPtr<CSSRule> convert_to_rule(NonnullRefPtr<StyleRule>);
|
||||||
RefPtr<PropertyOwningCSSStyleDeclaration> convert_to_style_declaration(Vector<DeclarationOrAtRule> declarations);
|
RefPtr<PropertyOwningCSSStyleDeclaration> convert_to_style_declaration(Vector<DeclarationOrAtRule> declarations);
|
||||||
|
|
|
@ -578,7 +578,7 @@ void dump_font_face_rule(StringBuilder& builder, CSS::CSSFontFaceRule const& rul
|
||||||
builder.append("sources:\n");
|
builder.append("sources:\n");
|
||||||
for (auto const& source : font_face.sources()) {
|
for (auto const& source : font_face.sources()) {
|
||||||
indent(builder, indent_levels + 2);
|
indent(builder, indent_levels + 2);
|
||||||
builder.appendff("{}\n", source.url);
|
builder.appendff("url={}, format={}\n", source.url, source.format.value_or("???"));
|
||||||
}
|
}
|
||||||
|
|
||||||
indent(builder, indent_levels + 1);
|
indent(builder, indent_levels + 1);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue