11use super :: transform_utils;
22use super :: utility_types:: ModifyInputsContext ;
3+ use crate :: consts:: { LAYER_INDENT_OFFSET , STACK_VERTICAL_GAP } ;
34use crate :: messages:: portfolio:: document:: graph_operation:: utility_types:: TransformIn ;
45use crate :: messages:: portfolio:: document:: utility_types:: document_metadata:: LayerNodeIdentifier ;
56use crate :: messages:: portfolio:: document:: utility_types:: network_interface:: { InputConnector , NodeNetworkInterface , OutputConnector } ;
@@ -442,6 +443,12 @@ fn parse_hex_stop_color(hex: &str, opacity: f32) -> Option<Color> {
442443 Some ( Color :: from_rgbaf32_unchecked ( r, g, b, opacity) )
443444}
444445
446+ /// Import a usvg node as the root of an SVG import operation.
447+ ///
448+ /// The root layer uses the full `move_layer_to_stack` (with push/collision logic) to correctly
449+ /// interact with any existing layers in the parent stack. All descendant layers use a lightweight
450+ /// O(n) import path that skips collision detection and instead calculates positions directly from
451+ /// the known tree structure.
445452fn import_usvg_node (
446453 modify_inputs : & mut ModifyInputsContext ,
447454 node : & usvg:: Node ,
@@ -452,44 +459,192 @@ fn import_usvg_node(
452459 graphite_gradient_stops : & HashMap < String , GradientStops > ,
453460) {
454461 let layer = modify_inputs. create_layer ( id) ;
462+
455463 modify_inputs. network_interface . move_layer_to_stack ( layer, parent, insert_index, & [ ] ) ;
456464 modify_inputs. layer_node = Some ( layer) ;
457465 if let Some ( upstream_layer) = layer. next_sibling ( modify_inputs. network_interface . document_metadata ( ) ) {
458- modify_inputs. network_interface . shift_node ( & upstream_layer. to_node ( ) , IVec2 :: new ( 0 , 3 ) , & [ ] ) ;
466+ modify_inputs. network_interface . shift_node ( & upstream_layer. to_node ( ) , IVec2 :: new ( 0 , STACK_VERTICAL_GAP ) , & [ ] ) ;
459467 }
468+
460469 match node {
461470 usvg:: Node :: Group ( group) => {
471+ // Collect child extents for O(n) position calculation
472+ let mut child_extents_svg_order: Vec < u32 > = Vec :: new ( ) ;
473+ let mut group_extents_map: HashMap < LayerNodeIdentifier , Vec < u32 > > = HashMap :: new ( ) ;
474+
475+ // Enable import mode: skips expensive is_acyclic checks and per-node cache invalidation
476+ // during wiring since we're building a known tree structure where cycles are impossible
477+ modify_inputs. import = true ;
478+
462479 for child in group. children ( ) {
463- import_usvg_node ( modify_inputs, child, transform, NodeId :: new ( ) , layer, 0 , graphite_gradient_stops) ;
480+ let extent = import_usvg_node_inner ( modify_inputs, child, transform, NodeId :: new ( ) , layer, 0 , graphite_gradient_stops, & mut group_extents_map) ;
481+ child_extents_svg_order. push ( extent) ;
464482 }
483+
484+ modify_inputs. import = false ;
465485 modify_inputs. layer_node = Some ( layer) ;
486+
487+ // Rebuild the layer tree once now that all wiring is complete
488+ modify_inputs. network_interface . load_structure ( ) ;
489+
490+ // Set positions for all imported descendants in a single O(n) pass
491+ let parent_pos = modify_inputs. network_interface . position ( & layer. to_node ( ) , & [ ] ) . unwrap_or ( IVec2 :: ZERO ) ;
492+ set_import_child_positions ( modify_inputs. network_interface , layer, parent_pos, & child_extents_svg_order, & group_extents_map) ;
493+
494+ // Invalidate caches once after all positions are set
495+ modify_inputs. network_interface . unload_all_nodes_click_targets ( & [ ] ) ;
496+ modify_inputs. network_interface . unload_all_nodes_bounding_box ( & [ ] ) ;
466497 }
467498 usvg:: Node :: Path ( path) => {
468- let subpaths = convert_usvg_path ( path) ;
469- let bounds = subpaths. iter ( ) . filter_map ( |subpath| subpath. bounding_box ( ) ) . reduce ( Quad :: combine_bounds) . unwrap_or_default ( ) ;
499+ import_usvg_path ( modify_inputs, node, path, transform, layer, graphite_gradient_stops) ;
500+ }
501+ usvg:: Node :: Image ( _image) => {
502+ warn ! ( "Skip image" ) ;
503+ }
504+ usvg:: Node :: Text ( text) => {
505+ let font = Font :: new ( graphene_std:: consts:: DEFAULT_FONT_FAMILY . to_string ( ) , graphene_std:: consts:: DEFAULT_FONT_STYLE . to_string ( ) ) ;
506+ modify_inputs. insert_text ( text. chunks ( ) . iter ( ) . map ( |chunk| chunk. text ( ) ) . collect ( ) , font, TypesettingConfig :: default ( ) , layer) ;
507+ modify_inputs. fill_set ( Fill :: Solid ( Color :: BLACK ) ) ;
508+ }
509+ }
510+ }
470511
471- modify_inputs. insert_vector ( subpaths, layer, true , path. fill ( ) . is_some ( ) , path. stroke ( ) . is_some ( ) ) ;
512+ /// Recursively import a usvg node as a descendant of the root import layer.
513+ /// Uses lightweight wiring (no push/collision) and returns the subtree extent for position calculation.
514+ ///
515+ /// The subtree extent represents the additional vertical grid units that this node's descendants
516+ /// occupy below the node's position. This is used to calculate correct y_offsets between siblings.
517+ fn import_usvg_node_inner (
518+ modify_inputs : & mut ModifyInputsContext ,
519+ node : & usvg:: Node ,
520+ transform : DAffine2 ,
521+ id : NodeId ,
522+ parent : LayerNodeIdentifier ,
523+ insert_index : usize ,
524+ graphite_gradient_stops : & HashMap < String , GradientStops > ,
525+ group_extents_map : & mut HashMap < LayerNodeIdentifier , Vec < u32 > > ,
526+ ) -> u32 {
527+ let layer = modify_inputs. create_layer ( id) ;
528+ modify_inputs. network_interface . move_layer_to_stack_for_import ( layer, parent, insert_index, & [ ] ) ;
529+ modify_inputs. layer_node = Some ( layer) ;
472530
473- if let Some ( transform_node_id) = modify_inputs. existing_network_node_id ( "Transform" , true ) {
474- transform_utils:: update_transform ( modify_inputs. network_interface , & transform_node_id, transform * usvg_transform ( node. abs_transform ( ) ) ) ;
531+ match node {
532+ usvg:: Node :: Group ( group) => {
533+ let mut child_extents: Vec < u32 > = Vec :: new ( ) ;
534+ for child in group. children ( ) {
535+ let extent = import_usvg_node_inner ( modify_inputs, child, transform, NodeId :: new ( ) , layer, 0 , graphite_gradient_stops, group_extents_map) ;
536+ child_extents. push ( extent) ;
475537 }
538+ modify_inputs. layer_node = Some ( layer) ;
476539
477- if let Some ( fill) = path. fill ( ) {
478- let bounds_transform = DAffine2 :: from_scale_angle_translation ( bounds[ 1 ] - bounds[ 0 ] , 0. , bounds[ 0 ] ) ;
479- apply_usvg_fill ( fill, modify_inputs, bounds_transform, graphite_gradient_stops) ;
480- }
481- if let Some ( stroke) = path. stroke ( ) {
482- apply_usvg_stroke ( stroke, modify_inputs, transform * usvg_transform ( node. abs_transform ( ) ) ) ;
483- }
540+ let n = child_extents. len ( ) ;
541+ let total_extent = if n == 0 {
542+ 0
543+ } else {
544+ ( 2 * STACK_VERTICAL_GAP as u32 ) * n as u32 - STACK_VERTICAL_GAP as u32 + child_extents. iter ( ) . sum :: < u32 > ( )
545+ } ;
546+ group_extents_map. insert ( layer, child_extents) ;
547+ total_extent
548+ }
549+ usvg:: Node :: Path ( path) => {
550+ import_usvg_path ( modify_inputs, node, path, transform, layer, graphite_gradient_stops) ;
551+ 0
484552 }
485553 usvg:: Node :: Image ( _image) => {
486- warn ! ( "Skip image" )
554+ warn ! ( "Skip image" ) ;
555+ 0
487556 }
488557 usvg:: Node :: Text ( text) => {
489558 let font = Font :: new ( graphene_std:: consts:: DEFAULT_FONT_FAMILY . to_string ( ) , graphene_std:: consts:: DEFAULT_FONT_STYLE . to_string ( ) ) ;
490559 modify_inputs. insert_text ( text. chunks ( ) . iter ( ) . map ( |chunk| chunk. text ( ) ) . collect ( ) , font, TypesettingConfig :: default ( ) , layer) ;
491560 modify_inputs. fill_set ( Fill :: Solid ( Color :: BLACK ) ) ;
561+ 0
562+ }
563+ }
564+ }
565+
566+ /// Helper to apply path data (vector geometry, fill, stroke, transform) to a layer.
567+ fn import_usvg_path (
568+ modify_inputs : & mut ModifyInputsContext ,
569+ node : & usvg:: Node ,
570+ path : & usvg:: Path ,
571+ transform : DAffine2 ,
572+ layer : LayerNodeIdentifier ,
573+ graphite_gradient_stops : & HashMap < String , GradientStops > ,
574+ ) {
575+ let subpaths = convert_usvg_path ( path) ;
576+ let bounds = subpaths. iter ( ) . filter_map ( |subpath| subpath. bounding_box ( ) ) . reduce ( Quad :: combine_bounds) . unwrap_or_default ( ) ;
577+
578+ modify_inputs. insert_vector ( subpaths, layer, true , path. fill ( ) . is_some ( ) , path. stroke ( ) . is_some ( ) ) ;
579+
580+ if let Some ( transform_node_id) = modify_inputs. existing_network_node_id ( "Transform" , true ) {
581+ transform_utils:: update_transform ( modify_inputs. network_interface , & transform_node_id, transform * usvg_transform ( node. abs_transform ( ) ) ) ;
582+ }
583+
584+ if let Some ( fill) = path. fill ( ) {
585+ let bounds_transform = DAffine2 :: from_scale_angle_translation ( bounds[ 1 ] - bounds[ 0 ] , 0. , bounds[ 0 ] ) ;
586+ apply_usvg_fill ( fill, modify_inputs, bounds_transform, graphite_gradient_stops) ;
587+ }
588+ if let Some ( stroke) = path. stroke ( ) {
589+ apply_usvg_stroke ( stroke, modify_inputs, transform * usvg_transform ( node. abs_transform ( ) ) ) ;
590+ }
591+ }
592+
593+ /// Set correct positions for all imported layers in a single top-down O(n) pass.
594+ ///
595+ /// For each group's child stack:
596+ /// - The top-of-stack child (last SVG child) gets an `Absolute` position at `(parent_x - LAYER_INDENT_OFFSET, parent_y + STACK_VERTICAL_GAP)`
597+ /// - All other children get `Stack(y_offset)` where `y_offset` accounts for the subtree extent of the sibling above them in the stack, ensuring no overlap.
598+ fn set_import_child_positions (
599+ network_interface : & mut NodeNetworkInterface ,
600+ group : LayerNodeIdentifier ,
601+ group_pos : IVec2 ,
602+ child_extents_svg_order : & [ u32 ] ,
603+ group_extents_map : & HashMap < LayerNodeIdentifier , Vec < u32 > > ,
604+ ) {
605+ use crate :: messages:: portfolio:: document:: utility_types:: network_interface:: LayerPosition ;
606+
607+ let layer_children: Vec < _ > = group. children ( network_interface. document_metadata ( ) ) . collect ( ) ;
608+ let n = child_extents_svg_order. len ( ) ;
609+
610+ if n == 0 || layer_children. is_empty ( ) {
611+ return ;
612+ }
613+
614+ // Children in the layer tree are in stack order (top to bottom), which is the REVERSE of SVG order.
615+ // SVG order: [s_0, s_1, ..., s_{n-1}] with extents [e_0, e_1, ..., e_{n-1}]
616+ // Stack order: [s_{n-1}, s_{n-2}, ..., s_0 ] (top to bottom)
617+ //
618+ // For stack child at index i:
619+ // - SVG index = n - 1 - i
620+ // - Previous stack sibling's SVG index = n - i
621+ // - y_offset = extent_of_previous_sibling + STACK_VERTICAL_GAP
622+
623+ let child_x = group_pos. x - LAYER_INDENT_OFFSET ;
624+ let mut current_y = group_pos. y + STACK_VERTICAL_GAP ;
625+
626+ for ( i, child_layer) in layer_children. iter ( ) . enumerate ( ) {
627+ let child_pos = IVec2 :: new ( child_x, current_y) ;
628+
629+ if i == 0 {
630+ // Top of stack — set to `Absolute` position
631+ network_interface. set_layer_position_for_import ( & child_layer. to_node ( ) , LayerPosition :: Absolute ( child_pos) , & [ ] ) ;
632+ } else {
633+ // Below top — set `Stack` with `y_offset` based on previous sibling's subtree extent
634+ let prev_sibling_svg_index = n - i;
635+ let y_offset = child_extents_svg_order[ prev_sibling_svg_index] + STACK_VERTICAL_GAP as u32 ;
636+ network_interface. set_layer_position_for_import ( & child_layer. to_node ( ) , LayerPosition :: Stack ( y_offset) , & [ ] ) ;
492637 }
638+
639+ // Recurse into group children to set their descendants' positions
640+ if let Some ( grandchild_extents) = group_extents_map. get ( child_layer) {
641+ set_import_child_positions ( network_interface, * child_layer, child_pos, grandchild_extents, group_extents_map) ;
642+ }
643+
644+ // Advance `current_y` for the next child: node height (STACK_VERTICAL_GAP) + gap (STACK_VERTICAL_GAP) + subtree extent
645+ let child_svg_index = n - 1 - i;
646+ let child_extent = child_extents_svg_order[ child_svg_index] ;
647+ current_y += 2 * STACK_VERTICAL_GAP + child_extent as i32 ;
493648 }
494649}
495650
0 commit comments