From d0a6ca4b67de51ac6eb3c6e0049dc5acc4fd3f3e Mon Sep 17 00:00:00 2001 From: VAN BOSSUYT Nicolas Date: Wed, 27 Nov 2024 21:43:15 +0100 Subject: [PATCH] meta: Update from upstream. --- src/libs/karm-math/radii.h | 32 +- src/web/vaev-base/background.h | 19 +- src/web/vaev-base/borders.h | 5 +- src/web/vaev-base/calc.h | 4 +- src/web/vaev-base/color.h | 89 +++ src/web/vaev-base/flex.h | 9 + src/web/vaev-base/image.h | 143 ++++ src/web/vaev-base/insets.h | 2 +- src/web/vaev-base/text.h | 21 +- src/web/vaev-driver/fetcher.cpp | 4 +- src/web/vaev-driver/fetcher.h | 2 +- src/web/vaev-driver/render.cpp | 6 +- src/web/vaev-driver/res/user-agent.css | 9 + src/web/vaev-layout/box.h | 2 +- src/web/vaev-layout/builder.cpp | 169 +++-- src/web/vaev-layout/flex.cpp | 135 ++-- src/web/vaev-layout/layout.cpp | 2 +- src/web/vaev-layout/paint.cpp | 24 +- src/web/vaev-layout/table.cpp | 366 +++++---- src/web/vaev-layout/values.h | 17 +- src/web/vaev-style/computed.h | 4 +- src/web/vaev-style/computer.cpp | 28 +- src/web/vaev-style/decls.h | 39 +- src/web/vaev-style/origin.h | 23 + src/web/vaev-style/rules.cpp | 11 +- src/web/vaev-style/rules.h | 8 +- src/web/vaev-style/select.cpp | 53 +- src/web/vaev-style/styles.cpp | 50 +- src/web/vaev-style/styles.h | 713 +++++++++++++++--- src/web/vaev-style/stylesheet.cpp | 5 +- src/web/vaev-style/stylesheet.h | 11 +- .../vaev-style/tests/test-parse-selectors.cpp | 57 ++ src/web/vaev-style/tests/test-parse-value.cpp | 3 +- src/web/vaev-tools/main.cpp | 2 +- src/web/vaev-tools/manifest.json | 3 +- src/web/vaev-view/view.cpp | 2 - 36 files changed, 1539 insertions(+), 533 deletions(-) create mode 100644 src/web/vaev-base/image.h create mode 100644 src/web/vaev-style/origin.h diff --git a/src/libs/karm-math/radii.h b/src/libs/karm-math/radii.h index ea158ae66d..0fb928aeac 100644 --- a/src/libs/karm-math/radii.h +++ b/src/libs/karm-math/radii.h @@ -54,7 +54,29 @@ struct Radii { constexpr Radii(T a, T b, T c, T d, T e, T f, T g, T h) : a(a), b(b), c(c), d(d), e(e), f(f), g(g), h(h) {} - bool zero() const { + constexpr Radii(Radii const &other) + : radii(other.radii) {} + + constexpr Radii(Radii const &&other) + : radii(std::move(other.radii)) {} + + constexpr Radii &operator=(Radii const &other) { + radii = other.radii; + return *this; + } + + constexpr Radii &operator=(Radii &&other) { + radii = std::move(other.radii); + return *this; + } + + constexpr ~Radii() { + radii.~Array(); + } + + bool zero() const + requires(Meta::Equatable) + { return iter(radii).all([](T radii) { return radii == T{}; }); @@ -143,9 +165,11 @@ struct Radii { } void repr(Io::Emit &_e) const { - if (zero()) { - _e("(radii {})", a); - return; + if constexpr (Meta::Equatable) { + if (zero()) { + _e("(radii {})", a); + return; + } } _e("(radii {} {} {} {} {} {} {} {})", a, b, c, d, e, f, g, h); diff --git a/src/web/vaev-base/background.h b/src/web/vaev-base/background.h index 6b943df294..e0903a0bde 100644 --- a/src/web/vaev-base/background.h +++ b/src/web/vaev-base/background.h @@ -4,6 +4,7 @@ #include #include "color.h" +#include "image.h" #include "length.h" #include "percent.h" @@ -122,15 +123,15 @@ struct BackgroundRepeat { } }; -struct BackgroundProps { - Color fill; +struct BackgroundLayer { + Opt image; BackgroundAttachment attachment; BackgroundPosition position; BackgroundRepeat repeat; void repr(Io::Emit &e) const { e("(background"); - e(" fill={}", fill); + e(" image={}", image); e(" attachment={}", attachment); e(" position={}", position); e(" repeat={}", repeat); @@ -138,4 +139,16 @@ struct BackgroundProps { } }; +struct BackgroundProps { + Color color; + Vec layers = {}; + + void repr(Io::Emit &e) const { + e("(background"); + e(" color={}", color); + e(" layers={}", layers); + e(")"); + } +}; + } // namespace Vaev diff --git a/src/web/vaev-base/borders.h b/src/web/vaev-base/borders.h index ec461603ba..4e2aea40a9 100644 --- a/src/web/vaev-base/borders.h +++ b/src/web/vaev-base/borders.h @@ -4,6 +4,7 @@ #include #include +#include "calc.h" #include "color.h" #include "length.h" #include "percent.h" @@ -34,7 +35,7 @@ enum struct BorderStyle { }; struct Border { - Length width; + CalcValue width; Gfx::BorderStyle style; Color color = Color::CURRENT; @@ -49,7 +50,7 @@ struct BorderProps { static constexpr Length THICK = 5_px; Border top, start, bottom, end; - Math::Radii> radii; + Math::Radii>> radii; void all(Border b) { top = start = bottom = end = b; diff --git a/src/web/vaev-base/calc.h b/src/web/vaev-base/calc.h index 6d3fb4a230..3bf0aa6291 100644 --- a/src/web/vaev-base/calc.h +++ b/src/web/vaev-base/calc.h @@ -40,8 +40,8 @@ struct CalcValue { : CalcValue(T{}) { } - constexpr CalcValue(T value) - : type(OpType::FIXED), lhs(value) { + constexpr CalcValue(Meta::Convertible auto value) + : type(OpType::FIXED), lhs(T{value}) { } constexpr CalcValue(Value value) diff --git a/src/web/vaev-base/color.h b/src/web/vaev-base/color.h index 170307ad31..b3ea855421 100644 --- a/src/web/vaev-base/color.h +++ b/src/web/vaev-base/color.h @@ -73,4 +73,93 @@ Opt parseSystemColor(Str name); Gfx::Color resolve(Color c, Gfx::Color currentColor); +// MARK: Color Interpolation --------------------------------------------------- + +// https://www.w3.org/TR/css-color-4/#typedef-color-space +struct ColorSpace { + enum struct _Type { + POLAR, + RECTANGULAR, + + _LEN0, + }; + + using enum _Type; + + // https://www.w3.org/TR/css-color-4/#typedef-rectangular-color-space + enum struct _Rectangular { + SRGB, + SRGB_LINEAR, + DISPLAY_P3, + A98_RGB, + PROPHOTO_RGB, + REC2020, + LAB, + OKLAB, + XYZ, + XYZ_D50, + XYZ_D65, + + _LEN1, + + }; + + using enum _Rectangular; + + // https://www.w3.org/TR/css-color-4/#typedef-hue-interpolation-method + enum struct _Interpolation { + SHORTER, + LONGER, + INCREASING, + DECREASING, + + _LEN2, + }; + + using enum _Interpolation; + + // https://www.w3.org/TR/css-color-4/#typedef-polar-color-space + enum struct _Polar { + HSL, + HWB, + LCH, + OKLCH, + + _LEN3, + }; + + using enum _Polar; + + _Type type; + + union { + _Rectangular rectangular; + + struct { + _Polar polar; + _Interpolation interpolation; + }; + }; + + constexpr ColorSpace(_Rectangular rectangular) + : type(_Type::RECTANGULAR), rectangular(rectangular) { + } + + constexpr ColorSpace(_Polar polar, _Interpolation interpolation) + : type(_Type::POLAR), polar(polar), interpolation(interpolation) { + } + + constexpr ColorSpace() + : type(_Type::RECTANGULAR), rectangular(_Rectangular::SRGB) { + } + + void repr(Io::Emit &e) const { + if (type == ColorSpace::RECTANGULAR) { + e("{}", rectangular); + } else { + e("{} {}", polar, interpolation); + } + } +}; + } // namespace Vaev diff --git a/src/web/vaev-base/flex.h b/src/web/vaev-base/flex.h index 8e3cd5462c..eae9deef0b 100644 --- a/src/web/vaev-base/flex.h +++ b/src/web/vaev-base/flex.h @@ -49,6 +49,15 @@ struct FlexBasis { } }; +struct FlexItemProps { + FlexBasis flexBasis; + Number flexGrow, flexShrink; + + void repr(Io::Emit &e) const { + e("({} {} {})", flexBasis, flexGrow, flexShrink); + } +}; + struct FlexProps { // FlexContainer FlexDirection direction = FlexDirection::ROW; diff --git a/src/web/vaev-base/image.h b/src/web/vaev-base/image.h new file mode 100644 index 0000000000..a8a8c3359b --- /dev/null +++ b/src/web/vaev-base/image.h @@ -0,0 +1,143 @@ +#pragma once + +#include + +#include "angle.h" +#include "color.h" +#include "percent.h" + +// https://www.w3.org/TR/css-images-4/ + +namespace Vaev { + +struct Image; + +// MARK: Linear Gradient ------------------------------------------------------- +// https://www.w3.org/TR/css-images-4/#linear-gradients + +struct LinearGradient { + struct Stop { + Gfx::Color color; + Percent position; + + void repr(Io::Emit &e) const { + e("({} {})", color, position); + } + }; + + Angle angle; + ColorSpace colorSpace = ColorSpace::SRGB; + Vec stops; + + void repr(Io::Emit &e) const { + e("(linear-gradient {} {} {}", angle, colorSpace, stops); + } +}; + +// MARK: Radial Gradient ------------------------------------------------------- +// https://www.w3.org/TR/css-images-4/#radial-gradients + +struct RadialGradient { + enum struct Shape { + CIRCLE, + ELLIPSE, + + _LEN, + }; + + Math::Vec2f size; + Math::Vec2f position; + ColorSpace colorSpace = ColorSpace::SRGB; + Vec stops; + + void repr(Io::Emit &e) const { + e("(radial-gradient {} {} {} {}", size, position, colorSpace, stops); + } +}; + +// MARK: Conic Gradient -------------------------------------------------------- +// https://www.w3.org/TR/css-images-4/#conic-gradients + +struct ConicGradient { + struct Stop { + Gfx::Color color; + Angle angle; + + void repr(Io::Emit &e) const { + e("({} {})", color, angle); + } + }; + + Angle angle; + Math::Vec2f position; + ColorSpace colorSpace = ColorSpace::SRGB; + Vec stops; + + void repr(Io::Emit &e) const { + e("(conic-gradient {} {} {} {}", angle, position, colorSpace, stops); + } +}; + +// MARK: Cross Fade ------------------------------------------------------------ +// https://www.w3.org/TR/css-images-4/#cross-fade-function + +struct CrossFade { + struct Layer { + Box image; + Percent opacity; + + void repr(Io::Emit &e) const { + e("({} {})", image, opacity); + } + }; + + Vec layers; + + void repr(Io::Emit &e) const { + e("(cross-fade {}", layers); + } +}; + +// MARK: Stripes --------------------------------------------------------------- +// https://www.w3.org/TR/css-images-4/#stripes + +struct Stripes { + struct Strip { + Gfx::Color color; + Percent size; + + void repr(Io::Emit &e) const { + e("({} {})", color, size); + } + }; + + Vec stripes; + + void repr(Io::Emit &e) const { + e("(stripes {}", stripes); + } +}; + +// MARK: Image ----------------------------------------------------------------- +// https://www.w3.org/TR/css-images-4/#typedef-image + +using _Image = Union< + Gfx::Color, + LinearGradient, + RadialGradient, + ConicGradient, + CrossFade, + Stripes, + Mime::Url>; + +struct Image : public _Image { + using _Image::_Image; + + void repr(Io::Emit &e) const { + visit([&](auto const &i) { + e("{}", i); + }); + } +}; + +} // namespace Vaev diff --git a/src/web/vaev-base/insets.h b/src/web/vaev-base/insets.h index d3bec21f54..1b2f6649ea 100644 --- a/src/web/vaev-base/insets.h +++ b/src/web/vaev-base/insets.h @@ -18,7 +18,7 @@ enum struct Position { using Margin = Math::Insets; -using Padding = Math::Insets>; +using Padding = Math::Insets>>; // https://www.w3.org/TR/CSS22/visuren.html#propdef-top // https://www.w3.org/TR/CSS22/visuren.html#propdef-right diff --git a/src/web/vaev-base/text.h b/src/web/vaev-base/text.h index 00814116f2..93a681488a 100644 --- a/src/web/vaev-base/text.h +++ b/src/web/vaev-base/text.h @@ -27,10 +27,10 @@ enum struct TextAlign { // https://drafts.csswg.org/css-text-4/#text-transform enum struct TextTransform { - NONE, + NONE, //< https://drafts.csswg.org/css-text-4/#valdef-text-transform-none - UPPERCASE, - LOWERCASE, + UPPERCASE, //< https://drafts.csswg.org/css-text-4/#valdef-text-transform-uppercase + LOWERCASE, //< https://drafts.csswg.org/css-text-4/#valdef-text-transform-lowercase _LEN, }; @@ -39,19 +39,20 @@ enum struct TextTransform { // https://drafts.csswg.org/css-text/#white-space-property enum struct WhiteSpace { - NORMAL, - PRE, - NOWRAP, - PRE_WRAP, - BREAK_SPACES, - PRE_LINE, + NORMAL, //< https://drafts.csswg.org/css-text/#valdef-white-space-normal + + PRE, //< https://drafts.csswg.org/css-text/#valdef-white-space-pre + NOWRAP, //< https://drafts.csswg.org/css-text/#valdef-white-space-nowrap + PRE_WRAP, //< https://drafts.csswg.org/css-text/#valdef-white-space-pre-wrap + BREAK_SPACES, //< https://drafts.csswg.org/css-text/#valdef-white-space-break-spaces + PRE_LINE, //< https://drafts.csswg.org/css-text/#valdef-white-space-pre-line _LEN, }; struct TextProps { TextAlign align = TextAlign::START; - TextTransform transform; + TextTransform transform = TextTransform::NONE; WhiteSpace whiteSpace = WhiteSpace::NORMAL; void repr(Io::Emit &e) const { diff --git a/src/web/vaev-driver/fetcher.cpp b/src/web/vaev-driver/fetcher.cpp index 657c865683..0b5fe96a9b 100644 --- a/src/web/vaev-driver/fetcher.cpp +++ b/src/web/vaev-driver/fetcher.cpp @@ -115,11 +115,11 @@ Res> fetchDocument(Mime::Url const &url) { } } -Res fetchStylesheet(Mime::Url url) { +Res fetchStylesheet(Mime::Url url, Style::Origin origin) { auto file = try$(Sys::File::open(url)); auto buf = try$(Io::readAllUtf8(file)); Io::SScan s{buf}; - return Ok(Style::StyleSheet::parse(s)); + return Ok(Style::StyleSheet::parse(s, origin)); } } // namespace Vaev::Driver diff --git a/src/web/vaev-driver/fetcher.h b/src/web/vaev-driver/fetcher.h index 7f94033128..a66703b708 100644 --- a/src/web/vaev-driver/fetcher.h +++ b/src/web/vaev-driver/fetcher.h @@ -6,7 +6,7 @@ namespace Vaev::Driver { -Res fetchStylesheet(Mime::Url url); +Res fetchStylesheet(Mime::Url url, Style::Origin origin = Style::Origin::AUTHOR); Res> fetchDocument(Mime::Url const &url); diff --git a/src/web/vaev-driver/render.cpp b/src/web/vaev-driver/render.cpp index 7a7cae7b02..7dfabeed06 100644 --- a/src/web/vaev-driver/render.cpp +++ b/src/web/vaev-driver/render.cpp @@ -37,7 +37,7 @@ static void _collectStyle(Markup::Node const &node, Style::StyleBook &sb) { return; } - auto sheet = fetchStylesheet(url.take()); + auto sheet = fetchStylesheet(url.take(), Style::Origin::AUTHOR); if (not sheet) { logWarn("failed to fetch stylesheet: {}", sheet); return; @@ -54,7 +54,7 @@ static void _collectStyle(Markup::Node const &node, Style::StyleBook &sb) { RenderResult render(Markup::Document const &dom, Style::Media const &media, Layout::Viewport viewport) { Style::StyleBook stylebook; stylebook.add( - fetchStylesheet("bundle://vaev-driver/user-agent.css"_url) + fetchStylesheet("bundle://vaev-driver/user-agent.css"_url, Style::Origin::USER_AGENT) .take("user agent stylesheet not available") ); @@ -113,7 +113,7 @@ RenderResult render(Markup::Document const &dom, Style::Media const &media, Layo Vec> print(Markup::Document const &dom, Style::Media const &media) { Style::StyleBook stylebook; stylebook.add( - fetchStylesheet("bundle://vaev-driver/user-agent.css"_url) + fetchStylesheet("bundle://vaev-driver/user-agent.css"_url, Style::Origin::USER_AGENT) .take("user agent stylesheet not available") ); diff --git a/src/web/vaev-driver/res/user-agent.css b/src/web/vaev-driver/res/user-agent.css index dd1ccb5a04..d2b684fe3d 100644 --- a/src/web/vaev-driver/res/user-agent.css +++ b/src/web/vaev-driver/res/user-agent.css @@ -216,3 +216,12 @@ td, th { display: table-cell; } + +td, +th { + padding: 1px; +} + +th { + font-weight: bold; +} diff --git a/src/web/vaev-layout/box.h b/src/web/vaev-layout/box.h index 4e01bc2ee6..64b5dc2162 100644 --- a/src/web/vaev-layout/box.h +++ b/src/web/vaev-layout/box.h @@ -15,7 +15,7 @@ using Content = Union< None, Vec, Strong, - Image::Picture>; + Karm::Image::Picture>; struct Attrs { usize span = 1; diff --git a/src/web/vaev-layout/builder.cpp b/src/web/vaev-layout/builder.cpp index 6e36d15c02..73c2000613 100644 --- a/src/web/vaev-layout/builder.cpp +++ b/src/web/vaev-layout/builder.cpp @@ -6,7 +6,7 @@ namespace Vaev::Layout { -void _buildNode(Style::Computer &c, Markup::Node const &node, Box &parent); +static void _buildNode(Style::Computer &c, Markup::Node const &node, Box &parent); // MARK: Attributes ------------------------------------------------------------ @@ -50,13 +50,18 @@ static Attrs _parseDomAttr(Markup::Element const &el) { return attrs; } -// MARK: Build ----------------------------------------------------------------- +// MARK: Build Inline ---------------------------------------------------------- +static Opt> _monospaceFontface = NONE; static Opt> _regularFontface = NONE; static Opt> _boldFontface = NONE; static Strong _lookupFontface(Style::Computed &style) { - if (style.font->style != FontStyle::NORMAL) { + if (contains(style.font->families, "monospace"s)) { + if (not _monospaceFontface) + _monospaceFontface = Karm::Text::loadFontfaceOrFallback("bundle://fonts-fira-code/fonts/FiraCode-Regular.ttf"_url).unwrap(); + return *_monospaceFontface; + } else if (style.font->style != FontStyle::NORMAL) { if (style.font->weight != FontWeight::NORMAL) { if (not _boldFontface) _boldFontface = Karm::Text::loadFontfaceOrFallback("bundle://fonts-inter/fonts/Inter-BoldItalic.ttf"_url).unwrap(); @@ -79,75 +84,6 @@ static Strong _lookupFontface(Style::Computed &style) { } } -void _buildChildren(Style::Computer &c, Vec> const &children, Box &parent) { - for (auto &child : children) { - _buildNode(c, *child, parent); - } -} - -static void _buildTableChildren(Style::Computer &c, Vec> const &children, Box &tableWrapperBox, Strong tableBoxStyle) { - Box tableBox{ - tableBoxStyle, tableWrapperBox.fontFace - }; - - tableBox.style->display = Display::Internal::TABLE_BOX; - - for (auto &child : children) { - if (auto el = child->is()) { - if (el->tagName == Html::CAPTION) { - _buildNode(c, *child, tableWrapperBox); - } else { - _buildNode(c, *child, tableBox); - } - } - } - tableWrapperBox.add(std::move(tableBox)); -} - -static void _buildElement(Style::Computer &c, Markup::Element const &el, Box &parent) { - auto style = c.computeFor(*parent.style, el); - auto font = _lookupFontface(*style); - - if (el.tagName == Html::IMG) { - auto src = el.getAttribute(Html::SRC_ATTR).unwrapOr(""s); - auto url = Mime::Url::parse(src); - Image::Picture img = Image::loadOrFallback(url).unwrap(); - parent.add({style, font, img}); - return; - } - - auto display = style->display; - - if (display == Display::NONE) - return; - - if (display == Display::CONTENTS) { - _buildChildren(c, el.children(), parent); - return; - } - - auto buildBox = [](Style::Computer &c, Markup::Element const &el, Strong font, Strong style) { - if (el.tagName == Html::TagId::TABLE) { - - auto wrapperStyle = makeStrong(Style::Computed::initial()); - wrapperStyle->display = style->display; - wrapperStyle->margin = style->margin; - - Box wrapper = {wrapperStyle, font}; - _buildTableChildren(c, el.children(), wrapper, style); - return wrapper; - } else { - Box box = {style, font}; - _buildChildren(c, el.children(), box); - return box; - } - }; - - auto box = buildBox(c, el, font, style); - box.attrs = _parseDomAttr(el); - parent.add(std::move(box)); -} - auto RE_SEGMENT_BREAK = Re::single('\n', '\r', '\f', '\v'); static void _buildRun(Style::Computer &, Markup::Text const &node, Box &parent) { @@ -242,7 +178,94 @@ static void _buildRun(Style::Computer &, Markup::Text const &node, Box &parent) parent.add({style, fontFace, std::move(prose)}); } -void _buildNode(Style::Computer &c, Markup::Node const &node, Box &parent) { +// MARK: Build Block ----------------------------------------------------------- + +void _buildChildren(Style::Computer &c, Vec> const &children, Box &parent) { + for (auto &child : children) { + _buildNode(c, *child, parent); + } +} + +static void _buildBlock(Style::Computer &c, Strong style, Markup::Element const &el, Box &parent) { + auto font = _lookupFontface(*style); + Box box = {style, font}; + _buildChildren(c, el.children(), box); + box.attrs = _parseDomAttr(el); + parent.add(std::move(box)); +} + +// MARK: Build Replace --------------------------------------------------------- + +static void _buildImage(Style::Computer &c, Markup::Element const &el, Box &parent) { + auto style = c.computeFor(*parent.style, el); + auto font = _lookupFontface(*style); + + auto src = el.getAttribute(Html::SRC_ATTR).unwrapOr(""s); + auto url = Mime::Url::parse(src); + auto img = Karm::Image::loadOrFallback(url).unwrap(); + parent.add({style, font, img}); +} + +// MARK: Build Table ----------------------------------------------------------- + +static void _buildTableChildren(Style::Computer &c, Vec> const &children, Box &tableWrapperBox, Strong tableBoxStyle) { + Box tableBox{ + tableBoxStyle, tableWrapperBox.fontFace + }; + + tableBox.style->display = Display::Internal::TABLE_BOX; + + for (auto &child : children) { + if (auto el = child->is()) { + if (el->tagName == Html::CAPTION) { + _buildNode(c, *child, tableWrapperBox); + } else { + _buildNode(c, *child, tableBox); + } + } + } + tableWrapperBox.add(std::move(tableBox)); +} + +static void _buildTable(Style::Computer &c, Strong style, Markup::Element const &el, Box &parent) { + auto font = _lookupFontface(*style); + + auto wrapperStyle = makeStrong(Style::Computed::initial()); + wrapperStyle->display = style->display; + wrapperStyle->margin = style->margin; + + Box wrapper = {wrapperStyle, font}; + _buildTableChildren(c, el.children(), wrapper, style); + wrapper.attrs = _parseDomAttr(el); + + parent.add(std::move(wrapper)); +} + +// MARK: Build ----------------------------------------------------------------- + +static void _buildElement(Style::Computer &c, Markup::Element const &el, Box &parent) { + if (el.tagName == Html::IMG) { + _buildImage(c, el, parent); + return; + } + + auto style = c.computeFor(*parent.style, el); + auto font = _lookupFontface(*style); + + auto display = style->display; + + if (display == Display::NONE) { + // Do nothing + } else if (display == Display::CONTENTS) { + _buildChildren(c, el.children(), parent); + } else if (display == Display::TABLE) { + _buildTable(c, style, el, parent); + } else { + _buildBlock(c, style, el, parent); + } +} + +static void _buildNode(Style::Computer &c, Markup::Node const &node, Box &parent) { if (auto el = node.is()) { _buildElement(c, *el, parent); } else if (auto text = node.is()) { diff --git a/src/web/vaev-layout/flex.cpp b/src/web/vaev-layout/flex.cpp index c8078b5276..2d82d9fe13 100644 --- a/src/web/vaev-layout/flex.cpp +++ b/src/web/vaev-layout/flex.cpp @@ -241,35 +241,46 @@ struct FlexItem { ); } - void computeFlexBaseSize(Tree &tree, Px mainContainerSize) { - // TODO: check specs - if (flexItemProps.basis.type == FlexBasis::WIDTH) { - if (flexItemProps.basis.width.type == Width::Type::VALUE) { - flexBaseSize = resolve( - tree, - *box, - flexItemProps.basis.width.value, - mainContainerSize - ); - } else if (flexItemProps.basis.width.type == Width::Type::AUTO) { - flexBaseSize = fa.mainAxis(speculativeSize); - } - } + // https://www.w3.org/TR/css-flexbox-1/#valdef-flex-basis-auto + void computeFlexBaseSize(Tree &tree, Px mainContainerSize, IntrinsicSize containerSizing) { + // A NONE return here indicates a CONTENT case for the flex basis + auto getDefiniteFlexBasisSize = [](FlexProps &flexItemProps, FlexAxis &fa, Box *box) -> Opt>> { + if (flexItemProps.basis.type != FlexBasis::WIDTH) + return NONE; - if (flexItemProps.basis.type == FlexBasis::Type::CONTENT and - box->style->sizing->height.type == Size::Type::LENGTH /* and - intrinsic aspect ratio*/ - ) { - // TODO: placehold value, check specs - Px aspectRatio{1}; - auto crossSize = resolve(tree, *box, box->style->sizing->height.value, 0_px); - flexBaseSize = (crossSize)*aspectRatio; + if (flexItemProps.basis.width.type == Width::Type::VALUE) + return flexItemProps.basis.width.value; + + if (fa.mainAxis(box->style->sizing).type == Size::Type::AUTO) + return NONE; + + return fa.mainAxis(box->style->sizing).value; + }; + + if (auto flexBasisDefiniteSize = getDefiniteFlexBasisSize(flexItemProps, fa, box)) { + flexBaseSize = resolve( + tree, + *box, + flexBasisDefiniteSize.unwrap(), + mainContainerSize + ); + return; } - if (false) { - // TODO: other flex base size cases - logWarn("not implemented flex base size case"); + if (isMinMaxIntrinsicSize(containerSizing)) { + flexBaseSize = fa.mainAxis( + containerSizing == IntrinsicSize::MIN_CONTENT + ? minContentSize + : maxContentSize + ); + return; } + + // TODO: + // https://www.w3.org/TR/css-flexbox-1/#algo-main-item + // - intrinsic aspect ratio case + // - what does it mean: "depends on its available space"? + flexBaseSize = fa.mainAxis(maxContentSize); } void computeHypotheticalMainSize(Tree &tree, Vec2Px containerSize) { @@ -280,6 +291,43 @@ struct FlexItem { ); } + // https://www.w3.org/TR/css-flexbox-1/#min-size-auto + Px getMinAutoPrefMainSize(Tree &tree, Vec2Px containerSize) const { + + Opt definiteMaxMainSize; + auto maxMainSize = box->style->sizing->maxSize(fa.isRowOriented ? Axis::HORIZONTAL : Axis::VERTICAL); + if (maxMainSize.type == Size::Type::LENGTH) { + definiteMaxMainSize = resolve( + tree, + *box, + maxMainSize.value, + fa.mainAxis(containerSize) + ); + } + + Px contentSizeSuggestion = fa.mainAxis(minContentSize); + // TODO: clamped by cross size if there is an aspect ratio + if (definiteMaxMainSize) + contentSizeSuggestion = min(contentSizeSuggestion, definiteMaxMainSize.unwrap()); + + if (fa.mainAxis(box->style->sizing).type == Size::Type::LENGTH) { + Px specifiedSizeSuggestion = resolve( + tree, + *box, + fa.mainAxis(box->style->sizing).value, + fa.mainAxis(containerSize) + ); + + if (definiteMaxMainSize) + specifiedSizeSuggestion = min(specifiedSizeSuggestion, definiteMaxMainSize.unwrap()); + + return min(contentSizeSuggestion, specifiedSizeSuggestion); + // TODO: else if(aspect ratio) + } else { + return contentSizeSuggestion; + } + } + Px getMinMaxPrefferedSize(Tree &tree, bool isWidth, bool isMin, Vec2Px containerSize) const { Size sizeToResolve; if (isWidth and isMin) @@ -308,27 +356,13 @@ struct FlexItem { case Size::FIT_CONTENT: logWarn("not implemented"); case Size::AUTO: - if (isMin) { - // https://www.w3.org/TR/css-flexbox-1/#min-size-auto - Size specifiedSizeToResolve{isWidth ? box->style->sizing->width : box->style->sizing->height}; - - if (specifiedSizeToResolve.type == Size::Type::LENGTH) { - auto resolvedSpecifiedSize = resolve( - tree, - *box, - specifiedSizeToResolve.value, - isWidth - ? containerSize.x - : containerSize.y - ); - return min(resolvedSpecifiedSize, isWidth ? minContentSize.x : minContentSize.y); - } else { - return isWidth ? minContentSize.x : minContentSize.y; - } - - } else { + if (not isMin) 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 + ? getMinAutoPrefMainSize(tree, containerSize) + : 0_px; case Size::NONE: if (isMin) panic("NONE is an invalid value for min-width"); @@ -632,11 +666,12 @@ struct FlexFormatingContext { // 3. MARK: Flex base size and hypothetical main size of each item --------- // https://www.w3.org/TR/css-flexbox-1/#algo-main-item - void _determineFlexBaseSizeAndHypotheticalMainSize(Tree &tree) { + void _determineFlexBaseSizeAndHypotheticalMainSize(Tree &tree, IntrinsicSize containerSize) { for (auto &i : _items) { i.computeFlexBaseSize( tree, - fa.mainAxis(availableSpace) + fa.mainAxis(availableSpace), + containerSize ); i.computeHypotheticalMainSize(tree, availableSpace); @@ -1043,8 +1078,6 @@ struct FlexFormatingContext { .availableSpace = fa.extractMainAxisAndFillOther(i.usedSize, availableCrossSpace), } ); - - // COMO PODE EU TER UM HEIGHT PEQUENO AQUI?????????? } } @@ -1136,7 +1169,7 @@ struct FlexFormatingContext { void _handleAlignContentStretch(Input input, Box &box) { // FIXME: If the flex container has a definite cross size <=?=> f.style->sizing->height.type != Size::Type::AUTO if ( - (not(input.intrinsic == IntrinsicSize::MIN_CONTENT) and true) and + not(input.intrinsic == IntrinsicSize::MIN_CONTENT) and (fa.crossAxis(box.style->sizing).type != Size::Type::AUTO or fa.crossAxis(input.knownSize)) and box.style->aligns.alignContent == Style::Align::STRETCH ) { @@ -1466,7 +1499,7 @@ struct FlexFormatingContext { _determineAvailableMainAndCrossSpace(tree, input); // 3. Determine the flex base size and hypothetical main size of each item - _determineFlexBaseSizeAndHypotheticalMainSize(tree); + _determineFlexBaseSizeAndHypotheticalMainSize(tree, input.intrinsic); // 4. Determine the main size of the flex container _determineMainSize(tree, input, box); diff --git a/src/web/vaev-layout/layout.cpp b/src/web/vaev-layout/layout.cpp index cbd34b1428..e231f17aa7 100644 --- a/src/web/vaev-layout/layout.cpp +++ b/src/web/vaev-layout/layout.cpp @@ -13,7 +13,7 @@ namespace Vaev::Layout { Output _contentLayout(Tree &tree, Box &box, Input input) { auto display = box.style->display; - if (auto image = box.content.is()) { + if (auto image = box.content.is()) { return Output::fromSize(image->bound().size().cast()); } else if (auto run = box.content.is>()) { return inlineLayout(tree, box, input); diff --git a/src/web/vaev-layout/paint.cpp b/src/web/vaev-layout/paint.cpp index ed3c0dc887..af2b09e511 100644 --- a/src/web/vaev-layout/paint.cpp +++ b/src/web/vaev-layout/paint.cpp @@ -36,26 +36,14 @@ static void _paintBox(Box &box, Gfx::Color currentColor, Scene::Stack &stack) { Gfx::Borders borders; Vec backgrounds; - - bool hasBackgrounds = any(cssBackground); - - if (hasBackgrounds) { - backgrounds.ensure(cssBackground.len()); - for (auto &bg : cssBackground) { - auto color = resolve(bg.fill, currentColor); - - // Skip transparent backgrounds - if (color.alpha == 0) - continue; - - backgrounds.pushBack(color); - } - } + auto color = resolve(cssBackground->color, currentColor); + if (color.alpha != 0) + backgrounds.pushBack(color); bool hasBorders = _paintBorders(box, currentColor, borders); Math::Rectf bound = box.layout.borderBox().cast(); - if (hasBackgrounds or hasBorders) + if (any(backgrounds) or hasBorders) stack.add(makeStrong(bound, std::move(borders), std::move(backgrounds))); } @@ -71,13 +59,11 @@ static void _paintBox(Box &box, Scene::Stack &stack) { if (auto prose = box.content.is>()) { (*prose)->_style.color = currentColor; - Karm::Text::Font font = {box.fontFace, box.layout.fontSize.cast()}; - Math::Vec2f baseline = {0, font.metrics().ascend}; stack.add(makeStrong( box.layout.borderBox().topStart().cast(), *prose )); - } else if (auto image = box.content.is()) { + } else if (auto image = box.content.is()) { stack.add(makeStrong( box.layout.borderBox().cast(), *image diff --git a/src/web/vaev-layout/table.cpp b/src/web/vaev-layout/table.cpp index ded9992e70..2fd7f303ac 100644 --- a/src/web/vaev-layout/table.cpp +++ b/src/web/vaev-layout/table.cpp @@ -71,6 +71,25 @@ struct TableGrid { } }; +template +struct PrefixSum { + + Vec pref; + + PrefixSum(Vec const &v) : pref(v) { + for (usize i = 1; i < v.len(); ++i) + pref[i] = pref[i - 1] + pref[i]; + } + + T query(isize l, isize r) { + if (r < l) + return T{}; + if (l == 0) + return pref[r]; + return pref[r] - pref[l - 1]; + } +}; + struct TableFormatingContext { TableGrid grid; @@ -94,6 +113,10 @@ struct TableFormatingContext { Vec downwardsGrowingCells; Vec pendingTfoots; + // TODO: amount of footers and headers + // footers will be the last rows of the grid; same for headers? + usize numOfHeaderRows = 0, numOfFooterRows = 0; + struct { usize width; Vec borders; @@ -123,9 +146,6 @@ struct TableFormatingContext { captionsIdxs.pushBack(i); } } - - run(); - buildBordersGrid(tree); } // https://html.spec.whatwg.org/multipage/tables.html#algorithm-for-growing-downward-growing-cells @@ -246,8 +266,24 @@ struct TableFormatingContext { endRowGroup(); } + Opt findFirstHeader() { + auto tableBoxChildren = tableBox.children(); + MutCursor tableBoxCursor{tableBoxChildren}; + + advanceUntil(tableBoxCursor, [](Display d) { + return d == Display::TABLE_HEADER_GROUP; + }); + + if (tableBoxCursor.ended()) + return NONE; + + return tableBoxCursor - tableBoxChildren.begin(); + } + // https://html.spec.whatwg.org/multipage/tables.html#forming-a-table - void run() { + void buildHTMLTable() { + auto indexOfHeaderGroup = findFirstHeader(); + auto tableBoxChildren = tableBox.children(); MutCursor tableBoxCursor{tableBoxChildren}; @@ -304,6 +340,11 @@ struct TableFormatingContext { // MARK: Rows + if (indexOfHeaderGroup) { + processRowGroup(tableBox.children()[indexOfHeaderGroup.unwrap()]); + numOfHeaderRows = grid.size.y; + } + while (true) { advanceUntil(tableBoxCursor, [](Display d) { return d.isHeadBodyFootOrRow(); @@ -333,14 +374,23 @@ struct TableFormatingContext { panic("current element should be thead or tbody"); } + if (indexOfHeaderGroup and + indexOfHeaderGroup.unwrap() == (usize)(tableBoxCursor - tableBoxChildren.begin())) { + // table header was already processed in the beggining of the Rows section of the algorithm + tableBoxCursor.next(); + continue; + } + processRowGroup(*tableBoxCursor); tableBoxCursor.next(); } + usize ystartFooterRows = grid.size.y; for (auto tfootIdx : pendingTfoots) { processRowGroup(tableBoxChildren[tfootIdx]); } + numOfFooterRows = grid.size.y - ystartFooterRows; } Box &findTableBox(Box &tableWrapperBox) { @@ -410,10 +460,13 @@ struct TableFormatingContext { return {borders, sumBorders}; } + Vec colWidth; + Px tableUsedWidth; + // MARK: Fixed Table Layout // https://www.w3.org/TR/CSS22/tables.html#fixed-table-layout - Tuple, Px> getFixedColWidths(Tree &tree, Input &input) { + void computeFixedColWidths(Tree &tree, Input &input) { // NOTE: Percentages for 'width' and 'height' on the table (box) // are calculated relative to the containing block of the // table wrapper box, not the table wrapper box itself. @@ -422,7 +475,7 @@ struct TableFormatingContext { // NOTE: The table does not automatically expand to fill its containing block. // (https://www.w3.org/TR/CSS22/tables.html#width-layout) - auto tableUsedWidth = + tableUsedWidth = tableBox.style->sizing->width == Size::AUTO ? 0_px : resolve(tree, tableBox, tableBox.style->sizing->width.value, input.availableSpace.x); @@ -431,8 +484,8 @@ struct TableFormatingContext { Px fixedWidthToAccount = boxBorder.horizontal() + Px{grid.size.x + 1} * spacing.x; - Vec> colWidth{}; - colWidth.resize(grid.size.x); + Vec> colWidthOrNone{}; + colWidthOrNone.resize(grid.size.x); for (auto &col : cols) { auto width = col.el.style->sizing->width; @@ -440,7 +493,7 @@ struct TableFormatingContext { continue; for (usize x = col.start; x <= col.end; ++x) { - colWidth[x] = resolve(tree, col.el, width.value, tableUsedWidth); + colWidthOrNone[x] = resolve(tree, col.el, width.value, tableUsedWidth); } } @@ -464,16 +517,16 @@ struct TableFormatingContext { // FIXME: Not overriding values already computed, // but should we subtract the already computed from // cellWidth before division? - if (colWidth[x] == NONE) - colWidth[x] = cellWidth / Px{colSpan}; + if (colWidthOrNone[x] == NONE) + colWidthOrNone[x] = cellWidth / Px{colSpan}; } } Px sumColsWidths{0}; usize emptyCols{0}; for (usize i = 0; i < grid.size.x; ++i) { - if (colWidth[i]) - sumColsWidths += colWidth[i].unwrap() + columnBorders[i]; + if (colWidthOrNone[i]) + sumColsWidths += colWidthOrNone[i].unwrap() + columnBorders[i]; else emptyCols++; } @@ -481,29 +534,28 @@ struct TableFormatingContext { if (emptyCols > 0) { if (sumColsWidths < tableUsedWidth - fixedWidthToAccount) { Px toDistribute = (tableUsedWidth - fixedWidthToAccount - sumColsWidths) / Px{emptyCols}; - for (auto &w : colWidth) + for (auto &w : colWidthOrNone) if (w == NONE) w = toDistribute; } } else if (sumColsWidths < tableUsedWidth - fixedWidthToAccount) { Px toDistribute = (tableUsedWidth - fixedWidthToAccount - sumColsWidths); - for (auto &w : colWidth) { + for (auto &w : colWidthOrNone) { w = w.unwrap() + (toDistribute * w.unwrap()) / sumColsWidths; } } - Vec finalColWidths{colWidth.len()}; + colWidth.ensure(colWidthOrNone.len()); for (usize i = 0; i < grid.size.x; ++i) { - auto finalColWidth = colWidth[i].unwrapOr(0_px); - finalColWidths.pushBack(finalColWidth); + auto finalColWidth = colWidthOrNone[i].unwrapOr(0_px); + colWidth.pushBack(finalColWidth); } - return {finalColWidths, tableUsedWidth}; } // MARK: Auto Table Layout ------------------------------------------------- // https://www.w3.org/TR/CSS22/tables.html#auto-table-layout - Tuple, Px> getAutoColWidths(Tree &tree, Input &input) { + void computeAutoColWidths(Tree &tree, Input &input) { // FIXME: This is a rough approximation of the algorithm. // We need to distinguish between percentage-based and fixed lengths: // - Percentage-based sizes are fixed and cannot have extra space distributed to them. @@ -675,34 +727,35 @@ struct TableFormatingContext { // TODO: should minColWidth or maxColWidth be forcelly used if input is MIN_CONTENT or MAX_CONTENT respectivelly? if (tableBox.style->sizing->width != Size::AUTO) { // TODO: how to resolve percentage if case of table width? - auto tableUsedWidth = max(capmin, max(tableComputedWidth.unwrap(), sumMinColWidths)); + tableUsedWidth = max(capmin, max(tableComputedWidth.unwrap(), sumMinColWidths)); // If the used width is greater than MIN, the extra width should be distributed over the columns. // NOTE: A bit obvious, but assuming that specs refers to MIN columns above if (sumMinColWidths < tableUsedWidth) { auto toDistribute = tableUsedWidth - sumMinColWidths; for (auto &w : minColWidth) - w += (toDistribute * w) / sumMaxColWidths; + w += (toDistribute * w) / sumMinColWidths; } - return {minColWidth, tableUsedWidth}; + + colWidth = minColWidth; + return; } // TODO: Specs doesnt say if we should distribute extra width over columns; // also would it be over min or max columns? - if (min(sumMaxColWidths, capmin) < input.containingBlock.x) - return { - maxColWidth, - max(sumMaxColWidths, capmin) - }; - - return { - minColWidth, - max(sumMinColWidths, capmin) - }; + if (min(sumMaxColWidths, capmin) < input.containingBlock.x) { + colWidth = maxColWidth; + tableUsedWidth = max(sumMaxColWidths, capmin); + } else { + colWidth = minColWidth; + tableUsedWidth = max(sumMinColWidths, capmin); + } } // https://www.w3.org/TR/CSS22/tables.html#height-layout - Vec getRowHeights(Tree &tree, Vec const &colWidth) { + Vec rowHeight; + + void computeRowHeights(Tree &tree) { // NOTE: CSS 2.2 does not define how the height of table cells and // table rows is calculated when their height is // specified using percentage values. @@ -711,7 +764,6 @@ struct TableFormatingContext { // If definite, percentages being considered 0px // (See https://www.w3.org/TR/css-tables-3/#computing-the-table-height) - Vec rowHeight; rowHeight.resize(grid.size.y); for (auto &row : rows) { @@ -774,176 +826,83 @@ struct TableFormatingContext { } rowHeight[i] += rowBorderHeight; } - - return rowHeight; } -}; - -Output tableLayout(Tree &tree, Box &wrapper, Input input) { - // TODO: - vertical and horizontal alignment - // - borders collapse - TableFormatingContext table(tree, wrapper); + struct AxisHelper { + Opt groupIdx = NONE; + Opt axisIdx = NONE; + }; - // NOTE: When "table-layout: fixed" is set but "width: auto", the specs suggest - // that the UA can use the fixed layout after computing the width - // (see https://www.w3.org/TR/CSS22/visudet.html#blockwidth). - // - // However, Chrome does not implement this exception, and we are not implementing it either. - bool shouldRunAutoAlgorithm = - table.tableBox.style->table->tableLayout == TableLayout::AUTO or - table.tableBox.style->sizing->width == Size::AUTO; - - auto [colWidth, tableUsedWidth] = - shouldRunAutoAlgorithm - ? table.getAutoColWidths(tree, input) - : table.getFixedColWidths(tree, input); - - auto rowHeight = table.getRowHeights(tree, colWidth); - - Px currPositionY{input.position.y}, captionsHeight{0}; - if (table.tableBox.style->table->captionSide == CaptionSide::TOP) { - for (auto i : table.captionsIdxs) { - auto cellOutput = layout( - tree, - wrapper.children()[i], - { - .commit = input.commit, - .position = {input.position.x, currPositionY}, - } - ); - captionsHeight += cellOutput.size.y; - currPositionY += captionsHeight; + Vec buildAxisHelper(Vec const &axes, Vec const &groups, usize len) { + Vec helper{Buf::init(len)}; + for (usize groupIdx = 0; groupIdx < groups.len(); groupIdx++) { + for (usize i = groups[groupIdx].start; i <= groups[groupIdx].end; ++i) + helper[i].groupIdx = groupIdx; } - } - - auto buildPref = [](Vec const &v) { - Vec pref(v); - for (usize i = 1; i < v.len(); ++i) - pref[i] = pref[i - 1] + pref[i]; - return pref; + for (usize axisIdx = 0; axisIdx < axes.len(); axisIdx++) { + for (usize i = axes[axisIdx].start; i <= axes[axisIdx].end; ++i) + helper[i].axisIdx = axisIdx; + } + return helper; }; - auto queryPref = [](Vec const &pref, isize l, isize r) -> Px { - if (r < l) - return 0_px; - if (l == 0) - return pref[r]; - return pref[r] - pref[l - 1]; - }; + Vec2Px tableBoxSize; + Vec rowHelper, colHelper; - auto colWidthPref = buildPref(colWidth); - auto rowHeightPref = buildPref(rowHeight); + void build(Tree &tree, Input input) { + buildHTMLTable(); + buildBordersGrid(tree); - auto tableBoxSize = Vec2Px{ - queryPref(colWidthPref, 0, table.grid.size.x - 1) + - table.spacing.x * Px{table.grid.size.x + 1}, + rowHelper = buildAxisHelper(rows, rowGroups, grid.size.y); + colHelper = buildAxisHelper(cols, colGroups, grid.size.x); - queryPref(rowHeightPref, 0, table.grid.size.y - 1) + - table.spacing.y * Px{table.grid.size.y + 1}, - }; + // NOTE: When "table-layout: fixed" is set but "width: auto", the specs suggest + // that the UA can use the fixed layout after computing the width + // (see https://www.w3.org/TR/CSS22/visudet.html#blockwidth). + // + // However, Chrome does not implement this exception, and we are not implementing it either. + bool shouldRunAutoAlgorithm = + tableBox.style->table->tableLayout == TableLayout::AUTO or + tableBox.style->sizing->width == Size::AUTO; + + if (shouldRunAutoAlgorithm) + computeAutoColWidths(tree, input); + else + computeFixedColWidths(tree, input); + + computeRowHeights(tree); + + tableBoxSize = Vec2Px{ + iter(colWidth).sum() + spacing.x * Px{grid.size.x + 1}, + iter(rowHeight).sum() + spacing.y * Px{grid.size.y + 1}, + }; + } - if (input.commit == Commit::YES) { + void runTableBox(Tree &tree, Input input, Px &currPositionY) { + PrefixSum colWidthPref{colWidth}, rowHeightPref{rowHeight}; Px currPositionX{input.position.x}; // table box layout( tree, - table.tableBox, + tableBox, { .commit = Commit::YES, .knownSize = { - tableBoxSize.x + table.boxBorder.horizontal(), - tableBoxSize.y + table.boxBorder.vertical(), + tableBoxSize.x + boxBorder.horizontal(), + tableBoxSize.y + boxBorder.vertical(), }, .position = {currPositionX, currPositionY}, } ); - currPositionX += table.boxBorder.start + table.spacing.x; - currPositionY += table.boxBorder.top + table.spacing.y; - - // column groups - for (auto &group : table.colGroups) { - layout( - tree, - group.el, - { - .commit = Commit::YES, - .knownSize = { - queryPref(colWidthPref, group.start, group.end), - tableBoxSize.y, - }, - .position = { - currPositionX + queryPref(colWidthPref, 0, group.start - 1), - currPositionY, - }, - } - ); - } - - // columns - for (auto &col : table.cols) { - layout( - tree, - col.el, - { - .commit = Commit::YES, - .knownSize = { - queryPref(colWidthPref, col.start, col.end), - tableBoxSize.y, - }, - .position = { - currPositionX, - currPositionY + queryPref(colWidthPref, 0, col.start - 1), - }, - } - ); - } - - // row groups - for (auto &group : table.rowGroups) { - layout( - tree, - group.el, - { - .commit = Commit::YES, - .knownSize = { - tableBoxSize.x, - queryPref(rowHeightPref, group.start, group.end), - }, - .position = { - currPositionX, - currPositionY + queryPref(rowHeightPref, 0, group.start - 1), - }, - } - ); - } - - // rows - for (auto &row : table.rows) { - layout( - tree, - row.el, - { - .commit = Commit::YES, - .knownSize = { - tableBoxSize.x, - queryPref(rowHeightPref, row.start, row.end), - }, - .position = { - currPositionX, - currPositionY + queryPref(rowHeightPref, 0, row.start - 1), - }, - } - ); - } - + currPositionX += boxBorder.start + spacing.x; + currPositionY += boxBorder.top + spacing.y; // cells - for (usize i = 0; i < table.grid.size.y; currPositionY += rowHeight[i] + table.spacing.y, i++) { + for (usize i = 0; i < grid.size.y; currPositionY += rowHeight[i] + spacing.y, i++) { Px innnerCurrPositionX = Px{currPositionX}; - for (usize j = 0; j < table.grid.size.x; innnerCurrPositionX += colWidth[j] + table.spacing.x, j++) { - auto cell = table.grid.get(j, i); + for (usize j = 0; j < grid.size.x; innnerCurrPositionX += colWidth[j] + spacing.x, j++) { + auto cell = grid.get(j, i); if (cell.anchorIdx != Math::Vec2u{j, i}) continue; @@ -958,27 +917,27 @@ Output tableLayout(Tree &tree, Box &wrapper, Input input) { // increase the height of the cell box. // // (See https://www.w3.org/TR/CSS22/tables.html#height-layout) - auto cellOutput = layout( + layout( tree, *cell.box, { .commit = Commit::YES, .knownSize = { - queryPref(colWidthPref, j, j + colSpan - 1) + table.spacing.x * Px{colSpan - 1}, - queryPref(rowHeightPref, i, i + rowSpan - 1) + table.spacing.y * Px{rowSpan - 1} + colWidthPref.query(j, j + colSpan - 1) + spacing.x * Px{colSpan - 1}, + rowHeightPref.query(i, i + rowSpan - 1) + spacing.y * Px{rowSpan - 1} }, - .position{innnerCurrPositionX, currPositionY}, + .position = {innnerCurrPositionX, currPositionY}, } ); }; } } - if (table.tableBox.style->table->captionSide == CaptionSide::BOTTOM) { - for (auto i : table.captionsIdxs) { + void runCaptions(Tree &tree, Input input, Px tableUsedWidth, Px &currPositionY, Px &captionsHeight) { + for (auto i : captionsIdxs) { auto cellOutput = layout( tree, - wrapper.children()[i], + wrapperBox.children()[i], { .commit = input.commit, .knownSize = {tableUsedWidth, NONE}, @@ -986,13 +945,38 @@ Output tableLayout(Tree &tree, Box &wrapper, Input input) { } ); captionsHeight += cellOutput.size.y; + currPositionY += captionsHeight; } } - return Output::fromSize({ - tableUsedWidth + table.boxBorder.horizontal(), - tableBoxSize.y + captionsHeight + table.boxBorder.vertical(), - }); + Output run(Tree &tree, Input input) { + Px currPositionY{input.position.y}, captionsHeight{0}; + if (tableBox.style->table->captionSide == CaptionSide::TOP) { + runCaptions(tree, input, tableUsedWidth, currPositionY, captionsHeight); + } + + if (input.commit == Commit::YES) { + runTableBox(tree, input, currPositionY); + } + + if (tableBox.style->table->captionSide == CaptionSide::BOTTOM) { + runCaptions(tree, input, tableUsedWidth, currPositionY, captionsHeight); + } + + return Output::fromSize({ + tableUsedWidth + boxBorder.horizontal(), + tableBoxSize.y + captionsHeight + boxBorder.vertical(), + }); + } +}; + +Output tableLayout(Tree &tree, Box &wrapper, Input input) { + // TODO: - vertical and horizontal alignment + // - borders collapse + + TableFormatingContext table(tree, wrapper); + table.build(tree, input); + return table.run(tree, input); } } // namespace Vaev::Layout diff --git a/src/web/vaev-layout/values.h b/src/web/vaev-layout/values.h index a5bb23d1b5..b9b410eb67 100644 --- a/src/web/vaev-layout/values.h +++ b/src/web/vaev-layout/values.h @@ -59,20 +59,20 @@ struct Resolver { } } - template - auto resolve(CalcValue const &value, Px relative) { + template + auto resolve(CalcValue const &value, Args... args) -> Resolved { if (value.type == CalcValue::OpType::FIXED) { - return resolve(value.lhs.template unwrap(), relative); + return resolve(value.lhs.template unwrap(), args...); } else if (value.type == CalcValue::OpType::SINGLE) { // TODO: compute result of funtion here with the resolved value - return resolve(value.lhs.template unwrap(), relative); + return resolve(value.lhs.template unwrap(), args...); } else if (value.type == CalcValue::OpType::CALC) { auto resolveUnion = Visitor{ [&](T const &v) { - return resolve(v, relative); + return resolve(v, args...); }, [&](CalcValue::Leaf const &v) { - return resolve(*v, relative); + return resolve(*v, args...); }, [&](Number const &v) { return Math::i24f8{v}; @@ -103,4 +103,9 @@ Px resolve(Tree const &tree, Box const &box, Width value, Px relative); Px resolve(Tree const &tree, Box const &box, FontSize value); +template +static inline auto resolve(Tree const &tree, Box const &box, CalcValue const &value, Args... args) -> Resolved { + return Resolver::from(tree, box).resolve(value, args...); +} + } // namespace Vaev::Layout diff --git a/src/web/vaev-style/computed.h b/src/web/vaev-style/computed.h index 4bf5584164..eda4860e61 100644 --- a/src/web/vaev-style/computed.h +++ b/src/web/vaev-style/computed.h @@ -30,9 +30,9 @@ struct Computed { Number opacity; AlignProps aligns; - Math::Vec2> gaps; + Math::Vec2>> gaps; - Vec backgrounds; + Cow backgrounds; Cow borders; Cow margin; Cow padding; diff --git a/src/web/vaev-style/computer.cpp b/src/web/vaev-style/computer.cpp index 64a1e899c0..fe1f4e3eaf 100644 --- a/src/web/vaev-style/computer.cpp +++ b/src/web/vaev-style/computer.cpp @@ -32,6 +32,7 @@ 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; @@ -42,10 +43,12 @@ Strong Computer::computeFor(Computed const &parent, Markup::Element co } } - // Sort rules by specificity + // Sort origin and specificity stableSort( matchingRules, [](auto const &a, auto const &b) { + if (a->origin != b->origin) + return a->origin <=> b->origin; return spec(a->selector) <=> spec(b->selector); } ); @@ -54,7 +57,6 @@ Strong Computer::computeFor(Computed const &parent, Markup::Element co auto styleAttr = el.getAttribute(Html::STYLE_ATTR); StyleRule styleRule{ - .selector = UNIVERSAL, .props = parseDeclarations(styleAttr ? *styleAttr : ""), }; matchingRules.pushBack(&styleRule); @@ -64,17 +66,27 @@ Strong Computer::computeFor(Computed const &parent, Markup::Element co computed->inherit(parent); Vec> importantProps; + // HACK: Apply custom properties first for (auto const &styleRule : matchingRules) { for (auto &prop : styleRule->props) { - if (prop.important == Important::NO) - prop.apply(*computed); - else - importantProps.pushBack(&prop); + if (prop.is()) + prop.apply(parent, *computed); } } - for (auto const &prop : importantProps) - prop->apply(*computed); + for (auto const &styleRule : matchingRules) { + for (auto &prop : styleRule->props) { + if (not prop.is()) { + if (prop.important == Important::NO) + prop.apply(parent, *computed); + else + importantProps.pushBack(&prop); + } + } + } + + for (auto const &prop : iterRev(importantProps)) + prop->apply(parent, *computed); return computed; } diff --git a/src/web/vaev-style/decls.h b/src/web/vaev-style/decls.h index 6547e85268..30d345e134 100644 --- a/src/web/vaev-style/decls.h +++ b/src/web/vaev-style/decls.h @@ -7,6 +7,8 @@ namespace Vaev::Style { +static bool DEBUG_DECL = false; + template Res parseDeclarationValue(Cursor &c) { if constexpr (requires { T{}.parse(c); }) { @@ -15,7 +17,7 @@ Res parseDeclarationValue(Cursor &c) { return Ok(std::move(t)); } else { - logError("missing parser for declaration: {}", T::name()); + logErrorIf(DEBUG_DECL, "missing parser for declaration: {}", T::name()); return Error::notImplemented("missing parser for declaration"); } } @@ -39,7 +41,7 @@ static inline Important _consumeImportant(Cursor &c, bool eatEverythin template static inline P _deferProperty(Css::Sst const &sst) { - StyleProp prop = DeferredProp{sst.token.data, sst.content}; + P prop = DeferredProp{sst.token.data, sst.content}; if constexpr (requires { P::important; }) { Cursor content = sst.content; prop.important = _consumeImportant(content, true); @@ -61,6 +63,22 @@ static inline Res

_parseDeclaration(Css::Sst const &sst) { return Ok(std::move(prop)); } +template +static inline Res

_parseDefaulted(Css::Sst const &sst) { + Cursor content = sst.content; + Res

res = Error::invalidData("unknown declaration"); + if (content.skip(Css::Token::ident("initial"))) { + res = Ok(DefaultedProp{sst.token.data, Default::INITIAL}); + } else if (content.skip(Css::Token::ident("inherit"))) { + res = Ok(DefaultedProp{sst.token.data, Default::INHERIT}); + } else if (content.skip(Css::Token::ident("unset"))) { + res = Ok(DefaultedProp{sst.token.data, Default::UNSET}); + } else if (content.skip(Css::Token::ident("revert"))) { + res = Ok(DefaultedProp{sst.token.data, Default::REVERT}); + } + return res; +} + template Res

parseDeclaration(Css::Sst const &sst, bool allowDeferred = true) { if (sst != Css::Sst::DECL) @@ -90,8 +108,15 @@ Res

parseDeclaration(Css::Sst const &sst, bool allowDeferred = true) { } resDecl = _parseDeclaration(sst); - if (not resDecl and allowDeferred) { - if constexpr (Meta::Constructible) { + + if constexpr (Meta::Constructible) { + if (not resDecl) { + resDecl = _parseDefaulted

(sst); + } + } + + if constexpr (Meta::Constructible) { + if (not resDecl and allowDeferred) { resDecl = Ok(_deferProperty

(sst)); } } @@ -102,7 +127,7 @@ Res

parseDeclaration(Css::Sst const &sst, bool allowDeferred = true) { ); if (not resDecl) - logWarn("failed to parse declaration: {} - {}", sst, resDecl); + logWarnIf(DEBUG_DECL, "failed to parse declaration: {} - {}", sst, resDecl); return resDecl; } @@ -113,14 +138,14 @@ Vec

parseDeclarations(Css::Content const &sst, bool allowDeferred = true) { for (auto const &item : sst) { if (item != Css::Sst::DECL) { - logWarn("unexpected item in declaration: {}", item.type); + logWarnIf(DEBUG_DECL, "unexpected item in declaration: {}", item.type); continue; } auto prop = parseDeclaration

(item, allowDeferred); if (not prop) { - logWarn("failed to parse declaration: {}", prop.none()); + logWarnIf(DEBUG_DECL, "failed to parse declaration: {}", prop.none()); continue; } res.pushBack(prop.take()); diff --git a/src/web/vaev-style/origin.h b/src/web/vaev-style/origin.h new file mode 100644 index 0000000000..c5725b5b0f --- /dev/null +++ b/src/web/vaev-style/origin.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace Vaev::Style { + +// https://drafts.csswg.org/css-cascade/#origin +enum struct Origin { + // NOTE: The order of these values is important + USER_AGENT, + USER, + AUTHOR, +}; + +static inline std::strong_ordering operator<=>(Origin a, Origin b) { + return static_cast(a) <=> static_cast(b); +} + +static inline bool operator==(Origin a, Origin b) { + return static_cast(a) == static_cast(b); +} + +} // namespace Vaev::Style diff --git a/src/web/vaev-style/rules.cpp b/src/web/vaev-style/rules.cpp index 38b8c262ae..2bfaf9c41f 100644 --- a/src/web/vaev-style/rules.cpp +++ b/src/web/vaev-style/rules.cpp @@ -4,6 +4,8 @@ namespace Vaev::Style { +static bool DEBUG_RULE = false; + // MARK: StyleRule ------------------------------------------------------------- void StyleRule::repr(Io::Emit &e) const { @@ -24,7 +26,7 @@ void StyleRule::repr(Io::Emit &e) const { e(")"); } -StyleRule StyleRule::parse(Css::Sst const &sst) { +StyleRule StyleRule::parse(Css::Sst const &sst, Origin origin) { if (sst != Css::Sst::RULE) panic("expected rule"); @@ -45,10 +47,11 @@ StyleRule StyleRule::parse(Css::Sst const &sst) { if (prop) res.props.pushBack(prop.take()); } else { - logWarn("unexpected item in style rule: {}", item.type); + logWarnIf(DEBUG_RULE, "unexpected item in style rule: {}", item); } } + res.origin = origin; return res; } @@ -128,7 +131,7 @@ void Rule::repr(Io::Emit &e) const { }); } -Rule Rule::parse(Css::Sst const &sst) { +Rule Rule::parse(Css::Sst const &sst, Origin origin) { if (sst != Css::Sst::RULE) panic("expected rule"); @@ -140,7 +143,7 @@ Rule Rule::parse(Css::Sst const &sst) { else if (tok.data == "@font-face") return FontFaceRule::parse(sst); else - return StyleRule::parse(sst); + return StyleRule::parse(sst, origin); } } // namespace Vaev::Style diff --git a/src/web/vaev-style/rules.h b/src/web/vaev-style/rules.h index 72bac63a1d..9e559e2aa4 100644 --- a/src/web/vaev-style/rules.h +++ b/src/web/vaev-style/rules.h @@ -2,6 +2,7 @@ #include "fonts.h" #include "media.h" +#include "origin.h" #include "select.h" #include "styles.h" @@ -11,8 +12,9 @@ struct Rule; // https://www.w3.org/TR/cssom-1/#the-cssstylerule-interface struct StyleRule { - Selector selector; + Selector selector = UNIVERSAL; Vec props; + Origin origin = Origin::AUTHOR; void repr(Io::Emit &e) const; @@ -20,7 +22,7 @@ struct StyleRule { return selector.match(el); } - static StyleRule parse(Css::Sst const &sst); + static StyleRule parse(Css::Sst const &sst, Origin origin = Origin::AUTHOR); }; // https://www.w3.org/TR/cssom-1/#the-cssimportrule-interface @@ -65,7 +67,7 @@ struct Rule : public _Rule { void repr(Io::Emit &e) const; - static Rule parse(Css::Sst const &sst); + static Rule parse(Css::Sst const &sst, Origin origin = Origin::AUTHOR); }; } // namespace Vaev::Style diff --git a/src/web/vaev-style/select.cpp b/src/web/vaev-style/select.cpp index e9b831e0bc..a6086faec7 100644 --- a/src/web/vaev-style/select.cpp +++ b/src/web/vaev-style/select.cpp @@ -176,6 +176,37 @@ static bool _matchLink(Markup::Element const &el) { return el.tagName == Html::A and el.hasAttribute(Html::HREF_ATTR); } +// 14.3.3. :first-child pseudo-class +// https://www.w3.org/TR/selectors-4/#the-first-child-pseudo + +static bool _matchFirstChild(Markup::Element const &e) { + Cursor curr = &e; + while (curr->hasPreviousSibling()) { + auto prev = curr->previousSibling(); + if (auto el = prev.is()) + return false; + curr = &prev.unwrap(); + } + return true; +} + +// 14.3.4. :last-child pseudo-class +// https://www.w3.org/TR/selectors-4/#the-last-child-pseudo + +static bool _matchLastChild(Markup::Element const &e) { + // yap("coucouuuuuu {}", e); + Cursor curr = &e; + while (curr->hasNextSibling()) { + auto next = curr->nextSibling(); + // yap("next: {}", next); + if (auto el = next.is()) + return false; + curr = &next.unwrap(); + } + // yap("bruh"); + return true; +} + // 14.4.3. :first-of-type pseudo-class // https://www.w3.org/TR/selectors-4/#the-first-of-type-pseudo @@ -225,6 +256,12 @@ static bool _match(Pseudo const &s, Markup::Element const &el) { case Pseudo::LAST_OF_TYPE: return _matchLastOfType(el); + case Pseudo::FIRST_CHILD: + return _matchFirstChild(el); + + case Pseudo::LAST_CHILD: + return _matchLastChild(el); + default: logDebugIf(DEBUG_SELECTORS, "unimplemented pseudo class: {}", s); return false; @@ -355,7 +392,8 @@ static OpCode _peekOpCode(Cursor &cur) { cur.peek() == Css::Token::IDENT or cur.peek() == Css::Token::HASH or cur.peek().token.data == "." or - cur.peek().token.data == "*" + cur.peek().token.data == "*" or + cur.peek().token.data == ":" ) { return OpCode::DESCENDANT; } else { @@ -409,7 +447,7 @@ static Selector _parseInfixExpr(Selector lhs, Cursor &cur, OpCode opCo static Selector _parseSelectorElement(Cursor &cur, OpCode currentOp) { if (cur.ended()) { - // logError("ERROR : unterminated selector"); + logErrorIf(DEBUG_SELECTORS, "ERROR : unterminated selector"); return EmptySelector{}; } Selector val; @@ -438,11 +476,17 @@ static Selector _parseSelectorElement(Cursor &cur, OpCode currentOp) { if (cur->token.type == Css::Token::COLON) { cur.next(); if (cur.ended()) { - // logError("ERROR : unterminated selector"); + logErrorIf(DEBUG_SELECTORS, "ERROR : unterminated selector"); return EmptySelector{}; } } - val = Pseudo::make(cur->token.data); + + if (cur->prefix == Css::Token::function("not(")) { + Cursor c = cur->content; + val = Selector::not_(_parseSelectorElement(c, OpCode::NOT)); + } else { + val = Pseudo::make(cur->token.data); + } break; default: val = ClassSelector{cur->token.data}; @@ -551,6 +595,7 @@ Selector Selector::parse(Cursor &c) { return EmptySelector{}; } + logDebugIf(DEBUG_SELECTORS, "PARSING SELECTOR : {}", c); Selector currentSelector = _parseSelectorElement(c, OpCode::NOP); while (not c.ended()) { diff --git a/src/web/vaev-style/styles.cpp b/src/web/vaev-style/styles.cpp index c2fabd4433..17097ad1d0 100644 --- a/src/web/vaev-style/styles.cpp +++ b/src/web/vaev-style/styles.cpp @@ -4,6 +4,8 @@ namespace Vaev::Style { +static bool DEBUG_PROPS = false; + // MARK: DeferredProp ---------------------------------------------------------- bool DeferredProp::_expandVariable(Cursor &c, Map const &env, Css::Content &out) { @@ -22,7 +24,8 @@ bool DeferredProp::_expandVariable(Cursor &c, Maptoken.data; if (auto ref = env.access(varName)) { - out.pushBack(*ref); + Cursor varContent = *ref; + _expandContent(varContent, env, out); return true; } content.next(); @@ -41,6 +44,7 @@ bool DeferredProp::_expandFunction(Cursor &c, Mapprefix; Cursor content = c->content; _expandContent(content, env, func.content); @@ -61,7 +65,7 @@ void DeferredProp::_expandContent(Cursor &c, Map } } -void DeferredProp::apply(Computed &c) const { +void DeferredProp::apply(Computed const &parent, Computed &c) const { Css::Sst decl{Css::Sst::DECL}; decl.token = Css::Token::ident(propName); Cursor cursor = value; @@ -70,10 +74,48 @@ void DeferredProp::apply(Computed &c) const { // Parse the expanded content Res computed = parseDeclaration(decl, false); if (not computed) { - logWarn("failed to parse declaration: {}", computed); + logWarnIf(DEBUG_PROPS, "failed to parse declaration: {}: {}", decl, computed); } else { - computed.unwrap().apply(c); + computed.unwrap().apply(parent, c); } } +// MARK: DefaultedProp --------------------------------------------------------- + +void DefaultedProp::apply(Computed const &parent, Computed &c) const { + if (value == Default::INITIAL) { + StyleProp::any([&](Meta::Type) { + if (T::name() != propName) + return false; + if constexpr (requires { T::initial(); }) + StyleProp{T{T::initial()}}.apply(parent, c); + return true; + }); + } else if (value == Default::INHERIT) { + StyleProp::any([&](Meta::Type) { + if (T::name() != propName) + return false; + if constexpr (requires { T::load(parent); }) + StyleProp{T{T::load(parent)}}.apply(parent, c); + return true; + }); + } else if (value == Default::UNSET) { + StyleProp::any([&](Meta::Type) { + if (T::name() != propName) + return false; + if constexpr (requires { T::inherit; T::load(parent); }) + StyleProp{T{T::load(parent)}}.apply(parent, c); + else if constexpr (requires { T::initial(); }) + StyleProp{T{T::initial()}}.apply(parent, c); + return true; + }); + } else { + logDebug("defaulted: unsupported value '{}'", value); + } +} + +void DefaultedProp::repr(Io::Emit &e) const { + e("(Defaulted {#} = {})", propName, value); +} + } // namespace Vaev::Style diff --git a/src/web/vaev-style/styles.h b/src/web/vaev-style/styles.h index 5d7f724712..a01ce74d75 100644 --- a/src/web/vaev-style/styles.h +++ b/src/web/vaev-style/styles.h @@ -32,6 +32,10 @@ struct AlignContentProp { c.aligns.alignContent = value; } + static Align load(Computed const &c) { + return c.aligns.alignContent; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -50,6 +54,10 @@ struct JustifyContentProp { c.aligns.justifyContent = value; } + static Align load(Computed const &c) { + return c.aligns.justifyContent; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -68,6 +76,10 @@ struct JustifySelfProp { c.aligns.justifySelf = value; } + static Align load(Computed const &c) { + return c.aligns.justifySelf; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -86,6 +98,10 @@ struct AlignSelfProp { c.aligns.alignSelf = value; } + static Align load(Computed const &c) { + return c.aligns.alignSelf; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -104,6 +120,10 @@ struct JustifyItemsProp { c.aligns.justifyItems = value; } + static Align load(Computed const &c) { + return c.aligns.justifyItems; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -122,6 +142,10 @@ struct AlignItemsProp { c.aligns.alignItems = value; } + static Align load(Computed const &c) { + return c.aligns.alignItems; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -130,7 +154,7 @@ struct AlignItemsProp { // https://drafts.csswg.org/css-align-3/#column-row-gap struct RowGapProp { - PercentOr value = initial(); + CalcValue> value = initial(); static constexpr Str name() { return "row-gap"; } @@ -140,15 +164,19 @@ struct RowGapProp { c.gaps.y = value; } + static CalcValue> load(Computed const &c) { + return c.gaps.y; + } + Res<> parse(Cursor &c) { - value = try$(parseValue>(c)); + value = try$(parseValue>>(c)); return Ok(); } }; // https://drafts.csswg.org/css-align-3/#column-row-gap struct ColumnGapProp { - PercentOr value = initial(); + CalcValue> value = initial(); static constexpr Str name() { return "column-gap"; } @@ -158,67 +186,87 @@ struct ColumnGapProp { c.gaps.x = value; } + static CalcValue> load(Computed &c) { + return c.gaps.x; + } + Res<> parse(Cursor &c) { value = try$(parseValue>(c)); return Ok(); } }; -// MARK: Background ------------------------------------------------------------ +// MARK: Background Color ------------------------------------------------------ -// https://www.w3.org/TR/CSS22/colors.html#propdef-background-attachment -struct BackgroundAttachmentProp { - Vec value = initial(); +// https://www.w3.org/TR/CSS22/colors.html#propdef-background-color +struct BackgroundColorProp { + Color value = initial(); - static constexpr Str name() { return "background-attachment"; } + static constexpr Str name() { return "background-color"; } - static constexpr Array initial() { - return {BackgroundAttachment::SCROLL}; - } + static constexpr Color initial() { return TRANSPARENT; } void apply(Computed &c) const { - c.backgrounds.resize(max(c.backgrounds.len(), value.len())); - for (usize i = 0; i < value.len(); ++i) - c.backgrounds[i].attachment = value[i]; + c.backgrounds.cow().color = value; + } + + static Color load(Computed const &c) { + return c.backgrounds->color; + } + + Res<> parse(Cursor &c) { + value = try$(parseValue(c)); + return Ok(); } }; -// https://www.w3.org/TR/CSS22/colors.html#propdef-background-color -struct BackgroundColorProp { - Vec value = initial(); +// MARK: Background Image ------------------------------------------------------ - static constexpr Str name() { return "background-color"; } +// https://www.w3.org/TR/CSS22/colors.html#propdef-background-attachment +struct BackgroundAttachmentProp { + Vec value = initial(); + + static constexpr Str name() { return "background-attachment"; } - static constexpr Array initial() { return {TRANSPARENT}; } + static constexpr Array initial() { + return {BackgroundAttachment::SCROLL}; + } void apply(Computed &c) const { - c.backgrounds.resize(max(c.backgrounds.len(), value.len())); + auto &layers = c.backgrounds.cow().layers; + layers.resize(max(layers.len(), value.len())); for (usize i = 0; i < value.len(); ++i) - c.backgrounds[i].fill = value[i]; + layers[i].attachment = value[i]; } - Res<> parse(Cursor &c) { - eatWhitespace(c); - value.clear(); - while (not c.ended()) { - value.pushBack(try$(parseValue(c))); - eatWhitespace(c); - } - return Ok(); + static Vec load(Computed const &c) { + Vec layers; + for (auto const &l : c.backgrounds->layers) + layers.pushBack(l.attachment); + return layers; } }; // https://www.w3.org/TR/CSS22/colors.html#propdef-background-image struct BackgroundImageProp { - Vec> value = initial(); + Vec value = initial(); static constexpr Str name() { return "background-image"; } - static Array, 1> initial() { return {NONE}; } + static Array initial() { return {}; } void apply(Computed &) const { // TODO } + + static Vec load(Computed const &) { + return {}; + } + + Res<> pase(Cursor &) { + // TODO + return Ok(); + } }; // https://www.w3.org/TR/CSS22/colors.html#propdef-background-position @@ -227,14 +275,21 @@ struct BackgroundPositionProp { static constexpr Str name() { return "background-position"; } - static constexpr Array initial() { - return {BackgroundPosition{Percent{0}, Percent{0}}}; + static constexpr Array initial() { + return {}; } - void apply(Computed &c) const { - c.backgrounds.resize(max(c.backgrounds.len(), value.len())); - for (usize i = 0; i < value.len(); ++i) - c.backgrounds[i].position = value[i]; + void apply(Computed &) const { + // TODO + } + + static Vec load(Computed const &) { + return {}; + } + + Res<> pase(Cursor &) { + // TODO + return Ok(); } }; @@ -248,39 +303,39 @@ struct BackgroundRepeatProp { return {BackgroundRepeat::REPEAT}; } - void apply(Computed &c) const { - c.backgrounds.resize(max(c.backgrounds.len(), value.len())); - for (usize i = 0; i < value.len(); ++i) - c.backgrounds[i].repeat = value[i]; + void apply(Computed &) const { + // TODO + } + + static Vec load(Computed const &) { + return {}; + } + + Res<> pase(Cursor &) { + // TODO + return Ok(); } }; // https://www.w3.org/TR/CSS22/colors.html#x10 struct BackgroundProp { // FIXME: this should cover everything else - Vec value = initial(); + BackgroundProps value = initial(); static constexpr Str name() { return "background"; } - static constexpr Array initial() { return {TRANSPARENT}; } + static BackgroundProps initial() { return {TRANSPARENT}; } void apply(Computed &c) const { - c.backgrounds.resize(max(c.backgrounds.len(), value.len())); - for (usize i = 0; i < value.len(); ++i) - c.backgrounds[i].fill = value[i]; + c.backgrounds.cow() = value; } - Res<> parse(Cursor &c) { - value.clear(); - - eatWhitespace(c); - auto maybeColor = parseValue(c); - while (maybeColor) { - value.pushBack(maybeColor.take()); - eatWhitespace(c); - maybeColor = parseValue(c); - } + static BackgroundProps load(Computed const &c) { + return *c.backgrounds; + } + Res<> parse(Cursor &c) { + value.color = try$(parseValue(c)); return Ok(); } }; @@ -301,6 +356,10 @@ struct ColorProp { c.color = value; } + static Color load(Computed const &c) { + return c.color; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -319,6 +378,10 @@ struct DisplayProp { s.display = value; } + static Display load(Computed const &s) { + return s.display; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -337,6 +400,10 @@ struct TableLayoutProp { s.table.cow().tableLayout = value; } + static TableLayout load(Computed const &s) { + return s.table->tableLayout; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -355,6 +422,10 @@ struct CaptionSideProp { s.table.cow().captionSide = value; } + static CaptionSide load(Computed const &s) { + return s.table->captionSide; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -375,6 +446,10 @@ struct BorderTopColorProp { c.borders.cow().top.color = value; } + static Color load(Computed const &c) { + return c.borders->top.color; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -393,6 +468,10 @@ struct BorderRightColorProp { c.borders.cow().end.color = value; } + static Color load(Computed const &c) { + return c.borders->end.color; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -411,6 +490,10 @@ struct BorderBottomColorProp { c.borders.cow().bottom.color = value; } + static Color load(Computed const &c) { + return c.borders->bottom.color; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -429,6 +512,10 @@ struct BorderLeftColorProp { c.borders.cow().start.color = value; } + static Color load(Computed const &c) { + return c.borders->start.color; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -436,22 +523,31 @@ struct BorderLeftColorProp { }; struct BorderColorProp { - Color value = initial(); + Math::Insets value = initial(); static constexpr Str name() { return "border-color"; } - static constexpr Color initial() { return BLACK; } + static constexpr Math::Insets initial() { return {BLACK}; } void apply(Computed &c) const { auto &borders = c.borders.cow(); - borders.start.color = value; - borders.end.color = value; - borders.top.color = value; - borders.bottom.color = value; + borders.start.color = value.start; + borders.end.color = value.end; + borders.top.color = value.top; + borders.bottom.color = value.bottom; + } + + static Math::Insets load(Computed const &c) { + return { + c.borders->start.color, + c.borders->end.color, + c.borders->top.color, + c.borders->bottom.color, + }; } Res<> parse(Cursor &c) { - value = try$(parseValue(c)); + value = try$(parseValue>(c)); return Ok(); } }; @@ -474,6 +570,15 @@ struct BorderStyle { c.borders.cow().bottom.style = value.bottom; } + static Math::Insets load(Computed const &c) { + return { + c.borders->start.style, + c.borders->end.style, + c.borders->top.style, + c.borders->bottom.style, + }; + } + Res<> parse(Cursor &c) { value = try$(parseValue>(c)); return Ok(); @@ -492,6 +597,10 @@ struct BorderLeftStyleProp { c.borders.cow().start.style = value; } + static Gfx::BorderStyle load(Computed const &c) { + return c.borders->start.style; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -510,6 +619,10 @@ struct BorderTopStyleProp { c.borders.cow().top.style = value; } + static Gfx::BorderStyle load(Computed const &c) { + return c.borders->top.style; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -528,6 +641,10 @@ struct BorderRightStyleProp { c.borders.cow().end.style = value; } + static Gfx::BorderStyle load(Computed const &c) { + return c.borders->end.style; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -546,6 +663,10 @@ struct BorderBottomStyleProp { c.borders.cow().bottom.style = value; } + static Gfx::BorderStyle load(Computed const &c) { + return c.borders->bottom.style; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -554,7 +675,7 @@ struct BorderBottomStyleProp { // https://www.w3.org/TR/css-backgrounds-3/#border-width struct BorderTopWidthProp { - Length value = initial(); + CalcValue value = initial(); static constexpr Str name() { return "border-top-width"; } @@ -564,15 +685,19 @@ struct BorderTopWidthProp { c.borders.cow().top.width = value; } + static CalcValue load(Computed const &c) { + return c.borders->top.width; + } + Res<> parse(Cursor &c) { - value = try$(parseValue(c)); + value = try$(parseValue>(c)); return Ok(); } }; // https://www.w3.org/TR/css-backgrounds-3/#border-width struct BorderRightWidthProp { - Length value = initial(); + CalcValue value = initial(); static constexpr Str name() { return "border-right-width"; } @@ -582,15 +707,19 @@ struct BorderRightWidthProp { c.borders.cow().end.width = value; } + static CalcValue load(Computed const &c) { + return c.borders->end.width; + } + Res<> parse(Cursor &c) { - value = try$(parseValue(c)); + value = try$(parseValue>(c)); return Ok(); } }; // https://www.w3.org/TR/css-backgrounds-3/#border-width struct BorderBottomWidthProp { - Length value = initial(); + CalcValue value = initial(); static constexpr Str name() { return "border-bottom-width"; } @@ -600,15 +729,19 @@ struct BorderBottomWidthProp { c.borders.cow().bottom.width = value; } + static CalcValue load(Computed const &c) { + return c.borders->bottom.width; + } + Res<> parse(Cursor &c) { - value = try$(parseValue(c)); + value = try$(parseValue>(c)); return Ok(); } }; // https://www.w3.org/TR/css-backgrounds-3/#border-width struct BorderLeftWidthProp { - Length value = initial(); + CalcValue value = initial(); static constexpr Str name() { return "border-left-width"; } @@ -618,15 +751,19 @@ struct BorderLeftWidthProp { c.borders.cow().start.width = value; } + static CalcValue load(Computed const &c) { + return c.borders->start.width; + } + Res<> parse(Cursor &c) { - value = try$(parseValue(c)); + value = try$(parseValue>(c)); return Ok(); } }; // https://drafts.csswg.org/css-backgrounds/#the-border-radius struct BorderRadiusTopRight { - Array, 2> value = {initial(), initial()}; + Array>, 2> value = {initial(), initial()}; static constexpr Str name() { return "border-top-right-radius"; } @@ -637,12 +774,19 @@ struct BorderRadiusTopRight { c.borders.cow().radii.d = value[1]; } + static Array>, 2> load(Computed const &c) { + return { + c.borders->radii.c, + c.borders->radii.d, + }; + } + Res<> parse(Cursor &c) { - value[0] = try$(parseValue>(c)); + value[0] = try$(parseValue>>(c)); if (c.ended()) { value[1] = value[0]; } else { - value[1] = try$(parseValue>(c)); + value[1] = try$(parseValue>>(c)); } return Ok(); @@ -651,7 +795,7 @@ struct BorderRadiusTopRight { // https://drafts.csswg.org/css-backgrounds/#the-border-radius struct BorderRadiusTopLeft { - Array, 2> value = {initial(), initial()}; + Array>, 2> value = {initial(), initial()}; static constexpr Str name() { return "border-top-left-radius"; } @@ -662,13 +806,20 @@ struct BorderRadiusTopLeft { c.borders.cow().radii.b = value[0]; } + static Array>, 2> load(Computed const &c) { + return { + c.borders->radii.a, + c.borders->radii.b, + }; + } + Res<> parse(Cursor &c) { - value[0] = try$(parseValue>(c)); + value[0] = try$(parseValue>>(c)); eatWhitespace(c); if (c.ended()) { value[1] = value[0]; } else { - value[1] = try$(parseValue>(c)); + value[1] = try$(parseValue>>(c)); } return Ok(); @@ -677,7 +828,7 @@ struct BorderRadiusTopLeft { // https://drafts.csswg.org/css-backgrounds/#the-border-radius struct BorderRadiusBottomRight { - Array, 2> value = {initial(), initial()}; + Array>, 2> value = {initial(), initial()}; static constexpr Str name() { return "border-bottom-right-radius"; } @@ -688,12 +839,19 @@ struct BorderRadiusBottomRight { c.borders.cow().radii.f = value[0]; } + static Array>, 2> load(Computed const &c) { + return { + c.borders->radii.e, + c.borders->radii.f, + }; + } + Res<> parse(Cursor &c) { - value[0] = try$(parseValue>(c)); + value[0] = try$(parseValue>>(c)); if (c.ended()) { value[1] = value[0]; } else { - value[1] = try$(parseValue>(c)); + value[1] = try$(parseValue>>(c)); } return Ok(); @@ -702,7 +860,7 @@ struct BorderRadiusBottomRight { // https://drafts.csswg.org/css-backgrounds/#the-border-radius struct BorderRadiusBottomLeft { - Array, 2> value = {initial(), initial()}; + Array>, 2> value = {initial(), initial()}; static constexpr Str name() { return "border-bottom-left-radius"; } @@ -713,12 +871,19 @@ struct BorderRadiusBottomLeft { c.borders.cow().radii.h = value[1]; } + static Array>, 2> load(Computed const &c) { + return { + c.borders->radii.g, + c.borders->radii.h, + }; + } + Res<> parse(Cursor &c) { - value[0] = try$(parseValue>(c)); + value[0] = try$(parseValue>>(c)); if (c.ended()) { value[1] = value[0]; } else { - value[1] = try$(parseValue>(c)); + value[1] = try$(parseValue>>(c)); } return Ok(); @@ -727,18 +892,22 @@ struct BorderRadiusBottomLeft { // https://drafts.csswg.org/css-backgrounds/#the-border-radius struct BorderRadius { - Math::Radii> value = initial(); + Math::Radii>> value = initial(); static constexpr Str name() { return "border-radius"; } - static constexpr Math::Radii> initial() { return {}; } + static Math::Radii>> initial() { return {}; } void apply(Computed &c) const { c.borders.cow().radii = value; } + static Math::Radii>> load(Computed const &c) { + return c.borders->radii; + } + Res<> parse(Cursor &c) { - value = try$(parseValue>>(c)); + value = try$(parseValue>>>(c)); return Ok(); } }; @@ -753,9 +922,13 @@ struct BorderTopProp { c.borders.cow().top = value; } + static Border load(Computed const &c) { + return c.borders->top; + } + Res<> parse(Cursor &c) { while (not c.ended()) { - auto width = parseValue(c); + auto width = parseValue>(c); if (width) { value.width = width.unwrap(); continue; @@ -790,9 +963,13 @@ struct BorderRightProp { c.borders.cow().end = value; } + static Border load(Computed const &c) { + return c.borders->end; + } + Res<> parse(Cursor &c) { while (not c.ended()) { - auto width = parseValue(c); + auto width = parseValue>(c); if (width) { value.width = width.unwrap(); continue; @@ -827,9 +1004,13 @@ struct BorderBottomProp { c.borders.cow().bottom = value; } + static Border load(Computed const &c) { + return c.borders->bottom; + } + Res<> parse(Cursor &c) { while (not c.ended()) { - auto width = parseValue(c); + auto width = parseValue>(c); if (width) { value.width = width.unwrap(); continue; @@ -864,9 +1045,13 @@ struct BorderLeftProp { c.borders.cow().start = value; } + static Border load(Computed const &c) { + return c.borders->start; + } + Res<> parse(Cursor &c) { while (not c.ended()) { - auto width = parseValue(c); + auto width = parseValue>(c); if (width) { value.width = width.unwrap(); continue; @@ -904,9 +1089,13 @@ struct BorderProp { c.borders.cow().end = value; } + static Border load(Computed const &c) { + return c.borders->top; + } + Res<> parse(Cursor &c) { while (not c.ended()) { - auto width = parseValue(c); + auto width = parseValue>(c); if (width) { value.width = width.unwrap(); continue; @@ -933,7 +1122,7 @@ struct BorderProp { // https://www.w3.org/TR/css-backgrounds-3/#border-width struct BorderWidthProp { - Math::Insets value = BorderProps::MEDIUM; + Math::Insets> value; static constexpr Str name() { return "border-width"; } @@ -944,8 +1133,17 @@ struct BorderWidthProp { c.borders.cow().bottom.width = value.bottom; } + static Math::Insets> load(Computed const &c) { + return { + c.borders->start.width, + c.borders->end.width, + c.borders->top.width, + c.borders->bottom.width, + }; + } + Res<> parse(Cursor &c) { - value = try$(parseValue>(c)); + value = try$(parseValue>>(c)); return Ok(); } @@ -965,6 +1163,10 @@ struct BorderCollapseProp { c.table.cow().collapse = value; } + static BorderCollapse load(Computed const &c) { + return c.table->collapse; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -983,6 +1185,10 @@ struct BorderSpacingProp { c.table.cow().spacing = value; } + static BorderSpacing load(Computed const &c) { + return c.table->spacing; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1003,6 +1209,10 @@ struct FlexBasisProp { c.flex.cow().basis = value; } + static FlexBasis load(Computed const &c) { + return c.flex->basis; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1021,6 +1231,10 @@ struct FlexDirectionProp { c.flex.cow().direction = value; } + static FlexDirection load(Computed const &c) { + return c.flex->direction; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1039,6 +1253,10 @@ struct FlexGrowProp { c.flex.cow().grow = value; } + static Number load(Computed const &c) { + return c.flex->grow; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1057,6 +1275,10 @@ struct FlexShrinkProp { c.flex.cow().shrink = value; } + static Number load(Computed const &c) { + return c.flex->shrink; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1075,6 +1297,10 @@ struct FlexWrapProp { c.flex.cow().wrap = value; } + static FlexWrap load(Computed const &c) { + return c.flex->wrap; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1099,6 +1325,13 @@ struct FlexFlowProp { c.flex.cow().wrap = value.v1; } + static Tuple load(Computed const &c) { + return { + c.flex->direction, + c.flex->wrap, + }; + } + Res<> parse(Cursor &c) { if (c.ended()) return Error::invalidData("unexpected end of input"); @@ -1127,16 +1360,11 @@ struct FlexFlowProp { // https://www.w3.org/TR/css-flexbox-1/#propdef-flex struct FlexProp { - struct Flex { - FlexBasis flexBasis; - Number flexGrow, flexShrink; + FlexItemProps value = initial(); - void repr(Io::Emit &e) const { - e("({} {} {})", flexBasis, flexGrow, flexShrink); - } - } value = initial(); + static constexpr Str name() { return "flex"; } - static Flex initial() { + static FlexItemProps initial() { return { Width{Width::AUTO}, 0, @@ -1144,8 +1372,6 @@ struct FlexProp { }; } - static constexpr Str name() { return "flex"; } - void apply(Computed &c) const { auto &flex = c.flex.cow(); flex.basis = value.flexBasis; @@ -1153,6 +1379,14 @@ struct FlexProp { flex.shrink = value.flexShrink; } + static FlexItemProps load(Computed const &c) { + return { + c.flex->basis, + c.flex->grow, + c.flex->shrink, + }; + } + Res<> parse(Cursor &c) { if (c.ended()) return Error::invalidData("unexpected end of input"); @@ -1177,7 +1411,7 @@ struct FlexProp { value.flexGrow = value.flexShrink = 1; value.flexBasis = FlexBasis(Width(Length(0, Length::Unit::PX))); - auto parseGrowShrink = [](Cursor &c, Flex &value) -> Res<> { + auto parseGrowShrink = [](Cursor &c, FlexItemProps &value) -> Res<> { auto grow = parseValue(c); if (not grow) return Error::invalidData("expected flex item grow"); @@ -1222,6 +1456,10 @@ struct FloatProp { c.float_ = value; } + static Float load(Computed const &c) { + return c.float_; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1239,6 +1477,10 @@ struct ClearProp { c.clear = value; } + static Clear load(Computed const &c) { + return c.clear; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1264,10 +1506,32 @@ struct FontFamilyProp { c.font.cow().families = value; } + static Vec load(Computed const &c) { + return c.font->families; + } + Res<> parse(Cursor &c) { eatWhitespace(c); while (not c.ended()) { - value.pushBack(try$(parseValue(c))); + if (c.skip(Css::Token::ident("serif"))) + value.pushBack("serif"s); + else if (c.skip(Css::Token::ident("sans-serif"))) + value.pushBack("sans-serif"s); + else if (c.skip(Css::Token::ident("cursive"))) + value.pushBack("cursive"s); + else if (c.skip(Css::Token::ident("fantasy"))) + value.pushBack("fantasy"s); + else if (c.skip(Css::Token::ident("monospace"))) + value.pushBack("monospace"s); + else if (c.skip(Css::Token::ident("system-ui"))) + value.pushBack("system-ui"s); + else if (c.skip(Css::Token::ident("math"))) + value.pushBack("math"s); + else + value.pushBack(try$(parseValue(c))); + + eatWhitespace(c); + c.skip(Css::Token::comma()); eatWhitespace(c); } return Ok(); @@ -1291,6 +1555,10 @@ struct FontWeightProp { c.font.cow().weight = value; } + static FontWeight load(Computed const &c) { + return c.font->weight; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1314,6 +1582,10 @@ struct FontWidthProp { c.font.cow().width = value; } + static FontWidth load(Computed const &c) { + return c.font->width; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1337,6 +1609,10 @@ struct FontStyleProp { c.font.cow().style = value; } + static FontStyle load(Computed const &c) { + return c.font->style; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1360,6 +1636,10 @@ struct FontSizeProp { c.font.cow().size = value; } + static FontSize load(Computed const &c) { + return c.font->size; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1379,6 +1659,10 @@ struct LineHeightProp { // TODO } + static LineHeight load(Computed const &) { + return initial(); // TODO + } + Res<> parse(Cursor &c) { if (c.skip(Css::Token::ident("normal"))) { value = LineHeight::NORMAL; @@ -1416,6 +1700,10 @@ struct MarginTopProp { c.margin.cow().top = value; } + static Width load(Computed const &c) { + return c.margin->top; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1433,6 +1721,10 @@ struct MarginRightProp { c.margin.cow().end = value; } + static Width load(Computed const &c) { + return c.margin->end; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1450,6 +1742,10 @@ struct MarginBottomProp { c.margin.cow().bottom = value; } + static Width load(Computed const &c) { + return c.margin->bottom; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1467,6 +1763,10 @@ struct MarginLeftProp { c.margin.cow().start = value; } + static Width load(Computed const &c) { + return c.margin->start; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1484,6 +1784,10 @@ struct MarginProp { c.margin.cow() = value; } + static Math::Insets load(Computed const &c) { + return c.margin->start; + } + Res<> parse(Cursor &c) { value = try$(parseValue>(c)); return Ok(); @@ -1504,6 +1808,10 @@ struct MarginInlineStartProp { c.margin.cow().start = value; } + static Width load(Computed const &c) { + return c.margin->start; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1522,6 +1830,10 @@ struct MarginInlineEndProp { c.margin.cow().end = value; } + static Width load(Computed const &c) { + return c.margin->end; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1541,6 +1853,13 @@ struct MarginInlineProp { c.margin.cow().end = value.end; } + static Math::Insets load(Computed const &c) { + return { + c.margin->start, + c.margin->end, + }; + } + Res<> parse(Cursor &c) { value = try$(parseValue>(c)); return Ok(); @@ -1559,6 +1878,10 @@ struct MarginBlockStartProp { c.margin.cow().top = value; } + static Width load(Computed const &c) { + return c.margin->top; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1577,6 +1900,10 @@ struct MarginBlockEndProp { c.margin.cow().bottom = value; } + static Width load(Computed const &c) { + return c.margin->bottom; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1596,6 +1923,13 @@ struct MarginBlockProp { c.margin.cow().bottom = value.bottom; } + static Math::Insets load(Computed const &c) { + return { + c.margin->top, + c.margin->bottom, + }; + } + Res<> parse(Cursor &c) { value = try$(parseValue>(c)); return Ok(); @@ -1615,6 +1949,10 @@ struct OpacityProp { c.opacity = value; } + static f64 load(Computed const &c) { + return c.opacity; + } + Res<> parse(Cursor &c) { auto maybePercent = parseValue(c); if (maybePercent) { @@ -1640,6 +1978,10 @@ struct OverflowXProp { c.overflows.x = value; } + static Overflow load(Computed const &c) { + return c.overflows.x; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1658,6 +2000,10 @@ struct OverflowYProp { c.overflows.y = value; } + static Overflow load(Computed const &c) { + return c.overflows.y; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1676,6 +2022,10 @@ struct OverflowBlockProp { c.overflows.block = value; } + static Overflow load(Computed const &c) { + return c.overflows.block; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1694,6 +2044,10 @@ struct OverflowInlineProp { c.overflows.inline_ = value; } + static Overflow load(Computed const &c) { + return c.overflows.inline_; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1705,7 +2059,7 @@ struct OverflowInlineProp { // https://www.w3.org/TR/css-box-3/#propdef-padding struct PaddingTopProp { - PercentOr value = initial(); + CalcValue> value = initial(); static Str name() { return "padding-top"; } @@ -1715,14 +2069,18 @@ struct PaddingTopProp { c.padding.cow().top = value; } + static CalcValue> load(Computed const &c) { + return c.padding->top; + } + Res<> parse(Cursor &c) { - value = try$(parseValue>(c)); + value = try$(parseValue>>(c)); return Ok(); } }; struct PaddingRightProp { - PercentOr value = initial(); + CalcValue> value = initial(); static Str name() { return "padding-right"; } @@ -1732,14 +2090,18 @@ struct PaddingRightProp { c.padding.cow().end = value; } + static CalcValue> load(Computed const &c) { + return c.padding->end; + } + Res<> parse(Cursor &c) { - value = try$(parseValue>(c)); + value = try$(parseValue>>(c)); return Ok(); } }; struct PaddingBottomProp { - PercentOr value = initial(); + CalcValue> value = initial(); static Str name() { return "padding-bottom"; } @@ -1749,14 +2111,18 @@ struct PaddingBottomProp { c.padding.cow().bottom = value; } + static CalcValue> load(Computed const &c) { + return c.padding->bottom; + } + Res<> parse(Cursor &c) { - value = try$(parseValue>(c)); + value = try$(parseValue>>(c)); return Ok(); } }; struct PaddingLeftProp { - PercentOr value = initial(); + CalcValue> value = initial(); static Str name() { return "padding-left"; } @@ -1766,25 +2132,33 @@ struct PaddingLeftProp { c.padding.cow().start = value; } + static CalcValue> load(Computed const &c) { + return c.padding->start; + } + Res<> parse(Cursor &c) { - value = try$(parseValue>(c)); + value = try$(parseValue>>(c)); return Ok(); } }; struct PaddingProp { - Math::Insets> value = initial(); + Math::Insets>> value = initial(); static Str name() { return "padding"; } - static Math::Insets> initial() { return {}; } + static Math::Insets>> initial() { return {}; } void apply(Computed &c) const { c.padding.cow() = value; } + static Math::Insets>> load(Computed const &c) { + return *c.padding; + } + Res<> parse(Cursor &c) { - value = try$(parseValue>>(c)); + value = try$(parseValue>>>(c)); return Ok(); } }; @@ -1801,6 +2175,10 @@ struct OrderProp { c.order = value; } + static Integer load(Computed const &c) { + return c.order; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1821,6 +2199,10 @@ struct PositionProp { c.position = value; } + static Position load(Computed const &c) { + return c.position; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1839,6 +2221,10 @@ struct TopProp { c.offsets.cow().top = value; } + static Width load(Computed const &c) { + return c.offsets->top; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1857,6 +2243,10 @@ struct RightProp { c.offsets.cow().end = value; } + static Width load(Computed const &c) { + return c.offsets->end; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1875,6 +2265,10 @@ struct BottomProp { c.offsets.cow().bottom = value; } + static Width load(Computed const &c) { + return c.offsets->bottom; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1893,6 +2287,10 @@ struct LeftProp { c.offsets.cow().start = value; } + static Width load(Computed const &c) { + return c.offsets->start; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1914,6 +2312,10 @@ struct BoxSizingProp { c.sizing.cow().boxSizing = value; } + static BoxSizing load(Computed const &c) { + return c.sizing->boxSizing; + } + Res<> parse(Cursor &c) { if (c.skip(Css::Token::ident("border-box"))) value = BoxSizing::BORDER_BOX; @@ -1939,6 +2341,10 @@ struct WidthProp { c.sizing.cow().width = value; } + static Size load(Computed const &c) { + return c.sizing->width; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1958,6 +2364,10 @@ struct HeightProp { c.sizing.cow().height = value; } + static Size load(Computed const &c) { + return c.sizing->height; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1977,6 +2387,10 @@ struct MinWidthProp { c.sizing.cow().minWidth = value; } + static Size load(Computed const &c) { + return c.sizing->minWidth; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -1996,6 +2410,10 @@ struct MinHeightProp { c.sizing.cow().minHeight = value; } + static Size load(Computed const &c) { + return c.sizing->minHeight; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -2015,6 +2433,10 @@ struct MaxWidthProp { c.sizing.cow().maxWidth = value; } + static Size load(Computed const &c) { + return c.sizing->maxWidth; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -2034,6 +2456,10 @@ struct MaxHeightProp { c.sizing.cow().maxHeight = value; } + static Size load(Computed const &c) { + return c.sizing->maxHeight; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -2056,6 +2482,10 @@ struct TextAlignProp { c.text.cow().align = value; } + static TextAlign load(Computed const &c) { + return c.text->align; + } + Res<> parse(Cursor &c) { if (c.skip(Css::Token::ident("left"))) { value = TextAlign::LEFT; @@ -2089,6 +2519,10 @@ struct TextTransformProp { c.text.cow().transform = value; } + static TextTransform load(Computed const &c) { + return c.text->transform; + } + Res<> parse(Cursor &c) { if (c.ended()) return Error::invalidData("unexpected end of input"); @@ -2120,6 +2554,10 @@ struct WhiteSpaceProp { c.text.cow().whiteSpace = value; } + static WhiteSpace load(Computed const &c) { + return c.text->whiteSpace; + } + Res<> parse(Cursor &c) { if (c.skip(Css::Token::ident("normal"))) { value = WhiteSpace::NORMAL; @@ -2154,6 +2592,10 @@ struct ZIndexProp { c.zIndex = value; } + static ZIndex load(Computed const &c) { + return c.zIndex; + } + Res<> parse(Cursor &c) { value = try$(parseValue(c)); return Ok(); @@ -2184,7 +2626,8 @@ struct CustomProp { } }; -// NOSPEC: This is a property that could not be parsed, it's used to store the value as is and apply it with the cascade and custom properties +// NOTE: A property that could not be parsed, it's used to store the value +// as-is and apply it with the cascade and custom properties struct DeferredProp { String propName; Css::Content value; @@ -2197,13 +2640,37 @@ struct DeferredProp { static void _expandContent(Cursor &c, Map const &env, Css::Content &out); - void apply(Computed &c) const; + // static void inherit(Computed const &parent, Computed &child) { + // child.variables = parent.variables; + // } + + void apply(Computed const &parent, Computed &c) const; void repr(Io::Emit &e) const { e("(Deffered {#} = {})", propName, value); } }; +enum struct Default { + INITIAL, //< represents the value defined as the property’s initial value. + INHERIT, //< represents the property’s computed value on the parent element. + UNSET, //< acts as either inherit or initial, depending on whether the property is inherited or not. + REVERT, //< rolls back the cascade to the cascaded value of the earlier origin. + + _LEN, +}; + +struct DefaultedProp { + String propName; + Default value; + + static constexpr Str name() { return "defaulted prop"; } + + void apply(Computed const &parent, Computed &c) const; + + void repr(Io::Emit &) const; +}; + // MARK: Style Property ------------------------------------------------------- using _StyleProp = Union< @@ -2344,8 +2811,9 @@ using _StyleProp = Union< ZIndexProp, // Other - CustomProp, // this is generally a variable declaration - DeferredProp + CustomProp, + DeferredProp, + DefaultedProp /**/ >; @@ -2379,10 +2847,13 @@ struct StyleProp : public _StyleProp { }); } - void apply(Computed &c) 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); }); } diff --git a/src/web/vaev-style/stylesheet.cpp b/src/web/vaev-style/stylesheet.cpp index cfece69a9d..678a20b979 100644 --- a/src/web/vaev-style/stylesheet.cpp +++ b/src/web/vaev-style/stylesheet.cpp @@ -22,19 +22,20 @@ void StyleSheet::repr(Io::Emit &e) const { e(")"); } -StyleSheet StyleSheet::parse(Css::Sst const &sst) { +StyleSheet StyleSheet::parse(Css::Sst const &sst, Origin origin) { if (sst != Css::Sst::LIST) panic("expected list"); Style::StyleSheet res; for (auto const &item : sst.content) { if (item == Css::Sst::RULE) { - res.rules.pushBack(Rule::parse(item)); + res.rules.pushBack(Rule::parse(item, origin)); } else { logWarn("unexpected item in stylesheet: {}", item.type); } } + res.origin = origin; return res; } diff --git a/src/web/vaev-style/stylesheet.h b/src/web/vaev-style/stylesheet.h index 887b695cd6..8713d1165a 100644 --- a/src/web/vaev-style/stylesheet.h +++ b/src/web/vaev-style/stylesheet.h @@ -12,15 +12,20 @@ struct StyleSheet { Mime::Url href = ""_url; Str title = ""; Vec rules; + Origin origin = Origin::AUTHOR; void repr(Io::Emit &e) const; - static StyleSheet parse(Css::Sst const &sst); + static StyleSheet parse(Css::Sst const &sst, Origin origin = Origin::AUTHOR); - static Style::StyleSheet parse(Io::SScan &s) { + static Style::StyleSheet parse(Io::SScan &s, Origin origin = Origin::AUTHOR) { Css::Lexer lex{s}; Css::Sst sst = consumeRuleList(lex, true); - return parse(sst); + return parse(sst, origin); + } + + void add(Rule &&rule) { + rules.pushBack(std::move(rule)); } }; diff --git a/src/web/vaev-style/tests/test-parse-selectors.cpp b/src/web/vaev-style/tests/test-parse-selectors.cpp index f39b92b08a..38ae97b8c1 100644 --- a/src/web/vaev-style/tests/test-parse-selectors.cpp +++ b/src/web/vaev-style/tests/test-parse-selectors.cpp @@ -117,6 +117,11 @@ test$("vaev-style-parse-infix-selectors") { ) ); + expectEq$( + Selector::parse(":not(.className)"), + Selector::not_(ClassSelector{"className"s}) + ); + return Ok(); } @@ -167,6 +172,40 @@ test$("vaev-style-parse-mixed-selectors") { ) ); + expectEq$( + Selector::parse(":not(:first-child)"), + Selector::not_(Pseudo{Pseudo::FIRST_CHILD}) + ); + + expectEq$( + Selector::parse("tr:not(:last-child) th:not(:first-child)"), + Selector::descendant( + Selector::and_({TypeSelector{Html::TR}, Selector::not_(Pseudo{Pseudo::LAST_CHILD})}), + Selector::and_({TypeSelector{Html::TH}, {Selector::not_(Pseudo{Pseudo::FIRST_CHILD})}}) + ) + ); + + expectEq$( + Selector::parse(".o_content .o_table > thead > tr:not(:last-child) th:not(:first-child)"), + Selector::descendant( + Selector::child( + Selector::descendant( + ClassSelector{"o_content"s}, + Selector::child(ClassSelector{"o_table"s}, TypeSelector{Html::THEAD}) + ), + Selector::and_({TypeSelector{Html::TR}, Selector::not_(Pseudo{Pseudo::LAST_CHILD})}) + ), + Selector::and_({TypeSelector{Html::TH}, {Selector::not_(Pseudo{Pseudo::FIRST_CHILD})}}) + ) + ); + + expectEq$( + Selector::parse(".o_content .o_table thead, .o_content .o_table tbody, .o_content .o_table tfoot, .o_content .o_table tr, .o_content .o_table td, .o_content .o_table th "), + Selector::descendant( + Selector::and_({TypeSelector{Html::TR}, Selector::not_(Pseudo{Pseudo::LAST_CHILD})}), + Selector::and_({TypeSelector{Html::TH}, {Selector::not_(Pseudo{Pseudo::FIRST_CHILD})}}) + ) + ); return Ok(); } @@ -186,6 +225,24 @@ test$("vaev-style-parse-pseudo-selectors") { Pseudo{Pseudo::make("root")} ); + expectEq$( + Selector::parse(":first-child"), + Pseudo{Pseudo::FIRST_CHILD} + ); + + expectEq$( + Selector::parse(":last-child"), + Pseudo{Pseudo::LAST_CHILD} + ); + + expectEq$( + Selector::parse(".class :last-child"), + Selector::descendant( + ClassSelector{"class"s}, + Pseudo{Pseudo::LAST_CHILD} + ) + ); + expectEq$( Selector::parse("html:hover"), Selector::and_({ diff --git a/src/web/vaev-style/tests/test-parse-value.cpp b/src/web/vaev-style/tests/test-parse-value.cpp index 3304e024ee..2cc01d2d33 100644 --- a/src/web/vaev-style/tests/test-parse-value.cpp +++ b/src/web/vaev-style/tests/test-parse-value.cpp @@ -76,7 +76,7 @@ test$("vaev-css-build-margin") { expect$(res); Computed c; - res.unwrap().apply(c); + res.unwrap().apply(c, c); expectEq$(Io::format("{}", *c.margin).unwrap(), Io::format("{}", expected).unwrap()); @@ -117,4 +117,5 @@ test$("vaev-css-build-margin") { return Ok(); } + } // namespace Vaev::Style::Tests diff --git a/src/web/vaev-tools/main.cpp b/src/web/vaev-tools/main.cpp index 5b64741fc3..a19e164c86 100644 --- a/src/web/vaev-tools/main.cpp +++ b/src/web/vaev-tools/main.cpp @@ -272,7 +272,7 @@ Res<> render(Mime::Url const &, Strong dom, Io::Writer &output paint->paint(g); g.end(); - try$(Image::save(image->pixels(), output)); + try$(Karm::Image::save(image->pixels(), output)); return Ok(); } diff --git a/src/web/vaev-tools/manifest.json b/src/web/vaev-tools/manifest.json index bcd3cba43d..04696381c6 100644 --- a/src/web/vaev-tools/manifest.json +++ b/src/web/vaev-tools/manifest.json @@ -7,7 +7,8 @@ "karm-cli", "karm-sys", "karm-image", - "vaev-driver" + "vaev-driver", + "fonts-all" ], "provides": [ "paper-muncher", diff --git a/src/web/vaev-view/view.cpp b/src/web/vaev-view/view.cpp index d7b1a3b74f..23d00033d1 100644 --- a/src/web/vaev-view/view.cpp +++ b/src/web/vaev-view/view.cpp @@ -68,8 +68,6 @@ struct View : public Ui::View { paint->paint(g, rect.offset(-bound().xy).cast()); if (Ui::debugShowLayoutBounds) { - logDebug("layout tree: {}", layout); - logDebug("paint tree: {}", paint); Layout::wireframe(*layout, g); }