1use crate::base::{CellRef, MAX_COLS, MAX_ROWS, RangeRef};
13use crate::model::{LineIndex, PageOrder, Sheet, Workbook};
14
15use crate::engine::cmd::print::{self, PX_TO_PT, PrintOptions};
16
17pub const PAGE_GAP: f32 = 14.0;
19
20#[derive(Clone, Debug)]
22pub struct PageGrid {
23 pub page_w: f64,
25 pub page_h: f64,
26 pub margins: [f64; 6],
28 pub gap: f64,
30 pub scale: f32,
32 pub hf_sides: [f64; 2],
35 pub hf_text_k: f32,
38 pub paper_pt: (f32, f32),
40 pub row_bands: Vec<(u32, u32)>,
41 pub col_bands: Vec<(u32, u32)>,
42 pub rows_complete: bool,
44 pub cols_complete: bool,
45 pub data_rows: (usize, usize),
47 pub data_cols: (usize, usize),
48 pub page_order: PageOrder,
50 ri: LineIndex,
51 ci: LineIndex,
52}
53
54#[derive(Clone, Copy, Debug, PartialEq)]
56pub struct HfZone {
57 pub footer: bool,
58 pub section: usize,
60}
61
62impl PageGrid {
63 pub fn build(wb: &Workbook, si: usize, need_row: u32, need_col: u32) -> Option<PageGrid> {
66 let sh = wb.sheet(si)?;
67 let printed = print::layout_sheet(wb, si, &PrintOptions::default(), None)?;
68 let mut lay = printed.clone();
69 let end_row = need_row.max(printed.area.end.row).min(MAX_ROWS - 1);
70 let end_col = need_col.max(printed.area.end.col).min(MAX_COLS - 1);
71 lay.area = RangeRef::new(CellRef::new(0, 0), CellRef::new(end_row, end_col));
72 let (row_bands, col_bands) = print::split_bands(sh, &lay, MAX_ROWS as usize);
73 let s = f64::from(lay.scale.clamp(0.1, 4.0));
74 let to_layout = |pt: f32| f64::from(pt) / f64::from(PX_TO_PT) / s;
75 let margins = lay.margins.map(to_layout);
76 let (hf_left, hf_right) = lay.hf_sides();
77 let hf_text_k = lay.hf_text_scale() / s as f32;
78 let band_range = |bands: &[(u32, u32)], lo: u32, hi: u32| {
79 let first = bands.iter().position(|b| b.1 >= lo).unwrap_or(0);
80 let last = bands.iter().rposition(|b| b.0 <= hi).unwrap_or(first).max(first);
81 (first, last)
82 };
83 let area = printed.area;
84 let data_rows = band_range(&row_bands, area.start.row, area.end.row);
85 let data_cols = band_range(&col_bands, area.start.col, area.end.col);
86 Some(PageGrid {
87 page_w: to_layout(lay.page_w),
88 page_h: to_layout(lay.page_h),
89 margins,
90 gap: f64::from(PAGE_GAP) / s,
91 scale: lay.scale,
92 hf_sides: [to_layout(hf_left), to_layout(hf_right)],
93 hf_text_k,
94 paper_pt: (lay.page_w, lay.page_h),
95 rows_complete: row_bands.last().is_some_and(|b| b.1 >= MAX_ROWS - 1),
96 cols_complete: col_bands.last().is_some_and(|b| b.1 >= MAX_COLS - 1),
97 row_bands,
98 col_bands,
99 data_rows,
100 data_cols,
101 page_order: printed.settings.page_order,
102 ri: LineIndex::new(&sh.rows, sh.default_row_height, MAX_ROWS),
103 ci: LineIndex::new(&sh.cols, sh.default_col_width, MAX_COLS),
104 })
105 }
106
107 pub fn covers(&self, r: u32, c: u32) -> bool {
109 (self.rows_complete || self.row_bands.last().is_some_and(|b| b.1 >= r))
110 && (self.cols_complete || self.col_bands.last().is_some_and(|b| b.1 >= c))
111 }
112
113 fn band_of(bands: &[(u32, u32)], line: u32) -> usize {
114 bands.partition_point(|b| b.1 < line).min(bands.len().saturating_sub(1))
115 }
116
117 pub fn page_x(&self, j: usize) -> f64 {
119 self.gap + j as f64 * (self.page_w + self.gap)
120 }
121 pub fn page_y(&self, i: usize) -> f64 {
122 self.gap + i as f64 * (self.page_h + self.gap)
123 }
124 pub fn page_rect(&self, i: usize, j: usize) -> (f64, f64, f64, f64) {
126 (self.page_x(j), self.page_y(i), self.page_w, self.page_h)
127 }
128 pub fn content_rect(&self, i: usize, j: usize) -> (f64, f64, f64, f64) {
130 let [l, r, t, b, ..] = self.margins;
131 (self.page_x(j) + l, self.page_y(i) + t, (self.page_w - l - r).max(1.0), (self.page_h - t - b).max(1.0))
132 }
133
134 pub fn col_x(&self, col: u32) -> f64 {
136 let j = Self::band_of(&self.col_bands, col);
137 let start = self.col_bands.get(j).map_or(0, |b| b.0);
138 self.page_x(j) + self.margins[0] + (self.ci.start(col) - self.ci.start(start))
139 }
140 pub fn row_y(&self, row: u32) -> f64 {
142 let i = Self::band_of(&self.row_bands, row);
143 let start = self.row_bands.get(i).map_or(0, |b| b.0);
144 self.page_y(i) + self.margins[2] + (self.ri.start(row) - self.ri.start(start))
145 }
146
147 pub fn page_col_at(&self, x: f64) -> usize {
149 let j = ((x - self.gap / 2.0) / (self.page_w + self.gap)).floor();
150 (j.max(0.0) as usize).min(self.col_bands.len().saturating_sub(1))
151 }
152 pub fn page_row_at(&self, y: f64) -> usize {
153 let i = ((y - self.gap / 2.0) / (self.page_h + self.gap)).floor();
154 (i.max(0.0) as usize).min(self.row_bands.len().saturating_sub(1))
155 }
156
157 pub fn col_at(&self, x: f64) -> u32 {
159 let j = self.page_col_at(x);
160 let Some(&(a, b)) = self.col_bands.get(j) else { return 0 };
161 let local = x - self.page_x(j) - self.margins[0];
162 if local <= 0.0 { a } else { self.ci.at(self.ci.start(a) + local).clamp(a, b) }
163 }
164 pub fn row_at(&self, y: f64) -> u32 {
165 let i = self.page_row_at(y);
166 let Some(&(a, b)) = self.row_bands.get(i) else { return 0 };
167 let local = y - self.page_y(i) - self.margins[2];
168 if local <= 0.0 { a } else { self.ri.at(self.ri.start(a) + local).clamp(a, b) }
169 }
170
171 pub fn page_at(&self, x: f64, y: f64) -> Option<(usize, usize)> {
173 let (i, j) = (self.page_row_at(y), self.page_col_at(x));
174 let (px, py, w, h) = self.page_rect(i, j);
175 (x >= px && x < px + w && y >= py && y < py + h).then_some((i, j))
176 }
177
178 pub fn on_cells(&self, x: f64, y: f64) -> bool {
180 let Some((i, j)) = self.page_at(x, y) else { return false };
181 let (cx, cy, w, h) = self.content_rect(i, j);
182 let (Some(&(_, c1)), Some(&(_, r1))) = (self.col_bands.get(j), self.row_bands.get(i)) else { return false };
183 let right = (self.col_x(c1) + self.ci.start(c1 + 1) - self.ci.start(c1)).min(cx + w);
185 let bottom = (self.row_y(r1) + self.ri.start(r1 + 1) - self.ri.start(r1)).min(cy + h);
186 x >= cx && x < right && y >= cy && y < bottom
187 }
188
189 pub fn hf_zone_at(&self, x: f64, y: f64) -> Option<((usize, usize), HfZone)> {
192 let (i, j) = self.page_at(x, y)?;
193 let (px, py, _, _) = self.page_rect(i, j);
194 let [_, _, t, b, ..] = self.margins;
195 let [l, r] = self.hf_sides;
196 let footer = if y < py + t {
197 false
198 } else if y >= py + self.page_h - b {
199 true
200 } else {
201 return None;
202 };
203 let inner = (self.page_w - l - r).max(3.0);
204 let section = (((x - px - l) / (inner / 3.0)).floor().max(0.0) as usize).min(2);
205 Some(((i, j), HfZone { footer, section }))
206 }
207
208 pub fn is_data_page(&self, i: usize, j: usize) -> bool {
210 (self.data_rows.0..=self.data_rows.1).contains(&i) && (self.data_cols.0..=self.data_cols.1).contains(&j)
211 }
212 pub fn page_number(&self, i: usize, j: usize) -> Option<(usize, usize)> {
214 if !self.is_data_page(i, j) {
215 return None;
216 }
217 let rows = self.data_rows.1 - self.data_rows.0 + 1;
218 let cols = self.data_cols.1 - self.data_cols.0 + 1;
219 Some((self.page_order.index(i - self.data_rows.0, j - self.data_cols.0, rows, cols) + 1, rows * cols))
220 }
221
222 pub fn pages_in(&self, x0: f64, y0: f64, x1: f64, y1: f64) -> ((usize, usize), (usize, usize)) {
224 ((self.page_row_at(y0), self.page_row_at(y1)), (self.page_col_at(x0), self.page_col_at(x1)))
225 }
226
227 pub fn extent(&self) -> (f64, f64) {
229 (self.page_x(self.col_bands.len()), self.page_y(self.row_bands.len()))
230 }
231
232 pub fn row_band(&self, i: usize) -> Option<(u32, u32)> {
234 self.row_bands.get(i).copied()
235 }
236 pub fn col_band(&self, j: usize) -> Option<(u32, u32)> {
237 self.col_bands.get(j).copied()
238 }
239}
240
241pub fn need_for(sh: &Sheet, x: f64, y: f64) -> (u32, u32) {
245 (sh.row_at(y.max(0.0)).saturating_add(1).min(MAX_ROWS - 1), sh.col_at(x.max(0.0)).saturating_add(1).min(MAX_COLS - 1))
246}
247
248#[cfg(test)]
249mod tests {
250 use crate::model::{Orientation, Sheet};
251
252 use super::*;
253
254 fn wb_with(rows: u32, cols: u32) -> Workbook {
255 let mut wb = Workbook::new();
256 if let Some(sh) = wb.sheet_mut(0) {
257 for r in 0..rows {
258 for c in 0..cols {
259 sh.set_value(CellRef::new(r, c), crate::base::Value::Number(1.0));
260 }
261 }
262 }
263 wb
264 }
265
266 #[test]
267 fn pages_match_print_pagination_and_cells_map_both_ways() {
268 let wb = wb_with(120, 14);
269 let g = PageGrid::build(&wb, 0, 400, 40).unwrap();
270 let printed = print::layout_sheet(&wb, 0, &PrintOptions::default(), None).unwrap();
271 let starts = |b: &[(u32, u32)], n: usize| b.iter().take(n).map(|x| x.0).collect::<Vec<_>>();
274 assert!(printed.row_bands.len() > 1 && printed.col_bands.len() > 1);
275 assert_eq!(starts(&g.row_bands, printed.row_bands.len()), starts(&printed.row_bands, usize::MAX));
276 assert_eq!(starts(&g.col_bands, printed.col_bands.len()), starts(&printed.col_bands, usize::MAX));
277 assert_eq!(g.data_rows, (0, printed.row_bands.len() - 1));
278 assert!(g.covers(400, 40));
279 for (r, c) in [(0, 0), (5, 3), (60, 9), (119, 13), (300, 30)] {
281 let (x, y) = (g.col_x(c) + 1.0, g.row_y(r) + 1.0);
282 assert_eq!((g.row_at(y), g.col_at(x)), (r, c), "cell ({r},{c})");
283 assert!(g.on_cells(x, y));
284 let (i, j) = g.page_at(x, y).unwrap();
285 let (cx, cy, _, _) = g.content_rect(i, j);
286 assert!(x >= cx && y >= cy);
287 }
288 let (a, _) = g.row_bands[1];
290 assert!((g.row_y(a) - (g.page_y(1) + g.margins[2])).abs() < 1e-6);
291 assert!(g.row_y(a) > g.page_h + g.gap);
292 }
293
294 #[test]
295 fn margins_gaps_and_header_zones() {
296 let mut wb = wb_with(10, 3);
297 if let Some(sh) = wb.sheet_mut(0) {
298 sh.print.orientation = Orientation::Landscape;
299 }
300 let g = PageGrid::build(&wb, 0, 50, 20).unwrap();
301 assert!(g.page_w > g.page_h, "landscape pages are wider than tall");
302 let (px, py, w, h) = g.page_rect(0, 0);
304 let mid_top = (px + w / 2.0, py + g.margins[2] / 2.0);
305 assert_eq!(g.hf_zone_at(mid_top.0, mid_top.1), Some(((0, 0), HfZone { footer: false, section: 1 })));
306 assert_eq!(g.hf_zone_at(px + g.margins[0] + 2.0, py + h - 2.0).map(|z| z.1), Some(HfZone { footer: true, section: 0 }));
307 assert_eq!(g.hf_zone_at(px + w - g.margins[1] - 2.0, py + 3.0).map(|z| z.1.section), Some(2));
308 assert!(!g.on_cells(mid_top.0, mid_top.1), "margins aren't cells");
309 assert_eq!(g.page_at(px + w + g.gap / 2.0, py + 10.0), None);
311 assert_eq!(g.hf_zone_at(px - 1.0, py + 3.0), None);
312 assert_eq!(g.page_number(0, 0), Some((1, 1)));
314 assert_eq!(g.page_number(0, 1), None);
315 assert!(g.is_data_page(0, 0) && !g.is_data_page(1, 0));
316 }
317
318 #[test]
319 fn page_numbers_follow_the_page_order() {
320 let mut wb = wb_with(60, 15);
322 let numbers = |wb: &Workbook| {
323 let g = PageGrid::build(wb, 0, 200, 60).unwrap();
324 assert_eq!((g.data_rows, g.data_cols), ((0, 1), (0, 1)));
325 assert_eq!((g.page_number(2, 0), g.page_number(0, 2)), (None, None));
326 [(0, 0), (1, 0), (0, 1), (1, 1)].map(|(i, j)| g.page_number(i, j).unwrap())
327 };
328 assert_eq!(numbers(&wb), [(1, 4), (2, 4), (3, 4), (4, 4)]);
329 wb.sheet_mut(0).unwrap().print.page_order = PageOrder::OverThenDown;
330 assert_eq!(numbers(&wb), [(1, 4), (3, 4), (2, 4), (4, 4)]);
331 }
332
333 #[test]
334 fn header_zones_and_text_follow_scale_with_document_and_align_with_page_margins() {
335 let mut wb = wb_with(10, 3);
336 if let Some(sh) = wb.sheet_mut(0) {
337 sh.print.scale = 50;
338 sh.print.margins = [2.0, 2.0, 0.75, 0.75, 0.3, 0.3];
339 }
340 let g = PageGrid::build(&wb, 0, 50, 20).unwrap();
341 let (px, py, _, _) = g.page_rect(0, 0);
342 assert_eq!(g.hf_sides, [g.margins[0], g.margins[1]]);
344 assert!((g.hf_text_k - 1.0).abs() < 1e-6);
345 assert_eq!(g.hf_zone_at(px + 1000.0, py + 3.0).map(|z| z.1.section), Some(2));
346 if let Some(sh) = wb.sheet_mut(0) {
347 sh.print.hf_scale_with_doc = false;
348 sh.print.hf_align_with_margins = false;
349 }
350 let g = PageGrid::build(&wb, 0, 50, 20).unwrap();
351 assert!(g.hf_sides.iter().all(|s| (s - 134.4).abs() < 1e-3), "{:?}", g.hf_sides);
354 assert!((g.hf_text_k - 2.0).abs() < 1e-6);
355 assert_eq!(g.hf_zone_at(px + 1000.0, py + 3.0).map(|z| z.1.section), Some(1));
356 }
357
358 #[test]
359 fn print_scale_and_hostile_settings_stay_bounded() {
360 let mut wb = wb_with(3, 3);
361 if let Some(sh) = wb.sheet_mut(0) {
362 sh.print.scale = 50;
363 sh.print.margins = [f32::NAN, 1e9, -4.0, f32::INFINITY, 0.0, 0.0];
364 sh.print.row_breaks = vec![0, 1, 2, u32::MAX];
365 }
366 let g = PageGrid::build(&wb, 0, MAX_ROWS, MAX_COLS).unwrap();
367 assert!(g.rows_complete && g.cols_complete);
368 assert!(g.page_w.is_finite() && g.page_h.is_finite() && g.page_w > 0.0);
369 let (ex, ey) = g.extent();
370 assert!(ex.is_finite() && ey.is_finite());
371 assert_eq!(g.row_at(ey * 2.0), MAX_ROWS - 1);
372 assert_eq!(g.col_at(-5.0), 0);
373 assert_eq!(need_for(&Sheet::new("S"), f64::MAX, f64::MAX), (MAX_ROWS - 1, MAX_COLS - 1));
374 assert!(PageGrid::build(&wb, 7, 0, 0).is_none());
375 }
376}