Skip to content

Commit 0a62477

Browse files
pauloamedsleepy-monax
authored andcommitted
vaev-layout,style: Add SVG groups and integration with transforms.
1 parent 986e1d6 commit 0a62477

File tree

9 files changed

+643
-94
lines changed

9 files changed

+643
-94
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ ATTR(SYSTEM_LANGUAGE, systemLanguage)
7777
ATTR(TABLE_VALUES, tableValues)
7878
ATTR(TARGET_X, targetX)
7979
ATTR(TARGET_Y, targetY)
80+
ATTR(TRANSFORM, transform)
81+
ATTR(TRANSFORM_ORIGIN, transform-origin)
82+
ATTR(TRANSFORM_BOX, transform-box)
8083
ATTR(TEXT_LENGTH, textLength)
8184
ATTR(VERSION, version)
8285
ATTR(VIEW_BOX, viewBox)

src/vaev-layout/base.cpp

Lines changed: 133 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -286,21 +286,31 @@ export struct InlineBox {
286286
}
287287
};
288288

289-
struct SVGRoot {
290-
using Element = Union<SVG::Shape, SVGRoot, ::Box<Box>>;
291-
Vec<Element> elements;
289+
struct SVGRoot;
290+
291+
namespace SVG {
292+
293+
struct Group {
294+
using Element = Union<Shape, SVGRoot, Karm::Box<Vaev::Layout::Box>, Group>;
295+
Vec<Element> elements = {};
292296

293-
Opt<ViewBox> viewBox;
294297
Rc<Style::SpecifiedValues> style;
295298

296-
void add(Element&& element) {
297-
elements.pushBack(std::move(element));
298-
}
299+
Group(Rc<Style::SpecifiedValues> style)
300+
: style(style) {}
301+
302+
void add(Element&& element);
303+
void add(Vaev::Layout::Box&& box);
304+
305+
void repr(Io::Emit& e) const;
306+
};
307+
} // namespace SVG
299308

300-
void add(Box&& box);
309+
struct SVGRoot : SVG::Group {
310+
Opt<ViewBox> viewBox;
301311

302312
SVGRoot(Rc<Style::SpecifiedValues> style)
303-
: viewBox(style->svg->viewBox), style(style) {}
313+
: SVG::Group(style), viewBox(style->svg->viewBox) {}
304314

305315
void repr(Io::Emit& e) const {
306316
e("(SVG {} viewBox:{}", SVG::buildRectangle(*style), viewBox);
@@ -313,6 +323,16 @@ struct SVGRoot {
313323
}
314324
};
315325

326+
void SVG::Group::repr(Io::Emit& e) const {
327+
e("(Group {} ");
328+
e.indentNewline();
329+
for (auto const& el : elements) {
330+
e("{}", el);
331+
e.newline();
332+
}
333+
e(")");
334+
}
335+
316336
export using Content = Union<
317337
None,
318338
Vec<Box>,
@@ -392,8 +412,12 @@ struct Box : Meta::NoCopy {
392412
}
393413
};
394414

395-
void SVGRoot::add(Box&& box) {
396-
add(makeBox<Box>(std::move(box)));
415+
void SVG::Group::add(Element&& element) {
416+
elements.pushBack(std::move(element));
417+
}
418+
419+
void SVG::Group::add(Vaev::Layout::Box&& box) {
420+
add(Element{makeBox<Vaev::Layout::Box>(std::move(box))});
397421
}
398422

399423
void InlineBox::add(Box&& b) {
@@ -452,28 +476,62 @@ export struct Metrics {
452476
};
453477

454478
export struct Frag;
479+
struct SVGRootFrag;
455480

456-
struct SVGRootFrag {
457-
using Element = Union<SVG::ShapeFrag, SVGRootFrag, ::Box<Frag>>;
481+
namespace SVG {
482+
483+
struct GroupFrag : SVG::Frag {
484+
using Element = Union<SVG::ShapeFrag, SVGRootFrag, ::Box<Vaev::Layout::Frag>, GroupFrag>;
458485
Vec<Element> elements = {};
459486

487+
RectAu _objectBoundingBox{};
488+
RectAu _strokeBoundingBox{};
489+
490+
Karm::Cursor<Group> box;
491+
492+
GroupFrag(Karm::Cursor<Group> group)
493+
: box(group) {}
494+
495+
static void computeBoundingBoxes(SVG::GroupFrag* group);
496+
497+
RectAu objectBoundingBox() override {
498+
return _objectBoundingBox;
499+
}
500+
501+
RectAu strokeBoundingBox() override {
502+
return _strokeBoundingBox;
503+
}
504+
505+
Style::SpecifiedValues const& style() override {
506+
return *box->style;
507+
}
508+
509+
void add(Element&& element);
510+
511+
void repr(Io::Emit& e) const {
512+
e("(GroupFrag)");
513+
}
514+
};
515+
} // namespace SVG
516+
517+
struct SVGRootFrag : SVG::GroupFrag {
460518
// NOTE: SVG viewports have these intrinsic transformations; choosing to store these transforms is more compliant
461519
// and somewhat rendering-friendly but makes it harder to debug
462520
Math::Trans2f transf;
463521
SVG::Rectangle<Au> boundingBox;
464522

523+
SVGRootFrag(Karm::Cursor<SVG::Group> group, Math::Trans2f transf, SVG::Rectangle<Au> boundingBox)
524+
: SVG::GroupFrag(group), transf(transf), boundingBox(boundingBox) {
525+
}
526+
465527
static SVGRootFrag build(SVGRoot const& box, Vec2Au position, Vec2Au viewportSize) {
466528
SVG::Rectangle<Karm::Au> rect{position.x, position.y, viewportSize.x, viewportSize.y};
467529

468530
Math::Trans2f transf =
469531
box.viewBox ? SVG::computeEquivalentTransformOfSVGViewport(*box.viewBox, position, viewportSize)
470532
: Math::Trans2f::translate(position.cast<f64>());
471533

472-
return {{}, transf, rect};
473-
}
474-
475-
void add(Element&& el) {
476-
elements.pushBack(std::move(el));
534+
return SVGRootFrag{&box, transf, rect};
477535
}
478536

479537
void repr(Io::Emit& e) const {
@@ -532,16 +590,72 @@ export struct Frag {
532590
}
533591
};
534592

593+
void SVG::GroupFrag::add(Element&& el) {
594+
elements.pushBack(std::move(el));
595+
}
596+
535597
void SVGRootFrag::offsetBoxFrags(Vec2Au d) {
536598
for (auto& element : elements) {
537-
if (auto frag = element.is<::Box<Frag>>()) {
599+
if (auto frag = element.is<::Box<Vaev::Layout::Frag>>()) {
538600
(*frag)->offset(d);
539601
} else if (auto nestedRoot = element.is<SVGRootFrag>()) {
540602
nestedRoot->offsetBoxFrags(d);
541603
}
542604
}
543605
}
544606

607+
void SVG::GroupFrag::computeBoundingBoxes(SVG::GroupFrag* group) {
608+
if(group->elements.len() == 0)
609+
return;
610+
611+
// FIXME: this could be implemented in the Union type
612+
auto upcast = [&](Element const& element, auto upcaster) {
613+
return element.visit(upcaster);
614+
};
615+
616+
auto toSVGFrag = [&]<typename T>(T const& el) -> SVG::Frag* {
617+
if constexpr (Meta::Derive<T, SVG::Frag>) {
618+
return (SVG::Frag*)(&el);
619+
}
620+
return nullptr;
621+
};
622+
623+
auto toSVGGroupFrag = [&]<typename T>(T const& el) -> SVG::GroupFrag* {
624+
if constexpr (Meta::Derive<T, SVG::GroupFrag>) {
625+
return (SVG::GroupFrag*)(&el);
626+
}
627+
return nullptr;
628+
};
629+
630+
auto getElementBoundingBoxes = [&](Element const& element) -> Pair<RectAu> {
631+
if (auto frag = element.is<::Box<Vaev::Layout::Frag>>()) {
632+
return {
633+
(*frag)->metrics.borderBox(),
634+
(*frag)->metrics.borderBox()
635+
};
636+
} else if (auto svgFrag = upcast(element, toSVGFrag)) {
637+
if (auto svgGroupFrag = upcast(element, toSVGGroupFrag)) {
638+
computeBoundingBoxes(svgGroupFrag);
639+
}
640+
return {
641+
svgFrag->objectBoundingBox(),
642+
svgFrag->strokeBoundingBox()
643+
};
644+
} else
645+
unreachable();
646+
};
647+
648+
auto [objectBoundingBox, strokeBoundingBox] = getElementBoundingBoxes(group->elements[0]);
649+
for(usize i = 1; i < group->elements.len(); i++) {
650+
auto [nextObjectBoundingBox, nextStrokeBoundingBox] = getElementBoundingBoxes(group->elements[i]);
651+
objectBoundingBox = objectBoundingBox.mergeWith(nextObjectBoundingBox);
652+
strokeBoundingBox = strokeBoundingBox.mergeWith(nextStrokeBoundingBox);
653+
}
654+
655+
group->_objectBoundingBox = objectBoundingBox;
656+
group->_strokeBoundingBox = strokeBoundingBox;
657+
}
658+
545659
// MARK: Input & Output --------------------------------------------------------
546660

547661
export enum struct IntrinsicSize {

src/vaev-layout/builder.cpp

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -389,18 +389,20 @@ static void _buildInputProse(BuilderContext bc, Gc::Ref<Dom::Element> el) {
389389
bc.content() = InlineBox{prose};
390390
}
391391

392-
void buildSVGChildren(Gc::Ref<Dom::Element> el, SVGRoot& svgRoot);
393392
static void buildBlockFlowFromElement(BuilderContext bc, Gc::Ref<Dom::Element> el);
393+
void buildSVGAggregate(Gc::Ref<Dom::Element> el, SVG::Group* group);
394394

395-
void buildSVGElement(Gc::Ref<Dom::Element> el, SVGRoot& svgRoot) {
395+
void buildSVGElement(Gc::Ref<Dom::Element> el, SVG::Group* group) {
396396
if (SVG::isShape(el->tagName)) {
397-
svgRoot.add(SVG::Shape::build(el->specifiedValues(), el->tagName));
397+
group->add(SVG::Shape::build(el->specifiedValues(), el->tagName));
398398
} else if (el->tagName == Svg::G) {
399-
buildSVGChildren(el, svgRoot);
399+
SVG::Group nestedGroup{el->specifiedValues()};
400+
buildSVGAggregate(el, &nestedGroup);
401+
group->add(std::move(nestedGroup));
400402
} else if (el->tagName == Svg::SVG) {
401403
SVGRoot newSvgRoot{el->specifiedValues()};
402-
buildSVGChildren(el, newSvgRoot);
403-
svgRoot.add(std::move(newSvgRoot));
404+
buildSVGAggregate(el, &newSvgRoot);
405+
group->add(std::move(newSvgRoot));
404406
} else if (el->tagName == Svg::FOREIGN_OBJECT) {
405407
Box box{el->specifiedValues(), el->computedValues()->fontFace, el};
406408

@@ -418,25 +420,25 @@ void buildSVGElement(Gc::Ref<Dom::Element> el, SVGRoot& svgRoot) {
418420

419421
buildBlockFlowFromElement(bc, *el);
420422

421-
svgRoot.add(std::move(box));
423+
group->add(std::move(box));
422424
} else {
423425
// TODO
424426
logWarn("cannot build element into svg tree: {}", el->tagName);
425427
}
426428
}
427429

428-
void buildSVGChildren(Gc::Ref<Dom::Element> el, SVGRoot& svgRoot) {
430+
void buildSVGAggregate(Gc::Ref<Dom::Element> el, SVG::Group* group) {
429431
for (auto child = el->firstChild(); child; child = child->nextSibling()) {
430432
if (auto el = child->is<Dom::Element>()) {
431-
buildSVGElement(*el, svgRoot);
433+
buildSVGElement(*el, group);
432434
}
433435
// TODO: process text into svg tree
434436
}
435437
}
436438

437439
SVGRoot _buildSVG(Gc::Ref<Dom::Element> el) {
438440
SVGRoot svgRoot{el->specifiedValues()};
439-
buildSVGChildren(el, svgRoot);
441+
buildSVGAggregate(el, &svgRoot);
440442
return svgRoot;
441443
}
442444

0 commit comments

Comments
 (0)