Skip to content

Commit

Permalink
Everywhere: Limit layout text fragments to use one font for all glyphs
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
kalenikaliaksandr committed Jun 30, 2024
1 parent b95c05b commit 7181c3f
Show file tree
Hide file tree
Showing 25 changed files with 98 additions and 83 deletions.
3 changes: 1 addition & 2 deletions Userland/Libraries/LibAccelGfx/Painter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion Userland/Libraries/LibAccelGfx/Painter.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class Painter {
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();
Expand Down
2 changes: 2 additions & 0 deletions Userland/Libraries/LibGfx/Font/Font.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ class Font : public RefCounted<Font> {
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;
Expand Down
4 changes: 2 additions & 2 deletions Userland/Libraries/LibGfx/Font/ScaledFont.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -44,6 +42,8 @@ class ScaledFont final : public Gfx::Font {
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
Expand Down
9 changes: 2 additions & 7 deletions Userland/Libraries/LibGfx/Path.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 0 additions & 4 deletions Userland/Libraries/LibGfx/TextLayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ DrawGlyphOrEmoji prepare_draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIter
return DrawGlyph {
.position = point,
.code_point = code_point,
.font = font,
};
}

Expand All @@ -46,7 +45,6 @@ DrawGlyphOrEmoji prepare_draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIter
return DrawEmoji {
.position = point,
.emoji = emoji,
.font = font,
};
}

Expand All @@ -55,7 +53,6 @@ DrawGlyphOrEmoji prepare_draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIter
return DrawGlyph {
.position = point,
.code_point = code_point,
.font = font,
};
}

Expand All @@ -64,7 +61,6 @@ DrawGlyphOrEmoji prepare_draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIter
return DrawGlyph {
.position = point,
.code_point = 0xFFFD,
.font = font,
};
}

Expand Down
25 changes: 12 additions & 13 deletions Userland/Libraries/LibGfx/TextLayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ enum class IncludeLeftBearing {
struct DrawGlyph {
FloatPoint position;
u32 code_point;
NonnullRefPtr<Font const> font;

void translate_by(FloatPoint const& delta)
{
Expand All @@ -44,7 +43,6 @@ struct DrawGlyph {
struct DrawEmoji {
FloatPoint position;
Gfx::Bitmap const* emoji;
NonnullRefPtr<Font const> font;

void translate_by(FloatPoint const& delta)
{
Expand All @@ -56,54 +54,55 @@ 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;

auto point = baseline_start;
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);
last_code_point = code_point;
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);
Expand Down
6 changes: 3 additions & 3 deletions Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
Expand All @@ -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,
Expand Down Expand Up @@ -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();
}
Expand Down
2 changes: 1 addition & 1 deletion Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class InlineLevelIterator {
};
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 };
Expand Down
6 changes: 3 additions & 3 deletions Userland/Libraries/LibWeb/Layout/LineBox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion Userland/Libraries/LibWeb/Layout/LineBox.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class LineBox {
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; }
Expand Down
8 changes: 4 additions & 4 deletions Userland/Libraries/LibWeb/Layout/LineBoxFragment.h
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
}

Expand Down Expand Up @@ -59,7 +59,7 @@ class LineBoxFragment {

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;
Expand All @@ -69,7 +69,7 @@ class LineBoxFragment {
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;
};

}
2 changes: 1 addition & 1 deletion Userland/Libraries/LibWeb/Layout/LineBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion Userland/Libraries/LibWeb/Layout/LineBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class LineBuilder {

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)
Expand Down
Loading

0 comments on commit 7181c3f

Please sign in to comment.