srctree

Robin Linden parent d7d08ca6 897a896e
style: Adopt UnresolvedProperty from //layout

This will allow us to keep all property-processing in one place.

inlinesplit
layout/layout.cpp added: 158, removed: 168, total 0
@@ -11,6 +11,7 @@
#include "dom/dom.h"
#include "geom/geom.h"
#include "style/styled_node.h"
#include "style/unresolved_value.h"
#include "type/naive.h"
#include "type/type.h"
#include "util/string.h"
@@ -426,10 +427,10 @@ void Layouter::calculate_left_and_right_margin(LayoutBox &box,
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, root_font_size_);
box.dimensions.margin.right = style::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, root_font_size_);
box.dimensions.margin.left = style::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
@@ -441,10 +442,10 @@ void Layouter::calculate_width_and_margin(LayoutBox &box, geom::Rect const &pare
assert(box.node != nullptr);
 
auto margin_top = box.get_property<css::PropertyId::MarginTop>();
box.dimensions.margin.top = to_px(margin_top, font_size, root_font_size_);
box.dimensions.margin.top = style::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, root_font_size_);
box.dimensions.margin.bottom = style::to_px(margin_bottom, font_size, root_font_size_);
 
auto margin_left = box.get_property<css::PropertyId::MarginLeft>();
auto margin_right = box.get_property<css::PropertyId::MarginRight>();
@@ -459,10 +460,10 @@ void Layouter::calculate_width_and_margin(LayoutBox &box, geom::Rect const &pare
calculate_left_and_right_margin(box, parent, margin_left, margin_right, font_size);
} else {
if (margin_left != "auto") {
box.dimensions.margin.left = to_px(margin_left, font_size, root_font_size_);
box.dimensions.margin.left = style::to_px(margin_left, font_size, root_font_size_);
}
if (margin_right != "auto") {
box.dimensions.margin.right = to_px(margin_right, font_size, root_font_size_);
box.dimensions.margin.right = style::to_px(margin_right, font_size, root_font_size_);
}
box.dimensions.content.width = parent.width - box.dimensions.margin_box().width;
}
@@ -497,30 +498,32 @@ void Layouter::calculate_height(LayoutBox &box, int const font_size) const {
}
 
if (auto height = box.get_property<css::PropertyId::Height>(); height != "auto") {
box.dimensions.content.height = to_px(height, font_size, root_font_size_);
box.dimensions.content.height = style::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, root_font_size_));
box.dimensions.content.height =
std::max(box.dimensions.content.height, style::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, root_font_size_));
box.dimensions.content.height =
std::min(box.dimensions.content.height, style::to_px(max, font_size, root_font_size_));
}
}
 
void Layouter::calculate_padding(LayoutBox &box, int const font_size) const {
auto padding_left = box.get_property<css::PropertyId::PaddingLeft>();
box.dimensions.padding.left = to_px(padding_left, font_size, root_font_size_);
box.dimensions.padding.left = style::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, root_font_size_);
box.dimensions.padding.right = style::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, root_font_size_);
box.dimensions.padding.top = style::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, root_font_size_);
box.dimensions.padding.bottom = style::to_px(padding_bottom, font_size, root_font_size_);
}
 
// https://drafts.csswg.org/css-backgrounds/#the-border-width
@@ -537,7 +540,7 @@ void Layouter::calculate_border(LayoutBox &box, int const font_size) const {
return it->second;
}
 
return to_px(border_width_property, font_size, root_font_size_);
return style::to_px(border_width_property, font_size, root_font_size_);
};
 
if (box.get_property<css::PropertyId::BorderLeftStyle>() != style::BorderStyle::None) {
 
layout/layout_box.cpp added: 158, removed: 168, total 0
@@ -9,20 +9,16 @@
#include "dom/dom.h"
#include "geom/geom.h"
#include "style/styled_node.h"
#include "util/from_chars.h"
#include "style/unresolved_value.h"
#include "util/string.h"
 
#include <spdlog/spdlog.h>
 
#include <cassert>
#include <cstdint>
#include <iterator>
#include <optional>
#include <ostream>
#include <sstream>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>
#include <variant>
 
@@ -108,7 +104,7 @@ std::pair<int, int> LayoutBox::get_border_radius_property(css::PropertyId id) co
 
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)};
return {style::to_px(horizontal, font_size, root_font_size), style::to_px(vertical, font_size, root_font_size)};
}
 
