1use super::header_footer::HF_FIXED_SIDE_MARGIN;
2use super::{
3 Anchor, CellRef, Json, MAX_COLS, MAX_PAGES, MAX_ROWS, Orientation, PX_TO_PT, PageOrder, PrintSettings, RangeRef, Result, Sheet, Visibility,
4 Workbook, bad, bool_param, split_sheet, str_param,
5};
6
7pub(super) const DENSE_BLOCK: u64 = 60_000;
9
10pub fn paper_size(name: &str) -> (f32, f32) {
14 known_paper(name).unwrap_or((612.0, 792.0))
15}
16
17pub(super) fn known_paper(name: &str) -> Option<(f32, f32)> {
21 let n = name.trim().to_ascii_lowercase().replace([' ', '-', '_', '(', ')'], "");
24 Some(match n.as_str() {
25 "letter" | "usletter" => (612.0, 792.0),
26 "legal" | "uslegal" => (612.0, 1008.0),
27 "a4" => (595.28, 841.89),
28 "a3" => (841.89, 1190.55),
29 "a5" => (419.53, 595.28),
30 "a6" => (297.64, 419.53),
31 "tabloid" | "11x17" => (792.0, 1224.0),
32 "ledger" => (1224.0, 792.0),
33 "executive" => (522.0, 756.0),
34 "b4" | "b4jis" => (728.50, 1031.81),
35 "b5" | "b5jis" => (515.91, 728.50),
36 "b6" | "b6jis" => (362.83, 515.91),
37 "b4iso" => (708.66, 1000.63),
38 "b5iso" => (498.90, 708.66),
39 "b6iso" => (354.33, 498.90),
40 "japanesepostcard" | "postcard" => (283.46, 419.53),
41 "japanesedoublepostcard" | "doublepostcard" => (419.53, 566.93),
42 "japaneseenvelope#3" | "chou#3" => (340.16, 666.14),
43 "japaneseenvelope#4" | "chou#4" => (255.12, 581.10),
45 "japaneseenvelopekaku#2" | "kaku#2" => (680.31, 941.10),
46 "japaneseenvelopekaku#3" | "kaku#3" => (612.28, 785.20),
47 _ => return None,
48 })
49}
50
51#[derive(Clone, Debug)]
55pub struct SheetLayout {
56 pub sheet: usize,
57 pub settings: PrintSettings,
58 pub page_w: f32,
59 pub page_h: f32,
60 pub margins: [f32; 6],
62 pub scale: f32,
63 pub area: RangeRef,
64 pub row_bands: Vec<(u32, u32)>,
65 pub col_bands: Vec<(u32, u32)>,
66 pub pages: Vec<(usize, usize)>,
68 pub title_rows: Option<(u32, u32)>,
69 pub title_cols: Option<(u32, u32)>,
70 pub heading_w: f32,
72 pub heading_h: f32,
73 pub manual_breaks: bool,
75}
76
77impl SheetLayout {
78 pub(super) fn k(&self) -> f32 {
79 PX_TO_PT * self.scale
80 }
81 pub(crate) fn avail(&self) -> (f32, f32) {
82 let [l, r, t, b, ..] = self.margins;
83 ((self.page_w - l - r).max(36.0), (self.page_h - t - b).max(36.0))
84 }
85 pub(crate) fn hf_sides(&self) -> (f32, f32) {
89 if self.settings.hf_align_with_margins {
90 (self.margins[0], self.margins[1])
91 } else {
92 let side = HF_FIXED_SIDE_MARGIN.min(self.page_w / 4.0);
93 (side, side)
94 }
95 }
96 pub(crate) fn hf_text_scale(&self) -> f32 {
98 if self.settings.hf_scale_with_doc { self.scale } else { 1.0 }
99 }
100}
101
102#[derive(Clone, Debug, Default)]
104pub struct PrintOptions {
105 pub all_sheets: bool,
106 pub range: Option<RangeRef>,
107 pub fit_to_page: Option<bool>,
108}
109
110impl PrintOptions {
111 pub(super) fn from_params(p: &Json, cmd: &str) -> Result<PrintOptions> {
112 let all_sheets = match str_param(p, "sheets") {
113 None | Some("active") => false,
114 Some("all") | Some("workbook") => true,
115 Some(other) => return Err(bad(cmd, format!("`sheets` must be active or all, not `{other}`"))),
116 };
117 let range = match str_param(p, "range").map(str::trim).filter(|r| !r.is_empty()) {
118 Some(r) => Some(RangeRef::parse(split_sheet(r).1).ok_or_else(|| bad(cmd, format!("not a range: `{r}`")))?),
119 None => None,
120 };
121 Ok(PrintOptions { all_sheets, range, fit_to_page: bool_param(p, "fitToPage") })
122 }
123}
124
125pub fn print_range(sh: &Sheet) -> Option<RangeRef> {
128 if let Some(a) = sh.print.print_area {
129 return Some(trim_full(sh, a));
130 }
131 let mut r = sh.used_range();
132 let mut grow = |a: &Anchor| {
133 let x = sh.col_left(a.cell.col) + a.dx as f64;
134 let y = sh.row_top(a.cell.row) + a.dy as f64;
135 let end = CellRef::new(sh.row_at(y + a.height.max(0.0) as f64), sh.col_at(x + a.width.max(0.0) as f64));
136 let ar = RangeRef::new(a.cell, end);
137 r = Some(r.map_or(ar, |u| u.union(&ar)));
138 };
139 for c in &sh.charts {
140 grow(&c.anchor);
141 }
142 for i in &sh.images {
143 grow(&i.anchor);
144 }
145 for s in &sh.shapes {
146 grow(&s.anchor);
147 }
148 r
151}
152
153fn trim_full(sh: &Sheet, r: RangeRef) -> RangeRef {
155 if !(r.is_full_cols() || r.is_full_rows()) {
156 return r;
157 }
158 match sh.used_range() {
159 Some(u) => r.intersection(&RangeRef::new(CellRef::new(0, 0), u.end)).unwrap_or(RangeRef::cell(r.start)),
160 None => RangeRef::cell(r.start),
161 }
162}
163
164pub(super) fn bands(start: u32, end: u32, size: impl Fn(u32) -> f32, avail: impl Fn(u32) -> f32, breaks: &[u32], cap: usize) -> Vec<(u32, u32)> {
167 let mut out = Vec::new();
168 let mut cur = start;
169 let mut used = 0.0f32;
170 let mut room = avail(start);
171 let mut line = start;
172 loop {
173 let sz = size(line);
174 let manual = line > cur && breaks.binary_search(&line).is_ok();
175 if line > cur && (manual || used + sz > room + 0.01) {
176 out.push((cur, line - 1));
177 if out.len() >= cap {
178 return out;
179 }
180 cur = line;
181 used = 0.0;
182 room = avail(line);
183 }
184 used += sz;
185 if line >= end {
186 break;
187 }
188 line += 1;
189 }
190 out.push((cur, end));
191 out
192}
193
194fn title_span(t: Option<(u32, u32)>, max: u32) -> Option<(u32, u32)> {
195 let (a, b) = t?;
196 let (a, b) = (a.min(b), a.max(b));
197 if a >= max {
198 return None;
199 }
200 Some((a, b.min(max - 1).min(a.saturating_add(499))))
202}
203
204pub fn layout_sheet(wb: &Workbook, si: usize, opts: &PrintOptions, range_override: Option<RangeRef>) -> Option<SheetLayout> {
206 let sh = wb.sheet(si)?;
207 let st = sh.print.clone();
208 let (mut pw, mut ph) = paper_size(&st.paper);
209 if st.orientation == Orientation::Landscape {
210 std::mem::swap(&mut pw, &mut ph);
211 }
212 let inch = |i: usize| st.margins.get(i).copied().filter(|v| v.is_finite()).unwrap_or(0.5).clamp(0.0, 20.0) * 72.0;
213 let mut margins = [inch(0), inch(1), inch(2), inch(3), inch(4), inch(5)];
214 for (a, b, total) in [(0usize, 1usize, pw), (2, 3, ph)] {
216 let sum = margins[a] + margins[b];
217 if total - sum < 72.0 {
218 let f = ((total - 72.0).max(0.0)) / sum.max(1.0);
219 margins[a] *= f;
220 margins[b] *= f;
221 }
222 }
223 let area = match range_override {
224 Some(r) => trim_full(sh, r),
225 None => print_range(sh).unwrap_or(RangeRef::cell(CellRef::new(0, 0))),
226 };
227 let title_rows = title_span(st.title_rows, MAX_ROWS);
228 let title_cols = title_span(st.title_cols, MAX_COLS);
229 let digits = format!("{}", area.end.row + 1).len() as f32;
230 let (heading_w, heading_h) = if st.headings { (10.0 + 7.0 * digits, sh.default_row_height.clamp(10.0, 60.0)) } else { (0.0, 0.0) };
231 let fit = match opts.fit_to_page {
232 Some(true) => Some((1u16, 1u16)),
233 Some(false) => None,
234 None => st.fit_to.filter(|(w, h)| *w > 0 || *h > 0),
235 };
236 let mut lay = SheetLayout {
237 sheet: si,
238 settings: st.clone(),
239 page_w: pw,
240 page_h: ph,
241 margins,
242 scale: (st.scale.clamp(10, 400) as f32) / 100.0,
243 area,
244 row_bands: vec![],
245 col_bands: vec![],
246 pages: vec![],
247 title_rows,
248 title_cols,
249 heading_w,
250 heading_h,
251 manual_breaks: fit.is_none(),
252 };
253 let paginate = |lay: &mut SheetLayout, manual: bool| {
254 lay.manual_breaks = manual;
255 (lay.row_bands, lay.col_bands) = split_bands(sh, lay, MAX_PAGES);
256 lay.pages = page_sequence(lay.row_bands.len(), lay.col_bands.len(), lay.settings.page_order, MAX_PAGES);
257 };
258 match fit {
259 Some((fw, fh)) => {
260 let total_w: f32 = (area.start.col..=area.end.col).map(|c| sh.col_width(c)).sum::<f32>() + heading_w;
261 let total_h: f32 = if area.height() > 200_000 {
262 f32::INFINITY
263 } else {
264 (area.start.row..=area.end.row).map(|r| sh.row_height(r)).sum::<f32>() + heading_h
265 };
266 let (aw, ah) = lay.avail();
267 let sw = if fw > 0 { aw * fw as f32 / (total_w * PX_TO_PT).max(1.0) } else { f32::INFINITY };
268 let sh_ = if fh > 0 { ah * fh as f32 / (total_h * PX_TO_PT).max(1.0) } else { f32::INFINITY };
269 lay.scale = sw.min(sh_).min(1.0).clamp(0.1, 1.0);
270 for _ in 0..40 {
271 paginate(&mut lay, false);
272 let ok_w = fw == 0 || lay.col_bands.len() <= fw as usize;
273 let ok_h = fh == 0 || lay.row_bands.len() <= fh as usize;
274 if (ok_w && ok_h) || lay.scale <= 0.1 {
275 break;
276 }
277 lay.scale = (lay.scale * 0.97).max(0.1);
278 }
279 }
280 None => paginate(&mut lay, true),
281 }
282 Some(lay)
283}
284
285pub(crate) fn page_sequence(rows: usize, cols: usize, order: PageOrder, cap: usize) -> Vec<(usize, usize)> {
289 let (outer, inner) = match order {
290 PageOrder::DownThenOver => (cols, rows),
291 PageOrder::OverThenDown => (rows, cols),
292 };
293 (0..outer)
294 .flat_map(|o| {
295 (0..inner).map(move |i| match order {
296 PageOrder::DownThenOver => (i, o),
297 PageOrder::OverThenDown => (o, i),
298 })
299 })
300 .take(cap)
301 .collect()
302}
303
304pub(crate) type Bands = Vec<(u32, u32)>;
306
307pub(crate) fn split_bands(sh: &Sheet, lay: &SheetLayout, cap: usize) -> (Bands, Bands) {
311 let k = lay.k();
312 let (aw, ah) = lay.avail();
313 let (aw, ah) = (aw / k, ah / k);
314 let st = &lay.settings;
315 let mut rb: Vec<u32> = if lay.manual_breaks { st.row_breaks.clone() } else { vec![] };
316 let mut cb: Vec<u32> = if lay.manual_breaks { st.col_breaks.clone() } else { vec![] };
317 rb.sort_unstable();
318 cb.sort_unstable();
319 let (title_rows, title_cols) = (lay.title_rows, lay.title_cols);
320 let title_h = |start: u32| -> f32 {
321 match title_rows {
322 Some((a, b)) if a < start => (a..=b.min(start - 1)).map(|r| sh.row_height(r)).sum(),
323 _ => 0.0,
324 }
325 };
326 let title_w = |start: u32| -> f32 {
327 match title_cols {
328 Some((a, b)) if a < start => (a..=b.min(start - 1)).map(|c| sh.col_width(c)).sum(),
329 _ => 0.0,
330 }
331 };
332 let area = lay.area;
333 let rows = bands(area.start.row, area.end.row, |r| sh.row_height(r), |start| (ah - lay.heading_h - title_h(start)).max(1.0), &rb, cap);
334 let cols = bands(area.start.col, area.end.col, |c| sh.col_width(c), |start| (aw - lay.heading_w - title_w(start)).max(1.0), &cb, cap);
335 (rows, cols)
336}
337
338pub fn layouts(wb: &Workbook, opts: &PrintOptions) -> Vec<SheetLayout> {
340 let mut out = Vec::new();
341 let mut pages = 0usize;
342 let sheets: Vec<usize> = if opts.all_sheets {
343 (0..wb.sheets.len()).filter(|i| wb.sheet(*i).is_some_and(|s| s.visibility == Visibility::Visible)).collect()
344 } else {
345 vec![wb.active_sheet()]
346 };
347 for si in sheets {
348 if pages >= MAX_PAGES {
349 break;
350 }
351 let ovr = if si == wb.active_sheet() { opts.range } else { None };
352 if opts.all_sheets && ovr.is_none() && wb.sheet(si).is_some_and(|s| print_range(s).is_none()) {
353 continue;
354 }
355 if let Some(mut l) = layout_sheet(wb, si, opts, ovr) {
356 l.pages.truncate(MAX_PAGES - pages);
357 pages += l.pages.len();
358 out.push(l);
359 }
360 }
361 if out.is_empty() && opts.all_sheets {
362 out.extend(layout_sheet(wb, wb.active_sheet(), opts, None));
363 }
364 out
365}
366
367pub(super) fn segments(titles: Option<(u32, u32)>, band: (u32, u32)) -> Vec<(u32, u32)> {
369 let mut v = Vec::new();
370 if let Some((a, b)) = titles
371 && a < band.0
372 {
373 v.push((a, b.min(band.0 - 1)));
374 }
375 v.push(band);
376 v
377}
378
379pub(super) fn seg_size(sh: &Sheet, seg: (u32, u32), rows: bool) -> f32 {
380 if rows { (seg.0..=seg.1).map(|r| sh.row_height(r)).sum() } else { (seg.0..=seg.1).map(|c| sh.col_width(c)).sum() }
381}