Skip to content

Commit a4620b5

Browse files
committed
(wip) vaev-style: Implement css counters.
1 parent a866ea4 commit a4620b5

26 files changed

Lines changed: 1616 additions & 98 deletions

‎src/vaev-engine/dom/element.cpp‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import :dom.node;
77
import :dom.names;
88
import :dom.text;
99
import :dom.tokenList;
10+
import :style.counter;
1011

1112
using namespace Karm;
1213

@@ -26,11 +27,12 @@ export struct PseudoElement {
2627
static Symbol const AFTER;
2728
static Symbol const MARKER;
2829
Symbol type;
29-
30+
3031
// https://drafts.csswg.org/css-pseudo/#dom-csspseudoelement-parent
3132
Gc::Ptr<Element> parent;
32-
33+
3334
Opt<Rc<Style::ComputedValues>> _computedValues = NONE;
35+
Style::CounterSet counters = {};
3436

3537
PseudoElement(Symbol type, Rc<Style::ComputedValues> computedValues)
3638
: type(type), _computedValues(computedValues) {}
@@ -69,6 +71,7 @@ export struct Element : Node {
6971
TokenList classList;
7072
Opt<Rc<Scene::Node>> imageContent;
7173
Map<Symbol, Rc<PseudoElement>> _pseudoElements;
74+
Style::CounterSet counters;
7275

7376
// MARK: Node --------------------------------------------------------------
7477

‎src/vaev-engine/driver/print.cpp‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,6 @@ Pair<Vec<Layout::Breakpoint>, Vec<PageLayoutInfos>> collectBreakPointsAndRunning
186186

187187
export Yield<Print::Page> print(Gc::Ref<Dom::Document> dom, Print::Settings const& settings) {
188188
auto media = Style::Media::forPrint(settings);
189-
190189
Style::Computer computer{
191190
media,
192191
dom->registeredPropertySet,

‎src/vaev-engine/layout/base/box.cpp‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ struct Box : Meta::NoCopy {
130130
Box(Rc<Style::ComputedValues> style, Content content, Opt<Dom::OriginatingElement> og)
131131
: style{std::move(style)}, content{std::move(content)}, origin{og} {}
132132

133+
Box(Rc<Style::SpecifiedValues> style, Content content, Opt<Symbol> pseudoOrigin)
134+
: style{std::move(style)}, content{std::move(content)}, pseudoOrigin{pseudoOrigin} {}
135+
133136
Slice<Box> children() const {
134137
if (auto children = content.is<Vec<Box>>())
135138
return *children;

‎src/vaev-engine/layout/base/running-position.cpp‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,15 @@ struct RunningPositionMap {
4848
}
4949

5050
// https://www.w3.org/TR/css-gcpm-3/#using-named-strings
51-
Res<RunningPositionInfo> match(ElementContent elt, usize currentPage = 0) {
51+
Res<RunningPositionInfo> match(ElementFunc elt, usize currentPage = 0) {
5252
auto id = elt.customIdent;
5353
auto const& list = try$(content.lookup(id).okOr(Error::notFound("element not found")));
5454

5555
switch (elt.target) {
56-
case ElementContent::Target::UNDEFINED:
56+
case ElementFunc::Target::UNDEFINED:
5757
return Ok(list[0]);
5858

59-
case ElementContent::Target::START:
59+
case ElementFunc::Target::START:
6060
for (usize i = 0; i < list.len(); i++) {
6161
auto elt = list[i];
6262
if (elt.page == currentPage and i > 0) {
@@ -65,13 +65,13 @@ struct RunningPositionMap {
6565
}
6666
return Ok(list[0]);
6767

68-
case ElementContent::Target::FIRST:
69-
case ElementContent::Target::FIRST_EXCEPT: {
68+
case ElementFunc::Target::FIRST:
69+
case ElementFunc::Target::FIRST_EXCEPT: {
7070
auto elements = _searchPage(list, currentPage);
7171
return Ok(elements[0]);
7272
}
7373

74-
case ElementContent::Target::LAST: {
74+
case ElementFunc::Target::LAST: {
7575
auto elements = _searchPage(list, currentPage);
7676
return Ok(elements[elements.len() - 1]);
7777
}

‎src/vaev-engine/layout/block.cpp‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,13 +231,17 @@ struct BlockFormatingContext : FormatingContext {
231231
.margin = computeMargins(tree, c, childInput)
232232
};
233233

234-
if (not impliesRemovingFromFlow(c.style->position)) {
234+
if (not(impliesRemovingFromFlow(c.style->position) or c.pseudoOrigin == Dom::PseudoElement::MARKER)) {
235235
// TODO: collapsed margins for sibling elements
236236
blockSize += max(usedSpacings.margin.top, lastMarginBottom) - lastMarginBottom;
237237
}
238238

239239
childInput.position = input.position + Vec2Au{usedSpacings.margin.start, blockSize};
240240

241+
if (c.pseudoOrigin == Dom::PseudoElement::MARKER) {
242+
childInput.position.x -= computeIntrinsicContentSize(tree, c, IntrinsicSize::MAX_CONTENT).x;
243+
}
244+
241245
// HACK: Table Box mostly behaves like a block box, let's compute its capmin
242246
// and avoid duplicating the layout code
243247
if (c.style->display == Display::Internal::TABLE_BOX) {
@@ -250,7 +254,7 @@ struct BlockFormatingContext : FormatingContext {
250254
? layoutAndCommitBorderBox(tree, c, childInput, *input.fragment, usedSpacings)
251255
: layoutBorderBox(tree, c, childInput, usedSpacings);
252256

253-
if (not impliesRemovingFromFlow(c.style->position)) {
257+
if (not(impliesRemovingFromFlow(c.style->position) or c.pseudoOrigin == Dom::PseudoElement::MARKER)) {
254258
blockSize += output.size.y + usedSpacings.margin.bottom;
255259
lastMarginBottom = usedSpacings.margin.bottom;
256260
}

‎src/vaev-engine/layout/builder/mod.cpp‎

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ struct BuilderContext {
289289
};
290290

291291
static void _buildNode(BuilderContext bc, Gc::Ref<Dom::Node> node);
292-
static void _buildPseudoElement(BuilderContext bc, Rc<Dom::PseudoElement> pseudoElement);
292+
static void _buildPseudoElement(BuilderContext bc, Gc::Ref<Dom::Element> originatingElement, Rc<Dom::PseudoElement> pseudoElement);
293293

294294
// MARK: Build void/leaves ---------------------------------------------------------
295295

@@ -720,7 +720,7 @@ static void _buildTableBox(BuilderContext tableWrapperBc, Gc::Ref<Dom::Element>
720720
}
721721

722722
if (auto before = el->getPseudoElement(Dom::PseudoElement::BEFORE)) {
723-
_buildPseudoElement(tableWrapperBc, before.unwrap());
723+
_buildPseudoElement(tableWrapperBc, el, before.unwrap());
724724
}
725725

726726
// An anonymous table-row box must be generated around each sequence of consecutive children of a table-root
@@ -735,7 +735,7 @@ static void _buildTableBox(BuilderContext tableWrapperBc, Gc::Ref<Dom::Element>
735735
}
736736

737737
if (auto before = el->getPseudoElement(Dom::PseudoElement::AFTER)) {
738-
_buildPseudoElement(tableWrapperBc, before.unwrap());
738+
_buildPseudoElement(tableWrapperBc, el, before.unwrap());
739739
}
740740
}
741741

@@ -802,16 +802,22 @@ static void _innerDisplayDispatchCreationOfInlineLevelBox(BuilderContext bc, Gc:
802802

803803
static void _buildChildren(BuilderContext bc, Gc::Ref<Dom::Node> parent) {
804804
auto el = parent->is<Dom::Element>();
805+
if (el->specifiedValues()->display == Display::Item::YES) {
806+
if (auto marker = el ? el->getPseudoElement(Dom::PseudoElement::MARKER) : NONE) {
807+
_buildPseudoElement(bc, el.upgrade(), marker.unwrap());
808+
}
809+
}
810+
805811
if (auto before = el ? el->getPseudoElement(Dom::PseudoElement::BEFORE) : NONE) {
806-
_buildPseudoElement(bc, before.unwrap());
812+
_buildPseudoElement(bc, el.upgrade(), before.unwrap());
807813
}
808814

809815
for (auto child = parent->firstChild(); child; child = child->nextSibling()) {
810816
_buildNode(bc, *child);
811817
}
812818

813819
if (auto after = el ? el->getPseudoElement(Dom::PseudoElement::AFTER) : NONE) {
814-
_buildPseudoElement(bc, after.unwrap());
820+
_buildPseudoElement(bc, el.upgrade(), after.unwrap());
815821
}
816822
}
817823

@@ -885,18 +891,56 @@ export Box _buildBlockPseudoElement(Rc<Dom::PseudoElement> el) {
885891
static void _buildPseudoElement(BuilderContext bc, Rc<Dom::PseudoElement> pseudoElement) {
886892
auto style = pseudoElement->computedValues();
887893
auto display = style->display;
894+
888895
if (display == Display::NONE)
889896
return;
890897

891-
if (display == Display::INLINE or
892-
display == Display::CONTENTS) {
898+
bool isBeforeOrAfter = (pseudoElement->type == Dom::PseudoElement::BEFORE or pseudoElement->type == Dom::PseudoElement::AFTER);
899+
if (isBeforeOrAfter and (style->content.is<Keywords::Normal>() or style->content.is<Keywords::None>())) {
900+
return;
901+
}
902+
903+
auto generateInnerContent = [&](BuilderContext& innerBc) {
904+
if (pseudoElement->type == Dom::PseudoElement::MARKER and style->content.is<Keywords::Normal>()) {
905+
String marker = ""s;
906+
auto listStyleType = originatingElement->specifiedValues()->list->type;
907+
yap("list style type: {}", listStyleType);
908+
if (listStyleType == CustomIdent{"disc"_sym}) {
909+
marker = "\u2022 "s;
910+
} else if (listStyleType == CustomIdent{"circle"_sym}) {
911+
marker = "\u25E6 "s;
912+
} else if (listStyleType == CustomIdent{"square"_sym}) {
913+
marker = "\u25AA "s;
914+
} else if (listStyleType == CustomIdent{"decimal"_sym}) {
915+
marker = Io::format("{}. ", originatingElement->counters.innerMost(CustomIdent{"list-item"_sym}).v1);
916+
}
917+
_buildText(innerBc, marker.str(), style);
918+
} else if (style->content.is<String>()) {
919+
// TODO: Expand this to iterate over a Vector of content items (Strings, URLs, Counters)
920+
_buildText(innerBc, style->content.unwrap<String>().str(), style);
921+
}
922+
};
923+
924+
if (display == Display::INLINE or display == Display::CONTENTS) {
893925
bc.startInlineBox(_proseStyleFromStyle(*style, style->fontFace));
894-
if (auto maybeStr = style->content.is<String>())
895-
_buildText(bc, maybeStr->str(), style);
926+
generateInnerContent(bc);
896927
bc.endInlineBox();
897928
} else {
898929
bc.flushRootInlineBoxIntoAnonymousBox();
899-
bc.addToParentBox(_buildBlockPseudoElement(pseudoElement));
930+
931+
Box pseudoBox = {style, pseudoElement->type}; // Ensure Box constructor accepts pseudo type
932+
InlineBox rootInlineBox{_proseStyleFromStyle(*style, style->fontFace)};
933+
934+
BuilderContext pseudoBc = (display == Display::Inside::FLEX)
935+
? bc.toFlexContext(pseudoBox, rootInlineBox)
936+
: bc.toBlockContext(pseudoBox, rootInlineBox);
937+
938+
pseudoBc.startInlineBox(_proseStyleFromStyle(*style, style->fontFace));
939+
generateInnerContent(pseudoBc);
940+
pseudoBc.endInlineBox();
941+
942+
pseudoBc.finalizeParentBoxAndFlushInline();
943+
bc.addToParentBox(std::move(pseudoBox));
900944
}
901945
}
902946

@@ -947,22 +991,21 @@ export Box buildForPseudoElement(Rc<Dom::PseudoElement> el, usize currentPage, R
947991
auto prose = makeRc<Gfx::Prose>(proseStyle);
948992
prose->append(style->content.unwrap<String>().str());
949993
return Box{style, InlineBox{prose}, el};
950-
} else if (style->content.is<ElementContent>()) {
951-
auto elt = style->content.unwrap<ElementContent>();
994+
} else if (style->content.is<ElementFunc>()) {
995+
auto elt = style->content.unwrap<ElementFunc>();
952996
if (auto infos = runningPos.match(elt, currentPage)) {
953997
Box box = buildElement(infos.unwrap().element);
954998
box.style->position = Keywords::STATIC;
955999
return box;
9561000
}
957-
} else if (auto elt = style->content.is<Counter>()) {
958-
if (elt->type == Counter::Type::PAGE) {
959-
auto prose = makeRc<Gfx::Prose>(proseStyle);
960-
prose->append(Io::toStr(currentPage + 1).str());
961-
return Box{style, InlineBox{prose}, el};
962-
}
1001+
} else if (auto it = style->content.is<CounterFunc>()) {
1002+
auto prose = makeRc<Gfx::Prose>(proseStyle);
1003+
auto maybeCounter = el.counters.innerMost(it->name).v0;
1004+
prose->append("{}"_f(maybeCounter ? maybeCounter->value : 0).str());
1005+
return {style, InlineBox{prose}, nullptr};
9631006
}
9641007

965-
return {style, el};
1008+
return {style, nullptr};
9661009
}
9671010

9681011
} // namespace Vaev::Layout

‎src/vaev-engine/props/base.cpp‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ using namespace Karm;
1414

1515
namespace Vaev::Style {
1616

17-
static auto debugProperties = Debug::Flag::debug("css-properties", "Print debug information about CSS properties");
17+
static auto debugProperties = Debug::Flag::debug("css-properties", "Print debug information about CSS properties", true);
1818

1919
namespace Properties {
2020

@@ -40,7 +40,8 @@ export struct Property : Meta::NoCopy {
4040
// https://svgwg.org/svg2-draft/styling.html#PresentationAttributes
4141
PRESENTATION_ATTRIBUTE = 1 << 3,
4242

43-
// Represent a miss-parsed property
43+
/// Indicates that the property represents an invalid or unrecognized, CSS declaration.
44+
/// Bogus properties are ignored during the cascade and do not affect computed styles.
4445
BOGUS_REGISTRATION = 1 << 4,
4546

4647
// The property is not reset by all:
@@ -50,6 +51,12 @@ export struct Property : Meta::NoCopy {
5051

5152
using enum Options;
5253

54+
/// Defines the metadata, lifecycle, and parsing rules for a specific CSS property.
55+
///
56+
/// The `Registration` acts as a singleton-like factory and behavior contract
57+
/// for all instances of a given property. It handles parsing raw CSS tokens
58+
/// into a concrete `Property` instance and dictates how the property interacts
59+
/// with the cascade (inheritance, initial values).
5360
// https://drafts.css-houdini.org/css-properties-values-api/#custom-property-registration
5461
struct Registration : Meta::NoCopy {
5562
Opt<Weak<Registration>> _self;
@@ -63,6 +70,7 @@ export struct Property : Meta::NoCopy {
6370

6471
virtual ~Registration() = default;
6572

73+
/// Returns the canonical CSS identifier for this property (e.g., `color`, `margin-top`).
6674
virtual Symbol name() const = 0;
6775

6876
// https://drafts.csswg.org/css-cascade-4/#legacy-name-alias
@@ -74,6 +82,7 @@ export struct Property : Meta::NoCopy {
7482
return {};
7583
}
7684

85+
// https://drafts.csswg.org/css-cascade/#initial-value
7786
virtual Rc<Property> initial() const = 0;
7887

7988
virtual Rc<Property> load(ComputedValues const& c) const = 0;
@@ -83,7 +92,7 @@ export struct Property : Meta::NoCopy {
8392
// commonly inherited. Any property marked with the INHERITED
8493
// flag should override this method with a faster implementation.
8594
if (flags().has(INHERITED))
86-
logFatal("property {#} marked as INHERITED is using the slow fallback path. override inherit()!", name());
95+
logWarn("property {#} marked as INHERITED is using the slow fallback path. override inherit()!", name());
8796
load(parent)->apply(child);
8897
}
8998

@@ -109,7 +118,7 @@ export struct Property : Meta::NoCopy {
109118

110119
virtual Vec<Rc<Property>> expandShorthand(RegisteredPropertySet&, [[maybe_unused]] ComputedValues const& parent, [[maybe_unused]] ComputedValues& child) const {
111120
if (isBogusProperty())
112-
logFatal("trying to expand {#} as a bug property");
121+
logFatal("trying to expand {#} which is a bogus property");
113122

114123
if (isShorthandProperty())
115124
logFatal("shorthand property {#} is missing expandShorthand() implementation", registration->name());
@@ -151,6 +160,7 @@ export struct Property : Meta::NoCopy {
151160
return false;
152161
}
153162

163+
/// Determines whether this property instance represents a parsing failure or an unrecognized CSS declaration.
154164
virtual bool isBogusProperty() const {
155165
return registration->flags().has(BOGUS_REGISTRATION);
156166
}
@@ -263,8 +273,8 @@ struct ToggleProperty : Property {
263273

264274
// MARK: Deferred Property -----------------------------------------------------
265275

266-
// NOTE: A property that could not be parsed, it's used to store the value
267-
// as-is and apply it with the cascade and custom properties
276+
/// A property that could not be parsed, it's used to store the value
277+
/// as-is and apply it with the cascade and custom properties
268278
struct DeferredProperty : Property {
269279
Css::Content _value;
270280

0 commit comments

Comments
 (0)