Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions editor/src/node_graph_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use graph_craft::document::value::{RenderOutput, TaggedValue};
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
use graph_craft::proto::GraphErrors;
use graph_craft::wasm_application_io::EditorPreferences;
use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig};
use graphene_std::application_io::{SurfaceFrame, TimingInformation};
use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig, TimingInformation};
use graphene_std::raster::{CPU, Raster};
use graphene_std::renderer::{RenderMetadata, format_transform_matrix};
use graphene_std::text::FontCache;
Expand Down Expand Up @@ -56,7 +55,6 @@ pub struct NodeGraphExecutor {
futures: VecDeque<(u64, ExecutionContext)>,
node_graph_hash: u64,
previous_node_to_inspect: Option<NodeId>,
last_svg_canvas: Option<SurfaceFrame>,
}

#[derive(Debug, Clone)]
Expand All @@ -79,7 +77,6 @@ impl NodeGraphExecutor {
node_graph_hash: 0,
current_execution_id: 0,
previous_node_to_inspect: None,
last_svg_canvas: None,
};
(node_runtime, node_executor)
}
Expand Down Expand Up @@ -384,19 +381,14 @@ impl NodeGraphExecutor {
// Send to frontend
responses.add(FrontendMessage::UpdateImageData { image_data });
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
self.last_svg_canvas = None;
}
RenderOutputType::CanvasFrame(frame) => 'block: {
if self.last_svg_canvas == Some(frame) {
break 'block;
}
RenderOutputType::CanvasFrame(frame) => {
let matrix = format_transform_matrix(frame.transform);
let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{matrix}\"") };
let svg = format!(
r#"<svg><foreignObject width="{}" height="{}"{transform}><div data-canvas-placeholder="{}" data-is-viewport="true"></div></foreignObject></svg>"#,
frame.resolution.x, frame.resolution.y, frame.surface_id.0,
);
Comment on lines 386 to 391
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current method of constructing the transform attribute by including a leading space in the string is a bit fragile. If the space is accidentally removed during future refactoring, it would result in invalid SVG. A more explicit approach using an if/else block to construct the SVG string with or without the transform attribute would be more robust and easier to maintain.

let matrix = format_transform_matrix(frame.transform);
let svg = if matrix.is_empty() {
	format!(
		r#"<svg><foreignObject width="{}" height="{}"><div data-canvas-placeholder="{}" data-is-viewport="true"></div></foreignObject></svg>"#,
		frame.resolution.x, frame.resolution.y, frame.surface_id.0
	)
} else {
	format!(
		r#"<svg><foreignObject width="{}" height="{}" transform="{}"><div data-canvas-placeholder="{}" data-is-viewport="true"></div></foreignObject></svg>"#,
		frame.resolution.x, frame.resolution.y, matrix, frame.surface_id.0
	)
};

self.last_svg_canvas = Some(frame);
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
}
RenderOutputType::Texture { .. } => {}
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/components/panels/Document.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,13 @@
const logicalWidth = parseFloat(foreignObject.getAttribute("width") || "0");
const logicalHeight = parseFloat(foreignObject.getAttribute("height") || "0");

// Clone canvas for repeated instances (layers that appear multiple times)
// Viewport canvas is marked with data-is-viewport and should never be cloned
// Viewport canvas is marked with data-is-viewport and should never be cloned.
// If it's already mounted in the viewport, skip the DOM replacement since it's already showing the rendered content.
// We check `canvas.isConnected` to ensure it's in the live DOM, not a detached tree from a destroyed component.
const isViewport = placeholder.hasAttribute("data-is-viewport");
if (isViewport && canvas.isConnected && canvas.parentElement?.closest("[data-viewport]")) return;

// Clone canvas for repeated instances (layers that appear multiple times)
if (!isViewport && canvas.parentElement) {
const newCanvas = window.document.createElement("canvas");
const context = newCanvas.getContext("2d");
Expand Down