Skip to content

Commit 6f1e085

Browse files
committed
vaev-layout: Add SVG support: shapes, foreign HTML/CSS content, nested SVG.
1 parent a2c63a3 commit 6f1e085

23 files changed

+1678
-34
lines changed

src/vaev-dom/defs/ns-svg-attr-names.inc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ ATTR(ATTRIBUTE_TYPE, attributeType)
55
ATTR(BASE_FREQUENCY, baseFrequency)
66
ATTR(BASE_PROFILE, baseProfile)
77
ATTR(CALC_MODE, calcMode)
8+
ATTR(CLASS, class)
89
ATTR(CLIP_PATH_UNITS, clipPathUnits)
910
ATTR(CONTENT_SCRIPT_TYPE, contentScriptType)
1011
ATTR(CONTENT_STYLE_TYPE, contentStyleType)
1112
ATTR(CX, cx)
1213
ATTR(CY, cy)
14+
ATTR(D, d)
1315
ATTR(DX, dx)
1416
ATTR(DY, dy)
1517
ATTR(DIFFUSE_CONSTANT, diffuseConstant)
1618
ATTR(EDGE_MODE, edgeMode)
19+
ATTR(FILL, fill)
20+
ATTR(FILL_OPACITY, fill-opacity)
1721
ATTR(FILTER_UNITS, filterUnits)
1822
ATTR(FR, fr)
1923
ATTR(FX, fx)
@@ -23,6 +27,7 @@ ATTR(GRADIENT_TRANSFORM, gradientTransform)
2327
ATTR(GRADIENT_UNITS, gradientUnits)
2428
ATTR(HEIGHT, height)
2529
ATTR(HREF, href)
30+
ATTR(ID, id)
2631
ATTR(KERNEL_MATRIX, kernelMatrix)
2732
ATTR(KERNEL_UNIT_LENGTH, kernelUnitLength)
2833
ATTR(KEY_POINTS, keyPoints)
@@ -64,6 +69,9 @@ ATTR(SPREAD_METHOD, spreadMethod)
6469
ATTR(START_OFFSET, startOffset)
6570
ATTR(STD_DEVIATION, stdDeviation)
6671
ATTR(STITCH_TILES, stitchTiles)
72+
ATTR(STYLE, style)
73+
ATTR(STROKE, stroke)
74+
ATTR(STROKE_WIDTH, stroke-width)
6775
ATTR(SURFACE_SCALE, surfaceScale)
6876
ATTR(SYSTEM_LANGUAGE, systemLanguage)
6977
ATTR(TABLE_VALUES, tableValues)

