Skip to main content

ravenrs_report/
model.rs

1//! The report definition: page, bands, groups, controls and styles. It is plain
2//! data (serde), saved as JSON (`*.rvreport.json`) and built in code with the
3//! fluent methods below. Units are points (1/72 inch).
4
5use std::collections::BTreeMap;
6
7use serde::{Deserialize, Serialize};
8
9/// A banded report definition.
10///
11/// ```
12/// use ravenrs_report::{Band, Control, Report};
13///
14/// let report = Report::new("Customers")
15///     .page_header(Band::new(24.0).control(Control::label("Name").at(0.0, 0.0, 200.0, 20.0)))
16///     .detail(Band::new(20.0).control(Control::field("[Name]").at(0.0, 0.0, 200.0, 20.0)));
17/// let json = report.to_json();
18/// assert_eq!(ravenrs_report::Report::from_json(&json).expect("round trip"), report);
19/// ```
20#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
21#[serde(rename_all = "camelCase", default)]
22pub struct Report {
23    /// The report's name, also the document title.
24    pub name: String,
25    /// Paper size and margins.
26    pub page: PageSettings,
27    /// Culture for numbers and dates, such as `pt-BR` or `en-US`.
28    pub culture: String,
29    /// Where the data comes from, saved with the layout. The engine renders
30    /// whatever data it is given; designers and the report server load it from here.
31    pub data_sources: Vec<DataSource>,
32    /// The data source that feeds the report (the first one when unset).
33    pub data_source: Option<String>,
34    /// Path to the rows in the data (`orders`, `report.lines`). Without it, an
35    /// array of data is the rows and an object is a single row.
36    pub data_member: Option<String>,
37    /// Rows for which this expression is false are left out.
38    pub filter: Option<String>,
39    /// Sorting of detail rows within their groups.
40    pub sort: Vec<SortField>,
41    /// Values the user supplies when running the report (`?Name` in expressions).
42    pub parameters: Vec<Parameter>,
43    /// Fields computed from other fields, usable like data fields.
44    pub calculated_fields: Vec<CalculatedField>,
45    /// Named styles that controls refer to.
46    pub styles: BTreeMap<String, Style>,
47    /// Printed once, at the top of the first page.
48    pub report_header: Option<Band>,
49    /// Printed at the top of every page.
50    pub page_header: Option<Band>,
51    /// Groups, outermost first.
52    pub groups: Vec<Group>,
53    /// Printed once per row.
54    pub detail: Band,
55    /// Nested reports printed after each row's detail (master-detail).
56    pub detail_reports: Vec<DetailReport>,
57    /// Printed once, after the last row.
58    pub report_footer: Option<Band>,
59    /// Printed at the bottom of every page.
60    pub page_footer: Option<Band>,
61}
62
63/// A named source of report data, stored with the layout.
64#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
65#[serde(rename_all = "camelCase", default)]
66pub struct DataSource {
67    /// Name, referred to by [`Report::data_source`].
68    pub name: String,
69    /// How the data is obtained.
70    #[serde(flatten)]
71    pub kind: DataSourceKind,
72}
73
74/// How a data source obtains its data.
75#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
76#[serde(tag = "type", rename_all = "camelCase")]
77#[non_exhaustive]
78pub enum DataSourceKind {
79    /// Data stored in the layout itself (samples, small lookup tables).
80    Json {
81        /// The data.
82        data: serde_json::Value,
83    },
84    /// JSON fetched over HTTP(S) when the report runs.
85    Url {
86        /// The address; `{?Name}` inserts a parameter value.
87        url: String,
88        /// Extra request headers. Secrets belong in the server's configuration, not here.
89        #[serde(default)]
90        headers: BTreeMap<String, String>,
91    },
92    /// A SQL query run by the report server against a named connection.
93    Sql {
94        /// A connection name from the server's configuration (never a connection string with a password).
95        connection: String,
96        /// The query; `@Name` binds a report parameter.
97        query: String,
98    },
99}
100
101impl Default for DataSourceKind {
102    fn default() -> Self {
103        Self::Json { data: serde_json::Value::Null }
104    }
105}
106
107/// Paper size and margins, in points.
108#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
109#[serde(rename_all = "camelCase", default)]
110pub struct PageSettings {
111    /// Paper width.
112    pub width: f32,
113    /// Paper height.
114    pub height: f32,
115    /// Top margin.
116    pub margin_top: f32,
117    /// Right margin.
118    pub margin_right: f32,
119    /// Bottom margin.
120    pub margin_bottom: f32,
121    /// Left margin.
122    pub margin_left: f32,
123}
124
125impl Default for PageSettings {
126    fn default() -> Self {
127        Self::a4()
128    }
129}
130
131impl PageSettings {
132    /// A4 portrait (210 × 297 mm) with 1.5 cm margins.
133    pub fn a4() -> Self {
134        Self {
135            width: 595.28,
136            height: 841.89,
137            margin_top: 42.52,
138            margin_right: 42.52,
139            margin_bottom: 42.52,
140            margin_left: 42.52,
141        }
142    }
143
144    /// US Letter portrait (8.5 × 11 in) with 0.5 in margins.
145    pub fn letter() -> Self {
146        Self {
147            width: 612.0,
148            height: 792.0,
149            margin_top: 36.0,
150            margin_right: 36.0,
151            margin_bottom: 36.0,
152            margin_left: 36.0,
153        }
154    }
155
156    /// The same paper turned sideways.
157    pub fn landscape(self) -> Self {
158        Self { width: self.height.max(self.width), height: self.width.min(self.height), ..self }
159    }
160
161    /// Sets all four margins.
162    pub fn margins(self, m: f32) -> Self {
163        Self { margin_top: m, margin_right: m, margin_bottom: m, margin_left: m, ..self }
164    }
165
166    /// The width between the side margins, where bands are laid out.
167    pub fn content_width(&self) -> f32 {
168        (self.width - self.margin_left - self.margin_right).max(0.0)
169    }
170}
171
172/// A sort key.
173#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
174#[serde(rename_all = "camelCase", default)]
175pub struct SortField {
176    /// The expression to sort by, such as `[Name]`.
177    pub expression: String,
178    /// Largest first.
179    pub descending: bool,
180}
181
182/// What kind of value a parameter takes.
183#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
184#[serde(rename_all = "camelCase")]
185#[non_exhaustive]
186pub enum ParameterKind {
187    /// Text.
188    #[default]
189    Text,
190    /// A number.
191    Number,
192    /// A date.
193    Date,
194    /// Yes or no.
195    Boolean,
196}
197
198/// A value supplied when the report runs.
199#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
200#[serde(rename_all = "camelCase", default)]
201pub struct Parameter {
202    /// Name, used as `?Name` in expressions.
203    pub name: String,
204    /// What the user is asked for.
205    pub label: Option<String>,
206    /// The kind of value.
207    pub kind: ParameterKind,
208    /// The value when none is supplied.
209    pub default: Option<serde_json::Value>,
210}
211
212/// A field computed from an expression.
213#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
214#[serde(rename_all = "camelCase", default)]
215pub struct CalculatedField {
216    /// Name, used as `[Name]` in expressions.
217    pub name: String,
218    /// The expression, such as `[Quantity] * [UnitPrice]`.
219    pub expression: String,
220}
221
222/// A grouping level with its header and footer.
223#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
224#[serde(rename_all = "camelCase", default)]
225pub struct Group {
226    /// Rows with the same value of this expression form a group.
227    pub expression: String,
228    /// Groups in descending order.
229    pub descending: bool,
230    /// Printed before each group.
231    pub header: Option<Band>,
232    /// Printed after each group; aggregates in it cover the group.
233    pub footer: Option<Band>,
234    /// Prints the header again at the top of each page the group continues on.
235    pub repeat_header: bool,
236    /// Starts each group (after the first) on a new page.
237    pub page_break_before: bool,
238}
239
240/// Master-detail: a nested list printed after each row of the report.
241#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
242#[serde(rename_all = "camelCase", default)]
243pub struct DetailReport {
244    /// The array field of the master row that holds the nested rows.
245    pub data_member: String,
246    /// Nested rows for which this is false are left out.
247    pub filter: Option<String>,
248    /// Sorting of the nested rows.
249    pub sort: Vec<SortField>,
250    /// Printed before the nested rows (only if there are any).
251    pub header: Option<Band>,
252    /// Printed once per nested row; `[^.Field]` reads the master row.
253    pub detail: Band,
254    /// Printed after the nested rows; aggregates in it cover them.
255    pub footer: Option<Band>,
256}
257
258/// A horizontal strip of the page holding controls.
259#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
260#[serde(rename_all = "camelCase", default)]
261pub struct Band {
262    /// Height in points before any control grows.
263    pub height: f32,
264    /// The controls, positioned relative to the band's top-left corner.
265    pub controls: Vec<Control>,
266    /// The band is left out when this is false.
267    pub visible: Option<String>,
268    /// Starts a new page before the band.
269    pub page_break_before: bool,
270    /// Starts a new page after the band.
271    pub page_break_after: bool,
272    /// Keeps the band on the same page as the band that follows it (headers).
273    pub keep_with_next: bool,
274    /// Style name applied to odd rows of a detail band (1st, 3rd...).
275    pub odd_style: Option<String>,
276    /// Style name applied to even rows of a detail band.
277    pub even_style: Option<String>,
278}
279
280impl Default for Band {
281    fn default() -> Self {
282        Self::new(0.0)
283    }
284}
285
286/// A printable element of a band.
287#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
288#[serde(rename_all = "camelCase", default)]
289pub struct Control {
290    /// What the control is and its content.
291    #[serde(flatten)]
292    pub kind: ControlKind,
293    /// An optional name, shown in the designer and in error messages.
294    pub name: Option<String>,
295    /// Left edge, from the band's left.
296    pub x: f32,
297    /// Top edge, from the band's top.
298    pub y: f32,
299    /// Width.
300    pub width: f32,
301    /// Height before growing.
302    pub height: f32,
303    /// A named style from [`Report::styles`].
304    pub style_name: Option<String>,
305    /// Style settings on top of the named style.
306    pub style: Style,
307    /// The control is left out when this is false.
308    pub visible: Option<String>,
309    /// Conditional formatting, applied in order when the condition holds.
310    pub rules: Vec<FormattingRule>,
311}
312
313impl Default for Control {
314    fn default() -> Self {
315        Self {
316            kind: ControlKind::Label(Label::default()),
317            name: None,
318            x: 0.0,
319            y: 0.0,
320            width: 100.0,
321            height: 18.0,
322            style_name: None,
323            style: Style::default(),
324            visible: None,
325            rules: Vec::new(),
326        }
327    }
328}
329
330/// The kinds of controls.
331#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
332#[serde(tag = "type", rename_all = "camelCase")]
333#[non_exhaustive]
334pub enum ControlKind {
335    /// Text: fixed, or from an expression.
336    Label(Label),
337    /// A check mark from a boolean expression, with optional text.
338    CheckBox(CheckBox),
339    /// A straight line across the control's box.
340    Line(Line),
341    /// A rectangle or ellipse.
342    Shape(Shape),
343    /// A grid of cells, each a label.
344    Table(Table),
345    /// A container whose controls move together.
346    Panel(Panel),
347    /// A PNG or JPEG image.
348    Picture(Picture),
349    /// Text that opens an address when clicked in a PDF, a spreadsheet or a web page.
350    Link(LinkLabel),
351    /// An invisible entry in the document's outline, at this control's position.
352    Bookmark(Bookmark),
353    /// A barcode or QR Code.
354    Barcode(Barcode),
355    /// A chart of the rows in scope, or of a nested list.
356    Chart(Chart),
357}
358
359/// A chart.
360#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
361#[serde(rename_all = "camelCase", default)]
362pub struct Chart {
363    /// The kind of chart.
364    pub kind: crate::chart::ChartKind,
365    /// A title above it.
366    pub title: String,
367    /// What each point is about, such as `[Region]`. Rows with the same value add
368    /// up into one point; without it, every row is a point.
369    pub argument: Option<String>,
370    /// The numbers to show.
371    pub series: Vec<ChartSeries>,
372    /// Orders the points by their argument, as numbers when they all are.
373    pub sorted: bool,
374    /// Leaves the legend out.
375    pub hide_legend: bool,
376    /// How numbers are shown, such as `N0` or `C2`.
377    pub format: Option<String>,
378    /// An array field of the current row to chart, instead of the rows in scope.
379    pub data_member: Option<String>,
380}
381
382/// A chart's series.
383#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
384#[serde(rename_all = "camelCase", default)]
385pub struct ChartSeries {
386    /// Its name in the legend.
387    pub name: String,
388    /// The number for each row, such as `[Quantity] * [Price]`.
389    pub value: String,
390    /// Its color; one from the palette when absent.
391    pub color: Option<String>,
392}
393
394impl ChartSeries {
395    /// A series named `name`, with `value` for each row.
396    pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
397        Self { name: name.into(), value: value.into(), color: None }
398    }
399}
400
401/// A barcode.
402#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
403#[serde(rename_all = "camelCase", default)]
404pub struct Barcode {
405    /// The format.
406    pub symbology: crate::barcode::Symbology,
407    /// Fixed content.
408    pub text: String,
409    /// An expression giving the content, such as `[Sku]`; it wins over `text`.
410    pub expression: Option<String>,
411    /// Prints the content under a linear code.
412    pub show_text: bool,
413}
414
415impl Default for Barcode {
416    fn default() -> Self {
417        Self {
418            symbology: crate::barcode::Symbology::default(),
419            text: String::new(),
420            expression: None,
421            show_text: true,
422        }
423    }
424}
425
426/// A picture.
427#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
428#[serde(rename_all = "camelCase", default)]
429pub struct Picture {
430    /// The image: a `data:image/...;base64,` address, or just base64.
431    pub source: String,
432    /// An expression giving the image in the same forms, such as `[Photo]`; it wins over `source`.
433    pub expression: Option<String>,
434    /// How the image fits the control's box.
435    pub mode: PictureMode,
436}
437
438/// How a picture fits its box.
439#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
440#[serde(rename_all = "camelCase")]
441#[non_exhaustive]
442pub enum PictureMode {
443    /// As large as fits, keeping its proportions, centered.
444    #[default]
445    Zoom,
446    /// Like zoom, but a small image is not enlarged.
447    Squeeze,
448    /// Fills the box, ignoring its proportions.
449    Stretch,
450    /// At its natural size (one pixel to a point) from the top left, cut at the box.
451    Normal,
452}
453
454/// A label that is also a link.
455#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
456#[serde(rename_all = "camelCase", default)]
457pub struct LinkLabel {
458    /// What it shows.
459    #[serde(flatten)]
460    pub label: Label,
461    /// The address (`https:`, `http:` or `mailto:`).
462    pub url: String,
463    /// An expression giving the address, such as `'mailto:' + [Email]`; it wins over `url`.
464    pub url_expression: Option<String>,
465}
466
467/// An entry in the document's outline.
468#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
469#[serde(rename_all = "camelCase", default)]
470pub struct Bookmark {
471    /// Fixed text.
472    pub text: String,
473    /// An expression giving the text, such as `[Region]`; it wins over `text`.
474    pub expression: Option<String>,
475    /// How deep it is: 0 at the top, 1 under the 0 before it, and so on.
476    pub level: u32,
477}
478
479/// Text content of a label or table cell.
480#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
481#[serde(rename_all = "camelCase", default)]
482pub struct Label {
483    /// Fixed text, used when there is no expression.
484    pub text: String,
485    /// An expression whose value is shown, such as `[Total]`.
486    pub expression: Option<String>,
487    /// A format for the value: `{0:C2}`, or just `C2`.
488    pub format: Option<String>,
489    /// Grows downwards to fit wrapped text (true by default).
490    pub can_grow: Option<bool>,
491}
492
493impl Label {
494    /// Whether the label grows to fit its text.
495    pub fn grows(&self) -> bool {
496        self.can_grow.unwrap_or(true)
497    }
498}
499
500/// A check box.
501#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
502#[serde(rename_all = "camelCase", default)]
503pub struct CheckBox {
504    /// A boolean expression for the checked state.
505    pub expression: String,
506    /// Text after the box.
507    pub text: String,
508}
509
510/// Which way a line runs.
511#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
512#[serde(rename_all = "camelCase")]
513#[non_exhaustive]
514pub enum LineDirection {
515    /// Across, centered vertically.
516    #[default]
517    Horizontal,
518    /// Down, centered horizontally.
519    Vertical,
520    /// From top-left to bottom-right.
521    Slant,
522}
523
524/// A line.
525#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
526#[serde(rename_all = "camelCase", default)]
527pub struct Line {
528    /// Its direction.
529    pub direction: LineDirection,
530    /// Thickness in points (1 by default).
531    pub thickness: Option<f32>,
532}
533
534/// Shape kinds.
535#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
536#[serde(rename_all = "camelCase")]
537#[non_exhaustive]
538pub enum ShapeKind {
539    /// A rectangle, optionally rounded.
540    #[default]
541    Rectangle,
542    /// An ellipse filling the box.
543    Ellipse,
544}
545
546/// A rectangle or ellipse, filled with the style's background and outlined with its border.
547#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
548#[serde(rename_all = "camelCase", default)]
549pub struct Shape {
550    /// The shape.
551    pub shape: ShapeKind,
552    /// Corner radius of a rectangle.
553    pub radius: f32,
554}
555
556/// A table of label cells.
557#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
558#[serde(rename_all = "camelCase", default)]
559pub struct Table {
560    /// The rows, top to bottom.
561    pub rows: Vec<TableRow>,
562}
563
564/// A table row; its height grows to its tallest cell.
565#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
566#[serde(rename_all = "camelCase", default)]
567pub struct TableRow {
568    /// Height before growing.
569    pub height: f32,
570    /// The cells, left to right.
571    pub cells: Vec<TableCell>,
572}
573
574/// A table cell.
575#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
576#[serde(rename_all = "camelCase", default)]
577pub struct TableCell {
578    /// Width in points.
579    pub width: f32,
580    /// The text.
581    #[serde(flatten)]
582    pub label: Label,
583    /// A named style.
584    pub style_name: Option<String>,
585    /// Style settings on top of the named style.
586    pub style: Style,
587}
588
589/// A container of controls.
590#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
591#[serde(rename_all = "camelCase", default)]
592pub struct Panel {
593    /// The controls, positioned relative to the panel.
594    pub controls: Vec<Control>,
595}
596
597/// Formatting applied when a condition holds.
598#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
599#[serde(rename_all = "camelCase", default)]
600pub struct FormattingRule {
601    /// A boolean expression, such as `[Total] > 1000`.
602    pub condition: String,
603    /// The settings applied.
604    pub style: Style,
605}
606
607/// Horizontal text alignment.
608#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
609#[serde(rename_all = "camelCase")]
610#[non_exhaustive]
611pub enum TextAlign {
612    /// Left.
613    #[default]
614    Left,
615    /// Centered.
616    Center,
617    /// Right (numbers).
618    Right,
619}
620
621/// Vertical text alignment.
622#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
623#[serde(rename_all = "camelCase")]
624#[non_exhaustive]
625pub enum VerticalAlign {
626    /// Top.
627    Top,
628    /// Middle.
629    #[default]
630    Middle,
631    /// Bottom.
632    Bottom,
633}
634
635/// Appearance settings. Every field is optional so styles can be layered:
636/// built-in defaults, then a named style, then the control's own settings,
637/// then conditional rules.
638#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
639#[serde(rename_all = "camelCase", default)]
640pub struct Style {
641    /// Font size in points.
642    pub font_size: Option<f32>,
643    /// Bold text.
644    pub bold: Option<bool>,
645    /// Text color, `#rrggbb` or `#rrggbbaa`.
646    pub color: Option<String>,
647    /// Fill color.
648    pub background: Option<String>,
649    /// Border color.
650    pub border_color: Option<String>,
651    /// Border width in points; 0 for none.
652    pub border_width: Option<f32>,
653    /// Which sides have a border: any of `top`, `right`, `bottom`, `left`, or `all`.
654    pub borders: Option<String>,
655    /// Inner padding in points.
656    pub padding: Option<f32>,
657    /// Horizontal alignment.
658    pub text_align: Option<TextAlign>,
659    /// Vertical alignment.
660    pub vertical_align: Option<VerticalAlign>,
661    /// Wraps long text onto more lines (true by default).
662    pub word_wrap: Option<bool>,
663}
664
665impl Style {
666    /// An empty style.
667    pub fn new() -> Self {
668        Self::default()
669    }
670
671    /// Sets the font size.
672    pub fn font_size(mut self, size: f32) -> Self {
673        self.font_size = Some(size);
674        self
675    }
676
677    /// Makes the text bold.
678    pub fn bold(mut self) -> Self {
679        self.bold = Some(true);
680        self
681    }
682
683    /// Sets the text color (`#rrggbb`).
684    pub fn color(mut self, color: &str) -> Self {
685        self.color = Some(color.to_owned());
686        self
687    }
688
689    /// Sets the fill color (`#rrggbb`).
690    pub fn background(mut self, color: &str) -> Self {
691        self.background = Some(color.to_owned());
692        self
693    }
694
695    /// Adds a border of `width` points in `color` on `sides` (`all`, `bottom`, `top bottom`...).
696    pub fn border(mut self, sides: &str, width: f32, color: &str) -> Self {
697        self.borders = Some(sides.to_owned());
698        self.border_width = Some(width);
699        self.border_color = Some(color.to_owned());
700        self
701    }
702
703    /// Sets the inner padding.
704    pub fn padding(mut self, padding: f32) -> Self {
705        self.padding = Some(padding);
706        self
707    }
708
709    /// Sets the horizontal alignment.
710    pub fn align(mut self, align: TextAlign) -> Self {
711        self.text_align = Some(align);
712        self
713    }
714
715    /// Sets the vertical alignment.
716    pub fn valign(mut self, align: VerticalAlign) -> Self {
717        self.vertical_align = Some(align);
718        self
719    }
720
721    /// Settings of `other` override these where `other` has them.
722    pub fn merged(&self, other: &Style) -> Style {
723        Style {
724            font_size: other.font_size.or(self.font_size),
725            bold: other.bold.or(self.bold),
726            color: other.color.clone().or_else(|| self.color.clone()),
727            background: other.background.clone().or_else(|| self.background.clone()),
728            border_color: other.border_color.clone().or_else(|| self.border_color.clone()),
729            border_width: other.border_width.or(self.border_width),
730            borders: other.borders.clone().or_else(|| self.borders.clone()),
731            padding: other.padding.or(self.padding),
732            text_align: other.text_align.or(self.text_align),
733            vertical_align: other.vertical_align.or(self.vertical_align),
734            word_wrap: other.word_wrap.or(self.word_wrap),
735        }
736    }
737}
738
739impl Report {
740    /// An empty report on A4 paper, in US English.
741    pub fn new(name: impl Into<String>) -> Self {
742        Self { name: name.into(), culture: "en-US".into(), ..Self::default() }
743    }
744
745    /// Reads a report from JSON.
746    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
747        serde_json::from_str(json)
748    }
749
750    /// The report as indented JSON.
751    pub fn to_json(&self) -> String {
752        serde_json::to_string_pretty(self).unwrap_or_default()
753    }
754
755    /// Sets the paper.
756    pub fn page(mut self, page: PageSettings) -> Self {
757        self.page = page;
758        self
759    }
760
761    /// Adds a data source and makes it the report's source.
762    pub fn data_source(mut self, source: DataSource) -> Self {
763        self.data_source = Some(source.name.clone());
764        self.data_sources.push(source);
765        self
766    }
767
768    /// The data of the report's source when it is stored in the layout.
769    pub fn embedded_data(&self) -> Option<&serde_json::Value> {
770        let source = match &self.data_source {
771            Some(name) => self.data_sources.iter().find(|s| &s.name == name),
772            None => self.data_sources.first(),
773        }?;
774        match &source.kind {
775            DataSourceKind::Json { data } => Some(data),
776            _ => None,
777        }
778    }
779
780    /// Sets the culture (`pt-BR`, `en-US`).
781    pub fn culture(mut self, culture: &str) -> Self {
782        self.culture = culture.to_owned();
783        self
784    }
785
786    /// Sets the path to the rows in the data.
787    pub fn data_member(mut self, path: &str) -> Self {
788        self.data_member = Some(path.to_owned());
789        self
790    }
791
792    /// Keeps only rows where `expression` is true.
793    pub fn filter(mut self, expression: &str) -> Self {
794        self.filter = Some(expression.to_owned());
795        self
796    }
797
798    /// Adds a sort key.
799    pub fn sort_by(mut self, expression: &str, descending: bool) -> Self {
800        self.sort.push(SortField { expression: expression.to_owned(), descending });
801        self
802    }
803
804    /// Adds a parameter with a default value.
805    pub fn parameter(mut self, name: &str, kind: ParameterKind, default: serde_json::Value) -> Self {
806        self.parameters.push(Parameter { name: name.to_owned(), label: None, kind, default: Some(default) });
807        self
808    }
809
810    /// Sets what the user is asked for the parameter added last.
811    pub fn parameter_label(mut self, label: &str) -> Self {
812        if let Some(p) = self.parameters.last_mut() {
813            p.label = Some(label.to_owned());
814        }
815        self
816    }
817
818    /// Adds a calculated field.
819    pub fn calculated(mut self, name: &str, expression: &str) -> Self {
820        self.calculated_fields
821            .push(CalculatedField { name: name.to_owned(), expression: expression.to_owned() });
822        self
823    }
824
825    /// Adds a named style.
826    pub fn style(mut self, name: &str, style: Style) -> Self {
827        self.styles.insert(name.to_owned(), style);
828        self
829    }
830
831    /// Sets the report header.
832    pub fn report_header(mut self, band: Band) -> Self {
833        self.report_header = Some(band);
834        self
835    }
836
837    /// Sets the page header.
838    pub fn page_header(mut self, band: Band) -> Self {
839        self.page_header = Some(band);
840        self
841    }
842
843    /// Adds a group level (outermost first).
844    pub fn group(mut self, group: Group) -> Self {
845        self.groups.push(group);
846        self
847    }
848
849    /// Sets the detail band.
850    pub fn detail(mut self, band: Band) -> Self {
851        self.detail = band;
852        self
853    }
854
855    /// Adds a master-detail report.
856    pub fn detail_report(mut self, detail: DetailReport) -> Self {
857        self.detail_reports.push(detail);
858        self
859    }
860
861    /// Sets the report footer.
862    pub fn report_footer(mut self, band: Band) -> Self {
863        self.report_footer = Some(band);
864        self
865    }
866
867    /// Sets the page footer.
868    pub fn page_footer(mut self, band: Band) -> Self {
869        self.page_footer = Some(band);
870        self
871    }
872}
873
874impl Group {
875    /// Groups rows by `expression`.
876    pub fn new(expression: &str) -> Self {
877        Self { expression: expression.to_owned(), ..Self::default() }
878    }
879
880    /// Sets the header.
881    pub fn header(mut self, band: Band) -> Self {
882        self.header = Some(band);
883        self
884    }
885
886    /// Sets the footer.
887    pub fn footer(mut self, band: Band) -> Self {
888        self.footer = Some(band);
889        self
890    }
891
892    /// Repeats the header on every page the group continues on.
893    pub fn repeat_header(mut self) -> Self {
894        self.repeat_header = true;
895        self
896    }
897
898    /// Starts each group on a new page.
899    pub fn page_break_before(mut self) -> Self {
900        self.page_break_before = true;
901        self
902    }
903}
904
905impl DetailReport {
906    /// A nested list over the array field `data_member` of each master row.
907    pub fn new(data_member: &str) -> Self {
908        Self { data_member: data_member.to_owned(), ..Self::default() }
909    }
910
911    /// Sets the header.
912    pub fn header(mut self, band: Band) -> Self {
913        self.header = Some(band);
914        self
915    }
916
917    /// Sets the detail band.
918    pub fn detail(mut self, band: Band) -> Self {
919        self.detail = band;
920        self
921    }
922
923    /// Sets the footer.
924    pub fn footer(mut self, band: Band) -> Self {
925        self.footer = Some(band);
926        self
927    }
928}
929
930impl Band {
931    /// An empty band `height` points tall.
932    pub fn new(height: f32) -> Self {
933        Self {
934            height,
935            controls: Vec::new(),
936            visible: None,
937            page_break_before: false,
938            page_break_after: false,
939            keep_with_next: false,
940            odd_style: None,
941            even_style: None,
942        }
943    }
944
945    /// Adds a control.
946    pub fn control(mut self, control: Control) -> Self {
947        self.controls.push(control);
948        self
949    }
950
951    /// Keeps the band on the page of the band after it.
952    pub fn keep_with_next(mut self) -> Self {
953        self.keep_with_next = true;
954        self
955    }
956
957    /// Alternating row styles.
958    pub fn zebra(mut self, odd: &str, even: &str) -> Self {
959        self.odd_style = Some(odd.to_owned());
960        self.even_style = Some(even.to_owned());
961        self
962    }
963}
964
965impl Control {
966    /// A label with fixed text.
967    pub fn label(text: impl Into<String>) -> Self {
968        Self { kind: ControlKind::Label(Label { text: text.into(), ..Label::default() }), ..Self::default() }
969    }
970
971    /// A chart: one series of `value` per `argument`.
972    pub fn chart(
973        kind: crate::chart::ChartKind,
974        argument: impl Into<String>,
975        value: impl Into<String>,
976    ) -> Self {
977        Self {
978            kind: ControlKind::Chart(Chart {
979                kind,
980                argument: Some(argument.into()),
981                series: vec![ChartSeries::new("", value)],
982                ..Chart::default()
983            }),
984            ..Self::default()
985        }
986    }
987
988    /// A barcode showing the value of `expression`.
989    pub fn barcode(symbology: crate::barcode::Symbology, expression: impl Into<String>) -> Self {
990        Self {
991            kind: ControlKind::Barcode(Barcode {
992                symbology,
993                expression: Some(expression.into()),
994                ..Barcode::default()
995            }),
996            ..Self::default()
997        }
998    }
999
1000    /// A picture from a `data:` address or base64 text.
1001    pub fn picture(source: impl Into<String>) -> Self {
1002        Self {
1003            kind: ControlKind::Picture(Picture { source: source.into(), ..Picture::default() }),
1004            ..Self::default()
1005        }
1006    }
1007
1008    /// Text that opens `url` when clicked.
1009    pub fn link(text: impl Into<String>, url: impl Into<String>) -> Self {
1010        Self {
1011            kind: ControlKind::Link(LinkLabel {
1012                label: Label { text: text.into(), ..Label::default() },
1013                url: url.into(),
1014                url_expression: None,
1015            }),
1016            ..Self::default()
1017        }
1018    }
1019
1020    /// An outline entry showing the value of `expression`, at `level`.
1021    pub fn bookmark(expression: impl Into<String>, level: u32) -> Self {
1022        Self {
1023            kind: ControlKind::Bookmark(Bookmark {
1024                expression: Some(expression.into()),
1025                level,
1026                ..Bookmark::default()
1027            }),
1028            height: 12.0,
1029            ..Self::default()
1030        }
1031    }
1032
1033    /// A label showing the value of `expression`.
1034    pub fn field(expression: impl Into<String>) -> Self {
1035        Self {
1036            kind: ControlKind::Label(Label { expression: Some(expression.into()), ..Label::default() }),
1037            ..Self::default()
1038        }
1039    }
1040
1041    /// A check box for a boolean expression.
1042    pub fn check_box(expression: &str, text: &str) -> Self {
1043        Self {
1044            kind: ControlKind::CheckBox(CheckBox {
1045                expression: expression.to_owned(),
1046                text: text.to_owned(),
1047            }),
1048            ..Self::default()
1049        }
1050    }
1051
1052    /// A horizontal line.
1053    pub fn line() -> Self {
1054        Self { kind: ControlKind::Line(Line::default()), height: 2.0, ..Self::default() }
1055    }
1056
1057    /// A rectangle.
1058    pub fn rect(radius: f32) -> Self {
1059        Self { kind: ControlKind::Shape(Shape { shape: ShapeKind::Rectangle, radius }), ..Self::default() }
1060    }
1061
1062    /// A table.
1063    pub fn table(table: Table) -> Self {
1064        let height = table.rows.iter().map(|r| r.height).sum();
1065        let width = table.rows.first().map_or(0.0, |r| r.cells.iter().map(|c| c.width).sum());
1066        Self { kind: ControlKind::Table(table), width, height, ..Self::default() }
1067    }
1068
1069    /// Positions and sizes the control.
1070    pub fn at(mut self, x: f32, y: f32, width: f32, height: f32) -> Self {
1071        (self.x, self.y, self.width, self.height) = (x, y, width, height);
1072        self
1073    }
1074
1075    /// Sets the value format (`C2`, `{0:dd/MM/yyyy}`...).
1076    pub fn format(mut self, format: &str) -> Self {
1077        if let ControlKind::Label(l) = &mut self.kind {
1078            l.format = Some(format.to_owned());
1079        }
1080        self
1081    }
1082
1083    /// Uses a named style.
1084    pub fn style_name(mut self, name: &str) -> Self {
1085        self.style_name = Some(name.to_owned());
1086        self
1087    }
1088
1089    /// Sets style settings on the control.
1090    pub fn style(mut self, style: Style) -> Self {
1091        self.style = style;
1092        self
1093    }
1094
1095    /// Prints the control only when `expression` is true.
1096    pub fn visible(mut self, expression: &str) -> Self {
1097        self.visible = Some(expression.to_owned());
1098        self
1099    }
1100
1101    /// Adds conditional formatting.
1102    pub fn rule(mut self, condition: &str, style: Style) -> Self {
1103        self.rules.push(FormattingRule { condition: condition.to_owned(), style });
1104        self
1105    }
1106
1107    /// Names the control.
1108    pub fn named(mut self, name: &str) -> Self {
1109        self.name = Some(name.to_owned());
1110        self
1111    }
1112}
1113
1114impl Table {
1115    /// An empty table.
1116    pub fn new() -> Self {
1117        Self::default()
1118    }
1119
1120    /// Adds a row.
1121    pub fn row(mut self, row: TableRow) -> Self {
1122        self.rows.push(row);
1123        self
1124    }
1125}
1126
1127impl TableRow {
1128    /// An empty row `height` points tall.
1129    pub fn new(height: f32) -> Self {
1130        Self { height, cells: Vec::new() }
1131    }
1132
1133    /// Adds a cell with fixed text.
1134    pub fn text(mut self, width: f32, text: &str) -> Self {
1135        self.cells.push(TableCell {
1136            width,
1137            label: Label { text: text.to_owned(), ..Label::default() },
1138            ..TableCell::default()
1139        });
1140        self
1141    }
1142
1143    /// Adds a cell showing an expression, with an optional format.
1144    pub fn field(mut self, width: f32, expression: &str, format: Option<&str>) -> Self {
1145        self.cells.push(TableCell {
1146            width,
1147            label: Label {
1148                expression: Some(expression.to_owned()),
1149                format: format.map(str::to_owned),
1150                ..Label::default()
1151            },
1152            ..TableCell::default()
1153        });
1154        self
1155    }
1156
1157    /// Styles the last cell added.
1158    pub fn cell_style(mut self, style: Style) -> Self {
1159        if let Some(c) = self.cells.last_mut() {
1160            c.style = style;
1161        }
1162        self
1163    }
1164
1165    /// Uses a named style for every cell of the row.
1166    pub fn style_name(mut self, name: &str) -> Self {
1167        for c in &mut self.cells {
1168            c.style_name = Some(name.to_owned());
1169        }
1170        self
1171    }
1172}