@@ -24,6 +24,12 @@ pub struct OverlayContext {
2424 #[ specta( skip) ]
2525 pub render_context : web_sys:: CanvasRenderingContext2d ,
2626 pub size : DVec2 ,
27+ // The device pixel ratio is a property provided
28+ // by the browser window and is the css pixel size
29+ // divided by the physical pixel size. It allows
30+ // better pixel density of visualizations on
31+ // high dpi displays (such as Retina displays).
32+ pub device_pixel_ratio : f64 ,
2733}
2834// Message hashing isn't used but is required by the message system macros
2935impl core:: hash:: Hash for OverlayContext {
@@ -37,6 +43,7 @@ impl OverlayContext {
3743
3844 pub fn dashed_quad ( & mut self , quad : Quad , color_fill : Option < & str > , dash_width : Option < f64 > , dash_gap_width : Option < f64 > , dash_offset : Option < f64 > ) {
3945 // Set the dash pattern
46+ self . start_dpi_aware_transform ( ) ;
4047 if let Some ( dash_width) = dash_width {
4148 let dash_gap_width = dash_gap_width. unwrap_or ( 1. ) ;
4249 let array = js_sys:: Array :: new ( ) ;
@@ -80,6 +87,7 @@ impl OverlayContext {
8087 if dash_offset. is_some ( ) && dash_offset != Some ( 0. ) {
8188 self . render_context . set_line_dash_offset ( 0. ) ;
8289 }
90+ self . end_dpi_aware_transform ( ) ;
8391 }
8492
8593 pub fn line ( & mut self , start : DVec2 , end : DVec2 , color : Option < & str > ) {
@@ -88,6 +96,7 @@ impl OverlayContext {
8896
8997 pub fn dashed_line ( & mut self , start : DVec2 , end : DVec2 , color : Option < & str > , dash_width : Option < f64 > , dash_gap_width : Option < f64 > , dash_offset : Option < f64 > ) {
9098 // Set the dash pattern
99+ self . start_dpi_aware_transform ( ) ;
91100 if let Some ( dash_width) = dash_width {
92101 let dash_gap_width = dash_gap_width. unwrap_or ( 1. ) ;
93102 let array = js_sys:: Array :: new ( ) ;
@@ -125,9 +134,11 @@ impl OverlayContext {
125134 if dash_offset. is_some ( ) && dash_offset != Some ( 0. ) {
126135 self . render_context . set_line_dash_offset ( 0. ) ;
127136 }
137+ self . end_dpi_aware_transform ( ) ;
128138 }
129139
130140 pub fn manipulator_handle ( & mut self , position : DVec2 , selected : bool , color : Option < & str > ) {
141+ self . start_dpi_aware_transform ( ) ;
131142 let position = position. round ( ) - DVec2 :: splat ( 0.5 ) ;
132143
133144 self . render_context . begin_path ( ) ;
@@ -140,6 +151,7 @@ impl OverlayContext {
140151 self . render_context . set_stroke_style_str ( color. unwrap_or ( COLOR_OVERLAY_BLUE ) ) ;
141152 self . render_context . fill ( ) ;
142153 self . render_context . stroke ( ) ;
154+ self . end_dpi_aware_transform ( ) ;
143155 }
144156
145157 pub fn manipulator_anchor ( & mut self , position : DVec2 , selected : bool , color : Option < & str > ) {
@@ -148,6 +160,25 @@ impl OverlayContext {
148160 self . square ( position, None , Some ( color_fill) , Some ( color_stroke) ) ;
149161 }
150162
163+ /// Transforms the Canvas Context To Adjust for DPI
164+ ///
165+ /// Overwrites all existing tranforms.
166+ /// This operation can be reversed with [`Self::reset_transform`].
167+ fn start_dpi_aware_transform ( & self ) {
168+ let [ a, b, c, d, e, f] = DAffine2 :: from_scale ( DVec2 :: splat ( self . device_pixel_ratio ) ) . to_cols_array ( ) ;
169+ self . render_context
170+ . set_transform ( a, b, c, d, e, f)
171+ . expect ( "transform should be able to be set to be able to account for DPI" ) ;
172+ }
173+
174+ /// Untransforms the Canvas Context To Adjust for DPI
175+ ///
176+ /// Warning: this function doesn't only reset the
177+ /// DPI adjustment, it resets the entire transform.
178+ fn end_dpi_aware_transform ( & self ) {
179+ self . render_context . reset_transform ( ) . expect ( "transform should be able to be reset to be able to account for DPI" ) ;
180+ }
181+
151182 pub fn square ( & mut self , position : DVec2 , size : Option < f64 > , color_fill : Option < & str > , color_stroke : Option < & str > ) {
152183 let size = size. unwrap_or ( MANIPULATOR_GROUP_MARKER_SIZE ) ;
153184 let color_fill = color_fill. unwrap_or ( COLOR_OVERLAY_WHITE ) ;
@@ -156,12 +187,14 @@ impl OverlayContext {
156187 let position = position. round ( ) - DVec2 :: splat ( 0.5 ) ;
157188 let corner = position - DVec2 :: splat ( size) / 2. ;
158189
190+ self . start_dpi_aware_transform ( ) ;
159191 self . render_context . begin_path ( ) ;
160192 self . render_context . rect ( corner. x , corner. y , size, size) ;
161193 self . render_context . set_fill_style_str ( color_fill) ;
162194 self . render_context . set_stroke_style_str ( color_stroke) ;
163195 self . render_context . fill ( ) ;
164196 self . render_context . stroke ( ) ;
197+ self . end_dpi_aware_transform ( ) ;
165198 }
166199
167200 pub fn pixel ( & mut self , position : DVec2 , color : Option < & str > ) {
@@ -171,26 +204,32 @@ impl OverlayContext {
171204 let position = position. round ( ) - DVec2 :: splat ( 0.5 ) ;
172205 let corner = position - DVec2 :: splat ( size) / 2. ;
173206
207+ self . start_dpi_aware_transform ( ) ;
174208 self . render_context . begin_path ( ) ;
175209 self . render_context . rect ( corner. x , corner. y , size, size) ;
176210 self . render_context . set_fill_style_str ( color_fill) ;
177211 self . render_context . fill ( ) ;
212+ self . end_dpi_aware_transform ( ) ;
178213 }
179214
180215 pub fn circle ( & mut self , position : DVec2 , radius : f64 , color_fill : Option < & str > , color_stroke : Option < & str > ) {
181216 let color_fill = color_fill. unwrap_or ( COLOR_OVERLAY_WHITE ) ;
182217 let color_stroke = color_stroke. unwrap_or ( COLOR_OVERLAY_BLUE ) ;
183218 let position = position. round ( ) ;
219+
220+ self . start_dpi_aware_transform ( ) ;
184221 self . render_context . begin_path ( ) ;
185222 self . render_context . arc ( position. x , position. y , radius, 0. , TAU ) . expect ( "Failed to draw the circle" ) ;
186223 self . render_context . set_fill_style_str ( color_fill) ;
187224 self . render_context . set_stroke_style_str ( color_stroke) ;
188225 self . render_context . fill ( ) ;
189226 self . render_context . stroke ( ) ;
227+ self . end_dpi_aware_transform ( ) ;
190228 }
191229 pub fn pivot ( & mut self , position : DVec2 ) {
192230 let ( x, y) = ( position. round ( ) - DVec2 :: splat ( 0.5 ) ) . into ( ) ;
193231
232+ self . start_dpi_aware_transform ( ) ;
194233 // Circle
195234
196235 self . render_context . begin_path ( ) ;
@@ -215,9 +254,11 @@ impl OverlayContext {
215254 self . render_context . move_to ( x, y - crosshair_radius) ;
216255 self . render_context . line_to ( x, y + crosshair_radius) ;
217256 self . render_context . stroke ( ) ;
257+ self . end_dpi_aware_transform ( ) ;
218258 }
219259
220260 pub fn outline_vector ( & mut self , vector_data : & VectorData , transform : DAffine2 ) {
261+ self . start_dpi_aware_transform ( ) ;
221262 self . render_context . begin_path ( ) ;
222263 let mut last_point = None ;
223264 for ( _, bezier, start_id, end_id) in vector_data. segment_bezier_iter ( ) {
@@ -229,16 +270,20 @@ impl OverlayContext {
229270
230271 self . render_context . set_stroke_style_str ( COLOR_OVERLAY_BLUE ) ;
231272 self . render_context . stroke ( ) ;
273+ self . end_dpi_aware_transform ( ) ;
232274 }
233275
234276 pub fn outline_bezier ( & mut self , bezier : Bezier , transform : DAffine2 ) {
277+ self . start_dpi_aware_transform ( ) ;
235278 self . render_context . begin_path ( ) ;
236279 self . bezier_command ( bezier, transform, true ) ;
237280 self . render_context . set_stroke_style_str ( COLOR_OVERLAY_BLUE ) ;
238281 self . render_context . stroke ( ) ;
282+ self . end_dpi_aware_transform ( ) ;
239283 }
240284
241285 fn bezier_command ( & self , bezier : Bezier , transform : DAffine2 , move_to : bool ) {
286+ self . start_dpi_aware_transform ( ) ;
242287 let Bezier { start, end, handles } = bezier. apply_transformation ( |point| transform. transform_point2 ( point) ) ;
243288 if move_to {
244289 self . render_context . move_to ( start. x , start. y ) ;
@@ -249,9 +294,11 @@ impl OverlayContext {
249294 bezier_rs:: BezierHandles :: Quadratic { handle } => self . render_context . quadratic_curve_to ( handle. x , handle. y , end. x , end. y ) ,
250295 bezier_rs:: BezierHandles :: Cubic { handle_start, handle_end } => self . render_context . bezier_curve_to ( handle_start. x , handle_start. y , handle_end. x , handle_end. y , end. x , end. y ) ,
251296 }
297+ self . end_dpi_aware_transform ( ) ;
252298 }
253299
254300 pub fn outline ( & mut self , subpaths : impl Iterator < Item = impl Borrow < Subpath < PointId > > > , transform : DAffine2 ) {
301+ self . start_dpi_aware_transform ( ) ;
255302 self . render_context . begin_path ( ) ;
256303 for subpath in subpaths {
257304 let subpath = subpath. borrow ( ) ;
@@ -298,6 +345,7 @@ impl OverlayContext {
298345
299346 self . render_context . set_stroke_style_str ( COLOR_OVERLAY_BLUE ) ;
300347 self . render_context . stroke ( ) ;
348+ self . end_dpi_aware_transform ( ) ;
301349 }
302350
303351 pub fn text ( & self , text : & str , font_color : & str , background_color : Option < & str > , transform : DAffine2 , padding : f64 , pivot : [ Pivot ; 2 ] ) {
@@ -313,7 +361,7 @@ impl OverlayContext {
313361 Pivot :: End => -padding,
314362 } ;
315363
316- let [ a, b, c, d, e, f] = ( transform * DAffine2 :: from_translation ( DVec2 :: new ( x, y) ) ) . to_cols_array ( ) ;
364+ let [ a, b, c, d, e, f] = ( DAffine2 :: from_scale ( DVec2 :: splat ( self . device_pixel_ratio ) ) * transform * DAffine2 :: from_translation ( DVec2 :: new ( x, y) ) ) . to_cols_array ( ) ;
317365 self . render_context . set_transform ( a, b, c, d, e, f) . expect ( "Failed to rotate the render context to the specified angle" ) ;
318366
319367 if let Some ( background) = background_color {
0 commit comments