#[cfg(feature = "graphics")]
mod graphics_tests {
use std::fmt::Write;
use extendr_api::graphics::color::predefined::{antiquewhite, black, darkkhaki, deepskyblue};
use extendr_api::graphics::color::Color;
use extendr_api::graphics::{
Context, DevDesc, Device, DeviceDescriptor, DeviceDriver, FontFace, R_GE_gcontext, Raster,
TextMetric, Unit,
};
use extendr_api::prelude::*;
#[test]
fn graphics_test() {
let use_postscript = true;
let dir = std::env::temp_dir();
let path = dir.join("test.ps");
let path_str = path.to_string_lossy().to_string();
test! {
if use_postscript {
R!("postscript({{path_str}})")?;
}
let dev = Device::current()?;
let mut gc = Context::from_device(&dev, Unit::Inches);
gc.fill(antiquewhite());
dev.new_page(&gc);
dev.mode_on()?;
gc.color(darkkhaki());
gc.line_width(0.1);
dev.line((2.0, 2.0), (3.0, 3.0), &gc);
let scale = std::f64::consts::PI*2.0/10.0;
gc.fill(deepskyblue());
dev.polygon(
(0..10).map(|i| (
((i as f64) * scale).cos() + 4.0,
((i as f64) * scale).sin() + 2.0
)), &gc);
gc.fill(Color::rgb(0x20, 0x20, 0xc0));
dev.circle((1.0, 1.0), 0.5, &gc);
gc.color(black());
gc.point_size(36.0);
gc.font_face(FontFace::Plain);
gc.font_family("Helvetica");
let w = dev.text_width("Hello -", &gc);
dev.text((1.0, 3.0), "Hello -", (0.0, 0.0), 0.0, &gc);
dev.text((1.0 + w, 3.0), "- World", (0.0, 0.0), 0.0, &gc);
gc.line_width(0.01);
for i in 0..10 {
dev.symbol((1.0 + i as f64 * 0.3, 4.0), i, 0.25, &gc);
}
println!("{:?}", dev.char_metric('a', &gc));
println!("{:?}", dev.char_metric('g', &gc));
println!("{:?}", dev.char_metric('ê', &gc));
println!("{:?}", dev.text_metric("a", &gc));
println!("{:?}", dev.text_metric("g", &gc));
println!("{:?}", dev.text_metric("ê", &gc));
dev.mode_off()?;
if use_postscript {
R!("dev.off()")?;
let ps = std::fs::read_to_string(path).expect("PS file not written.");
if let Some(split) = ps.split_once("%%EndProlog") {
let epilogue = split.1;
println!("epilogue:\n{}", epilogue);
} else {
println!("ps:\n{}", ps);
assert!(("epilogue not found", false).1);
}
} else {
unreachable!("This shouldn't be reached?");
}
}
}
struct TestDevice<'a> {
last_mode: &'a mut i32,
value: &'a mut f64,
canvas: &'a mut String,
closed: &'a mut bool,
}
impl<'a> DeviceDriver for TestDevice<'a> {
fn activate(&mut self, _: DevDesc) {
*self.value = 100.0;
}
fn mode(&mut self, mode: i32, _: DevDesc) {
*self.last_mode = mode;
*self.value += 1.0;
}
fn close(&mut self, _: DevDesc) {
*self.closed = true;
}
fn new_page(&mut self, _: R_GE_gcontext, _: DevDesc) {
self.canvas.clear();
}
fn clip(&mut self, from: (f64, f64), to: (f64, f64), _dd: DevDesc) {
let (f_x, f_y) = from;
let (t_x, t_y) = to;
writeln!(
*self.canvas,
"clip from=({f_x:.1}, {f_y:.1}) to=({t_x:.1}, {t_y:.1})"
)
.unwrap();
}
fn circle(&mut self, center: (f64, f64), r: f64, _: R_GE_gcontext, _: DevDesc) {
let (x, y) = center;
writeln!(*self.canvas, "circle center=({x:.1}, {y:.1}) r={r:.1}").unwrap();
}
fn line(&mut self, from: (f64, f64), to: (f64, f64), _: R_GE_gcontext, _: DevDesc) {
let (f_x, f_y) = from;
let (t_x, t_y) = to;
writeln!(
*self.canvas,
"line from=({f_x:.1}, {f_y:.1}) to=({t_x:.1}, {t_y:.1})"
)
.unwrap();
}
fn rect(&mut self, from: (f64, f64), to: (f64, f64), _: R_GE_gcontext, _: DevDesc) {
let (f_x, f_y) = from;
let (t_x, t_y) = to;
writeln!(
*self.canvas,
"rect from=({f_x:.1}, {f_y:.1}) to=({t_x:.1}, {t_y:.1})"
)
.unwrap();
}
fn polyline<T: IntoIterator<Item = (f64, f64)>>(
&mut self,
coords: T,
_: R_GE_gcontext,
_: DevDesc,
) {
let coords = coords
.into_iter()
.map(|(x, y)| format!("({x:.1}, {y:.1})"))
.collect::<Vec<String>>()
.join(" ");
writeln!(*self.canvas, "polyline coords=[{coords}]").unwrap();
}
fn polygon<T: IntoIterator<Item = (f64, f64)>>(
&mut self,
coords: T,
_: R_GE_gcontext,
_: DevDesc,
) {
let coords = coords
.into_iter()
.map(|(x, y)| format!("({x:.1}, {y:.1})"))
.collect::<Vec<String>>()
.join(" ");
writeln!(*self.canvas, "polygon coords=[{coords}]").unwrap();
}
fn path<T: IntoIterator<Item = impl IntoIterator<Item = (f64, f64)>>>(
&mut self,
coords: T,
winding: bool,
_: R_GE_gcontext,
_: DevDesc,
) {
let coords = coords
.into_iter()
.map(|i| {
let xy = i
.into_iter()
.map(|(x, y)| format!("({x:.1}, {y:.1})"))
.collect::<Vec<String>>()
.join(" ");
format!("({xy})")
})
.collect::<Vec<String>>()
.join(", ");
writeln!(*self.canvas, "path coords=[{coords}] winding={winding}").unwrap();
}
fn text(
&mut self,
pos: (f64, f64),
str: &str,
rot: f64,
hadj: f64,
_: R_GE_gcontext,
_: DevDesc,
) {
let (x, y) = pos;
writeln!(
*self.canvas,
"text pos=({x:.1}, {y:.1}) str='{str}' rot={rot:.1} hadj={hadj:.1}"
)
.unwrap();
}
fn char_metric(&mut self, c: char, _: R_GE_gcontext, _: DevDesc) -> TextMetric {
TextMetric {
ascent: 0.0,
descent: 0.0,
width: c as i32 as _,
}
}
fn raster<T: AsRef<[u32]>>(
&mut self,
raster: Raster<T>,
pos: (f64, f64),
size: (f64, f64),
rot: f64,
interpolate: bool,
_: R_GE_gcontext,
_: DevDesc,
) {
let (x, y) = pos;
let (width, height) = size;
let Raster { pixels, width: w } = raster;
let pixels_str = pixels
.as_ref()
.iter()
.map(|&p| format!("{p}"))
.collect::<Vec<String>>()
.join("|");
writeln!(
*self.canvas,
"raster {pixels_str} w={w} pos=({x:.1}, {y:.1}) size=({width:.1}, {height:.1}) rot={rot:.1} interpolate={interpolate}"
)
.unwrap();
}
}
#[test]
fn device_driver_test() {
test! {
let mut value = 0.0;
let mut last_mode = 0;
let mut closed = false;
let mut canvas = String::new();
let device_driver = TestDevice {
last_mode: &mut last_mode,
value: &mut value,
canvas: &mut canvas,
closed: &mut closed,
};
let device_descriptor = DeviceDescriptor::new();
let device = device_driver.create_device::<TestDevice>(device_descriptor, "test device");
let gc = Context::from_device(&device, Unit::Device);
assert_eq!(value, 100.0);
assert!(!closed);
device.mode_on().unwrap();
assert_eq!(last_mode, 1);
assert_eq!(value, 101.0);
device.mode_off().unwrap();
assert_eq!(last_mode, 0);
assert_eq!(value, 102.0);
let c1 = 'c';
assert_eq!(device.char_metric(c1, &gc).width, c1 as i32 as f64);
let c2 = 'ú';
let c3 = '鬼';
assert_eq!(device.char_metric(c2, &gc).width, c2 as i32 as f64);
assert_eq!(device.char_metric(c3, &gc).width, c3 as i32 as f64);
let t1 = "ab";
assert_eq!(device.text_width(t1, &gc), ('a' as i32 + 'b' as i32) as f64);
device.clip((1.1, 2.2), (3.3, 4.4), &gc);
device.circle((1.1, 2.2), 3.3, &gc);
device.line((1.1, 2.2), (3.3, 4.4), &gc);
device.rect((1.1, 2.2), (3.3, 4.4), &gc);
device.polyline([(0.0, 0.0), (0.0, 2.0)], &gc);
device.polygon([(0.0, 0.0), (1.0, 2.0), (2.0, 0.0)], &gc);
device.path([[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], [(0.3, 0.0), (0.3, 0.3), (0.7, 0.3), (0.3, 0.7)]], true, &gc);
device.text((1.1, 2.2), "foo", (0.5, 0.0), 5.5, &gc);
let r = Raster {
pixels: &[1, 2, 3, 4, 5, 6],
width: 3,
};
device.raster(r, (1.1, 2.2), (3.3, 4.4), 5.5, false, &gc);
assert_eq!(canvas, "clip from=(1.1, 2.2) to=(3.3, 4.4)\n\
circle center=(1.1, 2.2) r=3.3\n\
line from=(1.1, 2.2) to=(3.3, 4.4)\n\
rect from=(1.1, 2.2) to=(3.3, 4.4)\n\
polyline coords=[(0.0, 0.0) (0.0, 2.0)]\n\
polygon coords=[(0.0, 0.0) (1.0, 2.0) (2.0, 0.0)]\n\
path coords=[((0.0, 0.0) (1.0, 0.0) (1.0, 1.0) (0.0, 1.0)), ((0.3, 0.0) (0.3, 0.3) (0.7, 0.3) (0.3, 0.7))] winding=true\n\
text pos=(1.1, 2.2) str='foo' rot=5.5 hadj=0.5\n\
raster 1|2|3|4|5|6 w=3 pos=(1.1, 2.2) size=(3.3, 4.4) rot=5.5 interpolate=false\n\
");
device.new_page(&gc);
assert_eq!(canvas, "");
R!("dev.off()")?;
assert!(closed);
}
}
}