Open sunjay opened 6 years ago
A static version of this where the current image is just saved as an SVG is definitely possible and pretty easy to implement for anyone who knows about SVG using the svg crate. You would simply have to take the drawings stored by the renderer and write an SVG file that contains each drawing. We could optimize it so that paths with the same pen are merged into a single long path (thus resulting in a smaller file).
It would be cool to look into exporting to other image formats. There is probably a crate for drawing into image files (does the image crate support that?).
As far as implementing this into the library goes, this is something that would take place purely in the rendering process. It would be done synchronously like a drawing command. Will be interesting to see how that impacts performance.
@sunjay I have some experience with SVG and recently got interested in the turtle. I am using it in a L-system workshop I am preparing. As a thank you for such an amazing crate I could offer an SVG exporter, If you are interested
@dvberkel Wow! That sounds great! I would love to work with you as you implement that.
Could you email me some more information about your workshop? I would love to hear how you are using turtle as part of it. :smile:
Here's some info to help get you started:
Note: Please try to add documentation as you add things to this codebase. Explaining something that is unclear or clarifying something that may not be obvious can be a great way to improve the codebase overall. We want to make sure that future people have an easy time using any of the code you add. To further understand what I am looking for, please see the copious amounts of documentation already present throughout the crate. :smile:
Add a save_svg<P: AsRef<Path>>(&mut self, path: P)
method to the Renderer
struct. Its first argument path
is the filename/path to save the drawing to. The type of path
is the same as what you'd see in something like File::open
and will allow you to use the function with any type that can be used as a path. That means that you will be able to do save_svg("/path/to/my/file.svg")
and save_svg(my_path_variable)
.
This method should save the entire drawing, exactly as it is stored in the renderer, to the given filename in SVG format.
All the drawings are stored in the renderer here:
You would basically need to use the svg crate to create SVG elements for each of different drawings that we currently support. Right now we just have simple path and filled polygon shapes, so hopefully that is not too difficult. You'll need to take care to style the shapes correctly and draw them in the right order so your SVG is consistent with the drawings that the turtle shows on the screen. We currently draw things back-to-front in the order they are stored in the drawings
vector. You can see the exact code for that here:
For best results, you should try and copy that algorithm directly, though I am okay with doing this in smaller more simplified phases if that is easier for you.
You can test this by adding a call to save_svg
in something like EndFill
(see below) and then calling turtle.end_fill()
from one of the example programs.
The simplest way I can see to do this is to do this is to add a new drawing command similar to the ones we have for adding paths and clearing the drawing.
https://github.com/sunjay/turtle/blob/05a5fa118b154c5504bfc9fb770081c2846dd80d/src/query.rs#L32-L50
You can do this as follows:
Step 1: Add a variant to the DrawingCommand
enum above called SaveSVG(PathBuf)
. The PathBuf
type is an owned variant of Path
that you can pass into the save_svg
function created earlier.
When you add this variant, you will get various compiler errors. Try to look at the code around where the error is to see if you can figure out how to fix the problem. One of the places you have to fix will be where you add the call to save_svg
that I mentioned earlier. I can help if you run into anything you don't know how to deal with.
Step 2: Add the method pub fn save_svg<P: AsRef<Path>>(&self, path: P)
to the TurtleWindow
struct. This should send the drawing command you created in step 1. (Make sure this method is &self
, not &mut self
or the next step will not work.)
(Feel free to make a PR at this point and then do the final step as a follow-up.)
Step 3: Add the method pub fn save_svg<P: AsRef<Path>>(&self, path: P)
to the Drawing
struct. This should call the save_svg
method you just added to TurtleWindow
.
For this step, please add detailed documentation to the method to describe what the method does and some examples of where it might be useful. See the other methods in Drawing
and in Turtle
for examples of what your documentation may look like. You can even include an example SVG as an image by adding the file to the docs/assets/images/docs
directory.
I highly recommend checking out the docs online for the Drawing
struct and for Turtle
struct to get some inspriation.
Hopefully these instructions are detailed enough to get started. Please don't hesitate to ask me questions. You can break the work up into smaller PRs and do as much or as little of it as you can. I am happy to help with whatever you need. Your work is very appreciated! :smile:
@sunjay That is a very detailed plan to implement SVG export! Thanks for giving so many pointers. I will start working on it shortly.
I also send you a mail via your website, answering your question about the workshop I am preparing.
In src/state.rs
de Pen
struct has a enabled
field.
I am guessing that it indicates if the pen is drawing or not? Is this correct?
So paths and polygons that are drawn with a disabled pen do not show up?
@dvberkel Yes. You are correct. We handle that here in the renderer:
It would be really cool if we could embed actual demos of programs running by pasting some JavaScript or SVG into an HTML page. It could be as simple as adding "record" and stop recording methods to the window which let you save the result as an animated SVG. In that case we wouldn't even need to compile anything to javascript. We could just record the actions and starting point and then have a small js runtime that plays everything.
Note: for programs that don't terminate, the approach outlined above would not work. That may not be a problem though because it's not like you can have infinite SVGs either.