1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-08 05:27:14 +09:00

LibWeb/CSS: Parse and use tech() in @font-face { src }

This commit is contained in:
Sam Atkins 2025-06-03 12:32:05 +01:00
parent 5b42f8d707
commit d611806f18
Notes: github-actions[bot] 2025-06-05 11:39:21 +00:00
10 changed files with 131 additions and 35 deletions

View file

@ -258,6 +258,20 @@
"right",
"oblique"
],
"font-tech": [
"avar2",
"color-cbdt",
"color-colrv0",
"color-colrv1",
"color-sbix",
"color-svg",
"features-aat",
"features-graphite",
"features-opentype",
"incremental",
"palettes",
"variations"
],
"font-variant-alternates": [
"normal",
"historical-forms"

View file

@ -534,35 +534,47 @@ bool font_format_is_supported(FlyString const& name)
return false;
}
bool font_tech_is_supported(FlyString const& name)
bool font_tech_is_supported(FontTech font_tech)
{
// https://drafts.csswg.org/css-fonts-4/#font-tech-definitions
// FIXME: Determine this automatically somehow?
if (name.equals_ignoring_ascii_case("features-opentype"sv))
switch (font_tech) {
case FontTech::FeaturesOpentype:
return true;
if (name.equals_ignoring_ascii_case("features-aat"sv))
case FontTech::FeaturesAat:
return false;
if (name.equals_ignoring_ascii_case("features-graphite"sv))
case FontTech::FeaturesGraphite:
return false;
if (name.equals_ignoring_ascii_case("variations"sv))
case FontTech::Variations:
return true;
if (name.equals_ignoring_ascii_case("color-colrv0"sv))
case FontTech::ColorColrv0:
return true;
if (name.equals_ignoring_ascii_case("color-colrv1"sv))
case FontTech::ColorColrv1:
return true;
if (name.equals_ignoring_ascii_case("color-svg"sv))
case FontTech::ColorSvg:
return false;
if (name.equals_ignoring_ascii_case("color-sbix"sv))
case FontTech::ColorSbix:
return false;
if (name.equals_ignoring_ascii_case("color-cbdt"sv))
case FontTech::ColorCbdt:
return false;
if (name.equals_ignoring_ascii_case("palettes"sv))
case FontTech::Palettes:
return false;
if (name.equals_ignoring_ascii_case("incremental"sv))
case FontTech::Incremental:
return false;
// https://drafts.csswg.org/css-fonts-5/#font-tech-definitions
if (name.equals_ignoring_ascii_case("avar2"sv))
case FontTech::Avar2:
return false;
}
return false;
}
bool font_tech_is_supported(FlyString const& name)
{
if (auto keyword = keyword_from_string(name); keyword.has_value()) {
if (auto font_tech = keyword_to_font_tech(*keyword); font_tech.has_value()) {
return font_tech_is_supported(*font_tech);
}
}
return false;
}

View file

@ -115,6 +115,8 @@ private:
};
bool font_format_is_supported(FlyString const& name);
bool font_tech_is_supported(FontTech);
bool font_tech_is_supported(FlyString const& name);
}

View file

@ -85,6 +85,7 @@
"appworkspace",
"auto",
"auto-add",
"avar2",
"b4",
"b5",
"back",
@ -128,7 +129,12 @@
"collapse",
"color",
"color-burn",
"color-cbdt",
"color-colrv0",
"color-colrv1",
"color-dodge",
"color-sbix",
"color-svg",
"column",
"column-reverse",
"common-ligatures",
@ -191,6 +197,9 @@
"false",
"fantasy",
"fast",
"features-aat",
"features-graphite",
"features-opentype",
"field",
"fieldtext",
"fill",
@ -231,6 +240,7 @@
"inactivecaption",
"inactivecaptiontext",
"increasing",
"incremental",
"infinite",
"infinity",
"infobackground",
@ -363,6 +373,7 @@
"padding-box",
"paged",
"paint",
"palettes",
"pan-down",
"pan-left",
"pan-right",
@ -523,6 +534,7 @@
"uppercase",
"upright",
"use-credentials",
"variations",
"vertical-lr",
"vertical-rl",
"vertical-text",

View file

