userSpaceOnUse/objectBoundingBox - #4283
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for preserving and rendering SVG gradientUnits (userSpaceOnUse and objectBoundingBox) during import and export. It adds a GradientUnits enum, extracts the units from the raw SVG XML during import, propagates the property through the node graph, and utilizes it during rendering to correctly set the gradientUnits attribute and adjust the gradient transform. The feedback recommends optimizing the import process by avoiding redundant XML parsing of the same SVG string across multiple functions, and suggests renaming the _bounds variable to bounds since it is now actively used.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| fn extract_svg_gradient_units(svg: &str) -> HashMap<String, GradientUnits> { | ||
| let mut result = HashMap::new(); | ||
| let mut gradient_nodes = HashMap::new(); | ||
|
|
||
| let doc = match usvg::roxmltree::Document::parse(svg) { | ||
| Ok(doc) => doc, | ||
| Err(_) => return result, | ||
| }; |
There was a problem hiding this comment.
Redundant XML Parsing
Both extract_graphite_gradient_stops and extract_svg_gradient_units parse the same raw SVG XML string independently using usvg::roxmltree::Document::parse. For large SVG files, parsing the XML tree multiple times introduces unnecessary CPU and memory overhead.
Consider refactoring these functions to accept a shared reference to the parsed roxmltree::Document, or combining them into a single pass over the XML descendants to extract both gradient stops and units simultaneously.
|
|
||
| let placement = gradient_placement(document_transform, gradient_type); | ||
| let gradient_transform = format_transform_matrix(element_transform_inverse * placement); | ||
| let (gradient_units, gradient_transform) = svg_gradient_transform(element_transform_inverse * placement, _bounds, gradient_units); |
There was a problem hiding this comment.
Used Variable with Underscore Prefix
The _bounds variable is now used in the call to svg_gradient_transform. By Rust convention, variables prefixed with an underscore should remain unused. Since it is now used, consider renaming _bounds to bounds in the function signature of RenderExt::render for List<GradientStops> to improve code clarity and adhere to standard style guidelines.
|
@jsjgdh I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 10 files
Confidence score: 5/5
- Safe to merge after the addressed issues were fixed.
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
3a673d1 to
3ae1a44
Compare
3ae1a44 to
e4ab060
Compare
There was a problem hiding this comment.
Review completed against the latest diff
Confidence score: 5/5
- Safe to merge after the addressed issues were fixed.
Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.
Re-trigger cubic
d27f647 to
754f5e9
Compare
a934ff8 to
7eb8f76
Compare
There was a problem hiding this comment.
2 issues found across 9 files
Confidence score: 3/5
node-graph/libraries/rendering/src/render_ext.rs:ObjectBoundingBoxgradients do not convertgradient_transformusing the object bounds, so transformed or imported gradients can render with incorrect scale or position — passboundsthrough the gradient-transform conversion.editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs:extract_gradient_unitsreparses the full SVG, adding a third parse during import and potentially increasing processing cost — reuse the existing parsed document or consolidate the extraction passes.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="node-graph/libraries/rendering/src/render_ext.rs">
<violation number="1" location="node-graph/libraries/rendering/src/render_ext.rs:102">
P1: When `gradient_units` is `ObjectBoundingBox`, this emits normalized coordinates without converting `gradient_transform`, so transformed or imported gradients render with incorrect scale or position. Pass `bounds` through and apply `svg_gradient_transform` before emitting the effective units and transform.</violation>
</file>
<file name="editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs">
<violation number="1" location="editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs:629">
P3: `extract_gradient_units` re-parses the entire SVG document, so the import path now parses it a third time: `extract_graphite_gradient_stops` (line 765), `extract_gradient_spaces` (line 594), and this new `extract_gradient_units` (line 629) each invoke `Document::parse(svg)` independently. Parsing untrusted SVG XML is comparatively expensive and all run on every SVG import. Consider parsing the document once and reusing it across the extraction helpers to avoid redundant full-document parses.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| let item = item?; | ||
| let stops = item.element()?; | ||
| let gradient_form: GradientForm = item.attribute_cloned_or_default(ATTR_GRADIENT_FORM); | ||
| let gradient_units: GradientUnits = item.attribute_cloned_or_default(ATTR_GRADIENT_UNITS); |
There was a problem hiding this comment.
P1: When gradient_units is ObjectBoundingBox, this emits normalized coordinates without converting gradient_transform, so transformed or imported gradients render with incorrect scale or position. Pass bounds through and apply svg_gradient_transform before emitting the effective units and transform.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At node-graph/libraries/rendering/src/render_ext.rs, line 102:
<comment>When `gradient_units` is `ObjectBoundingBox`, this emits normalized coordinates without converting `gradient_transform`, so transformed or imported gradients render with incorrect scale or position. Pass `bounds` through and apply `svg_gradient_transform` before emitting the effective units and transform.</comment>
<file context>
@@ -90,6 +99,7 @@ fn render_gradient_paint(item: Option<ItemRef<'_, Gradient>>, svg_defs: &mut Str
let item = item?;
let stops = item.element()?;
let gradient_form: GradientForm = item.attribute_cloned_or_default(ATTR_GRADIENT_FORM);
+ let gradient_units: GradientUnits = item.attribute_cloned_or_default(ATTR_GRADIENT_UNITS);
let local_gradient_transform: DAffine2 = item.attribute_cloned_or_default(ATTR_TRANSFORM);
let settings = gradient_settings_from_item(item);
</file context>
| fn extract_gradient_units(svg: &str) -> HashMap<String, GradientUnits> { | ||
| let mut result = HashMap::new(); | ||
|
|
||
| let doc = match usvg::roxmltree::Document::parse(svg) { |
There was a problem hiding this comment.
P3: extract_gradient_units re-parses the entire SVG document, so the import path now parses it a third time: extract_graphite_gradient_stops (line 765), extract_gradient_spaces (line 594), and this new extract_gradient_units (line 629) each invoke Document::parse(svg) independently. Parsing untrusted SVG XML is comparatively expensive and all run on every SVG import. Consider parsing the document once and reusing it across the extraction helpers to avoid redundant full-document parses.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs, line 629:
<comment>`extract_gradient_units` re-parses the entire SVG document, so the import path now parses it a third time: `extract_graphite_gradient_stops` (line 765), `extract_gradient_spaces` (line 594), and this new `extract_gradient_units` (line 629) each invoke `Document::parse(svg)` independently. Parsing untrusted SVG XML is comparatively expensive and all run on every SVG import. Consider parsing the document once and reusing it across the extraction helpers to avoid redundant full-document parses.</comment>
<file context>
@@ -620,6 +623,59 @@ fn extract_gradient_spaces(svg: &str) -> HashMap<String, GradientSpace> {
+fn extract_gradient_units(svg: &str) -> HashMap<String, GradientUnits> {
+ let mut result = HashMap::new();
+
+ let doc = match usvg::roxmltree::Document::parse(svg) {
+ Ok(doc) => doc,
+ Err(_) => return result,
</file context>
Request
This feature depends on Yohei's ongoing refactor and should be merged after that work is completed.