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.
This commit is contained in:
Aliaksandr Kalenik 2024-06-29 17:14:23 +02:00 committed by Alexander Kalenik
commit 7181c3f2ea
Notes: sideshowbarker 2024-07-17 06:35:16 +09:00
25 changed files with 98 additions and 83 deletions

View file

@ -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<Gfx::DrawGlyphOrEmoji const> glyph_run, Color const& color)
void Painter::draw_glyph_run(Span<Gfx::DrawGlyphOrEmoji const> glyph_run, Gfx::Font const& font, Color const& color)
{
bind_target_canvas();
@ -420,7 +420,6 @@ void Painter::draw_glyph_run(Span<Gfx::DrawGlyphOrEmoji const> glyph_run, Color
if (glyph_or_emoji.has<Gfx::DrawGlyph>()) {
auto const& glyph = glyph_or_emoji.get<Gfx::DrawGlyph>();
auto const& font = *glyph.font;
auto code_point = glyph.code_point;
auto point = glyph.position;

View file

@ -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<Gfx::DrawGlyphOrEmoji const> glyph_run, Color const& color);
void draw_glyph_run(Span<Gfx::DrawGlyphOrEmoji const> glyph_run, Gfx::Font const&, Color const& color);
void set_clip_rect(Gfx::IntRect);
void clear_clip_rect();

View file

@ -120,6 +120,8 @@ public:
virtual Optional<Glyph> 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;

View file

@ -24,11 +24,9 @@ struct GlyphIndexWithSubpixelOffset {
class ScaledFont final : public Gfx::Font {
public:
ScaledFont(NonnullRefPtr<Typeface>, 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<Gfx::Bitmap> 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

View file

@ -167,10 +167,8 @@ void Path::text(Utf8View text, Font const& font)
}
auto& scaled_font = static_cast<ScaledFont const&>(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<DrawGlyph>()) {
auto& glyph = glyph_or_emoji.get<DrawGlyph>();
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<Gfx::ScaledFont const&>(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<Gfx::DrawGlyph>();
if (!glyph)
return;

View file

@ -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,
};
}

View file

@ -33,7 +33,6 @@ enum class IncludeLeftBearing {
struct DrawGlyph {
FloatPoint position;
u32 code_point;
NonnullRefPtr<Font const> font;
void translate_by(FloatPoint const& delta)
{
@ -44,7 +43,6 @@ struct DrawGlyph {
struct DrawEmoji {
FloatPoint position;
Gfx::Bitmap const* emoji;
NonnullRefPtr<Font const> font;
void translate_by(FloatPoint const& delta)
{
@ -56,28 +54,30 @@ using DrawGlyphOrEmoji = Variant<DrawGlyph, DrawEmoji>;
class GlyphRun : public RefCounted<GlyphRun> {
public:
GlyphRun() = default;
GlyphRun(Vector<Gfx::DrawGlyphOrEmoji>&& glyphs)
GlyphRun(Vector<Gfx::DrawGlyphOrEmoji>&& glyphs, NonnullRefPtr<Font> font)
: m_glyphs(move(glyphs))
, m_font(move(font))
{
}
[[nodiscard]] Font const& font() const { return m_font; }
[[nodiscard]] Vector<Gfx::DrawGlyphOrEmoji> const& glyphs() const { return m_glyphs; }
[[nodiscard]] Vector<Gfx::DrawGlyphOrEmoji>& 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<Gfx::DrawGlyphOrEmoji> m_glyphs;
NonnullRefPtr<Font> m_font;
};
Variant<DrawGlyph, DrawEmoji> prepare_draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIterator& it, Font const& font);
template<typename Callback>
void for_each_glyph_position(FloatPoint baseline_start, Utf8View string, FontCascadeList const& font_list, Callback callback, IncludeLeftBearing include_left_bearing = IncludeLeftBearing::No, Optional<float&> width = {})
void for_each_glyph_position(FloatPoint baseline_start, Utf8View string, Gfx::Font const& font, Callback callback, IncludeLeftBearing include_left_bearing = IncludeLeftBearing::No, Optional<float&> 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<Gfx::Font const> 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<DrawGlyph>())
glyph_or_emoji.get<DrawGlyph>().position += FloatPoint(font->glyph_left_bearing(code_point), 0);
glyph_or_emoji.get<DrawGlyph>().position += FloatPoint(font.glyph_left_bearing(code_point), 0);
}
callback(glyph_or_emoji);

View file

@ -198,7 +198,7 @@ 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.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::Item> 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();
}

View file

@ -32,7 +32,7 @@ public:
};
Type type {};
JS::GCPtr<Layout::Node const> node {};
Vector<Gfx::DrawGlyphOrEmoji> glyph_run {};
RefPtr<Gfx::GlyphRun> glyph_run {};
size_t offset_in_node { 0 };
size_t length_in_node { 0 };
CSSPixels width { 0.0f };

View file

@ -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<Gfx::DrawGlyphOrEmoji> 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<Gfx::GlyphRun> 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);
}

View file

@ -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<Gfx::DrawGlyphOrEmoji> = {});
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<Gfx::GlyphRun> glyph_run = {});
Vector<LineBoxFragment> const& fragments() const { return m_fragments; }
Vector<LineBoxFragment>& fragments() { return m_fragments; }

View file

@ -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<Gfx::DrawGlyphOrEmoji> glyphs)
LineBoxFragment(Node const& layout_node, int start, int length, CSSPixelPoint offset, CSSPixelSize size, CSSPixels border_box_top, RefPtr<Gfx::GlyphRun> 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<Gfx::GlyphRun> glyph_run() const { return m_glyph_run; }
private:
JS::NonnullGCPtr<Node const> m_layout_node;
@ -69,7 +69,7 @@ private:
CSSPixelSize m_size;
CSSPixels m_border_box_top { 0 };
CSSPixels m_baseline { 0 };
NonnullRefPtr<Gfx::GlyphRun> m_glyph_run;
RefPtr<Gfx::GlyphRun> m_glyph_run;
};
}

