@@ -26,6 +26,9 @@ pub struct OverlayContext {
2626 #[ specta( skip) ]
2727 pub render_context : web_sys:: CanvasRenderingContext2d ,
2828 pub size : DVec2 ,
29+ // The device pixel ratio is a property provided by the browser window and is the CSS pixel size divided by the physical monitor's pixel size.
30+ // It allows better pixel density of visualizations on high-DPI displays where the OS display scaling is not 100%, or where the browser is zoomed.
31+ pub device_pixel_ratio : f64 ,
2932}
3033// Message hashing isn't used but is required by the message system macros
3134impl core:: hash:: Hash for OverlayContext {
@@ -38,6 +41,8 @@ impl OverlayContext {
3841 }
3942
4043 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 > ) {
44+ self . start_dpi_aware_transform ( ) ;
45+
4146 // Set the dash pattern
4247 if let Some ( dash_width) = dash_width {
4348 let dash_gap_width = dash_gap_width. unwrap_or ( 1. ) ;
@@ -82,13 +87,17 @@ impl OverlayContext {
8287 if dash_offset. is_some ( ) && dash_offset != Some ( 0. ) {
8388 self . render_context . set_line_dash_offset ( 0. ) ;
8489 }
90+
91+ self . end_dpi_aware_transform ( ) ;
8592 }
8693
8794 pub fn line ( & mut self , start : DVec2 , end : DVec2 , color : Option < & str > ) {
8895 self . dashed_line ( start, end, color, None , None , None )
8996 }
9097
9198 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 > ) {
99+ self . start_dpi_aware_transform ( ) ;
100+
92101 // Set the dash pattern
93102 if let Some ( dash_width) = dash_width {
94103 let dash_gap_width = dash_gap_width. unwrap_or ( 1. ) ;
@@ -127,9 +136,13 @@ impl OverlayContext {
127136 if dash_offset. is_some ( ) && dash_offset != Some ( 0. ) {
128137 self . render_context . set_line_dash_offset ( 0. ) ;
129138 }
139+
140+ self . end_dpi_aware_transform ( ) ;
130141 }
131142
132143 pub fn manipulator_handle ( & mut self , position : DVec2 , selected : bool , color : Option < & str > ) {
144+ self . start_dpi_aware_transform ( ) ;
145+
133146 let position = position. round ( ) - DVec2 :: splat ( 0.5 ) ;
134147
135148 self . render_context . begin_path ( ) ;
@@ -142,6 +155,8 @@ impl OverlayContext {
142155 self . render_context . set_stroke_style_str ( color. unwrap_or ( COLOR_OVERLAY_BLUE ) ) ;
143156 self . render_context . fill ( ) ;
144157 self . render_context . stroke ( ) ;
158+
159+ self . end_dpi_aware_transform ( ) ;
145160 }
146161
147162 pub fn manipulator_anchor ( & mut self , position : DVec2 , selected : bool , color : Option < & str > ) {
@@ -150,6 +165,23 @@ impl OverlayContext {
150165 self . square ( position, None , Some ( color_fill) , Some ( color_stroke) ) ;
151166 }
152167
168+ /// Transforms the canvas context to adjust for DPI scaling
169+ ///
170+ /// Overwrites all existing tranforms. This operation can be reversed with [`Self::reset_transform`].
171+ fn start_dpi_aware_transform ( & self ) {
172+ let [ a, b, c, d, e, f] = DAffine2 :: from_scale ( DVec2 :: splat ( self . device_pixel_ratio ) ) . to_cols_array ( ) ;
173+ self . render_context
174+ . set_transform ( a, b, c, d, e, f)
175+ . expect ( "transform should be able to be set to be able to account for DPI" ) ;
176+ }
177+
178+ /// Un-transforms the Canvas context to adjust for DPI scaling
179+ ///
180+ /// Warning: this function doesn't only reset the DPI scaling adjustment, it resets the entire transform.
181+ fn end_dpi_aware_transform ( & self ) {
182+ self . render_context . reset_transform ( ) . expect ( "transform should be able to be reset to be able to account for DPI" ) ;
183+ }
184+
153185 pub fn square ( & mut self , position : DVec2 , size : Option < f64 > , color_fill : Option < & str > , color_stroke : Option < & str > ) {
154186 let size = size. unwrap_or ( MANIPULATOR_GROUP_MARKER_SIZE ) ;
155187 let color_fill = color_fill. unwrap_or ( COLOR_OVERLAY_WHITE ) ;
@@ -158,12 +190,16 @@ impl OverlayContext {
158190 let position = position. round ( ) - DVec2 :: splat ( 0.5 ) ;
159191 let corner = position - DVec2 :: splat ( size) / 2. ;
160192
193+ self . start_dpi_aware_transform ( ) ;
194+
161195 self . render_context . begin_path ( ) ;
162196 self . render_context . rect ( corner. x , corner. y , size, size) ;
163197 self . render_context . set_fill_style_str ( color_fill) ;
164198 self . render_context . set_stroke_style_str ( color_stroke) ;
165199 self . render_context . fill ( ) ;
166200 self . render_context . stroke ( ) ;
201+
202+ self . end_dpi_aware_transform ( ) ;
167203 }
168204
169205 pub fn pixel ( & mut self , position : DVec2 , color : Option < & str > ) {
@@ -173,22 +209,31 @@ impl OverlayContext {
173209 let position = position. round ( ) - DVec2 :: splat ( 0.5 ) ;
174210 let corner = position - DVec2 :: splat ( size) / 2. ;
175211
212+ self . start_dpi_aware_transform ( ) ;
213+
176214 self . render_context . begin_path ( ) ;
177215 self . render_context . rect ( corner. x , corner. y , size, size) ;
178216 self . render_context . set_fill_style_str ( color_fill) ;
179217 self . render_context . fill ( ) ;
218+
219+ self . end_dpi_aware_transform ( ) ;
180220 }
181221
182222 pub fn circle ( & mut self , position : DVec2 , radius : f64 , color_fill : Option < & str > , color_stroke : Option < & str > ) {
183223 let color_fill = color_fill. unwrap_or ( COLOR_OVERLAY_WHITE ) ;
184224 let color_stroke = color_stroke. unwrap_or ( COLOR_OVERLAY_BLUE ) ;
185225 let position = position. round ( ) ;
226+
227+ self . start_dpi_aware_transform ( ) ;
228+
186229 self . render_context . begin_path ( ) ;
187230 self . render_context . arc ( position. x , position. y , radius, 0. , TAU ) . expect ( "Failed to draw the circle" ) ;
188231 self . render_context . set_fill_style_str ( color_fill) ;
189232 self . render_context . set_stroke_style_str ( color_stroke) ;
190233 self . render_context . fill ( ) ;
191234 self . render_context . stroke ( ) ;
235+
236+ self . end_dpi_aware_transform ( ) ;
192237 }
193238
194239 pub fn draw_arc ( & mut self , center : DVec2 , radius : f64 , start_from : f64 , end_at : f64 ) {
@@ -252,6 +297,8 @@ impl OverlayContext {
252297 pub fn pivot ( & mut self , position : DVec2 ) {
253298 let ( x, y) = ( position. round ( ) - DVec2 :: splat ( 0.5 ) ) . into ( ) ;
254299
300+ self . start_dpi_aware_transform ( ) ;
301+
255302 // Circle
256303
257304 self . render_context . begin_path ( ) ;
@@ -276,9 +323,15 @@ impl OverlayContext {
276323 self . render_context . move_to ( x, y - crosshair_radius) ;
277324 self . render_context . line_to ( x, y + crosshair_radius) ;
278325 self . render_context . stroke ( ) ;
326+
327+ self . render_context . set_line_cap ( "butt" ) ;
328+
329+ self . end_dpi_aware_transform ( ) ;
279330 }
280331
281332 pub fn outline_vector ( & mut self , vector_data : & VectorData , transform : DAffine2 ) {
333+ self . start_dpi_aware_transform ( ) ;
334+
282335 self . render_context . begin_path ( ) ;
283336 let mut last_point = None ;
284337 for ( _, bezier, start_id, end_id) in vector_data. segment_bezier_iter ( ) {
@@ -290,16 +343,24 @@ impl OverlayContext {
290343
291344 self . render_context . set_stroke_style_str ( COLOR_OVERLAY_BLUE ) ;
292345 self . render_context . stroke ( ) ;
346+
347+ self . end_dpi_aware_transform ( ) ;
293348 }
294349
295350 pub fn outline_bezier ( & mut self , bezier : Bezier , transform : DAffine2 ) {
351+ self . start_dpi_aware_transform ( ) ;
352+
296353 self . render_context . begin_path ( ) ;
297354 self . bezier_command ( bezier, transform, true ) ;
298355 self . render_context . set_stroke_style_str ( COLOR_OVERLAY_BLUE ) ;
299356 self . render_context . stroke ( ) ;
357+
358+ self . end_dpi_aware_transform ( ) ;
300359 }
301360
302361 fn bezier_command ( & self , bezier : Bezier , transform : DAffine2 , move_to : bool ) {
362+ self . start_dpi_aware_transform ( ) ;
363+
303364 let Bezier { start, end, handles } = bezier. apply_transformation ( |point| transform. transform_point2 ( point) ) ;
304365 if move_to {
305366 self . render_context . move_to ( start. x , start. y ) ;
@@ -310,9 +371,13 @@ impl OverlayContext {
310371 bezier_rs:: BezierHandles :: Quadratic { handle } => self . render_context . quadratic_curve_to ( handle. x , handle. y , end. x , end. y ) ,
311372 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 ) ,
312373 }
374+
375+ self . end_dpi_aware_transform ( ) ;
313376 }
314377
315378 pub fn outline ( & mut self , subpaths : impl Iterator < Item = impl Borrow < Subpath < PointId > > > , transform : DAffine2 ) {
379+ self . start_dpi_aware_transform ( ) ;
380+
316381 self . render_context . begin_path ( ) ;
317382 for subpath in subpaths {
318383 let subpath = subpath. borrow ( ) ;
@@ -359,6 +424,8 @@ impl OverlayContext {
359424
360425 self . render_context . set_stroke_style_str ( COLOR_OVERLAY_BLUE ) ;
361426 self . render_context . stroke ( ) ;
427+
428+ self . end_dpi_aware_transform ( ) ;
362429 }
363430
364431 pub fn get_width ( & self , text : & str ) -> f64 {
@@ -378,7 +445,7 @@ impl OverlayContext {
378445 Pivot :: End => -padding,
379446 } ;
380447
381- let [ a, b, c, d, e, f] = ( transform * DAffine2 :: from_translation ( DVec2 :: new ( x, y) ) ) . to_cols_array ( ) ;
448+ 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 ( ) ;
382449 self . render_context . set_transform ( a, b, c, d, e, f) . expect ( "Failed to rotate the render context to the specified angle" ) ;
383450
384451 if let Some ( background) = background_color {
0 commit comments