LayoutBox const *box_at_position(LayoutBox const &box, geom::Position p) {
@@ -135,60 +131,4 @@ std::string to_string(LayoutBox const &box) {
return std::move(ss).str();
}
 
std::optional<int> try_to_px(std::string_view property,
int const font_size,
int const root_font_size,
std::optional<int> parent_property_value) {
// 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 std::nullopt;
}
 
auto const parsed_length = std::distance(property.data(), parse_result.ptr);
auto const unit = property.substr(parsed_length);
 
if (unit == "%") {
if (!parent_property_value.has_value()) {
spdlog::warn("Missing parent-value for property w/ '%' unit");
return std::nullopt;
}
 
return static_cast<int>(res / 100.f * (*parent_property_value));
}
 
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);
}
 
// https://www.w3.org/TR/css3-values/#ex
// https://www.w3.org/TR/css3-values/#ch
if (unit == "ex" || unit == "ch") {
// Technically, these are the height of an 'x' or '0' glyph
// respectively, but we're allowed to approximate it as 50% of the em
// value.
static constexpr float kExToEmRatio = 0.5f;
return static_cast<int>(res * kExToEmRatio * font_size);
}
 
spdlog::warn("Bad property '{}' w/ unit '{}' in to_px", property, unit);
return std::nullopt;
}
 
} // namespace layout
 
layout/layout_box.h added: 158, removed: 168, total 0
@@ -6,12 +6,12 @@
#define LAYOUT_LAYOUT_BOX_H_
 
#include "layout/box_model.h"
#include "layout/unresolved_value.h"
 
#include "css/property_id.h"
#include "dom/dom.h"
#include "geom/geom.h"
#include "style/styled_node.h"
#include "style/unresolved_value.h"
 
#include <cassert>
#include <optional>
@@ -44,7 +44,7 @@ struct LayoutBox {
return get_border_radius_property(T);
} else if constexpr (T == css::PropertyId::MinWidth || T == css::PropertyId::Width
|| T == css::PropertyId::MaxWidth) {
return UnresolvedValue{node->get_raw_property(T)};
return style::UnresolvedValue{node->get_raw_property(T)};
} else {
return node->get_property<T>();
}
@@ -91,19 +91,6 @@ inline std::vector<LayoutBox const *> dom_children(LayoutBox const &node) {
return children;
}
 
// TODO(robinlinden): This should be internal.
std::optional<int> try_to_px(std::string_view property,
int font_size,
int root_font_size,
std::optional<int> parent_property_value = std::nullopt);
 
inline int to_px(std::string_view property,
int font_size,
int root_font_size,
std::optional<int> parent_property_value = std::nullopt) {
return try_to_px(property, font_size, root_font_size, parent_property_value).value_or(0);
}
 
} // namespace layout
 
#endif // LAYOUT_LAYOUT_BOX_H_
 
layout/layout_property_test.cpp added: 158, removed: 168, total 0
@@ -8,8 +8,8 @@
#include "dom/dom.h"
#include "etest/etest2.h"
#include "gfx/color.h"
#include "layout/unresolved_value.h"
#include "style/styled_node.h"
#include "style/unresolved_value.h"
 
#include <optional>
#include <source_location>
@@ -68,14 +68,14 @@ int main() {
});
 
s.add_test("width", [](etest::IActions &a) {
expect_property_eq<MinWidth>(a, "13px", layout::UnresolvedValue{"13px"});
expect_property_eq<MinWidth>(a, "auto", layout::UnresolvedValue{"auto"});
expect_property_eq<MinWidth>(a, "13px", style::UnresolvedValue{"13px"});
expect_property_eq<MinWidth>(a, "auto", style::UnresolvedValue{"auto"});
 
expect_property_eq<Width>(a, "42px", layout::UnresolvedValue{"42px"});
expect_property_eq<Width>(a, "auto", layout::UnresolvedValue{"auto"});
expect_property_eq<Width>(a, "42px", style::UnresolvedValue{"42px"});
expect_property_eq<Width>(a, "auto", style::UnresolvedValue{"auto"});
 
expect_property_eq<MaxWidth>(a, "420px", layout::UnresolvedValue{"420px"});
expect_property_eq<MaxWidth>(a, "none", layout::UnresolvedValue{"none"});
expect_property_eq<MaxWidth>(a, "420px", style::UnresolvedValue{"420px"});
expect_property_eq<MaxWidth>(a, "none", style::UnresolvedValue{"none"});
});
 
return s.run();
 
