diff options
author | Andreas Kling <awesomekling@gmail.com> | 2019-10-03 15:20:13 +0200 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-10-03 15:20:13 +0200 |
commit | 1d65cf367f08aaa0f6c5cbc3afc780839445864b (patch) | |
tree | 8d1f72a0e2c07260c53eac0c9774f1f4db401006 /Libraries | |
parent | 5966fcff31c62469a1a18b16aa42f9cb1af8ac96 (diff) | |
download | serenity-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.cpp | 39 | ||||
-rw-r--r-- | Libraries/LibHTML/Layout/LayoutBlock.cpp | 70 | ||||
-rw-r--r-- | Libraries/LibHTML/Layout/LayoutBlock.h | 13 | ||||
-rw-r--r-- | Libraries/LibHTML/Layout/LayoutInline.cpp | 12 | ||||
-rw-r--r-- | Libraries/LibHTML/Layout/LayoutInline.h | 3 | ||||
-rw-r--r-- | Libraries/LibHTML/Layout/LayoutText.cpp | 250 | ||||
-rw-r--r-- | Libraries/LibHTML/Layout/LayoutText.h | 21 | ||||
-rw-r--r-- | Libraries/LibHTML/Layout/LineBox.cpp | 14 | ||||
-rw-r--r-- | Libraries/LibHTML/Layout/LineBox.h | 20 | ||||
-rw-r--r-- | Libraries/LibHTML/Layout/LineBoxFragment.cpp | 12 | ||||
-rw-r--r-- | Libraries/LibHTML/Layout/LineBoxFragment.h | 32 | ||||
-rw-r--r-- | Libraries/LibHTML/Makefile.shared | 2 |
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 |