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))); }