1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 13:28:11 +00:00

LibHTML: Use floating point numbers throughout the layout tree

This commit is contained in:
Andreas Kling 2019-11-18 16:25:38 +01:00
parent da23864c8d
commit c628ebda0f
15 changed files with 59 additions and 52 deletions

View file

@ -15,21 +15,26 @@ public:
, m_value(value) , m_value(value)
{ {
} }
Length(float value, Type type)
: m_type(type)
, m_value(value)
{
}
~Length() {} ~Length() {}
bool is_auto() const { return m_type == Type::Auto; } bool is_auto() const { return m_type == Type::Auto; }
bool is_absolute() const { return m_type == Type::Absolute; } bool is_absolute() const { return m_type == Type::Absolute; }
int value() const { return m_value; } float value() const { return m_value; }
String to_string() const String to_string() const
{ {
if (is_auto()) if (is_auto())
return "[Length/auto]"; return "[Length/auto]";
return String::format("%d [Length/px]", m_value); return String::format("%g [Length/px]", m_value);
} }
int to_px() const float to_px() const
{ {
if (is_auto()) if (is_auto())
return 0; return 0;
@ -38,7 +43,7 @@ public:
private: private:
Type m_type { Type::Auto }; Type m_type { Type::Auto };
int m_value { 0 }; float m_value { 0 };
}; };
inline const LogStream& operator<<(const LogStream& stream, const Length& value) inline const LogStream& operator<<(const LogStream& stream, const Length& value)

View file

@ -101,10 +101,9 @@ void StyleProperties::load_font() const
FontCache::the().set({ font_family, font_weight }, *m_font); FontCache::the().set({ font_family, font_weight }, *m_font);
} }
int StyleProperties::line_height() const float StyleProperties::line_height() const
{ {
// FIXME: Allow overriding the line-height. We currently default to 140% which seems to look nice. return (float)font().glyph_height() * 1.4f;
return (int)(font().glyph_height() * 1.4f);
} }
bool StyleProperties::operator==(const StyleProperties& other) const bool StyleProperties::operator==(const StyleProperties& other) const

View file

@ -32,7 +32,7 @@ public:
return *m_font; return *m_font;
} }
int line_height() const; float line_height() const;
bool operator==(const StyleProperties&) const; bool operator==(const StyleProperties&) const;
bool operator!=(const StyleProperties& other) const { return !(*this == other); } bool operator!=(const StyleProperties& other) const { return !(*this == other); }

View file

@ -85,14 +85,14 @@ void HtmlView::layout_and_sync_size()
main_frame().set_size(available_size()); main_frame().set_size(available_size());
document()->layout(); document()->layout();
set_content_size(layout_root()->size()); set_content_size(enclosing_int_rect(layout_root()->rect()).size());
// NOTE: If layout caused us to gain or lose scrollbars, we have to lay out again // NOTE: If layout caused us to gain or lose scrollbars, we have to lay out again
// since the scrollbars now take up some of the available space. // since the scrollbars now take up some of the available space.
if (had_vertical_scrollbar != vertical_scrollbar().is_visible() || had_horizontal_scrollbar != horizontal_scrollbar().is_visible()) { if (had_vertical_scrollbar != vertical_scrollbar().is_visible() || had_horizontal_scrollbar != horizontal_scrollbar().is_visible()) {
main_frame().set_size(available_size()); main_frame().set_size(available_size());
document()->layout(); document()->layout();
set_content_size(layout_root()->size()); set_content_size(enclosing_int_rect(layout_root()->rect()).size());
} }
#ifdef HTML_DEBUG #ifdef HTML_DEBUG
@ -360,7 +360,8 @@ void HtmlView::scroll_to_anchor(const StringView& name)
return; return;
} }
auto& layout_node = *element->layout_node(); auto& layout_node = *element->layout_node();
scroll_into_view({ layout_node.box_type_agnostic_position(), visible_content_rect().size() }, true, true); FloatRect float_rect { layout_node.box_type_agnostic_position(), { (float)visible_content_rect().width(), (float)visible_content_rect().height() } };
scroll_into_view(enclosing_int_rect(float_rect), true, true);
window()->set_override_cursor(GStandardCursor::None); window()->set_override_cursor(GStandardCursor::None);
} }

