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

LibGfx+LibWeb: Produce font cascade list in CSS font matching algorithm

According to the CSS font matching algorithm specification, it is
supposed to be executed for each glyph instead of each text run, as is
currently done. This change partially implements this by having the
font matching algorithm produce a list of fonts against which each
glyph will be tested to find its suitable font.

Now, it becomes possible to have per-glyph fallback fonts: if the
needed glyph is not present in a font, we can check the subsequent
fonts in the list.
This commit is contained in:
Aliaksandr Kalenik 2023-12-09 23:42:02 +01:00 committed by Andreas Kling
parent f50bf00814
commit 2cb0039a13
23 changed files with 250 additions and 109 deletions

View file

@ -1093,17 +1093,17 @@ void BlockFormattingContext::layout_list_item_marker(ListItemBox const& list_ite
image_height = list_style_image->natural_height().value_or(0);
}
CSSPixels default_marker_width = max(4, marker.font().pixel_size_rounded_up() - 4);
CSSPixels default_marker_width = max(4, marker.first_available_font().pixel_size_rounded_up() - 4);
auto marker_text = marker.text().value_or("");
if (marker_text.is_empty()) {
marker_state.set_content_width(image_width + default_marker_width);
} else {
auto text_width = marker.font().width(marker_text);
auto text_width = marker.first_available_font().width(marker_text);
marker_state.set_content_width(image_width + CSSPixels::nearest_value_for(text_width));
}
marker_state.set_content_height(max(image_height, marker.font().pixel_size_rounded_up() + 1));
marker_state.set_content_height(max(image_height, marker.first_available_font().pixel_size_rounded_up() + 1));
auto final_marker_width = marker_state.content_width() + default_marker_width;

View file

@ -25,8 +25,8 @@ void ButtonBox::prepare_for_replaced_layout()
// value attribute. This is not the case with <button />, which contains
// its contents normally.
if (is<HTML::HTMLInputElement>(dom_node())) {
set_natural_width(CSSPixels::nearest_value_for(font().width(static_cast<HTML::HTMLInputElement&>(dom_node()).value())));
set_natural_height(font().pixel_size_rounded_up());
set_natural_width(CSSPixels::nearest_value_for(first_available_font().width(static_cast<HTML::HTMLInputElement&>(dom_node()).value())));
set_natural_height(first_available_font().pixel_size_rounded_up());
}
}

View file

@ -1720,7 +1720,7 @@ CSSPixels FormattingContext::box_baseline(Box const& box) const
return box.computed_values().font_size();
case CSS::VerticalAlign::TextBottom:
// TextTop: Align the bottom of the box with the bottom of the parent's content area (see 10.6.1).
return box_state.content_height() - CSSPixels::nearest_value_for(box.containing_block()->font().pixel_metrics().descent * 2);
return box_state.content_height() - CSSPixels::nearest_value_for(box.containing_block()->first_available_font().pixel_metrics().descent * 2);
default:
break;
}

View file

