use super::base64_serde; use super::layer_info::LayerData; use super::style::{RenderData, ViewMode}; use crate::intersection::{intersect_quad_bez_path, Quad}; use crate::layers::text_layer::FontCache; use crate::LayerId; use glam::{DAffine2, DMat2, DVec2}; use graph_craft::proto::Type; use kurbo::{Affine, BezPath, Shape as KurboShape}; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::fmt::Write; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct NodeGraphFrameLayer { // Image stored in layer after generation completes pub mime: String, /// The document node network that this layer contains pub network: graph_craft::document::NodeNetwork, // TODO: Have the browser dispose of this blob URL when this is dropped (like when the layer is deleted) #[serde(skip)] pub blob_url: Option, #[serde(skip)] pub dimensions: DVec2, pub image_data: Option, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct ImageData { #[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")] pub image_data: std::rc::Rc>, } impl LayerData for NodeGraphFrameLayer { fn render(&mut self, svg: &mut String, _svg_defs: &mut String, transforms: &mut Vec, render_data: RenderData) { 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; } 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 { "," })); if let Some(blob_url) = &self.blob_url { let _ = write!( svg, r#""#, width.abs(), height.abs(), blob_url, matrix ); } let _ = write!( svg, r#""#, width.abs(), height.abs(), matrix, ); let _ = svg.write_str(r#""#); } fn bounding_box(&self, transform: glam::DAffine2, _font_cache: &FontCache) -> Option<[DVec2; 2]> { 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>, _font_cache: &FontCache) { 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.) } } fn glam_to_kurbo(transform: DAffine2) -> Affine { Affine::new(transform.to_cols_array()) } impl Default for NodeGraphFrameLayer { fn default() -> Self { use graph_craft::document::*; use graph_craft::proto::NodeIdentifier; Self { mime: String::new(), network: NodeNetwork { inputs: vec![0], output: 1, nodes: [ ( 0, DocumentNode { name: "Input".into(), inputs: vec![NodeInput::Network], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic(Cow::Borrowed("T"))])), metadata: DocumentNodeMetadata { position: (8, 4) }, }, ), ( 1, DocumentNode { name: "Output".into(), inputs: vec![NodeInput::Node(0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic(Cow::Borrowed("T"))])), metadata: DocumentNodeMetadata { position: (20, 4) }, }, ), ] .into_iter() .collect(), }, blob_url: None, dimensions: DVec2::ZERO, image_data: None, } } }