Skip to main content

appcore_filemaker/
resolved_table.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: resolved_table.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/08/30 05:00:00 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/08/30 05:00:00 by dnettoRaw
8//      ###########      S: 1.0.2-rc
9// =============================================================================
10
11use serde::{Deserialize, Serialize};
12
13use crate::{
14    ComputedStyle, ErrorCode, FileMakerError, Insets, Rect, ResolvedTableColumn, TextLayout,
15};
16
17/// One exporter-ready table cell with final geometry and shaped text.
18#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
19pub struct ResolvedTableCell {
20    /// Stable source field.
21    pub field: String,
22    /// Display text derived before export.
23    pub text: String,
24    /// Cell rectangle in page coordinates.
25    pub bounds: Rect,
26    /// Resolved per-side padding; absent fields in older serialized scenes use zero.
27    #[serde(default)]
28    pub padding: Insets,
29    /// Final style after the data-rule layer.
30    pub style: ComputedStyle,
31    /// Shaped text constrained to the cell.
32    pub text_layout: TextLayout,
33}
34
35impl ResolvedTableCell {
36    /// Returns the text rectangle after resolving the retained cell insets.
37    pub fn content_bounds(&self) -> crate::Result<Rect> {
38        let width = self
39            .bounds
40            .size
41            .width
42            .checked_sub(self.padding.left.checked_add(self.padding.right)?)?;
43        let height = self
44            .bounds
45            .size
46            .height
47            .checked_sub(self.padding.top.checked_add(self.padding.bottom)?)?;
48        if width <= crate::Unit::ZERO || height <= crate::Unit::ZERO {
49            return Err(FileMakerError::new(
50                ErrorCode::LayoutInvalid,
51                "resolved cell padding leaves no text content area",
52            ));
53        }
54        Rect::new(
55            self.bounds.origin.x.checked_add(self.padding.left)?,
56            self.bounds.origin.y.checked_add(self.padding.top)?,
57            width,
58            height,
59        )
60    }
61}
62
63/// One exporter-ready table row.
64#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
65pub struct ResolvedTableRow {
66    /// Zero-based source row index across fragments.
67    pub source_index: u64,
68    /// Row rectangle in page coordinates.
69    pub bounds: Rect,
70    /// Group key when this row begins a group.
71    pub group_start: Option<String>,
72    /// Final row style after ordered conditional rules.
73    pub style: ComputedStyle,
74    /// Cells in visual column order.
75    pub cells: Vec<ResolvedTableCell>,
76}
77
78/// One physical-page fragment of a first-class table.
79#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
80pub struct ResolvedTableFragment {
81    /// Zero-based fragment index.
82    pub index: usize,
83    /// Final column widths shared by every fragment.
84    pub columns: Vec<ResolvedTableColumn>,
85    /// Header cells, empty when the header is not repeated.
86    pub header: Vec<ResolvedTableCell>,
87    /// Bounded rows assigned to this fragment.
88    pub rows: Vec<ResolvedTableRow>,
89    /// Final exact totals row, empty before the last fragment.
90    pub totals: Vec<ResolvedTableCell>,
91    /// Group active at the first row of this fragment.
92    pub starting_group: Option<String>,
93}