diff --git a/crates/svg-filters/src/main.rs b/crates/svg-filters/src/main.rs index d628d3e..5531784 100644 --- a/crates/svg-filters/src/main.rs +++ b/crates/svg-filters/src/main.rs @@ -1,6 +1,20 @@ -use svg_filters::{types::nodes::primitives::color_matrix::ColorMatrixType, Edge, Filter, Node}; +use svg_filters::{ + types::nodes::{primitives::color_matrix::ColorMatrixType, standard_input::StandardInput}, + Edge, Filter, Node, +}; fn main() { + let mut supersimple = Filter::new(); + + let cm = supersimple.add_node(Node::color_matrix(ColorMatrixType::Matrix(Box::new([ + 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., + ])))); + + supersimple + .graph + .extend_with_edges(&[(supersimple.source_graphic(), cm)]); + dbg!(supersimple.to_svg()); + let mut filter = Filter::new(); // @@ -50,5 +64,5 @@ fn main() { (chan_g, composite_final, Edge::in_idx(1)), ]); - println!("{filter:#?}") + println!("Result: {}", filter.to_svg()) } diff --git a/crates/svg-filters/src/types.rs b/crates/svg-filters/src/types.rs index 31e0954..df679ba 100644 --- a/crates/svg-filters/src/types.rs +++ b/crates/svg-filters/src/types.rs @@ -1,8 +1,15 @@ -use std::collections::HashMap; +use std::{borrow::Cow, collections::HashMap, fmt::Debug, io::BufWriter, primitive}; use petgraph::{ - adj::EdgeIndex, data::Build, graph::DiGraph, graphmap::DiGraphMap, prelude::NodeIndex, + adj::EdgeIndex, + algo::{toposort, DfsSpace}, + data::{Build, DataMap}, + graph::DiGraph, + graphmap::DiGraphMap, + prelude::NodeIndex, + visit::NodeIndexable, }; +use quick_xml::{events::attributes::Attribute, name::QName, ElementWriter}; pub mod length; pub mod nodes; @@ -11,7 +18,10 @@ use crate::types::nodes::primitives::color_matrix::{ColorMatrix, ColorMatrixType use self::{ length::{Coordinate, Length}, - nodes::Node, + nodes::{ + primitives::{FePrimitive, WriteElement}, + Node, + }, }; #[derive(Debug)] @@ -40,6 +50,52 @@ impl Filter<'_> { pub fn source_graphic(&self) -> NodeIndex { self.source_graphic_idx } + + pub fn to_svg(&self) -> String { + let mut result = Vec::new(); + let mut writer = quick_xml::Writer::new(&mut result); + + let mut dfs_space = DfsSpace::new(&self.graph); + let sorted = toposort(&self.graph, Some(&mut dfs_space)).expect("No cycles! Bad user!"); + + sorted + .into_iter() + .filter_map(|node_idx| { + let node = self + .graph + .node_weight(node_idx) + .expect("toposorting will not return invalid indices"); + + if let Node::Primitive { + primitive, + common_attrs, + } = node + { + Some((node_idx, primitive, common_attrs)) + } else { + None + } + }) + .fold( + &mut writer, + |writer, (node_idx, primitive, common_attrs)| { + let el_writer = primitive.element_writer(writer); + create_input_elements( + &self.graph, + el_writer, + node_idx, + self.graph + .node_weight(node_idx) + .expect("cannot get invalid node_idx from toposort") + .input_count(), + ) + .write_empty() + .expect("should write successfully") + }, + ); + + String::from_utf8_lossy(&result).to_string() + } } impl Default for Filter<'_> { @@ -50,10 +106,10 @@ impl Default for Filter<'_> { #[derive(Debug, Clone)] pub struct Edge<'a> { - edge_type: EdgeType<'a>, + pub edge_type: EdgeType<'a>, /// the index of the `in` attribute on the target element /// if None, just `in` - in_idx: Option, + pub in_idx: Option, } impl Edge<'_> { @@ -72,9 +128,60 @@ impl Edge<'_> { } } +impl Default for Edge<'_> { + fn default() -> Self { + Self::new() + } +} + #[derive(Debug, Clone)] pub enum EdgeType<'a> { Named(&'a str), /// For standard inputs such as SourceGraphic etc., which we'll just be representing as nodes for simplicity Unnamed, } +fn create_input_elements<'a>( + g: &'a DiGraph>, + mut el_writer: ElementWriter<'a, &'a mut Vec>, + node_idx: NodeIndex, + input_count: u8, +) -> ElementWriter<'a, &'a mut Vec> { + let inputs = g + .neighbors_directed(node_idx, petgraph::Direction::Incoming) + .collect::>(); + + if inputs.len() != input_count as usize { + // TODO: better error handling + panic!("input couns didnt match"); + } + + inputs + .into_iter() + .enumerate() + .fold(el_writer, |el_writer, (i, incoming_idx)| { + let incoming_node = g.node_weight(incoming_idx).expect("cannot fail here"); + + // find incoming edge and get weight + let Edge { edge_type, in_idx } = g + .edge_weight( + g.find_edge(incoming_idx, node_idx) + .expect("there should always be an edge"), + ) + .expect("once again, should always exist"); + + let in_attr_name = match in_idx { + None | Some(0) => "in".to_owned(), + Some(n) => format!("in{}", n + 1), + }; + + let v = match incoming_node { + Node::StdInput(std_in) => format!("{std_in:?}"), + Node::Primitive { + primitive, + common_attrs, + } => todo!(), + }; + + el_writer.with_attribute((in_attr_name.as_str(), v.as_str())) + }) +} diff --git a/crates/svg-filters/src/types/nodes.rs b/crates/svg-filters/src/types/nodes.rs index 29982bc..63e041c 100644 --- a/crates/svg-filters/src/types/nodes.rs +++ b/crates/svg-filters/src/types/nodes.rs @@ -1,3 +1,7 @@ +use core::panic; + +use quick_xml::events::attributes::Attribute; + use self::{ primitives::{ blend::{Blend, BlendMode}, @@ -31,7 +35,7 @@ impl Default for Node { } #[derive(Default, Debug)] -struct CommonAttrs { +pub(crate) struct CommonAttrs { x: Coordinate, y: Coordinate, width: Length, diff --git a/crates/svg-filters/src/types/nodes/primitives.rs b/crates/svg-filters/src/types/nodes/primitives.rs index 6be79b3..012fd9a 100644 --- a/crates/svg-filters/src/types/nodes/primitives.rs +++ b/crates/svg-filters/src/types/nodes/primitives.rs @@ -1,3 +1,5 @@ +use quick_xml::{events::attributes::Attribute, ElementWriter, Writer}; + use crate::types::length::{Coordinate, Length}; use self::blend::BlendMode; @@ -21,6 +23,19 @@ pub mod specular_lighting; pub mod tile; pub mod turbulence; +pub trait WriteElement { + fn attrs(&self) -> Vec; + fn tag_name(&self) -> &'static str; + fn element_writer<'writer, 'result>( + &self, + writer: &'writer mut Writer<&'result mut Vec>, + ) -> ElementWriter<'writer, &'result mut Vec> { + writer + .create_element(self.tag_name()) + .with_attributes(self.attrs()) + } +} + /// svg filter effects primitives #[derive(Debug)] pub enum FePrimitive { @@ -42,3 +57,47 @@ pub enum FePrimitive { Tile(tile::Tile), Turbulence(turbulence::Turbulence), } + +impl WriteElement for FePrimitive { + fn attrs(&self) -> std::vec::Vec> { + match self { + FePrimitive::Blend(_) => todo!(), + FePrimitive::ColorMatrix(cm) => cm.attrs(), + FePrimitive::ComponentTransfer(_) => todo!(), + FePrimitive::Composite(_) => todo!(), + FePrimitive::ConvolveMatrix(_) => todo!(), + FePrimitive::DiffuseLighting(_) => todo!(), + FePrimitive::DisplacementMap(_) => todo!(), + FePrimitive::Flood(_) => todo!(), + FePrimitive::GaussianBlur(_) => todo!(), + FePrimitive::Image(_) => todo!(), + FePrimitive::Merge(_) => todo!(), + FePrimitive::Morphology(_) => todo!(), + FePrimitive::Offset(_) => todo!(), + FePrimitive::SpecularLighting(_) => todo!(), + FePrimitive::Tile(_) => todo!(), + FePrimitive::Turbulence(_) => todo!(), + } + } + + fn tag_name(&self) -> &'static str { + match self { + FePrimitive::Blend(_) => todo!(), + FePrimitive::ColorMatrix(cm) => cm.tag_name(), + FePrimitive::ComponentTransfer(_) => todo!(), + FePrimitive::Composite(_) => todo!(), + FePrimitive::ConvolveMatrix(_) => todo!(), + FePrimitive::DiffuseLighting(_) => todo!(), + FePrimitive::DisplacementMap(_) => todo!(), + FePrimitive::Flood(_) => todo!(), + FePrimitive::GaussianBlur(_) => todo!(), + FePrimitive::Image(_) => todo!(), + FePrimitive::Merge(_) => todo!(), + FePrimitive::Morphology(_) => todo!(), + FePrimitive::Offset(_) => todo!(), + FePrimitive::SpecularLighting(_) => todo!(), + FePrimitive::Tile(_) => todo!(), + FePrimitive::Turbulence(_) => todo!(), + } + } +} diff --git a/crates/svg-filters/src/types/nodes/primitives/blend.rs b/crates/svg-filters/src/types/nodes/primitives/blend.rs index 242f763..1aba6d0 100644 --- a/crates/svg-filters/src/types/nodes/primitives/blend.rs +++ b/crates/svg-filters/src/types/nodes/primitives/blend.rs @@ -1,6 +1,6 @@ /// [feBlend](https://www.w3.org/TR/SVG11/filters.html#feBlendElement) #[derive(Debug)] -pub(in crate::types::nodes) struct Blend { +pub struct Blend { mode: BlendMode, } diff --git a/crates/svg-filters/src/types/nodes/primitives/color_matrix.rs b/crates/svg-filters/src/types/nodes/primitives/color_matrix.rs index fbae6b2..ef32b60 100644 --- a/crates/svg-filters/src/types/nodes/primitives/color_matrix.rs +++ b/crates/svg-filters/src/types/nodes/primitives/color_matrix.rs @@ -1,3 +1,9 @@ +use std::borrow::Cow; + +use quick_xml::{events::attributes::Attribute, name::QName, se::to_string}; + +use super::WriteElement; + /// [feColorMatrix](https://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement) #[derive(Debug)] pub struct ColorMatrix { @@ -10,6 +16,34 @@ impl ColorMatrix { } } +impl WriteElement for ColorMatrix { + fn attrs(&self) -> Vec { + match &self.cm_type { + ColorMatrixType::Matrix(v) => vec![Attribute { + key: QName(b"values"), + value: Cow::from( + v.iter() + .map(|v| v.to_string()) + .reduce(|mut acc, e| { + acc.push(' '); + acc.push_str(&e); + acc + }) + .expect("Should be able to concatenate the thingies") + .into_bytes(), + ), + }], + ColorMatrixType::Saturate(v) => todo!(), + ColorMatrixType::HueRotate(v) => todo!(), + ColorMatrixType::LuminanceToAlpha => todo!(), + } + } + + fn tag_name(&self) -> &'static str { + "feColorMatrix" + } +} + #[derive(Debug)] pub enum ColorMatrixType { Matrix(Box<[f32; 20]>),