-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
userSpaceOnUse/objectBoundingBox #4283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,7 +13,7 @@ use graph_craft::document::{NodeId, NodeInput}; | |
| use graphene_std::list::List; | ||
| use graphene_std::renderer::convert_usvg_path::convert_usvg_path; | ||
| use graphene_std::text::{Font, TypesettingConfig}; | ||
| use graphene_std::vector::style::{GradientSpreadMethod, GradientStop, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin}; | ||
| use graphene_std::vector::style::{GradientSpreadMethod, GradientStop, GradientStops, GradientType, GradientUnits, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin}; | ||
| use graphene_std::{Artboard, Color}; | ||
|
|
||
| #[derive(ExtractField)] | ||
|
|
@@ -562,6 +562,51 @@ fn extract_graphite_gradient_stops(svg: &str) -> HashMap<String, GradientStops> | |
| result | ||
| } | ||
|
|
||
| #[allow(dead_code)] | ||
| 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, | ||
| }; | ||
|
Comment on lines
+566
to
+573
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant XML ParsingBoth Consider refactoring these functions to accept a shared reference to the parsed |
||
|
|
||
| for node in doc.descendants() { | ||
| if matches!(node.tag_name().name(), "linearGradient" | "radialGradient") | ||
| && let Some(gradient_id) = node.attribute("id") | ||
| { | ||
| gradient_nodes.insert(gradient_id, node); | ||
| } | ||
| } | ||
|
|
||
| fn resolve_units<'a>(node: usvg::roxmltree::Node<'a, 'a>, gradient_nodes: &HashMap<&'a str, usvg::roxmltree::Node<'a, 'a>>, seen: &mut Vec<&'a str>) -> Option<GradientUnits> { | ||
| match node.attribute("gradientUnits") { | ||
| Some("userSpaceOnUse") => return Some(GradientUnits::UserSpaceOnUse), | ||
| Some("objectBoundingBox") => return Some(GradientUnits::ObjectBoundingBox), | ||
| Some(_) => return None, | ||
| None => {} | ||
| } | ||
|
|
||
| let href = node.attribute("href").or_else(|| node.attribute(("http://www.w3.org/1999/xlink", "href")))?; | ||
| let referenced_id = href.strip_prefix('#')?; | ||
| if seen.contains(&referenced_id) { | ||
| return None; | ||
| } | ||
| let referenced_node = *gradient_nodes.get(referenced_id)?; | ||
| seen.push(referenced_id); | ||
| resolve_units(referenced_node, gradient_nodes, seen) | ||
| } | ||
|
|
||
| for (&gradient_id, &node) in &gradient_nodes { | ||
| // Per SVG spec, the default gradientUnits is objectBoundingBox. | ||
| let units = resolve_units(node, &gradient_nodes, &mut vec![gradient_id]).unwrap_or(GradientUnits::ObjectBoundingBox); | ||
| result.insert(gradient_id.to_string(), units); | ||
| } | ||
|
|
||
| result | ||
| } | ||
|
|
||
| fn parse_hex_stop_color(hex: &str, opacity: f32) -> Option<Color> { | ||
| let hex = hex.strip_prefix('#')?; | ||
| if hex.len() != 6 { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,10 +3,10 @@ use crate::{Render, RenderSvgSegmentList, SvgRender}; | |
| use core_types::color::SRGBA8; | ||
| use core_types::list::List; | ||
| use core_types::uuid::generate_uuid; | ||
| use core_types::{ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD, ATTR_TRANSFORM, Color}; | ||
| use core_types::{ATTR_GRADIENT_TYPE, ATTR_GRADIENT_UNITS, ATTR_SPREAD_METHOD, ATTR_TRANSFORM, Color}; | ||
| use glam::{DAffine2, DVec2}; | ||
| use graphic_types::Graphic; | ||
| use graphic_types::vector_types::gradient::GradientType; | ||
| use graphic_types::vector_types::gradient::{GradientType, GradientUnits}; | ||
| use graphic_types::vector_types::vector::style::{PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin}; | ||
| use std::fmt::Write; | ||
| use vector_types::GradientStops; | ||
|
|
@@ -18,6 +18,16 @@ pub enum PaintTarget { | |
| Stroke, | ||
| } | ||
|
|
||
| fn svg_gradient_transform(transform: DAffine2, bounds: DAffine2, units: GradientUnits) -> (GradientUnits, String) { | ||
| let (units, transform) = match units { | ||
| GradientUnits::UserSpaceOnUse => (GradientUnits::UserSpaceOnUse, transform), | ||
| GradientUnits::ObjectBoundingBox if transform_is_invertible(bounds) => (GradientUnits::ObjectBoundingBox, bounds.inverse() * transform), | ||
| GradientUnits::ObjectBoundingBox => (GradientUnits::UserSpaceOnUse, transform), | ||
| }; | ||
|
|
||
| (units, format_transform_matrix(transform)) | ||
| } | ||
|
|
||
| impl PaintTarget { | ||
| fn paint_attr(self) -> &'static str { | ||
| match self { | ||
|
|
@@ -94,6 +104,7 @@ impl RenderExt for List<GradientStops> { | |
|
|
||
| let Some(stops) = self.element(0) else { return 0 }; | ||
| let gradient_type: GradientType = self.attribute_cloned_or_default(ATTR_GRADIENT_TYPE, 0); | ||
| let gradient_units: GradientUnits = self.attribute_cloned_or_default(ATTR_GRADIENT_UNITS, 0); | ||
| let local_gradient_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0); | ||
| let spread_method: GradientSpreadMethod = self.attribute_cloned_or_default(ATTR_SPREAD_METHOD, 0); | ||
|
|
||
|
|
@@ -122,7 +133,7 @@ impl RenderExt for List<GradientStops> { | |
| let document_transform = item_transform * local_gradient_transform; | ||
|
|
||
| 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Used Variable with Underscore PrefixThe
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: The Prompt for AI agents |
||
| let gradient_transform = if gradient_transform.is_empty() { | ||
| String::new() | ||
| } else { | ||
|
|
@@ -141,15 +152,19 @@ impl RenderExt for List<GradientStops> { | |
| GradientType::Linear => { | ||
| let _ = write!( | ||
| svg_defs, | ||
| r#"<linearGradient id="{}" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="1" y2="0"{spread_method}{gradient_transform}>{}</linearGradient>"#, | ||
| gradient_id, stop | ||
| r#"<linearGradient id="{}" gradientUnits="{}" x1="0" y1="0" x2="1" y2="0"{spread_method}{gradient_transform}>{}</linearGradient>"#, | ||
| gradient_id, | ||
| gradient_units.svg_name(), | ||
| stop | ||
| ); | ||
| } | ||
| GradientType::Radial => { | ||
| let _ = write!( | ||
| svg_defs, | ||
| r#"<radialGradient id="{}" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1"{spread_method}{gradient_transform}>{}</radialGradient>"#, | ||
| gradient_id, stop | ||
| r#"<radialGradient id="{}" gradientUnits="{}" cx="0" cy="0" r="1"{spread_method}{gradient_transform}>{}</radialGradient>"#, | ||
| gradient_id, | ||
| gradient_units.svg_name(), | ||
| stop | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,24 @@ pub enum GradientType { | |
| Radial, | ||
| } | ||
|
|
||
| #[cfg_attr(feature = "wasm", derive(tsify::Tsify))] | ||
| #[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, graphene_hash::CacheHash, DynAny)] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Prompt for AI agents |
||
| #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] | ||
| pub enum GradientUnits { | ||
| #[default] | ||
| UserSpaceOnUse, | ||
| ObjectBoundingBox, | ||
| } | ||
|
|
||
| impl GradientUnits { | ||
| pub fn svg_name(&self) -> &'static str { | ||
| match self { | ||
| GradientUnits::UserSpaceOnUse => "userSpaceOnUse", | ||
| GradientUnits::ObjectBoundingBox => "objectBoundingBox", | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // TODO: Someday we could switch this to a Box[T] to avoid over-allocation | ||
| /// A list of colors (linear, unassociated alpha) associated with positions (in the range 0 to 1) along a gradient. | ||
| /// | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Imported SVG
gradientUnitsstill have no effect because the new extractor is never called or wired intoapply_usvg_fill;#[allow(dead_code)]masks that the feature path is currently inactive. Consider either integrating this map into gradient fill creation or leaving the helper out until the refactor can consume it.Prompt for AI agents