Skip to content

Commit f45c381

Browse files
committed
vaev-engine: Initial implementation of pseudo elements.
1 parent ff7582f commit f45c381

File tree

17 files changed

+824
-492
lines changed

17 files changed

+824
-492
lines changed

src/vaev-engine/dom/element.cpp

Lines changed: 71 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import :dom.attr;
66
import :dom.node;
77
import :dom.names;
88
import :dom.text;
9-
import :dom.token_list;
9+
import :dom.tokenList;
1010

1111
using namespace Karm;
1212

@@ -17,14 +17,25 @@ export struct SpecifiedValues;
1717

1818
namespace Vaev::Dom {
1919

20+
// https://drafts.csswg.org/css-pseudo/#CSSPseudoElement-interface
2021
export struct PseudoElement {
22+
// https://drafts.csswg.org/css-pseudo/#dom-csspseudoelement-type
23+
static Symbol const BEFORE;
24+
static Symbol const AFTER;
25+
static Symbol const MARKER;
26+
27+
Symbol type;
2128
Opt<Rc<Style::SpecifiedValues>> _specifiedValues = NONE;
2229

2330
Rc<Style::SpecifiedValues> specifiedValues() const {
2431
return _specifiedValues.unwrap("unstyled pseudo-element");
2532
}
2633
};
2734

35+
Symbol const PseudoElement::BEFORE = "::before"_sym;
36+
Symbol const PseudoElement::AFTER = "::after"_sym;
37+
Symbol const PseudoElement::MARKER = "::marker"_sym;
38+
2839
// https://dom.spec.whatwg.org/#interface-element
2940
export struct Element : Node {
3041
static constexpr auto TYPE = NodeType::ELEMENT;
@@ -35,45 +46,18 @@ export struct Element : Node {
3546
Opt<Rc<Style::SpecifiedValues>> _specifiedValues; // FIXME: We should not have this store here
3647
TokenList classList;
3748
Opt<Rc<Scene::Node>> imageContent;
49+
Map<Symbol, Rc<PseudoElement>> _pseudoElements;
50+
51+
// MARK: Node --------------------------------------------------------------
3852

3953
Element(QualifiedName const& qualifiedName)
4054
: qualifiedName(qualifiedName) {
4155
}
4256

43-
Symbol namespaceUri() const {
44-
return qualifiedName.ns;
45-
}
46-
47-
Symbol localName() const {
48-
return qualifiedName.name;
49-
}
50-
5157
NodeType nodeType() const override {
5258
return TYPE;
5359
}
5460

55-
Rc<Style::SpecifiedValues> specifiedValues() const {
56-
return _specifiedValues.unwrap("unstyled element");
57-
}
58-
59-
Opt<Str> id() const {
60-
return this->getAttributeUnqualified("id"_sym);
61-
}
62-
63-
Opt<Str> style() const {
64-
return this->getAttributeUnqualified("style"_sym);
65-
}
66-
67-
// https://dom.spec.whatwg.org/#concept-descendant-text-content
68-
String textContent() {
69-
StringBuilder sb;
70-
for (auto child : iterDepthFirst()) {
71-
if (auto text = child->is<Text>())
72-
sb.append(text->data());
73-
}
74-
return sb.take();
75-
}
76-
7761
void _repr(Io::Emit& e) const override {
7862
e(" qualifiedName={}", qualifiedName);
7963
if (this->attributes.len()) {
@@ -85,6 +69,26 @@ export struct Element : Node {
8569
}
8670
}
8771

72+
// MARK: Name --------------------------------------------------------------
73+
74+
Symbol namespaceUri() const {
75+
return qualifiedName.ns;
76+
}
77+
78+
Symbol localName() const {
79+
return qualifiedName.name;
80+
}
81+
82+
// MARK: Attributes --------------------------------------------------------
83+
84+
Opt<Str> id() const {
85+
return getAttributeUnqualified("id"_sym);
86+
}
87+
88+
Opt<Str> style() const {
89+
return getAttributeUnqualified("style"_sym);
90+
}
91+
8892
void setAttribute(QualifiedName name, String value) {
8993
if (name == Html::CLASS_ATTR or name == Svg::CLASS_ATTR) {
9094
for (auto class_ : iterSplit(value, ' ')) {
@@ -121,6 +125,42 @@ export struct Element : Node {
121125
return attr->value;
122126
return NONE;
123127
}
128+
129+
// MARK: Style -------------------------------------------------------------
130+
131+
Rc<Style::SpecifiedValues> specifiedValues() const {
132+
return _specifiedValues.unwrap("unstyled element");
133+
}
134+
135+
// MARK: Content -----------------------------------------------------------
136+
137+
// https://dom.spec.whatwg.org/#concept-descendant-text-content
138+
String textContent() {
139+
StringBuilder sb;
140+
for (auto child : iterDepthFirst()) {
141+
if (auto text = child->is<Text>())
142+
sb.append(text->data());
143+
}
144+
return sb.take();
145+
}
146+
147+
// MARK: Pseudo Elements ---------------------------------------------------
148+
149+
void clearPseudoElement() {
150+
_pseudoElements.clear();
151+
}
152+
153+
bool hasPseudoElement(Symbol type) const {
154+
return _pseudoElements.has(type);
155+
}
156+
157+
void addPseudoElement(Rc<PseudoElement> pseudoElement) {
158+
_pseudoElements.put(pseudoElement->type, pseudoElement);
159+
}
160+
161+
Opt<Rc<PseudoElement>> getPseudoElement(Symbol type) const {
162+
return _pseudoElements.tryGet(type);
163+
}
124164
};
125165

126166
} // namespace Vaev::Dom

src/vaev-engine/dom/mod.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ export import :dom.node;
1010
export import :dom.names;
1111
export import :dom.node;
1212
export import :dom.text;
13-
export import :dom.token_list;
13+
export import :dom.tokenList;
1414
export import :dom.tree;
1515
export import :dom.window;

src/vaev-engine/dom/token-list.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export module Vaev.Engine:dom.token_list;
1+
export module Vaev.Engine:dom.tokenList;
22

33
import Karm.Core;
44

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

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,15 @@ void _appendTextToInlineBox(Io::SScan scan, Rc<Style::SpecifiedValues> parentSty
140140
}
141141
}
142142

143-
bool _buildText(Gc::Ref<Dom::Text> node, Rc<Style::SpecifiedValues> parentStyle, InlineBox& rootInlineBox, bool skipIfWhitespace) {
143+
bool _buildText(Str text, Rc<Style::SpecifiedValues> parentStyle, InlineBox& rootInlineBox, bool skipIfWhitespace) {
144144
if (skipIfWhitespace) {
145-
Io::SScan scan{node->data()};
145+
Io::SScan scan{text};
146146
scan.eat(Re::space());
147147
if (scan.ended())
148148
return false;
149149
}
150150

151-
_appendTextToInlineBox(node->data(), parentStyle, rootInlineBox);
151+
_appendTextToInlineBox(text, parentStyle, rootInlineBox);
152152
return true;
153153
}
154154

@@ -170,7 +170,7 @@ struct BuilderContext {
170170

171171
// https://www.w3.org/TR/css-inline-3/#model
172172
void flushRootInlineBoxIntoAnonymousBox() {
173-
if (not rootInlineBox().active())
173+
if (not _rootInlineBox or not rootInlineBox().active())
174174
return;
175175

176176
// The root inline box inherits from its parent block container, but is otherwise unstyleable.
@@ -289,11 +289,12 @@ 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);
292293

293294
// MARK: Build void/leaves ---------------------------------------------------------
294295

295296
// https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace#how_does_css_process_whitespace/
296-
static void _buildText(BuilderContext bc, Gc::Ref<Dom::Text> node, Rc<Style::SpecifiedValues> parentStyle) {
297+
static void _buildText(BuilderContext bc, Str text, Rc<Style::SpecifiedValues> parentStyle) {
297298
// https://www.w3.org/TR/css-tables-3/#fixup-algorithm
298299
// TODO: For tables, the default case is to skip whitespace text, but there are some extra checks to be done
299300

@@ -307,7 +308,7 @@ static void _buildText(BuilderContext bc, Gc::Ref<Dom::Text> node, Rc<Style::Spe
307308
bc.from == BuilderContext::From::FLEX or
308309
bc.from == BuilderContext::From::BLOCK;
309310

310-
bool addedNonWhitespace = _buildText(node, parentStyle, bc.rootInlineBox(), shouldSkipWhitespace);
311+
bool addedNonWhitespace = _buildText(text, parentStyle, bc.rootInlineBox(), shouldSkipWhitespace);
311312

312313
// https://www.w3.org/TR/css-flexbox-1/#algo-anon-box
313314
// https://www.w3.org/TR/css-flexbox-1/#flex-items
@@ -428,7 +429,7 @@ static void createAndBuildInlineFlowfromElement(BuilderContext bc, Rc<Style::Spe
428429
bc.flushRootInlineBoxIntoAnonymousBox();
429430
return;
430431
}
431-
432+
432433
if (isVoidElement(el)) {
433434
_buildVoidElement(bc, el);
434435
return;
@@ -588,7 +589,7 @@ static void _buildTableChildrenWhileWrappingIntoAnonymousBox(BuilderContext bc,
588589
if (bc.parentStyle->display != Display::Internal::TABLE_ROW)
589590
anonTableWrapper.createRowIfNone(style);
590591
anonTableWrapper.createCellIfNone(style);
591-
_buildText(anonTableWrapper.cellBuilderContext(), *text, style);
592+
_buildText(anonTableWrapper.cellBuilderContext(), text->data(), style);
592593
}
593594
}
594595

@@ -668,6 +669,10 @@ static void _buildTableBox(BuilderContext tableWrapperBc, Gc::Ref<Dom::Element>
668669
searchAndBuildCaption();
669670
}
670671

672+
if (auto before = el->getPseudoElement(Dom::PseudoElement::BEFORE)) {
673+
_buildPseudoElement(tableWrapperBc, before.unwrap());
674+
}
675+
671676
// An anonymous table-row box must be generated around each sequence of consecutive children of a table-root
672677
// box which are not proper table child boxes.
673678
_buildTableChildrenWhileWrappingIntoAnonymousBox(tableWrapperBc.toTableContent(tableBox), *el, tableBoxStyle, true, [](Display const& display) {
@@ -678,6 +683,10 @@ static void _buildTableBox(BuilderContext tableWrapperBc, Gc::Ref<Dom::Element>
678683
if (not captionsOnTop) {
679684
searchAndBuildCaption();
680685
}
686+
687+
if (auto before = el->getPseudoElement(Dom::PseudoElement::AFTER)) {
688+
_buildPseudoElement(tableWrapperBc, before.unwrap());
689+
}
681690
}
682691

683692
static Box _createTableWrapperAndBuildTable(BuilderContext bc, Rc<Style::SpecifiedValues> tableStyle, Gc::Ref<Dom::Element> tableBoxEl) {
@@ -742,9 +751,18 @@ static void _innerDisplayDispatchCreationOfInlineLevelBox(BuilderContext bc, Gc:
742751
// MARK: Dispatching from Node to builder based on outside role ------------------------------------------------------
743752

744753
static void _buildChildren(BuilderContext bc, Gc::Ref<Dom::Node> parent) {
754+
auto el = parent->is<Dom::Element>();
755+
if (auto before = el ? el->getPseudoElement(Dom::PseudoElement::BEFORE) : NONE) {
756+
_buildPseudoElement(bc, before.unwrap());
757+
}
758+
745759
for (auto child = parent->firstChild(); child; child = child->nextSibling()) {
746760
_buildNode(bc, *child);
747761
}
762+
763+
if (auto after = el ? el->getPseudoElement(Dom::PseudoElement::AFTER) : NONE) {
764+
_buildPseudoElement(bc, after.unwrap());
765+
}
748766
}
749767

750768
static void _buildChildBoxDisplay(BuilderContext bc, Gc::Ref<Dom::Node> node, Display display) {
@@ -797,7 +815,36 @@ static void _buildNode(BuilderContext bc, Gc::Ref<Dom::Node> node) {
797815
_buildChildDefaultDisplay(bc, *el, childStyle, display);
798816
}
799817
} else if (auto text = node->is<Dom::Text>()) {
800-
_buildText(bc, *text, bc.parentStyle);
818+
_buildText(bc, text->data(), bc.parentStyle);
819+
}
820+
}
821+
822+
export Box _buildBlockPseudoElement(Dom::PseudoElement& el) {
823+
auto proseStyle = _proseStyleFromStyle(*el.specifiedValues(), el.specifiedValues()->fontFace);
824+
825+
if (el.specifiedValues()->content.is<String>()) {
826+
auto prose = makeRc<Gfx::Prose>(proseStyle);
827+
prose->append(el.specifiedValues()->content.unwrap<String>().str());
828+
return {el.specifiedValues(), InlineBox{prose}, nullptr};
829+
}
830+
831+
return {el.specifiedValues(), nullptr};
832+
}
833+
834+
static void _buildPseudoElement(BuilderContext bc, Rc<Dom::PseudoElement> pseudoElement) {
835+
auto style = pseudoElement->specifiedValues();
836+
auto display = style->display;
837+
if (display == Display::NONE)
838+
return;
839+
840+
if (display == Display::INLINE) {
841+
bc.startInlineBox(_proseStyleFromStyle(*style, style->fontFace));
842+
if (auto maybeStr = style->content.is<String>())
843+
_buildText(bc, maybeStr->str(), style);
844+
bc.endInlineBox();
845+
} else {
846+
bc.flushRootInlineBoxIntoAnonymousBox();
847+
bc.addToParentBox(_buildBlockPseudoElement(*pseudoElement));
801848
}
802849
}
803850

@@ -852,7 +899,6 @@ export Box buildForPseudoElement(Dom::PseudoElement& el, usize currentPage, Runn
852899

853900
if (el.specifiedValues()->content.is<String>()) {
854901
auto prose = makeRc<Gfx::Prose>(proseStyle);
855-
856902
prose->append(el.specifiedValues()->content.unwrap<String>().str());
857903
return {el.specifiedValues(), InlineBox{prose}, nullptr};
858904
} else if (el.specifiedValues()->content.is<ElementContent>()) {

0 commit comments

Comments
 (0)