use super::layer_info::LayerData; use super::style::{RenderData, ViewMode}; use crate::intersection::{intersect_quad_bez_path, intersect_quad_subpath, Quad}; use crate::LayerId; use glam::{DAffine2, DMat2, DVec2}; use graphene_core::vector::VectorData; use kurbo::{Affine, BezPath, Shape as KurboShape}; use serde::{Deserialize, Serialize}; use std::fmt::Write; #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub enum CachedOutputData { #[default] None, BlobURL(String), VectorPath(Box), } #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct NodeGraphFrameLayer { /// The document node network that this layer contains pub network: graph_craft::document::NodeNetwork, #[serde(skip)] pub cached_output_data: CachedOutputData, } impl LayerData for NodeGraphFrameLayer { fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec, render_data: &RenderData) -> bool { let transform = self.transform(transforms, render_data.view_mode); let inverse = transform.inverse(); let (width, height) = (transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length()); if !inverse.is_finite() { let _ = write!(svg, ""); return false; } let _ = writeln!(svg, r#""#); let matrix = (transform * DAffine2::from_scale((width, height).into()).inverse()) .to_cols_array() .iter() .enumerate() .fold(String::new(), |val, (i, entry)| val + &(entry.to_string() + if i == 5 { "" } else { "," })); // Render any paths if they exist match &self.cached_output_data { CachedOutputData::VectorPath(vector_data) => { let layer_bounds = vector_data.bounding_box().unwrap_or_default(); let transfomed_bounds = vector_data.bounding_box_with_transform(transform).unwrap_or_default(); let _ = write!(svg, ""); } CachedOutputData::BlobURL(blob_url) => { // Render the image if it exists let _ = write!( svg, r#""#, width.abs(), height.abs(), blob_url, matrix ); } _ => { // Render a dotted blue outline if there is no image or vector data let _ = write!( svg, r#""#, width.abs(), height.abs(), matrix, ); } } let _ = svg.write_str(r#""#); false } fn bounding_box(&self, transform: glam::DAffine2, _render_data: &RenderData) -> Option<[DVec2; 2]> { if let CachedOutputData::VectorPath(vector_data) = &self.cached_output_data { return vector_data.bounding_box_with_transform(transform); } let mut path = self.bounds(); if transform.matrix2 == DMat2::ZERO { return None; } path.apply_affine(glam_to_kurbo(transform)); let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box(); Some([(x0, y0).into(), (x1, y1).into()]) } fn intersects_quad(&self, quad: Quad, path: &mut Vec, intersections: &mut Vec>, _render_data: &RenderData) { if let CachedOutputData::VectorPath(vector_data) = &self.cached_output_data { let filled_style = vector_data.style.fill().is_some(); if vector_data.subpaths.iter().any(|subpath| intersect_quad_subpath(quad, subpath, filled_style || subpath.closed())) { intersections.push(path.clone()); } } else if intersect_quad_bez_path(quad, &self.bounds(), true) { intersections.push(path.clone()); } } } impl NodeGraphFrameLayer { pub fn transform(&self, transforms: &[DAffine2], mode: ViewMode) -> DAffine2 { let start = match mode { ViewMode::Outline => 0, _ => (transforms.len() as i32 - 1).max(0) as usize, }; transforms.iter().skip(start).cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY) } fn bounds(&self) -> BezPath { kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.)).to_path(0.) } pub fn as_vector_data(&self) -> Option<&VectorData> { if let CachedOutputData::VectorPath(vector_data) = &self.cached_output_data { Some(vector_data) } else { None } } pub fn as_blob_url(&self) -> Option<&String> { if let CachedOutputData::BlobURL(blob_url) = &self.cached_output_data { Some(blob_url) } else { None } } } fn glam_to_kurbo(transform: DAffine2) -> Affine { Affine::new(transform.to_cols_array()) }