Merge PR #1527: engine: pageLayout.printTitles only touches the axes it is given

This commit is contained in:
aiongg
2026-10-22 02:35:30 +00:00
2 changed files with 49 additions and 4 deletions
+22 -4
View File
@@ -232,13 +232,31 @@ fn print_area(s: &mut Session, p: &Json) -> Result<Json> {
}
fn print_titles(s: &mut Session, p: &Json) -> Result<Json> {
let rows = str_param(p, "rows").and_then(RangeRef::parse).map(|r| (r.start.row, r.end.row));
let cols = str_param(p, "cols").and_then(RangeRef::parse).map(|r| (r.start.col, r.end.col));
// An absent key leaves that axis alone, and an empty or null value clears it — the same
// reading `pageLayout.pageSetup` gives `titleRows`/`titleCols`. Assigning `None` for a
// key that was never sent used to wipe the other axis.
let axis = |key: &str, is_cols: bool| -> Result<Option<Option<(u32, u32)>>> {
match p.get(key) {
None => Ok(None),
Some(Json::Null) => Ok(Some(None)),
Some(Json::String(t)) if t.trim().is_empty() => Ok(Some(None)),
Some(Json::String(t)) => match RangeRef::parse(t) {
Some(r) => Ok(Some(Some(if is_cols { (r.start.col, r.end.col) } else { (r.start.row, r.end.row) }))),
None => Err(bad("pageLayout.printTitles", format!("`{key}` is not a range: `{t}`"))),
},
Some(_) => Err(bad("pageLayout.printTitles", format!("`{key}` must be text like \"1:2\" or \"A:A\""))),
}
};
let (rows, cols) = (axis("rows", false)?, axis("cols", true)?);
edit(s, |cx| {
let si = cx.sheet_index();
let pr = &mut cx.sheet_mut(si)?.print;
pr.title_rows = rows;
pr.title_cols = cols;
if let Some(t) = rows {
pr.title_rows = t;
}
if let Some(t) = cols {
pr.title_cols = t;
}
Ok(Json::Null)
})
}
+27
View File
@@ -2961,6 +2961,33 @@ fn notes_comments_and_links_survive_a_json_save() {
assert_eq!(after.cells, before.cells);
}
/// Page Layout ▸ Print Titles: naming one axis must not clear the other. `print_titles` read
/// both `rows` and `cols` into `Option`s and assigned both unconditionally, so
/// `{"rows":"2:2"}` silently dropped the repeated columns from every printed page.
#[test]
fn print_titles_keeps_the_axis_it_is_not_given() {
let mut s = s();
s.execute("pageLayout.printTitles", json!({"rows": "1:1", "cols": "A:A"})).unwrap();
let titles = |s: &Session| {
let d = s.doc().unwrap();
let sh = d.wb.active().unwrap();
(sh.print.title_rows, sh.print.title_cols)
};
assert_eq!(titles(&s), (Some((0, 0)), Some((0, 0))));
// Only rows: the columns stay.
s.execute("pageLayout.printTitles", json!({"rows": "2:2"})).unwrap();
assert_eq!(titles(&s), (Some((1, 1)), Some((0, 0))), "rows alone must not clear the columns");
// Only columns: the rows stay.
s.execute("pageLayout.printTitles", json!({"cols": "B:B"})).unwrap();
assert_eq!(titles(&s), (Some((1, 1)), Some((1, 1))), "columns alone must not clear the rows");
// An empty value clears that axis on purpose.
s.execute("pageLayout.printTitles", json!({"cols": ""})).unwrap();
assert_eq!(titles(&s), (Some((1, 1)), None));
// A bad range is an error, not a silent wipe.
assert!(s.execute("pageLayout.printTitles", json!({"rows": "not a range"})).is_err());
assert_eq!(titles(&s), (Some((1, 1)), None));
}
/// Insert › Link sorts a target as Excel's dialog does: a sheet reference, a defined name or `#…` is
/// a place in this document, anything else an existing file or web page. A file saved as a
/// `location` opens nothing in Excel (#800).