@@ -14,7 +14,7 @@ use graphene_std::renderer::Quad;
1414use graphene_std:: renderer:: convert_usvg_path:: convert_usvg_path;
1515use graphene_std:: table:: Table ;
1616use graphene_std:: text:: { Font , TypesettingConfig } ;
17- use graphene_std:: vector:: style:: { Fill , Gradient , GradientStops , GradientType , PaintOrder , Stroke , StrokeAlign , StrokeCap , StrokeJoin } ;
17+ use graphene_std:: vector:: style:: { Fill , Gradient , GradientStop , GradientStops , GradientType , PaintOrder , Stroke , StrokeAlign , StrokeCap , StrokeJoin } ;
1818
1919#[ derive( ExtractField ) ]
2020pub struct GraphOperationMessageContext < ' a > {
@@ -337,7 +337,17 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
337337 let offset_to_center = DVec2 :: new ( size. width ( ) as f64 , size. height ( ) as f64 ) / -2. ;
338338 let transform = transform * DAffine2 :: from_translation ( offset_to_center) ;
339339
340- import_usvg_node ( & mut modify_inputs, & usvg:: Node :: Group ( Box :: new ( tree. root ( ) . clone ( ) ) ) , transform, id, parent, insert_index) ;
340+ let graphite_gradient_stops = extract_graphite_gradient_stops ( & svg) ;
341+
342+ import_usvg_node (
343+ & mut modify_inputs,
344+ & usvg:: Node :: Group ( Box :: new ( tree. root ( ) . clone ( ) ) ) ,
345+ transform,
346+ id,
347+ parent,
348+ insert_index,
349+ & graphite_gradient_stops,
350+ ) ;
341351 }
342352 }
343353 }
@@ -362,7 +372,85 @@ fn usvg_transform(c: usvg::Transform) -> DAffine2 {
362372 DAffine2 :: from_cols_array ( & [ c. sx as f64 , c. ky as f64 , c. kx as f64 , c. sy as f64 , c. tx as f64 , c. ty as f64 ] )
363373}
364374
365- fn import_usvg_node ( modify_inputs : & mut ModifyInputsContext , node : & usvg:: Node , transform : DAffine2 , id : NodeId , parent : LayerNodeIdentifier , insert_index : usize ) {
375+ const GRAPHITE_NAMESPACE : & str = "https://graphite.art" ;
376+
377+ /// Pre-parses the raw SVG XML to extract gradient stops that have `graphite:midpoint` attributes.
378+ /// Graphite exports gradients with midpoint curve data by writing interpolated approximation stops
379+ /// alongside the real stops. Real stops are tagged with `graphite:midpoint` attributes.
380+ /// Returns a map from gradient element `id` to `GradientStops` containing only the real stops.
381+ fn extract_graphite_gradient_stops ( svg : & str ) -> HashMap < String , GradientStops > {
382+ let mut result = HashMap :: new ( ) ;
383+
384+ // Quick check: if the SVG doesn't reference `graphite:midpoint` at all, skip parsing
385+ if !svg. contains ( "graphite:midpoint" ) {
386+ return result;
387+ }
388+
389+ let doc = match usvg:: roxmltree:: Document :: parse ( svg) {
390+ Ok ( doc) => doc,
391+ Err ( _) => return result,
392+ } ;
393+
394+ for node in doc. descendants ( ) {
395+ match node. tag_name ( ) . name ( ) {
396+ "linearGradient" | "radialGradient" => { }
397+ _ => continue ,
398+ }
399+
400+ let gradient_id = match node. attribute ( "id" ) {
401+ Some ( id) => id. to_string ( ) ,
402+ None => continue ,
403+ } ;
404+
405+ let mut real_stops = Vec :: new ( ) ;
406+ let mut has_any_midpoint = false ;
407+
408+ for child in node. children ( ) {
409+ if child. tag_name ( ) . name ( ) != "stop" {
410+ continue ;
411+ }
412+
413+ let midpoint = child. attribute ( ( GRAPHITE_NAMESPACE , "midpoint" ) ) . and_then ( |v| v. parse :: < f64 > ( ) . ok ( ) ) ;
414+
415+ if let Some ( midpoint) = midpoint {
416+ has_any_midpoint = true ;
417+
418+ let offset = child. attribute ( "offset" ) . and_then ( |v| v. parse :: < f64 > ( ) . ok ( ) ) . unwrap_or ( 0. ) ;
419+ let opacity = child. attribute ( "stop-opacity" ) . and_then ( |v| v. parse :: < f32 > ( ) . ok ( ) ) . unwrap_or ( 1. ) ;
420+ let color = child. attribute ( "stop-color" ) . and_then ( |hex| parse_hex_stop_color ( hex, opacity) ) . unwrap_or ( Color :: BLACK ) ;
421+
422+ real_stops. push ( GradientStop { position : offset, midpoint, color } ) ;
423+ }
424+ }
425+
426+ if has_any_midpoint && !real_stops. is_empty ( ) {
427+ result. insert ( gradient_id, GradientStops :: new ( real_stops) ) ;
428+ }
429+ }
430+
431+ result
432+ }
433+
434+ fn parse_hex_stop_color ( hex : & str , opacity : f32 ) -> Option < Color > {
435+ let hex = hex. strip_prefix ( '#' ) ?;
436+ if hex. len ( ) != 6 {
437+ return None ;
438+ }
439+ let r = u8:: from_str_radix ( & hex[ 0 ..2 ] , 16 ) . ok ( ) ? as f32 / 255. ;
440+ let g = u8:: from_str_radix ( & hex[ 2 ..4 ] , 16 ) . ok ( ) ? as f32 / 255. ;
441+ let b = u8:: from_str_radix ( & hex[ 4 ..6 ] , 16 ) . ok ( ) ? as f32 / 255. ;
442+ Some ( Color :: from_rgbaf32_unchecked ( r, g, b, opacity) )
443+ }
444+
445+ fn import_usvg_node (
446+ modify_inputs : & mut ModifyInputsContext ,
447+ node : & usvg:: Node ,
448+ transform : DAffine2 ,
449+ id : NodeId ,
450+ parent : LayerNodeIdentifier ,
451+ insert_index : usize ,
452+ graphite_gradient_stops : & HashMap < String , GradientStops > ,
453+ ) {
366454 let layer = modify_inputs. create_layer ( id) ;
367455 modify_inputs. network_interface . move_layer_to_stack ( layer, parent, insert_index, & [ ] ) ;
368456 modify_inputs. layer_node = Some ( layer) ;
@@ -372,7 +460,7 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
372460 match node {
373461 usvg:: Node :: Group ( group) => {
374462 for child in group. children ( ) {
375- import_usvg_node ( modify_inputs, child, transform, NodeId :: new ( ) , layer, 0 ) ;
463+ import_usvg_node ( modify_inputs, child, transform, NodeId :: new ( ) , layer, 0 , graphite_gradient_stops ) ;
376464 }
377465 modify_inputs. layer_node = Some ( layer) ;
378466 }
@@ -388,7 +476,7 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
388476
389477 if let Some ( fill) = path. fill ( ) {
390478 let bounds_transform = DAffine2 :: from_scale_angle_translation ( bounds[ 1 ] - bounds[ 0 ] , 0. , bounds[ 0 ] ) ;
391- apply_usvg_fill ( fill, modify_inputs, bounds_transform) ;
479+ apply_usvg_fill ( fill, modify_inputs, bounds_transform, graphite_gradient_stops ) ;
392480 }
393481 if let Some ( stroke) = path. stroke ( ) {
394482 apply_usvg_stroke ( stroke, modify_inputs, transform * usvg_transform ( node. abs_transform ( ) ) ) ;
@@ -432,7 +520,7 @@ fn apply_usvg_stroke(stroke: &usvg::Stroke, modify_inputs: &mut ModifyInputsCont
432520 }
433521}
434522
435- fn apply_usvg_fill ( fill : & usvg:: Fill , modify_inputs : & mut ModifyInputsContext , bounds_transform : DAffine2 ) {
523+ fn apply_usvg_fill ( fill : & usvg:: Fill , modify_inputs : & mut ModifyInputsContext , bounds_transform : DAffine2 , graphite_gradient_stops : & HashMap < String , GradientStops > ) {
436524 modify_inputs. fill_set ( match & fill. paint ( ) {
437525 usvg:: Paint :: Color ( color) => Fill :: solid ( usvg_color ( * color, fill. opacity ( ) . get ( ) ) ) ,
438526 usvg:: Paint :: LinearGradient ( linear) => {
@@ -443,8 +531,17 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, b
443531
444532 let gradient_type = GradientType :: Linear ;
445533
446- let stops = linear. stops ( ) . iter ( ) . map ( |stop| ( stop. offset ( ) . get ( ) as f64 , usvg_color ( stop. color ( ) , stop. opacity ( ) . get ( ) ) ) ) . collect ( ) ;
447- let stops = GradientStops :: new ( stops) ;
534+ let stops = match graphite_gradient_stops. get ( linear. id ( ) ) {
535+ Some ( graphite_stops) => graphite_stops. clone ( ) ,
536+ None => {
537+ let stops = linear. stops ( ) . iter ( ) . map ( |stop| GradientStop {
538+ position : stop. offset ( ) . get ( ) as f64 ,
539+ midpoint : 0.5 ,
540+ color : usvg_color ( stop. color ( ) , stop. opacity ( ) . get ( ) ) ,
541+ } ) ;
542+ GradientStops :: new ( stops)
543+ }
544+ } ;
448545
449546 Fill :: Gradient ( Gradient { start, end, gradient_type, stops } )
450547 }
@@ -457,8 +554,17 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, b
457554
458555 let gradient_type = GradientType :: Radial ;
459556
460- let stops = radial. stops ( ) . iter ( ) . map ( |stop| ( stop. offset ( ) . get ( ) as f64 , usvg_color ( stop. color ( ) , stop. opacity ( ) . get ( ) ) ) ) . collect ( ) ;
461- let stops = GradientStops :: new ( stops) ;
557+ let stops = match graphite_gradient_stops. get ( radial. id ( ) ) {
558+ Some ( graphite_stops) => graphite_stops. clone ( ) ,
559+ None => {
560+ let stops = radial. stops ( ) . iter ( ) . map ( |stop| GradientStop {
561+ position : stop. offset ( ) . get ( ) as f64 ,
562+ midpoint : 0.5 ,
563+ color : usvg_color ( stop. color ( ) , stop. opacity ( ) . get ( ) ) ,
564+ } ) ;
565+ GradientStops :: new ( stops)
566+ }
567+ } ;
462568
463569 Fill :: Gradient ( Gradient { start, end, gradient_type, stops } )
464570 }
0 commit comments