srctree

Robin Linden parent 5e3b12a3 90c01611
layout: Apply text-transform transforms

inlinesplit
layout/layout.cpp added: 197, removed: 2, total 195
@@ -192,6 +192,51 @@ void collapse_whitespace(LayoutBox &box) {
}
}
 
void apply_text_transforms(LayoutBox &box) {
if (std::holds_alternative<std::string>(box.layout_text)
|| std::holds_alternative<std::string_view>(box.layout_text)) {
if (auto transform = box.get_property<css::PropertyId::TextTransform>();
transform && *transform != style::TextTransform::None) {
if (std::holds_alternative<std::string_view>(box.layout_text)) {
box.layout_text = std::string{std::get<std::string_view>(box.layout_text)};
}
 
auto &text = std::get<std::string>(box.layout_text);
 
// TODO(robinlinden): FullWidth, FullSizeKana.
// TODO(robinlinden): Handle language-specific cases.
switch (*transform) {
case style::TextTransform::FullWidth:
case style::TextTransform::FullSizeKana:
case style::TextTransform::None:
break;
case style::TextTransform::Uppercase:
std::ranges::for_each(text, [](char &c) { c = util::uppercased(c); });
break;
case style::TextTransform::Lowercase:
std::ranges::for_each(text, [](char &c) { c = util::lowercased(c); });
break;
case style::TextTransform::Capitalize:
std::ranges::for_each(text, [first = true](char &c) mutable {
if (first && util::is_alpha(c)) {
first = false;
c = util::uppercased(c);
} else if (!first && !util::is_alpha(c)) {
first = true;
} else {
c = util::lowercased(c);
}
});
break;
}
}
}
 
for (auto &child : box.children) {
apply_text_transforms(child);
}
}
 
void calculate_position(LayoutBox &box, geom::Rect const &parent) {
auto const &d = box.dimensions;
box.dimensions.content.x = parent.x + d.padding.left + d.border.left + d.margin.left;
@@ -495,7 +540,10 @@ std::optional<LayoutBox> create_layout(style::StyledNode const &node, int width,
return {};
}
 
// TODO(robinlinden): Merge the different passes. They're separate because
// it was easier, but it's definitely less efficient.
collapse_whitespace(*tree);
apply_text_transforms(*tree);
 
Layouter{node.get_property<css::PropertyId::FontSize>(), type}.layout(*tree, {0, 0, width, 0});
return tree;
 
layout/layout_test.cpp added: 197, removed: 2, total 195
@@ -333,6 +333,152 @@ void whitespace_collapsing_tests() {
});
}
 