ev/null added: 158, removed: 168, total 0
@@ -1,22 +0,0 @@
// SPDX-FileCopyrightText: 2023-2024 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "layout/unresolved_value.h"
 
#include "layout/layout_box.h"
 
#include <optional>
 
namespace layout {
 
int UnresolvedValue::resolve(int font_size, int root_font_size, std::optional<int> percent_relative_to) const {
return to_px(raw, font_size, root_font_size, percent_relative_to);
}
 
std::optional<int> UnresolvedValue::try_resolve(
int font_size, int root_font_size, std::optional<int> percent_relative_to) const {
return try_to_px(raw, font_size, root_font_size, percent_relative_to);
}
 
} // namespace layout
 
style/BUILD added: 158, removed: 168, total 0
@@ -3,14 +3,11 @@ load("//bzl:copts.bzl", "HASTUR_COPTS")
 
cc_library(
name = "style",
srcs = [
"style.cpp",
"styled_node.cpp",
],
hdrs = [
"style.h",
"styled_node.h",
],
srcs = glob(
include = ["*.cpp"],
exclude = ["*_test.cpp"],
),
hdrs = glob(["*.h"]),
copts = HASTUR_COPTS,
visibility = ["//visibility:public"],
deps = [
@@ -23,24 +20,10 @@ cc_library(
],
)
 
cc_test(
name = "style_test",
[cc_test(
name = src[:-4],
size = "small",
srcs = ["style_test.cpp"],
copts = HASTUR_COPTS,
deps = [
":style",
"//css",
"//dom",
"//etest",
"@fmt",
],
)
 
cc_test(
name = "styled_node_test",
size = "small",
srcs = ["styled_node_test.cpp"],
srcs = [src],
copts = HASTUR_COPTS,
deps = [
":style",
@@ -48,5 +31,6 @@ cc_test(
"//dom",
"//etest",
"//gfx",
"@fmt",
],
)
) for src in glob(["*_test.cpp"])]
 
filename was Deleted added: 158, removed: 168, total 0
@@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: 2023-2024 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "style/unresolved_value.h"
 
#include "util/from_chars.h"
 
#include <spdlog/spdlog.h>
 
#include <iterator>
#include <optional>
#include <string_view>
#include <system_error>
 
namespace style {
 
int UnresolvedValue::resolve(int font_size, int root_font_size, std::optional<int> percent_relative_to) const {
return to_px(raw, font_size, root_font_size, percent_relative_to);
}
 
std::optional<int> UnresolvedValue::try_resolve(
int font_size, int root_font_size, std::optional<int> percent_relative_to) const {
return try_to_px(raw, font_size, root_font_size, percent_relative_to);
}
 
std::optional<int> try_to_px(std::string_view property,
int const font_size,
int const root_font_size,
std::optional<int> parent_property_value) {
// 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 std::nullopt;
}
 
auto const parsed_length = std::distance(property.data(), parse_result.ptr);
auto const unit = property.substr(parsed_length);
 
if (unit == "%") {
if (!parent_property_value.has_value()) {
spdlog::warn("Missing parent-value for property w/ '%' unit");
return std::nullopt;
}
 
return static_cast<int>(res / 100.f * (*parent_property_value));
}
 
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);
}
 
// https://www.w3.org/TR/css3-values/#ex
// https://www.w3.org/TR/css3-values/#ch
if (unit == "ex" || unit == "ch") {
// Technically, these are the height of an 'x' or '0' glyph
// respectively, but we're allowed to approximate it as 50% of the em
// value.
static constexpr float kExToEmRatio = 0.5f;
return static_cast<int>(res * kExToEmRatio * font_size);
}
 
spdlog::warn("Bad property '{}' w/ unit '{}' in to_px", property, unit);
return std::nullopt;
}
 
} // namespace style
 
layout/unresolved_value.h added: 158, removed: 168, total 0
@@ -2,13 +2,13 @@
//
// SPDX-License-Identifier: BSD-2-Clause
 
#ifndef LAYOUT_UNRESOLVED_VALUE_H_
#define LAYOUT_UNRESOLVED_VALUE_H_
#ifndef STYLE_UNRESOLVED_VALUE_H_
#define STYLE_UNRESOLVED_VALUE_H_
 
#include <optional>
#include <string_view>
 
