Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use graphene_std::Color;
use graphene_std::raster::BlendMode;
use graphene_std::raster_types::Image;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::style::{GradientForm, GradientHueDirection, GradientInterpolation, GradientSettings, GradientSpace, GradientSpread, PaintOrder, Stroke};
use graphene_std::vector::style::{GradientForm, GradientHueDirection, GradientInterpolation, GradientSettings, GradientSpace, GradientSpread, GradientUnits, PaintOrder, Stroke};
use graphene_std::vector::{Gradient, VectorModificationType};

#[impl_message(Message, DocumentMessage, GraphOperation)]
Expand All @@ -28,6 +28,7 @@ pub enum GraphOperationMessage {
gradient: Gradient,
gradient_form: GradientForm,
gradient_settings: GradientSettings,
gradient_units: GradientUnits,
transform: DAffine2,
},
BlendingFillSet {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use graph_craft::document::{NodeId, NodeInput};
use graph_craft::list;
use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::style::{Gradient, GradientForm, GradientSettings, GradientSpace, GradientSpread, GradientStop, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
use graphene_std::vector::style::{Gradient, GradientForm, GradientSettings, GradientSpace, GradientSpread, GradientStop, GradientUnits, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
use graphene_std::{Artboard, Color};

#[derive(ExtractField)]
Expand Down Expand Up @@ -533,6 +533,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
let gradient_info = SvgGradientInfo {
graphite_stops: extract_graphite_gradient_stops(&svg),
spaces: extract_gradient_spaces(&svg),
units: extract_gradient_units(&svg),
};

// Pass identity so each leaf layer receives only its SVG-native transform from `abs_transform`.
Expand Down Expand Up @@ -575,6 +576,8 @@ struct SvgGradientInfo {
graphite_stops: HashMap<String, Gradient>,
/// Gradient spaces, keyed by gradient element `id`, resolved from the `color-interpolation` property.
spaces: HashMap<String, GradientSpace>,
/// Gradient units, keyed by gradient element `id`, resolved from the `gradientUnits` attribute.
units: HashMap<String, GradientUnits>,
}

/// Pre-parses the raw SVG XML to resolve each gradient's inherited `color-interpolation` property, which usvg's
Expand Down Expand Up @@ -620,6 +623,59 @@ fn extract_gradient_spaces(svg: &str) -> HashMap<String, GradientSpace> {
result
}

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,
};

// Collect gradient nodes by id for href resolution
let mut gradient_nodes: HashMap<&str, usvg::roxmltree::Node> = HashMap::new();
for node in doc.descendants() {
if matches!(node.tag_name().name(), "linearGradient" | "radialGradient") {
if let Some(id) = node.attribute("id") {
gradient_nodes.insert(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> {
if let Some(units) = node.attribute("gradientUnits") {
match units {
"userSpaceOnUse" => return Some(GradientUnits::UserSpaceOnUse),
"objectBoundingBox" => return Some(GradientUnits::ObjectBoundingBox),
_ => {}
}
}
if let Some(href) = node.attribute("href").or_else(|| node.attribute("xlink:href")) {
let id = href.trim_start_matches('#');
if seen.contains(&id) {
return None;
}
seen.push(id);
if let Some(referenced) = gradient_nodes.get(id) {
if let Some(units) = resolve_units(*referenced, gradient_nodes, seen) {
return Some(units);
}
}
}
None
}

for node in doc.descendants() {
if !matches!(node.tag_name().name(), "linearGradient" | "radialGradient") {
continue;
}
let Some(gradient_id) = node.attribute("id") else { continue };
let units = resolve_units(node, &gradient_nodes, &mut Vec::new()).unwrap_or(GradientUnits::ObjectBoundingBox);
result.insert(gradient_id.to_string(), units);
}

result
}

/// The `color-interpolation` in effect for an element: the nearest self-or-ancestor declaration, taking each
/// element's own winning declaration per [`declared_color_interpolation`]'s cascade order.
fn resolve_color_interpolation(element: usvg::roxmltree::Node, stylesheet: &simplecss::StyleSheet) -> Option<GradientSpace> {
Expand Down Expand Up @@ -1033,7 +1089,8 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, g
space: gradient_info.spaces.get(linear.id()).copied().unwrap_or(GradientSpace::RgbGamma),
..Default::default()
};
modify_inputs.fill_gradient_set(gradient, gradient_form, settings, transform);
let gradient_units = gradient_info.units.get(linear.id()).copied().unwrap_or(GradientUnits::ObjectBoundingBox);
modify_inputs.fill_gradient_set(gradient, gradient_form, settings, gradient_units, transform);
}
usvg::Paint::RadialGradient(radial) => {
let gradient_transform = usvg_transform(radial.transform());
Expand Down Expand Up @@ -1061,7 +1118,8 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, g
space: gradient_info.spaces.get(radial.id()).copied().unwrap_or(GradientSpace::RgbGamma),
..Default::default()
};
modify_inputs.fill_gradient_set(gradient, gradient_form, settings, transform);
let gradient_units = gradient_info.units.get(radial.id()).copied().unwrap_or(GradientUnits::ObjectBoundingBox);
modify_inputs.fill_gradient_set(gradient, gradient_form, settings, gradient_units, transform);
}
usvg::Paint::Pattern(_) => warn!("SVG patterns are not currently supported"),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use graphene_std::raster::BlendMode;
use graphene_std::raster_types::Image;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::style::{GradientForm, GradientHueDirection, GradientInterpolation, GradientSettings, GradientSpace, GradientSpread, PaintOrder, Stroke};
use graphene_std::vector::style::{GradientForm, GradientHueDirection, GradientInterpolation, GradientSettings, GradientSpace, GradientSpread, GradientUnits, PaintOrder, Stroke};
use graphene_std::vector::{Gradient, GradientRamp, Vector, VectorModification, VectorModificationType};
use graphene_std::{Artboard, Color, Graphic};
use kurbo::BezPath;
Expand Down Expand Up @@ -449,7 +449,7 @@
}
}

pub fn fill_gradient_set(&mut self, gradient: Gradient, gradient_form: GradientForm, settings: GradientSettings, transform: DAffine2) {
pub fn fill_gradient_set(&mut self, gradient: Gradient, gradient_form: GradientForm, settings: GradientSettings, gradient_units: GradientUnits, transform: DAffine2) {
let existing_fill_node_id = self.existing_chain_hosted_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, false);
let Some(fill_node_id) = existing_fill_node_id.or_else(|| self.existing_chain_hosted_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true)) else {
return;
Expand Down Expand Up @@ -490,6 +490,12 @@
self.set_input_with_refresh(
InputConnector::node(fill_node_id, graphene_std::vector::fill::GradientFormInput),
NodeInput::value(TaggedValue::GradientForm(gradient_form), false),
true,
);

self.set_input_with_refresh(
InputConnector::node(fill_node_id, graphene_std::vector::fill::GradientUnitsInput),

Check failure on line 497 in editor/src/messages/portfolio/document/graph_operation/utility_types.rs

View workflow job for this annotation

GitHub Actions / build / web

cannot find value `GradientUnitsInput` in module `graphene_std::vector::fill`
NodeInput::value(TaggedValue::GradientUnits(gradient_units), false),
false,
);

Expand Down
1 change: 1 addition & 0 deletions node-graph/graph-craft/src/document/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@ tagged_value! {
GradientSpace(vector::style::GradientSpace),
GradientHueDirection(vector::style::GradientHueDirection),
GradientInterpolation(vector::style::GradientInterpolation),
GradientUnits(vector::style::GradientUnits),
ReferencePoint(vector::ReferencePoint),
CentroidType(vector::misc::CentroidType),
BooleanOperation(vector::misc::BooleanOperation),
Expand Down
5 changes: 3 additions & 2 deletions node-graph/libraries/core-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ pub use graphene_hash;
pub use graphene_hash::CacheHash;
pub use list::{
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_EDITOR_TEXT_FRAME, ATTR_END,
ATTR_FONT, ATTR_FONT_SIZE, ATTR_GRADIENT_CYCLIC, ATTR_GRADIENT_FORM, ATTR_GRADIENT_HUE_DIRECTION, ATTR_GRADIENT_INTERPOLATION, ATTR_GRADIENT_SPACE, ATTR_GRADIENT_SPREAD, ATTR_LETTER_SPACING,
ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_LOCATION, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_NAME, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_START, ATTR_TEXT_ALIGN, ATTR_TRANSFORM, ATTR_TYPE,
ATTR_FONT, ATTR_FONT_SIZE, ATTR_GRADIENT_CYCLIC, ATTR_GRADIENT_FORM, ATTR_GRADIENT_HUE_DIRECTION, ATTR_GRADIENT_INTERPOLATION, ATTR_GRADIENT_SPACE, ATTR_GRADIENT_SPREAD, ATTR_GRADIENT_UNITS,
ATTR_LETTER_SPACING, ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_LOCATION, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_NAME, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_START, ATTR_TEXT_ALIGN, ATTR_TRANSFORM,
ATTR_TYPE,
};
pub use memo::MemoHash;
pub use no_std_types::AsU32;
Expand Down
2 changes: 2 additions & 0 deletions node-graph/libraries/core-types/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ pub const ATTR_GRADIENT_INTERPOLATION: &str = "gradient_interpolation";
/// Gradient's `bool` (implicit default `false`) for treating the stop list as a cycle, where a wrapped interval
/// interpolates from the last stop through the 1|0 boundary back to the first.
pub const ATTR_GRADIENT_CYCLIC: &str = "gradient_cyclic";
/// Gradient's SVG coordinate system (`userSpaceOnUse` or `objectBoundingBox`).
pub const ATTR_GRADIENT_UNITS: &str = "gradient_units";
/// Gradient stop's `f64` position from 0 to 1 along the gradient, on the `List<Color>` inside a `Gradient`.
/// When the attribute is absent, stops distribute evenly across the 0 to 1 range.
pub const ATTR_POSITION: &str = "position";
Expand Down
26 changes: 20 additions & 6 deletions node-graph/libraries/rendering/src/render_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,24 @@
use core_types::color::SRGBA8;
use core_types::list::List;
use core_types::uuid::generate_uuid;
use core_types::{ATTR_GRADIENT_FORM, ATTR_TRANSFORM, Color};
use core_types::{ATTR_GRADIENT_FORM, ATTR_GRADIENT_UNITS, ATTR_TRANSFORM, Color};
use glam::{DAffine2, DVec2};
use graphic_types::Graphic;
use graphic_types::vector_types::gradient::GradientForm;
use graphic_types::vector_types::gradient::{GradientForm, GradientUnits};
use graphic_types::vector_types::vector::style::{Stroke, StrokeAlign, StrokeCap, StrokeJoin};
use std::fmt::Write;
use vector_types::Gradient;
use vector_types::gradient::GradientSpread;

fn svg_gradient_transform(transform: DAffine2, bounds: DAffine2, units: GradientUnits) -> (GradientUnits, String) {

Check failure on line 18 in node-graph/libraries/rendering/src/render_ext.rs

View workflow job for this annotation

GitHub Actions / test

function `svg_gradient_transform` is never used

Check warning on line 18 in node-graph/libraries/rendering/src/render_ext.rs

View workflow job for this annotation

GitHub Actions / build / web

function `svg_gradient_transform` is never used
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))
}

#[derive(Copy, Clone, PartialEq)]
pub enum PaintTarget {
Fill,
Expand Down Expand Up @@ -90,6 +99,7 @@
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);

Expand Down Expand Up @@ -155,15 +165,19 @@
GradientForm::Linear => {
let _ = write!(
svg_defs,
r#"<linearGradient id="{}" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="1" y2="0"{gradient_spread}{gradient_transform}>{}</linearGradient>"#,
gradient_id, stop
r#"<linearGradient id="{}" gradientUnits="{}" x1="0" y1="0" x2="1" y2="0"{gradient_spread}{gradient_transform}>{}</linearGradient>"#,
gradient_id,
gradient_units.svg_name(),
stop
);
}
GradientForm::Radial => {
let _ = write!(
svg_defs,
r#"<radialGradient id="{}" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1"{gradient_spread}{gradient_transform}>{}</radialGradient>"#,
gradient_id, stop
r#"<radialGradient id="{}" gradientUnits="{}" cx="0" cy="0" r="1"{gradient_spread}{gradient_transform}>{}</radialGradient>"#,
gradient_id,
gradient_units.svg_name(),
stop
);
}
}
Expand Down
19 changes: 19 additions & 0 deletions node-graph/libraries/vector-types/src/gradient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,25 @@ pub enum GradientForm {
Radial,
}

