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#[derive(Debug, Clone, PartialEq, Default)]
13pub struct Table {
14 pub columns: Vec<String>,
15 pub rows: Vec<Vec<String>>,
16}
17
18#[derive(Debug, Clone, PartialEq, Default)]
20pub struct Series {
21 pub name: String,
22 pub values: Vec<Option<f64>>,
23}
24
25#[derive(Debug, Clone, PartialEq, Default)]
27pub struct Chart {
28 pub x: Vec<Option<f64>>,
29 pub series: Vec<Series>,
30}
31
32#[derive(Debug, Clone, PartialEq)]
34pub enum Drawing {
35 Table(Table),
36 Chart { chart: Chart, width: usize, height: usize },
37}
38
39pub const CHART_WIDTH: usize = 900;
41pub const CHART_HEIGHT: usize = 320;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct Limits {
46 pub max_columns: usize,
47 pub max_rows: usize,
48 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 pub fn from_json(v: &serde_json::Value, limits: &Limits) -> Result<Drawing> {
91 json::drawing(v, limits)
92 }
93}
94
95pub 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}