void text_transform_tests() {
etest::test("text-transform: uppercase", [] {
constexpr auto kText = "hello goodbye"sv;
constexpr auto kExpectedText = "HELLO GOODBYE"sv;
constexpr auto kTextWidth = kExpectedText.length() * 5;
 
dom::Element p{.name{"p"}, .children{dom::Text{std::string{kText}}}};
dom::Node html = dom::Element{.name{"html"}, .children{std::move(p)}};
dom::Element const &html_element = std::get<dom::Element>(html);
 
style::StyledNode p_style{
.node{html_element.children[0]},
.properties{{css::PropertyId::Display, "inline"}, {css::PropertyId::TextTransform, "uppercase"}},
.children{style::StyledNode{std::get<dom::Element>(html_element.children.at(0)).children.at(0)}},
};
style::StyledNode style{
.node{html},
.properties{{css::PropertyId::Display, "block"}, {css::PropertyId::FontSize, "10px"}},
.children{std::move(p_style)},
};
set_up_parent_ptrs(style);
 
layout::LayoutBox p_layout{
.node = &style.children.at(0),
.type = LayoutType::Inline,
.dimensions{{0, 0, kTextWidth, 10}},
.children{layout::LayoutBox{
.node = &style.children.at(0).children.at(0),
.type = LayoutType::Inline,
.dimensions{{0, 0, kTextWidth, 10}},
.layout_text{std::string{kExpectedText}},
}},
};
layout::LayoutBox expected_layout{
.node = &style,
.type = LayoutType::Block,
.dimensions{{0, 0, 1234, 10}},
.children{layout::LayoutBox{
.node = nullptr,
.type = LayoutType::AnonymousBlock,
.dimensions{{0, 0, kTextWidth, 10}},
.children{std::move(p_layout)},
}},
};
 
auto actual = layout::create_layout(style, 1234);
expect_eq(actual, expected_layout);
});
 
etest::test("text-transform: lowercase", [] {
constexpr auto kText = "HELLO GOODBYE"sv;
constexpr auto kExpectedText = "hello goodbye"sv;
constexpr auto kTextWidth = kExpectedText.length() * 5;
 
dom::Element p{.name{"p"}, .children{dom::Text{std::string{kText}}}};
dom::Node html = dom::Element{.name{"html"}, .children{std::move(p)}};
dom::Element const &html_element = std::get<dom::Element>(html);
 
style::StyledNode p_style{
.node{html_element.children[0]},
.properties{{css::PropertyId::Display, "inline"}, {css::PropertyId::TextTransform, "lowercase"}},
.children{style::StyledNode{std::get<dom::Element>(html_element.children.at(0)).children.at(0)}},
};
style::StyledNode style{
.node{html},
.properties{{css::PropertyId::Display, "block"}, {css::PropertyId::FontSize, "10px"}},
.children{std::move(p_style)},
};
set_up_parent_ptrs(style);
 
layout::LayoutBox p_layout{
.node = &style.children.at(0),
.type = LayoutType::Inline,
.dimensions{{0, 0, kTextWidth, 10}},
.children{layout::LayoutBox{
.node = &style.children.at(0).children.at(0),
.type = LayoutType::Inline,
.dimensions{{0, 0, kTextWidth, 10}},
.layout_text{std::string{kExpectedText}},
}},
};
layout::LayoutBox expected_layout{
.node = &style,
.type = LayoutType::Block,
.dimensions{{0, 0, 1234, 10}},
.children{layout::LayoutBox{
.node = nullptr,
.type = LayoutType::AnonymousBlock,
.dimensions{{0, 0, kTextWidth, 10}},
.children{std::move(p_layout)},
}},
};
 
auto actual = layout::create_layout(style, 1234);
expect_eq(actual, expected_layout);
});
 
etest::test("text-transform: capitalize", [] {
constexpr auto kText = "HE?LO GOODBYE!"sv;
constexpr auto kExpectedText = "He?Lo Goodbye!"sv;
constexpr auto kTextWidth = kExpectedText.length() * 5;
 
dom::Element p{.name{"p"}, .children{dom::Text{std::string{kText}}}};
dom::Node html = dom::Element{.name{"html"}, .children{std::move(p)}};
dom::Element const &html_element = std::get<dom::Element>(html);
 
style::StyledNode p_style{
.node{html_element.children[0]},
.properties{{css::PropertyId::Display, "inline"}, {css::PropertyId::TextTransform, "capitalize"}},
.children{style::StyledNode{std::get<dom::Element>(html_element.children.at(0)).children.at(0)}},
};
style::StyledNode style{
.node{html},
.properties{{css::PropertyId::Display, "block"}, {css::PropertyId::FontSize, "10px"}},
.children{std::move(p_style)},
};
set_up_parent_ptrs(style);
 
layout::LayoutBox p_layout{
.node = &style.children.at(0),
.type = LayoutType::Inline,
.dimensions{{0, 0, kTextWidth, 10}},
.children{layout::LayoutBox{
.node = &style.children.at(0).children.at(0),
.type = LayoutType::Inline,
.dimensions{{0, 0, kTextWidth, 10}},
.layout_text{std::string{kExpectedText}},
}},
};
layout::LayoutBox expected_layout{
.node = &style,
.type = LayoutType::Block,
.dimensions{{0, 0, 1234, 10}},
.children{layout::LayoutBox{
.node = nullptr,
.type = LayoutType::AnonymousBlock,
.dimensions{{0, 0, kTextWidth, 10}},
.children{std::move(p_layout)},
}},
};
 
auto actual = layout::create_layout(style, 1234);
expect_eq(actual, expected_layout);
});
}
 
void img_tests() {
etest::test("img, no alt or src", [] {
dom::Node dom = dom::Element{"body", {}, {dom::Element{"img"}}};
@@ -1707,6 +1853,7 @@ int main() {
});
 
whitespace_collapsing_tests();
text_transform_tests();
img_tests();
 
return etest::run_all_tests();