srctree

Robin Linden parent beef3be6 646acdea
layout: Split out LayoutBox code to its own file

inlinesplit
layout/BUILD added: 515, removed: 381, total 134
@@ -3,12 +3,16 @@ load("//bzl:copts.bzl", "HASTUR_COPTS")
 
cc_library(
name = "layout",
srcs = ["layout.cpp"],
srcs = glob(
include = ["*.cpp"],
exclude = ["*_test.cpp"],
),
hdrs = glob(["*.h"]),
copts = HASTUR_COPTS,
visibility = ["//visibility:public"],
deps = [
"//css",
"//dom",
"//geom",
"//style",
"//util:from_chars",
@@ -23,6 +27,7 @@ cc_library(
srcs = [src],
deps = [
":layout",
"//dom",
"//etest",
],
) for src in glob(["*_test.cpp"])]
 
layout/layout.cpp added: 515, removed: 381, total 134
@@ -5,23 +5,15 @@
 
#include "layout/layout.h"
 
#include "util/from_chars.h"
#include "util/string.h"
 
#include <spdlog/spdlog.h>
 
#include <algorithm>
#include <cassert>
#include <cerrno>
#include <charconv>
#include <cstdint>
#include <cstdlib>
#include <list>
#include <map>
#include <optional>
#include <sstream>
#include <string_view>
#include <system_error>
#include <utility>
#include <variant>
 
@@ -110,41 +102,6 @@ void collapse_whitespace(LayoutBox &box) {
}
}
 
// 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;
}
 
float res{};
auto parse_result = util::from_chars(property.data(), property.data() + property.size(), res);
if (parse_result.ec != std::errc{}) {
spdlog::warn("Unable to parse property '{}' in to_px", property);
return 0;
}
 
auto const parsed_length = std::distance(property.data(), parse_result.ptr);
auto const unit = property.substr(parsed_length);
 
if (unit == "px") {
return static_cast<int>(res);
}
 
if (unit == "em") {
res *= static_cast<float>(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);
}
 
void calculate_left_and_right_margin(LayoutBox &box,
geom::Rect const &parent,
std::string_view margin_left,
@@ -351,119 +308,8 @@ void layout(LayoutBox &box, geom::Rect const &bounds, int const root_font_size)
}
}
 
std::string_view to_str(LayoutType type) {
switch (type) {
case LayoutType::Inline:
return "inline";
case LayoutType::Block:
return "block";
case LayoutType::AnonymousBlock:
return "ablock";
}
assert(false);
std::abort();
}
 
std::string_view to_str(dom::Node const &node) {
if (auto const *element = std::get_if<dom::Element>(&node)) {
return element->name;
}
 
return std::get<dom::Text>(node).text;
}
 
std::string to_str(geom::Rect const &rect) {
std::stringstream ss;
ss << "{" << rect.x << "," << rect.y << "," << rect.width << "," << rect.height << "}";
return std::move(ss).str();
}
 
std::string to_str(geom::EdgeSize const &edge) {
std::stringstream ss;
ss << "{" << edge.top << "," << edge.right << "," << edge.bottom << "," << edge.left << "}";
return std::move(ss).str();
}
 
void print_box(LayoutBox const &box, std::ostream &os, std::uint8_t depth = 0) {
for (std::uint8_t i = 0; i < depth; ++i) {
os << " ";
}
 
if (box.node != nullptr) {
os << to_str(box.node->node) << '\n';
for (std::uint8_t i = 0; i < depth; ++i) {
os << " ";
}
}
 
auto const &d = box.dimensions;
os << to_str(box.type) << " " << to_str(d.content) << " " << to_str(d.padding) << " " << to_str(d.margin) << '\n';
for (auto const &child : box.children) {
print_box(child, os, depth + 1);
}
}
 
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::optional<std::string_view> LayoutBox::text() const {
struct Visitor {
std::optional<std::string_view> operator()(std::monostate) { return std::nullopt; }
std::optional<std::string_view> operator()(std::string const &s) { return s; }
std::optional<std::string_view> operator()(std::string_view const &s) { return s; }
};
return std::visit(Visitor{}, layout_text);
}
 
