diff --git a/samples/page-margins.xhtml b/samples/page-margins.xhtml
new file mode 100644
index 0000000..790fea8
--- /dev/null
+++ b/samples/page-margins.xhtml
@@ -0,0 +1,105 @@
+
+
+
+
+
+ Lorem ipsum dolor, sit amet...
+
+ ...consectetur adipisicing elit. Ab sunt aliquid voluptates fuga, autem earum minus
+ debitis temporibus ut facilis reprehenderit voluptatum maiores ipsum quis totam esse nemo sint minima.
+
+
+
+
diff --git a/src/libs/karm-io/traits.h b/src/libs/karm-io/traits.h
index 0465415..a0b0f46 100644
--- a/src/libs/karm-io/traits.h
+++ b/src/libs/karm-io/traits.h
@@ -41,7 +41,6 @@ template
concept SeekableDuplexable = Duplexable and Seekable;
struct Writer {
-
virtual ~Writer() = default;
virtual Res write(Bytes) = 0;
@@ -89,7 +88,9 @@ struct TextWriter :
virtual Res writeRune(Rune rune) = 0;
- Res flush() override { return Ok(0uz); }
+ Res flush() override {
+ return Ok(0uz);
+ }
};
template
diff --git a/src/web/vaev-base/page.h b/src/web/vaev-base/page.h
new file mode 100644
index 0000000..06b743e
--- /dev/null
+++ b/src/web/vaev-base/page.h
@@ -0,0 +1,5 @@
+#pragma once
+
+namespace Vaev {
+
+} // namespace Vaev
diff --git a/src/web/vaev-driver/render.cpp b/src/web/vaev-driver/render.cpp
index 4ea561e..4ca70db 100644
--- a/src/web/vaev-driver/render.cpp
+++ b/src/web/vaev-driver/render.cpp
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#include
#include
@@ -13,7 +14,7 @@
namespace Vaev::Driver {
-static constexpr bool DEBUG_RENDER = false;
+static constexpr bool DEBUG_RENDER = true;
static void _collectStyle(Markup::Node const &node, Style::StyleBook &sb) {
auto el = node.is();
@@ -79,7 +80,6 @@ RenderResult render(Markup::Document const &dom, Style::Media const &media, Layo
Layout::layout(
tree,
- tree.root,
{
.commit = Layout::Commit::YES,
.knownSize = {viewport.small.width, NONE},
@@ -88,8 +88,6 @@ RenderResult render(Markup::Document const &dom, Style::Media const &media, Layo
}
);
- Layout::layoutPositioned(tree, tree.root, {viewport.small.width, viewport.small.height});
-
auto sceneRoot = makeStrong();
elapsed = Sys::now() - start;
@@ -110,6 +108,215 @@ RenderResult render(Markup::Document const &dom, Style::Media const &media, Layo
};
}
+static void _paintMargins(Style::PageComputedStyle &pageStyle, RectPx pageRect, RectPx pageContent, Scene::Stack &stack) {
+ // MARK: Top Left Corner ---------------------------------------------------
+
+ auto topLeftMarginCornerRect = RectPx::fromTwoPoint(
+ pageRect.topStart(),
+ pageContent.topStart()
+ );
+ Layout::Tree topLeftMarginCornerTree{
+ .root = Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::TOP_LEFT_CORNER)),
+ .viewport = Layout::Viewport{.small = topLeftMarginCornerRect.size()}
+ };
+ Layout::layout(
+ topLeftMarginCornerTree,
+ {
+ .commit = Layout::Commit::YES,
+ .knownSize = topLeftMarginCornerRect.size().cast>(),
+ .position = topLeftMarginCornerRect.topStart(),
+ .availableSpace = topLeftMarginCornerRect.size(),
+ .containingBlock = topLeftMarginCornerRect.size(),
+ }
+ );
+ Layout::paint(topLeftMarginCornerTree.root, stack);
+
+ // MARK: Top Right Corner --------------------------------------------------
+
+ auto topRightMarginCornerRect = RectPx::fromTwoPoint(
+ pageRect.topEnd(),
+ pageContent.topEnd()
+ );
+ Layout::Tree topRightMarginCornerTree{
+ .root = Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::TOP_RIGHT_CORNER)),
+ .viewport = Layout::Viewport{.small = topRightMarginCornerRect.size()}
+ };
+ Layout::layout(
+ topRightMarginCornerTree,
+ {
+ .commit = Layout::Commit::YES,
+ .knownSize = topRightMarginCornerRect.size().cast>(),
+ .position = topRightMarginCornerRect.topStart(),
+ .availableSpace = topRightMarginCornerRect.size(),
+ .containingBlock = topRightMarginCornerRect.size(),
+ }
+ );
+ Layout::paint(topRightMarginCornerTree.root, stack);
+
+ // MARK: Bottom Left Corner ------------------------------------------------
+
+ auto bottomLeftMarginCornerRect = RectPx::fromTwoPoint(
+ pageRect.bottomStart(),
+ pageContent.bottomStart()
+ );
+ Layout::Tree bottomLeftMarginCornerTree{
+ .root = Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::BOTTOM_LEFT_CORNER)),
+ .viewport = Layout::Viewport{.small = bottomLeftMarginCornerRect.size()}
+ };
+ Layout::layout(
+ bottomLeftMarginCornerTree,
+ bottomLeftMarginCornerTree.root,
+ {
+ .commit = Layout::Commit::YES,
+ .knownSize = bottomLeftMarginCornerRect.size().cast>(),
+ .position = bottomLeftMarginCornerRect.topStart(),
+ .availableSpace = bottomLeftMarginCornerRect.size(),
+ .containingBlock = bottomLeftMarginCornerRect.size(),
+ }
+ );
+ Layout::paint(bottomLeftMarginCornerTree.root, stack);
+
+ // MARK: Bottom Right Corner -----------------------------------------------
+
+ auto bottomRightMarginCornerRect = RectPx::fromTwoPoint(
+ pageRect.bottomEnd(),
+ pageContent.bottomEnd()
+ );
+ Layout::Tree bottomRightMarginCornerTree{
+ .root = Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::BOTTOM_RIGHT_CORNER)),
+ .viewport = Layout::Viewport{.small = bottomRightMarginCornerRect.size()}
+ };
+ Layout::layout(
+ bottomRightMarginCornerTree,
+ {
+ .commit = Layout::Commit::YES,
+ .knownSize = bottomRightMarginCornerRect.size().cast>(),
+ .position = bottomRightMarginCornerRect.topStart(),
+ .availableSpace = bottomRightMarginCornerRect.size(),
+ .containingBlock = bottomRightMarginCornerRect.size(),
+ }
+ );
+ Layout::paint(bottomRightMarginCornerTree.root, stack);
+
+ // MARK: Top ---------------------------------------------------------------
+
+ auto topRect = RectPx::fromTwoPoint(
+ topLeftMarginCornerRect.topEnd(),
+ topRightMarginCornerRect.bottomStart()
+ );
+
+ auto topBox = Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::TOP));
+ topBox.add(Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::TOP_LEFT)));
+ topBox.add(Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::TOP_CENTER)));
+ topBox.add(Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::TOP_RIGHT)));
+
+ Layout::Tree topTree{
+ .root = std::move(topBox),
+ .viewport = Layout::Viewport{.small = topRect.size()}
+ };
+
+ Layout::layout(
+ topTree,
+ {
+ .commit = Layout::Commit::YES,
+ .knownSize = topRect.size().cast>(),
+ .position = topRect.topStart(),
+ .availableSpace = topRect.size(),
+ .containingBlock = topRect.size(),
+ }
+ );
+ Layout::paint(topTree.root, stack);
+
+ // MARK: Bottom ------------------------------------------------------------
+
+ auto bottomRect = RectPx::fromTwoPoint(
+ bottomLeftMarginCornerRect.topEnd(),
+ bottomRightMarginCornerRect.bottomStart()
+ );
+
+ auto bottomBox = Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::BOTTOM));
+ bottomBox.add(Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::BOTTOM_LEFT)));
+ bottomBox.add(Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::BOTTOM_CENTER)));
+ bottomBox.add(Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::BOTTOM_RIGHT)));
+
+ Layout::Tree bottomTree{
+ .root = std::move(bottomBox),
+ .viewport = Layout::Viewport{.small = bottomRect.size()}
+ };
+
+ Layout::layout(
+ bottomTree,
+ {
+ .commit = Layout::Commit::YES,
+ .knownSize = bottomRect.size().cast>(),
+ .position = bottomRect.topStart(),
+ .availableSpace = bottomRect.size(),
+ .containingBlock = bottomRect.size(),
+ }
+ );
+
+ Layout::paint(bottomTree.root, stack);
+
+ // MARK: Left --------------------------------------------------------------
+ auto leftRect = RectPx::fromTwoPoint(
+ topLeftMarginCornerRect.bottomEnd(),
+ bottomLeftMarginCornerRect.topStart()
+ );
+
+ auto leftBox = Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::LEFT));
+ leftBox.add(Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::LEFT_TOP)));
+ leftBox.add(Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::LEFT_MIDDLE)));
+ leftBox.add(Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::LEFT_BOTTOM)));
+
+ Layout::Tree leftTree{
+ .root = std::move(leftBox),
+ .viewport = Layout::Viewport{.small = leftRect.size()}
+ };
+
+ Layout::layout(
+ leftTree,
+ {
+ .commit = Layout::Commit::YES,
+ .knownSize = leftRect.size().cast>(),
+ .position = leftRect.topStart(),
+ .availableSpace = leftRect.size(),
+ .containingBlock = leftRect.size(),
+ }
+ );
+
+ Layout::paint(leftTree.root, stack);
+
+ // MARK: Right -------------------------------------------------------------
+
+ auto rightRect = RectPx::fromTwoPoint(
+ topRightMarginCornerRect.bottomEnd(),
+ bottomRightMarginCornerRect.topStart()
+ );
+
+ auto rightBox = Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::RIGHT));
+ rightBox.add(Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::RIGHT_TOP)));
+ rightBox.add(Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::RIGHT_MIDDLE)));
+ rightBox.add(Layout::buildForPseudoElement(pageStyle.area(Style::PageArea::RIGHT_BOTTOM)));
+
+ Layout::Tree rightTree{
+ .root = std::move(rightBox),
+ .viewport = Layout::Viewport{.small = rightRect.size()}
+ };
+
+ Layout::layout(
+ rightTree,
+ {
+ .commit = Layout::Commit::YES,
+ .knownSize = rightRect.size().cast>(),
+ .position = rightRect.topStart(),
+ .availableSpace = rightRect.size(),
+ .containingBlock = rightRect.size(),
+ }
+ );
+
+ Layout::paint(rightTree.root, stack);
+}
+
Vec> print(Markup::Document const &dom, Style::Media const &media) {
Style::StyleBook stylebook;
stylebook.add(
@@ -121,43 +328,63 @@ Vec> print(Markup::Document const &dom, Style::Media const &
.take("print stylesheet not available")
);
- auto start = Sys::now();
_collectStyle(dom, stylebook);
- auto elapsed = Sys::now() - start;
- logDebugIf(DEBUG_RENDER, "style collection time: {}", elapsed);
Style::Computer computer{media, stylebook};
+ // MARK: Page and Margins --------------------------------------------------
+
+ Vec> pages;
+
+ Layout::Resolver resolver{};
+ Style::Page page{.name = ""s, .number = pages.len(), .blank = false};
+ auto pageStyle = computer.computeFor(page);
+ RectPx pageRect{
+ media.width / Px{media.resolution.toDppx()},
+ media.height / Px{media.resolution.toDppx()}
+ };
+
+ auto scaleMatrix = Math::Trans2f::makeScale(media.resolution.toDppx());
+ auto pageScene = makeStrong(pageRect.size().cast(), scaleMatrix);
+
+ InsetsPx pageMargin = {
+ resolver.resolve(pageStyle->style->margin->top, pageRect.height),
+ resolver.resolve(pageStyle->style->margin->end, pageRect.width),
+ resolver.resolve(pageStyle->style->margin->bottom, pageRect.height),
+ resolver.resolve(pageStyle->style->margin->start, pageRect.width),
+ };
+
+ RectPx pageContent = pageRect.shrink(pageMargin);
+
+ _paintMargins(*pageStyle, pageRect, pageContent, *pageScene);
+
+ // MARK: Page Content ------------------------------------------------------
+
Layout::Viewport vp{
- .small = {
- media.width / Px{media.resolution.toDppx()},
- media.height / Px{media.resolution.toDppx()},
- },
+ .small = pageContent.size(),
+ .large = pageRect.size(),
+ .dynamic = pageRect.size(),
};
- Layout::Tree tree = {
+ Layout::Tree contentTree = {
Layout::build(computer, dom),
vp,
};
Layout::layout(
- tree,
- tree.root,
+ contentTree,
{
.commit = Layout::Commit::YES,
- .knownSize = {vp.small.width, NONE},
- .availableSpace = {vp.small.width, 0_px},
- .containingBlock = {vp.small.width, vp.small.height},
+ .knownSize = {pageContent.width, NONE},
+ .position = pageContent.topStart(),
+ .availableSpace = pageContent.size(),
+ .containingBlock = pageContent.size(),
}
);
- auto scaleMatrix = Math::Trans2f::makeScale(media.resolution.toDppx());
-
- Vec> pages;
- auto page = makeStrong(vp.small.size().cast(), scaleMatrix);
- Layout::paint(tree.root, *page);
- page->prepare();
- pages.pushBack(page);
+ Layout::paint(contentTree.root, *pageScene);
+ pageScene->prepare();
+ pages.pushBack(pageScene);
return pages;
}
diff --git a/src/web/vaev-driver/res/print.css b/src/web/vaev-driver/res/print.css
index d7cd41e..0ffaeff 100644
--- a/src/web/vaev-driver/res/print.css
+++ b/src/web/vaev-driver/res/print.css
@@ -1,87 +1,128 @@
@page {
- margin: 1in;
+ margin: 0.5in;
@footnote {
- margin-top: 1em
+ margin-top: 10px;
+ }
+
+ /* MARK: Top ------------------------------------------------------------ */
+
+ @-vaev-top {
+ display: flex;
}
@top-left-corner {
text-align: right;
- vertical-align: middle
+ vertical-align: middle;
}
@top-left {
text-align: left;
- vertical-align: middle
+ vertical-align: middle;
+ flex-grow: 1;
}
@top-center {
text-align: center;
- vertical-align: middle
+ vertical-align: middle;
+ flex-grow: 1;
}
@top-right {
text-align: right;
- vertical-align: middle
+ vertical-align: middle;
+ flex-grow: 1;
}
@top-right-corner {
text-align: left;
- vertical-align: middle
+ vertical-align: middle;
+ }
+
+ /* MARK: Left ----------------------------------------------------------- */
+
+ @-vaev-left {
+ display: flex;
+ flex-direction: column;
}
@left-top {
text-align: center;
- vertical-align: top
+ vertical-align: top;
+ flex-grow: 1;
}
+
@left-middle {
text-align: center;
- vertical-align: middle
+ vertical-align: middle;
+ flex-grow: 1;
}
@left-bottom {
text-align: center;
- vertical-align: bottom
+ vertical-align: bottom;
+ flex-grow: 1;
+ }
+
+ /* MARK: Right ---------------------------------------------------------- */
+
+ @-vaev-right {
+ display: flex;
+ flex-direction: column;
}
@right-top {
text-align: center;
- vertical-align: top
+ vertical-align: top;
+ flex-grow: 1;
}
@right-middle {
text-align: center;
- vertical-align: middle
+ vertical-align: middle;
+ flex-grow: 1;
}
@right-bottom {
text-align: center;
- vertical-align: bottom
+ vertical-align: bottom;
+ flex-grow: 1;
+ }
+
+ /* MARK: Bottom --------------------------------------------------------- */
+
+ @-vaev-bottom {
+ display: flex;
}
@bottom-left-corner {
text-align: right;
- vertical-align: middle
+ vertical-align: middle;
}
+
@bottom-left {
text-align: left;
- vertical-align: middle
+ vertical-align: middle;
+ flex-grow: 1;
}
@bottom-center {
text-align: center;
- vertical-align: middle
+ vertical-align: middle;
+ flex-grow: 1;
}
@bottom-right {
text-align: right;
- vertical-align: middle
+ vertical-align: middle;
+ flex-grow: 1;
+
}
@bottom-right-corner {
text-align: left;
- vertical-align: middle
+ vertical-align: middle;
}
}
diff --git a/src/web/vaev-layout/builder.cpp b/src/web/vaev-layout/builder.cpp
index 918d31a..0c90ca1 100644
--- a/src/web/vaev-layout/builder.cpp
+++ b/src/web/vaev-layout/builder.cpp
@@ -302,4 +302,30 @@ Box build(Style::Computer &c, Markup::Document const &doc) {
return root;
}
+Box buildForPseudoElement(Strong style) {
+ auto fontFace = _lookupFontface(*style);
+
+ // FIXME: We should pass this around from the top in order to properly resolve rems
+ Resolver resolver{
+ .rootFont = Text::Font{fontFace, 16},
+ .boxFont = Text::Font{fontFace, 16},
+ };
+ Text::ProseStyle proseStyle{
+ .font = {
+ fontFace,
+ resolver.resolve(style->font->size).cast(),
+ },
+ .multiline = true,
+ };
+
+ auto prose = makeStrong(proseStyle);
+ if (style->content) {
+ logDebug("content: '{}'", style->content);
+ prose->append(style->content.str());
+ return {style, fontFace, prose};
+ }
+
+ return {style, fontFace};
+}
+
} // namespace Vaev::Layout
diff --git a/src/web/vaev-layout/builder.h b/src/web/vaev-layout/builder.h
index ebcd6d2..915fa90 100644
--- a/src/web/vaev-layout/builder.h
+++ b/src/web/vaev-layout/builder.h
@@ -6,4 +6,6 @@ namespace Vaev::Layout {
Box build(Style::Computer &c, Markup::Document const &doc);
+Box buildForPseudoElement(Strong style);
+
} // namespace Vaev::Layout
diff --git a/src/web/vaev-layout/flex.cpp b/src/web/vaev-layout/flex.cpp
index 9ebd554..6e08842 100644
--- a/src/web/vaev-layout/flex.cpp
+++ b/src/web/vaev-layout/flex.cpp
@@ -349,15 +349,22 @@ struct FlexItem {
? containerSize.x
: containerSize.y
);
+
case Size::MIN_CONTENT:
return isWidth ? minContentSize.x : minContentSize.y;
+
case Size::MAX_CONTENT:
return isWidth ? maxContentSize.x : maxContentSize.y;
+
case Size::FIT_CONTENT:
logWarn("not implemented");
+ return 0_px;
+
case Size::AUTO:
- if (not isMin)
+ if (not isMin) {
+ logDebug("{}", box->style->sizing);
panic("AUTO is an invalid value for max-width");
+ }
// used cross min sizes are resolved to 0 whereas main sizes have specific method
return isWidth == fa.isRowOriented
diff --git a/src/web/vaev-layout/layout.cpp b/src/web/vaev-layout/layout.cpp
index 8a4a1e6..7710382 100644
--- a/src/web/vaev-layout/layout.cpp
+++ b/src/web/vaev-layout/layout.cpp
@@ -5,6 +5,7 @@
#include "flex.h"
#include "grid.h"
#include "inline.h"
+#include "positioned.h"
#include "table.h"
#include "values.h"
@@ -191,4 +192,10 @@ Output layout(Tree &tree, Box &box, Input input) {
return Output::fromSize(size);
}
+Output layout(Tree &tree, Input input) {
+ auto out = layout(tree, tree.root, input);
+ layoutPositioned(tree, tree.root, input.containingBlock);
+ return out;
+}
+
} // namespace Vaev::Layout
diff --git a/src/web/vaev-layout/layout.h b/src/web/vaev-layout/layout.h
index d274638..7c0479d 100644
--- a/src/web/vaev-layout/layout.h
+++ b/src/web/vaev-layout/layout.h
@@ -12,4 +12,6 @@ Vec2Px computeIntrinsicSize(Tree &tree, Box &box, IntrinsicSize intrinsic, Vec2P
Output layout(Tree &tree, Box &box, Input input);
+Output layout(Tree &tree, Input input);
+
} // namespace Vaev::Layout
diff --git a/src/web/vaev-style/computed.h b/src/web/vaev-style/computed.h
index eda4860..bb9e4bf 100644
--- a/src/web/vaev-style/computed.h
+++ b/src/web/vaev-style/computed.h
@@ -28,6 +28,7 @@ struct Computed {
Color color;
Number opacity;
+ String content = ""s;
AlignProps aligns;
Math::Vec2>> gaps;
diff --git a/src/web/vaev-style/computer.cpp b/src/web/vaev-style/computer.cpp
index fe1f4e3..679592f 100644
--- a/src/web/vaev-style/computer.cpp
+++ b/src/web/vaev-style/computer.cpp
@@ -32,17 +32,24 @@ void Computer::_evalRule(Rule const &rule, Markup::Element const &el, MatchingRu
});
}
-// https://drafts.csswg.org/css-cascade/#cascade-origin
-Strong Computer::computeFor(Computed const &parent, Markup::Element const &el) {
- MatchingRules matchingRules;
-
- // Collect matching styles rules
- for (auto const &sheet : _styleBook.styleSheets) {
- for (auto const &rule : sheet.rules) {
- _evalRule(rule, el, matchingRules);
+void Computer::_evalRule(Rule const &rule, Page const &page, PageComputedStyle &c) {
+ rule.visit(Visitor{
+ [&](PageRule const &r) {
+ if (r.match(page))
+ r.apply(c);
+ },
+ [&](MediaRule const &r) {
+ if (r.match(_media))
+ for (auto const &subRule : r.rules)
+ _evalRule(subRule, page, c);
+ },
+ [&](auto const &) {
+ // Ignore other rule types
}
- }
+ });
+}
+Strong Computer::_evalCascade(Computed const &parent, MatchingRules &matchingRules) {
// Sort origin and specificity
stableSort(
matchingRules,
@@ -53,14 +60,6 @@ Strong Computer::computeFor(Computed const &parent, Markup::Element co
}
);
- // Get the style attribute if any
- auto styleAttr = el.getAttribute(Html::STYLE_ATTR);
-
- StyleRule styleRule{
- .props = parseDeclarations(styleAttr ? *styleAttr : ""),
- };
- matchingRules.pushBack(&styleRule);
-
// Compute computed style
auto computed = makeStrong(Computed::initial());
computed->inherit(parent);
@@ -91,4 +90,35 @@ Strong Computer::computeFor(Computed const &parent, Markup::Element co
return computed;
}
+// https://drafts.csswg.org/css-cascade/#cascade-origin
+Strong Computer::computeFor(Computed const &parent, Markup::Element const &el) {
+ MatchingRules matchingRules;
+
+ // Collect matching styles rules
+ for (auto const &sheet : _styleBook.styleSheets)
+ for (auto const &rule : sheet.rules)
+ _evalRule(rule, el, matchingRules);
+
+ // Get the style attribute if any
+ auto styleAttr = el.getAttribute(Html::STYLE_ATTR);
+
+ StyleRule styleRule{
+ .props = parseDeclarations(styleAttr ? *styleAttr : ""),
+ .origin = Origin::INLINE,
+ };
+ matchingRules.pushBack(&styleRule);
+
+ return _evalCascade(parent, matchingRules);
+}
+
+Strong Computer::computeFor(Page const &page) {
+ auto computed = makeStrong();
+
+ for (auto const &sheet : _styleBook.styleSheets)
+ for (auto const &rule : sheet.rules)
+ _evalRule(rule, page, *computed);
+
+ return computed;
+}
+
} // namespace Vaev::Style
diff --git a/src/web/vaev-style/computer.h b/src/web/vaev-style/computer.h
index 5ea944b..83e0449 100644
--- a/src/web/vaev-style/computer.h
+++ b/src/web/vaev-style/computer.h
@@ -15,7 +15,13 @@ struct Computer {
void _evalRule(Rule const &rule, Markup::Element const &el, MatchingRules &matches);
+ void _evalRule(Rule const &rule, Page const &page, PageComputedStyle &c);
+
+ Strong _evalCascade(Computed const &parent, MatchingRules &matches);
+
Strong computeFor(Computed const &parent, Markup::Element const &el);
+
+ Strong computeFor(Page const &page);
};
} // namespace Vaev::Style
diff --git a/src/web/vaev-style/origin.h b/src/web/vaev-style/origin.h
index c5725b5..aa0033d 100644
--- a/src/web/vaev-style/origin.h
+++ b/src/web/vaev-style/origin.h
@@ -10,6 +10,7 @@ enum struct Origin {
USER_AGENT,
USER,
AUTHOR,
+ INLINE, //< Declarations from style attributes
};
static inline std::strong_ordering operator<=>(Origin a, Origin b) {
diff --git a/src/web/vaev-style/page.cpp b/src/web/vaev-style/page.cpp
new file mode 100644
index 0000000..54fa04f
--- /dev/null
+++ b/src/web/vaev-style/page.cpp
@@ -0,0 +1,171 @@
+#include "page.h"
+
+#include "decls.h"
+
+namespace Vaev::Style {
+
+static bool DEBUG_PAGE = true;
+
+// MARK: Page Selector ----------------------------------------------------------
+
+bool PageSelector::match(Page const &page) const {
+ if (name and page.name != name)
+ return false;
+
+ for (auto const &pseudo : pseudos) {
+ switch (pseudo) {
+ case PagePseudo::FIRST:
+ if (not page.number)
+ return false;
+ break;
+ case PagePseudo::BLANK:
+ if (page.blank)
+ return false;
+ break;
+ case PagePseudo::LEFT:
+ if (page.number % 2 == 0)
+ return false;
+ break;
+ case PagePseudo::RIGHT:
+ if (page.number % 2 == 1)
+ return false;
+ break;
+ }
+ }
+
+ return true;
+}
+
+void PageSelector::repr(Io::Emit &e) const {
+ e("({} pseudos: {})", name, pseudos);
+}
+
+PageSelector PageSelector::parse(Cursor &c) {
+ PageSelector res;
+
+ if (c.peek() == Css::Token::IDENT) {
+ res.name = c.next().token.data;
+ }
+
+ return res;
+}
+
+Vec PageSelector::parseList(Cursor &c) {
+ Vec res;
+
+ eatWhitespace(c);
+ while (not c.ended()) {
+ res.pushBack(parse(c));
+ eatWhitespace(c);
+ }
+
+ return res;
+}
+
+// MARK: Page Margin Rule ------------------------------------------------------
+
+void PageAreaRule::apply(Style::Computed &c) const {
+ for (auto const &prop : props) {
+ prop.apply(c, c);
+ }
+}
+
+void PageAreaRule::repr(Io::Emit &e) const {
+ e("(page-margin-rule\nmargin: {}\nprops: {})", area, props);
+}
+
+static Opt _parsePageArea(Css::Token tok) {
+ Str name = next(tok.data);
+
+#define ITER(ID, NAME) \
+ if (name == NAME) \
+ return PageArea::ID;
+ FOREACH_PAGE_AREA(ITER)
+#undef ITER
+
+ logWarn("unknown page area: {}", name);
+
+ return NONE;
+}
+
+Opt PageAreaRule::parse(Css::Sst const &sst) {
+ PageAreaRule res;
+
+ res.area = try$(_parsePageArea(sst.token));
+
+ for (auto const &item : sst.content) {
+ if (item == Css::Sst::DECL) {
+ auto prop = parseDeclaration(item);
+ if (prop)
+ res.props.pushBack(prop.take());
+ } else {
+ logWarnIf(DEBUG_PAGE, "unexpected item in style rule: {}", item);
+ }
+ }
+
+ return res;
+}
+
+// MARK: Page Rule -------------------------------------------------------------
+
+bool PageRule::match(Page const &page) const {
+ if (selectors.len() == 0)
+ return true;
+
+ for (auto &s : selectors) {
+ if (s.match(page))
+ return true;
+ }
+
+ return false;
+}
+
+void PageRule::apply(PageComputedStyle &c) const {
+ for (auto const &prop : props) {
+ prop.apply(*c.style, *c.style);
+ }
+
+ for (auto const &area : areas) {
+ auto computed = c.area(area.area);
+ area.apply(*computed);
+ }
+}
+
+void PageRule::repr(Io::Emit &e) const {
+ e("(page-rule\nselectors: {}\nprops: {}\nmargins: {})", selectors, props, areas);
+}
+
+PageRule PageRule::parse(Css::Sst const &sst) {
+ if (sst != Css::Sst::RULE)
+ panic("expected rule");
+
+ if (sst.prefix != Css::Sst::LIST)
+ panic("expected list");
+
+ PageRule res;
+
+ // Parse the selector
+ auto &prefix = sst.prefix.unwrap();
+ Cursor prefixContent = prefix->content;
+ res.selectors = PageSelector::parseList(prefixContent);
+
+ // Parse the properties.
+ for (auto const &item : sst.content) {
+ if (item == Css::Sst::DECL) {
+ auto prop = parseDeclaration(item);
+ if (prop)
+ res.props.pushBack(prop.take());
+ } else if (item == Css::Sst::RULE and
+ item.token == Css::Token::AT_KEYWORD) {
+ auto rule = PageAreaRule::parse(item);
+ if (rule)
+ res.areas.pushBack(*rule);
+ } else {
+ logWarnIf(DEBUG_PAGE, "unexpected item in style rule: {}", item);
+ }
+ }
+
+ return res;
+}
+
+} // namespace Vaev::Style
diff --git a/src/web/vaev-style/page.h b/src/web/vaev-style/page.h
new file mode 100644
index 0000000..2492826
--- /dev/null
+++ b/src/web/vaev-style/page.h
@@ -0,0 +1,110 @@
+#pragma once
+
+#include "origin.h"
+#include "styles.h"
+
+namespace Vaev::Style {
+
+// https://drafts.csswg.org/css-page-3/#margin-at-rules
+#define FOREACH_PAGE_AREA(ITER) \
+ ITER(FOOTNOTE, "footnote") \
+ ITER(TOP, "-vaev-top") \
+ ITER(TOP_LEFT_CORNER, "top-left-corner") \
+ ITER(TOP_LEFT, "top-left") \
+ ITER(TOP_CENTER, "top-center") \
+ ITER(TOP_RIGHT, "top-right") \
+ ITER(TOP_RIGHT_CORNER, "top-right-corner") \
+ ITER(RIGHT, "-vaev-right") \
+ ITER(RIGHT_TOP, "right-top") \
+ ITER(RIGHT_MIDDLE, "right-middle") \
+ ITER(RIGHT_BOTTOM, "right-bottom") \
+ ITER(BOTTOM, "-vaev-bottom") \
+ ITER(BOTTOM_RIGHT_CORNER, "bottom-right-corner") \
+ ITER(BOTTOM_RIGHT, "bottom-right") \
+ ITER(BOTTOM_CENTER, "bottom-center") \
+ ITER(BOTTOM_LEFT, "bottom-left") \
+ ITER(BOTTOM_LEFT_CORNER, "bottom-left-corner") \
+ ITER(LEFT, "-vaev-left") \
+ ITER(LEFT_BOTTOM, "left-bottom") \
+ ITER(LEFT_MIDDLE, "left-middle") \
+ ITER(LEFT_TOP, "left-top")
+
+enum struct PageArea {
+#define ITER(ID, ...) ID,
+ FOREACH_PAGE_AREA(ITER)
+#undef ITER
+ _LEN,
+};
+
+struct Page {
+ String name = ""s;
+ usize number;
+ bool blank;
+};
+
+struct PageComputedStyle {
+ Strong style = makeStrong(Computed::initial());
+ Array, static_cast(PageArea::_LEN)> _areas = {
+#define ITER(...) makeStrong(Computed::initial()),
+ FOREACH_PAGE_AREA(ITER)
+#undef ITER
+ };
+
+ Strong area(PageArea margin) const {
+ return _areas[static_cast(margin)];
+ }
+};
+
+// https://drafts.csswg.org/css-page-3/#at-page-rule
+
+#define FOREACH_PAGE_PSEUDO(ITER) \
+ ITER(FIRST, "first") \
+ ITER(BLANK, "blank") \
+ ITER(LEFT, "left") \
+ ITER(RIGHT, "right")
+
+enum struct PagePseudo {
+#define ITER(ID, ...) ID,
+ FOREACH_PAGE_PSEUDO(ITER)
+#undef ITER
+};
+
+struct PageSelector {
+ String name = ""s;
+ Vec pseudos;
+
+ static PageSelector parse(Cursor &c);
+
+ static Vec parseList(Cursor &c);
+
+ bool match(Page const &page) const;
+
+ void repr(Io::Emit &e) const;
+};
+
+struct PageAreaRule {
+ PageArea area;
+ Vec props;
+
+ static Opt parse(Css::Sst const &sst);
+
+ void apply(Style::Computed &c) const;
+
+ void repr(Io::Emit &e) const;
+};
+
+struct PageRule {
+ Vec selectors;
+ Vec props;
+ Vec areas;
+
+ static PageRule parse(Css::Sst const &sst);
+
+ bool match(Page const &page) const;
+
+ void apply(PageComputedStyle &c) const;
+
+ void repr(Io::Emit &e) const;
+};
+
+} // namespace Vaev::Style
diff --git a/src/web/vaev-style/rules.cpp b/src/web/vaev-style/rules.cpp
index 2bfaf9c..be1132c 100644
--- a/src/web/vaev-style/rules.cpp
+++ b/src/web/vaev-style/rules.cpp
@@ -8,6 +8,10 @@ static bool DEBUG_RULE = false;
// MARK: StyleRule -------------------------------------------------------------
+bool StyleRule::match(Markup::Element const &el) const {
+ return selector.match(el);
+}
+
void StyleRule::repr(Io::Emit &e) const {
e("(style-rule");
e.indent();
@@ -142,6 +146,8 @@ Rule Rule::parse(Css::Sst const &sst, Origin origin) {
return ImportRule::parse(sst);
else if (tok.data == "@font-face")
return FontFaceRule::parse(sst);
+ else if (tok.data == "@page")
+ return PageRule::parse(sst);
else
return StyleRule::parse(sst, origin);
}
diff --git a/src/web/vaev-style/rules.h b/src/web/vaev-style/rules.h
index 9e559e2..060cdf5 100644
--- a/src/web/vaev-style/rules.h
+++ b/src/web/vaev-style/rules.h
@@ -3,6 +3,7 @@
#include "fonts.h"
#include "media.h"
#include "origin.h"
+#include "page.h"
#include "select.h"
#include "styles.h"
@@ -18,9 +19,7 @@ struct StyleRule {
void repr(Io::Emit &e) const;
- bool match(Markup::Element const &el) const {
- return selector.match(el);
- }
+ bool match(Markup::Element const &el) const;
static StyleRule parse(Css::Sst const &sst, Origin origin = Origin::AUTHOR);
};
@@ -60,7 +59,8 @@ using _Rule = Union<
StyleRule,
FontFaceRule,
MediaRule,
- ImportRule>;
+ ImportRule,
+ PageRule>;
struct Rule : public _Rule {
using _Rule::_Rule;
diff --git a/src/web/vaev-style/styles.cpp b/src/web/vaev-style/styles.cpp
index 17097ad..f25327d 100644
--- a/src/web/vaev-style/styles.cpp
+++ b/src/web/vaev-style/styles.cpp
@@ -118,4 +118,39 @@ void DefaultedProp::repr(Io::Emit &e) const {
e("(Defaulted {#} = {})", propName, value);
}
+// MARK: Style Property -------------------------------------------------------
+
+Str StyleProp::name() const {
+ return visit([](auto const &p) {
+ return p.name();
+ });
+}
+
+void StyleProp::inherit(Computed const &parent, Computed &child) const {
+ visit([&](auto const &p) {
+ if constexpr (requires { p.inherit(parent, child); })
+ p.inherit(parent, child);
+ });
+}
+
+void StyleProp::apply(Computed const &parent, Computed &c) const {
+ visit([&](auto const &p) {
+ if constexpr (requires { p.apply(c); })
+ p.apply(c);
+
+ if constexpr (requires { p.apply(parent, c); })
+ p.apply(parent, c);
+ });
+}
+
+void StyleProp::repr(Io::Emit &e) const {
+ e("({}", name());
+ visit([&](auto const &p) {
+ e(" {#}", p.value);
+ if (important == Important::YES)
+ e(" !important");
+ });
+ e(")");
+}
+
} // namespace Vaev::Style
diff --git a/src/web/vaev-style/styles.h b/src/web/vaev-style/styles.h
index a01ce74..bb34ecf 100644
--- a/src/web/vaev-style/styles.h
+++ b/src/web/vaev-style/styles.h
@@ -1149,7 +1149,31 @@ struct BorderWidthProp {
}
};
-// MARK: Borders - Table ---------------------------------------------------------------
+// MARK: Content ---------------------------------------------------------------
+
+// https://drafts.csswg.org/css-content/#content-property
+struct ContentProp {
+ String value = initial();
+
+ static constexpr Str name() { return "content"; }
+
+ static String initial() { return ""s; }
+
+ void apply(Computed &c) const {
+ c.content = value;
+ }
+
+ static String load(Computed const &c) {
+ return c.content;
+ }
+
+ Res<> parse(Cursor &c) {
+ value = try$(parseValue(c));
+ return Ok();
+ }
+};
+
+// MARK: Borders - Table -------------------------------------------------------
// https://www.w3.org/TR/CSS22/tables.html#propdef-border-collapse
struct BorderCollapseProp {
@@ -2733,6 +2757,9 @@ using _StyleProp = Union<
BorderCollapseProp,
BorderSpacingProp,
+ // Content
+ ContentProp,
+
// Flex
FlexBasisProp,
FlexDirectionProp,
@@ -2834,38 +2861,13 @@ struct StyleProp : public _StyleProp {
Cons{"grid-gap", "gap"},
};
- Str name() const {
- return visit([](auto const &p) {
- return p.name();
- });
- }
+ Str name() const;
- void inherit(Computed const &parent, Computed &child) const {
- visit([&](auto const &p) {
- if constexpr (requires { p.inherit(parent, child); })
- p.inherit(parent, child);
- });
- }
+ void inherit(Computed const &parent, Computed &child) const;
- void apply(Computed const &parent, Computed &c) const {
- visit([&](auto const &p) {
- if constexpr (requires { p.apply(c); })
- p.apply(c);
-
- if constexpr (requires { p.apply(parent, c); })
- p.apply(parent, c);
- });
- }
+ void apply(Computed const &parent, Computed &c) const;
- void repr(Io::Emit &e) const {
- e("({}", name());
- visit([&](auto const &p) {
- e(" {}", p.value);
- if (important == Important::YES)
- e(" !important");
- });
- e(")");
- }
+ void repr(Io::Emit &e) const;
};
} // namespace Vaev::Style