@ -37,11 +37,10 @@ Vector<ParsedFontFace::Source> ParsedFontFace::sources_from_style_value(CSSStyle
auto add_source = [&sources](FontSourceStyleValue const& font_source) {
font_source.source().visit(
[&](FontSourceStyleValue::Local const& local) {
sources.empend(extract_font_name(local.name), OptionalNone {});
sources.empend(extract_font_name(local.name), OptionalNone {}, Vector<FontTech> {});
},
[&](URL const& url) {
// FIXME: tech()
sources.empend(url, font_source.format());
sources.empend(url, font_source.format(), font_source.tech());
});
};

View file

@ -20,8 +20,8 @@ class ParsedFontFace {
public:
struct Source {
Variant<FlyString, URL> local_or_url;
// FIXME: Do we need to keep this around, or is it only needed to discard unwanted formats during parsing?
Optional<FlyString> format;
Vector<FontTech> tech;
};
static Vector<Source> sources_from_style_value(CSSStyleValue const&);

View file

@ -3888,17 +3888,20 @@ RefPtr<FontSourceStyleValue const> Parser::parse_font_source_value(TokenStream<C
TokenStream function_tokens { function.value };
if (auto family_name = parse_family_name_value(function_tokens)) {
transaction.commit();
return FontSourceStyleValue::create(FontSourceStyleValue::Local { family_name.release_nonnull() }, {});
return FontSourceStyleValue::create(FontSourceStyleValue::Local { family_name.release_nonnull() }, {}, {});
}
return nullptr;
}
// <url> [ format(<font-format>)]? [ tech( <font-tech>#)]?
// <url>
auto url = parse_url_function(tokens);
if (!url.has_value())
return nullptr;
Optional<FlyString> format;
Vector<FontTech> tech;
tokens.discard_whitespace();
@ -3920,10 +3923,10 @@ RefPtr<FontSourceStyleValue const> Parser::parse_font_source_value(TokenStream<C
return nullptr;
}
// FIXME: Some of the formats support an optional "-variations" suffix that's really supposed to map to tech(variations).
// Once we support tech(*), we should ensure this propagates correctly.
// NOTE: Some of the formats support an optional "-variations" suffix that's really supposed to map to tech(variations).
if (format_name.is_one_of("woff2-variations"sv, "woff-variations"sv, "truetype-variations"sv, "opentype-variations"sv)) {
format_name = MUST(format_name.to_string().substring_from_byte_offset(0, format_name.bytes().size() - strlen("-variations")));
tech.append(FontTech::Variations);
}
if (!font_format_is_supported(format_name)) {
@ -3942,10 +3945,53 @@ RefPtr<FontSourceStyleValue const> Parser::parse_font_source_value(TokenStream<C
tokens.discard_whitespace();
// FIXME: [ tech( <font-tech>#)]?
// [ tech( <font-tech>#)]?
if (tokens.next_token().is_function("tech"sv)) {
auto const& function = tokens.consume_a_token().function();
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function.name });
TokenStream function_tokens { function.value };
auto tech_items = parse_a_comma_separated_list_of_component_values(function_tokens);
if (tech_items.is_empty()) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: font source invalid (`tech()` has no arguments); discarding.");
return nullptr;
}
for (auto const& tech_item : tech_items) {
TokenStream tech_tokens { tech_item };
tech_tokens.discard_whitespace();
auto& ident_token = tech_tokens.consume_a_token();
if (!ident_token.is(Token::Type::Ident)) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: font source invalid (`tech()` parameters must be idents, got: {}); discarding.", ident_token.to_debug_string());
return nullptr;
}
tech_tokens.discard_whitespace();
if (tech_tokens.has_next_token()) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: font source invalid (`tech()` has trailing tokens); discarding.");
return nullptr;
}
auto& font_tech_name = ident_token.token().ident();
if (auto keyword = keyword_from_string(font_tech_name); keyword.has_value()) {
if (auto font_tech = keyword_to_font_tech(*keyword); font_tech.has_value()) {
if (!font_tech_is_supported(*font_tech)) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: font source tech({}) not supported; skipping.", font_tech_name);
return nullptr;
}
tech.append(font_tech.release_value());
continue;
}
}
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: font source invalid (`{}` is not a supported value in `tech()`); discarding.", font_tech_name);
return nullptr;
}
}
transaction.commit();
return FontSourceStyleValue::create(url.release_value(), move(format));
return FontSourceStyleValue::create(url.release_value(), move(format), move(tech));
}
NonnullRefPtr<CSSStyleValue const> Parser::resolve_unresolved_style_value(ParsingParams const& context, DOM::Element& element, Optional<PseudoElement> pseudo_element, PropertyID property_id, UnresolvedStyleValue const& unresolved)

