GraphiteEditor/Graphite · warning

transform should be able to be reset to be able to account f

Error message

transform should be able to be reset to be able to account for DPI

What it means

end_dpi_aware_transform calls ctx.reset_transform(). Per the HTML spec resetTransform() is equivalent to setTransform(1, 0, 0, 1, 0, 0), and the identity matrix is always invertible, so on a live 2D context this call effectively cannot throw; web_sys still types it as Result and the .expect is purely defensive. If it ever fires it indicates an invalid or lost context rather than a geometry bug.

Source

Thrown at editor/src/messages/portfolio/document/overlays/utility_types_web.rs:620

			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();

		self.render_context.begin_path();
		self.render_context.rect(corner.x, corner.y, size, size);
		self.render_context.set_fill_style_str(color_fill);
		self.render_context.set_stroke_style_str(color_stroke);
		self.render_context.set_line_width(1.);
		self.render_context.fill();

View on GitHub (pinned to c507b35645)

Solutions

  1. Treat it as defensive: if observed, check canvas/context health and look for an earlier root-cause panic (e.g., a set_transform NaN failure in the same frame).
  2. Degrade to a logged .ok() so a context anomaly cannot abort the overlay pass.
  3. Ensure the context cache is invalidated when the underlying canvas element is recreated.

Example fix

// before
self.render_context.reset_transform().expect("transform should be able to be reset to be able to account for DPI");

// after — resetTransform() applies the identity matrix and cannot fail on a live context
if let Err(err) = self.render_context.reset_transform() {
	log::warn!("Failed to reset canvas transform: {:?}", err);
}
Defensive patterns

Strategy: fallback

Try / catch

if let Err(err) = self.render_context.reset_transform() {
	log::warn!("failed to reset canvas transform (context may be invalid): {:?}", err);
}

Prevention

When it happens

Trigger: Practically unreachable on a valid context. Conceivable only with an already-invalid context (lost after a GPU reset or detached canvas) or an engine-level failure inside resetTransform.

Common situations: Appearing in crash reports almost always as a secondary effect of a corrupted render context or a stale cached context after the canvas element was replaced — not as an independent defect.

Related errors


AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16). Data as JSON: /api/errors/36391a8f5373667f. Report an issue: GitHub.