use super::scene::{Scene, Viewport};
const EVCXR_BEGIN: &str = "EVCXR_BEGIN_CONTENT";
const EVCXR_END: &str = "EVCXR_END_CONTENT";
pub struct SceneSvgDisplay<'a> {
scene: &'a Scene,
vp: &'a Viewport,
}
impl<'a> SceneSvgDisplay<'a> {
pub fn new(scene: &'a Scene, vp: &'a Viewport) -> Self {
SceneSvgDisplay { scene, vp }
}
pub fn evcxr_display(&self) {
let svg = self.scene.to_svg(self.vp);
println!("{EVCXR_BEGIN} image/svg+xml\n{svg}\n{EVCXR_END}");
}
}
impl Scene {
pub fn display<'a>(&'a self, vp: &'a Viewport) -> SceneSvgDisplay<'a> {
SceneSvgDisplay::new(self, vp)
}
}
#[cfg(feature = "raster")]
pub struct ScenePngDisplay<'a> {
scene: &'a Scene,
vp: &'a Viewport,
}
#[cfg(feature = "raster")]
impl<'a> ScenePngDisplay<'a> {
pub fn new(scene: &'a Scene, vp: &'a Viewport) -> Self {
ScenePngDisplay { scene, vp }
}
pub fn evcxr_display(&self) {
let png = self.scene.to_png(self.vp).expect("PNG render");
let b64 = encode_base64(&png);
println!("{EVCXR_BEGIN} image/png\n{b64}\n{EVCXR_END}");
}
}
#[cfg(feature = "raster")]
impl Scene {
pub fn display_png<'a>(&'a self, vp: &'a Viewport) -> ScenePngDisplay<'a> {
ScenePngDisplay::new(self, vp)
}
}
#[cfg(feature = "raster")]
fn encode_base64(bytes: &[u8]) -> String {
const ALPHA: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
for chunk in bytes.chunks(3) {
let b0 = chunk[0] as u32;
let b1 = chunk.get(1).copied().unwrap_or(0) as u32;
let b2 = chunk.get(2).copied().unwrap_or(0) as u32;
let v = (b0 << 16) | (b1 << 8) | b2;
out.push(ALPHA[((v >> 18) & 0x3F) as usize] as char);
out.push(ALPHA[((v >> 12) & 0x3F) as usize] as char);
if chunk.len() > 1 {
out.push(ALPHA[((v >> 6) & 0x3F) as usize] as char);
} else {
out.push('=');
}
if chunk.len() > 2 {
out.push(ALPHA[(v & 0x3F) as usize] as char);
} else {
out.push('=');
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vis::scene::{Color, Fill, Item, MarkerShape};
fn simple_scene() -> Scene {
let mut s = Scene::new().with_background(Color::WHITE);
s.push(Item::Marker {
center: (0.5, 0.5),
shape: MarkerShape::Circle,
size: 0.2,
fill: Some(Fill::solid(Color::RED)),
stroke: None,
});
s
}
#[test]
fn svg_display_wrapper_carries_scene_and_viewport() {
let scene = simple_scene();
let vp = Viewport::square_for(64, ((0.0, 0.0), (1.0, 1.0)), 4);
let display = scene.display(&vp);
let svg = display.scene.to_svg(display.vp);
assert!(svg.starts_with("<svg "));
assert!(svg.contains("<circle "));
}
#[cfg(feature = "raster")]
#[test]
fn png_display_wrapper_renders_png_under_the_hood() {
let scene = simple_scene();
let vp = Viewport::square_for(64, ((0.0, 0.0), (1.0, 1.0)), 4);
let display = scene.display_png(&vp);
let png = display.scene.to_png(display.vp).unwrap();
assert_eq!(&png[..8], &[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
}
#[cfg(feature = "raster")]
#[test]
fn base64_known_values() {
assert_eq!(encode_base64(b""), "");
assert_eq!(encode_base64(b"f"), "Zg==");
assert_eq!(encode_base64(b"fo"), "Zm8=");
assert_eq!(encode_base64(b"foo"), "Zm9v");
assert_eq!(encode_base64(b"foob"), "Zm9vYg==");
assert_eq!(encode_base64(b"fooba"), "Zm9vYmE=");
assert_eq!(encode_base64(b"foobar"), "Zm9vYmFy");
}
}