Skip to content

Commit 2ba7f6d

Browse files
committed
fix: serde migration for old documents
1 parent 521ad51 commit 2ba7f6d

File tree

5 files changed

+140
-6
lines changed

5 files changed

+140
-6
lines changed

editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,10 +515,12 @@ fn apply_usvg_stroke(stroke: &usvg::Stroke, modify_inputs: &mut ModifyInputsCont
515515
align: StrokeAlign::Center,
516516
paint_order: PaintOrder::StrokeAbove,
517517
transform,
518+
non_scaling: false,
518519
})
519520
}
520521
}
521522

523+
522524
fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, bounds_transform: DAffine2, graphite_gradient_stops: &HashMap<String, GradientStops>) {
523525
modify_inputs.fill_set(match &fill.paint() {
524526
usvg::Paint::Color(color) => Fill::solid(usvg_color(*color, fill.opacity().get())),

editor/src/messages/portfolio/document_migration.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,26 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
10931093
document.network_interface.set_input(&InputConnector::node(*node_id, 9), old_inputs[4].clone(), network_path);
10941094
}
10951095

1096+
// Upgrade Stroke node to add "_backup_color" and "_backup_gradient" (10 -> 12)
1097+
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER) && inputs_count == 10 {
1098+
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
1099+
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
1100+
for i in 0..10 {
1101+
document.network_interface.set_input(&InputConnector::node(*node_id, i), old_inputs[i].clone(), network_path);
1102+
}
1103+
}
1104+
1105+
// Upgrade Fill node to add "_backup_color" and "_backup_gradient" (2 -> 4)
1106+
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::fill::IDENTIFIER) && inputs_count == 2 {
1107+
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
1108+
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
1109+
for i in 0..2 {
1110+
document.network_interface.set_input(&InputConnector::node(*node_id, i), old_inputs[i].clone(), network_path);
1111+
}
1112+
}
1113+
1114+
1115+
10961116
// Upgrade the old "Spline" node to the new "Spline" node
10971117
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::spline::IDENTIFIER)
10981118
|| reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::generator_nodes::SplineNode"))

node-graph/graph-craft/src/document/value.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,9 +403,34 @@ impl TaggedValue {
403403
() if ty == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?,
404404
// `Color` (not in a table) is still currently needed by `BlackAndWhiteNode` and `ColorOverlayNode` GPU `shader_node(PerPixelAdjust)` variants
405405
() if ty == TypeId::of::<Color>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,
406-
() if ty == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,
407-
() if ty == TypeId::of::<Table<GradientStops>>() => to_gradient(string).map(|color| TaggedValue::GradientTable(Table::new_from_element(color)))?,
408-
() if ty == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?,
406+
() if ty == TypeId::of::<Table<Color>>() => {
407+
if string.trim().replace(' ', "") == "Table::new()" {
408+
Some(TaggedValue::Color(Table::new()))
409+
} else {
410+
to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))
411+
}
412+
}?,
413+
() if ty == TypeId::of::<Table<GradientStops>>() => {
414+
if string.trim().replace(' ', "") == "Table::new()" {
415+
Some(TaggedValue::GradientTable(Table::new()))
416+
} else {
417+
to_gradient(string).map(|color| TaggedValue::GradientTable(Table::new_from_element(color)))
418+
}
419+
}?,
420+
() if ty == TypeId::of::<Fill>() => {
421+
if string.trim().replace(' ', "") == "Fill::default()" {
422+
Some(TaggedValue::Fill(Fill::default()))
423+
} else {
424+
to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))
425+
}
426+
}?,
427+
() if ty == TypeId::of::<graphic_types::vector_types::vector::style::Gradient>() => {
428+
if string.trim().replace(' ', "") == "Gradient::default()" {
429+
Some(TaggedValue::Gradient(graphic_types::vector_types::vector::style::Gradient::default()))
430+
} else {
431+
None
432+
}
433+
}?,
409434
() if ty == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
410435
_ => return None,
411436
};

node-graph/libraries/vector-types/src/vector/style.rs

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,10 +316,80 @@ fn daffine2_identity() -> DAffine2 {
316316
DAffine2::IDENTITY
317317
}
318318

