srctree

Mikael Larsson parent f32f32e4 930f036e
layout: Update calculations for width and margin to handle auto values

inlinesplit
layout/layout.cpp added: 178, removed: 50, total 128
@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2021 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2022 Mikael Larsson <c.mikael.larsson@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
 
@@ -83,8 +84,27 @@ int to_px(std::string_view property, int const font_size) {
return res;
}
 
void calculate_left_and_right_margin(LayoutBox &box,
geom::Rect const &parent,
std::string_view margin_left,
std::string_view margin_right,
int const font_size) {
if (margin_left == "auto" && margin_right == "auto") {
int margin_px = (parent.width - box.dimensions.border_box().width) / 2;
box.dimensions.margin.left = box.dimensions.margin.right = margin_px;
} else if (margin_left == "auto" && margin_right != "auto") {
box.dimensions.margin.right = to_px(margin_right, font_size);
box.dimensions.margin.left = parent.width - box.dimensions.margin_box().width;
} else if (margin_left != "auto" && margin_right == "auto") {
box.dimensions.margin.left = to_px(margin_left, font_size);
box.dimensions.margin.right = parent.width - box.dimensions.margin_box().width;
} else {
// TODO(mkiael): Compute margin depending on direction property
}
}
 
// https://www.w3.org/TR/CSS2/visudet.html#blockwidth
void calculate_width(LayoutBox &box, geom::Rect const &parent, int const font_size) {
void calculate_width_and_margin(LayoutBox &box, geom::Rect const &parent, int const font_size) {
assert(box.node != nullptr);
 
if (std::holds_alternative<dom::Text>(box.node->node)) {
@@ -94,18 +114,45 @@ void calculate_width(LayoutBox &box, geom::Rect const &parent, int const font_si
return;
}
 
if (auto margin_top = style::get_property(*box.node, "margin-top")) {
box.dimensions.margin.top = to_px(*margin_top, font_size);
}
 
if (auto margin_bottom = style::get_property(*box.node, "margin-bottom")) {
box.dimensions.margin.bottom = to_px(*margin_bottom, font_size);
}
 
auto width = style::get_property_or(*box.node, "width", "auto");
int width_px = width == "auto" ? parent.width : to_px(width, font_size);
auto margin_left = style::get_property_or(*box.node, "margin-left", "0");
auto margin_right = style::get_property_or(*box.node, "margin-right", "0");
if (width == "auto") {
if (margin_left != "auto") {
box.dimensions.margin.left = to_px(margin_left, font_size);
}
if (margin_right != "auto") {
box.dimensions.margin.right = to_px(margin_right, font_size);
}
box.dimensions.content.width = parent.width - box.dimensions.margin_box().width;
} else {
box.dimensions.content.width = to_px(width, font_size);
calculate_left_and_right_margin(box, parent, margin_left, margin_right, font_size);
}
 
if (auto min = style::get_property(*box.node, "min-width")) {
width_px = std::max(width_px, to_px(*min, font_size));
int min_width_px = to_px(*min, font_size);
if (box.dimensions.content.width < min_width_px) {
box.dimensions.content.width = min_width_px;
calculate_left_and_right_margin(box, parent, margin_left, margin_right, font_size);
}
}
 
if (auto max = style::get_property(*box.node, "max-width")) {
width_px = std::min(width_px, to_px(*max, font_size));
int max_width_px = to_px(*max, font_size);
if (box.dimensions.content.width > max_width_px) {
box.dimensions.content.width = max_width_px;
calculate_left_and_right_margin(box, parent, margin_left, margin_right, font_size);
}
}
 
box.dimensions.content.width = width_px;
}
 
void calculate_position(LayoutBox &box, geom::Rect const &parent) {
@@ -152,37 +199,6 @@ void calculate_padding(LayoutBox &box, int const font_size) {
}
}
 
void calculate_margins_and_overflow(LayoutBox &box, geom::Rect const &parent, int const font_size) {
if (auto margin_top = style::get_property(*box.node, "margin-top")) {
box.dimensions.margin.top = to_px(*margin_top, font_size);
}
 
if (auto margin_bottom = style::get_property(*box.node, "margin-bottom")) {
box.dimensions.margin.bottom = to_px(*margin_bottom, font_size);
}
 
auto margin_left = style::get_property(*box.node, "margin-left");
if (margin_left && margin_left != "auto"sv) {
box.dimensions.margin.left = to_px(*margin_left, font_size);
}
 
auto margin_right = style::get_property(*box.node, "margin-right");
if (margin_right && margin_right != "auto"sv) {
box.dimensions.margin.right = to_px(*margin_right, font_size);
}
 
int underflow = parent.width - box.dimensions.margin_box().width;
 
if (box.type == LayoutType::Block && margin_left == "auto"sv && margin_right == "auto"sv && underflow > 0) {
box.dimensions.margin.left = box.dimensions.margin.right = underflow / 2;
}
 
if (underflow < 0) {
// Overflow, adjust the right margin.
box.dimensions.margin.right += underflow;
}
}
 
void layout(LayoutBox &box, geom::Rect const &bounds) {
switch (box.type) {
case LayoutType::Inline:
@@ -190,9 +206,8 @@ void layout(LayoutBox &box, geom::Rect const &bounds) {
// TODO(robinlinden): font-size should be inherited.
auto font_size =
to_px(style::get_property_or(*box.node, "font-size", kDefaultFontSize), kDefaultFontSizePx);
calculate_width(box, bounds, font_size);
calculate_padding(box, font_size);
calculate_margins_and_overflow(box, bounds, font_size);
calculate_width_and_margin(box, bounds, font_size);
calculate_position(box, bounds);
for (auto &child : box.children) {
layout(child, box.dimensions.content);
 
layout/layout_test.cpp added: 178, removed: 50, total 128
@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2021 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2022 Mikael Larsson <c.mikael.larsson@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
 
@@ -229,9 +230,7 @@ int main() {
}
};
 
// TOOD(robinlinden): This test breaks if the width here is less than the min width due
// to the hack that reduces the width instead of the right margin.
expect(layout::create_layout(style_root, 100) == expected_layout);
expect(layout::create_layout(style_root, 20) == expected_layout);
});
 
etest::test("max-width", [] {
@@ -501,7 +500,7 @@ int main() {
.dimensions = {{0, 0, 100, 120}},
.children = {
{&style_root.children[0], LayoutType::Block, {{0, 0, 100, 120}}, {
{&style_root.children[0].children[0], LayoutType::Block, {{10, 10, 100, 100}, {10, 10, 10, 10}, {}, {0, -20, 0, 0}}, {}},
{&style_root.children[0].children[0], LayoutType::Block, {{10, 10, 80, 100}, {10, 10, 10, 10}, {}, {0, 0, 0, 0}}, {}},
{&style_root.children[0].children[1], LayoutType::Block, {{0, 120, 100, 0}}, {}},
}},
}
@@ -543,7 +542,7 @@ int main() {
.dimensions = {{0, 0, 100, 20}},
.children = {
{&style_root.children[0], LayoutType::Block, {{0, 0, 100, 20}}, {
{&style_root.children[0].children[0], LayoutType::Block, {{10, 10, 100, 0}, {}, {}, {10, -10, 10, 10}}, {}},
{&style_root.children[0].children[0], LayoutType::Block, {{10, 10, 80, 0}, {}, {}, {10, 10, 10, 10}}, {}},
{&style_root.children[0].children[1], LayoutType::Block, {{0, 20, 100, 0}}, {}},
}},
}
@@ -552,6 +551,120 @@ int main() {
expect(layout::create_layout(style_root, 100) == expected_layout);
});
 
etest::test("auto margin is handled", [] {
auto dom_root = dom::create_element_node("html", {}, {
dom::create_element_node("body", {}, {
dom::create_element_node("p", {}, {}),
}),
});
 
auto properties = std::vector{
std::pair{"width"s, "100px"s},
std::pair{"margin-left"s, "auto"s},
std::pair{"margin-right"s, "auto"s},
};
 
auto const &children = std::get<dom::Element>(dom_root).children;
auto style_root = style::StyledNode{
.node = dom_root,
.properties = {},
.children = {
{children[0], {}, {
{std::get<dom::Element>(children[0]).children[0], std::move(properties), {}},
}},
},
};
 
auto expected_layout = layout::LayoutBox{
.node = &style_root,
.type = LayoutType::Block,
.dimensions = {{0, 0, 200, 0}},
.children = {
{&style_root.children[0], LayoutType::Block, {{0, 0, 200, 0}}, {
{&style_root.children[0].children[0], LayoutType::Block, {{50, 0, 100, 0}, {}, {}, {50, 50, 0, 0}}, {}},
}},
}
};
 
expect(layout::create_layout(style_root, 200) == expected_layout);
});
 
etest::test("auto left margin and fixed right margin is handled", [] {
auto dom_root = dom::create_element_node("html", {}, {
dom::create_element_node("body", {}, {
dom::create_element_node("p", {}, {}),
}),
});
 
auto properties = std::vector{
std::pair{"width"s, "100px"s},
std::pair{"margin-left"s, "auto"s},
std::pair{"margin-right"s, "20px"s},
};
 
auto const &children = std::get<dom::Element>(dom_root).children;
auto style_root = style::StyledNode{
.node = dom_root,
.properties = {},
.children = {
{children[0], {}, {
{std::get<dom::Element>(children[0]).children[0], std::move(properties), {}},
}},
},
};
 
auto expected_layout = layout::LayoutBox{
.node = &style_root,
.type = LayoutType::Block,
.dimensions = {{0, 0, 200, 0}},
.children = {
{&style_root.children[0], LayoutType::Block, {{0, 0, 200, 0}}, {
{&style_root.children[0].children[0], LayoutType::Block, {{80, 0, 100, 0}, {}, {}, {80, 20, 0, 0}}, {}},
}},
}
};
 
expect(layout::create_layout(style_root, 200) == expected_layout);
});
 
etest::test("fixed left margin and auto right margin is handled", [] {
auto dom_root = dom::create_element_node("html", {}, {
dom::create_element_node("body", {}, {
dom::create_element_node("p", {}, {}),
}),
});
 
auto properties = std::vector{
std::pair{"width"s, "100px"s},
std::pair{"margin-left"s, "75px"s},
std::pair{"margin-right"s, "auto"s},
};
 
auto const &children = std::get<dom::Element>(dom_root).children;
auto style_root = style::StyledNode{
.node = dom_root,
.properties = {},
.children = {
{children[0], {}, {
{std::get<dom::Element>(children[0]).children[0], std::move(properties), {}},
}},
},
};
 
auto expected_layout = layout::LayoutBox{
.node = &style_root,
.type = LayoutType::Block,
.dimensions = {{0, 0, 200, 0}},
.children = {
{&style_root.children[0], LayoutType::Block, {{0, 0, 200, 0}}, {
{&style_root.children[0].children[0], LayoutType::Block, {{75, 0, 100, 0}, {}, {}, {75, 25, 0, 0}}, {}},
}},
}
};
 
expect(layout::create_layout(style_root, 200) == expected_layout);
});
 
etest::test("em sizes depend on the font-size", [] {
auto dom_root = dom::create_element_node("html", {}, {});
{
@@ -707,7 +820,7 @@ int main() {
.node = dom_root,
.properties = {},
.children = {
{children[0], {}, {
{children[0], {{"width", "50px"}}, {
{std::get<dom::Element>(children[0]).children[0], {{"height", "25px"}}, {}},
{std::get<dom::Element>(children[0]).children[1], {{"padding-top", "5px"}, {"padding-right", "15px"}}, {}},
}},
@@ -718,11 +831,11 @@ int main() {
"html\n"
"block {0,0,0,30} {0,0,0,0} {0,0,0,0}\n"
" body\n"
" block {0,0,0,30} {0,0,0,0} {0,0,0,0}\n"
" block {0,0,50,30} {0,0,0,0} {0,0,0,0}\n"
" p\n"
" block {0,0,0,25} {0,0,0,0} {0,0,0,0}\n"
" block {0,0,50,25} {0,0,0,0} {0,0,0,0}\n"
" p\n"
" block {0,30,0,0} {5,15,0,0} {0,-15,0,0}\n";
" block {0,30,35,0} {5,15,0,0} {0,0,0,0}\n";
expect_eq(to_string(layout::create_layout(style_root, 0)), expected);
});