@@ -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+
316336export 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
399423void InlineBox::add (Box&& b) {
@@ -452,28 +476,62 @@ export struct Metrics {
452476};
453477
454478export 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+
535597void 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
547661export enum struct IntrinsicSize {
0 commit comments