summaryrefslogtreecommitdiff
path: root/Libraries
diff options
context:
space:
mode:
authorAndreas Kling <awesomekling@gmail.com>2019-10-03 15:20:13 +0200
committerAndreas Kling <awesomekling@gmail.com>2019-10-03 15:20:13 +0200
commit1d65cf367f08aaa0f6c5cbc3afc780839445864b (patch)
tree8d1f72a0e2c07260c53eac0c9774f1f4db401006 /Libraries
parent5966fcff31c62469a1a18b16aa42f9cb1af8ac96 (diff)
downloadserenity-1d65cf367f08aaa0f6c5cbc3afc780839445864b.zip
LibHTML: Rewrite inline and text layout
Inline layout is now done by LayoutBlock. Blocks with inline children will split them into line boxes during layout. A LayoutBlock can have zero or more LineBox objects. Each LineBox represents one visual line. A LineBox can have any number of LineBoxFragment children. A fragment is an offset+length into a specific LayoutNode. To paint a LayoutBlock with inline children, we walk its line boxes, and walk their fragments, painting each fragment at a time by calling LineBoxFragment::render(), which in turn calls the LayoutNode via LayoutText::render_fragment(). Hit testing works similarly. This is very incomplete and has many bugs, but should make it easier for us to move forward with this code.
Diffstat (limited to 'Libraries')
-rw-r--r--Libraries/LibHTML/Dump.cpp39
-rw-r--r--Libraries/LibHTML/Layout/LayoutBlock.cpp70
-rw-r--r--Libraries/LibHTML/Layout/LayoutBlock.h13
-rw-r--r--Libraries/LibHTML/Layout/LayoutInline.cpp12
-rw-r--r--Libraries/LibHTML/Layout/LayoutInline.h3
-rw-r--r--Libraries/LibHTML/Layout/LayoutText.cpp250
-rw-r--r--Libraries/LibHTML/Layout/LayoutText.h21
-rw-r--r--Libraries/LibHTML/Layout/LineBox.cpp14
-rw-r--r--Libraries/LibHTML/Layout/LineBox.h20
-rw-r--r--Libraries/LibHTML/Layout/LineBoxFragment.cpp12
-rw-r--r--Libraries/LibHTML/Layout/LineBoxFragment.h32
-rw-r--r--Libraries/LibHTML/Makefile.shared2
12 files changed, 320 insertions, 168 deletions
diff --git a/Libraries/LibHTML/Dump.cpp b/Libraries/LibHTML/Dump.cpp
index 03d8263d1b..5707042b28 100644
--- a/Libraries/LibHTML/Dump.cpp
+++ b/Libraries/LibHTML/Dump.cpp
@@ -1,8 +1,10 @@
+#include <AK/Utf8View.h>
#include <LibHTML/CSS/StyleSheet.h>
#include <LibHTML/DOM/Document.h>
#include <LibHTML/DOM/Element.h>
#include <LibHTML/DOM/Text.h>
#include <LibHTML/Dump.h>
+#include <LibHTML/Layout/LayoutBlock.h>
#include <LibHTML/Layout/LayoutNode.h>
#include <LibHTML/Layout/LayoutText.h>
#include <stdio.h>
@@ -78,13 +80,40 @@ void dump_tree(const LayoutNode& layout_node)
layout_node.style().border().bottom.to_px(),
layout_node.style().margin().bottom.to_px());
- if (layout_node.is_text()) {
- const LayoutText& layout_text = static_cast<const LayoutText&>(layout_node);
- dbgprintf(" \"%s\", %d runs", layout_text.text().characters(), layout_text.runs().size());
- }
-
dbgprintf("\n");
+ if (layout_node.is_block() && static_cast<const LayoutBlock&>(layout_node).children_are_inline()) {
+ auto& block = static_cast<const LayoutBlock&>(layout_node);
+ for (int i = 0; i < indent; ++i)
+ dbgprintf(" ");
+ dbgprintf(" Line boxes (%d):\n", block.line_boxes().size());
+ for (int line_box_index = 0; line_box_index < block.line_boxes().size(); ++line_box_index) {
+ auto& line_box = block.line_boxes()[line_box_index];
+ for (int i = 0; i < indent; ++i)
+ dbgprintf(" ");
+ dbgprintf(" [%d] width: %d\n", line_box_index, line_box.width());
+ for (int fragment_index = 0; fragment_index < line_box.fragments().size(); ++fragment_index) {
+ auto& fragment = line_box.fragments()[fragment_index];
+ for (int i = 0; i < indent; ++i)
+ dbgprintf(" ");
+ dbgprintf(" [%d] layout_node: %s{%p}, start: %d, length: %d, rect: %s\n",
+ fragment_index,
+ fragment.layout_node().class_name(),
+ &fragment.layout_node(),
+ fragment.start(),
+ fragment.length(),
+ fragment.rect().to_string().characters());
+ if (fragment.layout_node().is_text()) {
+ for (int i = 0; i < indent; ++i)
+ dbgprintf(" ");
+ auto& layout_text = static_cast<const LayoutText&>(fragment.layout_node());
+ dbgprintf(" text: \"%s\"\n",
+ String(Utf8View(layout_text.node().data()).substring_view(fragment.start(), fragment.length()).as_string()).characters());
+ }
+ }
+ }
+ }
+
layout_node.style_properties().for_each_property([&](auto& key, auto& value) {
for (int i = 0; i < indent; ++i)
dbgprintf(" ");
diff --git a/Libraries/LibHTML/Layout/LayoutBlock.cpp b/Libraries/LibHTML/Layout/LayoutBlock.cpp
index 5ca7a76381..04f6ad80bc 100644
--- a/Libraries/LibHTML/Layout/LayoutBlock.cpp
+++ b/Libraries/LibHTML/Layout/LayoutBlock.cpp
@@ -1,6 +1,7 @@
#include <LibGUI/GPainter.h>
#include <LibHTML/DOM/Element.h>
#include <LibHTML/Layout/LayoutBlock.h>
+#include <LibHTML/Layout/LayoutInline.h>
LayoutBlock::LayoutBlock(const Node* node, StyleProperties&& style_properties)
: LayoutNode(node, move(style_properties))
@@ -24,14 +25,52 @@ void LayoutBlock::layout()
compute_width();
compute_position();
+ if (children_are_inline())
+ layout_inline_children();
+ else
+ layout_block_children();
+
+ compute_height();
+}
+
+void LayoutBlock::layout_block_children()
+{
+ ASSERT(!children_are_inline());
int content_height = 0;
for_each_child([&](auto& child) {
child.layout();
content_height = child.rect().bottom() + child.style().full_margin().bottom - rect().top();
});
rect().set_height(content_height);
+}
- compute_height();
+void LayoutBlock::layout_inline_children()
+{
+ ASSERT(children_are_inline());
+ m_line_boxes.clear();
+ for_each_child([&](auto& child) {
+ ASSERT(child.is_inline());
+ static_cast<LayoutInline&>(child).split_into_lines(*this);
+ });
+
+ int content_height = 0;
+
+ for (auto& line_box : m_line_boxes) {
+ int max_height = 0;
+ for (auto& fragment : line_box.fragments()) {
+ max_height = max(max_height, fragment.rect().height());
+ }
+ for (auto& fragment : line_box.fragments()) {
+ // Vertically align everyone's bottom to the line.
+ // FIXME: Support other kinds of vertical alignment.
+ fragment.rect().set_x(rect().x() + fragment.rect().x());
+ fragment.rect().set_y(rect().y() + content_height + (max_height - fragment.rect().height()));
+ }
+
+ content_height += max_height;
+ }
+
+ rect().set_height(content_height);
}
void LayoutBlock::compute_width()
@@ -163,4 +202,33 @@ void LayoutBlock::render(RenderingContext& context)
};
context.painter().fill_rect(bullet_rect, Color::Black);
}
+
+ if (children_are_inline()) {
+ for (auto& line_box : m_line_boxes) {
+ for (auto& fragment : line_box.fragments()) {
+ fragment.render(context);
+ }
+ }
+ }
+}
+
+bool LayoutBlock::children_are_inline() const
+{
+ return first_child() && !first_child()->is_block();
+}
+
+HitTestResult LayoutBlock::hit_test(const Point& position) const
+{
+ if (!children_are_inline())
+ return LayoutNode::hit_test(position);
+
+ HitTestResult result;
+ for (auto& line_box : m_line_boxes) {
+ for (auto& fragment : line_box.fragments()) {
+ if (fragment.rect().contains(position)) {
+ return { fragment.layout_node() };
+ }
+ }
+ }
+ return {};
}
diff --git a/Libraries/LibHTML/Layout/LayoutBlock.h b/Libraries/LibHTML/Layout/LayoutBlock.h
index 575e73430b..093f0adb6f 100644
--- a/Libraries/LibHTML/Layout/LayoutBlock.h
+++ b/Libraries/LibHTML/Layout/LayoutBlock.h
@@ -1,6 +1,7 @@
#pragma once
#include <LibHTML/Layout/LayoutNode.h>
+#include <LibHTML/Layout/LineBox.h>
class Element;
@@ -16,10 +17,22 @@ public:
virtual LayoutNode& inline_wrapper() override;
+ bool children_are_inline() const;
+
+ Vector<LineBox>& line_boxes() { return m_line_boxes; }
+ const Vector<LineBox>& line_boxes() const { return m_line_boxes; }
+
+ virtual HitTestResult hit_test(const Point&) const override;
+
private:
virtual bool is_block() const override { return true; }
+ void layout_inline_children();
+ void layout_block_children();
+
void compute_width();
void compute_position();
void compute_height();
+
+ Vector<LineBox> m_line_boxes;
};
diff --git a/Libraries/LibHTML/Layout/LayoutInline.cpp b/Libraries/LibHTML/Layout/LayoutInline.cpp
index d8c1652dab..687a78b8ed 100644
--- a/Libraries/LibHTML/Layout/LayoutInline.cpp
+++ b/Libraries/LibHTML/Layout/LayoutInline.cpp
@@ -1,4 +1,5 @@
#include <LibHTML/DOM/Element.h>
+#include <LibHTML/Layout/LayoutBlock.h>
#include <LibHTML/Layout/LayoutInline.h>
LayoutInline::LayoutInline(const Node& node, StyleProperties&& style_properties)
@@ -35,3 +36,14 @@ void LayoutInline::layout()
rect().set_bottom(child.rect().bottom() + child.style().full_margin().bottom);
});
}
+
+void LayoutInline::split_into_lines(LayoutBlock& container)
+{
+ for_each_child([&](auto& child) {
+ if (child.is_inline()) {
+ static_cast<LayoutInline&>(child).split_into_lines(container);
+ } else {
+ // FIXME: Support block children of inlines.
+ }
+ });
+}
diff --git a/Libraries/LibHTML/Layout/LayoutInline.h b/Libraries/LibHTML/Layout/LayoutInline.h
index 424fe654de..c8dcfb0625 100644
--- a/Libraries/LibHTML/Layout/LayoutInline.h
+++ b/Libraries/LibHTML/Layout/LayoutInline.h
@@ -3,6 +3,7 @@
#include <LibHTML/Layout/LayoutNode.h>
class Element;
+class LayoutBlock;
class LayoutInline : public LayoutNode {
public:
@@ -14,5 +15,7 @@ public:
virtual void layout() override;
+ virtual void split_into_lines(LayoutBlock& container);
+
private:
};
diff --git a/Libraries/LibHTML/Layout/LayoutText.cpp b/Libraries/LibHTML/Layout/LayoutText.cpp
index 9ea5c58a38..d9195e4018 100644
--- a/Libraries/LibHTML/Layout/LayoutText.cpp
+++ b/Libraries/LibHTML/Layout/LayoutText.cpp
@@ -1,4 +1,5 @@
#include <AK/StringBuilder.h>
+#include <AK/Utf8View.h>
#include <LibCore/CDirIterator.h>
#include <LibDraw/Font.h>
#include <LibGUI/GPainter.h>
@@ -7,7 +8,7 @@
#include <ctype.h>
LayoutText::LayoutText(const Text& text, StyleProperties&& style_properties)
- : LayoutNode(&text, move(style_properties))
+ : LayoutInline(text, move(style_properties))
{
}
@@ -86,184 +87,135 @@ const String& LayoutText::text() const
return node().data();
}
-static void split_first_word(const StringView& str, StringView& out_space, StringView& out_word)
+void LayoutText::render_fragment(RenderingContext& context, const LineBoxFragment& fragment) const
{
- int first_nonspace = -1;
- for (int i = 0; i < str.length(); i++)
- if (!isspace(str[i])) {
- first_nonspace = i;
- break;
- }
+ auto& painter = context.painter();
+ painter.set_font(*m_font);
- if (first_nonspace == -1) {
- out_space = str;
- out_word = {};
- return;
- }
+ auto color = style_properties().color_or_fallback("color", Color::Black);
+ auto text_decoration = style_properties().string_or_fallback("text-decoration", "none");
- int first_space = str.length();
- for (int i = first_nonspace + 1; i < str.length(); i++)
- if (isspace(str[i])) {
- first_space = i;
- break;
- }
+ bool is_underline = text_decoration == "underline";
+ if (is_underline)
+ painter.draw_line(fragment.rect().bottom_left().translated(0, 1), fragment.rect().bottom_right().translated(0, 1), color);
- out_space = str.substring_view(0, first_nonspace);
- out_word = str.substring_view(first_nonspace, first_space - first_nonspace);
+ painter.draw_text(fragment.rect(), node().data().substring_view(fragment.start(), fragment.length()), TextAlignment::TopLeft, color);
}
-void LayoutText::compute_runs()
+template<typename Callback>
+void LayoutText::for_each_word(Callback callback) const
{
- StringView remaining_text = node().data();
- if (remaining_text.is_empty())
+ Utf8View view(node().data());
+ if (view.is_empty())
return;
- int right_border = containing_block()->rect().x() + containing_block()->rect().width();
-
- StringBuilder builder;
- Point run_origin = rect().location();
-
- int total_right_margin = style().full_margin().right;
- bool is_preformatted = style_properties().string_or_fallback("white-space", "normal") != "normal";
-
- while (!remaining_text.is_empty()) {
- String saved_text = builder.string_view();
-
- // Try to append a new word.
- StringView space;
- StringView word;
- split_first_word(remaining_text, space, word);
-
- int forced_line_break_index = -1;
- if (is_preformatted)
- for (int i = 0; i < space.length(); i++)
- if (space[i] == '\n') {
- forced_line_break_index = i;
- break;
- }
-
- if (!space.is_empty()) {
- if (!is_preformatted) {
- builder.append(' ');
- } else if (forced_line_break_index != -1) {
- builder.append(space.substring_view(0, forced_line_break_index));
- } else {
- builder.append(space);
- }
+ auto start_of_word = view.begin();
+
+ auto commit_word = [&](auto it) {
+ int start = view.byte_offset_of(start_of_word);
+ int length = view.byte_offset_of(it) - view.byte_offset_of(start_of_word);
+
+ if (length > 0) {
+ callback(view.substring_view(start, length), start, length);
}
- if (forced_line_break_index == -1)
- builder.append(word);
- if (forced_line_break_index != -1)
- remaining_text = remaining_text.substring_view(forced_line_break_index + 1, remaining_text.length() - forced_line_break_index - 1);
- else if (!word.is_null())
- remaining_text = remaining_text.substring_view_starting_after_substring(word);
- else
- remaining_text = {};
+ start_of_word = it;
+ };
- // See if that fits.
- int width = m_font->width(builder.string_view());
- if (forced_line_break_index == -1 && run_origin.x() + width + total_right_margin < right_border)
- continue;
+ bool last_was_space = isspace(*view.begin());
- // If it doesn't, create a run from
- // what we had there previously.
- if (forced_line_break_index == -1)
- m_runs.append({ run_origin, move(saved_text) });
- else
- m_runs.append({ run_origin, builder.string_view() });
-
- // Start a new run at the new line.
- int line_spacing = 4;
- run_origin.set_x(containing_block()->rect().x() + style().full_margin().left);
- run_origin.move_by(0, m_font->glyph_height() + line_spacing);
- builder = StringBuilder();
- if (forced_line_break_index != -1)
+ for (auto it = view.begin(); it != view.end();) {
+ bool is_space = isspace(*it);
+ if (is_space == last_was_space) {
+ ++it;
continue;
- if (is_preformatted)
- builder.append(space);
- builder.append(word);
+ }
+ last_was_space = is_space;
+ commit_word(it);
+ ++it;
}
-
- // Add the last run.
- m_runs.append({ run_origin, builder.build() });
+ if (start_of_word != view.end())
+ commit_word(view.end());
}
-void LayoutText::layout()
+template<typename Callback>
+void LayoutText::for_each_source_line(Callback callback) const
{
- ASSERT(!has_children());
-
- if (!m_font)
- load_font();
+ Utf8View view(node().data());
+ if (view.is_empty())
+ return;
- int origin_x = -1;
- int origin_y = -1;
- if (previous_sibling() != nullptr) {
- auto& previous_sibling_rect = previous_sibling()->rect();
- auto& previous_sibling_style = previous_sibling()->style();
- origin_x = previous_sibling_rect.x() + previous_sibling_rect.width();
- origin_x += previous_sibling_style.full_margin().right;
- origin_y = previous_sibling_rect.y() + previous_sibling_rect.height() - m_font->glyph_height() - previous_sibling_style.full_margin().top;
- } else {
- origin_x = parent()->rect().x();
- origin_y = parent()->rect().y();
- }
- rect().set_x(origin_x + style().full_margin().left);
- rect().set_y(origin_y + style().full_margin().top);
+ auto start_of_line = view.begin();
- m_runs.clear();
- compute_runs();
+ auto commit_line = [&](auto it) {
+ int start = view.byte_offset_of(start_of_line);
+ int length = view.byte_offset_of(it) - view.byte_offset_of(start_of_line);
- if (m_runs.is_empty())
- return;
-
- const Run& last_run = m_runs[m_runs.size() - 1];
- rect().set_right(last_run.pos.x() + m_font->width(last_run.text));
- rect().set_bottom(last_run.pos.y() + m_font->glyph_height());
-}
+ if (length > 0) {
+ callback(view.substring_view(start, length), start, length);
+ }
+ };
-template<typename Callback>
-void LayoutText::for_each_run(Callback callback) const
-{
- for (auto& run : m_runs) {
- Rect rect {
- run.pos.x(),
- run.pos.y(),
- m_font->width(run.text),
- m_font->glyph_height()
- };
- if (callback(run, rect) == IterationDecision::Break)
- break;
+ for (auto it = view.begin(); it != view.end();) {
+ if (*it == '\n')
+ commit_line(it);
+ ++it;
+ start_of_line = it;
}
+ if (start_of_line != view.end())
+ commit_line(view.end());
}
-void LayoutText::render(RenderingContext& context)
+void LayoutText::split_into_lines(LayoutBlock& container)
{
- auto& painter = context.painter();
- painter.set_font(*m_font);
+ if (!m_font)
+ load_font();
- auto color = style_properties().color_or_fallback("color", Color::Black);
- auto text_decoration = style_properties().string_or_fallback("text-decoration", "none");
+ int space_width = m_font->glyph_width(' ') + m_font->glyph_spacing();
+ // FIXME: Allow overriding the line-height. We currently default to 140% which seems to look nice.
+ int line_height = (int)(m_font->glyph_height() * 1.4f);
+
+ auto& line_boxes = container.line_boxes();
+ if (line_boxes.is_empty())
+ line_boxes.append(LineBox());
+ int available_width = container.rect().width() - line_boxes.last().width();
+
+ bool is_preformatted = style_properties().string_or_fallback("white-space", "normal") == "pre";
+ if (is_preformatted) {
+ for_each_source_line([&](const Utf8View& view, int start, int length) {
+ line_boxes.last().add_fragment(*this, start, length, m_font->width(view), line_height);
+ line_boxes.append(LineBox());
+ });
+ return;
+ }
- bool is_underline = text_decoration == "underline";
+ struct Word {
+ Utf8View view;
+ int start;
+ int length;
+ };
+ Vector<Word> words;
- for_each_run([&](auto& run, auto& rect) {
- painter.draw_text(rect, run.text, TextAlignment::TopLeft, color);
- if (is_underline)
- painter.draw_line(rect.bottom_left().translated(0, 1), rect.bottom_right().translated(0, 1), color);
- return IterationDecision::Continue;
+ for_each_word([&](const Utf8View& view, int start, int length) {
+ words.append({ Utf8View(view), start, length });
+ dbg() << "Added _" << words.last().view.as_string() << "_";
});
-}
-HitTestResult LayoutText::hit_test(const Point& position) const
-{
- HitTestResult result;
- for_each_run([&](auto&, auto& rect) {
- if (rect.contains(position)) {
- result.layout_node = this;
- return IterationDecision::Break;
+ for (int i = 0; i < words.size(); ++i) {
+ auto& word = words[i];
+
+ int word_width;
+ if (isspace(*word.view.begin()))
+ word_width = space_width;
+ else
+ word_width = m_font->width(word.view);
+
+ if (word_width > available_width) {
+ line_boxes.append(LineBox());
+ available_width = container.rect().width();
}
- return IterationDecision::Continue;
- });
- return result;
+
+ line_boxes.last().add_fragment(*this, word.start, word.length, word_width, line_height);
+ available_width -= word_width;
+ }
}
diff --git a/Libraries/LibHTML/Layout/LayoutText.h b/Libraries/LibHTML/Layout/LayoutText.h
index 43d0f7265a..5d89f6e3a1 100644
--- a/Libraries/LibHTML/Layout/LayoutText.h
+++ b/Libraries/LibHTML/Layout/LayoutText.h
@@ -1,11 +1,12 @@
#pragma once
#include <LibHTML/DOM/Text.h>
-#include <LibHTML/Layout/LayoutNode.h>
+#include <LibHTML/Layout/LayoutInline.h>
class Font;
+class LineBoxFragment;
-class LayoutText : public LayoutNode {
+class LayoutText : public LayoutInline {
public:
LayoutText(const Text&, StyleProperties&&);
virtual ~LayoutText() override;
@@ -16,25 +17,19 @@ public:
virtual const char* class_name() const override { return "LayoutText"; }
virtual bool is_text() const final { return true; }
- virtual void layout() override;
- virtual void render(RenderingContext&) override;
- struct Run {
- Point pos;
- String text;
- };
+ void render_fragment(RenderingContext&, const LineBoxFragment&) const;
- const Vector<Run>& runs() const { return m_runs; }
-
- virtual HitTestResult hit_test(const Point&) const override;
+ virtual void split_into_lines(LayoutBlock& container) override;
private:
template<typename Callback>
- void for_each_run(Callback) const;
+ void for_each_word(Callback) const;
+ template<typename Callback>
+ void for_each_source_line(Callback) const;
void load_font();
void compute_runs();
- Vector<Run> m_runs;
RefPtr<Font> m_font;
};
diff --git a/Libraries/LibHTML/Layout/LineBox.cpp b/Libraries/LibHTML/Layout/LineBox.cpp
new file mode 100644
index 0000000000..55ea426b4b
--- /dev/null
+++ b/Libraries/LibHTML/Layout/LineBox.cpp
@@ -0,0 +1,14 @@
+#include <LibHTML/Layout/LineBox.h>
+
+void LineBox::add_fragment(const LayoutNode& layout_node, int start, int length, int width, int height)
+{
+ if (!m_fragments.is_empty() && &m_fragments.last().layout_node() == &layout_node) {
+ // The fragment we're adding is from the last LayoutNode on the line.
+ // Expand the last fragment instead of adding a new one with the same LayoutNode.
+ m_fragments.last().m_length = (start - m_fragments.last().m_start) + length;
+ m_fragments.last().m_rect.set_width(m_fragments.last().m_rect.width() + width);
+ } else {
+ m_fragments.empend(layout_node, start, length, Rect(m_width, 0, width, height));
+ }
+ m_width += width;
+}
diff --git a/Libraries/LibHTML/Layout/LineBox.h b/Libraries/LibHTML/Layout/LineBox.h
new file mode 100644
index 0000000000..a6cdf2810c
--- /dev/null
+++ b/Libraries/LibHTML/Layout/LineBox.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <AK/Vector.h>
+#include <LibHTML/Layout/LineBoxFragment.h>
+
+class LineBox {
+public:
+ LineBox() {}
+
+ int width() const { return m_width; }
+
+ void add_fragment(const LayoutNode& layout_node, int start, int length, int width, int height);
+
+ const Vector<LineBoxFragment>& fragments() const { return m_fragments; }
+ Vector<LineBoxFragment>& fragments() { return m_fragments; }
+
+private:
+ Vector<LineBoxFragment> m_fragments;
+ int m_width { 0 };
+};
diff --git a/Libraries/LibHTML/Layout/LineBoxFragment.cpp b/Libraries/LibHTML/Layout/LineBoxFragment.cpp
new file mode 100644
index 0000000000..1ff6755d6d
--- /dev/null
+++ b/Libraries/LibHTML/Layout/LineBoxFragment.cpp
@@ -0,0 +1,12 @@
+#include <LibGUI/GPainter.h>
+#include <LibHTML/Layout/LayoutText.h>
+#include <LibHTML/Layout/LineBoxFragment.h>
+#include <LibHTML/RenderingContext.h>
+
+void LineBoxFragment::render(RenderingContext& context)
+{
+ if (layout_node().is_text()) {
+ auto& layout_text = static_cast<const LayoutText&>(layout_node());
+ layout_text.render_fragment(context, *this);
+ }
+}
diff --git a/Libraries/LibHTML/Layout/LineBoxFragment.h b/Libraries/LibHTML/Layout/LineBoxFragment.h
new file mode 100644
index 0000000000..28e028cf8b
--- /dev/null
+++ b/Libraries/LibHTML/Layout/LineBoxFragment.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <LibDraw/Rect.h>
+
+class LayoutNode;
+class RenderingContext;
+
+class LineBoxFragment {
+ friend class LineBox;
+public:
+ LineBoxFragment(const LayoutNode& layout_node, int start, int length, const Rect& rect)
+ : m_layout_node(layout_node)
+ , m_start(start)
+ , m_length(length)
+ , m_rect(rect)
+ {
+ }
+
+ const LayoutNode& layout_node() const { return m_layout_node; }
+ int start() const { return m_start; }
+ int length() const { return m_length; }
+ const Rect& rect() const { return m_rect; }
+ Rect& rect() { return m_rect; }
+
+ void render(RenderingContext&);
+
+private:
+ const LayoutNode& m_layout_node;
+ int m_start { 0 };
+ int m_length { 0 };
+ Rect m_rect;
+};
diff --git a/Libraries/LibHTML/Makefile.shared b/Libraries/LibHTML/Makefile.shared
index 0f90ab67d2..61081b967c 100644
--- a/Libraries/LibHTML/Makefile.shared
+++ b/Libraries/LibHTML/Makefile.shared
@@ -28,6 +28,8 @@ LIBHTML_OBJS = \
Layout/LayoutInline.o \
Layout/LayoutDocument.o \
Layout/ComputedStyle.o \
+ Layout/LineBox.o \
+ Layout/LineBoxFragment.o \
HtmlView.o \
Dump.o