1use serde::{Deserialize, Serialize};
14
15use crate::source::StyleSource;
16use crate::source_style::convert_style;
17use crate::{
18 ErrorCode, FileMakerError, Length, ResourceLimits, Result, TableColumn, TableIr, TableSpec,
19 TableStyleRule,
20};
21
22#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
24#[serde(deny_unknown_fields)]
25pub struct TableStyleRuleSource {
26 pub when: String,
28 #[serde(default)]
30 pub style: StyleSource,
31 #[serde(default)]
33 pub padding: Option<crate::CellPadding>,
34 #[serde(default)]
36 pub padding_first_page: Option<crate::CellPadding>,
37 #[serde(default)]
39 pub padding_continuation: Option<crate::CellPadding>,
40 #[serde(default)]
42 pub text_offset_y: Option<Length>,
43 #[serde(default)]
45 pub text_offset_y_first_page: Option<Length>,
46 #[serde(default)]
48 pub text_offset_y_continuation: Option<Length>,
49 #[serde(default)]
51 pub min_height: Option<Length>,
52 #[serde(default)]
54 pub min_height_first_page: Option<Length>,
55 #[serde(default)]
57 pub min_height_continuation: Option<Length>,
58 #[serde(default)]
60 pub reserve_after: Option<Length>,
61}
62
63#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
65#[serde(deny_unknown_fields)]
66pub struct TableSource {
67 pub columns: Vec<TableColumn>,
69 #[serde(default = "default_true")]
71 pub repeat_header: bool,
72 #[serde(default)]
74 pub group_by: Option<String>,
75 #[serde(default)]
78 pub keep_together_by: Option<String>,
79 #[serde(default)]
81 pub row_anchor_field: Option<String>,
82 #[serde(default)]
84 pub total_fields: Vec<String>,
85 #[serde(default)]
87 pub conditional_styles: Vec<TableStyleRuleSource>,
88 #[serde(default = "default_auto_samples")]
90 pub auto_sample_rows: usize,
91 #[serde(default)]
93 pub max_rows: Option<u64>,
94 #[serde(default)]
96 pub max_row_fields: Option<usize>,
97 #[serde(default)]
99 pub max_cell_bytes: Option<usize>,
100 #[serde(default = "default_header_height")]
102 pub header_height: Length,
103 #[serde(default)]
105 pub row_height: Option<Length>,
106 #[serde(default)]
108 pub page_bodies: Option<crate::TablePageBodies>,
109}
110
111pub(crate) fn convert_table(source: &TableSource, limits: &ResourceLimits) -> Result<TableIr> {
112 if matches!(source.header_height, Length::Auto) {
113 return Err(table_source_error("table header height cannot be auto"));
114 }
115 if source.page_bodies.as_ref().is_some_and(|regions| {
116 [
117 ®ions.first.offset_y,
118 ®ions.first.height,
119 ®ions.continuation.offset_y,
120 ®ions.continuation.height,
121 ]
122 .into_iter()
123 .any(|length| matches!(length, Length::Auto))
124 }) {
125 return Err(table_source_error(
126 "table page body offsets and heights cannot be auto",
127 ));
128 }
129 reject_raised_limit(source.max_rows, limits.max_rows, "table row")?;
130 reject_raised_limit(
131 source.max_row_fields,
132 limits.max_elements,
133 "table row field",
134 )?;
135 reject_raised_limit(
136 source.max_cell_bytes,
137 limits.max_text_bytes,
138 "table cell byte",
139 )?;
140 let spec = TableSpec {
141 columns: source.columns.clone(),
142 repeat_header: source.repeat_header,
143 group_by: source.group_by.clone(),
144 keep_together_by: source.keep_together_by.clone(),
145 row_anchor_field: source.row_anchor_field.clone(),
146 total_fields: source.total_fields.clone(),
147 conditional_styles: source
148 .conditional_styles
149 .iter()
150 .map(|rule| {
151 Ok(TableStyleRule {
152 when: rule.when.clone(),
153 style: convert_style(&rule.style)?,
154 padding: rule.padding,
155 padding_first_page: rule.padding_first_page,
156 padding_continuation: rule.padding_continuation,
157 text_offset_y: rule.text_offset_y,
158 text_offset_y_first_page: rule.text_offset_y_first_page,
159 text_offset_y_continuation: rule.text_offset_y_continuation,
160 min_height: rule.min_height,
161 min_height_first_page: rule.min_height_first_page,
162 min_height_continuation: rule.min_height_continuation,
163 reserve_after: rule.reserve_after,
164 })
165 })
166 .collect::<Result<Vec<_>>>()?,
167 style_expression_steps: limits.max_expression_steps,
168 auto_sample_rows: source.auto_sample_rows,
169 max_rows: source.max_rows.unwrap_or(limits.max_rows),
170 max_row_fields: source.max_row_fields.unwrap_or(limits.max_elements),
171 max_cell_bytes: source.max_cell_bytes.unwrap_or(limits.max_text_bytes),
172 };
173 spec.validate()?;
174 Ok(TableIr {
175 spec,
176 header_height: source.header_height,
177 row_height: source.row_height,
178 page_bodies: source.page_bodies.clone(),
179 rows: Vec::new(),
180 })
181}
182
183const fn default_true() -> bool {
184 true
185}
186
187const fn default_auto_samples() -> usize {
188 16
189}
190
191fn default_header_height() -> Length {
192 Length::Absolute(crate::Unit::from_raw(18_000_000))
193}
194
195fn table_source_error(message: impl Into<String>) -> FileMakerError {
196 FileMakerError::new(ErrorCode::SchemaField, message)
197}
198
199fn reject_raised_limit<T>(requested: Option<T>, global: T, label: &str) -> Result<()>
200where
201 T: Copy + PartialOrd,
202{
203 if requested.is_some_and(|value| value > global) {
204 return Err(FileMakerError::new(
205 ErrorCode::LimitExceeded,
206 format!("{label} limit cannot exceed the compiler limit"),
207 ));
208 }
209 Ok(())
210}