forked from katzen-cafe/iowo
svg-filters: rework codegen
This commit is contained in:
parent
a42ec014e5
commit
5368951254
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)]
|
||||
|
||||
pub mod codegen;
|
||||
pub mod types;
|
||||
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::{
|
||||
types::nodes::{primitives::color_matrix::ColorMatrixType, standard_input::StandardInput},
|
||||
Edge, Filter, Node,
|
||||
codegen::SvgDocument,
|
||||
types::{graph::edge::Edge, nodes::primitives::color_matrix::ColorMatrixType},
|
||||
Node,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut filter = Filter::new();
|
||||
|
||||
// <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" />
|
||||
// <feOffset dx="25" dy="0" />
|
||||
|
@ -21,37 +22,42 @@ fn main() {
|
|||
// <feComposite in="rb" in2="grn" operator="arithmetic" k2="1" k3="1" />
|
||||
// </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.,
|
||||
]))));
|
||||
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.,
|
||||
]))));
|
||||
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.,
|
||||
]))));
|
||||
|
||||
let offset_r = filter.add_node(Node::offset(25., 0.));
|
||||
let offset_b = filter.add_node(Node::offset(-25., 0.));
|
||||
let blur_r = filter.add_node(Node::gaussian_blur_xy(5, 0));
|
||||
let blur_b = filter.add_node(Node::gaussian_blur_xy(5, 0));
|
||||
let offset_r = chromabb.add_node(Node::offset(25., 0.));
|
||||
let offset_b = chromabb.add_node(Node::offset(-25., 0.));
|
||||
let blur_r = chromabb.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_final = 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 = chromabb.add_node(Node::composite_arithmetic(0., 1., 1., 0.));
|
||||
|
||||
filter.graph.extend_with_edges(&[
|
||||
(filter.source_graphic(), chan_r, Edge::unnamed()),
|
||||
(filter.source_graphic(), chan_b, Edge::unnamed()),
|
||||
(filter.source_graphic(), chan_g, Edge::unnamed()),
|
||||
(chan_r, offset_r, Edge::new("ro")),
|
||||
(offset_r, blur_r, Edge::new("rob")),
|
||||
(chan_b, offset_b, Edge::new("bo")),
|
||||
(offset_b, blur_b, Edge::new("bob")),
|
||||
(blur_r, composite_rb, Edge::new("robc").with_idx(0)),
|
||||
(blur_b, composite_rb, Edge::new("bobc").with_idx(1)),
|
||||
(composite_rb, composite_final, Edge::new("cf").with_idx(0)),
|
||||
(chan_g, composite_final, Edge::new("gf").with_idx(1)),
|
||||
chromabb.dag.extend_with_edges(&[
|
||||
(chromabb.source_graphic(), chan_r),
|
||||
(chromabb.source_graphic(), chan_b),
|
||||
(chromabb.source_graphic(), chan_g),
|
||||
(chan_r, offset_r),
|
||||
(offset_r, blur_r),
|
||||
(chan_b, offset_b),
|
||||
(offset_b, blur_b),
|
||||
]);
|
||||
chromabb.dag.extend_with_edges(&[
|
||||
(blur_r, composite_rb, Edge::new(0)),
|
||||
(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 nodes;
|
||||
|
||||
use crate::types::nodes::primitives::color_matrix::{ColorMatrix, ColorMatrixType};
|
||||
// pub mod old;
|
||||
|
||||
use self::{
|
||||
length::{Coordinate, Length},
|
||||
nodes::{
|
||||
primitives::{FePrimitive, WriteElement},
|
||||
Node,
|
||||
},
|
||||
};
|
||||
pub mod graph {
|
||||
use std::iter::Iterator;
|
||||
|
||||
use petgraph::{data::Build, prelude::*};
|
||||
|
||||
use crate::Node;
|
||||
|
||||
use self::edge::Edge;
|
||||
|
||||
use super::nodes::standard_input::StandardInput;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Filter<'a> {
|
||||
pub graph: DiGraph<Node, Edge<'a>>,
|
||||
pub struct FilterGraph {
|
||||
pub dag: DiGraph<Node, Edge>,
|
||||
source_graphic_idx: NodeIndex,
|
||||
}
|
||||
|
||||
impl Filter<'_> {
|
||||
impl FilterGraph {
|
||||
pub fn new() -> Self {
|
||||
let mut graph = DiGraph::new();
|
||||
let source_graphic_idx = graph.add_node(Node::StdInput(
|
||||
nodes::standard_input::StandardInput::SourceGraphic,
|
||||
));
|
||||
let mut dag = DiGraph::new();
|
||||
let source_graphic_idx = dag.add_node(Node::StdInput(StandardInput::SourceGraphic));
|
||||
|
||||
Self {
|
||||
graph,
|
||||
dag,
|
||||
source_graphic_idx,
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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>(())
|
||||
})
|
||||
.expect("shouldnt fail to write or something");
|
||||
String::from_utf8_lossy(&result).to_string()
|
||||
pub mod edge {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Edge {
|
||||
input_idx: u8,
|
||||
}
|
||||
|
||||
impl Edge {
|
||||
pub fn new(input_idx: u8) -> Self {
|
||||
Self { input_idx }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Filter<'_> {
|
||||
impl Default for Edge {
|
||||
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]
|
||||
pub fn with_idx(mut self, idx: u8) -> Self {
|
||||
self.in_idx = Some(idx);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn unnamed() -> Self {
|
||||
Self {
|
||||
edge_type: EdgeType::Unnamed,
|
||||
in_idx: None,
|
||||
impl ToString for Edge {
|
||||
fn to_string(&self) -> String {
|
||||
match self.input_idx {
|
||||
0 => "in".to_owned(),
|
||||
n => format!("in{}", n + 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub enum Unit {
|
||||
#[default]
|
||||
None,
|
||||
|
@ -15,3 +24,19 @@ pub enum Unit {
|
|||
Pt,
|
||||
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 {
|
||||
x: Coordinate,
|
||||
y: Coordinate,
|
||||
width: Length,
|
||||
height: Length,
|
||||
pub x: Coordinate,
|
||||
pub y: Coordinate,
|
||||
pub width: Length,
|
||||
pub height: Length,
|
||||
}
|
||||
|
||||
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 {
|
||||
match self {
|
||||
Node::Primitive {
|
||||
|
|
Loading…
Reference in a new issue