src/vaev-dom/element.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,21 @@ struct Element : Node {
1313
static constexpr auto TYPE = NodeType::ELEMENT;
1414

1515
Opt<Str> id() const {
16-
return this->getAttribute(Html::ID_ATTR);
16+
if (tagName.ns == HTML)
17+
return this->getAttribute(Html::ID_ATTR);
18+
else if (tagName.ns == SVG)
19+
return this->getAttribute(Svg::ID_ATTR);
20+
else
21+
return NONE;
22+
}
23+
24+
Opt<Str> style() const {
25+
if (tagName.ns == HTML)
26+
return this->getAttribute(Html::STYLE_ATTR);
27+
else if (tagName.ns == SVG)
28+
return this->getAttribute(Svg::STYLE_ATTR);
29+
else
30+
return NONE;
1731
}
1832

1933
TagName tagName;
@@ -55,7 +69,7 @@ struct Element : Node {
5569
}
5670

5771
void setAttribute(AttrName name, String value) {
58-
if (name == Html::CLASS_ATTR) {
72+
if (name == Html::CLASS_ATTR or name == Svg::CLASS_ATTR) {
5973
for (auto class_ : iterSplit(value, ' ')) {
6074
this->classList.add(class_);
6175
}

src/vaev-layout/base.cpp

Lines changed: 122 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module;
66
#include <vaev-style/computer.h>
77

88
export module Vaev.Layout:base;
9+
import :svg;
910

1011
namespace Vaev::Layout {
1112

@@ -285,11 +286,39 @@ export struct InlineBox {
285286
}
286287
};
287288

289+
struct SVGRoot {
290+
using Element = Union<SVG::Shape, SVGRoot, ::Box<Box>>;
291+
Vec<Element> elements;
292+
293+
Opt<ViewBox> viewBox;
294+
Rc<Style::SpecifiedStyle> style;
295+
296+
void add(Element&& element) {
297+
elements.pushBack(std::move(element));
298+
}
299+
300+
void add(Box&& box);
301+
302+
SVGRoot(Rc<Style::SpecifiedStyle> style)
303+
: viewBox(style->svg->viewBox), style(style) {}
304+
305+
void repr(Io::Emit& e) const {
306+
e("(SVG {} viewBox:{}", SVG::buildRectangle(*style), viewBox);
307+
e.indentNewline();
308+
for (auto const& el : elements) {
309+
e("{}", el);
310+
e.newline();
311+
}
312+
e(")");
313+
}
314+
};
315+
288316
export using Content = Union<
289317
None,
290318
Vec<Box>,
291319
InlineBox,
292-
Karm::Image::Picture>;
320+
Karm::Image::Picture,
321+
SVGRoot>;
293322

294323
export struct Attrs {
295324
usize span = 1;
@@ -337,28 +366,36 @@ struct Box : Meta::NoCopy {
337366
}
338367
}
339368

369+
bool isReplaced() {
370+
return content.is<Karm::Image::Picture>() or content.is<SVGRoot>();
371+
}
372+
340373
void repr(Io::Emit& e) const {
374+
e("(box {} {} {}", attrs, style->display, style->position);
341375
if (children()) {
342-
e("(box {} {} {}", attrs, style->display, style->position);
343376
e.indentNewline();
344377
for (auto& c : children()) {
345378
c.repr(e);
346379
e.newline();
347380
}
348381
e.deindent();
349-
e(")");
350382
} else if (content.is<InlineBox>()) {
351-
e("(box {} {} {}", attrs, style->display, style->position);
352383
e.indentNewline();
353384
e("{}", content.unwrap<InlineBox>());
354385
e.deindent();
355-
e(")");
356-
} else {
357-
e("(box {} {} {})", attrs, style->display, style->position);
386+
} else if (content.is<SVGRoot>()) {
387+
e.indentNewline();
388+
e("{}", content.unwrap<SVGRoot>());
389+
e.deindent();
358390
}
391+
e(")");
359392
}
360393
};
361394

395+
void SVGRoot::add(Box&& box) {
396+
add(makeBox<Box>(std::move(box)));
397+
}
398+
362399
void InlineBox::add(Box&& b) {
363400
prose->append(Text::Prose::StrutCell{atomicBoxes.len()});
364401
atomicBoxes.pushBack(makeBox<Box>(std::move(b)));
@@ -414,10 +451,51 @@ export struct Metrics {
414451
}
415452
};
416453

454+
export struct Frag;
455+
456+
struct SVGRootFrag {
457+
using Element = Union<SVG::ShapeFrag, SVGRootFrag, ::Box<Frag>>;
458+
Vec<Element> elements = {};
459+
460+
// NOTE: SVG viewports have these intrinsic transformations; choosing to store these transforms is more compliant
461+
// and somewhat rendering-friendly but makes it harder to debug
462+
Math::Trans2f transf;
463+
SVG::Rectangle<Au> boundingBox;
464+
465+
static SVGRootFrag build(SVGRoot const& box, Vec2Au position, Vec2Au viewportSize) {
466+
SVG::Rectangle<Karm::Au> rect{position.x, position.y, viewportSize.x, viewportSize.y};
467+
468+
Math::Trans2f transf =
469+
box.viewBox ? SVG::computeEquivalentTransformOfSVGViewport(*box.viewBox, position, viewportSize)
470+
: Math::Trans2f::translate(position.cast<f64>());
471+
472+
return {{}, transf, rect};
473+
}
474+
475+
void add(Element&& el) {
476+
elements.pushBack(std::move(el));
477+
}
478+
479+
void repr(Io::Emit& e) const {
480+
e("(SVGRootFrag)");
481+
}
482+
483+
void offsetBoxFrags(Vec2Au d);
484+
485+
void offset(Vec2Au d) {
486+
transf = transf.translated(d.cast<f64>());
487+
offsetBoxFrags(d);
488+
}
489+
};
490+
491+
export using FragContent = Union<
492+
Vec<Frag>,
493+
SVGRootFrag>;
494+
417495
export struct Frag {
418496
MutCursor<Box> box;
419497
Metrics metrics;
420-
Vec<Frag> children;
498+
FragContent content = Vec<Frag>{};
421499

422500
Frag(MutCursor<Box> box) : box{std::move(box)} {}
423501

@@ -430,16 +508,40 @@ export struct Frag {
430508
/// Offset the position of this fragment and its subtree.
431509
void offset(Vec2Au d) {
432510
metrics.position = metrics.position + d;
433-
for (auto& c : children)
434-
c.offset(d);
511+
512+
if (auto children = content.is<Vec<Frag>>()) {
513+
for (auto& c : *children)
514+
c.offset(d);
515+
} else if (auto svg = content.is<SVGRootFrag>()) {
516+
svg->offset(d);
517+
}
518+
}
519+
520+
MutSlice<Frag> children() {
521+
if (auto children = content.is<Vec<Frag>>()) {
522+
return *children;
523+
}
524+
return {};
435525
}
436526

437527
/// Add a child fragment.
438528
void add(Frag&& frag) {
439-
children.pushBack(std::move(frag));
529+
if (auto children = content.is<Vec<Frag>>()) {
530+
children->pushBack(std::move(frag));
531+
}
440532
}
441533
};
442534

535+
void SVGRootFrag::offsetBoxFrags(Vec2Au d) {
536+
for (auto& element : elements) {
537+
if (auto frag = element.is<::Box<Frag>>()) {
538+
(*frag)->offset(d);
539+
} else if (auto nestedRoot = element.is<SVGRootFrag>()) {
540+
nestedRoot->offsetBoxFrags(d);
541+
}
542+
}
543+
}
544+
443545
// MARK: Input & Output --------------------------------------------------------
444546

445547
export enum struct IntrinsicSize {
@@ -532,6 +634,15 @@ export struct BaselinePositionsSet {
532634
Au xMiddle;
533635
Au capHeight;
534636

637+
static BaselinePositionsSet fromSinglePosition(Au pos) {
638+
return {
639+
pos,
640+
pos,
641+
pos,
642+
pos,
643+
};
644+
}
645+
535646
BaselinePositionsSet translate(Au delta) const {
536647
return {
537648
alphabetic + delta,

src/vaev-layout/builder.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module;
99
export module Vaev.Layout:builder;
1010

1111
import :values;
12+
import :svg;
1213

1314
namespace Vaev::Layout {
1415

@@ -425,6 +426,58 @@ static void _buildInputProse(BuilderContext bc, Gc::Ref<Dom::Element> el) {
425426
bc.content() = InlineBox{prose};
426427
}
427428

429+
void buildSVGChildren(Style::Computer& computer, Rc<Style::SpecifiedStyle> parentStyle, Gc::Ref<Dom::Element> el, SVGRoot& svgRoot);
430+
static void buildBlockFlowFromElement(BuilderContext bc, Gc::Ref<Dom::Element> el);
431+
432+
void buildSVGElement(Style::Computer& computer, Rc<Style::SpecifiedStyle> parentStyle, Gc::Ref<Dom::Element> el, SVGRoot& svgRoot) {
433+
auto style = computer.computeFor(*parentStyle, *el);
434+
435+
if (SVG::isShape(el->tagName)) {
436+
svgRoot.add(SVG::Shape::build(style, el->tagName));
437+
} else if (el->tagName == Svg::G) {
438+
buildSVGChildren(computer, style, el, svgRoot);
439+
} else if (el->tagName == Svg::SVG) {
440+
SVGRoot newSvgRoot{style};
441+
buildSVGChildren(computer, style, el, newSvgRoot);
442+
svgRoot.add(std::move(newSvgRoot));
443+
} else if (el->tagName == Svg::FOREIGN_OBJECT) {
444+
auto font = _lookupFontface(computer.fontBook, *style);
445+
446+
Box box = {style, font, el};
447+
InlineBox rootInlineBox{_proseStyleFomStyle(computer, *style)};
448+
449+
BuilderContext bc{
450+
computer,
451+
BuilderContext::From::BLOCK,
452+
style,
453+
box,
454+
&rootInlineBox,
455+
};
456+
457+
buildBlockFlowFromElement(bc, *el);
458+
459+
svgRoot.add(std::move(box));
460+
} else {
461+
// TODO
462+
logWarn("cannot build element into svg tree: {}", el->tagName);
463+
}
464+
}
465+
466+
void buildSVGChildren(Style::Computer& computer, Rc<Style::SpecifiedStyle> parentStyle, Gc::Ref<Dom::Element> el, SVGRoot& svgRoot) {
467+
for (auto child = el->firstChild(); child; child = child->nextSibling()) {
468+
if (auto el = child->is<Dom::Element>()) {
469+
buildSVGElement(computer, parentStyle, *el, svgRoot);
470+
}
471+
// TODO: process text into svg tree
472+
}
473+
}
474+
475+
SVGRoot _buildSVG(Style::Computer& computer, Gc::Ref<Dom::Element> el, Rc<Style::SpecifiedStyle> rootStyle) {
476+
SVGRoot svgRoot{rootStyle};
477+
buildSVGChildren(computer, rootStyle, el, svgRoot);
478+
return svgRoot;
479+
}
480+
428481
always_inline static bool isVoidElement(Gc::Ref<Dom::Element> el) {
429482
return contains(Html::VOID_TAGS, el->tagName);
430483
}
@@ -471,6 +524,8 @@ static void createAndBuildInlineFlowfromElement(BuilderContext bc, Rc<Style::Spe
471524
static void buildBlockFlowFromElement(BuilderContext bc, Gc::Ref<Dom::Element> el) {
472525
if (el->tagName == Html::BR) {
473526
// do nothing
527+
} else if (el->tagName == Svg::SVG) {
528+
bc.content() = _buildSVG(bc.computer, el, bc.parentStyle);
474529
} else if (isVoidElement(el)) {
475530
_buildVoidElement(bc, el);
476531
} else {

0 commit comments

Comments
 (0)