View file

@ -17,10 +17,10 @@ public:
const LengthBox& border() const { return m_border; } const LengthBox& border() const { return m_border; }
struct PixelBox { struct PixelBox {
int top; float top;
int right; float right;
int bottom; float bottom;
int left; float left;
}; };
PixelBox full_margin() const; PixelBox full_margin() const;

View file

@ -41,7 +41,7 @@ void LayoutBlock::layout()
void LayoutBlock::layout_block_children() void LayoutBlock::layout_block_children()
{ {
ASSERT(!children_are_inline()); ASSERT(!children_are_inline());
int content_height = 0; float content_height = 0;
for_each_child([&](auto& child) { for_each_child([&](auto& child) {
// FIXME: What should we do here? Something like a <table> might have a bunch of useless text children.. // FIXME: What should we do here? Something like a <table> might have a bunch of useless text children..
if (child.is_inline()) if (child.is_inline())
@ -66,8 +66,8 @@ void LayoutBlock::layout_inline_children()
line_box.trim_trailing_whitespace(); line_box.trim_trailing_whitespace();
} }
int min_line_height = style().line_height(); float min_line_height = style().line_height();
int content_height = 0; float content_height = 0;
// FIXME: This should be done by the CSS parser! // FIXME: This should be done by the CSS parser!
CSS::ValueID text_align = CSS::ValueID::Left; CSS::ValueID text_align = CSS::ValueID::Left;
@ -82,9 +82,9 @@ void LayoutBlock::layout_inline_children()
text_align = CSS::ValueID::Justify; text_align = CSS::ValueID::Justify;
for (auto& line_box : m_line_boxes) { for (auto& line_box : m_line_boxes) {
int max_height = min_line_height; float max_height = min_line_height;
for (auto& fragment : line_box.fragments()) { for (auto& fragment : line_box.fragments()) {
max_height = max(max_height, enclosing_int_rect(fragment.rect()).height()); max_height = max(max_height, fragment.rect().height());
} }
float x_offset = x(); float x_offset = x();
@ -137,7 +137,7 @@ void LayoutBlock::layout_inline_children()
} }
if (is<LayoutReplaced>(fragment.layout_node())) if (is<LayoutReplaced>(fragment.layout_node()))
const_cast<LayoutReplaced&>(to<LayoutReplaced>(fragment.layout_node())).set_rect(enclosing_int_rect(fragment.rect())); const_cast<LayoutReplaced&>(to<LayoutReplaced>(fragment.layout_node())).set_rect(fragment.rect());
float final_line_box_width = 0; float final_line_box_width = 0;
for (auto& fragment : line_box.fragments()) for (auto& fragment : line_box.fragments())
@ -178,7 +178,7 @@ void LayoutBlock::compute_width()
padding_left = style.length_or_fallback(CSS::PropertyID::PaddingLeft, zero_value); padding_left = style.length_or_fallback(CSS::PropertyID::PaddingLeft, zero_value);
padding_right = style.length_or_fallback(CSS::PropertyID::PaddingRight, zero_value); padding_right = style.length_or_fallback(CSS::PropertyID::PaddingRight, zero_value);
int total_px = 0; float total_px = 0;
for (auto& value : { margin_left, border_left, padding_left, width, padding_right, border_right, margin_right }) { for (auto& value : { margin_left, border_left, padding_left, width, padding_right, border_right, margin_right }) {
total_px += value.to_px(); total_px += value.to_px();
} }
@ -275,7 +275,7 @@ void LayoutBlock::compute_position()
box_model().padding().bottom = style.length_or_fallback(CSS::PropertyID::PaddingBottom, zero_value); box_model().padding().bottom = style.length_or_fallback(CSS::PropertyID::PaddingBottom, zero_value);
rect().set_x(containing_block()->x() + box_model().margin().left.to_px() + box_model().border().left.to_px() + box_model().padding().left.to_px()); rect().set_x(containing_block()->x() + box_model().margin().left.to_px() + box_model().border().left.to_px() + box_model().padding().left.to_px());
int top_border = -1; float top_border = -1;
if (previous_sibling() != nullptr) { if (previous_sibling() != nullptr) {
auto& previous_sibling_rect = previous_sibling()->rect(); auto& previous_sibling_rect = previous_sibling()->rect();
auto& previous_sibling_style = previous_sibling()->box_model(); auto& previous_sibling_style = previous_sibling()->box_model();

View file

@ -22,7 +22,7 @@ void LayoutBox::render(RenderingContext& context)
#endif #endif
if (node() && document().inspected_node() == node()) if (node() && document().inspected_node() == node())
context.painter().draw_rect(m_rect, Color::Magenta); context.painter().draw_rect(enclosing_int_rect(m_rect), Color::Magenta);
Rect padded_rect; Rect padded_rect;
padded_rect.set_x(x() - box_model().padding().left.to_px()); padded_rect.set_x(x() - box_model().padding().left.to_px());
@ -51,7 +51,7 @@ void LayoutBox::render(RenderingContext& context)
auto border_style_value = style().property(CSS::PropertyID::BorderTopStyle); auto border_style_value = style().property(CSS::PropertyID::BorderTopStyle);
if (border_width_value.has_value()) { if (border_width_value.has_value()) {
int border_width = border_width_value.value()->to_length().to_px(); float border_width = border_width_value.value()->to_length().to_px();
Color border_color; Color border_color;
if (border_color_value.has_value()) if (border_color_value.has_value())
@ -99,7 +99,7 @@ HitTestResult LayoutBox::hit_test(const Point& position) const
// FIXME: It would be nice if we could confidently skip over hit testing // FIXME: It would be nice if we could confidently skip over hit testing
// parts of the layout tree, but currently we can't just check // parts of the layout tree, but currently we can't just check
// m_rect.contains() since inline text rects can't be trusted.. // m_rect.contains() since inline text rects can't be trusted..
HitTestResult result { m_rect.contains(position) ? this : nullptr }; HitTestResult result { m_rect.contains(FloatPoint(position.x(), position.y())) ? this : nullptr };
for_each_child([&](auto& child) { for_each_child([&](auto& child) {
auto child_result = child.hit_test(position); auto child_result = child.hit_test(position);
if (child_result.layout_node) if (child_result.layout_node)
@ -114,7 +114,7 @@ void LayoutBox::set_needs_display()
ASSERT(frame); ASSERT(frame);
if (!is_inline()) { if (!is_inline()) {
const_cast<Frame*>(frame)->set_needs_display(rect()); const_cast<Frame*>(frame)->set_needs_display(enclosing_int_rect(rect()));
return; return;
} }

View file

@ -1,19 +1,20 @@
#pragma once #pragma once
#include <LibDraw/FloatRect.h>
#include <LibHTML/Layout/LayoutNode.h> #include <LibHTML/Layout/LayoutNode.h>
class LayoutBox : public LayoutNodeWithStyleAndBoxModelMetrics { class LayoutBox : public LayoutNodeWithStyleAndBoxModelMetrics {
public: public:
const Rect& rect() const { return m_rect; } const FloatRect& rect() const { return m_rect; }
Rect& rect() { return m_rect; } FloatRect& rect() { return m_rect; }
void set_rect(const Rect& rect) { m_rect = rect; } void set_rect(const FloatRect& rect) { m_rect = rect; }
int x() const { return rect().x(); } float x() const { return rect().x(); }
int y() const { return rect().y(); } float y() const { return rect().y(); }
int width() const { return rect().width(); } float width() const { return rect().width(); }
int height() const { return rect().height(); } float height() const { return rect().height(); }
Size size() const { return rect().size(); } FloatSize size() const { return rect().size(); }
Point position() const { return rect().location(); } FloatPoint position() const { return rect().location(); }
virtual HitTestResult hit_test(const Point& position) const override; virtual HitTestResult hit_test(const Point& position) const override;
virtual void set_needs_display() override; virtual void set_needs_display() override;
@ -31,7 +32,7 @@ protected:
private: private:
virtual bool is_box() const override { return true; } virtual bool is_box() const override { return true; }
Rect m_rect; FloatRect m_rect;
}; };
template<> template<>

View file

@ -38,18 +38,18 @@ void LayoutImage::render(RenderingContext& context)
return; return;
// FIXME: This should be done at a different level. Also rect() does not include padding etc! // FIXME: This should be done at a different level. Also rect() does not include padding etc!
if (!context.viewport_rect().intersects(rect())) if (!context.viewport_rect().intersects(enclosing_int_rect(rect())))
return; return;
if (renders_as_alt_text()) { if (renders_as_alt_text()) {
context.painter().set_font(Font::default_font()); context.painter().set_font(Font::default_font());
StylePainter::paint_frame(context.painter(), rect(), FrameShape::Container, FrameShadow::Sunken, 2); StylePainter::paint_frame(context.painter(), enclosing_int_rect(rect()), FrameShape::Container, FrameShadow::Sunken, 2);
auto alt = node().alt(); auto alt = node().alt();
if (alt.is_empty()) if (alt.is_empty())
alt = node().src(); alt = node().src();
context.painter().draw_text(rect(), alt, TextAlignment::Center, style().color_or_fallback(CSS::PropertyID::Color, document(), Color::Black), TextElision::Right); context.painter().draw_text(enclosing_int_rect(rect()), alt, TextAlignment::Center, style().color_or_fallback(CSS::PropertyID::Color, document(), Color::Black), TextElision::Right);
} else { } else {
context.painter().draw_scaled_bitmap(rect(), *node().bitmap(), node().bitmap()->rect()); context.painter().draw_scaled_bitmap(enclosing_int_rect(rect()), *node().bitmap(), node().bitmap()->rect());
} }
LayoutReplaced::render(context); LayoutReplaced::render(context);
} }

View file

@ -21,6 +21,6 @@ void LayoutListItem::layout()
append_child(*m_marker); append_child(*m_marker);
} }
Rect marker_rect { x() - 8, y(), 4, height() }; FloatRect marker_rect { x() - 8, y(), 4, height() };
m_marker->set_rect(marker_rect); m_marker->set_rect(marker_rect);
} }

View file

@ -13,7 +13,7 @@ LayoutListItemMarker::~LayoutListItemMarker()
void LayoutListItemMarker::render(RenderingContext& context) void LayoutListItemMarker::render(RenderingContext& context)
{ {
Rect bullet_rect { 0, 0, 4, 4 }; Rect bullet_rect { 0, 0, 4, 4 };
bullet_rect.center_within(rect()); bullet_rect.center_within(enclosing_int_rect(rect()));
// FIXME: It would be nicer to not have to go via the parent here to get our inherited style. // FIXME: It would be nicer to not have to go via the parent here to get our inherited style.
context.painter().fill_rect(bullet_rect, parent()->style().color_or_fallback(CSS::PropertyID::Color, document(), Color::Black)); context.painter().fill_rect(bullet_rect, parent()->style().color_or_fallback(CSS::PropertyID::Color, document(), Color::Black));
} }

View file

@ -109,16 +109,16 @@ void LayoutNode::set_needs_display()
} }
} }
Point LayoutNode::box_type_agnostic_position() const FloatPoint LayoutNode::box_type_agnostic_position() const
{ {
if (is_box()) if (is_box())
return to<LayoutBox>(*this).position(); return to<LayoutBox>(*this).position();
ASSERT(is_inline()); ASSERT(is_inline());
Point position; FloatPoint position;
if (auto* block = containing_block()) { if (auto* block = containing_block()) {
block->for_each_fragment([&](auto& fragment) { block->for_each_fragment([&](auto& fragment) {
if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) { if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) {
position = enclosing_int_rect(fragment.rect()).location(); position = fragment.rect().location();
return IterationDecision::Break; return IterationDecision::Break;
} }
return IterationDecision::Continue; return IterationDecision::Continue;

View file

@ -2,6 +2,7 @@
#include <AK/NonnullRefPtr.h> #include <AK/NonnullRefPtr.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibDraw/FloatRect.h>
#include <LibDraw/Rect.h> #include <LibDraw/Rect.h>
#include <LibHTML/CSS/StyleProperties.h> #include <LibHTML/CSS/StyleProperties.h>
#include <LibHTML/Layout/BoxModelMetrics.h> #include <LibHTML/Layout/BoxModelMetrics.h>
@ -108,7 +109,7 @@ public:
template<typename T> template<typename T>
T* first_ancestor_of_type(); T* first_ancestor_of_type();
Point box_type_agnostic_position() const; FloatPoint box_type_agnostic_position() const;
protected: protected:
explicit LayoutNode(const Node*); explicit LayoutNode(const Node*);

View file

@ -126,13 +126,13 @@ void LayoutText::for_each_source_line(Callback callback) const
void LayoutText::split_into_lines(LayoutBlock& container) void LayoutText::split_into_lines(LayoutBlock& container)
{ {
auto& font = style().font(); auto& font = style().font();
int space_width = font.glyph_width(' ') + font.glyph_spacing(); float space_width = font.glyph_width(' ') + font.glyph_spacing();
int line_height = style().line_height(); float line_height = style().line_height();
auto& line_boxes = container.line_boxes(); auto& line_boxes = container.line_boxes();
if (line_boxes.is_empty()) if (line_boxes.is_empty())
line_boxes.append(LineBox()); line_boxes.append(LineBox());
int available_width = container.width() - line_boxes.last().width(); float available_width = container.width() - line_boxes.last().width();
bool is_preformatted = style().string_or_fallback(CSS::PropertyID::WhiteSpace, "normal") == "pre"; bool is_preformatted = style().string_or_fallback(CSS::PropertyID::WhiteSpace, "normal") == "pre";
if (is_preformatted) { if (is_preformatted) {
@ -176,7 +176,7 @@ void LayoutText::split_into_lines(LayoutBlock& container)
for (int i = 0; i < words.size(); ++i) { for (int i = 0; i < words.size(); ++i) {
auto& word = words[i]; auto& word = words[i];
int word_width; float word_width;
bool is_whitespace = isspace(*word.view.begin()); bool is_whitespace = isspace(*word.view.begin());
if (is_whitespace) if (is_whitespace)

View file

@ -28,7 +28,7 @@ NonnullRefPtr<StyleValue> parse_css_value(const StringView& view)
char* endptr = nullptr; char* endptr = nullptr;
long value = strtol(String(view).characters(), &endptr, 10); long value = strtol(String(view).characters(), &endptr, 10);
if (endptr && ((!*endptr) || (endptr[0] == 'p' && endptr[1] == 'x' && endptr[2] == '\0'))) if (endptr && ((!*endptr) || (endptr[0] == 'p' && endptr[1] == 'x' && endptr[2] == '\0')))
return LengthStyleValue::create(Length(value, Length::Type::Absolute)); return LengthStyleValue::create(Length((float)value, Length::Type::Absolute));
if (string == "inherit") if (string == "inherit")
return InheritStyleValue::create(); return InheritStyleValue::create();
if (string == "initial") if (string == "initial")