1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 17:57:35 +00:00

LibWeb: Implement and use FontStyleValue

After working with the code for a while, it makes more sense to put all
the parsing in Parser, instead of some of it living in StyleResolver.
That means our current ValueListStyleValue needs to be replaced with
specific StyleValue types for the properties that are shorthands or
otherwise combine several values together.

Here we implement FontStyleProperty, which represents a `font` CSS
property.

Also adjusted the fonts.html test page so that font-weights are featured
in test cases without things we do not yet support.
This commit is contained in:
Sam Atkins 2021-08-03 11:37:24 +01:00 committed by Andreas Kling
parent 5e1fad2dff
commit 59501f1940
5 changed files with 247 additions and 164 deletions

View file

@ -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 @@
<div id="monospace">font: 20px monospace;</div>
<div id="a">font: 12pt/14pt sans-serif;</div>
<div id="b">font: 80% cursive;</div>
<div id="b2">font: bold 80% cursive;</div>
<div id="c">font: x-large/110% fantasy, serif;</div>
<div id="d">font: 2em SerenitySans;</div>
<div id="d2">font: 400 2em SerenitySans;</div>
<div id="e">font: bold italic large Helvetica, sans-serif;</div>
<div id="f">font: normal small-caps 120%/120% monospace;</div>
<div id="g">font: condensed oblique 12pt "Helvetica Neue", serif;</div>

View file

@ -1787,6 +1787,180 @@ RefPtr<StyleValue> Parser::parse_box_shadow_value(ParsingContext const& context,
return BoxShadowStyleValue::create(offset_x, offset_y, blur_radius, color);
}
RefPtr<StyleValue> Parser::parse_font_value(ParsingContext const& context, Vector<StyleComponentValueRule> 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<NumericStyleValue const&>(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<StyleValue> font_style;
RefPtr<StyleValue> font_weight;
RefPtr<StyleValue> font_size;
RefPtr<StyleValue> line_height;
NonnullRefPtrVector<StyleValue> 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<StyleValue> Parser::parse_as_css_value(PropertyID property_id)
{
auto component_values = parse_as_list_of_component_values();
@ -1816,9 +1990,17 @@ RefPtr<StyleValue> Parser::parse_css_value(PropertyID property_id, TokenStream<S
return {};
// Special-case property handling
if (property_id == PropertyID::BoxShadow) {
switch (property_id) {
case PropertyID::BoxShadow:
if (auto parsed_box_shadow = parse_box_shadow_value(m_context, component_values))
return parsed_box_shadow;
break;
case PropertyID::Font:
if (auto parsed_value = parse_font_value(m_context, component_values))
return parsed_value;
break;
default:
break;
}
if (component_values.size() == 1)

View file

@ -176,6 +176,7 @@ private:
static RefPtr<StyleValue> parse_string_value(ParsingContext const&, StyleComponentValueRule const&);
static RefPtr<StyleValue> parse_image_value(ParsingContext const&, StyleComponentValueRule const&);
static RefPtr<StyleValue> parse_box_shadow_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
static RefPtr<StyleValue> parse_font_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
// calc() parsing, according to https://www.w3.org/TR/css-values-3/#calc-syntax
static OwnPtr<CalculatedStyleValue::CalcSum> parse_calc_sum(ParsingContext const&, TokenStream<StyleComponentValueRule>&);

View file

@ -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<NumericStyleValue const&>(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<CSS::ValueListStyleValue const&>(value).values();
RefPtr<StyleValue> font_style_value;
RefPtr<StyleValue> font_weight_value;
RefPtr<StyleValue> font_size_value;
RefPtr<StyleValue> line_height_value;
RefPtr<StyleValue> 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());
if (value.is_font()) {
auto& font_shorthand = static_cast<CSS::FontStyleValue const&>(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;
}
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);
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,14 +1065,10 @@ static void set_property_expanding_shorthands(StyleProperties& style, CSS::Prope
return;
}
if (is_font_family(value)) {
style.set_property(CSS::PropertyID::FontFamily, value);
return;
}
return;
}
if (property_id == CSS::PropertyID::Flex) {
if (value.is_length() || (value.is_identifier() && value.to_identifier() == CSS::ValueID::Content)) {
style.set_property(CSS::PropertyID::FlexBasis, value);

View file

@ -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<Gfx::Bitmap> m_bitmap;
};
class FontStyleValue final : public StyleValue {
public:
static NonnullRefPtr<FontStyleValue> create(NonnullRefPtr<StyleValue> font_style, NonnullRefPtr<StyleValue> font_weight, NonnullRefPtr<StyleValue> font_size, NonnullRefPtr<StyleValue> line_height, NonnullRefPtrVector<StyleValue>&& font_families) { return adopt_ref(*new FontStyleValue(font_style, font_weight, font_size, line_height, move(font_families))); }
virtual ~FontStyleValue() override { }
NonnullRefPtr<StyleValue> font_style() const { return m_font_style; }
NonnullRefPtr<StyleValue> font_weight() const { return m_font_weight; }
NonnullRefPtr<StyleValue> font_size() const { return m_font_size; }
NonnullRefPtr<StyleValue> line_height() const { return m_line_height; }
NonnullRefPtrVector<StyleValue> 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<StyleValue> font_style, NonnullRefPtr<StyleValue> font_weight, NonnullRefPtr<StyleValue> font_size, NonnullRefPtr<StyleValue> line_height, NonnullRefPtrVector<StyleValue>&& 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<StyleValue> m_font_style;
NonnullRefPtr<StyleValue> m_font_weight;
NonnullRefPtr<StyleValue> m_font_size;
NonnullRefPtr<StyleValue> m_line_height;
NonnullRefPtrVector<StyleValue> m_font_families;
// FIXME: Implement font-stretch and font-variant.
};
class StyleValueList final : public StyleValue {
public:
static NonnullRefPtr<StyleValueList> create(NonnullRefPtrVector<StyleValue>&& values) { return adopt_ref(*new StyleValueList(move(values))); }