use super::DocumentNode; use crate::application_io::PlatformEditorApi; use crate::proto::{Any as DAny, FutureAny}; use brush_nodes::brush_cache::BrushCache; use brush_nodes::brush_stroke::BrushStroke; use core_types::table::Table; use core_types::transform::Footprint; use core_types::uuid::NodeId; use core_types::{CacheHash, Color, ContextFeatures, MemoHash, Node, Type}; use dyn_any::DynAny; pub use dyn_any::StaticType; use glam::{Affine2, Vec2}; pub use glam::{DAffine2, DVec2, IVec2, UVec2}; use graphic_types::raster_types::{CPU, Image, Raster}; use graphic_types::vector_types::vector::style::{Fill, Gradient, GradientStops, Stroke}; use graphic_types::vector_types::vector::{self, ReferencePoint}; use graphic_types::{Artboard, Graphic, Vector}; use raster_nodes::curve::Curve; use rendering::RenderMetadata; use std::fmt::Display; use std::hash::Hash; use std::marker::PhantomData; use std::str::FromStr; pub use std::sync::Arc; use text_nodes::Font; use text_nodes::vector_types::GradientStop; use vector::VectorModification; pub struct TaggedValueTypeError; /// Macro to generate the tagged value enum. macro_rules! tagged_value { ($ ($( #[$meta:meta] )* $identifier:ident ($ty:ty) ),* $(,)?) => { /// A type that is known, allowing serialization (serde::Deserialize is not object safe) #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] #[allow(clippy::large_enum_variant)] // TODO(TrueDoctor): Properly solve this disparity between the size of the largest and next largest variants pub enum TaggedValue { None, $( $(#[$meta] ) *$identifier( $ty ), )* RenderOutput(RenderOutput), #[serde(skip)] EditorApi(Arc) } impl CacheHash for TaggedValue { fn cache_hash(&self, state: &mut H) { core::mem::discriminant(self).hash(state); match self { Self::None => {} $( Self::$identifier(x) => { x.cache_hash(state) }),* Self::RenderOutput(x) => x.cache_hash(state), Self::EditorApi(x) => x.cache_hash(state), } } } impl<'a> TaggedValue { /// Converts to a Box pub fn to_dynany(self) -> DAny<'a> { match self { Self::None => Box::new(()), $( Self::$identifier(x) => Box::new(x), )* Self::RenderOutput(x) => Box::new(x), Self::EditorApi(x) => Box::new(x), } } /// Converts to a Arc pub fn to_any(self) -> Arc { match self { Self::None => Arc::new(()), $( Self::$identifier(x) => Arc::new(x), )* Self::RenderOutput(x) => Arc::new(x), Self::EditorApi(x) => Arc::new(x), } } /// Creates a core_types::Type::Concrete(TypeDescriptor { .. }) with the type of the value inside the tagged value pub fn ty(&self) -> Type { match self { Self::None => concrete!(()), $( Self::$identifier(_) => concrete!($ty), )* Self::RenderOutput(_) => concrete!(RenderOutput), Self::EditorApi(_) => concrete!(&PlatformEditorApi) } } /// Attempts to downcast the dynamic type to a tagged value pub fn try_from_any(input: Box + 'a>) -> Result { use dyn_any::downcast; use std::any::TypeId; match DynAny::type_id(input.as_ref()) { x if x == TypeId::of::<()>() => Ok(TaggedValue::None), $( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(*downcast(input).unwrap())), )* x if x == TypeId::of::() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())), _ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))), } } /// Attempts to downcast the dynamic type to a tagged value pub fn try_from_std_any_ref(input: &dyn std::any::Any) -> Result { use std::any::TypeId; match input.type_id() { x if x == TypeId::of::<()>() => Ok(TaggedValue::None), $( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(<$ty as Clone>::clone(input.downcast_ref().unwrap()))), )* x if x == TypeId::of::() => Ok(TaggedValue::RenderOutput(RenderOutput::clone(input.downcast_ref().unwrap()))), _ => Err(format!("Cannot convert {:?} to TaggedValue", std::any::type_name_of_val(input))), } } /// Returns a TaggedValue from the type, where that value is its type's `Default::default()` pub fn from_type(input: &Type) -> Option { match input { Type::Generic(_) => None, Type::Concrete(concrete_type) => { use std::any::TypeId; // TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types // Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned. Some(match concrete_type.id? { x if x == TypeId::of::<()>() => TaggedValue::None, // Table-wrapped types need a single-item default with the element's default, not an empty table x if x == TypeId::of::>() => TaggedValue::Color(Table::new_from_element(Color::default())), x if x == TypeId::of::>() => TaggedValue::GradientTable(Table::new_from_element(GradientStops::default())), $( x if x == TypeId::of::<$ty>() => TaggedValue::$identifier(Default::default()), )* _ => return None, }) } Type::Fn(_, output) => TaggedValue::from_type(output), Type::Future(output) => { TaggedValue::from_type(output) } } } pub fn from_type_or_none(input: &Type) -> Self { Self::from_type(input).unwrap_or(TaggedValue::None) } pub fn to_debug_string(&self) -> String { match self { Self::None => "()".to_string(), $( Self::$identifier(x) => format!("{:?}", x), )* Self::RenderOutput(_) => "RenderOutput".to_string(), Self::EditorApi(_) => "PlatformEditorApi".to_string(), } } } $( impl From<$ty> for TaggedValue { fn from(value: $ty) -> Self { Self::$identifier(value) } } )* $( impl<'a> TryFrom<&'a TaggedValue> for &'a $ty { type Error = TaggedValueTypeError; fn try_from(value: &'a TaggedValue) -> Result { match value{ TaggedValue::$identifier(value) => Ok(value), _ => Err(TaggedValueTypeError), } } } )* }; } tagged_value! { // =========== // TABLE TYPES // =========== StringTable(Table), #[serde(deserialize_with = "core_types::misc::migrate_vec_f64_to_table")] // TODO: Eventually remove this migration document upgrade code #[serde(alias = "VecF64", alias = "VecF32", alias = "F64Array4")] F64Table(Table), NodeIdTable(Table), #[serde(deserialize_with = "graphic_types::migrations::migrate_vector")] // TODO: Eventually remove this migration document upgrade code #[serde(alias = "VectorData")] Vector(Table), #[serde(deserialize_with = "graphic_types::raster_types::image::migrate_image_frame")] // TODO: Eventually remove this migration document upgrade code #[serde(alias = "ImageFrame", alias = "RasterData", alias = "Image")] Raster(Table>), #[serde(deserialize_with = "graphic_types::graphic::migrate_graphic")] // TODO: Eventually remove this migration document upgrade code #[serde(alias = "GraphicGroup", alias = "Group")] Graphic(Table), #[serde(deserialize_with = "graphic_types::artboard::migrate_artboard")] // TODO: Eventually remove this migration document upgrade code #[serde(alias = "ArtboardGroup")] Artboard(Table), #[serde(deserialize_with = "core_types::misc::migrate_color")] // TODO: Eventually remove this migration document upgrade code #[serde(alias = "ColorTable", alias = "OptionalColor", alias = "ColorNotInTable")] Color(Table), #[serde(deserialize_with = "graphic_types::vector_types::gradient::migrate_gradient_stops")] // TODO: Eventually remove this migration document upgrade code #[serde(alias = "GradientPositions", alias = "GradientStops")] GradientTable(Table), #[serde(deserialize_with = "brush_nodes::migrations::migrate_brush_strokes_to_table")] // TODO: Eventually remove this migration document upgrade code #[serde(alias = "BrushStrokes")] BrushStrokeTable(Table), // ============ // SCALAR TYPES // ============ F32(f32), F64(f64), U32(u32), U64(u64), Bool(bool), String(String), FVec2(Vec2), FAffine2(Affine2), #[serde(alias = "IVec2", alias = "UVec2")] DVec2(DVec2), DAffine2(DAffine2), Stroke(Stroke), Gradient(Gradient), Font(Font), BrushCache(BrushCache), DocumentNode(DocumentNode), ContextFeatures(ContextFeatures), Curve(Curve), Footprint(Footprint), VectorModification(Box), ImageData(Image), // ========== // ENUM TYPES // ========== Fill(vector::style::Fill), BlendMode(core_types::blending::BlendMode), LuminanceCalculation(raster_nodes::adjustments::LuminanceCalculation), QRCodeErrorCorrectionLevel(vector_nodes::generator_nodes::QRCodeErrorCorrectionLevel), XY(graphene_core::extract_xy::XY), StringCapitalization(text_nodes::StringCapitalization), RedGreenBlue(raster_nodes::adjustments::RedGreenBlue), RedGreenBlueAlpha(raster_nodes::adjustments::RedGreenBlueAlpha), RealTimeMode(graphene_core::animation::RealTimeMode), NoiseType(raster_nodes::adjustments::NoiseType), FractalType(raster_nodes::adjustments::FractalType), CellularDistanceFunction(raster_nodes::adjustments::CellularDistanceFunction), CellularReturnType(raster_nodes::adjustments::CellularReturnType), DomainWarpType(raster_nodes::adjustments::DomainWarpType), RelativeAbsolute(raster_nodes::adjustments::RelativeAbsolute), SelectiveColorChoice(raster_nodes::adjustments::SelectiveColorChoice), GridType(vector::misc::GridType), ArcType(vector::misc::ArcType), RowsOrColumns(vector::misc::RowsOrColumns), MergeByDistanceAlgorithm(vector::misc::MergeByDistanceAlgorithm), ExtrudeJoiningAlgorithm(vector::misc::ExtrudeJoiningAlgorithm), PointSpacingType(vector::misc::PointSpacingType), SpiralType(vector::misc::SpiralType), InterpolationDistribution(vector::misc::InterpolationDistribution), #[serde(alias = "LineCap")] StrokeCap(vector::style::StrokeCap), #[serde(alias = "LineJoin")] StrokeJoin(vector::style::StrokeJoin), StrokeAlign(vector::style::StrokeAlign), PaintOrder(vector::style::PaintOrder), FillType(vector::style::FillType), GradientType(vector::style::GradientType), ReferencePoint(vector::ReferencePoint), CentroidType(vector::misc::CentroidType), BooleanOperation(vector::misc::BooleanOperation), TextAlign(text_nodes::TextAlign), ScaleType(core_types::transform::ScaleType), } impl TaggedValue { pub fn to_primitive_string(&self) -> String { match self { TaggedValue::None => "()".to_string(), TaggedValue::String(x) => format!("\"{x}\""), TaggedValue::U32(x) => x.to_string() + "_u32", TaggedValue::U64(x) => x.to_string() + "_u64", TaggedValue::F32(x) => x.to_string() + "_f32", TaggedValue::F64(x) => x.to_string() + "_f64", TaggedValue::Bool(x) => x.to_string(), TaggedValue::BlendMode(x) => "BlendMode::".to_string() + &x.to_string(), _ => panic!("Cannot convert to primitive string"), } } pub fn from_primitive_string(string: &str, ty: &Type) -> Option { fn to_dvec2(input: &str) -> Option { let mut split = input.split(','); let x = split.next()?.trim().parse().ok()?; let y = split.next()?.trim().parse().ok()?; Some(DVec2::new(x, y)) } fn to_color(input: &str) -> Option { // String syntax (e.g. "000000ff") if input.starts_with('"') && input.ends_with('"') { let hex = input.trim().trim_matches('"').trim().trim_start_matches('#'); let color = Color::from_hex_str(hex); if color.is_none() { log::error!("Invalid default value color string: {input}"); } return color; } // Color constant syntax (e.g. Color::BLACK) let mut choices = input.split("::"); let (first, second) = (choices.next()?.trim(), choices.next()?.trim()); if first == "Color" { return Some(match second { "BLACK" => Color::BLACK, "WHITE" => Color::WHITE, "RED" => Color::RED, "GREEN" => Color::GREEN, "BLUE" => Color::BLUE, "YELLOW" => Color::YELLOW, "CYAN" => Color::CYAN, "MAGENTA" => Color::MAGENTA, "TRANSPARENT" => Color::TRANSPARENT, _ => { log::error!("Invalid default value color constant: {input}"); return None; } }); } log::error!("Invalid default value color: {input}"); None } fn to_gradient(input: &str) -> Option { // String syntax: (e.g. "000000ff, ff0000ff") let stops = input.split(',').filter_map(|s| to_color(s.trim())).collect::>(); if stops.len() == 1 { Some(GradientStops::new(vec![ GradientStop { position: 0., midpoint: 0.5, color: stops[0], }, GradientStop { position: 1., midpoint: 0.5, color: stops[0], }, ])) } else if stops.len() >= 2 { let step = 1. / (stops.len() - 1) as f64; Some(GradientStops::new(stops.into_iter().enumerate().map(|(i, color)| GradientStop { position: i as f64 * step, midpoint: 0.5, color, }))) } else { log::error!("Invalid default value gradient string: {input}"); None } } fn to_reference_point(input: &str) -> Option { let mut choices = input.split("::"); let (first, second) = (choices.next()?.trim(), choices.next()?.trim()); if first == "ReferencePoint" { return Some(match second { "None" => ReferencePoint::None, "TopLeft" => ReferencePoint::TopLeft, "TopCenter" => ReferencePoint::TopCenter, "TopRight" => ReferencePoint::TopRight, "CenterLeft" => ReferencePoint::CenterLeft, "Center" => ReferencePoint::Center, "CenterRight" => ReferencePoint::CenterRight, "BottomLeft" => ReferencePoint::BottomLeft, "BottomCenter" => ReferencePoint::BottomCenter, "BottomRight" => ReferencePoint::BottomRight, _ => { log::error!("Invalid ReferencePoint default type variant: {input}"); return None; } }); } log::error!("Invalid ReferencePoint default type: {input}"); None } match ty { Type::Generic(_) => None, Type::Concrete(concrete_type) => { let ty = concrete_type.id?; use std::any::TypeId; // TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types // Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned. let ty = match () { () if ty == TypeId::of::<()>() => TaggedValue::None, () if ty == TypeId::of::() => TaggedValue::String(string.into()), () if ty == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::F64).ok()?, () if ty == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::F32).ok()?, () if ty == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::U64).ok()?, () if ty == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::U32).ok()?, () if ty == TypeId::of::() => to_dvec2(string).map(TaggedValue::DVec2)?, () if ty == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?, // `Color` (not in a table) is still currently needed by `BlackAndWhiteNode` and `ColorOverlayNode` GPU `shader_node(PerPixelAdjust)` variants () if ty == TypeId::of::() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?, () if ty == TypeId::of::>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?, () if ty == TypeId::of::>() => to_gradient(string).map(|color| TaggedValue::GradientTable(Table::new_from_element(color)))?, () if ty == TypeId::of::() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?, () if ty == TypeId::of::() => to_reference_point(string).map(TaggedValue::ReferencePoint)?, _ => return None, }; Some(ty) } Type::Fn(_, output) => TaggedValue::from_primitive_string(string, output), Type::Future(fut) => TaggedValue::from_primitive_string(string, fut), } } pub fn to_u32(&self) -> u32 { match self { TaggedValue::U32(x) => *x, _ => panic!("Passed value is not of type u32"), } } } impl Display for TaggedValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { TaggedValue::String(x) => f.write_str(x), TaggedValue::U32(x) => f.write_fmt(format_args!("{x}")), TaggedValue::U64(x) => f.write_fmt(format_args!("{x}")), TaggedValue::F32(x) => f.write_fmt(format_args!("{x}")), TaggedValue::F64(x) => f.write_fmt(format_args!("{x}")), TaggedValue::Bool(x) => f.write_fmt(format_args!("{x}")), _ => panic!("Cannot convert to string"), } } } pub struct UpcastNode { value: MemoHash, } impl<'input> Node<'input, DAny<'input>> for UpcastNode { type Output = FutureAny<'input>; fn eval(&'input self, _: DAny<'input>) -> Self::Output { let memo_clone = MemoHash::clone(&self.value); Box::pin(async move { memo_clone.into_inner().as_ref().clone().to_dynany() }) } } impl UpcastNode { pub fn new(value: MemoHash) -> Self { Self { value } } } #[derive(Default, Debug, Clone, Copy)] pub struct UpcastAsRefNode + Sync + Send, U: Sync + Send>(pub T, PhantomData); impl<'i, T: 'i + AsRef + Sync + Send, U: 'i + StaticType + Sync + Send> Node<'i, DAny<'i>> for UpcastAsRefNode { type Output = FutureAny<'i>; #[inline(always)] fn eval(&'i self, _: DAny<'i>) -> Self::Output { Box::pin(async move { Box::new(self.0.as_ref()) as DAny<'i> }) } } impl + Sync + Send, U: Sync + Send> UpcastAsRefNode { pub const fn new(value: T) -> UpcastAsRefNode { UpcastAsRefNode(value, PhantomData) } } #[derive(Debug, Clone, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)] pub struct RenderOutput { pub data: RenderOutputType, pub metadata: RenderMetadata, } #[derive(Debug, Clone, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)] pub enum RenderOutputType { #[serde(skip)] Texture(graphene_application_io::ImageTexture), #[serde(skip)] Buffer { data: Vec, width: u32, height: u32, }, Svg { svg: String, image_data: Vec<(u64, Image)>, }, #[cfg(target_family = "wasm")] CanvasFrame { canvas_id: u64, resolution: DVec2, }, } impl CacheHash for RenderOutputType { fn cache_hash(&self, state: &mut H) { core::mem::discriminant(self).hash(state); match self { Self::Texture(texture) => texture.hash(state), Self::Buffer { data, width, height } => { data.cache_hash(state); width.cache_hash(state); height.cache_hash(state); } Self::Svg { svg, image_data } => { svg.cache_hash(state); image_data.cache_hash(state); } #[cfg(target_family = "wasm")] Self::CanvasFrame { canvas_id, resolution } => { canvas_id.cache_hash(state); resolution.cache_hash(state); } } } } // Metadata is excluded because it's editor-side auxiliary data (click targets, transforms) // that shouldn't affect render cache invalidation, and it contains HashMaps with non-deterministic iteration order impl CacheHash for RenderOutput { fn cache_hash(&self, state: &mut H) { self.data.cache_hash(state); } }