From f1415d2effdde8bd313268ff6596c5980bbef7de Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 11 Aug 2020 12:38:34 +0100 Subject: [PATCH 1/5] initial trail of docs_image feature Currently using: cargo test --features "test docs_image" --doc --package turtle -- drawing::Drawing::set_title for testing however it isn't currently failing when it should do. As there is an assert_eq that should fail... and no files are being written to the path. --- Cargo.toml | 6 +++++- src/drawing.rs | 17 ++++++++++++++++- turtle_docs_helper/Cargo.toml | 9 +++++++++ turtle_docs_helper/src/lib.rs | 22 ++++++++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 turtle_docs_helper/Cargo.toml create mode 100644 turtle_docs_helper/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index f62bc42a..4492d7a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,8 @@ once_cell = "1.3" cfg-if = "0.1" +turtle_docs_helper = { path = "turtle_docs_helper", optional = true } + [dependencies.futures-util] version = "0.3" default-features = false @@ -90,7 +92,8 @@ opt-level = 3 [features] # The reason we do this is because doctests don't get cfg(test) -# See: https://github.com/rust-lang/cargo/issues/4669 +# See: https://github.com/rust-lang/cargo/issues/4669 (original issue) +# See: https://github.com/rust-lang/rust/issues/45599 (updated issue) # # This allows us to write attributes like the following and have it work # in all tests. @@ -105,3 +108,4 @@ test = [] # # Users of the crate must explicitly opt-in to activate them. unstable = [] +docs_image = ["turtle_docs_helper"] \ No newline at end of file diff --git a/src/drawing.rs b/src/drawing.rs index d4124316..907b2e79 100644 --- a/src/drawing.rs +++ b/src/drawing.rs @@ -67,6 +67,13 @@ impl From for Drawing { } } +#[cfg(docs_images)] +impl turtle_docs_helpers::SaveSvg for Drawing { + fn save_svg(&self, path: &Path) { + self.drawing.save_svg(path).unwrap(); + } +} + impl Drawing { /// Creates a new drawing /// @@ -142,7 +149,7 @@ impl Drawing { /// /// # Example /// - /// ```rust,no_run + /// ```rust /// use turtle::Drawing; /// /// fn main() { @@ -150,6 +157,14 @@ impl Drawing { /// # #[allow(unused)] // Good to show turtle creation here even if unused /// let mut turtle = drawing.add_turtle(); /// drawing.set_title("My Fancy Title! - Yay!"); + /// println!("hello"); + /// # #[cfg(docs_image)] + /// # println!("test"); + /// # #[cfg(docs_image)] + /// # turtle_docs_helpers::foo(); + /// # #[cfg(docs_image)] + /// # turtle_docs_helpers::save_docs_image(&drawing, "cats"); + /// /// } /// ``` /// diff --git a/turtle_docs_helper/Cargo.toml b/turtle_docs_helper/Cargo.toml new file mode 100644 index 00000000..ce942362 --- /dev/null +++ b/turtle_docs_helper/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "turtle_docs_helper" +version = "0.1.0" +authors = ["Joe Ling"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/turtle_docs_helper/src/lib.rs b/turtle_docs_helper/src/lib.rs new file mode 100644 index 00000000..5a356846 --- /dev/null +++ b/turtle_docs_helper/src/lib.rs @@ -0,0 +1,22 @@ +use std::path::Path; + + + + /// Saves the image being drawn as an SVG and panics if an error occurs + /// + /// This is different from the `save_svg` method on `Drawing` and `AsyncDrawing` + /// because this is only meant to be used for automation and may need to access + /// internal APIs. + pub trait SaveSvg { + fn save_svg(&self, path: &Path); +} + + + +/// Saves the currently drawn image to `docs/assets/images/docs/{output_name}` +pub fn save_docs_image(drawing: &T, output_name: &str) { + println!("saving image"); + let svg_path = &Path::new("docs/assets/images/docs").join(output_name).with_extension("svg"); + drawing.save_svg(svg_path); + assert_eq!(1,2); +} From fd042d872ec91a23e29bcdf0254db42c53276b74 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 11 Aug 2020 16:54:50 +0100 Subject: [PATCH 2/5] added save_svg for docs image feature and small formatting --- src/async_drawing.rs | 21 ++++-- src/async_turtle.rs | 50 ++++++++++---- src/drawing.rs | 91 ++++++++++++++++++++------ src/ipc_protocol/protocol.rs | 119 ++++++++++++++++++++++------------ src/turtle.rs | 57 +++++++++++++--- turtle_docs_helper/src/lib.rs | 11 +--- 6 files changed, 252 insertions(+), 97 deletions(-) diff --git a/src/async_drawing.rs b/src/async_drawing.rs index 1877dfa5..7519b210 100644 --- a/src/async_drawing.rs +++ b/src/async_drawing.rs @@ -1,11 +1,11 @@ use std::fmt::Debug; use std::path::Path; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -use crate::ipc_protocol::ProtocolClient; use crate::async_turtle::AsyncTurtle; -use crate::{Drawing, Point, Color, Event, ExportError}; +use crate::ipc_protocol::ProtocolClient; +use crate::{Color, Drawing, Event, ExportError, Point}; /// Represents a size /// @@ -64,6 +64,16 @@ impl From for AsyncDrawing { } } +use crate::sync_runtime::block_on; +#[cfg(feature = "docs_image")] +use turtle_docs_helper; +#[cfg(feature = "docs_image")] +impl turtle_docs_helper::SaveSvg for AsyncDrawing { + fn save_svg(&self, path: &Path) -> Result<(), String> { + self.client.save_svg(path) + } +} + impl AsyncDrawing { pub async fn new() -> Self { // This needs to be called as close to the start of the program as possible. We call it @@ -71,9 +81,8 @@ impl AsyncDrawing { // of many programs that use the turtle crate. crate::start(); - let client = ProtocolClient::new().await - .expect("unable to create renderer client"); - Self {client} + let client = ProtocolClient::new().await.expect("unable to create renderer client"); + Self { client } } pub async fn add_turtle(&mut self) -> AsyncTurtle { diff --git a/src/async_turtle.rs b/src/async_turtle.rs index 27a7227c..ccfa31bb 100644 --- a/src/async_turtle.rs +++ b/src/async_turtle.rs @@ -2,10 +2,19 @@ use std::fmt::Debug; use tokio::time; -use crate::radians::{self, Radians}; use crate::ipc_protocol::{ProtocolClient, RotationDirection}; +use crate::radians::{self, Radians}; use crate::renderer_server::TurtleId; -use crate::{Turtle, Color, Point, Speed}; +use crate::{Color, Point, Speed, Turtle}; + +#[cfg(feature = "docs_image")] +use turtle_docs_helper; + +#[cfg(feature = "docs_image")] +use std::path::Path; + +#[cfg(feature = "docs_image")] +use crate::sync_runtime::block_on; /// Any distance value (positive or negative) pub type Distance = f64; @@ -45,6 +54,16 @@ pub struct AsyncTurtle { angle_unit: AngleUnit, } +#[cfg(feature = "docs_image")] +impl turtle_docs_helper::SaveSvg for AsyncTurtle { + fn save_svg(&self, path: &Path) -> Result<(), String> { + match self.client.save_svg(path) { + Ok(()) => Ok(()), + Err(e) => Err(e.to_string()), + } + } +} + impl From for AsyncTurtle { fn from(turtle: Turtle) -> Self { turtle.into_async() @@ -58,8 +77,7 @@ impl AsyncTurtle { // of many programs that use the turtle crate. crate::start(); - let client = ProtocolClient::new().await - .expect("unable to create renderer client"); + let client = ProtocolClient::new().await.expect("unable to create renderer client"); Self::with_client(client).await } @@ -68,7 +86,7 @@ impl AsyncTurtle { let id = client.create_turtle().await; let angle_unit = AngleUnit::Degrees; - Self {client, id, angle_unit} + Self { client, id, angle_unit } } pub async fn forward(&mut self, distance: Distance) { @@ -87,7 +105,9 @@ impl AsyncTurtle { pub async fn left(&mut self, angle: Angle) { let angle = self.angle_unit.to_radians(angle); - self.client.rotate_in_place(self.id, angle, RotationDirection::Counterclockwise).await + self.client + .rotate_in_place(self.id, angle, RotationDirection::Counterclockwise) + .await } pub async fn wait(&mut self, secs: f64) { @@ -122,13 +142,13 @@ impl AsyncTurtle { } pub async fn set_x(&mut self, x: f64) { - let Point {x: _, y} = self.position().await; - self.go_to(Point {x, y}).await + let Point { x: _, y } = self.position().await; + self.go_to(Point { x, y }).await } pub async fn set_y(&mut self, y: f64) { - let Point {x, y: _} = self.position().await; - self.go_to(Point {x, y}).await + let Point { x, y: _ } = self.position().await; + self.go_to(Point { x, y }).await } pub async fn home(&mut self) { @@ -155,7 +175,9 @@ impl AsyncTurtle { // Formula from: https://stackoverflow.com/a/24234924/551904 let angle = angle - radians::TWO_PI * ((angle + radians::PI) / radians::TWO_PI).floor(); - self.client.rotate_in_place(self.id, angle, RotationDirection::Counterclockwise).await + self.client + .rotate_in_place(self.id, angle, RotationDirection::Counterclockwise) + .await } pub fn is_using_degrees(&self) -> bool { @@ -291,13 +313,15 @@ impl AsyncTurtle { angle }; - self.client.rotate_in_place(self.id, angle, RotationDirection::Counterclockwise).await + self.client + .rotate_in_place(self.id, angle, RotationDirection::Counterclockwise) + .await } pub async fn wait_for_click(&mut self) { use crate::{ + event::{MouseButton::LeftButton, PressedState::Pressed}, Event::MouseButton, - event::{PressedState::Pressed, MouseButton::LeftButton}, }; loop { diff --git a/src/drawing.rs b/src/drawing.rs index 907b2e79..662ec68d 100644 --- a/src/drawing.rs +++ b/src/drawing.rs @@ -1,9 +1,12 @@ use std::fmt::Debug; use std::path::Path; -use crate::{Turtle, Color, Point, Size, ExportError}; use crate::async_drawing::AsyncDrawing; use crate::sync_runtime::block_on; +use crate::{Color, ExportError, Point, Size, Turtle}; + +#[cfg(feature = "docs_image")] +use turtle_docs_helper; /// Provides access to properties of the drawing that the turtle is creating /// @@ -63,14 +66,17 @@ impl From for Drawing { fn from(drawing: AsyncDrawing) -> Self { //TODO: There is no way to set `turtles` properly here, but that's okay since it is going // to be removed soon. - Self {drawing, turtles: 1} + Self { drawing, turtles: 1 } } } -#[cfg(docs_images)] -impl turtle_docs_helpers::SaveSvg for Drawing { - fn save_svg(&self, path: &Path) { - self.drawing.save_svg(path).unwrap(); +#[cfg(feature = "docs_image")] +impl turtle_docs_helper::SaveSvg for Drawing { + fn save_svg(&self, path: &Path) -> Result<(), String> { + match block_on(self.drawing.save_svg(path)) { + Ok(()) => Ok(()), + Err(e) => Err(e.to_string()), + } } } @@ -157,13 +163,8 @@ impl Drawing { /// # #[allow(unused)] // Good to show turtle creation here even if unused /// let mut turtle = drawing.add_turtle(); /// drawing.set_title("My Fancy Title! - Yay!"); - /// println!("hello"); - /// # #[cfg(docs_image)] - /// # println!("test"); - /// # #[cfg(docs_image)] - /// # turtle_docs_helpers::foo(); - /// # #[cfg(docs_image)] - /// # turtle_docs_helpers::save_docs_image(&drawing, "cats"); + /// # #[cfg(feature = "docs_image")] + /// # turtle_docs_helper::save_docs_image(&drawing, "changed_title"); /// /// } /// ``` @@ -196,7 +197,7 @@ impl Drawing { /// /// # Example /// - /// ```rust,no_run + /// ```rust /// use turtle::Drawing; /// /// fn main() { @@ -204,6 +205,8 @@ impl Drawing { /// # #[allow(unused)] // Good to show turtle creation here even if unused /// let mut turtle = drawing.add_turtle(); /// drawing.set_background_color("orange"); + /// # #[cfg(feature = "docs_image")] + /// # turtle_docs_helper::save_docs_image(&drawing, "orange_background"); /// } /// ``` /// @@ -238,7 +241,7 @@ impl Drawing { /// /// # Example /// - /// ```rust,no_run + /// ```rust /// use turtle::Drawing; /// /// fn main() { @@ -251,9 +254,31 @@ impl Drawing { /// // Rotate to the right (clockwise) by 1 degree /// turtle.right(1.0); /// } - /// + /// # #[cfg(feature = "docs_image")] + /// # turtle_docs_helper::save_docs_image(&drawing, "circle"); + /// # } + /// ``` + /// ```rust, no_run + /// # use turtle::Drawing; + /// # let mut drawing = Drawing::new(); + /// # let mut turtle = drawing.add_turtle(); /// turtle.wait_for_click(); + /// ``` + /// ```rust + /// # use turtle::Drawing; + /// # fn main() { + /// # let mut drawing = Drawing::new(); + /// # let mut turtle = drawing.add_turtle(); + /// + /// # for _ in 0..360 { + /// # // Move forward three steps + /// # turtle.forward(3.0); + /// # // Rotate to the right (clockwise) by 1 degree + /// # turtle.right(1.0); + /// # } /// drawing.set_center([50.0, 100.0]); + /// # #[cfg(feature = "docs_image")] + /// # turtle_docs_helper::save_docs_image(&drawing, "circle_offset_center"); /// } /// ``` /// @@ -321,7 +346,7 @@ impl Drawing { /// /// # Example /// - /// ```rust,no_run + /// ```rust /// use turtle::Drawing; /// /// fn main() { @@ -334,9 +359,31 @@ impl Drawing { /// // Rotate to the right (clockwise) by 1 degree /// turtle.right(1.0); /// } - /// + /// # #[cfg(feature = "docs_image")] + /// # turtle_docs_helper::save_docs_image(&drawing, "circle"); + /// # } + /// ``` + /// ```rust, no_run + /// # use turtle::Drawing; + /// # let mut drawing = Drawing::new(); + /// # let mut turtle = drawing.add_turtle(); /// turtle.wait_for_click(); + /// ``` + /// ```rust + /// # use turtle::Drawing; + /// # fn main() { + /// # let mut drawing = Drawing::new(); + /// # let mut turtle = drawing.add_turtle(); + /// + /// # for _ in 0..360 { + /// # // Move forward three steps + /// # turtle.forward(3.0); + /// # // Rotate to the right (clockwise) by 1 degree + /// # turtle.right(1.0); + /// # } /// drawing.set_size((300, 300)); + /// # #[cfg(feature = "docs_image")] + /// # turtle_docs_helper::save_docs_image(&drawing, "small_drawing"); /// } /// ``` /// @@ -585,7 +632,7 @@ impl Drawing { /// Saves the current drawings in SVG format at the location specified by `path`. /// - /// ```rust,no_run + /// ```rust /// use turtle::{Drawing, Turtle, Color, ExportError}; /// /// fn main() -> Result<(), ExportError> { @@ -634,7 +681,9 @@ mod tests { use super::*; #[test] - #[should_panic(expected = "Invalid color: Color { red: NaN, green: 0.0, blue: 0.0, alpha: 0.0 }. See the color module documentation for more information.")] + #[should_panic( + expected = "Invalid color: Color { red: NaN, green: 0.0, blue: 0.0, alpha: 0.0 }. See the color module documentation for more information." + )] fn rejects_invalid_background_color() { let mut drawing = Drawing::new(); drawing.set_background_color(Color { @@ -655,7 +704,7 @@ mod tests { #[test] fn ignores_center_nan_inf() { - let center = Point {x: 5.0, y: 10.0}; + let center = Point { x: 5.0, y: 10.0 }; let mut drawing = Drawing::new(); drawing.set_center(center); diff --git a/src/ipc_protocol/protocol.rs b/src/ipc_protocol/protocol.rs index 79f659ac..8e72a22e 100644 --- a/src/ipc_protocol/protocol.rs +++ b/src/ipc_protocol/protocol.rs @@ -1,22 +1,13 @@ use std::path::PathBuf; -use crate::renderer_client::RendererClient; -use crate::renderer_server::{TurtleId, ExportError}; use crate::radians::Radians; -use crate::{Distance, Point, Color, Speed, Event, Size}; +use crate::renderer_client::RendererClient; +use crate::renderer_server::{ExportError, TurtleId}; +use crate::{Color, Distance, Event, Point, Size, Speed}; use super::{ - ConnectionError, - ClientRequest, - ServerResponse, - ExportFormat, - DrawingProp, - DrawingPropValue, - TurtleProp, - TurtlePropValue, - PenProp, - PenPropValue, - RotationDirection, + ClientRequest, ConnectionError, DrawingProp, DrawingPropValue, ExportFormat, PenProp, PenPropValue, RotationDirection, ServerResponse, + TurtleProp, TurtlePropValue, }; /// A wrapper for `RendererClient` that encodes the the IPC protocol in a type-safe manner @@ -26,7 +17,26 @@ pub struct ProtocolClient { impl From for ProtocolClient { fn from(client: RendererClient) -> Self { - Self {client} + Self { client } + } +} + +#[cfg(feature = "docs_image")] +use turtle_docs_helper; + +#[cfg(feature = "docs_image")] +use std::path::Path; + +#[cfg(feature = "docs_image")] +use crate::sync_runtime::block_on; + +#[cfg(feature = "docs_image")] +impl turtle_docs_helper::SaveSvg for ProtocolClient { + fn save_svg(&self, path: &Path) -> Result<(), String> { + match block_on(self.export_svg(path.to_path_buf())) { + Ok(()) => Ok(()), + Err(e) => Err(e.to_string()), + } } } @@ -137,26 +147,37 @@ impl ProtocolClient { } pub fn drawing_set_background(&self, value: Color) { - debug_assert!(value.is_valid(), "bug: colors should be validated before sending to renderer server"); + debug_assert!( + value.is_valid(), + "bug: colors should be validated before sending to renderer server" + ); self.client.send(ClientRequest::SetDrawingProp(DrawingPropValue::Background(value))) } pub fn drawing_set_center(&self, value: Point) { - debug_assert!(value.is_finite(), "bug: center should be validated before sending to renderer server"); + debug_assert!( + value.is_finite(), + "bug: center should be validated before sending to renderer server" + ); self.client.send(ClientRequest::SetDrawingProp(DrawingPropValue::Center(value))) } pub fn drawing_set_size(&self, value: Size) { - debug_assert!(value.width > 0 && value.height > 0, "bug: size should be validated before sending to renderer server"); + debug_assert!( + value.width > 0 && value.height > 0, + "bug: size should be validated before sending to renderer server" + ); self.client.send(ClientRequest::SetDrawingProp(DrawingPropValue::Size(value))) } pub fn drawing_set_is_maximized(&self, value: bool) { - self.client.send(ClientRequest::SetDrawingProp(DrawingPropValue::IsMaximized(value))) + self.client + .send(ClientRequest::SetDrawingProp(DrawingPropValue::IsMaximized(value))) } pub fn drawing_set_is_fullscreen(&self, value: bool) { - self.client.send(ClientRequest::SetDrawingProp(DrawingPropValue::IsFullscreen(value))) + self.client + .send(ClientRequest::SetDrawingProp(DrawingPropValue::IsFullscreen(value))) } pub fn drawing_reset_center(&self) { @@ -175,7 +196,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Pen(PenPropValue::IsEnabled(value))) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -188,7 +209,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Pen(PenPropValue::Thickness(value))) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -201,7 +222,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Pen(PenPropValue::Color(value))) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -214,7 +235,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::FillColor(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -227,7 +248,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::IsFilling(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -240,7 +261,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Position(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -253,7 +274,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Heading(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -266,7 +287,7 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::Speed(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } @@ -279,28 +300,45 @@ impl ProtocolClient { ServerResponse::TurtleProp(recv_id, TurtlePropValue::IsVisible(value)) => { debug_assert_eq!(id, recv_id, "bug: received data for incorrect turtle"); value - }, + } _ => unreachable!("bug: expected to receive `TurtleProp` in response to `TurtleProp` request"), } } pub fn turtle_pen_set_is_enabled(&self, id: TurtleId, value: bool) { - self.client.send(ClientRequest::SetTurtleProp(id, TurtlePropValue::Pen(PenPropValue::IsEnabled(value)))) + self.client.send(ClientRequest::SetTurtleProp( + id, + TurtlePropValue::Pen(PenPropValue::IsEnabled(value)), + )) } pub fn turtle_pen_set_thickness(&self, id: TurtleId, value: f64) { - debug_assert!(value >= 0.0 && value.is_finite(), "bug: pen size should be validated before sending to renderer server"); - self.client.send(ClientRequest::SetTurtleProp(id, TurtlePropValue::Pen(PenPropValue::Thickness(value)))) + debug_assert!( + value >= 0.0 && value.is_finite(), + "bug: pen size should be validated before sending to renderer server" + ); + self.client.send(ClientRequest::SetTurtleProp( + id, + TurtlePropValue::Pen(PenPropValue::Thickness(value)), + )) } pub fn turtle_pen_set_color(&self, id: TurtleId, value: Color) { - debug_assert!(value.is_valid(), "bug: colors should be validated before sending to renderer server"); - self.client.send(ClientRequest::SetTurtleProp(id, TurtlePropValue::Pen(PenPropValue::Color(value)))) + debug_assert!( + value.is_valid(), + "bug: colors should be validated before sending to renderer server" + ); + self.client + .send(ClientRequest::SetTurtleProp(id, TurtlePropValue::Pen(PenPropValue::Color(value)))) } pub fn turtle_set_fill_color(&self, id: TurtleId, value: Color) { - debug_assert!(value.is_valid(), "bug: colors should be validated before sending to renderer server"); - self.client.send(ClientRequest::SetTurtleProp(id, TurtlePropValue::FillColor(value))) + debug_assert!( + value.is_valid(), + "bug: colors should be validated before sending to renderer server" + ); + self.client + .send(ClientRequest::SetTurtleProp(id, TurtlePropValue::FillColor(value))) } pub fn turtle_set_speed(&self, id: TurtleId, value: Speed) { @@ -308,7 +346,8 @@ impl ProtocolClient { } pub fn turtle_set_is_visible(&self, id: TurtleId, value: bool) { - self.client.send(ClientRequest::SetTurtleProp(id, TurtlePropValue::IsVisible(value))) + self.client + .send(ClientRequest::SetTurtleProp(id, TurtlePropValue::IsVisible(value))) } pub fn turtle_reset_heading(&self, id: TurtleId) { @@ -330,7 +369,7 @@ impl ProtocolClient { match response { ServerResponse::AnimationComplete(recv_id) => { debug_assert_eq!(id, recv_id, "bug: notified of complete animation for incorrect turtle"); - }, + } _ => unreachable!("bug: expected to receive `AnimationComplete` in response to `MoveForward` request"), } } @@ -346,7 +385,7 @@ impl ProtocolClient { match response { ServerResponse::AnimationComplete(recv_id) => { debug_assert_eq!(id, recv_id, "bug: notified of complete animation for incorrect turtle"); - }, + } _ => unreachable!("bug: expected to receive `AnimationComplete` in response to `MoveTo` request"), } } @@ -362,7 +401,7 @@ impl ProtocolClient { match response { ServerResponse::AnimationComplete(recv_id) => { debug_assert_eq!(id, recv_id, "bug: notified of complete animation for incorrect turtle"); - }, + } _ => unreachable!("bug: expected to receive `AnimationComplete` in response to `RotateInPlace` request"), } } diff --git a/src/turtle.rs b/src/turtle.rs index eeede870..fac608e3 100644 --- a/src/turtle.rs +++ b/src/turtle.rs @@ -1,8 +1,13 @@ use std::fmt::Debug; -use crate::{Color, Point, Speed, Distance, Angle}; use crate::async_turtle::AsyncTurtle; use crate::sync_runtime::block_on; +use crate::{Angle, Color, Distance, Point, Speed}; + +#[cfg(feature = "docs_image")] +use std::path::Path; +#[cfg(feature = "docs_image")] +use turtle_docs_helper; /// A turtle with a pen attached to its tail /// @@ -26,7 +31,17 @@ impl Default for Turtle { impl From for Turtle { fn from(turtle: AsyncTurtle) -> Self { - Self {turtle} + Self { turtle } + } +} + +#[cfg(feature = "docs_image")] +impl turtle_docs_helper::SaveSvg for Turtle { + fn save_svg(&self, path: &Path) -> Result<(), String> { + match self.turtle.save_svg(path) { + Ok(()) => Ok(()), + Err(e) => Err(e.to_string()), + } } } @@ -528,7 +543,7 @@ impl Turtle { /// /// # Example /// - /// ```rust,no_run + /// ```rust /// use turtle::Turtle; /// /// fn main() { @@ -550,6 +565,8 @@ impl Turtle { /// turtle.set_pen_color("#4CAF50"); // green /// turtle.set_pen_size(100.0); /// turtle.forward(200.0); + /// # #[cfg(feature = "docs_image")] + /// # turtle_docs_helper::save_docs_image(&turtle, "pen_thickness"); /// } /// ``` /// @@ -584,7 +601,7 @@ impl Turtle { /// /// # Example /// - /// ```rust,no_run + /// ```rust /// use turtle::Drawing; /// /// fn main() { @@ -600,6 +617,8 @@ impl Turtle { /// turtle.forward(25.0); /// turtle.right(10.0); /// } + /// # #[cfg(feature = "docs_image")] + /// # turtle_docs_helper::save_docs_image(&drawing, "colored_circle"); /// } /// ``` /// @@ -700,6 +719,8 @@ impl Turtle { /// } /// turtle.right(90.0); /// turtle.forward(120.0); + /// # #[cfg(feature = "docs_image")] + /// # turtle_docs_helper::save_docs_image(&turtle, "red_circle"); /// } /// ``` /// @@ -795,16 +816,32 @@ impl Turtle { /// /// # Example /// - /// ```rust,no_run + /// ```rust /// use turtle::Turtle; /// /// fn main() { /// let mut turtle = Turtle::new(); /// turtle.right(32.0); /// turtle.forward(150.0); - /// + /// # #[cfg(feature = "docs_image")] + /// # turtle_docs_helper::save_docs_image(&turtle, "clear_before_click"); + /// # } + /// ``` + /// ```rust, no_run + /// # use turtle::Turtle; + /// # let mut turtle = Turtle::new(); /// turtle.wait_for_click(); + /// ``` + /// ```rust + /// # use turtle::Turtle; + /// + /// # fn main() { + /// # let mut turtle = Turtle::new(); + /// # turtle.right(32.0); + /// # turtle.forward(150.0); /// turtle.clear(); + /// # #[cfg(feature = "docs_image")] + /// # turtle_docs_helper::save_docs_image(&turtle, "clear_after_click"); /// } /// ``` /// @@ -964,7 +1001,9 @@ mod tests { } #[test] - #[should_panic(expected = "Invalid color: Color { red: NaN, green: 0.0, blue: 0.0, alpha: 0.0 }. See the color module documentation for more information.")] + #[should_panic( + expected = "Invalid color: Color { red: NaN, green: 0.0, blue: 0.0, alpha: 0.0 }. See the color module documentation for more information." + )] fn rejects_invalid_pen_color() { let mut turtle = Turtle::new(); turtle.set_pen_color(Color { @@ -976,7 +1015,9 @@ mod tests { } #[test] - #[should_panic(expected = "Invalid color: Color { red: NaN, green: 0.0, blue: 0.0, alpha: 0.0 }. See the color module documentation for more information.")] + #[should_panic( + expected = "Invalid color: Color { red: NaN, green: 0.0, blue: 0.0, alpha: 0.0 }. See the color module documentation for more information." + )] fn rejects_invalid_fill_color() { let mut turtle = Turtle::new(); turtle.set_fill_color(Color { diff --git a/turtle_docs_helper/src/lib.rs b/turtle_docs_helper/src/lib.rs index 5a356846..62cbdd02 100644 --- a/turtle_docs_helper/src/lib.rs +++ b/turtle_docs_helper/src/lib.rs @@ -1,22 +1,15 @@ use std::path::Path; - - - /// Saves the image being drawn as an SVG and panics if an error occurs /// /// This is different from the `save_svg` method on `Drawing` and `AsyncDrawing` /// because this is only meant to be used for automation and may need to access /// internal APIs. pub trait SaveSvg { - fn save_svg(&self, path: &Path); + fn save_svg(&self, path: &Path) -> Result<(), String>; } - - /// Saves the currently drawn image to `docs/assets/images/docs/{output_name}` pub fn save_docs_image(drawing: &T, output_name: &str) { - println!("saving image"); let svg_path = &Path::new("docs/assets/images/docs").join(output_name).with_extension("svg"); - drawing.save_svg(svg_path); - assert_eq!(1,2); + assert!(drawing.save_svg(svg_path).is_ok()); } From 09609e1c6aa6dea990010fa56a28107bac314e7e Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 12 Aug 2020 13:42:12 +0100 Subject: [PATCH 3/5] changed to cfg target instead of feature for docs_image I have cfg set to docs_image (I know I haven't renamed it yet to docs_images...) so that it runs all the cfg(docs_image) code. However it fails to import 'turtle_docs_helper' as it is not an external crate. From reading up on this it should work... although it does suggest just to use features for doing this kind of thing in the docs. https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies --- Cargo.toml | 11 +- docs/assets/images/docs/squares.svg | 183 ---------------------------- src/async_drawing.rs | 4 +- src/async_turtle.rs | 8 +- src/drawing.rs | 16 +-- src/ipc_protocol/protocol.rs | 8 +- src/turtle.rs | 16 +-- 7 files changed, 32 insertions(+), 214 deletions(-) delete mode 100644 docs/assets/images/docs/squares.svg diff --git a/Cargo.toml b/Cargo.toml index 4492d7a7..845fd60b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,8 +46,6 @@ once_cell = "1.3" cfg-if = "0.1" -turtle_docs_helper = { path = "turtle_docs_helper", optional = true } - [dependencies.futures-util] version = "0.3" default-features = false @@ -74,6 +72,7 @@ features = [ # a library. [dev-dependencies] bitvec = "0.17" +# turtle_docs_helper = { path = "turtle_docs_helper", optional = true } # Since the debug performance of turtle isn't all that great, we recommend that # every user of turtle add the following to their Cargo.toml @@ -92,8 +91,7 @@ opt-level = 3 [features] # The reason we do this is because doctests don't get cfg(test) -# See: https://github.com/rust-lang/cargo/issues/4669 (original issue) -# See: https://github.com/rust-lang/rust/issues/45599 (updated issue) +# See: https://github.com/rust-lang/rust/issues/45599 # # This allows us to write attributes like the following and have it work # in all tests. @@ -108,4 +106,7 @@ test = [] # # Users of the crate must explicitly opt-in to activate them. unstable = [] -docs_image = ["turtle_docs_helper"] \ No newline at end of file +# docs_image = ["turtle_docs_helper"] + +[target.'cfg(docs_image)'.dev-dependencies] +turtle_docs_helper = {path = "turtle_docs_helper"} \ No newline at end of file diff --git a/docs/assets/images/docs/squares.svg b/docs/assets/images/docs/squares.svg deleted file mode 100644 index 870b7918..00000000 --- a/docs/assets/images/docs/squares.svg +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/async_drawing.rs b/src/async_drawing.rs index 7519b210..c0ba6caf 100644 --- a/src/async_drawing.rs +++ b/src/async_drawing.rs @@ -65,9 +65,9 @@ impl From for AsyncDrawing { } use crate::sync_runtime::block_on; -#[cfg(feature = "docs_image")] +#[cfg(docs_image)] use turtle_docs_helper; -#[cfg(feature = "docs_image")] +#[cfg(docs_image)] impl turtle_docs_helper::SaveSvg for AsyncDrawing { fn save_svg(&self, path: &Path) -> Result<(), String> { self.client.save_svg(path) diff --git a/src/async_turtle.rs b/src/async_turtle.rs index ccfa31bb..cd7a0084 100644 --- a/src/async_turtle.rs +++ b/src/async_turtle.rs @@ -7,13 +7,13 @@ use crate::radians::{self, Radians}; use crate::renderer_server::TurtleId; use crate::{Color, Point, Speed, Turtle}; -#[cfg(feature = "docs_image")] +#[cfg(docs_image)] use turtle_docs_helper; -#[cfg(feature = "docs_image")] +#[cfg(docs_image)] use std::path::Path; -#[cfg(feature = "docs_image")] +#[cfg(docs_image)] use crate::sync_runtime::block_on; /// Any distance value (positive or negative) @@ -54,7 +54,7 @@ pub struct AsyncTurtle { angle_unit: AngleUnit, } -#[cfg(feature = "docs_image")] +#[cfg(docs_image)] impl turtle_docs_helper::SaveSvg for AsyncTurtle { fn save_svg(&self, path: &Path) -> Result<(), String> { match self.client.save_svg(path) { diff --git a/src/drawing.rs b/src/drawing.rs index 662ec68d..a5c3da0f 100644 --- a/src/drawing.rs +++ b/src/drawing.rs @@ -5,7 +5,7 @@ use crate::async_drawing::AsyncDrawing; use crate::sync_runtime::block_on; use crate::{Color, ExportError, Point, Size, Turtle}; -#[cfg(feature = "docs_image")] +#[cfg(docs_image)] use turtle_docs_helper; /// Provides access to properties of the drawing that the turtle is creating @@ -70,7 +70,7 @@ impl From for Drawing { } } -#[cfg(feature = "docs_image")] +#[cfg(docs_image)] impl turtle_docs_helper::SaveSvg for Drawing { fn save_svg(&self, path: &Path) -> Result<(), String> { match block_on(self.drawing.save_svg(path)) { @@ -163,7 +163,7 @@ impl Drawing { /// # #[allow(unused)] // Good to show turtle creation here even if unused /// let mut turtle = drawing.add_turtle(); /// drawing.set_title("My Fancy Title! - Yay!"); - /// # #[cfg(feature = "docs_image")] + /// # #[cfg(docs_image)] /// # turtle_docs_helper::save_docs_image(&drawing, "changed_title"); /// /// } @@ -205,7 +205,7 @@ impl Drawing { /// # #[allow(unused)] // Good to show turtle creation here even if unused /// let mut turtle = drawing.add_turtle(); /// drawing.set_background_color("orange"); - /// # #[cfg(feature = "docs_image")] + /// # #[cfg(docs_image)] /// # turtle_docs_helper::save_docs_image(&drawing, "orange_background"); /// } /// ``` @@ -254,7 +254,7 @@ impl Drawing { /// // Rotate to the right (clockwise) by 1 degree /// turtle.right(1.0); /// } - /// # #[cfg(feature = "docs_image")] + /// # #[cfg(docs_image)] /// # turtle_docs_helper::save_docs_image(&drawing, "circle"); /// # } /// ``` @@ -277,7 +277,7 @@ impl Drawing { /// # turtle.right(1.0); /// # } /// drawing.set_center([50.0, 100.0]); - /// # #[cfg(feature = "docs_image")] + /// # #[cfg(docs_image)] /// # turtle_docs_helper::save_docs_image(&drawing, "circle_offset_center"); /// } /// ``` @@ -359,7 +359,7 @@ impl Drawing { /// // Rotate to the right (clockwise) by 1 degree /// turtle.right(1.0); /// } - /// # #[cfg(feature = "docs_image")] + /// # #[cfg(docs_image)] /// # turtle_docs_helper::save_docs_image(&drawing, "circle"); /// # } /// ``` @@ -382,7 +382,7 @@ impl Drawing { /// # turtle.right(1.0); /// # } /// drawing.set_size((300, 300)); - /// # #[cfg(feature = "docs_image")] + /// # #[cfg(docs_image)] /// # turtle_docs_helper::save_docs_image(&drawing, "small_drawing"); /// } /// ``` diff --git a/src/ipc_protocol/protocol.rs b/src/ipc_protocol/protocol.rs index 8e72a22e..caa79219 100644 --- a/src/ipc_protocol/protocol.rs +++ b/src/ipc_protocol/protocol.rs @@ -21,16 +21,16 @@ impl From for ProtocolClient { } } -#[cfg(feature = "docs_image")] +#[cfg(docs_image)] use turtle_docs_helper; -#[cfg(feature = "docs_image")] +#[cfg(docs_image)] use std::path::Path; -#[cfg(feature = "docs_image")] +#[cfg(docs_image)] use crate::sync_runtime::block_on; -#[cfg(feature = "docs_image")] +#[cfg(docs_image)] impl turtle_docs_helper::SaveSvg for ProtocolClient { fn save_svg(&self, path: &Path) -> Result<(), String> { match block_on(self.export_svg(path.to_path_buf())) { diff --git a/src/turtle.rs b/src/turtle.rs index fac608e3..6a67f939 100644 --- a/src/turtle.rs +++ b/src/turtle.rs @@ -4,9 +4,9 @@ use crate::async_turtle::AsyncTurtle; use crate::sync_runtime::block_on; use crate::{Angle, Color, Distance, Point, Speed}; -#[cfg(feature = "docs_image")] +#[cfg(docs_image)] use std::path::Path; -#[cfg(feature = "docs_image")] +#[cfg(docs_image)] use turtle_docs_helper; /// A turtle with a pen attached to its tail @@ -35,7 +35,7 @@ impl From for Turtle { } } -#[cfg(feature = "docs_image")] +#[cfg(docs_image)] impl turtle_docs_helper::SaveSvg for Turtle { fn save_svg(&self, path: &Path) -> Result<(), String> { match self.turtle.save_svg(path) { @@ -565,7 +565,7 @@ impl Turtle { /// turtle.set_pen_color("#4CAF50"); // green /// turtle.set_pen_size(100.0); /// turtle.forward(200.0); - /// # #[cfg(feature = "docs_image")] + /// # #[cfg(docs_image)] /// # turtle_docs_helper::save_docs_image(&turtle, "pen_thickness"); /// } /// ``` @@ -617,7 +617,7 @@ impl Turtle { /// turtle.forward(25.0); /// turtle.right(10.0); /// } - /// # #[cfg(feature = "docs_image")] + /// # #[cfg(docs_image)] /// # turtle_docs_helper::save_docs_image(&drawing, "colored_circle"); /// } /// ``` @@ -719,7 +719,7 @@ impl Turtle { /// } /// turtle.right(90.0); /// turtle.forward(120.0); - /// # #[cfg(feature = "docs_image")] + /// # #[cfg(docs_image)] /// # turtle_docs_helper::save_docs_image(&turtle, "red_circle"); /// } /// ``` @@ -823,7 +823,7 @@ impl Turtle { /// let mut turtle = Turtle::new(); /// turtle.right(32.0); /// turtle.forward(150.0); - /// # #[cfg(feature = "docs_image")] + /// # #[cfg(docs_image)] /// # turtle_docs_helper::save_docs_image(&turtle, "clear_before_click"); /// # } /// ``` @@ -840,7 +840,7 @@ impl Turtle { /// # turtle.right(32.0); /// # turtle.forward(150.0); /// turtle.clear(); - /// # #[cfg(feature = "docs_image")] + /// # #[cfg(docs_image)] /// # turtle_docs_helper::save_docs_image(&turtle, "clear_after_click"); /// } /// ``` From 1ea82af323df9adb27f61079f8db9ad6ee481776 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 29 Aug 2020 18:49:09 +0100 Subject: [PATCH 4/5] Added save_png helper for generating images for doc tests note: resvg is used over nsvg as it provides more accurate image dimensions --- CONTRIBUTING.md | 5 ++ Cargo.toml | 16 +++- docs/assets/images/docs/circle.png | Bin 7371 -> 14624 bytes .../images/docs/circle_offset_center.png | Bin 7443 -> 14629 bytes docs/assets/images/docs/clear_after_click.png | Bin 5504 -> 3157 bytes .../assets/images/docs/clear_before_click.png | Bin 5976 -> 4180 bytes docs/assets/images/docs/color_mixing.png | Bin 6889 -> 4275 bytes docs/assets/images/docs/colored_circle.png | Bin 8105 -> 14029 bytes docs/assets/images/docs/pen_thickness.png | Bin 5587 -> 6602 bytes docs/assets/images/docs/red_circle.png | Bin 7191 -> 15463 bytes docs/assets/images/docs/small_drawing.png | Bin 3807 -> 5211 bytes src/async_drawing.rs | 18 ++-- src/async_turtle.rs | 29 +++--- src/color.rs | 4 +- src/drawing.rs | 83 ++++-------------- src/ipc_protocol/protocol.rs | 36 ++++---- src/lib.rs | 6 ++ src/renderer_server/renderer/export.rs | 67 ++++++++------ src/turtle.rs | 63 +++++-------- turtle_docs_helper/Cargo.toml | 9 -- turtle_docs_helper/src/lib.rs | 15 ---- 21 files changed, 143 insertions(+), 208 deletions(-) delete mode 100644 turtle_docs_helper/Cargo.toml delete mode 100644 turtle_docs_helper/src/lib.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e4372857..ea9dc231 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -175,6 +175,11 @@ requires an extra flag which is documented below. ```bash RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --features "unstable" --open ``` +* To generate images for documentation, run `cargo test --features "test unstable docs_images_save_png" --doc` + * `clang` will need to be installed along with the following flags: + * `RUSTDOCFLAGS = "--cfg docs_images" ` + * `RUSTFLAGS = "--cfg docs_images" ` + * The same flags need to be used for `RUSTDOCFLAGS` and `RUSTFLAGS` is that we need to tell rust that we want to implement the helper trait `SavePng` for a bunch of things. Then when we are running the actual doc tests need to tell rust to create the images. But as they are run seperately the flag doesn't carry over so we need to have it in both places. (note: the flag `doctests_run_user_input` is used for tests that require userinput that we don't want to run but to show in the docs) ## Adding Examples [adding-examples]: #adding-examples diff --git a/Cargo.toml b/Cargo.toml index 845fd60b..802db6e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,10 @@ once_cell = "1.3" cfg-if = "0.1" +# docs_images_save_png feature +resvg = {version = "0.11.0", optional = true} +usvg = {version = "0.11.0", optional = true} + [dependencies.futures-util] version = "0.3" default-features = false @@ -72,7 +76,7 @@ features = [ # a library. [dev-dependencies] bitvec = "0.17" -# turtle_docs_helper = { path = "turtle_docs_helper", optional = true } +# doctest_run_user_input # Since the debug performance of turtle isn't all that great, we recommend that # every user of turtle add the following to their Cargo.toml @@ -106,7 +110,11 @@ test = [] # # Users of the crate must explicitly opt-in to activate them. unstable = [] -# docs_image = ["turtle_docs_helper"] -[target.'cfg(docs_image)'.dev-dependencies] -turtle_docs_helper = {path = "turtle_docs_helper"} \ No newline at end of file +# This feature is only designed for generating images to be used for documentation. Therefore you need to +# set both RUSTFLAGS and RUSTDOCFLAGS to "--cfg docs_images" in order to generate png files when running +# the doc tests. For more information please see: CONTRIBUTING.md +# note: clang is required for resvg +# note: the flag `doctests_run_user_input` is used for tests that +# require userinput that we don't want to run but to show in the docs +docs_images_save_png = ["resvg", "usvg"] diff --git a/docs/assets/images/docs/circle.png b/docs/assets/images/docs/circle.png index 64b67645c0c8c63abc3df19d5a8754d9a9aa5a4b..b1f37c4533a5b2ca10eb46b74c31e9894de7326b 100644 GIT binary patch literal 14624 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 literal 7371 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 diff --git a/docs/assets/images/docs/circle_offset_center.png b/docs/assets/images/docs/circle_offset_center.png index 1656d309f66c81f1986b58ac5a153c4eef9793f3..95e7dafdbd4b4d62d086220c422132ea0f86a187 100644 GIT binary patch literal 14629 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 literal 7443 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 OkgnDlT#<(L?f(L-3ZY^E diff --git a/docs/assets/images/docs/clear_after_click.png b/docs/assets/images/docs/clear_after_click.png index 2c67a5432441155e32e602a8fa6574fcc89aea27..0d594fc93070283ce86cc487c9673ed449db99b7 100644 GIT binary patch literal 3157 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYV2a>i1B%QlYbs!1;F0lkaSW-L^Y*$UBZC14 z!@{-o8Ky_~E?})c$IQUM!NI}6FhNj&fk8nTsMEpCfq|i+rva#2l97>t!H9{8fgy#3 zg@NJ3sKU|U8BG+USz)xK7%d-1YlYEDbF`^2+CUm@77p=NvzT#TWW$?R74Lx!eg;oh KKbLh*2~7YECybW> literal 5504 zcmeI0`#+oK8^<5jmQ}m9GrBsqy3W4CculnxMGsoFB3csa6r<>bL=ZtFRM*n^rq+3_ z4i&yTRCwZvsS-rcj?&Gc9Z%9CiB2md5;mTh>Y0I~O{oefC~ zB*vvtzDkJ&;t9m0SclY@l-O88>Q!ReoPMVV0GNE><8>-1qe3B)6pPdmz4IDZ-b;I* z1Fucp)bOo=F2m zgkU0f49Z-bf~N-V$sZY5*X=St8iYm3HL^cx;gsglVk=1ip>063j_IHoZB=%boz*RILZdFt85A?(~}6COYLgaIG% zW8A*&ud&bra`#sc&y3UtvOrMwW4;yWPF)IGt-eP95!b#}NT@BL*!su_yI@Y8`2K|w zEEM8Jq+0=?Kbh&zy6X^z<P%xI7Oo7VVK7iDEG++11!U(*E2$h1w4yJ+vim~Z*LR-3B4fjfbT*5{+HPQAd+3Vp z-8y-q>zZtCuKsMHX~e<{tQQc+c-J<+Pszqvc!V)0d2&yRMXfCCa+Pi^p54jnB!rK< z^=R@4j6`ZjrA||=G+)++W)4tclK4A6mp6*-6EfbVDn*GSE|NQ*azP>H^Oh82!4Q^> zV)`p2ot@w(gdSeDb^%3Od3(6XVZ)(oQ}l230_oI4*;{cA7~w__u)Qk@#L?4e4v7@} z?2G3pQgraT0=Jf`kXzSDveyd9zf};J8E?w6vZkXPS{Au-b92u?{v=g4SGCrQQl%D+ z1Y>7w9CS-r3`m>c-nQ&18`EX|0)mk%Y)^KHY^6;@p>sQuMcTuE1`Qt}PxUQjk*4pD zHfy|?P;1j`0S`YpMHS=D>P3CY8HryVW}$^EYB`(B<)$mgFH<#2=82s;M~M&zvTj(Zu&VckQBmy0tm+gAI^Yb04iFP(XV}NrjE+P8HPno)|8%0$)bqNa%?=2>* zPv!*SaBf_GYimx-q;Lepb4Q9K5dp_Ie7-W|lFLz4=;Y+Uwjpa>l{GwAjDf=Fkd^ka zOa@_Kk(Q-aP@09};_EI>=v#pEAJ3jW>$Q?R{CXHeTd$=L63eS2v*&xB8rFiSthZPB zs>eny$B&0Vf;a2qrW+xpJ|ff>-soEurlz@B7u-!nE7xDP(Ox~zk}3wfhs!DQ{_cI` zC6Nw_;VTN;Ls)|qC`>#EE}aO>S?HAx$$S}1B{r3zxZE}S1w8bs$bIR>Ya!ZmJq?3# zf44kYT*RM$VqW5&K76E!wz`B$j{3{r_WJefh&u4Sw=Ii{MCoHmB_@ZOtq)eh&q;YpRN-I2kTlj94v3tkAeGAJ|gykau+g^34>uT&-SHJJbC)rk8Ma$_*ai_ zl$>bZqgQ&vBjxNb@n4C4t&uVAFxC9Iw*pyaudGR7JH*tAzwKK!vXypJJqtG4_mC~S zx~|eflw`)U$<$$a1y23q88!rklR{nWHEmm7~nD zJV4MWMf1Y;?k8 zK!3LNejCME@*L4U+@NwlC=`!l$4sGG22OV=i8{qo6BZw-u6lfE!|r%%*R)h$1z8f4 z*ugi6oO~s?n<-J)*E)L4Gs<;Py!^g(M+ngc<3b_PlwvnFoCK0?8Ft0GaaR^rmvZhBwziO|iqm-PW$*opc(K&jr)d)3FRu*1}WUdj?h+|C{Qccl$K=w959e6X!+=Mkjswb4j&at4@gls2h8QZu%MaS6;9>LOE_i5QLDMvm=>p@sq8$u76K6nY{r5oXS0wuR@Imn0@DVtnsqR~#F|QETo7my% zX*IZQ8?X?A$U$#3%dS)ZGp-(Tn>G(e^1%MY;pqrpp&OF3PN>)Us58h~Kvu9coFk!~MP4 zY2b5pbb4;tNyGuGy9euy#NzzJ9=%-jUiP7`#Dhp2fz#2Hfe#LM`Pyt4vg_a73( diff --git a/docs/assets/images/docs/clear_before_click.png b/docs/assets/images/docs/clear_before_click.png index 06965336b6406df0ee7f3115c50d8ea22ab1cfd4..62d3831d46f0c375c2a30be794d19f0bf6115585 100644 GIT binary patch literal 4180 zcmeHI|5MX-7=I5)(1c-KI=|p3z33`M=nNEN(=;>?x=iKr+)xDSBA8}lqHdqla(eQ` zGZB<2U0PoGCDIaPQ>G--6i7V*<=ZAPLk0qi!PsEE9^~x&2l{EhJ)dW<=XqYw>v`VK z_IwZ<k<`b>pRvFhV#c^E#$znh!;7(NZl_xi_cHu{4`F@&?6p%axrQC43cjdO$7+{w zO#F9$i%KZbDMFmKhV;To`a!(P-3ydX#emaVqmIg*{<(y8%oMZ0-n*-%zU9M%9o=pD z(zH>htnH6Hlewy}qU}b0STNW3LLl>3I66yCG~|{vXMSe&h!5s@un4*7%Gd3-R)bJQ zWtVeI@7HwUi2`71VSQM0qLa`3g2c>t;OK%ap&F4&d4HH0`@DRZ=60P_^-hyA3o*K*D7m6E zv+vR7{*W{k>rO_&SksY==0j#(#LB!`n$@*Ck(3OJte#EE3 z^9jj5U@lDGRiql;0x1$Q@XN`J&ilW$r#IrWgg*Pbw2%GbiXx9dV9KHYDO;o+MDwJk zJdR_nt1w2+I>Ffd*iQ-EoX!2`3ytumpm3(fql0GBwxe_|e;gcaK-TH6pbkR~XkIWELVW$+TJS$+{XZw0BSAh9GKv zbc@o_DZ{(^6grN0BIu=sVx{ykAXUUT)?MY=r0$1N`=D<1> zhjq1-i<-96j$4ALE^UYlvz;#iFIcRChNc9w43%u9|gZM!Xz-o!=d6@gd8sSqcxHhVJ{s9O$H z@tFkkub+$_bnip$x6p$k4}%Fp4>EsHuf;kV=OF!Q+%x?;BwCY%M0FTeY97VYkmgd@ zi$JeJ$HvzPgg4I}BYh47l!M%|*8vLm1L#Krg##3X%gD8YtEPZJ(Ez+Di_a`d*zqqcqyYo~ literal 5976 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 JowbW_=3gVt?m_?n diff --git a/docs/assets/images/docs/color_mixing.png b/docs/assets/images/docs/color_mixing.png index e32333efbbb33bb641b548191a790b7216396ea9..e0ab5bf6ba1be5cd575e665f55e6570621de5a2a 100644 GIT binary patch literal 4275 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYV2a>i1B%QlYbs!15UBEWaSW-L^Y+$h?iH?# z?GHa{-10s1#%+qqO{FtRImwQ!LM)Olf>}E>7y3SOQ@SPM;n=;XMNBXy;K?G_ZasmJ zkPFKcyiYVO7EN+{)wn=;rHp>b8h{_1(Pp#bj-|XF=Z|9FKPM;_7Jm%M{!fT(;Tjy1- zvnhIZ^5gN~ewpl^?EC+xeZ1uzSEkcY$|=CGY_3)1{~v$U^X$d6EB+km{J4Ai{;WBc zh2ILL9^Y9$Pv-uQC!tC#5wjQ=6K?t2{ayQUvGxBtov;rFxX;Vi?|Yznyz1E1{2duJ zua>RuW8I*}$aG-K`}<$6e(bLHm+uz+^Yst+vD)YRe?*+$@#+%yab5HMzatp3g%lWk zcE2yaFSGsMug2t!P5;FeuJ3(a`uJG(It#G_vw&sSWxx85-wOYS@3Vfl^TX1|8#kY~ z%sXcB{gip%=XHDjL|mz>dvE{$dH)sn`_(shJbZN;X7_UDCl4S0&ez}d^YG(8j~9Rc zDu2f`@At#l<5g`8jA7~y3^UXmt|>V*$b!7iIbkb@K!Q6HOG748iY^n&jfp^C&SHq{ zW?(E+8C5zO0;3@?bVI;aR$+PG-*Z2Xe~vSdvwwZ0(D>NS{M|Kk%AYg|yI)vzr~J47@yGphCDY^o zoGN_OBrdnzCS0!W$(bGh7q<7C9-FJ uWMD8F6&wwa(Zn#C5k^afky8iY`PD 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 bonhv)c~4yLUZnmf4*)$8>|cBQ{PlkU=x=p0 diff --git a/docs/assets/images/docs/colored_circle.png b/docs/assets/images/docs/colored_circle.png index 5514d86086cb356ca840a179f911a6df23ad35bd..96907aa58f227d6835dcf6a67e92cbaf6b295226 100644 GIT binary patch literal 14029 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 Snu`GVI_>1<$o%zM+?B5Rd=~Rj!F7ToNwf649!q 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⋘z1|OpjDY&0u29}rXU?nzU;>ZE=0^Iy-<*vdHN`fxyN<_QxCo82 zW}fp=0ZX^jYcohpl0cT-w}+h_-i@BD{}}v2P@&oMfD(*8U)2HTDvun=jmH%P zB#J=;NkXVOIc~2yP1-OW6>Oij*S_1;*mH5{3|V2w)pVf z^RKkW1?cYvgS3{U$@ho^Q+!p6Kc+h_$Tq7c*Zb^4u!folFitCHmnXuYeetO$jenY6N0;U;gVibB zJfMl?(y{RQ^y*FP)1s@BE&~fv@R3sbr7>R0*BfWW=PL~>3i1~M6!rv?>_{twT(kJh zrhZ%etcrh@7G^HYi(Qp@^Sd)FpQ^^NOK}Df7E;aO@s0|at5fDMBBg-X;Yv@LF1lmp zX;ZXVO>#FPwy7!T2Rl34)(x9=F!1Fa#SWNDGh^aHGX|Rh85-B9gPM}0d--J-Lrwc% z+jZYCu3?fFAj=TU;f02*Z(c-Ua`HW=i+`E4`d~?I1I}Bu^ zKya|VTcjTaA1w6zMMie_AG4A@;7>pTruOIGl0HlN_xu#vrMSGM6TE#Ggh zMz}i$Z?*ZCDkOiLx*O=reNf<8K?M@;b~ zruf8XPWR^jrgr1TCGyI^eqc-93a>~vyjp!6)r;OsH?I43MM&?b|BwG&y2N?r-n^XM zgqlVAo}#^W4jN*1qgcSvolw^N6)C<%+;!?1aMOiK99}4xuy!4B-lc@Ox3WGsYSS38 zs{tZ9@XIS37?4&_oaTk^ ^)#ry;R{jcicyO%9b(+7P${$A-XV%~p~nT;TD=3Hp0 z1myRts~v@CoJ!F8N5hfVYC%6=KgzXd*RBtjIXHIhDlk01YuEidf(Lf(I(FoLAOFA6 d$;NiEOX>6?dYb_LR(ID`9pt6l-*5i;e*mG%bi)7u literal 7191 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 diff --git a/docs/assets/images/docs/small_drawing.png b/docs/assets/images/docs/small_drawing.png index b991abb64016b5e034ec2e9b8b5f8a4fa1bbc841..39d091a9f42967a2404b983ae68a9628a7d6ad94 100644 GIT binary patch literal 5211 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 literal 3807 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 for AsyncDrawing { } } -use crate::sync_runtime::block_on; -#[cfg(docs_image)] -use turtle_docs_helper; -#[cfg(docs_image)] -impl turtle_docs_helper::SaveSvg for AsyncDrawing { - fn save_svg(&self, path: &Path) -> Result<(), String> { - self.client.save_svg(path) - } -} - impl AsyncDrawing { pub async fn new() -> Self { // This needs to be called as close to the start of the program as possible. We call it @@ -179,3 +169,11 @@ impl AsyncDrawing { self.client.export_svg(path.as_ref().to_path_buf()).await } } + + +#[cfg(docs_images)] +impl crate::SavePng for AsyncDrawing { + fn save_png(&self, path: &str) -> Result<(), String> { + self.client.save_png(path) + } +} diff --git a/src/async_turtle.rs b/src/async_turtle.rs index cd7a0084..b4fb21b6 100644 --- a/src/async_turtle.rs +++ b/src/async_turtle.rs @@ -7,15 +7,6 @@ use crate::radians::{self, Radians}; use crate::renderer_server::TurtleId; use crate::{Color, Point, Speed, Turtle}; -#[cfg(docs_image)] -use turtle_docs_helper; - -#[cfg(docs_image)] -use std::path::Path; - -#[cfg(docs_image)] -use crate::sync_runtime::block_on; - /// Any distance value (positive or negative) pub type Distance = f64; @@ -54,16 +45,6 @@ pub struct AsyncTurtle { angle_unit: AngleUnit, } -#[cfg(docs_image)] -impl turtle_docs_helper::SaveSvg for AsyncTurtle { - fn save_svg(&self, path: &Path) -> Result<(), String> { - match self.client.save_svg(path) { - Ok(()) => Ok(()), - Err(e) => Err(e.to_string()), - } - } -} - impl From for AsyncTurtle { fn from(turtle: Turtle) -> Self { turtle.into_async() @@ -334,3 +315,13 @@ impl AsyncTurtle { } } } + +#[cfg(docs_images)] +impl crate::SavePng for AsyncTurtle { + fn save_png(&self, path: &str) -> Result<(), String> { + match self.client.save_png(path) { + Ok(()) => Ok(()), + Err(e) => Err(e.to_string()), + } + } +} \ No newline at end of file diff --git a/src/color.rs b/src/color.rs index c17a1528..60a61868 100644 --- a/src/color.rs +++ b/src/color.rs @@ -494,8 +494,9 @@ impl Color { /// /// Let's look at a more complete example to really show what happens when we're mixing colors together. /// - /// ```no_run + /// ``` /// use turtle::{Color, Drawing}; + /// # #[cfg(docs_images)] use crate::turtle::SavePng; /// /// fn main() { /// let mut drawing = Drawing::new(); @@ -525,6 +526,7 @@ impl Color { /// turtle.set_pen_color(red.mix("blue", 0.75)); /// turtle.forward(100.0); /// turtle.right(90.0); + /// # #[cfg(docs_images)] drawing.save_png("color_mixing").unwrap(); /// } /// ``` /// diff --git a/src/drawing.rs b/src/drawing.rs index a5c3da0f..3c3b1186 100644 --- a/src/drawing.rs +++ b/src/drawing.rs @@ -5,9 +5,6 @@ use crate::async_drawing::AsyncDrawing; use crate::sync_runtime::block_on; use crate::{Color, ExportError, Point, Size, Turtle}; -#[cfg(docs_image)] -use turtle_docs_helper; - /// Provides access to properties of the drawing that the turtle is creating /// /// # Accessing The Drawing @@ -70,16 +67,6 @@ impl From for Drawing { } } -#[cfg(docs_image)] -impl turtle_docs_helper::SaveSvg for Drawing { - fn save_svg(&self, path: &Path) -> Result<(), String> { - match block_on(self.drawing.save_svg(path)) { - Ok(()) => Ok(()), - Err(e) => Err(e.to_string()), - } - } -} - impl Drawing { /// Creates a new drawing /// @@ -163,8 +150,6 @@ impl Drawing { /// # #[allow(unused)] // Good to show turtle creation here even if unused /// let mut turtle = drawing.add_turtle(); /// drawing.set_title("My Fancy Title! - Yay!"); - /// # #[cfg(docs_image)] - /// # turtle_docs_helper::save_docs_image(&drawing, "changed_title"); /// /// } /// ``` @@ -205,8 +190,6 @@ impl Drawing { /// # #[allow(unused)] // Good to show turtle creation here even if unused /// let mut turtle = drawing.add_turtle(); /// drawing.set_background_color("orange"); - /// # #[cfg(docs_image)] - /// # turtle_docs_helper::save_docs_image(&drawing, "orange_background"); /// } /// ``` /// @@ -243,6 +226,7 @@ impl Drawing { /// /// ```rust /// use turtle::Drawing; + /// # #[cfg(docs_images)] use crate::turtle::SavePng; /// /// fn main() { /// let mut drawing = Drawing::new(); @@ -254,31 +238,11 @@ impl Drawing { /// // Rotate to the right (clockwise) by 1 degree /// turtle.right(1.0); /// } - /// # #[cfg(docs_image)] - /// # turtle_docs_helper::save_docs_image(&drawing, "circle"); - /// # } - /// ``` - /// ```rust, no_run - /// # use turtle::Drawing; - /// # let mut drawing = Drawing::new(); - /// # let mut turtle = drawing.add_turtle(); + /// # #[cfg(docs_images)] drawing.save_png("circle").unwrap(); + /// # #[cfg(doctests_run_user_input)] /// turtle.wait_for_click(); - /// ``` - /// ```rust - /// # use turtle::Drawing; - /// # fn main() { - /// # let mut drawing = Drawing::new(); - /// # let mut turtle = drawing.add_turtle(); - /// - /// # for _ in 0..360 { - /// # // Move forward three steps - /// # turtle.forward(3.0); - /// # // Rotate to the right (clockwise) by 1 degree - /// # turtle.right(1.0); - /// # } /// drawing.set_center([50.0, 100.0]); - /// # #[cfg(docs_image)] - /// # turtle_docs_helper::save_docs_image(&drawing, "circle_offset_center"); + /// # #[cfg(docs_images)] drawing.save_png("circle_offset_center").unwrap(); /// } /// ``` /// @@ -348,7 +312,8 @@ impl Drawing { /// /// ```rust /// use turtle::Drawing; - /// + /// + /// # #[cfg(docs_images)] use crate::turtle::SavePng; /// fn main() { /// let mut drawing = Drawing::new(); /// let mut turtle = drawing.add_turtle(); @@ -359,31 +324,11 @@ impl Drawing { /// // Rotate to the right (clockwise) by 1 degree /// turtle.right(1.0); /// } - /// # #[cfg(docs_image)] - /// # turtle_docs_helper::save_docs_image(&drawing, "circle"); - /// # } - /// ``` - /// ```rust, no_run - /// # use turtle::Drawing; - /// # let mut drawing = Drawing::new(); - /// # let mut turtle = drawing.add_turtle(); + /// # #[cfg(docs_images)] drawing.save_png("drawing").unwrap(); + /// # #[cfg(doctest_run_user_input)] /// turtle.wait_for_click(); - /// ``` - /// ```rust - /// # use turtle::Drawing; - /// # fn main() { - /// # let mut drawing = Drawing::new(); - /// # let mut turtle = drawing.add_turtle(); - /// - /// # for _ in 0..360 { - /// # // Move forward three steps - /// # turtle.forward(3.0); - /// # // Rotate to the right (clockwise) by 1 degree - /// # turtle.right(1.0); - /// # } /// drawing.set_size((300, 300)); - /// # #[cfg(docs_image)] - /// # turtle_docs_helper::save_docs_image(&drawing, "small_drawing"); + /// # #[cfg(docs_images)] drawing.save_png("small_drawing").unwrap(); /// } /// ``` /// @@ -676,6 +621,16 @@ impl Drawing { } } +#[cfg(docs_images)] +impl crate::SavePng for Drawing { + fn save_png(&self, path: &str) -> Result<(), String> { + match block_on(self.drawing.save_svg(Path::new(path))) { + Ok(()) => Ok(()), + Err(e) => Err(e.to_string()), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/ipc_protocol/protocol.rs b/src/ipc_protocol/protocol.rs index caa79219..21b718e1 100644 --- a/src/ipc_protocol/protocol.rs +++ b/src/ipc_protocol/protocol.rs @@ -21,25 +21,6 @@ impl From for ProtocolClient { } } -#[cfg(docs_image)] -use turtle_docs_helper; - -#[cfg(docs_image)] -use std::path::Path; - -#[cfg(docs_image)] -use crate::sync_runtime::block_on; - -#[cfg(docs_image)] -impl turtle_docs_helper::SaveSvg for ProtocolClient { - fn save_svg(&self, path: &Path) -> Result<(), String> { - match block_on(self.export_svg(path.to_path_buf())) { - Ok(()) => Ok(()), - Err(e) => Err(e.to_string()), - } - } -} - impl ProtocolClient { /// Spawns a new server process and creates a connection to it pub async fn new() -> Result { @@ -423,3 +404,20 @@ impl ProtocolClient { self.client.send(ClientRequest::ClearTurtle(id)) } } + + +#[cfg(docs_images)] +use std::path::Path; + +#[cfg(docs_images)] +use crate::sync_runtime::block_on; + +#[cfg(docs_images)] +impl crate::SavePng for ProtocolClient { + fn save_png(&self, path: &str) -> Result<(), String> { + match block_on(self.export_svg(Path::new(path).to_path_buf())) { + Ok(()) => Ok(()), + Err(e) => Err(e.to_string()), + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 79f5b63c..6a86bc1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,12 @@ broken unless you do so. See [Unstable features](#unstable-features) below for m #[cfg(all(test, not(feature = "test")))] compile_error!("Make sure you run tests with `cargo test --features \"test unstable\"`"); +/// Used to add helper method for doctests to generate pngs +#[cfg(docs_images)] +pub trait SavePng { + fn save_png(&self, path: &str) -> Result<(), String>; +} + mod radians; mod point; mod speed; diff --git a/src/renderer_server/renderer/export.rs b/src/renderer_server/renderer/export.rs index 4fca380a..8a9e32d3 100644 --- a/src/renderer_server/renderer/export.rs +++ b/src/renderer_server/renderer/export.rs @@ -1,22 +1,19 @@ use std::fmt::Write; use std::path::Path as FilePath; -use thiserror::Error; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use svg::node::element::{Line, Polygon, Rectangle}; +use thiserror::Error; use crate::Color; +use super::super::{coords::ScreenPoint, state::DrawingState}; use super::display_list::{DisplayList, DrawPrim, Line as DrawLine, Polygon as DrawPolygon}; -use super::super::{ - coords::ScreenPoint, - state::DrawingState, -}; - -/// Converts a color to its RGBA color string (suitable for SVG) -fn rgba(color: Color) -> String { - let Color {red, green, blue, alpha} = color; - format!("rgba({}, {}, {}, {})", red as u8, green as u8, blue as u8, alpha) + +/// Converts a color to its RGB color string (suitable for SVG) +fn rgb(color: Color) -> String { + let Color {red, green, blue, alpha: _ } = color; + format!("rgb({}, {}, {})", red as u8, green as u8, blue as u8) } /// Converts a value into a string with the unit "px" @@ -27,7 +24,7 @@ fn px(value: f64) -> String { /// Converts a list of pairs into a space-separated list of comma-separated pairs /// /// The list must be non-empty -fn pairs(mut items: impl Iterator) -> String { +fn pairs(mut items: impl Iterator) -> String { let first = items.next().expect("list must be non-empty"); let mut out = format!("{},{}", first.x, first.y); @@ -43,11 +40,7 @@ fn pairs(mut items: impl Iterator) -> String { #[error("{0}")] pub struct ExportError(String); -pub fn save_svg( - display_list: &DisplayList, - drawing: &DrawingState, - path: &FilePath, -) -> Result<(), ExportError> { +pub fn save_svg(display_list: &DisplayList, drawing: &DrawingState, path: &FilePath) -> Result<(), ExportError> { let mut document = svg::Document::new() .set("viewBox", (0, 0, drawing.width, drawing.height)); @@ -55,7 +48,8 @@ pub fn save_svg( let background = Rectangle::new() .set("width", "100%") .set("height", "100%") - .set("fill", rgba(drawing.background)); + .set("stroke-opacity", drawing.background.alpha.to_string()) + .set("fill", rgb(drawing.background)); document = document.add(background); let center = drawing.center; @@ -65,7 +59,12 @@ pub fn save_svg( }; for prim in display_list.iter() { match prim { - &DrawPrim::Line(DrawLine {start, end, thickness, color}) => { + &DrawPrim::Line(DrawLine { + start, + end, + thickness, + color, + }) => { let start = ScreenPoint::from_logical(start, 1.0, center, image_center); let end = ScreenPoint::from_logical(end, 1.0, center, image_center); @@ -76,29 +75,43 @@ pub fn save_svg( .set("y2", end.y) .set("stroke-linecap", "round") .set("stroke-linejoin", "round") - .set("stroke", rgba(color)) + .set("stroke", rgb(color)) + .set("stroke-opacity", color.alpha.to_string()) .set("stroke-width", px(thickness)); document = document.add(line); - }, + } - &DrawPrim::Polygon(DrawPolygon {ref points, fill_color}) => { + &DrawPrim::Polygon(DrawPolygon { ref points, fill_color }) => { // Skip obviously degenerate polygons if points.len() <= 2 { continue; } - let points = points.iter() - .map(|&p| ScreenPoint::from_logical(p, 1.0, center, image_center)); + let points = points.iter().map(|&p| ScreenPoint::from_logical(p, 1.0, center, image_center)); let polygon = Polygon::new() .set("points", pairs(points)) .set("fill-rule", "nonzero") - .set("fill", rgba(fill_color)); + .set("stroke-opacity", fill_color.alpha.to_string()) + .set("fill", rgb(fill_color)); document = document.add(polygon); - }, + } + } + } + + cfg_if::cfg_if! { + if #[cfg(all(feature = "docs_images_save_png", docs_images))] { + let save_path = FilePath::new(".\\docs\\assets\\images\\docs").join(path).with_extension("png"); + use resvg; + use usvg; + let svg = &usvg::Tree::from_str(&document.to_string(), &usvg::Options::default()).unwrap(); + let img = resvg::render(svg, usvg::FitTo::Original, None).unwrap(); + img.save_png(save_path).unwrap(); + } else { + svg::save("test.svg", &document).map_err(|err| ExportError(err.to_string()))?; } } - svg::save(path, &document).map_err(|err| ExportError(err.to_string())) + Ok(()) } diff --git a/src/turtle.rs b/src/turtle.rs index 6a67f939..747398cb 100644 --- a/src/turtle.rs +++ b/src/turtle.rs @@ -4,11 +4,6 @@ use crate::async_turtle::AsyncTurtle; use crate::sync_runtime::block_on; use crate::{Angle, Color, Distance, Point, Speed}; -#[cfg(docs_image)] -use std::path::Path; -#[cfg(docs_image)] -use turtle_docs_helper; - /// A turtle with a pen attached to its tail /// /// **The idea:** You control a turtle with a pen tied to its tail. As it moves @@ -35,16 +30,6 @@ impl From for Turtle { } } -#[cfg(docs_image)] -impl turtle_docs_helper::SaveSvg for Turtle { - fn save_svg(&self, path: &Path) -> Result<(), String> { - match self.turtle.save_svg(path) { - Ok(()) => Ok(()), - Err(e) => Err(e.to_string()), - } - } -} - impl Turtle { /// Create a new turtle. /// @@ -546,6 +531,7 @@ impl Turtle { /// ```rust /// use turtle::Turtle; /// + /// # #[cfg(docs_images)] use crate::turtle::SavePng; /// fn main() { /// let mut turtle = Turtle::new(); /// @@ -565,8 +551,7 @@ impl Turtle { /// turtle.set_pen_color("#4CAF50"); // green /// turtle.set_pen_size(100.0); /// turtle.forward(200.0); - /// # #[cfg(docs_image)] - /// # turtle_docs_helper::save_docs_image(&turtle, "pen_thickness"); + /// # #[cfg(docs_images)] turtle.save_png("pen_thickness").unwrap(); /// } /// ``` /// @@ -603,6 +588,7 @@ impl Turtle { /// /// ```rust /// use turtle::Drawing; + /// # #[cfg(docs_images)] use crate::turtle::SavePng; /// /// fn main() { /// let mut drawing = Drawing::new(); @@ -617,8 +603,7 @@ impl Turtle { /// turtle.forward(25.0); /// turtle.right(10.0); /// } - /// # #[cfg(docs_image)] - /// # turtle_docs_helper::save_docs_image(&drawing, "colored_circle"); + /// # #[cfg(docs_images)] drawing.save_png("colored_circle").unwrap(); /// } /// ``` /// @@ -694,9 +679,10 @@ impl Turtle { /// **Note:** The fill color must be set **before** `begin_fill()` is called in order to be /// used when filling the shape. /// - /// ```rust,no_run + /// ```rust /// use turtle::Turtle; /// + /// # #[cfg(docs_images)] use crate::turtle::SavePng; /// fn main() { /// let mut turtle = Turtle::new(); /// turtle.right(90.0); @@ -719,8 +705,7 @@ impl Turtle { /// } /// turtle.right(90.0); /// turtle.forward(120.0); - /// # #[cfg(docs_image)] - /// # turtle_docs_helper::save_docs_image(&turtle, "red_circle"); + /// # #[cfg(docs_images)] turtle.save_png("red_circle").unwrap(); /// } /// ``` /// @@ -818,30 +803,17 @@ impl Turtle { /// /// ```rust /// use turtle::Turtle; - /// + /// # #[cfg(docs_images)] use crate::turtle::SavePng; + /// /// fn main() { /// let mut turtle = Turtle::new(); /// turtle.right(32.0); /// turtle.forward(150.0); - /// # #[cfg(docs_image)] - /// # turtle_docs_helper::save_docs_image(&turtle, "clear_before_click"); - /// # } - /// ``` - /// ```rust, no_run - /// # use turtle::Turtle; - /// # let mut turtle = Turtle::new(); + /// # #[cfg(docs_images)] turtle.save_png("clear_before_click").unwrap(); + /// # #[cfg(doctests_run_user_input)] /// turtle.wait_for_click(); - /// ``` - /// ```rust - /// # use turtle::Turtle; - /// - /// # fn main() { - /// # let mut turtle = Turtle::new(); - /// # turtle.right(32.0); - /// # turtle.forward(150.0); /// turtle.clear(); - /// # #[cfg(docs_image)] - /// # turtle_docs_helper::save_docs_image(&turtle, "clear_after_click"); + /// # #[cfg(docs_images)] turtle.save_png("clear_after_click").unwrap(); /// } /// ``` /// @@ -921,6 +893,17 @@ impl Turtle { } } +#[cfg(docs_images)] +impl crate::SavePng for Turtle { + fn save_png(&self, path: &str) -> Result<(), String> { + match self.turtle.save_png(path) { + Ok(()) => Ok(()), + Err(e) => Err(e.to_string()), + } + } +} + + #[cfg(test)] mod tests { use super::*; diff --git a/turtle_docs_helper/Cargo.toml b/turtle_docs_helper/Cargo.toml deleted file mode 100644 index ce942362..00000000 --- a/turtle_docs_helper/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "turtle_docs_helper" -version = "0.1.0" -authors = ["Joe Ling"] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/turtle_docs_helper/src/lib.rs b/turtle_docs_helper/src/lib.rs deleted file mode 100644 index 62cbdd02..00000000 --- a/turtle_docs_helper/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -use std::path::Path; - /// Saves the image being drawn as an SVG and panics if an error occurs - /// - /// This is different from the `save_svg` method on `Drawing` and `AsyncDrawing` - /// because this is only meant to be used for automation and may need to access - /// internal APIs. - pub trait SaveSvg { - fn save_svg(&self, path: &Path) -> Result<(), String>; -} - -/// Saves the currently drawn image to `docs/assets/images/docs/{output_name}` -pub fn save_docs_image(drawing: &T, output_name: &str) { - let svg_path = &Path::new("docs/assets/images/docs").join(output_name).with_extension("svg"); - assert!(drawing.save_svg(svg_path).is_ok()); -} From 4005175d6dff21f491e2d02734b8e50ce5d47bb2 Mon Sep 17 00:00:00 2001 From: Joe Ling - uni laptop Date: Sat, 29 Aug 2020 22:44:41 +0100 Subject: [PATCH 5/5] fixed save_svg to no longer include test data --- src/renderer_server/renderer/export.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer_server/renderer/export.rs b/src/renderer_server/renderer/export.rs index 8a9e32d3..757feb50 100644 --- a/src/renderer_server/renderer/export.rs +++ b/src/renderer_server/renderer/export.rs @@ -109,7 +109,7 @@ pub fn save_svg(display_list: &DisplayList, drawing: &DrawingState, path: &FileP let img = resvg::render(svg, usvg::FitTo::Original, None).unwrap(); img.save_png(save_path).unwrap(); } else { - svg::save("test.svg", &document).map_err(|err| ExportError(err.to_string()))?; + svg::save(path, &document).map_err(|err| ExportError(err.to_string()))?; } }