11use crate :: instances:: Instance ;
2- use crate :: math:: math_ext:: QuadExt ;
32pub use crate :: math:: quad:: Quad ;
43pub use crate :: math:: rect:: Rect ;
54use crate :: raster:: { BlendMode , Image } ;
65use crate :: raster_types:: { CPU , GPU , RasterDataTable } ;
76use crate :: transform:: { Footprint , Transform } ;
87use crate :: uuid:: { NodeId , generate_uuid} ;
8+ use crate :: vector:: VectorDataTable ;
9+ use crate :: vector:: click_target:: { ClickTarget , FreePoint } ;
910use crate :: vector:: style:: { Fill , Stroke , StrokeAlign , ViewMode } ;
10- use crate :: vector:: { PointId , VectorDataTable } ;
1111use crate :: { Artboard , ArtboardGroupTable , Color , GraphicElement , GraphicGroupTable } ;
1212use base64:: Engine ;
1313use bezier_rs:: Subpath ;
1414use dyn_any:: DynAny ;
15- use glam:: { DAffine2 , DMat2 , DVec2 } ;
15+ use glam:: { DAffine2 , DVec2 } ;
1616use num_traits:: Zero ;
1717use std:: collections:: { HashMap , HashSet } ;
1818use std:: fmt:: Write ;
1919#[ cfg( feature = "vello" ) ]
2020use vello:: * ;
2121
22- #[ derive( Copy , Clone , Debug , PartialEq , serde:: Serialize , serde:: Deserialize ) ]
23- pub struct FreePoint {
24- pub id : PointId ,
25- pub position : DVec2 ,
26- }
27-
28- impl FreePoint {
29- pub fn new ( id : PointId , position : DVec2 ) -> Self {
30- Self { id, position }
31- }
32-
33- pub fn apply_transform ( & mut self , transform : DAffine2 ) {
34- self . position = transform. transform_point2 ( self . position ) ;
35- }
36- }
37-
38- #[ derive( Clone , Debug , PartialEq , serde:: Serialize , serde:: Deserialize ) ]
39- pub enum ClickTargetType {
40- Subpath ( Subpath < PointId > ) ,
41- FreePoint ( FreePoint ) ,
42- }
43-
44- /// Represents a clickable target for the layer
45- #[ derive( Clone , Debug , PartialEq , serde:: Serialize , serde:: Deserialize ) ]
46- pub struct ClickTarget {
47- target_type : ClickTargetType ,
48- stroke_width : f64 ,
49- bounding_box : Option < [ DVec2 ; 2 ] > ,
50- }
51-
5222#[ derive( Clone , Copy , Debug , PartialEq , serde:: Serialize , serde:: Deserialize ) ]
5323enum MaskType {
5424 Clip ,
@@ -72,133 +42,6 @@ impl MaskType {
7242 }
7343}
7444
75- impl ClickTarget {
76- pub fn new_with_subpath ( subpath : Subpath < PointId > , stroke_width : f64 ) -> Self {
77- let bounding_box = subpath. loose_bounding_box ( ) ;
78- Self {
79- target_type : ClickTargetType :: Subpath ( subpath) ,
80- stroke_width,
81- bounding_box,
82- }
83- }
84-
85- pub fn new_with_free_point ( point : FreePoint ) -> Self {
86- const MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT : f64 = 1e-4 / 2. ;
87- let stroke_width = 10. ;
88- let bounding_box = Some ( [
89- point. position - DVec2 :: splat ( MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT ) ,
90- point. position + DVec2 :: splat ( MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT ) ,
91- ] ) ;
92-
93- Self {
94- target_type : ClickTargetType :: FreePoint ( point) ,
95- stroke_width,
96- bounding_box,
97- }
98- }
99-
100- pub fn target_type ( & self ) -> & ClickTargetType {
101- & self . target_type
102- }
103-
104- pub fn bounding_box ( & self ) -> Option < [ DVec2 ; 2 ] > {
105- self . bounding_box
106- }
107-
108- pub fn bounding_box_with_transform ( & self , transform : DAffine2 ) -> Option < [ DVec2 ; 2 ] > {
109- self . bounding_box . map ( |[ a, b] | [ transform. transform_point2 ( a) , transform. transform_point2 ( b) ] )
110- }
111-
112- pub fn apply_transform ( & mut self , affine_transform : DAffine2 ) {
113- match self . target_type {
114- ClickTargetType :: Subpath ( ref mut subpath) => {
115- subpath. apply_transform ( affine_transform) ;
116- }
117- ClickTargetType :: FreePoint ( ref mut point) => {
118- point. apply_transform ( affine_transform) ;
119- }
120- }
121- self . update_bbox ( ) ;
122- }
123-
124- fn update_bbox ( & mut self ) {
125- match self . target_type {
126- ClickTargetType :: Subpath ( ref subpath) => {
127- self . bounding_box = subpath. bounding_box ( ) ;
128- }
129- ClickTargetType :: FreePoint ( ref point) => {
130- self . bounding_box = Some ( [ point. position - DVec2 :: splat ( self . stroke_width / 2. ) , point. position + DVec2 :: splat ( self . stroke_width / 2. ) ] ) ;
131- }
132- }
133- }
134-
135- /// Does the click target intersect the path
136- pub fn intersect_path < It : Iterator < Item = bezier_rs:: Bezier > > ( & self , mut bezier_iter : impl FnMut ( ) -> It , layer_transform : DAffine2 ) -> bool {
137- // Check if the matrix is not invertible
138- let mut layer_transform = layer_transform;
139- if layer_transform. matrix2 . determinant ( ) . abs ( ) <= f64:: EPSILON {
140- layer_transform. matrix2 += DMat2 :: IDENTITY * 1e-4 ; // TODO: Is this the cleanest way to handle this?
141- }
142-
143- let inverse = layer_transform. inverse ( ) ;
144- let mut bezier_iter = || bezier_iter ( ) . map ( |bezier| bezier. apply_transformation ( |point| inverse. transform_point2 ( point) ) ) ;
145-
146- match self . target_type ( ) {
147- ClickTargetType :: Subpath ( subpath) => {
148- // Check if outlines intersect
149- let outline_intersects = |path_segment : bezier_rs:: Bezier | bezier_iter ( ) . any ( |line| !path_segment. intersections ( & line, None , None ) . is_empty ( ) ) ;
150- if subpath. iter ( ) . any ( outline_intersects) {
151- return true ;
152- }
153- // Check if selection is entirely within the shape
154- if subpath. closed ( ) && bezier_iter ( ) . next ( ) . is_some_and ( |bezier| subpath. contains_point ( bezier. start ) ) {
155- return true ;
156- }
157-
158- // Check if shape is entirely within selection
159- let any_point_from_subpath = subpath. manipulator_groups ( ) . first ( ) . map ( |group| group. anchor ) ;
160- any_point_from_subpath. is_some_and ( |shape_point| bezier_iter ( ) . map ( |bezier| bezier. winding ( shape_point) ) . sum :: < i32 > ( ) != 0 )
161- }
162- ClickTargetType :: FreePoint ( point) => bezier_iter ( ) . map ( |bezier : bezier_rs:: Bezier | bezier. winding ( point. position ) ) . sum :: < i32 > ( ) != 0 ,
163- }
164- }
165-
166- /// Does the click target intersect the point (accounting for stroke size)
167- pub fn intersect_point ( & self , point : DVec2 , layer_transform : DAffine2 ) -> bool {
168- let target_bounds = [ point - DVec2 :: splat ( self . stroke_width / 2. ) , point + DVec2 :: splat ( self . stroke_width / 2. ) ] ;
169- let intersects = |a : [ DVec2 ; 2 ] , b : [ DVec2 ; 2 ] | a[ 0 ] . x <= b[ 1 ] . x && a[ 1 ] . x >= b[ 0 ] . x && a[ 0 ] . y <= b[ 1 ] . y && a[ 1 ] . y >= b[ 0 ] . y ;
170- // This bounding box is not very accurate as it is the axis aligned version of the transformed bounding box. However it is fast.
171- if !self
172- . bounding_box
173- . is_some_and ( |loose| ( loose[ 0 ] - loose[ 1 ] ) . abs ( ) . cmpgt ( DVec2 :: splat ( 1e-4 ) ) . any ( ) && intersects ( ( layer_transform * Quad :: from_box ( loose) ) . bounding_box ( ) , target_bounds) )
174- {
175- return false ;
176- }
177-
178- // Allows for selecting lines
179- // TODO: actual intersection of stroke
180- let inflated_quad = Quad :: from_box ( target_bounds) ;
181- self . intersect_path ( || inflated_quad. bezier_lines ( ) , layer_transform)
182- }
183-
184- /// Does the click target intersect the point (not accounting for stroke size)
185- pub fn intersect_point_no_stroke ( & self , point : DVec2 ) -> bool {
186- // Check if the point is within the bounding box
187- if self
188- . bounding_box
189- . is_some_and ( |bbox| bbox[ 0 ] . x <= point. x && point. x <= bbox[ 1 ] . x && bbox[ 0 ] . y <= point. y && point. y <= bbox[ 1 ] . y )
190- {
191- // Check if the point is within the shape
192- match self . target_type ( ) {
193- ClickTargetType :: Subpath ( subpath) => subpath. closed ( ) && subpath. contains_point ( point) ,
194- ClickTargetType :: FreePoint ( free_point) => free_point. position == point,
195- }
196- } else {
197- false
198- }
199- }
200- }
201-
20245/// Mutable state used whilst rendering to an SVG
20346pub struct SvgRender {
20447 pub svg : Vec < SvgSegment > ,
0 commit comments