View file

@ -9,10 +9,11 @@
namespace Web::CSS {
FontSourceStyleValue::FontSourceStyleValue(Source source, Optional<FlyString> format)
FontSourceStyleValue::FontSourceStyleValue(Source source, Optional<FlyString> format, Vector<FontTech> tech)
: StyleValueWithDefaultOperators(Type::FontSource)
, m_source(move(source))
, m_format(move(format))
, m_tech(move(tech))
{
}
@ -36,7 +37,6 @@ String FontSourceStyleValue::to_string(SerializationMode) const
},
[this](URL const& url) {
// <url> [ format(<font-format>)]? [ tech( <font-tech>#)]?
// FIXME: tech()
StringBuilder builder;
builder.append(url.to_string());
@ -46,6 +46,14 @@ String FontSourceStyleValue::to_string(SerializationMode) const
builder.append(")"sv);
}
if (!m_tech.is_empty()) {
builder.append(" tech("sv);
serialize_a_comma_separated_list(builder, m_tech, [](auto& builder, FontTech const tech) {
return builder.append(CSS::to_string(tech));
});
builder.append(")"sv);
}
return builder.to_string_without_validation();
});
}

View file

@ -8,6 +8,7 @@
#include <AK/FlyString.h>
#include <LibWeb/CSS/CSSStyleValue.h>
#include <LibWeb/CSS/Enums.h>
#include <LibWeb/CSS/URL.h>
namespace Web::CSS {
@ -19,24 +20,26 @@ public:
};
using Source = Variant<Local, URL>;
static ValueComparingNonnullRefPtr<FontSourceStyleValue const> create(Source source, Optional<FlyString> format)
static ValueComparingNonnullRefPtr<FontSourceStyleValue const> create(Source source, Optional<FlyString> format, Vector<FontTech> tech)
{
return adopt_ref(*new (nothrow) FontSourceStyleValue(move(source), move(format)));
return adopt_ref(*new (nothrow) FontSourceStyleValue(move(source), move(format), move(tech)));
}
virtual ~FontSourceStyleValue() override;
Source const& source() const { return m_source; }
Optional<FlyString> const& format() const { return m_format; }
Vector<FontTech> const& tech() const { return m_tech; }
virtual String to_string(SerializationMode) const override;
bool properties_equal(FontSourceStyleValue const&) const;
private:
FontSourceStyleValue(Source source, Optional<FlyString> format);
FontSourceStyleValue(Source source, Optional<FlyString> format, Vector<FontTech> tech);
Source m_source;
Optional<FlyString> m_format;
Vector<FontTech> m_tech;
};
}

View file

@ -2,17 +2,17 @@ Harness status: OK
Found 39 tests
28 Pass
11 Fail
34 Pass
5 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
Pass 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
Fail Check that src: url("foo.ttf") tech(color-COLRv1) is valid
Pass Check that src: url("foo.ttf") tech(color-COLRv0) is valid
Pass Check that src: url("foo.ttf") tech(color-COLRv1) is valid
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
Pass Check that src: url("foo.ttf") tech(variations) is valid
Fail Check that src: url("foo.ttf") tech(palettes) is valid
Pass Check that src: url("foo.ttf") tech("features-opentype") is invalid
Pass Check that src: url("foo.ttf") tech("color-COLRv0") is invalid
@ -31,7 +31,7 @@ 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
Pass Check that src: url("foo.ttf") format(opentype) tech(features-opentype) is valid
Pass Check that src: url("foo.ttf") tech(features-opentype) format(opentype) is invalid
Pass Check that src: url("foo.ttf") tech(incremental), url("bar.html") is valid
Pass Check that src: url("foo.ttf") tech(incremental, color-SVG, features-graphite, features-aat), url("bar.html") is valid
@ -41,5 +41,5 @@ Pass Check that src: url("foo.ttf") tech(features-graphite), url("bar.html") is
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
Pass Check that src: url("foo.ttf") format(dummy), url("foo.ttf") tech(variations) is valid
Pass Check that src: url("foo.ttf") tech(color), url("bar.html") is valid