View file

@ -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<Gfx::DrawGlyphOrEmoji> 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<Gfx::GlyphRun> 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);

View file

@ -25,7 +25,7 @@ public:
void break_line(ForcedBreak, Optional<CSSPixels> 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<Gfx::DrawGlyphOrEmoji>);
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<Gfx::GlyphRun>);
// Returns whether a line break occurred.
bool break_if_needed(CSSPixels next_item_width)

View file

@ -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::Chunk> 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::Chunk> 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::Chunk> 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::Chunk> TextNode::ChunkIterator::try_commit_chunk(Utf8View::Iterator const& start, Utf8View::Iterator const& end, bool has_breaking_newline) const
Optional<TextNode::Chunk> 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::Chunk> 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,

View file

@ -28,6 +28,7 @@ public:
struct Chunk {
Utf8View view;
NonnullRefPtr<Gfx::Font> 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<Chunk> next();
private:
Optional<Chunk> try_commit_chunk(Utf8View::Iterator const& start, Utf8View::Iterator const& end, bool has_breaking_newline) const;
Optional<Chunk> 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();

View file

@ -62,11 +62,15 @@ static Vector<Gfx::Path> compute_text_clip_paths(PaintContext& context, Paintabl
{
Vector<Gfx::Path> 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<float>(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<float>(context.device_pixels_per_css_pixel()));
glyph.position = glyph.position.scaled(context.device_pixels_per_css_pixel());
});
@ -75,13 +79,12 @@ static Vector<Gfx::Path> compute_text_clip_paths(PaintContext& context, Paintabl
// Get the path for the glyph.
Gfx::Path glyph_path;
auto const& scaled_font = static_cast<Gfx::ScaledFont const&>(*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<float>());
glyph_run_path.append_path(glyph_path.copy_transformed(transform));

View file

@ -86,11 +86,12 @@ void DisplayList::execute(DisplayListPlayer& executor)
auto& command = command_with_scroll_id.command;
if (command.has<DrawGlyphRun>()) {
auto scale = command.get<DrawGlyphRun>().scale;
auto const& font = command.get<DrawGlyphRun>().glyph_run->font();
auto scaled_font = font.with_size(font.point_size() * static_cast<float>(scale));
for (auto const& glyph_or_emoji : command.get<DrawGlyphRun>().glyph_run->glyphs()) {
if (glyph_or_emoji.has<Gfx::DrawGlyph>()) {
auto const& glyph = glyph_or_emoji.get<Gfx::DrawGlyph>();
auto font = glyph.font->with_size(glyph.font->point_size() * static_cast<float>(scale));
unique_glyphs.ensure(font, [] { return HashTable<u32> {}; }).set(glyph.code_point);
unique_glyphs.ensure(scaled_font, [] { return HashTable<u32> {}; }).set(glyph.code_point);
}
}
}

View file

@ -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<float>(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<float>(command.scale));
});
if (glyph_or_emoji.has<Gfx::DrawGlyph>()) {
auto& glyph = transformed_glyph.get<Gfx::DrawGlyph>();
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<Gfx::DrawEmoji>();
painter.draw_emoji(emoji.position.to_type<int>(), *emoji.emoji, *emoji.font);
painter.draw_emoji(emoji.position.to_type<int>(), *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<float>(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<float>(command.glyph_run_scale));
});
if (glyph_or_emoji.has<Gfx::DrawGlyph>()) {
auto& glyph = transformed_glyph.get<Gfx::DrawGlyph>();
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<Gfx::DrawEmoji>();
shadow_painter.draw_emoji(emoji.position.to_type<int>(), *emoji.emoji, *emoji.font);
shadow_painter.draw_emoji(emoji.position.to_type<int>(), *emoji.emoji, *scaled_font);
}
}

