Skip to content

Commit 986e1d6

Browse files
pauloamedsleepy-monax
authored andcommitted
vaev-layout: Add SVG support: shapes, foreign HTML/CSS content, nested SVG.
1 parent d50e142 commit 986e1d6

23 files changed

+1686
-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
@@ -31,7 +31,21 @@ struct Element : Node {
3131
static constexpr auto TYPE = NodeType::ELEMENT;
3232

3333
Opt<Str> id() const {
34-
return this->getAttribute(Html::ID_ATTR);
34+
if (tagName.ns == HTML)
35+
return this->getAttribute(Html::ID_ATTR);
36+
else if (tagName.ns == SVG)
37+
return this->getAttribute(Svg::ID_ATTR);
38+
else
39+
return NONE;
40+
}
41+
42+
Opt<Str> style() const {
43+
if (tagName.ns == HTML)
44+
return this->getAttribute(Html::STYLE_ATTR);
45+
else if (tagName.ns == SVG)
46+
return this->getAttribute(Svg::STYLE_ATTR);
47+
else
48+
return NONE;
3549
}
3650

3751
TagName tagName;
@@ -83,7 +97,7 @@ struct Element : Node {
8397
}
8498

8599
void setAttribute(AttrName name, String value) {
86-
if (name == Html::CLASS_ATTR) {
100+
if (name == Html::CLASS_ATTR or name == Svg::CLASS_ATTR) {
87101
for (auto class_ : iterSplit(value, ' ')) {
88102
this->classList.add(class_);
89103
}

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::SpecifiedValues> 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::SpecifiedValues> 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: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module;
1313
export module Vaev.Layout:builder;
1414

1515
import :values;
16+
import :svg;
1617

1718
namespace Vaev::Layout {
1819

@@ -388,6 +389,57 @@ static void _buildInputProse(BuilderContext bc, Gc::Ref<Dom::Element> el) {
388389
bc.content() = InlineBox{prose};
389390
}
390391

392+
void buildSVGChildren(Gc::Ref<Dom::Element> el, SVGRoot& svgRoot);
393+
static void buildBlockFlowFromElement(BuilderContext bc, Gc::Ref<Dom::Element> el);
394+
395+
void buildSVGElement(Gc::Ref<Dom::Element> el, SVGRoot& svgRoot) {
396+
if (SVG::isShape(el->tagName)) {
397+
svgRoot.add(SVG::Shape::build(el->specifiedValues(), el->tagName));
398+
} else if (el->tagName == Svg::G) {
399+
buildSVGChildren(el, svgRoot);
400+
} else if (el->tagName == Svg::SVG) {
401+
SVGRoot newSvgRoot{el->specifiedValues()};
402+
buildSVGChildren(el, newSvgRoot);
403+
svgRoot.add(std::move(newSvgRoot));
404+
} else if (el->tagName == Svg::FOREIGN_OBJECT) {
405+
Box box{el->specifiedValues(), el->computedValues()->fontFace, el};
406+
407+
InlineBox rootInlineBox{_proseStyleFomStyle(
408+
*el->specifiedValues(),
409+
el->computedValues()->fontFace
410+
)};
411+
412+
BuilderContext bc{
413+
BuilderContext::From::BLOCK,
414+
el->specifiedValues(),
415+
box,
416+
&rootInlineBox,
417+
};
418+
419+
buildBlockFlowFromElement(bc, *el);
420+
421+
svgRoot.add(std::move(box));
422+
} else {
423+
// TODO
424+
logWarn("cannot build element into svg tree: {}", el->tagName);
425+
}
426+
}
427+
428+
void buildSVGChildren(Gc::Ref<Dom::Element> el, SVGRoot& svgRoot) {
429+
for (auto child = el->firstChild(); child; child = child->nextSibling()) {
430+
if (auto el = child->is<Dom::Element>()) {
431+
buildSVGElement(*el, svgRoot);
432+
}
433+
// TODO: process text into svg tree
434+
}
435+
}
436+
437+
SVGRoot _buildSVG(Gc::Ref<Dom::Element> el) {
438+
SVGRoot svgRoot{el->specifiedValues()};
439+
buildSVGChildren(el, svgRoot);
440+
return svgRoot;
441+
}
442+
391443
always_inline static bool isVoidElement(Gc::Ref<Dom::Element> el) {
392444
return contains(Html::VOID_TAGS, el->tagName);
393445
}
@@ -434,6 +486,8 @@ static void createAndBuildInlineFlowfromElement(BuilderContext bc, Rc<Style::Spe
434486
static void buildBlockFlowFromElement(BuilderContext bc, Gc::Ref<Dom::Element> el) {
435487
if (el->tagName == Html::BR) {
436488
// do nothing
489+
} else if (el->tagName == Svg::SVG) {
490+
bc.content() = _buildSVG(el);
437491
} else if (isVoidElement(el)) {
438492
_buildVoidElement(bc, el);
439493
} else {

0 commit comments

Comments
 (0)