mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-28 12:46:48 -06:00
Headers and footers: printing follows the Scale with Document and Align with Page Margins check boxes
The Header & Footer tab now has the Scale with Document and Align with Page Margins check boxes, and pageLayout.headerFooter takes scaleWithDoc and alignWithMargins. XLSX already kept both settings, but nothing else read them. With Scale with Document on, header and footer text prints at the sheet's print scale. With it off, the text keeps its point size. With Align with Page Margins on, the left and right sections line up with the page's side margins. With it off, they start 0.7 inch from the paper's edges, or a quarter of the page width on paper narrower than 2.8 inches. The Page Layout view draws headers and footers and takes clicks on them the same way. Both settings are on by default, so a sheet printed at a scale other than 100% now prints its header and footer text at that scale.
This commit is contained in:
@@ -139,8 +139,26 @@ impl SheetLayout {
|
||||
let [l, r, t, b, ..] = self.margins;
|
||||
((self.page_w - l - r).max(36.0), (self.page_h - t - b).max(36.0))
|
||||
}
|
||||
/// Left and right margins, in points, that header and footer sections line up with: the
|
||||
/// page's side margins with Align with Page Margins on, otherwise 0.7 inch on each side, at
|
||||
/// most a quarter of the page width.
|
||||
pub(crate) fn hf_sides(&self) -> (f32, f32) {
|
||||
if self.settings.hf_align_with_margins {
|
||||
(self.margins[0], self.margins[1])
|
||||
} else {
|
||||
let side = HF_FIXED_SIDE_MARGIN.min(self.page_w / 4.0);
|
||||
(side, side)
|
||||
}
|
||||
}
|
||||
/// Scale of header and footer text: the print scale with Scale with Document on, otherwise 1.
|
||||
pub(crate) fn hf_text_scale(&self) -> f32 {
|
||||
if self.settings.hf_scale_with_doc { self.scale } else { 1.0 }
|
||||
}
|
||||
}
|
||||
|
||||
/// Side margin of headers and footers, in points, when Align with Page Margins is off.
|
||||
const HF_FIXED_SIDE_MARGIN: f32 = 0.7 * 72.0;
|
||||
|
||||
/// Options shared by the print commands.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PrintOptions {
|
||||
@@ -1476,7 +1494,9 @@ fn draw_headings(pg: &mut Page, b: &Block, col_heads: bool, row_heads: bool, at:
|
||||
}
|
||||
|
||||
fn draw_header_footer(pg: &mut Page, lay: &SheetLayout, which: gridcraft_model::HfPage, cx: &HfContext) {
|
||||
let [l, r, _, _, hm, fm] = lay.margins;
|
||||
let [_, _, _, _, hm, fm] = lay.margins;
|
||||
let (l, r) = lay.hf_sides();
|
||||
let text_scale = lay.hf_text_scale();
|
||||
let pw = lay.page_w;
|
||||
let ph = lay.page_h;
|
||||
for (spec, header) in [(lay.settings.hf(which, false), true), (lay.settings.hf(which, true), false)] {
|
||||
@@ -1488,7 +1508,7 @@ fn draw_header_footer(pg: &mut Page, lay: &SheetLayout, which: gridcraft_model::
|
||||
if sec.text.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
let size = sec.size.unwrap_or(10.0).clamp(1.0, 72.0);
|
||||
let size = sec.size.unwrap_or(10.0).clamp(1.0, 72.0) * text_scale;
|
||||
let font = Font::styled(gridcraft_pdf::Family::Sans, sec.bold, sec.italic);
|
||||
let lines: Vec<&str> = sec.text.split('\n').take(20).collect();
|
||||
let n = lines.len() as f32;
|
||||
@@ -2021,6 +2041,40 @@ mod tests {
|
||||
assert!(contains(&pdf, "(Odd 1) Tj") && contains(&pdf, "(Odd 2) Tj") && !contains(&pdf, "(Cover) Tj"));
|
||||
}
|
||||
|
||||
/// The x position and font size of the first `(text) Tj` in a PDF.
|
||||
fn text_at(pdf: &[u8], text: &str) -> Option<(f32, f32)> {
|
||||
let s = String::from_utf8_lossy(pdf);
|
||||
let before = &s[..s.find(&format!("({text}) Tj"))?];
|
||||
let tf = before.rfind(" Tf")?;
|
||||
let size = before[..tf].rsplit(' ').next()?.parse().ok()?;
|
||||
let tm = before.rfind(" Tm")?;
|
||||
let mut nums = before[..tm].rsplit([' ', '\n']);
|
||||
let _y = nums.next()?;
|
||||
let x = nums.next()?.parse().ok()?;
|
||||
Some((x, size))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headers_scale_with_the_sheet_and_align_with_its_margins_unless_turned_off() {
|
||||
let mut s = s();
|
||||
s.execute("cell.set", json!({"cell": "A1", "input": "x"})).unwrap();
|
||||
s.execute("pageLayout.pageSetup", json!({"scale": 50, "left": 1.5, "right": 1.5})).unwrap();
|
||||
s.execute("pageLayout.headerFooter", json!({"header": "&LLeft&C&14Middle"})).unwrap();
|
||||
// Both on (the default): the text prints at half size, from the 1.5-inch left margin.
|
||||
let (pdf, _) = pdf_of(&mut s, json!({}));
|
||||
assert_eq!(text_at(&pdf, "Left"), Some((108.0, 5.0)));
|
||||
assert_eq!(text_at(&pdf, "Middle").map(|t| t.1), Some(7.0));
|
||||
let r = s.execute("pageLayout.headerFooter", json!({"scaleWithDoc": false, "alignWithMargins": false})).unwrap();
|
||||
assert_eq!((&r["scaleWithDoc"], &r["alignWithMargins"]), (&json!(false), &json!(false)));
|
||||
// Both off: full size, 0.7 inch from the paper's left edge.
|
||||
let (pdf, _) = pdf_of(&mut s, json!({}));
|
||||
assert_eq!(text_at(&pdf, "Left"), Some((50.4, 10.0)));
|
||||
assert_eq!(text_at(&pdf, "Middle").map(|t| t.1), Some(14.0));
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
let pr = &s.doc().unwrap().wb.sheet(0).unwrap().print;
|
||||
assert!(pr.hf_scale_with_doc && pr.hf_align_with_margins);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pdf_prints_cjk_text_instead_of_question_marks() {
|
||||
let mut s = s();
|
||||
|
||||
@@ -115,7 +115,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Header & Footer",
|
||||
["Insert", "Text"],
|
||||
None,
|
||||
"{sheet?, header?: \"&C&P\", footer?: \"&CPage &P of &N\", differentFirst?: bool, differentOddEven?: bool, firstHeader?, firstFooter?, evenHeader?, evenFooter?} or one section: {sheet?, page?: odd|even|first, part: header|footer, section: left|center|right, text: \"Page &[Page] of &[Pages]\"} (field names or codes) → {header, footer, differentFirst, differentOddEven, firstHeader, firstFooter, evenHeader, evenFooter}",
|
||||
"{sheet?, header?: \"&C&P\", footer?: \"&CPage &P of &N\", differentFirst?: bool, differentOddEven?: bool, scaleWithDoc?: bool, alignWithMargins?: bool, firstHeader?, firstFooter?, evenHeader?, evenFooter?} or one section: {sheet?, page?: odd|even|first, part: header|footer, section: left|center|right, text: \"Page &[Page] of &[Pages]\"} (field names or codes) → {header, footer, differentFirst, differentOddEven, scaleWithDoc, alignWithMargins, firstHeader, firstFooter, evenHeader, evenFooter}",
|
||||
has_doc,
|
||||
header_footer
|
||||
),
|
||||
@@ -392,6 +392,12 @@ fn header_footer(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
if let Some(on) = bool_param(p, "differentOddEven") {
|
||||
pr.different_odd_even = on;
|
||||
}
|
||||
if let Some(on) = bool_param(p, "scaleWithDoc") {
|
||||
pr.hf_scale_with_doc = on;
|
||||
}
|
||||
if let Some(on) = bool_param(p, "alignWithMargins") {
|
||||
pr.hf_align_with_margins = on;
|
||||
}
|
||||
if let Some((page, footer, idx, text)) = §ion {
|
||||
let spec = pr.hf_mut(*page, *footer);
|
||||
let mut parts = super::print::split_header_footer(spec);
|
||||
@@ -405,6 +411,8 @@ fn header_footer(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
"footer": pr.footer,
|
||||
"differentFirst": pr.different_first,
|
||||
"differentOddEven": pr.different_odd_even,
|
||||
"scaleWithDoc": pr.hf_scale_with_doc,
|
||||
"alignWithMargins": pr.hf_align_with_margins,
|
||||
"firstHeader": pr.first_header,
|
||||
"firstFooter": pr.first_footer,
|
||||
"evenHeader": pr.even_header,
|
||||
|
||||
@@ -29,6 +29,12 @@ pub struct PageGrid {
|
||||
pub gap: f64,
|
||||
/// Print scale (1.0 = 100%): screen sizes of cells are `zoom × scale`.
|
||||
pub scale: f32,
|
||||
/// Left and right margins of headers and footers in layout units (the side margins, or a
|
||||
/// fixed 0.7 inch, at most a quarter of the page width, with Align with Page Margins off).
|
||||
pub hf_sides: [f64; 2],
|
||||
/// Layout units per sheet pixel of header and footer text: 1 with Scale with Document on,
|
||||
/// otherwise `1 / scale`, so the text keeps its point size on paper.
|
||||
pub hf_text_k: f32,
|
||||
/// Paper size in points (after orientation), for the rulers.
|
||||
pub paper_pt: (f32, f32),
|
||||
pub row_bands: Vec<(u32, u32)>,
|
||||
@@ -65,6 +71,8 @@ impl PageGrid {
|
||||
let s = f64::from(lay.scale.clamp(0.1, 4.0));
|
||||
let to_layout = |pt: f32| f64::from(pt) / f64::from(PX_TO_PT) / s;
|
||||
let margins = lay.margins.map(to_layout);
|
||||
let (hf_left, hf_right) = lay.hf_sides();
|
||||
let hf_text_k = lay.hf_text_scale() / s as f32;
|
||||
let band_range = |bands: &[(u32, u32)], lo: u32, hi: u32| {
|
||||
let first = bands.iter().position(|b| b.1 >= lo).unwrap_or(0);
|
||||
let last = bands.iter().rposition(|b| b.0 <= hi).unwrap_or(first).max(first);
|
||||
@@ -79,6 +87,8 @@ impl PageGrid {
|
||||
margins,
|
||||
gap: f64::from(PAGE_GAP) / s,
|
||||
scale: lay.scale,
|
||||
hf_sides: [to_layout(hf_left), to_layout(hf_right)],
|
||||
hf_text_k,
|
||||
paper_pt: (lay.page_w, lay.page_h),
|
||||
rows_complete: row_bands.last().is_some_and(|b| b.1 >= MAX_ROWS - 1),
|
||||
cols_complete: col_bands.last().is_some_and(|b| b.1 >= MAX_COLS - 1),
|
||||
@@ -174,11 +184,12 @@ impl PageGrid {
|
||||
}
|
||||
|
||||
/// The header or footer section under the point: the top or bottom margin of a page, split
|
||||
/// into thirds between the side margins.
|
||||
/// into thirds between the header and footer side margins.
|
||||
pub fn hf_zone_at(&self, x: f64, y: f64) -> Option<((usize, usize), HfZone)> {
|
||||
let (i, j) = self.page_at(x, y)?;
|
||||
let (px, py, _, _) = self.page_rect(i, j);
|
||||
let [l, r, t, b, ..] = self.margins;
|
||||
let [_, _, t, b, ..] = self.margins;
|
||||
let [l, r] = self.hf_sides;
|
||||
let footer = if y < py + t {
|
||||
false
|
||||
} else if y >= py + self.page_h - b {
|
||||
@@ -301,6 +312,31 @@ mod tests {
|
||||
assert!(g.is_data_page(0, 0) && !g.is_data_page(1, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_zones_and_text_follow_scale_with_document_and_align_with_page_margins() {
|
||||
let mut wb = wb_with(10, 3);
|
||||
if let Some(sh) = wb.sheet_mut(0) {
|
||||
sh.print.scale = 50;
|
||||
sh.print.margins = [2.0, 2.0, 0.75, 0.75, 0.3, 0.3];
|
||||
}
|
||||
let g = PageGrid::build(&wb, 0, 50, 20).unwrap();
|
||||
let (px, py, _, _) = g.page_rect(0, 0);
|
||||
// Both on: the sections sit between the 2-inch side margins and the text scales with the cells.
|
||||
assert_eq!(g.hf_sides, [g.margins[0], g.margins[1]]);
|
||||
assert!((g.hf_text_k - 1.0).abs() < 1e-6);
|
||||
assert_eq!(g.hf_zone_at(px + 1000.0, py + 3.0).map(|z| z.1.section), Some(2));
|
||||
if let Some(sh) = wb.sheet_mut(0) {
|
||||
sh.print.hf_scale_with_doc = false;
|
||||
sh.print.hf_align_with_margins = false;
|
||||
}
|
||||
let g = PageGrid::build(&wb, 0, 50, 20).unwrap();
|
||||
// Both off: 0.7 inch (67.2 sheet pixels, 134.4 layout units at 50%) on each side, and the
|
||||
// text keeps its point size on paper.
|
||||
assert!(g.hf_sides.iter().all(|s| (s - 134.4).abs() < 1e-3), "{:?}", g.hf_sides);
|
||||
assert!((g.hf_text_k - 2.0).abs() < 1e-6);
|
||||
assert_eq!(g.hf_zone_at(px + 1000.0, py + 3.0).map(|z| z.1.section), Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_scale_and_hostile_settings_stay_bounded() {
|
||||
let mut wb = wb_with(3, 3);
|
||||
|
||||
@@ -206,6 +206,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Footer" => "Zápatí",
|
||||
"Different First Page" => "Jiná první stránka",
|
||||
"Different Odd & Even Pages" => "Jiné liché a sudé stránky",
|
||||
"Scale with Document" => "Měnit velikost s dokumentem",
|
||||
"Align with Page Margins" => "Zarovnat k okrajům stránky",
|
||||
"Go to Footer" => "Přejít na zápatí",
|
||||
"Go to Header" => "Přejít na záhlaví",
|
||||
"Go to\nFooter" => "Přejít\nna zápatí",
|
||||
|
||||
@@ -210,6 +210,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Footer" => "Fußzeile",
|
||||
"Different First Page" => "Erste Seite anders",
|
||||
"Different Odd & Even Pages" => "Gerade und ungerade Seiten anders",
|
||||
"Scale with Document" => "Mit Dokument skalieren",
|
||||
"Align with Page Margins" => "An Seitenrändern ausrichten",
|
||||
"Go to Footer" => "Zur Fußzeile wechseln",
|
||||
"Go to Header" => "Zur Kopfzeile wechseln",
|
||||
"Go to\nFooter" => "Zur\nFußzeile",
|
||||
|
||||
@@ -202,6 +202,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Footer" => "Υποσέλιδο",
|
||||
"Different First Page" => "Διαφορετική πρώτη σελίδα",
|
||||
"Different Odd & Even Pages" => "Διαφορετικές μονές και ζυγές σελίδες",
|
||||
"Scale with Document" => "Κλιμάκωση με το έγγραφο",
|
||||
"Align with Page Margins" => "Στοίχιση με τα περιθώρια σελίδας",
|
||||
"Go to Footer" => "Μετάβαση στο υποσέλιδο",
|
||||
"Go to Header" => "Μετάβαση στην κεφαλίδα",
|
||||
"Go to\nFooter" => "Μετάβαση στο\nυποσέλιδο",
|
||||
|
||||
@@ -209,6 +209,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Footer" => "Pie de página",
|
||||
"Different First Page" => "Primera página diferente",
|
||||
"Different Odd & Even Pages" => "Páginas pares e impares diferentes",
|
||||
"Scale with Document" => "Escalar con el documento",
|
||||
"Align with Page Margins" => "Alinear con los márgenes de página",
|
||||
"Go to Footer" => "Ir al pie de página",
|
||||
"Go to Header" => "Ir al encabezado",
|
||||
"Go to\nFooter" => "Ir al pie\nde página",
|
||||
|
||||
@@ -4,6 +4,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
Some(match text {
|
||||
"Different First Page" => "Première page différente",
|
||||
"Different Odd & Even Pages" => "Pages paires et impaires différentes",
|
||||
"Scale with Document" => "Mettre à l'échelle avec le document",
|
||||
"Align with Page Margins" => "Aligner sur les marges de la page",
|
||||
"Paste" => "Coller",
|
||||
"Paste Values" => "Coller les valeurs",
|
||||
"Paste Formulas" => "Coller les formules",
|
||||
|
||||
@@ -327,6 +327,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Go to Header" => "Vai a intestazione",
|
||||
"Different First Page" => "Prima pagina diversa",
|
||||
"Different Odd & Even Pages" => "Pagine pari e dispari diverse",
|
||||
"Scale with Document" => "Ridimensiona con il documento",
|
||||
"Align with Page Margins" => "Allinea ai margini della pagina",
|
||||
"Go to Footer" => "Vai a piè di pagina",
|
||||
"Ruler" => "Righello",
|
||||
// File › Backstage view.
|
||||
|
||||
@@ -331,6 +331,8 @@ pub(super) const CORE: &[(&str, &str)] = &[
|
||||
("Go to Header", "ヘッダーに移動"),
|
||||
("Different First Page", "先頭ページのみ別指定"),
|
||||
("Different Odd & Even Pages", "奇数/偶数ページ別指定"),
|
||||
("Scale with Document", "文書に合わせて拡大/縮小"),
|
||||
("Align with Page Margins", "ページ余白に合わせる"),
|
||||
("Go to Footer", "フッターに移動"),
|
||||
("Ruler", "ルーラー"),
|
||||
("File", "ファイル"),
|
||||
|
||||
@@ -332,6 +332,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Go to Header" => "머리글로 이동",
|
||||
"Different First Page" => "첫 페이지를 다르게 지정",
|
||||
"Different Odd & Even Pages" => "짝수와 홀수 페이지를 다르게 지정",
|
||||
"Scale with Document" => "문서에 맞게 배율 조정",
|
||||
"Align with Page Margins" => "페이지 여백에 맞추기",
|
||||
"Go to Footer" => "바닥글로 이동",
|
||||
"Ruler" => "눈금자",
|
||||
// File › Backstage view.
|
||||
|
||||
@@ -326,6 +326,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Go to Header" => "Naar koptekst",
|
||||
"Different First Page" => "Eerste pagina afwijkend",
|
||||
"Different Odd & Even Pages" => "Even en oneven pagina's afwijkend",
|
||||
"Scale with Document" => "Meeschalen met document",
|
||||
"Align with Page Margins" => "Uitlijnen op paginamarges",
|
||||
"Go to Footer" => "Naar voettekst",
|
||||
"Ruler" => "Liniaal",
|
||||
"File" => "Bestand",
|
||||
|
||||
@@ -322,6 +322,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Go to Header" => "Przejdź do nagłówka",
|
||||
"Different First Page" => "Inna pierwsza strona",
|
||||
"Different Odd & Even Pages" => "Inne strony nieparzyste i parzyste",
|
||||
"Scale with Document" => "Skaluj z dokumentem",
|
||||
"Align with Page Margins" => "Wyrównaj do marginesów strony",
|
||||
"Go to Footer" => "Przejdź do stopki",
|
||||
"Ruler" => "Linijka",
|
||||
"File" => "Plik",
|
||||
|
||||
@@ -328,6 +328,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Go to Header" => "Ir para o Cabeçalho",
|
||||
"Different First Page" => "Primeira Página Diferente",
|
||||
"Different Odd & Even Pages" => "Páginas Pares e Ímpares Diferentes",
|
||||
"Scale with Document" => "Dimensionar com o Documento",
|
||||
"Align with Page Margins" => "Alinhar com as Margens da Página",
|
||||
"Go to Footer" => "Ir para o Rodapé",
|
||||
"Ruler" => "Régua",
|
||||
// File › Backstage view.
|
||||
|
||||
@@ -329,6 +329,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Go to Header" => "Salt la antet",
|
||||
"Different First Page" => "Prima pagină diferită",
|
||||
"Different Odd & Even Pages" => "Pagini pare și impare diferite",
|
||||
"Scale with Document" => "Scalare cu documentul",
|
||||
"Align with Page Margins" => "Aliniere la marginile paginii",
|
||||
"Go to Footer" => "Salt la subsol",
|
||||
"Ruler" => "Riglă",
|
||||
"File" => "Fișier",
|
||||
|
||||
@@ -332,6 +332,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Go to Header" => "Перейти к верхнему колонтитулу",
|
||||
"Different First Page" => "Особый колонтитул для первой страницы",
|
||||
"Different Odd & Even Pages" => "Разные колонтитулы для четных и нечетных страниц",
|
||||
"Scale with Document" => "Масштабировать вместе с документом",
|
||||
"Align with Page Margins" => "Выровнять по полям страницы",
|
||||
"Go to Footer" => "Перейти к нижнему колонтитулу",
|
||||
"Ruler" => "Линейка",
|
||||
// File › Backstage view.
|
||||
|
||||
@@ -325,6 +325,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Go to Header" => "Idi na zaglavlje",
|
||||
"Different First Page" => "Drugačija prva stranica",
|
||||
"Different Odd & Even Pages" => "Drugačije neparne i parne stranice",
|
||||
"Scale with Document" => "Skaliraj sa dokumentom",
|
||||
"Align with Page Margins" => "Poravnaj sa marginama stranice",
|
||||
"Go to Footer" => "Idi na podnožje",
|
||||
"Ruler" => "Lenjir",
|
||||
"File" => "Datoteka",
|
||||
|
||||
@@ -205,6 +205,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Footer" => "Altbilgi",
|
||||
"Different First Page" => "İlk Sayfada Farklı",
|
||||
"Different Odd & Even Pages" => "Tek ve Çift Sayfalarda Farklı",
|
||||
"Scale with Document" => "Belgeyle Ölçeklendir",
|
||||
"Align with Page Margins" => "Sayfa Kenar Boşluklarıyla Hizala",
|
||||
"Go to Footer" => "Altbilgiye Git",
|
||||
"Go to Header" => "Üstbilgiye Git",
|
||||
"Go to\nFooter" => "Altbilgiye\nGit",
|
||||
|
||||
@@ -325,6 +325,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Go to Header" => "Перейти до верхнього колонтитула",
|
||||
"Different First Page" => "Окремий колонтитул для першої сторінки",
|
||||
"Different Odd & Even Pages" => "Різні колонтитули для парних і непарних сторінок",
|
||||
"Scale with Document" => "Масштабувати разом із документом",
|
||||
"Align with Page Margins" => "Вирівняти за полями сторінки",
|
||||
"Go to Footer" => "Перейти до нижнього колонтитула",
|
||||
"Ruler" => "Лінійка",
|
||||
// File › Backstage view.
|
||||
|
||||
@@ -330,6 +330,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Go to Header" => "Đi tới đầu trang",
|
||||
"Different First Page" => "Trang đầu khác",
|
||||
"Different Odd & Even Pages" => "Trang chẵn lẻ khác nhau",
|
||||
"Scale with Document" => "Co giãn theo tài liệu",
|
||||
"Align with Page Margins" => "Căn theo lề trang",
|
||||
"Go to Footer" => "Đi tới chân trang",
|
||||
"Ruler" => "Thước",
|
||||
// File › Backstage view.
|
||||
|
||||
@@ -330,6 +330,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
"Go to Header" => "转至页眉",
|
||||
"Different First Page" => "首页不同",
|
||||
"Different Odd & Even Pages" => "奇偶页不同",
|
||||
"Scale with Document" => "随文档缩放",
|
||||
"Align with Page Margins" => "与页边距对齐",
|
||||
"Go to Footer" => "转至页脚",
|
||||
"Ruler" => "标尺",
|
||||
// File › Backstage view.
|
||||
|
||||
@@ -182,7 +182,8 @@ fn tr<'a>(app: &SheetApp, s: &'a str) -> &'a str {
|
||||
/// Section rectangles (left, centre, right) of a page's header or footer on screen.
|
||||
fn section_rects(geo: &Geo, pg: &PageGrid, (i, j): (usize, usize), footer: bool) -> [Rect; 3] {
|
||||
let (px, py, w, h) = pg.page_rect(i, j);
|
||||
let [l, r, t, b, ..] = pg.margins;
|
||||
let [_, _, t, b, ..] = pg.margins;
|
||||
let [l, r] = pg.hf_sides;
|
||||
let (y0, y1) = if footer { (py + h - b, py + h) } else { (py, py + t) };
|
||||
let inner = (w - l - r).max(3.0);
|
||||
let third = inner / 3.0;
|
||||
@@ -195,8 +196,8 @@ fn section_rects(geo: &Geo, pg: &PageGrid, (i, j): (usize, usize), footer: bool)
|
||||
out
|
||||
}
|
||||
|
||||
fn hf_font(geo: &Geo, size_pt: Option<f32>, bold: bool) -> FontId {
|
||||
let px = size_pt.unwrap_or(10.0).clamp(1.0, 72.0) * 96.0 / 72.0 * geo.z;
|
||||
fn hf_font(geo: &Geo, pg: &PageGrid, size_pt: Option<f32>, bold: bool) -> FontId {
|
||||
let px = size_pt.unwrap_or(10.0).clamp(1.0, 72.0) * 96.0 / 72.0 * pg.hf_text_k * geo.z;
|
||||
let px = px.clamp(4.0, 120.0);
|
||||
if bold { theme::ui_bold(px) } else { theme::ui_font(px) }
|
||||
}
|
||||
@@ -235,7 +236,7 @@ pub(crate) fn paint_headers_footers(app: &SheetApp, ui: &egui::Ui, p: &Painter,
|
||||
if spec.is_empty() {
|
||||
if hovered {
|
||||
let label = if footer { "Add footer" } else { "Add header" };
|
||||
p.text(rects[1].center(), Align2::CENTER_CENTER, tr(app, label), hf_font(geo, Some(10.0), false), Color32::from_gray(120));
|
||||
p.text(rects[1].center(), Align2::CENTER_CENTER, tr(app, label), hf_font(geo, &pg, Some(10.0), false), Color32::from_gray(120));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -255,7 +256,7 @@ pub(crate) fn paint_headers_footers(app: &SheetApp, ui: &egui::Ui, p: &Painter,
|
||||
if sec.text.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
let font = hf_font(geo, sec.size, sec.bold);
|
||||
let font = hf_font(geo, &pg, sec.size, sec.bold);
|
||||
let r = rects[k];
|
||||
let (anchor, x) = match k {
|
||||
0 => (Align2::LEFT_TOP, r.left()),
|
||||
@@ -326,8 +327,9 @@ pub fn start_editing(app: &mut SheetApp) {
|
||||
app.grid.hf_start = true;
|
||||
}
|
||||
|
||||
/// Turns Different First Page / Different Odd & Even Pages on or off, keeping the edit on the
|
||||
/// same page (which may now show another header).
|
||||
/// Turns a Header & Footer option (Different First Page, Different Odd & Even Pages, Scale with
|
||||
/// Document, Align with Page Margins) on or off, keeping the edit on the same page (which may
|
||||
/// now show another header).
|
||||
fn set_option(app: &mut SheetApp, key: &str, on: bool) {
|
||||
let Some(e) = app.grid.hf_edit.as_ref() else { return };
|
||||
let (sheet, page, zone) = (e.sheet, e.page, HfZone { footer: e.footer, section: e.section });
|
||||
@@ -446,7 +448,7 @@ fn editor(app: &mut SheetApp, ui: &mut egui::Ui, geo: &Geo, pg: &PageGrid) {
|
||||
let rects = section_rects(geo, pg, e.page, e.footer);
|
||||
let p = ui.painter().with_clip_rect(geo.cells);
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let font = hf_font(geo, Some(10.0), false);
|
||||
let font = hf_font(geo, pg, Some(10.0), false);
|
||||
let mut switch_to = None;
|
||||
for (k, r) in rects.iter().enumerate() {
|
||||
if !r.intersects(geo.cells) {
|
||||
@@ -893,11 +895,10 @@ pub fn ribbon_tab(app: &mut SheetApp, ui: &mut egui::Ui) {
|
||||
go_to(app, true);
|
||||
}
|
||||
sep(ui);
|
||||
let (first, odd_even) = app
|
||||
.session
|
||||
.active()
|
||||
.and_then(|d| d.wb.sheet(app.grid.hf_edit.as_ref().map_or(0, |e| e.sheet)).map(|s| (s.print.different_first, s.print.different_odd_even)))
|
||||
.unwrap_or_default();
|
||||
let (first, odd_even, scale_doc, align) =
|
||||
app.session.active().and_then(|d| d.wb.sheet(app.grid.hf_edit.as_ref().map_or(0, |e| e.sheet))).map_or((false, false, true, true), |s| {
|
||||
(s.print.different_first, s.print.different_odd_even, s.print.hf_scale_with_doc, s.print.hf_align_with_margins)
|
||||
});
|
||||
ui.vertical(|ui| {
|
||||
let (mut f, mut o) = (first, odd_even);
|
||||
if ui.checkbox(&mut f, tr(app, "Different First Page")).changed() {
|
||||
@@ -907,6 +908,15 @@ pub fn ribbon_tab(app: &mut SheetApp, ui: &mut egui::Ui) {
|
||||
set_option(app, "differentOddEven", o);
|
||||
}
|
||||
});
|
||||
ui.vertical(|ui| {
|
||||
let (mut sd, mut am) = (scale_doc, align);
|
||||
if ui.checkbox(&mut sd, tr(app, "Scale with Document")).changed() {
|
||||
set_option(app, "scaleWithDoc", sd);
|
||||
}
|
||||
if ui.checkbox(&mut am, tr(app, "Align with Page Margins")).changed() {
|
||||
set_option(app, "alignWithMargins", am);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn sep(ui: &mut egui::Ui) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! transform, and headers are edited in place, committed on a click elsewhere, and undoable.
|
||||
|
||||
use egui::{Event, Key, Modifiers, PointerButton, Pos2, pos2, vec2};
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use gridcraft_engine::Session;
|
||||
use gridcraft_engine::core::CellRef;
|
||||
use gridcraft_engine::model::SheetViewMode;
|
||||
@@ -163,6 +164,27 @@ fn the_first_page_edits_its_own_header_when_different_first_page_is_on() {
|
||||
assert_eq!(pr.header, "&LLeft", "the other pages keep their header");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scale_with_document_and_align_with_page_margins_are_check_boxes_on_the_header_and_footer_tab() {
|
||||
let mut h = fixture();
|
||||
gridcraft_ui_egui::paper_view::start_editing(h.state_mut());
|
||||
h.run_steps(4);
|
||||
let options = |h: &Harness| {
|
||||
let pr = &h.state().session.doc().unwrap().wb.active().unwrap().print;
|
||||
(pr.hf_scale_with_doc, pr.hf_align_with_margins)
|
||||
};
|
||||
assert_eq!(options(&h), (true, true));
|
||||
h.get_by_label("Scale with Document").click();
|
||||
h.run_steps(3);
|
||||
assert_eq!(options(&h), (false, true));
|
||||
h.get_by_label("Align with Page Margins").click();
|
||||
h.run_steps(3);
|
||||
assert_eq!(options(&h), (false, false));
|
||||
assert!(h.state().grid.hf_edit.is_some(), "the header stays in edit");
|
||||
h.state_mut().run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(options(&h), (false, true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_header_and_footer_switches_to_page_layout_view() {
|
||||
let mut h = fixture();
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ Sources: `crates/ui-egui/src` (ribbon, grid, editor, formula bar, dialogs, panes
|
||||
| Dark mode, system appearance | yes | yes | done (10-09); follows the system on first run, title-bar switch and File › Display theme (#194, #239) |
|
||||
| Backstage (File) view: Info, New with templates, Open recent, Export, Options | yes | yes (#174, 10-11): the File tab opens a full-window view (Esc or the back arrow return) with Home, New (blank + the three samples), Open (recent + Browse; the recent list tells when each workbook was last changed, 10-12), Info (location, properties, Protect Workbook), Save, Save As, Print (page preview, active sheet or workbook, fit to page, Page Setup), Export (PDF, CSV, web page), Close and Options (`file.options`: theme, language, formula bar, AutoSave, calculation) | no Account, Share or template gallery beyond the samples; Options applies at once (no OK/Cancel dialog) and holds only the settings GridCraft has |
|
||||
| Dark mode, system appearance | yes | yes | done (10-09) |
|
||||
| Page Layout view (paper view with rulers and header editing) | yes | yes (#329, 10-11): per-sheet view saved in XLSX, pages with margins and gaps on the print pagination, "Click to add data" pages, rulers (inches or centimetres), headers and footers drawn and edited in place with a Header & Footer tab | margins drag on the rulers (#792, with header and footer margins); header/footer pictures, Different First Page / Odd & Even missing (1–3 h) |
|
||||
| Page Layout view (paper view with rulers and header editing) | yes | yes (#329, 10-11): per-sheet view saved in XLSX, pages with margins and gaps on the print pagination, "Click to add data" pages, rulers (inches or centimetres), headers and footers drawn and edited in place with a Header & Footer tab, whose Scale with Document and Align with Page Margins set whether header and footer text follows the print scale and the side margins, on paper and in the view | margins drag on the rulers (#792, with header and footer margins); header/footer pictures, Different First Page / Odd & Even missing (1–3 h) |
|
||||
| Multiple windows per workbook | yes | no (#162) | see [hardware-parity.md](hardware-parity.md) |
|
||||
|
||||
## Grid interaction
|
||||
|
||||
Reference in New Issue
Block a user