use dioxus::prelude::*;
use serde_xml_rs::from_str;
use std::io::BufRead;
use std::{
fs, io,
process::{Command, ExitStatus},
};
pub mod svg_types;
use svg_types::*;
pub mod error;
use error::*;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn example_test() {
let rsx = typst_to_rsx("./tmp/temp.typ").unwrap();
let expected_output = read_file("./test/expected_output.txt").unwrap();
assert_eq!(format!("{:?}", rsx).trim(), expected_output.trim());
}
}
pub fn typst_compile(
input_typ_file: &str,
output_svg_file: &str,
) -> Result<ExitStatus, ConvertError> {
let path = std::path::Path::new(output_svg_file);
if !path.exists() {
fs::create_dir_all(path.parent().unwrap()).unwrap();
}
let status = Command::new("typst")
.arg("compile") .arg(input_typ_file) .arg(output_svg_file) .status();
match status {
Ok(status_content) => Ok(status_content),
Err(e) => {
eprintln!("{:?}", e);
Err(ConvertError::new(&e.to_string()))
}
}
}
pub fn parse_svg_to_rsx(svg_str: &str) -> Result<Element, ConvertError> {
match from_str(svg_str) {
Ok(svg) => {
let parsed: Svg = svg;
Ok(rsx!(
svg {
view_box: parsed.view_box.clone(),
width: parsed.width.clone(),
height: parsed.height.clone(),
{parsed.elements.iter().map(|element| { construct_rsx_from_ele(&element) })}
}
))
}
Err(e) => Err(ConvertError::new(&e.to_string())),
}
}
fn construct_rsx_from_ele(tag: &SvgEle) -> Element {
match tag {
SvgEle::Path(path) => {
rsx!(path {
d: path.d.clone(),
class: path.class.clone().unwrap_or_default(),
fill: path.fill.clone().unwrap_or_default(),
fill_rule: path.fill_rule.clone().unwrap_or_default(),
stroke: path.stroke.clone().unwrap_or_default(),
stroke_width: path.stroke_width.clone().unwrap_or_default(),
stroke_linecap: path.stroke_linecap.clone().unwrap_or_default(),
stroke_linejoin: path.stroke_linejoin.clone().unwrap_or_default(),
stroke_miterlimit: path.stroke_miterlimit.clone().unwrap_or_default(),
})
}
SvgEle::G(g) => {
rsx!(
g {
class: g.class.clone().unwrap_or_default(),
transform: g.transform.clone().unwrap_or_default(),
{
let map_fn = |element: &svg_types::GEle| construct_rsx_from_gele(element);
g.elements
.as_ref()
.map(|elements| elements.iter().map(map_fn))
.unwrap_or_else(|| [].iter().map(map_fn))
}
}
)
}
SvgEle::Defs(defs) => {
rsx!(
defs { id: defs.id.clone(),
{defs.elements.iter().map(|element| { construct_rsx_from_symbol(&element) })}
}
)
}
}
}
fn construct_rsx_from_gele(tag: &GEle) -> Element {
match tag {
GEle::G(g) => {
rsx! {
g {
class: g.class.clone().unwrap_or_default(),
transform: g.transform.clone().unwrap_or_default(),
{
let map_fn = |element: &svg_types::GEle| construct_rsx_from_gele(element);
g.elements
.as_ref()
.map(|elements| elements.iter().map(map_fn))
.unwrap_or_else(|| [].iter().map(map_fn))
}
}
}
}
GEle::Use(uuse) => {
rsx! {
r#use {
fill: uuse.fill.clone(),
x: uuse.x.clone(),
fill_rule: uuse.fill_rule.clone(),
href: uuse.href.clone(),
transform: uuse.transform.clone(),
}
}
}
GEle::Path(path) => {
rsx!(path {
d: path.d.clone(),
class: path.class.clone().unwrap_or_default(),
fill: path.fill.clone().unwrap_or_default(),
fill_rule: path.fill_rule.clone().unwrap_or_default(),
stroke: path.stroke.clone().unwrap_or_default(),
stroke_width: path.stroke_width.clone().unwrap_or_default(),
stroke_linecap: path.stroke_linecap.clone().unwrap_or_default(),
stroke_linejoin: path.stroke_linejoin.clone().unwrap_or_default(),
stroke_miterlimit: path.stroke_miterlimit.clone().unwrap_or_default(),
})
}
GEle::Image(image) => {
rsx!(image {
width: image.width.clone(),
height: image.height.clone(),
preserve_aspect_ratio: image.preserve_aspect_ratio.clone(),
href: image.href.clone(),
transform: image.transform.clone(),
})
}
}
}
fn construct_rsx_from_symbol(tag: &Symbol) -> Element {
rsx!(
symbol { id: tag.id.clone(), overflow: tag.overflow.clone(),
{
match &tag.element {
SymbolEle::Path(path) => {
rsx! {
path {
d: path.d.clone(),
class: path.class.clone(),
fill: path.fill.clone(),
fill_rule: path.fill_rule.clone(),
}
}
}
SymbolEle::Image(image) => {
rsx! {
image {
width: image.width.clone(),
height: image.height.clone(),
preserve_aspect_ratio: image.preserve_aspect_ratio.clone(),
href: image.href.clone(),
transform: image.transform.clone(),
}
}
}
}
}
}
)
}
fn read_file(path: &str) -> Result<String, ConvertError> {
match fs::File::open(path).with_context(|| "Failed to open file") {
Ok(file) => {
let reader = io::BufReader::new(file);
let mut content = String::new();
for line in reader.lines() {
match line.with_context(|| "Failed to read content") {
Ok(lines) => {
content.push_str(&lines);
content.push('\n'); }
Err(e) => {
eprintln!("{:?}", e)
}
}
}
Ok(content)
}
Err(e) => Err(ConvertError::new(&e.to_string())),
}
}
pub fn typst_to_rsx(input_typ_file: &str) -> Result<Element, ConvertError> {
match typst_compile(input_typ_file, "./tmp/temp.svg") {
Ok(_) => {
let content_result = read_file("./tmp/temp.svg");
match content_result {
Ok(content) => {
let rsx_result = parse_svg_to_rsx(&content);
match rsx_result {
Ok(rsx) => {
println!("{:?}", rsx);
Ok(rsx)
}
Err(e) => Err(ConvertError::new(&e.to_string())),
}
}
Err(e) => Err(ConvertError::new(&e.to_string())),
}
}
Err(e) => Err(ConvertError::new(&e.to_string())),
}
}