diff --git a/src/main.cpp b/src/main.cpp index 173ada1b..d55e1d99 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -53,9 +53,6 @@ struct PrintOption { auto paper = self.paper; - if (self.orientation == Print::Orientation::LANDSCAPE) - paper = paper.landscape(); - if (self.width) { paper.name = "custom"; paper.width = resolver.resolve(*self.width).template cast(); @@ -68,6 +65,7 @@ struct PrintOption { return { .paper = paper, + .orientation = self.orientation, .scale = self.scale.toDppx(), }; } diff --git a/src/vaev-engine/driver/print.cpp b/src/vaev-engine/driver/print.cpp index 751a57eb..2ead5683 100644 --- a/src/vaev-engine/driver/print.cpp +++ b/src/vaev-engine/driver/print.cpp @@ -88,8 +88,65 @@ void _paintMargins(Style::PageSpecifiedValues& pageStyle, RectAu pageRect, RectA _paintMainMargin(pageStyle, stack, rightRect, Style::PageArea::RIGHT, {Style::PageArea::RIGHT_TOP, Style::PageArea::RIGHT_MIDDLE, Style::PageArea::RIGHT_BOTTOM}); } +Pair _computePageBounds(Print::Settings const& settings, Style::Media const& media, Style::PageSpecifiedValues const& pageStyle) { + Vec2Au pageRectSize = resolve( + *pageStyle.style->pageSize, + Vec2Au{ + media.width / Au{media.resolution.toDppx()}, + media.height / Au{media.resolution.toDppx()} + } + ); + + InsetsAu pageMargin; + if (settings.margins == Print::Margins::DEFAULT) { + Layout::Resolver resolver{}; + pageMargin = { + resolver.resolve(pageStyle.style->margin->top, pageRectSize.height), + resolver.resolve(pageStyle.style->margin->end, pageRectSize.width), + resolver.resolve(pageStyle.style->margin->bottom, pageRectSize.height), + resolver.resolve(pageStyle.style->margin->start, pageRectSize.width), + }; + } else if (settings.margins == Print::Margins::CUSTOM) { + pageMargin = settings.margins.custom.cast(); + } else if (settings.margins == Print::Margins::MINIMUM) { + pageMargin = {}; + } + + return { + pageRectSize, + RectAu{pageRectSize}.shrink(pageMargin) + }; +} + +// https://www.w3.org/TR/css-page-3/#issue-59a903e8 +Style::Media _constructMediaForBasePageSize(Gc::Ref dom, Print::Settings const& settings) { + auto const media = Style::Media::forPrint(settings); + + // ----------------- Computing UA page size + InsetsAu uAPageMargin; + if (settings.margins == Print::Margins::DEFAULT) { + uAPageMargin = {resolveAbsoluteLength(Length{0.5, Length::Unit::IN})}; + } else if (settings.margins == Print::Margins::CUSTOM) { + uAPageMargin = settings.margins.custom.cast(); + } else if (settings.margins == Print::Margins::MINIMUM) { + uAPageMargin = {}; + } + + RectAu uAPageRect{ + media.width / Au{media.resolution.toDppx()}, + media.height / Au{media.resolution.toDppx()} + }; + + RectAu uAPageContent = uAPageRect.shrink(uAPageMargin); + + // ----------------- Computing Page Base Size + auto pageStyleFromUAPage = computePageBaseSize(*dom->styleSheets, media); + auto [basePageRect, _] = _computePageBounds(settings, media, *pageStyleFromUAPage); + return media.withPixelsDimensions(basePageRect.size()); +} + export Generator print(Gc::Ref dom, Print::Settings const& settings) { - auto media = Style::Media::forPrint(settings); + auto const media = _constructMediaForBasePageSize(dom, settings); Font::Database db; if (not db.loadSystemFonts()) @@ -98,6 +155,7 @@ export Generator print(Gc::Ref dom, Print::Settings Style::Computer computer{ media, *dom->styleSheets, db }; + computer.build(); computer.styleDocument(*dom); @@ -130,36 +188,10 @@ export Generator print(Gc::Ref dom, Print::Settings }; auto pageStyle = computer.computeFor(initialStyle, page); - RectAu pageRect{ - media.width / Au{media.resolution.toDppx()}, - media.height / Au{media.resolution.toDppx()} - }; - auto pageStack = makeRc(); - InsetsAu pageMargin; - - if (settings.margins == Print::Margins::DEFAULT) { - Layout::Resolver resolver{}; - 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), - }; - } else if (settings.margins == Print::Margins::CUSTOM) { - pageMargin = settings.margins.custom.cast(); - } else if (settings.margins == Print::Margins::MINIMUM) { - pageMargin = {}; - } - - RectAu pageContent = pageRect.shrink(pageMargin); + auto [pageRect, pageContent] = _computePageBounds(settings, media, *pageStyle); - Layout::Viewport vp{ - .small = pageContent.size(), - }; - - contentTree.viewport = vp; - contentTree.fc = {pageContent.size()}; + auto pageStack = makeRc(); if (settings.headerFooter and settings.margins != Print::Margins::NONE) _paintMargins(*pageStyle, pageRect, pageContent, *pageStack); @@ -171,6 +203,13 @@ export Generator print(Gc::Ref dom, Print::Settings .containingBlock = pageContent.size(), }; + Layout::Viewport vp{ + .small = pageContent.size(), + }; + + contentTree.viewport = vp; + contentTree.fc = {pageContent.size()}; + contentTree.fc.enterDiscovery(); auto outDiscovery = Layout::layout( contentTree, @@ -192,8 +231,14 @@ export Generator print(Gc::Ref dom, Print::Settings Layout::paint(fragment, *pageStack); pageStack->prepare(); + Print::PaperStock pagePaperStock{ + ""s, + (f64)pageRect.size().width * media.resolution.toDppx(), + (f64)pageRect.size().height * media.resolution.toDppx() + }; + co_yield Print::Page( - settings.paper, + pagePaperStock, makeRc( makeRc( pageStack, diff --git a/src/vaev-engine/driver/tests/manifest.json b/src/vaev-engine/driver/tests/manifest.json new file mode 100644 index 00000000..1e6e92cc --- /dev/null +++ b/src/vaev-engine/driver/tests/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", + "id": "vaev-engine.driver.tests", + "type": "lib", + "props": { + "cpp-excluded": true + }, + "requires": [ + "karm-test", + "vaev-engine" + ], + "injects": [ + "__tests__" + ] +} diff --git a/src/vaev-engine/driver/tests/test-print-paper-dimensions.cpp b/src/vaev-engine/driver/tests/test-print-paper-dimensions.cpp new file mode 100644 index 00000000..c6b702d3 --- /dev/null +++ b/src/vaev-engine/driver/tests/test-print-paper-dimensions.cpp @@ -0,0 +1,239 @@ +#include +#include + +import Vaev.Engine; +import Karm.Gc; +import Karm.Print; + +using namespace Karm; + +namespace Vaev::Driver::Tests { + +f64 const EPSILON = 1e-2; + +void buildTestCase(Gc::Heap& gc, Gc::Ref dom, usize amountOfPages, Str styleCase) { + dom->styleSheets = gc.alloc(); + Html::HtmlParser parser{gc, dom}; + + Karm::StringBuilder styleBuilder; + styleBuilder.append("html, body, div { display: block; }\n"s); + styleBuilder.append("#break { break-after: page; }"s); + styleBuilder.append(styleCase); + auto finalStyle = styleBuilder.take(); + Io::SScan textScan{finalStyle}; + auto sheet = Style::StyleSheet::parse(textScan, ""_url); + dom->styleSheets->add(std::move(sheet)); + + Karm::StringBuilder contentBuilder; + contentBuilder.append(""s); + contentBuilder.append("
hi
"s); + contentBuilder.append("
hi
"s); + for (usize i = 0; i < amountOfPages; ++i) { + contentBuilder.append("
hi
"s); + } + contentBuilder.append(""s); + + parser.write(contentBuilder.take()); +} + +Vec printedPaperSizes(Gc::Ref dom, Print::Settings settings) { + auto pagesGen = Vaev::Driver::print(*dom, settings); + + Vec sizes; + while (true) { + auto next = pagesGen.next(); + if (not next) + break; + + sizes.pushBack({next->_paper.width, next->_paper.height}); + } + + return sizes; +} + +bool sizesAreEqual( + Vec const& expected, + Vec const& actual +) { + if (expected.len() != actual.len()) + return false; + + for (usize i = 0; i < expected.len(); ++i) { + auto diff = expected[i] - actual[i]; + if (not Math::epsilonEq(expected[i].x, actual[i].x, EPSILON) or + not Math::epsilonEq(expected[i].y, actual[i].y, EPSILON)) { + return false; + } + } + + return true; +} + +test$("sanity-test") { + Gc::Heap gc; + + auto dom = gc.alloc(Mime::Url()); + + usize const amountOfPages = 3; + + buildTestCase(gc, dom, amountOfPages, ""); + + Print::Settings settings = { + .paper = Print::A4, + .orientation = Print::Orientation::PORTRAIT, + }; + + Vec expectedPageSizes; + for (usize i = 0; i < amountOfPages; ++i) { + expectedPageSizes.pushBack({Print::A4.width, Print::A4.height}); + } + + auto actualPageSizes = printedPaperSizes(dom, settings); + + if (not sizesAreEqual(expectedPageSizes, actualPageSizes)) { + logError("expected: {}, actual: {}", expectedPageSizes, actualPageSizes); + expect$(false); + } + return Ok(); +} + +test$("page-as-landscape") { + Gc::Heap gc; + + auto dom = gc.alloc(Mime::Url()); + + usize const amountOfPages = 3; + + buildTestCase(gc, dom, amountOfPages, "@page { size: landscape; }"s); + + Print::Settings settings = { + .paper = Print::A4, + .orientation = Print::Orientation::PORTRAIT, + }; + + Vec expectedPageSizes; + for (usize i = 0; i < amountOfPages; ++i) { + expectedPageSizes.pushBack({Print::A4.height, Print::A4.width}); + } + + auto actualPageSizes = printedPaperSizes(dom, settings); + + if (not sizesAreEqual(expectedPageSizes, actualPageSizes)) { + logError("expected: {}, actual: {}", expectedPageSizes, actualPageSizes); + expect$(false); + } + + return Ok(); +} + +test$("page-as-a5") { + Gc::Heap gc; + + auto dom = gc.alloc(Mime::Url()); + + usize const amountOfPages = 3; + + buildTestCase(gc, dom, amountOfPages, "@page { size: A5; }"s); + + Print::Settings settings = { + .paper = Print::A4, + .orientation = Print::Orientation::PORTRAIT, + }; + + Vec expectedPageSizes; + for (usize i = 0; i < amountOfPages; ++i) { + expectedPageSizes.pushBack({Print::A5.width, Print::A5.height}); + } + + auto actualPageSizes = printedPaperSizes(dom, settings); + + if (not sizesAreEqual(expectedPageSizes, actualPageSizes)) { + logError("expected: {}, actual: {}", expectedPageSizes, actualPageSizes); + expect$(false); + } + + return Ok(); +} + +test$("page-left-right") { + Gc::Heap gc; + + auto dom = gc.alloc(Mime::Url()); + + usize const amountOfPages = 3; + + buildTestCase( + gc, dom, amountOfPages, + "@page:left { size: A3 portrait; }\n"s + "@page:right { size: A5 landscape; }"s + ); + + Print::Settings settings = { + .paper = Print::A4, + .orientation = Print::Orientation::PORTRAIT, + }; + + Vec expectedPageSizes; + for (usize i = 0; i < amountOfPages; ++i) { + if (i % 2) + expectedPageSizes.pushBack({Print::A3.width, Print::A3.height}); + else + expectedPageSizes.pushBack({Print::A5.height, Print::A5.width}); + } + + auto actualPageSizes = printedPaperSizes(dom, settings); + + if (not sizesAreEqual(expectedPageSizes, actualPageSizes)) { + logError("expected: {}, actual: {}", expectedPageSizes, actualPageSizes); + expect$(false); + } + + return Ok(); +} + +test$("page-media-query") { + Gc::Heap gc; + + auto dom = gc.alloc(Mime::Url()); + + usize const amountOfPages = 1; + + Karm::StringBuilder styleBuilder; + + // we use UA size and get bage pase sz of 900px + styleBuilder.append("@media (max-width: 800px) { @page { size: 900px; } }\n"s); + + // base page size is used to solve media queries, and sets new page sizes without changing the media + styleBuilder.append( + "@media (width: 900px) { @page { size: 1000px; } #maybe1 { break-after: page; } }\n"s + ); + + // this media query should not be used + styleBuilder.append( + "@media (width: 1000px) { @page { size: 1100px; } #maybe1 { break-after: page; } #maybe2 { break-after: page; } }\n"s + ); + + buildTestCase(gc, dom, amountOfPages, styleBuilder.take()); + + Print::Settings settings = { + .paper = Print::A4, + .orientation = Print::Orientation::PORTRAIT, + .scale = 1, + }; + + Vec expectedPageSizes; + for (usize i = 0; i < amountOfPages + 1; ++i) { + expectedPageSizes.pushBack({1000.0, 1000.0}); + } + + auto actualPageSizes = printedPaperSizes(dom, settings); + + if (not sizesAreEqual(expectedPageSizes, actualPageSizes)) { + logError("expected: {}, actual: {}", expectedPageSizes, actualPageSizes); + expect$(false); + } + + return Ok(); +} + +} // namespace Vaev::Driver::Tests diff --git a/src/vaev-engine/layout/values.cpp b/src/vaev-engine/layout/values.cpp index d1585cf1..813905a3 100644 --- a/src/vaev-engine/layout/values.cpp +++ b/src/vaev-engine/layout/values.cpp @@ -211,32 +211,12 @@ export struct Resolver { resolve(Length(value.val(), Length::SVH)) ); - // Absolute - // https://drafts.csswg.org/css-values/#absolute-lengths - case Length::CM: - return Au::fromFloatNearest(value.val() * 96 / 2.54); - - case Length::MM: - return Au::fromFloatNearest(value.val() * 96 / 25.4); - - case Length::Q: - return Au::fromFloatNearest(value.val() * 96 / 101.6); - - case Length::IN: - return Au::fromFloatNearest(value.val() * 96); - - case Length::PT: - return Au::fromFloatNearest(value.val() * 96 / 72.0); - - case Length::PC: - return Au::fromFloatNearest(value.val() * 96 / 6.0); - - case Length::PX: - return Au::fromFloatNearest(value.val()); - - default: + default: { + if (value.isAbsolute()) + return resolveAbsoluteLength(value); panic("invalid length unit"); } + } } Au resolve(LineWidth const& value) { diff --git a/src/vaev-engine/style/computer.cpp b/src/vaev-engine/style/computer.cpp index 3bb47200..84b2ff54 100644 --- a/src/vaev-engine/style/computer.cpp +++ b/src/vaev-engine/style/computer.cpp @@ -15,8 +15,35 @@ import :style.stylesheet; namespace Vaev::Style { +void _evalGuardlessPageRule(Rule const& rule, Media const& media, PageSpecifiedValues& c) { + rule.visit(Visitor{ + [&](PageRule const& r) { + if (r.selectors.len() == 0) + r.apply(c); + }, + [&](MediaRule const& r) { + if (r.match(media)) + for (auto const& subRule : r.rules) + _evalGuardlessPageRule(subRule, media, c); + }, + [&](auto const&) { + // Ignore other rule types + }, + }); +} + +export Rc computePageBaseSize(StyleSheetList const& styleBook, Media const& media) { + auto computed = makeRc(SpecifiedValues::initial()); + + for (auto const& sheet : styleBook.styleSheets) + for (auto const& rule : sheet.rules) + _evalGuardlessPageRule(rule, media, *computed); + + return computed; +} + export struct Computer { - Media _media; + Media const _media; StyleSheetList const& _styleBook; Font::Database& fontBook; StyleRuleLookup _styleRuleLookup{}; diff --git a/src/vaev-engine/style/media.cpp b/src/vaev-engine/style/media.cpp index cd601cb6..4e1eb50f 100644 --- a/src/vaev-engine/style/media.cpp +++ b/src/vaev-engine/style/media.cpp @@ -16,6 +16,7 @@ namespace Vaev::Style { // MARK: Media ----------------------------------------------------------------- +// FIXME: media queries not correctly comparing different length units export struct Media { /// 2.3. Media Types /// https://drafts.csswg.org/mediaqueries/#media-types @@ -37,6 +38,7 @@ export struct Media { /// 4.4. Orientation: the orientation feature /// https://drafts.csswg.org/mediaqueries/#orientation + // FIXME: this should not be a field, but a method dependent on width and height Print::Orientation orientation; // 5. MARK: Display Quality Media Features @@ -255,6 +257,26 @@ export struct Media { .deviceAspectRatio = settings.paper.width / settings.paper.height, }; } + + Media withPixelsDimensions(Vec2Au newSize) const { + newSize = newSize * Au{resolution.toDppx()}; + + Media newMedia = *this; + + newMedia.width = newSize.x; + newMedia.height = newSize.y; + newMedia.aspectRatio = (f64)newSize.x / (f64)newSize.y; + + newMedia.orientation = newMedia.height >= newMedia.width + ? Print::Orientation::PORTRAIT + : Print::Orientation::LANDSCAPE; + + newMedia.deviceWidth = newMedia.width; + newMedia.deviceHeight = newMedia.height; + newMedia.deviceAspectRatio = newMedia.aspectRatio; + + return newMedia; + } }; // MARK: Media Features -------------------------------------------------------- diff --git a/src/vaev-engine/style/props.cpp b/src/vaev-engine/style/props.cpp index 93554af6..8ee4bd75 100644 --- a/src/vaev-engine/style/props.cpp +++ b/src/vaev-engine/style/props.cpp @@ -2936,6 +2936,39 @@ export struct TextTransformProp { } }; +// https://www.w3.org/TR/css-page-3/#page-size-prop +export struct PageSizeProp { + PageSize value = initial(); + + static constexpr Str name() { return "size"; } + + static PageSize initial() { return Keywords::AUTO; } + + void apply(SpecifiedValues& c) const { + c.pageSize.cow() = value; + } + + Res<> parse(Cursor& c) { + if (c.ended()) + return Error::invalidData("unexpected end of input"); + + if (c.skip(Css::Token::ident("auto"))) { + value = Keywords::AUTO; + } else if (auto firstLength = parseValue(c)) { + auto secondLength = parseValue(c); + if (secondLength) { + value = Pair{firstLength.unwrap(), secondLength.unwrap()}; + } else { + value = Pair{firstLength.unwrap(), firstLength.unwrap()}; + } + } else { + value = try$(parseValue(c)); + } + + return Ok(); + } +}; + // MARK: Transform ------------------------------------------------------------- // https://drafts.csswg.org/css-transforms/#transform-property @@ -3606,6 +3639,8 @@ using _StyleProp = Union< MaxWidthProp, MaxHeightProp, + PageSizeProp, + // Text TextAlignProp, TextTransformProp, diff --git a/src/vaev-engine/style/specified.cpp b/src/vaev-engine/style/specified.cpp index 479d38bf..78bd40d7 100644 --- a/src/vaev-engine/style/specified.cpp +++ b/src/vaev-engine/style/specified.cpp @@ -91,6 +91,8 @@ export struct SpecifiedValues { Cow svg; + Cow pageSize = makeCow(PageSize{Keywords::AUTO}); + // ---------- Computed Style --------------------- Rc fontFace; diff --git a/src/vaev-engine/values/length.cpp b/src/vaev-engine/values/length.cpp index 0f1f7b05..03bfb8d3 100644 --- a/src/vaev-engine/values/length.cpp +++ b/src/vaev-engine/values/length.cpp @@ -174,4 +174,37 @@ struct ValueParser { } }; +Au resolveAbsoluteLength(Length const& value) { + if (not value.isAbsolute()) + panic("expected absolute length"); + + switch (value.unit()) { + // Absolute + // https://drafts.csswg.org/css-values/#absolute-lengths + case Length::CM: + return Au::fromFloatNearest(value.val() * 96 / 2.54); + + case Length::MM: + return Au::fromFloatNearest(value.val() * 96 / 25.4); + + case Length::Q: + return Au::fromFloatNearest(value.val() * 96 / 101.6); + + case Length::IN: + return Au::fromFloatNearest(value.val() * 96); + + case Length::PT: + return Au::fromFloatNearest(value.val() * 96 / 72.0); + + case Length::PC: + return Au::fromFloatNearest(value.val() * 96 / 6.0); + + case Length::PX: + return Au::fromFloatNearest(value.val()); + + default: + panic("invalid absolute length unit"); + } +} + } // namespace Vaev diff --git a/src/vaev-engine/values/page.cpp b/src/vaev-engine/values/page.cpp index 59665e6a..7d26d6b5 100644 --- a/src/vaev-engine/values/page.cpp +++ b/src/vaev-engine/values/page.cpp @@ -1,3 +1,7 @@ +module; + +#include + export module Vaev.Engine:values.page; import Karm.Core; @@ -5,6 +9,8 @@ import Karm.Print; import :css; import :values.base; +import :values.length; +import :values.keywords; using namespace Karm; @@ -28,4 +34,128 @@ struct ValueParser { } }; +// https://www.w3.org/TR/css-page-3/#valdef-page-size-landscape +// https://www.w3.org/TR/css-page-3/#valdef-page-size-portrait +Math::Vec2f resolve( + Print::Orientation orientation, + Math::Vec2f size +) { + if (orientation == Print::Orientation::LANDSCAPE) { + return { + max(size.x, size.y), + min(size.x, size.y) + }; + } else { + return { + min(size.x, size.y), + max(size.x, size.y) + }; + } +} + +// MARK: Size ------------------------------------------------------------- +// https://www.w3.org/TR/css-page-3/#page-size-prop + +export template <> +struct ValueParser { + static Res parse(Cursor& c) { + if (c.ended()) + return Error::invalidData("unexpected end of input"); + + if (c.skip(Css::Token::ident("A5"))) + return Ok(Print::A5); + else if (c.skip(Css::Token::ident("A4"))) + return Ok(Print::A4); + else if (c.skip(Css::Token::ident("A3"))) + return Ok(Print::A3); + else if (c.skip(Css::Token::ident("B5"))) + return Ok(Print::B5); + else if (c.skip(Css::Token::ident("B4"))) + return Ok(Print::B4); + else if (c.skip(Css::Token::ident("JIS-B5"))) + return Ok(Print::JIS_B5); + else if (c.skip(Css::Token::ident("JIS-B4"))) + return Ok(Print::JIS_B4); + else if (c.skip(Css::Token::ident("letter"))) + return Ok(Print::LETTER); + else if (c.skip(Css::Token::ident("legal"))) + return Ok(Print::LEGAL); + else if (c.skip(Css::Token::ident("ledger"))) + return Ok(Print::LEDGER); + else + return Error::invalidData("expected paper stock"); + } +}; + +export struct PageStockWithOrientation { + // https://www.w3.org/TR/css-page-3/#typedef-page-size-page-size + Opt stock; + + // https://www.w3.org/TR/css-page-3/#valdef-page-size-landscape + // https://www.w3.org/TR/css-page-3/#valdef-page-size-portrait + Opt orientation; + + void repr(Io::Emit& e) const { + e("({} {})", stock, orientation); + } +}; + +export template <> +struct ValueParser { + static Res parse(Cursor& c) { + if (c.ended()) + return Error::invalidData("unexpected end of input"); + + PageStockWithOrientation res; + + auto maybeStock = parseValue(c); + if (maybeStock) { + res.stock = maybeStock.take(); + } + + auto maybeOrientation = parseValue(c); + if (maybeOrientation) { + res.orientation = maybeOrientation.take(); + } + + maybeStock = parseValue(c); + if (maybeStock) { + res.stock = maybeStock.take(); + } + + return Ok(res); + } +}; + +export using PageSize = Union< + Pair, + Keywords::Auto, + PageStockWithOrientation>; + +Vec2Au resolve(PageSize const& pageSize, Vec2Au const& mediaSize) { + if (pageSize.is()) { + // https://www.w3.org/TR/css-page-3/#valdef-page-size-auto + // The page box will be set to a size and orientation chosen by the UA. + return mediaSize; + } else if (auto length = pageSize.is>()) { + // https://www.w3.org/TR/css-page-3/#valdef-page-size-length + return {resolveAbsoluteLength(length->v0), resolveAbsoluteLength(length->v1)}; + } else if (auto pageStockWithOrientation = pageSize.is()) { + if (pageStockWithOrientation->stock and pageStockWithOrientation->orientation) { + return resolve( + *pageStockWithOrientation->orientation, + pageStockWithOrientation->stock->size() + ) + .cast(); + } else if (pageStockWithOrientation->orientation) { + // If a is not specified, the size of the page sheet is chosen by the UA. + return resolve(*pageStockWithOrientation->orientation, mediaSize.cast()).cast(); + } else if (pageStockWithOrientation->stock) { + return pageStockWithOrientation->stock->size().cast(); + } + } + + unreachable(); +} + } // namespace Vaev