appcore_filemaker/
page.rs1use serde::{Deserialize, Serialize};
14
15use crate::{Insets, Size};
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum PageRole {
21 First,
23 Continuation,
25 Last,
27 Master,
29}
30
31#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
33#[serde(rename_all = "snake_case")]
34pub enum PageBand {
35 Background,
37 Header,
39 Footer,
41}
42
43#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
45pub struct PagePlacement {
46 pub role: PageRole,
48 pub band: PageBand,
50}
51
52#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
54pub struct PageTemplate {
55 pub name: String,
57 pub role: PageRole,
59 pub size: Size,
61 pub margin: Insets,
63 pub bleed: Insets,
65 pub safe: Insets,
67 pub crop_marks: bool,
69}
70
71impl PageTemplate {
72 pub fn content_bounds(&self) -> crate::Result<crate::Rect> {
74 let width = self
75 .size
76 .width
77 .checked_sub(self.margin.left)?
78 .checked_sub(self.margin.right)?;
79 let height = self
80 .size
81 .height
82 .checked_sub(self.margin.top)?
83 .checked_sub(self.margin.bottom)?;
84 crate::Rect::new(self.margin.left, self.margin.top, width, height)
85 }
86
87 pub fn safe_bounds(&self) -> crate::Result<crate::Rect> {
89 let width = self
90 .size
91 .width
92 .checked_sub(self.safe.left)?
93 .checked_sub(self.safe.right)?;
94 let height = self
95 .size
96 .height
97 .checked_sub(self.safe.top)?
98 .checked_sub(self.safe.bottom)?;
99 crate::Rect::new(self.safe.left, self.safe.top, width, height)
100 }
101}
102
103#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
105pub struct PageTemplateSet {
106 pub first: Option<PageTemplate>,
108 pub continuation: Option<PageTemplate>,
110 pub last: Option<PageTemplate>,
112 pub master: Option<PageTemplate>,
114}
115
116impl PageTemplateSet {
117 #[must_use]
119 pub fn from_base(base: &PageTemplate) -> Self {
120 let with_role = |role| {
121 let mut template = base.clone();
122 template.role = role;
123 template
124 };
125 Self {
126 first: Some(with_role(PageRole::First)),
127 continuation: Some(with_role(PageRole::Continuation)),
128 last: Some(with_role(PageRole::Last)),
129 master: Some(with_role(PageRole::Master)),
130 }
131 }
132
133 #[must_use]
135 pub fn select(&self, index: usize, total: usize) -> Option<&PageTemplate> {
136 if index == 0 {
137 self.first.as_ref().or(self.continuation.as_ref())
138 } else if index + 1 == total {
139 self.last.as_ref().or(self.continuation.as_ref())
140 } else {
141 self.continuation.as_ref()
142 }
143 }
144
145 #[must_use]
147 pub fn number_text(source: &str, index: usize, total: usize) -> String {
148 source
149 .replace("{page}", &(index + 1).to_string())
150 .replace("{pages}", &total.to_string())
151 }
152
153 #[must_use]
155 pub fn role_is_active(role: PageRole, index: usize, total: usize) -> bool {
156 match role {
157 PageRole::Master => true,
158 PageRole::First => index == 0,
159 PageRole::Continuation => index > 0 && index + 1 < total,
160 PageRole::Last => total > 1 && index + 1 == total,
161 }
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn page_numbers_and_roles_are_deterministic() {
171 assert_eq!(
172 PageTemplateSet::number_text("Page {page}/{pages}", 1, 3),
173 "Page 2/3"
174 );
175 assert!(PageTemplateSet::role_is_active(PageRole::Master, 2, 3));
176 assert!(PageTemplateSet::role_is_active(
177 PageRole::Continuation,
178 1,
179 3
180 ));
181 assert!(!PageTemplateSet::role_is_active(PageRole::Last, 0, 1));
182 }
183}