@ -190,14 +190,14 @@ Optional<InlineLevelIterator::Item> InlineLevelIterator::next_without_lookahead(
Vector<Gfx::DrawGlyphOrEmoji> glyph_run;
float glyph_run_width = 0;
Gfx::for_each_glyph_position(
{ 0, 0 }, chunk.view, text_node.font(), [&](Gfx::DrawGlyphOrEmoji const& glyph_or_emoji) {
{ 0, 0 }, chunk.view, text_node.first_available_font(), [&](Gfx::DrawGlyphOrEmoji const& glyph_or_emoji) {
glyph_run.append(glyph_or_emoji);
return IterationDecision::Continue;
},
Gfx::IncludeLeftBearing::No, glyph_run_width);
if (!m_text_node_context->is_last_chunk)
glyph_run_width += text_node.font().glyph_spacing();
glyph_run_width += text_node.first_available_font().glyph_spacing();
CSSPixels chunk_width = CSSPixels::nearest_value_for(glyph_run_width);

View file

@ -70,7 +70,8 @@ void LineBox::trim_trailing_whitespace()
if (!is_ascii_space(last_character))
break;
int last_character_width = last_fragment->layout_node().font().glyph_width(last_character);
// FIXME: Use fragment's glyph run to determine the width of the last character.
int last_character_width = last_fragment->layout_node().first_available_font().glyph_width(last_character);
last_fragment->m_length -= 1;
last_fragment->set_width(last_fragment->width() - last_character_width);
m_width -= last_character_width;

View file

@ -47,7 +47,7 @@ int LineBoxFragment::text_index_at(CSSPixels x) const
if (!is<TextNode>(layout_node()))
return 0;
auto& layout_text = verify_cast<TextNode>(layout_node());
auto& font = layout_text.font();
auto& font = layout_text.first_available_font();
Utf8View view(text());
CSSPixels relative_x = x - absolute_x();

View file

@ -192,7 +192,7 @@ void LineBuilder::update_last_line()
}
auto strut_baseline = [&] {
auto& font = m_context.containing_block().font();
auto& font = m_context.containing_block().first_available_font();
auto const line_height = m_context.containing_block().line_height();
auto const font_metrics = font.pixel_metrics();
auto const typographic_height = CSSPixels::nearest_value_for(font_metrics.ascent + font_metrics.descent);
@ -204,7 +204,7 @@ void LineBuilder::update_last_line()
auto line_box_baseline = [&] {
CSSPixels line_box_baseline = strut_baseline;
for (auto& fragment : line_box.fragments()) {
auto const& font = fragment.layout_node().font();
auto const& font = fragment.layout_node().first_available_font();
auto const line_height = fragment.layout_node().line_height();
auto const font_metrics = font.pixel_metrics();
auto const typographic_height = CSSPixels::nearest_value_for(font_metrics.ascent + font_metrics.descent);
@ -301,7 +301,7 @@ void LineBuilder::update_last_line()
top_of_inline_box = (fragment.offset().y() - fragment_box_state.margin_box_top());
bottom_of_inline_box = (fragment.offset().y() + fragment_box_state.content_height() + fragment_box_state.margin_box_bottom());
} else {
auto font_metrics = fragment.layout_node().font().pixel_metrics();
auto font_metrics = fragment.layout_node().first_available_font().pixel_metrics();
auto typographic_height = CSSPixels::nearest_value_for(font_metrics.ascent + font_metrics.descent);
auto leading = fragment.layout_node().line_height() - typographic_height;
auto half_leading = leading / 2;

View file

@ -349,7 +349,7 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
// NOTE: We have to be careful that font-related properties get set in the right order.
// m_font is used by Length::to_px() when resolving sizes against this layout node.
// That's why it has to be set before everything else.
m_font = computed_style.computed_font();
m_font_list = computed_style.computed_font_list();
computed_values.set_font_size(computed_style.property(CSS::PropertyID::FontSize)->as_length().length().to_px(*this));
computed_values.set_font_weight(round_to<int>(computed_style.property(CSS::PropertyID::FontWeight)->as_number().number()));
m_line_height = computed_style.line_height(*this);
@ -930,7 +930,7 @@ JS::NonnullGCPtr<NodeWithStyle> NodeWithStyle::create_anonymous_wrapper() const
{
auto wrapper = heap().allocate_without_realm<BlockContainer>(const_cast<DOM::Document&>(document()), nullptr, m_computed_values.clone_inherited_values());
static_cast<CSS::MutableComputedValues&>(wrapper->m_computed_values).set_display(CSS::Display(CSS::DisplayOutside::Block, CSS::DisplayInside::Flow));
wrapper->m_font = m_font;
wrapper->m_font_list = m_font_list;
wrapper->m_line_height = m_line_height;
return *wrapper;
}

View file

@ -143,7 +143,8 @@ public:
bool can_contain_boxes_with_position_absolute() const;
Gfx::Font const& font() const;
Gfx::FontCascadeList const& font_list() const;
Gfx::Font const& first_available_font() const;
Gfx::Font const& scaled_font(PaintContext&) const;
Gfx::Font const& scaled_font(float scale_factor) const;
@ -217,10 +218,11 @@ public:
void apply_style(const CSS::StyleProperties&);
Gfx::Font const& font() const { return *m_font; }
Gfx::Font const& first_available_font() const;
Gfx::FontCascadeList const& font_list() const { return *m_font_list; }
CSSPixels line_height() const { return m_line_height; }
void set_line_height(CSSPixels line_height) { m_line_height = line_height; }
void set_font(Gfx::Font const& font) { m_font = font; }
void set_font_list(Gfx::FontCascadeList const& font_list) { m_font_list = font_list; }
Vector<CSS::BackgroundLayerData> const& background_layers() const { return computed_values().background_layers(); }
const CSS::AbstractImageStyleValue* list_style_image() const { return m_list_style_image; }
@ -238,7 +240,7 @@ private:
void reset_table_box_computed_values_used_by_wrapper_to_init_values();
CSS::ComputedValues m_computed_values;
RefPtr<Gfx::Font const> m_font;
RefPtr<Gfx::FontCascadeList const> m_font_list;
CSSPixels m_line_height { 0 };
RefPtr<CSS::AbstractImageStyleValue const> m_list_style_image;
};
@ -275,13 +277,20 @@ inline bool Node::has_style_or_parent_with_style() const
return m_has_style || (parent() != nullptr && parent()->has_style_or_parent_with_style());
}
inline Gfx::Font const& Node::font() const
inline Gfx::Font const& Node::first_available_font() const
{
VERIFY(has_style_or_parent_with_style());
if (m_has_style)
return static_cast<NodeWithStyle const*>(this)->font();
return parent()->font();
return static_cast<NodeWithStyle const*>(this)->first_available_font();
return parent()->first_available_font();
}
inline Gfx::FontCascadeList const& Node::font_list() const
{
VERIFY(has_style_or_parent_with_style());
if (m_has_style)
return static_cast<NodeWithStyle const*>(this)->font_list();
return parent()->font_list();
}
inline Gfx::Font const& Node::scaled_font(PaintContext& context) const
@ -291,7 +300,7 @@ inline Gfx::Font const& Node::scaled_font(PaintContext& context) const
inline Gfx::Font const& Node::scaled_font(float scale_factor) const
{
return document().style_computer().font_cache().scaled_font(font(), scale_factor);
return document().style_computer().font_cache().scaled_font(first_available_font(), scale_factor);
}
inline const CSS::ImmutableComputedValues& Node::computed_values() const
@ -322,4 +331,12 @@ inline NodeWithStyle* Node::parent()
return static_cast<NodeWithStyle*>(TreeNode<Node>::parent());
}
inline Gfx::Font const& NodeWithStyle::first_available_font() const
{
// https://drafts.csswg.org/css-fonts/#first-available-font
// FIXME: Should be be the first font for which the character U+0020 (space) instead of
// any first font in the list
return m_font_list->first();
}
}

