From ec725a4e6b5476e6c58ac7acb3d5a0cdd986c188 Mon Sep 17 00:00:00 2001 From: Nikita Rakitin Date: Wed, 14 Oct 2026 01:10:03 +0300 Subject: [PATCH] Notes Page view: draw master text wrapped, as the PDF does pdf::master_text_pieces turns the text engine's layout of the notes master's header, date, footer and page number into per-run, per-line pieces scaled to the page; the Notes Page view paints them in their sizes and colours. Stacked on #583. --- crates/pdf/src/lib.rs | 48 ++++++++++++++++++++++++++++++++++++ crates/pdf/src/tests.rs | 8 ++++++ crates/ui-egui/src/sorter.rs | 11 +++++---- docs/gaps.md | 7 +++--- 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/crates/pdf/src/lib.rs b/crates/pdf/src/lib.rs index fca04d71..b3870222 100644 --- a/crates/pdf/src/lib.rs +++ b/crates/pdf/src/lib.rs @@ -233,6 +233,54 @@ pub fn master_text_style(pres: &Presentation, m: &deckcraft_model::Master, sh: & TextStyle { family, size: r.size.unwrap_or(12.0).clamp(1.0, 400.0), bold: r.bold == Some(true), italic: r.italic == Some(true), color } } +/// One laid-out piece of master text on a page of `paper` points: a run's text on one line, its +/// baseline start and its size, colour, weight and slant. +#[derive(Clone, Debug, PartialEq)] +pub struct TextPiece { + pub at: (f64, f64), + pub text: String, + pub size: f64, + pub color: deckcraft_model::Rgba, + pub bold: bool, + pub italic: bool, +} + +/// The header, date, footer and page number of `m` for page `n`, laid out by the text engine +/// (wrapped, mixed formatting) and scaled to `paper`, for drawing without glyph access (the +/// Notes Page view). +pub fn master_text_pieces(pres: &Presentation, m: &deckcraft_model::Master, paper: (f64, f64), n: usize) -> Vec { + let (kx, ky) = (paper.0 / pres.notes_size.width.max(1.0), paper.1 / pres.notes_size.height.max(1.0)); + let mut out = vec![]; + for pt in deckcraft_render::place_master_texts(pres, m, n) { + for run in &pt.layout.runs { + let Some(g) = run.glyphs.first() else { continue }; + let text: String = pt + .body + .paragraphs + .get(run.para) + .map(|p| p.runs.iter().map(|r| r.text.as_str()).collect::()) + .unwrap_or_default() + .chars() + .skip(run.chars.0) + .take(run.chars.1.saturating_sub(run.chars.0)) + .collect(); + if text.trim().is_empty() { + continue; + } + let p = pt.transform * kurbo::Point::new(g.1, g.2); + out.push(TextPiece { + at: (p.x * kx, p.y * ky), + text, + size: run.size * ky, + color: run.color, + bold: run.fake_bold || run.face.weight >= 600.0, + italic: run.fake_italic || run.face.italic, + }); + } + } + out +} + /// The header, date, footer and page-number text a master puts on page `n`, with where it goes, /// whether it's right-aligned and its style. Placeholders with no text are left out; the page /// number is always `n`. diff --git a/crates/pdf/src/tests.rs b/crates/pdf/src/tests.rs index 1af447ed..321ae28c 100644 --- a/crates/pdf/src/tests.rs +++ b/crates/pdf/src/tests.rs @@ -176,6 +176,14 @@ fn master_text_wraps_and_keeps_mixed_formatting() { assert!(h.runs.iter().any(|r| r.color == Rgba::rgb(200, 0, 0))); let num: String = placed[1].body.text(); assert_eq!(num, "4"); + // The same layout as pieces for the Notes Page view: on more than one line, scaled to paper. + let pieces = master_text_pieces(&p, &m, (306.0, 396.0), 4); + let mut ys: Vec = pieces.iter().map(|x| x.at.1.round() as i64).collect(); + ys.dedup(); + assert!(ys.len() >= 3, "two header lines and the number: {pieces:?}"); + assert!(pieces.iter().any(|x| x.bold && (x.size - 9.0).abs() < 0.5), "18 pt bold at half size"); + assert!(pieces.iter().any(|x| x.color == Rgba::rgb(200, 0, 0) && !x.bold)); + assert!(pieces.iter().any(|x| x.text == "4")); p.notes_master = Some(Arc::new(m)); assert!(export(&p, &PdfOptions { layout: PageLayout::Notes, ..Default::default() }).unwrap().starts_with(b"%PDF")); } diff --git a/crates/ui-egui/src/sorter.rs b/crates/ui-egui/src/sorter.rs index 2b426669..8cf333c5 100644 --- a/crates/ui-egui/src/sorter.rs +++ b/crates/ui-egui/src/sorter.rs @@ -132,11 +132,12 @@ pub fn notes_page(app: &mut SlideApp, ui: &mut Ui) { let srect = to_screen((slide_box.x0, slide_box.y0, slide_box.x1, slide_box.y1)); let sw = srect.width(); if let Some(m) = doc.notes_master.as_deref() { - for (r, text, right, st) in deckcraft_engine::pdf::master_texts(&doc, m, paper, idx + 1) { - let r = to_screen((r.x0, r.y0, r.x1, r.y1)); - let (at, align) = if right { (r.right_center(), egui::Align2::RIGHT_CENTER) } else { (r.left_center(), egui::Align2::LEFT_CENTER) }; - let c = Color32::from_rgba_unmultiplied(st.color.r, st.color.g, st.color.b, st.color.a); - ui.painter().text(at, align, text.lines().next().unwrap_or(""), theme::font((st.size as f32 * k).max(6.0)), c); + // Laid out by the text engine as on the PDF page: wrapped, each run in its size and colour. + for piece in deckcraft_engine::pdf::master_text_pieces(&doc, m, paper, idx + 1) { + let size = (piece.size as f32 * k).max(4.0); + let at = page.min + vec2(piece.at.0 as f32 * k, piece.at.1 as f32 * k - size * 0.8); + let c = Color32::from_rgba_unmultiplied(piece.color.r, piece.color.g, piece.color.b, piece.color.a); + ui.painter().text(at, egui::Align2::LEFT_TOP, &piece.text, theme::font(size), c); } } let ppp = ui.ctx().pixels_per_point(); diff --git a/docs/gaps.md b/docs/gaps.md index e3a1091b..711f357d 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -166,9 +166,9 @@ Detail in [animation-parity.md](animation-parity.md). 10–16 h; the pane is a * PDF) place the slide and notes where the notes master does and print its header, date, footer and page number in their own fonts, sizes and colours; PDF handouts print the handout master's. Notes Master view previews the slide in its slide-image placeholder. PDF pages lay master text - out with the text engine (wrapping, mixed formatting). Left: the on-screen Notes Page view still - draws it as one line per placeholder. -- **Estimate:** 0.5 h. + out with the text engine (wrapping, mixed formatting), and the Notes Page view draws the same + layout. Left: the view draws it in the interface font (sizes, colours and weights kept). +- **Estimate:** done for beta. ### Edit points, text warp, 3-D - **Evidence:** checklist rows "Edit points" (P1 missing), "Text warp transforms" (partial: all 40 @@ -262,6 +262,7 @@ Detail in [localization-parity.md](localization-parity.md). | Date | Change | Summary | |---|---|---| +| 2026-10-14 | trivial | Notes Page view draws master text wrapped, as the PDF does | | 2026-10-14 | trivial | PDF master header/footer text wraps and keeps mixed formatting | | 2026-10-14 | trivial | Notes Master slide preview; master header/footer text keeps its formatting | | 2026-10-14 | minor | Notes pages and PDF handouts follow the notes and handout masters |