Skip to main content

weirdnet_render/
lib.rs

1#![doc = include_str!("../README.md")]
2mod canvas;
3mod chart;
4mod json;
5mod table;
6
7pub use chart::{chart_png, legend};
8pub use json::json_png;
9pub use table::table_png;
10
11/// A table: column names and rows of cell text (each row as long as `columns`).
12#[derive(Debug, Clone, PartialEq, Default)]
13pub struct Table {
14    pub columns: Vec<String>,
15    pub rows: Vec<Vec<String>>,
16}
17
18/// One line of a chart; `None` (or a non-finite number) is a gap.
19#[derive(Debug, Clone, PartialEq, Default)]
20pub struct Series {
21    pub name: String,
22    pub values: Vec<Option<f64>>,
23}
24
25/// A line chart: `x` positions (a missing one is its index) and the series over them.
26#[derive(Debug, Clone, PartialEq, Default)]
27pub struct Chart {
28    pub x: Vec<Option<f64>>,
29    pub series: Vec<Series>,
30}
31
32/// Something to draw, as read from JSON.
33#[derive(Debug, Clone, PartialEq)]
34pub enum Drawing {
35    Table(Table),
36    Chart { chart: Chart, width: usize, height: usize },
37}
38
39/// Default chart size when JSON does not give one.
40pub const CHART_WIDTH: usize = 900;
41pub const CHART_HEIGHT: usize = 320;
42
43/// What a caller is willing to have drawn. Anything over a limit is an [`Error::TooLarge`].
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct Limits {
46    pub max_columns: usize,
47    pub max_rows: usize,
48    /// Characters kept per cell; longer cells are cut and end in `~`.
49    pub max_cell_chars: usize,
50    pub max_series: usize,
51    pub max_points: usize,
52    pub max_width: usize,
53    pub max_height: usize,
54    pub max_pixels: usize,
55    pub max_json_bytes: usize,
56}
57
58impl Default for Limits {
59    fn default() -> Self {
60        Limits {
61            max_columns: 64,
62            max_rows: 1_000,
63            max_cell_chars: 80,
64            max_series: 64,
65            max_points: 100_000,
66            max_width: 8_192,
67            max_height: 16_384,
68            max_pixels: 16 * 1024 * 1024,
69            max_json_bytes: 8 * 1024 * 1024,
70        }
71    }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
75pub enum Error {
76    #[error("invalid input: {0}")]
77    Invalid(String),
78    #[error("too large: {0}")]
79    TooLarge(String),
80    #[error("nothing to draw: no finite values")]
81    Empty,
82    #[error("png: {0}")]
83    Encode(String),
84}
85
86pub type Result<T> = std::result::Result<T, Error>;
87
88impl Drawing {
89    /// Read a table or a chart from JSON (the forms are in the crate docs).
90    pub fn from_json(v: &serde_json::Value, limits: &Limits) -> Result<Drawing> {
91        json::drawing(v, limits)
92    }
93}
94
95/// Draw a table or a chart.
96pub fn render(d: &Drawing, limits: &Limits) -> Result<Vec<u8>> {
97    match d {
98        Drawing::Table(t) => table_png(t, limits),
99        Drawing::Chart { chart, width, height } => chart_png(chart, *width, *height, limits),
100    }
101}
102
103pub(crate) fn check_size(w: usize, h: usize, l: &Limits) -> Result<()> {
104    if w == 0 || h == 0 {
105        return Err(Error::Invalid(format!("image size {w}x{h}")));
106    }
107    if w > l.max_width || h > l.max_height || w.saturating_mul(h) > l.max_pixels {
108        return Err(Error::TooLarge(format!("image {w}x{h} (at most {}x{}, {} pixels)", l.max_width, l.max_height, l.max_pixels)));
109    }
110    Ok(())
111}