srctree

Robin Linden parent 6adcd423 b22bff10
layout: Support rem units

inlinesplit
layout/layout.cpp added: 82, removed: 42, total 40
@@ -73,8 +73,9 @@ std::optional<LayoutBox> create_tree(style::StyledNode const &node) {
// TODO(robinlinden):
// * margin, border, etc.
// * Not all measurements have to be in pixels.
// * %, rem
int to_px(std::string_view property, int const font_size) {
// * %
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
int to_px(std::string_view property, int const font_size, int const root_font_size) {
// Special case for 0 since it won't ever have a unit that needs to be handled.
if (property == "0") {
return 0;
@@ -99,6 +100,11 @@ int to_px(std::string_view property, int const font_size) {
return static_cast<int>(res);
}
 
if (unit == "rem") {
res *= static_cast<float>(root_font_size);
return static_cast<int>(res);
}
 
spdlog::warn("Bad property '{}' w/ unit '{}' in to_px", property, unit);
return static_cast<int>(res);
}
@@ -107,15 +113,16 @@ 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) {
int const font_size,
int const root_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.right = to_px(margin_right, font_size, root_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.left = to_px(margin_left, font_size, root_font_size);
box.dimensions.margin.right = parent.width - box.dimensions.margin_box().width;
} else {
// TODO(mkiael): Compute margin depending on direction property
@@ -123,44 +130,45 @@ void calculate_left_and_right_margin(LayoutBox &box,
}
 
// https://www.w3.org/TR/CSS2/visudet.html#blockwidth
void calculate_width_and_margin(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, int const root_font_size) {
assert(box.node != nullptr);
 
auto margin_top = box.get_property<css::PropertyId::MarginTop>();
box.dimensions.margin.top = to_px(margin_top, font_size);
box.dimensions.margin.top = to_px(margin_top, font_size, root_font_size);
 
auto margin_bottom = box.get_property<css::PropertyId::MarginBottom>();
box.dimensions.margin.bottom = to_px(margin_bottom, font_size);
box.dimensions.margin.bottom = to_px(margin_bottom, font_size, root_font_size);
 
auto width = box.get_property<css::PropertyId::Width>();
auto margin_left = box.get_property<css::PropertyId::MarginLeft>();
auto margin_right = box.get_property<css::PropertyId::MarginRight>();
if (width == "auto") {
if (margin_left != "auto") {
box.dimensions.margin.left = to_px(margin_left, font_size);
box.dimensions.margin.left = to_px(margin_left, font_size, root_font_size);
}
if (margin_right != "auto") {
box.dimensions.margin.right = to_px(margin_right, font_size);
box.dimensions.margin.right = to_px(margin_right, font_size, root_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);
box.dimensions.content.width = to_px(width, font_size, root_font_size);
calculate_left_and_right_margin(box, parent, margin_left, margin_right, font_size, root_font_size);
}
 
if (auto min = box.get_property<css::PropertyId::MinWidth>(); min != "auto") {
int min_width_px = to_px(min, font_size);
int min_width_px = to_px(min, font_size, root_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);
calculate_left_and_right_margin(box, parent, margin_left, margin_right, font_size, root_font_size);
}
}
 
if (auto max = box.get_property<css::PropertyId::MaxWidth>(); max != "none") {
int max_width_px = to_px(max, font_size);
int max_width_px = to_px(max, font_size, root_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);
calculate_left_and_right_margin(box, parent, margin_left, margin_right, font_size, root_font_size);
}
}
}
@@ -172,7 +180,7 @@ void calculate_position(LayoutBox &box, geom::Rect const &parent) {
box.dimensions.content.y = parent.y + parent.height + d.border.top + d.padding.top + d.margin.top;
}
 
void calculate_height(LayoutBox &box, int const font_size) {
void calculate_height(LayoutBox &box, int const font_size, int const root_font_size) {
assert(box.node != nullptr);
if (auto const *text = std::get_if<dom::Text>(&box.node->node)) {
int lines = static_cast<int>(std::ranges::count(text->text, '\n')) + 1;
@@ -180,30 +188,30 @@ void calculate_height(LayoutBox &box, int const font_size) {
}
 
if (auto height = box.get_property<css::PropertyId::Height>(); height != "auto") {
box.dimensions.content.height = to_px(height, font_size);
box.dimensions.content.height = to_px(height, font_size, root_font_size);
}
 
if (auto min = box.get_property<css::PropertyId::MinHeight>(); min != "auto") {
box.dimensions.content.height = std::max(box.dimensions.content.height, to_px(min, font_size));
box.dimensions.content.height = std::max(box.dimensions.content.height, to_px(min, font_size, root_font_size));
}
 
if (auto max = box.get_property<css::PropertyId::MaxHeight>(); max != "none") {
box.dimensions.content.height = std::min(box.dimensions.content.height, to_px(max, font_size));
box.dimensions.content.height = std::min(box.dimensions.content.height, to_px(max, font_size, root_font_size));
}
}
 
void calculate_padding(LayoutBox &box, int const font_size) {
void calculate_padding(LayoutBox &box, int const font_size, int const root_font_size) {
auto padding_left = box.get_property<css::PropertyId::PaddingLeft>();
box.dimensions.padding.left = to_px(padding_left, font_size);
box.dimensions.padding.left = to_px(padding_left, font_size, root_font_size);
 
auto padding_right = box.get_property<css::PropertyId::PaddingRight>();
box.dimensions.padding.right = to_px(padding_right, font_size);
box.dimensions.padding.right = to_px(padding_right, font_size, root_font_size);
 
auto padding_top = box.get_property<css::PropertyId::PaddingTop>();
box.dimensions.padding.top = to_px(padding_top, font_size);
box.dimensions.padding.top = to_px(padding_top, font_size, root_font_size);
 
auto padding_bottom = box.get_property<css::PropertyId::PaddingBottom>();
box.dimensions.padding.bottom = to_px(padding_bottom, font_size);
box.dimensions.padding.bottom = to_px(padding_bottom, font_size, root_font_size);
}
 
// https://w3c.github.io/csswg-drafts/css-backgrounds/#the-border-width
@@ -213,13 +221,13 @@ std::map<std::string_view, int> const kBorderWidthKeywords{
{"thick", 7},
};
 
void calculate_border(LayoutBox &box, int const font_size) {
void calculate_border(LayoutBox &box, int const font_size, int const root_font_size) {
auto as_px = [&](std::string_view border_width_property) {
if (kBorderWidthKeywords.contains(border_width_property)) {
return kBorderWidthKeywords.at(border_width_property);
}
 
return to_px(border_width_property, font_size);
return to_px(border_width_property, font_size, root_font_size);
};
 
if (box.get_property<css::PropertyId::BorderLeftStyle>() != style::BorderStyle::None) {
@@ -243,13 +251,13 @@ void calculate_border(LayoutBox &box, int const font_size) {
}
}
 
void layout(LayoutBox &box, geom::Rect const &bounds) {
void layout(LayoutBox &box, geom::Rect const &bounds, int const root_font_size) {
switch (box.type) {
case LayoutType::Inline: {
assert(box.node);
auto font_size = box.get_property<css::PropertyId::FontSize>();
calculate_padding(box, font_size);
calculate_border(box, font_size);
calculate_padding(box, font_size, root_font_size);
calculate_border(box, font_size, root_font_size);
 
if (auto const *text_node = std::get_if<dom::Text>(&box.node->node)) {
// TODO(robinlinden): Measure the text for real.
@@ -264,27 +272,27 @@ void layout(LayoutBox &box, geom::Rect const &bounds) {
 
int last_child_end{};
for (auto &child : box.children) {
layout(child, box.dimensions.content.translated(last_child_end, 0));
layout(child, box.dimensions.content.translated(last_child_end, 0), root_font_size);
last_child_end += child.dimensions.margin_box().width;
box.dimensions.content.height =
std::max(box.dimensions.content.height, child.dimensions.margin_box().height);
box.dimensions.content.width += child.dimensions.margin_box().width;
}
calculate_height(box, font_size);
calculate_height(box, font_size, root_font_size);
return;
}
case LayoutType::Block: {
assert(box.node);
auto font_size = box.get_property<css::PropertyId::FontSize>();
calculate_padding(box, font_size);
calculate_border(box, font_size);
calculate_width_and_margin(box, bounds, font_size);
calculate_padding(box, font_size, root_font_size);
calculate_border(box, font_size, root_font_size);
calculate_width_and_margin(box, bounds, font_size, root_font_size);
calculate_position(box, bounds);
for (auto &child : box.children) {
layout(child, box.dimensions.content);
layout(child, box.dimensions.content, root_font_size);
box.dimensions.content.height += child.dimensions.margin_box().height;
}
calculate_height(box, font_size);
calculate_height(box, font_size, root_font_size);
return;
}
// TODO(robinlinden): Children wider than the available area need to be split across multiple lines.
@@ -292,7 +300,7 @@ void layout(LayoutBox &box, geom::Rect const &bounds) {
calculate_position(box, bounds);
int last_child_end{};
for (auto &child : box.children) {
layout(child, box.dimensions.content.translated(last_child_end, 0));
layout(child, box.dimensions.content.translated(last_child_end, 0), root_font_size);
last_child_end += child.dimensions.margin_box().width;
box.dimensions.content.height =
std::max(box.dimensions.content.height, child.dimensions.margin_box().height);
@@ -355,6 +363,14 @@ void print_box(LayoutBox const &box, std::ostream &os, uint8_t depth = 0) {
}
}
 
int get_root_font_size(style::StyledNode const &node) {
auto const *n = &node;
while (n->parent) {
n = n->parent;
}
return n->get_property<css::PropertyId::FontSize>();
}
 
} // namespace
 
std::pair<int, int> LayoutBox::get_border_radius_property(css::PropertyId id) const {
@@ -362,7 +378,8 @@ std::pair<int, int> LayoutBox::get_border_radius_property(css::PropertyId id) co
auto [horizontal, vertical] = raw.contains('/') ? util::split_once(raw, "/") : std::pair{raw, raw};
 
int font_size = node->get_property<css::PropertyId::FontSize>();
return {to_px(horizontal, font_size), to_px(vertical, font_size)};
int root_font_size = get_root_font_size(*node);
return {to_px(horizontal, font_size, root_font_size), to_px(vertical, font_size, root_font_size)};
}
 
std::optional<LayoutBox> create_layout(style::StyledNode const &node, int width) {
@@ -371,7 +388,7 @@ std::optional<LayoutBox> create_layout(style::StyledNode const &node, int width)
return {};
}
 
layout(*tree, {0, 0, width, 0});
layout(*tree, {0, 0, width, 0}, node.get_property<css::PropertyId::FontSize>());
return *tree;
}
 
 
layout/layout_test.cpp added: 82, removed: 42, total 40
@@ -1167,5 +1167,28 @@ int main() {
expect_eq(dom::nodes_by_xpath(layout, "//div"), NodeVec{&layout.children[0], &anon_block.children[1]});
});
 
etest::test("rem units", [] {
dom::Node dom = dom::Element{"html", {}, {dom::Element{"div"}}};
auto const &div = std::get<dom::Element>(dom).children[0];
style::StyledNode style{
.node{dom},
.properties{{css::PropertyId::FontSize, "10px"}, {css::PropertyId::Display, "block"}},
.children{
style::StyledNode{
.node{div},
.properties{{css::PropertyId::Width, "2rem"}, {css::PropertyId::Display, "block"}},
},
},
};
set_up_parent_ptrs(style);
 
auto layout = layout::create_layout(style, 1000).value();
expect_eq(layout.children.at(0).dimensions.border_box().width, 20);
 
style.properties.at(0).second = "16px";
layout = layout::create_layout(style, 1000).value();
expect_eq(layout.children.at(0).dimensions.border_box().width, 32);
});
 
return etest::run_all_tests();
}