View file

@ -222,7 +222,7 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available
} else if (is<SVGTextBox>(descendant)) {
auto& text_element = static_cast<SVG::SVGTextPositioningElement&>(dom_node);
auto& font = graphics_box.font();
auto& font = graphics_box.first_available_font();
auto text_contents = text_element.text_contents();
Utf8View text_utf8 { text_contents };
auto text_width = font.width(text_utf8);

View file

@ -427,7 +427,7 @@ ErrorOr<void> TreeBuilder::create_layout_tree(DOM::Node& dom_node, TreeBuilder::
auto content_box_computed_values = parent.computed_values().clone_inherited_values();
auto content_box_wrapper = parent.heap().template allocate_without_realm<BlockContainer>(parent.document(), nullptr, move(content_box_computed_values));
content_box_wrapper->set_font(parent.font());
content_box_wrapper->set_font_list(parent.font_list());
content_box_wrapper->set_line_height(parent.line_height());
content_box_wrapper->set_children_are_inline(parent.children_are_inline());
@ -630,7 +630,7 @@ static void wrap_in_anonymous(Vector<JS::Handle<Node>>& sequence, Node* nearest_
}
wrapper->set_children_are_inline(parent.children_are_inline());
wrapper->set_line_height(parent.line_height());
wrapper->set_font(parent.font());
wrapper->set_font_list(parent.font_list());
if (nearest_sibling)
parent.insert_before(*wrapper, *nearest_sibling);
else
@ -717,7 +717,7 @@ Vector<JS::Handle<Box>> TreeBuilder::generate_missing_parents(NodeWithStyle& roo
parent.remove_child(*table_box);
wrapper->append_child(*table_box);
wrapper->set_font(parent.font());
wrapper->set_font_list(parent.font_list());
wrapper->set_line_height(parent.line_height());
if (nearest_sibling)
@ -753,7 +753,7 @@ static void fixup_row(Box& row_box, TableGrid const& table_grid, size_t row_inde
// Ensure that the cell (with zero content height) will have the same height as the row by setting vertical-align to middle.
cell_computed_values.set_vertical_align(CSS::VerticalAlign::Middle);
auto cell_box = row_box.heap().template allocate_without_realm<BlockContainer>(row_box.document(), nullptr, cell_computed_values);
cell_box->set_font(row_box.font());
cell_box->set_font_list(row_box.font_list());
cell_box->set_line_height(row_box.line_height());
row_box.append_child(cell_box);
}