std::pair<int, int> LayoutBox::get_border_radius_property(css::PropertyId id) const {
auto raw = node->get_raw_property(id);
auto [horizontal, vertical] = raw.contains('/') ? util::split_once(raw, "/") : std::pair{raw, raw};
 
int font_size = node->get_property<css::PropertyId::FontSize>();
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<int> LayoutBox::get_min_width_property() const {
auto raw = node->get_raw_property(css::PropertyId::MinWidth);
if (raw == "auto") {
return std::nullopt;
}
 
int font_size = node->get_property<css::PropertyId::FontSize>();
int root_font_size = get_root_font_size(*node);
return to_px(raw, font_size, root_font_size);
}
 
std::optional<int> LayoutBox::get_width_property() const {
auto raw = node->get_raw_property(css::PropertyId::Width);
if (raw == "auto") {
return std::nullopt;
}
 
int font_size = node->get_property<css::PropertyId::FontSize>();
int root_font_size = get_root_font_size(*node);
return to_px(raw, font_size, root_font_size);
}
 
std::optional<int> LayoutBox::get_max_width_property() const {
auto raw = node->get_raw_property(css::PropertyId::MaxWidth);
if (raw == "none") {
return std::nullopt;
}
 
int font_size = node->get_property<css::PropertyId::FontSize>();
int root_font_size = get_root_font_size(*node);
return to_px(raw, font_size, root_font_size);
}
 
std::optional<LayoutBox> create_layout(style::StyledNode const &node, int width) {
auto tree = create_tree(node);
if (!tree) {
@@ -475,28 +321,4 @@ std::optional<LayoutBox> create_layout(style::StyledNode const &node, int width)
return *tree;
}
 
LayoutBox const *box_at_position(LayoutBox const &box, geom::Position p) {
if (!box.dimensions.contains(p)) {
return nullptr;
}
 
for (auto const &child : box.children) {
if (auto const *maybe = box_at_position(child, p)) {
return maybe;
}
}
 
if (box.type == LayoutType::AnonymousBlock) {
return nullptr;
}
 
return &box;
}
 
std::string to_string(LayoutBox const &box) {
std::stringstream ss;
print_box(box, ss);
return std::move(ss).str();
}
 
} // namespace layout
 
layout/layout.h added: 515, removed: 381, total 134
@@ -5,102 +5,16 @@
#ifndef LAYOUT_LAYOUT_H_
#define LAYOUT_LAYOUT_H_
 
#include "layout/box_model.h"
#include "layout/layout_box.h"
 
#include "css/property_id.h"
#include "geom/geom.h"
#include "style/styled_node.h"
 
#include <cassert>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>
 
namespace layout {
 
enum class LayoutType {
Inline,
Block,
AnonymousBlock, // Holds groups of sequential inline boxes.
};
 
struct LayoutBox {
style::StyledNode const *node;
LayoutType type;
BoxModel dimensions;
std::vector<LayoutBox> children;
std::variant<std::monostate, std::string_view, std::string> layout_text;
[[nodiscard]] bool operator==(LayoutBox const &) const = default;
 
std::optional<std::string_view> text() const;
 
template<css::PropertyId T>
auto get_property() const {
// Calling get_property on an anonymous block (the only type that
// doesn't have a StyleNode) is a programming error.
assert(type != LayoutType::AnonymousBlock);
assert(node);
if constexpr (T == css::PropertyId::BorderBottomLeftRadius || T == css::PropertyId::BorderBottomRightRadius
|| T == css::PropertyId::BorderTopLeftRadius || T == css::PropertyId::BorderTopRightRadius) {
return get_border_radius_property(T);
} else if constexpr (T == css::PropertyId::MinWidth) {
return get_min_width_property();
} else if constexpr (T == css::PropertyId::Width) {
return get_width_property();
} else if constexpr (T == css::PropertyId::MaxWidth) {
return get_max_width_property();
} else {
return node->get_property<T>();
}
}
 
private:
std::pair<int, int> get_border_radius_property(css::PropertyId) const;
std::optional<int> get_min_width_property() const;
std::optional<int> get_width_property() const;
std::optional<int> get_max_width_property() const;
};
 
std::optional<LayoutBox> create_layout(style::StyledNode const &node, int width);
 
LayoutBox const *box_at_position(LayoutBox const &, geom::Position);
 
std::string to_string(LayoutBox const &box);
 
inline std::string_view dom_name(LayoutBox const &node) {
assert(node.node);
return std::get<dom::Element>(node.node->node).name;
}
 
inline std::vector<LayoutBox const *> dom_children(LayoutBox const &node) {
assert(node.node);
std::vector<LayoutBox const *> children{};
for (auto const &child : node.children) {
if (child.type == LayoutType::AnonymousBlock) {
for (auto const &inline_child : child.children) {
assert(inline_child.node);
if (!std::holds_alternative<dom::Element>(inline_child.node->node)) {
continue;
}
 
children.push_back(&inline_child);
}
continue;
}
 
assert(child.node);
if (!std::holds_alternative<dom::Element>(child.node->node)) {
continue;
}
 
children.push_back(&child);
}
return children;
}
 
} // namespace layout
 
#endif
 
filename was Deleted added: 515, removed: 381, total 134
@@ -0,0 +1,198 @@
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2022 Mikael Larsson <c.mikael.larsson@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "layout/layout_box.h"
 
#include "util/from_chars.h"
 
#include <spdlog/spdlog.h>
 
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <optional>
#include <sstream>
#include <string_view>
#include <utility>
#include <variant>
 
using namespace std::literals;
 
namespace layout {
namespace {
 
std::string_view to_str(LayoutType type) {
switch (type) {
case LayoutType::Inline:
return "inline";
case LayoutType::Block:
return "block";
case LayoutType::AnonymousBlock:
return "ablock";
}
assert(false);
std::abort();
}
 
std::string_view to_str(dom::Node const &node) {
if (auto const *element = std::get_if<dom::Element>(&node)) {
return element->name;
}
 
return std::get<dom::Text>(node).text;
}
 
std::string to_str(geom::Rect const &rect) {
std::stringstream ss;
ss << "{" << rect.x << "," << rect.y << "," << rect.width << "," << rect.height << "}";
return std::move(ss).str();
}
 
std::string to_str(geom::EdgeSize const &edge) {
std::stringstream ss;
ss << "{" << edge.top << "," << edge.right << "," << edge.bottom << "," << edge.left << "}";
return std::move(ss).str();
}
 
void print_box(LayoutBox const &box, std::ostream &os, std::uint8_t depth = 0) {
for (std::uint8_t i = 0; i < depth; ++i) {
os << " ";
}
 
if (box.node != nullptr) {
os << to_str(box.node->node) << '\n';
for (std::uint8_t i = 0; i < depth; ++i) {
os << " ";
}
}
 
auto const &d = box.dimensions;
os << to_str(box.type) << " " << to_str(d.content) << " " << to_str(d.padding) << " " << to_str(d.margin) << '\n';
for (auto const &child : box.children) {
print_box(child, os, depth + 1);
}
}
 
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::optional<std::string_view> LayoutBox::text() const {
struct Visitor {
std::optional<std::string_view> operator()(std::monostate) { return std::nullopt; }
std::optional<std::string_view> operator()(std::string const &s) { return s; }
std::optional<std::string_view> operator()(std::string_view const &s) { return s; }
};
return std::visit(Visitor{}, layout_text);
}
 
std::pair<int, int> LayoutBox::get_border_radius_property(css::PropertyId id) const {
auto raw = node->get_raw_property(id);
auto [horizontal, vertical] = raw.contains('/') ? util::split_once(raw, "/") : std::pair{raw, raw};
 
int font_size = node->get_property<css::PropertyId::FontSize>();
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<int> LayoutBox::get_min_width_property() const {
auto raw = node->get_raw_property(css::PropertyId::MinWidth);
if (raw == "auto") {
return std::nullopt;
}
 
int font_size = node->get_property<css::PropertyId::FontSize>();
int root_font_size = get_root_font_size(*node);
return to_px(raw, font_size, root_font_size);
}
 
std::optional<int> LayoutBox::get_width_property() const {
auto raw = node->get_raw_property(css::PropertyId::Width);
if (raw == "auto") {
return std::nullopt;
}
 
int font_size = node->get_property<css::PropertyId::FontSize>();
int root_font_size = get_root_font_size(*node);
return to_px(raw, font_size, root_font_size);
}
 
std::optional<int> LayoutBox::get_max_width_property() const {
auto raw = node->get_raw_property(css::PropertyId::MaxWidth);
if (raw == "none") {
return std::nullopt;
}
 
int font_size = node->get_property<css::PropertyId::FontSize>();
int root_font_size = get_root_font_size(*node);
return to_px(raw, font_size, root_font_size);
}
 
LayoutBox const *box_at_position(LayoutBox const &box, geom::Position p) {
if (!box.dimensions.contains(p)) {
return nullptr;
}
 
for (auto const &child : box.children) {
if (auto const *maybe = box_at_position(child, p)) {
return maybe;
}
}
 
if (box.type == LayoutType::AnonymousBlock) {
return nullptr;
}
 
return &box;
}
 
std::string to_string(LayoutBox const &box) {
std::stringstream ss;
print_box(box, ss);
return std::move(ss).str();
}
 
// 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;
}
 
float res{};
auto parse_result = util::from_chars(property.data(), property.data() + property.size(), res);
if (parse_result.ec != std::errc{}) {
spdlog::warn("Unable to parse property '{}' in to_px", property);
return 0;
}
 
auto const parsed_length = std::distance(property.data(), parse_result.ptr);
auto const unit = property.substr(parsed_length);
 
if (unit == "px") {
return static_cast<int>(res);
}
 
if (unit == "em") {
res *= static_cast<float>(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);
}
 
} // namespace layout
 
filename was Deleted added: 515, removed: 381, total 134
@@ -0,0 +1,109 @@
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#ifndef LAYOUT_LAYOUT_BOX_H_
#define LAYOUT_LAYOUT_BOX_H_
 
#include "layout/box_model.h"
 
#include "css/property_id.h"
#include "dom/dom.h"
#include "geom/geom.h"
#include "style/styled_node.h"
 
#include <cassert>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>
 
namespace layout {
 
enum class LayoutType {
Inline,
Block,
AnonymousBlock, // Holds groups of sequential inline boxes.
};
 
struct LayoutBox {
style::StyledNode const *node;
LayoutType type;
BoxModel dimensions;
std::vector<LayoutBox> children;
std::variant<std::monostate, std::string_view, std::string> layout_text;
[[nodiscard]] bool operator==(LayoutBox const &) const = default;
 
std::optional<std::string_view> text() const;
 
template<css::PropertyId T>
auto get_property() const {
// Calling get_property on an anonymous block (the only type that
// doesn't have a StyleNode) is a programming error.
assert(type != LayoutType::AnonymousBlock);
assert(node);
if constexpr (T == css::PropertyId::BorderBottomLeftRadius || T == css::PropertyId::BorderBottomRightRadius
|| T == css::PropertyId::BorderTopLeftRadius || T == css::PropertyId::BorderTopRightRadius) {
return get_border_radius_property(T);
} else if constexpr (T == css::PropertyId::MinWidth) {
return get_min_width_property();
} else if constexpr (T == css::PropertyId::Width) {
return get_width_property();
} else if constexpr (T == css::PropertyId::MaxWidth) {
return get_max_width_property();
} else {
return node->get_property<T>();
}
}
 
private:
std::pair<int, int> get_border_radius_property(css::PropertyId) const;
std::optional<int> get_min_width_property() const;
std::optional<int> get_width_property() const;
std::optional<int> get_max_width_property() const;
};
 
LayoutBox const *box_at_position(LayoutBox const &, geom::Position);
 
std::string to_string(LayoutBox const &box);
 
inline std::string_view dom_name(LayoutBox const &node) {
assert(node.node);
return std::get<dom::Element>(node.node->node).name;
}
 
inline std::vector<LayoutBox const *> dom_children(LayoutBox const &node) {
assert(node.node);
std::vector<LayoutBox const *> children{};
for (auto const &child : node.children) {
if (child.type == LayoutType::AnonymousBlock) {
for (auto const &inline_child : child.children) {
assert(inline_child.node);
if (!std::holds_alternative<dom::Element>(inline_child.node->node)) {
continue;
}
 
children.push_back(&inline_child);
}
continue;
}
 
assert(child.node);
if (!std::holds_alternative<dom::Element>(child.node->node)) {
continue;
}
 
children.push_back(&child);
}
return children;
}
 
// TODO(robinlinden): This should be internal.
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
int to_px(std::string_view property, int const font_size, int const root_font_size);
 
} // namespace layout
 
#endif // LAYOUT_LAYOUT_BOX_H_
 
filename was Deleted added: 515, removed: 381, total 134
@@ -0,0 +1,194 @@
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2022 Mikael Larsson <c.mikael.larsson@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "layout/layout_box.h"
 
#include "layout/layout.h"
 
#include "dom/dom.h"
#include "etest/etest.h"
 
#include <string_view>
#include <vector>
 
using etest::expect;
using etest::expect_eq;
using layout::LayoutType;
using namespace std::literals;
 
namespace {
 
// Until we have a nicer tree-creation abstraction for the tests, this needs to
// be called if a test relies on property inheritance.
void set_up_parent_ptrs(style::StyledNode &parent) {
for (auto &child : parent.children) {
child.parent = &parent;
set_up_parent_ptrs(child);
}
}
 
// TODO(robinlinden): Remove.
dom::Node create_element_node(std::string_view name, dom::AttrMap attrs, std::vector<dom::Node> children) {
return dom::Element{std::string{name}, std::move(attrs), std::move(children)};
}
 
} // namespace
 
int main() {
// clang-format off
etest::test("text", [] {
auto dom_root = create_element_node("html", {}, {
create_element_node("body", {}, {
dom::Text{"hello"},
dom::Text{"goodbye"},
}),
});
 
auto const &children = std::get<dom::Element>(dom_root).children;
auto style_root = style::StyledNode{
.node = dom_root,
.properties = {{css::PropertyId::Display, "block"}, {css::PropertyId::FontSize, "10px"}},
.children = {
{children[0], {{css::PropertyId::Display, "block"}}, {
{std::get<dom::Element>(children[0]).children[0], {}, {}},
{std::get<dom::Element>(children[0]).children[1], {}, {}},
}},
},
};
set_up_parent_ptrs(style_root);
 
auto expected_layout = layout::LayoutBox{
.node = &style_root,
.type = LayoutType::Block,
.dimensions = {{0, 0, 0, 10}},
.children = {
{&style_root.children[0], LayoutType::Block, {{0, 0, 0, 10}}, {
{nullptr, LayoutType::AnonymousBlock, {{0, 0, 60, 10}}, {
{&style_root.children[0].children[0], LayoutType::Inline, {{0, 0, 25, 10}}, {}, "hello"sv},
{&style_root.children[0].children[1], LayoutType::Inline, {{25, 0, 35, 10}}, {}, "goodbye"sv},
}},
}},
}
};
 
auto layout_root = layout::create_layout(style_root, 0);
expect(expected_layout == layout_root);
 
expect_eq(expected_layout.children.at(0).children.at(0).children.at(0).text(), "hello");
expect_eq(expected_layout.children.at(0).children.at(0).children.at(1).text(), "goodbye");
});
 
etest::test("box_at_position", [] {
auto layout = layout::LayoutBox{
.node = nullptr,
.type = LayoutType::Block,
.dimensions = {{0, 0, 100, 100}},
.children = {
{nullptr, LayoutType::Block, {{25, 25, 50, 50}}, {
{nullptr, LayoutType::AnonymousBlock, {{30, 30, 5, 5}}, {}},
{nullptr, LayoutType::Block, {{45, 45, 5, 5}}, {}},
}},
}
};
 
expect(box_at_position(layout, {-1, -1}) == nullptr);
expect(box_at_position(layout, {101, 101}) == nullptr);
 
expect(box_at_position(layout, {100, 100}) == &layout);
expect(box_at_position(layout, {0, 0}) == &layout);
 
// We don't want to end up in anonymous blocks, so this should return its parent.
expect(box_at_position(layout, {31, 31}) == &layout.children[0]);
 
expect(box_at_position(layout, {75, 75}) == &layout.children[0]);
expect(box_at_position(layout, {47, 47}) == &layout.children[0].children[1]);
});
 
// clang-format on
 
etest::test("xpath", [] {
dom::Node html_node = dom::Element{"html"s};
dom::Node div_node = dom::Element{"div"s};
dom::Node p_node = dom::Element{"p"s};
dom::Node text_node = dom::Text{"hello!"s};
style::StyledNode styled_node{
.node = html_node,
.properties{{css::PropertyId::Display, "block"}},
.children{
style::StyledNode{.node = div_node, .properties{{css::PropertyId::Display, "block"}}},
style::StyledNode{.node = text_node},
style::StyledNode{.node = div_node,
.children{
style::StyledNode{.node = p_node},
style::StyledNode{.node = text_node},
}},
},
};
auto layout = layout::create_layout(styled_node, 123).value();
 
// Verify that we have a shady anon-box to deal with in here.
expect_eq(layout.children.size(), std::size_t{2});
expect_eq(layout.children.at(1).type, LayoutType::AnonymousBlock);
 
auto const &anon_block = layout.children.at(1);
 
using NodeVec = std::vector<layout::LayoutBox const *>;
expect_eq(dom::nodes_by_xpath(layout, "/html"), NodeVec{&layout});
expect_eq(dom::nodes_by_xpath(layout, "/html/div"), NodeVec{&layout.children[0], &anon_block.children[1]});
expect_eq(dom::nodes_by_xpath(layout, "/html/div/"), NodeVec{});
expect_eq(dom::nodes_by_xpath(layout, "/html/div/p"), NodeVec{&anon_block.children[1].children[0]});
expect_eq(dom::nodes_by_xpath(layout, "/htm/div"), NodeVec{});
expect_eq(dom::nodes_by_xpath(layout, "//div"), NodeVec{&layout.children[0], &anon_block.children[1]});
});
 
etest::test("to_string", [] {
dom::Node body = dom::Element{"body", {}, {dom::Element{"p", {}, {dom::Text{"!!!"}}}, dom::Element{"p"}}};
dom::Node dom_root = dom::Element{.name{"html"}, .children{std::move(body)}};
 
auto const &html_children = std::get<dom::Element>(dom_root).children;
auto const &body_children = std::get<dom::Element>(html_children[0]).children;
 
auto text_child = style::StyledNode{std::get<dom::Element>(body_children[0]).children[0]};
auto body_style_children = std::vector<style::StyledNode>{
{
body_children[0],
{{css::PropertyId::Height, "25px"}, {css::PropertyId::Display, "block"}},
{std::move(text_child)},
},
{
body_children[1],
{{css::PropertyId::PaddingTop, "5px"},
{css::PropertyId::PaddingRight, "15px"},
{css::PropertyId::Display, "block"}},
},
};
auto body_style = style::StyledNode{
html_children[0],
{{css::PropertyId::Width, "50px"}, {css::PropertyId::Display, "block"}},
std::move(body_style_children),
};
auto style_root = style::StyledNode{
.node = dom_root,
.properties = {{css::PropertyId::Display, "block"}, {css::PropertyId::FontSize, "10px"}},
.children{{std::move(body_style)}},
};
 
auto const *expected =
"html\n"
"block {0,0,0,30} {0,0,0,0} {0,0,0,0}\n"
" body\n"
" block {0,0,50,30} {0,0,0,0} {0,0,0,0}\n"
" p\n"
" block {0,0,50,25} {0,0,0,0} {0,0,0,0}\n"
" ablock {0,0,15,10} {0,0,0,0} {0,0,0,0}\n"
" !!!\n"
" inline {0,0,15,10} {0,0,0,0} {0,0,0,0}\n"
" p\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).value()), expected);
});
 
return etest::run_all_tests();
}
 
layout/layout_test.cpp added: 515, removed: 381, total 134
@@ -898,80 +898,7 @@ int main() {
expect(layout::create_layout(style_root, 1000) == expected_layout);
});
 
etest::test("box_at_position", [] {
auto layout = layout::LayoutBox{
.node = nullptr,
.type = LayoutType::Block,
.dimensions = {{0, 0, 100, 100}},
.children = {
{nullptr, LayoutType::Block, {{25, 25, 50, 50}}, {
{nullptr, LayoutType::AnonymousBlock, {{30, 30, 5, 5}}, {}},
{nullptr, LayoutType::Block, {{45, 45, 5, 5}}, {}},
}},
}
};
 
expect(box_at_position(layout, {-1, -1}) == nullptr);
expect(box_at_position(layout, {101, 101}) == nullptr);
 
expect(box_at_position(layout, {100, 100}) == &layout);
expect(box_at_position(layout, {0, 0}) == &layout);
 
// We don't want to end up in anonymous blocks, so this should return its parent.
expect(box_at_position(layout, {31, 31}) == &layout.children[0]);
 
expect(box_at_position(layout, {75, 75}) == &layout.children[0]);
expect(box_at_position(layout, {47, 47}) == &layout.children[0].children[1]);
});
 
// clang-format on
etest::test("to_string", [] {
dom::Node body = dom::Element{"body", {}, {dom::Element{"p", {}, {dom::Text{"!!!"}}}, dom::Element{"p"}}};
dom::Node dom_root = dom::Element{.name{"html"}, .children{std::move(body)}};
 
auto const &html_children = std::get<dom::Element>(dom_root).children;
auto const &body_children = std::get<dom::Element>(html_children[0]).children;
 
auto text_child = style::StyledNode{std::get<dom::Element>(body_children[0]).children[0]};
auto body_style_children = std::vector<style::StyledNode>{
{
body_children[0],
{{css::PropertyId::Height, "25px"}, {css::PropertyId::Display, "block"}},
{std::move(text_child)},
},
{
body_children[1],
{{css::PropertyId::PaddingTop, "5px"},
{css::PropertyId::PaddingRight, "15px"},
{css::PropertyId::Display, "block"}},
},
};
auto body_style = style::StyledNode{
html_children[0],
{{css::PropertyId::Width, "50px"}, {css::PropertyId::Display, "block"}},
std::move(body_style_children),
};
auto style_root = style::StyledNode{
.node = dom_root,
.properties = {{css::PropertyId::Display, "block"}, {css::PropertyId::FontSize, "10px"}},
.children{{std::move(body_style)}},
};
 
auto const *expected =
"html\n"
"block {0,0,0,30} {0,0,0,0} {0,0,0,0}\n"
" body\n"
" block {0,0,50,30} {0,0,0,0} {0,0,0,0}\n"
" p\n"
" block {0,0,50,25} {0,0,0,0} {0,0,0,0}\n"
" ablock {0,0,15,10} {0,0,0,0} {0,0,0,0}\n"
" !!!\n"
" inline {0,0,15,10} {0,0,0,0} {0,0,0,0}\n"
" p\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).value()), expected);
});
 
etest::test("max-width: none", [] {
dom::Node dom = dom::Element{.name{"html"}};
style::StyledNode style{
@@ -1152,41 +1079,6 @@ int main() {
expect_eq(layout::create_layout(style, 0), std::nullopt);
});
 
etest::test("xpath", [] {
dom::Node html_node = dom::Element{"html"s};
dom::Node div_node = dom::Element{"div"s};
dom::Node p_node = dom::Element{"p"s};
dom::Node text_node = dom::Text{"hello!"s};
style::StyledNode styled_node{
.node = html_node,
.properties{{css::PropertyId::Display, "block"}},
.children{
style::StyledNode{.node = div_node, .properties{{css::PropertyId::Display, "block"}}},
style::StyledNode{.node = text_node},
style::StyledNode{.node = div_node,
.children{
style::StyledNode{.node = p_node},
style::StyledNode{.node = text_node},
}},
},
};
auto layout = layout::create_layout(styled_node, 123).value();
 
// Verify that we have a shady anon-box to deal with in here.
expect_eq(layout.children.size(), std::size_t{2});
expect_eq(layout.children.at(1).type, LayoutType::AnonymousBlock);
 
auto const &anon_block = layout.children.at(1);
 
using NodeVec = std::vector<layout::LayoutBox const *>;
expect_eq(dom::nodes_by_xpath(layout, "/html"), NodeVec{&layout});
expect_eq(dom::nodes_by_xpath(layout, "/html/div"), NodeVec{&layout.children[0], &anon_block.children[1]});
expect_eq(dom::nodes_by_xpath(layout, "/html/div/"), NodeVec{});
expect_eq(dom::nodes_by_xpath(layout, "/html/div/p"), NodeVec{&anon_block.children[1].children[0]});
expect_eq(dom::nodes_by_xpath(layout, "/htm/div"), NodeVec{});
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];