diff --git a/crates/pdf/src/lib.rs b/crates/pdf/src/lib.rs index b3de0ad0..7f316f90 100644 --- a/crates/pdf/src/lib.rs +++ b/crates/pdf/src/lib.rs @@ -260,6 +260,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 fc3747aa..609a4891 100644 --- a/crates/pdf/src/tests.rs +++ b/crates/pdf/src/tests.rs @@ -209,6 +209,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 7c7de869..64c20a2e 100644 --- a/crates/ui-egui/src/sorter.rs +++ b/crates/ui-egui/src/sorter.rs @@ -378,11 +378,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 1bd207af..c6fc83b0 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -250,9 +250,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 @@ -387,6 +387,7 @@ Detail in [localization-parity.md](localization-parity.md). | 2026-10-14 | minor | Size and Position dialog landed (position, size, rotation, scale, lock aspect, one undo). The dialog gap remains Font, Bullets and Numbering, Find/Replace, Replace Fonts, Action Settings, Print, Insert Object and the rest of the ~60 | | 2026-10-14 | minor | File › New has six original starter decks; a large template gallery is still open | | 2026-10-13 | minor | Artistic effects: 7 of 22 (blur, pencil grayscale, line drawing, film grain, cutout, glow edges, photocopy) applied, removed and round-tripped (`a14:imgLayer`) | +| 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 |