mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 17:07:34 +00:00
LibWeb: Support inline-level padding and border properly
Here's roughly how this works: - InlineLevelIterator keeps a nesting stack of inline-level nodes with box model metrics. - When entering a node with box model metrics, we add them to the current "leading metrics". - When exiting a node with box model metrics, we add them to the current "trailing metrics". - Pending leading metrics are consumed by the first fragment added to the line. - Pending trailing metrics are consumed by the last fragment added to the line. Like before, the position of a line box fragment is the top left of its content box. However, fragments are placed horizontally along the line with space inserted for padding and border. InlineNode::paint() now expands the content rect as appropriate when painting background and borders. Note that margins and margin collapsing is not yet implemented. This makes the eyes on ACID2 horizontally centered. :^)
This commit is contained in:
parent
7d2a49eeb8
commit
f2a917229a
8 changed files with 191 additions and 36 deletions
|
@ -166,7 +166,7 @@ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode)
|
|||
{
|
||||
containing_block().line_boxes().clear();
|
||||
|
||||
InlineLevelIterator iterator(containing_block(), layout_mode);
|
||||
InlineLevelIterator iterator(*this, containing_block(), layout_mode);
|
||||
LineBuilder line_builder(*this);
|
||||
|
||||
for (;;) {
|
||||
|
@ -185,18 +185,19 @@ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode)
|
|||
break;
|
||||
case InlineLevelIterator::Item::Type::Element: {
|
||||
auto& box = verify_cast<Layout::Box>(*item.node);
|
||||
dimension_box_on_line(box, layout_mode);
|
||||
line_builder.break_if_needed(layout_mode, box.content_width(), item.should_force_break);
|
||||
line_builder.append_box(box);
|
||||
line_builder.break_if_needed(layout_mode, item.border_box_width(), item.should_force_break);
|
||||
line_builder.append_box(box, item.border_start + item.padding_start, item.padding_end + item.border_end);
|
||||
break;
|
||||
}
|
||||
case InlineLevelIterator::Item::Type::Text: {
|
||||
auto& text_node = verify_cast<Layout::TextNode>(*item.node);
|
||||
line_builder.break_if_needed(layout_mode, item.width, item.should_force_break);
|
||||
line_builder.break_if_needed(layout_mode, item.border_box_width(), item.should_force_break);
|
||||
line_builder.append_text_chunk(
|
||||
text_node,
|
||||
item.offset_in_node,
|
||||
item.length_in_node,
|
||||
item.border_start + item.padding_start,
|
||||
item.padding_end + item.border_end,
|
||||
item.width,
|
||||
text_node.font().glyph_height());
|
||||
break;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
#include <LibWeb/Layout/BreakNode.h>
|
||||
#include <LibWeb/Layout/InlineFormattingContext.h>
|
||||
#include <LibWeb/Layout/InlineLevelIterator.h>
|
||||
#include <LibWeb/Layout/InlineNode.h>
|
||||
#include <LibWeb/Layout/ListItemMarkerBox.h>
|
||||
|
@ -12,8 +13,51 @@
|
|||
|
||||
namespace Web::Layout {
|
||||
|
||||
InlineLevelIterator::InlineLevelIterator(Layout::InlineFormattingContext& inline_formatting_context, Layout::BlockContainer& container, LayoutMode layout_mode)
|
||||
: m_inline_formatting_context(inline_formatting_context)
|
||||
, m_container(container)
|
||||
, m_next_node(container.first_child())
|
||||
, m_layout_mode(layout_mode)
|
||||
{
|
||||
skip_to_next();
|
||||
}
|
||||
|
||||
void InlineLevelIterator::enter_node_with_box_model_metrics(Layout::NodeWithStyleAndBoxModelMetrics& node)
|
||||
{
|
||||
if (!m_extra_leading_metrics.has_value())
|
||||
m_extra_leading_metrics = ExtraBoxMetrics {};
|
||||
|
||||
node.box_model().margin.left = node.computed_values().margin().left.resolved(node, CSS::Length::make_px(m_container.content_width())).to_px(node);
|
||||
node.box_model().border.left = node.computed_values().border_left().width;
|
||||
node.box_model().padding.left = node.computed_values().padding().left.resolved(node, CSS::Length::make_px(m_container.content_width())).to_px(node);
|
||||
|
||||
m_extra_leading_metrics->margin += node.box_model().margin.left;
|
||||
m_extra_leading_metrics->border += node.box_model().border.left;
|
||||
m_extra_leading_metrics->padding += node.box_model().padding.left;
|
||||
|
||||
m_box_model_node_stack.append(node);
|
||||
}
|
||||
|
||||
void InlineLevelIterator::exit_node_with_box_model_metrics()
|
||||
{
|
||||
if (!m_extra_trailing_metrics.has_value())
|
||||
m_extra_trailing_metrics = ExtraBoxMetrics {};
|
||||
|
||||
auto& node = m_box_model_node_stack.last();
|
||||
|
||||
node.box_model().margin.right = node.computed_values().margin().right.resolved(node, CSS::Length::make_px(m_container.content_width())).to_px(node);
|
||||
node.box_model().border.right = node.computed_values().border_right().width;
|
||||
node.box_model().padding.right = node.computed_values().padding().right.resolved(node, CSS::Length::make_px(m_container.content_width())).to_px(node);
|
||||
|
||||
m_extra_trailing_metrics->margin += node.box_model().margin.right;
|
||||
m_extra_trailing_metrics->border += node.box_model().border.right;
|
||||
m_extra_trailing_metrics->padding += node.box_model().padding.right;
|
||||
|
||||
m_box_model_node_stack.take_last();
|
||||
}
|
||||
|
||||
// This is similar to Layout::Node::next_in_pre_order() but will not descend into inline-block nodes.
|
||||
static Layout::Node* next_inline_node_in_pre_order(Layout::Node& current, Layout::Node const* stay_within)
|
||||
Layout::Node* InlineLevelIterator::next_inline_node_in_pre_order(Layout::Node& current, Layout::Node const* stay_within)
|
||||
{
|
||||
if (current.first_child() && current.first_child()->is_inline() && !current.is_inline_block())
|
||||
return current.first_child();
|
||||
|
@ -22,6 +66,12 @@ static Layout::Node* next_inline_node_in_pre_order(Layout::Node& current, Layout
|
|||
Layout::Node* next = nullptr;
|
||||
while (!(next = node->next_sibling())) {
|
||||
node = node->parent();
|
||||
|
||||
// If node is the last node on the "box model node stack", pop it off.
|
||||
if (!m_box_model_node_stack.is_empty()
|
||||
&& &m_box_model_node_stack.last() == node) {
|
||||
exit_node_with_box_model_metrics();
|
||||
}
|
||||
if (!node || node == stay_within)
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -29,12 +79,22 @@ static Layout::Node* next_inline_node_in_pre_order(Layout::Node& current, Layout
|
|||
return next;
|
||||
}
|
||||
|
||||
void InlineLevelIterator::compute_next()
|
||||
{
|
||||
if (m_next_node == nullptr)
|
||||
return;
|
||||
do {
|
||||
m_next_node = next_inline_node_in_pre_order(*m_next_node, &m_container);
|
||||
} while (m_next_node && !m_next_node->is_inline());
|
||||
}
|
||||
|
||||
void InlineLevelIterator::skip_to_next()
|
||||
{
|
||||
VERIFY(m_current_node);
|
||||
do {
|
||||
m_current_node = next_inline_node_in_pre_order(*m_current_node, &m_container);
|
||||
} while (m_current_node && !m_current_node->is_inline());
|
||||
if (m_next_node && is<Layout::NodeWithStyleAndBoxModelMetrics>(*m_next_node))
|
||||
enter_node_with_box_model_metrics(static_cast<Layout::NodeWithStyleAndBoxModelMetrics&>(*m_next_node));
|
||||
|
||||
m_current_node = m_next_node;
|
||||
compute_next();
|
||||
}
|
||||
|
||||
Optional<InlineLevelIterator::Item> InlineLevelIterator::next(float available_width)
|
||||
|
@ -50,13 +110,17 @@ Optional<InlineLevelIterator::Item> InlineLevelIterator::next(float available_wi
|
|||
enter_text_node(text_node, previous_is_empty_or_ends_in_whitespace);
|
||||
}
|
||||
|
||||
auto chunk_opt = m_text_node_context->chunk_iterator.next();
|
||||
auto chunk_opt = m_text_node_context->next_chunk;
|
||||
if (!chunk_opt.has_value()) {
|
||||
m_text_node_context = {};
|
||||
skip_to_next();
|
||||
return next(available_width);
|
||||
}
|
||||
|
||||
m_text_node_context->next_chunk = m_text_node_context->chunk_iterator.next();
|
||||
if (!m_text_node_context->next_chunk.has_value())
|
||||
m_text_node_context->is_last_chunk = true;
|
||||
|
||||
auto& chunk = chunk_opt.value();
|
||||
float chunk_width = text_node.font().width(chunk.view) + text_node.font().glyph_spacing();
|
||||
Item item {
|
||||
|
@ -69,6 +133,7 @@ Optional<InlineLevelIterator::Item> InlineLevelIterator::next(float available_wi
|
|||
.is_collapsible_whitespace = m_text_node_context->do_collapse && chunk.is_all_whitespace,
|
||||
};
|
||||
|
||||
add_extra_box_model_metrics_to_item(item, m_text_node_context->is_first_chunk, m_text_node_context->is_last_chunk);
|
||||
return item;
|
||||
}
|
||||
|
||||
|
@ -95,15 +160,24 @@ Optional<InlineLevelIterator::Item> InlineLevelIterator::next(float available_wi
|
|||
}
|
||||
|
||||
auto& box = verify_cast<Layout::Box>(*m_current_node);
|
||||
m_inline_formatting_context.dimension_box_on_line(box, m_layout_mode);
|
||||
|
||||
skip_to_next();
|
||||
return Item {
|
||||
auto item = Item {
|
||||
.type = Item::Type::Element,
|
||||
.node = &box,
|
||||
.offset_in_node = 0,
|
||||
.length_in_node = 0,
|
||||
.width = box.content_width(),
|
||||
.padding_start = box.box_model().padding.left,
|
||||
.padding_end = box.box_model().padding.right,
|
||||
.border_start = box.box_model().border.left,
|
||||
.border_end = box.box_model().border.right,
|
||||
.margin_start = box.box_model().margin.left,
|
||||
.margin_end = box.box_model().margin.right,
|
||||
};
|
||||
add_extra_box_model_metrics_to_item(item, true, true);
|
||||
return item;
|
||||
}
|
||||
|
||||
void InlineLevelIterator::enter_text_node(Layout::TextNode& text_node, bool previous_is_empty_or_ends_in_whitespace)
|
||||
|
@ -136,8 +210,28 @@ void InlineLevelIterator::enter_text_node(Layout::TextNode& text_node, bool prev
|
|||
.do_collapse = do_collapse,
|
||||
.do_wrap_lines = do_wrap_lines,
|
||||
.do_respect_linebreaks = do_respect_linebreaks,
|
||||
.is_first_chunk = true,
|
||||
.is_last_chunk = false,
|
||||
.chunk_iterator = TextNode::ChunkIterator { text_node.text_for_rendering(), m_layout_mode, do_wrap_lines, do_respect_linebreaks },
|
||||
};
|
||||
m_text_node_context->next_chunk = m_text_node_context->chunk_iterator.next();
|
||||
}
|
||||
|
||||
void InlineLevelIterator::add_extra_box_model_metrics_to_item(Item& item, bool add_leading_metrics, bool add_trailing_metrics)
|
||||
{
|
||||
if (add_leading_metrics && m_extra_leading_metrics.has_value()) {
|
||||
item.margin_start += m_extra_leading_metrics->margin;
|
||||
item.border_start += m_extra_leading_metrics->border;
|
||||
item.padding_start += m_extra_leading_metrics->padding;
|
||||
m_extra_leading_metrics = {};
|
||||
}
|
||||
|
||||
if (add_trailing_metrics && m_extra_trailing_metrics.has_value()) {
|
||||
item.margin_end += m_extra_trailing_metrics->margin;
|
||||
item.border_end += m_extra_trailing_metrics->border;
|
||||
item.padding_end += m_extra_trailing_metrics->padding;
|
||||
m_extra_trailing_metrics = {};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <LibWeb/Layout/BlockContainer.h>
|
||||
#include <LibWeb/Layout/InlineNode.h>
|
||||
#include <LibWeb/Layout/TextNode.h>
|
||||
|
||||
namespace Web::Layout {
|
||||
|
@ -31,36 +32,66 @@ public:
|
|||
size_t offset_in_node { 0 };
|
||||
size_t length_in_node { 0 };
|
||||
float width { 0.0f };
|
||||
float padding_start { 0.0f };
|
||||
float padding_end { 0.0f };
|
||||
float border_start { 0.0f };
|
||||
float border_end { 0.0f };
|
||||
float margin_start { 0.0f };
|
||||
float margin_end { 0.0f };
|
||||
bool should_force_break { false };
|
||||
bool is_collapsible_whitespace { false };
|
||||
|
||||
float border_box_width() const
|
||||
{
|
||||
return border_start + padding_start + width + padding_end + border_end;
|
||||
}
|
||||
};
|
||||
|
||||
explicit InlineLevelIterator(Layout::BlockContainer& container, LayoutMode layout_mode)
|
||||
: m_container(container)
|
||||
, m_current_node(container.first_child())
|
||||
, m_layout_mode(layout_mode)
|
||||
{
|
||||
}
|
||||
InlineLevelIterator(Layout::InlineFormattingContext&, Layout::BlockContainer&, LayoutMode);
|
||||
|
||||
Optional<Item> next(float available_width);
|
||||
|
||||
private:
|
||||
void skip_to_next();
|
||||
void compute_next();
|
||||
|
||||
void enter_text_node(Layout::TextNode&, bool previous_is_empty_or_ends_in_whitespace);
|
||||
|
||||
void enter_node_with_box_model_metrics(Layout::NodeWithStyleAndBoxModelMetrics&);
|
||||
void exit_node_with_box_model_metrics();
|
||||
|
||||
void add_extra_box_model_metrics_to_item(Item&, bool add_leading_metrics, bool add_trailing_metrics);
|
||||
|
||||
Layout::Node* next_inline_node_in_pre_order(Layout::Node& current, Layout::Node const* stay_within);
|
||||
|
||||
Layout::InlineFormattingContext& m_inline_formatting_context;
|
||||
Layout::BlockContainer& m_container;
|
||||
Layout::Node* m_current_node { nullptr };
|
||||
Layout::Node* m_next_node { nullptr };
|
||||
LayoutMode const m_layout_mode;
|
||||
|
||||
struct TextNodeContext {
|
||||
bool do_collapse {};
|
||||
bool do_wrap_lines {};
|
||||
bool do_respect_linebreaks {};
|
||||
bool is_first_chunk {};
|
||||
bool is_last_chunk {};
|
||||
TextNode::ChunkIterator chunk_iterator;
|
||||
Optional<TextNode::Chunk> next_chunk {};
|
||||
};
|
||||
|
||||
Optional<TextNodeContext> m_text_node_context;
|
||||
|
||||
struct ExtraBoxMetrics {
|
||||
float margin { 0 };
|
||||
float border { 0 };
|
||||
float padding { 0 };
|
||||
};
|
||||
|
||||
Optional<ExtraBoxMetrics> m_extra_leading_metrics;
|
||||
Optional<ExtraBoxMetrics> m_extra_trailing_metrics;
|
||||
|
||||
Vector<NodeWithStyleAndBoxModelMetrics&> m_box_model_node_stack;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -38,8 +38,20 @@ void InlineNode::paint(PaintContext& context, PaintPhase phase)
|
|||
auto bottom_left_border_radius = computed_values().border_bottom_left_radius();
|
||||
auto containing_block_position_in_absolute_coordinates = containing_block()->absolute_position();
|
||||
|
||||
for_each_fragment([&](auto const& fragment) {
|
||||
for_each_fragment([&](auto const& fragment, bool is_first_fragment, bool is_last_fragment) {
|
||||
Gfx::FloatRect absolute_fragment_rect { containing_block_position_in_absolute_coordinates.translated(fragment.offset()), fragment.size() };
|
||||
|
||||
if (is_first_fragment) {
|
||||
float extra_start_width = box_model().padding.left;
|
||||
absolute_fragment_rect.translate_by(-extra_start_width, 0);
|
||||
absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_start_width);
|
||||
}
|
||||
|
||||
if (is_last_fragment) {
|
||||
float extra_end_width = box_model().padding.right;
|
||||
absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_end_width);
|
||||
}
|
||||
|
||||
auto border_radius_data = Painting::normalized_border_radius_data(*this, absolute_fragment_rect, top_left_border_radius, top_right_border_radius, bottom_right_border_radius, bottom_left_border_radius);
|
||||
Painting::paint_background(context, *this, enclosing_int_rect(absolute_fragment_rect), computed_values().background_color(), &computed_values().background_layers(), border_radius_data);
|
||||
|
||||
|
@ -77,8 +89,20 @@ void InlineNode::paint(PaintContext& context, PaintPhase phase)
|
|||
|
||||
auto containing_block_position_in_absolute_coordinates = containing_block()->absolute_position();
|
||||
|
||||
for_each_fragment([&](auto& fragment) {
|
||||
for_each_fragment([&](auto const& fragment, bool is_first_fragment, bool is_last_fragment) {
|
||||
Gfx::FloatRect absolute_fragment_rect { containing_block_position_in_absolute_coordinates.translated(fragment.offset()), fragment.size() };
|
||||
|
||||
if (is_first_fragment) {
|
||||
float extra_start_width = box_model().padding.left;
|
||||
absolute_fragment_rect.translate_by(-extra_start_width, 0);
|
||||
absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_start_width);
|
||||
}
|
||||
|
||||
if (is_last_fragment) {
|
||||
float extra_end_width = box_model().padding.right;
|
||||
absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_end_width);
|
||||
}
|
||||
|
||||
auto bordered_rect = absolute_fragment_rect.inflated(borders_data.top.width, borders_data.right.width, borders_data.bottom.width, borders_data.left.width);
|
||||
auto border_radius_data = Painting::normalized_border_radius_data(*this, bordered_rect, top_left_border_radius, top_right_border_radius, bottom_right_border_radius, bottom_left_border_radius);
|
||||
|
||||
|
@ -92,7 +116,7 @@ void InlineNode::paint(PaintContext& context, PaintPhase phase)
|
|||
// FIXME: This paints a double-thick border between adjacent fragments, where ideally there
|
||||
// would be none. Once we implement non-rectangular outlines for the `outline` CSS
|
||||
// property, we can use that here instead.
|
||||
for_each_fragment([&](auto& fragment) {
|
||||
for_each_fragment([&](auto& fragment, bool, bool) {
|
||||
painter.draw_rect(enclosing_int_rect(fragment.absolute_rect()), Color::Magenta);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
@ -103,11 +127,16 @@ template<typename Callback>
|
|||
void InlineNode::for_each_fragment(Callback callback)
|
||||
{
|
||||
// FIXME: This will be slow if the containing block has a lot of fragments!
|
||||
Vector<LineBoxFragment const&> fragments;
|
||||
containing_block()->for_each_fragment([&](auto& fragment) {
|
||||
if (!is_inclusive_ancestor_of(fragment.layout_node()))
|
||||
if (is_inclusive_ancestor_of(fragment.layout_node()))
|
||||
fragments.append(fragment);
|
||||
return IterationDecision::Continue;
|
||||
return callback(fragment);
|
||||
});
|
||||
for (size_t i = 0; i < fragments.size(); ++i) {
|
||||
auto const& fragment = fragments[i];
|
||||
callback(fragment, i == 0, i == fragments.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,18 +15,18 @@
|
|||
|
||||
namespace Web::Layout {
|
||||
|
||||
void LineBox::add_fragment(Node& layout_node, int start, int length, float width, float height, LineBoxFragment::Type fragment_type)
|
||||
void LineBox::add_fragment(Node& layout_node, int start, int length, float leading_size, float trailing_size, float content_width, float content_height, LineBoxFragment::Type fragment_type)
|
||||
{
|
||||
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) {
|
||||
// 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() + width);
|
||||
m_fragments.last().set_width(m_fragments.last().width() + content_width);
|
||||
} else {
|
||||
m_fragments.append(make<LineBoxFragment>(layout_node, start, length, Gfx::FloatPoint(m_width, 0.0f), Gfx::FloatSize(width, height), fragment_type));
|
||||
m_fragments.append(make<LineBoxFragment>(layout_node, start, length, Gfx::FloatPoint(m_width + leading_size, 0.0f), Gfx::FloatSize(content_width, content_height), fragment_type));
|
||||
}
|
||||
m_width += width;
|
||||
m_width += content_width + leading_size + trailing_size;
|
||||
|
||||
if (is<Box>(layout_node))
|
||||
verify_cast<Box>(layout_node).set_containing_line_box_fragment(m_fragments.last());
|
||||
|
|
|
@ -18,7 +18,7 @@ public:
|
|||
|
||||
float width() const { return m_width; }
|
||||
|
||||
void add_fragment(Node& layout_node, int start, int length, float width, float height, LineBoxFragment::Type = LineBoxFragment::Type::Normal);
|
||||
void add_fragment(Node& layout_node, int start, int length, float leading_size, float trailing_size, float content_width, float content_height, LineBoxFragment::Type = LineBoxFragment::Type::Normal);
|
||||
|
||||
const NonnullOwnPtrVector<LineBoxFragment>& fragments() const { return m_fragments; }
|
||||
NonnullOwnPtrVector<LineBoxFragment>& fragments() { return m_fragments; }
|
||||
|
|
|
@ -39,16 +39,16 @@ void LineBuilder::begin_new_line(bool increment_y)
|
|||
m_last_line_needs_update = true;
|
||||
}
|
||||
|
||||
void LineBuilder::append_box(Box& box)
|
||||
void LineBuilder::append_box(Box& box, float leading_size, float trailing_size)
|
||||
{
|
||||
m_context.containing_block().ensure_last_line_box().add_fragment(box, 0, 0, box.content_width(), box.content_height());
|
||||
m_context.containing_block().ensure_last_line_box().add_fragment(box, 0, 0, leading_size, trailing_size, box.content_width(), box.content_height());
|
||||
m_max_height_on_current_line = max(m_max_height_on_current_line, box.content_height());
|
||||
}
|
||||
|
||||
void LineBuilder::append_text_chunk(TextNode& text_node, size_t offset_in_node, size_t length_in_node, float width, float height)
|
||||
void LineBuilder::append_text_chunk(TextNode& text_node, size_t offset_in_node, size_t length_in_node, float leading_size, float trailing_size, float content_width, float content_height)
|
||||
{
|
||||
m_context.containing_block().ensure_last_line_box().add_fragment(text_node, offset_in_node, length_in_node, width, height);
|
||||
m_max_height_on_current_line = max(m_max_height_on_current_line, height);
|
||||
m_context.containing_block().ensure_last_line_box().add_fragment(text_node, offset_in_node, length_in_node, leading_size, trailing_size, content_width, content_height);
|
||||
m_max_height_on_current_line = max(m_max_height_on_current_line, content_height);
|
||||
}
|
||||
|
||||
bool LineBuilder::should_break(LayoutMode layout_mode, float next_item_width, bool should_force_break)
|
||||
|
|
|
@ -19,8 +19,8 @@ public:
|
|||
~LineBuilder();
|
||||
|
||||
void break_line();
|
||||
void append_box(Box&);
|
||||
void append_text_chunk(TextNode&, size_t offset_in_node, size_t length_in_node, float width, float height);
|
||||
void append_box(Box&, float leading_size, float trailing_size);
|
||||
void append_text_chunk(TextNode&, size_t offset_in_node, size_t length_in_node, float leading_size, float trailing_size, float content_width, float content_height);
|
||||
|
||||
void break_if_needed(LayoutMode layout_mode, float next_item_width, bool should_force_break)
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue