From c42c3f798fa7db6c1b0231308d881a21ab222e08 Mon Sep 17 00:00:00 2001 From: Paulo Medeiros Date: Wed, 16 Jul 2025 13:11:31 +0200 Subject: [PATCH 1/4] vaev-engine: Rollback `computeValues` separate struct, merging into `specifiedValues`. --- src/vaev-engine/dom/element.cpp | 12 ------- src/vaev-engine/layout/builder.cpp | 54 ++++++++++++++--------------- src/vaev-engine/style/computed.cpp | 18 ---------- src/vaev-engine/style/computer.cpp | 16 ++++----- src/vaev-engine/style/mod.cpp | 1 - src/vaev-engine/style/specified.cpp | 7 ++++ 6 files changed, 41 insertions(+), 67 deletions(-) delete mode 100644 src/vaev-engine/style/computed.cpp diff --git a/src/vaev-engine/dom/element.cpp b/src/vaev-engine/dom/element.cpp index 57fe1ba1..2e9cfdba 100644 --- a/src/vaev-engine/dom/element.cpp +++ b/src/vaev-engine/dom/element.cpp @@ -10,8 +10,6 @@ import :dom.token_list; using namespace Karm; namespace Vaev::Style { - -export struct ComputedValues; export struct SpecifiedValues; } // namespace Vaev::Style @@ -19,13 +17,8 @@ export struct SpecifiedValues; namespace Vaev::Dom { export struct PseudoElement { - Opt> _computedValues = NONE; Opt> _specifiedValues = NONE; - Rc computedValues() const { - return _computedValues.unwrap("unstyled pseudo-element"); - } - Rc specifiedValues() const { return _specifiedValues.unwrap("unstyled pseudo-element"); } @@ -38,7 +31,6 @@ export struct Element : Node { QualifiedName qualifiedName; // NOSPEC: Should be a NamedNodeMap Map> attributes; - Opt> _computedValues; Opt> _specifiedValues; // FIXME: We should not have this store here TokenList classList; @@ -58,10 +50,6 @@ export struct Element : Node { return TYPE; } - Rc computedValues() const { - return _computedValues.unwrap("unstyled element"); - } - Rc specifiedValues() const { return _specifiedValues.unwrap("unstyled element"); } diff --git a/src/vaev-engine/layout/builder.cpp b/src/vaev-engine/layout/builder.cpp index 0cde773a..f30bdce8 100644 --- a/src/vaev-engine/layout/builder.cpp +++ b/src/vaev-engine/layout/builder.cpp @@ -1,7 +1,7 @@ module; -#include #include +#include #include export module Vaev.Engine:layout.builder; @@ -372,7 +372,7 @@ static void _buildImage(BuilderContext bc, Gc::Ref el) { } static void _buildInputProse(BuilderContext bc, Gc::Ref el) { - auto font = el->computedValues()->fontFace; + auto font = el->specifiedValues()->fontFace; Resolver resolver{ .rootFont = Gfx::Font{font, 16}, .boxFont = Gfx::Font{font, 16}, @@ -406,11 +406,11 @@ void buildSVGElement(Gc::Ref el, SVG::Group* group) { buildSVGAggregate(el, &newSvgRoot); group->add(std::move(newSvgRoot)); } else if (el->qualifiedName == Svg::FOREIGN_OBJECT_TAG) { - Box box{el->specifiedValues(), el->computedValues()->fontFace, el}; + Box box{el->specifiedValues(), el->specifiedValues()->fontFace, el}; InlineBox rootInlineBox{_proseStyleFomStyle( *el->specifiedValues(), - el->computedValues()->fontFace + el->specifiedValues()->fontFace )}; BuilderContext bc{ @@ -480,7 +480,7 @@ static void createAndBuildInlineFlowfromElement(BuilderContext bc, RccomputedValues()->fontFace); + auto proseStyle = _proseStyleFomStyle(*style, el->specifiedValues()->fontFace); bc.startInlineBox(proseStyle); _buildChildren(bc.toInlineContext(style), el); @@ -501,8 +501,8 @@ static void buildBlockFlowFromElement(BuilderContext bc, Gc::Ref e } static Box createAndBuildBoxFromElement(BuilderContext bc, Rc style, Gc::Ref el, Display display) { - Box box = {style, el->computedValues()->fontFace, el}; - InlineBox rootInlineBox{_proseStyleFomStyle(*style, el->computedValues()->fontFace)}; + Box box = {style, el->specifiedValues()->fontFace, el}; + InlineBox rootInlineBox{_proseStyleFomStyle(*style, el->specifiedValues()->fontFace)}; auto newBc = display == Display::Inside::FLEX ? bc.toFlexContext(box, rootInlineBox) @@ -526,7 +526,7 @@ struct AnonymousTableBoxWrapper { AnonymousTableBoxWrapper(BuilderContext& bc) : bc(bc) {} - void createRowIfNone(Rc style, Rc fontFace) { + void createRowIfNone(Rc style) { if (rowBox) return; @@ -534,10 +534,10 @@ struct AnonymousTableBoxWrapper { rowStyle->inherit(*style); rowStyle->display = Display::Internal::TABLE_ROW; - rowBox = Box{rowStyle, fontFace, nullptr}; + rowBox = Box{rowStyle, style->fontFace, nullptr}; } - void createCellIfNone(Rc style, Rc fontFace) { + void createCellIfNone(Rc style) { if (cellBox) return; @@ -545,8 +545,8 @@ struct AnonymousTableBoxWrapper { cellStyle->inherit(*style); cellStyle->display = Display::Internal::TABLE_CELL; - cellBox = Box{cellStyle, fontFace, nullptr}; - rootInlineBoxForCell = InlineBox{_proseStyleFomStyle(*style, fontFace)}; + cellBox = Box{cellStyle, style->fontFace, nullptr}; + rootInlineBoxForCell = InlineBox{_proseStyleFomStyle(*style, style->fontFace)}; } void finalizeAndResetCell() { @@ -615,17 +615,17 @@ static void _buildTableChildrenWhileWrappingIntoAnonymousBox(BuilderContext bc, // Unexpected display type for children, it will be wrapped by anonymous box // First dispatching based on the parent's display type if (bc.parentStyle->display == Display::Internal::TABLE_ROW) { - anonTableWrapper.createCellIfNone(style, parentEl->computedValues()->fontFace); + anonTableWrapper.createCellIfNone(style); _buildNodeWrappedByCell(anonTableWrapper.cellBuilderContext(), *childEL); } else { if (display == Display::Internal::TABLE_CELL) { anonTableWrapper.finalizeAndResetCell(); - anonTableWrapper.createRowIfNone(style, parentEl->computedValues()->fontFace); + anonTableWrapper.createRowIfNone(style); _buildCell(anonTableWrapper.rowBuilderContext(), *childEL); } else { - anonTableWrapper.createRowIfNone(style, parentEl->computedValues()->fontFace); - anonTableWrapper.createCellIfNone(style, parentEl->computedValues()->fontFace); + anonTableWrapper.createRowIfNone(style); + anonTableWrapper.createCellIfNone(style); _buildNodeWrappedByCell(anonTableWrapper.cellBuilderContext(), *childEL); } @@ -633,8 +633,8 @@ static void _buildTableChildrenWhileWrappingIntoAnonymousBox(BuilderContext bc, } } else if (auto text = child->is()) { if (bc.parentStyle->display != Display::Internal::TABLE_ROW) - anonTableWrapper.createRowIfNone(style, parentEl->computedValues()->fontFace); - anonTableWrapper.createCellIfNone(style, parentEl->computedValues()->fontFace); + anonTableWrapper.createRowIfNone(style); + anonTableWrapper.createCellIfNone(style); _buildText(anonTableWrapper.cellBuilderContext(), *text, style); } } @@ -645,7 +645,7 @@ static void _buildTableChildrenWhileWrappingIntoAnonymousBox(BuilderContext bc, // https://www.w3.org/TR/css-tables-3/#fixup-algorithm static void _buildTableInternal(BuilderContext bc, Gc::Ref el, Rc style, Display display) { - Box tableInternalBox = {style, el->computedValues()->fontFace, el}; + Box tableInternalBox = {style, el->specifiedValues()->fontFace, el}; tableInternalBox.attrs = _parseDomAttr(el); switch (display.internal()) { @@ -709,7 +709,7 @@ static void _buildTableBox(BuilderContext tableWrapperBc, Gc::Ref }; tableBoxStyle->display = Display::Internal::TABLE_BOX; - Box tableBox = {tableBoxStyle, el->computedValues()->fontFace, el}; + Box tableBox = {tableBoxStyle, el->specifiedValues()->fontFace, el}; tableBox.attrs = _parseDomAttr(el); bool captionsOnTop = tableBoxStyle->table->captionSide == CaptionSide::TOP; @@ -734,8 +734,8 @@ static Box _createTableWrapperAndBuildTable(BuilderContext bc, Rcdisplay = tableStyle->display; wrapperStyle->margin = tableStyle->margin; - Box wrapper = {wrapperStyle, tableBoxEl->computedValues()->fontFace, tableBoxEl}; - InlineBox rootInlineBox{_proseStyleFomStyle(*wrapperStyle, tableBoxEl->computedValues()->fontFace)}; + Box wrapper = {wrapperStyle, tableBoxEl->specifiedValues()->fontFace, tableBoxEl}; + InlineBox rootInlineBox{_proseStyleFomStyle(*wrapperStyle, tableBoxEl->specifiedValues()->fontFace)}; // SPEC: The table wrapper box establishes a block formatting context. _buildTableBox(bc.toBlockContextWithoutRootInline(wrapper), tableBoxEl, tableStyle); @@ -845,8 +845,8 @@ export Box build(Gc::Ref doc) { }; if (auto el = doc->documentElement()) { - root = {el->specifiedValues(), el->computedValues()->fontFace, el}; - InlineBox rootInlineBox{_proseStyleFomStyle(*el->specifiedValues(), el->computedValues()->fontFace)}; + root = {el->specifiedValues(), el->specifiedValues()->fontFace, el}; + InlineBox rootInlineBox{_proseStyleFomStyle(*el->specifiedValues(), el->specifiedValues()->fontFace)}; BuilderContext bc{ BuilderContext::From::BLOCK, @@ -864,15 +864,15 @@ export Box build(Gc::Ref doc) { } export Box buildForPseudoElement(Dom::PseudoElement& el) { - auto proseStyle = _proseStyleFomStyle(*el.specifiedValues(), el.computedValues()->fontFace); + auto proseStyle = _proseStyleFomStyle(*el.specifiedValues(), el.specifiedValues()->fontFace); auto prose = makeRc(proseStyle); if (el.specifiedValues()->content) { prose->append(el.specifiedValues()->content.str()); - return {el.specifiedValues(), el.computedValues()->fontFace, InlineBox{prose}, nullptr}; + return {el.specifiedValues(), el.specifiedValues()->fontFace, InlineBox{prose}, nullptr}; } - return {el.specifiedValues(), el.computedValues()->fontFace, nullptr}; + return {el.specifiedValues(), el.specifiedValues()->fontFace, nullptr}; } } // namespace Vaev::Layout diff --git a/src/vaev-engine/style/computed.cpp b/src/vaev-engine/style/computed.cpp deleted file mode 100644 index 99db48bc..00000000 --- a/src/vaev-engine/style/computed.cpp +++ /dev/null @@ -1,18 +0,0 @@ -module; - -#include - -export module Vaev.Engine:style.computed; - -import Karm.Core; - -using namespace Karm; - -namespace Vaev::Style { - -// https://www.w3.org/TR/css-cascade/#computed -export struct ComputedValues { - Rc fontFace; -}; - -} // namespace Vaev::Style diff --git a/src/vaev-engine/style/computer.cpp b/src/vaev-engine/style/computer.cpp index 23ce8549..3bb47200 100644 --- a/src/vaev-engine/style/computer.cpp +++ b/src/vaev-engine/style/computer.cpp @@ -10,7 +10,6 @@ export module Vaev.Engine:style.computer; import Karm.Gc; import Karm.Font; import :dom; -import :style.computed; import :style.specified; import :style.stylesheet; @@ -191,7 +190,7 @@ export struct Computer { for (auto& area : computed->_areas) { auto font = _lookupFontface(fontBook, *area.specifiedValues()); - area._computedValues = makeRc(font); + area.specifiedValues()->fontFace = font; } return computed; @@ -199,7 +198,7 @@ export struct Computer { // MARK: Styling ----------------------------------------------------------- - void styleElement(SpecifiedValues const& parentSpecifiedValues, ComputedValues const& parentComputedValues, Dom::Element& el) { + void styleElement(SpecifiedValues const& parentSpecifiedValues, Dom::Element& el) { auto specifiedValues = computeFor(parentSpecifiedValues, el); el._specifiedValues = specifiedValues; @@ -210,23 +209,22 @@ export struct Computer { (parentSpecifiedValues.font->families != specifiedValues->font->families or parentSpecifiedValues.font->weight != specifiedValues->font->weight)) { auto font = _lookupFontface(fontBook, *specifiedValues); - el._computedValues = makeRc(font); + specifiedValues->fontFace = font; } else { - el._computedValues = makeRc(parentComputedValues.fontFace); + specifiedValues->fontFace = parentSpecifiedValues.fontFace; } for (auto child = el.firstChild(); child; child = child->nextSibling()) { if (auto childEl = child->is()) - styleElement(*specifiedValues, *el.computedValues(), *childEl); + styleElement(*specifiedValues, *childEl); } } void styleDocument(Dom::Document& doc) { if (auto el = doc.documentElement()) { auto rootSpecifiedValues = makeRc(SpecifiedValues::initial()); - auto font = _lookupFontface(fontBook, *rootSpecifiedValues); - auto rootComputedValues = makeRc(font); - styleElement(*rootSpecifiedValues, *rootComputedValues, *el); + rootSpecifiedValues->fontFace = _lookupFontface(fontBook, *rootSpecifiedValues); + styleElement(*rootSpecifiedValues, *el); } } diff --git a/src/vaev-engine/style/mod.cpp b/src/vaev-engine/style/mod.cpp index 13a345a7..5bde44cd 100644 --- a/src/vaev-engine/style/mod.cpp +++ b/src/vaev-engine/style/mod.cpp @@ -1,7 +1,6 @@ export module Vaev.Engine:style; export import :style.computer; -export import :style.computed; export import :style.specified; export import :style.decls; export import :style.fonts; diff --git a/src/vaev-engine/style/specified.cpp b/src/vaev-engine/style/specified.cpp index 79ee141b..479d38bf 100644 --- a/src/vaev-engine/style/specified.cpp +++ b/src/vaev-engine/style/specified.cpp @@ -2,6 +2,7 @@ module; #include #include +#include export module Vaev.Engine:style.specified; @@ -30,6 +31,8 @@ struct TransformProps { export struct SpecifiedValues { static SpecifiedValues const& initial(); + SpecifiedValues() : fontFace(Gfx::Fontface::fallback()) {} + Gfx::Color color; Number opacity; String content = ""s; @@ -88,6 +91,10 @@ export struct SpecifiedValues { Cow svg; + // ---------- Computed Style --------------------- + + Rc fontFace; + void inherit(SpecifiedValues const& parent) { color = parent.color; font = parent.font; From ebb6f9165b44802c8bb0fe10c5330f40dba0fd38 Mon Sep 17 00:00:00 2001 From: Paulo Medeiros Date: Wed, 16 Jul 2025 13:23:04 +0200 Subject: [PATCH 2/4] vaev-engine: Remove duplicated fontface ptr from Box. --- src/vaev-engine/layout/base.cpp | 9 ++++----- src/vaev-engine/layout/builder.cpp | 23 +++++++++++------------ src/vaev-engine/layout/values.cpp | 4 ++-- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/vaev-engine/layout/base.cpp b/src/vaev-engine/layout/base.cpp index eca29282..a5b612de 100644 --- a/src/vaev-engine/layout/base.cpp +++ b/src/vaev-engine/layout/base.cpp @@ -355,17 +355,16 @@ export struct Attrs { struct Box : Meta::NoCopy { Rc style; - Rc fontFace; Content content = NONE; Attrs attrs; Opt> formatingContext = NONE; Gc::Ptr origin; - Box(Rc style, Rc font, Gc::Ptr og) - : style{std::move(style)}, fontFace{font}, origin{og} {} + Box(Rc style, Gc::Ptr og) + : style{std::move(style)}, origin{og} {} - Box(Rc style, Rc font, Content content, Gc::Ptr og) - : style{std::move(style)}, fontFace{font}, content{std::move(content)}, origin{og} {} + Box(Rc style, Content content, Gc::Ptr og) + : style{std::move(style)}, content{std::move(content)}, origin{og} {} Slice children() const { if (auto children = content.is>()) diff --git a/src/vaev-engine/layout/builder.cpp b/src/vaev-engine/layout/builder.cpp index f30bdce8..e852ed11 100644 --- a/src/vaev-engine/layout/builder.cpp +++ b/src/vaev-engine/layout/builder.cpp @@ -219,7 +219,7 @@ struct BuilderContext { style->display = Display{Display::Inside::FLOW, Display::Outside::BLOCK}; auto newInlineBox = InlineBox::fromInterruptedInlineBox(*_rootInlineBox); - _parent.add({style, _parent.fontFace, std::move(*_rootInlineBox), nullptr}); + _parent.add({style, std::move(*_rootInlineBox), nullptr}); *_rootInlineBox = std::move(newInlineBox); } @@ -406,7 +406,7 @@ void buildSVGElement(Gc::Ref el, SVG::Group* group) { buildSVGAggregate(el, &newSvgRoot); group->add(std::move(newSvgRoot)); } else if (el->qualifiedName == Svg::FOREIGN_OBJECT_TAG) { - Box box{el->specifiedValues(), el->specifiedValues()->fontFace, el}; + Box box{el->specifiedValues(), el}; InlineBox rootInlineBox{_proseStyleFomStyle( *el->specifiedValues(), @@ -501,7 +501,7 @@ static void buildBlockFlowFromElement(BuilderContext bc, Gc::Ref e } static Box createAndBuildBoxFromElement(BuilderContext bc, Rc style, Gc::Ref el, Display display) { - Box box = {style, el->specifiedValues()->fontFace, el}; + Box box = {style, el}; InlineBox rootInlineBox{_proseStyleFomStyle(*style, el->specifiedValues()->fontFace)}; auto newBc = display == Display::Inside::FLEX @@ -534,7 +534,7 @@ struct AnonymousTableBoxWrapper { rowStyle->inherit(*style); rowStyle->display = Display::Internal::TABLE_ROW; - rowBox = Box{rowStyle, style->fontFace, nullptr}; + rowBox = Box{rowStyle, nullptr}; } void createCellIfNone(Rc style) { @@ -545,7 +545,7 @@ struct AnonymousTableBoxWrapper { cellStyle->inherit(*style); cellStyle->display = Display::Internal::TABLE_CELL; - cellBox = Box{cellStyle, style->fontFace, nullptr}; + cellBox = Box{cellStyle, nullptr}; rootInlineBoxForCell = InlineBox{_proseStyleFomStyle(*style, style->fontFace)}; } @@ -645,7 +645,7 @@ static void _buildTableChildrenWhileWrappingIntoAnonymousBox(BuilderContext bc, // https://www.w3.org/TR/css-tables-3/#fixup-algorithm static void _buildTableInternal(BuilderContext bc, Gc::Ref el, Rc style, Display display) { - Box tableInternalBox = {style, el->specifiedValues()->fontFace, el}; + Box tableInternalBox = {style, el}; tableInternalBox.attrs = _parseDomAttr(el); switch (display.internal()) { @@ -709,7 +709,7 @@ static void _buildTableBox(BuilderContext tableWrapperBc, Gc::Ref }; tableBoxStyle->display = Display::Internal::TABLE_BOX; - Box tableBox = {tableBoxStyle, el->specifiedValues()->fontFace, el}; + Box tableBox = {tableBoxStyle, el}; tableBox.attrs = _parseDomAttr(el); bool captionsOnTop = tableBoxStyle->table->captionSide == CaptionSide::TOP; @@ -734,7 +734,7 @@ static Box _createTableWrapperAndBuildTable(BuilderContext bc, Rcdisplay = tableStyle->display; wrapperStyle->margin = tableStyle->margin; - Box wrapper = {wrapperStyle, tableBoxEl->specifiedValues()->fontFace, tableBoxEl}; + Box wrapper = {wrapperStyle, tableBoxEl}; InlineBox rootInlineBox{_proseStyleFomStyle(*wrapperStyle, tableBoxEl->specifiedValues()->fontFace)}; // SPEC: The table wrapper box establishes a block formatting context. @@ -840,12 +840,11 @@ export Box build(Gc::Ref doc) { // NOTE: Fallback in case of an empty document Box root{ makeRc(Style::SpecifiedValues::initial()), - Gfx::Fontface::fallback(), nullptr }; if (auto el = doc->documentElement()) { - root = {el->specifiedValues(), el->specifiedValues()->fontFace, el}; + root = {el->specifiedValues(), el}; InlineBox rootInlineBox{_proseStyleFomStyle(*el->specifiedValues(), el->specifiedValues()->fontFace)}; BuilderContext bc{ @@ -869,10 +868,10 @@ export Box buildForPseudoElement(Dom::PseudoElement& el) { auto prose = makeRc(proseStyle); if (el.specifiedValues()->content) { prose->append(el.specifiedValues()->content.str()); - return {el.specifiedValues(), el.specifiedValues()->fontFace, InlineBox{prose}, nullptr}; + return {el.specifiedValues(), InlineBox{prose}, nullptr}; } - return {el.specifiedValues(), el.specifiedValues()->fontFace, nullptr}; + return {el.specifiedValues(), nullptr}; } } // namespace Vaev::Layout diff --git a/src/vaev-engine/layout/values.cpp b/src/vaev-engine/layout/values.cpp index 46dc8451..d1585cf1 100644 --- a/src/vaev-engine/layout/values.cpp +++ b/src/vaev-engine/layout/values.cpp @@ -26,8 +26,8 @@ export struct Resolver { Au fontSize{16}; Resolver resolver; - resolver.rootFont = Gfx::Font{tree.root.fontFace, fontSize.cast()}; - resolver.boxFont = Gfx::Font{box.fontFace, fontSize.cast()}; + resolver.rootFont = Gfx::Font{tree.root.style->fontFace, fontSize.cast()}; + resolver.boxFont = Gfx::Font{box.style->fontFace, fontSize.cast()}; resolver.viewport = tree.viewport; resolver.boxAxis = mainAxis(box); return resolver; From 2183efd82e16b4be8a941e7059818e435f055f21 Mon Sep 17 00:00:00 2001 From: Paulo Medeiros Date: Fri, 18 Jul 2025 07:34:19 +0200 Subject: [PATCH 3/4] vaev-engine: Implement page size css property. --- src/main.cpp | 4 +- src/vaev-engine/driver/print.cpp | 75 ++++--- src/vaev-engine/driver/tests/manifest.json | 15 ++ .../tests/test-print-paper-dimensions.cpp | 192 ++++++++++++++++++ src/vaev-engine/layout/values.cpp | 28 +-- src/vaev-engine/style/props.cpp | 35 ++++ src/vaev-engine/style/specified.cpp | 2 + src/vaev-engine/values/length.cpp | 33 +++ src/vaev-engine/values/page.cpp | 130 ++++++++++++ 9 files changed, 458 insertions(+), 56 deletions(-) create mode 100644 src/vaev-engine/driver/tests/manifest.json create mode 100644 src/vaev-engine/driver/tests/test-print-paper-dimensions.cpp 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..d7259811 100644 --- a/src/vaev-engine/driver/print.cpp +++ b/src/vaev-engine/driver/print.cpp @@ -88,6 +88,36 @@ 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) + }; +} + export Generator print(Gc::Ref dom, Print::Settings const& settings) { auto media = Style::Media::forPrint(settings); @@ -130,36 +160,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 = {}; - } + auto [pageRect, pageContent] = _computePageBounds(settings, media, *pageStyle); - RectAu pageContent = pageRect.shrink(pageMargin); - - 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 +175,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 +203,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..05fed83a --- /dev/null +++ b/src/vaev-engine/driver/tests/test-print-paper-dimensions.cpp @@ -0,0 +1,192 @@ +#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); + 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(); +} + +} // 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/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 From 67c870d279d703750612827308d10760e42eedfd Mon Sep 17 00:00:00 2001 From: Paulo Medeiros Date: Tue, 22 Jul 2025 15:47:43 +0200 Subject: [PATCH 4/4] vaev-engine: Implement page base size computation. --- src/vaev-engine/driver/print.cpp | 30 +++++++++++- .../tests/test-print-paper-dimensions.cpp | 47 +++++++++++++++++++ src/vaev-engine/style/computer.cpp | 29 +++++++++++- src/vaev-engine/style/media.cpp | 22 +++++++++ 4 files changed, 126 insertions(+), 2 deletions(-) diff --git a/src/vaev-engine/driver/print.cpp b/src/vaev-engine/driver/print.cpp index d7259811..2ead5683 100644 --- a/src/vaev-engine/driver/print.cpp +++ b/src/vaev-engine/driver/print.cpp @@ -118,8 +118,35 @@ Pair _computePageBounds(Print::Settings const& settings, Style::Media co }; } +// 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()) @@ -128,6 +155,7 @@ export Generator print(Gc::Ref dom, Print::Settings Style::Computer computer{ media, *dom->styleSheets, db }; + computer.build(); computer.styleDocument(*dom); diff --git a/src/vaev-engine/driver/tests/test-print-paper-dimensions.cpp b/src/vaev-engine/driver/tests/test-print-paper-dimensions.cpp index 05fed83a..c6b702d3 100644 --- a/src/vaev-engine/driver/tests/test-print-paper-dimensions.cpp +++ b/src/vaev-engine/driver/tests/test-print-paper-dimensions.cpp @@ -26,6 +26,8 @@ void buildTestCase(Gc::Heap& gc, Gc::Ref dom, usize amountOfPages 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); } @@ -189,4 +191,49 @@ test$("page-left-right") { 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/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 --------------------------------------------------------