1use std::collections::{BTreeMap, BTreeSet};
14
15use serde::{Deserialize, Serialize};
16
17use crate::source::ModelKind;
18use crate::{
19 CollisionPolicy, DataSchema, ErrorCode, FileMakerError, ImageOptions, Length, Orientation,
20 PageTemplate, Result, Size, Style,
21};
22
23#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash, Serialize)]
25#[serde(transparent)]
26pub struct ElementId(String);
27
28impl ElementId {
29 pub fn new(value: impl Into<String>) -> Result<Self> {
31 let value = value.into();
32 if value.is_empty()
33 || value.len() > 128
34 || !value.bytes().all(|byte| {
35 byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'-' | b'_' | b'/')
36 })
37 {
38 return Err(FileMakerError::new(
39 ErrorCode::SchemaField,
40 "element ID must be 1..128 safe ASCII characters",
41 ));
42 }
43 Ok(Self(value))
44 }
45
46 #[must_use]
48 pub fn as_str(&self) -> &str {
49 &self.0
50 }
51}
52
53impl<'de> Deserialize<'de> for ElementId {
54 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
55 where
56 D: serde::Deserializer<'de>,
57 {
58 let value = String::deserialize(deserializer)?;
59 Self::new(value).map_err(serde::de::Error::custom)
60 }
61}
62
63#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
65#[serde(rename_all = "snake_case")]
66pub enum ElementKind {
67 Text,
69 Image,
71 Line,
73 Rect,
75 Circle,
77 Ellipse,
79 Polygon,
81 Path,
83 Group,
85 Table,
87 Chart,
89 Qr,
91 Barcode,
93}
94
95impl ElementKind {
96 pub fn parse(value: &str) -> Result<Self> {
98 match value {
99 "text" => Ok(Self::Text),
100 "image" => Ok(Self::Image),
101 "line" => Ok(Self::Line),
102 "rect" => Ok(Self::Rect),
103 "circle" => Ok(Self::Circle),
104 "ellipse" => Ok(Self::Ellipse),
105 "polygon" => Ok(Self::Polygon),
106 "path" => Ok(Self::Path),
107 "group" => Ok(Self::Group),
108 "table" => Ok(Self::Table),
109 "chart" => Ok(Self::Chart),
110 "qr" => Ok(Self::Qr),
111 "barcode" => Ok(Self::Barcode),
112 _ => Err(FileMakerError::new(
113 ErrorCode::SchemaField,
114 format!("unsupported element type `{value}`"),
115 )),
116 }
117 }
118}
119
120#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
122pub struct GeometryIr {
123 pub x: Option<Length>,
125 pub y: Option<Length>,
127 pub width: Option<Length>,
129 pub height: Option<Length>,
131 pub constraints: crate::LayoutConstraints,
133 pub align_x: Option<crate::Alignment>,
135 pub align_y: Option<crate::Alignment>,
137 pub region: Option<String>,
139 pub anchors: BTreeMap<String, String>,
141}
142
143#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
145pub struct TransformIr {
146 pub translate_x: Length,
148 pub translate_y: Length,
150 pub rotate: i32,
152 pub scale_x: i64,
154 pub scale_y: i64,
156 pub origin_x: Length,
158 pub origin_y: Length,
160}
161
162#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
164pub struct TextIr {
165 pub overflow: crate::TextOverflow,
167 pub max_lines: Option<usize>,
169 pub min_font_size: Option<Length>,
171 pub line_height: u32,
173 pub writing_mode: crate::WritingMode,
175 pub align_x: crate::Alignment,
177 #[serde(default = "default_inline_padding")]
179 pub padding_inline: crate::Length,
180 #[serde(default)]
182 pub padding: crate::TextBlockPadding,
183}
184
185fn default_inline_padding() -> crate::Length {
186 crate::Length::Absolute(crate::Unit::ZERO)
187}
188
189#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
191pub struct TableIr {
192 pub spec: crate::TableSpec,
194 pub header_height: Length,
196 pub row_height: Option<Length>,
198 #[serde(default)]
200 pub page_bodies: Option<TablePageBodies>,
201 pub rows: Vec<crate::DataRow>,
203}
204
205#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
207#[serde(deny_unknown_fields)]
208pub struct TablePageBody {
209 pub offset_y: Length,
211 pub height: Length,
213}
214
215#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
217#[serde(deny_unknown_fields)]
218pub struct TablePageBodies {
219 pub first: TablePageBody,
221 pub continuation: TablePageBody,
223}
224
225#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
227#[serde(tag = "command", rename_all = "snake_case")]
228pub enum PathCommandIr {
229 Move { x: Length, y: Length },
231 Line { x: Length, y: Length },
233 Curve {
235 x1: Length,
236 y1: Length,
237 x2: Length,
238 y2: Length,
239 x: Length,
240 y: Length,
241 },
242 Close,
244}
245
246#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
248pub struct RegionIr {
249 pub x: Length,
251 pub y: Length,
253 pub width: Length,
255 pub height: Length,
257 pub collision: Option<crate::CollisionPolicy>,
259}
260
261#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
263pub struct ExclusionIr {
264 pub x: Length,
266 pub y: Length,
268 pub width: Length,
270 pub height: Length,
272 pub group: String,
274 pub collides_with: BTreeSet<String>,
276}
277
278#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
280#[serde(rename_all = "snake_case")]
281pub enum LayoutMode {
282 #[default]
284 Absolute,
285 FlowVertical,
287 FlowHorizontal,
289}
290
291#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
293pub struct Provenance {
294 pub source: String,
296 pub components: Vec<String>,
298 pub styles: Vec<String>,
300 pub patches: Vec<u64>,
302}
303
304#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
306pub struct AiPolicy {
307 pub purpose: String,
309 pub rules: Vec<String>,
311 pub editable: BTreeSet<String>,
313 pub locked: BTreeSet<String>,
315}
316
317#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
319pub struct ElementIr {
320 pub id: ElementId,
322 pub kind: ElementKind,
324 pub geometry: GeometryIr,
326 pub transform: TransformIr,
328 pub text: Option<String>,
330 #[serde(default)]
332 pub text_segments: Vec<TextSegmentIr>,
333 pub text_options: TextIr,
335 pub table: Option<TableIr>,
337 pub asset: Option<String>,
339 pub path: Vec<PathCommandIr>,
341 pub image: ImageOptions,
343 pub style: Style,
345 #[serde(default)]
347 pub style_rules: Vec<crate::ElementStyleRule>,
348 pub layout: LayoutMode,
350 pub distribute: crate::Distribution,
352 pub gap: Length,
354 pub collision: Option<CollisionPolicy>,
356 pub children: Vec<ElementIr>,
358 pub locked: bool,
360 pub hidden: bool,
362 #[serde(default)]
365 pub keep_with_next: bool,
366 pub layer: String,
368 pub z_index: i32,
370 pub binding: Option<String>,
372 pub when: Option<String>,
374 pub repeat: Option<String>,
376 pub provenance: Provenance,
378 pub page_placement: Option<crate::PagePlacement>,
380}
381
382#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
384pub struct TextSegmentIr {
385 pub text: Option<String>,
387 pub binding: Option<String>,
389 pub gap_after: Length,
391}
392
393impl ElementIr {
394 pub(crate) fn clone_with_text(&self, text: String) -> Self {
395 Self {
396 id: self.id.clone(),
397 kind: self.kind,
398 geometry: self.geometry.clone(),
399 transform: self.transform,
400 text: Some(text),
401 text_segments: Vec::new(),
402 text_options: self.text_options,
403 table: self.table.clone(),
404 asset: self.asset.clone(),
405 path: self.path.clone(),
406 image: self.image,
407 style: self.style.clone(),
408 style_rules: self.style_rules.clone(),
409 layout: self.layout,
410 distribute: self.distribute,
411 gap: self.gap,
412 collision: self.collision.clone(),
413 children: self.children.clone(),
414 locked: self.locked,
415 hidden: self.hidden,
416 keep_with_next: self.keep_with_next,
417 layer: self.layer.clone(),
418 z_index: self.z_index,
419 binding: self.binding.clone(),
420 when: self.when.clone(),
421 repeat: self.repeat.clone(),
422 provenance: self.provenance.clone(),
423 page_placement: self.page_placement,
424 }
425 }
426}
427
428#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
430pub struct TemplateIr {
431 pub id: String,
433 pub model: ModelKind,
435 pub page_size: Option<Size>,
437 pub orientation: Orientation,
439 pub page_template: Option<PageTemplate>,
441 pub collision: Option<crate::CollisionPolicy>,
443 pub page_collision: Option<crate::CollisionPolicy>,
445 pub guides: BTreeMap<String, Length>,
447 pub regions: BTreeMap<String, RegionIr>,
449 pub exclusions: BTreeMap<String, ExclusionIr>,
451 pub data_schema: DataSchema,
453 pub ai_policy: AiPolicy,
455 pub elements: Vec<ElementIr>,
457}
458
459#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
461pub struct DocumentIr {
462 pub template_id: String,
464 pub model: ModelKind,
466 pub page_size: Option<Size>,
468 pub page_template: Option<PageTemplate>,
470 pub collision: Option<crate::CollisionPolicy>,
472 pub page_collision: Option<crate::CollisionPolicy>,
474 pub guides: BTreeMap<String, Length>,
476 pub regions: BTreeMap<String, RegionIr>,
478 pub exclusions: BTreeMap<String, ExclusionIr>,
480 pub ai_policy: AiPolicy,
482 pub elements: Vec<ElementIr>,
484}