From 7181c3f2ea5fba73e77d98acbf9e46640b4a9015 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Sat, 29 Jun 2024 17:14:23 +0200 Subject: [PATCH] Everywhere: Limit layout text fragments to use one font for all glyphs The ChunkIterator now limits a chunk to using only one font (before, it was possible to have a chunk with >1 font, when `unicode-range` CSS property is used). This change allows us to reduce some complexity in the text shaping and painting code and makes us compatible with the APIs in Skia and HarfBuzz. --- Userland/Libraries/LibAccelGfx/Painter.cpp | 3 +-- Userland/Libraries/LibAccelGfx/Painter.h | 2 +- Userland/Libraries/LibGfx/Font/Font.h | 2 ++ Userland/Libraries/LibGfx/Font/ScaledFont.h | 4 +-- Userland/Libraries/LibGfx/Path.cpp | 9 ++----- Userland/Libraries/LibGfx/TextLayout.cpp | 4 --- Userland/Libraries/LibGfx/TextLayout.h | 25 +++++++++---------- .../LibWeb/Layout/InlineLevelIterator.cpp | 6 ++--- .../LibWeb/Layout/InlineLevelIterator.h | 2 +- Userland/Libraries/LibWeb/Layout/LineBox.cpp | 6 ++--- Userland/Libraries/LibWeb/Layout/LineBox.h | 2 +- .../Libraries/LibWeb/Layout/LineBoxFragment.h | 8 +++--- .../Libraries/LibWeb/Layout/LineBuilder.cpp | 2 +- .../Libraries/LibWeb/Layout/LineBuilder.h | 2 +- Userland/Libraries/LibWeb/Layout/TextNode.cpp | 22 ++++++++++------ Userland/Libraries/LibWeb/Layout/TextNode.h | 6 +++-- .../LibWeb/Painting/BackgroundPainting.cpp | 15 ++++++----- .../Libraries/LibWeb/Painting/DisplayList.cpp | 5 ++-- .../LibWeb/Painting/DisplayListPlayerCPU.cpp | 16 ++++++------ .../LibWeb/Painting/DisplayListPlayerGPU.cpp | 7 +++--- .../LibWeb/Painting/DisplayListPlayerSkia.cpp | 7 +++--- .../LibWeb/Painting/DisplayListRecorder.cpp | 6 ++--- .../LibWeb/Painting/PaintableBox.cpp | 8 ++++-- .../LibWeb/Painting/PaintableFragment.h | 4 +-- .../LibWeb/Painting/ShadowPainting.cpp | 8 ++++-- 25 files changed, 98 insertions(+), 83 deletions(-) diff --git a/Userland/Libraries/LibAccelGfx/Painter.cpp b/Userland/Libraries/LibAccelGfx/Painter.cpp index f50ca367bdd..d68ec19d526 100644 --- a/Userland/Libraries/LibAccelGfx/Painter.cpp +++ b/Userland/Libraries/LibAccelGfx/Painter.cpp @@ -407,7 +407,7 @@ void Painter::draw_scaled_bitmap(Gfx::FloatRect const& dst_rect, Gfx::Bitmap con GL::delete_texture(texture); } -void Painter::draw_glyph_run(Span glyph_run, Color const& color) +void Painter::draw_glyph_run(Span glyph_run, Gfx::Font const& font, Color const& color) { bind_target_canvas(); @@ -420,7 +420,6 @@ void Painter::draw_glyph_run(Span glyph_run, Color if (glyph_or_emoji.has()) { auto const& glyph = glyph_or_emoji.get(); - auto const& font = *glyph.font; auto code_point = glyph.code_point; auto point = glyph.position; diff --git a/Userland/Libraries/LibAccelGfx/Painter.h b/Userland/Libraries/LibAccelGfx/Painter.h index 0cc6768561c..a74dc50c72d 100644 --- a/Userland/Libraries/LibAccelGfx/Painter.h +++ b/Userland/Libraries/LibAccelGfx/Painter.h @@ -70,7 +70,7 @@ public: void draw_scaled_immutable_bitmap(Gfx::IntRect const& dst_rect, Gfx::ImmutableBitmap const&, Gfx::IntRect const& src_rect, ScalingMode = ScalingMode::NearestNeighbor); void draw_scaled_immutable_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const&, Gfx::FloatRect const& src_rect, ScalingMode = ScalingMode::NearestNeighbor); - void draw_glyph_run(Span glyph_run, Color const& color); + void draw_glyph_run(Span glyph_run, Gfx::Font const&, Color const& color); void set_clip_rect(Gfx::IntRect); void clear_clip_rect(); diff --git a/Userland/Libraries/LibGfx/Font/Font.h b/Userland/Libraries/LibGfx/Font/Font.h index b31ddbc0110..80969e9eb76 100644 --- a/Userland/Libraries/LibGfx/Font/Font.h +++ b/Userland/Libraries/LibGfx/Font/Font.h @@ -120,6 +120,8 @@ public: virtual Optional glyph(u32 code_point, GlyphSubpixelOffset) const = 0; virtual bool contains_glyph(u32 code_point) const = 0; + virtual bool append_glyph_path_to(Gfx::Path&, u32 glyph_id) const = 0; + virtual u32 glyph_id_for_code_point(u32 code_point) const = 0; virtual float glyph_left_bearing(u32 code_point) const = 0; virtual float glyph_width(u32 code_point) const = 0; virtual float glyph_or_emoji_width(Utf8CodePointIterator&) const = 0; diff --git a/Userland/Libraries/LibGfx/Font/ScaledFont.h b/Userland/Libraries/LibGfx/Font/ScaledFont.h index 42895d0bd51..0e6502c9d36 100644 --- a/Userland/Libraries/LibGfx/Font/ScaledFont.h +++ b/Userland/Libraries/LibGfx/Font/ScaledFont.h @@ -24,11 +24,9 @@ struct GlyphIndexWithSubpixelOffset { class ScaledFont final : public Gfx::Font { public: ScaledFont(NonnullRefPtr, float point_width, float point_height, unsigned dpi_x = DEFAULT_DPI, unsigned dpi_y = DEFAULT_DPI); - u32 glyph_id_for_code_point(u32 code_point) const { return m_font->glyph_id_for_code_point(code_point); } ScaledFontMetrics metrics() const { return m_font->metrics(m_x_scale, m_y_scale); } ScaledGlyphMetrics glyph_metrics(u32 glyph_id) const { return m_font->glyph_metrics(glyph_id, m_x_scale, m_y_scale, m_point_width, m_point_height); } RefPtr rasterize_glyph(u32 glyph_id, GlyphSubpixelOffset) const; - bool append_glyph_path_to(Gfx::Path&, u32 glyph_id) const; // ^Gfx::Font virtual float point_size() const override; @@ -44,6 +42,8 @@ public: virtual float glyph_width(u32 code_point) const override; virtual float glyph_or_emoji_width(Utf8CodePointIterator&) const override; virtual float glyphs_horizontal_kerning(u32 left_code_point, u32 right_code_point) const override; + virtual u32 glyph_id_for_code_point(u32 code_point) const override { return m_font->glyph_id_for_code_point(code_point); } + virtual bool append_glyph_path_to(Gfx::Path&, u32 glyph_id) const override; virtual float preferred_line_height() const override { return metrics().height() + metrics().line_gap; } virtual int x_height() const override { return m_point_height; } // FIXME: Read from font virtual u8 baseline() const override { return m_point_height; } // FIXME: Read from font diff --git a/Userland/Libraries/LibGfx/Path.cpp b/Userland/Libraries/LibGfx/Path.cpp index 98daee05626..8dfacb786fb 100644 --- a/Userland/Libraries/LibGfx/Path.cpp +++ b/Userland/Libraries/LibGfx/Path.cpp @@ -167,10 +167,8 @@ void Path::text(Utf8View text, Font const& font) } auto& scaled_font = static_cast(font); - auto font_list = Gfx::FontCascadeList::create(); - font_list->add(scaled_font); for_each_glyph_position( - last_point(), text, font_list, [&](DrawGlyphOrEmoji glyph_or_emoji) { + last_point(), text, scaled_font, [&](DrawGlyphOrEmoji glyph_or_emoji) { if (glyph_or_emoji.has()) { auto& glyph = glyph_or_emoji.get(); move_to(glyph.position); @@ -208,13 +206,10 @@ Path Path::place_text_along(Utf8View text, Font const& font) const return lines[line_index].a(); }; - auto font_list = Gfx::FontCascadeList::create(); - font_list->add(font); auto& scaled_font = static_cast(font); - Gfx::Path result_path; Gfx::for_each_glyph_position( - {}, text, font_list, [&](Gfx::DrawGlyphOrEmoji glyph_or_emoji) { + {}, text, font, [&](Gfx::DrawGlyphOrEmoji glyph_or_emoji) { auto* glyph = glyph_or_emoji.get_pointer(); if (!glyph) return; diff --git a/Userland/Libraries/LibGfx/TextLayout.cpp b/Userland/Libraries/LibGfx/TextLayout.cpp index db55074d9b7..e5f12afb150 100644 --- a/Userland/Libraries/LibGfx/TextLayout.cpp +++ b/Userland/Libraries/LibGfx/TextLayout.cpp @@ -37,7 +37,6 @@ DrawGlyphOrEmoji prepare_draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIter return DrawGlyph { .position = point, .code_point = code_point, - .font = font, }; } @@ -46,7 +45,6 @@ DrawGlyphOrEmoji prepare_draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIter return DrawEmoji { .position = point, .emoji = emoji, - .font = font, }; } @@ -55,7 +53,6 @@ DrawGlyphOrEmoji prepare_draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIter return DrawGlyph { .position = point, .code_point = code_point, - .font = font, }; } @@ -64,7 +61,6 @@ DrawGlyphOrEmoji prepare_draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIter return DrawGlyph { .position = point, .code_point = 0xFFFD, - .font = font, }; } diff --git a/Userland/Libraries/LibGfx/TextLayout.h b/Userland/Libraries/LibGfx/TextLayout.h index 450d6d352ef..bbe0778f2cf 100644 --- a/Userland/Libraries/LibGfx/TextLayout.h +++ b/Userland/Libraries/LibGfx/TextLayout.h @@ -33,7 +33,6 @@ enum class IncludeLeftBearing { struct DrawGlyph { FloatPoint position; u32 code_point; - NonnullRefPtr font; void translate_by(FloatPoint const& delta) { @@ -44,7 +43,6 @@ struct DrawGlyph { struct DrawEmoji { FloatPoint position; Gfx::Bitmap const* emoji; - NonnullRefPtr font; void translate_by(FloatPoint const& delta) { @@ -56,28 +54,30 @@ using DrawGlyphOrEmoji = Variant; class GlyphRun : public RefCounted { public: - GlyphRun() = default; - GlyphRun(Vector&& glyphs) + GlyphRun(Vector&& glyphs, NonnullRefPtr font) : m_glyphs(move(glyphs)) + , m_font(move(font)) { } + [[nodiscard]] Font const& font() const { return m_font; } [[nodiscard]] Vector const& glyphs() const { return m_glyphs; } + [[nodiscard]] Vector& glyphs() { return m_glyphs; } [[nodiscard]] bool is_empty() const { return m_glyphs.is_empty(); } void append(Gfx::DrawGlyphOrEmoji glyph) { m_glyphs.append(glyph); } private: Vector m_glyphs; + NonnullRefPtr m_font; }; Variant prepare_draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIterator& it, Font const& font); template -void for_each_glyph_position(FloatPoint baseline_start, Utf8View string, FontCascadeList const& font_list, Callback callback, IncludeLeftBearing include_left_bearing = IncludeLeftBearing::No, Optional width = {}) +void for_each_glyph_position(FloatPoint baseline_start, Utf8View string, Gfx::Font const& font, Callback callback, IncludeLeftBearing include_left_bearing = IncludeLeftBearing::No, Optional width = {}) { - auto const& space_glyph_font = font_list.font_for_code_point(' '); - float space_width = space_glyph_font.glyph_width(' '); + auto space_width = font.glyph_width(' '); u32 last_code_point = 0; @@ -85,9 +85,8 @@ void for_each_glyph_position(FloatPoint baseline_start, Utf8View string, FontCas for (auto code_point_iterator = string.begin(); code_point_iterator != string.end(); ++code_point_iterator) { auto it = code_point_iterator; // The callback function will advance the iterator, so create a copy for this lookup. auto code_point = *code_point_iterator; - RefPtr font = font_list.font_for_code_point(code_point); - point.set_y(baseline_start.y() - font->pixel_metrics().ascent); + point.set_y(baseline_start.y() - font.pixel_metrics().ascent); if (should_paint_as_space(code_point)) { point.translate_by(space_width, 0); @@ -95,15 +94,15 @@ void for_each_glyph_position(FloatPoint baseline_start, Utf8View string, FontCas continue; } - auto kerning = font->glyphs_horizontal_kerning(last_code_point, code_point); + auto kerning = font.glyphs_horizontal_kerning(last_code_point, code_point); if (kerning != 0.0f) point.translate_by(kerning, 0); - auto glyph_width = font->glyph_or_emoji_width(it); - auto glyph_or_emoji = prepare_draw_glyph_or_emoji(point, code_point_iterator, *font); + auto glyph_width = font.glyph_or_emoji_width(it); + auto glyph_or_emoji = prepare_draw_glyph_or_emoji(point, code_point_iterator, font); if (include_left_bearing == IncludeLeftBearing::Yes) { if (glyph_or_emoji.has()) - glyph_or_emoji.get().position += FloatPoint(font->glyph_left_bearing(code_point), 0); + glyph_or_emoji.get().position += FloatPoint(font.glyph_left_bearing(code_point), 0); } callback(glyph_or_emoji); diff --git a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp index fc32c38aca8..dd113b8aed9 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp +++ b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp @@ -198,7 +198,7 @@ Optional InlineLevelIterator::next_without_lookahead( Vector glyph_run; float glyph_run_width = 0; Gfx::for_each_glyph_position( - { 0, 0 }, chunk.view, text_node.computed_values().font_list(), [&](Gfx::DrawGlyphOrEmoji const& glyph_or_emoji) { + { 0, 0 }, chunk.view, chunk.font, [&](Gfx::DrawGlyphOrEmoji const& glyph_or_emoji) { glyph_run.append(glyph_or_emoji); return IterationDecision::Continue; }, @@ -212,7 +212,7 @@ Optional InlineLevelIterator::next_without_lookahead( Item item { .type = Item::Type::Text, .node = &text_node, - .glyph_run = move(glyph_run), + .glyph_run = adopt_ref(*new Gfx::GlyphRun(move(glyph_run), chunk.font)), .offset_in_node = chunk.start, .length_in_node = chunk.length, .width = chunk_width, @@ -321,7 +321,7 @@ void InlineLevelIterator::enter_text_node(Layout::TextNode const& text_node) .do_respect_linebreaks = do_respect_linebreaks, .is_first_chunk = true, .is_last_chunk = false, - .chunk_iterator = TextNode::ChunkIterator { text_node.text_for_rendering(), do_wrap_lines, do_respect_linebreaks }, + .chunk_iterator = TextNode::ChunkIterator { text_node.text_for_rendering(), do_wrap_lines, do_respect_linebreaks, text_node.computed_values().font_list() }, }; m_text_node_context->next_chunk = m_text_node_context->chunk_iterator.next(); } diff --git a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h index de7f568a616..43fd444db33 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h +++ b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h @@ -32,7 +32,7 @@ public: }; Type type {}; JS::GCPtr node {}; - Vector glyph_run {}; + RefPtr glyph_run {}; size_t offset_in_node { 0 }; size_t length_in_node { 0 }; CSSPixels width { 0.0f }; diff --git a/Userland/Libraries/LibWeb/Layout/LineBox.cpp b/Userland/Libraries/LibWeb/Layout/LineBox.cpp index e8b45897271..0e5a6c527d8 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/LineBox.cpp @@ -15,16 +15,16 @@ namespace Web::Layout { -void LineBox::add_fragment(Node const& layout_node, int start, int length, CSSPixels leading_size, CSSPixels trailing_size, CSSPixels leading_margin, CSSPixels trailing_margin, CSSPixels content_width, CSSPixels content_height, CSSPixels border_box_top, CSSPixels border_box_bottom, Vector glyph_run) +void LineBox::add_fragment(Node const& layout_node, int start, int length, CSSPixels leading_size, CSSPixels trailing_size, CSSPixels leading_margin, CSSPixels trailing_margin, CSSPixels content_width, CSSPixels content_height, CSSPixels border_box_top, CSSPixels border_box_bottom, RefPtr glyph_run) { bool text_align_is_justify = layout_node.computed_values().text_align() == CSS::TextAlign::Justify; - if (!text_align_is_justify && !m_fragments.is_empty() && &m_fragments.last().layout_node() == &layout_node) { + if (glyph_run && !text_align_is_justify && !m_fragments.is_empty() && &m_fragments.last().layout_node() == &layout_node && &m_fragments.last().m_glyph_run->font() == &glyph_run->font()) { auto const fragment_width = m_fragments.last().width(); // The fragment we're adding is from the last Layout::Node on the line. // Expand the last fragment instead of adding a new one with the same Layout::Node. m_fragments.last().m_length = (start - m_fragments.last().m_start) + length; m_fragments.last().set_width(m_fragments.last().width() + content_width); - for (auto& glyph : glyph_run) { + for (auto& glyph : glyph_run->glyphs()) { glyph.visit([&](auto& glyph) { glyph.position.translate_by(fragment_width.to_float(), 0); }); m_fragments.last().m_glyph_run->append(glyph); } diff --git a/Userland/Libraries/LibWeb/Layout/LineBox.h b/Userland/Libraries/LibWeb/Layout/LineBox.h index 172ee5e43b4..bc4debde8ff 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBox.h +++ b/Userland/Libraries/LibWeb/Layout/LineBox.h @@ -20,7 +20,7 @@ public: CSSPixels bottom() const { return m_bottom; } CSSPixels baseline() const { return m_baseline; } - void add_fragment(Node const& layout_node, int start, int length, CSSPixels leading_size, CSSPixels trailing_size, CSSPixels leading_margin, CSSPixels trailing_margin, CSSPixels content_width, CSSPixels content_height, CSSPixels border_box_top, CSSPixels border_box_bottom, Vector = {}); + void add_fragment(Node const& layout_node, int start, int length, CSSPixels leading_size, CSSPixels trailing_size, CSSPixels leading_margin, CSSPixels trailing_margin, CSSPixels content_width, CSSPixels content_height, CSSPixels border_box_top, CSSPixels border_box_bottom, RefPtr glyph_run = {}); Vector const& fragments() const { return m_fragments; } Vector& fragments() { return m_fragments; } diff --git a/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h index e44a87b0f05..b5fb84a2aeb 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h +++ b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h @@ -19,14 +19,14 @@ class LineBoxFragment { friend class LineBox; public: - LineBoxFragment(Node const& layout_node, int start, int length, CSSPixelPoint offset, CSSPixelSize size, CSSPixels border_box_top, Vector glyphs) + LineBoxFragment(Node const& layout_node, int start, int length, CSSPixelPoint offset, CSSPixelSize size, CSSPixels border_box_top, RefPtr glyph_run) : m_layout_node(layout_node) , m_start(start) , m_length(length) , m_offset(offset) , m_size(size) , m_border_box_top(border_box_top) - , m_glyph_run(adopt_ref(*new Gfx::GlyphRun(move(glyphs)))) + , m_glyph_run(move(glyph_run)) { } @@ -59,7 +59,7 @@ public: bool is_atomic_inline() const; - Gfx::GlyphRun const& glyph_run() const { return *m_glyph_run; } + RefPtr glyph_run() const { return m_glyph_run; } private: JS::NonnullGCPtr m_layout_node; @@ -69,7 +69,7 @@ private: CSSPixelSize m_size; CSSPixels m_border_box_top { 0 }; CSSPixels m_baseline { 0 }; - NonnullRefPtr m_glyph_run; + RefPtr m_glyph_run; }; } diff --git a/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp b/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp index cf7d861516b..72f80c8cae6 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp +++ b/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp @@ -97,7 +97,7 @@ void LineBuilder::append_box(Box const& box, CSSPixels leading_size, CSSPixels t }; } -void LineBuilder::append_text_chunk(TextNode const& text_node, size_t offset_in_node, size_t length_in_node, CSSPixels leading_size, CSSPixels trailing_size, CSSPixels leading_margin, CSSPixels trailing_margin, CSSPixels content_width, CSSPixels content_height, Vector glyph_run) +void LineBuilder::append_text_chunk(TextNode const& text_node, size_t offset_in_node, size_t length_in_node, CSSPixels leading_size, CSSPixels trailing_size, CSSPixels leading_margin, CSSPixels trailing_margin, CSSPixels content_width, CSSPixels content_height, RefPtr glyph_run) { ensure_last_line_box().add_fragment(text_node, offset_in_node, length_in_node, leading_size, trailing_size, leading_margin, trailing_margin, content_width, content_height, 0, 0, move(glyph_run)); m_max_height_on_current_line = max(m_max_height_on_current_line, content_height); diff --git a/Userland/Libraries/LibWeb/Layout/LineBuilder.h b/Userland/Libraries/LibWeb/Layout/LineBuilder.h index 3515740a564..7b3f6ff8aee 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBuilder.h +++ b/Userland/Libraries/LibWeb/Layout/LineBuilder.h @@ -25,7 +25,7 @@ public: void break_line(ForcedBreak, Optional next_item_width = {}); void append_box(Box const&, CSSPixels leading_size, CSSPixels trailing_size, CSSPixels leading_margin, CSSPixels trailing_margin); - void append_text_chunk(TextNode const&, size_t offset_in_node, size_t length_in_node, CSSPixels leading_size, CSSPixels trailing_size, CSSPixels leading_margin, CSSPixels trailing_margin, CSSPixels content_width, CSSPixels content_height, Vector); + void append_text_chunk(TextNode const&, size_t offset_in_node, size_t length_in_node, CSSPixels leading_size, CSSPixels trailing_size, CSSPixels leading_margin, CSSPixels trailing_margin, CSSPixels content_width, CSSPixels content_height, RefPtr); // Returns whether a line break occurred. bool break_if_needed(CSSPixels next_item_width) diff --git a/Userland/Libraries/LibWeb/Layout/TextNode.cpp b/Userland/Libraries/LibWeb/Layout/TextNode.cpp index ccba8e6e5c8..aee8cb6720a 100644 --- a/Userland/Libraries/LibWeb/Layout/TextNode.cpp +++ b/Userland/Libraries/LibWeb/Layout/TextNode.cpp @@ -391,11 +391,12 @@ void TextNode::compute_text_for_rendering() m_text_for_rendering = MUST(builder.to_string()); } -TextNode::ChunkIterator::ChunkIterator(StringView text, bool wrap_lines, bool respect_linebreaks) +TextNode::ChunkIterator::ChunkIterator(StringView text, bool wrap_lines, bool respect_linebreaks, Gfx::FontCascadeList const& font_cascade_list) : m_wrap_lines(wrap_lines) , m_respect_linebreaks(respect_linebreaks) , m_utf8_view(text) , m_iterator(m_utf8_view.begin()) + , m_font_cascade_list(font_cascade_list) { } @@ -406,16 +407,22 @@ Optional TextNode::ChunkIterator::next() auto start_of_chunk = m_iterator; + Gfx::Font const& font = m_font_cascade_list.font_for_code_point(*m_iterator); while (m_iterator != m_utf8_view.end()) { + if (&font != &m_font_cascade_list.font_for_code_point(*m_iterator)) { + if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false, font); result.has_value()) + return result.release_value(); + } + if (m_respect_linebreaks && *m_iterator == '\n') { // Newline encountered, and we're supposed to preserve them. // If we have accumulated some code points in the current chunk, commit them now and continue with the newline next time. - if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false); result.has_value()) + if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false, font); result.has_value()) return result.release_value(); // Otherwise, commit the newline! ++m_iterator; - auto result = try_commit_chunk(start_of_chunk, m_iterator, true); + auto result = try_commit_chunk(start_of_chunk, m_iterator, true, font); VERIFY(result.has_value()); return result.release_value(); } @@ -424,12 +431,12 @@ Optional TextNode::ChunkIterator::next() if (is_ascii_space(*m_iterator)) { // Whitespace encountered, and we're allowed to break on whitespace. // If we have accumulated some code points in the current chunk, commit them now and continue with the whitespace next time. - if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false); result.has_value()) + if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false, font); result.has_value()) return result.release_value(); // Otherwise, commit the whitespace! ++m_iterator; - if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false); result.has_value()) + if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false, font); result.has_value()) return result.release_value(); continue; } @@ -440,14 +447,14 @@ Optional TextNode::ChunkIterator::next() if (start_of_chunk != m_utf8_view.end()) { // Try to output whatever's left at the end of the text node. - if (auto result = try_commit_chunk(start_of_chunk, m_utf8_view.end(), false); result.has_value()) + if (auto result = try_commit_chunk(start_of_chunk, m_utf8_view.end(), false, font); result.has_value()) return result.release_value(); } return {}; } -Optional TextNode::ChunkIterator::try_commit_chunk(Utf8View::Iterator const& start, Utf8View::Iterator const& end, bool has_breaking_newline) const +Optional TextNode::ChunkIterator::try_commit_chunk(Utf8View::Iterator const& start, Utf8View::Iterator const& end, bool has_breaking_newline, Gfx::Font const& font) const { auto byte_offset = m_utf8_view.byte_offset_of(start); auto byte_length = m_utf8_view.byte_offset_of(end) - byte_offset; @@ -456,6 +463,7 @@ Optional TextNode::ChunkIterator::try_commit_chunk(Utf8View::It auto chunk_view = m_utf8_view.substring_view(byte_offset, byte_length); return Chunk { .view = chunk_view, + .font = font, .start = byte_offset, .length = byte_length, .has_breaking_newline = has_breaking_newline, diff --git a/Userland/Libraries/LibWeb/Layout/TextNode.h b/Userland/Libraries/LibWeb/Layout/TextNode.h index 6662f9184a3..72c3b09033e 100644 --- a/Userland/Libraries/LibWeb/Layout/TextNode.h +++ b/Userland/Libraries/LibWeb/Layout/TextNode.h @@ -28,6 +28,7 @@ public: struct Chunk { Utf8View view; + NonnullRefPtr font; size_t start { 0 }; size_t length { 0 }; bool has_breaking_newline { false }; @@ -36,16 +37,17 @@ public: class ChunkIterator { public: - ChunkIterator(StringView text, bool wrap_lines, bool respect_linebreaks); + ChunkIterator(StringView text, bool wrap_lines, bool respect_linebreaks, Gfx::FontCascadeList const&); Optional next(); private: - Optional try_commit_chunk(Utf8View::Iterator const& start, Utf8View::Iterator const& end, bool has_breaking_newline) const; + Optional try_commit_chunk(Utf8View::Iterator const& start, Utf8View::Iterator const& end, bool has_breaking_newline, Gfx::Font const&) const; bool const m_wrap_lines; bool const m_respect_linebreaks; Utf8View m_utf8_view; Utf8View::Iterator m_iterator; + Gfx::FontCascadeList const& m_font_cascade_list; }; void invalidate_text_for_rendering(); diff --git a/Userland/Libraries/LibWeb/Painting/BackgroundPainting.cpp b/Userland/Libraries/LibWeb/Painting/BackgroundPainting.cpp index 26e21a91071..edbaa15a923 100644 --- a/Userland/Libraries/LibWeb/Painting/BackgroundPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/BackgroundPainting.cpp @@ -62,11 +62,15 @@ static Vector compute_text_clip_paths(PaintContext& context, Paintabl { Vector text_clip_paths; auto add_text_clip_path = [&](PaintableFragment const& fragment) { + auto glyph_run = fragment.glyph_run(); + if (!glyph_run || glyph_run->glyphs().is_empty()) + return; // Scale to the device pixels. Gfx::Path glyph_run_path; - for (auto glyph : fragment.glyph_run().glyphs()) { + auto const& font = fragment.glyph_run()->font(); + auto scaled_font = font.with_size(font.point_size() * static_cast(context.device_pixels_per_css_pixel())); + for (auto glyph : fragment.glyph_run()->glyphs()) { glyph.visit([&](auto& glyph) { - glyph.font = glyph.font->with_size(glyph.font->point_size() * static_cast(context.device_pixels_per_css_pixel())); glyph.position = glyph.position.scaled(context.device_pixels_per_css_pixel()); }); @@ -75,13 +79,12 @@ static Vector compute_text_clip_paths(PaintContext& context, Paintabl // Get the path for the glyph. Gfx::Path glyph_path; - auto const& scaled_font = static_cast(*draw_glyph.font); - auto glyph_id = scaled_font.glyph_id_for_code_point(draw_glyph.code_point); - scaled_font.append_glyph_path_to(glyph_path, glyph_id); + auto glyph_id = scaled_font->glyph_id_for_code_point(draw_glyph.code_point); + scaled_font->append_glyph_path_to(glyph_path, glyph_id); // Transform the path to the fragment's position. // FIXME: Record glyphs and use Painter::draw_glyphs() instead to avoid this duplicated code. - auto top_left = draw_glyph.position + Gfx::FloatPoint(scaled_font.glyph_left_bearing(draw_glyph.code_point), 0); + auto top_left = draw_glyph.position + Gfx::FloatPoint(scaled_font->glyph_left_bearing(draw_glyph.code_point), 0); auto glyph_position = Gfx::GlyphRasterPosition::get_nearest_fit_for(top_left); auto transform = Gfx::AffineTransform {}.translate(glyph_position.blit_position.to_type()); glyph_run_path.append_path(glyph_path.copy_transformed(transform)); diff --git a/Userland/Libraries/LibWeb/Painting/DisplayList.cpp b/Userland/Libraries/LibWeb/Painting/DisplayList.cpp index f0e2b4193a2..9f852501b24 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayList.cpp +++ b/Userland/Libraries/LibWeb/Painting/DisplayList.cpp @@ -86,11 +86,12 @@ void DisplayList::execute(DisplayListPlayer& executor) auto& command = command_with_scroll_id.command; if (command.has()) { auto scale = command.get().scale; + auto const& font = command.get().glyph_run->font(); + auto scaled_font = font.with_size(font.point_size() * static_cast(scale)); for (auto const& glyph_or_emoji : command.get().glyph_run->glyphs()) { if (glyph_or_emoji.has()) { auto const& glyph = glyph_or_emoji.get(); - auto font = glyph.font->with_size(glyph.font->point_size() * static_cast(scale)); - unique_glyphs.ensure(font, [] { return HashTable {}; }).set(glyph.code_point); + unique_glyphs.ensure(scaled_font, [] { return HashTable {}; }).set(glyph.code_point); } } } diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerCPU.cpp b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerCPU.cpp index d33cbb70106..eaba8d6a2fb 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerCPU.cpp +++ b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerCPU.cpp @@ -30,18 +30,19 @@ CommandResult DisplayListPlayerCPU::draw_glyph_run(DrawGlyphRun const& command) { auto& painter = this->painter(); auto const& glyphs = command.glyph_run->glyphs(); - for (auto& glyph_or_emoji : glyphs) { + auto const& font = command.glyph_run->font(); + auto scaled_font = font.with_size(font.point_size() * static_cast(command.scale)); + for (auto const& glyph_or_emoji : glyphs) { auto transformed_glyph = glyph_or_emoji; transformed_glyph.visit([&](auto& glyph) { glyph.position = glyph.position.scaled(command.scale).translated(command.translation); - glyph.font = glyph.font->with_size(glyph.font->point_size() * static_cast(command.scale)); }); if (glyph_or_emoji.has()) { auto& glyph = transformed_glyph.get(); - painter.draw_glyph(glyph.position, glyph.code_point, *glyph.font, command.color); + painter.draw_glyph(glyph.position, glyph.code_point, *scaled_font, command.color); } else { auto& emoji = transformed_glyph.get(); - painter.draw_emoji(emoji.position.to_type(), *emoji.emoji, *emoji.font); + painter.draw_emoji(emoji.position.to_type(), *emoji.emoji, *scaled_font); } } return CommandResult::Continue; @@ -273,18 +274,19 @@ CommandResult DisplayListPlayerCPU::paint_text_shadow(PaintTextShadow const& com Gfx::IntPoint const baseline_start(command.text_rect.x(), command.text_rect.y()); shadow_painter.translate(baseline_start); auto const& glyphs = command.glyph_run->glyphs(); + auto const& font = command.glyph_run->font(); + auto scaled_font = font.with_size(font.point_size() * static_cast(command.glyph_run_scale)); for (auto const& glyph_or_emoji : glyphs) { auto transformed_glyph = glyph_or_emoji; transformed_glyph.visit([&](auto& glyph) { glyph.position = glyph.position.scaled(command.glyph_run_scale); - glyph.font = glyph.font->with_size(glyph.font->point_size() * static_cast(command.glyph_run_scale)); }); if (glyph_or_emoji.has()) { auto& glyph = transformed_glyph.get(); - shadow_painter.draw_glyph(glyph.position, glyph.code_point, *glyph.font, command.color); + shadow_painter.draw_glyph(glyph.position, glyph.code_point, *scaled_font, command.color); } else { auto& emoji = transformed_glyph.get(); - shadow_painter.draw_emoji(emoji.position.to_type(), *emoji.emoji, *emoji.font); + shadow_painter.draw_emoji(emoji.position.to_type(), *emoji.emoji, *scaled_font); } } diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerGPU.cpp b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerGPU.cpp index 5948daf9e9e..6535a4c2f9b 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerGPU.cpp +++ b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerGPU.cpp @@ -36,15 +36,16 @@ CommandResult DisplayListPlayerGPU::draw_glyph_run(DrawGlyphRun const& command) Vector transformed_glyph_run; auto const& glyphs = command.glyph_run->glyphs(); transformed_glyph_run.ensure_capacity(glyphs.size()); + auto const& font = command.glyph_run->font(); + auto scaled_font = font.with_size(font.point_size() * static_cast(command.scale)); for (auto& glyph : glyphs) { auto transformed_glyph = glyph; transformed_glyph.visit([&](auto& glyph) { glyph.position = glyph.position.scaled(command.scale).translated(command.translation); - glyph.font = glyph.font->with_size(glyph.font->point_size() * static_cast(command.scale)); }); transformed_glyph_run.append(transformed_glyph); } - painter().draw_glyph_run(transformed_glyph_run, command.color); + painter().draw_glyph_run(transformed_glyph_run, scaled_font, command.color); return CommandResult::Continue; } @@ -192,7 +193,7 @@ CommandResult DisplayListPlayerGPU::paint_text_shadow(PaintTextShadow const& com Gfx::FloatRect const shadow_location { command.draw_location, command.shadow_bounding_rect.size() }; Gfx::IntPoint const baseline_start(command.text_rect.x(), command.text_rect.y()); text_shadow_painter->translate(baseline_start.to_type()); - text_shadow_painter->draw_glyph_run(command.glyph_run->glyphs(), command.color); + text_shadow_painter->draw_glyph_run(command.glyph_run->glyphs(), command.glyph_run->font(), command.color); if (command.blur_radius == 0) { painter().blit_canvas(shadow_location, *text_shadow_canvas); return CommandResult::Continue; diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp index 2386fd9e6e8..ee09ec8216f 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp +++ b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp @@ -289,19 +289,20 @@ CommandResult DisplayListPlayerSkia::draw_glyph_run(DrawGlyphRun const& command) SkPaint paint; paint.setColorFilter(SkColorFilters::Blend(to_skia_color(command.color), SkBlendMode::kSrcIn)); auto const& glyphs = command.glyph_run->glyphs(); + auto const& font = command.glyph_run->font(); + auto scaled_font = font.with_size(font.point_size() * static_cast(command.scale)); for (auto const& glyph_or_emoji : glyphs) { auto transformed_glyph = glyph_or_emoji; transformed_glyph.visit([&](auto& glyph) { glyph.position = glyph.position.scaled(command.scale).translated(command.translation); - glyph.font = glyph.font->with_size(glyph.font->point_size() * static_cast(command.scale)); }); if (transformed_glyph.has()) { auto& glyph = transformed_glyph.get(); auto const& point = glyph.position; auto const& code_point = glyph.code_point; - auto top_left = point + Gfx::FloatPoint(glyph.font->glyph_left_bearing(code_point), 0); + auto top_left = point + Gfx::FloatPoint(scaled_font->glyph_left_bearing(code_point), 0); auto glyph_position = Gfx::GlyphRasterPosition::get_nearest_fit_for(top_left); - auto maybe_font_glyph = glyph.font->glyph(code_point, glyph_position.subpixel_offset); + auto maybe_font_glyph = scaled_font->glyph(code_point, glyph_position.subpixel_offset); if (!maybe_font_glyph.has_value()) continue; if (maybe_font_glyph->is_color_bitmap()) { diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp index 79843b8b8e5..48148b34633 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp +++ b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp @@ -227,12 +227,10 @@ void DisplayListRecorder::draw_text(Gfx::IntRect const& rect, String raw_text, G if (rect.is_empty()) return; - auto glyph_run = adopt_ref(*new Gfx::GlyphRun); - auto font_cascade_list = Gfx::FontCascadeList::create(); - font_cascade_list->add(font); + auto glyph_run = adopt_ref(*new Gfx::GlyphRun({}, font)); float glyph_run_width = 0; Gfx::for_each_glyph_position( - { 0, 0 }, raw_text.code_points(), font_cascade_list, [&](Gfx::DrawGlyphOrEmoji const& glyph_or_emoji) { + { 0, 0 }, raw_text.code_points(), font, [&](Gfx::DrawGlyphOrEmoji const& glyph_or_emoji) { glyph_run->append(glyph_or_emoji); return IterationDecision::Continue; }, diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index 70fc9765dbc..d6c349a7d78 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -663,16 +663,20 @@ void paint_text_fragment(PaintContext& context, TextPaintable const& paintable, auto text = paintable.text_for_rendering(); + auto glyph_run = fragment.glyph_run(); + if (!glyph_run) + return; + DevicePixelPoint baseline_start { fragment_absolute_device_rect.x(), fragment_absolute_device_rect.y() + context.rounded_device_pixels(fragment.baseline()) }; auto scale = context.device_pixels_per_css_pixel(); - painter.draw_text_run(baseline_start.to_type(), fragment.glyph_run(), paintable.computed_values().color(), fragment_absolute_device_rect.to_type(), scale); + painter.draw_text_run(baseline_start.to_type(), *glyph_run, paintable.computed_values().color(), fragment_absolute_device_rect.to_type(), scale); auto selection_rect = context.enclosing_device_rect(fragment.selection_rect(paintable.layout_node().first_available_font())).to_type(); if (!selection_rect.is_empty()) { painter.fill_rect(selection_rect, CSS::SystemColor::highlight()); DisplayListRecorderStateSaver saver(painter); painter.add_clip_rect(selection_rect); - painter.draw_text_run(baseline_start.to_type(), fragment.glyph_run(), CSS::SystemColor::highlight_text(), fragment_absolute_device_rect.to_type(), scale); + painter.draw_text_run(baseline_start.to_type(), *glyph_run, CSS::SystemColor::highlight_text(), fragment_absolute_device_rect.to_type(), scale); } paint_text_decoration(context, paintable, fragment); diff --git a/Userland/Libraries/LibWeb/Painting/PaintableFragment.h b/Userland/Libraries/LibWeb/Painting/PaintableFragment.h index 222bde133b6..5be106778a7 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableFragment.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableFragment.h @@ -38,7 +38,7 @@ public: CSSPixelRect const absolute_rect() const; - Gfx::GlyphRun const& glyph_run() const { return *m_glyph_run; } + RefPtr glyph_run() const { return m_glyph_run; } CSSPixelRect selection_rect(Gfx::Font const&) const; @@ -57,7 +57,7 @@ private: int m_start; int m_length; Painting::BorderRadiiData m_border_radii_data; - NonnullRefPtr m_glyph_run; + RefPtr m_glyph_run; Vector m_shadows; }; diff --git a/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp b/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp index 67a25be25b1..76de6318082 100644 --- a/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp @@ -579,7 +579,11 @@ void paint_box_shadow(PaintContext& context, void paint_text_shadow(PaintContext& context, PaintableFragment const& fragment, Vector const& shadow_layers) { - if (shadow_layers.is_empty() || fragment.glyph_run().is_empty()) + if (shadow_layers.is_empty()) + return; + + auto glyph_run = fragment.glyph_run(); + if (!glyph_run || glyph_run->glyphs().is_empty()) return; auto fragment_width = context.enclosing_device_pixels(fragment.width()).value(); @@ -610,7 +614,7 @@ void paint_text_shadow(PaintContext& context, PaintableFragment const& fragment, draw_rect.y() + offset_y - margin }; - context.display_list_recorder().paint_text_shadow(blur_radius, bounding_rect, text_rect.translated(0, fragment_baseline), fragment.glyph_run(), context.device_pixels_per_css_pixel(), layer.color, draw_location); + context.display_list_recorder().paint_text_shadow(blur_radius, bounding_rect, text_rect.translated(0, fragment_baseline), *glyph_run, context.device_pixels_per_css_pixel(), layer.color, draw_location); } }