Skip to content

Commit edb5dc6

Browse files
committed
vaev-engine: Implement page size css property.
1 parent c1e5c53 commit edb5dc6

File tree

9 files changed

+464
-64
lines changed

9 files changed

+464
-64
lines changed

src/main.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,6 @@ struct PrintOption {
5050

5151
auto paper = self.paper;
5252

53-
if (self.orientation == Print::Orientation::LANDSCAPE)
54-
paper = paper.landscape();
55-
5653
if (self.width) {
5754
paper.name = "custom";
5855
paper.width = resolver.resolve(*self.width).template cast<f64>();
@@ -65,6 +62,7 @@ struct PrintOption {
6562

6663
return {
6764
.paper = paper,
65+
.orientation = self.orientation,
6866
.scale = self.scale.toDppx(),
6967
};
7068
}

src/vaev-engine/driver/print.cpp

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,13 @@ void _paintMargins(Style::PageSpecifiedValues& pageStyle, RectAu pageRect, RectA
8484
}
8585

8686
static Style::Media _constructMedia(Print::Settings const& settings) {
87+
Math::Vec2f paperSize = resolve(settings.orientation, settings.paper.size());
88+
8789
return {
8890
.type = MediaType::SCREEN,
89-
.width = Au{settings.paper.width},
90-
.height = Au{settings.paper.height},
91-
.aspectRatio = settings.paper.width / (f64)settings.paper.height,
91+
.width = Au{paperSize.width},
92+
.height = Au{paperSize.height},
93+
.aspectRatio = paperSize.width / paperSize.height,
9294
.orientation = settings.orientation,
9395

9496
.resolution = Resolution{settings.scale, Resolution::X},
@@ -116,9 +118,39 @@ static Style::Media _constructMedia(Print::Settings const& settings) {
116118
.prefersReducedData = ReducedData::NO_PREFERENCE,
117119

118120
// NOTE: Deprecated Media Features
119-
.deviceWidth = Au{settings.paper.width},
120-
.deviceHeight = Au{settings.paper.height},
121-
.deviceAspectRatio = settings.paper.width / settings.paper.height,
121+
.deviceWidth = Au{paperSize.width},
122+
.deviceHeight = Au{paperSize.height},
123+
.deviceAspectRatio = paperSize.width / paperSize.height,
124+
};
125+
}
126+
127+
Pair<RectAu> _computePageBounds(Print::Settings const& settings, Style::Media const& media, Style::PageSpecifiedValues const& pageStyle) {
128+
Vec2Au pageRectSize = resolve(
129+
*pageStyle.style->pageSize,
130+
Vec2Au{
131+
media.width / Au{media.resolution.toDppx()},
132+
media.height / Au{media.resolution.toDppx()}
133+
}
134+
);
135+
136+
InsetsAu pageMargin;
137+
if (settings.margins == Print::Margins::DEFAULT) {
138+
Layout::Resolver resolver{};
139+
pageMargin = {
140+
resolver.resolve(pageStyle.style->margin->top, pageRectSize.height),
141+
resolver.resolve(pageStyle.style->margin->end, pageRectSize.width),
142+
resolver.resolve(pageStyle.style->margin->bottom, pageRectSize.height),
143+
resolver.resolve(pageStyle.style->margin->start, pageRectSize.width),
144+
};
145+
} else if (settings.margins == Print::Margins::CUSTOM) {
146+
pageMargin = settings.margins.custom.cast<Au>();
147+
} else if (settings.margins == Print::Margins::MINIMUM) {
148+
pageMargin = {};
149+
}
150+
151+
return {
152+
pageRectSize,
153+
RectAu{pageRectSize}.shrink(pageMargin)
122154
};
123155
}
124156

@@ -164,40 +196,11 @@ export Generator<Print::Page> print(Gc::Ref<Dom::Document> dom, Print::Settings
164196
};
165197

166198
auto pageStyle = computer.computeFor(initialStyle, page);
167-
RectAu pageRect{
168-
media.width / Au{media.resolution.toDppx()},
169-
media.height / Au{media.resolution.toDppx()}
170-
};
171199

172-
auto pageSize = pageRect.size().cast<f64>();
200+
auto [pageRect, pageContent] = _computePageBounds(settings, media, *pageStyle);
173201

174202
auto pageStack = makeRc<Scene::Stack>();
175203

176-
InsetsAu pageMargin;
177-
178-
if (settings.margins == Print::Margins::DEFAULT) {
179-
Layout::Resolver resolver{};
180-
pageMargin = {
181-
resolver.resolve(pageStyle->style->margin->top, pageRect.height),
182-
resolver.resolve(pageStyle->style->margin->end, pageRect.width),
183-
resolver.resolve(pageStyle->style->margin->bottom, pageRect.height),
184-
resolver.resolve(pageStyle->style->margin->start, pageRect.width),
185-
};
186-
} else if (settings.margins == Print::Margins::CUSTOM) {
187-
pageMargin = settings.margins.custom.cast<Au>();
188-
} else if (settings.margins == Print::Margins::MINIMUM) {
189-
pageMargin = {};
190-
}
191-
192-
RectAu pageContent = pageRect.shrink(pageMargin);
193-
194-
Layout::Viewport vp{
195-
.small = pageContent.size(),
196-
};
197-
198-
contentTree.viewport = vp;
199-
contentTree.fc = {pageContent.size()};
200-
201204
if (settings.headerFooter and settings.margins != Print::Margins::NONE)
202205
_paintMargins(*pageStyle, pageRect, pageContent, *pageStack);
203206

@@ -208,6 +211,13 @@ export Generator<Print::Page> print(Gc::Ref<Dom::Document> dom, Print::Settings
208211
.containingBlock = pageContent.size(),
209212
};
210213

214+
Layout::Viewport vp{
215+
.small = pageContent.size(),
216+
};
217+
218+
contentTree.viewport = vp;
219+
contentTree.fc = {pageContent.size()};
220+
211221
contentTree.fc.enterDiscovery();
212222
auto outDiscovery = Layout::layout(
213223
contentTree,
@@ -229,7 +239,16 @@ export Generator<Print::Page> print(Gc::Ref<Dom::Document> dom, Print::Settings
229239
Layout::paint(fragment, *pageStack);
230240
pageStack->prepare();
231241

232-
co_yield Print::Page(settings.paper, makeRc<Scene::Clear>(makeRc<Scene::Transform>(pageStack, Math::Trans2f::scale(media.resolution.toDppx())), canvasColor));
242+
Print::PaperStock pagePaperStock{
243+
""s,
244+
(f64)pageRect.size().width * media.resolution.toDppx(),
245+
(f64)pageRect.size().height * media.resolution.toDppx()
246+
};
247+
248+
co_yield Print::Page(
249+
pagePaperStock,
250+
makeRc<Scene::Clear>(makeRc<Scene::Transform>(pageStack, Math::Trans2f::scale(media.resolution.toDppx())), canvasColor)
251+
);
233252

234253
if (outFragmentation.completelyLaidOut)
235254
break;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1",
3+
"id": "vaev-engine.driver.tests",
4+
"type": "lib",
5+
"props": {
6+
"cpp-excluded": true
7+
},
8+
"requires": [
9+
"karm-test",
10+
"vaev-engine"
11+
],
12+
"injects": [
13+
"__tests__"
14+
]
15+
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#include <karm-test/macros.h>
2+
#include <karm-text/prose.h>
3+
4+
import Vaev.Engine;
5+
import Karm.Gc;
6+
import Karm.Print;
7+
8+
namespace Vaev::Driver::Tests {
9+
10+
f64 const EPSILON = 1e-2;
11+
12+
void buildTestCase(Gc::Heap& gc, Gc::Ref<Dom::Document> dom, usize amountOfPages, Str styleCase) {
13+
dom->styleSheets = gc.alloc<Style::StyleSheetList>();
14+
Html::HtmlParser parser{gc, dom};
15+
16+
Karm::StringBuilder styleBuilder;
17+
styleBuilder.append("html, body, div { display: block; }\n"s);
18+
styleBuilder.append("#break { break-after: page; }"s);
19+
styleBuilder.append(styleCase);
20+
auto finalStyle = styleBuilder.take();
21+
Io::SScan textScan{finalStyle};
22+
auto sheet = Style::StyleSheet::parse(textScan, ""_url);
23+
dom->styleSheets->add(std::move(sheet));
24+
25+
Karm::StringBuilder contentBuilder;
26+
contentBuilder.append("<html><body>"s);
27+
for (usize i = 0; i < amountOfPages; ++i) {
28+
contentBuilder.append("<div id=\"break\">hi</div>"s);
29+
}
30+
contentBuilder.append("</body></html>"s);
31+
32+
parser.write(contentBuilder.take());
33+
}
34+
35+
Vec<Math::Vec2f> printedPaperSizes(Gc::Ref<Dom::Document> dom, Print::Settings settings) {
36+
auto pagesGen = Vaev::Driver::print(*dom, settings);
37+
38+
Vec<Math::Vec2f> sizes;
39+
while (true) {
40+
auto next = pagesGen.next();
41+
if (not next)
42+
break;
43+
44+
sizes.pushBack({next->_paper.width, next->_paper.height});
45+
}
46+
47+
return sizes;
48+
}
49+
50+
bool sizesAreEqual(
51+
Vec<Math::Vec2f> const& expected,
52+
Vec<Math::Vec2f> const& actual
53+
) {
54+
if (expected.len() != actual.len())
55+
return false;
56+
57+
for (usize i = 0; i < expected.len(); ++i) {
58+
auto diff = expected[i] - actual[i];
59+
if (not Math::epsilonEq(expected[i].x, actual[i].x, EPSILON) or
60+
not Math::epsilonEq(expected[i].y, actual[i].y, EPSILON)) {
61+
return false;
62+
}
63+
}
64+
65+
return true;
66+
}
67+
68+
test$("sanity-test") {
69+
Gc::Heap gc;
70+
71+
auto dom = gc.alloc<Dom::Document>(Mime::Url());
72+
73+
usize const amountOfPages = 3;
74+
75+
buildTestCase(gc, dom, amountOfPages, "");
76+
77+
Print::Settings settings = {
78+
.paper = Print::A4,
79+
.orientation = Print::Orientation::PORTRAIT,
80+
};
81+
82+
Vec<Math::Vec2f> expectedPageSizes;
83+
for (usize i = 0; i < amountOfPages; ++i) {
84+
expectedPageSizes.pushBack({Print::A4.width, Print::A4.height});
85+
}
86+
87+
auto actualPageSizes = printedPaperSizes(dom, settings);
88+
89+
if (not sizesAreEqual(expectedPageSizes, actualPageSizes)) {
90+
logError("expected: {}, actual: {}", expectedPageSizes, actualPageSizes);
91+
expect$(false);
92+
}
93+
return Ok();
94+
}
95+
96+
test$("page-as-landscape") {
97+
Gc::Heap gc;
98+
99+
auto dom = gc.alloc<Dom::Document>(Mime::Url());
100+
101+
usize const amountOfPages = 3;
102+
103+
buildTestCase(gc, dom, amountOfPages, "@page { size: landscape; }"s);
104+
105+
Print::Settings settings = {
106+
.paper = Print::A4,
107+
.orientation = Print::Orientation::PORTRAIT,
108+
};
109+
110+
Vec<Math::Vec2f> expectedPageSizes;
111+
for (usize i = 0; i < amountOfPages; ++i) {
112+
expectedPageSizes.pushBack({Print::A4.height, Print::A4.width});
113+
}
114+
115+
auto actualPageSizes = printedPaperSizes(dom, settings);
116+
117+
if (not sizesAreEqual(expectedPageSizes, actualPageSizes)) {
118+
logError("expected: {}, actual: {}", expectedPageSizes, actualPageSizes);
119+
expect$(false);
120+
}
121+
122+
return Ok();
123+
}
124+
125+
test$("page-as-a5") {
126+
Gc::Heap gc;
127+
128+
auto dom = gc.alloc<Dom::Document>(Mime::Url());
129+
130+
usize const amountOfPages = 3;
131+
132+
buildTestCase(gc, dom, amountOfPages, "@page { size: A5; }"s);
133+
134+
Print::Settings settings = {
135+
.paper = Print::A4,
136+
.orientation = Print::Orientation::PORTRAIT,
137+
};
138+
139+
Vec<Math::Vec2f> expectedPageSizes;
140+
for (usize i = 0; i < amountOfPages; ++i) {
141+
expectedPageSizes.pushBack({Print::A5.width, Print::A5.height});
142+
}
143+
144+
auto actualPageSizes = printedPaperSizes(dom, settings);
145+
146+
if (not sizesAreEqual(expectedPageSizes, actualPageSizes)) {
147+
logError("expected: {}, actual: {}", expectedPageSizes, actualPageSizes);
148+
expect$(false);
149+
}
150+
151+
return Ok();
152+
}
153+
154+
test$("page-left-right") {
155+
Gc::Heap gc;
156+
157+
auto dom = gc.alloc<Dom::Document>(Mime::Url());
158+
159+
usize const amountOfPages = 3;
160+
161+
buildTestCase(
162+
gc, dom, amountOfPages,
163+
"@page:left { size: A3 portrait; }\n"s
164+
"@page:right { size: A5 landscape; }"s
165+
);
166+
167+
Print::Settings settings = {
168+
.paper = Print::A4,
169+
.orientation = Print::Orientation::PORTRAIT,
170+
};
171+
172+
Vec<Math::Vec2f> expectedPageSizes;
173+
for (usize i = 0; i < amountOfPages; ++i) {
174+
if (i % 2)
175+
expectedPageSizes.pushBack({Print::A3.width, Print::A3.height});
176+
else
177+
expectedPageSizes.pushBack({Print::A5.height, Print::A5.width});
178+
}
179+
180+
auto actualPageSizes = printedPaperSizes(dom, settings);
181+
182+
if (not sizesAreEqual(expectedPageSizes, actualPageSizes)) {
183+
logError("expected: {}, actual: {}", expectedPageSizes, actualPageSizes);
184+
expect$(false);
185+
}
186+
187+
return Ok();
188+
}
189+
190+
} // namespace Vaev::Driver::Tests

src/vaev-engine/layout/values.cpp

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -211,32 +211,12 @@ export struct Resolver {
211211
resolve(Length(value.val(), Length::SVH))
212212
);
213213

214-
// Absolute
215-
// https://drafts.csswg.org/css-values/#absolute-lengths
216-
case Length::CM:
217-
return Au::fromFloatNearest(value.val() * 96 / 2.54);
218-
219-
case Length::MM:
220-
return Au::fromFloatNearest(value.val() * 96 / 25.4);
221-
222-
case Length::Q:
223-
return Au::fromFloatNearest(value.val() * 96 / 101.6);
224-
225-
case Length::IN:
226-
return Au::fromFloatNearest(value.val() * 96);
227-
228-
case Length::PT:
229-
return Au::fromFloatNearest(value.val() * 96 / 72.0);
230-
231-
case Length::PC:
232-
return Au::fromFloatNearest(value.val() * 96 / 6.0);
233-
234-
case Length::PX:
235-
return Au::fromFloatNearest(value.val());
236-
237-
default:
214+
default: {
215+
if (value.isAbsolute())
216+
return resolveAbsoluteLength(value);
238217
panic("invalid length unit");
239218
}
219+
}
240220
}
241221

242222
Au resolve(LineWidth const& value) {

0 commit comments

Comments
 (0)