diff --git a/Userland/Libraries/LibWeb/CSS/Length.cpp b/Userland/Libraries/LibWeb/CSS/Length.cpp index 3edf6e7b16..db59cdccce 100644 --- a/Userland/Libraries/LibWeb/CSS/Length.cpp +++ b/Userland/Libraries/LibWeb/CSS/Length.cpp @@ -7,6 +7,8 @@ #include #include +#include +#include #include #include #include @@ -14,37 +16,49 @@ namespace Web::CSS { -float Length::relative_length_to_px(const Layout::Node& layout_node) const +float Length::relative_length_to_px(Gfx::IntRect const& viewport_rect, Gfx::FontMetrics const& font_metrics, float root_font_size) const { switch (m_type) { case Type::Ex: - return m_value * layout_node.font().x_height(); + return m_value * font_metrics.x_height; case Type::Em: - return m_value * layout_node.font_size(); + return m_value * font_metrics.size; case Type::Ch: // FIXME: Use layout_node.font().glyph_height() when writing-mode is not horizontal-tb (it has to be implemented first) - return m_value * (layout_node.font().glyph_width('0') + layout_node.font().glyph_spacing()); + return m_value * (font_metrics.glyph_width + font_metrics.glyph_spacing); case Type::Rem: - return m_value * layout_node.document().document_element()->layout_node()->font_size(); + return m_value * root_font_size; case Type::Vw: - return layout_node.document().browsing_context()->viewport_rect().width() * (m_value / 100); + return viewport_rect.width() * (m_value / 100); case Type::Vh: - return layout_node.document().browsing_context()->viewport_rect().height() * (m_value / 100); - case Type::Vmin: { - auto viewport = layout_node.document().browsing_context()->viewport_rect(); - - return min(viewport.width(), viewport.height()) * (m_value / 100); - } - case Type::Vmax: { - auto viewport = layout_node.document().browsing_context()->viewport_rect(); - - return max(viewport.width(), viewport.height()) * (m_value / 100); - } + return viewport_rect.height() * (m_value / 100); + case Type::Vmin: + return min(viewport_rect.width(), viewport_rect.height()) * (m_value / 100); + case Type::Vmax: + return max(viewport_rect.width(), viewport_rect.height()) * (m_value / 100); default: VERIFY_NOT_REACHED(); } } +float Length::to_px(Layout::Node const& layout_node) const +{ + if (!layout_node.document().browsing_context()) + return 0; + auto viewport_rect = layout_node.document().browsing_context()->viewport_rect(); + auto* root_element = layout_node.document().document_element(); + if (!root_element || !root_element->layout_node()) + return 0; + return to_px(viewport_rect, layout_node.font().metrics('M'), root_element->layout_node()->font().presentation_size()); +} + +float Length::relative_length_to_px(Layout::Node const& layout_node) const +{ + auto viewport_rect = layout_node.document().browsing_context()->viewport_rect(); + auto root_element = layout_node.document().document_element(); + return relative_length_to_px(viewport_rect, layout_node.font().metrics('M'), root_element->layout_node()->font().presentation_size()); +} + static float resolve_calc_value(CalculatedStyleValue::CalcValue const&, const Layout::Node& layout_node, float reference_for_percent); static float resolve_calc_number_value(CalculatedStyleValue::CalcNumberValue const&); static float resolve_calc_product(NonnullOwnPtr const&, const Layout::Node& layout_node, float reference_for_percent); diff --git a/Userland/Libraries/LibWeb/CSS/Length.h b/Userland/Libraries/LibWeb/CSS/Length.h index b90324a419..615d8cb4a8 100644 --- a/Userland/Libraries/LibWeb/CSS/Length.h +++ b/Userland/Libraries/LibWeb/CSS/Length.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include namespace Web::CSS { @@ -103,10 +104,13 @@ public: } float raw_value() const { return m_value; } - ALWAYS_INLINE float to_px(const Layout::Node& layout_node) const + + float to_px(Layout::Node const&) const; + + ALWAYS_INLINE float to_px(Gfx::IntRect const& viewport_rect, Gfx::FontMetrics const& font_metrics, float root_font_size) const { if (is_relative()) - return relative_length_to_px(layout_node); + return relative_length_to_px(viewport_rect, font_metrics, root_font_size); constexpr float inch_pixels = 96.0f; constexpr float centimeter_pixels = (inch_pixels / 2.54f); switch (m_type) { @@ -153,6 +157,8 @@ public: void set_calculated_style(CalculatedStyleValue* value) { m_calculated_style = value; } + float relative_length_to_px(Gfx::IntRect const& viewport_rect, Gfx::FontMetrics const& font_metrics, float root_font_size) const; + private: float relative_length_to_px(const Layout::Node&) const; float resolve_calculated_value(const Layout::Node& layout_node, float reference_for_percent) const; diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp index 4d548aa293..e39facaef0 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp @@ -231,7 +231,6 @@ void StyleProperties::load_font(Layout::Node const& node) const } if (!found_font) { - dbgln("Font not found: '{}' {} {}", family_value->to_string(), size, weight); found_font = font_fallback(monospace, bold); } @@ -239,7 +238,7 @@ void StyleProperties::load_font(Layout::Node const& node) const FontCache::the().set(font_selector, *m_font); } -RefPtr StyleProperties::font_fallback(bool monospace, bool bold) const +NonnullRefPtr StyleProperties::font_fallback(bool monospace, bool bold) { if (monospace && bold) return Gfx::FontDatabase::default_fixed_width_font().bold_variant(); diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.h b/Userland/Libraries/LibWeb/CSS/StyleProperties.h index 05fcc8cefb..f5681e206c 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.h +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.h @@ -33,6 +33,9 @@ public: callback((CSS::PropertyID)it.key, *it.value); } + HashMap>& properties() { return m_property_values; } + HashMap> const& properties() const { return m_property_values; } + void set_property(CSS::PropertyID, NonnullRefPtr value); void set_property(CSS::PropertyID, const StringView&); Optional> property(CSS::PropertyID) const; @@ -73,6 +76,17 @@ public: return *m_font; } + Gfx::Font const& computed_font() const + { + VERIFY(m_font); + return *m_font; + } + + void set_computed_font(NonnullRefPtr font) + { + m_font = move(font); + } + float line_height(const Layout::Node&) const; bool operator==(const StyleProperties&) const; @@ -81,6 +95,8 @@ public: Optional position() const; Optional z_index() const; + static NonnullRefPtr font_fallback(bool monospace, bool bold); + private: friend class StyleResolver; @@ -88,7 +104,6 @@ private: Optional overflow(CSS::PropertyID) const; void load_font(Layout::Node const&) const; - RefPtr font_fallback(bool monospace, bool bold) const; mutable RefPtr m_font; }; diff --git a/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp b/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp index fbb1c2539e..122d41a86f 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleResolver.cpp @@ -7,6 +7,8 @@ */ #include +#include +#include #include #include #include @@ -15,6 +17,8 @@ #include #include #include +#include +#include #include #include @@ -574,50 +578,253 @@ void StyleResolver::compute_cascaded_values(StyleProperties& style, DOM::Element // FIXME: Transition declarations [css-transitions-1] } -// https://drafts.csswg.org/css-cascade/#defaulting -void StyleResolver::compute_defaulted_values(StyleProperties& style, DOM::Element const& element) const +static NonnullRefPtr get_initial_value(CSS::PropertyID property_id) +{ + auto value = property_initial_value(property_id); + if (!value) + return InitialStyleValue::the(); + return value.release_nonnull(); +}; + +static NonnullRefPtr get_inherit_value(CSS::PropertyID property_id, DOM::Element const& element) +{ + if (!element.parent_element() || !element.parent_element()->specified_css_values()) + return get_initial_value(property_id); + auto& map = element.parent_element()->specified_css_values()->properties(); + auto it = map.find(property_id); + VERIFY(it != map.end()); + return *it->value; +}; + +void StyleResolver::compute_defaulted_property_value(StyleProperties& style, DOM::Element const& element, CSS::PropertyID property_id) const { // FIXME: If we don't know the correct initial value for a property, we fall back to InitialStyleValue. - auto get_initial_value = [&](PropertyID property_id) -> NonnullRefPtr { - auto value = property_initial_value(property_id); - if (!value) - return InitialStyleValue::the(); - return value.release_nonnull(); - }; + auto it = style.m_property_values.find(property_id); + if (it == style.m_property_values.end()) { + if (is_inherited_property(property_id)) + style.m_property_values.set(property_id, get_inherit_value(property_id, element)); + else + style.m_property_values.set(property_id, get_initial_value(property_id)); + return; + } - auto get_inherit_value = [&](PropertyID property_id) -> NonnullRefPtr { - if (!element.parent_element() || !element.parent_element()->specified_css_values()) - return get_initial_value(property_id); - auto& map = element.parent_element()->specified_css_values()->m_property_values; - auto it = map.find(property_id); - VERIFY(it != map.end()); - return const_cast(*it->value); - }; + if (it->value->is_initial()) { + it->value = get_initial_value(property_id); + return; + } + if (it->value->is_inherit()) { + it->value = get_inherit_value(property_id, element); + return; + } +} + +// https://drafts.csswg.org/css-cascade/#defaulting +void StyleResolver::compute_defaulted_values(StyleProperties& style, DOM::Element const& element) const +{ // Walk the list of all known CSS properties and: // - Add them to `style` if they are missing. // - Resolve `inherit` and `initial` as needed. for (auto i = to_underlying(CSS::first_longhand_property_id); i <= to_underlying(CSS::last_longhand_property_id); ++i) { auto property_id = (CSS::PropertyID)i; - auto it = style.m_property_values.find(property_id); - if (it == style.m_property_values.end()) { + compute_defaulted_property_value(style, element, property_id); + } +} - if (is_inherited_property(property_id)) - style.m_property_values.set(property_id, get_inherit_value(property_id)); - else - style.m_property_values.set(property_id, get_initial_value(property_id)); - continue; +void StyleResolver::compute_font(StyleProperties& style, DOM::Element const& element) const +{ + // To compute the font, first ensure that we've defaulted the relevant CSS font properties. + // FIXME: This should be more sophisticated. + compute_defaulted_property_value(style, element, CSS::PropertyID::FontFamily); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontSize); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontWeight); + + auto viewport_rect = document().browsing_context()->viewport_rect(); + + auto font_size = style.property(CSS::PropertyID::FontSize).value(); + auto font_weight = style.property(CSS::PropertyID::FontWeight).value(); + + int weight = Gfx::FontWeight::Regular; + if (font_weight->is_identifier()) { + switch (static_cast(*font_weight).id()) { + case CSS::ValueID::Normal: + weight = Gfx::FontWeight::Regular; + break; + case CSS::ValueID::Bold: + weight = Gfx::FontWeight::Bold; + break; + case CSS::ValueID::Lighter: + // FIXME: This should be relative to the parent. + weight = Gfx::FontWeight::Regular; + break; + case CSS::ValueID::Bolder: + // FIXME: This should be relative to the parent. + weight = Gfx::FontWeight::Bold; + break; + default: + break; } + } else if (font_weight->is_numeric()) { + int font_weight_integer = roundf(static_cast(*font_weight).value()); + if (font_weight_integer <= Gfx::FontWeight::Regular) + weight = Gfx::FontWeight::Regular; + else if (font_weight_integer <= Gfx::FontWeight::Bold) + weight = Gfx::FontWeight::Bold; + else + weight = Gfx::FontWeight::Black; + } + // FIXME: calc() for font-weight - if (it->value->is_initial()) { - it->value = get_initial_value(property_id); - continue; + bool bold = weight > Gfx::FontWeight::Regular; + + int size = 10; + + if (font_size->is_identifier()) { + switch (static_cast(*font_size).id()) { + case CSS::ValueID::XxSmall: + case CSS::ValueID::XSmall: + case CSS::ValueID::Small: + case CSS::ValueID::Medium: + // FIXME: Should be based on "user's default font size" + size = 10; + break; + case CSS::ValueID::Large: + case CSS::ValueID::XLarge: + case CSS::ValueID::XxLarge: + case CSS::ValueID::XxxLarge: + // FIXME: Should be based on "user's default font size" + size = 12; + break; + case CSS::ValueID::Smaller: + case CSS::ValueID::Larger: + // FIXME: Should be based on parent element + break; + default: + break; } + } else { + float root_font_size = 10; + if (element.document().document_element() != &element) + root_font_size = element.document().document_element()->layout_node()->font().presentation_size(); - if (it->value->is_inherit()) { - it->value = get_inherit_value(property_id); + Gfx::FontMetrics font_metrics; + if (element.parent_element()) + font_metrics = element.parent_element()->specified_css_values()->computed_font().metrics('M'); + else + font_metrics = Gfx::FontDatabase::default_font().metrics('M'); + + Optional maybe_length; + if (font_size->is_length()) { + maybe_length = font_size->to_length(); + if (maybe_length->is_percentage()) { + auto parent_font_size = size; + if (element.parent_element() && element.parent_element()->layout_node() && element.parent_element()->specified_css_values()) { + auto value = element.parent_element()->specified_css_values()->property(CSS::PropertyID::FontSize).value(); + if (value->is_length()) { + auto length = static_cast(*value).to_length(); + if (length.is_absolute() || length.is_relative()) + parent_font_size = length.to_px(viewport_rect, font_metrics, root_font_size); + } + } + + maybe_length = Length::make_px(maybe_length->raw_value() / 100.0f * (parent_font_size)); + } + } else if (font_size->is_calculated()) { + Length length = Length(0, Length::Type::Calculated); + length.set_calculated_style(verify_cast(font_size.ptr())); + maybe_length = length; + } + if (maybe_length.has_value()) { + auto calculated_size = maybe_length.value().to_px(viewport_rect, font_metrics, root_font_size); + if (calculated_size != 0) + size = calculated_size; + } + } + + // FIXME: Implement the full font-matching algorithm: https://www.w3.org/TR/css-fonts-4/#font-matching-algorithm + + // Note: This is modified by the find_font() lambda + FontSelector font_selector; + bool monospace = false; + + auto find_font = [&](String const& family) -> RefPtr { + font_selector = { family, size, weight }; + + if (auto found_font = FontCache::the().get(font_selector)) + return found_font; + + if (auto found_font = Gfx::FontDatabase::the().get(family, size, weight)) + return found_font; + + return {}; + }; + + // FIXME: Replace hard-coded font names with a relevant call to FontDatabase. + // Currently, we cannot request the default font's name, or request it at a specific size and weight. + // So, hard-coded font names it is. + auto find_generic_font = [&](ValueID font_id) -> RefPtr { + switch (font_id) { + case ValueID::Monospace: + case ValueID::UiMonospace: + monospace = true; + return find_font("Csilla"); + case ValueID::Serif: + case ValueID::SansSerif: + case ValueID::Cursive: + case ValueID::Fantasy: + case ValueID::UiSerif: + case ValueID::UiSansSerif: + case ValueID::UiRounded: + return find_font("Katica"); + default: + return {}; + } + }; + + RefPtr found_font; + + auto family_value = style.property(PropertyID::FontFamily).value(); + if (family_value->is_value_list()) { + auto& family_list = static_cast(*family_value).values(); + for (auto& family : family_list) { + if (family.is_identifier()) { + found_font = find_generic_font(family.to_identifier()); + } else if (family.is_string()) { + found_font = find_font(family.to_string()); + } + if (found_font) + break; + } + } else if (family_value->is_identifier()) { + found_font = find_generic_font(family_value->to_identifier()); + } else if (family_value->is_string()) { + found_font = find_font(family_value->to_string()); + } + + if (!found_font) { + found_font = StyleProperties::font_fallback(monospace, bold); + } + + FontCache::the().set(font_selector, *found_font); + + style.set_computed_font(found_font.release_nonnull()); +} + +void StyleResolver::absolutize_values(StyleProperties& style, DOM::Element const&) const +{ + auto viewport_rect = document().browsing_context()->viewport_rect(); + auto font_metrics = style.computed_font().metrics('M'); + // FIXME: Get the root element font. + float root_font_size = 10; + + for (auto& it : style.properties()) { + if (!it.value->is_length()) continue; + auto length = it.value->to_length(); + if (length.is_relative()) { + auto px = length.relative_length_to_px(viewport_rect, font_metrics, root_font_size); + it.value = LengthStyleValue::create(CSS::Length::make_px(px)); } } } @@ -625,9 +832,18 @@ void StyleResolver::compute_defaulted_values(StyleProperties& style, DOM::Elemen NonnullRefPtr StyleResolver::resolve_style(DOM::Element& element) const { auto style = StyleProperties::create(); + // 1. Perform the cascade. This produces the "specified style" compute_cascaded_values(style, element); + + // 2. Compute the font, since that may be needed for font-relative CSS units + compute_font(style, element); + + // 3. Absolutize values, turning font/viewport relative lengths into absolute lengths + absolutize_values(style, element); + + // 4. Default the values, applying inheritance and 'initial' as needed compute_defaulted_values(style, element); + return style; } - } diff --git a/Userland/Libraries/LibWeb/CSS/StyleResolver.h b/Userland/Libraries/LibWeb/CSS/StyleResolver.h index 04dd65cf0a..fc2f789467 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleResolver.h +++ b/Userland/Libraries/LibWeb/CSS/StyleResolver.h @@ -53,7 +53,11 @@ public: private: void compute_cascaded_values(StyleProperties&, DOM::Element&) const; + void compute_font(StyleProperties&, DOM::Element const&) const; void compute_defaulted_values(StyleProperties&, DOM::Element const&) const; + void absolutize_values(StyleProperties&, DOM::Element const&) const; + + void compute_defaulted_property_value(StyleProperties&, DOM::Element const&, CSS::PropertyID) const; template void for_each_stylesheet(CascadeOrigin, Callback) const;