319+
/// Helper module for deserializing the `Stroke::paint` field, supporting both the
320+
/// new `"paint": Fill` format and the legacy `"color": Color` format from old `.graphite` files.
321+
mod stroke_paint_migration {
322+
use super::*;
323+
use serde::Deserialize;
324+
325+
/// Legacy representation: old `.graphite` files stored a bare `Color` in a field called `"color"`.
326+
#[derive(Deserialize)]
327+
struct OldStroke {
328+
#[serde(default)]
329+
color: Option<Color>,
330+
#[serde(default)]
331+
paint: Option<Fill>,
332+
#[serde(default)]
333+
weight: f64,
334+
#[serde(default)]
335+
dash_lengths: Vec<f64>,
336+
#[serde(default)]
337+
dash_offset: f64,
338+
#[serde(default, alias = "line_cap")]
339+
cap: StrokeCap,
340+
#[serde(default, alias = "line_join")]
341+
join: StrokeJoin,
342+
#[serde(default = "default_miter_limit", alias = "line_join_miter_limit")]
343+
join_miter_limit: f64,
344+
#[serde(default)]
345+
align: StrokeAlign,
346+
#[serde(default = "super::daffine2_identity")]
347+
transform: DAffine2,
348+
#[serde(default)]
349+
non_scaling: bool,
350+
#[serde(default)]
351+
paint_order: PaintOrder,
352+
}
353+
354+
355+
fn default_miter_limit() -> f64 {
356+
4.
357+
}
358+
359+
/// Attempt to deserialize a `Stroke` from either the new or old format.
360+
pub fn deserialize_stroke<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Stroke, D::Error> {
361+
let old = OldStroke::deserialize(deserializer)?;
362+
363+
// If the new `paint` field is present, use it directly.
364+
// Otherwise, fall back to converting the legacy `color` field into `Fill::Solid`.
365+
let paint = match old.paint {
366+
Some(paint) => paint,
367+
None => match old.color {
368+
Some(color) => Fill::Solid(color),
369+
None => Fill::Solid(Color::from_rgba8_srgb(0, 0, 0, 255)),
370+
},
371+
};
372+
373+
Ok(Stroke {
374+
paint,
375+
weight: old.weight,
376+
dash_lengths: old.dash_lengths,
377+
dash_offset: old.dash_offset,
378+
cap: old.cap,
379+
join: old.join,
380+
join_miter_limit: old.join_miter_limit,
381+
align: old.align,
382+
transform: old.transform,
383+
non_scaling: old.non_scaling,
384+
paint_order: old.paint_order,
385+
})
386+
}
387+
388+
}
389+
319390
#[repr(C)]
320391
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
321-
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny)]
322-
#[serde(default)]
392+
#[derive(Debug, Clone, PartialEq, serde::Serialize, DynAny)]
323393
pub struct Stroke {
324394
/// Stroke paint (solid color or gradient)
325395
pub paint: Fill,
@@ -337,10 +407,17 @@ pub struct Stroke {
337407
pub align: StrokeAlign,
338408
#[serde(default = "daffine2_identity")]
339409
pub transform: DAffine2,
340-
#[serde(default)]
410+
pub non_scaling: bool,
341411
pub paint_order: PaintOrder,
342412
}
343413

414+
415+
impl<'de> serde::Deserialize<'de> for Stroke {
416+
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
417+
stroke_paint_migration::deserialize_stroke(deserializer)
418+
}
419+
}
420+
344421
impl std::hash::Hash for Stroke {
345422
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
346423
self.paint.hash(state);
@@ -355,8 +432,10 @@ impl std::hash::Hash for Stroke {
355432
self.join_miter_limit.to_bits().hash(state);
356433
self.align.hash(state);
357434
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
435+
self.non_scaling.hash(state);
358436
self.paint_order.hash(state);
359437
}
438+
360439
}
361440

362441
impl Stroke {
@@ -371,10 +450,12 @@ impl Stroke {
371450
join_miter_limit: 4.,
372451
align: StrokeAlign::Center,
373452
transform: DAffine2::IDENTITY,
453+
non_scaling: false,
374454
paint_order: PaintOrder::StrokeAbove,
375455
}
376456
}
377457

458+
378459
pub fn lerp(&self, other: &Self, time: f64) -> Self {
379460
Self {
380461
paint: self.paint.lerp(&other.paint, time),
@@ -389,10 +470,12 @@ impl Stroke {
389470
time * self.transform.matrix2 + (1. - time) * other.transform.matrix2,
390471
self.transform.translation * time + other.transform.translation * (1. - time),
391472
),
473+
non_scaling: if time < 0.5 { self.non_scaling } else { other.non_scaling },
392474
paint_order: if time < 0.5 { self.paint_order } else { other.paint_order },
393475
}
394476
}
395477

478+
396479
/// Get the current stroke color.
397480
pub fn color(&self) -> Option<Color> {
398481
match &self.paint {
@@ -515,6 +598,7 @@ impl Default for Stroke {
515598
join_miter_limit: 4.,
516599
align: StrokeAlign::Center,
517600
transform: DAffine2::IDENTITY,
601+
non_scaling: false,
518602
paint_order: PaintOrder::default(),
519603
}
520604
}

node-graph/nodes/vector/src/vector_nodes.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ async fn stroke<V, L: IntoF64Vec, F: Into<Fill> + 'n + Send>(
241241
miter_limit: f64,
242242
// <https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>
243243
/// The order to paint the stroke on top of the fill, or the fill on top of the stroke.
244+
#[default(PaintOrder::StrokeAbove)]
244245
paint_order: PaintOrder,
245246
/// The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.
246247
#[implementations(
@@ -289,9 +290,11 @@ where
289290
join_miter_limit: miter_limit,
290291
align,
291292
transform: DAffine2::IDENTITY,
293+
non_scaling: false,
292294
paint_order,
293295
};
294296

297+
295298
for vector in content.vector_iter_mut() {
296299
let mut stroke = stroke.clone();
297300
stroke.transform *= *vector.transform;

0 commit comments

Comments
 (0)