GraphiteEditor/Graphite · error
transform should be able to be set to be able to account for
Error message
transform should be able to be set to be able to account for DPI
What it means
start_dpi_aware_transform pushes a uniform scale matrix built from self.viewport.scale() via ctx.set_transform(...). The JS setTransform throws when any matrix entry is non-finite or the matrix is non-invertible. Because the matrix is a pure scale, the only realistic trigger is a non-finite viewport scale (NaN or Inf zoom) — and since every DPI-aware overlay primitive funnels through this helper, one bad scale panics on the first overlay draw of the frame.
Source
Thrown at editor/src/messages/portfolio/document/overlays/utility_types_web.rs:613
}
pub fn skew_handles(&mut self, edge_start: DVec2, edge_end: DVec2) {
let edge_dir = (edge_end - edge_start).normalize();
let mid = edge_end.midpoint(edge_start);
for edge in [edge_dir, -edge_dir] {
self.draw_triangle(mid + edge * (3. + SKEW_TRIANGLE_OFFSET), edge, SKEW_TRIANGLE_SIZE, None, None);
}
}
/// Transforms the canvas context to adjust for DPI scaling
///
/// Overwrites all existing tranforms. This operation can be reversed with [`Self::reset_transform`].
fn start_dpi_aware_transform(&self) {
let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(self.viewport.scale())).to_cols_array();
self.render_context
.set_transform(a, b, c, d, e, f)
.expect("transform should be able to be set to be able to account for DPI");
}
/// Un-transforms the Canvas context to adjust for DPI scaling
///
/// Warning: this function doesn't only reset the DPI scaling adjustment, it resets the entire transform.
fn end_dpi_aware_transform(&self) {
self.render_context.reset_transform().expect("transform should be able to be reset to be able to account for DPI");
}
pub fn square(&mut self, position: DVec2, size: Option<f64>, color_fill: Option<&str>, color_stroke: Option<&str>) {
let size = size.unwrap_or(MANIPULATOR_GROUP_MARKER_SIZE);
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
let position = self.snap_to_physical_pixel_center(position);
let corner = position - DVec2::splat(size) / 2.;
self.start_dpi_aware_transform();View on GitHub (pinned to c507b35645)
Solutions
- Guard the scale at its computation site: clamp zoom to a finite positive range and skip updates when viewport size is zero.
- Skip overlay drawing entirely while the viewport has zero or non-finite dimensions.
- Degrade the .expect to .ok() with a warn so one bad frame logs instead of crashing.
Example fix
// before
let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(self.viewport.scale())).to_cols_array();
self.render_context
.set_transform(a, b, c, d, e, f)
.expect("transform should be able to be set to be able to account for DPI");
// after
let scale = self.viewport.scale();
if !scale.is_finite() || scale <= 0. {
log::warn!("Non-finite viewport scale; skipping DPI-aware transform");
return;
}
let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(scale)).to_cols_array();
let _ = self.render_context.set_transform(a, b, c, d, e, f); Defensive patterns
Strategy: validation
Validate before calling
fn viewport_scale_drawable(scale: f64) -> bool {
scale.is_finite() && scale > 0.
}
// check before start_dpi_aware_transform runs Type guard
fn is_finite_positive(scale: f64) -> bool {
scale.is_finite() && scale > 0.
} Try / catch
if let Err(err) = self.render_context.set_transform(a, b, c, d, e, f) {
log::warn!("failed to set DPI transform: {:?}", err);
} Prevention
- Clamp zoom/scale to a finite positive range where it is computed
- Skip overlay rendering while the viewport has zero dimensions (resize/teardown)
- Never propagate non-finite scale into every overlay primitive via a shared helper
When it happens
Trigger: Viewport scale computed as NaN/Inf — e.g., dividing by a zero-size viewport during window resize or teardown, or a zoom animation hitting a division by zero — followed by any overlay draw calling start_dpi_aware_transform (square, manipulator, pivot, compass).
Common situations: Resizing the editor pane to 0x0 or during window teardown; zoom animation edge cases; hi-DPI scale computations dividing by zero dimensions.
Related errors
- Failed to draw ellipse
- Failed to transform circle
- Failed to draw the circle
- Failed to draw hover ring
- Failed to draw main ring
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/be09bcf6e618c831.
Report an issue: GitHub.