svg-filters & basic parser #15
6 changed files with 361 additions and 267 deletions
229
crates/svg-filters/src/codegen.rs
Normal file
229
crates/svg-filters/src/codegen.rs
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
cmp,
|
||||||
|
collections::{BTreeSet, HashMap, HashSet},
|
||||||
|
};
|
||||||
|
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use petgraph::{
|
||||||
|
algo::toposort,
|
||||||
|
graph::DiGraph,
|
||||||
|
prelude::{EdgeIndex, NodeIndex},
|
||||||
|
};
|
||||||
|
use quick_xml::{events::attributes::Attribute, name::QName, ElementWriter};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
types::{
|
||||||
|
graph::{edge::Edge, FilterGraph},
|
||||||
|
nodes::{primitives::WriteElement, CommonAttrs},
|
||||||
|
},
|
||||||
|
Node,
|
||||||
|
};
|
||||||
|
|
||||||
|
use self::error::CodegenError;
|
||||||
|
|
||||||
|
pub struct SvgDocument {
|
||||||
|
filters: HashMap<String, FilterGraph>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SvgDocument {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
filters: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unwrap_used, reason = "we literally just did the insertion")]
|
||||||
|
pub fn create_filter(&mut self, id: impl ToString) -> &mut FilterGraph {
|
||||||
|
let filter = FilterGraph::new();
|
||||||
|
|
||||||
|
self.filters.insert(id.to_string(), filter);
|
||||||
|
self.filters.get_mut(&id.to_string()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_svg(&self) -> String {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let mut doc_writer = quick_xml::Writer::new_with_indent(&mut result, b' ', 4);
|
||||||
|
|
||||||
|
doc_writer
|
||||||
|
.create_element("svg")
|
||||||
|
.write_inner_content(|writer| {
|
||||||
|
self.filters
|
||||||
|
.iter()
|
||||||
|
.try_fold(writer, Self::gen_filter)
|
||||||
|
.map(|_| {})
|
||||||
|
});
|
||||||
|
|
||||||
|
String::from_utf8_lossy(&result).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_filter<'w, 'r>(
|
||||||
|
writer: &'w mut quick_xml::Writer<&'r mut Vec<u8>>,
|
||||||
|
(id, graph): (&String, &FilterGraph),
|
||||||
|
) -> Result<&'w mut quick_xml::Writer<&'r mut Vec<u8>>, CodegenError> {
|
||||||
|
writer
|
||||||
|
.create_element("filter")
|
||||||
|
.with_attribute(("id", id.as_str()))
|
||||||
|
.write_inner_content(|writer| Self::graph_to_svg(writer, graph))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn graph_to_svg(
|
||||||
|
writer: &mut quick_xml::Writer<&mut Vec<u8>>,
|
||||||
|
graph: &FilterGraph,
|
||||||
|
) -> Result<(), CodegenError> {
|
||||||
|
let sorted = toposort(&graph.dag, None).expect("no cycles allowed in a DAG");
|
||||||
|
sorted
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|node_idx| {
|
||||||
|
graph
|
||||||
|
.dag
|
||||||
|
.node_weight(node_idx)
|
||||||
|
.and_then(|node| node.primitive())
|
||||||
|
.map(|(primitive, common_attrs)| (node_idx, primitive, common_attrs))
|
||||||
|
})
|
||||||
|
.try_fold(writer, |writer, (node_idx, primitive, common_attrs)| {
|
||||||
|
let mut el_writer = primitive.element_writer(writer);
|
||||||
|
el_writer = input_attrs(node_idx, &graph.dag).write_into(el_writer);
|
||||||
|
el_writer = output_attrs(node_idx, &graph.dag).write_into(el_writer);
|
||||||
|
el_writer = Attrs::from(*common_attrs).write_into(el_writer);
|
||||||
|
el_writer.write_empty()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Attrs(IndexMap<String, String>);
|
||||||
|
|
||||||
|
impl Attrs {
|
||||||
|
fn write_into<'w, 'b>(
|
||||||
|
self,
|
||||||
|
el_writer: ElementWriter<'w, &'b mut Vec<u8>>,
|
||||||
|
) -> ElementWriter<'w, &'b mut Vec<u8>> {
|
||||||
|
let attrs = self.0.iter().map(|(k, v)| (k.as_str(), v.as_str()));
|
||||||
|
el_writer.with_attributes(attrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CommonAttrs> for Attrs {
|
||||||
|
fn from(value: CommonAttrs) -> Self {
|
||||||
|
let CommonAttrs {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
} = value;
|
||||||
|
Self(IndexMap::from([
|
||||||
|
("x".to_owned(), x.to_string()),
|
||||||
|
("y".to_owned(), y.to_string()),
|
||||||
|
("width".to_owned(), width.to_string()),
|
||||||
|
("height".to_owned(), height.to_string()),
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(
|
||||||
|
clippy::unwrap_used,
|
||||||
|
reason = "in all cases of use of unwrap, it's values we got from the graph so we know it's safe"
|
||||||
|
)]
|
||||||
|
fn input_attrs(node_idx: NodeIndex, g: &DiGraph<Node, Edge>) -> Attrs {
|
||||||
|
let inputs: Vec<NodeIndex> = g
|
||||||
|
.neighbors_directed(node_idx, petgraph::Direction::Incoming)
|
||||||
|
.collect();
|
||||||
|
let node = g.node_weight(node_idx).unwrap();
|
||||||
|
|
||||||
|
if node.input_count() as usize != inputs.len() {
|
||||||
|
todo!("proper error handling for wrong numbers of inputs")
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut inputs = inputs
|
||||||
|
.into_iter()
|
||||||
|
.map(|input_idx| {
|
||||||
|
let edge_idx = g.find_edge(input_idx, node_idx).unwrap();
|
||||||
|
let edge = g.edge_weight(edge_idx).unwrap();
|
||||||
|
let input_node = g.node_weight(input_idx).unwrap();
|
||||||
|
|
||||||
|
(input_node, edge_idx, edge)
|
||||||
|
})
|
||||||
|
.collect::<Vec<(&Node, EdgeIndex, &Edge)>>();
|
||||||
|
inputs.sort_by(|a, b| a.2.cmp(b.2));
|
||||||
|
|
||||||
|
let mut uniq = BTreeSet::new();
|
||||||
|
let no_duplicates = inputs.iter().all(|(_, _, edge)| uniq.insert(edge));
|
||||||
|
|
||||||
|
if no_duplicates {
|
||||||
|
Attrs(
|
||||||
|
inputs
|
||||||
|
.into_iter()
|
||||||
|
.map(|(input, edge_idx, edge)| {
|
||||||
|
let name = match input {
|
||||||
|
Node::StdInput(s) => format!("{s:?}"),
|
||||||
|
Node::Primitive { .. } => format_edge_idx(edge_idx),
|
||||||
|
};
|
||||||
|
(edge.to_string(), name)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
todo!("better error handling for inputs with multiple values")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(
|
||||||
|
clippy::unwrap_used,
|
||||||
|
reason = "in all cases of use of unwrap, it's values we got from the graph so we know it's safe"
|
||||||
|
)]
|
||||||
|
fn output_attrs(node_idx: NodeIndex, g: &DiGraph<Node, Edge>) -> Attrs {
|
||||||
|
let outputs: Vec<NodeIndex> = g
|
||||||
|
.neighbors_directed(node_idx, petgraph::Direction::Outgoing)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
match outputs.len().cmp(&1) {
|
||||||
|
cmp::Ordering::Less => Attrs(IndexMap::new()),
|
||||||
|
cmp::Ordering::Equal => {
|
||||||
|
let output = outputs.first().unwrap();
|
||||||
|
let edge_idx = g.find_edge(node_idx, *output).unwrap();
|
||||||
|
Attrs(IndexMap::from([(
|
||||||
|
"result".to_string(),
|
||||||
|
format_edge_idx(edge_idx),
|
||||||
|
)]))
|
||||||
|
}
|
||||||
|
cmp::Ordering::Greater => todo!("better error handling for too many outputs"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// convenience method to avoid fuckups during future changes
|
||||||
|
fn format_edge_idx(idx: EdgeIndex) -> String {
|
||||||
|
format!("edge{}", idx.index())
|
||||||
|
}
|
||||||
|
|
||||||
|
mod error {
|
||||||
|
use std::{error::Error, fmt::Display};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum CodegenError {
|
||||||
|
QuickXmlError(quick_xml::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<quick_xml::Error> for CodegenError {
|
||||||
|
fn from(value: quick_xml::Error) -> Self {
|
||||||
|
Self::QuickXmlError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CodegenError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
CodegenError::QuickXmlError(e) => e.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for CodegenError {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SvgDocument {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
#![feature(lint_reasons)]
|
#![feature(lint_reasons)]
|
||||||
|
|
||||||
|
pub mod codegen;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub use types::nodes::Node;
|
pub use types::nodes::Node;
|
||||||
pub use types::Edge;
|
|
||||||
pub use types::Filter;
|
#[cfg(test)]
|
||||||
|
mod tests {}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
|
use std::hint::black_box;
|
||||||
|
|
||||||
use svg_filters::{
|
use svg_filters::{
|
||||||
types::nodes::{primitives::color_matrix::ColorMatrixType, standard_input::StandardInput},
|
codegen::SvgDocument,
|
||||||
Edge, Filter, Node,
|
types::{graph::edge::Edge, nodes::primitives::color_matrix::ColorMatrixType},
|
||||||
|
Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut filter = Filter::new();
|
|
||||||
|
|
||||||
// <filter id="chromabb" >
|
// <filter id="chromabb" >
|
||||||
// <feColorMatrix values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" in="SourceGraphic" />
|
// <feColorMatrix values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" in="SourceGraphic" />
|
||||||
// <feOffset dx="25" dy="0" />
|
// <feOffset dx="25" dy="0" />
|
||||||
|
@ -21,37 +22,42 @@ fn main() {
|
||||||
// <feComposite in="rb" in2="grn" operator="arithmetic" k2="1" k3="1" />
|
// <feComposite in="rb" in2="grn" operator="arithmetic" k2="1" k3="1" />
|
||||||
// </filter>
|
// </filter>
|
||||||
|
|
||||||
let chan_r = filter.add_node(Node::color_matrix(ColorMatrixType::Matrix(Box::new([
|
let mut doc = SvgDocument::new();
|
||||||
|
let chromabb = doc.create_filter("chromabb_gen");
|
||||||
|
|
||||||
|
let chan_r = chromabb.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.,
|
1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0.,
|
||||||
]))));
|
]))));
|
||||||
let chan_g = filter.add_node(Node::color_matrix(ColorMatrixType::Matrix(Box::new([
|
let chan_g = chromabb.add_node(Node::color_matrix(ColorMatrixType::Matrix(Box::new([
|
||||||
0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0.,
|
0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0.,
|
||||||
]))));
|
]))));
|
||||||
let chan_b = filter.add_node(Node::color_matrix(ColorMatrixType::Matrix(Box::new([
|
let chan_b = chromabb.add_node(Node::color_matrix(ColorMatrixType::Matrix(Box::new([
|
||||||
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 1., 0.,
|
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 1., 0.,
|
||||||
]))));
|
]))));
|
||||||
|
|
||||||
let offset_r = filter.add_node(Node::offset(25., 0.));
|
let offset_r = chromabb.add_node(Node::offset(25., 0.));
|
||||||
let offset_b = filter.add_node(Node::offset(-25., 0.));
|
let offset_b = chromabb.add_node(Node::offset(-25., 0.));
|
||||||
let blur_r = filter.add_node(Node::gaussian_blur_xy(5, 0));
|
let blur_r = chromabb.add_node(Node::gaussian_blur_xy(5, 0));
|
||||||
let blur_b = filter.add_node(Node::gaussian_blur_xy(5, 0));
|
let blur_b = chromabb.add_node(Node::gaussian_blur_xy(5, 0));
|
||||||
|
|
||||||
let composite_rb = filter.add_node(Node::composite_arithmetic(0., 1., 1., 0.));
|
let composite_rb = chromabb.add_node(Node::composite_arithmetic(0., 1., 1., 0.));
|
||||||
let composite_final = filter.add_node(Node::composite_arithmetic(0., 1., 1., 0.));
|
let composite_final = chromabb.add_node(Node::composite_arithmetic(0., 1., 1., 0.));
|
||||||
|
|
||||||
filter.graph.extend_with_edges(&[
|
chromabb.dag.extend_with_edges(&[
|
||||||
(filter.source_graphic(), chan_r, Edge::unnamed()),
|
(chromabb.source_graphic(), chan_r),
|
||||||
(filter.source_graphic(), chan_b, Edge::unnamed()),
|
(chromabb.source_graphic(), chan_b),
|
||||||
(filter.source_graphic(), chan_g, Edge::unnamed()),
|
(chromabb.source_graphic(), chan_g),
|
||||||
(chan_r, offset_r, Edge::new("ro")),
|
(chan_r, offset_r),
|
||||||
(offset_r, blur_r, Edge::new("rob")),
|
(offset_r, blur_r),
|
||||||
(chan_b, offset_b, Edge::new("bo")),
|
(chan_b, offset_b),
|
||||||
(offset_b, blur_b, Edge::new("bob")),
|
(offset_b, blur_b),
|
||||||
(blur_r, composite_rb, Edge::new("robc").with_idx(0)),
|
]);
|
||||||
(blur_b, composite_rb, Edge::new("bobc").with_idx(1)),
|
chromabb.dag.extend_with_edges(&[
|
||||||
(composite_rb, composite_final, Edge::new("cf").with_idx(0)),
|
(blur_r, composite_rb, Edge::new(0)),
|
||||||
(chan_g, composite_final, Edge::new("gf").with_idx(1)),
|
(blur_b, composite_rb, Edge::new(1)),
|
||||||
|
(composite_rb, composite_final, Edge::new(0)),
|
||||||
|
(chan_g, composite_final, Edge::new(1)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
println!("{}", filter.to_svg())
|
black_box(doc.generate_svg());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,250 +1,70 @@
|
||||||
use core::panic;
|
|
||||||
use std::{
|
|
||||||
borrow::Cow,
|
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
fmt::Debug,
|
|
||||||
io::BufWriter,
|
|
||||||
primitive,
|
|
||||||
};
|
|
||||||
|
|
||||||
use petgraph::{
|
|
||||||
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, Error};
|
|
||||||
|
|
||||||
pub mod length;
|
pub mod length;
|
||||||
pub mod nodes;
|
pub mod nodes;
|
||||||
|
|
||||||
use crate::types::nodes::primitives::color_matrix::{ColorMatrix, ColorMatrixType};
|
// pub mod old;
|
||||||
|
|
||||||
use self::{
|
pub mod graph {
|
||||||
length::{Coordinate, Length},
|
use std::iter::Iterator;
|
||||||
nodes::{
|
|
||||||
primitives::{FePrimitive, WriteElement},
|
|
||||||
Node,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
use petgraph::{data::Build, prelude::*};
|
||||||
pub struct Filter<'a> {
|
|
||||||
pub graph: DiGraph<Node, Edge<'a>>,
|
use crate::Node;
|
||||||
|
|
||||||
|
use self::edge::Edge;
|
||||||
|
|
||||||
|
use super::nodes::standard_input::StandardInput;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FilterGraph {
|
||||||
|
pub dag: DiGraph<Node, Edge>,
|
||||||
source_graphic_idx: NodeIndex,
|
source_graphic_idx: NodeIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Filter<'_> {
|
impl FilterGraph {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut graph = DiGraph::new();
|
let mut dag = DiGraph::new();
|
||||||
let source_graphic_idx = graph.add_node(Node::StdInput(
|
let source_graphic_idx = dag.add_node(Node::StdInput(StandardInput::SourceGraphic));
|
||||||
nodes::standard_input::StandardInput::SourceGraphic,
|
|
||||||
));
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
graph,
|
dag,
|
||||||
source_graphic_idx,
|
source_graphic_idx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_node(&mut self, node: Node) -> NodeIndex {
|
pub fn add_node(&mut self, node: Node) -> NodeIndex {
|
||||||
self.graph.add_node(node)
|
self.dag.add_node(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_graphic(&self) -> NodeIndex {
|
pub fn source_graphic(&self) -> NodeIndex {
|
||||||
self.source_graphic_idx
|
self.source_graphic_idx
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_svg(&self) -> String {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
// let mut doc_writer = quick_xml::Writer::new(&mut result);
|
|
||||||
let mut doc_writer = quick_xml::Writer::new_with_indent(&mut result, b' ', 2);
|
|
||||||
|
|
||||||
doc_writer
|
|
||||||
.create_element("filter")
|
|
||||||
.with_attribute(("id", "chromabb_gen"))
|
|
||||||
.write_inner_content(|writer| {
|
|
||||||
let mut dfs_space = DfsSpace::new(&self.graph);
|
|
||||||
let sorted =
|
|
||||||
toposort(&self.graph, Some(&mut dfs_space)).expect("No cycles! Bad user!");
|
|
||||||
|
|
||||||
let v = 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
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.try_fold(writer, |acc, (node_idx, primitive, common_attrs)| {
|
|
||||||
let mut el_writer = primitive.element_writer(&mut *acc);
|
|
||||||
el_writer = create_input_attr(
|
|
||||||
&self.graph,
|
|
||||||
el_writer,
|
|
||||||
node_idx,
|
|
||||||
self.graph
|
|
||||||
.node_weight(node_idx)
|
|
||||||
.expect("cannot get invalid node_idx from toposort")
|
|
||||||
.input_count(),
|
|
||||||
);
|
|
||||||
create_output_attr(&self.graph, el_writer, node_idx).write_empty()
|
|
||||||
})
|
|
||||||
.map(|_| ());
|
|
||||||
|
|
||||||
Ok::<(), Error>(())
|
pub mod edge {
|
||||||
})
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
.expect("shouldnt fail to write or something");
|
pub struct Edge {
|
||||||
String::from_utf8_lossy(&result).to_string()
|
input_idx: u8,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Filter<'_> {
|
impl Edge {
|
||||||
|
pub fn new(input_idx: u8) -> Self {
|
||||||
|
Self { input_idx }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Edge {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self { input_idx: 0 }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Edge<'a> {
|
|
||||||
pub edge_type: EdgeType<'a>,
|
|
||||||
/// the index of the `in` attribute on the target element
|
|
||||||
/// if None, just `in`
|
|
||||||
pub in_idx: Option<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Edge<'a> {
|
|
||||||
pub fn new(name: &'a str) -> Self {
|
|
||||||
Self {
|
|
||||||
edge_type: EdgeType::Named(name),
|
|
||||||
in_idx: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
impl ToString for Edge {
|
||||||
pub fn with_idx(mut self, idx: u8) -> Self {
|
fn to_string(&self) -> String {
|
||||||
self.in_idx = Some(idx);
|
match self.input_idx {
|
||||||
self
|
0 => "in".to_owned(),
|
||||||
|
n => format!("in{}", n + 1),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unnamed() -> Self {
|
|
||||||
Self {
|
|
||||||
edge_type: EdgeType::Unnamed,
|
|
||||||
in_idx: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Edge<'_> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::unnamed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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_attr<'w, 'b>(
|
|
||||||
g: &'_ DiGraph<Node, Edge<'_>>,
|
|
||||||
mut el_writer: ElementWriter<'w, &'b mut Vec<u8>>,
|
|
||||||
node_idx: NodeIndex,
|
|
||||||
input_count: u8,
|
|
||||||
) -> ElementWriter<'w, &'b mut Vec<u8>> {
|
|
||||||
let inputs = g
|
|
||||||
.neighbors_directed(node_idx, petgraph::Direction::Incoming)
|
|
||||||
.collect::<Vec<NodeIndex>>();
|
|
||||||
|
|
||||||
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 { .. } => {
|
|
||||||
if let EdgeType::Named(name) = edge_type {
|
|
||||||
(*name).to_owned()
|
|
||||||
} else {
|
|
||||||
panic!(
|
|
||||||
"unnamed edges should not be used for connections between primitives"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
el_writer.with_attribute((in_attr_name.as_str(), v.as_str()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used, reason = "all unwraps are for finding on options")]
|
|
||||||
fn create_output_attr<'w, 'b>(
|
|
||||||
g: &'_ DiGraph<Node, Edge<'_>>,
|
|
||||||
mut el_writer: ElementWriter<'w, &'b mut Vec<u8>>,
|
|
||||||
node_idx: NodeIndex,
|
|
||||||
) -> ElementWriter<'w, &'b mut Vec<u8>> {
|
|
||||||
let output = g
|
|
||||||
.neighbors_directed(node_idx, petgraph::Direction::Outgoing)
|
|
||||||
.map(|neighbor_idx| {
|
|
||||||
let edge_idx = g.find_edge(node_idx, neighbor_idx).unwrap();
|
|
||||||
|
|
||||||
let Edge {
|
|
||||||
edge_type: EdgeType::Named(name),
|
|
||||||
..
|
|
||||||
} = g.edge_weight(edge_idx).unwrap()
|
|
||||||
else {
|
|
||||||
panic!("Unnamed edge used for connection between primitives");
|
|
||||||
};
|
|
||||||
*name
|
|
||||||
})
|
|
||||||
.collect::<HashSet<&str>>();
|
|
||||||
|
|
||||||
if output.is_empty() {
|
|
||||||
el_writer
|
|
||||||
} else if output.len() == 1 {
|
|
||||||
el_writer.with_attribute(Attribute {
|
|
||||||
key: QName(b"result"),
|
|
||||||
value: Cow::from(
|
|
||||||
(*output.into_iter().collect::<Vec<&str>>().first().unwrap())
|
|
||||||
.to_string()
|
|
||||||
.into_bytes(),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
panic!("Can't have more then one named output: {output:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
#[derive(Default, Debug)]
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Copy)]
|
||||||
pub struct Length(f32, Unit);
|
pub struct Length(f32, Unit);
|
||||||
|
|
||||||
|
impl Display for Length {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}{}", self.0, self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type Coordinate = Length;
|
pub type Coordinate = Length;
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug, Clone, Copy)]
|
||||||
pub enum Unit {
|
pub enum Unit {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
|
@ -15,3 +24,19 @@ pub enum Unit {
|
||||||
Pt,
|
Pt,
|
||||||
Pc,
|
Pc,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Unit {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Unit::None => f.write_str(""),
|
||||||
|
Unit::Em => f.write_str("em"),
|
||||||
|
Unit::Ex => f.write_str("ex"),
|
||||||
|
Unit::Px => f.write_str("px"),
|
||||||
|
Unit::In => f.write_str("in"),
|
||||||
|
Unit::Cm => f.write_str("cm"),
|
||||||
|
Unit::Mm => f.write_str("mm"),
|
||||||
|
Unit::Pt => f.write_str("pt"),
|
||||||
|
Unit::Pc => f.write_str("pc"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -34,12 +34,12 @@ impl Default for Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug, Clone, Copy)]
|
||||||
pub(crate) struct CommonAttrs {
|
pub(crate) struct CommonAttrs {
|
||||||
x: Coordinate,
|
pub x: Coordinate,
|
||||||
y: Coordinate,
|
pub y: Coordinate,
|
||||||
width: Length,
|
pub width: Length,
|
||||||
height: Length,
|
pub height: Length,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
|
@ -50,6 +50,18 @@ impl Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn primitive(&self) -> Option<(&FePrimitive, &CommonAttrs)> {
|
||||||
|
if let Node::Primitive {
|
||||||
|
primitive,
|
||||||
|
common_attrs,
|
||||||
|
} = self
|
||||||
|
{
|
||||||
|
Some((primitive, common_attrs))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn input_count(&self) -> u8 {
|
pub fn input_count(&self) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
Node::Primitive {
|
Node::Primitive {
|
||||||
|
|
Loading…
Reference in a new issue