use super::sizing::Canvas;
use super::Format;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct QuartoFigure {
pub format: Format,
pub canvas: Canvas,
}
pub fn from_env() -> Option<QuartoFigure> {
from_vars(|key| std::env::var(key).ok())
}
pub fn from_vars(get: impl Fn(&str) -> Option<String>) -> Option<QuartoFigure> {
let format = match get("QUARTO_FIG_FORMAT")?.trim().to_lowercase().as_str() {
"png" => Format::Png,
"jpeg" | "jpg" => Format::Jpeg,
"svg" => Format::Svg,
"pdf" => Format::Pdf,
"retina" => Format::Png,
_ => return None,
};
let number = |key: &str| get(key).and_then(|v| v.trim().parse::<f64>().ok());
let width = number("QUARTO_FIG_WIDTH").filter(|v| *v > 0.0);
let height = number("QUARTO_FIG_HEIGHT").filter(|v| *v > 0.0);
let dpi = number("QUARTO_FIG_DPI").filter(|v| *v > 0.0);
let dpi = dpi.unwrap_or(super::sizing::CSS_DPI);
let canvas = match (width, height) {
(Some(w), Some(h)) => Canvas::from_inches(w, h, dpi),
(Some(w), None) => Canvas::from_inches(w, w / 1.618, dpi),
(None, Some(h)) => Canvas::from_inches(h * 1.618, h, dpi),
(None, None) => Canvas::default_at(dpi),
};
Some(QuartoFigure { format, canvas })
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn vars(pairs: &[(&str, &str)]) -> impl Fn(&str) -> Option<String> {
let map: HashMap<String, String> = pairs
.iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect();
move |key: &str| map.get(key).cloned()
}
#[test]
fn nothing_set_is_not_a_quarto_render() {
assert!(from_vars(vars(&[])).is_none());
}
#[test]
fn each_format_maps_to_its_writer() {
let cases = [
("png", Format::Png),
("jpeg", Format::Jpeg),
("jpg", Format::Jpeg),
("svg", Format::Svg),
("pdf", Format::Pdf),
("retina", Format::Png),
];
for (value, expected) in cases {
let figure = from_vars(vars(&[("QUARTO_FIG_FORMAT", value)])).unwrap();
assert_eq!(figure.format, expected, "{value}");
}
}
#[test]
fn a_format_we_cannot_produce_is_declined_rather_than_guessed() {
assert!(from_vars(vars(&[("QUARTO_FIG_FORMAT", "gif")])).is_none());
assert!(from_vars(vars(&[("QUARTO_FIG_FORMAT", "")])).is_none());
}
#[test]
fn inches_and_dpi_become_a_canvas() {
let figure = from_vars(vars(&[
("QUARTO_FIG_FORMAT", "png"),
("QUARTO_FIG_WIDTH", "6"),
("QUARTO_FIG_HEIGHT", "4"),
("QUARTO_FIG_DPI", "150"),
]))
.unwrap();
assert_eq!(figure.canvas.width, 900);
assert_eq!(figure.canvas.height, 600);
assert_eq!(figure.canvas.dpi, 150.0);
}
#[test]
fn a_format_without_a_size_still_renders() {
let figure = from_vars(vars(&[("QUARTO_FIG_FORMAT", "pdf")])).unwrap();
assert_eq!(figure.format, Format::Pdf);
assert_eq!(figure.canvas, Canvas::default());
}
#[test]
fn a_dpi_without_a_size_still_sets_the_resolution() {
let figure = from_vars(vars(&[
("QUARTO_FIG_FORMAT", "png"),
("QUARTO_FIG_DPI", "300"),
]))
.unwrap();
assert_eq!(figure.canvas.dpi, 300.0);
assert_eq!(figure.canvas.css_size(), Canvas::default().css_size());
}
#[test]
fn unusable_numbers_fall_back_rather_than_failing_the_render() {
let figure = from_vars(vars(&[
("QUARTO_FIG_FORMAT", "png"),
("QUARTO_FIG_WIDTH", "not-a-number"),
("QUARTO_FIG_DPI", "0"),
]))
.unwrap();
assert_eq!(figure.canvas, Canvas::default());
}
}