namespace layout {
namespace style {
 
struct UnresolvedValue {
std::string_view raw{};
@@ -21,6 +21,19 @@ struct UnresolvedValue {
int font_size, int root_font_size, std::optional<int> percent_relative_to = std::nullopt) const;
};
 
} // namespace layout
// TODO(robinlinden): This should be internal.
std::optional<int> try_to_px(std::string_view property,
int font_size,
int root_font_size,
std::optional<int> parent_property_value = std::nullopt);
 
inline int to_px(std::string_view property,
int font_size,
int root_font_size,
std::optional<int> parent_property_value = std::nullopt) {
return try_to_px(property, font_size, root_font_size, parent_property_value).value_or(0);
}
 
} // namespace style
 
#endif
 
layout/unresolved_value_test.cpp added: 158, removed: 168, total 0
@@ -2,18 +2,20 @@
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "layout/unresolved_value.h"
#include "style/unresolved_value.h"
 
#include "etest/etest2.h"
 
#include <optional>
 
using style::UnresolvedValue;
 
int main() {
etest::Suite s{"UnresolvedValue"};
 
s.add_test("unit/px", [](etest::IActions &a) {
// Just a raw numeric value.
auto const uv = layout::UnresolvedValue{.raw = "37px"};
auto const uv = UnresolvedValue{.raw = "37px"};
a.expect_eq(uv.resolve(100, 100), 37);
a.expect_eq(uv.resolve(123, 456), 37);
a.expect_eq(uv.resolve(0, 0), 37);
@@ -21,7 +23,7 @@ int main() {
 
s.add_test("unit/em", [](etest::IActions &a) {
// Based on the first argument, the current element's font-size.
auto const uv = layout::UnresolvedValue{.raw = "2em"};
auto const uv = UnresolvedValue{.raw = "2em"};
a.expect_eq(uv.resolve(100, 100), 200);
a.expect_eq(uv.resolve(123, 456), 246);
a.expect_eq(uv.resolve(0, 0), 0);
@@ -29,7 +31,7 @@ int main() {
 
s.add_test("unit/ex", [](etest::IActions &a) {
// Based on the first argument, the current element's font-size.
auto const uv = layout::UnresolvedValue{.raw = "1ex"};
auto const uv = UnresolvedValue{.raw = "1ex"};
a.expect_eq(uv.resolve(100, 100), 50);
a.expect_eq(uv.resolve(123, 456), 61);
a.expect_eq(uv.resolve(0, 0), 0);
@@ -37,7 +39,7 @@ int main() {
 
s.add_test("unit/ch", [](etest::IActions &a) {
// Based on the first argument, the current element's font-size.
auto const uv = layout::UnresolvedValue{.raw = "1ch"};
auto const uv = UnresolvedValue{.raw = "1ch"};
a.expect_eq(uv.resolve(100, 100), 50);
a.expect_eq(uv.resolve(123, 456), 61);
a.expect_eq(uv.resolve(0, 0), 0);
@@ -45,7 +47,7 @@ int main() {
 
s.add_test("unit/rem", [](etest::IActions &a) {
// Based on the second argument, the root element's font-size.
auto const uv = layout::UnresolvedValue{.raw = "2rem"};
auto const uv = UnresolvedValue{.raw = "2rem"};
a.expect_eq(uv.resolve(100, 100), 200);
a.expect_eq(uv.resolve(123, 456), 912);
a.expect_eq(uv.resolve(0, 0), 0);
@@ -54,7 +56,7 @@ int main() {
s.add_test("unit/%", [](etest::IActions &a) {
// Based on the third argument, whatever the spec wants the property
// this came from to be resolved against.
auto const uv = layout::UnresolvedValue{.raw = "50%"};
auto const uv = UnresolvedValue{.raw = "50%"};
a.expect_eq(uv.resolve(100, 100, 100), 50);
a.expect_eq(uv.resolve(100, 100, 200), 100);
a.expect_eq(uv.resolve(0, 0, 1000), 500);
@@ -65,12 +67,12 @@ int main() {
 
s.add_test("try_resolve", [](etest::IActions &a) {
// %, no parent provided.
auto const percent = layout::UnresolvedValue{.raw = "50%"};
auto const percent = UnresolvedValue{.raw = "50%"};
a.expect_eq(percent.try_resolve(100, 100), std::nullopt);
a.expect_eq(percent.try_resolve(100, 100, 100), 50);
 
// Nonsense.
auto const nonsense = layout::UnresolvedValue{.raw = "foo"};
auto const nonsense = UnresolvedValue{.raw = "foo"};
a.expect_eq(nonsense.try_resolve(100, 100, 100), std::nullopt);
});