View file

@ -36,15 +36,16 @@ CommandResult DisplayListPlayerGPU::draw_glyph_run(DrawGlyphRun const& command)
Vector<Gfx::DrawGlyphOrEmoji> 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<float>(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<float>(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<float>());
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;

View file

@ -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<float>(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<float>(command.scale));
});
if (transformed_glyph.has<Gfx::DrawGlyph>()) {
auto& glyph = transformed_glyph.get<Gfx::DrawGlyph>();
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()) {

View file

@ -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;
},

View file

@ -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<int>(), fragment.glyph_run(), paintable.computed_values().color(), fragment_absolute_device_rect.to_type<int>(), scale);
painter.draw_text_run(baseline_start.to_type<int>(), *glyph_run, paintable.computed_values().color(), fragment_absolute_device_rect.to_type<int>(), scale);
auto selection_rect = context.enclosing_device_rect(fragment.selection_rect(paintable.layout_node().first_available_font())).to_type<int>();
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<int>(), fragment.glyph_run(), CSS::SystemColor::highlight_text(), fragment_absolute_device_rect.to_type<int>(), scale);
painter.draw_text_run(baseline_start.to_type<int>(), *glyph_run, CSS::SystemColor::highlight_text(), fragment_absolute_device_rect.to_type<int>(), scale);
}
paint_text_decoration(context, paintable, fragment);

View file

@ -38,7 +38,7 @@ public:
CSSPixelRect const absolute_rect() const;
Gfx::GlyphRun const& glyph_run() const { return *m_glyph_run; }
RefPtr<Gfx::GlyphRun> 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<Gfx::GlyphRun> m_glyph_run;
RefPtr<Gfx::GlyphRun> m_glyph_run;
Vector<ShadowData> m_shadows;
};

View file

@ -579,7 +579,11 @@ void paint_box_shadow(PaintContext& context,
void paint_text_shadow(PaintContext& context, PaintableFragment const& fragment, Vector<ShadowData> 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);
}
}