@@ -340,23 +340,73 @@ fn fill_covers_opaquely(fill_graphic: Option<&Graphic>) -> bool {
340340 }
341341}
342342
343+ /// Emits a SVG `<pattern>` paint server element that renders any graphic element into def and returns the id.
344+ /// Currently this function uses `<pattern>` as a clip-based paint server, which means the content is rendered once without tiling.
345+ fn render_svg_fill_pattern ( svg_defs : & mut String , fill_graphic_list : & List < Graphic > , path_bbox : [ DVec2 ; 2 ] , item_transform : DAffine2 , render_params : & RenderParams ) -> Option < String > {
346+ let [ min, max] = path_bbox;
347+ let size = max - min;
348+ if size. x <= 0. || size. y <= 0. {
349+ return None ;
350+ }
351+
352+ // Render the pattern content recursively
353+ let mut content = SvgRender :: new ( ) ;
354+ fill_graphic_list. render_svg ( & mut content, render_params) ;
355+
356+ // Unwrap the inner def element
357+ write ! ( svg_defs, "{}" , content. svg_defs) . unwrap ( ) ;
358+
359+ let pattern_transform = item_transform * DAffine2 :: from_translation ( min) ;
360+ let transform_str = format_transform_matrix ( pattern_transform) ;
361+ let transform_attr = if transform_str. is_empty ( ) {
362+ String :: new ( )
363+ } else {
364+ format ! ( r#" patternTransform="{transform_str}""# )
365+ } ;
366+
367+ let pattern_id = format ! ( "pattern-{}" , generate_uuid( ) ) ;
368+ write ! (
369+ svg_defs,
370+ r##"<pattern id="{pattern_id}" patternUnits="userSpaceOnUse" x="0" y="0" width="{}" height="{}"{transform_attr}>"## ,
371+ size. x, size. y,
372+ )
373+ . unwrap ( ) ;
374+
375+ let content_shift = format_transform_matrix ( DAffine2 :: from_translation ( -min) ) ;
376+ write ! ( svg_defs, r##"<g transform="{content_shift}">{}</g></pattern>"## , content. svg. to_svg_string( ) ) . unwrap ( ) ;
377+
378+ Some ( pattern_id)
379+ }
380+
343381/// Returns the fill attribute for SVG tags corresponding to the given fill_graphic.
382+ #[ allow( clippy:: too_many_arguments) ]
344383fn compute_svg_fill_attribute (
345- fill_graphic : Option < & Graphic > ,
384+ fill_graphic_list : Option < & List < Graphic > > ,
346385 defs : & mut String ,
347386 element_transform : DAffine2 ,
348387 applied_stroke_transform : DAffine2 ,
349388 bounds_matrix : DAffine2 ,
350389 transformed_bounds_matrix : DAffine2 ,
390+ item_transform : DAffine2 ,
351391 render_params : & RenderParams ,
352392) -> String {
393+ let fill_graphic = fill_graphic_list. and_then ( |l| l. element ( 0 ) ) ;
394+
353395 match fill_graphic {
354396 Some ( Graphic :: Color ( color_list) ) => color_list. render ( defs, element_transform, applied_stroke_transform, bounds_matrix, transformed_bounds_matrix, render_params) ,
355397 Some ( Graphic :: Gradient ( gradient_list) ) => {
356398 let gradient_id = gradient_list. render ( defs, element_transform, applied_stroke_transform, bounds_matrix, transformed_bounds_matrix, render_params) ;
357- format ! ( r##" fill="url(' #{gradient_id}' )""## )
399+ format ! ( r##" fill="url(#{gradient_id})""## )
358400 }
359- _ => r#" fill="none""# . to_string ( ) ,
401+ Some ( Graphic :: Vector ( _) ) | Some ( Graphic :: RasterCPU ( _) ) | Some ( Graphic :: RasterGPU ( _) ) | Some ( Graphic :: Graphic ( _) ) => {
402+ let list = fill_graphic_list. unwrap ( ) ;
403+ let min = bounds_matrix. transform_point2 ( DVec2 :: ZERO ) ;
404+ let max = bounds_matrix. transform_point2 ( DVec2 :: ONE ) ;
405+ render_svg_fill_pattern ( defs, list, [ min, max] , item_transform, render_params)
406+ . map ( |id| format ! ( r##" fill="url(#{id})""## ) )
407+ . unwrap_or_else ( || r#" fill="none""# . to_string ( ) )
408+ }
409+ None => r#" fill="none""# . to_string ( ) ,
360410 }
361411}
362412
@@ -366,10 +416,11 @@ fn emit_svg_fill_path(
366416 render : & mut SvgRender ,
367417 d : String ,
368418 element_transform : DAffine2 ,
369- fill_graphic : Option < & Graphic > ,
419+ fill_graphic_list : Option < & List < Graphic > > ,
370420 applied_stroke_transform : DAffine2 ,
371421 bounds_matrix : DAffine2 ,
372422 transformed_bounds_matrix : DAffine2 ,
423+ item_transform : DAffine2 ,
373424 render_params : & RenderParams ,
374425) {
375426 render. leaf_tag ( "path" , |attributes| {
@@ -379,73 +430,20 @@ fn emit_svg_fill_path(
379430 attributes. push ( ATTR_TRANSFORM , matrix) ;
380431 }
381432 let defs = & mut attributes. 0 . svg_defs ;
382- let fill_attribute = compute_svg_fill_attribute ( fill_graphic, defs, element_transform, applied_stroke_transform, bounds_matrix, transformed_bounds_matrix, render_params) ;
433+ let fill_attribute = compute_svg_fill_attribute (
434+ fill_graphic_list,
435+ defs,
436+ element_transform,
437+ applied_stroke_transform,
438+ bounds_matrix,
439+ transformed_bounds_matrix,
440+ item_transform,
441+ render_params,
442+ ) ;
383443 attributes. push_val ( fill_attribute) ;
384444 } ) ;
385445}
386446
387- /// Emits an SVG `<g clip-path>` group that renders the fill graphic clipped to the path referenced by `clip_id`.
388- fn emit_svg_fill_clip ( render : & mut SvgRender , clip_id : & str , fill_graphic_list : & List < Graphic > , item_transform : DAffine2 , render_params : & RenderParams ) {
389- render. parent_tag (
390- "g" ,
391- |attributes| {
392- attributes. push ( "clip-path" , format ! ( "url(#{clip_id})" ) ) ;
393- } ,
394- |render| {
395- let matrix = format_transform_matrix ( item_transform) ;
396- if matrix. is_empty ( ) {
397- fill_graphic_list. render_svg ( render, render_params) ;
398- return ;
399- }
400- render. parent_tag (
401- "g" ,
402- |attributes| {
403- attributes. push ( ATTR_TRANSFORM , matrix) ;
404- } ,
405- |render| {
406- fill_graphic_list. render_svg ( render, render_params) ;
407- } ,
408- ) ;
409- } ,
410- ) ;
411- }
412-
413- /// Emits the fill element for aligned-stroke paths, dispatching between `<path>` for Color/Gradient and `<g clip-path>` for Vector/Raster/Graphic.
414- #[ allow( clippy:: too_many_arguments) ]
415- fn emit_aligned_fill_pass (
416- render : & mut SvgRender ,
417- d : String ,
418- element_transform : DAffine2 ,
419- item_transform : DAffine2 ,
420- fill_graphic_list : Option < & List < Graphic > > ,
421- clip_id : Option < & str > ,
422- applied_stroke_transform : DAffine2 ,
423- bounds_matrix : DAffine2 ,
424- transformed_bounds_matrix : DAffine2 ,
425- render_params : & RenderParams ,
426- ) {
427- let fill_graphic = fill_graphic_list. and_then ( |l| l. element ( 0 ) ) ;
428- match fill_graphic {
429- Some ( Graphic :: Color ( _) | Graphic :: Gradient ( _) ) | None => {
430- emit_svg_fill_path (
431- render,
432- d,
433- element_transform,
434- fill_graphic,
435- applied_stroke_transform,
436- bounds_matrix,
437- transformed_bounds_matrix,
438- render_params,
439- ) ;
440- }
441- Some ( Graphic :: Vector ( _) | Graphic :: RasterCPU ( _) | Graphic :: RasterGPU ( _) | Graphic :: Graphic ( _) ) => {
442- if let ( Some ( clip_id) , Some ( fill_graphic_list) ) = ( clip_id, fill_graphic_list) {
443- emit_svg_fill_clip ( render, clip_id, fill_graphic_list, item_transform, render_params) ;
444- }
445- }
446- }
447- }
448-
449447// TODO: Click targets can be removed from the render output, since the vector data is available in the vector modify data from Monitor nodes.
450448// This will require that the transform for child layers into that layer space be calculated, or it could be returned from the RenderOutput instead of click targets.
451449#[ derive( Debug , Default , Clone , PartialEq , DynAny ) ]
@@ -1076,37 +1074,25 @@ impl Render for List<Vector> {
10761074 . or_else ( || fill_to_graphic_list ( vector. style . fill ( ) ) . map ( Cow :: Owned ) ) ;
10771075 let fill_graphic = fill_graphic_list. as_ref ( ) . and_then ( |l| l. element ( 0 ) ) ;
10781076
1079- let need_clipping = matches ! ( fill_graphic, Some ( Graphic :: Vector ( _) | Graphic :: RasterCPU ( _) | Graphic :: RasterGPU ( _) | Graphic :: Graphic ( _) ) ) ;
1080-
10811077 let path_is_closed = vector. stroke_bezier_paths ( ) . all ( |path| path. closed ( ) ) ;
10821078 let can_draw_aligned_stroke = path_is_closed && vector. style . stroke ( ) . is_some_and ( |stroke| stroke. has_renderable_stroke ( ) && stroke. align . is_not_centered ( ) ) ;
1083- let can_use_paint_order = !( fill_graphic. is_none ( ) || !fill_covers_opaquely ( fill_graphic) || mask_type == MaskType :: Clip || need_clipping ) ;
1079+ let can_use_paint_order = !( fill_graphic. is_none ( ) || !fill_covers_opaquely ( fill_graphic) || mask_type == MaskType :: Clip ) ;
10841080
10851081 let needs_separate_alignment_fill = can_draw_aligned_stroke && !can_use_paint_order;
10861082 let wants_stroke_below = vector. style . stroke ( ) . map ( |s| s. paint_order ) == Some ( PaintOrder :: StrokeBelow ) ;
10871083 let override_paint_order = can_draw_aligned_stroke && can_use_paint_order;
10881084 let use_face_fill = vector. use_face_fill ( ) ;
10891085
1090- // Register the clipPath in <defs> and remember its id for the <g clip-path> below
1091- let clip_id = if need_clipping && !use_face_fill {
1092- let id = format ! ( "clip-{}" , generate_uuid( ) ) ;
1093- write ! ( & mut render. svg_defs, r##"<clipPath id="{id}"><path d="{path}"/></clipPath>"## ) . unwrap ( ) ;
1094- Some ( id)
1095- } else {
1096- None
1097- } ;
1098-
10991086 if needs_separate_alignment_fill && !wants_stroke_below {
1100- emit_aligned_fill_pass (
1087+ emit_svg_fill_path (
11011088 render,
11021089 path. clone ( ) ,
11031090 element_transform,
1104- item_transform,
11051091 fill_graphic_list. as_deref ( ) ,
1106- clip_id. as_deref ( ) ,
11071092 applied_stroke_transform,
11081093 bounds_matrix,
11091094 transformed_bounds_matrix,
1095+ item_transform,
11101096 render_params,
11111097 ) ;
11121098 }
@@ -1130,41 +1116,20 @@ impl Render for List<Vector> {
11301116 face_path. apply_affine ( Affine :: new ( applied_stroke_transform. to_cols_array ( ) ) ) ;
11311117 let face_d = face_path. to_svg ( ) ;
11321118
1133- match fill_graphic {
1134- Some ( Graphic :: Color ( _) | Graphic :: Gradient ( _) ) | None => {
1135- emit_svg_fill_path (
1136- render,
1137- face_d,
1138- element_transform,
1139- fill_graphic,
1140- applied_stroke_transform,
1141- bounds_matrix,
1142- transformed_bounds_matrix,
1143- render_params,
1144- ) ;
1145- }
1146-
1147- Some ( Graphic :: Vector ( _) | Graphic :: RasterCPU ( _) | Graphic :: RasterGPU ( _) | Graphic :: Graphic ( _) ) => {
1148- if let Some ( fill_graphic_list) = fill_graphic_list. as_deref ( ) {
1149- let face_clip_id = format ! ( "clip-{}" , generate_uuid( ) ) ;
1150- write ! ( & mut render. svg_defs, r##"<clipPath id="{face_clip_id}"><path d="{face_d}"/></clipPath>"## ) . unwrap ( ) ;
1151- emit_svg_fill_clip ( render, & face_clip_id, fill_graphic_list, item_transform, render_params) ;
1152- }
1153- }
1154- }
1119+ emit_svg_fill_path (
1120+ render,
1121+ face_d,
1122+ element_transform,
1123+ fill_graphic_list. as_deref ( ) ,
1124+ applied_stroke_transform,
1125+ bounds_matrix,
1126+ transformed_bounds_matrix,
1127+ item_transform,
1128+ render_params,
1129+ ) ;
11551130 }
11561131 }
11571132
1158- // Clipping-based fill should be drawn before the stroke path (default paint order)
1159- if !needs_separate_alignment_fill
1160- && !use_face_fill
1161- && !wants_stroke_below
1162- && !override_paint_order
1163- && let ( Some ( clip_id) , Some ( fill_graphic_list) ) = ( clip_id. as_ref ( ) , fill_graphic_list. as_deref ( ) )
1164- {
1165- emit_svg_fill_clip ( render, clip_id, fill_graphic_list, item_transform, render_params) ;
1166- }
1167-
11681133 render. leaf_tag ( "path" , |attributes| {
11691134 attributes. push ( "d" , path. clone ( ) ) ;
11701135 let matrix = format_transform_matrix ( element_transform) ;
@@ -1213,12 +1178,13 @@ impl Render for List<Vector> {
12131178 r#" fill="none""# . to_string ( )
12141179 } else {
12151180 compute_svg_fill_attribute (
1216- fill_graphic ,
1181+ fill_graphic_list . as_deref ( ) ,
12171182 defs,
12181183 element_transform,
12191184 applied_stroke_transform,
12201185 bounds_matrix,
12211186 transformed_bounds_matrix,
1187+ item_transform,
12221188 & render_params,
12231189 )
12241190 } ;
@@ -1244,27 +1210,17 @@ impl Render for List<Vector> {
12441210 }
12451211 } ) ;
12461212
1247- // Clipping-based fill should be drawn after the stroke path
1248- if !needs_separate_alignment_fill
1249- && !use_face_fill
1250- && ( wants_stroke_below || override_paint_order)
1251- && let ( Some ( clip_id) , Some ( fill_graphic_list) ) = ( clip_id. as_ref ( ) , fill_graphic_list. as_deref ( ) )
1252- {
1253- emit_svg_fill_clip ( render, clip_id, fill_graphic_list, item_transform, render_params) ;
1254- }
1255-
12561213 // When splitting passes and stroke is below, draw the fill after the stroke.
12571214 if needs_separate_alignment_fill && wants_stroke_below {
1258- emit_aligned_fill_pass (
1215+ emit_svg_fill_path (
12591216 render,
1260- path,
1217+ path. clone ( ) ,
12611218 element_transform,
1262- item_transform,
12631219 fill_graphic_list. as_deref ( ) ,
1264- clip_id. as_deref ( ) ,
12651220 applied_stroke_transform,
12661221 bounds_matrix,
12671222 transformed_bounds_matrix,
1223+ item_transform,
12681224 render_params,
12691225 ) ;
12701226 }
0 commit comments