georust / geo-svg

A rust library to generate SVGs for geo-types
5 stars 4 forks source link

Provide capability to modify `SVG` before str serialization #13

Open dsgallups opened 3 months ago

dsgallups commented 3 months ago

Hello! I'm working on a project that requires adding additional attributes to particular xml nodes of the svg, of which are derived by the svg's external environment. In other words, I would like to target particular shapes on the SVG using css animations via an id attribute or inline style.

Since the ToSvg accepts a Box<dyn ToSvgStr>, it is difficult to add additional attributes to the nodes. If it's alright, I'd like to suggest some approaches (and implement one) to allow users to edit values for an SVG prior to and/or during serialization. Both invert/detach the connection between ToSvg and ToSvgStr

  1. Allow passing in a Vec of targeting attributes during serialization via .to_svg()

An implementation could include attaching an identifier to svg's Node types. i.e:

pub struct SvgNode {
   id: Option<String>,
   node: Box<dyn svg::Node>
}

and using the assign method to update attributes.

The drawback is that after processing of the svg data into an svg::Document, it's difficult (impossible?) to add additional attributes to the SVG, since there is no way to identify a type that implements Node.

This partially does what exists now...so maybe would it be alright to "target" particular nodes with additional attributes by passing in a Vec<(Identifier, (String, String))> into .to_svg()?

  1. Create a tree of nodes prior to implementing to_string() and serialize via quick_xml::Writer. Something of this sort?
    pub struct SvgNode {
    tag: String,
    id: Option<String>,
    attrs: Vec<(String, String)>,
    children: Option<Vec<SvgNode>>
    }

The drawback of course is that this is slower than the existing implementation of ToSvgStr. What if there could be a hybrid situation of the two where only ToSvg utilizes this?

If an approach here feels like it would be an improvement to the crate, I would be more than happy to contribute an implementation. Thank you for your consideration!

dsgallups commented 3 months ago

Perhaps both ideas should lie in an svg crate instead...it's only been 15 minutes and I'm realizing that this is most likely out of scope for the repo...but I'd still like to be able to identify the shapes somehow...hmm.

Edit: I could also

  1. Work on the svg crate to manage the identification problem through structural organization
  2. Make a svg crate which sounds pretty bad for it

Edit 2: The conflict I'm having is that I don't know much about what the scope of this crate should be. There are unique properties of SVGs that don't line up with the types necessarily, which is why I'd like to be able to identify the outputs at some point during the serialization process. This becomes a lot harder though if the outputs are not 1:1 with the types, especially with regard to having children nodes and whatnot.

RobWalt commented 3 months ago

Thanks for the initiative! I appreciate it!

I also had some similar issues with the current implementation in the past which led me to a attempt to refactor the lib in it's core. I failed though, since I got stuck at some point due to some not-so-well-thought-through designs. Back then I also attempted to structure it more like a tree, since this seems more natural. After giving it some more though nowadays after your comment I think this here (a slight modification of your idea) might work though:

pub struct SvgTree {
    // main content
    pub tag: String,
    pub content: String,
    pub attrs: HashMap<String, String>,

    // meta, isn't this "just" another attribute though?
    pub id: Option<String>,
    pub viewbox: Viewbox,

    // structure
    pub children: Vec<SvgTree>,
}

The main issue here is that we need to keep the SVG virtually editable until it's "Stringified". This is where I failed previously since I kept the ToSvgStr trait. I think we just need to ditch that. The issue with the trait is that we lose the ability to edit the svg once it's stringified and before that we have no real options of editing it either since dyn ToSvgStr is too generic. Not sure if that makes sense 🙈


As for complexity and performance: I'm currently not really worried about neither of those. In my eyes this crate mainly serves as a debug or static visualization utility. As long as no-one opens an issue about creating 1000s of SVGs per frame at 30FPS my opinion won't change and I'll keep being more focused on usability rather than performance.

RobWalt commented 3 months ago

I played around with the idea a bit further. Code can be found here. Not going to work on it for a moment. Feel free to base something off of it if you want. Looks like it might work out this time.

https://github.com/RobWalt/geo-svg/commit/24af9736ee4bf0f20c1d35cceb95cb8bc8c8b5c3

dsgallups commented 3 months ago

Awesome, thanks! Will work from there...potentially using usvg in some way could be cool as well, looking into it

dsgallups commented 3 months ago

Update: I've come up with an interesting plan.

I've actually written a lot of code already tackling this specific issue, along with the capability of resolving https://github.com/georust/geo-svg/issues/4. I wrote a library that is able to take a type and generate svgs (and therefore bitmap rasterizations), HTML Canvas, docx (and therefore pdf), and pptx. I even temporarily parked a crate package called awesome_docs of which I've just been waiting for the right use case to realize. The company I work at currently uses it internally for a lot of things like graphs and reports. I'm currently working on an internal project that utilizes the library. After I've completed this project, I'll see to getting approval to release it under an MIT/Apache 2 license. From there, I'd like to use the crate to implement all of the interfaces for this crate. That's the plan at least so I can use this crate for my personal project, too haha. Expect it to take a few weeks, but will adapt to what happens

dsgallups commented 3 months ago

Hey! I'm back again...wondering if you'd be interested in having the capability of turning these into actual HtmlElements via websys? fastest way to get there would probably be via leptos's view! macro...just a thought..

edit: here is an example I'm working on right now. Haven't pulled in all of the components I'd like for drawable backends, but hopefully that's a good thing for simplicity...

definitely needs a lot of work though, especially comment-wise. Essentially, things become "drawable" when they are styled. Anything that has a default style implementation will implement the ToElement trait if not provided one. Unfortunately, as I have encountered in another project already, the styles should interface directly with the thing that is being drawn to. i.e. if some geo data has an associated unique identifier, that should be transferable to a set of properties on the element, of which are explicitly clear. The implementation I've written for [Point], as described here is not written in a way where the possible properties of this element are identified. That makes it easy, but it also worries me a bit if going down this route. I would greatly appreciate your opinions on calculating the viewbox as well. Not sure if I have provided the Space trait, but it feels like an interface of which something takes up space and is Positioned could determine the parameters of the viewbox as you have implemented. I copied a bit of the viewbox code to workaround this for now.

last edit: An easy thing to do on an HtmlCanvasElement, but not on a SvgElement is to implement gradients. The implementation now for drawing a circle will fail in this case. Needs a lot more think.

RobWalt commented 3 months ago

I'm not really seeing myself actively maintaining such a feature. Furthermore this is a bit out of scope for this crate. I think it would fit better in an extra crate. Does that make sense to you too or is there anything you definitely need from geo-svg.

I still supporting the general idea. I think the best thing I could offer is to add a small note about the extta crate in the readme if you choose that path.