From dd57bf69ab31971bd7205070614da5e786a83b30 Mon Sep 17 00:00:00 2001 From: Mick Chanthaseth Date: Sat, 16 Nov 2024 17:24:38 -0800 Subject: [PATCH 01/10] added get_b64 fn --- plotly/src/plot.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index f1d1b300..48385d52 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -417,6 +417,28 @@ impl Plot { .unwrap_or_else(|_| panic!("failed to export plot to {:?}", filename.as_ref())); } + // similar to write_image, but returns b64 string + #[cfg(feature = "kaleido")] + pub fn get_b64( + &self, + format: ImageFormat, + width: usize, + height: usize, + scale: f64, + ) -> String { + let kaleido = plotly_kaleido::Kaleido::new(); + let output = kaleido + .to_b64( + &serde_json::to_value(self).unwrap(), + &format.to_string(), + width, + height, + scale, + ) + .unwrap_or_else(|_| panic!("failed to generate b64")); + output + } + fn render(&self) -> String { let tmpl = PlotTemplate { plot: self, From 106931e0d646f379c7a0c9e960464d037e66c4be Mon Sep 17 00:00:00 2001 From: Mick Chanthaseth Date: Sat, 16 Nov 2024 17:28:44 -0800 Subject: [PATCH 02/10] added to_b64 --- plotly_kaleido/src/lib.rs | 54 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/plotly_kaleido/src/lib.rs b/plotly_kaleido/src/lib.rs index f6641c25..88f9d3c0 100644 --- a/plotly_kaleido/src/lib.rs +++ b/plotly_kaleido/src/lib.rs @@ -180,6 +180,60 @@ impl Kaleido { Ok(()) } + + // similar to save, but returns b64 string + pub fn to_b64( + &self, + plotly_data: &Value, + format: &str, + width: usize, + height: usize, + scale: f64, + ) -> Result> { + + let p = self.cmd_path.as_path(); + let p = p.to_str().unwrap(); + let p = String::from(p); + + let mut process = Command::new(p.as_str()) + .current_dir(self.cmd_path.parent().unwrap()) + .args([ + "plotly", + "--disable-gpu", + "--allow-file-access-from-files", + "--disable-breakpad", + "--disable-dev-shm-usage", + "--disable-software-rasterizer", + "--single-process", + ]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("failed to spawn Kaleido binary"); + + { + let plot_data = PlotData::new(plotly_data, format, width, height, scale).to_json(); + let mut process_stdin = process.stdin.take().unwrap(); + process_stdin + .write_all(plot_data.as_bytes()) + .expect("couldn't write to Kaleido stdin"); + process_stdin.flush()?; + } + + let output_lines = BufReader::new(process.stdout.take().unwrap()).lines(); + + let mut b64_str: String = "".into(); + for line in output_lines.map_while(Result::ok) { + // println!("{}", &line); + let res = KaleidoResult::from(line.as_str()); + if let Some(image_data) = res.result { + b64_str = image_data + } + } + + Ok(b64_str) + } } #[cfg(test)] From e78583d89010853720c59d13b5e159d96892b3a4 Mon Sep 17 00:00:00 2001 From: Mick Chanthaseth Date: Sat, 16 Nov 2024 17:34:07 -0800 Subject: [PATCH 03/10] renamed get_b64 to to_b64 --- plotly/src/plot.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index 48385d52..9549fbfb 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -419,7 +419,7 @@ impl Plot { // similar to write_image, but returns b64 string #[cfg(feature = "kaleido")] - pub fn get_b64( + pub fn to_b64( &self, format: ImageFormat, width: usize, From 3c0976fb3528e6fcfd601cc1be2bb0c4351d2dd5 Mon Sep 17 00:00:00 2001 From: Mick Chanthaseth Date: Sun, 17 Nov 2024 06:22:00 -0800 Subject: [PATCH 04/10] removed debug code --- plotly_kaleido/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/plotly_kaleido/src/lib.rs b/plotly_kaleido/src/lib.rs index 88f9d3c0..7d8d964d 100644 --- a/plotly_kaleido/src/lib.rs +++ b/plotly_kaleido/src/lib.rs @@ -225,7 +225,6 @@ impl Kaleido { let mut b64_str: String = "".into(); for line in output_lines.map_while(Result::ok) { - // println!("{}", &line); let res = KaleidoResult::from(line.as_str()); if let Some(image_data) = res.result { b64_str = image_data From 59fae2d4b2d6e19a1744031e7899468906bb3d70 Mon Sep 17 00:00:00 2001 From: Mick Chanthaseth Date: Thu, 21 Nov 2024 18:51:52 -0800 Subject: [PATCH 05/10] limit datatypes for b_64 --- plotly/src/plot.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index 9549fbfb..4b3662e4 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -425,7 +425,15 @@ impl Plot { width: usize, height: usize, scale: f64, - ) -> String { + ) -> Result { + match format{ + ImageFormat::JPEG => {}, + ImageFormat::PNG => {}, + ImageFormat::WEBP => {}, + _ => { + return Err(Error::new("format can only be JPEG, PNG, or WEBP")); + }, + } let kaleido = plotly_kaleido::Kaleido::new(); let output = kaleido .to_b64( From a930fcaaa8280373be98aadda3ddbda165396557 Mon Sep 17 00:00:00 2001 From: Mick Chanthaseth Date: Thu, 21 Nov 2024 19:08:36 -0800 Subject: [PATCH 06/10] added to_svg --- plotly/src/plot.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index 4b3662e4..8d31b14e 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -436,7 +436,35 @@ impl Plot { } let kaleido = plotly_kaleido::Kaleido::new(); let output = kaleido - .to_b64( + .get_image_data( + &serde_json::to_value(self).unwrap(), + &format.to_string(), + width, + height, + scale, + ) + .unwrap_or_else(|_| panic!("failed to generate b64")); + output + } + + // similar to write_image, but returns svg contents + #[cfg(feature = "kaleido")] + pub fn to_svg( + &self, + format: ImageFormat, + width: usize, + height: usize, + scale: f64, + ) -> Result { + match format{ + ImageFormat::SVG => {}, + _ => { + return Err(Error::new("format can only be SVG")); + }, + } + let kaleido = plotly_kaleido::Kaleido::new(); + let output = kaleido + .get_image_data( &serde_json::to_value(self).unwrap(), &format.to_string(), width, From 90956c1d83f84205966a3306bc66d5c395164110 Mon Sep 17 00:00:00 2001 From: Mick Chanthaseth Date: Thu, 21 Nov 2024 19:08:57 -0800 Subject: [PATCH 07/10] refactored --- plotly_kaleido/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plotly_kaleido/src/lib.rs b/plotly_kaleido/src/lib.rs index 7d8d964d..f7d3c3f9 100644 --- a/plotly_kaleido/src/lib.rs +++ b/plotly_kaleido/src/lib.rs @@ -182,7 +182,7 @@ impl Kaleido { } // similar to save, but returns b64 string - pub fn to_b64( + pub fn get_image_data( &self, plotly_data: &Value, format: &str, From 75cb2d5dace1202a58c30cc8390bf912fa70e3f2 Mon Sep 17 00:00:00 2001 From: Mick Chanthaseth Date: Thu, 21 Nov 2024 19:59:01 -0800 Subject: [PATCH 08/10] corrected some errors --- plotly/src/plot.rs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index 8d31b14e..86cbcc67 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -425,13 +425,13 @@ impl Plot { width: usize, height: usize, scale: f64, - ) -> Result { + ) -> Result { match format{ ImageFormat::JPEG => {}, ImageFormat::PNG => {}, ImageFormat::WEBP => {}, _ => { - return Err(Error::new("format can only be JPEG, PNG, or WEBP")); + return Err("Format can only be JPEG, PNG, WEBP are allowed".into()); }, } let kaleido = plotly_kaleido::Kaleido::new(); @@ -444,29 +444,22 @@ impl Plot { scale, ) .unwrap_or_else(|_| panic!("failed to generate b64")); - output + Ok(output) } // similar to write_image, but returns svg contents #[cfg(feature = "kaleido")] pub fn to_svg( &self, - format: ImageFormat, width: usize, height: usize, scale: f64, - ) -> Result { - match format{ - ImageFormat::SVG => {}, - _ => { - return Err(Error::new("format can only be SVG")); - }, - } + ) -> String { let kaleido = plotly_kaleido::Kaleido::new(); let output = kaleido .get_image_data( &serde_json::to_value(self).unwrap(), - &format.to_string(), + "svg", width, height, scale, From 4bc54ca2b6be9ec6637dd4016deb4beaf18de7ba Mon Sep 17 00:00:00 2001 From: Mick Chanthaseth Date: Fri, 22 Nov 2024 05:27:52 -0800 Subject: [PATCH 09/10] fixed clippy error --- plotly/src/plot.rs | 22 ++++++++-------------- plotly_kaleido/src/lib.rs | 1 - 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index 86cbcc67..2f2c2cb0 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -426,13 +426,13 @@ impl Plot { height: usize, scale: f64, ) -> Result { - match format{ - ImageFormat::JPEG => {}, - ImageFormat::PNG => {}, - ImageFormat::WEBP => {}, + match format { + ImageFormat::JPEG => {} + ImageFormat::PNG => {} + ImageFormat::WEBP => {} _ => { return Err("Format can only be JPEG, PNG, WEBP are allowed".into()); - }, + } } let kaleido = plotly_kaleido::Kaleido::new(); let output = kaleido @@ -449,14 +449,9 @@ impl Plot { // similar to write_image, but returns svg contents #[cfg(feature = "kaleido")] - pub fn to_svg( - &self, - width: usize, - height: usize, - scale: f64, - ) -> String { + pub fn to_svg(&self, width: usize, height: usize, scale: f64) -> String { let kaleido = plotly_kaleido::Kaleido::new(); - let output = kaleido + kaleido .get_image_data( &serde_json::to_value(self).unwrap(), "svg", @@ -464,8 +459,7 @@ impl Plot { height, scale, ) - .unwrap_or_else(|_| panic!("failed to generate b64")); - output + .unwrap_or_else(|_| panic!("failed to generate b64")) } fn render(&self) -> String { diff --git a/plotly_kaleido/src/lib.rs b/plotly_kaleido/src/lib.rs index f7d3c3f9..620a4e22 100644 --- a/plotly_kaleido/src/lib.rs +++ b/plotly_kaleido/src/lib.rs @@ -190,7 +190,6 @@ impl Kaleido { height: usize, scale: f64, ) -> Result> { - let p = self.cmd_path.as_path(); let p = p.to_str().unwrap(); let p = String::from(p); From c4513321d490d41b4c831fa6b9b2d5c8d9f9d3d4 Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Sat, 23 Nov 2024 11:22:27 +0100 Subject: [PATCH 10/10] refactor and add few unittests - kaleido seems to be flaky on windows and macos CI even for newly added tests - target new kaleido unittests for linux only Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- plotly/Cargo.toml | 1 + plotly/src/plot.rs | 87 +++++++++++++++++++++++++++++---------- plotly_kaleido/src/lib.rs | 84 ++++++++++++++++--------------------- 3 files changed, 102 insertions(+), 70 deletions(-) diff --git a/plotly/Cargo.toml b/plotly/Cargo.toml index d2408b3b..41867f34 100644 --- a/plotly/Cargo.toml +++ b/plotly/Cargo.toml @@ -50,3 +50,4 @@ itertools-num = "0.1.3" ndarray = "0.16.0" plotly_kaleido = { version = "0.10.0", path = "../plotly_kaleido" } rand_distr = "0.4" +base64 = "0.22" diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index 2f2c2cb0..1bbaacb1 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -417,49 +417,50 @@ impl Plot { .unwrap_or_else(|_| panic!("failed to export plot to {:?}", filename.as_ref())); } - // similar to write_image, but returns b64 string + /// Convert the `Plot` to a static image and return the image as a `base64` + /// String Supported formats are [ImageFormat::JPEG], [ImageFormat::PNG] + /// and [ImageFormat::WEBP] #[cfg(feature = "kaleido")] - pub fn to_b64( + pub fn to_base64( &self, format: ImageFormat, width: usize, height: usize, scale: f64, - ) -> Result { + ) -> String { match format { - ImageFormat::JPEG => {} - ImageFormat::PNG => {} - ImageFormat::WEBP => {} + ImageFormat::JPEG | ImageFormat::PNG | ImageFormat::WEBP => { + let kaleido = plotly_kaleido::Kaleido::new(); + kaleido + .image_to_string( + &serde_json::to_value(self).unwrap(), + &format.to_string(), + width, + height, + scale, + ) + .unwrap_or_else(|_| panic!("Kaleido failed to generate image")) + } _ => { - return Err("Format can only be JPEG, PNG, WEBP are allowed".into()); + eprintln!("Cannot generate base64 string for ImageFormat:{format}. Allowed formats are JPEG, PNG, WEBP"); + String::default() } } - let kaleido = plotly_kaleido::Kaleido::new(); - let output = kaleido - .get_image_data( - &serde_json::to_value(self).unwrap(), - &format.to_string(), - width, - height, - scale, - ) - .unwrap_or_else(|_| panic!("failed to generate b64")); - Ok(output) } - // similar to write_image, but returns svg contents + /// Convert the `Plot` to SVG and return it as a String. #[cfg(feature = "kaleido")] pub fn to_svg(&self, width: usize, height: usize, scale: f64) -> String { let kaleido = plotly_kaleido::Kaleido::new(); kaleido - .get_image_data( + .image_to_string( &serde_json::to_value(self).unwrap(), "svg", width, height, scale, ) - .unwrap_or_else(|_| panic!("failed to generate b64")) + .unwrap_or_else(|_| panic!("Kaleido failed to generate image")) } fn render(&self) -> String { @@ -584,6 +585,7 @@ impl PartialEq for Plot { mod tests { use std::path::PathBuf; + use base64::{engine::general_purpose, Engine as _}; use serde_json::{json, to_value}; use super::*; @@ -818,4 +820,47 @@ mod tests { assert!(std::fs::remove_file(&dst).is_ok()); assert!(!dst.exists()); } + + #[cfg(target_os = "linux")] + #[test] + #[cfg(feature = "kaleido")] + fn test_image_to_base64() { + let plot = create_test_plot(); + + let image_base64 = plot.to_base64(ImageFormat::PNG, 200, 150, 1.0); + + assert!(!image_base64.is_empty()); + + let result_decoded = general_purpose::STANDARD.decode(image_base64).unwrap(); + let expected = "iVBORw0KGgoAAAANSUhEUgAAAMgAAACWCAYAAACb3McZAAAH0klEQVR4Xu2bSWhVZxiGv2gC7SKJWrRWxaGoULsW7L7gXlAMKApiN7pxI46ggnNQcDbOoAZUcCG4CCiIQ4MSkWKFLNSCihTR2ESTCNVb/lMTEmvu8OYuTN/nQBHb895zv+f9H+6ZWpHL5XLBBgEIfJZABYKwMiAwMAEEYXVAIA8BBGF5QABBWAMQ0AjwC6JxI2VCAEFMimZMjQCCaNxImRBAEJOiGVMjgCAaN1ImBBDEpGjG1AggiMaNlAkBBDEpmjE1AgiicSNlQgBBTIpmTI0AgmjcSJkQQBCTohlTI4AgGjdSJgQQxKRoxtQIIIjGjZQJAQQxKZoxNQIIonEjZUIAQUyKZkyNAIJo3EiZEEAQk6IZUyOAIBo3UiYEEMSkaMbUCCCIxo2UCQEEMSmaMTUCCKJxI2VCAEFMimZMjQCCaNxImRBAEJOiGVMjgCAaN1ImBBDEpGjG1AggiMaNlAkBBDEpmjE1AgiicSNlQgBBTIpmTI0AgmjcSJkQQBCTohlTI4AgGjdSJgQQxKRoxtQIIIjGjZQJAQQxKZoxNQIIonEjZUIAQUyKZkyNAIJo3EiZEEAQk6IZUyOAIBo3UiYEEMSkaMbUCCCIxo2UCQEEMSmaMTUCCKJxI2VCAEFMimZMjQCCaNxImRBAEJOiGVMjgCAaN1ImBBDEpGjG1AggiMaNlAkBBDEpmjE1AgiicSNlQgBBTIpmTI0AgmjcSJkQQBCTohlTI4AgGjdSJgQQxKRoxtQIIIjGjZQJAQQxKZoxNQIIonEjZUIAQUyKZkyNAIJo3EiZEEAQk6IZUyOAIBo3UiYEEMSkaMbUCCCIxo2UCQEEMSmaMTUCCPKR26NHj+LUqVNx69atuHDhQtTW1vYSvX37dhw4cCC6u7tj4sSJsXr16hg5cqRGnNSQIoAgH+vavHlzzJ49O9auXRvnzp3rFeTNmzdRV1cXHz58yP7J5XIxbdq02Lt375Aqmi+rEUCQT7glSfoKcunSpdizZ0+MGDEik+PVq1cxfPjwuHz5clRVVWnUSQ0ZAghSQJA1a9ZEOsVqaGiIHTt2xLNnz6Krqys7HRs/fvyQKZovqhFAkAKCpFOuO3fuxOjRo+Pdu3fR3t6e/ZIcPHgwpk6dqlEnNWQIIEgBQTZu3Bg3b96MioqKmDBhQjx58iQT5OTJk/1+QX599DLqGpr/U3wuF1FRUb71MOv7b6Lmq8qYMa42Hjz/K5p+/7Pfh6f/9tuG2eU7oPknIUgBQbZu3RpXrlyJ7du3Z9ceK1euzAQ5c+ZMjBkzpjc9kCDVaTF/V5PtlxZ3z1bzdVXMGPfvv69vao2WP9r6fZMfx9XEzz98G0/buuJpW2c8eN4eHd1/99tnIPkaf5kVP/U5lvkaH9T4CFJAkBUrVsT9+/dj6dKlkS7YOzo6It3ZOnr0aEyePHlQ8Al/+QQQJCJb9EmAtL18+TJGjRqVnVIdOnQo6uvro7m5Ofv7sGHDslu9aduyZUvMnDnzy2+YbzgoAghSAN/bt29j/vz58f79++zUKv2ZZJo7d+6gwBMeGgQQpEBPTU1NsWvXruw5SNra2tqiuro6Tpw4kf3J9v8mgCBl7Hcwr6Tke9Ul31e8evVqnD59OrsFnW4apGum9DoMW3kIIEh5OGYX7osWLYp012v69OnZon38+HGsX7++qCMM9KpLvnB6aLl8+fLYt29fdsu5sbEx7t69Gzt37izqmOxUmACCFGZU1B7Xrl2LdDqWFnraOjs7Y968eXHx4sWSXkn59FWXfAdP10cvXrzovZv28OHDWLduXSYKW3kIIEh5OGbPRV6/fh3Lli3r/cQkyO7du0t6JaUUQT796ufPn4/W1tZMErbyEECQ8nCM48eP997h6vnIBQsWxIYNG0p6JUUV5N69e9mpVRKy7wPMMo1n+zEIUqbqz549m93h6vsLMmfOnOy1+FJealQEuXHjRhw+fDg2bdoUU6ZMKdNEfEwigCBlWgfXr1/PXoFPF+lpS6dbCxcuzK5BKisriz5KqYKkFyn3798f27Zti7FjxxZ9HHYsjgCCFMep4F7pgnnx4sXZRXq6i3Xs2LHsqXx6d6uUrRRB0jGXLFmSvSc2adKkUg7DvkUSQJAiQRWzW0tLS3ZKle5gpf/rcNWqVUU9TMz3qkvPA8rPHf/Th5g9+xw5cqSo4xYzk/s+COK+Apg/LwEEYYFAIA8BBGF5QABBWAMQ0AjwC6JxI2VCAEFMimZMjQCCaNxImRBAEJOiGVMjgCAaN1ImBBDEpGjG1AggiMaNlAkBBDEpmjE1AgiicSNlQgBBTIpmTI0AgmjcSJkQQBCTohlTI4AgGjdSJgQQxKRoxtQIIIjGjZQJAQQxKZoxNQIIonEjZUIAQUyKZkyNAIJo3EiZEEAQk6IZUyOAIBo3UiYEEMSkaMbUCCCIxo2UCQEEMSmaMTUCCKJxI2VCAEFMimZMjQCCaNxImRBAEJOiGVMjgCAaN1ImBBDEpGjG1AggiMaNlAkBBDEpmjE1AgiicSNlQgBBTIpmTI0AgmjcSJkQQBCTohlTI4AgGjdSJgQQxKRoxtQIIIjGjZQJAQQxKZoxNQIIonEjZUIAQUyKZkyNAIJo3EiZEEAQk6IZUyOAIBo3UiYEEMSkaMbUCCCIxo2UCQEEMSmaMTUCCKJxI2VC4B+Ci/5sJeSfvgAAAABJRU5ErkJggg=="; + let expected_decoded = general_purpose::STANDARD.decode(expected).unwrap(); + + // Comparing the result seems to end up being a flaky test. + // Limit the comparison to the first characters; + // As image contents seem to be slightly inconsistent across platforms + assert_eq!(expected_decoded[..2], result_decoded[..2]); + } + + #[test] + #[cfg(feature = "kaleido")] + fn test_image_to_base64_invalid_format() { + let plot = create_test_plot(); + let image_base64 = plot.to_base64(ImageFormat::EPS, 200, 150, 1.0); + assert!(image_base64.is_empty()); + } + + #[cfg(target_os = "linux")] + #[test] + #[cfg(feature = "kaleido")] + fn test_image_to_svg_string() { + let plot = create_test_plot(); + let image_svg = plot.to_svg(200, 150, 1.0); + + assert!(!image_svg.is_empty()); + + let expected = "012246810"; + // Limit the test to the first LEN characters + const LEN: usize = 100; + assert_eq!(expected[..LEN], image_svg[..LEN]); + } } diff --git a/plotly_kaleido/src/lib.rs b/plotly_kaleido/src/lib.rs index 620a4e22..6b2fee7b 100644 --- a/plotly_kaleido/src/lib.rs +++ b/plotly_kaleido/src/lib.rs @@ -122,6 +122,7 @@ impl Kaleido { Ok(p) } + /// Generate a static image from a Plotly graph and save it to a file pub fn save( &self, dst: &Path, @@ -134,55 +135,37 @@ impl Kaleido { let mut dst = PathBuf::from(dst); dst.set_extension(format); - let p = self.cmd_path.as_path(); - let p = p.to_str().unwrap(); - let p = String::from(p); - - let mut process = Command::new(p.as_str()) - .current_dir(self.cmd_path.parent().unwrap()) - .args([ - "plotly", - "--disable-gpu", - "--allow-file-access-from-files", - "--disable-breakpad", - "--disable-dev-shm-usage", - "--disable-software-rasterizer", - "--single-process", - ]) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .expect("failed to spawn Kaleido binary"); - - { - let plot_data = PlotData::new(plotly_data, format, width, height, scale).to_json(); - let mut process_stdin = process.stdin.take().unwrap(); - process_stdin - .write_all(plot_data.as_bytes()) - .expect("couldn't write to Kaleido stdin"); - process_stdin.flush()?; - } - - let output_lines = BufReader::new(process.stdout.take().unwrap()).lines(); - for line in output_lines.map_while(Result::ok) { - let res = KaleidoResult::from(line.as_str()); - if let Some(image_data) = res.result { - let data: Vec = match format { - "svg" | "eps" => image_data.as_bytes().to_vec(), - _ => general_purpose::STANDARD.decode(image_data).unwrap(), - }; - let mut file = File::create(dst.as_path())?; - file.write_all(&data)?; - file.flush()?; - } - } + let image_data = self.convert(plotly_data, format, width, height, scale)?; + let data: Vec = match format { + "svg" | "eps" => image_data.as_bytes().to_vec(), + _ => general_purpose::STANDARD.decode(image_data).unwrap(), + }; + let mut file = File::create(dst.as_path())?; + file.write_all(&data)?; + file.flush()?; Ok(()) } - // similar to save, but returns b64 string - pub fn get_image_data( + /// Generate a static image from a Plotly graph and return it as a String + /// The output may be base64 encoded or a plain text depending on the image + /// format provided as argument. SVG and EPS are returned in plain text + /// while JPEG, PNG, WEBP will be returned as a base64 encoded string. + pub fn image_to_string( + &self, + plotly_data: &Value, + format: &str, + width: usize, + height: usize, + scale: f64, + ) -> Result> { + let image_data = self.convert(plotly_data, format, width, height, scale)?; + Ok(image_data) + } + + /// Convert the Plotly graph to a static image using Kaleido and return the + /// result as a String + pub fn convert( &self, plotly_data: &Value, format: &str, @@ -221,16 +204,19 @@ impl Kaleido { } let output_lines = BufReader::new(process.stdout.take().unwrap()).lines(); - - let mut b64_str: String = "".into(); for line in output_lines.map_while(Result::ok) { let res = KaleidoResult::from(line.as_str()); if let Some(image_data) = res.result { - b64_str = image_data + // TODO: this should be refactored + // The assumption is that KaleidoResult contains a single image. + // We should end the loop on the first valid one. + // If that is not the case, prior implementation would have returned the last + // valid image + return Ok(image_data); } } - Ok(b64_str) + Ok(String::default()) } }