#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[widget(Radio)]
pub enum GradientUnits {
#[default]
UserSpaceOnUse,
ObjectBoundingBox,
}

impl GradientUnits {
pub fn svg_name(self) -> &'static str {
match self {
GradientUnits::UserSpaceOnUse => "userSpaceOnUse",
GradientUnits::ObjectBoundingBox => "objectBoundingBox",
}
}
}

/// A gradient's stops: a list of colors (linear, unassociated alpha) whose optional `position` and `midpoint`
/// attributes place each stop along the 0 to 1 range. Stops lacking the `position` attribute distribute evenly,
/// and stops lacking the `midpoint` attribute interpolate linearly (`0.5`).
Expand Down
2 changes: 1 addition & 1 deletion node-graph/libraries/vector-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub mod vector;

// Re-export commonly used types at the crate root
pub use core_types as gcore;
pub use gradient::{Gradient, GradientForm, GradientHueDirection, GradientInterpolation, GradientRamp, GradientSettings, GradientSpace, GradientSpread, GradientStop};
pub use gradient::{Gradient, GradientForm, GradientHueDirection, GradientInterpolation, GradientRamp, GradientSettings, GradientSpace, GradientSpread, GradientStop, GradientUnits};
pub use math::QuadExt;
pub use vector::Vector;
pub use vector::reference_point::ReferencePoint;
Expand Down
Loading