diff --git a/Base/res/html/misc/fonts.html b/Base/res/html/misc/fonts.html
index 1218ef8996..be7beb981d 100644
--- a/Base/res/html/misc/fonts.html
+++ b/Base/res/html/misc/fonts.html
@@ -6,8 +6,10 @@
#monospace { font: 20px monospace; }
#a { font: 12pt/14pt sans-serif; }
#b { font: 80% cursive; }
+ #b2 { font: bold 80% cursive; }
#c { font: x-large/110% fantasy, serif; }
#d { font: 2em SerenitySans; }
+ #d2 { font: 400 2em SerenitySans; }
#e { font: bold italic large Helvetica, sans-serif; }
#f { font: normal small-caps 120%/120% monospace; }
#g { font: condensed oblique 12pt "Helvetica Neue", serif; }
@@ -18,8 +20,10 @@
font: 20px monospace;
font: 12pt/14pt sans-serif;
font: 80% cursive;
+ font: bold 80% cursive;
font: x-large/110% fantasy, serif;
font: 2em SerenitySans;
+ font: 400 2em SerenitySans;
font: bold italic large Helvetica, sans-serif;
font: normal small-caps 120%/120% monospace;
font: condensed oblique 12pt "Helvetica Neue", serif;
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
index ccce9c5bff..0553a43f69 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
@@ -1787,6 +1787,180 @@ RefPtr Parser::parse_box_shadow_value(ParsingContext const& context,
return BoxShadowStyleValue::create(offset_x, offset_y, blur_radius, color);
}
+RefPtr Parser::parse_font_value(ParsingContext const& context, Vector const& component_values)
+{
+ auto is_font_size = [](StyleValue const& value) -> bool {
+ if (value.is_length())
+ return true;
+ switch (value.to_identifier()) {
+ case ValueID::XxSmall:
+ case ValueID::XSmall:
+ case ValueID::Small:
+ case ValueID::Medium:
+ case ValueID::Large:
+ case ValueID::XLarge:
+ case ValueID::XxLarge:
+ case ValueID::XxxLarge:
+ case ValueID::Smaller:
+ case ValueID::Larger:
+ return true;
+ default:
+ return false;
+ }
+ };
+
+ auto is_font_style = [](StyleValue const& value) -> bool {
+ // FIXME: Handle angle parameter to `oblique`: https://www.w3.org/TR/css-fonts-4/#font-style-prop
+ switch (value.to_identifier()) {
+ case ValueID::Normal:
+ case ValueID::Italic:
+ case ValueID::Oblique:
+ return true;
+ default:
+ return false;
+ }
+ };
+
+ auto is_font_weight = [](StyleValue const& value) -> bool {
+ if (value.is_numeric()) {
+ auto weight = static_cast(value).value();
+ return (weight >= 1 && weight <= 1000);
+ }
+ switch (value.to_identifier()) {
+ case ValueID::Normal:
+ case ValueID::Bold:
+ case ValueID::Bolder:
+ case ValueID::Lighter:
+ return true;
+ default:
+ return false;
+ }
+ };
+
+ auto is_line_height = [](StyleValue const& value) -> bool {
+ if (value.is_numeric())
+ return true;
+ if (value.is_length())
+ return true;
+ if (value.to_identifier() == ValueID::Normal)
+ return true;
+ return false;
+ };
+
+ auto is_font_family = [](StyleValue const& value) -> bool {
+ if (value.is_string())
+ return true;
+ switch (value.to_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 font_style;
+ RefPtr font_weight;
+ RefPtr font_size;
+ RefPtr line_height;
+ NonnullRefPtrVector font_families;
+ // FIXME: Implement font-stretch and font-variant.
+
+ // FIXME: Handle system fonts. (caption, icon, menu, message-box, small-caption, status-bar)
+
+ // Several sub-properties can be "normal", and appear in any order: style, variant, weight, stretch
+ // So, we have to handle that separately.
+ int normal_count = 0;
+
+ for (size_t i = 0; i < component_values.size(); ++i) {
+ auto value = parse_css_value(context, PropertyID::Font, component_values[i]);
+ if (!value)
+ return nullptr;
+
+ if (value->to_identifier() == ValueID::Normal) {
+ normal_count++;
+ continue;
+ }
+ if (is_font_style(*value)) {
+ if (font_style)
+ return nullptr;
+ font_style = value.release_nonnull();
+ continue;
+ }
+ if (is_font_weight(*value)) {
+ if (font_weight)
+ return nullptr;
+ font_weight = value.release_nonnull();
+ continue;
+ }
+ if (is_font_size(*value)) {
+ if (font_size)
+ return nullptr;
+ font_size = value.release_nonnull();
+
+ // Consume `/ line-height` if present
+ if (i + 2 < component_values.size()) {
+ auto maybe_solidus = component_values[i + 1];
+ if (maybe_solidus.is(Token::Type::Delim) && maybe_solidus.token().delim() == "/"sv) {
+ auto maybe_line_height = parse_css_value(context, PropertyID::Font, component_values[i + 2]);
+ if (!(maybe_line_height && is_line_height(*maybe_line_height)))
+ return nullptr;
+ line_height = maybe_line_height.release_nonnull();
+ i += 2;
+ }
+ }
+
+ // Consume font-family
+ // FIXME: Handle multiple font-families separated by commas, for fallback purposes.
+ if (i + 1 < component_values.size()) {
+ auto& font_family_part = component_values[i + 1];
+ auto maybe_font_family = parse_css_value(context, PropertyID::Font, font_family_part);
+ if (!maybe_font_family) {
+ // Single-word font-families may not be quoted. We convert it to a String for convenience.
+ if (font_family_part.is(Token::Type::Ident))
+ maybe_font_family = StringStyleValue::create(font_family_part.token().ident());
+ else
+ return nullptr;
+ } else if (!is_font_family(*maybe_font_family)) {
+ dbgln("Unable to parse '{}' as a font-family.", font_family_part.to_debug_string());
+ return nullptr;
+ }
+
+ font_families.append(maybe_font_family.release_nonnull());
+ }
+ break;
+ }
+
+ return nullptr;
+ }
+
+ // Since normal is the default value for all the properties that can have it, we don't have to actually
+ // set anything to normal here. It'll be set when we create the FontStyleValue below.
+ // We just need to make sure we were not given more normals than will fit.
+ int unset_value_count = (font_style ? 1 : 0) + (font_weight ? 1 : 0);
+ if (unset_value_count < normal_count)
+ return nullptr;
+
+ if (!font_size || font_families.is_empty())
+ return nullptr;
+
+ if (!font_style)
+ font_style = IdentifierStyleValue::create(ValueID::Normal);
+ if (!font_weight)
+ font_weight = IdentifierStyleValue::create(ValueID::Normal);
+ if (!line_height)
+ line_height = IdentifierStyleValue::create(ValueID::Normal);
+
+ return FontStyleValue::create(font_style.release_nonnull(), font_weight.release_nonnull(), font_size.release_nonnull(), line_height.release_nonnull(), move(font_families));
+}
+
RefPtr Parser::parse_as_css_value(PropertyID property_id)
{
auto component_values = parse_as_list_of_component_values();
@@ -1816,9 +1990,17 @@ RefPtr Parser::parse_css_value(PropertyID property_id, TokenStream parse_string_value(ParsingContext const&, StyleComponentValueRule const&);
static RefPtr parse_image_value(ParsingContext const&, StyleComponentValueRule const&);
static RefPtr parse_box_shadow_value(ParsingContext const&, Vector const&);
+ static RefPtr parse_font_value(ParsingContext const&, Vector const&);
// calc() parsing, according to https://www.w3.org/TR/css-values-3/#calc-syntax
static OwnPtr parse_calc_sum(ParsingContext const&, TokenStream&);
diff --git a/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp b/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp
index 6a89b6af26..f86dd6d7a2 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp
+++ b/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp
@@ -326,76 +326,6 @@ static inline bool is_font_family(StyleValue const& value)
}
}
-static inline bool is_font_size(StyleValue const& value)
-{
- if (value.is_builtin_or_dynamic())
- return true;
- if (value.is_length())
- return true;
- switch (value.to_identifier()) {
- case ValueID::XxSmall:
- case ValueID::XSmall:
- case ValueID::Small:
- case ValueID::Medium:
- case ValueID::Large:
- case ValueID::XLarge:
- case ValueID::XxLarge:
- case ValueID::XxxLarge:
- case ValueID::Smaller:
- case ValueID::Larger:
- return true;
- default:
- return false;
- }
-}
-
-static inline bool is_font_style(StyleValue const& value)
-{
- // FIXME: Handle angle parameter to `oblique`: https://www.w3.org/TR/css-fonts-4/#font-style-prop
- if (value.is_builtin_or_dynamic())
- return true;
- switch (value.to_identifier()) {
- case ValueID::Normal:
- case ValueID::Italic:
- case ValueID::Oblique:
- return true;
- default:
- return false;
- }
-}
-
-static inline bool is_font_weight(StyleValue const& value)
-{
- if (value.is_builtin_or_dynamic())
- return true;
- if (value.is_numeric()) {
- auto weight = static_cast(value).value();
- return (weight >= 1 && weight <= 1000);
- }
- switch (value.to_identifier()) {
- case ValueID::Normal:
- case ValueID::Bold:
- case ValueID::Bolder:
- case ValueID::Lighter:
- return true;
- default:
- return false;
- }
-}
-
-static inline bool is_line_height(StyleValue const& value)
-{
- if (value.is_builtin_or_dynamic())
- return true;
- if (value.is_numeric())
- return true;
- if (value.is_length())
- return true;
- if (value.to_identifier() == ValueID::Normal)
- return true;
- return false;
-}
-
static inline bool is_line_style(StyleValue const& value)
{
if (value.is_builtin_or_dynamic())
@@ -1096,103 +1026,27 @@ static void set_property_expanding_shorthands(StyleProperties& style, CSS::Prope
}
if (property_id == CSS::PropertyID::Font) {
- if (value.is_component_value_list()) {
- auto parts = static_cast(value).values();
-
- RefPtr font_style_value;
- RefPtr font_weight_value;
- RefPtr font_size_value;
- RefPtr line_height_value;
- RefPtr font_family_value;
- // FIXME: Implement font-stretch and font-variant.
-
- for (size_t i = 0; i < parts.size(); ++i) {
- auto value = Parser::parse_css_value(context, property_id, parts[i]);
- if (!value)
- return;
-
- if (is_font_style(*value)) {
- if (font_style_value)
- return;
- font_style_value = move(value);
- continue;
- }
- if (is_font_weight(*value)) {
- if (font_weight_value)
- return;
- font_weight_value = move(value);
- continue;
- }
- if (is_font_size(*value)) {
- if (font_size_value)
- return;
- font_size_value = move(value);
-
- // Consume `/ line-height` if present
- if (i + 2 < parts.size()) {
- auto solidus_part = parts[i + 1];
- if (!(solidus_part.is(Token::Type::Delim) && solidus_part.token().delim() == "/"sv))
- break;
- auto line_height = Parser::parse_css_value(context, property_id, parts[i + 2]);
- if (!(line_height && is_line_height(*line_height)))
- return;
- line_height_value = move(line_height);
- i += 2;
- }
-
- // Consume font-family
- // FIXME: Handle multiple font-families separated by commas, for fallback purposes.
- if (i + 1 < parts.size()) {
- auto& font_family_part = parts[i + 1];
- auto font_family = Parser::parse_css_value(context, property_id, font_family_part);
- if (!font_family) {
- // Single-word font-families may not be quoted. We convert it to a String for convenience.
- if (font_family_part.is(Token::Type::Ident))
- font_family = StringStyleValue::create(font_family_part.token().ident());
- else
- return;
- } else if (!is_font_family(*font_family)) {
- dbgln("*** Unable to parse '{}' as a font-family.", font_family_part.to_debug_string());
- return;
- }
-
- font_family_value = move(font_family);
- }
- break;
- }
-
- return;
- }
-
- if (!font_size_value || !font_family_value)
- return;
-
- style.set_property(CSS::PropertyID::FontSize, *font_size_value);
- style.set_property(CSS::PropertyID::FontFamily, *font_family_value);
-
- if (font_style_value)
- style.set_property(CSS::PropertyID::FontStyle, *font_style_value);
- if (font_weight_value)
- style.set_property(CSS::PropertyID::FontWeight, *font_weight_value);
- if (line_height_value)
- style.set_property(CSS::PropertyID::LineHeight, *line_height_value);
-
+ if (value.is_font()) {
+ auto& font_shorthand = static_cast(value);
+ style.set_property(CSS::PropertyID::FontSize, font_shorthand.font_size());
+ // FIXME: Support multiple font-families
+ style.set_property(CSS::PropertyID::FontFamily, font_shorthand.font_families().first());
+ style.set_property(CSS::PropertyID::FontStyle, font_shorthand.font_style());
+ style.set_property(CSS::PropertyID::FontWeight, font_shorthand.font_weight());
+ style.set_property(CSS::PropertyID::LineHeight, font_shorthand.line_height());
+ // FIXME: Implement font-stretch and font-variant
return;
}
-
- if (value.is_inherit()) {
+ if (value.is_builtin()) {
style.set_property(CSS::PropertyID::FontSize, value);
+ // FIXME: Support multiple font-families
style.set_property(CSS::PropertyID::FontFamily, value);
style.set_property(CSS::PropertyID::FontStyle, value);
- style.set_property(CSS::PropertyID::FontVariant, value);
style.set_property(CSS::PropertyID::FontWeight, value);
style.set_property(CSS::PropertyID::LineHeight, value);
- // FIXME: Implement font-stretch
+ // FIXME: Implement font-stretch and font-variant
return;
}
-
- // FIXME: Handle system fonts. (caption, icon, menu, message-box, small-caption, status-bar)
-
return;
}
@@ -1211,11 +1065,7 @@ static void set_property_expanding_shorthands(StyleProperties& style, CSS::Prope
return;
}
- if (is_font_family(value)) {
- style.set_property(CSS::PropertyID::FontFamily, value);
- return;
- }
-
+ style.set_property(CSS::PropertyID::FontFamily, value);
return;
}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValue.h
index 1e6e7c250b..c3a0a97a3a 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleValue.h
+++ b/Userland/Libraries/LibWeb/CSS/StyleValue.h
@@ -230,6 +230,7 @@ public:
ComponentValueList,
Calculated,
BoxShadow,
+ Font,
};
Type type() const { return m_type; }
@@ -247,6 +248,7 @@ public:
bool is_component_value_list() const { return type() == Type::ComponentValueList; }
bool is_calculated() const { return type() == Type::Calculated; }
bool is_box_shadow() const { return type() == Type::BoxShadow; }
+ bool is_font() const { return type() == Type::Font; }
bool is_builtin() const { return is_inherit() || is_initial(); }
@@ -623,6 +625,50 @@ private:
RefPtr m_bitmap;
};
+class FontStyleValue final : public StyleValue {
+public:
+ static NonnullRefPtr create(NonnullRefPtr font_style, NonnullRefPtr font_weight, NonnullRefPtr font_size, NonnullRefPtr line_height, NonnullRefPtrVector&& font_families) { return adopt_ref(*new FontStyleValue(font_style, font_weight, font_size, line_height, move(font_families))); }
+ virtual ~FontStyleValue() override { }
+
+ NonnullRefPtr font_style() const { return m_font_style; }
+ NonnullRefPtr font_weight() const { return m_font_weight; }
+ NonnullRefPtr font_size() const { return m_font_size; }
+ NonnullRefPtr line_height() const { return m_line_height; }
+ NonnullRefPtrVector const& font_families() const { return m_font_families; }
+
+ virtual String to_string() const override
+ {
+ StringBuilder string_builder;
+ string_builder.appendff("Font style: {}, weight: {}, size: {}, line_height: {}, families: [",
+ m_font_style->to_string(), m_font_weight->to_string(), m_font_size->to_string(), m_line_height->to_string());
+ for (auto& family : m_font_families) {
+ string_builder.append(family.to_string());
+ string_builder.append(",");
+ }
+ string_builder.append("]");
+
+ return string_builder.to_string();
+ }
+
+private:
+ FontStyleValue(NonnullRefPtr font_style, NonnullRefPtr font_weight, NonnullRefPtr font_size, NonnullRefPtr line_height, NonnullRefPtrVector&& font_families)
+ : StyleValue(Type::Font)
+ , m_font_style(font_style)
+ , m_font_weight(font_weight)
+ , m_font_size(font_size)
+ , m_line_height(line_height)
+ , m_font_families(move(font_families))
+ {
+ }
+
+ NonnullRefPtr m_font_style;
+ NonnullRefPtr m_font_weight;
+ NonnullRefPtr m_font_size;
+ NonnullRefPtr m_line_height;
+ NonnullRefPtrVector m_font_families;
+ // FIXME: Implement font-stretch and font-variant.
+};
+
class StyleValueList final : public StyleValue {
public:
static NonnullRefPtr create(NonnullRefPtrVector&& values) { return adopt_ref(*new StyleValueList(move(values))); }