sunjay / turtle

Create Animated Drawings in Rust
http://turtle.rs
Mozilla Public License 2.0
562 stars 53 forks source link

Compile/Save to SVG/JavaScript #38

Open sunjay opened 6 years ago

sunjay commented 6 years ago

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.

sunjay commented 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.

dvberkel commented 5 years ago

@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

sunjay commented 5 years ago

@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:

Mentoring Instructions

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:

Phase 1: Export Drawings to SVG

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:

https://github.com/sunjay/turtle/blob/05a5fa118b154c5504bfc9fb770081c2846dd80d/src/renderer.rs#L39-L51

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:

https://github.com/sunjay/turtle/blob/05a5fa118b154c5504bfc9fb770081c2846dd80d/src/renderer.rs#L200-L246

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.

https://github.com/sunjay/turtle/blob/05a5fa118b154c5504bfc9fb770081c2846dd80d/src/renderer.rs#L180-L189

Phase 2: Expose Functionality Via Drawing Struct

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.)

https://github.com/sunjay/turtle/blob/05a5fa118b154c5504bfc9fb770081c2846dd80d/src/turtle_window.rs#L96-L99

(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.

https://github.com/sunjay/turtle/blob/05a5fa118b154c5504bfc9fb770081c2846dd80d/src/drawing.rs#L78-L80

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.

Questions? Please ask!

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:

dvberkel commented 5 years ago

@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.

dvberkel commented 5 years ago

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?

sunjay commented 5 years ago

@dvberkel Yes. You are correct. We handle that here in the renderer:

https://github.com/sunjay/turtle/blob/05a5fa118b154c5504bfc9fb770081c2846dd80d/src/renderer.rs#L153-L166