From bd74b39a279f60e6c97fa34b5dea436eeb2e68fd Mon Sep 17 00:00:00 2001 From: Florian Purchess Date: Mon, 12 Oct 2026 09:47:01 +0200 Subject: [PATCH 01/50] feat: lay out stamps with Blank's engine, on a page that is the stamp's box, and paint them and write their one-page PDFs from stamp engines --- .claude/rules/layout-engine.md | 1 + src-tauri/layout/src/engine/display.rs | 8 + src-tauri/layout/src/engine/select.rs | 4 + src-tauri/layout/src/items/mod.rs | 43 +++++ src-tauri/layout/src/items/stamp.rs | 212 +++++++++++++++++++++++ src-tauri/layout/src/model.rs | 113 +++++++++++- src-tauri/layout/src/pdf/tags.rs | 3 +- src-tauri/layout/src/text.rs | 9 +- src-tauri/layout/tests/exact.rs | 95 +++++++++- src/engine/__fixtures__/contract.json | 29 ++++ src/engine/contract.test.ts | 18 +- src/engine/engine.ts | 16 ++ src/engine/wasm/blank_layout_bg.wasm | Bin 3421035 -> 3432630 bytes src/pdf/stampEngine.test.ts | 187 ++++++++++++++++++++ src/pdf/stampEngine.ts | 230 +++++++++++++++++++++++++ src/pdf/stamps.ts | 72 ++++++++ 16 files changed, 1030 insertions(+), 10 deletions(-) create mode 100644 src-tauri/layout/src/items/stamp.rs create mode 100644 src/pdf/stampEngine.test.ts create mode 100644 src/pdf/stampEngine.ts create mode 100644 src/pdf/stamps.ts diff --git a/.claude/rules/layout-engine.md b/.claude/rules/layout-engine.md index 4943965d..465b847a 100644 --- a/.claude/rules/layout-engine.md +++ b/.claude/rules/layout-engine.md @@ -58,6 +58,7 @@ The wasm API between Rust (`model.rs`, `wasm.rs`) and TypeScript (`flatten.ts`, - A heading's `text` item has `listed: true` when it is one of the document's headings (`isListed` in `src/markdown/headings.ts`: at the top, with text, on its first piece of text): those are the PDF's bookmarks and what tables of contents list. - A `toc` item (one position) is a table of contents: `title`, `depth` and its `entries` (`{level, text}`, the listed headings up to `depth`, which `flatten.ts` gives it). Its n-th entry is the n-th listed heading up to `depth`, so it holds no positions that could go stale. `items/toc.rs` lays out the title and the entries, as extras (not text of the document), leaving a column for the page numbers as wide as the widest number a page shows (`format_number` of the settings' style and start, up to page 999), so the numbers never change its lines. After paginating, `engine/toc.rs` (`set_toc_numbers`, next to the chapters) works out each entry's page label, as the footer's `{page}` writes it, and gives a new body version only to the pages showing an entry whose number changed. `display.rs` sets in the numbers, the leader dots up to them, and a link `#toc:` per entry (`TOC_LINK`), which the PDF makes a link to the heading (`toc_target`) and the editor follows on Ctrl+Click. The PDF tags it `TOC` > `TOCI` > `Link`. - A table of contents depends on other blocks than itself, which `flattenBlocks` otherwise never assumes: `pageSync` (`tocBlocks` in `src/editor/plugins/pageView.ts`) flattens the tables of contents again whenever `headingsSignature` (the listed headings' levels and texts, not their positions) changes. + - A `stamp` item is a stamp placed on a PDF's page (`items/stamp.rs`): an image (`src`, with `width` and `height` for its shape) contained in the box, centred, and `lines` under it in the body font at `size` pt, `align`ed left or `center`. It's drawn as an artifact, not text of the document. Only stamp engines (`src/pdf/stampEngine.ts`, one per stamp that looks different, `sharing()` the page view's fonts, with the user's marks as `blank-signing:signature`/`:initials`) lay it out, on settings with `stamp: true`: the page is the stamp's box, from `MIN_STAMP` (2 pt), without margins or bands. `boxes(0, 1)` gives what it shows (`Laid::extent`): its natural size, laid out on a 600 × 200 pt page. - A table cell has `paragraphs`, or `blocks` when it holds more: `text` blocks with their own `indent`, `marker` and `bars` (lists, quotes, code) and `image` blocks, fitted to the cell's width. - The engine clamps what it gets (sizes, margins, start numbers, columns) and never panics on odd input; a panic that is left is written to `console.error` before the instance traps. - **Pages:** `pageCount()`, `versions()`, and `bodyVersions()`/`bandVersions()`, one per page, which change only when a page's body or its header and footer change, so only those are painted again. `pageBody(page)` and `pageBands(page)` are the display lists (JSON strings of drawing ops), `page(page)` both; `bands(page)`, `bottoms()`, `glyphPath(font, glyph)` and `unitsPerEm(font)`. A glyph run's `font` is a font index: below the number of faces a face of a file, from `INSTANCE_BASE` (2^20) on an instance of a variable face at other coordinates (e.g. the bold of Noto Emoji). An index never changes its meaning, so a cache keyed by it stays right; the `fontFile*` calls count files, never instances. diff --git a/src-tauri/layout/src/engine/display.rs b/src-tauri/layout/src/engine/display.rs index ce66fc12..1ba64ec0 100644 --- a/src-tauri/layout/src/engine/display.rs +++ b/src-tauri/layout/src/engine/display.rs @@ -155,6 +155,7 @@ impl Engine { let unit = &laid.units[frag.unit]; let dx = left; let dy = frag.y - unit.top; + let first = ops.len(); for deco in &unit.decos { let part = match deco { Deco::Image { x, y, .. } => match item.content { @@ -316,6 +317,13 @@ impl Engine { } } } + // a stamp isn't text of the document, so all of it is drawn as + // an artifact + if matches!(item.content, Content::Stamp { .. }) { + for (_, part) in &mut ops[first..] { + *part = Part::Decoration; + } + } } ops } diff --git a/src-tauri/layout/src/engine/select.rs b/src-tauri/layout/src/engine/select.rs index 6f9bad11..4c342056 100644 --- a/src-tauri/layout/src/engine/select.rs +++ b/src-tauri/layout/src/engine/select.rs @@ -106,6 +106,10 @@ impl Engine { let laid = &self.laid[frag.item]; let unit = &laid.units[frag.unit]; let left = self.settings.margins.left; + // a stamp is as wide as what it shows + if let Some((x, w)) = laid.extent { + return (left + x, frag.y, w, unit.height); + } // an image is as wide as it is shown if let Some(crate::items::Deco::Image { x, w, .. }) = unit .decos diff --git a/src-tauri/layout/src/items/mod.rs b/src-tauri/layout/src/items/mod.rs index 9ee7b758..16dda7ff 100644 --- a/src-tauri/layout/src/items/mod.rs +++ b/src-tauri/layout/src/items/mod.rs @@ -2,6 +2,7 @@ //! pagination places, which are lines for text and rows for tables. mod signature; +mod stamp; mod table; mod toc; @@ -15,6 +16,7 @@ use crate::style::{MARKER_GAP, RULE}; use crate::text::{align_offset, alignment_of, TextBox}; pub use signature::{SignatureSpec, SIGNATURE_ROOM, SIGNATURE_SHARE}; +pub use stamp::StampSpec; use table::table_units; pub use table::{TableSpec, MAX_COLUMNS}; pub use toc::{leaders, number_text, TocLaid, TocLine}; @@ -158,6 +160,9 @@ pub struct Laid { pub missing: Vec, /// for a table of contents: where its page numbers go pub toc: Option, + /// for a stamp: where what it shows starts, from its left, and how + /// wide it is, which its box is (see `Engine::boxes`) + pub extent: Option<(f32, f32)>, } /// a cell of a table as laid out: where it is in the grid, and what of the @@ -252,6 +257,44 @@ impl Laid { indent, inner, ), + Content::Stamp { + src, + width: image_width, + height, + alt, + lines, + size, + align, + .. + } => { + // the whole page, which is the stamp's box + let mut laid = stamp::stamp_units( + fonts, + StampSpec { + src, + width: *image_width, + height: *height, + alt, + lines, + size: *size, + center: align.as_deref() == Some("center"), + }, + // as small as a stamp's page may be, not the room for a + // line of text + (right - indent).max(0.0), + room, + ); + if let Some((left, _)) = &mut laid.extent { + *left += indent; + } + for deco in &mut laid.units[0].decos { + *deco = deco.moved(indent, 0.0); + } + for (boxed, _) in &mut laid.extras { + boxed.x = indent; + } + laid + } Content::Image { src, width: image_width, diff --git a/src-tauri/layout/src/items/stamp.rs b/src-tauri/layout/src/items/stamp.rs new file mode 100644 index 00000000..647fe902 --- /dev/null +++ b/src-tauri/layout/src/items/stamp.rs @@ -0,0 +1,212 @@ +//! A stamp placed on a PDF's page: an image (the user's signature or +//! initials) contained in the stamp's box, and the lines under it, e.g. a +//! date or a place. A stamp engine's page is the stamp's box, so the item +//! fills it: the image as large as the room the lines leave, centred, and +//! the lines at the left or centred. + +use parley::Alignment; + +use super::{Deco, Laid, Role, Unit}; +use crate::fonts::Fonts; +use crate::model::{Text, TextKind}; +use crate::style::BODY; +use crate::text::TextBox; + +/// what a stamp shows, see `Content::Stamp` +pub struct StampSpec<'a> { + pub src: &'a str, + pub width: f32, + pub height: f32, + pub alt: &'a str, + pub lines: &'a [String], + pub size: f32, + pub center: bool, +} + +/// lays out a stamp in a box of `width` × `height` points; its lines are +/// extras, as they aren't text of the document. `Laid::extent` is where +/// what it shows starts and how wide it is, which is the stamp's own +/// width, e.g. its widest line. +pub fn stamp_units(fonts: &mut Fonts, spec: StampSpec, width: f32, height: f32) -> Laid { + let width = width.max(0.0); + let height = height.max(0.0); + let size = if spec.size > 0.0 { spec.size } else { BODY }; + let alignment = if spec.center { + Alignment::Center + } else { + Alignment::Start + }; + let mut extras = vec![]; + let mut lines_height = 0.0; + for line in spec.lines { + let text = Text { + pos: 0, + text: line.clone(), + style: TextKind::P, + size: Some(size), + ..Default::default() + }; + let boxed = TextBox::new(fonts, &text, width, alignment); + lines_height += boxed.height(); + extras.push((boxed, Role::Text)); + } + let mut decos = vec![]; + let mut image_height = 0.0; + // the edges of what it shows, from the left of the box + let (mut left, mut right) = (f32::INFINITY, f32::NEG_INFINITY); + let room = (height - lines_height).max(0.0); + if !spec.src.is_empty() && spec.width > 0.0 && spec.height > 0.0 && room > 0.0 { + // as large as the room allows, its shape kept + let scale = (width / spec.width).min(room / spec.height); + let (w, h) = (spec.width * scale, spec.height * scale); + let x = if spec.center || spec.lines.is_empty() { + (width - w) / 2.0 + } else { + 0.0 + }; + // in the middle of the room the lines leave + let y = (room - h) / 2.0; + decos.push(Deco::Image { + src: spec.src.to_string(), + alt: spec.alt.to_string(), + x, + y, + w, + h, + }); + left = left.min(x); + right = right.max(x + w); + // the image's room is the stamp's, but for the lines + image_height = room; + } + let mut top = image_height; + for (boxed, _) in &mut extras { + boxed.y = top; + top += boxed.height(); + let shown = boxed.layout.width(); + let x = if spec.center { + (width - shown) / 2.0 + } else { + 0.0 + }; + left = left.min(x); + right = right.max(x + shown); + } + let extent = if left <= right { + (left, right - left) + } else { + (0.0, 0.0) + }; + Laid { + units: vec![Unit { + height: top, + decos, + extras: 0..extras.len(), + ..Default::default() + }], + extras, + extent: Some(extent), + ..Default::default() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::fonts::repository_fonts; + + fn laid(src: &str, lines: &[String], center: bool, width: f32, height: f32) -> Laid { + let mut fonts = repository_fonts(); + stamp_units( + &mut fonts, + StampSpec { + src, + width: 300.0, + height: 100.0, + alt: "Signature", + lines, + size: 12.0, + center, + }, + width, + height, + ) + } + + fn image(laid: &Laid) -> (f32, f32, f32, f32) { + laid.units[0] + .decos + .iter() + .find_map(|deco| match deco { + Deco::Image { x, y, w, h, .. } => Some((*x, *y, *w, *h)), + _ => None, + }) + .unwrap() + } + + #[test] + fn contains_the_image_in_the_box_centred() { + // a wide box: as high as it, in the middle + let wide = laid("signature.png", &[], false, 600.0, 100.0); + let (x, y, w, h) = image(&wide); + assert_eq!((y, w, h), (0.0, 300.0, 100.0)); + assert!((x - 150.0).abs() < 0.01); + assert_eq!(wide.extent, Some((150.0, 300.0))); + assert!((wide.units[0].height - 100.0).abs() < 0.01); + // a tall box: as wide as it, its shape kept + let tall = laid("signature.png", &[], false, 60.0, 200.0); + let (x, y, w, h) = image(&tall); + assert_eq!((x, w), (0.0, 60.0)); + assert!((h - 20.0).abs() < 0.01); + assert!((y - 90.0).abs() < 0.01); + assert!((tall.units[0].height - 200.0).abs() < 0.01); + } + + #[test] + fn sets_the_lines_in_the_body_font_at_its_size() { + let lines = ["12 October 2026".to_string(), "Berlin".to_string()]; + let laid = laid("", &lines, false, 600.0, 200.0); + assert!(laid.units[0].decos.is_empty()); + assert_eq!(laid.extras.len(), 2); + let (first, second) = (&laid.extras[0].0, &laid.extras[1].0); + // a line each, one under the other, as high as 12 pt body text + assert_eq!(first.line_count(), 1); + let line = 12.0 * crate::style::NATURAL * 1.12; + assert!((first.height() - line).abs() < 0.01); + assert!((second.y - first.height()).abs() < 0.01); + assert!((laid.units[0].height - 2.0 * line).abs() < 0.01); + // as wide as the wider line, from the left + let (left, width) = laid.extent.unwrap(); + assert_eq!(left, 0.0); + let widest = first.layout.width().max(second.layout.width()); + assert!((width - widest).abs() < 0.01); + assert!(width < 200.0); + } + + #[test] + fn centres_the_lines() { + let lines = ["Berlin".to_string()]; + let laid = laid("", &lines, true, 600.0, 200.0); + let (left, width) = laid.extent.unwrap(); + assert!((left - (600.0 - width) / 2.0).abs() < 0.01); + } + + #[test] + fn leaves_the_lines_their_room_under_the_image() { + let lines = ["Ada Lovelace".to_string()]; + let laid = laid("signature.png", &lines, true, 300.0, 100.0); + let (_, y, _, h) = image(&laid); + let text = &laid.extras[0].0; + assert!(y + h <= text.y + 0.01); + assert!((text.y + text.height() - 100.0).abs() < 0.01); + assert!((laid.units[0].height - 100.0).abs() < 0.01); + } + + #[test] + fn lays_out_in_no_room_instead_of_panicking() { + for (width, height) in [(f32::NAN, 10.0), (-5.0, -5.0), (0.0, 0.0)] { + let laid = laid("signature.png", &["a".into()], true, width, height); + assert_eq!(laid.units.len(), 1); + } + } +} diff --git a/src-tauri/layout/src/model.rs b/src-tauri/layout/src/model.rs index ccaeece0..5640070c 100644 --- a/src-tauri/layout/src/model.rs +++ b/src-tauri/layout/src/model.rs @@ -60,6 +60,10 @@ pub struct Text { /// center, right or justify, none for left (see `alignment_of`) #[serde(default)] pub align: Option, + /// the size of its letters in points instead of its style's, with its + /// lines as much taller, e.g. a stamp's; the webview never sends it + #[serde(skip)] + pub size: Option, } /// an entry of a table of contents: a heading's level and text @@ -334,6 +338,33 @@ pub enum Content { #[serde(default)] share: f32, }, + /// a stamp placed on a PDF's page: an image, e.g. the user's signature + /// or initials, contained in the page, and lines of text under it. + /// Only a stamp engine's page (`Settings::stamp`) holds one; it isn't + /// text of the document, so the PDF tags it as an artifact + Stamp { + pos: u32, + /// the image, empty for none + #[serde(default)] + src: String, + /// the image's size in points, for its shape; 0 while it isn't + /// loaded + #[serde(default)] + width: f32, + #[serde(default)] + height: f32, + #[serde(default)] + alt: String, + #[serde(default)] + lines: Vec, + /// the size of the lines' letters, in points; 0 for the body's + #[serde(default)] + size: f32, + /// "center" centres the image and the lines, anything else sets the + /// lines at the left + #[serde(default)] + align: Option, + }, Image { pos: u32, src: String, @@ -603,6 +634,10 @@ pub struct Settings { pub start_number: i64, #[serde(default)] pub fields: Fields, + /// the page is a stamp's box (see `Content::Stamp`): as small as + /// MIN_STAMP, without margins, headers or footers + #[serde(default)] + pub stamp: bool, } fn arabic() -> String { @@ -652,6 +687,8 @@ fn finite(value: f32, fallback: f32, min: f32, max: f32) -> f32 { /// and the largest page a PDF can have (200 inches) pub const MIN_PAGE: f32 = 36.0; pub const MAX_PAGE: f32 = 14_400.0; +/// the smallest page of a stamp, in points: a stamp may be tiny +pub const MIN_STAMP: f32 = 2.0; impl Default for Settings { fn default() -> Self { @@ -674,6 +711,7 @@ impl Default for Settings { number_style: arabic(), start_number: 1, fields: Fields::default(), + stamp: false, } } } @@ -681,11 +719,22 @@ impl Default for Settings { impl Settings { /// the settings as the engine can lay out with them, whatever JS handed /// over: a page of MIN_PAGE to MAX_PAGE points, margins that leave - /// MIN_PAGE of it for the text, and a number style it knows + /// MIN_PAGE of it for the text, and a number style it knows; a stamp's + /// page from MIN_STAMP on, without margins or bands pub fn sanitize(&mut self) { let default = Settings::default(); - self.width = finite(self.width, default.width, MIN_PAGE, MAX_PAGE); - self.height = finite(self.height, default.height, MIN_PAGE, MAX_PAGE); + let min = if self.stamp { MIN_STAMP } else { MIN_PAGE }; + self.width = finite(self.width, default.width, min, MAX_PAGE); + self.height = finite(self.height, default.height, min, MAX_PAGE); + if self.stamp { + // the stamp is the whole page + self.margins = Margins::default(); + self.header = Slots::default(); + self.footer = Slots::default(); + self.first_page = FirstPage::default(); + self.even_pages = None; + self.new_page_before.clear(); + } let margins = &mut self.margins; for margin in [ &mut margins.top, @@ -697,7 +746,7 @@ impl Settings { } // opposite margins shrink alike until the text has its room let fit = |a: &mut f32, b: &mut f32, size: f32| { - let room = size - MIN_PAGE; + let room = size - min; if *a + *b > room { let scale = room / (*a + *b); *a *= scale; @@ -734,6 +783,7 @@ impl Item { | Content::Boxed { pos, .. } | Content::Toc { pos, .. } | Content::Signature { pos, .. } + | Content::Stamp { pos, .. } | Content::Image { pos, .. } => *pos, Content::Table { pos, .. } => *pos, } @@ -748,6 +798,7 @@ impl Item { | Content::Boxed { pos, .. } | Content::Toc { pos, .. } | Content::Signature { pos, .. } + | Content::Stamp { pos, .. } | Content::Image { pos, .. } => pos.saturating_add(1), Content::Table { end, .. } => *end, } @@ -799,6 +850,7 @@ impl Item { | Content::Boxed { pos, .. } | Content::Toc { pos, .. } | Content::Signature { pos, .. } + | Content::Stamp { pos, .. } | Content::Image { pos, .. } => moved(pos), Content::Table { pos, end, rows, .. } => { moved(pos); @@ -870,6 +922,22 @@ impl Item { truncate(hint, MAX_LABEL); *share = finite(*share, 0.0, 0.0, 1.0); } + Content::Stamp { + width, + height, + alt, + lines, + size, + .. + } => { + image_size(width, height); + truncate(alt, MAX_LABEL); + lines.truncate(MAX_SIGNATURE_LINES); + for line in lines { + truncate(line, MAX_LABEL); + } + *size = finite(*size, 0.0, 0.0, MAX_STAMP_SIZE); + } Content::Toc { title, depth, @@ -947,6 +1015,8 @@ pub const MAX_LABEL: usize = 500; pub const MAX_TOC_ENTRIES: usize = 10_000; /// the lines a signature block has under its line, at most pub const MAX_SIGNATURE_LINES: usize = 8; +/// the largest letters of a stamp, in points +pub const MAX_STAMP_SIZE: f32 = 288.0; /// cuts `text` to at most `max` characters fn truncate(text: &mut String, max: usize) { @@ -1133,7 +1203,15 @@ mod tests { .unwrap(); let items: Vec = serde_json::from_value(sent["items"].clone()).unwrap(); let settings: Settings = serde_json::from_value(sent["settings"].clone()).unwrap(); - let read = serde_json::json!({ "items": items, "settings": settings }); + // and what a stamp engine gets (src/pdf/stampEngine.ts) + let stamp = &sent["stamp"]; + let stamp_items: Vec = serde_json::from_value(stamp["items"].clone()).unwrap(); + let stamp_settings: Settings = serde_json::from_value(stamp["settings"].clone()).unwrap(); + let read = serde_json::json!({ + "items": items, + "settings": settings, + "stamp": { "items": stamp_items, "settings": stamp_settings }, + }); let (mut wanted, mut found) = (Vec::new(), Vec::new()); keys(&sent, "", &mut wanted); keys(&read, "", &mut found); @@ -1149,5 +1227,30 @@ mod tests { "keys the engine doesn't read: {missing:?}" ); assert!(items.iter().any(|item| item.bars_continue)); + assert!(stamp_settings.stamp); + assert!(matches!(stamp_items[0].content, Content::Stamp { .. })); + } + + #[test] + fn a_stamp_page_is_its_box() { + let mut settings = Settings { + width: 1.0, + height: 30.0, + stamp: true, + ..Settings::default() + }; + settings.header.left = "{title}".into(); + settings.sanitize(); + assert_eq!((settings.width, settings.height), (MIN_STAMP, 30.0)); + assert_eq!(settings.margins, Margins::default()); + assert!(!settings.header.has_text()); + assert_eq!(settings.content_width(), MIN_STAMP); + // a document's page keeps its least size and margins + let mut page = Settings { + width: 1.0, + ..Settings::default() + }; + page.sanitize(); + assert_eq!(page.width, MIN_PAGE); } } diff --git a/src-tauri/layout/src/pdf/tags.rs b/src-tauri/layout/src/pdf/tags.rs index 404e821b..4df01efa 100644 --- a/src-tauri/layout/src/pdf/tags.rs +++ b/src-tauri/layout/src/pdf/tags.rs @@ -275,7 +275,8 @@ fn item_node(engine: &Engine, index: usize, ids: &mut Ids) -> Option { Some(group(Tag::Div, children)) } Content::Toc { entries, .. } => Some(toc_node(index, entries.len(), ids)), - Content::Break { .. } | Content::Rule { .. } => None, + // a stamp isn't text of the document: it is drawn as an artifact + Content::Break { .. } | Content::Rule { .. } | Content::Stamp { .. } => None, } } diff --git a/src-tauri/layout/src/text.rs b/src-tauri/layout/src/text.rs index 6d92624c..2ddddf36 100644 --- a/src-tauri/layout/src/text.rs +++ b/src-tauri/layout/src/text.rs @@ -171,7 +171,14 @@ impl TextBox { alignment: Alignment, tabs: &[(std::ops::Range, f32)], ) -> TextBox { - let style = text_style(text.style); + let mut style = text_style(text.style); + // its own size, with lines and letter spacing as much larger + if let Some(size) = text.size.filter(|size| *size > 0.0) { + let scale = size / style.size; + style.size = size; + style.line *= scale; + style.tracking *= scale; + } let len = utf16_len(&text.text); let laid_text = if text.text.is_empty() { " ".to_string() diff --git a/src-tauri/layout/tests/exact.rs b/src-tauri/layout/tests/exact.rs index 4434caa7..97511604 100644 --- a/src-tauri/layout/tests/exact.rs +++ b/src-tauri/layout/tests/exact.rs @@ -21,6 +21,7 @@ fn text_item(pos: u32, text: &str, style: &str, level: u8, spans: Vec) -> hint: None, picture: false, align: None, + size: None, }), indent: 0.0, before: if level > 0 { 16.0 } else { 0.0 }, @@ -210,8 +211,11 @@ fn settings() -> Settings { settings } -/// the words pdftotext finds in a PDF: page, xMin, yMin, xMax, yMax, text -fn read_words(pdf: &std::path::Path) -> Option> { +/// a word pdftotext finds in a PDF: page, xMin, yMin, xMax, yMax, text +type PdfWord = (usize, f32, f32, f32, f32, String); + +/// the words pdftotext finds in a PDF +fn read_words(pdf: &std::path::Path) -> Option> { let out = pdf.with_extension("html"); let status = match Command::new("pdftotext") .arg("-bbox") @@ -1048,3 +1052,90 @@ fn pdf_tags_links_around_their_text() { let links = structure.matches("Link (inline)").count(); assert_eq!(links, 6 * 8, "{links} Links"); } + +/// a stamp engine's item: lines of text, centred, at 14 pt +fn stamp(lines: &[&str]) -> Item { + Item { + content: Content::Stamp { + pos: 0, + src: String::new(), + width: 0.0, + height: 0.0, + alt: String::new(), + lines: lines.iter().map(|line| line.to_string()).collect(), + size: 14.0, + align: Some("center".into()), + }, + indent: 0.0, + before: 0.0, + after: 0.0, + marker: None, + bars: vec![], + bars_continue: false, + page_start: false, + column: None, + frame: None, + flow_top: 0.0, + } +} + +#[test] +fn the_pdf_of_a_stamp_is_its_box() { + let mut engine = Engine::new(repository_fonts()); + // laid out as the webview finds its natural size, then on its box + let mut settings = Settings { + width: 600.0, + height: 200.0, + stamp: true, + ..Default::default() + }; + engine.set_settings(settings.clone()); + engine.set_items(vec![stamp(&["12 October 2026", "Berlin"])]); + let (page, x, y, width, height) = engine.boxes(0, 1)[0]; + assert_eq!(page, 0); + assert!(y.abs() < 0.01 && width > 50.0 && width < 200.0); + assert!((x - (600.0 - width) / 2.0).abs() < 0.01); + let line = 14.0 * blank_layout::style::NATURAL * 1.12; + assert!((height - 2.0 * line).abs() < 0.01); + settings.width = width.ceil(); + settings.height = height.ceil(); + engine.set_settings(settings); + assert_eq!(engine.pages.len(), 1); + // the lines stay unbroken in a box as wide as the wider one + assert_eq!(engine.words().len(), 4); + let pdf = pdf_of(&mut engine); + let path = std::env::temp_dir().join("blank-layout-stamp.pdf"); + std::fs::write(&path, &pdf).unwrap(); + let Some(info) = run("pdfinfo", &[path.to_str().unwrap()]) else { + return; + }; + assert!(info.contains("Pages: 1"), "{info}"); + let size = format!("Page size: {} x {} pts", width.ceil(), height.ceil()); + assert!(info.contains(&size), "{info}"); + compare(&mut engine, "stamp"); +} + +#[test] +fn the_pdf_tags_a_stamp_as_an_artifact() { + let mut engine = Engine::new(repository_fonts()); + engine.set_settings(Settings { + width: 120.0, + height: 30.0, + stamp: true, + ..Default::default() + }); + engine.set_items(vec![stamp(&["Berlin"])]); + let pdf = pdf_of(&mut engine); + let path = std::env::temp_dir().join("blank-layout-stamp-tags.pdf"); + std::fs::write(&path, &pdf).unwrap(); + let Some(content) = run( + "qpdf", + &["--qdf", "--object-streams=disable", path.to_str().unwrap(), "-"], + ) else { + return; + }; + // its text is drawn, but marked as an artifact, not as content with a + // place in the structure + assert!(content.contains("/Artifact"), "no artifact"); + assert!(!content.contains("/MCID"), "tagged as content"); +} diff --git a/src/engine/__fixtures__/contract.json b/src/engine/__fixtures__/contract.json index e894fd92..ad401d9b 100644 --- a/src/engine/__fixtures__/contract.json +++ b/src/engine/__fixtures__/contract.json @@ -609,5 +609,34 @@ "date": "1 January 2026", "file": "contract" } + }, + "stamp": { + "items": [ + { + "kind": "stamp", + "pos": 0, + "src": "blank-signing:initials", + "width": 60, + "height": 30, + "alt": "Initials", + "lines": [ + "12 October 2026", + "Berlin" + ], + "size": 9, + "align": "center" + } + ], + "settings": { + "width": 120, + "height": 60, + "margins": { + "top": 0, + "right": 0, + "bottom": 0, + "left": 0 + }, + "stamp": true + } } } diff --git a/src/engine/contract.test.ts b/src/engine/contract.test.ts index 62175d26..e841b705 100644 --- a/src/engine/contract.test.ts +++ b/src/engine/contract.test.ts @@ -27,6 +27,7 @@ import { tr, ul, } from "../test/editor"; +import { stampItem, stampSettings } from "../pdf/stamps"; import { settingsOf } from "./engine"; import { flatten } from "./flatten"; @@ -194,7 +195,22 @@ describe("the engine's contract", () => { now: new Date("2026-01-01T00:00:00Z"), locale: "en-GB", }); - const sent = { items, settings: settingsOf(layout, fields) }; + // and a stamp engine's page: initials over two lines + const stamp = { + items: [ + stampItem( + { + image: "initials", + lines: ["12 October 2026", "Berlin"], + size: 9, + align: "center", + }, + { source: { kind: "image" }, png: "", width: 60, height: 30 }, + ), + ], + settings: stampSettings(120, 60), + }; + const sent = { items, settings: settingsOf(layout, fields), stamp }; expect(items.some((item) => item.barsContinue)).toBe(true); await expect(JSON.stringify(sent, null, 2) + "\n").toMatchFileSnapshot( "./__fixtures__/contract.json", diff --git a/src/engine/engine.ts b/src/engine/engine.ts index 97c7293c..2126b838 100644 --- a/src/engine/engine.ts +++ b/src/engine/engine.ts @@ -458,6 +458,22 @@ export class PageEngine { }); } + /** + * setContent lays out `items` on the page of `settings`, as model.rs reads + * them, e.g. a stamp's (src/pdf/stampEngine.ts), which aren't flattened + * from a document: the next sync starts over + */ + setContent(settings: object, items: readonly object[]) { + this.call(undefined, () => { + this.doc = null; + this.records = []; + this.blockStarts = []; + this.settings = ""; + this.raw.setSettings(JSON.stringify(settings)); + this.raw.setItems(JSON.stringify(items)); + }); + } + /** * sync hands the engine what changed in the document since the last sync: * with `changes`, only the top-level blocks they touch are flattened diff --git a/src/engine/wasm/blank_layout_bg.wasm b/src/engine/wasm/blank_layout_bg.wasm index bdd88e8687a2a4fbeb1b2d8d73beb4da917c076e..016c24753335c9b725314715879f10e22abe0587 100644 GIT binary patch delta 546487 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ℜLeWJwfK$p$H|4;g7A!$|(L`i%P6IB2 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

cc!J_?B;sP_v%?Wk)^8mxz}XlZg3Ho+r`fOT@(* zB&)YKP_KNRM4T?3BN4O0V>f2ibU|+kvht3!9eW@V=NDHJai9OHMC^8}h(x^4-pQCb z60uu(aS<~|A|~|<5s5trm*Cc&E2Z_?s~+ctOK__#-sNnmm>8mhJRwzGZC)LefO2%$gdD3Z-a0$#l^ zS!cm$vPJ>)_%uYAw5rfi6Ys*uAOi9j1fnt`@+^P>1u|@2kf7MeIgOKi9ss7knN< z#%G{lRCt8Lq}v`&L~yV-6}CY!O-X838oajhDL|r`bG4!N?Y}d8{Y$J;mm+-pKcqvb<7? z6cLnf0XgsayQ3@{w|n4K;*3JpGieBuyEd#ua1Cw)r(KL2oCTo zMH!Uzx~D=r0HBjXI|cySn4V-u%q+BHe^8(u)IV9wI?mSd;UWoknrcGDItD*rE&D`a z_%IWL$Gd&%lkOAn$m`R=s!vIDs&8(mygo_sD#My4$LI2>FauGa*kDMg;P$C?MxSJV za3v}_1eR;DY@P)!grG9ePLmIw(= z2OxqbEk;ck)g^9@O?X}x=*+25(J@H?7>-r#LSUJq`3Ez4l0!sAGGpCAwD~`bk@*Oj ztevjM$9SW35mOGHWqEo9NTsF6JbN$bVFi>%EZ`(caXiM+?2eFc5ZM=}ML`Gf?FbW8 zjzZ>Y@s9YdCK68U6FE^s?!i<;O9V`-ltC^@>>w$GVx*ARK{XNXLF@=XmV(&Pn#B%| znNx`vF-q*H9SWEGD!Y~%CX%&#{~U<&$#RTMx=OgZ)kFY6^F8e<1#&^YD*PiYYb9YM zsF8{&DN%@=b+A_vq$o-I20ZRjPR0Tzy(^GAT?gq~)&Ig+q?e234Uy8raAbt&8nl1u z6I=A)9oPagy9c&-m&=Y4gAjpAM+#SRc}~mB)sgN{M`;iok_3rHa4a^;O->&ordN)u zBgLTthjpZr(uasC!;m~3DKcOJK{l|SsdBc^v(HUuS4a9jPMwg?O=sJr*`e%iHt!Je z`x!dY+-gucd~V*w{nsS>t6Eq`sv7fZc{f`}x?e906dO2TTO^_ibsT!0I=+0qmv53^ zoOWEKI$6OZf{K#5B5?$BG|cRaIf^K+7Npd%A%|B%VI7f23-|}Q8B#=+ypT4oVIQsrb%@JC<3vX~c^&CQ z9oer)N0K;&$UVt*w}79H)Vuu}Jc0A&ZnLD9lVIkUL#!?X8gYke-3}>r8FnWO3bzjx z1T%Cc3ejs_M6VS`pNO!=|9f6OB%9{N$VoyB%n2hT^=&Z){X$A!IyNaa?r>ydXy9yt z#1jP#qMl3_3gH|!vgv-8mK=Cu#EMk7K&klO1_}w@dk2(qlqjStt{~zKlDj+9PV12C zc%Nd}<*d|w>?piPcC%Cqpf;M_15!cc+-=q$@2XI{FLB4%CNlb+%ndV@p^vVk^W1XM zb2{F;2<{V+CkmbJw`8NRB++c4ekZ-?R+7e{0#VNMnMX zO8YTh+r1Pc-gAZ!>%Nz9a0jF3xX1{_ti2!gUjASv6I2xhl#xp zXR`OLU{y%Ms?y5k@8P~cc7(om6$=E)%3vug1K||D!WhrTw88-h5K^%e^At>wA2Pz` zw?+0KA}Z&e&1x^vjLv2y5wpG=*&?bDiueHLm7NwbQ6#3qGDON1%nms_4#v;X(%J-{ zM_>V=7*NTsrt_^V>|plE^$`u2r7S~*Y2;zn5@E%8=hEo9R0=d9uL!Wqa48RjTo#8* z?KULokrd;G!GMC#n}1qavUxgghp>pA`WyYEYc+Agb_>xDb;34|J>1Iz5CCYI6ZESg zMr&k0!*p!Us9`IPSn^7jc%(o8?4mZDmLfKsT<^jHHk@Kb*$dh;ps15^xn7OM3X@{{ z*KIh(N|O(PXWH<(knh!4tUM{H(hNxCIO&2OhVsS%rSqQHaPo^Q8%~%1stw2ORuLP{Uit~<*l^s+ ziyK68Y&e|&OJc?Lz=q@2ooi3%_@CKu+-i$=IopPlDAwbA8;)CbW{6O%eh zC}kkqK}R3<^pjwxSjkOlNkl+!HIz|6g~C)zZ&2wwid*u;qJ~Jj!=&33aZzbKBt7h* z9umxe5~Nf#uuJSnt{!rW(nA79o*okAa`liHIte!kI{$&YmIgvI$Fo6)0~<*Xi42~v zW{`X}Jv}5~9dW}=fg=M5n_&P9AUdEgpauL;N=GQ56Nj)yfUpr@|)707`a)~Y#Vo;cp`b&+-?nHyMp~lAD<5G=Hic;3A=8UIlZ-_*v00lx97GtS;s04C|6mIL`3MK*P##=!IiPX?yfN26-I$486qmWCFouRs69l*K^ z_7!ogDY%}B*G^bYiWqrrkFb*6PF)>dPA!O*4^7l=su?_9)g_}v9xlD=21~8Aw2vrW zdUxZ7WRK5PB%VmUa8~A|6mwblCRx|nxZZV)%z;!I8i>H6pixa^tZNZyK(Z;O-I1DN z>Ij9$ET|@Mr;F6ZX^8=(RYq~aw4_79#E5v3KcDAonFH!BMYe}>uyay*9TZQfosJJ?;ig z@}h)W|5&8SK#kYPq8JqtSC zN@)eKVM4P)=NxfEB)N4;Cr+9^FdI-J{afl4GRG5o6Ct53kGLi1S8ySlCxf|)1dVN| zt#v-XT87I41JVhETwch|?_)bZy2kc>mpg==9r0G#^(NoGzZLO{uzO4g@n>x_?Mt*| z*}e}gb}!#O2ZSmC@wv<1(>7AsA9 zE>DrN8;w!1@~rIg6fpT3qhjS)&*hcN*BCVw(-@J8E3Kl+*BBLRkW^dVK)r_ZG)C#- zIT|BYaqUK9lrHE|E7lm1hHDSJe)EefjnRKMn3CJAA{wLrESQp8d2#hij>d?zZ_L0T z*4!fERB$Oc zj0)u^mbfD|nSc6J+l|J^tty9aD5^0^P>=Gxe%)#^M?gJ_7w(bN7`cV=+AgdyB7GS6 zQgulhg;}I4xnLxu>(MlXB*jZ zsJEV#M1EhHkD~=k<1ttssU$*WF04T%$?rT{;+EcHu@wy z0x8ZSZaib@%$Y*mvTGp;D|Q>3Ke$mE2=j_^(^m{9*a=#lSPFT@p9<3Rz>Y%qs`0H! z`S+^PRp0I%(Pa9`x>(IW%%so}h}1-TIam=qfM5Cby_{5vyH{X=TVlbikQNKKJ?CDo z28lI&xtfG1sjTSwEb0)FV4zuLg)8gHyyfTECQ`#AIYJfi9NcMIZDrZR_gHTQmp{*w zMtSGu#XL_C4_7GSxl%)AhCBy`VOlF(B@|&YUDGyR-N|QHc>1S`O#PDv{S(;Iih8J; zw!U=3Z0MdUpnD=s%~szf=VgaHN1f#3`11Qyyw}Sdam%5cuQr^SHNxsz0jb_fdDA0LW=8_@I@z(Er z?7l@b8aEl_py(=sIgMnzU&e2z=9?7!LK-AX1Q(ahBT%o~JSS!I!Hjt2)I5j=<)Y>} zDVyh{Y#vF`GAYt7Swu&n_<8b!U8~fWm9(Te_PnPEg7#0oaA$_(Dw-J;ksNr7MTj-2 zqLO1&@W><8tXwI;Vvw9p5VES3x(F#x>%=&Uyh`f?201GVR#r8`5GQr_wcrK?S(Q{e z^_7B1vJY!lAXJtUA}Vcg5!H>e^fOoZ&)qU3-gSbx0~}^1cGEMCQ2NS%l@PMId06Y>#^aU4aN!4agbP3Tdr*;}bTWXPMOy-E4cJQn*~l*-drLr%J&yn> zSj$McwP|pqRSC73ssx;Z@c3DBxK|O5AoW(M5Jv3en&%6+1r#gFN2NRo ztB6}bvBGSx&MaI|$SYQw_-0S`@oLF;3n*5;3ys>7nev6aV&#bYO_$3T@*2M>66}as zUa!G?A+K12#QS;!^~&c7dFkRgLLM`=yAkrz1wHI!A&)rRJrMHpiz^||{;#?PxZNru ziQdG`WbYCTQ49@Y-x_80Udox--n&dz7PA~^)P@${QvI5h7^NIg7o*lb079HCGxZ) z36qS7d=8iekpp4GfuO)5utWlwb@t>o>^c!&p|w6vU2yIdCCu8*_175;Hpi0%6d+*a zT(sSetA;4SxPq$fY+0w1yzbiG?XfU&4uT~rQ#c^mAjURK*e~rESuk=Ld|YNV!33p& zxsGq>0Ber})gC_qiw<$SG2*EaSLWZcq%rxFhBP7vil@dBXM+)K^OE)?cV}kIh_IZN z4O$bK$QD>K#zPm56dq1t5g|!_UF3#L2tzhs$i0a$AKWbFcuj|&)Oc`S%^MF?REm}s z6Lm5?2fX1VDFoRQ6yG;U{l;98tvpdmw@8L<{FEkQ#G%MgH`GzO5l{Z2!wCFC9gcB_ zk=vJGeg>JOySO@pawXX$9iC9wtTM%;WX9MtE)xRA{xN|_Z$1*LT-Wo66T1}d4-uyn z*C;tx>r6Joys+y1i#55EEn-Wr%!8~{f+X}^+5}XDzfS4Ii=h&TO}rRjg7PxZ;P}Uw zp|%uQ7{P~W0_YCRGO`o9*Re|vJkVl0yffnSk@r|*;hMoC(Y=99^b7pMaZIc~;1YXXU``QbXfqQn!d%jc0uDE8 zJS$S4!DtOhUmgqeC&HMU(xP*dflR0fiUHgVLl#LjJ48j2IC@DT2o4C8!j~M$DM$j3 zC`tG5a%fqmTT+AoB|)pyuSP%X7zkZr%i%T@blMR<;LBv_n=xlZ2vD+xE9B%NZibUf zki^=?_`3y`Azgze(cK2gG%{Kf8mDyjER4I~hRnL31rX#qg29q`h* zYBb&qL^>cZmg#`U8oPsgsSVWINtLO;T~8V)TaOzCWW^G@3*Xzd(l3*UthrVt5%_iq*SG9*>+40L2zT&Ek_bgUYAs!XS~pv&cj zo)AIMZG+aA!Rrf!7Y7qM6~$M%z~Ejn!tPxgH|%3M(1m?yZTxuqWSKssHh!#qj7%R~ z8$ZTAPNoOd#*ee%IW-~P`w06W`t44G?%;gw!+Cx(DF;W>(nHVUHdbrUwn@91Fq|@6 z=H^t7EAb4H1{F3MjHv`!k%2R_=*4CiOSm=`QX_ zA;7@QT56Z}@_{GEO-TcNHIi@*#YM{!U^EzlAfX-G2!C&GcK0Mz}&*%fA;o4yAGZb@kg=7W_;GI=f!pN@7tp|?5UOh`oXw<4T zyQDiUz!M1m>7q&MNV+V|4vL@;h030oT{g35X7@9T;+88yV_UU;5;WVO=a^$B;B3K_ zc1c&9AveUr&;=o;N*GlNLRZ1|myj~4Fznz?<%@%$pe0|tH~zH?un-_c0SE&U?ifD+ z&qGcG=tgE}J;)}5TMSf#v`WYPT<$RD>ox$*{SEp3O-E^Qiy z^TXS>lP52~WzW|#;%}*3Rw=_RMH{HiJ1#n()SPSSY%Kf0wTBBZHViJRYUNT?E4+G1 z*BpqwpeNeHW+TZ*APZLk2|{KpV0552I;V2uJrkQi7FJ=Ub@wL;C9%KvrpkMhliQCJ zs$V0Xhq?m}78?e8zx|Z^p@aIMPHK83Fxl;iK1~3K-Rhvyrs}{4pcJOY4bYfOe^8yp z92e*;mLV^nlhD@=QH}>mvr|8o8>`q-o%}?|?Wx$eO@9w!l zyWJRf&+W&1Za?00JJ6mRaQEDR+H;e<=`y{d%WiQn!EQ$~7q#zh!LVIrH{XYv@5{{_ zICdh*9eKLCyD#^EUSs#wsa7Cy#_mguK|A_4vKEf2!_?-AZHISi5W)2JO1az&a`)|! zwfiRHI$?LZvlJ>hdow+edV8wZ+w{J>5m}SjdC#D{XUV+nGR%lT$qs7c?U1waHf3$R zk{c6!(%h2$RXx{CPk@umxS-~qv`_a!}&>`$F z{o*xnE6{0CX0GoHen4(RAcyIUk~1KquPVbk^N-BSP)4dB47Sc930Gf|!~>2vYtB+^ zuI@^W*U$n-X6#Z&%DOT*5{fWDLm@r^oX}xD{FfN#4i5Z8TObVzXE6n!d0Zp90p@5Z zs74J~-@wEqQ4?(COwsiCI9dr4;sc8B?o#Y=H`@Zd6Z%WA{{>ozcstJ{f%{9_g)cq; zREUmB)zbz)E@xV(+gPQ0y0N<;4b#TX(>OgKRy`E2!iB}|mJTY3BN(AlUkN6&hPG$i zGA_608rq)2?nX=ToPy5h>ux7U2!6XmZra!$(M?&8zr+^+Ro;C)CAf1ruObt33PFkP zUZ0}2a8cJBIjv#<_G;%&dPVoPV)7$mP;;afKH`{|fOxVw(w?@x8bU_inQGt$;qFWs zyk{CC8EtuM*S+nmUoh!t0Fi?jW~fFY{jn11#{vTt)?=*L*6N9(GIhQh=yqIOxCT2k3-y3n3ogJp@PN%9?u z1qBhZ10JRD>m+q-PdVEjcJl!5+_bpSByV>*^Y(IE6E{2->7liVNh8`;i{+0+Xy+s5 zO-J-=?Cmu;>5p3FqQsiS5|-t#w>kbC>cFIfslK~!Q?$wKJqmOK0VgFThZ)QzQyvwl zIg4t^8wa*;&ID(lyzyUPCgXI7#X??cC21T>&&P=Av zhtZ^~n$Gg3CsHQ~E0{*l$dEVDxGP#N`7}?ogeyaikh5HgkjqGf1o@f{8axELti~lc z#l>PSs3JZsW2ur+1vGIjt|(6u9VGJoo|Hn6iL=)Gm$_oyT6_To=bfH+g%LD(rsepZ z8i3k?(4o73T@XqO8c%>W4EjV6;q?()18n0`m<<2K2K)E{&~xG%2#^JcPSju7uIeK7 zXe}60#mJ&&Rbzs5F0vv+DS!&0QKop+T`mfPkScyS@Cnmw5Iz~er=$gI8M@z{5Xz7d zF9LRVSv9}FM`SFOS~sL{w>)iIz-5R#%mn+u-%BPK-y{=EEI-E@_mGs70zmri6ef)H z1JGxZrUv>!soe+wV~AM`*!5%3Q|8~GGGexlR{!{3KA%JS6SPK|=-)`zpD2-6W=2*& zXC+rKA2LCG!WmEzn(u}0n)*JB_5>(m)0abO8LtNIMyb_s5h#awGU$9q)yBi6G>0XZl8_#UQy-pQ7u? z#@j>OdG^EXZ;;$H=+1zrr1T9Fw}GeFj0z}ugP2!npY5R3`35QKe0-GB^cr@OFbjHd z^qq9MtcwBANf%>}0zW?b@r$3F3|%1!2q5@u6m$XcpvzmixG9fX6BvjI!7)x63C}jy zU(zkGHh4gQ7O9Q&Mx<9J@cMlvox5%Gbsq=%2|wRNGpq1OMqLfjy0&jGL$7m zKyoFLrAUq@^(#<=kjRi8wj~4-EU%?Odz4Ns@C=qX3z$WsH8wQ~%9@rI!PLnPQ_p+j z-%!;yL@m`lfU`ZRJbz8&iI&GFKwzXKsx+SMLETjZ8q#$*R}D|vaoYO_V!bHsZf~qU ze7br#y+7Ptio~e~?Ou*=ggul#^tXFq6{7NPb~jeCu>{zS1Y6zi!f^3ZKrT+MMY2>T zHzFC6$t_6MBxQOV(zZ9;*J z%=9~8pl>q$jvL@_q~Cu1{X233v*{}%e~-`~H9 ze(U@DH_|VVdL8{@(_Txz9s2v%;1>rINcv_DlX(ZQ=d@jcgkGj8boJFgMdOzJc)ZO zaTa02?BYz11h*fj(l4Q0R9;&(JW8DPdG{9;9nR z2Ye$K%5lj4dc9S~5FG7rmtHeY$+>bItvs9+M3vJ5L_Iwd2SF5d+ufj((1G?P4u%!m zfHeRjX=}EY#`qE&G_RE8{24T>=nw6s)FDPw%sNdjrUaW0v4)r=Xpne%HvkB6Ehv6z zVcxL&{79F7(j{_(AOi5@%bGU~}6bt(BNC z-RgYj8MWP08|Ft2?NIsj5Pz3s;A%`$QwTd3{~Qn6%p1bbVh^T013i7l%m9kttpu_kh0^6wZey0Vn_ybJ&=4K@IJ z8I&1y&#~|dCt@2C<#sC!3nk0xrvk;#L&SzwvbQrWyS4!rc(tO}4RMRvM#Kv^HQ@&u zQzy4u@Lb04Hmd{>z^4;aZ?1{7*Ai$I1^GZUamt*Pq9^+>JU^EZ`a6+M+IG?Boyarz z`RtuY`@^`IYP%B6N8+WJYS^XG7#@*0F%bgjz(>jn+Ger0ZYbdg5r~IW6P7sg-N?~( zTP@q7-LyWo3_B@8!||55-I6Uo^KK-*FZHn0t_H(|4F&ycvhWBflp{M1*ft>wHWqnn zKFrYwEv19mXpmW065UEPekmL-0 z?EU>G!c88EGa|P*eKS+TD46KtVGgE)wmJGR10OI9q?+(n@xJs3f|xErr^(!%lrbNy zct{Qm-31){_}@Mp-JQ-5z)(xZ+m*vnGt?QtC8m$Jg39pHEFs*i1odXP%&=gxZjDi4 zXLp)JxA!8Q4<$kT@-k!e0LBNL8DNYQ>M+9DC4j5k zeF*i@S@)~#1;d?tYJyTF5KtO7I#8d&Xqfm)wA&C*V!0wQ9u1R-jj>D2fjETOA`b>0 z06Zsn@ZhG$C#b*$*o?qD1F5i*LB8YQP6z^|9;rb^b}5LX#4PN_E@lyLUfuu-UPXgW zgo3c1^I$L%lU`xuSSfZ$GVEAw6nD^c!AjIKfSadDJB8ddQ`G4M)I_w(!OoL3Dgg>2 zPJo0t4zRR%38U{Qg`t9?NVvj#A(#)?5CN92+hV{H=$Qf0asdl+GCpCEIElat)B`tc zB6lw2L6(dvm|mx@C-VvA74h^cA7eB!TLQ^=W8Mz8QU{c5>M zukbhhYH9_&!r%0(HC6Nqf77p~*U~HeO}|>#p}{^nh%R~WKsGzk-T<#L@&^Wbb!w38 z117r!b3rO_;toJjBa(6RrZqtO0!A(BKZ?YYm@2GA+Wwiorzea7YGaQ0)UOYi%;a57 zr$zvuBRLr$2g3s-g<+4IRpjjY0amcJ;}a#!*WgeRR74oo1<2{QtBGu%1-cEKW|OX* zdXVfOE$%w>AJP-DW6nt?0LivMYcQWgvgL#Wfh>AMIIepXo4ZDz#(*f&NPB)9>6K(t zbMcX48)k$Ag@Tl^CxewkgJ~L=NDG_r33STBEtNna@3|b$LEX>{vH9ajeS3Vi0T`Ef zvd~eHV4+v1u6`1!?d#%N!sJS!;>UgsLQ(>-0U^;(xQYkVgiEkl;!mGM+O}2Dl3*f_ zOMC_A3?PYlsM*G6 z11)!%=io(&>c5L05QXU)LxLAH7=+FsA9T|TO&$?3(+-VyVvHhKuAvwx1y-DRa1%Bc zVnbsR?-D_xFbWw+wC7<0RjDD~zX=nO;60ou1@w?g9cM?eWQc>pOapZh`*}R_UH~dh z5DgASy8&ufO&|~-eTLK~4*WEt4@a%c4_6=_3v<>z#-iJ39(*ie9Ds@+5_Iv78FHfQ z;81DqESZEb;RMA;n>)7>XMEB#)EoWwlNg4EtkQ7Ur}^oAC`ULv+?gZr0}~7|3y!#JrwGsQu)At z>Gdu!NB#Ug-C7D%(B1ByuAnCgJqC$duz5h_jV36JKHU{Y9_h{iant5V{Gg03<##Bn zJ|E@;fHp~bCZWOb% zac}xM1DI=c{Nms(n9jXu1AYsjt5m0}Ob8=FCsa~FDI%Cq6k&EE@~M=#3J8fMv(&R+M|x@X5RyPB+5-D2c3iwI7&q7=X|o`Gc#M`BE`*KE5YcVW(QFWX zwnaLOCc3By5K$kZG1y?4{18IM0}zVB_!q@L6aVCT=s8~UYSLbd@D}jf4mhpEgWDoq z4hIDAjw3l_k2RZK5t78%n>92C;!goH-Cxpw*7?_iAxdseVX0(?QzDJThUI zWxnnuZYx8T@>^ER0P zesR%3`bpxxdj0*5Gb3bbT--GgfYIxdhz&JkPxd(>x>k>=iM=FVKUnW4R&>*E)gDQm z+Fh?K*X~cDhW)iJZ-S$v(lmv|D@dV&E*c7NJ48QK)4olO9;8FAyMDET9J~_99u^tw&%=Zl*@X$|Ll9L;N;% z;gNbPO{J#{*ZXRnrnvnA_eX-}6fQ$*UWoe6bbd;09HF1D9dnbH6<@Y^Pb4Dj$apfH zDPE;mlK9^oUJEL-#p@~6yo^hTrcuB;PNsewrFYfhUPZM27kN)~7Ilg&;vA*VffD%Z zqxAMbC3B44O`DNAV2pmXR(~UU$SMVL*<9rH0{R^zI4 z{|^suNZ{{ib^;(~j!s@p|L$#7BR#isrtq^^HcsCa;_AinWAsy1ve&VC4JYTOMjWg6 z@rSs6kvv{MqN~>wFcB=HGd<%4bQd9Krp)}+@p|q4H@N-6=~Wmg*3u^)76=lacTmgx zOsw%TS@56}^zZw51%2T20Ke(!7kHYPev+RT@r%^iC+btRxc8CR;kDiqSPf?jKZ&Z7 z^?6AT^RXPAW#0lk3_;qw*A{+aLgnQIp#_wkwJu)mT|hvzxMYa-o8~t3#AT zOrJB;o61N4kIN)7A9ad;ga4#S>wUd^7%%wY*v+_>a0y=UpKbwPqH2hCB^i;?;MQe6 zydnE>*xBel*mzim0{o>9goLUC0`+(|pdq$G^f_Jc5&Cf%dY3xmbp1uG^!u>sBPE8| z_**?8X8cZ%h{##`fnuLCbzR(ZhQ8GJW|_~aa;8zCRjG5&)WZQ;ftYxfK2BR9UOP)) zs4_KkCNbq~eQ*!jiooIwNNCtykvJH8oVwBjU9)k2EleX$sZ$!eE%lGH^)Y_!J8{@K z`o+NiXU@^vAGli>w3aY%#|aUP#^*rHiG3r?bM*tXH&a8;)#nEiviF;Qrrt-1)WN?g zq0!Ibr1Xn{meicDe;%R?t5e|%^sD^ZEvd;D=^tupP?L93S#iO|`p>Af;S#;0`(+w` zN!>FMyTRFNyhg$&#~Z_>)<~fzET0(@|~I?L$$ zN_=PdkUNAk?OX2PjhE^_2edoH^;hT}{f+Cz>?`y-e`8ni#ua)`?VsX@D?s@FDcb)@ z@6v78Ita_t*Yd8PhL zsIlbnMC-e+(yPR#EA@Z1huIHiGqRN$4bEK5TWlFL%<(d?Sg}bJsa;p;^R=YH6c5&< zHVRC{iRRD`+NV(;?0094 zdy)FnHTtI^ZF1_&Df-Yr=qG}qn0=GpS>;+vZ&sz&-K1ZmX~8%?@IT+&t)HU3AVv!PK(u|Gz*ezdJSp@Mc)3I9&vw4? zH86$S-~dmNp&D@(>0d}Y+dyog4+G7AA{d*bi+87^^EZl<@6mhU=lXl}e%fTQ7*FlK zCABqWoWyLyHZ9>J!K1|X!I?gAuYR9)Tk64k^&d2#jF(z^%00%nO* zoAkCJ9CL|%B~EIBar-OrrzX9N|Hm@X<{`bEc%ccLzan{MXQ{AdC zb<}7t-&ouM0sY0|mtBZ&MU9MA=;2KC@E7sa<9f$lyP~MDDGaK^Mo4U`tZ+ds%l!%U z5)U|1Nwe>t3CZY1amy@ye{tDN{R!jMl5{Jg*DU=v^6PqlW&9KRxP2HbXbz2T!uf>I zuK{N36Z(D`fay9LoDBn}gE)J(-bO;FYv%~m4G_|H{yLPzN3-<-{TD|-aR@Y1`CP~} z4TF9RYAF|3q}0Mx6Ii0islX=2%+Uv$?;VNIT!D!I201L;Jx8Bgh85<34a3Zhep2te zn+`7Xau%D$TI}mIv$i+4iMjfi4iA*Zeu+3se9m&XT@XD^4y%oasB3V}FFtbgfY@U8jTiRVP#s!&ExkygsP4lo|UKLN$&qDFm zJiQK2U&*Ivi{6@rLX=&qmOqlqumQ5MSqy5?YarE}*rHFUnnp?sVv?D}lwp&=_IR_{ z&;r8ph|uRVVR7c`J!upKq7yx5ReDXh(+c6Vb{xu^r;pSMNoUzgQ2(I zK+i?pTY8_aoFsvYESm`C2V)qWC&d#E^}sCTEMLu;<16)!;dL^7|6BTy&>DWd=`Fo` zv4Y`H5{Ubm&&g@ z-a*6LWjg*Y(raY8%iDUV&?2s23?=2;Kc?S4^fn0Q1LD26^|w^M!Qbm$#ICh^XR%{7 zTn#={tyOYsvZ@|yFrL@h&a1kyNq(5{lio%=_@Q1Np7s|xig%$Fn72l6r7B;x1_P9z zcBGRt-bJzV-$$_p@*8?pEiU{!B(EQwAM1TW&vF$9eXRFZ$+rSV=NNp@)CiZ#40_P$CYFD!TPoLvkM#po zvi1`%dBP|9CWV`WH=!MgifcCMCx`y(&TXgAVot8#thW`HeyU&Kexi={-K_Ul-!I&( z_o!@!!o&m50$Gs3^Xg{3B3;pKDv80~;3j{fRNz?3if7E>;tb4Gvlclt>6<+dGI8=pek z=1u;ljQT&>p;v~td-b;wt$zgJo4lNRfJUaOT3%E`?kq-b*Ut>e?-=)uV%raTP+Kj~ zfj_9%`9ogg<>w*08Bu1Wc<-MW@rtYwo9Y<@?xd1Ef0W~`5*KdMPY6$w1-OF2JMl)mCA^kd8ifY8P!i!NsM&1N}^P!(5qZC@cu^kEg|g0pMm!BQ$E@+ zkRKSmcy-BVVEyf?3}cD@FChJE?#Mfar^&R7^pSA0d;wHUsC>Y}w!;4lrd?Ko^O1gy*Ob~7H+k*#2rc7oxJZv3Q@%iZQ&oi?2HdsY_(|ml3s8AFPl=A?U1i3s?B|#~>iAh1D zgL($J>?Zz}eg@6#mZ}XI|I|X;z?#h!xAZe=BYmJB*tIJ#A$F%YsKhv1o0EE|#Q4D< znnAo^>i!6v8AFd!S{yjgxJH{Th8o6}_V=@IW7I(?J&0;x-tRAg8$iIRiMMqYd?iMj zMxFM7*m#gplbU22=V;ny@r7kv5B2JVs8L<_1+`BuTCIsIUqp|r?dxH80z3X_)VN%G zG1Vbv+^D*mnpD8(rT;3iRgNQyC0Vjl)%PZ8{m>&p1N8J%2wpL+Y9RFlyA)tFO@k zKco8^zY8(Ch(&#kVR*Ake(2E8Xq|~4l^HWVqMvbSNMH#w%69_9fayKOhy9ELA;#47 zCv<<8R(Pt_frRcK@@iM;zIdRq<=}gA(fuA5-4o7^JKpfcI|7qQQ|v_G8*1nho$TI_ z-QG)K6*STUIP(zW`hGZK)sE!jR?dsC^80}%jFU@lrS4&Z-(4I{h#~UHPcC)BB(Kr`BSDtA64W+OU z#;a0)Jqg(TC@lf^P1DKz1}mT;JXKbG-6{MEOTf+4aH??`iUIl0b2A-#n(+&|dCcjI z?w6kqbXOfa9q2BTJ5L8T%jD4Cc*$FT19X>f-}m08I-Ze*?$4eHbZ1=1ME9Pgh=4C^ zxocq#5VB`Nn6Y2YYU+QC+oPCOsRUijDRIJYjf8en>WbeQknEpLoqskC>_D==^?ZzQ z7Buh%B>S~u=!M3;_}O@&(KR%kFfwIa1XHt0S57b>*{>BRTx<-)&m9*Vzb&~3xq!xE z(&a{_h+bke$h_j}OK@autypr2@mKsnz;7j{PBbo$t<9A3D`IO!n?D$XLsZM>snh>p zK+2cxmR(`&7YaT4Ce8Waq){i6ssBkDjehMx@yR4(LFhsLI^!DSI+;v$xt6}(BObrb zXuz?UitCMiVFo_@dLzymK2J4VZ!8L^%w2CZ?swC#-(s{5X!qZJhtak==rrH+CbX7_ zpXZz4G*72AyxhgUcNlF%x7&fGl&{X@Ol2yQ=zfRsV~_hsIvpn902TSw1oqJy$r5@H zerU^7wYmjTN8M?3_3t+y4$JlMaYOu)p76NBq0&km#r!-9Wt4@}r10e?r4_WF*j(1g z@jiZ+@v`<{YW&@X?k{OvL6Hca6IV?)288ecXgxdK80c?YA^tht7+=;nIsui)j>gfV z;U1%xAHVn9gL~%i`LFjFbrwo+z!liTDbMD6jEibMgU(DNgTqRs&@`-MpDy>s7KjV( zHSS0v>GXhcxDT#7p!BrO_%@%cyk!*fyN|OaH%GIV!{_)%BA(A*;m2>IR}0X&*4(i3 zlLqDoLkx@oOR<=~w??!=GSefIQ-kP**jw?}CzAIW`|2nNXHEVn{(7Hrr+iTi0eM8+^>@rmElXF{5X?4?6lVnfo9nJZ7A!^1S;P&Y+{( zwvQY8((kIrK{C#G+^F^*Zq9sIlzxDnOl6PFG}>1EGd)(E@IYKjejx_Wl=WUR6CAR9 zwPdEzMkPO-Y22bM6X(ss#_^F@G|M;8{_P@m4VV�JTvCP9=K03SnzkzCJZ*f_T6;IO@9W0# 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%0A><=BG9EngWF=a+BPG2t}nNNZ$>Q=@(K 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 dcd==yALP_SvJhFWXmpW4AV}a8k!{ z)2WqZI)x}X^&>^QR+p}#g+}p`6sEh*30GWXGZ<7>O0gGKmzw&Sw^B#)^T#GnEH}v| zE5g2~&F`*yD1J4wy2NQ})>$pC;s-Wo)gt!1Ja|`m+5s?dVo)P6N_I%*a4f1qbc_XC zK?hnBCgvdWD+g9g)D$%^ia?DrZh~EsRdQ%s87f716SlD(^0$?Wo=qnHjx@4zHpF!o zHxtO(I2|CH=tyRuA`93Wve0ZbVF*H0gH47r^XgsJXVuF?b{>liOJsft2U>+!g^QzWJZ6rr z(&ET24S>6=+%-Jlbu1kH&}sl*m^#^bXHYolN;&qonBYx3B9qJVe$7kQ9)pX|uHxc) zU?<&y#Q1JCY>dl{;bWE>+x@(i4zrA%7p`$p404gBnPb(cUx`)Qje2a(PT&gO}^$nbk8Q(Vzd!*&cw;@O2X+a&h{pwvzi zRHjk}Kh0g~nnf@D-~+$>;GTVh2Qv4RuJSWkx^mHfe)FI1{_ge{27j5k=bfM|_nznm zch#cS_qfaNDZOz~$9tkTxoZ~v@|#b6>!Eeef9^=;p6EJv<)VK*aQ`pAbLjae4)J`w zK4pugquZ>O$Se9%A=3QAX3>&OKewiIwYyH_uRz=G`gfMD(!(e{-0;rQHTTdL-dxFg zzR`DN(W?Pnx>hf!btU!Qq>f+hu1R}Z`>V_8bh-6Cs&K9U`S71!#ZdI}n##+X52mB7 z`Q?@O=#%=5rr~m{z)zK>8G3T1HM5p1WdL21fG)Z3&Y%OBli=j}ffVM1NJRgydQF%E zDl0Gtwy5_o2P_G6P6%^XxvLE3l7(}-n#%+*)Nxk0&=lHGXb5F_|2M;3h9QXO$DZ+a zXe}GOk3s=?CP#7t9B+-<-OlreV(I+JV6Ws-{6Bm_^*L)I>M+widiQ!AQD>t&?*~tA zJfGu8z>O7@L&OXuj2(E7EN|D0UNH0HCke$j;u?N@rpR#+>pQ4;> z0ioeEEbpdZwZcIKVsntUbWTa`9T*ikJ|^)7U{mrV%aBYHsDdV4FA)uXdCAE(mkB`o9|1XWpL-=lB5$I zThPZ0A;=*8sMA~{3rE#^wc0KUxtxEZ|`RHih$`?ltI(oS- zeYr+y#C>3CyA_IM0eYDmdu`(`f+Tpn;l-}$MI;LVu0D0 z*D|^tw4^y)PyTVs6P=o&?o7+Qe%7YV!ki!nY4Q&w?Okqs8MQ+w_8G?u$)3E}hC7X* zT$*uqXR4W>LD2*^qw>gG`P-`&v@;JZqOk>)Np)zXk-MIA5x)4cvxoc^)NK^S>@2B2 zA)fi*V6TQtk7TC`8QmR_;gvrc;!kT!p#YS#?<&6Wx=*_1XoweT#n#$)ISW$xuJCI{ z2LX=zTG{C0@HShjZJOH={(GF}v@$Y)epP;ko39l2Qk+WjUOno}24WMm-cKmZji$zW zd8Rv$l-VmwjhEFl9>SStsK13ZvehKX$?|^SjHpJa&Ym^>ITRA$sm0wHz@aM2< z#z_6cX)BulgF*E$=9c4TBI4&W$zl4d)Eg6a!CA)*a$Hs@T zUrInZZ)PC%Kl6#=25ltT3l1I|F(CP9{-%sp1Z+iViWLFU$Hqq6DwRftZ!LYDXi^qX zTNW1o!=B{TZg+aHJUM;5n_OP5klX}B-;T6!Sb?~S@4dB65e*O86I3h>%gqZsxw)1g zOP%adVt^NX_Apub;ow;(zZtv-dq~i{IhPee9M97QZaK%Ox&Um?b7N}7`+iYol#c_; zZy-+c#bdja1#IsdP} z-kG7U;`%@L5!7~jbs-~^Sz*hiWPs0CT-cx+8WZ#pr^tiw3ii#4G`1oTt=*uTL?XM9 zgLnzVTTf#t4!t`+g={5+qg2v~RAE<#aA%0F4q2hXmgpo`CME1RJ!5qrZ>gEn9WAZ^ zdX|ZJMr?_FxX;w7D8uaXg^>s*HMcJ4qA1&yBmI66Pb(k1qZB5m3)Ratn~f53v8!ORElBp;u~Zf@bb z@FfW2#y}gEZdXkdb5aVs_R4i6oaDgwK{hcR)Uf+VIIH2>edG(xb$T5M@ebskprC)t znEn;7kHVL3^f!@k&59#$D9HG~D+O(K38lka`!YWxeRZet^00LtV-cL}^FRvVsB ztTtS=SZ$ufYMXmPTxW(yjMyojQ4Y((Yv4o@fthb%<5MR^CFH_SRf}CMj3HEqk)kqUS~hi ziG0t3nbs*E@2bNG3Dz{A{c7`jvScYrf1?Fbr^@i+R(>>;JAnlqRQXnRo2qm0-ikQ_ zsABFckt@b>vK+xzb_LnP_FEvMItn~J^v61A6jhQ=u5#&+0><*LX2FhsDct9=-wEEy zD!yC`?cZtb`2Jq&pr#~oC#Q!`Dh8`K;tOLt9JmC+Lt==~qxE^KU{}M9G^a zo`IHUzkoHX%l$ZP=|PxrR~i$Yg(jo{2U&{l~t;;3axA%yBf!dB$G2H%Yn)oF=-gjU}xZ|B}6>mwR*OqRm-K| z9u!XV$#~vX_ynD<^t{p7<|DN8m_|<_)0i zm6sxcr3!klo4U9Wmbc|>-_ghUsO{mR~`JyeB>i7R3H1C2GY!7Y3^?#=A=*p zR&?81_F!Bxx8P)rFCw48)29sx6ac5Op>c6JWPvzx9U|8_Ii8Y`c1 z-6k$+UL+Xd7M|gi7ms~pxCu0toufDnmYX3xisjI#6nxH!1Gh3*^bE!Vyy-ZGP=gW5 zEcjwZvlNs;KMbLRpNC=dkf;f9J$h5nIP#qjqR?oYa~}wndmV%tJz8!Q_t2Ok;v+4U zO}rPzD6MyZn8a!JXcM>HrTKLjSJEt!XeIk7*f}lT4FXTIHDBEi_g&dNz(R#pC;i=c z<#KuxQ$-ArmP;2v!3wV+X;Nq(<8_V^3XTytuxqorslcK0;#zi~wZgw4bMk*K?Bp~> z37#=4_J#SzTFkmqA|3?!AW)ePTcFE5S%&dCuRBtA$Z)hFYGBrNtrm(%RCh8Fhs#O< z8odm~IIZ4=xEl}f4~(=g%%yb+*ln2-hElNB<&H_9OlzeA?fk$? z;XF3jd;NwMUJYt~C|r@&L#ZvHw!yExVqVm=S8ZU}Z!6yr_o$8|_{702e;Nn3#pm4W z3Wf0uQtJiw7rp5D@8mTuiPi~i_hubbUC$#1;2)9wY7$UmXLN? zJ3F<*%n2#TMwkv?-6vEB)*e!w8t0r68-h=jX#x>S*^>MqYg%qjTV{dyWY&)JaIR5Y zH_BEDwc~(ooeu&yZKG%@rR`>je1^r8Q!9(a$YGsju=eO2g}Wb^)}OqE7O?2J$d5;f z^9j7*aslA75^khi!AEk=B@S0X`{cqKfq!YVF`XW*xDUB6?`{So-dh18ZNN&^tZick zpU#RTy|SVbH)6>!V>bUg?f7id+j}*8=QmX+Y8dP`7)97wK%G{f|*ST)NU-hqcLFw}amt>+C7b;zujy|2u=uAGj z4Q6IS97H2d)PWJdy#u=4kfovnd-KLkaYDNvBQj(b7hxJjSDh=Jiq|v$Eh~-b#>HLE z4pFlTZUG2L6xoGqw~DMM)~p#6)uCXS^KR^mQguhu#DxqX1Yph$7Xlk)S1)rrPN8yC z;QU@7+piW=*rtAZCWyfwf$sAuiniqAH_FE)#9rirTdZnWsTxw1F6D2#C6c}BW}WE< z1;;HEsLiZZ4C4{E5%~9TX0@8nR)6E`a5zE?9s1}CKsYw!ztcC zwKiG&FfOmP#zt4U(F}4-ZsDR#bhQP#bJNbuD4*lt9k|DbG@I6cMg}+m8$SGQclDa+ zYVslD;N-}|45B=GS?*5KY5i}gtybr*ZeHZxP4)0j)@}^KPy=NSbL+J-l3wGka93fY z#o8^a>gDeQ49)mSH?MKmVycxf)U3BxVKfFnHOwnzKfVSiz51T$D!2gbbevNY1n_-X zK>x3PfPsQ>_y$ZEC}{?>g)2O3H#A|4e_DTa(!!ca<2az^O;n^ZZx99UkrNUR-3)=i zGyy@sORZQQ+cKBQ`L+$2o(fu6d(lxeehGM5}mT2f!zN+fpybmpb9TF7KGvgL6`k zo8Y-??zAnPBDHNmS&w(jYZTHt#XIH!zqg}HrhuFvU#F29<(N0Sj2m2!q}5P~BTfsx zc&E4?`Qo+Uv}?Y29jPxKHClYd1}dJ&7q4BNFK}*(YenId8eM@s1{pmHS*qvIpD3z0 zdheLmSor0t4t;mLZ?%h7y=NX}oxZGKp<`@AMbl03JYAl7h14^zgKt5cPbcx7dF|$z zcamd1GNq>I3}x`w1W%ojL{|hO@yMg`Xu!3*>kjYD(^uoeS@pV`p2T~`jw<8Y)#|2m)lJpiZaR96hE>!y*D-UqQxSZsu0CzF z$wGafJ_qu;1IGa+^3u<699whTDSl7WnocUxpHxz(xakt8c%d*9r?}G*F>|CRr4D!| zpu?N;DK0gOZ?5>?SviN3R;9cLPU!k!mv@rrG5vYr2SXU=Q?#|$rxtXrmjxXRxayvZ}S+^hR7u*D@{b4^DN5YiwOFFsV z|IngW+1qaaR+U8U)q<~^k4M}d-)x_;Q&n|}??sHF<}zz-s&j4&fg|s;o9CEJvrrVg z20g989KbbeZ;SIz-w8|nn&Xff`w1=?R#aVGAgt-aa9*cpT`sJNBD@8XBU-TiRxmg7 zL(+;}Y^S}nupn01Y*h^WM{WAHjPU9q*daCE!8pC+yx1Ec%zDSg8Ud;~#-z#tMsbff z+3=pWG`jB9n1uiMx}alXjXxxD>jc_DY@k|c>>HCmx-O`?Ll%a%iEE?*bBAn7{>Nn_ zCnoC;P0E+;ay;&CO1&Wsei3j4(v3ABZQX&5#!pEuj^tFyqh_j}Dlio)R%jOG3OG~x zTqPWZwXjvZAh+t&>ujPtD;1$!mvqgMGRF3X*%6DT=F#9+1* z2E3Xveze+x**6BfiEaQr=yJpNu;u|kfmK6nxvmxwUmmoEq;iJEnAXjyzmzRUIL^6} zeFe(omjZh+Lsa*Lh~u%Wt4fT{$<2kadp8#Yg<~#ONA0zEgIj)Iw2byf<_M~VIqx<& zTHVIB(C+&*BOltrcoU0Shzr>K1J3E=%LOiLLzqMP!KU-~t+%0gtWkf!FG)93YB6Pv zbQK=nX|8f3yXhgfVBbMS2|%0bNxR{y8p9g4K`DGMQ!+Pm#Soy&QYft9ckY#H!R&^z zy&723r!qH_dn4WO>$K?4P11~QrZ z+x_mwqP?TN)F&2?(tT0{cXIE=54gsaetyTG61O_DPS_`_G}U<}zA}$^>{JPMbV!@LuzpcHmR-kvJ(N zO?!*iw6}O*=8{^|xVLE9rtuOtf*xN7Nw!AuPFq@LO8`(lq1`9lx9^(LO%02Y2iQq3 z(3Lhx`5-IUSshMFFT^u{gNn^I5JVhgK)hFLTco^0Ii(&Vs60q(^4ZD*dZYLgl?Mi=p`t{M zo9AT!OHIiP5){w!x% z#`4-SAGf@=xjT*2x+p1?URB4d>A1;0yo2VU8o zm%6V`=XkeaiILaMR#{~58t~2+yK*AUgm^RsV+q77jh*3^0~IPNr^d&5&{`P{W)m3= z=6QQtIYmJ-DiNYj#Q61SZ`Ez6!6&p~{ z%lGwyq-_o&I8T~b+n;u2ILwMbP?SZ@Ec^-|oT2()pUU$)tF}7S_E|UN)}yLfflRN% zokhT&t)c;3o~n5Q6Zuajr+%SW`2U3;5PI-jyai`DU8W7(?QbtxtT}LMHvakJo7;4~ z@hIIIX=rUt+Rw$y?`fnCs@bpm>b% z92}L!1Y9ZH<-IPmAkV7G9|~tia;{QaW?;uXXjn%52kVdj;EFhP`GXsVq4w9RHER3e zM&odm0yWEXiYQKP9<~}WD6fDDC%>8Nq9Q~Da6=qbUFCX-`{B8^9z>kvoOy0WP;Y}M zAJ^|86|Hu%9EE<%BtnMF-iVrdwP(v$IwC>ND2cn1XM*hb6&tt8h-(8}i|&VVS-I|9 zW=A1}@mxuNp^ZYZPjFWh&nTaho!WzyI;7_RpgqC?LyXHxoM>=})yV=LbLciT;zPN3 z+oQ>V7&pWgSh#^JICXfp!AE80gpP1ugG_@Q1@TiWDM<9NSNC#N918`6JbK^-SBk7u z2x-OdPTpx%$UBU|(cmwIJn#?P#V$l_u-bitRf}xBlaNV-acC#MYW4-$gA7sjmq$? zgY~R$CWFXO7w;zki3BQUkiy-IYEiiDA6?c3g+8EL8=uIB3;mzEI!9eS*TSxwE?c%n zYtXZ5oAW*t1~q3w3!lJc2;tmJ8A1GbpoghgwU+QB&xmf;HIUTaL~xUaI_qLHAKeKT z7#}tNYF@tGyFE*~Yj&C#1~3E-i~_&MX6Ja()gPxcvo2n*JKwHAO#~?I&KM|><4z+X zMDz&WRf+>8K7(c+^nzl>KRUEM+HIs33DY4z7}rTbp_D9rtax7831~X`vL<*>&bjK+ z?BYS^2jYwxcoY3soNjJUMDDSo-IxI>YtZZQrFtcfArUjRN%@&@yd-mslSD71n;L^? zBLCUVA@Znj4Ft4ydF&EpplD)@JvBq7+cK)mL#pR!;uU*Vp5|bmsM|IB!!+zikUu0b zW2IRdHT$W)6x#7(kiQl@us19IW z1l$RECRiSU3#DpXjkie$CH|U6Ui3%kW2C!jjTg$ik_FB{1`Ai=xn{RbV|XUWCC$=B zjCzO>jQAvefde7UpX4m?IAZ}QIygAf#&7kuRmY6l;$0c@%}Af18#V9t40~dW)x0AF zea#cWw`-pE4SQmRtMAHf@qwDhw)o(%r$fV@4i9_c!ij1#Z36r1QBK2;#%x0*7YaCK7A>UqYir>$6XyZ4tC;ij)PPg? zeegV`dtl?lrn)I$QL(8sSJ_)G-kW1@HScT)bLHSpw7-XgRmsOMa*Kn{Brjd$mIwcs zoOQ8V792_9mF~LWlga*z-Neyf0oN9G>2Mdq&3Zb9Ij?Xe>Ab}KdGM8F-6ig{hOgw~ z`Jw^IT^G2kgL{(+m%0_5?~@xScfJra6hMxhRk-6)cX9CE#sCV9hU z?v(LA$17CL7w+OaoC^y|ARyr1WePizw_oPoaM}k^-sI^sV`y)Vw|w*KnZh$thvee< z6*SRP$+*JiW8iB)f2Dg{rS{MNyE`rK%Lm_cmHTc`_f_UJZSe`8A8PT;o7J?OS$)IctUq(NWC<$r30z@XU*Z@>6z?D@JUC+Tc`an!z28i~Ixo%E zesudyeAH@GKf2EUfREmu{=mNJi#6#CW%KJc`o3HK0FCzF{s(Au**ku}MiZOzNB+`% zE(mrGzWEN<76dORZ@bf7K(gyj_dn|1&uo7>Ilb(T1|LsO8E}7E_u+3)GMEgwmxH>W z?yW+=MxRd??JhA*ejAcxm^4$Lxxzq-%;aTq*3_@)oKZ8_R%d;r9BFuCCYPDj4gKR^4d)pL9fDDnvv!dHru6TO9QjA6hw;v82 zWB;++7bE<|;9`P!mpyV17PnbiKl5#27-Wdf$`s%+csoIlagAz%=W2=%NkN-(B8}We zL=D&e-&d8WM_*umvsA+)ySCcO{s^kiy?fFn!%`99-4UE5!Z*AjLSfGlemRSYlakAo z%>zQ2=_2cR>}RH0lCF2VNeWw{`6JZd+zenOJ*}>j7$HfSW@RHI{*PD=_)ERx{I1vs zybkpaHdI_YO-6No=ODoH?q&1YwYPxzT+{JuX7g21Vzlj}T(r+#rLa@;q zk(&~V!_(nlM5g3D54f4Zhm$Wq;QFC8Q#QNnN&dX?H_fYCb{-+ z-RvfeN~~T69YRw6VsDgu>2F;<@1Fcy*oh>0@bBE`gy$~a3ZC1JC&O?&_m^9p;kkMX zu9Fp>LmQzJ`y@kn?!Z>?Tr&7SKL*oC+n*ZMGj?#~L+;5S*quDT%}oowmgGM{@@#U( zC)|R0@A-NrK85{@RJs4J$v?N35r5WWZEW&r2{<4wmxv60gz;wH##7?ms`TLC!=G?} zo{f&0yQt*af%5edCxJd}%S0EZr)a>v82n{@DR9(K0`!KT61&$w(D zJd<>M)|F2Espz`CYd^PNJ_Dy$AMa@JJnR#yAs%*pL)e~N{mjV8$?O;0ys1wd%>Z1v zm{c)<5sAQi;-BqS0GT3?Toq$2pFHq_n^=cH6V)YOe!(po2e#oghpKMS9A1ZYgXF3b zVQG_b`#5xpi9+qkMg!Qbg2V_9Q3oZsAE>@2TOr@aIwI2}9|FnFk->r7lyJuEENVLC zdS`?8Eaz~t_qi^)-dh=$3-$UO?)c7f$>;XDNe%G#>{)vM{e5oCdEC~;#j|)MgBG>j z*S0!F1eA^2zM)yDOt9*`>0Bu$z0si=-xEOKRJ( zFFOH8z0=C{QCRO0Z<0wB@RXq0%xq=n^ zY)iW01Mx?2{eSK!%*a0#s*NT;`GIS01r3|r16&~6T=Q9XQSkocQ=fJBku3O}dn9-= z`Q_)_!r=XbGaqqtg4T}#*1yWcYd;QLA5Q+_QP)ky`yX{*fa|&NG56+CFBjyyfRTI| zq>$?_(Zm%#mVEOuxS)?GPkz3pMn*TmDwY>};A4Xe2i@Lma5U-oA~NF9Eii z1BA%+hr~gmSQF6>Tz@`U-Svw7Nvy2ouIJq3nOnCU5%DwA|oZzvj*l z)27@VMVI_w7vYazW`o7tAq%uvb{nTD((JyW?0UlZq@}xtvPXxq+tC@O74{EhH!7l) zZ+G`l_LXYZg@Ye|(sef1eNA{I`O02*Yw(3+@ps(M^P4^=R@Qm2?qA#`+2Ff_SN+g^y)M|5xPxwS z{?YG?Re~)nf}uP(86M!0*}A*SaS1E-0afeP%(%l zUQ_cdO!Dr*Zw`+C54R>vR=(u^WW=lA8`j9@U;SRP`X}zpNk1HTq54A29H@C#GcOLj zpk|&AgP$h(Z$YWu|N?@12-!dlihqq3*|5*hW3c>NKRQTgN+a`DOW?rOn) zNwK~0+W%0En5a4C2cQVg6Rf+REKg7y)-!U*RB|>{8u!u(ino%4d{!kJ6Kh=8isiC5^^0$fq>uXPa3RG42*JR6Y+__`Gf=FTk2s0ON z^ftLnyynNr%fE5s+iQfSji|hn$*;P7!H)aG$hfrizxf)Q_x>5tf8}IkPNx4^M^{b( zR+7a*G&y)rawB@<@uFV{lEzS*Zdq}80ZqBfC4Z-iJCbh%(L*Gw!$=(717UPd@ZjM0 z!l)w%)+L9s(bXf?!6ztY5X$IX4?dZR8d{RQ*{FMp$`MzlWFh=6-eRweHOAQVkvs^V z_{h&g(Vj^j$VKPzf6t? zo=!JVxG;n^$Dv6eB?xsKM0N4kRckl@u-2KSTnSA{a!Fe>KX@v+w=L=>`BYnU8p#W7 z(d;QyBm^_mBVOMi$9(^Loj|f5<2bPCB8g=kK2VDMg^ zgxx0R|UD4T$?WbMQvU+3>@kPm9cXV|o7mkgt z<;ADQa!sVk;z2ynz{EW@mg(J*{A{eP>!@+jJDc}$a)Z-kg?FO^*gP&8H*()~{jh!y zm)h1oB-!q?;OTwIH^%XUmy(~2i#~q#i~BP1`1lBkM>=c%or0ct8;LL2^smfU6hB@m z0KT4L0`>Ug6oq09+#Q5kh3$NCTm&A@AAN2KRPKY9vLbd;`^T#7yrJd%WyqFZ^oeNuFO!+Z0w0{gsZu|C%TH{pL?R6 zu-@w@M{f=O?Sm)rv#rT(Cq;kKE%=8c+#YJ58FRQp=1YYZN{*ZqEe`%?GVkQ*`r!HG zLnkw*yONjQT8fg(uDdz~__qpB7EY?Zk~bS-rG*RI>Lpy7r~y zB}zx#-{E@qCnIn2Ibn}?HAW<-mEk0m$Fh`t4jH}?(Elfj-3o*qpqtS^+PslC;! zni~htI6Yby2HTUJGoybY*>Yy|VBHVt`KJdL&5Hha2z1UrE1Ep@$4|2ja-QkcuXSMv zs(A|oBy*iu!0m>?lJ}hz^{TUh@9b$`oK%-Qbrw_mrR2wF)$|x{6l<<}oTJAl&$S-+ z&$S-Unx`HougvpZ)}1O;rOP?GeA@hIZV>EE`p=HWpSAZ2d|JvJsL_QfFlfCJ(nFLL zT)}YcyUyBo$yd&1v+qq_AenG%O*^nxX(c)B9IL6Mb6CyvGsz9-03>^pd(Htm4wFD;0sPd#vj zu7}C2j_TND@usZ)zjpJpDVV|O^8cd`=s7uWA;`3ltX~*)1}(|Q7Di{X%r7jAKEc+y zCysUm|C!8M1e`yfJiaKpzwy^~+RgdGuahgzjb{HpynP3JRK?o&p0m5@Wg!*P_LPMH zp$P)g5kqJ`Q9*;Eq5=j*7XcwEQUiphREdfd2?z=p z1oZnqGiS3KMAY|v??-;@oS8HI%shRbQD#xWHJTS&s+d#Q2?|@AiI6N_VY5 znc*+&u5EP!eBIego9;dd*z+Ns>!lsFACLCd7AaHwv-@au6lEj%uM@Cw-*sAA>dYvH z4T(A!0f1KKKVjqu-5h!PYGc47Zt1Hf(aU|cXpHUczWAadDGRZaeYMmu$pKP)KP^2$ z-loHBT+37Vr@r02h><4WdzVLSMlAEw4g1za;4x-B6iR|p^B`GZYtAp;6y z5g0C9V^jrub*C^NVU{0KrOs?Mz!nTJs%ONr<85pgOdvubDuAOyRKt=2H>A7hiY&%7 zA4Vt$O>9)pNXs=EoUsrgrsm@O@M&fy7}Yy2o+^_#6_`D4oVtGsZ&Rhzu@G@a++LILjMYQA}n^LZaZZ`9w1M6wZ7y z2nQ*daOh6Xi9y;v)|r;E1rvO@10pwW3O@J1u(5aty%instb&avw+JxAS8u>Hlt%&A z3_Ka|mChVHhJlG_r4DAfCuRiNO;nFvC49}nk_w_b!JQ*D8eF05ycEHyAUgFp{uXg> z^02$JT?Lm!fAJA{l7%81l7uK4`dF=mj!~=$RrIvNPBdp8cXT{ItdG zkGNU;Ns%1&^8>YwV5m0@)Vld=-K>4DNM<>hd^bq!R<~&HsEgc`GgNCrwQfa;`LzF5 ztv-VAL0XrDFCusnnQ|7p2OyK^_CeaM>a!jy*%NJ0r?40oHM&zvqYHzyhvPRhFVCFb za`8mDw}B>kmiFAHZ3V5k@OJHSCqQxb9cbY+^}7Qte0fm|QJ@0fyaO#vxu}JL_-V}S zbLC~?i7RhQ>M-pZrNG~9m^LuXv!GCMwaHbVP{ul0BU4WAj?f0y!KKXt%peqCrUDX4 z0cS`X39|SU^&bhMr;>_BYGWIes*tI(bk6d!b(U%^+_MVsBL`1VEI=e^y)6<`T|0$2y7BN!nY0|0}%ZvR)8tU*YbeGS)N{{#H3=eHFvmhbEj(I z6o)9@7T1se#o{R_-a-i$&*kR-M)_ovzY_cOf7pByiZ^gZ1dHRM)4$U^te2e4?9r=# zfAk3`euX`HSsd5+{f*JrM){WZ=>G@J!`h{K^mg&wztcRtemfhY`40H@STf?HY__17UKd(tuG~A5X}_U+0*Ui%jjSqf{)DfO7@G~N86+x{r6j2*EhwE)y z!|R549@5(Jn0RuGY3&%#T~Zr3MzwaFG8KqN zt~OtV4++kW=R7=%ua4&`g#~Y3(4lF9;t^!vJXJ$?p~F`tVOB ?_CPJU{k`CN(N< z99R^02907;jE93ss3u%^kbxNx5Fc@UfrdyA+ua^g6`{bA!Be2DY9?J10lQ5xUDXeqK0xmb!6T(|%Ol5TojJX5|An%y

rqaNa%)0p@Oq${b$cOYukK?~uY2W1RH__z7<< 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 delta 537820 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

5Gp@x1u@Xd(6DtLwmq`B>=J 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ؼu|D#tpFlH6!k-fNPVy*7$tn=?vErK=jg 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 diff --git a/src/pdf/stampEngine.test.ts b/src/pdf/stampEngine.test.ts new file mode 100644 index 00000000..fd6d3758 --- /dev/null +++ b/src/pdf/stampEngine.test.ts @@ -0,0 +1,187 @@ +import { execFileSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +import { afterAll, afterEach, beforeEach, describe, expect, it } from "vitest"; + +import { forgetEngineFailure, useFallbackEditor } from "../engine/engine"; +import { savedMarks } from "../state"; +import { IMAGES } from "../test/images"; +import { paintCalls } from "../test/canvas"; +import { testEngine } from "../test/engine"; +import { + naturalSize, + paintStamp, + releaseStamp, + releaseStamps, + stampPdf, + STAMPS_NEED_LAYOUT, +} from "./stampEngine"; +import type { StampSpec } from "./stamps"; + +// The stamps as Blank's engine lays them out: their own size, what is +// painted of them, and their one-page PDFs. + +const has = (tool: string) => { + try { + execFileSync(tool, ["-v"], { stdio: "ignore" }); + return true; + } catch { + if (process.env.CI) + throw new Error( + `${tool} is missing: install poppler, CI doesn't skip this test`, + ); + return false; + } +}; + +const dir = mkdtempSync(join(tmpdir(), "blank-stamps-")); +afterAll(() => rmSync(dir, { recursive: true, force: true })); + +const pdfInfo = (contents: Uint8Array, name: string) => { + const file = join(dir, name); + writeFileSync(file, contents); + return { + info: execFileSync("pdfinfo", [file], { encoding: "utf8" }), + text: execFileSync("pdftotext", [file, "-"], { encoding: "utf8" }), + }; +}; + +const date: StampSpec = { + lines: ["12 October 2026"], + size: 11, + align: "left", +}; + +const initials: StampSpec = { + image: "initials", + lines: [], + size: 11, + align: "center", +}; + +beforeEach(() => { + testEngine(); + savedMarks.value = { + signature: null, + initials: { + source: { kind: "image" }, + png: `data:image/png;base64,${IMAGES.png}`, + width: 60, + height: 30, + }, + }; +}); + +afterEach(async () => { + await releaseStamps(); + savedMarks.value = undefined; + forgetEngineFailure(); +}); + +describe("a stamp's natural size", () => { + it("is as wide as its widest line and as high as its lines", async () => { + const one = await naturalSize(date); + expect(one.width).toBeGreaterThan(40); + expect(one.width).toBeLessThan(150); + expect(one.height).toBeCloseTo(11 * 1.3 * 1.12, 1); + const two = await naturalSize({ + ...date, + lines: ["12 October 2026", "in Berlin"], + }); + expect(two.width).toBe(one.width); + expect(two.height).toBeCloseTo(2 * one.height, 1); + // larger letters, a larger stamp + const larger = await naturalSize({ ...date, size: 22 }); + expect(larger.width).toBeCloseTo(2 * one.width, 0); + }); + + it("is its image's as large as fits on the page it's laid out on", async () => { + expect(await naturalSize(initials)).toEqual({ width: 400, height: 200 }); + }); +}); + +describe("a stamp's PDF", () => { + it("is one page of the size asked, holding its lines", async () => { + if (!has("pdfinfo")) return; + const { width, height } = await naturalSize(date); + const pdf = await stampPdf(date, width, height); + const { info, text } = pdfInfo(pdf, "date.pdf"); + expect(info).toMatch(/^Pages:\s+1$/m); + const [, w, h] = /^Page size:\s+([\d.]+) x ([\d.]+) pts/m.exec(info)!; + expect(Number(w)).toBeCloseTo(width, 1); + expect(Number(h)).toBeCloseTo(height, 1); + // the line unbroken in a box of its natural width + expect(text.trim()).toBe("12 October 2026"); + }); + + it("holds the initials' image", async () => { + if (!has("pdfimages")) return; + const pdf = await stampPdf(initials, 56, 28); + const file = join(dir, "initials.pdf"); + writeFileSync(file, pdf); + const images = execFileSync("pdfimages", ["-list", file], { + encoding: "utf8", + }); + expect(images.split("\n").slice(2).filter(Boolean)).toHaveLength(1); + expect(pdfInfo(pdf, "initials.pdf").info).toMatch( + /^Page size:\s+56 x 28 pts/m, + ); + }); + + it("isn't written without the initials it stamps", async () => { + savedMarks.value = { signature: null, initials: null }; + await expect(stampPdf(initials, 56, 28)).rejects.toThrow(/initials/); + }); + + it("follows the stamp's size, on the one engine of what it shows", async () => { + if (!has("pdfinfo")) return; + await stampPdf(date, 100, 20); + const pdf = await stampPdf({ ...date, size: 5.5 }, 50, 10); + expect(pdfInfo(pdf, "small.pdf").info).toMatch( + /^Page size:\s+50 x 10 pts/m, + ); + }); +}); + +describe("painting a stamp", () => { + it("paints its lines in the ink of paper, at the canvas' scale", async () => { + const canvas = document.createElement("canvas"); + const painted = await paintStamp( + date, + canvas, + { width: 100, height: 20 }, + 2, + ); + expect(painted).toBe(true); + expect(canvas.width).toBe(200 * (window.devicePixelRatio || 1)); + const calls = paintCalls(canvas); + const glyphs = calls.filter((call) => call[0] === "fill"); + // a glyph for each character, the spaces' too + expect(glyphs).toHaveLength("12 October 2026".length); + expect(calls).toContainEqual(["style", "#000000", 1]); + }); + + it("says so while its image hasn't loaded", async () => { + const canvas = document.createElement("canvas"); + // jsdom never loads images + expect( + await paintStamp(initials, canvas, { width: 56, height: 28 }, 1), + ).toBe(false); + }); + + it("lays it out again on a new engine once its engine was released", async () => { + await naturalSize(date); + await releaseStamp(date); + expect((await naturalSize(date)).width).toBeGreaterThan(40); + }); +}); + +describe("stamps without the page layout", () => { + it("can't be laid out", async () => { + useFallbackEditor("unavailable"); + await expect(naturalSize(date)).rejects.toThrow(STAMPS_NEED_LAYOUT); + await expect(stampPdf(date, 10, 10)).rejects.toThrow(STAMPS_NEED_LAYOUT); + }); +}); diff --git a/src/pdf/stampEngine.ts b/src/pdf/stampEngine.ts new file mode 100644 index 00000000..96d14bca --- /dev/null +++ b/src/pdf/stampEngine.ts @@ -0,0 +1,230 @@ +import { engineless, newEngine, type PageEngine } from "../engine/engine"; +import { fallbackFonts, findFonts } from "../engine/fallback"; +import { loadedImage } from "../engine/images"; +import { pdfDate } from "../engine/pdf"; +import { fromDataUrl } from "../images/dataUrl"; +import type { Mark } from "../signing/profile"; +import { language, savedMarks } from "../state"; +import { sizeCanvas } from "../ui/pageLayer"; +import { paintDisplay, PAPER_COLORS } from "../ui/painter/canvas2d"; +import { + MARK_SRC, + stampItem, + stampKey, + stampSettings, + type StampSpec, +} from "./stamps"; + +// The stamps' engines: one for each stamp that looks different, sharing the +// page view's fonts (`newEngine`), with the user's signature and initials +// added as images. Each lays its stamp out on a page that is the stamp's +// box, which is painted on screen from its display list and written as the +// stamp's one-page PDF. + +// what the user reads when a stamp can't be laid out +export const STAMPS_NEED_LAYOUT = + "Stamps need the page layout, which isn't running. Restart Blank to place them."; + +// the page a stamp is laid out on to find its own size, in points +export const NATURAL_PAGE = { width: 600, height: 200 }; + +interface StampEngine { + engine: PageEngine; + // the PNG of each mark added to it, as its data URL + marks: Map; + // the settings and items it laid out last, as JSON + content: string; + // the work on it, one task after the other, since a task may wait for + // fonts between laying out and reading what it laid out + queue: Promise; +} + +// by stampKey +const engines = new Map>(); + +/** + * engineOf returns the engine of the stamp `spec` is, made if there's none + */ +const engineOf = (spec: StampSpec) => { + if (engineless()) throw new Error(STAMPS_NEED_LAYOUT); + const key = stampKey(spec); + let made = engines.get(key); + if (!made) { + made = newEngine(true).then((engine) => ({ + engine, + marks: new Map(), + content: "", + queue: Promise.resolve(), + })); + engines.set(key, made); + // the next stamp tries again + made.catch(() => { + if (engines.get(key) === made) engines.delete(key); + }); + } + return made; +}; + +/** + * onStamp runs `task` on the engine of `spec`, once it laid the stamp out + * in a box of `width` × `height` points; an engine that failed is given up, + * so the next stamp gets a new one + */ +const onStamp = async ( + spec: StampSpec, + width: number, + height: number, + task: (engine: PageEngine) => T, +): Promise => { + const made = engineOf(spec); + const stamp = await made; + const run = stamp.queue.then(async () => { + const { engine } = stamp; + if (engine.broken) throw new Error("the page layout failed before"); + const mark = spec.image ? (savedMarks.value?.[spec.image] ?? null) : null; + if (spec.image && mark && stamp.marks.get(spec.image) !== mark.png) { + const decoded = fromDataUrl(mark.png); + if (decoded) { + engine.addImage( + MARK_SRC[spec.image], + decoded.bytes, + decoded.mime === "image/jpeg", + ); + stamp.marks.set(spec.image, mark.png); + } + } + const settings = stampSettings(width, height); + const items = [stampItem(spec, mark)]; + const content = JSON.stringify([settings, items]); + if (content !== stamp.content) { + engine.setContent(settings, items); + stamp.content = content; + // fonts for what Blank's fonts lack, e.g. a place in Chinese + const missing = engine.missing(); + if (missing) { + await findFonts(missing, language.value); + engine.addFonts(fallbackFonts.value); + } + } + return task(engine); + }); + stamp.queue = run.catch(() => undefined); + try { + return await run; + } catch (error) { + if (stamp.engine.broken && engines.get(stampKey(spec)) === made) { + engines.delete(stampKey(spec)); + stamp.engine.free(); + } + throw error; + } +}; + +/** + * naturalSize returns the size of a stamp as it lays itself out, in points: + * its lines as wide as the widest, or its image as large as fits on + * NATURAL_PAGE + */ +export const naturalSize = (spec: StampSpec) => + onStamp(spec, NATURAL_PAGE.width, NATURAL_PAGE.height, (engine) => { + const box = engine.boxes(0, 1)[0]; + // up to a hundredth of a point, so a box of that size never breaks its + // widest line + const up = (value: number) => Math.ceil(value * 100) / 100; + return box + ? { width: up(box.width), height: up(box.height) } + : { width: 0, height: 0 }; + }); + +/** + * paintStamp paints a stamp of `box` (its size in points) into `canvas`, at + * `scale` CSS pixels per point and the screen's pixel ratio, in the colours + * of paper, since a PDF's page is white, over nothing + * @returns whether all of it is painted: false while its image loads, so + * paint again once it did (`imagesLoaded`) + */ +export const paintStamp = ( + spec: StampSpec, + canvas: HTMLCanvasElement, + box: { width: number; height: number }, + scale: number, +) => + onStamp(spec, box.width, box.height, (engine) => { + const ratio = window.devicePixelRatio || 1; + sizeCanvas( + canvas, + Math.max(1, Math.round(box.width * scale * ratio)), + Math.max(1, Math.round(box.height * scale * ratio)), + ratio, + ); + const context = canvas.getContext("2d"); + if (!context) return false; + let complete = true; + paintDisplay( + context, + engine.printDisplay(0), + { scale, ratio, x: 0, y: 0, colors: PAPER_COLORS }, + engine, + (src) => { + const mark = (Object.keys(MARK_SRC) as Mark[]).find( + (name) => MARK_SRC[name] === src, + ); + const png = mark && savedMarks.value?.[mark]?.png; + const loaded = png ? loadedImage(png, null) : null; + if (!loaded) complete = false; + return loaded?.image ?? null; + }, + ); + return complete; + }); + +/** + * stampPdf writes a stamp as a PDF of one page, `width` × `height` points, + * a PDF/A-2u with subsets of its fonts, which the signer copies into the + * PDF it signs; it throws when the stamp's signature or initials aren't + * saved, or the layout fails + */ +export const stampPdf = async ( + spec: StampSpec, + width: number, + height: number, +): Promise => { + if (spec.image && !savedMarks.value?.[spec.image]) + throw new Error(`Your ${spec.image} isn't saved, so it can't be stamped`); + return onStamp(spec, width, height, (engine) => { + const pdf = engine.pdf("", "", undefined, pdfDate()); + if (pdf.length === 0) throw new Error("the page layout wrote no PDF"); + return pdf; + }); +}; + +/** + * releaseStamp frees the engine of a stamp once it's gone, e.g. removed or + * its tab closed; a stamp that looks the same gets a new one when it's + * painted again + */ +export const releaseStamp = async (spec: StampSpec) => { + const key = stampKey(spec); + const made = engines.get(key); + if (!made) return; + engines.delete(key); + const stamp = await made.catch(() => null); + if (!stamp) return; + await stamp.queue; + stamp.engine.free(); +}; + +/** + * releaseStamps frees the engines of all stamps + */ +export const releaseStamps = async () => { + const made = [...engines.values()]; + engines.clear(); + for (const stamp of await Promise.all( + made.map((one) => one.catch(() => null)), + )) { + if (!stamp) continue; + await stamp.queue; + stamp.engine.free(); + } +}; diff --git a/src/pdf/stamps.ts b/src/pdf/stamps.ts new file mode 100644 index 00000000..e687ae40 --- /dev/null +++ b/src/pdf/stamps.ts @@ -0,0 +1,72 @@ +import type { Mark, SavedMark } from "../signing/profile"; + +// A stamp placed on a PDF's page: the user's signature or initials, lines +// of text (a date, a place), or both. Blank's layout engine lays each out +// on a page of its own that is the stamp's box (`Content::Stamp` and +// `Settings::stamp` in src-tauri/layout/src/model.rs): stampEngine.ts paints +// it on screen and writes its one-page PDF, which the signer copies into +// the PDF as a Form XObject. + +export interface StampSpec { + // the user's signature or initials, contained in the box + image?: Mark; + // the lines under the image, in the body font + lines: string[]; + // the size of the lines' letters, in points + size: number; + align: "left" | "center"; +} + +// the src the engine knows a mark's PNG by +export const MARK_SRC: Record = { + signature: "blank-signing:signature", + initials: "blank-signing:initials", +}; + +// what a screen reader would call the image, were a stamp's text read +const MARK_ALT: Record = { + signature: "Signature", + initials: "Initials", +}; + +/** + * stampItem returns the engine's item for a stamp: its image by its mark's + * src, with the mark's size for its shape (none while the user has none) + */ +export const stampItem = (spec: StampSpec, mark?: SavedMark | null) => { + const image = + spec.image && mark && mark.width > 0 && mark.height > 0 + ? { + src: MARK_SRC[spec.image], + width: mark.width, + height: mark.height, + alt: MARK_ALT[spec.image], + } + : { src: "", width: 0, height: 0, alt: "" }; + return { + kind: "stamp", + pos: 0, + ...image, + lines: spec.lines, + size: spec.size, + align: spec.align, + }; +}; + +/** + * stampSettings returns the engine's settings for a stamp's page: its box, + * `width` × `height` points, without margins or headers and footers + */ +export const stampSettings = (width: number, height: number) => ({ + width, + height, + margins: { top: 0, right: 0, bottom: 0, left: 0 }, + stamp: true, +}); + +/** + * stampKey returns what tells stamps apart: what they show, but not its + * size, which a stamp's resize changes and its engine lays out again + */ +export const stampKey = (spec: StampSpec) => + JSON.stringify([spec.image ?? null, spec.lines, spec.align]); From a8b38de38ce3f397e8df05d31dfcc44d1f034f5c Mon Sep 17 00:00:00 2001 From: Florian Purchess Date: Mon, 12 Oct 2026 09:41:48 +0200 Subject: [PATCH 02/50] feat: find where a PDF without a form asks for a signature, a date or a place, by its labels next to lines --- src/pdf/fields.ts | 50 +++ .../labels/de-captions-under-rules.json | 73 ++++ .../__fixtures__/labels/de-dots-one-line.json | 38 +++ .../__fixtures__/labels/de-misses.json | 70 ++++ .../labels/en-colon-underscores.json | 62 ++++ .../labels/en-parens-under-underscores.json | 34 ++ .../__fixtures__/labels/en-prose-traps.json | 301 +++++++++++++++++ .../__fixtures__/labels/en-table-trap.json | 54 +++ src/signing/__fixtures__/labels/form-pdf.json | 46 +++ .../__fixtures__/labels/glued-labels.json | 26 ++ src/signing/__fixtures__/labels/initials.json | 46 +++ .../labels/labels-above-lines.json | 45 +++ .../__fixtures__/labels/whose-signature.json | 65 ++++ src/signing/labels.test.ts | 118 +++++++ src/signing/labels.ts | 313 ++++++++++++++++++ 15 files changed, 1341 insertions(+) create mode 100644 src/pdf/fields.ts create mode 100644 src/signing/__fixtures__/labels/de-captions-under-rules.json create mode 100644 src/signing/__fixtures__/labels/de-dots-one-line.json create mode 100644 src/signing/__fixtures__/labels/de-misses.json create mode 100644 src/signing/__fixtures__/labels/en-colon-underscores.json create mode 100644 src/signing/__fixtures__/labels/en-parens-under-underscores.json create mode 100644 src/signing/__fixtures__/labels/en-prose-traps.json create mode 100644 src/signing/__fixtures__/labels/en-table-trap.json create mode 100644 src/signing/__fixtures__/labels/form-pdf.json create mode 100644 src/signing/__fixtures__/labels/glued-labels.json create mode 100644 src/signing/__fixtures__/labels/initials.json create mode 100644 src/signing/__fixtures__/labels/labels-above-lines.json create mode 100644 src/signing/__fixtures__/labels/whose-signature.json create mode 100644 src/signing/labels.test.ts create mode 100644 src/signing/labels.ts diff --git a/src/pdf/fields.ts b/src/pdf/fields.ts new file mode 100644 index 00000000..8edb3e64 --- /dev/null +++ b/src/pdf/fields.ts @@ -0,0 +1,50 @@ +// What the helper reads of a PDF's pages for signing it: the words and drawn +// lines of a page (the `text` op), and the fields of its form (the protocol's +// `Field`, src-tauri/pdf/ipc/src/protocol.rs, as serde writes it). Every rect +// is `[x, y, width, height]` in display points, from the top left of the +// page as it shows. + +export type Rect = [number, number, number, number]; + +// the words of a page, in reading order as MuPDF gives them, and its drawn +// thin horizontals (strokes and filled rects at least 40 pt wide and at most +// 2 pt high) +export interface PageText { + words: { text: string; rect: Rect }[]; + rules: Rect[]; +} + +export type PdfFieldKind = + "text" | "checkbox" | "radio" | "combo" | "list" | "button" | "signature"; + +export interface PdfFieldFlags { + readOnly: boolean; + required: boolean; + multiline: boolean; + comb: boolean; + maxLen: number | null; +} + +// where a field shows: one of its widgets, on the page `page` (from 0) +export interface PdfWidget { + page: number; + rect: Rect; + // a checkbox's or radio button's state when it is on + onValue: string | null; +} + +// a field of the PDF's form, with its full name and what it inherits +export interface PdfField { + name: string; + kind: PdfFieldKind; + // its `/TU`, what it is called for people + label: string | null; + value: string; + // a choice's options + options: string[]; + flags: PdfFieldFlags; + // a signature field that holds a signature + signed: boolean; + // its widgets on the pages; none for one that shows nowhere + widgets: PdfWidget[]; +} diff --git a/src/signing/__fixtures__/labels/de-captions-under-rules.json b/src/signing/__fixtures__/labels/de-captions-under-rules.json new file mode 100644 index 00000000..1d682ffe --- /dev/null +++ b/src/signing/__fixtures__/labels/de-captions-under-rules.json @@ -0,0 +1,73 @@ +{ + "about": "German captions just below drawn rules: Ort, Datum and Unterschrift", + "text": { + "words": [ + { + "text": "Berlin,", + "rect": [72, 592.82, 33.98, 9.25] + }, + { + "text": "den", + "rect": [108.76, 592.82, 15.6, 9.25] + }, + { + "text": "1.", + "rect": [127.14, 592.82, 7.98, 9.25] + }, + { + "text": "Oktober", + "rect": [137.9, 592.82, 36.4, 9.25] + }, + { + "text": "2026.", + "rect": [177.08, 592.82, 23.58, 9.25] + }, + { + "text": "Beide", + "rect": [203.44, 592.82, 26, 9.25] + }, + { + "text": "Seiten", + "rect": [232.22, 592.82, 31.2, 9.25] + }, + { + "text": "erhalten", + "rect": [266.2, 592.82, 41.6, 9.25] + }, + { + "text": "eine", + "rect": [310.58, 592.82, 20.8, 9.25] + }, + { + "text": "Ausfertigung.", + "rect": [334.16, 592.82, 65.18, 9.25] + }, + { + "text": "Ort,", + "rect": [72, 703.25, 18.38, 9.25] + }, + { + "text": "Datum", + "rect": [93.16, 703.25, 26, 9.25] + }, + { + "text": "Unterschrift", + "rect": [320, 703.25, 62.4, 9.25] + } + ], + "rules": [ + [72, 699.75, 180, 0.5], + [320, 699.75, 200, 0.5] + ] + }, + "expected": [ + { + "kind": "placeAndDate", + "rect": [72, 681.75, 180, 18] + }, + { + "kind": "signature", + "rect": [320, 663.75, 200, 36] + } + ] +} diff --git a/src/signing/__fixtures__/labels/de-dots-one-line.json b/src/signing/__fixtures__/labels/de-dots-one-line.json new file mode 100644 index 00000000..c83d2221 --- /dev/null +++ b/src/signing/__fixtures__/labels/de-dots-one-line.json @@ -0,0 +1,38 @@ +{ + "about": "German labels left of dot runs, both on one line", + "text": { + "words": [ + { + "text": "Ort,", + "rect": [72, 712.82, 18.38, 9.25] + }, + { + "text": "Datum", + "rect": [93.16, 712.82, 26, 9.25] + }, + { + "text": "......................................", + "rect": [121.94, 712.82, 105.64, 9.25] + }, + { + "text": "Unterschrift", + "rect": [235.92, 712.82, 62.4, 9.25] + }, + { + "text": "..........................................", + "rect": [301.1, 712.82, 116.76, 9.25] + } + ], + "rules": [] + }, + "expected": [ + { + "kind": "placeAndDate", + "rect": [121.94, 707.27, 105.64, 14.8] + }, + { + "kind": "signature", + "rect": [301.1, 707.27, 116.76, 14.8] + } + ] +} diff --git a/src/signing/__fixtures__/labels/de-misses.json b/src/signing/__fixtures__/labels/de-misses.json new file mode 100644 index 00000000..750b2c39 --- /dev/null +++ b/src/signing/__fixtures__/labels/de-misses.json @@ -0,0 +1,70 @@ +{ + "about": "German captions the labels don't cover (expected, found or not), and Ort und Datum", + "text": { + "words": [ + { + "text": "Unterschrift", + "rect": [72, 653.94, 56.16, 8.33] + }, + { + "text": "Mieter", + "rect": [130.66, 653.94, 28.08, 8.33] + }, + { + "text": "Signatur/Stempel", + "rect": [260, 653.94, 74.88, 8.33] + }, + { + "text": "Datum", + "rect": [440, 653.94, 23.4, 8.33] + }, + { + "text": "der", + "rect": [465.9, 653.94, 14.04, 8.33] + }, + { + "text": "Unterschrift", + "rect": [482.44, 653.94, 56.16, 8.33] + }, + { + "text": "Ort", + "rect": [72, 692.82, 15.6, 9.25] + }, + { + "text": "und", + "rect": [90.38, 692.82, 15.6, 9.25] + }, + { + "text": "Datum:", + "rect": [108.76, 692.82, 28.78, 9.25] + }, + { + "text": "____________________", + "rect": [140.32, 692.82, 111.2, 9.25] + } + ], + "rules": [ + [72, 649.75, 160, 0.5], + [260, 649.75, 160, 0.5], + [440, 649.75, 120, 0.5] + ] + }, + "expected": [ + { + "kind": "signature", + "rect": [72, 613.75, 160, 36] + }, + { + "kind": "signature", + "rect": [260, 613.75, 160, 36] + }, + { + "kind": "date", + "rect": [440, 631.75, 120, 18] + }, + { + "kind": "placeAndDate", + "rect": [140.32, 687.27, 111.2, 14.8] + } + ] +} diff --git a/src/signing/__fixtures__/labels/en-colon-underscores.json b/src/signing/__fixtures__/labels/en-colon-underscores.json new file mode 100644 index 00000000..69256878 --- /dev/null +++ b/src/signing/__fixtures__/labels/en-colon-underscores.json @@ -0,0 +1,62 @@ +{ + "about": "English labels with a colon, left of underscores, in single-spaced lines; Name gets no place", + "text": { + "words": [ + { + "text": "Application", + "rect": [72, 89.95, 80.08, 12.95] + }, + { + "text": "for", + "rect": [155.97, 89.95, 21.84, 12.95] + }, + { + "text": "a", + "rect": [181.7, 89.95, 7.28, 12.95] + }, + { + "text": "parking", + "rect": [192.88, 89.95, 50.96, 12.95] + }, + { + "text": "permit", + "rect": [247.73, 89.95, 43.68, 12.95] + }, + { + "text": "Name:", + "rect": [72, 592.82, 23.58, 9.25] + }, + { + "text": "____________________", + "rect": [98.36, 592.82, 111.2, 9.25] + }, + { + "text": "Signature:", + "rect": [72, 604.82, 49.58, 9.25] + }, + { + "text": "____________________", + "rect": [124.36, 604.82, 111.2, 9.25] + }, + { + "text": "Date:", + "rect": [72, 616.82, 23.58, 9.25] + }, + { + "text": "____________", + "rect": [98.36, 616.82, 66.72, 9.25] + } + ], + "rules": [] + }, + "expected": [ + { + "kind": "signature", + "rect": [124.36, 599.27, 111.2, 14.8] + }, + { + "kind": "date", + "rect": [98.36, 611.27, 66.72, 14.8] + } + ] +} diff --git a/src/signing/__fixtures__/labels/en-parens-under-underscores.json b/src/signing/__fixtures__/labels/en-parens-under-underscores.json new file mode 100644 index 00000000..b8992938 --- /dev/null +++ b/src/signing/__fixtures__/labels/en-parens-under-underscores.json @@ -0,0 +1,34 @@ +{ + "about": "English captions in brackets below runs of underscores", + "text": { + "words": [ + { + "text": "________________________________", + "rect": [72, 642.82, 177.92, 9.25] + }, + { + "text": "______________________", + "rect": [330, 642.82, 122.32, 9.25] + }, + { + "text": "(signature)", + "rect": [120, 656.26, 42.77, 7.4] + }, + { + "text": "(date)", + "rect": [380, 656.26, 21.97, 7.4] + } + ], + "rules": [] + }, + "expected": [ + { + "kind": "signature", + "rect": [72, 615.07, 177.92, 36] + }, + { + "kind": "date", + "rect": [330, 633.07, 122.32, 18] + } + ] +} diff --git a/src/signing/__fixtures__/labels/en-prose-traps.json b/src/signing/__fixtures__/labels/en-prose-traps.json new file mode 100644 index 00000000..74944d8b --- /dev/null +++ b/src/signing/__fixtures__/labels/en-prose-traps.json @@ -0,0 +1,301 @@ +{ + "about": "Labels in running text, a heading under a separator rule: no places", + "text": { + "words": [ + { + "text": "Notes", + "rect": [72, 89.95, 36.4, 12.95] + }, + { + "text": "on", + "rect": [112.29, 89.95, 14.56, 12.95] + }, + { + "text": "the", + "rect": [130.74, 89.95, 21.84, 12.95] + }, + { + "text": "agreement", + "rect": [156.48, 89.95, 65.52, 12.95] + }, + { + "text": "The", + "rect": [72, 122.82, 15.6, 9.25] + }, + { + "text": "parties", + "rect": [90.38, 122.82, 36.4, 9.25] + }, + { + "text": "agree", + "rect": [129.56, 122.82, 26, 9.25] + }, + { + "text": "that", + "rect": [158.34, 122.82, 20.8, 9.25] + }, + { + "text": "the", + "rect": [181.92, 122.82, 15.6, 9.25] + }, + { + "text": "start", + "rect": [200.3, 122.82, 26, 9.25] + }, + { + "text": "date", + "rect": [229.08, 122.82, 20.8, 9.25] + }, + { + "text": "of", + "rect": [252.66, 122.82, 10.4, 9.25] + }, + { + "text": "this", + "rect": [265.84, 122.82, 20.8, 9.25] + }, + { + "text": "contract", + "rect": [289.42, 122.82, 41.6, 9.25] + }, + { + "text": "is", + "rect": [333.8, 122.82, 10.4, 9.25] + }, + { + "text": "the", + "rect": [346.98, 122.82, 15.6, 9.25] + }, + { + "text": "date", + "rect": [365.36, 122.82, 20.8, 9.25] + }, + { + "text": "it", + "rect": [388.94, 122.82, 10.4, 9.25] + }, + { + "text": "is", + "rect": [402.12, 122.82, 10.4, 9.25] + }, + { + "text": "signed", + "rect": [72, 134.82, 31.2, 9.25] + }, + { + "text": "by", + "rect": [105.98, 134.82, 10.4, 9.25] + }, + { + "text": "both", + "rect": [119.16, 134.82, 20.8, 9.25] + }, + { + "text": "of", + "rect": [142.74, 134.82, 10.4, 9.25] + }, + { + "text": "them.", + "rect": [155.92, 134.82, 23.58, 9.25] + }, + { + "text": "Signed", + "rect": [182.28, 134.82, 31.2, 9.25] + }, + { + "text": "copies", + "rect": [216.26, 134.82, 31.2, 9.25] + }, + { + "text": "are", + "rect": [250.24, 134.82, 15.6, 9.25] + }, + { + "text": "kept", + "rect": [268.62, 134.82, 20.8, 9.25] + }, + { + "text": "by", + "rect": [292.2, 134.82, 10.4, 9.25] + }, + { + "text": "both", + "rect": [305.38, 134.82, 20.8, 9.25] + }, + { + "text": "parties,", + "rect": [328.96, 134.82, 39.18, 9.25] + }, + { + "text": "and", + "rect": [370.92, 134.82, 15.6, 9.25] + }, + { + "text": "the", + "rect": [389.3, 134.82, 15.6, 9.25] + }, + { + "text": "signature", + "rect": [72, 146.82, 46.8, 9.25] + }, + { + "text": "of", + "rect": [121.58, 146.82, 10.4, 9.25] + }, + { + "text": "a", + "rect": [134.76, 146.82, 5.2, 9.25] + }, + { + "text": "witness", + "rect": [142.74, 146.82, 36.4, 9.25] + }, + { + "text": "is", + "rect": [181.92, 146.82, 10.4, 9.25] + }, + { + "text": "not", + "rect": [195.1, 146.82, 15.6, 9.25] + }, + { + "text": "needed.", + "rect": [213.48, 146.82, 33.98, 9.25] + }, + { + "text": "The", + "rect": [250.24, 146.82, 15.6, 9.25] + }, + { + "text": "place", + "rect": [268.62, 146.82, 26, 9.25] + }, + { + "text": "of", + "rect": [297.4, 146.82, 10.4, 9.25] + }, + { + "text": "signing", + "rect": [310.58, 146.82, 36.4, 9.25] + }, + { + "text": "doesn't", + "rect": [349.76, 146.82, 36.4, 9.25] + }, + { + "text": "matter.", + "rect": [388.94, 146.82, 33.98, 9.25] + }, + { + "text": "Place", + "rect": [72, 173.63, 31.2, 11.1] + }, + { + "text": "of", + "rect": [106.54, 173.63, 12.48, 11.1] + }, + { + "text": "performance", + "rect": [122.35, 173.63, 68.64, 11.1] + }, + { + "text": "Date", + "rect": [72, 192.82, 20.8, 9.25] + }, + { + "text": "and", + "rect": [95.58, 192.82, 15.6, 9.25] + }, + { + "text": "place", + "rect": [113.96, 192.82, 26, 9.25] + }, + { + "text": "are", + "rect": [142.74, 192.82, 15.6, 9.25] + }, + { + "text": "given", + "rect": [161.12, 192.82, 26, 9.25] + }, + { + "text": "in", + "rect": [189.9, 192.82, 10.4, 9.25] + }, + { + "text": "the", + "rect": [203.08, 192.82, 15.6, 9.25] + }, + { + "text": "header.", + "rect": [221.46, 192.82, 33.98, 9.25] + }, + { + "text": "Sign", + "rect": [258.22, 192.82, 20.8, 9.25] + }, + { + "text": "here", + "rect": [281.8, 192.82, 20.8, 9.25] + }, + { + "text": "only", + "rect": [305.38, 192.82, 20.8, 9.25] + }, + { + "text": "if", + "rect": [328.96, 192.82, 10.4, 9.25] + }, + { + "text": "you", + "rect": [342.14, 192.82, 15.6, 9.25] + }, + { + "text": "agree.", + "rect": [360.52, 192.82, 28.78, 9.25] + }, + { + "text": "Initials:", + "rect": [72, 222.82, 44.38, 9.25] + }, + { + "text": "see", + "rect": [119.16, 222.82, 15.6, 9.25] + }, + { + "text": "the", + "rect": [137.54, 222.82, 15.6, 9.25] + }, + { + "text": "appendix", + "rect": [155.92, 222.82, 41.6, 9.25] + }, + { + "text": "for", + "rect": [200.3, 222.82, 15.6, 9.25] + }, + { + "text": "the", + "rect": [218.68, 222.82, 15.6, 9.25] + }, + { + "text": "initials", + "rect": [237.06, 222.82, 41.6, 9.25] + }, + { + "text": "of", + "rect": [281.44, 222.82, 10.4, 9.25] + }, + { + "text": "every", + "rect": [294.62, 222.82, 26, 9.25] + }, + { + "text": "clerk.", + "rect": [323.4, 222.82, 28.78, 9.25] + } + ], + "rules": [[72, 169.75, 450, 0.5]] + }, + "expected": [] +} diff --git a/src/signing/__fixtures__/labels/en-table-trap.json b/src/signing/__fixtures__/labels/en-table-trap.json new file mode 100644 index 00000000..60ca4714 --- /dev/null +++ b/src/signing/__fixtures__/labels/en-table-trap.json @@ -0,0 +1,54 @@ +{ + "about": "A table whose header says Date, between drawn borders: no places", + "text": { + "words": [ + { + "text": "Payments", + "rect": [72, 262.82, 41.6, 9.25] + }, + { + "text": "received", + "rect": [116.38, 262.82, 41.6, 9.25] + }, + { + "text": "so", + "rect": [160.76, 262.82, 10.4, 9.25] + }, + { + "text": "far:", + "rect": [173.94, 262.82, 18.38, 9.25] + }, + { + "text": "Date", + "rect": [76, 304.82, 20.8, 9.25] + }, + { + "text": "Amount", + "rect": [276, 304.82, 31.2, 9.25] + }, + { + "text": "2026-01-05", + "rect": [76, 322.82, 52, 9.25] + }, + { + "text": "100", + "rect": [276, 322.82, 15.6, 9.25] + }, + { + "text": "EUR", + "rect": [294.38, 322.82, 15.6, 9.25] + }, + { + "text": "Datum", + "rect": [76, 340.82, 26, 9.25] + } + ], + "rules": [ + [72, 299.75, 400, 0.5], + [72, 317.75, 400, 0.5], + [72, 335.75, 400, 0.5], + [72, 353.75, 200, 0.5] + ] + }, + "expected": [] +} diff --git a/src/signing/__fixtures__/labels/form-pdf.json b/src/signing/__fixtures__/labels/form-pdf.json new file mode 100644 index 00000000..c9382f49 --- /dev/null +++ b/src/signing/__fixtures__/labels/form-pdf.json @@ -0,0 +1,46 @@ +{ + "about": "src-tauri/pdf/fixtures/form.pdf: its words as pdftotext -bbox reads them, and its rule (0.5 w 100 490 m 300 490 l S) drawn by hand", + "text": { + "words": [ + { + "text": "Contract", + "rect": [100, 73.384, 45.35, 11.1] + }, + { + "text": "A-17", + "rect": [352, 128.82, 21.12, 9.25] + }, + { + "text": "Green", + "rect": [102, 228.82, 27.79, 9.25] + }, + { + "text": "Unterschrift", + "rect": [100, 364.82, 51.12, 9.25] + }, + { + "text": "Ort,", + "rect": [350, 364.82, 16.67, 9.25] + }, + { + "text": "Datum:", + "rect": [369.45, 364.82, 32.23, 9.25] + }, + { + "text": "______________", + "rect": [404.46, 364.82, 77.84, 9.25] + } + ], + "rules": [[100, 351.75, 200, 0.5]] + }, + "expected": [ + { + "kind": "signature", + "rect": [100, 315.75, 200, 36] + }, + { + "kind": "placeAndDate", + "rect": [404.46, 359.27, 77.84, 14.8] + } + ] +} diff --git a/src/signing/__fixtures__/labels/glued-labels.json b/src/signing/__fixtures__/labels/glued-labels.json new file mode 100644 index 00000000..d65e8f06 --- /dev/null +++ b/src/signing/__fixtures__/labels/glued-labels.json @@ -0,0 +1,26 @@ +{ + "about": "Labels written into the same word as their underscores", + "text": { + "words": [ + { + "text": "Signature:______________________________", + "rect": [72, 492.82, 216.38, 9.25] + }, + { + "text": "Datum:____________________", + "rect": [72, 532.82, 139.98, 9.25] + } + ], + "rules": [] + }, + "expected": [ + { + "kind": "signature", + "rect": [126.1, 487.27, 162.29, 14.8] + }, + { + "kind": "date", + "rect": [104.3, 527.27, 107.68, 14.8] + } + ] +} diff --git a/src/signing/__fixtures__/labels/initials.json b/src/signing/__fixtures__/labels/initials.json new file mode 100644 index 00000000..29ac04d5 --- /dev/null +++ b/src/signing/__fixtures__/labels/initials.json @@ -0,0 +1,46 @@ +{ + "about": "Initials at the foot of a page, in English, German and French-style German", + "text": { + "words": [ + { + "text": "Initials:", + "rect": [420, 792.82, 44.38, 9.25] + }, + { + "text": "__________", + "rect": [467.16, 792.82, 55.6, 9.25] + }, + { + "text": "Kürzel", + "rect": [72, 792.82, 31.2, 9.25] + }, + { + "text": "________", + "rect": [105.98, 792.82, 44.48, 9.25] + }, + { + "text": "Paraphe:", + "rect": [230, 792.82, 39.18, 9.25] + }, + { + "text": "_________", + "rect": [271.96, 792.82, 50.04, 9.25] + } + ], + "rules": [] + }, + "expected": [ + { + "kind": "initials", + "rect": [467.16, 787.27, 55.6, 14.8] + }, + { + "kind": "initials", + "rect": [105.98, 787.27, 44.48, 14.8] + }, + { + "kind": "initials", + "rect": [271.96, 787.27, 50.04, 14.8] + } + ] +} diff --git a/src/signing/__fixtures__/labels/labels-above-lines.json b/src/signing/__fixtures__/labels/labels-above-lines.json new file mode 100644 index 00000000..ade7abaa --- /dev/null +++ b/src/signing/__fixtures__/labels/labels-above-lines.json @@ -0,0 +1,45 @@ +{ + "about": "Labels above drawn rules, and Sign here left of underscores", + "text": { + "words": [ + { + "text": "Signature", + "rect": [72, 492.82, 46.8, 9.25] + }, + { + "text": "Datum", + "rect": [320, 492.82, 26, 9.25] + }, + { + "text": "Sign", + "rect": [72, 592.82, 20.8, 9.25] + }, + { + "text": "here:", + "rect": [95.58, 592.82, 23.58, 9.25] + }, + { + "text": "____________________", + "rect": [121.94, 592.82, 111.2, 9.25] + } + ], + "rules": [ + [72, 539.75, 200, 0.5], + [320, 524.75, 150, 0.5] + ] + }, + "expected": [ + { + "kind": "signature", + "rect": [72, 502.07, 200, 37.68] + }, + { + "kind": "date", + "rect": [320, 502.07, 150, 22.68] + }, + { + "kind": "signature", + "rect": [121.94, 587.27, 111.2, 14.8] + } + ] +} diff --git a/src/signing/__fixtures__/labels/whose-signature.json b/src/signing/__fixtures__/labels/whose-signature.json new file mode 100644 index 00000000..5962f63e --- /dev/null +++ b/src/signing/__fixtures__/labels/whose-signature.json @@ -0,0 +1,65 @@ +{ + "about": "Signature labels that say whose they are", + "text": { + "words": [ + { + "text": "Unterschrift", + "rect": [72, 653.94, 56.16, 8.33] + }, + { + "text": "des", + "rect": [130.66, 653.94, 14.04, 8.33] + }, + { + "text": "Vermieters", + "rect": [147.2, 653.94, 46.8, 8.33] + }, + { + "text": "Signature", + "rect": [320, 653.94, 42.12, 8.33] + }, + { + "text": "of", + "rect": [364.62, 653.94, 9.36, 8.33] + }, + { + "text": "the", + "rect": [376.48, 653.94, 14.04, 8.33] + }, + { + "text": "tenant", + "rect": [393.03, 653.94, 28.08, 8.33] + }, + { + "text": "Signed", + "rect": [72, 712.82, 31.2, 9.25] + }, + { + "text": "by:", + "rect": [105.98, 712.82, 13.18, 9.25] + }, + { + "text": "______________________", + "rect": [121.94, 712.82, 122.32, 9.25] + } + ], + "rules": [ + [72, 649.75, 200, 0.5], + [320, 649.75, 200, 0.5] + ] + }, + "expected": [ + { + "kind": "signature", + "rect": [72, 613.75, 200, 36] + }, + { + "kind": "signature", + "rect": [320, 613.75, 200, 36] + }, + { + "kind": "signature", + "rect": [121.94, 707.27, 122.32, 14.8] + } + ] +} diff --git a/src/signing/labels.test.ts b/src/signing/labels.test.ts new file mode 100644 index 00000000..1f742fa6 --- /dev/null +++ b/src/signing/labels.test.ts @@ -0,0 +1,118 @@ +import { readdirSync, readFileSync } from "node:fs"; +import { join } from "node:path"; + +import { describe, expect, it } from "vitest"; + +import type { PageText, Rect } from "../pdf/fields"; +import { findLabels, type PlaceKind } from "./labels"; + +// The fixtures are pages as the helper reads them, each with the places it +// has: hand-made forms and traps, and src-tauri/pdf/fixtures/form.pdf. +interface Fixture { + about: string; + text: PageText; + expected: { kind: PlaceKind; rect: Rect }[]; +} + +const folder = join(__dirname, "__fixtures__", "labels"); +const fixtures = readdirSync(folder) + .filter((name) => name.endsWith(".json")) + .map((name) => ({ + name, + ...(JSON.parse(readFileSync(join(folder, name), "utf8")) as Fixture), + })); + +const iou = (a: Rect, b: Rect) => { + const width = Math.min(a[0] + a[2], b[0] + b[2]) - Math.max(a[0], b[0]); + const height = Math.min(a[1] + a[3], b[1] + b[3]) - Math.max(a[1], b[1]); + const shared = Math.max(0, width) * Math.max(0, height); + return shared / (a[2] * a[3] + b[2] * b[3] - shared); +}; + +// a found place is right when it asks for what an expected one does, about +// where it is +const matches = ( + found: { kind: PlaceKind; rect: Rect }, + expected: { kind: PlaceKind; rect: Rect }, +) => found.kind === expected.kind && iou(found.rect, expected.rect) >= 0.5; + +describe("found places", () => { + it("finds the places of the fixtures, with few wrong ones", () => { + let right = 0; + let foundCount = 0; + let expectedCount = 0; + const rows: string[] = []; + for (const fixture of fixtures) { + const found = findLabels(fixture.text, 0); + const hits = found.filter((place) => + fixture.expected.some((expected) => matches(place, expected)), + ).length; + const caught = fixture.expected.filter((expected) => + found.some((place) => matches(place, expected)), + ).length; + right += caught; + foundCount += found.length; + expectedCount += fixture.expected.length; + rows.push( + `${fixture.name}: ${hits}/${found.length} found right, ${caught}/${fixture.expected.length} expected found`, + ); + } + const precision = foundCount ? right / foundCount : 1; + const recall = expectedCount ? right / expectedCount : 1; + console.log( + `${rows.join("\n")}\nprecision ${precision.toFixed(3)}, recall ${recall.toFixed(3)}`, + ); + expect(precision).toBeGreaterThanOrEqual(0.9); + expect(recall).toBeGreaterThanOrEqual(0.8); + }); + + it("finds form.pdf's signature over its rule and its place and date", () => { + const fixture = fixtures.find((each) => each.name === "form-pdf.json")!; + const found = findLabels(fixture.text, 2); + expect( + found.map(({ kind, label, page }) => ({ kind, label, page })), + ).toEqual([ + { kind: "placeAndDate", label: "Ort, Datum", page: 2 }, + { kind: "signature", label: "Unterschrift", page: 2 }, + ]); + expect(found[1].rect).toEqual([100, 315.75, 200, 36]); + }); + + it("finds nothing in running text or a table", () => { + for (const name of ["en-prose-traps.json", "en-table-trap.json"]) { + const fixture = fixtures.find((each) => each.name === name)!; + expect(findLabels(fixture.text, 0)).toEqual([]); + } + }); + + it("gives an unpaired label nothing", () => { + const text: PageText = { + words: [{ text: "Signature:", rect: [72, 100, 50, 9.25] }], + rules: [], + }; + expect(findLabels(text, 0)).toEqual([]); + }); + + it("finds a place once when underscores lie on a drawn rule", () => { + const text: PageText = { + words: [ + { text: "Date:", rect: [72, 100, 25, 9.25] }, + { text: "__________", rect: [100, 100, 55.6, 9.25] }, + ], + rules: [[100, 108.5, 56, 0.5]], + }; + expect(findLabels(text, 0)).toHaveLength(1); + }); + + it("leaves a line alone that a short rule or another word takes", () => { + const text: PageText = { + words: [ + { text: "Date:", rect: [72, 100, 25, 9.25] }, + { text: "today", rect: [100, 100, 25, 9.25] }, + { text: "__________", rect: [130, 100, 55.6, 9.25] }, + ], + rules: [[300, 100, 30, 0.5]], + }; + expect(findLabels(text, 0)).toEqual([]); + }); +}); diff --git a/src/signing/labels.ts b/src/signing/labels.ts new file mode 100644 index 00000000..b859dd8c --- /dev/null +++ b/src/signing/labels.ts @@ -0,0 +1,313 @@ +import type { PageText, Rect } from "../pdf/fields"; + +// Found places: where a PDF without a form asks for a signature, a date or a +// place, read from the words and lines of its pages. A label ("Unterschrift", +// "Ort, Datum:", "(signature)") counts only next to a line to write on (a run +// of underscores or dots, or a drawn rule), so a word in running text finds +// nothing: a place that isn't there is worse than one the user adds. + +export type PlaceKind = + "signature" | "date" | "place" | "placeAndDate" | "initials"; + +// a place found on the page `page` (from 0): what goes there, where, and the +// label it was found by, as the page writes it +export interface FoundPlace { + page: number; + kind: PlaceKind; + rect: Rect; + label: string; +} + +// the labels, normalized (see `normalize`) +const LABELS: Record = { + signature: "signature", + unterschrift: "signature", + signed: "signature", + "sign here": "signature", + signatur: "signature", + date: "date", + datum: "date", + place: "place", + ort: "place", + "place, date": "placeAndDate", + "date, place": "placeAndDate", + "ort, datum": "placeAndDate", + "datum, ort": "placeAndDate", + initials: "initials", + kürzel: "initials", + paraphe: "initials", +}; + +// the words that may follow a signature's label and say whose it is, as in +// "Signature of the tenant" or "Unterschrift des Mieters" +const WHOSE = new Set(["of", "by", "for", "des", "der", "von", "vom", "für"]); + +// how far a label may be from its line, in points +const BASELINE = 3; +const LEFT_GAP = 48; +const BELOW = 14; +const ABOVE_MIN = 12; +const ABOVE_MAX = 72; +// how far apart two places may be on every side and still be one +export const NEAR = 4; + +const RUN = /_{5,}|\.{8,}|…{3,}/gu; +const LETTER = /[\p{L}\p{N}]/u; + +/** + * normalize returns a label as LABELS has it: in lower case, without its + * colon, brackets or asterisk, with "/", "&", "und" and "and" between two + * words written as ", " + */ +const normalize = (text: string) => + text + .normalize("NFC") + .toLowerCase() + .replace(/[():*"“”„]/gu, "") + .replace(/\s+(?:und|and|&)\s+/gu, ", ") + .replace(/\s*[,/]\s*/gu, ", ") + .replace(/[\s,.;]+$/u, "") + .replace(/^[\s,.;]+/u, "") + .replace(/\s+/gu, " "); + +/** + * labelOf returns what the words `texts` ask for, if they are a label as a + * whole, or a signature's label followed by whose it is in up to three words + */ +const labelOf = (texts: string[]): PlaceKind | undefined => { + const whole = LABELS[normalize(texts.join(" "))]; + if (whole) return whole; + for (const length of [2, 1]) { + if (texts.length <= length || texts.length > length + 3) continue; + const kind = LABELS[normalize(texts.slice(0, length).join(" "))]; + if ( + (kind === "signature" || kind === "initials") && + WHOSE.has(normalize(texts[length])) + ) + return kind; + } + return undefined; +}; + +// a word of the page, or the part of one that is text or a line +interface Token { + text: string; + rect: Rect; + line: boolean; +} + +const right = (rect: Rect) => rect[0] + rect[2]; +const bottom = (rect: Rect) => rect[1] + rect[3]; +const centre = (rect: Rect) => rect[0] + rect[2] / 2; + +/** + * tokensOf splits the words at their runs of underscores or dots, e.g. + * "Datum:__________" into "Datum:" and a line, sharing the word's width out + * by characters + */ +const tokensOf = (words: PageText["words"]): Token[] => + words.flatMap(({ text, rect }) => { + const chars = [...text]; + const at = (index: number) => + rect[0] + (rect[2] * [...text.slice(0, index)].length) / chars.length; + const tokens: Token[] = []; + const add = (start: number, end: number, line: boolean) => { + const part = text.slice(start, end); + if (!line && !LETTER.test(part)) return; + const x = at(start); + tokens.push({ + text: part.trim(), + rect: [x, rect[1], at(end) - x, rect[3]], + line, + }); + }; + let last = 0; + for (const match of text.matchAll(RUN)) { + add(last, match.index, false); + add(match.index, match.index + match[0].length, true); + last = match.index + match[0].length; + } + add(last, text.length, false); + return tokens; + }); + +/** + * overlap returns the area two rects share + */ +export const overlap = (a: Rect, b: Rect) => + Math.max(0, Math.min(right(a), right(b)) - Math.max(a[0], b[0])) * + Math.max(0, Math.min(bottom(a), bottom(b)) - Math.max(a[1], b[1])); + +/** + * near tells whether two rects are within NEAR points on every side + */ +export const near = (a: Rect, b: Rect) => + Math.abs(a[0] - b[0]) <= NEAR && + Math.abs(a[1] - b[1]) <= NEAR && + Math.abs(right(a) - right(b)) <= NEAR && + Math.abs(bottom(a) - bottom(b)) <= NEAR; + +/** + * mergeNear keeps the first of places on the same page within NEAR points of + * each other, e.g. a run of underscores over a drawn rule + */ +export const mergeNear = ( + places: T[], +): T[] => + places.reduce((kept, place) => { + if ( + !kept.some( + (other) => other.page === place.page && near(other.rect, place.rect), + ) + ) + kept.push(place); + return kept; + }, []); + +/** + * findLabels returns the places the page `page` asks to be signed, dated or + * given a place on, in the order they were found; `text` is what the helper + * read of the page + */ +export const findLabels = (text: PageText, page: number): FoundPlace[] => { + const tokens = tokensOf(text.words); + const words = tokens.filter((token) => !token.line); + // every line as a thin rect where it is drawn: a run of characters at the + // bottom of its word + const lines: Rect[] = [ + ...tokens + .filter((token) => token.line) + .map((token): Rect => [ + token.rect[0], + bottom(token.rect) - 1, + token.rect[2], + 1, + ]), + ...text.rules.filter((rule) => rule[2] >= 40), + ]; + const usedWords = new Set(); + const usedLines = new Set(); + const found: FoundPlace[] = []; + + const sameLine = (a: Token, b: Token) => + Math.abs(bottom(a.rect) - bottom(b.rect)) <= BASELINE; + // the gap between two words of one phrase + const spacing = (token: Token) => Math.max(4, token.rect[3] * 0.8); + + // a place must be free to write in: no word of the page covers half of + // itself there, but the label's and the lines' + const free = (rect: Rect, label: Token[]) => + words.every( + (word) => + label.includes(word) || + overlap(word.rect, rect) <= (word.rect[2] * word.rect[3]) / 2, + ); + + const add = (kind: PlaceKind, rect: Rect, line: Rect, label: Token[]) => { + if (!free(rect, label)) return false; + usedLines.add(line); + for (const word of label) usedWords.add(word); + const written = label + .map((word) => word.text) + .join(" ") + .replace(/^\(|\)$|:$/gu, "") + .trim(); + found.push({ page, kind, rect, label: written }); + return true; + }; + + // a caption: the words of the text line nearest to the line among + // `candidates` that stand over or under it, if no other word of their text + // line runs on from them + const caption = ( + candidates: Token[], + nearest: (a: Token, b: Token) => number, + ) => { + if (!candidates.length) return undefined; + const first = [...candidates].sort(nearest)[0]; + const label = candidates + .filter((word) => sameLine(word, first)) + .sort((a, b) => a.rect[0] - b.rect[0]); + const start = label[0].rect[0]; + const end = right(label[label.length - 1].rect); + const runsOn = words.some( + (word) => + !label.includes(word) && + sameLine(word, first) && + ((right(word.rect) <= start && + start - right(word.rect) < spacing(word)) || + (word.rect[0] >= end && word.rect[0] - end < spacing(word))), + ); + return runsOn ? undefined : label; + }; + + // a label left of a line on its baseline: the place is on the line + for (const line of lines) { + const before = words + .filter( + (word) => + !usedWords.has(word) && + Math.abs(bottom(word.rect) - bottom(line)) <= BASELINE && + right(word.rect) <= line[0] + 2, + ) + .sort((a, b) => right(b.rect) - right(a.rect)); + if (!before.length || line[0] - right(before[0].rect) > LEFT_GAP) continue; + const phrase = [before[0]]; + for (const word of before.slice(1, 5)) { + const next = phrase[phrase.length - 1]; + if (next.rect[0] - right(word.rect) > spacing(word)) break; + phrase.push(word); + } + for (let length = phrase.length; length > 0; length--) { + const label = phrase.slice(0, length).reverse(); + const kind = labelOf(label.map((word) => word.text)); + if (!kind) continue; + const height = 1.6 * Math.max(...label.map((word) => word.rect[3])); + add(kind, [line[0], bottom(line) - height, line[2], height], line, label); + break; + } + } + + const over = (line: Rect, word: Token) => + centre(word.rect) >= line[0] - 2 && centre(word.rect) <= right(line) + 2; + + // a label just below a line: the place is above the line + for (const line of lines) { + if (usedLines.has(line)) continue; + const label = caption( + words.filter( + (word) => + !usedWords.has(word) && + over(line, word) && + word.rect[1] >= bottom(line) - 2 && + word.rect[1] - bottom(line) <= BELOW, + ), + (a, b) => a.rect[1] - b.rect[1], + ); + const kind = label && labelOf(label.map((word) => word.text)); + if (!label || !kind) continue; + const height = kind === "signature" || kind === "initials" ? 36 : 18; + add(kind, [line[0], line[1] - height, line[2], height], line, label); + } + + // a label above a line: the place is between them + for (const line of lines) { + if (usedLines.has(line)) continue; + const label = caption( + words.filter( + (word) => + !usedWords.has(word) && + over(line, word) && + line[1] - bottom(word.rect) >= ABOVE_MIN && + line[1] - bottom(word.rect) <= ABOVE_MAX, + ), + (a, b) => bottom(b.rect) - bottom(a.rect), + ); + const kind = label && labelOf(label.map((word) => word.text)); + if (!label || !kind) continue; + const top = Math.max(...label.map((word) => bottom(word.rect))); + add(kind, [line[0], top, line[2], line[1] - top], line, label); + } + + return mergeNear(found); +}; From 2162bd57ff9b866bd8cb9f2cb66d41d2fc5e10c5 Mon Sep 17 00:00:00 2001 From: Florian Purchess Date: Mon, 12 Oct 2026 09:42:52 +0200 Subject: [PATCH 03/50] feat: put a PDF's fields and found places in reading order, with Next, dismissing and what filling each does --- src/pdf/places.test.ts | 304 +++++++++++++++++++++++++++++++++++++ src/pdf/places.ts | 334 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 638 insertions(+) create mode 100644 src/pdf/places.test.ts create mode 100644 src/pdf/places.ts diff --git a/src/pdf/places.test.ts b/src/pdf/places.test.ts new file mode 100644 index 00000000..502bbf94 --- /dev/null +++ b/src/pdf/places.test.ts @@ -0,0 +1,304 @@ +import { describe, expect, it } from "vitest"; + +import type { FoundPlace } from "../signing/labels"; +import type { PdfField, Rect } from "./fields"; +import { + dismissPlace, + fieldLabel, + fillOf, + isTarget, + nextButton, + nextPlace, + type Place, + placesOf, + prefillOf, + readingOrder, +} from "./places"; + +const field = ( + name: string, + rect: Rect, + more: Partial = {}, + page = 0, +): PdfField => ({ + name, + kind: "text", + label: null, + value: "", + options: [], + flags: { + readOnly: false, + required: false, + multiline: false, + comb: false, + maxLen: null, + }, + signed: false, + widgets: [{ page, rect, onValue: null }], + ...more, +}); + +const found = (kind: FoundPlace["kind"], rect: Rect, page = 0): FoundPlace => ({ + page, + kind, + rect, + label: kind, +}); + +const ids = (places: Place[]) => places.map((place) => place.id); + +describe("the places of a PDF", () => { + it("takes the fields that can be filled or signed", () => { + const locked = new Set(["locked"]); + const fields = [ + field("name", [0, 0, 10, 10]), + field("id", [0, 0, 10, 10], { + flags: { ...field("", [0, 0, 0, 0]).flags, readOnly: true }, + }), + field("locked", [0, 0, 10, 10]), + field("send", [0, 0, 10, 10], { kind: "button" }), + field("signed", [0, 0, 10, 10], { kind: "signature", signed: true }), + field("signature", [0, 0, 10, 10], { kind: "signature" }), + field("hidden", [0, 0, 10, 10], { widgets: [] }), + ]; + expect( + fields.filter((each) => isTarget(each, locked)).map((each) => each.name), + ).toEqual(["name", "signature"]); + }); + + it("calls a field by its /TU, else by its name in words", () => { + expect( + fieldLabel(field("name", [0, 0, 1, 1], { label: "Your name" })), + ).toBe("Your name"); + expect(fieldLabel(field("form.signature_1", [0, 0, 1, 1]))).toBe( + "Signature", + ); + expect(fieldLabel(field("page1.firstName[0]", [0, 0, 1, 1]))).toBe( + "First name", + ); + expect(fieldLabel(field("Text-12", [0, 0, 1, 1]))).toBe("Text"); + expect(fieldLabel(field("7", [0, 0, 1, 1]))).toBe("7"); + }); + + it("orders places by page, line and from left to right", () => { + const places = placesOf({ + fields: [ + field("b", [300, 100, 100, 20]), + field("c", [100, 200, 100, 20]), + field("d", [50, 50, 100, 20], {}, 1), + // three points lower and further left: on the line of b + field("a", [100, 103, 50, 20]), + ], + found: [], + }); + expect(ids(places)).toEqual(["field:a", "field:b", "field:c", "field:d"]); + }); + + it("keeps the order of things on a line by their bottoms", () => { + // a tall signature place and a short date place standing on one line + const order = readingOrder([ + { page: 0, rect: [300, 482, 100, 18] as Rect, id: "date" }, + { page: 0, rect: [72, 464, 200, 36] as Rect, id: "signature" }, + ]); + expect(order.map((each) => each.id)).toEqual(["signature", "date"]); + }); + + it("puts a radio group at its buttons on the first page it shows on", () => { + const radio = field("pay", [100, 100, 10, 10], { + kind: "radio", + widgets: [ + { page: 0, rect: [100, 100, 10, 10], onValue: "card" }, + { page: 0, rect: [200, 100, 10, 10], onValue: "cash" }, + { page: 1, rect: [0, 0, 10, 10], onValue: "later" }, + ], + }); + expect(placesOf({ fields: [radio], found: [] })[0]).toMatchObject({ + page: 0, + rect: [100, 100, 110, 10], + kind: "radio", + }); + }); + + it("lets a field win over a found place at its spot", () => { + const places = placesOf({ + // form.pdf's signature field and the place found over its rule + fields: [field("signature", [100, 292, 200, 50], { kind: "signature" })], + found: [ + found("signature", [100, 315.75, 200, 36]), + found("date", [350, 330, 100, 18]), + ], + }); + expect(places.map(({ source, kind }) => ({ source, kind }))).toEqual([ + { source: "field", kind: "signature" }, + { source: "found", kind: "date" }, + ]); + }); + + it("keeps a found place a field barely touches, or one on another page", () => { + const places = placesOf({ + fields: [field("note", [100, 100, 200, 20])], + found: [ + found("date", [100, 115, 200, 18]), + found("date", [100, 100, 200, 20], 1), + ], + }); + expect(places).toHaveLength(3); + }); + + it("lets a signed signature's widget hide the place found under it", () => { + const places = placesOf({ + fields: [ + field("signed", [100, 300, 200, 50], { + kind: "signature", + signed: true, + }), + ], + found: [found("signature", [100, 310, 200, 36])], + }); + expect(places).toEqual([]); + }); + + it("makes found places within 4 pt one", () => { + const places = placesOf({ + fields: [], + found: [ + found("date", [100, 100, 100, 18]), + found("date", [103, 98, 98, 21]), + found("date", [100, 130, 100, 18]), + ], + }); + expect(ids(places)).toEqual(["found:0:100:100", "found:0:100:130"]); + }); + + it("dismisses found places, never fields, and undoes with the old set", () => { + const fields = [field("name", [0, 0, 10, 10])]; + const places = placesOf({ + fields, + found: [found("date", [0, 50, 50, 18])], + }); + const none = new Set(); + const dismissed = dismissPlace(none, places[1]); + expect(dismissPlace(dismissed, places[0])).toBe(dismissed); + expect( + ids( + placesOf({ + fields, + found: [found("date", [0, 50, 50, 18])], + dismissed, + }), + ), + ).toEqual(["field:name"]); + expect(none.size).toBe(0); + expect( + placesOf({ + fields, + found: [found("date", [0, 50, 50, 18])], + dismissed: none, + }), + ).toHaveLength(2); + }); +}); + +describe("Next", () => { + const places = placesOf({ + fields: [ + field("a", [0, 0, 10, 10]), + field("b", [0, 100, 10, 10]), + field("c", [0, 200, 10, 10]), + ], + found: [], + }); + + it("goes to the next place that isn't filled, coming round", () => { + const filled = new Set(["field:b"]); + expect(nextPlace(places, filled)?.id).toBe("field:a"); + expect(nextPlace(places, filled, "field:a")?.id).toBe("field:c"); + expect(nextPlace(places, filled, "field:c")?.id).toBe("field:a"); + expect(nextPlace(places, filled, "gone")?.id).toBe("field:a"); + expect(nextPlace([], filled)).toBeUndefined(); + }); + + it("counts the places left", () => { + expect(nextButton(places, new Set())).toMatchObject({ + text: "Next · 1 of 3", + disabled: false, + }); + expect(nextButton(places, new Set(["field:a"]), "field:a")).toMatchObject({ + text: "Next · 1 of 2", + place: places[1], + }); + expect(nextButton(places, new Set(["field:a"]), "field:b").text).toBe( + "Next · 2 of 2", + ); + }); + + it("says when every place is filled", () => { + expect(nextButton(places, new Set(ids(places)))).toEqual({ + text: "All places filled", + place: undefined, + disabled: true, + }); + expect(nextButton([], new Set()).disabled).toBe(true); + }); +}); + +describe("filling a place", () => { + const placeOf = (each: PdfField) => + placesOf({ fields: [each], found: [] })[0]; + + it("stamps a found place with what its label asks for", () => { + for (const kind of [ + "signature", + "initials", + "date", + "place", + "placeAndDate", + ] as const) { + const [place] = placesOf({ + fields: [], + found: [found(kind, [0, 0, 100, 18])], + }); + expect(fillOf(place)).toEqual({ action: "stamp", stamp: kind }); + } + }); + + it("signs a signature field, toggles a checkbox, lets the user choose", () => { + const rect: Rect = [0, 0, 10, 10]; + expect(fillOf(placeOf(field("s", rect, { kind: "signature" })))).toEqual({ + action: "stamp", + stamp: "signature", + }); + expect(fillOf(placeOf(field("c", rect, { kind: "checkbox" })))).toEqual({ + action: "toggle", + }); + for (const kind of ["radio", "combo", "list"] as const) + expect(fillOf(placeOf(field("x", rect, { kind })))).toEqual({ + action: "choose", + }); + expect(fillOf(placeOf(field("street", rect)))).toEqual({ action: "text" }); + expect(fillOf(placeOf(field("f", rect, { label: "Datum" })))).toEqual({ + action: "text", + prefill: "date", + }); + }); + + it("prefills text fields asking for the signer's name, the date or the place", () => { + const prefill = (name: string, label: string | null = null) => + prefillOf(field(name, [0, 0, 1, 1], { label })); + expect(prefill("name", "Your name")).toBe("name"); + expect(prefill("fullName")).toBe("name"); + expect(prefill("Text1", "Date")).toBe("date"); + expect(prefill("signing_date")).toBe("date"); + expect(prefill("ort")).toBe("place"); + expect(prefill("x", "Ort, Datum")).toBe("placeAndDate"); + expect(prefill("x", "Place and date")).toBe("placeAndDate"); + // other dates, places and names + expect(prefill("x", "Date of birth")).toBeUndefined(); + expect(prefill("Geburtsdatum")).toBeUndefined(); + expect(prefill("Wohnort")).toBeUndefined(); + expect(prefill("firstName")).toBeUndefined(); + expect(prefill("x", "Name, Vorname")).toBeUndefined(); + expect(prefill("x", "Name and date")).toBeUndefined(); + expect(prefill("street")).toBeUndefined(); + }); +}); diff --git a/src/pdf/places.ts b/src/pdf/places.ts new file mode 100644 index 00000000..21f96bf8 --- /dev/null +++ b/src/pdf/places.ts @@ -0,0 +1,334 @@ +import { + type FoundPlace, + mergeNear, + overlap, + type PlaceKind, +} from "../signing/labels"; +import type { PdfField, Rect } from "./fields"; + +// The places of a PDF tab: where the user signs, dates or fills it in. They +// are its form's fields that can take a value, and the places found by their +// labels (src/signing/labels.ts), which a field at the same spot wins over; +// in reading order, for Tab and "Next". + +export type FieldPlaceKind = + "text" | "checkbox" | "radio" | "combo" | "list" | "signature"; + +interface PlaceBase { + // stays the same while the PDF does: what filled and dismissed places are + // remembered by + id: string; + // from 0 + page: number; + rect: Rect; + // what it is called for people + label: string; +} + +// a field of the form +export interface FieldPlace extends PlaceBase { + source: "field"; + kind: FieldPlaceKind; + field: PdfField; +} + +// a place found by its label +export interface LabelPlace extends PlaceBase { + source: "found"; + kind: PlaceKind; +} + +export type Place = FieldPlace | LabelPlace; + +// how far apart, in points, places count as on one line +const BAND = 4; +// how much a found place may share with a field and still count +const SHARED = 0.3; + +/** + * isTarget tells whether `field` can be filled or signed: not a push button, + * a read-only field, one an earlier signature locked (FieldMDP, its full name + * in `locked`) or a signature field that holds a signature, and it shows on a + * page + */ +export const isTarget = (field: PdfField, locked: ReadonlySet) => + field.kind !== "button" && + !field.flags.readOnly && + !locked.has(field.name) && + !(field.kind === "signature" && field.signed) && + field.widgets.length > 0; + +/** + * fieldLabel returns what a field is called: its `/TU`, else the last part of + * its full name in words, e.g. "Signature" for "form.signature_1" + */ +export const fieldLabel = (field: PdfField) => { + const told = field.label?.trim(); + if (told) return told; + const last = field.name.split(".").pop() ?? ""; + const words = last + .replace(/\[\d+\]/gu, "") + .replace(/(\p{Ll})(\p{Lu})/gu, "$1 $2") + .replace(/[_\-\s]+/gu, " ") + .replace(/(?:\s*\d+)+$/u, "") + .trim() + .toLowerCase(); + return words ? words[0].toUpperCase() + words.slice(1) : field.name; +}; + +const union = (rects: Rect[]): Rect => { + const left = Math.min(...rects.map((rect) => rect[0])); + const top = Math.min(...rects.map((rect) => rect[1])); + const right = Math.max(...rects.map((rect) => rect[0] + rect[2])); + const bottom = Math.max(...rects.map((rect) => rect[1] + rect[3])); + return [left, top, right - left, bottom - top]; +}; + +const iou = (a: Rect, b: Rect) => { + const shared = overlap(a, b); + return shared / (a[2] * a[3] + b[2] * b[3] - shared || 1); +}; + +/** + * foundId returns the id of a found place, from where it is + */ +const foundId = (place: FoundPlace) => + `found:${place.page}:${Math.round(place.rect[0])}:${Math.round(place.rect[1])}`; + +/** + * readingOrder sorts places by page, then by line, then from left to right: + * places whose bottoms (where text and lines stand) are within BAND points + * of the first of a line are on that line + */ +export const readingOrder = ( + places: readonly T[], +): T[] => { + const bottom = (place: T) => place.rect[1] + place.rect[3]; + const sorted = [...places].sort( + (a, b) => a.page - b.page || bottom(a) - bottom(b), + ); + const lines: T[][] = []; + for (const place of sorted) { + const line = lines[lines.length - 1]; + if ( + line && + line[0].page === place.page && + bottom(place) - bottom(line[0]) <= BAND + ) + line.push(place); + else lines.push([place]); + } + return lines.flatMap((line) => line.sort((a, b) => a.rect[0] - b.rect[0])); +}; + +/** + * placesOf returns the places of a PDF in reading order: the fields of + * `fields` that are targets (see isTarget), and the places of `found` that + * no field covers (more than SHARED of both, as intersection over union) and + * that the user didn't dismiss; found places within 4 pt of each other are + * one + */ +export const placesOf = ({ + fields, + found, + locked = new Set(), + dismissed = new Set(), +}: { + fields: readonly PdfField[]; + found: readonly FoundPlace[]; + locked?: ReadonlySet; + dismissed?: ReadonlySet; +}): Place[] => { + const fieldPlaces = fields + .filter((field) => isTarget(field, locked)) + .map((field): FieldPlace => { + const page = field.widgets[0].page; + return { + id: `field:${field.name}`, + source: "field", + page, + rect: union( + field.widgets + .filter((widget) => widget.page === page) + .map((widget) => widget.rect), + ), + label: fieldLabel(field), + kind: field.kind as FieldPlaceKind, + field, + }; + }); + // every widget counts against found places, also those of fields that + // can't be filled: a signed signature isn't a line to sign on + const widgets = fields.flatMap((field) => field.widgets); + const labelPlaces = mergeNear([...found]) + .filter( + (place) => + !widgets.some( + (widget) => + widget.page === place.page && iou(widget.rect, place.rect) > SHARED, + ), + ) + .map((place): LabelPlace => ({ + ...place, + id: foundId(place), + source: "found", + })) + .filter((place) => !dismissed.has(place.id)); + return readingOrder([...fieldPlaces, ...labelPlaces]); +}; + +/** + * dismissPlace returns the dismissed places with `place` among them, if it is + * a found place; fields can't be dismissed. The caller keeps the set it had + * to undo it. + */ +export const dismissPlace = ( + dismissed: ReadonlySet, + place: Place, +): ReadonlySet => + place.source === "found" ? new Set([...dismissed, place.id]) : dismissed; + +/** + * nextPlace returns the first place after the one of id `from` (else from + * the start) that isn't filled, coming round to the start; none when every + * place is filled + */ +export const nextPlace = ( + places: readonly Place[], + filled: ReadonlySet, + from?: string, +): Place | undefined => { + const start = places.findIndex((place) => place.id === from) + 1; + for (let step = 0; step < places.length; step++) { + const place = places[(start + step) % places.length]; + if (!filled.has(place.id)) return place; + } + return undefined; +}; + +// what the Next button says and where it goes +export interface NextButton { + text: string; + place: Place | undefined; + disabled: boolean; +} + +/** + * nextButton returns the Next button for `places`: "Next · i of n", where n + * counts the places not filled yet and i is where among them the next one + * is, or "All places filled", disabled + */ +export const nextButton = ( + places: readonly Place[], + filled: ReadonlySet, + from?: string, +): NextButton => { + const place = nextPlace(places, filled, from); + if (!place) + return { text: "All places filled", place: undefined, disabled: true }; + const open = places.filter((each) => !filled.has(each.id)); + return { + text: `Next · ${open.indexOf(place) + 1} of ${open.length}`, + place, + disabled: false, + }; +}; + +// what a text field asks for, which it starts with when it is filled +export type Prefill = "name" | "date" | "place" | "placeAndDate"; + +// what filling a place does: +// - stamp: puts that stamp on it (the signature or initials, or the lines of +// the date, the place, or both); +// - text: focuses the field's input, with what `prefill` asks for in it, +// selected; +// - toggle: a checkbox switches; choose: picks one of a radio's buttons or a +// choice's options +export type PlaceFill = + | { + action: "stamp"; + stamp: "signature" | "initials" | "date" | "place" | "placeAndDate"; + } + | { action: "text"; prefill?: Prefill } + | { action: "toggle" } + | { action: "choose" }; + +// the words that make a text field's label one Blank fills, in English and +// German; whole words only, so "Geburtsdatum" or "Wohnort" are none +const DATE_WORDS = new Set(["date", "datum"]); +const PLACE_WORDS = new Set(["place", "ort"]); +const NAME_WORDS = new Set(["name"]); +// words that make it another date, place or name than the signer's +const OTHER_WORDS = new Set([ + "birth", + "expiry", + "expiration", + "due", + "start", + "end", + "issue", + "beginn", + "ende", + "geburt", + "first", + "last", + "middle", + "given", + "family", + "maiden", + "company", + "business", + "organisation", + "organization", + "user", + "file", + "firma", + "vorname", + "nachname", + "geburtsname", +]); + +/** + * prefillOf returns what a text field asks for, by the words of its `/TU` + * and its name: the signer's name, the date, the place, or the place and + * date; none for anything else or for a mix + */ +export const prefillOf = (field: PdfField): Prefill | undefined => { + const words = [field.label ?? "", field.name.split(".").pop() ?? ""] + .join(" ") + .replace(/(\p{Ll})(\p{Lu})/gu, "$1 $2") + .toLowerCase() + .split(/[^\p{L}]+/u) + .filter(Boolean); + if (words.some((word) => OTHER_WORDS.has(word))) return undefined; + const has = (set: Set) => words.some((word) => set.has(word)); + const date = has(DATE_WORDS); + const place = has(PLACE_WORDS); + const name = has(NAME_WORDS); + if (name) return date || place ? undefined : "name"; + if (date && place) return "placeAndDate"; + if (date) return "date"; + if (place) return "place"; + return undefined; +}; + +/** + * fillOf returns what filling `place` does (see PlaceFill) + */ +export const fillOf = (place: Place): PlaceFill => { + if (place.source === "found") return { action: "stamp", stamp: place.kind }; + switch (place.kind) { + case "signature": + return { action: "stamp", stamp: "signature" }; + case "checkbox": + return { action: "toggle" }; + case "radio": + case "combo": + case "list": + return { action: "choose" }; + case "text": { + const prefill = prefillOf(place.field); + return prefill ? { action: "text", prefill } : { action: "text" }; + } + } +}; From e512a46f141a0086ee3660a75249bd31f000feec Mon Sep 17 00:00:00 2001 From: Florian Purchess Date: Mon, 12 Oct 2026 09:51:38 +0200 Subject: [PATCH 04/50] feat: stamp and sign received PDFs natively: stamps as Form XObjects in the pages or a field's appearance, empty fields for the other signers, one signing_sign_pdf with a plan and parts --- .claude/rules/signing.md | 12 +- src-tauri/src/signing/commands.rs | 380 ++++++++-- src-tauri/src/signing/commands/received.rs | 811 +++++++++++++++++++++ src-tauri/src/signing/commands/tests.rs | 131 ++-- src-tauri/src/signing/mod.rs | 14 + src-tauri/src/signing/pdf/mod.rs | 93 +++ src-tauri/src/signing/pdf/rules.rs | 160 ++++ src-tauri/src/signing/pdf/sign.rs | 525 ++++++++++--- src-tauri/src/signing/pdf/stamp.rs | 146 ++++ src-tauri/src/signing/pdf/xobject.rs | 340 +++++++++ src/editor/commands/signing.test.ts | 28 +- src/editor/commands/signing.ts | 27 +- src/signing/ipc.test.ts | 46 ++ src/signing/ipc.ts | 95 ++- 14 files changed, 2580 insertions(+), 228 deletions(-) create mode 100644 src-tauri/src/signing/commands/received.rs create mode 100644 src-tauri/src/signing/pdf/rules.rs create mode 100644 src-tauri/src/signing/pdf/stamp.rs create mode 100644 src-tauri/src/signing/pdf/xobject.rs create mode 100644 src/signing/ipc.test.ts diff --git a/.claude/rules/signing.md b/.claude/rules/signing.md index 67b2f880..1c2d7c66 100644 --- a/.claude/rules/signing.md +++ b/.claude/rules/signing.md @@ -40,7 +40,7 @@ Blank signs what the user writes by exporting a signed PDF; a markdown file neve ## Certificates and digital signatures (native, `src-tauri/src/signing/`) - Ported from the spike (`spike/signer` on `spike/signing`, with its SPIKE.md): `keys` (a P-256 key and a self-signed certificate made with rcgen, for document signing, in a PKCS#12 file of 600,000 PBKDF2 rounds, returned with its identity so it isn't read back; any .p12 read, NSS's BER ones through a shim that lets p12-keystore check the MAC on the original bytes and then reads the DER copy without one; `OpenError` says why one doesn't open: `WrongPassphrase`, `Unsupported`, `Malformed`), `cms` (CAdES-BES SignedData over the byte ranges, ESS signing-certificate-v2, no signing-time; a timestamp token as an unsigned attribute), `tsa` (RFC 3161 over reqwest, only the signature value's SHA-256 sent), `pdf` (lopdf's incremental update with our guards: the old trailer cleaned, reconstructed xrefs, encrypted files and DocMDP P=1 refused; a /Sig field and widget with an empty appearance, since the block is in the page content and Blank draws no frame), `verify` (every signature, against USF and wrapping attacks; `valid()` doesn't cover later bytes, `covers_the_whole_file()` does), `certificate` (`CertificateInfo`, the one summary of a certificate, and `common_name` from the name's parts), `commands` (what the webview asks for). HTTP clients come from `src-tauri/src/http.rs`, which installs rustls' provider. -- The key and certificate are `key.p12` and `certificate.cer` in the app config's `signing` folder. The key never goes to the webview: the passphrase comes once per signing (`signing_check_passphrase` before anything is exported, then `signing_sign_pdf` with the PDF as the raw body and the options in the `blank-signing` header), and a signed PDF is checked with `verify` before it's returned (`check_signed`: valid and covering the whole file, and with a timestamp server a timestamp that holds, else `TimestampUnusable`). `key.p12` is written 0600 on unix, both files through temp files, under one lock (`FILES`) with removing and exporting them; a key there is replaced only when the call says `replace` (else `keyExists`, which the webview turns into `ConfirmDialog`'s "Replace your key?"), and if the certificate can't be put in place the old key comes back (`keep_files`). An email that isn't ASCII is refused with a sentence (rcgen can't hold it), in `certificateDialogModel.ts` and natively. The signature names the certificate's holder (its common name) as /Name, never the block's name. +- The key and certificate are `key.p12` and `certificate.cer` in the app config's `signing` folder. The key never goes to the webview: the passphrase comes once per signing (`signing_check_passphrase` before anything is exported, then `signing_sign_pdf`, see below), and a signed PDF is checked with `verify` before it's returned (`check_signed`: valid and covering the whole file, and with a timestamp server a timestamp that holds, else `TimestampUnusable`). `key.p12` is written 0600 on unix, both files through temp files, under one lock (`FILES`) with removing and exporting them; a key there is replaced only when the call says `replace` (else `keyExists`, which the webview turns into `ConfirmDialog`'s "Replace your key?"), and if the certificate can't be put in place the old key comes back (`keep_files`). An email that isn't ASCII is refused with a sentence (rcgen can't hold it), in `certificateDialogModel.ts` and natively. The signature names the certificate's holder (its common name) as /Name, never the block's name. - Errors are typed, never matched by their text: `keys::OpenError` and `signing::Refusal` (`Restricted`, `Certified`, `NoTimestamp`, `TimestampRefused`, `TimestampUnusable`, whose text is the user's sentence). They're logged by context and kind only (`forTheLog`), never their text, which crypto crates may fill with what they got; the user gets fixed sentences (`SigningError`: `wrongPassphrase`, `noCertificate`, `keyExists`, `failed`). Passphrases are `Zeroizing`. - The crypto crates are optimized in debug builds (the dev profile in `Cargo.toml`), or opening a key takes minutes; the tests make keys with few rounds (`create_certificate_with`). - Webview: `src/signing/ipc.ts` (every call; `signingMessage(error, fallback)` with a fallback per action, `signingKind` for the log), `src/signing/certificate.ts` (`signingCertificate`, read once; made, imported, exported, removed after `confirmRemoval`'s question in `ConfirmDialog.vue`, which offers Back up key… first; failures through `reportSigningFailure`), `CertificateDialog.vue` (request `certificateDialog`), `CertificateRows.vue` in Settings ▸ Signing, and the Sign dialog's choice of a digital signature with the passphrase. @@ -49,3 +49,13 @@ Blank signs what the user writes by exporting a signed PDF; a markdown file neve ## Export signed PDF `exportSigned` (`export.signed_pdf`, `Mod-Alt-Shift-p`): no block, a notification; no signature yet, the signature dialog first; then `SignDialog.vue` (request `signDialog`, closed by `closeRequests` before a tab switch: which block (`targetsOf`/`chosenOf`, `src/signing/targets.ts`), the place, digital or a picture only and the passphrase, the Word warning for imported documents; a certificate made from it switches it to digital), then `runExport` (`exportAs.ts`, the save dialog first, " signed" before the extension of the file captured when the command ran) on the editor's state when the dialog closes, the block still at its place or else the one of its number, never another. It signs the tab that showed when it ran (`activeTabId`, checked before the Sign dialog opens and when it's submitted), and nothing once another shows; the Sign dialog stops after its passphrase check when its request was closed meanwhile. The signature and certificate dialogs stay open across a tab switch: they work on the user's profile, not the document. The PDF's /Name is the certificate's name, else Settings ▸ Signing's, never the block's (the dialog warns when the chosen block names someone else, `namedNote`); the place is the one the page shows, the block's own (the dialog then shows `placeNote` instead of the field) else the one typed, and that is what /Location gets and what is remembered. For a digital signature, its `finish` signs the exported PDF (`signExport`), the field over the block's box, which the export's engine gives (`signatureBox`, `PdfJob.signedPos`); no box or a failure writes nothing, and says so (`signingFailure`, with how to sign without a timestamp while a server is set). + +## Stamps and signing received PDFs (native, `signing/pdf/`) + +- One command, `signing_sign_pdf`: the header `blank-signing` is percent-encoded JSON `{ passphrase, source, plan }` (`SignRequest` in `src/signing/ipc.ts`, `Header` in `commands.rs`), the body the parts (`encodeParts`: a little-endian u32 count, then each part as a little-endian u64 length and its bytes). `source` is `{kind:"body"}` (the export flow: part 0 is the PDF) or `{kind:"tab", tab, path, sha256, fills}` (read again, `fileChanged`/`fileMissing` when it isn't the file the tab opened; the fills come from the tab's helper as their own revision through `tab_bytes`, the one place that calls it). `plan.stamps[].part` and the signature's `stamp` index the parts. A `null` passphrase writes the stamps and fields alone, no signature. Pages are from 1, rects display points (`PageGeometry` converts). +- `xobject.rs`: a stamp's one-page PDF (from Blank's engine) becomes a Form XObject, `/BBox [0 0 w h]`: loaded with bounded `LoadOptions`, every object its resources reach copied once with new ids (through a queue, so cycles end; page tree and structure objects become null), `/StructParent(s)` dropped, marked content with an `/MCID` turned into `/Artifact BMC`. For a PDF/A-1 target (`pdfa_part`, the XMP's `pdfaid:part`) the transparency goes: no `/Group`, soft masks of images drawn over white, graphics states opaque. +- `stamp.rs`: `draw_on_page` wraps the page's content in `q … Q` and draws its stamps after it (`cm` from `PageGeometry::placement`, so /Rotate, the crop box's origin and UserUnit hold), with a direct copy of the page's effective (maybe inherited) `/Resources`. A repeated stamp is one XObject drawn on every page at the same place, moved (and made smaller) into smaller pages (`clamp`). `appearance` is a widget's appearance: empty, or a stamp fitted into it. +- `sign.rs` (`update_pdf`): the stamps, then empty `/Sig` fields for the others who sign ("Signature of ‹name›", unique, no periods), then the signature: an existing empty field keeps its name and gets `/V` and the stamp as its `/AP /N`; a new `SignatureN` shows its stamp in the page content on a PDF nobody signed yet and as its appearance on signed or certified ones; or invisible. Without a signer, a signature's stamp goes into the page content. +- `rules.rs` (`Rules::read`, once per update): DocMDP P, signed, FieldMDP locks. Refused natively (`Refusal`): anything on P=1 (`Certified`), stamps in the pages' content of signed or certified PDFs (`SignedPages`), a field that isn't there, is signed or locked, encrypted PDFs (`Restricted`, `load_for_update`). +- After the update, `verify_pdf` must find exactly one new signature, valid and covering the whole file (none for a stamps-only update), and every earlier one, known by its byte range, as valid as it was; else the generic sentence and nothing is returned. +- Tests: `commands/received.rs` on the PDF tab's fixtures with stamp PDFs made by lopdf; `pdftotext -bbox -cropbox`, `pdfsig -nssdir ` and `qpdf --check` where installed (CI never skips them). diff --git a/src-tauri/src/signing/commands.rs b/src-tauri/src/signing/commands.rs index f2b522c1..68ea948d 100644 --- a/src-tauri/src/signing/commands.rs +++ b/src-tauri/src/signing/commands.rs @@ -12,11 +12,12 @@ use serde::{Deserialize, Deserializer, Serialize}; use tauri::{AppHandle, Manager}; use zeroize::Zeroizing; +use crate::pdf_view::{read_checked, PdfTabs}; use crate::signing::certificate::CertificateInfo; use crate::signing::keys::{self, Identity, OpenError}; -use crate::signing::pdf::sign::{sign_pdf, SignRequest}; +use crate::signing::pdf::sign::{update_pdf, Other, PlacedStamp, Plan as UpdatePlan, Signer, Spot}; use crate::signing::trust::{self, CheckedSignature}; -use crate::signing::verify::verify_pdf; +use crate::signing::verify::{verify_pdf, SignatureReport}; use crate::signing::Refusal; /// the folder of the user's signature, initials, key and certificate @@ -369,46 +370,186 @@ pub async fn signing_check_passphrase(app: AppHandle, passphrase: String) -> Ans .map_err(|_| SigningError::Failed("Blank can't open your key".into()))? } -/// how to sign, besides the PDF and the passphrase +/// a place on a page: x, y, width and height in points from the top left +/// of the page as shown +type Rect = [f64; 4]; + +/// a stamp to put on the pages #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SignOptions { - /// the page of the signature's field, from 1 +pub struct StampPlan { + /// the body's part with its one-page PDF + pub part: usize, + /// from 1 pub page: u32, - /// where the signature is shown: x, y, width and height in points from - /// the page's top left; none for an invisible signature - pub rect: Option<[f64; 4]>, + pub rect: Rect, + /// on every page, at the same place, moved into smaller pages + #[serde(default)] + pub repeat: bool, +} + +/// where the signature goes +#[derive(Debug, Deserialize)] +#[serde(tag = "kind", rename_all = "camelCase")] +pub enum SignaturePlan { + /// in no place on the pages + Invisible, + /// the empty signature field of this full name, showing the stamp of + /// the body's part `stamp` + Existing { field: String, stamp: Option }, + /// a new field on page `page` (from 1) + New { + page: u32, + rect: Rect, + stamp: Option, + }, +} + +/// an empty signature field for someone else, named after them +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OtherPlan { + pub page: u32, + pub rect: Rect, + /// who is to sign it: the field is called "Signature of ‹name›" + pub name: String, +} + +/// what to add to the PDF, and what the signature says +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Plan { + #[serde(default)] + pub stamps: Vec, + pub signature: SignaturePlan, + #[serde(default)] + pub others: Vec, pub location: Option, pub reason: Option, /// an RFC 3161 server's URL, for a timestamp (B-T) pub timestamp_server: Option, } -/// signer is the name of the certificate's holder, which the signature -/// names as who signed it; None when it has no common name -fn signer(cert: &[u8]) -> Option { +/// a form field's new value, which the PDF tab's helper writes (see +/// pdf_view): text, or whether a box is ticked +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum FillValue { + Text(String), + Ticked(bool), +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Fill { + /// the field's full name + pub field: String, + pub value: FillValue, +} + +/// where the PDF to sign comes from +#[derive(Debug, Deserialize)] +#[serde(tag = "kind", rename_all = "camelCase")] +pub enum Source { + /// the body's part 0 (the export flow) + Body, + /// a PDF tab's file, which must still be the one the tab opened, with + /// the tab's fills + Tab { + tab: String, + path: PathBuf, + sha256: String, + #[serde(default)] + fills: Vec, + }, +} + +/// the user's key file and its passphrase +pub struct Key<'a> { + pub p12: &'a [u8], + pub passphrase: &'a str, +} + +/// signer_name is the name of the certificate's holder, which the +/// signature names as who signed it; None when it has no common name +fn signer_name(cert: &[u8]) -> Option { use der::Decode; let cert = x509_cert::Certificate::from_der(cert).ok()?; crate::signing::certificate::common_name(&cert.tbs_certificate.subject) } -/// sign signs `pdf` with the user's key, in the name of its certificate's -/// holder -pub fn sign(p12: &[u8], passphrase: &str, pdf: &[u8], options: &SignOptions) -> Answer> { - let identity = open_key(p12, passphrase, "Blank can't open your key")?; +/// signing's fixed sentence for what failed +const SIGN_FAILED: &str = "Blank couldn't sign the PDF"; +const STAMP_FAILED: &str = "Blank couldn't add to the PDF"; + +/// sign adds `plan` to `pdf` and signs it with the user's key, or, without +/// a key, adds the stamps and fields alone; the stamps' PDFs are `parts` +/// of the body, by `part` +pub fn sign(key: Option, pdf: &[u8], parts: &[&[u8]], plan: &Plan) -> Answer> { + let message = if key.is_some() { + SIGN_FAILED + } else { + STAMP_FAILED + }; + let identity = key + .map(|key| open_key(key.p12, key.passphrase, "Blank can't open your key")) + .transpose()?; + let part = |index: usize| -> Answer<&[u8]> { + parts.get(index).copied().ok_or_else(|| { + log::warn!("a stamp names a part the request doesn't have"); + SigningError::Failed(message.into()) + }) + }; + let stamps = plan + .stamps + .iter() + .map(|s| { + Ok(PlacedStamp { + pdf: part(s.part)?, + page: s.page, + rect: s.rect, + repeat: s.repeat, + }) + }) + .collect::>>()?; + let signature = match &plan.signature { + SignaturePlan::Invisible => Spot::Invisible, + SignaturePlan::Existing { field, stamp } => Spot::Existing { + field: field.clone(), + stamp: stamp.map(part).transpose()?, + }, + SignaturePlan::New { page, rect, stamp } => Spot::New { + page: *page, + rect: *rect, + stamp: stamp.map(part).transpose()?, + }, + }; + let others = plan + .others + .iter() + .map(|o| Other { + page: o.page, + rect: o.rect, + name: o.name.clone(), + }) + .collect(); + let update = UpdatePlan { + stamps, + signature, + others, + }; let blank = |value: &Option| value.clone().filter(|text| !text.trim().is_empty()); - let request = SignRequest { - identity: &identity, - page: options.page.max(1), - rect: options.rect, - reason: blank(&options.reason), - location: blank(&options.location), - name: blank(&signer(&identity.cert)), - tsa: blank(&options.timestamp_server), + let signer = identity.as_ref().map(|identity| Signer { + identity, + reason: blank(&plan.reason), + location: blank(&plan.location), + name: blank(&signer_name(&identity.cert)), + tsa: blank(&plan.timestamp_server), reserve: RESERVE, - }; - let signed = sign_pdf(pdf, &request).map_err(|error| { - log::warn!("failed to sign a PDF: {}", kind_of(&error)); + }); + // what the signatures it has were, to compare after + let before = verify_pdf(pdf).unwrap_or_default(); + let updated = update_pdf(pdf, &update, signer.as_ref()).map_err(|error| { + log::warn!("failed to update a PDF: {}", kind_of(&error)); match error.downcast_ref::() { Some( refusal @ (Refusal::NoTimestamp @@ -416,28 +557,53 @@ pub fn sign(p12: &[u8], passphrase: &str, pdf: &[u8], options: &SignOptions) -> | Refusal::TimestampUnusable), ) => SigningError::Timestamp(refusal.to_string()), Some(refusal) => SigningError::Failed(refusal.to_string()), - None => SigningError::Failed("Blank couldn't sign the PDF".into()), + None => SigningError::Failed(message.into()), } })?; - check_signed(&signed, request.tsa.is_some())?; - Ok(signed) + match &signer { + Some(signer) => check_signed(&updated, signer.tsa.is_some(), &before)?, + None => check_unsigned(&updated, &before)?, + } + Ok(updated) } -/// check_signed makes sure what was just signed checks out, and that its -/// last signature covers the whole file, or it isn't saved; `timestamped` -/// asks for a timestamp that holds as well, since the user asked for one -fn check_signed(signed: &[u8], timestamped: bool) -> Answer<()> { - let reports = verify_pdf(signed).map_err(failed( - "failed to check a signed PDF", - "Blank couldn't sign the PDF", - ))?; - let Some(last) = reports - .last() - .filter(|report| report.valid() && report.covers_the_whole_file()) - else { +/// added returns the signatures of `after` that weren't in `before`, if +/// every one of `before` is still there and checks out as it did; a +/// signature is known by its byte range, since the reports come in the +/// order of the form's fields, not of the revisions +fn added<'a>( + after: &'a [SignatureReport], + before: &[SignatureReport], +) -> Option> { + let kept = before.iter().all(|b| { + after + .iter() + .any(|a| a.byte_range == b.byte_range && a.field == b.field && a.valid() == b.valid()) + }); + kept.then(|| { + after + .iter() + .filter(|a| !before.iter().any(|b| b.byte_range == a.byte_range)) + .collect() + }) +} + +/// check_signed makes sure what was just signed checks out: one new +/// signature, valid and covering the whole file, and the signatures it had +/// `before` as valid as they were, or it isn't saved; `timestamped` asks +/// for a timestamp that holds as well, since the user asked for one +fn check_signed(signed: &[u8], timestamped: bool, before: &[SignatureReport]) -> Answer<()> { + let reports = + verify_pdf(signed).map_err(failed("failed to check a signed PDF", SIGN_FAILED))?; + let new = added(&reports, before).unwrap_or_default(); + let [last] = new[..] else { log::warn!("a PDF Blank signed didn't check out"); - return Err(SigningError::Failed("Blank couldn't sign the PDF".into())); + return Err(SigningError::Failed(SIGN_FAILED.into())); }; + if !(last.valid() && last.covers_the_whole_file()) { + log::warn!("a PDF Blank signed didn't check out"); + return Err(SigningError::Failed(SIGN_FAILED.into())); + } if timestamped && (last.timestamp.is_none() || last.timestamp_unreadable) { log::warn!("a PDF Blank signed has no timestamp that holds"); return Err(SigningError::Timestamp( @@ -447,17 +613,31 @@ fn check_signed(signed: &[u8], timestamped: bool) -> Answer<()> { Ok(()) } +/// check_unsigned makes sure an update without a signature added none, and +/// left the signatures as valid as they were +fn check_unsigned(updated: &[u8], before: &[SignatureReport]) -> Answer<()> { + let reports = + verify_pdf(updated).map_err(failed("failed to check an updated PDF", STAMP_FAILED))?; + if added(&reports, before).is_none_or(|new| !new.is_empty()) { + log::warn!("a PDF Blank added to didn't check out"); + return Err(SigningError::Failed(STAMP_FAILED.into())); + } + Ok(()) +} + /// what the header `blank-signing` holds #[derive(Deserialize)] struct Header { - #[serde(deserialize_with = "passphrase")] - passphrase: Passphrase, - options: SignOptions, + /// none: the stamps and fields alone, no signature + #[serde(default, deserialize_with = "passphrase")] + passphrase: Option, + source: Source, + plan: Plan, } /// passphrase reads a passphrase straight into memory that is wiped -fn passphrase<'de, D: Deserializer<'de>>(deserializer: D) -> Result { - String::deserialize(deserializer).map(Passphrase::new) +fn passphrase<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + Option::::deserialize(deserializer).map(|p| p.map(Passphrase::new)) } /// header reads the header `blank-signing`, which the webview encodes with @@ -470,30 +650,113 @@ fn header(value: &str) -> Option

{ serde_json::from_str(&json).ok() } -/// signing_sign_pdf signs the PDF in the request's body with the user's -/// key; its header `blank-signing` holds the passphrase and the -/// SignOptions as JSON. It returns the signed PDF. +/// the most parts a request may have +const MAX_PARTS: usize = 1_024; + +/// parts splits the body into its parts: a little-endian u32 count, then +/// each part as a little-endian u64 length and its bytes, nothing after +fn parts(body: &[u8]) -> Option> { + let count = u32::from_le_bytes(body.get(..4)?.try_into().ok()?) as usize; + if count > MAX_PARTS { + return None; + } + let mut rest = &body[4..]; + let mut out = Vec::with_capacity(count); + for _ in 0..count { + let length = u64::from_le_bytes(rest.get(..8)?.try_into().ok()?); + let length = usize::try_from(length).ok()?; + let part = rest.get(8..8usize.checked_add(length)?)?; + out.push(part); + rest = &rest[8 + length..]; + } + rest.is_empty().then_some(out) +} + +/// tab_bytes is the PDF of a PDF tab to sign: its file, read again, while +/// it is the one the tab opened, with the tab's fills written into it by +/// the tab's helper as a revision of their own. +/// +/// INTEGRATION POINT: the fills are `pdf_view::filled_bytes(tabs, tab, +/// path, sha256, fills)` of the helper's stream; until it lands here, a PDF +/// with fills isn't signed. +fn tab_bytes( + _tabs: &PdfTabs, + _tab: &str, + path: &Path, + sha256: &str, + fills: &[Fill], +) -> Answer> { + let bytes = read_checked(path, sha256).map_err(|error| { + log::warn!("failed to read a PDF tab's file to sign: {}", error.kind()); + match error.kind() { + std::io::ErrorKind::InvalidData => SigningError::FileChanged, + std::io::ErrorKind::NotFound => SigningError::FileMissing, + _ => SigningError::Failed("Blank can't read this PDF again".into()), + } + })?; + if !fills.is_empty() { + log::warn!("a PDF tab's fills can't be written yet"); + return Err(SigningError::Failed( + "Blank can't fill in this PDF's fields yet".into(), + )); + } + Ok(bytes.to_vec()) +} + +/// signing_sign_pdf adds stamps, empty signature fields for others and a +/// signature to a PDF, and returns it. Its header `blank-signing` holds, +/// as JSON, the passphrase (null: no signature, the stamps and fields +/// alone), where the PDF comes from (`source`: the body's part 0, or a PDF +/// tab's file) and the `plan`; the body holds the parts (see `parts`), the +/// stamps' one-page PDFs among them. #[tauri::command] pub async fn signing_sign_pdf( app: AppHandle, + tabs: tauri::State<'_, PdfTabs>, request: tauri::ipc::Request<'_>, ) -> Answer { - let tauri::ipc::InvokeBody::Raw(pdf) = request.body() else { - return Err(SigningError::Failed("Blank couldn't sign the PDF".into())); + let refused = || SigningError::Failed(SIGN_FAILED.into()); + let tauri::ipc::InvokeBody::Raw(body) = request.body() else { + return Err(refused()); }; let header = request .headers() .get("blank-signing") .and_then(|value| value.to_str().ok()) .and_then(header) - .ok_or_else(|| SigningError::Failed("Blank couldn't sign the PDF".into()))?; - let p12 = read_key(&app)?; - let pdf = pdf.clone(); + .ok_or_else(refused)?; + if parts(body).is_none() { + log::warn!("a request to sign had a body that isn't parts"); + return Err(refused()); + } + let pdf = match &header.source { + Source::Body => None, + Source::Tab { + tab, + path, + sha256, + fills, + } => Some(tab_bytes(&tabs, tab, path, sha256, fills)?), + }; + let p12 = match header.passphrase { + Some(_) => Some(read_key(&app)?), + None => None, + }; + let body = body.clone(); let signed = tauri::async_runtime::spawn_blocking(move || { - sign(&p12, &header.passphrase, &pdf, &header.options) + let parts = parts(&body).ok_or_else(refused)?; + let pdf = match &pdf { + Some(pdf) => pdf.as_slice(), + None => parts.first().copied().ok_or_else(refused)?, + }; + let key = match (&p12, &header.passphrase) { + (Some(p12), Some(passphrase)) => Some(Key { p12, passphrase }), + _ => None, + }; + sign(key, pdf, &parts, &header.plan) }) .await - .map_err(|_| SigningError::Failed("Blank couldn't sign the PDF".into()))??; + .map_err(|_| refused())??; Ok(tauri::ipc::Response::new(signed)) } @@ -545,3 +808,6 @@ pub async fn signing_untrust(app: AppHandle, fingerprint: String) -> Answer<()> #[cfg(test)] mod tests; + +#[cfg(test)] +mod received; diff --git a/src-tauri/src/signing/commands/received.rs b/src-tauri/src/signing/commands/received.rs new file mode 100644 index 00000000..59227465 --- /dev/null +++ b/src-tauri/src/signing/commands/received.rs @@ -0,0 +1,811 @@ +//! signing PDFs Blank didn't write, and stamping them: stamps in the pages' +//! content, signature fields the PDF has, empty fields for others, and the +//! rules of what may be added. The fixtures are the PDF tab's +//! (`src-tauri/pdf/fixtures`, see `scripts/build-pdf-fixtures.ts`); the +//! stamps are one-page PDFs made here with lopdf, as Blank's engine would +//! make them: marked content, a structure parent, an indirect font. + +use std::path::Path; +use std::process::Command; + +use lopdf::{dictionary, Document, Object, ObjectId, Stream}; + +use super::tests::{key, options, pdf, sign_as, signed, widgets, PASSPHRASE, RSA_KEY}; +use super::*; + +const ROTATED: &[u8] = include_bytes!("../../../pdf/fixtures/rotated.pdf"); +const FORM: &[u8] = include_bytes!("../../../pdf/fixtures/form.pdf"); +const PASSWORD: &[u8] = include_bytes!("../../../pdf/fixtures/password.pdf"); + +/// a stamp's one-page PDF of `w` × `h` points with `text` in it, its +/// marked content tagged with an /MCID, its font an object that refers to +/// itself +fn stamp_pdf(w: f64, h: f64, text: &str) -> Vec { + let mut doc = Document::with_version("1.7"); + let pages = doc.new_object_id(); + let font = doc.new_object_id(); + doc.objects.insert( + font, + Object::Dictionary(dictionary! { + "Type" => "Font", "Subtype" => "Type1", "BaseFont" => "Helvetica", + // a cycle the copy must end + "Itself" => font, + }), + ); + let content = doc.add_object(Stream::new( + dictionary! {}, + format!("/P <> BDC BT /F1 10 Tf 4 6 Td ({text}) Tj ET EMC").into_bytes(), + )); + let page = doc.add_object(dictionary! { + "Type" => "Page", "Parent" => pages, "Contents" => content, + "MediaBox" => vec![0.into(), 0.into(), (w as f32).into(), (h as f32).into()], + "Resources" => dictionary! { "Font" => dictionary! { "F1" => font } }, + "StructParents" => 0, + }); + doc.objects.insert( + pages, + Object::Dictionary(dictionary! { + "Type" => "Pages", "Kids" => vec![page.into()], "Count" => 1, + }), + ); + let catalog = doc.add_object(dictionary! { "Type" => "Catalog", "Pages" => pages }); + doc.trailer.set("Root", catalog); + let mut bytes = vec![]; + doc.save_to(&mut bytes).unwrap(); + bytes +} + +/// a plan with `stamps`, signed `signature`, nothing else +fn plan(stamps: Vec, signature: SignaturePlan) -> Plan { + Plan { + stamps, + signature, + ..options(None) + } +} + +fn stamp_at(part: usize, page: u32, rect: [f64; 4], repeat: bool) -> StampPlan { + StampPlan { + part, + page, + rect, + repeat, + } +} + +/// stamped adds `plan` to `pdf` without a signature, the stamps' PDFs +/// `stamps` from part 1 on +fn stamped(pdf: &[u8], plan: &Plan, stamps: &[&[u8]]) -> Answer> { + let parts: Vec<&[u8]> = std::iter::once(pdf).chain(stamps.iter().copied()).collect(); + sign(None, pdf, &parts, plan) +} + +/// has tells whether `tool` of poppler-utils or qpdf runs; CI never skips +fn has(tool: &str) -> bool { + let found = Command::new(tool).arg("--version").output().is_ok(); + if !found && std::env::var_os("CI").is_some() { + panic!("{tool} is missing: CI doesn't skip the check"); + } + found +} + +/// written writes `bytes` to a file of `dir` +fn written(dir: &Path, name: &str, bytes: &[u8]) -> std::path::PathBuf { + let path = dir.join(name); + std::fs::write(&path, bytes).unwrap(); + path +} + +/// a word pdftotext found: its page from 1, xMin, yMin, xMax, yMax +type Word = (usize, f64, f64, f64, f64, String); + +/// words runs `pdftotext -bbox` on `pdf`, in the pages' crop boxes, the +/// pages as shown +fn words(pdf: &Path) -> Vec { + let html = pdf.with_extension("html"); + assert!(Command::new("pdftotext") + .arg("-bbox") + .arg("-cropbox") + .arg(pdf) + .arg(&html) + .status() + .unwrap() + .success()); + let html = std::fs::read_to_string(html).unwrap(); + let mut found = vec![]; + let mut page = 0; + for line in html.lines().map(str::trim) { + if line.starts_with(" f64 { + let at = rest.find(&format!("{key}=\"")).unwrap() + key.len() + 2; + rest[at..].split('"').next().unwrap().parse().unwrap() + }; + let text = rest.split('>').nth(1).unwrap().split('<').next().unwrap(); + found.push(( + page, + number("xMin"), + number("yMin"), + number("xMax"), + number("yMax"), + text.to_string(), + )); + } + found +} + +/// pdfsig_valid tells how many signatures of `pdf` pdfsig calls valid, and +/// how many it found +fn pdfsig_valid(dir: &Path, pdf: &Path) -> (usize, usize) { + let nss = dir.join("nss"); + std::fs::create_dir_all(&nss).unwrap(); + let out = Command::new("pdfsig") + .arg("-nssdir") + .arg(&nss) + .arg(pdf) + .output() + .unwrap(); + let text = String::from_utf8_lossy(&out.stdout); + let found = text.matches("Signature Field Name").count(); + let valid = text + .matches("Signature Validation: Signature is Valid") + .count(); + (valid, found) +} + +/// the page objects of `pdf`, by number +fn pages(doc: &Document) -> Vec { + doc.get_pages().values().copied().collect() +} + +/// the object `id` of `doc`, followed if it's a reference +fn deref<'a>(doc: &'a Document, o: &'a Object) -> &'a Object { + doc.dereference(o).unwrap().1 +} + +/// the XObjects of a page's direct /Resources +fn page_xobjects(doc: &Document, page: ObjectId) -> lopdf::Dictionary { + let page = doc.get_dictionary(page).unwrap(); + let resources = deref(doc, page.get(b"Resources").unwrap()) + .as_dict() + .unwrap(); + deref(doc, resources.get(b"XObject").unwrap()) + .as_dict() + .unwrap() + .clone() +} + +#[test] +fn puts_a_stamp_where_it_was_placed_on_a_turned_page() { + let stamp = stamp_pdf(120., 30., "Stamped"); + let rect = [200., 150., 120., 30.]; + let out = stamped( + ROTATED, + &plan(vec![stamp_at(1, 1, rect, false)], SignaturePlan::Invisible), + &[&stamp], + ) + .unwrap(); + assert!(out.starts_with(ROTATED)); + if !has("pdftotext") { + return; + } + let dir = tempfile::tempdir().unwrap(); + // where the word is on the stamp itself, and so where it must land + let alone = words(&written(dir.path(), "stamp.pdf", &stamp)); + let (_, sx, sy, sx1, sy1, _) = alone.iter().find(|w| w.5 == "Stamped").unwrap().clone(); + let found = words(&written(dir.path(), "stamped.pdf", &out)); + let (page, x, y, x1, y1, _) = found.iter().find(|w| w.5 == "Stamped").unwrap().clone(); + assert_eq!(page, 1); + // upright, as wide and high as on the stamp + assert!(((x1 - x) - (sx1 - sx)).abs() <= 1. && ((y1 - y) - (sy1 - sy)).abs() <= 1.); + assert!((x - (rect[0] + sx)).abs() <= 1., "{x} vs {}", rect[0] + sx); + assert!((y - (rect[1] + sy)).abs() <= 1., "{y} vs {}", rect[1] + sy); + // the page's own text still has its font, which the page inherits + assert!( + found.iter().any(|w| w.0 == 1 && w.5 == "Turned"), + "{found:?}" + ); +} + +#[test] +fn keeps_the_resources_a_page_inherits() { + let stamp = stamp_pdf(120., 30., "Stamped"); + let out = stamped( + ROTATED, + &plan( + vec![stamp_at(1, 1, [20., 20., 120., 30.], false)], + SignaturePlan::Invisible, + ), + &[&stamp], + ) + .unwrap(); + let doc = Document::load_mem(&out).unwrap(); + let page = pages(&doc)[0]; + let resources = deref( + &doc, + doc.get_dictionary(page).unwrap().get(b"Resources").unwrap(), + ) + .as_dict() + .unwrap(); + // a copy of what /Pages gave it, with the stamp + assert!(deref(&doc, resources.get(b"Font").unwrap()) + .as_dict() + .unwrap() + .has(b"F1")); + assert!(page_xobjects(&doc, page).has(b"BlankStamp0")); + // the shared resources weren't touched + let parent = doc + .get_dictionary(page) + .unwrap() + .get(b"Parent") + .unwrap() + .as_reference() + .unwrap(); + let shared = doc + .get_dictionary(parent) + .unwrap() + .get(b"Resources") + .unwrap() + .as_dict() + .unwrap(); + assert!(!shared.has(b"XObject")); +} + +#[test] +fn makes_the_stamps_marked_content_artifacts() { + let stamp = stamp_pdf(120., 30., "Stamped"); + let out = stamped( + ROTATED, + &plan( + vec![stamp_at(1, 1, [20., 20., 120., 30.], false)], + SignaturePlan::Invisible, + ), + &[&stamp], + ) + .unwrap(); + let doc = Document::load_mem(&out).unwrap(); + let id = page_xobjects(&doc, pages(&doc)[0]) + .get(b"BlankStamp0") + .unwrap() + .as_reference() + .unwrap(); + let form = doc.get_object(id).unwrap().as_stream().unwrap(); + let bbox: Vec = form + .dict + .get(b"BBox") + .unwrap() + .as_array() + .unwrap() + .iter() + .map(|n| n.as_float().unwrap()) + .collect(); + assert_eq!(bbox, [0., 0., 120., 30.]); + assert!(!form.dict.has(b"StructParents")); + let content = form.get_plain_content().unwrap(); + let content = String::from_utf8_lossy(&content); + assert!(!content.contains("MCID"), "{content}"); + assert!(content.contains("/Artifact BMC"), "{content}"); + // the font that refers to itself came along once + let resources = form.dict.get(b"Resources").unwrap().as_dict().unwrap(); + let fonts = resources.get(b"Font").unwrap().as_dict().unwrap(); + let font_id = fonts.get(b"F1").unwrap().as_reference().unwrap(); + let font = doc.get_dictionary(font_id).unwrap(); + assert_eq!( + font.get(b"Itself").unwrap().as_reference().unwrap(), + font_id + ); +} + +#[test] +fn puts_a_repeated_stamp_on_every_page_inside_it() { + let stamp = stamp_pdf(56., 28., "Initials"); + // bottom right of the first page as shown (842 × 495), outside the A5 + // page (420 × 595) + let rect = [750., 430., 56., 28.]; + let out = stamped( + ROTATED, + &plan(vec![stamp_at(1, 1, rect, true)], SignaturePlan::Invisible), + &[&stamp], + ) + .unwrap(); + let doc = Document::load_mem(&out).unwrap(); + let ids: Vec = pages(&doc) + .into_iter() + .map(|page| { + page_xobjects(&doc, page) + .get(b"BlankStamp0") + .unwrap() + .clone() + }) + .collect(); + // one XObject, drawn on both pages + assert_eq!(ids.len(), 2); + assert_eq!(ids[0], ids[1]); + if !has("pdftotext") { + return; + } + let dir = tempfile::tempdir().unwrap(); + let found = words(&written(dir.path(), "initials.pdf", &out)); + let on: Vec<&Word> = found.iter().filter(|w| w.5 == "Initials").collect(); + assert_eq!(on.iter().map(|w| w.0).collect::>(), [1, 2]); + // inside the smaller page + let second = on[1]; + assert!(second.3 <= 420. && second.4 <= 595., "{second:?}"); +} + +#[test] +fn adds_stamps_without_a_signature() { + let stamp = stamp_pdf(120., 30., "Stamped"); + let original = pdf(); + let out = stamped( + &original, + &plan( + vec![stamp_at(1, 1, [72., 72., 120., 30.], false)], + SignaturePlan::Invisible, + ), + &[&stamp], + ) + .unwrap(); + assert!(out.starts_with(&original)); + assert!(verify_pdf(&out).unwrap().is_empty()); + assert!(widgets(&out).is_empty()); + if has("qpdf") { + let dir = tempfile::tempdir().unwrap(); + let path = written(dir.path(), "stamped.pdf", &out); + let status = Command::new("qpdf") + .arg("--check") + .arg(&path) + .output() + .unwrap(); + assert!( + status.status.success(), + "{}", + String::from_utf8_lossy(&status.stdout) + ); + } +} + +#[test] +fn signs_a_signature_field_the_pdf_has() { + let stamp = stamp_pdf(200., 50., "Signed"); + let plan = plan( + vec![], + SignaturePlan::Existing { + field: "signature".into(), + stamp: Some(1), + }, + ); + let out = sign_as(key(), PASSPHRASE, FORM, &plan, &[&stamp]).unwrap(); + assert!(out.starts_with(FORM)); + let reports = verify_pdf(&out).unwrap(); + assert_eq!(reports.len(), 1); + assert_eq!(reports[0].field, "signature"); + assert!(reports[0].valid() && reports[0].covers_the_whole_file()); + // no new field: the one it had, with the stamp as its appearance + let found = widgets(&out); + assert_eq!(found.len(), 1, "{found:?}"); + let doc = Document::load_mem(&out).unwrap(); + let widget = &found[0].1; + assert_eq!(widget.get(b"T").unwrap().as_str().unwrap(), b"signature"); + let ap = deref(&doc, widget.get(b"AP").unwrap()).as_dict().unwrap(); + let normal = deref(&doc, ap.get(b"N").unwrap()).as_stream().unwrap(); + let shows = normal + .dict + .get(b"Resources") + .unwrap() + .as_dict() + .unwrap() + .get(b"XObject") + .unwrap() + .as_dict() + .unwrap() + .get(b"BlankStamp") + .unwrap() + .as_reference() + .unwrap(); + let form = doc.get_object(shows).unwrap().as_stream().unwrap(); + assert_eq!( + form.dict.get(b"Subtype").unwrap().as_name().unwrap(), + b"Form" + ); + assert_eq!(widget.get(b"F").unwrap().as_i64().unwrap(), 4); + if has("pdfsig") { + let dir = tempfile::tempdir().unwrap(); + let path = written(dir.path(), "signed.pdf", &out); + assert_eq!(pdfsig_valid(dir.path(), &path), (1, 1)); + } +} + +#[test] +fn refuses_a_field_that_is_signed_or_isnt_there() { + let once = sign_as( + key(), + PASSPHRASE, + FORM, + &plan( + vec![], + SignaturePlan::Existing { + field: "signature".into(), + stamp: None, + }, + ), + &[], + ) + .unwrap(); + for (pdf, field, refusal) in [ + (&once, "signature", Refusal::FieldSigned), + (&FORM.to_vec(), "Name", Refusal::NoSuchField), + (&FORM.to_vec(), "nothing", Refusal::NoSuchField), + ] { + let plan = plan( + vec![], + SignaturePlan::Existing { + field: field.into(), + stamp: None, + }, + ); + let Err(SigningError::Failed(message)) = sign_as(key(), PASSPHRASE, pdf, &plan, &[]) else { + panic!("signed {field}"); + }; + assert_eq!(message, refusal.to_string()); + } +} + +#[test] +fn adds_empty_named_fields_for_the_others_who_sign() { + let others = |plan: Plan| Plan { + others: vec![ + OtherPlan { + page: 1, + rect: [72., 600., 200., 60.], + name: "Ben Sample".into(), + }, + OtherPlan { + page: 1, + rect: [320., 600., 200., 60.], + name: "Ben Sample".into(), + }, + OtherPlan { + page: 1, + rect: [72., 700., 200., 60.], + name: "Dr. Zoë Ng".into(), + }, + ], + ..plan + }; + let original = pdf(); + let signed_out = signed( + key(), + &original, + &others(options(Some([300., 100., 200., 60.]))), + ); + let stamped_out = stamped(&original, &others(options(None)), &[]).unwrap(); + for (out, signatures) in [(signed_out, 1), (stamped_out, 0)] { + assert_eq!(verify_pdf(&out).unwrap().len(), signatures); + let doc = Document::load_mem(&out).unwrap(); + let names: Vec<(String, bool)> = widgets(&out) + .into_iter() + .map(|(_, w)| { + ( + lopdf::decode_text_string(w.get(b"T").unwrap()).unwrap(), + w.has(b"V"), + ) + }) + .collect(); + let empty: Vec<&str> = names + .iter() + .filter(|(_, v)| !v) + .map(|(n, _)| n.as_str()) + .collect(); + assert_eq!( + empty, + [ + "Signature of Ben Sample", + "Signature of Ben Sample 2", + "Signature of Dr Zoë Ng" + ] + ); + assert_eq!(names.len(), 3 + signatures); + // in the form, so PDF apps offer them to sign + let form = deref(&doc, doc.catalog().unwrap().get(b"AcroForm").unwrap()) + .as_dict() + .unwrap(); + let fields = deref(&doc, form.get(b"Fields").unwrap()) + .as_array() + .unwrap(); + assert_eq!(fields.len(), 3 + signatures); + } +} + +#[test] +fn draws_the_signature_on_an_unsigned_pdf_and_in_the_widget_on_a_signed_one() { + let stamp = stamp_pdf(200., 50., "Signed"); + let new = |page| SignaturePlan::New { + page, + rect: [72., 72., 200., 50.], + stamp: Some(1), + }; + // unsigned: in the page content, the widget's appearance empty + let original = pdf(); + let once = sign_as( + key(), + PASSPHRASE, + &original, + &plan(vec![], new(1)), + &[&stamp], + ) + .unwrap(); + let doc = Document::load_mem(&once).unwrap(); + assert!(page_xobjects(&doc, pages(&doc)[0]).has(b"BlankStamp0")); + // signed: in the widget, the pages as they were + let twice = sign_as(RSA_KEY, PASSPHRASE, &once, &plan(vec![], new(1)), &[&stamp]).unwrap(); + assert!(twice.starts_with(&once)); + let reports = verify_pdf(&twice).unwrap(); + assert_eq!(reports.len(), 2); + assert!(reports.iter().all(|r| r.valid())); + let doc_twice = Document::load_mem(&twice).unwrap(); + let page = pages(&doc_twice)[0]; + assert_eq!( + doc_twice + .get_dictionary(page) + .unwrap() + .get(b"Contents") + .unwrap(), + doc.get_dictionary(page).unwrap().get(b"Contents").unwrap() + ); + let last = widgets(&twice).pop().unwrap().1; + let ap = deref(&doc_twice, last.get(b"AP").unwrap()) + .as_dict() + .unwrap(); + let normal = deref(&doc_twice, ap.get(b"N").unwrap()) + .as_stream() + .unwrap(); + assert!(normal.dict.has(b"Resources")); + if has("pdfsig") { + let dir = tempfile::tempdir().unwrap(); + let path = written(dir.path(), "twice.pdf", &twice); + // the earlier signature still valid + assert_eq!(pdfsig_valid(dir.path(), &path), (2, 2)); + } +} + +/// certified certifies `pdf` with DocMDP permission `p` (the dictionary +/// alone, as the tests of verify do: Blank doesn't certify) +fn certified(pdf: &[u8], p: i64) -> Vec { + let mut doc = Document::load_mem(pdf).unwrap(); + let docmdp = doc.add_object(dictionary! { + "Type" => "Sig", + "Reference" => vec![Object::Dictionary(dictionary! { + "TransformMethod" => "DocMDP", + "TransformParams" => dictionary! { "P" => p }, + })], + }); + let root = doc.trailer.get(b"Root").unwrap().as_reference().unwrap(); + doc.get_dictionary_mut(root) + .unwrap() + .set("Perms", dictionary! { "DocMDP" => docmdp }); + let mut out = vec![]; + doc.save_to(&mut out).unwrap(); + out +} + +#[test] +fn refuses_what_the_pdf_doesnt_allow() { + let stamp = stamp_pdf(120., 30., "Stamped"); + let content = plan( + vec![stamp_at(1, 1, [72., 72., 120., 30.], false)], + SignaturePlan::Invisible, + ); + let earlier = signed(RSA_KEY, &pdf(), &options(None)); + let fails = |result: Answer>, refusal: Refusal| { + let Err(SigningError::Failed(message)) = result else { + panic!("not refused: {refusal:?}"); + }; + assert_eq!(message, refusal.to_string()); + }; + // P=1: nothing at all, a signature or stamps + let p1 = certified(&pdf(), 1); + fails( + sign_as(key(), PASSPHRASE, &p1, &options(None), &[]), + Refusal::Certified, + ); + fails(stamped(&p1, &content, &[&stamp]), Refusal::Certified); + // P=2, and signed: no stamps in the pages, with or without a signature + let p2 = certified(&pdf(), 2); + for pdf in [&p2, &earlier] { + fails(stamped(pdf, &content, &[&stamp]), Refusal::SignedPages); + fails( + sign_as(key(), PASSPHRASE, pdf, &content, &[&stamp]), + Refusal::SignedPages, + ); + } + // but a signature on P=2, its stamp as its appearance + let new = SignaturePlan::New { + page: 1, + rect: [72., 72., 120., 30.], + stamp: Some(1), + }; + sign_as(key(), PASSPHRASE, &p2, &plan(vec![], new), &[&stamp]).unwrap(); + // encrypted + fails(stamped(PASSWORD, &content, &[&stamp]), Refusal::Restricted); + fails( + sign_as(key(), PASSPHRASE, PASSWORD, &options(None), &[]), + Refusal::Restricted, + ); +} + +#[test] +fn reads_the_rules_of_a_pdf() { + use crate::signing::pdf::rules::{Lock, Rules}; + let read = |pdf: &[u8]| Rules::read(&Document::load_mem(pdf).unwrap()); + assert_eq!(read(&pdf()), Rules::default()); + assert_eq!(read(&certified(&pdf(), 1)).docmdp, Some(1)); + let earlier = read(&signed(key(), &pdf(), &options(None))); + assert!(earlier.signed && earlier.docmdp.is_none()); + assert!(!earlier.allows_page_content() && earlier.allows_changes()); + // a FieldMDP lock of a signed field + let mut doc = Document::load_mem(&signed(key(), &pdf(), &options(None))).unwrap(); + let field = widgets(&doc_bytes(&doc))[0].1.clone(); + let id = doc + .objects + .iter() + .find(|(_, o)| o.as_dict().is_ok_and(|d| *d == field)) + .map(|(id, _)| *id) + .unwrap(); + doc.get_dictionary_mut(id).unwrap().set( + "Lock", + dictionary! { "Action" => "Include", "Fields" => vec![Object::string_literal("Name")] }, + ); + let locked = read(&doc_bytes(&doc)); + assert_eq!(locked.locks, [Lock::Include(vec!["Name".into()])]); + assert!(locked.locks("Name") && !locked.locks("Other")); +} + +fn doc_bytes(doc: &Document) -> Vec { + let mut out = vec![]; + doc.clone().save_to(&mut out).unwrap(); + out +} + +#[test] +fn drops_transparency_for_a_pdfa1_target() { + // a stamp with a transparency group and an image with a soft mask + let mut doc = Document::load_mem(&stamp_pdf(10., 10., "x")).unwrap(); + let mask = doc.add_object(Stream::new( + dictionary! { "Type" => "XObject", "Subtype" => "Image", "Width" => 2, "Height" => 1, + "ColorSpace" => "DeviceGray", "BitsPerComponent" => 8 }, + vec![0, 255], + )); + let image = doc.add_object(Stream::new( + dictionary! { "Type" => "XObject", "Subtype" => "Image", "Width" => 2, "Height" => 1, + "ColorSpace" => "DeviceGray", "BitsPerComponent" => 8, "SMask" => mask }, + vec![0, 0], + )); + let page = pages(&doc)[0]; + let p = doc.get_dictionary_mut(page).unwrap(); + p.set( + "Group", + dictionary! { "S" => "Transparency", "CS" => "DeviceRGB" }, + ); + let resources = p.get_mut(b"Resources").unwrap().as_dict_mut().unwrap(); + resources.set("XObject", dictionary! { "Im0" => image }); + resources.set( + "ExtGState", + dictionary! { "G0" => dictionary! { "Type" => "ExtGState", "ca" => 0.5 } }, + ); + let stamp = doc_bytes(&doc); + + let import = |pdfa1: bool| { + let target = pdf(); + let mut incr = lopdf::IncrementalDocument::create_from( + target.clone(), + Document::load_mem(&target).unwrap(), + ); + let form = crate::signing::pdf::xobject::import_stamp(&mut incr, &stamp, pdfa1).unwrap(); + (incr.new_document, form.id) + }; + let (kept, id) = import(false); + assert!(kept + .get_object(id) + .unwrap() + .as_stream() + .unwrap() + .dict + .has(b"Group")); + let (flat, id) = import(true); + let form = flat.get_object(id).unwrap().as_stream().unwrap(); + assert!(!form.dict.has(b"Group")); + let resources = form.dict.get(b"Resources").unwrap().as_dict().unwrap(); + let image_id = resources + .get(b"XObject") + .unwrap() + .as_dict() + .unwrap() + .get(b"Im0") + .unwrap() + .as_reference() + .unwrap(); + let image = flat.get_object(image_id).unwrap().as_stream().unwrap(); + assert!(!image.dict.has(b"SMask")); + // the transparent pixel is white now, the opaque one as it was + assert_eq!(image.get_plain_content().unwrap(), [255, 0]); + let state = resources + .get(b"ExtGState") + .unwrap() + .as_dict() + .unwrap() + .get(b"G0") + .unwrap() + .as_dict() + .unwrap(); + assert_eq!(state.get(b"ca").unwrap().as_float().unwrap(), 1.0); +} + +#[test] +fn reads_the_parts_of_a_body() { + let mut body = 2u32.to_le_bytes().to_vec(); + for part in [&b"%PDF"[..], b"ab"] { + body.extend((part.len() as u64).to_le_bytes()); + body.extend(part); + } + assert_eq!(parts(&body).unwrap(), [&b"%PDF"[..], b"ab"]); + // short, long, or with bytes after + assert!(parts(&body[..body.len() - 1]).is_none()); + let mut longer = body.clone(); + longer.push(0); + assert!(parts(&longer).is_none()); + assert!(parts(&[1, 0]).is_none()); + assert_eq!(parts(&0u32.to_le_bytes()).unwrap(), Vec::<&[u8]>::new()); +} + +#[test] +fn reads_a_tabs_file_while_it_is_the_one_opened() { + let dir = tempfile::tempdir().unwrap(); + let path = written(dir.path(), "a.pdf", FORM); + let sha = crate::pdf_view::sha256(FORM); + let tabs = PdfTabs::default(); + assert_eq!(tab_bytes(&tabs, "t", &path, &sha, &[]).unwrap(), FORM); + assert!(matches!( + tab_bytes(&tabs, "t", &path, "00", &[]), + Err(SigningError::FileChanged) + )); + assert!(matches!( + tab_bytes(&tabs, "t", &dir.path().join("gone.pdf"), &sha, &[]), + Err(SigningError::FileMissing) + )); + // fills wait for the helper's revision (pdf_view::filled_bytes) + let fill = Fill { + field: "Name".into(), + value: FillValue::Text("Ada".into()), + }; + assert!(tab_bytes(&tabs, "t", &path, &sha, &[fill]).is_err()); +} + +#[test] +fn signs_a_field_listed_before_an_earlier_signature() { + // the earlier signature in a new field, after the form's own + let earlier = signed(RSA_KEY, FORM, &options(Some([72., 72., 200., 50.]))); + let stamp = stamp_pdf(200., 50., "Signed"); + let plan = plan( + vec![], + SignaturePlan::Existing { + field: "signature".into(), + stamp: Some(1), + }, + ); + let out = sign_as(key(), PASSPHRASE, &earlier, &plan, &[&stamp]).unwrap(); + assert!(out.starts_with(&earlier)); + let reports = verify_pdf(&out).unwrap(); + let fields: Vec<&str> = reports.iter().map(|r| r.field.as_str()).collect(); + assert_eq!(fields, ["signature", "Signature1"]); + assert!(reports.iter().all(|r| r.valid())); + if has("pdfsig") { + let dir = tempfile::tempdir().unwrap(); + let path = written(dir.path(), "twice.pdf", &out); + assert_eq!(pdfsig_valid(dir.path(), &path), (2, 2)); + } +} diff --git a/src-tauri/src/signing/commands/tests.rs b/src-tauri/src/signing/commands/tests.rs index 66faabd3..9c86b37e 100644 --- a/src-tauri/src/signing/commands/tests.rs +++ b/src-tauri/src/signing/commands/tests.rs @@ -3,11 +3,11 @@ use lopdf::{dictionary, Document, Object, ObjectId, Stream}; use super::*; use crate::signing::verify::{verify_pdf, Coverage}; -const PASSPHRASE: &str = "correct horse"; +pub(super) const PASSPHRASE: &str = "correct horse"; /// a PDF of `pages` A4 pages with a line of text each, as lopdf writes it; /// `modern` writes object streams and a cross-reference stream -fn pdf_of(pages: usize, modern: bool) -> Vec { +pub(super) fn pdf_of(pages: usize, modern: bool) -> Vec { let mut doc = Document::with_version("1.7"); let parent = doc.new_object_id(); let font = doc.add_object(dictionary! { @@ -44,27 +44,49 @@ fn pdf_of(pages: usize, modern: bool) -> Vec { bytes } -fn pdf() -> Vec { +pub(super) fn pdf() -> Vec { pdf_of(1, false) } /// what Blank's layout engine writes, see fixtures/README.md const BLANK_PDF: &[u8] = include_bytes!("../fixtures/blank.pdf"); /// an RSA key from OpenSSL, see fixtures/README.md -const RSA_KEY: &[u8] = include_bytes!("../fixtures/rsa.p12"); - -fn options(rect: Option<[f64; 4]>) -> SignOptions { - SignOptions { - page: 1, - rect, +pub(super) const RSA_KEY: &[u8] = include_bytes!("../fixtures/rsa.p12"); + +/// a plan that signs in a new field at `rect` on page 1, or invisibly +pub(super) fn options(rect: Option<[f64; 4]>) -> Plan { + Plan { + stamps: vec![], + signature: match rect { + Some(rect) => SignaturePlan::New { + page: 1, + rect, + stamp: None, + }, + None => SignaturePlan::Invisible, + }, + others: vec![], location: Some("Zürich".into()), reason: None, timestamp_server: None, } } +/// sign_as signs `pdf` as the export flow sends it: the PDF the body's +/// part 0, the stamps' PDFs `stamps` after it +pub(super) fn sign_as( + p12: &[u8], + passphrase: &str, + pdf: &[u8], + plan: &Plan, + stamps: &[&[u8]], +) -> Answer> { + let parts: Vec<&[u8]> = std::iter::once(pdf).chain(stamps.iter().copied()).collect(); + sign(Some(Key { p12, passphrase }), pdf, &parts, plan) +} + // a key made once, with few rounds of PBKDF2, which opens fast -fn key() -> &'static [u8] { +pub(super) fn key() -> &'static [u8] { static KEY: std::sync::OnceLock> = std::sync::OnceLock::new(); KEY.get_or_init(|| { keys::create_certificate_with("Ada Lovelace", Some("ada@example.org"), PASSPHRASE, 1000) @@ -74,8 +96,8 @@ fn key() -> &'static [u8] { } /// signs `pdf` and checks that it was appended and checks out -fn signed(key: &[u8], pdf: &[u8], options: &SignOptions) -> Vec { - let signed = sign(key, PASSPHRASE, pdf, options).unwrap(); +pub(super) fn signed(key: &[u8], pdf: &[u8], options: &Plan) -> Vec { + let signed = sign_as(key, PASSPHRASE, pdf, options, &[]).unwrap(); // an incremental update: the original bytes stay as they were assert!(signed.starts_with(pdf)); let reports = verify_pdf(&signed).unwrap(); @@ -86,7 +108,7 @@ fn signed(key: &[u8], pdf: &[u8], options: &SignOptions) -> Vec { } /// the signature widgets on each page of `pdf`, by page number -fn widgets(pdf: &[u8]) -> Vec<(u32, lopdf::Dictionary)> { +pub(super) fn widgets(pdf: &[u8]) -> Vec<(u32, lopdf::Dictionary)> { let doc = Document::load_mem(pdf).unwrap(); let mut found = vec![]; for (number, page) in doc.get_pages() { @@ -110,7 +132,7 @@ fn widgets(pdf: &[u8]) -> Vec<(u32, lopdf::Dictionary)> { found } -fn numbers(array: &Object) -> Vec { +pub(super) fn numbers(array: &Object) -> Vec { array .as_array() .unwrap() @@ -187,9 +209,13 @@ fn puts_the_field_on_the_page_asked_for() { let signed = signed( key(), &original, - &SignOptions { - page: 2, - ..options(Some([72., 72., 100., 40.])) + &Plan { + signature: SignaturePlan::New { + page: 2, + rect: [72., 72., 100., 40.], + stamp: None, + }, + ..options(None) }, ); let pages: Vec = widgets(&signed).iter().map(|(page, _)| *page).collect(); @@ -246,24 +272,27 @@ fn signs_invisibly_and_twice() { fn signs_again_with_more_room_when_the_signature_doesnt_fit() { let identity = keys::load_p12(key(), PASSPHRASE).unwrap(); let original = pdf(); - let request = SignRequest { + let signer = Signer { identity: &identity, - page: 1, - rect: None, reason: None, location: None, name: None, tsa: None, reserve: 64, }; - let signed = sign_pdf(&original, &request).unwrap(); + let plan = UpdatePlan { + stamps: vec![], + signature: Spot::Invisible, + others: vec![], + }; + let signed = update_pdf(&original, &plan, Some(&signer)).unwrap(); assert!(signed.starts_with(&original)); - check_signed(&signed, false).unwrap(); + check_signed(&signed, false, &[]).unwrap(); } #[test] fn finds_a_change_after_signing() { - let mut signed = sign(key(), PASSPHRASE, &pdf(), &options(None)).unwrap(); + let mut signed = sign_as(key(), PASSPHRASE, &pdf(), &options(None), &[]).unwrap(); // a byte of the page's text changed let at = signed.windows(6).position(|w| w == b"Signed").unwrap(); signed[at] = b'X'; @@ -272,7 +301,7 @@ fn finds_a_change_after_signing() { #[test] fn finds_what_was_added_after_signing() { - let signed = sign(key(), PASSPHRASE, &pdf(), &options(None)).unwrap(); + let signed = sign_as(key(), PASSPHRASE, &pdf(), &options(None), &[]).unwrap(); // an update after the signature, which no signature covers let doc = Document::load_mem(&signed).unwrap(); let mut update = lopdf::IncrementalDocument::create_from(signed.clone(), doc); @@ -289,7 +318,7 @@ fn finds_what_was_added_after_signing() { reports[0].coverage, crate::signing::verify::Coverage::ChangedAfterSigning ); - assert!(check_signed(&signed, false).is_err()); + assert!(check_signed(&signed, false, &[]).is_err()); } /// validated adds `change` to the signed `pdf` as an unsigned update, and @@ -337,8 +366,8 @@ fn first_contents(update: &lopdf::IncrementalDocument) -> ObjectId { #[test] fn takes_validation_data_added_after_signing_for_what_it_is() { - let signed = sign(key(), PASSPHRASE, &pdf(), &options(None)).unwrap(); - let modern = sign(key(), PASSPHRASE, &pdf_of(1, true), &options(None)).unwrap(); + let signed = sign_as(key(), PASSPHRASE, &pdf(), &options(None), &[]).unwrap(); + let modern = sign_as(key(), PASSPHRASE, &pdf_of(1, true), &options(None), &[]).unwrap(); // lopdf writes the update's cross-references as a stream, read here by // Blank's own reader of them for signed in [signed, modern] { @@ -351,7 +380,7 @@ fn takes_validation_data_added_after_signing_for_what_it_is() { #[test] fn calls_a_page_written_again_beside_validation_data_a_change() { - let signed = sign(key(), PASSPHRASE, &pdf(), &options(None)).unwrap(); + let signed = sign_as(key(), PASSPHRASE, &pdf(), &options(None), &[]).unwrap(); let rewrite = |update: &mut lopdf::IncrementalDocument| { let contents = first_contents(update); update.new_document.set_object( @@ -450,7 +479,7 @@ fn calls_the_info_its_own_dss_lists_written_again_a_change() { #[test] fn calls_an_emptied_dictionary_or_an_array_of_numbers_a_change() { - let signed = sign(key(), PASSPHRASE, &pdf(), &options(None)).unwrap(); + let signed = sign_as(key(), PASSPHRASE, &pdf(), &options(None), &[]).unwrap(); let emptied = validated( &signed, |update| { @@ -485,7 +514,7 @@ fn calls_an_emptied_dictionary_or_an_array_of_numbers_a_change() { #[test] fn calls_an_object_freed_after_signing_a_change() { - let signed = sign(key(), PASSPHRASE, &pdf(), &options(None)).unwrap(); + let signed = sign_as(key(), PASSPHRASE, &pdf(), &options(None), &[]).unwrap(); let mut out = validated(&signed, |_| {}, |_| vec![]); // another update, whose cross-reference table marks the page's content // stream free @@ -527,7 +556,7 @@ fn calls_an_object_freed_after_signing_a_change() { #[test] fn says_when_the_passphrase_is_wrong() { assert!(matches!( - sign(key(), "wrong", &pdf(), &options(None)), + sign_as(key(), "wrong", &pdf(), &options(None), &[]), Err(SigningError::WrongPassphrase) )); } @@ -535,7 +564,7 @@ fn says_when_the_passphrase_is_wrong() { #[test] fn refuses_what_isnt_a_pdf_without_quoting_it() { let Err(SigningError::Failed(message)) = - sign(key(), PASSPHRASE, b"%PDF-1.7 junk", &options(None)) + sign_as(key(), PASSPHRASE, b"%PDF-1.7 junk", &options(None), &[]) else { panic!("signed what isn't a PDF"); }; @@ -551,7 +580,7 @@ fn refuses_a_place_that_isnt_a_rectangle() { [300., f64::INFINITY, 200., 60.], ] { let Err(SigningError::Failed(message)) = - sign(key(), PASSPHRASE, &pdf(), &options(Some(rect))) + sign_as(key(), PASSPHRASE, &pdf(), &options(Some(rect)), &[]) else { panic!("signed in {rect:?}"); }; @@ -575,7 +604,8 @@ fn says_when_a_certified_pdf_allows_no_changes() { .set("Perms", dictionary! { "DocMDP" => docmdp }); let mut certified = vec![]; doc.save_to(&mut certified).unwrap(); - let Err(SigningError::Failed(message)) = sign(key(), PASSPHRASE, &certified, &options(None)) + let Err(SigningError::Failed(message)) = + sign_as(key(), PASSPHRASE, &certified, &options(None), &[]) else { panic!("signed a PDF that allows no changes"); }; @@ -584,11 +614,30 @@ fn says_when_a_certified_pdf_allows_no_changes() { #[test] fn reads_the_header_the_webview_encodes() { - // encodeURIComponent of {"passphrase":"grüß","options":{"page":2,"rect":null}} - let value = "%7B%22passphrase%22%3A%22gr%C3%BC%C3%9F%22%2C%22options%22%3A%7B%22page%22%3A2%2C%22rect%22%3Anull%7D%7D"; - let read = header(value).unwrap(); - assert_eq!(read.passphrase.as_str(), "grüß"); - assert_eq!(read.options.page, 2); + let json = r#"{"passphrase":"grüß","source":{"kind":"tab","tab":"t1","path":"/tmp/a.pdf","sha256":"ab","fills":[{"field":"Name","value":"Ada"},{"field":"Agree","value":true}]},"plan":{"stamps":[{"part":0,"page":1,"rect":[1,2,3,4],"repeat":true}],"signature":{"kind":"existing","field":"Signature1","stamp":1},"others":[{"page":2,"rect":[5,6,7,8],"name":"Ben Sample"}],"name":null,"location":"Zürich","reason":null,"timestampServer":null}}"#; + let value: String = + percent_encoding::utf8_percent_encode(json, percent_encoding::NON_ALPHANUMERIC).to_string(); + let read = header(&value).unwrap(); + assert_eq!(read.passphrase.as_deref().map(String::as_str), Some("grüß")); + let Source::Tab { fills, sha256, .. } = &read.source else { + panic!("not a tab"); + }; + assert_eq!(sha256, "ab"); + assert!(matches!(&fills[1].value, FillValue::Ticked(true))); + assert!(read.plan.stamps[0].repeat); + assert!(matches!( + read.plan.signature, + SignaturePlan::Existing { stamp: Some(1), .. } + )); + assert_eq!(read.plan.others[0].name, "Ben Sample"); + // without a passphrase: the stamps alone + let json = + r#"{"passphrase":null,"source":{"kind":"body"},"plan":{"signature":{"kind":"invisible"}}}"#; + let value: String = + percent_encoding::utf8_percent_encode(json, percent_encoding::NON_ALPHANUMERIC).to_string(); + let read = header(&value).unwrap(); + assert!(read.passphrase.is_none()); + assert!(matches!(read.source, Source::Body)); assert!(header("%7B%Z1").is_none()); } @@ -675,8 +724,8 @@ fn keeps_the_old_pair_when_the_new_certificate_cant_be_put_in_place() { #[test] fn refuses_a_signature_without_the_timestamp_asked_for() { let signed = signed(key(), &pdf(), &options(None)); - assert!(check_signed(&signed, false).is_ok()); - let Err(SigningError::Timestamp(message)) = check_signed(&signed, true) else { + assert!(check_signed(&signed, false, &[]).is_ok()); + let Err(SigningError::Timestamp(message)) = check_signed(&signed, true, &[]) else { panic!("a signature without its timestamp passed"); }; assert_eq!(message, Refusal::TimestampUnusable.to_string()); diff --git a/src-tauri/src/signing/mod.rs b/src-tauri/src/signing/mod.rs index 5226e0e7..28b4cc97 100644 --- a/src-tauri/src/signing/mod.rs +++ b/src-tauri/src/signing/mod.rs @@ -32,6 +32,14 @@ pub enum Refusal { Restricted, /// a certified PDF that allows no changes (DocMDP P=1) Certified, + /// stamps in the pages' content of a PDF that is signed or certified + SignedPages, + /// the signature field asked for isn't in the PDF + NoSuchField, + /// the signature field asked for is signed already + FieldSigned, + /// an earlier signer locked the signature field asked for + FieldLocked, /// the timestamp server can't be reached or didn't answer NoTimestamp, /// the timestamp server answered, and refused @@ -47,6 +55,12 @@ impl std::fmt::Display for Refusal { "This PDF has restrictions set by its author. Blank can't sign it yet." } Refusal::Certified => "This PDF doesn't allow any changes, not even signatures", + Refusal::SignedPages => { + "This PDF is signed already: what you placed on its pages would change what was signed. Only your signature can still be added." + } + Refusal::NoSuchField => "This PDF has no signature field of that name", + Refusal::FieldSigned => "This signature field is signed already", + Refusal::FieldLocked => "An earlier signer locked this signature field", Refusal::NoTimestamp => "The timestamp server didn't answer", Refusal::TimestampRefused => "The timestamp server refused to timestamp the signature", Refusal::TimestampUnusable => "The timestamp server's answer can't be used", diff --git a/src-tauri/src/signing/pdf/mod.rs b/src-tauri/src/signing/pdf/mod.rs index 2a0d62e8..a0e9d3e6 100644 --- a/src-tauri/src/signing/pdf/mod.rs +++ b/src-tauri/src/signing/pdf/mod.rs @@ -1,7 +1,16 @@ //! loading a PDF for an incremental update, and what signing needs of it: //! page geometry, inherited attributes, text strings +//! +//! - `rules`: what may be added to a PDF (DocMDP, earlier signatures, +//! FieldMDP locks) +//! - `xobject`: a stamp's one-page PDF as a Form XObject +//! - `stamp`: stamps drawn in a page's content +//! - `sign`: the update itself: stamps, fields, the signature +pub mod rules; pub mod sign; +pub mod stamp; +pub mod xobject; use anyhow::{bail, Context, Result}; use lopdf::{Dictionary, Document, IncrementalDocument, Object, ObjectId, StringFormat}; @@ -102,6 +111,8 @@ const MAX_NAME: usize = 1_024; /// a field of a PDF's form, as form_fields finds it pub struct FormField<'a> { + /// its object, when it is one of its own (as fields are) + pub id: Option, pub dict: &'a Dictionary, /// its partial name, `/T` as written pub partial: Option<&'a [u8]>, @@ -189,6 +200,7 @@ pub fn form_fields(doc: &Document) -> Vec> { } } out.push(FormField { + id, dict, partial, name, @@ -296,6 +308,57 @@ impl PageGeometry { [ax.min(bx), ay.min(by), ax.max(bx), ay.max(by)] } + /// visible_size is the page as shown, in points: its width and height + pub fn visible_size(&self) -> (f64, f64) { + let w = (self.bbox[2] - self.bbox[0]) * self.user_unit; + let h = (self.bbox[3] - self.bbox[1]) * self.user_unit; + if self.rotate % 180 == 0 { + (w, h) + } else { + (h, w) + } + } + + /// placement is the matrix that draws a form made upright, its BBox + /// `[0 0 w h]` in points, into `user` (a user-space rectangle that + /// rect_to_user gave for a w × h rectangle) on this page + pub fn placement(&self, user: [f64; 4]) -> [f64; 6] { + let s = 1.0 / self.user_unit; + // turned counter-clockwise by the page's rotation, which the viewer + // turns clockwise again + let (a, b, c, d) = match self.rotate { + 90 => (0., s, -s, 0.), + 180 => (-s, 0., 0., -s), + 270 => (0., -s, s, 0.), + _ => (s, 0., 0., s), + }; + // where the form's origin lands: the corner that ends up lowest-left + let (e, f) = match self.rotate { + 90 => (user[2], user[1]), + 180 => (user[2], user[3]), + 270 => (user[0], user[3]), + _ => (user[0], user[1]), + }; + [a, b, c, d, e, f] + } + + /// user_to_display turns a user-space rectangle [llx lly urx ury] into + /// x, y, w, h in points from the top left of the page as shown + pub fn user_to_display(&self, r: [f64; 4]) -> [f64; 4] { + let [x0, y0, x1, y1] = self.bbox; + let u = self.user_unit; + // the corners in display space, by the inverse of point_to_user + let corner = |x: f64, y: f64| match self.rotate { + 90 => ((y - y0) * u, (x - x0) * u), + 180 => ((x1 - x) * u, (y - y0) * u), + 270 => ((y1 - y) * u, (x1 - x) * u), + _ => ((x - x0) * u, (y1 - y) * u), + }; + let (ax, ay) = corner(r[0], r[1]); + let (bx, by) = corner(r[2], r[3]); + [ax.min(bx), ay.min(by), (ax - bx).abs(), (ay - by).abs()] + } + /// appearance_matrix is the rotation alone, for a widget's appearance /// /Matrix pub fn appearance_matrix(&self) -> [f64; 6] { @@ -308,6 +371,36 @@ impl PageGeometry { } } +/// is_rect tells whether `r` (x, y, w, h) is a rectangle with room in it +pub fn is_rect(r: &[f64; 4]) -> bool { + r.iter().all(|value| value.is_finite()) && r[2] > 0. && r[3] > 0. +} + +/// current is the object `id` as this update has it: changed, or as it was +pub fn current(incr: &IncrementalDocument, id: ObjectId) -> Result<&Object> { + match incr.new_document.objects.get(&id) { + Some(object) => Ok(object), + None => Ok(incr.get_prev_documents().get_object(id)?), + } +} + +/// pdfa_part is the part of PDF/A the document claims in its XMP +/// metadata (`pdfaid:part`), if it claims one +pub fn pdfa_part(doc: &Document) -> Option { + let catalog = doc.catalog().ok()?; + let metadata = resolve(doc, catalog.get(b"Metadata").ok()?)? + .as_stream() + .ok()?; + let xmp = metadata.get_plain_content_with_limit(1 << 20).ok()?; + let text = String::from_utf8_lossy(&xmp); + // as an element, 1, or an attribute, + // pdfaid:part="1" + let at = text.find("pdfaid:part")? + "pdfaid:part".len(); + let rest = text[at..].trim_start_matches(['>', '=', '"', '\'', ' ']); + let digits: String = rest.chars().take_while(char::is_ascii_digit).collect(); + digits.parse().ok() +} + /// reals is an array of reals pub fn reals(v: &[f64]) -> Object { Object::Array(v.iter().map(|x| Object::Real(*x as f32)).collect()) diff --git a/src-tauri/src/signing/pdf/rules.rs b/src-tauri/src/signing/pdf/rules.rs new file mode 100644 index 00000000..6a8d7f86 --- /dev/null +++ b/src-tauri/src/signing/pdf/rules.rs @@ -0,0 +1,160 @@ +//! what may be added to a PDF (the plan's §6.3), read once from it: a +//! certification's DocMDP permission, whether it is signed already, and +//! the fields earlier signatures locked (FieldMDP). Encrypted PDFs never +//! get this far: `load_for_update` refuses them. + +use std::collections::HashSet; + +use lopdf::{Dictionary, Document, Object}; +use serde::Serialize; + +use super::{docmdp_permission, form_fields, resolve}; + +/// the fields one signature locks once it is signed +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[serde(tag = "action", content = "fields", rename_all = "camelCase")] +pub enum Lock { + /// every field + All, + /// only these, by full name + Include(Vec), + /// every field but these + Exclude(Vec), +} + +impl Lock { + /// read reads a FieldMDP lock: a field's `/Lock`, or the + /// TransformParams of a FieldMDP reference + fn read(doc: &Document, dict: &Dictionary) -> Option { + let names = || -> Vec { + dict.get(b"Fields") + .ok() + .and_then(|f| resolve(doc, f)) + .and_then(|f| f.as_array().ok()) + .map(|fields| { + fields + .iter() + .filter_map(|f| resolve(doc, f)) + .filter_map(|f| lopdf::decode_text_string(f).ok()) + .collect() + }) + .unwrap_or_default() + }; + match dict.get(b"Action").and_then(Object::as_name).ok()? { + b"All" => Some(Lock::All), + b"Include" => Some(Lock::Include(names())), + b"Exclude" => Some(Lock::Exclude(names())), + _ => None, + } + } + + /// locks tells whether this lock covers the field `name` + fn locks(&self, name: &str) -> bool { + match self { + Lock::All => true, + Lock::Include(names) => names.iter().any(|n| n == name), + Lock::Exclude(names) => !names.iter().any(|n| n == name), + } + } +} + +/// what may be added to a PDF +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Rules { + /// the DocMDP permission of a certified PDF: 1 no changes, 2 filling in + /// and signing, 3 annotations too + pub docmdp: Option, + /// a signature field of it is signed + pub signed: bool, + /// what the signed signatures lock + pub locks: Vec, +} + +impl Rules { + /// read reads the rules of `doc` + pub fn read(doc: &Document) -> Self { + let docmdp = docmdp_permission(doc); + let mut signed = docmdp.is_some(); + let mut locks = vec![]; + let mut seen = HashSet::new(); + for field in form_fields(doc).iter().filter(|field| field.signature) { + let Some(value) = field + .dict + .get(b"V") + .ok() + .and_then(|v| resolve(doc, v)) + .and_then(|v| v.as_dict().ok()) + else { + continue; + }; + signed = true; + // a lock on the field takes effect once it is signed + if let Some(lock) = field + .dict + .get(b"Lock") + .ok() + .and_then(|l| resolve(doc, l)) + .and_then(|l| l.as_dict().ok()) + .and_then(|l| Lock::read(doc, l)) + { + if seen.insert(format!("{lock:?}")) { + locks.push(lock); + } + } + // and the signature's own FieldMDP reference + let references = value + .get(b"Reference") + .ok() + .and_then(|r| resolve(doc, r)) + .and_then(|r| r.as_array().ok()); + for reference in references.into_iter().flatten() { + let Some(reference) = resolve(doc, reference).and_then(|r| r.as_dict().ok()) else { + continue; + }; + if reference + .get(b"TransformMethod") + .and_then(Object::as_name) + .ok() + != Some(b"FieldMDP") + { + continue; + } + if let Some(lock) = reference + .get(b"TransformParams") + .ok() + .and_then(|p| resolve(doc, p)) + .and_then(|p| p.as_dict().ok()) + .and_then(|p| Lock::read(doc, p)) + { + if seen.insert(format!("{lock:?}")) { + locks.push(lock); + } + } + } + } + Self { + docmdp, + signed, + locks, + } + } + + /// allows_changes tells whether anything may be added, a signature + /// included: not to a certified PDF that allows no changes + pub fn allows_changes(&self) -> bool { + self.docmdp != Some(1) + } + + /// allows_page_content tells whether stamps may go into the pages' + /// content: not once the PDF is signed or certified (P=2 or 3 allow + /// filling in and signing, not changing the pages) + pub fn allows_page_content(&self) -> bool { + !self.signed && self.docmdp.is_none() + } + + /// locks tells whether an earlier signature locked the field `name` + pub fn locks(&self, name: &str) -> bool { + self.locks.iter().any(|lock| lock.locks(name)) + } +} diff --git a/src-tauri/src/signing/pdf/sign.rs b/src-tauri/src/signing/pdf/sign.rs index 4d2d3c1d..59d85fcb 100644 --- a/src-tauri/src/signing/pdf/sign.rs +++ b/src-tauri/src/signing/pdf/sign.rs @@ -1,24 +1,28 @@ -//! signing a PDF in an incremental update: a new /Sig field and widget, -//! the signature dictionary with a reserved /Contents, then the byte ranges -//! are patched in, hashed and signed +//! an incremental update of a PDF: stamps in its pages' content, empty +//! signature fields for others, and a signature: a /Sig field and widget +//! (one the PDF has, or a new one), the signature dictionary with a +//! reserved /Contents, then the byte ranges are patched in, hashed and +//! signed. Without a signer the update holds the stamps and fields alone. +use std::collections::HashMap; + +use super::rules::Rules; +use super::stamp::{appearance, clamp, draw_on_page, Draw}; +use super::xobject::{import_stamp, Imported}; use super::{ - clean_trailer, docmdp_permission, load_for_update, page_id, reals, text_string, PageGeometry, + clean_trailer, current, form_fields, is_rect, load_for_update, pdfa_part, reals, resolve, + text_string, PageGeometry, }; use crate::signing::cms::sign_detached; use crate::signing::keys::Identity; use crate::signing::Refusal; use anyhow::{bail, Context, Result}; -use lopdf::{dictionary, Dictionary, IncrementalDocument, Object, ObjectId, Stream, StringFormat}; +use lopdf::{dictionary, Dictionary, IncrementalDocument, Object, ObjectId, StringFormat}; use sha2::{Digest, Sha256}; -/// what to sign with, and where the signature goes -pub struct SignRequest<'a> { +/// who signs, and what the signature says +pub struct Signer<'a> { pub identity: &'a Identity, - /// 1-based page of the signature widget - pub page: u32, - /// x, y, w, h in points from the top left of the page as shown; None: invisible - pub rect: Option<[f64; 4]>, pub reason: Option, pub location: Option, pub name: Option, @@ -27,9 +31,55 @@ pub struct SignRequest<'a> { pub reserve: usize, } +/// a stamp to put on the pages: its one-page PDF, made by Blank's engine +pub struct PlacedStamp<'a> { + pub pdf: &'a [u8], + /// from 1 + pub page: u32, + /// x, y, w, h in points from the top left of the page as shown + pub rect: [f64; 4], + /// on every page, at the same place, moved into smaller pages + pub repeat: bool, +} + +/// where the signature goes, and what it shows +pub enum Spot<'a> { + /// nowhere on the pages: PDF apps show it in their signature panel + Invisible, + /// the empty signature field of this full name + Existing { + field: String, + stamp: Option<&'a [u8]>, + }, + /// a new field, `SignatureN` + New { + /// from 1 + page: u32, + rect: [f64; 4], + stamp: Option<&'a [u8]>, + }, +} + +/// an empty signature field for someone else to sign later +pub struct Other { + pub page: u32, + pub rect: [f64; 4], + /// who is to sign it: the field is "Signature of ‹name›" + pub name: String, +} + +/// what goes into the update +pub struct Plan<'a> { + pub stamps: Vec>, + pub signature: Spot<'a>, + pub others: Vec, +} + /// the most room for a signature: a long chain and a timestamp token fit /// many times over const MAX_RESERVE: usize = 256 * 1024; +/// the most stamps and other signers' fields one update takes +const MAX_STAMPS: usize = 1_000; /// how a signing went: signed, or the signature needed more room enum Attempt { @@ -40,20 +90,36 @@ enum Attempt { const BR_PLACEHOLDER: [i64; 4] = [0, 1_111_111_111, 2_222_222_222, 3_333_333_333]; -/// sign_pdf signs `original` as `req` asks, and returns it with the -/// signature appended; a signature larger than `req.reserve` is made again -/// with room for it -pub fn sign_pdf(original: &[u8], req: &SignRequest) -> Result> { - if let Some(rect) = req.rect { - if !rect.iter().all(|value| value.is_finite()) || rect[2] <= 0. || rect[3] <= 0. { - bail!("the signature's place isn't a rectangle"); +/// update_pdf appends `plan` to `original` as an incremental update, signed +/// by `signer`, or with no signature without one. A signature larger than +/// `signer.reserve` is made again with room for it. +pub fn update_pdf(original: &[u8], plan: &Plan, signer: Option<&Signer>) -> Result> { + let rects = plan + .stamps + .iter() + .map(|s| &s.rect) + .chain(plan.others.iter().map(|o| &o.rect)) + .chain(match &plan.signature { + Spot::New { rect, .. } => Some(rect), + _ => None, + }); + for rect in rects { + if !is_rect(rect) { + bail!("a place on the page isn't a rectangle"); } } - match sign_once(original, req, req.reserve)? { + if plan.stamps.len() + plan.others.len() > MAX_STAMPS { + bail!("too many stamps"); + } + let Some(signer) = signer else { + let (incr, _) = build(original, plan, None)?; + return save(incr, original); + }; + match sign_once(original, plan, signer, signer.reserve)? { Attempt::Signed(signed) => Ok(signed), // a new signature, and a new timestamp, may be a little longer Attempt::TooLarge(length) if length + 2048 <= MAX_RESERVE => { - match sign_once(original, req, length + 2048)? { + match sign_once(original, plan, signer, length + 2048)? { Attempt::Signed(signed) => Ok(signed), Attempt::TooLarge(_) => bail!("the signature doesn't fit"), } @@ -62,6 +128,18 @@ pub fn sign_pdf(original: &[u8], req: &SignRequest) -> Result> { } } +/// save writes the update after `original` +fn save(mut incr: IncrementalDocument, original: &[u8]) -> Result> { + let mut out = Vec::new(); + incr.save_to(&mut out)?; + // lopdf ends with "%%EOF" and no newline; add one like most writers + out.push(b'\n'); + if !out.starts_with(original) { + bail!("the update changed the bytes before it"); + } + Ok(out) +} + /// pdf_date is now, as a PDF date fn pdf_date() -> String { let t = time::OffsetDateTime::now_utc(); @@ -78,12 +156,7 @@ fn pdf_date() -> String { /// add_annot adds `widget` to the page's /Annots, wherever that array lives fn add_annot(incr: &mut IncrementalDocument, page: ObjectId, widget: ObjectId) -> Result<()> { - let annots = incr - .get_prev_documents() - .get_dictionary(page)? - .get(b"Annots") - .ok() - .cloned(); + let annots = current(incr, page)?.as_dict()?.get(b"Annots").ok().cloned(); match annots { Some(Object::Reference(r)) => { incr.opt_clone_object_to_new_document(r)?; @@ -107,40 +180,46 @@ fn add_annot(incr: &mut IncrementalDocument, page: ObjectId, widget: ObjectId) - Ok(()) } -/// add_field adds the field to /AcroForm /Fields and sets /SigFlags 3 -fn add_field(incr: &mut IncrementalDocument, field: ObjectId) -> Result<()> { +/// add_fields adds `fields` to /AcroForm /Fields, and with `signed` sets +/// /SigFlags 3 (signatures exist, append only) +fn add_fields(incr: &mut IncrementalDocument, fields: &[ObjectId], signed: bool) -> Result<()> { + if fields.is_empty() && !signed { + return Ok(()); + } let root = incr .get_prev_documents() .trailer .get(b"Root")? .as_reference()?; - let form = incr - .get_prev_documents() - .get_dictionary(root)? + let form = current(incr, root)? + .as_dict()? .get(b"AcroForm") .ok() .cloned(); let update = |incr: &mut IncrementalDocument, form: &mut Dictionary| -> Result<()> { - form.set("SigFlags", 3); + if signed { + form.set("SigFlags", 3); + } + let added = fields.iter().map(|f| Object::Reference(*f)); match form.get(b"Fields").ok().cloned() { Some(Object::Reference(r)) => { incr.opt_clone_object_to_new_document(r)?; match incr.new_document.get_object_mut(r)? { - Object::Array(a) => a.push(Object::Reference(field)), + Object::Array(a) => a.extend(added), _ => bail!("/Fields doesn't point to an array"), } } Some(Object::Array(mut a)) => { - a.push(Object::Reference(field)); + a.extend(added); form.set("Fields", a); } - _ => form.set("Fields", vec![Object::Reference(field)]), + _ => form.set("Fields", added.collect::>()), } Ok(()) }; match form { Some(Object::Reference(r)) => { - let mut f = incr.get_prev_documents().get_dictionary(r)?.clone(); + let mut f = current(incr, r)?.as_dict()?.clone(); update(incr, &mut f)?; incr.new_document.set_object(r, f); } @@ -164,82 +243,337 @@ fn add_field(incr: &mut IncrementalDocument, field: ObjectId) -> Result<()> { Ok(()) } -/// appearance is the widget's appearance: an empty form. What the -/// signature looks like is in the page content (a signature block Blank -/// drew), or there is none (an invisible signature); Blank draws no frame -/// around a signature. -fn appearance(incr: &mut IncrementalDocument, geo: &PageGeometry, w: f64, h: f64) -> ObjectId { - let dict = dictionary! { - "Type" => "XObject", "Subtype" => "Form", "BBox" => reals(&[0., 0., w, h]), - "Matrix" => reals(&geo.appearance_matrix()), +/// free_field_name is `wanted`, or `wanted` with the first number that +/// makes it a name no field has; `numbered` starts with `wanted1` +fn free_field_name(taken: &mut Vec, wanted: &str, numbered: bool) -> String { + let name = (if numbered { 1 } else { 0 }..) + .map(|n| match n { + 0 => wanted.to_string(), + n if numbered => format!("{wanted}{n}"), + n => format!("{wanted} {}", n + 1), + }) + .find(|name| !taken.contains(name)) + .unwrap_or_default(); + taken.push(name.clone()); + name +} + +/// other_field_name is the field name for someone else's signature: a +/// partial name can't hold a period, which joins the parts of full names +fn other_field_name(name: &str) -> String { + let name: String = name + .chars() + .filter(|c| !c.is_control()) + .map(|c| if c == '.' { ' ' } else { c }) + .collect(); + let name = name.split_whitespace().collect::>().join(" "); + if name.is_empty() { + "Signature".into() + } else { + format!("Signature of {name}") + } +} + +/// an empty signature field the PDF has, and the widget showing it +struct Target { + field: ObjectId, + widget: ObjectId, + page: ObjectId, + /// the widget's rectangle in user space + rect: [f64; 4], +} + +/// find_target finds the empty signature field `name` of the PDF in `incr` +fn find_target(incr: &IncrementalDocument, rules: &Rules, name: &str) -> Result { + let doc = incr.get_prev_documents(); + let fields = form_fields(doc); + let field = fields + .iter() + .filter(|f| f.signature && f.name == name) + // a field with kids is the one the kids' values belong to + .find(|f| f.partial.is_some()) + .ok_or(Refusal::NoSuchField)?; + if field.dict.has(b"V") { + return Err(Refusal::FieldSigned.into()); + } + if rules.locks(name) { + return Err(Refusal::FieldLocked.into()); + } + let field_id = field.id.context("a signature field isn't an object")?; + let is_widget = + |d: &Dictionary| d.get(b"Subtype").and_then(Object::as_name).ok() == Some(b"Widget"); + let widget = if is_widget(field.dict) { + field_id + } else { + field + .dict + .get(b"Kids") + .ok() + .and_then(|k| resolve(doc, k)) + .and_then(|k| k.as_array().ok()) + .into_iter() + .flatten() + .filter_map(|k| k.as_reference().ok()) + .find(|k| doc.get_dictionary(*k).is_ok_and(is_widget)) + .context("a signature field has no widget")? + }; + let widget_dict = doc.get_dictionary(widget)?; + let page = match widget_dict.get(b"P").and_then(Object::as_reference) { + Ok(page) if doc.get_pages().values().any(|p| *p == page) => page, + _ => doc + .get_pages() + .values() + .copied() + .find(|page| { + doc.get_dictionary(*page) + .ok() + .and_then(|p| p.get(b"Annots").ok()) + .and_then(|a| resolve(doc, a)) + .and_then(|a| a.as_array().ok()) + .is_some_and(|a| a.iter().any(|a| a.as_reference().ok() == Some(widget))) + }) + .context("a signature field isn't on a page")?, }; - incr.new_document.add_object(Stream::new(dict, Vec::new())) + let numbers: Vec = widget_dict + .get(b"Rect") + .ok() + .and_then(|r| resolve(doc, r)) + .and_then(|r| r.as_array().ok()) + .into_iter() + .flatten() + .filter_map(|v| resolve(doc, v)) + .filter_map(|v| v.as_float().ok().map(f64::from)) + .collect(); + let [x0, y0, x1, y1] = numbers[..] else { + bail!("a signature field's widget has no rectangle"); + }; + Ok(Target { + field: field_id, + widget, + page, + rect: [x0.min(x1), y0.min(y1), x0.max(x1), y0.max(y1)], + }) } -/// sign_once signs `original` with `reserve` bytes of room for the DER -/// signature -fn sign_once(original: &[u8], req: &SignRequest, reserve: usize) -> Result { +/// the stamps of a plan, each copied into the update once +struct Forms { + pdfa1: bool, + imported: HashMap<(usize, usize), Imported>, +} + +impl Forms { + fn get(&mut self, incr: &mut IncrementalDocument, pdf: &[u8]) -> Result { + let key = (pdf.as_ptr() as usize, pdf.len()); + if let Some(form) = self.imported.get(&key) { + return Ok(*form); + } + let form = import_stamp(incr, pdf, self.pdfa1)?; + self.imported.insert(key, form); + Ok(form) + } +} + +/// build makes the update `plan` asks for, signed with the signature +/// dictionary `signature` if there is one +fn build( + original: &[u8], + plan: &Plan, + signature: Option, +) -> Result<(IncrementalDocument, Option)> { let doc = load_for_update(original)?; - // P=2 allows signing, though Acrobat may count the new field as a - // change it didn't allow - if docmdp_permission(&doc) == Some(1) { + let rules = Rules::read(&doc); + if !rules.allows_changes() { return Err(Refusal::Certified.into()); } - let names: Vec> = super::form_fields(&doc) - .iter() - .filter_map(|field| field.partial.map(<[u8]>::to_vec)) - .collect(); + let mut taken: Vec = form_fields(&doc).into_iter().map(|f| f.name).collect(); + let mut forms = Forms { + pdfa1: pdfa_part(&doc) == Some(1), + imported: HashMap::new(), + }; let mut incr = IncrementalDocument::create_from(original.to_vec(), doc); clean_trailer(&mut incr); + let pages = incr.get_prev_documents().get_pages(); + let page_of = |number: u32| page_id_in(&pages, number); + let signing = signature.is_some(); + + let target = match &plan.signature { + Spot::Existing { field, .. } => Some(find_target(&incr, &rules, field)?), + _ => None, + }; + // the signature's stamp: in the page content on a PDF nobody signed + // yet, else as the signature's appearance; without a signature always + // in the page content + let signature_stamp = match &plan.signature { + Spot::New { stamp, .. } | Spot::Existing { stamp, .. } => *stamp, + Spot::Invisible => None, + }; + let stamp_in_content = signature_stamp.is_some() + && match &plan.signature { + Spot::New { .. } => !signing || rules.allows_page_content(), + _ => !signing, + }; + + // the stamps in the pages' content + let mut draws: Vec<(ObjectId, Draw)> = vec![]; + for stamp in &plan.stamps { + let form = forms.get(&mut incr, stamp.pdf)?; + if stamp.repeat { + for page in pages.values() { + let size = PageGeometry::of(incr.get_prev_documents(), *page)?.visible_size(); + draws.push(( + *page, + Draw { + form, + rect: clamp(stamp.rect, size), + }, + )); + } + } else { + draws.push(( + page_of(stamp.page)?, + Draw { + form, + rect: stamp.rect, + }, + )); + } + } + if let (true, Some(pdf)) = (stamp_in_content, signature_stamp) { + let form = forms.get(&mut incr, pdf)?; + match (&plan.signature, &target) { + (Spot::New { page, rect, .. }, _) => { + draws.push((page_of(*page)?, Draw { form, rect: *rect })); + } + (Spot::Existing { .. }, Some(target)) => { + let geo = PageGeometry::of(incr.get_prev_documents(), target.page)?; + let rect = geo.user_to_display(target.rect); + draws.push((target.page, Draw { form, rect })); + } + _ => {} + } + } + if !draws.is_empty() && !rules.allows_page_content() { + return Err(Refusal::SignedPages.into()); + } + let mut by_page: Vec<(ObjectId, Vec)> = vec![]; + for (page, draw) in draws { + match by_page.iter_mut().find(|(p, _)| *p == page) { + Some((_, list)) => list.push(draw), + None => by_page.push((page, vec![draw])), + } + } + for (page, list) in &by_page { + draw_on_page(&mut incr, *page, list)?; + } - let page = page_id(incr.get_prev_documents(), req.page)?; - let geo = PageGeometry::of(incr.get_prev_documents(), page)?; - let date = pdf_date(); + // empty signature fields for the others who sign + let mut new_fields = vec![]; + for other in &plan.others { + let page = page_of(other.page)?; + let geo = PageGeometry::of(incr.get_prev_documents(), page)?; + let ap = appearance(&mut incr, &geo, other.rect[2], other.rect[3], None); + let name = free_field_name(&mut taken, &other_field_name(&other.name), false); + let widget = incr.new_document.add_object(dictionary! { + "Type" => "Annot", "Subtype" => "Widget", "FT" => "Sig", + "T" => text_string(&name), "TU" => text_string(&name), + "F" => 4, "Rect" => reals(&geo.rect_to_user(other.rect)), "P" => page, + "AP" => dictionary! { "N" => ap }, + }); + add_annot(&mut incr, page, widget)?; + new_fields.push(widget); + } + // the signature + let mut sig_id = None; + if let Some(sig) = signature { + let id = incr.new_document.add_object(sig); + sig_id = Some(id); + let shown = if stamp_in_content { + None + } else { + signature_stamp + .map(|pdf| forms.get(&mut incr, pdf)) + .transpose()? + }; + match (&plan.signature, target) { + (Spot::Existing { .. }, Some(target)) => { + let geo = PageGeometry::of(incr.get_prev_documents(), target.page)?; + let [x0, y0, x1, y1] = target.rect; + let (w, h) = if geo.rotate % 180 == 0 { + (x1 - x0, y1 - y0) + } else { + (y1 - y0, x1 - x0) + }; + let ap = appearance(&mut incr, &geo, w, h, shown); + incr.opt_clone_object_to_new_document(target.field)?; + incr.new_document + .get_dictionary_mut(target.field)? + .set("V", id); + incr.opt_clone_object_to_new_document(target.widget)?; + let widget = incr.new_document.get_dictionary_mut(target.widget)?; + widget.set("AP", dictionary! { "N" => ap }); + widget.set("F", 4); + widget.remove(b"AS"); + } + (spot, _) => { + let (page, rect) = match spot { + Spot::New { page, rect, .. } => (page_of(*page)?, Some(*rect)), + _ => (page_of(1)?, None), + }; + let geo = PageGeometry::of(incr.get_prev_documents(), page)?; + let (user, ap) = match rect { + Some(r) => ( + geo.rect_to_user(r), + appearance(&mut incr, &geo, r[2], r[3], shown), + ), + None => ([0.; 4], appearance(&mut incr, &geo, 0., 0., None)), + }; + let name = free_field_name(&mut taken, "Signature", true); + let widget = incr.new_document.add_object(dictionary! { + "Type" => "Annot", "Subtype" => "Widget", "FT" => "Sig", + "T" => text_string(&name), + "V" => id, "F" => 4, "Rect" => reals(&user), "P" => page, + "AP" => dictionary! { "N" => ap }, + }); + add_annot(&mut incr, page, widget)?; + new_fields.push(widget); + } + } + } + add_fields(&mut incr, &new_fields, signing)?; + Ok((incr, sig_id)) +} + +/// page_id_in is page `number` (from 1) of `pages` +fn page_id_in(pages: &std::collections::BTreeMap, number: u32) -> Result { + pages + .get(&number) + .copied() + .with_context(|| format!("no page {number}")) +} + +/// sign_once signs `original` with `reserve` bytes of room for the DER +/// signature +fn sign_once(original: &[u8], plan: &Plan, signer: &Signer, reserve: usize) -> Result { let mut sig = dictionary! { "Type" => "Sig", "Filter" => "Adobe.PPKLite", "SubFilter" => "ETSI.CAdES.detached", "ByteRange" => BR_PLACEHOLDER.iter().map(|n| Object::Integer(*n)).collect::>(), "Contents" => Object::String(vec![0; reserve], StringFormat::Hexadecimal), - "M" => Object::String(date.clone().into_bytes(), StringFormat::Literal), + "M" => Object::String(pdf_date().into_bytes(), StringFormat::Literal), }; - if let Some(n) = &req.name { + if let Some(n) = &signer.name { sig.set("Name", text_string(n)); } - if let Some(r) = &req.reason { + if let Some(r) = &signer.reason { sig.set("Reason", text_string(r)); } - if let Some(l) = &req.location { + if let Some(l) = &signer.location { sig.set("Location", text_string(l)); } - let sig_id = incr.new_document.add_object(sig); - let (rect, ap) = match req.rect { - Some(r) => (geo.rect_to_user(r), appearance(&mut incr, &geo, r[2], r[3])), - None => ([0.; 4], appearance(&mut incr, &geo, 0., 0.)), - }; - let mut n = 1; - let field_name = loop { - let name = format!("Signature{n}"); - if !names.iter().any(|x| x == name.as_bytes()) { - break name; - } - n += 1; - }; - let widget = dictionary! { - "Type" => "Annot", "Subtype" => "Widget", "FT" => "Sig", - "T" => Object::String(field_name.into_bytes(), StringFormat::Literal), - "V" => sig_id, "F" => 4, "Rect" => reals(&rect), "P" => page, - "AP" => dictionary! { "N" => ap }, - }; - let widget_id = incr.new_document.add_object(widget); - add_annot(&mut incr, page, widget_id)?; - add_field(&mut incr, widget_id)?; - - let mut out = Vec::new(); - incr.save_to(&mut out)?; - // lopdf ends with "%%EOF" and no newline; add one like most writers - out.push(b'\n'); + let (incr, _) = build(original, plan, Some(sig))?; + let mut out = save(incr, original)?; // patch /ByteRange and find /Contents in what we appended let appended = original.len(); @@ -277,14 +611,15 @@ fn sign_once(original: &[u8], req: &SignRequest, reserve: usize) -> Result Result [f64; 4] { + let [mut x, mut y, mut w, mut h] = rect; + let scale = (size.0 / w).min(size.1 / h).min(1.); + w *= scale; + h *= scale; + x = x.min(size.0 - w).max(0.); + y = y.min(size.1 - h).max(0.); + [x, y, w, h] +} + +/// matrix draws `form` into the display rectangle `rect` of a page of +/// `geo`, stretched to it +fn matrix(geo: &PageGeometry, form: &Imported, rect: [f64; 4]) -> [f64; 6] { + let [a, b, c, d, e, f] = geo.placement(geo.rect_to_user(rect)); + let (sx, sy) = (rect[2] / form.width, rect[3] / form.height); + // first scaled to the rectangle, then placed + [sx * a, sx * b, sy * c, sy * d, e, f] +} + +/// operator writes the numbers of a matrix and `cm` +fn cm(m: &[f64; 6]) -> String { + let n: Vec = m.iter().map(|v| format!("{v:.4}")).collect(); + format!("{} cm", n.join(" ")) +} + +/// free_name is the first of BlankStamp0, BlankStamp1, … that `xobjects` +/// doesn't hold +fn free_name(xobjects: &Dictionary) -> String { + (0..) + .map(|n| format!("BlankStamp{n}")) + .find(|name| !xobjects.has(name.as_bytes())) + .unwrap_or_default() +} + +/// draw_on_page draws `draws` on the page `page`, over its content. The +/// page gets a direct /Resources, a copy of what it has or inherits with +/// the stamps added: a resources object other pages share is never edited. +pub fn draw_on_page(incr: &mut IncrementalDocument, page: ObjectId, draws: &[Draw]) -> Result<()> { + if draws.is_empty() { + return Ok(()); + } + let prev = incr.get_prev_documents(); + let geo = PageGeometry::of(prev, page)?; + let page_dict = current(incr, page)? + .as_dict() + .context("a page isn't a dictionary")?; + let mut resources = match page_dict.get(b"Resources") { + Ok(r) => resolve(prev, r).and_then(|r| r.as_dict().ok()).cloned(), + Err(_) => inherited(prev, page, b"Resources") + .and_then(|r| r.as_dict().ok()) + .cloned(), + } + .unwrap_or_default(); + let mut xobjects = resources + .get(b"XObject") + .ok() + .and_then(|x| resolve(prev, x)) + .and_then(|x| x.as_dict().ok()) + .cloned() + .unwrap_or_default(); + let mut ops = String::from("\nQ\n"); + for draw in draws { + let name = free_name(&xobjects); + xobjects.set(name.as_bytes(), draw.form.id); + ops.push_str(&format!( + "q {} /{name} Do Q\n", + cm(&matrix(&geo, &draw.form, draw.rect)) + )); + } + resources.set("XObject", xobjects); + + // the content: [q, the old streams…, Q and the stamps] + let mut contents: Vec = match page_dict.get(b"Contents") { + Ok(Object::Array(a)) => a.clone(), + Ok(Object::Reference(r)) => match current(incr, *r)? { + // an indirect array of streams + Object::Array(a) => a.clone(), + _ => vec![Object::Reference(*r)], + }, + Ok(_) => bail!("a page's /Contents isn't a stream or an array"), + Err(_) => vec![], + }; + let open = incr + .new_document + .add_object(Stream::new(Dictionary::new(), b"q\n".to_vec())); + let close = incr + .new_document + .add_object(Stream::new(Dictionary::new(), ops.into_bytes())); + contents.insert(0, Object::Reference(open)); + contents.push(Object::Reference(close)); + + incr.opt_clone_object_to_new_document(page)?; + let p = incr.new_document.get_dictionary_mut(page)?; + p.set("Contents", Object::Array(contents)); + p.set("Resources", resources); + Ok(()) +} + +/// appearance is a widget's appearance: a form of `w` × `h` points +/// (upright, turned with the page by its /Matrix) that shows `stamp` +/// fitted into it and centred, or nothing: Blank draws no frame around a +/// signature +pub fn appearance( + incr: &mut IncrementalDocument, + geo: &PageGeometry, + w: f64, + h: f64, + stamp: Option, +) -> ObjectId { + let mut dict = dictionary! { + "Type" => "XObject", "Subtype" => "Form", "BBox" => reals(&[0., 0., w, h]), + "Matrix" => reals(&geo.appearance_matrix()), + }; + let mut content = vec![]; + if let Some(stamp) = stamp.filter(|_| w > 0. && h > 0.) { + let s = (w / stamp.width).min(h / stamp.height); + let (dx, dy) = ((w - stamp.width * s) / 2., (h - stamp.height * s) / 2.); + content = format!("q {} /BlankStamp Do Q\n", cm(&[s, 0., 0., s, dx, dy])).into_bytes(); + dict.set( + "Resources", + dictionary! { "XObject" => dictionary! { "BlankStamp" => stamp.id } }, + ); + } + incr.new_document.add_object(Stream::new(dict, content)) +} diff --git a/src-tauri/src/signing/pdf/xobject.rs b/src-tauri/src/signing/pdf/xobject.rs new file mode 100644 index 00000000..f1bbe572 --- /dev/null +++ b/src-tauri/src/signing/pdf/xobject.rs @@ -0,0 +1,340 @@ +//! a stamp's one-page PDF, which Blank's engine made with krilla, as a Form +//! XObject of the PDF being updated: page 1's content and every object its +//! resources reach, copied with new ids. Its marked content becomes +//! artifacts, since the stamp isn't part of the PDF's structure tree, and +//! for a PDF/A-1 target its transparency goes (PDF/A-1 has none). + +use std::collections::HashMap; + +use anyhow::{bail, Context, Result}; +use lopdf::content::{Content, Operation}; +use lopdf::Stream; +use lopdf::{dictionary, Dictionary, Document, IncrementalDocument, LoadOptions, Object, ObjectId}; + +use super::{inherited, page_id, reals, resolve}; + +/// the largest stamp PDF taken, what any stream of it may decompress to, +/// how many objects it may bring along and how deep they may nest: it's +/// Blank's own, but it came through the webview +const MAX_PDF: usize = 16 << 20; +const MAX_STREAM: usize = 64 << 20; +const MAX_OBJECTS: usize = 20_000; +const MAX_DEPTH: usize = 64; +/// the largest side of a stamp, in points: PDF's own limit +const MAX_SIDE: f64 = 14_400.; + +/// a stamp copied into the update +#[derive(Debug, Clone, Copy)] +pub struct Imported { + pub id: ObjectId, + /// the size of its BBox, in points + pub width: f64, + pub height: f64, +} + +/// import_stamp copies page 1 of the PDF `pdf` into `incr` as a Form +/// XObject with the BBox `[0 0 w h]`; `pdfa1` drops what PDF/A-1 forbids +/// (transparency groups, soft masks), since the PDF it goes into claims it +pub fn import_stamp(incr: &mut IncrementalDocument, pdf: &[u8], pdfa1: bool) -> Result { + if pdf.len() > MAX_PDF { + bail!("the stamp's PDF is too large"); + } + let options = LoadOptions { + max_decompressed_size: Some(MAX_STREAM), + ..Default::default() + }; + let doc = Document::load_mem_with_options(pdf, options).context("can't read a stamp's PDF")?; + if doc.trailer.get(b"Encrypt").is_ok() || doc.is_encrypted() { + bail!("a stamp's PDF is encrypted"); + } + let page = page_id(&doc, 1)?; + let media = inherited(&doc, page, b"CropBox") + .or_else(|| inherited(&doc, page, b"MediaBox")) + .and_then(|o| o.as_array().ok()) + .context("a stamp's page has no size")?; + let numbers: Vec = media + .iter() + .filter_map(|v| resolve(&doc, v)) + .filter_map(|v| v.as_float().ok().map(f64::from)) + .collect(); + let [x0, y0, x1, y1] = numbers[..] else { + bail!("a stamp's page box isn't four numbers"); + }; + let (x0, y0, x1, y1) = (x0.min(x1), y0.min(y1), x0.max(x1), y0.max(y1)); + let (width, height) = (x1 - x0, y1 - y0); + if !(width > 0. && height > 0. && width <= MAX_SIDE && height <= MAX_SIDE) { + bail!("a stamp's page has no room in it"); + } + + let resources = inherited(&doc, page, b"Resources") + .and_then(|r| r.as_dict().ok()) + .cloned() + .unwrap_or_default(); + let content = doc + .get_page_content_with_limit(page, MAX_STREAM) + .context("can't read a stamp's content")?; + let content = unmark(&doc, &content, &resources)?; + + let mut copier = Copier { + source: &doc, + pdfa1, + map: HashMap::new(), + queue: vec![], + }; + let resources = copier.copy_dict(incr, &resources, 0)?; + let mut form = dictionary! { + "Type" => "XObject", "Subtype" => "Form", + "BBox" => reals(&[0., 0., width, height]), + "Resources" => resources, + }; + if x0 != 0. || y0 != 0. { + form.set("Matrix", reals(&[1., 0., 0., 1., -x0, -y0])); + } + if !pdfa1 { + if let Some(group) = doc + .get_dictionary(page) + .ok() + .and_then(|p| p.get(b"Group").ok()) + .and_then(|g| resolve(&doc, g)) + .and_then(|g| g.as_dict().ok()) + .cloned() + { + let group = copier.copy_dict(incr, &group, 0)?; + form.set("Group", group); + } + } + copier.drain(incr)?; + let mut stream = Stream::new(form, content); + // a stream that doesn't get smaller stays as it is + let _ = stream.compress(); + let id = incr.new_document.add_object(stream); + Ok(Imported { id, width, height }) +} + +/// unmark turns every marked-content sequence with an /MCID (`/P <> BDC`, or a named property list that has one) into `/Artifact BMC`: +/// the stamp's structure elements aren't copied, so nothing may point +/// into them +fn unmark(doc: &Document, content: &[u8], resources: &Dictionary) -> Result> { + if !content.windows(4).any(|w| w == b"MCID") { + return Ok(content.to_vec()); + } + let properties = resources + .get(b"Properties") + .ok() + .and_then(|p| resolve(doc, p)) + .and_then(|p| p.as_dict().ok()); + let has_mcid = |operand: Option<&Object>| -> bool { + let dict = match operand { + Some(Object::Dictionary(d)) => Some(d), + Some(Object::Name(name)) => properties + .and_then(|p| p.get(name).ok()) + .and_then(|p| resolve(doc, p)) + .and_then(|p| p.as_dict().ok()), + Some(Object::Reference(_)) => operand + .and_then(|o| resolve(doc, o)) + .and_then(|o| o.as_dict().ok()), + _ => None, + }; + dict.is_some_and(|d| d.has(b"MCID")) + }; + let mut decoded = Content::decode(content).context("can't read a stamp's content")?; + for operation in &mut decoded.operations { + if operation.operator == "BDC" && has_mcid(operation.operands.get(1)) { + *operation = Operation::new("BMC", vec![Object::Name(b"Artifact".to_vec())]); + } + } + Ok(decoded.encode()?) +} + +/// copies objects of the stamp's PDF into the update, each once, with new +/// ids; references are followed through a queue, so cycles end +struct Copier<'a> { + source: &'a Document, + pdfa1: bool, + /// the stamp's ids to the update's + map: HashMap, + /// objects given an id in the update, to be copied + queue: Vec<(ObjectId, ObjectId)>, +} + +impl Copier<'_> { + /// id_for is the update's id of the stamp's object `old`, queued to be + /// copied the first time; what would make the stamp part of the stamp + /// PDF's own page tree becomes null + fn id_for(&mut self, incr: &mut IncrementalDocument, old: ObjectId) -> Result { + if let Some(new) = self.map.get(&old) { + return Ok(Object::Reference(*new)); + } + let Ok(object) = self.source.get_object(old) else { + return Ok(Object::Null); + }; + let kind = match object { + Object::Dictionary(d) => d.get(b"Type").and_then(Object::as_name).ok(), + _ => None, + }; + if matches!( + kind, + Some(b"Page" | b"Pages" | b"Catalog" | b"StructTreeRoot" | b"StructElem") + ) { + return Ok(Object::Null); + } + if self.map.len() >= MAX_OBJECTS { + bail!("a stamp's PDF has too many objects"); + } + let new = incr.new_document.new_object_id(); + self.map.insert(old, new); + self.queue.push((old, new)); + Ok(Object::Reference(new)) + } + + fn copy(&mut self, incr: &mut IncrementalDocument, o: &Object, depth: usize) -> Result { + if depth > MAX_DEPTH { + bail!("a stamp's PDF nests too deep"); + } + Ok(match o { + Object::Reference(id) => self.id_for(incr, *id)?, + Object::Array(items) => Object::Array( + items + .iter() + .map(|item| self.copy(incr, item, depth + 1)) + .collect::>()?, + ), + Object::Dictionary(d) => Object::Dictionary(self.copy_dict(incr, d, depth + 1)?), + Object::Stream(s) => Object::Stream(self.copy_stream(incr, s, depth + 1)?), + other => other.clone(), + }) + } + + fn copy_dict( + &mut self, + incr: &mut IncrementalDocument, + d: &Dictionary, + depth: usize, + ) -> Result { + let mut out = Dictionary::new(); + for (key, value) in d.iter() { + // the stamp's structure tree isn't copied + if key == b"StructParent" || key == b"StructParents" { + continue; + } + out.set(key.clone(), self.copy(incr, value, depth + 1)?); + } + if self.pdfa1 { + pdfa1_dict(&mut out); + } + Ok(out) + } + + fn copy_stream( + &mut self, + incr: &mut IncrementalDocument, + s: &Stream, + depth: usize, + ) -> Result { + let subtype = s.dict.get(b"Subtype").and_then(Object::as_name).ok(); + let mut stream = s.clone(); + if subtype == Some(b"Form") { + // a form inside the stamp: its marked content too + let resources = s + .dict + .get(b"Resources") + .ok() + .and_then(|r| resolve(self.source, r)) + .and_then(|r| r.as_dict().ok()) + .cloned() + .unwrap_or_default(); + let plain = s + .get_plain_content_with_limit(MAX_STREAM) + .context("can't read a form of a stamp")?; + let unmarked = unmark(self.source, &plain, &resources)?; + if unmarked != plain { + stream.set_plain_content(unmarked); + let _ = stream.compress(); + } + if self.pdfa1 { + stream.dict.remove(b"Group"); + } + } + if self.pdfa1 && subtype == Some(b"Image") && s.dict.has(b"SMask") { + flatten_over_white(self.source, &mut stream); + } + stream.dict = self.copy_dict(incr, &stream.dict, depth + 1)?; + Ok(stream) + } + + /// drain copies every queued object + fn drain(&mut self, incr: &mut IncrementalDocument) -> Result<()> { + while let Some((old, new)) = self.queue.pop() { + let object = self.source.get_object(old)?.clone(); + let copied = self.copy(incr, &object, 0)?; + incr.new_document.objects.insert(new, copied); + } + Ok(()) + } +} + +/// pdfa1_dict takes the transparency out of a graphics state for PDF/A-1: +/// no soft mask, full opacity, the normal blend mode +fn pdfa1_dict(d: &mut Dictionary) { + if d.get(b"Type").and_then(Object::as_name).ok() == Some(b"ExtGState") + || d.has(b"ca") + || d.has(b"CA") + { + if d.has(b"SMask") { + d.set("SMask", Object::Name(b"None".to_vec())); + } + for key in [&b"ca"[..], b"CA"] { + if d.has(key) { + d.set(key.to_vec(), Object::Real(1.0)); + } + } + if d.has(b"BM") { + d.set("BM", Object::Name(b"Normal".to_vec())); + } + } +} + +/// flatten_over_white draws an image with a soft mask over white and drops +/// the mask, for PDF/A-1, which has no transparency: 8-bit samples of one +/// or three components, as krilla writes them. An image Blank can't +/// decode keeps its colour and loses the mask. +fn flatten_over_white(source: &Document, image: &mut Stream) { + let flattened = (|| -> Option> { + let mask = resolve(source, image.dict.get(b"SMask").ok()?)? + .as_stream() + .ok()?; + let dim = |d: &Dictionary, key: &[u8]| d.get(key).ok()?.as_i64().ok(); + let (w, h) = (dim(&image.dict, b"Width")?, dim(&image.dict, b"Height")?); + if dim(&mask.dict, b"Width")? != w + || dim(&mask.dict, b"Height")? != h + || dim(&image.dict, b"BitsPerComponent")? != 8 + || dim(&mask.dict, b"BitsPerComponent")? != 8 + { + return None; + } + let pixels = usize::try_from(w) + .ok()? + .checked_mul(usize::try_from(h).ok()?)?; + let colour = image.get_plain_content_with_limit(MAX_STREAM).ok()?; + let alpha = mask.get_plain_content_with_limit(MAX_STREAM).ok()?; + if pixels == 0 || alpha.len() < pixels || colour.len() % pixels != 0 { + return None; + } + let components = colour.len() / pixels; + if components != 1 && components != 3 { + return None; + } + let mut out = colour; + for (i, sample) in out.iter_mut().enumerate() { + let a = u32::from(alpha[i / components]); + *sample = ((u32::from(*sample) * a + 255 * (255 - a) + 127) / 255) as u8; + } + Some(out) + })(); + image.dict.remove(b"SMask"); + if let Some(samples) = flattened { + image.set_plain_content(samples); + let _ = image.compress(); + } +} diff --git a/src/editor/commands/signing.test.ts b/src/editor/commands/signing.test.ts index cc920305..dc50b07d 100644 --- a/src/editor/commands/signing.test.ts +++ b/src/editor/commands/signing.test.ts @@ -226,13 +226,21 @@ describe("signing", () => { expect(await request.check("right")).toBeNull(); request.submit(request.targets[0].pos, "Hamburg", "right"); await flushPromises(); - expect(ipc.signPdf).toHaveBeenCalledWith(new Uint8Array([1]), "right", { - page: 1, - rect: [70, 600, 230, 70], - location: "Hamburg", - reason: null, - timestampServer: null, - }); + expect(ipc.signPdf).toHaveBeenCalledWith( + { + passphrase: "right", + source: { kind: "body" }, + plan: { + stamps: [], + signature: { kind: "new", page: 1, rect: [70, 600, 230, 70] }, + others: [], + location: "Hamburg", + reason: null, + timestampServer: null, + }, + }, + [new Uint8Array([1])], + ); // the signed PDF is what is written expect(writeFile).toHaveBeenCalledWith( "/docs/lease signed.pdf", @@ -364,8 +372,10 @@ describe("signing", () => { }); // the native side names the certificate's holder, never the block's // name - expect(ipc.signPdf.mock.calls[0][2]).toMatchObject({ location: "Paris" }); - expect(ipc.signPdf.mock.calls[0][2]).not.toHaveProperty("name"); + expect(ipc.signPdf.mock.calls[0][0].plan).toMatchObject({ + location: "Paris", + }); + expect(ipc.signPdf.mock.calls[0][0].plan).not.toHaveProperty("name"); expect(signingPlace.value).toBe("Paris"); signingCertificate.value = undefined; }); diff --git a/src/editor/commands/signing.ts b/src/editor/commands/signing.ts index 44f5880f..9c30650c 100644 --- a/src/editor/commands/signing.ts +++ b/src/editor/commands/signing.ts @@ -338,13 +338,26 @@ const signExport = async ( if (!box) throw new Error("the signature block isn't in the PDF"); const timestampServer = config.value.signing.timestampServer; try { - return await signPdf(result.contents, passphrase, { - page: box.page + 1, - rect: [box.x, box.y, box.width, box.height], - location: place || null, - reason: null, - timestampServer: timestampServer || null, - }); + return await signPdf( + { + passphrase, + source: { kind: "body" }, + plan: { + // the block is in the page content already, from the engine + stamps: [], + signature: { + kind: "new", + page: box.page + 1, + rect: [box.x, box.y, box.width, box.height], + }, + others: [], + location: place || null, + reason: null, + timestampServer: timestampServer || null, + }, + }, + [result.contents], + ); } catch (error) { throw new Error(signingFailure(error), { cause: error }); } diff --git a/src/signing/ipc.test.ts b/src/signing/ipc.test.ts new file mode 100644 index 00000000..ec137923 --- /dev/null +++ b/src/signing/ipc.test.ts @@ -0,0 +1,46 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const { invoke } = vi.hoisted(() => ({ invoke: vi.fn() })); +vi.mock("@tauri-apps/api/core", () => ({ invoke })); + +import { encodeParts, signPdf, type SignRequest } from "./ipc"; + +describe("signPdf", () => { + beforeEach(() => { + invoke.mockResolvedValue(new Uint8Array([9]).buffer); + }); + + it("packs the parts with their lengths, as the native side reads them", () => { + const body = encodeParts([new Uint8Array([1, 2, 3]), new Uint8Array()]); + const view = new DataView(body.buffer); + expect(view.getUint32(0, true)).toBe(2); + expect(view.getBigUint64(4, true)).toBe(3n); + expect([...body.slice(12, 15)]).toEqual([1, 2, 3]); + expect(view.getBigUint64(15, true)).toBe(0n); + expect(body.length).toBe(23); + }); + + it("sends the request in the header and the parts as the body", async () => { + const request: SignRequest = { + passphrase: null, + source: { kind: "body" }, + plan: { + stamps: [{ part: 1, page: 1, rect: [1, 2, 3, 4], repeat: true }], + signature: { kind: "invisible" }, + others: [{ page: 1, rect: [5, 6, 7, 8], name: "Bea Müller" }], + name: null, + location: null, + reason: null, + timestampServer: null, + }, + }; + const parts = [new Uint8Array([1]), new Uint8Array([2])]; + expect(await signPdf(request, parts)).toEqual(new Uint8Array([9])); + const [command, body, options] = invoke.mock.calls[0]; + expect(command).toBe("signing_sign_pdf"); + expect(body).toEqual(encodeParts(parts)); + expect( + JSON.parse(decodeURIComponent(options.headers["blank-signing"])), + ).toEqual(request); + }); +}); diff --git a/src/signing/ipc.ts b/src/signing/ipc.ts index 2c98de7d..960c6bc5 100644 --- a/src/signing/ipc.ts +++ b/src/signing/ipc.ts @@ -85,19 +85,85 @@ export const signingMessage = (error: unknown, fallback: string) => { } }; -// how to sign, besides the PDF and the passphrase -export interface SignOptions { - // the page of the signature's field, from 1 +// a place on a page: x, y, width and height in points from the top left +// of the page as shown (its crop box after /Rotate, UserUnit applied) +export type PageRect = [number, number, number, number]; + +// a stamp to put in the pages' content: the one-page PDF of the body's +// part `part`, on page `page` (from 1); `repeat` puts it on every page at +// the same place, moved into smaller pages +export interface StampPlan { + part: number; + page: number; + rect: PageRect; + repeat: boolean; +} + +// where the signature goes: nowhere on the pages, an empty signature field +// the PDF has (its full name), or a new field; `stamp` is the body's part +// with the stamp it shows +export type SignaturePlan = + | { kind: "invisible" } + | { kind: "existing"; field: string; stamp?: number | null } + | { kind: "new"; page: number; rect: PageRect; stamp?: number | null }; + +// an empty signature field for someone else to sign later, called +// "Signature of ‹name›" +export interface OtherSigner { page: number; - // where the signature shows, in points from the page's top left; none for - // an invisible signature - rect: [number, number, number, number] | null; + rect: PageRect; + name: string; +} + +// what to add to the PDF, and what the signature says +export interface SignPlan { + stamps: StampPlan[]; + signature: SignaturePlan; + others: OtherSigner[]; location: string | null; reason: string | null; // an RFC 3161 server, for a timestamp timestampServer: string | null; } +// a form field's new value: text (text, combo, list, the export value of a +// radio button) or whether a box is ticked +export interface Fill { + field: string; + value: string | boolean; +} + +// where the PDF comes from: the body's part 0 (an export), or a PDF tab's +// file, which must still be the one it opened, with the tab's fills +export type SignSource = + | { kind: "body" } + | { kind: "tab"; tab: string; path: string; sha256: string; fills: Fill[] }; + +export interface SignRequest { + // null: the stamps and fields alone, no signature + passphrase: string | null; + source: SignSource; + plan: SignPlan; +} + +/** + * encodeParts packs `parts` into one body: a little-endian u32 count, then + * each part as a little-endian u64 length and its bytes + */ +export const encodeParts = (parts: Uint8Array[]) => { + const size = parts.reduce((sum, part) => sum + 8 + part.length, 4); + const body = new Uint8Array(size); + const view = new DataView(body.buffer); + view.setUint32(0, parts.length, true); + let at = 4; + for (const part of parts) { + view.setBigUint64(at, BigInt(part.length), true); + body.set(part, at + 8); + at += 8 + part.length; + } + return body; +}; + export const certificate = () => invoke("signing_certificate"); @@ -130,19 +196,16 @@ export const checkPassphrase = (passphrase: string) => invoke("signing_check_passphrase", { passphrase }); /** - * signPdf signs `pdf` with the user's key and returns the signed PDF + * signPdf adds `request.plan` to a PDF and signs it with the user's key, or + * without a passphrase adds the stamps and fields alone, and returns the + * PDF. `parts` are the body: for `source.kind` "body" the PDF first, then + * the stamps' PDFs, which `plan` names by their index in `parts`. */ -export const signPdf = async ( - pdf: Uint8Array, - passphrase: string, - options: SignOptions, -) => +export const signPdf = async (request: SignRequest, parts: Uint8Array[]) => new Uint8Array( - await invoke("signing_sign_pdf", pdf, { + await invoke("signing_sign_pdf", encodeParts(parts), { headers: { - "blank-signing": encodeURIComponent( - JSON.stringify({ passphrase, options }), - ), + "blank-signing": encodeURIComponent(JSON.stringify(request)), }, }), ); From 610ef88ea4d5dea51e3e4bff2511874c53c35d2c Mon Sep 17 00:00:00 2001 From: Florian Purchess Date: Mon, 12 Oct 2026 09:55:53 +0200 Subject: [PATCH 05/50] feat: find signature places by the signer's role, with a stamp, and the date of signing, and rebuild the label fixtures from a script --- scripts/build-label-fixtures.ts | 416 ++++++++++++++++++ .../__fixtures__/labels/de-misses.json | 70 --- .../__fixtures__/labels/de-prose-traps.json | 160 +++++++ src/signing/__fixtures__/labels/de-roles.json | 112 +++++ .../__fixtures__/labels/en-prose-traps.json | 37 +- src/signing/__fixtures__/labels/en-roles.json | 57 +++ .../__fixtures__/labels/stamps-and-dates.json | 133 ++++++ src/signing/labels.test.ts | 40 ++ src/signing/labels.ts | 143 +++++- 9 files changed, 1080 insertions(+), 88 deletions(-) create mode 100644 scripts/build-label-fixtures.ts delete mode 100644 src/signing/__fixtures__/labels/de-misses.json create mode 100644 src/signing/__fixtures__/labels/de-prose-traps.json create mode 100644 src/signing/__fixtures__/labels/de-roles.json create mode 100644 src/signing/__fixtures__/labels/en-roles.json create mode 100644 src/signing/__fixtures__/labels/stamps-and-dates.json diff --git a/scripts/build-label-fixtures.ts b/scripts/build-label-fixtures.ts new file mode 100644 index 00000000..036c583e --- /dev/null +++ b/scripts/build-label-fixtures.ts @@ -0,0 +1,416 @@ +// Writes the pages the found places' test reads (src/signing/__fixtures__/ +// labels/), each with the places it has: +// +// - form-pdf.json: src-tauri/pdf/fixtures/form.pdf's words as +// `pdftotext -bbox` reads them, and its rule, which pdftotext doesn't give; +// - the others: hand-made forms and traps, their words laid out with +// Helvetica's widths, roughly, and boxes as pdftotext gives them (the top +// 0.718 em above the baseline, the bottom 0.207 em below). +// +// The places expected are where the plan puts them (§5.2 of the e-signing +// plan's PR 3): on a line a label stands left of, 1.6 label heights tall; +// 36 pt (a signature or initials) or 18 pt above a line a caption stands +// under; between a label and the line below it. +// +// Run with `bun scripts/build-label-fixtures.ts` (needs poppler-utils) and +// commit the result. +import { spawnSync } from "node:child_process"; +import { mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; + +type Rect = [number, number, number, number]; +interface Page { + words: { text: string; rect: Rect }[]; + rules: Rect[]; +} + +const root = join(import.meta.dir, ".."); +const out = join(root, "src", "signing", "__fixtures__", "labels"); +mkdirSync(out, { recursive: true }); + +const round = (n: number) => Math.round(n * 100) / 100; +const bottom = (rect: Rect) => rect[1] + rect[3]; +const em = (c: string) => + c === "_" + ? 0.556 + : " .,:".includes(c) + ? 0.278 + : "()".includes(c) + ? 0.333 + : 0.52; + +const write = ( + name: string, + about: string, + text: Page, + expected: { kind: string; rect: Rect }[], +) => + writeFileSync( + join(out, `${name}.json`), + JSON.stringify({ about, text, expected }, null, 2) + "\n", + ); + +// form.pdf, as pdftotext reads it +{ + const { stdout } = spawnSync("pdftotext", [ + "-bbox", + join(root, "src-tauri", "pdf", "fixtures", "form.pdf"), + "-", + ]); + const words = [ + ...stdout + .toString() + .matchAll( + /xMin="([\d.]+)" yMin="([\d.]+)" xMax="([\d.]+)" yMax="([\d.]+)">([^<]*) ({ + text, + rect: [+x0, +y0, round(+x1 - +x0), round(+y1 - +y0)] as Rect, + })); + const run = words.find((word) => word.text.startsWith("___"))!.rect; + const label = 1.6 * run[3]; + write( + "form-pdf", + "src-tauri/pdf/fixtures/form.pdf: its words as pdftotext -bbox reads them, and its rule (0.5 w 100 490 m 300 490 l S) drawn by hand", + // 842 − 490, half the stroke's width up + { words, rules: [[100, 351.75, 200, 0.5]] }, + [ + { kind: "signature", rect: [100, 315.75, 200, 36] }, + { + kind: "placeAndDate", + rect: [run[0], round(bottom(run) - label), run[2], round(label)], + }, + ], + ); +} + +let page: Page; +// writes `string` from x on `baseline`, returning its words' rects +const text = (x: number, baseline: number, string: string, size = 10) => + string.split(" ").flatMap((word) => { + const width = [...word].reduce((sum, c) => sum + em(c), 0) * size; + const rect: Rect = [ + round(x), + round(baseline - 0.718 * size), + round(width), + round(0.925 * size), + ]; + x += width + 0.278 * size; + if (!word) return []; + page.words.push({ text: word, rect }); + return [rect]; + }); +// draws a rule of half a point at y +const rule = (x: number, y: number, width: number): Rect => { + const rect: Rect = [x, y - 0.25, width, 0.5]; + page.rules.push(rect); + return rect; +}; +// a run of characters is a line at its word's bottom +const run = (word: Rect): Rect => [word[0], bottom(word) - 1, word[2], 1]; +// the part of a word from its character `from` of `chars` +const part = (word: Rect, chars: number, from: number): Rect => [ + word[0] + (word[2] * from) / chars, + word[1], + (word[2] * (chars - from)) / chars, + word[3], +]; +const onLine = (line: Rect, label: Rect): Rect => { + const height = 1.6 * label[3]; + return [ + round(line[0]), + round(bottom(line) - height), + round(line[2]), + round(height), + ]; +}; +const above = (line: Rect, height: number): Rect => [ + round(line[0]), + round(line[1] - height), + round(line[2]), + height, +]; +const between = (label: Rect, line: Rect): Rect => [ + round(line[0]), + round(bottom(label)), + round(line[2]), + round(line[1] - bottom(label)), +]; +// the baseline of 9 pt words just below a rule at y +const under = (y: number) => y + 10.4; +// writes the page `build` makes +const fixture = ( + name: string, + about: string, + build: () => { kind: string; rect: Rect }[], +) => { + page = { words: [], rules: [] }; + const expected = build(); + write(name, about, page, expected); +}; + +fixture( + "en-colon-underscores", + "English labels with a colon, left of underscores, in single-spaced lines; Name gets no place", + () => { + text(72, 100, "Application for a parking permit", 14); + text(72, 600, "Name: ____________________"); + const signature = text(72, 612, "Signature: ____________________"); + const date = text(72, 624, "Date: ____________"); + return [ + { kind: "signature", rect: onLine(run(signature[1]), signature[0]) }, + { kind: "date", rect: onLine(run(date[1]), date[0]) }, + ]; + }, +); + +fixture( + "de-captions-under-rules", + "German captions just below drawn rules: Ort, Datum and Unterschrift", + () => { + text( + 72, + 600, + "Berlin, den 1. Oktober 2026. Beide Seiten erhalten eine Ausfertigung.", + ); + const a = rule(72, 700, 180); + const b = rule(320, 700, 200); + text(72, 710.43, "Ort, Datum"); + text(320, 710.43, "Unterschrift"); + return [ + { kind: "placeAndDate", rect: above(a, 18) }, + { kind: "signature", rect: above(b, 36) }, + ]; + }, +); + +fixture( + "en-parens-under-underscores", + "English captions in brackets below runs of underscores", + () => { + const [a] = text(72, 650, "________________________________"); + const [b] = text(330, 650, "______________________"); + text(120, 662, "(signature)", 8); + text(380, 662, "(date)", 8); + return [ + { kind: "signature", rect: above(run(a), 36) }, + { kind: "date", rect: above(run(b), 18) }, + ]; + }, +); + +fixture( + "en-prose-traps", + "Labels in running text, a heading under a separator rule: no places", + () => { + text(72, 100, "Notes on the agreement", 14); + text( + 72, + 130, + "The parties agree that the start date of this contract is the date it is", + ); + text( + 72, + 142, + "signed by both of them. Signed copies are kept by both parties, and the", + ); + text( + 72, + 154, + "signature of a witness is not needed. The place of signing doesn't matter.", + ); + rule(72, 170, 450); + text(72, 182.25, "Place of performance", 12); + text( + 72, + 200, + "Date and place are given in the header. Sign here only if you agree.", + ); + text( + 72, + 230, + "Initials: see the appendix for the initials of every clerk.", + ); + rule(72, 260, 450); + text(72, 270.4, "Payment is due upon signature of the agreement.", 9); + return []; + }, +); + +fixture( + "de-prose-traps", + "German running text about signatures, under and over drawn rules: no places", + () => { + text(72, 100, "§ 12 Schlussbestimmungen", 14); + rule(72, 120, 450); + text(72, 130.4, "Die Unterschrift des Mieters ist erforderlich.", 9); + text( + 72, + 160, + "Mit der Unterschrift erkennt der Mieter die Hausordnung an. Ort und", + ); + text( + 72, + 172, + "Datum der Übergabe stehen im Protokoll. Unterschriften Dritter sind", + ); + text(72, 184, "nicht nötig; ein Stempel und eine Unterschrift genügen."); + rule(72, 230, 450); + return []; + }, +); + +fixture( + "en-table-trap", + "A table whose header says Date, between drawn borders: no places", + () => { + text(72, 270, "Payments received so far:"); + rule(72, 300, 400); + text(76, 312, "Date"); + text(276, 312, "Amount"); + rule(72, 318, 400); + text(76, 330, "2026-01-05"); + text(276, 330, "100 EUR"); + rule(72, 336, 400); + text(76, 348, "Datum"); + rule(72, 354, 200); + return []; + }, +); + +fixture( + "de-dots-one-line", + "German labels left of dot runs, both on one line", + () => { + const w = text( + 72, + 720, + "Ort, Datum ...................................... Unterschrift ..........................................", + ); + return [ + { kind: "placeAndDate", rect: onLine(run(w[2]), w[1]) }, + { kind: "signature", rect: onLine(run(w[4]), w[3]) }, + ]; + }, +); + +fixture( + "glued-labels", + "Labels written into the same word as their underscores", + () => { + const [s] = text(72, 500, "Signature:______________________________"); + const [d] = text(72, 540, "Datum:____________________"); + return [ + { kind: "signature", rect: onLine(run(part(s, 40, 10)), s) }, + { kind: "date", rect: onLine(run(part(d, 26, 6)), d) }, + ]; + }, +); + +fixture( + "initials", + "Initials at the foot of a page, in English, German and French-style German", + () => { + const a = text(420, 800, "Initials: __________"); + const b = text(72, 800, "Kürzel ________"); + const c = text(230, 800, "Paraphe: _________"); + return [a, b, c].map((w) => ({ + kind: "initials", + rect: onLine(run(w[1]), w[0]), + })); + }, +); + +fixture( + "labels-above-lines", + "Labels above drawn rules, and Sign here left of underscores", + () => { + const [s] = text(72, 500, "Signature"); + const a = rule(72, 540, 200); + const [d] = text(320, 500, "Datum"); + const b = rule(320, 525, 150); + const h = text(72, 600, "Sign here: ____________________"); + return [ + { kind: "signature", rect: between(s, a) }, + { kind: "date", rect: between(d, b) }, + { kind: "signature", rect: onLine(run(h[2]), h[1]) }, + ]; + }, +); + +fixture("whose-signature", "Signature labels that say whose they are", () => { + const a = rule(72, 650, 200); + const b = rule(320, 650, 200); + text(72, under(650), "Unterschrift des Vermieters", 9); + text(320, under(650), "Signature of the tenant", 9); + const c = text(72, 720, "Signed by: ______________________"); + return [ + { kind: "signature", rect: above(a, 36) }, + { kind: "signature", rect: above(b, 36) }, + { kind: "signature", rect: onLine(run(c[2]), c[0]) }, + ]; +}); + +fixture( + "de-roles", + "German signature captions with the signer's role before or after them", + () => { + const rules = [72, 260, 440].map((x) => rule(x, 650, 160)); + text(72, under(650), "Unterschrift Mieter", 9); + text(260, under(650), "Unterschrift Vermieter/in", 9); + text(440, under(650), "Mieter/in (Unterschrift)", 9); + const d = rule(72, 720, 220); + text(72, under(720), "Erziehungsberechtigte(r) (Unterschrift)", 9); + const e = text(72, 770, "Unterschrift Arbeitgeber: ____________________"); + const f = text(320, 770, "Arbeitnehmer (Unterschrift) _______________"); + const g = rule(72, 810, 200); + text(72, under(810), "Unterschriften Auftraggeber, Auftragnehmer", 9); + return [ + ...rules.map((line) => ({ kind: "signature", rect: above(line, 36) })), + { kind: "signature", rect: above(d, 36) }, + { kind: "signature", rect: onLine(run(e[2]), e[1]) }, + { kind: "signature", rect: onLine(run(f[2]), f[1]) }, + { kind: "signature", rect: above(g, 36) }, + ]; + }, +); + +fixture( + "en-roles", + "English signature captions that name the signer's role", + () => { + const a = rule(72, 650, 200); + const b = rule(320, 650, 200); + text(72, under(650), "Signature of Tenant", 9); + text(320, under(650), "Tenant's signature", 9); + const c = text(72, 720, "Landlord signature: ______________________"); + return [ + { kind: "signature", rect: above(a, 36) }, + { kind: "signature", rect: above(b, 36) }, + { kind: "signature", rect: onLine(run(c[2]), c[1]) }, + ]; + }, +); + +fixture( + "stamps-and-dates", + "Signature captions that ask for a stamp too, and the date of signing", + () => { + const stamps = [72, 210, 340, 470].map((x) => rule(x, 600, 110)); + text(72, under(600), "Signatur/Stempel", 9); + text(210, under(600), "Unterschrift/Stempel", 9); + text(340, under(600), "Unterschrift und Stempel", 9); + text(470, under(600), "Stempel und Unterschrift", 9); + const dates = [72, 260].map((x) => rule(x, 680, 160)); + text(72, under(680), "Datum der Unterschrift", 9); + text(260, under(680), "Date of signature", 9); + const c = text(72, 740, "Date signed: ______________"); + const d = text(320, 740, "Ort und Datum: ____________________"); + return [ + ...stamps.map((line) => ({ kind: "signature", rect: above(line, 36) })), + ...dates.map((line) => ({ kind: "date", rect: above(line, 18) })), + { kind: "date", rect: onLine(run(c[2]), c[1]) }, + { kind: "placeAndDate", rect: onLine(run(d[3]), d[2]) }, + ]; + }, +); diff --git a/src/signing/__fixtures__/labels/de-misses.json b/src/signing/__fixtures__/labels/de-misses.json deleted file mode 100644 index 750b2c39..00000000 --- a/src/signing/__fixtures__/labels/de-misses.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "about": "German captions the labels don't cover (expected, found or not), and Ort und Datum", - "text": { - "words": [ - { - "text": "Unterschrift", - "rect": [72, 653.94, 56.16, 8.33] - }, - { - "text": "Mieter", - "rect": [130.66, 653.94, 28.08, 8.33] - }, - { - "text": "Signatur/Stempel", - "rect": [260, 653.94, 74.88, 8.33] - }, - { - "text": "Datum", - "rect": [440, 653.94, 23.4, 8.33] - }, - { - "text": "der", - "rect": [465.9, 653.94, 14.04, 8.33] - }, - { - "text": "Unterschrift", - "rect": [482.44, 653.94, 56.16, 8.33] - }, - { - "text": "Ort", - "rect": [72, 692.82, 15.6, 9.25] - }, - { - "text": "und", - "rect": [90.38, 692.82, 15.6, 9.25] - }, - { - "text": "Datum:", - "rect": [108.76, 692.82, 28.78, 9.25] - }, - { - "text": "____________________", - "rect": [140.32, 692.82, 111.2, 9.25] - } - ], - "rules": [ - [72, 649.75, 160, 0.5], - [260, 649.75, 160, 0.5], - [440, 649.75, 120, 0.5] - ] - }, - "expected": [ - { - "kind": "signature", - "rect": [72, 613.75, 160, 36] - }, - { - "kind": "signature", - "rect": [260, 613.75, 160, 36] - }, - { - "kind": "date", - "rect": [440, 631.75, 120, 18] - }, - { - "kind": "placeAndDate", - "rect": [140.32, 687.27, 111.2, 14.8] - } - ] -} diff --git a/src/signing/__fixtures__/labels/de-prose-traps.json b/src/signing/__fixtures__/labels/de-prose-traps.json new file mode 100644 index 00000000..6acdcc3b --- /dev/null +++ b/src/signing/__fixtures__/labels/de-prose-traps.json @@ -0,0 +1,160 @@ +{ + "about": "German running text about signatures, under and over drawn rules: no places", + "text": { + "words": [ + { + "text": "§", + "rect": [72, 89.95, 7.28, 12.95] + }, + { + "text": "12", + "rect": [83.17, 89.95, 14.56, 12.95] + }, + { + "text": "Schlussbestimmungen", + "rect": [101.62, 89.95, 138.32, 12.95] + }, + { + "text": "Die", + "rect": [72, 123.94, 14.04, 8.33] + }, + { + "text": "Unterschrift", + "rect": [88.54, 123.94, 56.16, 8.33] + }, + { + "text": "des", + "rect": [147.2, 123.94, 14.04, 8.33] + }, + { + "text": "Mieters", + "rect": [163.75, 123.94, 32.76, 8.33] + }, + { + "text": "ist", + "rect": [199.01, 123.94, 14.04, 8.33] + }, + { + "text": "erforderlich.", + "rect": [215.55, 123.94, 58.66, 8.33] + }, + { + "text": "Mit", + "rect": [72, 152.82, 15.6, 9.25] + }, + { + "text": "der", + "rect": [90.38, 152.82, 15.6, 9.25] + }, + { + "text": "Unterschrift", + "rect": [108.76, 152.82, 62.4, 9.25] + }, + { + "text": "erkennt", + "rect": [173.94, 152.82, 36.4, 9.25] + }, + { + "text": "der", + "rect": [213.12, 152.82, 15.6, 9.25] + }, + { + "text": "Mieter", + "rect": [231.5, 152.82, 31.2, 9.25] + }, + { + "text": "die", + "rect": [265.48, 152.82, 15.6, 9.25] + }, + { + "text": "Hausordnung", + "rect": [283.86, 152.82, 57.2, 9.25] + }, + { + "text": "an.", + "rect": [343.84, 152.82, 13.18, 9.25] + }, + { + "text": "Ort", + "rect": [359.8, 152.82, 15.6, 9.25] + }, + { + "text": "und", + "rect": [378.18, 152.82, 15.6, 9.25] + }, + { + "text": "Datum", + "rect": [72, 164.82, 26, 9.25] + }, + { + "text": "der", + "rect": [100.78, 164.82, 15.6, 9.25] + }, + { + "text": "Übergabe", + "rect": [119.16, 164.82, 41.6, 9.25] + }, + { + "text": "stehen", + "rect": [163.54, 164.82, 31.2, 9.25] + }, + { + "text": "im", + "rect": [197.52, 164.82, 10.4, 9.25] + }, + { + "text": "Protokoll.", + "rect": [210.7, 164.82, 49.58, 9.25] + }, + { + "text": "Unterschriften", + "rect": [263.06, 164.82, 72.8, 9.25] + }, + { + "text": "Dritter", + "rect": [338.64, 164.82, 36.4, 9.25] + }, + { + "text": "sind", + "rect": [377.82, 164.82, 20.8, 9.25] + }, + { + "text": "nicht", + "rect": [72, 176.82, 26, 9.25] + }, + { + "text": "nötig;", + "rect": [100.78, 176.82, 31.2, 9.25] + }, + { + "text": "ein", + "rect": [134.76, 176.82, 15.6, 9.25] + }, + { + "text": "Stempel", + "rect": [153.14, 176.82, 36.4, 9.25] + }, + { + "text": "und", + "rect": [192.32, 176.82, 15.6, 9.25] + }, + { + "text": "eine", + "rect": [210.7, 176.82, 20.8, 9.25] + }, + { + "text": "Unterschrift", + "rect": [234.28, 176.82, 62.4, 9.25] + }, + { + "text": "genügen.", + "rect": [299.46, 176.82, 39.18, 9.25] + } + ], + "rules": [ + [72, 119.75, 450, 0.5], + [72, 229.75, 450, 0.5] + ] + }, + "expected": [] +} diff --git a/src/signing/__fixtures__/labels/de-roles.json b/src/signing/__fixtures__/labels/de-roles.json new file mode 100644 index 00000000..e3904944 --- /dev/null +++ b/src/signing/__fixtures__/labels/de-roles.json @@ -0,0 +1,112 @@ +{ + "about": "German signature captions with the signer's role before or after them", + "text": { + "words": [ + { + "text": "Unterschrift", + "rect": [72, 653.94, 56.16, 8.33] + }, + { + "text": "Mieter", + "rect": [130.66, 653.94, 28.08, 8.33] + }, + { + "text": "Unterschrift", + "rect": [260, 653.94, 56.16, 8.33] + }, + { + "text": "Vermieter/in", + "rect": [318.66, 653.94, 56.16, 8.33] + }, + { + "text": "Mieter/in", + "rect": [440, 653.94, 42.12, 8.33] + }, + { + "text": "(Unterschrift)", + "rect": [484.62, 653.94, 62.15, 8.33] + }, + { + "text": "Erziehungsberechtigte(r)", + "rect": [72, 723.94, 108.95, 8.33] + }, + { + "text": "(Unterschrift)", + "rect": [183.46, 723.94, 62.15, 8.33] + }, + { + "text": "Unterschrift", + "rect": [72, 762.82, 62.4, 9.25] + }, + { + "text": "Arbeitgeber:", + "rect": [137.18, 762.82, 59.98, 9.25] + }, + { + "text": "____________________", + "rect": [199.94, 762.82, 111.2, 9.25] + }, + { + "text": "Arbeitnehmer", + "rect": [320, 762.82, 62.4, 9.25] + }, + { + "text": "(Unterschrift)", + "rect": [385.18, 762.82, 69.06, 9.25] + }, + { + "text": "_______________", + "rect": [457.02, 762.82, 83.4, 9.25] + }, + { + "text": "Unterschriften", + "rect": [72, 813.94, 65.52, 8.33] + }, + { + "text": "Auftraggeber,", + "rect": [140.02, 813.94, 58.66, 8.33] + }, + { + "text": "Auftragnehmer", + "rect": [201.19, 813.94, 60.84, 8.33] + } + ], + "rules": [ + [72, 649.75, 160, 0.5], + [260, 649.75, 160, 0.5], + [440, 649.75, 160, 0.5], + [72, 719.75, 220, 0.5], + [72, 809.75, 200, 0.5] + ] + }, + "expected": [ + { + "kind": "signature", + "rect": [72, 613.75, 160, 36] + }, + { + "kind": "signature", + "rect": [260, 613.75, 160, 36] + }, + { + "kind": "signature", + "rect": [440, 613.75, 160, 36] + }, + { + "kind": "signature", + "rect": [72, 683.75, 220, 36] + }, + { + "kind": "signature", + "rect": [199.94, 757.27, 111.2, 14.8] + }, + { + "kind": "signature", + "rect": [457.02, 757.27, 83.4, 14.8] + }, + { + "kind": "signature", + "rect": [72, 773.75, 200, 36] + } + ] +} diff --git a/src/signing/__fixtures__/labels/en-prose-traps.json b/src/signing/__fixtures__/labels/en-prose-traps.json index 74944d8b..d8d85d01 100644 --- a/src/signing/__fixtures__/labels/en-prose-traps.json +++ b/src/signing/__fixtures__/labels/en-prose-traps.json @@ -293,9 +293,44 @@ { "text": "clerk.", "rect": [323.4, 222.82, 28.78, 9.25] + }, + { + "text": "Payment", + "rect": [72, 263.94, 32.76, 8.33] + }, + { + "text": "is", + "rect": [107.26, 263.94, 9.36, 8.33] + }, + { + "text": "due", + "rect": [119.12, 263.94, 14.04, 8.33] + }, + { + "text": "upon", + "rect": [135.67, 263.94, 18.72, 8.33] + }, + { + "text": "signature", + "rect": [156.89, 263.94, 42.12, 8.33] + }, + { + "text": "of", + "rect": [201.51, 263.94, 9.36, 8.33] + }, + { + "text": "the", + "rect": [213.37, 263.94, 14.04, 8.33] + }, + { + "text": "agreement.", + "rect": [229.91, 263.94, 44.62, 8.33] } ], - "rules": [[72, 169.75, 450, 0.5]] + "rules": [ + [72, 169.75, 450, 0.5], + [72, 259.75, 450, 0.5] + ] }, "expected": [] } diff --git a/src/signing/__fixtures__/labels/en-roles.json b/src/signing/__fixtures__/labels/en-roles.json new file mode 100644 index 00000000..357acbef --- /dev/null +++ b/src/signing/__fixtures__/labels/en-roles.json @@ -0,0 +1,57 @@ +{ + "about": "English signature captions that name the signer's role", + "text": { + "words": [ + { + "text": "Signature", + "rect": [72, 653.94, 42.12, 8.33] + }, + { + "text": "of", + "rect": [116.62, 653.94, 9.36, 8.33] + }, + { + "text": "Tenant", + "rect": [128.48, 653.94, 28.08, 8.33] + }, + { + "text": "Tenant's", + "rect": [320, 653.94, 37.44, 8.33] + }, + { + "text": "signature", + "rect": [359.94, 653.94, 42.12, 8.33] + }, + { + "text": "Landlord", + "rect": [72, 712.82, 41.6, 9.25] + }, + { + "text": "signature:", + "rect": [116.38, 712.82, 49.58, 9.25] + }, + { + "text": "______________________", + "rect": [168.74, 712.82, 122.32, 9.25] + } + ], + "rules": [ + [72, 649.75, 200, 0.5], + [320, 649.75, 200, 0.5] + ] + }, + "expected": [ + { + "kind": "signature", + "rect": [72, 613.75, 200, 36] + }, + { + "kind": "signature", + "rect": [320, 613.75, 200, 36] + }, + { + "kind": "signature", + "rect": [168.74, 707.27, 122.32, 14.8] + } + ] +} diff --git a/src/signing/__fixtures__/labels/stamps-and-dates.json b/src/signing/__fixtures__/labels/stamps-and-dates.json new file mode 100644 index 00000000..fb4aac46 --- /dev/null +++ b/src/signing/__fixtures__/labels/stamps-and-dates.json @@ -0,0 +1,133 @@ +{ + "about": "Signature captions that ask for a stamp too, and the date of signing", + "text": { + "words": [ + { + "text": "Signatur/Stempel", + "rect": [72, 603.94, 74.88, 8.33] + }, + { + "text": "Unterschrift/Stempel", + "rect": [210, 603.94, 93.6, 8.33] + }, + { + "text": "Unterschrift", + "rect": [340, 603.94, 56.16, 8.33] + }, + { + "text": "und", + "rect": [398.66, 603.94, 14.04, 8.33] + }, + { + "text": "Stempel", + "rect": [415.2, 603.94, 32.76, 8.33] + }, + { + "text": "Stempel", + "rect": [470, 603.94, 32.76, 8.33] + }, + { + "text": "und", + "rect": [505.26, 603.94, 14.04, 8.33] + }, + { + "text": "Unterschrift", + "rect": [521.8, 603.94, 56.16, 8.33] + }, + { + "text": "Datum", + "rect": [72, 683.94, 23.4, 8.33] + }, + { + "text": "der", + "rect": [97.9, 683.94, 14.04, 8.33] + }, + { + "text": "Unterschrift", + "rect": [114.44, 683.94, 56.16, 8.33] + }, + { + "text": "Date", + "rect": [260, 683.94, 18.72, 8.33] + }, + { + "text": "of", + "rect": [281.22, 683.94, 9.36, 8.33] + }, + { + "text": "signature", + "rect": [293.08, 683.94, 42.12, 8.33] + }, + { + "text": "Date", + "rect": [72, 732.82, 20.8, 9.25] + }, + { + "text": "signed:", + "rect": [95.58, 732.82, 33.98, 9.25] + }, + { + "text": "______________", + "rect": [132.34, 732.82, 77.84, 9.25] + }, + { + "text": "Ort", + "rect": [320, 732.82, 15.6, 9.25] + }, + { + "text": "und", + "rect": [338.38, 732.82, 15.6, 9.25] + }, + { + "text": "Datum:", + "rect": [356.76, 732.82, 28.78, 9.25] + }, + { + "text": "____________________", + "rect": [388.32, 732.82, 111.2, 9.25] + } + ], + "rules": [ + [72, 599.75, 110, 0.5], + [210, 599.75, 110, 0.5], + [340, 599.75, 110, 0.5], + [470, 599.75, 110, 0.5], + [72, 679.75, 160, 0.5], + [260, 679.75, 160, 0.5] + ] + }, + "expected": [ + { + "kind": "signature", + "rect": [72, 563.75, 110, 36] + }, + { + "kind": "signature", + "rect": [210, 563.75, 110, 36] + }, + { + "kind": "signature", + "rect": [340, 563.75, 110, 36] + }, + { + "kind": "signature", + "rect": [470, 563.75, 110, 36] + }, + { + "kind": "date", + "rect": [72, 661.75, 160, 18] + }, + { + "kind": "date", + "rect": [260, 661.75, 160, 18] + }, + { + "kind": "date", + "rect": [132.34, 727.27, 77.84, 14.8] + }, + { + "kind": "placeAndDate", + "rect": [388.32, 727.27, 111.2, 14.8] + } + ] +} diff --git a/src/signing/labels.test.ts b/src/signing/labels.test.ts index 1f742fa6..f05ddec9 100644 --- a/src/signing/labels.test.ts +++ b/src/signing/labels.test.ts @@ -85,6 +85,46 @@ describe("found places", () => { } }); + it("names a signature by the role its caption gives", () => { + const labels = (name: string) => + findLabels(fixtures.find((each) => each.name === name)!.text, 0).map( + (place) => place.label, + ); + expect(labels("de-roles.json")).toEqual([ + "Signature · Arbeitgeber", + "Signature · Arbeitnehmer", + "Signature · Mieter", + "Signature · Vermieter/in", + "Signature · Mieter/in", + "Signature · Erziehungsberechtigte(r)", + "Signature · Auftraggeber, Auftragnehmer", + ]); + expect(labels("en-roles.json")).toEqual([ + "Signature · Landlord", + "Signature of Tenant", + "Signature · Tenant", + ]); + }); + + it("takes no label, name or article for a role", () => { + const caption = (words: string) => { + const text: PageText = { words: [], rules: [[72, 100, 200, 0.5]] }; + let x = 72; + for (const word of words.split(" ")) { + text.words.push({ text: word, rect: [x, 104, word.length * 5, 9] }); + x += word.length * 5 + 3; + } + return findLabels(text, 0).map(({ kind, label }) => ({ kind, label })); + }; + expect(caption("Datum Unterschrift")).toEqual([]); + expect(caption("Name Unterschrift")).toEqual([]); + expect(caption("Your signature")).toEqual([]); + expect(caption("Unterschrift Kunde")).toEqual([ + { kind: "signature", label: "Signature · Kunde" }, + ]); + expect(caption("unterschrift kunde")).toEqual([]); + }); + it("gives an unpaired label nothing", () => { const text: PageText = { words: [{ text: "Signature:", rect: [72, 100, 50, 9.25] }], diff --git a/src/signing/labels.ts b/src/signing/labels.ts index b859dd8c..042fe720 100644 --- a/src/signing/labels.ts +++ b/src/signing/labels.ts @@ -33,6 +33,21 @@ const LABELS: Record = { "date, place": "placeAndDate", "ort, datum": "placeAndDate", "datum, ort": "placeAndDate", + unterschriften: "signature", + "signatur, stempel": "signature", + "stempel, signatur": "signature", + "unterschrift, stempel": "signature", + "stempel, unterschrift": "signature", + "unterschrift stempel": "signature", + "signature, stamp": "signature", + "stamp, signature": "signature", + "datum der unterschrift": "date", + unterschriftsdatum: "date", + "date of signature": "date", + "date of signing": "date", + "date signed": "date", + "signing date": "date", + "signature date": "date", initials: "initials", kürzel: "initials", paraphe: "initials", @@ -70,13 +85,87 @@ const normalize = (text: string) => .replace(/^[\s,.;]+/u, "") .replace(/\s+/gu, " "); +// the words for a signature that a role may stand next to, as in +// "Unterschrift Mieter", "Mieter/in (Unterschrift)" or "Tenant's signature" +const SIGNATURE_WORDS = new Set([ + "signature", + "unterschrift", + "unterschriften", + "signatur", +]); +// a role: a word with a capital, e.g. "Vermieter", "Mieter/in", +// "Erziehungsberechtigte(r)" or "Tenant's" +const ROLE = /^\p{Lu}[\p{L}'’/()-]*[,:]?$/u; +// capitalised words next to a signature's label that aren't whose it is +const NOT_ROLES = new Set([ + "the", + "a", + "an", + "your", + "my", + "our", + "his", + "her", + "their", + "this", + "please", + "die", + "der", + "das", + "ihre", + "ihr", + "eine", + "ein", + "seine", + "meine", + "unsere", + "bitte", + "name", + "vorname", + "nachname", + "stempel", + "stamp", + "seite", + "page", +]); + +/** + * roleOf returns the role one or two capitalised words name, e.g. "Tenant" + * for "Tenant's"; none for other words, or a label's + */ +const roleOf = (texts: string[]) => { + if (!texts.length || texts.length > 2) return undefined; + const roles = texts.every((text) => { + const word = normalize(text); + return ( + ROLE.test(text) && + !NOT_ROLES.has(word) && + !LABELS[word] && + !SIGNATURE_WORDS.has(word) + ); + }); + return roles + ? texts + .join(" ") + .replace(/[,:]$/u, "") + .replace(/['’]s$/u, "") + : undefined; +}; + +// what a label asks for, and whose signature it is if it names a role +interface Label { + kind: PlaceKind; + role?: string; +} + /** * labelOf returns what the words `texts` ask for, if they are a label as a - * whole, or a signature's label followed by whose it is in up to three words + * whole, a signature's label followed by whose it is in up to three words, + * or a signature's label with a role before or after it */ -const labelOf = (texts: string[]): PlaceKind | undefined => { +const labelOf = (texts: string[]): Label | undefined => { const whole = LABELS[normalize(texts.join(" "))]; - if (whole) return whole; + if (whole) return { kind: whole }; for (const length of [2, 1]) { if (texts.length <= length || texts.length > length + 3) continue; const kind = LABELS[normalize(texts.slice(0, length).join(" "))]; @@ -84,9 +173,13 @@ const labelOf = (texts: string[]): PlaceKind | undefined => { (kind === "signature" || kind === "initials") && WHOSE.has(normalize(texts[length])) ) - return kind; + return { kind }; } - return undefined; + const role = + (SIGNATURE_WORDS.has(normalize(texts[0])) && roleOf(texts.slice(1))) || + (SIGNATURE_WORDS.has(normalize(texts[texts.length - 1])) && + roleOf(texts.slice(0, -1))); + return role ? { kind: "signature", role } : undefined; }; // a word of the page, or the part of one that is text or a line @@ -203,7 +296,12 @@ export const findLabels = (text: PageText, page: number): FoundPlace[] => { overlap(word.rect, rect) <= (word.rect[2] * word.rect[3]) / 2, ); - const add = (kind: PlaceKind, rect: Rect, line: Rect, label: Token[]) => { + const add = ( + { kind, role }: Label, + rect: Rect, + line: Rect, + label: Token[], + ) => { if (!free(rect, label)) return false; usedLines.add(line); for (const word of label) usedWords.add(word); @@ -212,7 +310,12 @@ export const findLabels = (text: PageText, page: number): FoundPlace[] => { .join(" ") .replace(/^\(|\)$|:$/gu, "") .trim(); - found.push({ page, kind, rect, label: written }); + found.push({ + page, + kind, + rect, + label: role ? `Signature · ${role}` : written, + }); return true; }; @@ -260,10 +363,15 @@ export const findLabels = (text: PageText, page: number): FoundPlace[] => { } for (let length = phrase.length; length > 0; length--) { const label = phrase.slice(0, length).reverse(); - const kind = labelOf(label.map((word) => word.text)); - if (!kind) continue; + const asked = labelOf(label.map((word) => word.text)); + if (!asked) continue; const height = 1.6 * Math.max(...label.map((word) => word.rect[3])); - add(kind, [line[0], bottom(line) - height, line[2], height], line, label); + add( + asked, + [line[0], bottom(line) - height, line[2], height], + line, + label, + ); break; } } @@ -284,10 +392,11 @@ export const findLabels = (text: PageText, page: number): FoundPlace[] => { ), (a, b) => a.rect[1] - b.rect[1], ); - const kind = label && labelOf(label.map((word) => word.text)); - if (!label || !kind) continue; - const height = kind === "signature" || kind === "initials" ? 36 : 18; - add(kind, [line[0], line[1] - height, line[2], height], line, label); + const asked = label && labelOf(label.map((word) => word.text)); + if (!label || !asked) continue; + const image = asked.kind === "signature" || asked.kind === "initials"; + const height = image ? 36 : 18; + add(asked, [line[0], line[1] - height, line[2], height], line, label); } // a label above a line: the place is between them @@ -303,10 +412,10 @@ export const findLabels = (text: PageText, page: number): FoundPlace[] => { ), (a, b) => bottom(b.rect) - bottom(a.rect), ); - const kind = label && labelOf(label.map((word) => word.text)); - if (!label || !kind) continue; + const asked = label && labelOf(label.map((word) => word.text)); + if (!label || !asked) continue; const top = Math.max(...label.map((word) => bottom(word.rect))); - add(kind, [line[0], top, line[2], line[1] - top], line, label); + add(asked, [line[0], top, line[2], line[1] - top], line, label); } return mergeNear(found); From b35f5332ec95b640ed1213e5898d37f9e24ad102 Mon Sep 17 00:00:00 2001 From: Florian Purchess Date: Mon, 12 Oct 2026 09:56:24 +0200 Subject: [PATCH 06/50] feat: read a PDF page's words and lines, fill its form in and save the fills as an update, in the PDF helper --- scripts/build-pdf-fixtures.ts | 16 +- src-tauri/Cargo.lock | 1 + src-tauri/pdf/fixtures/form.pdf | Bin 2249 -> 2904 bytes src-tauri/pdf/fixtures/password.pdf | Bin 4109 -> 5046 bytes src-tauri/pdf/fixtures/repaired.pdf | Bin 2249 -> 2904 bytes src-tauri/pdf/helper/Cargo.toml | 3 + src-tauri/pdf/helper/shim/blank_mupdf.c | 510 +++++++++++++++++++ src-tauri/pdf/helper/shim/blank_mupdf.h | 32 ++ src-tauri/pdf/helper/src/mupdf.rs | 130 ++++- src-tauri/pdf/helper/tests/fills.rs | 351 +++++++++++++ src-tauri/pdf/helper/tests/mupdf.rs | 19 +- src-tauri/pdf/helper/tests/served.rs | 32 ++ src-tauri/pdf/ipc/src/bin/fake-pdf-helper.rs | 100 +++- src-tauri/pdf/ipc/src/client.rs | 81 ++- src-tauri/pdf/ipc/src/engine.rs | 18 +- src-tauri/pdf/ipc/src/protocol.rs | 122 ++++- src-tauri/pdf/ipc/src/serve.rs | 21 + src-tauri/pdf/ipc/tests/client.rs | 50 +- 18 files changed, 1453 insertions(+), 33 deletions(-) create mode 100644 src-tauri/pdf/helper/tests/fills.rs diff --git a/scripts/build-pdf-fixtures.ts b/scripts/build-pdf-fixtures.ts index c57117da..52113f9c 100644 --- a/scripts/build-pdf-fixtures.ts +++ b/scripts/build-pdf-fixtures.ts @@ -1,8 +1,9 @@ // Writes the small PDFs the PDF helper's tests open (src-tauri/pdf/fixtures/), // by hand, object by object, so each holds exactly what its test needs: // -// - form.pdf: a form of every kind of field, a nested one, a read-only one -// and an empty signature field, over a label and a line; +// - form.pdf: a form of every kind of field (text, checkbox, combo box, +// radio buttons, list box), a nested one, a read-only one and an empty +// signature field, over a label and a line; // - rotated.pdf: a page turned by /Rotate 90 with an offset crop box and a // box drawn under a field, an A5 page, resources inherited from the pages; // - signature.pdf: a signature field that holds a signature's dictionary @@ -43,11 +44,12 @@ const helvetica = "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>"; // form.pdf: 1 catalog, 2 pages, 3 page, 4 font, 5 content, 6 name, 7 agree, // 8 its on appearance, 9 its off appearance, 10 color, 11 signature, -// 12 id, 13 address, 14 street +// 12 id, 13 address, 14 street, 15 size (radio buttons), 16 and 17 its +// buttons S and L, 18 fruit (a list box) const form = pdf([ - "<< /Type /Catalog /Pages 2 0 R /AcroForm << /Fields [6 0 R 7 0 R 10 0 R 11 0 R 12 0 R 13 0 R] /DA (/Helv 10 Tf 0 g) /DR << /Font << /Helv 4 0 R >> >> >> >>", + "<< /Type /Catalog /Pages 2 0 R /AcroForm << /Fields [6 0 R 7 0 R 10 0 R 11 0 R 12 0 R 13 0 R 15 0 R 18 0 R] /DA (/Helv 10 Tf 0 g) /DR << /Font << /Helv 4 0 R >> >> >> >>", "<< /Type /Pages /Kids [3 0 R] /Count 1 >>", - "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R /Annots [6 0 R 7 0 R 10 0 R 11 0 R 12 0 R 14 0 R] >>", + "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R /Annots [6 0 R 7 0 R 10 0 R 11 0 R 12 0 R 14 0 R 16 0 R 17 0 R 18 0 R] >>", helvetica, stream( "BT /F1 12 Tf 100 760 Td (Contract) Tj ET\n" + @@ -64,6 +66,10 @@ const form = pdf([ "<< /FT /Tx /Ff 1 /T (id) /V (A-17) /Type /Annot /Subtype /Widget /Rect [350 700 500 720] /P 3 0 R /F 4 /DA (/Helv 10 Tf 0 g) >>", "<< /FT /Tx /T (address) /Kids [14 0 R] >>", "<< /T (street) /Parent 13 0 R /Type /Annot /Subtype /Widget /Rect [100 550 300 570] /P 3 0 R /F 4 /DA (/Helv 10 Tf 0 g) >>", + "<< /FT /Btn /Ff 49152 /T (size) /V /Off /Kids [16 0 R 17 0 R] >>", + "<< /Parent 15 0 R /Type /Annot /Subtype /Widget /Rect [100 420 115 435] /P 3 0 R /F 4 /AS /Off /AP << /N << /S 8 0 R /Off 9 0 R >> >> >>", + "<< /Parent 15 0 R /Type /Annot /Subtype /Widget /Rect [130 420 145 435] /P 3 0 R /F 4 /AS /Off /AP << /N << /L 8 0 R /Off 9 0 R >> >> >>", + "<< /FT /Ch /T (fruit) /Opt [(Apple) (Pear)] /Type /Annot /Subtype /Widget /Rect [350 600 500 640] /P 3 0 R /F 4 /DA (/Helv 10 Tf 0 g) >>", ]); writeFileSync(join(out, "form.pdf"), form); diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index b6345108..3d6b68a9 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -568,6 +568,7 @@ dependencies = [ "mupdf-sys", "serde", "serde_json", + "tempfile", ] [[package]] diff --git a/src-tauri/pdf/fixtures/form.pdf b/src-tauri/pdf/fixtures/form.pdf index e6a940f3ded7a952dc381314b9e95144685897ea..86a413d515a066882e2c560b1b9ba8159e46e34e 100644 GIT binary patch delta 764 zcmb7C%}T>S7^MDHaw~!dFMbG>w3pp)yW3=;6e`$@VoO`RlpZQgAX=J|Mi4K31tCu& zzJ$JkSFb*WU)G3i1A+?)o5^HmzRkRq-VdJ^7N7zSJwW$4v_zbCRe)Us>-avfi(s;V;hNeAnoY}_O;R26;zzJM zj=Wi%B}s7a)IrWc3JyrdvZhABx==HTSQ{j;RzI_8bs>#(W$yjtbNl27)98Ol*D`7O zAL+|fdJEm;W9Q(QruC!yAf|CU^B9J9Yd)XS@OIZ5N0kvxP_^nbfugG}lWI{HbTTRO zr67Q(3ua)M4d+5p-VIDDnZuOGV~%ulJk4`F<{ ZD^#~dJdWar$nz~B)xK+$%jcc8^##+ysP_N> delta 341 zcmca1c2aOc@y13K#?8AJkFau96s4wd87LThd zd%LFYsq^!lbH2JK)|Q)h!3R|1#HD{d`JV=4B{u@jPq zlG3%OlI+T)JlPY=wzpZ{+H+;~Y?1)AplaDjN;0yRa-Ce<%s6^+12;U#@-z0}bnIlD zWcgYKeMjf((U<68AS6@x$I_LO?13?z?`wyxSfY&CQ3oF};?WYv6a^-oqlJ<>XyPDB z2N=_hduNLwhFDHw3?30=G}n{_!YqU6eL=ZRVk&nobUNK~ED#AHwF?*4s#t%x8dr-V zgFcG#`%^^wSQ_ALp_S!nMLzV`Vu{F@>Ln=%;z49mimUY!iL4-mun5FiAHfvF5&>F7 zX$xlKgn9YdzIG#|q!g&lyxvGzI)`pZ&H|z+ZoHG=F*DsMkJMtGN5y!w9*Y8Ixiv-< z1y@v|NwY=R!T~j2MDBO1sow+s_EfV6?9m|rGKQ`N3jz25Lmg)YH&irEZJO*Jl= zehoEFM_mMw$`cJCQjWg@7+x3~9Fg8nn2p&xGtLEfR0}VdvPL+~g{6dRqP?&JK$+AH z>t2(ZUG2QOvftCLx+GSm)TS_;S5T^jk0C-Nm?Yo}X_RAB2gIu5k_W-Y_e`yIe9x*n zqL;Y=fiE(#L%{j-2?1gi4F=QGRR2IMEF{z02z#Si5bq^)Vl_M3~ zD$iw%CXu^kFWU2sx4z?~Zf>}pPk!<1-+ktnzx&hUD^K73;z@PKA8y+J(e1ywWfSS< zVC8^#Ry5|Cb>JFpZ*P~ehu3Xe#^va{A-;xUkG%Br-~M?2V{d%h^S5`u^ytIKpSbx_ zk;+WY0-pS$zdkJ87FD*FGTETe9|)(`Cf0%N>3wYbD(mHncv!DIRKbHS| z;biBXzjzn@O#4kgIQ7D}*5^O+{U_IMz2VCjzkS=wZ~yPh7vD_oyy>yqUV7>gs}A1q zvrixYYWdfTJ2z}UOpZNs%j$2A?`_`rm8buB&rk2z{@f=Xd*5F+-~HgNOHbVQ;P*Dn z7f#*yo`3)C=4U^3>HeKpU-`y+kDdN~`-7ifKAH*R&2*_*6`ocZ26qd4<++vpRHE|b zu3z23^IE>tJc9@DmDX8yxN2a2j=a$-Z70}X7%rhgVkgVyq7q6IS|I;*hB!|>MFs=N z%0m$;Ewtj&GDDSC&Oo5ZYam1qG7yN0V^3zp2NrE$V5G!gN@Vq^K^s;eRYx6G@l-7o zW&^ST6GY%hf&*i&uug&!5rjuNZvdGgP$5DTf#Ciq!)c)m(tel3tYvp$c-La@JeFgF zM*vL-0C=PDFoJIxz&NDSG013(bOc<_(85-EzM014K)m!uKk%FxsS(CQ6xdLqPcFRC zc*rtNDFv2tK!KuQDAXHTc&-fviF*WI6igQ)YRY6Nkc}Bef(4W(3H|{^(vn_c+>5pY zu})z_cANqf=x1rAi^fZ8D92N;P#igx0h5H$E?QV7iMA{)-7u9!YqXE?`Fs!q=7qjQR2E1g(p7!tR`bHF8{bz&|tj>$PA zjyy~8BsRH@(xW=l#AGJqQ7Q3_%i(>gmdGq7=8VB}{NXtU^2ol7C(1IhPDrdU6LX3l z@hLNW?Afws+uPVD6J1#E;=6~zo1s?w;}PSP literal 4109 zcmcInTZm*w8O~GGe()u(_zVA=Nm`hJ%HoMO3%x1lW1nTnFY=_zIwdd?) zBDxA*P!SgsL|HGy8~73*lq`9P1~(w_!H2{M5n-bu3W67W5XGo}o$j9QonDz_cWt`s zbXQmXRsW^F@2kUW?amQ)muVgT(ci!M?-nDJ^uvR#xj9lT#zDW+ADkiOM|+XXQDBjK zVWn15b3+Z-=)AW{OT`SPwhUD!(7g2;`fmF-B_FMf;9MOVaDlsYB9LJ`ZZk(u! z2T*sTC)LKeP&adn1x7}Onqd&I!X$vy;iwOkLRLUUh1? zyGf;#q940?vnyr!G=>3#B^Wgv7d~6c48Ve;$~a|BVo)-6pb00G zfJifwno=PGqe>(ysK{A`WYQ}!p?wxPFlmJs%tof2ND8P9)I(+%Sgh-7XBF$7V`lXY z)V>eSx#YE1+#9B}5(gpf>XqCnudCicpBqr3S(99E;luF&c0JnY6Q zA7$1_$N-5N77*-Nq!fb7KJIsTvekmq3v{($TygydFwO{}WipJW zkg(dBwwiMngy9K%u=zUjj98Kp<$^Z~vB5`^Xii*6uR&xNS>eAi*A#ji2XBn*_jDUx zWJ6-abiM+p&LLWh$I~GSQRbqR)#*HF zH;4%JMWgJgiVnM4Q%v?e_w;FLM9|3b_pOEp3z=3(vYYj3A`m@0 z-K`o1_}ua}v?2Brly$c20r)em*WZ4p?VJo}&wuXXcP~Bj+)KCqr8@S;6Gu9Cbn2sz z{bv4gR2QR>Bc0K()(vk=+E=@=yZ`w%_Ki2(b?fD4-tkELp$kv2i*J#C`1WVN^YxFM zIJWbPoo8n^`v1~06YyRerhBONswo*$5Ut1Dym>A-;3v%a8u>=dU@ce|^j1 zC*JzUNB;V+^S^!9^A|7u^a~4v&p(~36aQHL+oPXecd7CgqaXg_S04E2 zn{K;eU$}73pFj4*ukt6qf6GHZJ96l2-}=n=9{Bj>$G_Ts-Ak*#d-=9YFJ5?i=a=Ls zAK;tqeE!Y<`MojZNB0vmH^^oF!_PvID2$SamQ1n$ujGV=h>rzB5DdXcL$JLsEu_C#q8)sv{FKmO!oQ^K%4(a zZ?BI#JxglPdgOt`^GtrNON@`!xKBxr#b4QedC;*nTywpnRPj1vwhql@LX*V zs|YU``}&HW+}~&Q%v?b&BFA`cJ@DNvq>aK{>wUc7j4m!4-46lAmsEGR$$fKzNrshx zG7}5mIKj*H8;e|E{c}{_^9PSWDqHEr!NaKWOJ16*HjS7^MDHaw~!dFMbG>w3pp)v)d$43Ki@{v7s$qN)M4H5S6y15yVShLCBMc zFQISX)vHh8mo-A#fZ#&HW-^(XFFWtGkHhDs0jPk(0ML1vo0S@1>*vwV(bu%{wtJ^Z z^P5|v-3Hbh0Xs`(vbn`S!t#jl{} z$H6SglcczJY9r?$1qUQ!X{iY?4=kOC^*{pa4s)BX2U(;meIKS@+b2huMgK$E%BAIh zq%Sk+Ep*b)or7bVHjE#lgvRYJ5*VA^#p0fZH@(1*8xxwKX|!kpMORCv&8k~*(`l8j z1V`Z6f*BZk!?{pS36qKrWrL?g8MCEZ;<>!ULvD+Vw<;PVI&{?QCMX>G%Nv+%$r7gQ zQfRb=Hd*tq9bWjB{Ars!|wX@j~b%_M`hC)}8?=S WmWdxHk8uzhLaKeosMpW?J>whFLa5{b delta 341 zcmca1c2aOc@y13K#?8AJkFau96s4wd87LT(8$CBL(Ig;5<|??#27=&4AXOl z<|d{HbtUFSiJ3X6MO-#E3i`pBRjI&WQP2;{&o5B`x #include #include @@ -23,6 +24,9 @@ /* the most fields and kids a walk of the form looks at */ #define MAX_VISITS 100000 #define MAX_OPTIONS 1000 +/* what a drawn line is to be a rule: wide, thin (protocol.rs, RULE_*) */ +#define RULE_MIN_WIDTH 40 +#define RULE_MAX_HEIGHT 2 typedef struct { @@ -47,6 +51,10 @@ struct blank_engine struct blank_doc { blank_engine *engine; + /* the bytes it was opened from, and its password, to open it afresh */ + const unsigned char *data; + size_t len; + char *password; pdf_document *pdf; int pages; int repaired; @@ -270,6 +278,16 @@ int blank_open(blank_engine *engine, const unsigned char *data, size_t len, cons return status; } doc->engine = engine; + doc->data = data; + doc->len = len; + if (password && (doc->password = malloc(strlen(password) + 1)) != NULL) + memcpy(doc->password, password, strlen(password) + 1); + else if (password) + { + pdf_drop_document(ctx, pdf); + free(doc); + return BLANK_TOO_COMPLEX; + } doc->pdf = pdf; doc->pages = pages; doc->page_index = -1; @@ -288,6 +306,7 @@ void blank_doc_drop(blank_doc *doc) return; fz_drop_page(doc->engine->ctx, doc->page); pdf_drop_document(doc->engine->ctx, doc->pdf); + free(doc->password); free(doc); } @@ -776,3 +795,494 @@ void blank_free(char *json) { free(json); } + +/* a page's text and rules, as JSON */ + +/* a character that separates words */ +static int is_space(int c) +{ + return c <= 32 || c == 0xa0 || (c >= 0x2000 && c <= 0x200b) || c == 0x202f || c == 0x205f || c == 0x3000 || c == 0xfeff; +} + +static int finite_rect(fz_rect rect) +{ + return isfinite(rect.x0) && isfinite(rect.y0) && isfinite(rect.x1) && isfinite(rect.y1); +} + +/* writes the word gathered in `word` with its box, if there is one, and empties it */ +static void flush_word(fz_context *ctx, fz_buffer *out, fz_buffer *word, fz_rect *box, int *count) +{ + unsigned char *data; + size_t size = fz_buffer_storage(ctx, word, &data); + if (size && finite_rect(*box) && !fz_is_empty_rect(*box)) + { + fz_append_string(ctx, out, (*count)++ ? ",{\"text\":" : "{\"text\":"); + fz_terminate_buffer(ctx, word); + json_string(ctx, out, (const char *)data); + fz_append_string(ctx, out, ",\"rect\":"); + json_rect(ctx, out, *box); + fz_append_byte(ctx, out, '}'); + } + fz_clear_buffer(ctx, word); + *box = fz_empty_rect; +} + +/* a character's box: MuPDF's quad spans the font's ascender and descender, + * which many fonts give with their line gap; this one spans at most 0.8 em + * above the baseline and 0.25 below it, close to the type itself (and to + * what poppler reads from base 14 metrics) */ +static fz_rect char_box(fz_context *ctx, fz_stext_char *ch) +{ + float ascender = fz_font_ascender(ctx, ch->font); + float descender = fz_font_descender(ctx, ch->font); + float span = ascender - descender; + fz_point up, along, low; + fz_quad quad; + if (!(span > 0.01f)) + return fz_rect_from_quad(ch->quad); + /* a vector of 1 em from the baseline up, and the advance along it */ + up = fz_make_point((ch->quad.ul.x - ch->quad.ll.x) / span, (ch->quad.ul.y - ch->quad.ll.y) / span); + along = fz_make_point(ch->quad.lr.x - ch->quad.ll.x, ch->quad.lr.y - ch->quad.ll.y); + ascender = fz_min(ascender, 0.8f); + descender = fz_max(descender, -0.25f); + low = fz_make_point(ch->origin.x + up.x * descender, ch->origin.y + up.y * descender); + quad.ll = low; + quad.lr = fz_make_point(low.x + along.x, low.y + along.y); + quad.ul = fz_make_point(low.x + up.x * (ascender - descender), low.y + up.y * (ascender - descender)); + quad.ur = fz_make_point(quad.ul.x + along.x, quad.ul.y + along.y); + return fz_rect_from_quad(quad); +} + +/* whether a vector is nearly white: not a line anyone sees */ +static int is_white(uint32_t argb) +{ + return ((argb >> 16) & 0xff) > 0xf0 && ((argb >> 8) & 0xff) > 0xf0 && (argb & 0xff) > 0xf0; +} + +int blank_page_text_json(blank_doc *doc, int index, char **json, size_t *len) +{ + blank_engine *engine = doc->engine; + fz_context *ctx = engine->ctx; + fz_stext_page *text = NULL; + fz_device *device = NULL; + fz_buffer *out = NULL, *rules = NULL, *word = NULL; + int status = BLANK_OK; + *json = NULL; + *len = 0; + engine->refused = 0; + fz_var(text); + fz_var(device); + fz_var(out); + fz_var(rules); + fz_var(word); + fz_var(status); + fz_try(ctx) + { + fz_page *page = load_page(doc, index); + fz_stext_options options; + fz_stext_block *block; + unsigned char *data; + size_t size; + int words = 0, lines = 0; + memset(&options, 0, sizeof options); + options.flags = FZ_STEXT_COLLECT_VECTORS; + text = fz_new_stext_page(ctx, fz_bound_page(ctx, page)); + device = fz_new_stext_device(ctx, text, &options); + /* the page and its annotations, without the form's widgets: their + * values aren't the page's text */ + fz_run_page_contents(ctx, page, device, fz_identity, NULL); + fz_run_page_annots(ctx, page, device, fz_identity, NULL); + fz_close_device(ctx, device); + + out = fz_new_buffer(ctx, 4096); + rules = fz_new_buffer(ctx, 256); + word = fz_new_buffer(ctx, 64); + fz_append_string(ctx, out, "{\"words\":["); + for (block = text->first_block; block; block = block->next) + { + if (block->type == FZ_STEXT_BLOCK_TEXT) + { + fz_stext_line *line; + for (line = block->u.t.first_line; line; line = line->next) + { + fz_stext_char *ch; + fz_rect box = fz_empty_rect; + for (ch = line->first_char; ch; ch = ch->next) + { + char utf8[FZ_UTFMAX]; + if (is_space(ch->c)) + { + flush_word(ctx, out, word, &box, &words); + continue; + } + fz_append_data(ctx, word, utf8, fz_runetochar(utf8, ch->c)); + box = fz_union_rect(box, char_box(ctx, ch)); + } + flush_word(ctx, out, word, &box, &words); + } + } + else if (block->type == FZ_STEXT_BLOCK_VECTOR) + { + fz_rect box = block->bbox; + if (finite_rect(box) && box.x1 - box.x0 >= RULE_MIN_WIDTH && box.y1 - box.y0 <= RULE_MAX_HEIGHT && !is_white(block->u.v.argb)) + { + if (lines++) + fz_append_byte(ctx, rules, ','); + json_rect(ctx, rules, box); + } + } + } + fz_append_string(ctx, out, "],\"rules\":["); + size = fz_buffer_storage(ctx, rules, &data); + fz_append_data(ctx, out, data, size); + fz_append_string(ctx, out, "]}"); + size = fz_buffer_storage(ctx, out, &data); + *json = malloc(size); + if (!*json) + fz_throw(ctx, FZ_ERROR_SYSTEM, "out of memory"); + memcpy(*json, data, size); + *len = size; + } + fz_always(ctx) + { + fz_drop_device(ctx, device); + fz_drop_stext_page(ctx, text); + fz_drop_buffer(ctx, out); + fz_drop_buffer(ctx, rules); + fz_drop_buffer(ctx, word); + } + fz_catch(ctx) + status = caught(engine, BLANK_ENGINE); + return status; +} + +/* filling the form in */ + +/* the base 14 font MuPDF draws a field's text in: the one its /DA names, + * if it is one of the five it knows, else Helvetica (pdf-appearance.c) */ +static const char *appearance_font(fz_context *ctx, pdf_document *pdf, pdf_obj *field) +{ + const char *da = pdf_to_str_buf(ctx, pdf_dict_get_inheritable(ctx, field, PDF_NAME(DA))); + const char *font; + float size, color[4]; + int n; + if (!da || !*da) + da = pdf_to_str_buf(ctx, pdf_dict_getp(ctx, pdf_trailer(ctx, pdf), "Root/AcroForm/DA")); + pdf_parse_default_appearance(ctx, da ? da : "", &font, &size, &n, color); + if (!strcmp(font, "Cour")) return "Courier"; + if (!strcmp(font, "TiRo")) return "Times-Roman"; + if (!strcmp(font, "Symb")) return "Symbol"; + if (!strcmp(font, "ZaDb")) return "ZapfDingbats"; + return "Helvetica"; +} + +/* whether MuPDF can show `text` in the field: it writes a field's value in + * its base 14 font with the Windows-1252 encoding, and puts anything else + * in fonts of its own (Greek, Cyrillic, CJK fonts it doesn't embed) or + * replaces it, so only what that encoding and font have is shown as typed */ +static int shows(fz_context *ctx, pdf_document *pdf, pdf_obj *field, const char *text) +{ + fz_font *font = fz_new_base14_font(ctx, appearance_font(ctx, pdf, field)); + int ok = 1; + fz_try(ctx) + { + while (*text && ok) + { + int c; + text += fz_chartorune(&c, text); + if (c == '\n' || c == '\r') + continue; + if (c == FZ_REPLACEMENT_CHARACTER || fz_windows_1252_from_unicode(c) <= 0 || fz_encode_character(ctx, font, c) <= 0) + ok = 0; + } + } + fz_always(ctx) + fz_drop_font(ctx, font); + fz_catch(ctx) + fz_rethrow(ctx); + return ok; +} + +/* the widget `i` of a terminal field: its kids without /T, or itself; NULL past the last */ +static pdf_obj *widget_of(fz_context *ctx, pdf_obj *field, int i) +{ + pdf_obj *kids = pdf_dict_get(ctx, field, PDF_NAME(Kids)); + pdf_obj *widget; + if (!pdf_is_array(ctx, kids)) + return i == 0 ? field : NULL; + while (i < pdf_array_len(ctx, kids)) + { + widget = pdf_array_get(ctx, kids, i); + if (!pdf_dict_get(ctx, widget, PDF_NAME(T))) + return widget; + i++; + } + return NULL; +} + +/* the number of runes in `text` */ +static int runes(const char *text) +{ + int count = 0, c; + while (*text) + { + text += fz_chartorune(&c, text); + count++; + } + return count; +} + +/* sets a check box or radio button group to the state `value` (a name): + * the field's /V, and each widget's /AS where it has that state, else /Off */ +static void set_button(fz_context *ctx, pdf_document *pdf, pdf_obj *field, pdf_obj *value) +{ + pdf_obj *widget; + int i; + pdf_dict_put(ctx, field, PDF_NAME(V), value); + pdf_dirty_obj(ctx, field); + for (i = 0; (widget = widget_of(ctx, field, i)) != NULL; i++) + { + pdf_obj *states = pdf_dict_getp(ctx, widget, "AP/N"); + pdf_dict_put(ctx, widget, PDF_NAME(AS), pdf_dict_get(ctx, states, value) ? value : PDF_NAME(Off)); + pdf_dirty_obj(ctx, widget); + } + pdf->resynth_required = 1; +} + +/* whether a radio button group has a widget with the state `name` */ +static int has_state(fz_context *ctx, pdf_obj *field, const char *name) +{ + pdf_obj *widget; + int i; + for (i = 0; (widget = widget_of(ctx, field, i)) != NULL; i++) + if (pdf_dict_gets(ctx, pdf_dict_getp(ctx, widget, "AP/N"), name)) + return 1; + return 0; +} + +/* fills one field in; inside fz_try */ +static int fill_field(fz_context *ctx, pdf_document *pdf, pdf_obj *fields, const blank_fill *fill) +{ + pdf_obj *field = pdf_lookup_field(ctx, fields, fill->field); + char *name; + int same, type, flags, max_len, i, options; + const char *value = fill->text; + if (!field || has_field_kids(ctx, pdf_dict_get(ctx, field, PDF_NAME(Kids)))) + return BLANK_BAD_REQUEST; + /* the field of that full name, not one whose name only starts so */ + name = pdf_load_field_name(ctx, field); + same = !strcmp(name, fill->field); + fz_free(ctx, name); + if (!same) + return BLANK_BAD_REQUEST; + type = pdf_field_type(ctx, field); + flags = pdf_field_flags(ctx, field); + if (flags & PDF_FIELD_IS_READ_ONLY) + return BLANK_BAD_REQUEST; + switch (type) + { + case PDF_WIDGET_TYPE_TEXT: + if (!value) + return BLANK_BAD_REQUEST; + max_len = pdf_dict_get_inheritable_int(ctx, field, PDF_NAME(MaxLen)); + if (max_len > 0 && runes(value) > max_len) + return BLANK_BAD_REQUEST; + if (!(flags & PDF_TX_FIELD_IS_MULTILINE) && strpbrk(value, "\r\n")) + return BLANK_BAD_REQUEST; + if (!shows(ctx, pdf, field, value)) + return BLANK_MISSING_GLYPHS; + pdf_set_field_value(ctx, pdf, field, value, 1); + return BLANK_OK; + case PDF_WIDGET_TYPE_COMBOBOX: + case PDF_WIDGET_TYPE_LISTBOX: + if (!value) + return BLANK_BAD_REQUEST; + /* one of its options, by what it shows; its export value is set */ + options = pdf_choice_field_option_count(ctx, field); + for (i = 0; i < options; i++) + if (!strcmp(pdf_choice_field_option(ctx, field, 0, i), value)) + { + value = pdf_choice_field_option(ctx, field, 1, i); + break; + } + if (i == options && *value && !(type == PDF_WIDGET_TYPE_COMBOBOX && (flags & PDF_CH_FIELD_IS_EDIT))) + return BLANK_BAD_REQUEST; + if (!shows(ctx, pdf, field, fill->text)) + return BLANK_MISSING_GLYPHS; + pdf_set_field_value(ctx, pdf, field, value, 1); + return BLANK_OK; + case PDF_WIDGET_TYPE_CHECKBOX: + if (value) + return BLANK_BAD_REQUEST; + if (fill->checked) + { + pdf_obj *widget, *on = NULL; + for (i = 0; !on && (widget = widget_of(ctx, field, i)) != NULL; i++) + { + const char *state = on_state(ctx, widget); + if (state) + on = pdf_new_name(ctx, state); + } + if (!on) + on = PDF_NAME(Yes); + fz_try(ctx) + set_button(ctx, pdf, field, on); + fz_always(ctx) + pdf_drop_obj(ctx, on); + fz_catch(ctx) + fz_rethrow(ctx); + } + else + set_button(ctx, pdf, field, PDF_NAME(Off)); + return BLANK_OK; + case PDF_WIDGET_TYPE_RADIOBUTTON: + if (!value) + return BLANK_BAD_REQUEST; + if (!strcmp(value, "Off")) + { + set_button(ctx, pdf, field, PDF_NAME(Off)); + return BLANK_OK; + } + if (!has_state(ctx, field, value)) + return BLANK_BAD_REQUEST; + { + pdf_obj *on = pdf_new_name(ctx, value); + fz_try(ctx) + set_button(ctx, pdf, field, on); + fz_always(ctx) + pdf_drop_obj(ctx, on); + fz_catch(ctx) + fz_rethrow(ctx); + } + return BLANK_OK; + default: + return BLANK_BAD_REQUEST; + } +} + +/* draws the appearances of the filled fields' widgets again: their pages + * are loaded together, since MuPDF updates the pages that are open */ +static void update_pages(blank_doc *doc, const blank_fill *fills, int count) +{ + fz_context *ctx = doc->engine->ctx; + pdf_obj *fields = pdf_dict_getp(ctx, pdf_trailer(ctx, doc->pdf), "Root/AcroForm/Fields"); + form_walk walk; + fz_page **pages = NULL; + int i, j, page; + memset(&walk, 0, sizeof walk); + walk.doc = doc; + fz_var(pages); + fz_try(ctx) + { + find_widgets(&walk); + pages = fz_calloc(ctx, doc->pages ? doc->pages : 1, sizeof *pages); + for (i = 0; i < count; i++) + { + pdf_obj *field = pdf_lookup_field(ctx, fields, fills[i].field), *widget; + for (j = 0; field && (widget = widget_of(ctx, field, j)) != NULL; j++) + { + page = page_of(&walk, widget); + if (page >= 0 && !pages[page]) + pages[page] = fz_load_page(ctx, &doc->pdf->super, page); + } + } + for (page = 0; page < doc->pages; page++) + if (pages[page]) + pdf_update_page(ctx, (pdf_page *)pages[page]); + } + fz_always(ctx) + { + if (pages) + for (page = 0; page < doc->pages; page++) + fz_drop_page(ctx, pages[page]); + fz_free(ctx, pages); + fz_free(ctx, walk.widgets); + fz_free(ctx, walk.ctms); + } + fz_catch(ctx) + fz_rethrow(ctx); +} + +int blank_set_fills(blank_doc *doc, const blank_fill *fills, int count, blank_doc **out, int *failed) +{ + blank_engine *engine = doc->engine; + fz_context *ctx = engine->ctx; + blank_doc *filled = NULL; + int status, i; + *out = NULL; + *failed = -1; + status = blank_open(engine, doc->data, doc->len, doc->password, &filled); + if (status != BLANK_OK) + return status; + engine->refused = 0; + fz_var(status); + fz_var(i); + fz_try(ctx) + { + pdf_obj *fields = pdf_dict_getp(ctx, pdf_trailer(ctx, filled->pdf), "Root/AcroForm/Fields"); + for (i = 0; i < count && status == BLANK_OK; i++) + { + status = fill_field(ctx, filled->pdf, fields, &fills[i]); + if (status != BLANK_OK) + *failed = i; + } + if (status == BLANK_OK) + update_pages(filled, fills, count); + } + fz_catch(ctx) + status = caught(engine, BLANK_ENGINE); + if (status != BLANK_OK) + { + blank_doc_drop(filled); + return status; + } + *out = filled; + return BLANK_OK; +} + +int blank_save(blank_doc *doc, unsigned char **data, size_t *len) +{ + blank_engine *engine = doc->engine; + fz_context *ctx = engine->ctx; + fz_buffer *buffer = NULL; + fz_output *out = NULL; + int status = BLANK_OK; + *data = NULL; + *len = 0; + /* MuPDF can't append to a repaired PDF; one with bytes before its + * header would get offsets that are off by them (pdf_write_document + * doesn't take its bias into account) */ + if (doc->repaired || doc->pdf->bias) + return BLANK_REPAIRED; + if (doc->encrypted) + return BLANK_ENCRYPTED; + engine->refused = 0; + fz_var(buffer); + fz_var(out); + fz_var(status); + fz_try(ctx) + { + pdf_write_options options = pdf_default_write_options; + unsigned char *saved; + size_t size; + options.do_incremental = 1; + buffer = fz_new_buffer(ctx, doc->len + 4096); + out = fz_new_output_with_buffer(ctx, buffer); + pdf_write_document(ctx, doc->pdf, out, &options); + fz_close_output(ctx, out); + size = fz_buffer_storage(ctx, buffer, &saved); + *data = malloc(size ? size : 1); + if (!*data) + fz_throw(ctx, FZ_ERROR_SYSTEM, "out of memory"); + memcpy(*data, saved, size); + *len = size; + } + fz_always(ctx) + { + fz_drop_output(ctx, out); + fz_drop_buffer(ctx, buffer); + } + fz_catch(ctx) + status = caught(engine, BLANK_ENGINE); + return status; +} diff --git a/src-tauri/pdf/helper/shim/blank_mupdf.h b/src-tauri/pdf/helper/shim/blank_mupdf.h index 2aa5936a..995c3bcb 100644 --- a/src-tauri/pdf/helper/shim/blank_mupdf.h +++ b/src-tauri/pdf/helper/shim/blank_mupdf.h @@ -20,6 +20,14 @@ enum BLANK_CANCELLED = 3, BLANK_NEEDS_PASSWORD = 4, BLANK_DAMAGED = 5, + /* a fill names no field it can fill in, or a value it can't take */ + BLANK_BAD_REQUEST = 6, + /* a fill's value has characters the field's font can't show */ + BLANK_MISSING_GLYPHS = 7, + /* blank_save: the PDF was repaired to be read */ + BLANK_REPAIRED = 8, + /* blank_save: the PDF is encrypted */ + BLANK_ENCRYPTED = 9, }; /* @@ -60,4 +68,28 @@ void blank_cookie_drop(blank_cookie *cookie); int blank_form_json(blank_doc *doc, int signatures, char **json, size_t *len); void blank_free(char *json); +/* the words of a page and its thin horizontal lines, as JSON in the shape + * of protocol.rs's PageText, in *json (blank_free frees it) */ +int blank_page_text_json(blank_doc *doc, int page, char **json, size_t *len); + +/* a field (its full name) and its value: `text`, or for a check box (text + * NULL) whether it is `checked` */ +typedef struct blank_fill +{ + const char *field; + const char *text; + int checked; +} blank_fill; + +/* + * The PDF of `doc` opened afresh, from its bytes, with `fills` filled in and + * their appearances drawn again, in *out. On a failure *failed is the + * index of the fill that failed, or -1. + */ +int blank_set_fills(blank_doc *doc, const blank_fill *fills, int count, blank_doc **out, int *failed); + +/* the PDF as opened with its changes as an incremental update, in *data + * (blank_free frees it) */ +int blank_save(blank_doc *doc, unsigned char **data, size_t *len); + #endif diff --git a/src-tauri/pdf/helper/src/mupdf.rs b/src-tauri/pdf/helper/src/mupdf.rs index f7f3fa02..1450a036 100644 --- a/src-tauri/pdf/helper/src/mupdf.rs +++ b/src-tauri/pdf/helper/src/mupdf.rs @@ -6,7 +6,9 @@ use std::rc::Rc; use std::sync::Arc; use blank_pdf_ipc::engine::{Bitmap, CancelFlag, EngineError, PdfDoc, PdfEngine}; -use blank_pdf_ipc::protocol::{ErrorKind, Field, OpenInfo, PageInfo, SigInfo, MAX_PIXELS}; +use blank_pdf_ipc::protocol::{ + ErrorKind, Field, Fill, FillValue, OpenInfo, PageInfo, PageText, SigInfo, MAX_PIXELS, +}; use serde::de::DeserializeOwned; // the static MuPDF libraries the shim needs are linked through mupdf-sys @@ -25,6 +27,14 @@ struct RawCookie { _private: [u8; 0], } +/// `blank_fill` of shim/blank_mupdf.h +#[repr(C)] +struct RawFill { + field: *const c_char, + text: *const c_char, + checked: c_int, +} + /// see `blank_font_finder` in shim/blank_mupdf.h type FontFinder = unsafe extern "C" fn( name: *const c_char, @@ -89,6 +99,20 @@ extern "C" { len: *mut usize, ) -> c_int; fn blank_free(json: *mut c_char); + fn blank_page_text_json( + doc: *mut RawDoc, + page: c_int, + json: *mut *mut c_char, + len: *mut usize, + ) -> c_int; + fn blank_set_fills( + doc: *mut RawDoc, + fills: *const RawFill, + count: c_int, + out: *mut *mut RawDoc, + failed: *mut c_int, + ) -> c_int; + fn blank_save(doc: *mut RawDoc, data: *mut *mut u8, len: *mut usize) -> c_int; } /// a shim status as a result @@ -99,6 +123,10 @@ fn checked(status: c_int) -> Result<(), EngineError> { 3 => ErrorKind::Cancelled, 4 => ErrorKind::NeedsPassword, 5 => ErrorKind::Damaged, + 6 => ErrorKind::BadRequest, + 7 => ErrorKind::MissingGlyphs, + 8 => ErrorKind::Repaired, + 9 => ErrorKind::Encrypted, _ => ErrorKind::Engine, }; Err(kind.into()) @@ -172,6 +200,7 @@ impl PdfEngine for Mupdf { Ok(MupdfDoc { raw, info, + fills: vec![], _bytes: bytes, _engine: self.engine.clone(), }) @@ -183,6 +212,8 @@ impl PdfEngine for Mupdf { pub struct MupdfDoc { raw: *mut RawDoc, info: OpenInfo, + /// the fills `raw` has: it is the PDF opened afresh with them + fills: Vec, _bytes: Arc<[u8]>, _engine: Rc, } @@ -213,16 +244,69 @@ impl MupdfDoc { } fn form(&mut self, signatures: bool) -> Result { - let mut json = std::ptr::null_mut(); - let mut len = 0; - checked(unsafe { blank_form_json(self.raw, signatures as c_int, &mut json, &mut len) })?; - let bytes = unsafe { std::slice::from_raw_parts(json as *const u8, len) }; - let parsed = serde_json::from_str(&String::from_utf8_lossy(bytes)); - unsafe { blank_free(json) }; - parsed.map_err(|_| ErrorKind::Engine.into()) + let raw = self.raw; + read_json(|json, len| unsafe { blank_form_json(raw, signatures as c_int, json, len) }) + } + + /// the PDF opened afresh with `fills`, their appearances drawn again + fn filled(&self, fills: &[Fill]) -> Result<*mut RawDoc, EngineError> { + let text = |text: &str| CString::new(text).map_err(|_| ErrorKind::BadRequest); + let mut strings = Vec::with_capacity(fills.len()); + for fill in fills { + let value = match &fill.value { + FillValue::Text(value) => Some(text(value)?), + FillValue::Checked(_) => None, + }; + strings.push((text(&fill.field)?, value)); + } + let raw: Vec = fills + .iter() + .zip(&strings) + .map(|(fill, (field, value))| RawFill { + field: field.as_ptr(), + text: value + .as_ref() + .map_or(std::ptr::null(), |value| value.as_ptr()), + checked: matches!(fill.value, FillValue::Checked(true)) as c_int, + }) + .collect(); + let count = c_int::try_from(raw.len()).map_err(|_| ErrorKind::BadRequest)?; + let (mut out, mut failed) = (std::ptr::null_mut(), -1); + checked(unsafe { blank_set_fills(self.raw, raw.as_ptr(), count, &mut out, &mut failed) })?; + Ok(out) } } +/// reads the JSON a shim function writes, which it allocated +fn read_json( + write: impl FnOnce(*mut *mut c_char, *mut usize) -> c_int, +) -> Result { + let mut json = std::ptr::null_mut(); + let mut len = 0; + checked(write(&mut json, &mut len))?; + let bytes = unsafe { std::slice::from_raw_parts(json as *const u8, len) }; + let parsed = serde_json::from_str(&String::from_utf8_lossy(bytes)); + unsafe { blank_free(json) }; + parsed.map_err(|_| ErrorKind::Engine.into()) +} + +/// the pages of the fields whose value differs between `before` and `after` +fn changed_pages(before: &[Field], after: &[Field]) -> Vec { + let mut pages: Vec = after + .iter() + .filter(|field| { + before + .iter() + .find(|old| old.name == field.name) + .is_none_or(|old| old.value != field.value) + }) + .flat_map(|field| field.widgets.iter().map(|widget| widget.page)) + .collect(); + pages.sort_unstable(); + pages.dedup(); + pages +} + impl PdfDoc for MupdfDoc { fn info(&self) -> OpenInfo { self.info.clone() @@ -297,10 +381,40 @@ impl PdfDoc for MupdfDoc { }) } + fn text(&mut self, index: usize) -> Result { + let (raw, page) = (self.raw, self.index(index)?); + read_json(|json, len| unsafe { blank_page_text_json(raw, page, json, len) }) + } + fn fields(&mut self) -> Result, EngineError> { self.form(false) } + fn set_fills(&mut self, fills: &[Fill]) -> Result, EngineError> { + let before = self.fields()?; + let filled = self.filled(fills)?; + unsafe { blank_doc_drop(self.raw) }; + self.raw = filled; + self.fills = fills.to_vec(); + Ok(changed_pages(&before, &self.fields()?)) + } + + fn save_incremental(&mut self) -> Result, EngineError> { + // from a document of its own: MuPDF takes what it saved as saved + let fresh = MupdfDoc { + raw: self.filled(&self.fills)?, + info: self.info.clone(), + fills: self.fills.clone(), + _bytes: self._bytes.clone(), + _engine: self._engine.clone(), + }; + let (mut data, mut len) = (std::ptr::null_mut(), 0); + checked(unsafe { blank_save(fresh.raw, &mut data, &mut len) })?; + let saved = unsafe { std::slice::from_raw_parts(data, len) }.to_vec(); + unsafe { blank_free(data as *mut c_char) }; + Ok(saved) + } + fn signatures(&mut self) -> Result, EngineError> { self.form(true) } diff --git a/src-tauri/pdf/helper/tests/fills.rs b/src-tauri/pdf/helper/tests/fills.rs new file mode 100644 index 00000000..1792d6ac --- /dev/null +++ b/src-tauri/pdf/helper/tests/fills.rs @@ -0,0 +1,351 @@ +//! A page's text, and filling the form in, on the fixtures of +//! scripts/build-pdf-fixtures.ts, checked with poppler and qpdf where they +//! are installed (CI has them: there a missing tool fails the test). + +use std::io::Write; +use std::process::{Command, Stdio}; +use std::sync::Arc; + +use blank_pdf::mupdf::{Mupdf, MupdfDoc}; +use blank_pdf_ipc::engine::{CancelFlag, PdfDoc, PdfEngine}; +use blank_pdf_ipc::protocol::{ErrorKind, Field, Fill, FillValue, Rect}; + +fn fixture(name: &str) -> Arc<[u8]> { + let path = format!("{}/../fixtures/{name}", env!("CARGO_MANIFEST_DIR")); + std::fs::read(path).unwrap().into() +} + +fn engine() -> Mupdf { + Mupdf::new(256 << 20, 32 << 20).unwrap() +} + +fn open(engine: &Mupdf, name: &str) -> MupdfDoc { + engine.open(fixture(name), None).unwrap() +} + +/// whether `tool` runs; without it a test is skipped, except on CI +fn has(tool: &str) -> bool { + let found = Command::new(tool) + .arg("-v") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .is_ok(); + if !found { + assert!(std::env::var_os("CI").is_none(), "CI needs {tool}"); + eprintln!("skipped: no {tool}"); + } + found +} + +/// runs `tool` with `args` and the PDF `pdf` as a file, then `-` (stdout) +fn run(tool: &str, args: &[&str], pdf: &[u8]) -> (bool, String) { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("in.pdf"); + std::fs::File::create(&path) + .unwrap() + .write_all(pdf) + .unwrap(); + let output = Command::new(tool) + .args(args) + .arg(&path) + .args(if tool == "qpdf" { vec![] } else { vec!["-"] }) + .output() + .unwrap(); + ( + output.status.success(), + String::from_utf8_lossy(&output.stdout).into_owned(), + ) +} + +/// pdftotext's words on `page` (from 0) with their boxes, `[x, y, w, h]` +fn poppler_words(pdf: &[u8], page: usize) -> Vec<(String, Rect)> { + let first = (page + 1).to_string(); + let (ok, html) = run("pdftotext", &["-bbox", "-f", &first, "-l", &first], pdf); + assert!(ok); + html.lines() + .filter_map(|line| { + let line = line.trim().strip_prefix(" f32 { + let at = line.find(&format!("{name}=\"")).unwrap() + name.len() + 2; + line[at..].split('"').next().unwrap().parse().unwrap() + }; + let text = line.split('>').nth(1)?.split('<').next()?.to_string(); + let (x0, y0) = (number("xMin"), number("yMin")); + Some((text, [x0, y0, number("xMax") - x0, number("yMax") - y0])) + }) + .collect() +} + +/// whether the edges of `rect` are each within `within` of `expected`'s +fn near(rect: Rect, expected: Rect, within: f32) -> bool { + let edges = |[x, y, w, h]: Rect| [x, y, x + w, y + h]; + edges(rect) + .iter() + .zip(edges(expected)) + .all(|(a, b)| (a - b).abs() <= within) +} + +#[test] +fn reads_the_words_and_lines_of_a_page() { + let engine = engine(); + let text = open(&engine, "form.pdf").text(0).unwrap(); + let words: Vec<&str> = text.words.iter().map(|word| word.text.as_str()).collect(); + // the page's own text, not the values of its fields + assert_eq!( + words, + [ + "Contract", + "Unterschrift", + "Ort,", + "Datum:", + "______________" + ] + ); + // the line over "Unterschrift": 100 490 to 300 490, 0.5 pt wide; the + // underscores are a word, not a line + assert_eq!(text.rules.len(), 1, "{:?}", text.rules); + assert!( + near(text.rules[0], [100.0, 351.75, 200.0, 0.5], 1.0), + "{:?}", + text.rules[0] + ); + + if !has("pdftotext") { + return; + } + for word in &text.words { + let poppler = poppler_words(&fixture("form.pdf"), 0); + let (_, expected) = poppler.iter().find(|(text, _)| *text == word.text).unwrap(); + assert!( + near(word.rect, *expected, 1.0), + "{}: {:?}, pdftotext {:?}", + word.text, + word.rect, + expected + ); + } +} + +#[test] +fn reads_the_words_of_turned_and_cropped_pages() { + let engine = engine(); + let mut doc = open(&engine, "rotated.pdf"); + let turned = doc.text(0).unwrap(); + assert_eq!(turned.words.len(), 1); + assert_eq!(turned.words[0].text, "Turned"); + // the filled box is no line: 30 pt high + assert!(turned.rules.is_empty()); + let a5 = doc.text(1).unwrap(); + assert_eq!(a5.words[0].text, "A5"); + assert_eq!(doc.text(2).unwrap_err().kind, ErrorKind::BadRequest); + + if !has("pdftotext") { + return; + } + // pdftotext turns the page, but from the media box: the crop box is + // 50 pt in on both sides + let (_, [x, y, w, h]) = poppler_words(&fixture("rotated.pdf"), 0).remove(0); + let expected = [x - 50.0, y - 50.0, w, h]; + assert!( + near(turned.words[0].rect, expected, 1.0), + "{:?}, pdftotext {expected:?}", + turned.words[0].rect + ); + let (_, expected) = poppler_words(&fixture("rotated.pdf"), 1).remove(0); + assert!(near(a5.words[0].rect, expected, 1.0)); +} + +fn fill(field: &str, value: &str) -> Fill { + Fill { + field: field.into(), + value: FillValue::Text(value.into()), + } +} + +fn check(field: &str, checked: bool) -> Fill { + Fill { + field: field.into(), + value: FillValue::Checked(checked), + } +} + +fn values(fields: &[Field]) -> Vec<(&str, &str)> { + fields + .iter() + .map(|field| (field.name.as_str(), field.value.as_str())) + .collect() +} + +fn the_fills() -> Vec { + vec![ + fill("name", "Ada Müller"), + check("agree", true), + fill("color", "Blue"), + fill("address.street", "Main Street 1"), + fill("size", "L"), + fill("fruit", "Pear"), + ] +} + +const FILLED: [(&str, &str); 8] = [ + ("name", "Ada Müller"), + ("agree", "Yes"), + ("color", "Blue"), + ("signature", ""), + ("id", "A-17"), + ("address.street", "Main Street 1"), + ("size", "L"), + ("fruit", "Pear"), +]; + +/// whether the middle of a widget of `rect` is dark on a page drawn at scale 1 +fn inked(doc: &mut MupdfDoc, rect: Rect) -> bool { + let bitmap = doc.render(0, 1.0, &CancelFlag::default()).unwrap(); + let (x, y) = ( + (rect[0] + rect[2] / 2.0) as u32, + (rect[1] + rect[3] / 2.0) as u32, + ); + let at = ((y * bitmap.width + x) * 4) as usize; + bitmap.rgba[at] < 100 +} + +#[test] +fn fills_the_form_in_and_draws_it_so() { + let engine = engine(); + let mut doc = open(&engine, "form.pdf"); + let fields = doc.fields().unwrap(); + let widget = |name: &str, at: usize| { + fields + .iter() + .find(|field| field.name == name) + .unwrap() + .widgets[at] + .clone() + }; + let (agree, large) = (widget("agree", 0), widget("size", 1)); + assert_eq!(widget("size", 0).on_value.as_deref(), Some("S")); + assert_eq!(large.on_value.as_deref(), Some("L")); + assert!(!inked(&mut doc, agree.rect)); + + assert_eq!(doc.set_fills(&the_fills()).unwrap(), [0]); + assert_eq!(values(&doc.fields().unwrap()), FILLED); + assert!(inked(&mut doc, agree.rect), "the checkbox shows its check"); + assert!(inked(&mut doc, large.rect), "and the radio button L"); + assert!(!inked(&mut doc, widget("size", 0).rect)); + + // the same set again changes nothing; another undoes the rest + assert!(doc.set_fills(&the_fills()).unwrap().is_empty()); + doc.set_fills(&[fill("size", "S"), check("agree", false)]) + .unwrap(); + let fields = doc.fields().unwrap(); + let values = values(&fields); + assert_eq!(values[0], ("name", ""), "an earlier fill isn't kept"); + assert_eq!(values[1], ("agree", "Off")); + assert_eq!(values[6], ("size", "S")); + assert!(!inked(&mut doc, agree.rect)); +} + +#[test] +fn saves_the_fills_as_an_update_of_the_pdf() { + let engine = engine(); + let original = fixture("form.pdf"); + let mut doc = open(&engine, "form.pdf"); + assert_eq!(doc.save_incremental().unwrap(), &original[..], "no fills"); + doc.set_fills(&the_fills()).unwrap(); + let saved = doc.save_incremental().unwrap(); + assert!(saved.starts_with(&original) && saved.len() > original.len()); + assert_eq!(doc.save_incremental().unwrap(), saved, "the same again"); + + // read back, by MuPDF + let mut reopened = engine.open(saved.clone().into(), None).unwrap(); + assert!(!reopened.info().repaired); + assert_eq!(values(&reopened.fields().unwrap()), FILLED); + + if has("qpdf") { + let (ok, _) = run("qpdf", &["--check"], &saved); + assert!(ok, "qpdf --check"); + } + if has("pdftotext") { + let (ok, text) = run("pdftotext", &[], &saved); + assert!(ok); + for value in ["Ada Müller", "Main Street 1", "Blue"] { + assert!(text.contains(value), "{value} in {text}"); + } + } +} + +#[test] +fn saves_a_fill_on_a_turned_page() { + let engine = engine(); + let original = fixture("rotated.pdf"); + let mut doc = open(&engine, "rotated.pdf"); + assert_eq!(doc.set_fills(&[fill("stamp", "Signed")]).unwrap(), [0]); + let saved = doc.save_incremental().unwrap(); + assert!(saved.starts_with(&original)); + if has("pdftotext") { + let (_, text) = run("pdftotext", &["-f", "1", "-l", "1"], &saved); + assert!(text.contains("Signed"), "{text}"); + } +} + +#[test] +fn refuses_what_a_field_cant_take() { + let engine = engine(); + let mut doc = open(&engine, "form.pdf"); + doc.set_fills(&[fill("name", "Ada")]).unwrap(); + let refused = |doc: &mut MupdfDoc, fills: &[Fill]| doc.set_fills(fills).unwrap_err().kind; + // Helvetica in Windows-1252: no Chinese, no Polish Ł + for value in ["中文", "Łukasz", "Ada \u{1F600}"] { + assert_eq!( + refused(&mut doc, &[fill("name", value)]), + ErrorKind::MissingGlyphs, + "{value}" + ); + } + for fills in [ + vec![fill("id", "B-2")], // read-only + vec![fill("nobody", "x")], // no such field + vec![fill("address", "x")], // not a field to fill in + vec![fill("addr", "x")], // a part of a name + vec![fill("signature", "x")], // signed elsewhere + vec![fill("name", &"x".repeat(41))], // its /MaxLen is 40 + vec![fill("name", "two\nlines")], // one line only + vec![fill("agree", "Yes")], // a checkbox takes true or false + vec![check("name", true)], + vec![fill("size", "XL")], // not one of its buttons + vec![fill("fruit", "Fig")], // not one of its options + vec![fill("color", "Teal")], + ] { + assert_eq!( + refused(&mut doc, &fills), + ErrorKind::BadRequest, + "{fills:?}" + ); + } + // the last set that held stays + assert_eq!(values(&doc.fields().unwrap())[0], ("name", "Ada")); + // a radio button group can be emptied, a choice too + doc.set_fills(&[fill("size", "Off"), fill("color", "")]) + .unwrap(); +} + +#[test] +fn refuses_to_save_a_repaired_or_encrypted_pdf() { + let engine = engine(); + let mut repaired = open(&engine, "repaired.pdf"); + repaired.set_fills(&[fill("name", "Ada")]).unwrap(); + assert_eq!( + repaired.save_incremental().unwrap_err().kind, + ErrorKind::Repaired + ); + let mut locked = engine + .open(fixture("password.pdf"), Some("secret")) + .unwrap(); + locked.set_fills(&[fill("name", "Ada")]).unwrap(); + assert_eq!(values(&locked.fields().unwrap())[0], ("name", "Ada")); + assert_eq!( + locked.save_incremental().unwrap_err().kind, + ErrorKind::Encrypted + ); +} diff --git a/src-tauri/pdf/helper/tests/mupdf.rs b/src-tauri/pdf/helper/tests/mupdf.rs index d75dac9e..b062e5bb 100644 --- a/src-tauri/pdf/helper/tests/mupdf.rs +++ b/src-tauri/pdf/helper/tests/mupdf.rs @@ -105,7 +105,9 @@ fn reads_the_form() { "color", "signature", "id", - "address.street" + "address.street", + "size", + "fruit" ] ); let field = |name: &str| fields.iter().find(|field| field.name == name).unwrap(); @@ -133,6 +135,21 @@ fn reads_the_form() { assert_eq!(color.options, ["Red", "Green", "Blue"]); assert_eq!(color.value, "Green"); + let size = field("size"); + assert_eq!(size.kind, FieldKind::Radio); + assert_eq!(size.value, "Off"); + let states: Vec<_> = size + .widgets + .iter() + .map(|widget| widget.on_value.as_deref()) + .collect(); + assert_eq!(states, [Some("S"), Some("L")]); + + let fruit = field("fruit"); + assert_eq!(fruit.kind, FieldKind::List); + assert_eq!(fruit.options, ["Apple", "Pear"]); + assert_eq!(fruit.value, ""); + let signature = field("signature"); assert_eq!(signature.kind, FieldKind::Signature); assert!(!signature.signed); diff --git a/src-tauri/pdf/helper/tests/served.rs b/src-tauri/pdf/helper/tests/served.rs index abb96241..a091fa0d 100644 --- a/src-tauri/pdf/helper/tests/served.rs +++ b/src-tauri/pdf/helper/tests/served.rs @@ -58,3 +58,35 @@ fn serves_a_pdf_to_the_client() { rounds[1] ); } + +#[test] +fn fills_a_form_in_and_saves_it_through_the_client() { + use blank_pdf_ipc::protocol::{Field, Fill, FillValue, PageText}; + let path = format!("{}/../fixtures/form.pdf", env!("CARGO_MANIFEST_DIR")); + let pdf: Arc<[u8]> = std::fs::read(path).unwrap().into(); + let config = HelperConfig { + program: env!("CARGO_BIN_EXE_blank-pdf").into(), + args: vec!["--memory-limit-mb".into(), "256".into()], + quiet: true, + timeouts: Timeouts::default(), + }; + let original = pdf.clone(); + let supervisor = Supervisor::new(config, Box::new(move || Ok(pdf.clone()))); + let text: PageText = supervisor.ask(Op::Text { page: 0 }).unwrap(); + assert_eq!(text.words[0].text, "Contract"); + let fills = vec![ + Fill { + field: "name".into(), + value: FillValue::Text("Ada".into()), + }, + Fill { + field: "agree".into(), + value: FillValue::Checked(true), + }, + ]; + assert_eq!(supervisor.set_fills(fills).unwrap(), [0]); + let fields: Vec = supervisor.ask(Op::Fields).unwrap(); + assert_eq!(fields[0].value, "Ada"); + let saved = supervisor.save().unwrap(); + assert!(saved.starts_with(&original) && saved.len() > original.len()); +} diff --git a/src-tauri/pdf/ipc/src/bin/fake-pdf-helper.rs b/src-tauri/pdf/ipc/src/bin/fake-pdf-helper.rs index e3803e11..595be2f3 100644 --- a/src-tauri/pdf/ipc/src/bin/fake-pdf-helper.rs +++ b/src-tauri/pdf/ipc/src/bin/fake-pdf-helper.rs @@ -3,7 +3,10 @@ //! page does: "ok" draws a solid colour, "slow" runs until cancelled, "stuck" //! ignores the cancel, "crash" dies, "reserve" asks for more memory than //! the helper's limits allow, "short" draws fewer pixels than its size says, -//! "huge" a page past `MAX_PIXELS`. `--protocol N` says hello with another +//! "huge" a page past `MAX_PIXELS`. Its form is a text field "name" on page +//! 0 and a checkbox "agree" on page 1; a fill with "中" lacks glyphs, and +//! `save` appends the fills to the "PDF" as a line of JSON. Each page's text +//! is the word "page" and its number, over a rule. `--protocol N` says hello with another //! version; `--garbage` answers every request with bytes that aren't a frame //! and then keeps running. @@ -12,13 +15,18 @@ use std::sync::Arc; use std::time::{Duration, Instant}; use blank_pdf_ipc::engine::{Bitmap, CancelFlag, EngineError, PdfDoc, PdfEngine}; -use blank_pdf_ipc::protocol::{ErrorKind, Field, OpenInfo, PageInfo, SigInfo}; +use blank_pdf_ipc::protocol::{ + ErrorKind, Field, FieldFlags, FieldKind, Fill, FillValue, OpenInfo, PageInfo, PageText, + SigInfo, Widget, Word, +}; use blank_pdf_ipc::{frame, limits, serve}; struct Fake; struct FakeDoc { + bytes: Arc<[u8]>, pages: Vec, + fills: Vec, } impl PdfEngine for Fake { @@ -41,7 +49,11 @@ impl PdfEngine for Fake { .iter() .map(|page| page.as_str().unwrap_or("ok").to_string()) .collect(); - Ok(FakeDoc { pages }) + Ok(FakeDoc { + bytes, + pages, + fills: vec![], + }) } } @@ -121,8 +133,86 @@ impl PdfDoc for FakeDoc { }) } + fn text(&mut self, index: usize) -> Result { + Ok(PageText { + words: vec![ + Word { + text: "page".into(), + rect: [1.0, 2.0, 3.0, 1.0], + }, + Word { + text: index.to_string(), + rect: [5.0, 2.0, 1.0, 1.0], + }, + ], + rules: vec![[1.0, 4.0, 8.0, 0.5]], + }) + } + fn fields(&mut self) -> Result, EngineError> { - Ok(vec![]) + let value = + |name: &str, empty: &str| match self.fills.iter().find(|fill| fill.field == name) { + Some(Fill { + value: FillValue::Text(text), + .. + }) => text.clone(), + Some(Fill { + value: FillValue::Checked(true), + .. + }) => "Yes".into(), + _ => empty.into(), + }; + let field = |name: &str, kind, page, value| Field { + name: name.into(), + kind, + label: None, + value, + options: vec![], + flags: FieldFlags::default(), + signed: false, + widgets: vec![Widget { + page, + rect: [1.0, 1.0, 5.0, 2.0], + on_value: None, + }], + }; + Ok(vec![ + field("name", FieldKind::Text, 0, value("name", "")), + field("agree", FieldKind::Checkbox, 1, value("agree", "Off")), + ]) + } + + fn set_fills(&mut self, fills: &[Fill]) -> Result, EngineError> { + let mut pages = vec![]; + for fill in fills { + let page = match fill.field.as_str() { + "name" => 0, + "agree" => 1, + _ => return Err(ErrorKind::BadRequest.into()), + }; + if matches!(&fill.value, FillValue::Text(text) if text.contains('中')) { + return Err(ErrorKind::MissingGlyphs.into()); + } + pages.push(page); + } + for fill in &self.fills { + if !fills.contains(fill) { + pages.push(if fill.field == "name" { 0 } else { 1 }); + } + } + pages.sort_unstable(); + pages.dedup(); + self.fills = fills.to_vec(); + Ok(pages) + } + + fn save_incremental(&mut self) -> Result, EngineError> { + let mut saved = self.bytes.to_vec(); + if !self.fills.is_empty() { + saved.push(b'\n'); + saved.extend(serde_json::to_vec(&self.fills).map_err(|_| ErrorKind::Engine)?); + } + Ok(saved) } fn signatures(&mut self) -> Result, EngineError> { @@ -140,7 +230,7 @@ fn main() { return; } if args.iter().any(|arg| arg == "--garbage") { - let hello = serde_json::json!({"id": 0, "event": "hello", "protocol": blank_pdf_ipc::protocol::PROTOCOL, "engine": "fake", "engineVersion": "1", "ops": []}); + let hello = serde_json::json!({"id": 0, "event": "hello", "protocol": blank_pdf_ipc::protocol::PROTOCOL, "engine": "fake", "engineVersion": "1", "ops": blank_pdf_ipc::protocol::Op::NAMES}); let mut stdout = io::stdout(); frame::write_frame(&mut stdout, &serde_json::to_vec(&hello).unwrap(), &[]).unwrap(); let mut stdin = io::stdin(); diff --git a/src-tauri/pdf/ipc/src/client.rs b/src-tauri/pdf/ipc/src/client.rs index 8057ea8e..ad546baf 100644 --- a/src-tauri/pdf/ipc/src/client.rs +++ b/src-tauri/pdf/ipc/src/client.rs @@ -5,12 +5,14 @@ //! new helper and opens the PDF again, and the request that failed isn't //! asked again; after the second, the supervisor gives up (`Failed`), and only //! a new one (the tab's "Open again") tries again. A helper stopped for being -//! idle isn't a failure. +//! idle isn't a failure. The supervisor keeps the form's fills (`set_fills`), +//! so a new helper gets them again once it has opened the PDF. use std::collections::VecDeque; use std::io; use std::path::PathBuf; use std::process::{Child, ChildStdin, Command, Stdio}; +use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::mpsc::{self, Receiver, RecvTimeoutError}; use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; use std::thread; @@ -22,7 +24,7 @@ use serde_json::Value; use crate::frame::{read_frame, write_frame, MAX_BODY}; use crate::limits::Backstop; use crate::protocol::{ErrorKind, Hello, Op, OpenInfo, Outcome, Request, Response, PROTOCOL}; -use crate::protocol::{PageInfo, RenderInfo, MAX_PIXELS, PAGES_PER_REQUEST}; +use crate::protocol::{Fill, Filled, PageInfo, RenderInfo, MAX_PIXELS, PAGES_PER_REQUEST}; /// why a request got no answer #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -65,6 +67,10 @@ pub struct Timeouts { pub open: Duration, pub render: Duration, pub read: Duration, + /// `setFills` + pub fill: Duration, + /// `save` + pub save: Duration, /// how long a cancelled request has to answer before the helper is killed pub cancel: Duration, } @@ -76,6 +82,8 @@ impl Default for Timeouts { open: Duration::from_secs(20), render: Duration::from_secs(15), read: Duration::from_secs(15), + fill: Duration::from_secs(10), + save: Duration::from_secs(30), cancel: Duration::from_secs(1), } } @@ -108,6 +116,11 @@ struct Helper { answers: Receiver, next_id: u64, backstop: Backstop, + /// the ops it knows, from its hello + ops: Vec, + /// the most the reader takes of an answer's body: a page's pixels, or + /// for `save` the PDF and a little more + max_answer: Arc, } /// how many answers the reader holds before it waits for the client: a @@ -146,8 +159,10 @@ impl Helper { return Err(PdfError::Start); }; let (send, answers) = mpsc::sync_channel(QUEUED_ANSWERS); + let max_answer = Arc::new(AtomicU64::new(MAX_ANSWER)); + let limit = max_answer.clone(); thread::spawn(move || { - while let Ok(frame) = read_frame(&mut stdout, MAX_ANSWER) { + while let Ok(frame) = read_frame(&mut stdout, limit.load(Ordering::SeqCst)) { let Ok(header) = serde_json::from_slice(&frame.header) else { break; }; @@ -163,6 +178,8 @@ impl Helper { answers, next_id: 1, backstop, + ops: vec![], + max_answer, }; helper.greet(config.timeouts.start)?; Ok(helper) @@ -172,7 +189,10 @@ impl Helper { fn greet(&mut self, timeout: Duration) -> Result<(), PdfError> { match self.answers.recv_timeout(timeout) { Ok(Incoming::Frame(header, _)) => match serde_json::from_value::(header) { - Ok(hello) if hello.protocol == PROTOCOL => Ok(()), + Ok(hello) if hello.protocol == PROTOCOL => { + self.ops = hello.ops; + Ok(()) + } _ => { log::warn!("PDF helper: start: another protocol"); self.kill(); @@ -236,6 +256,10 @@ impl Helper { cancel_timeout: Duration, sent: impl FnOnce(u64, Arc>), ) -> Result<(Value, Vec), PdfError> { + // an op the helper doesn't know (one newer than it): not asked + if !self.ops.iter().any(|known| known == op.name()) { + return Err(PdfError::Engine(ErrorKind::UnknownOp)); + } let id = self.next_id; self.next_id += 1; let header = @@ -306,6 +330,8 @@ struct State { info: Option, /// the PDF while a helper has it open; dropped with the helper bytes: Option>, + /// the form's fills the helper has, which a new helper gets again + fills: Vec, failures: u32, last_used: Instant, } @@ -356,6 +382,7 @@ impl Supervisor { helper: None, info: None, bytes: None, + fills: vec![], failures: 0, last_used: Instant::now(), }), @@ -426,7 +453,8 @@ impl Supervisor { } /// asks `op`, starting the helper and opening the PDF in it first if - /// none runs; `ticket` lets `cancel` stop it + /// none runs (and giving it the fills again); `ticket` lets `cancel` + /// stop it. A `setFills` that succeeds replaces the fills kept pub fn request(&self, op: Op, ticket: Option) -> Result<(Value, Vec), PdfError> { let mut state = locked(&self.state); if state.failures >= MAX_FAILURES { @@ -441,9 +469,17 @@ impl Supervisor { let timeout = match op { Op::Open { .. } => timeouts.open, Op::Render { .. } => timeouts.render, + Op::SetFills { .. } => timeouts.fill, + Op::Save => timeouts.save, _ => timeouts.read, }; + // a saved PDF is the one opened and the fills' update + let max_answer = match (&op, &state.bytes) { + (Op::Save, Some(bytes)) => (bytes.len() as u64 + MAX_ANSWER).min(MAX_BODY), + _ => MAX_ANSWER, + }; let helper = state.helper.as_mut().ok_or(PdfError::Start)?; + helper.max_answer.store(max_answer, Ordering::SeqCst); let result = helper.call(&op, &[], timeout, timeouts.cancel, |id, stdin| { if let Some(ticket) = ticket { let mut in_flight = locked(&self.in_flight); @@ -462,6 +498,10 @@ impl Supervisor { log::warn!("PDF helper: render: pixels don't match their size"); Err(PdfError::Engine(ErrorKind::Engine)) } + (Op::SetFills { fills }, Ok(answer)) => { + state.fills = fills.clone(); + Ok(answer) + } (_, result) => result, }; self.settle(&mut state, result) @@ -483,6 +523,25 @@ impl Supervisor { Ok(pages) } + /// fills the form in with `fills`, the whole set (see `Op::SetFills`), + /// and keeps them for a new helper; gives the pages to draw again + pub fn set_fills(&self, fills: Vec) -> Result, PdfError> { + let (value, _) = self.request(Op::SetFills { fills }, None)?; + let filled: Filled = + serde_json::from_value(value).map_err(|_| PdfError::Engine(ErrorKind::Engine))?; + Ok(filled.pages) + } + + /// the fills the helper has + pub fn fills(&self) -> Vec { + locked(&self.state).fills.clone() + } + + /// the PDF with the fills as an incremental update (see `Op::Save`) + pub fn save(&self) -> Result, PdfError> { + self.request(Op::Save, None).map(|(_, bytes)| bytes) + } + /// asks `op` and reads its answer as `T` pub fn ask(&self, op: Op) -> Result { let (value, _) = self.request(op, None)?; @@ -524,6 +583,18 @@ impl Supervisor { let (value, _) = self.settle(state, opened)?; let info: OpenInfo = serde_json::from_value(value).map_err(|_| PdfError::Engine(ErrorKind::Engine))?; + if !state.fills.is_empty() { + let refill = helper.call( + &Op::SetFills { + fills: state.fills.clone(), + }, + &[], + self.config.timeouts.fill, + self.config.timeouts.cancel, + |_, _| {}, + ); + self.settle(state, refill)?; + } state.helper = Some(helper); state.bytes = Some(bytes); state.info = Some(info.clone()); diff --git a/src-tauri/pdf/ipc/src/engine.rs b/src-tauri/pdf/ipc/src/engine.rs index d8654595..c7dcb944 100644 --- a/src-tauri/pdf/ipc/src/engine.rs +++ b/src-tauri/pdf/ipc/src/engine.rs @@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex, PoisonError}; -use crate::protocol::{ErrorKind, Field, OpenInfo, PageInfo, SigInfo}; +use crate::protocol::{ErrorKind, Field, Fill, OpenInfo, PageInfo, PageText, SigInfo}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct EngineError { @@ -41,9 +41,25 @@ pub trait PdfDoc { scale: f32, cancel: &CancelFlag, ) -> Result; + /// a page's words and thin horizontal lines, in display points + fn text(&mut self, _index: usize) -> Result { + Err(ErrorKind::UnknownOp.into()) + } /// every field of the form, with full names and what they inherit fn fields(&mut self) -> Result, EngineError>; fn signatures(&mut self) -> Result, EngineError>; + /// fills the form in with `fills`, the whole set, on the PDF as it was + /// opened; what's drawn and saved from then on is the filled PDF. Gives + /// the pages whose fields changed since the last set. On a failure the + /// last set stays + fn set_fills(&mut self, _fills: &[Fill]) -> Result, EngineError> { + Err(ErrorKind::UnknownOp.into()) + } + /// the PDF opened, byte for byte, with the fills appended as an + /// incremental update (none without fills) + fn save_incremental(&mut self) -> Result, EngineError> { + Err(ErrorKind::UnknownOp.into()) + } } type Hook = Box; diff --git a/src-tauri/pdf/ipc/src/protocol.rs b/src-tauri/pdf/ipc/src/protocol.rs index 3406ec72..f686f7b6 100644 --- a/src-tauri/pdf/ipc/src/protocol.rs +++ b/src-tauri/pdf/ipc/src/protocol.rs @@ -8,10 +8,11 @@ //! //! Within version 1 a new op is optional: a helper that doesn't know it //! answers `unknownOp`. The hello lists the ops the helper knows -//! (`Hello::ops`); the app doesn't read that list yet, since every op it -//! asks is in version 1 from the start. A breaking change raises `PROTOCOL` and the -//! frame's magic. Positions and sizes are display points: from the top left -//! of the page's crop box after its `/Rotate`, with its UserUnit applied. +//! (`Hello::ops`), and the client asks none other (`text`, `setFills` and +//! `save` came after the first ops). A breaking change raises `PROTOCOL` +//! and the frame's magic. Positions and sizes are display points: from the +//! top left of the page's crop box after its `/Rotate`, with its UserUnit +//! applied. use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -44,13 +45,27 @@ pub enum Op { page: usize, scale: f32, }, - /// the form's fields. Not asked by the app yet: it is there for the - /// PDF forms of the next step (filling them in), see .claude/rules/pdf.md + /// the words and drawn lines of a page (`PageText`), for the places to + /// sign or fill in on a PDF without a form + Text { + page: usize, + }, + /// the form's fields (`Field`), with the values the fills gave them Fields, /// what the signatures say of themselves. Not asked by the app yet: the /// signature check reads the PDF itself (signing/verify.rs); this is for /// placing signature fields in the next step, see .claude/rules/pdf.md Signatures, + /// fills in the form: always the whole set, on the PDF as it was opened + /// (what an earlier `setFills` did is undone first); the answer is + /// `Filled`, the pages to draw again. A value the field can't take, or + /// a field that isn't there, fails the whole set and keeps the last one + SetFills { + fills: Vec, + }, + /// the PDF with the fills as an incremental update: the bytes opened, + /// unchanged, then the update; refused for a repaired or encrypted PDF + Save, /// stops request `target`, which then answers `cancelled`; no answer of /// its own Cancel { @@ -61,12 +76,15 @@ pub enum Op { impl Op { /// every op's name, as the helper says in its hello - pub const NAMES: [&'static str; 7] = [ + pub const NAMES: [&'static str; 10] = [ "open", "pages", "render", + "text", "fields", "signatures", + "setFills", + "save", "cancel", "ping", ]; @@ -77,8 +95,11 @@ impl Op { Op::Open { .. } => "open", Op::Pages { .. } => "pages", Op::Render { .. } => "render", + Op::Text { .. } => "text", Op::Fields => "fields", Op::Signatures => "signatures", + Op::SetFills { .. } => "setFills", + Op::Save => "save", Op::Cancel { .. } => "cancel", Op::Ping => "ping", } @@ -106,6 +127,13 @@ pub enum ErrorKind { Cancelled, /// anything else the engine threw Engine, + /// `save`: the engine had to repair the PDF to read it, so an update + /// can't be appended to it + Repaired, + /// `save`: the PDF is encrypted + Encrypted, + /// `setFills`: a value has characters the field's font can't show + MissingGlyphs, } impl ErrorKind { @@ -119,6 +147,9 @@ impl ErrorKind { ErrorKind::TooComplex => "tooComplex", ErrorKind::Cancelled => "cancelled", ErrorKind::Engine => "engine", + ErrorKind::Repaired => "repaired", + ErrorKind::Encrypted => "encrypted", + ErrorKind::MissingGlyphs => "missingGlyphs", } } } @@ -235,6 +266,51 @@ pub struct Field { pub widgets: Vec, } +/// a word on a page, as the engine reads the page's text +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct Word { + pub text: String, + pub rect: Rect, +} + +/// what a page says and the lines drawn on it: its words in reading order, +/// and its thin horizontal lines (at least `RULE_MIN_WIDTH` wide, at most +/// `RULE_MAX_HEIGHT` high, stroked or filled), where people sign or write +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] +pub struct PageText { + pub words: Vec, + pub rules: Vec, +} + +/// the narrowest a drawn line is to be one of `PageText::rules`, in points +pub const RULE_MIN_WIDTH: f32 = 40.0; +/// the highest a drawn line is to be one of `PageText::rules`, in points +pub const RULE_MAX_HEIGHT: f32 = 2.0; + +/// what a field is filled in with: a checkbox's `true` or `false`; else +/// text: a text field's, a choice's (one of its options, or any text for a +/// combo box that may be edited), a radio button's state when on (its +/// widget's `onValue`, or "Off" for none) +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(untagged)] +pub enum FillValue { + Checked(bool), + Text(String), +} + +/// a field (by its full name) and what it is filled in with +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct Fill { + pub field: String, + pub value: FillValue, +} + +/// what `setFills` changed: the pages whose fields look different now +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] +pub struct Filled { + pub pages: Vec, +} + /// a signature in the PDF: where it is and what it says of itself; whether /// it is valid is for the app's verify.rs to say #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] @@ -274,6 +350,30 @@ mod tests { assert_eq!(serde_json::from_value::(written).unwrap(), request); let fields: Request = serde_json::from_value(json!({"id": 4, "op": "fields"})).unwrap(); assert_eq!(fields.op, Op::Fields); + let fills = Request { + id: 6, + op: Op::SetFills { + fills: vec![ + Fill { + field: "agree".into(), + value: FillValue::Checked(true), + }, + Fill { + field: "name".into(), + value: FillValue::Text("Ada".into()), + }, + ], + }, + }; + let written = serde_json::to_value(&fills).unwrap(); + assert_eq!( + written, + json!({"id": 6, "op": "setFills", "fills": [ + {"field": "agree", "value": true}, + {"field": "name", "value": "Ada"} + ]}) + ); + assert_eq!(serde_json::from_value::(written).unwrap(), fills); let ok = Response { id: 3, @@ -307,6 +407,11 @@ mod tests { request["page"] = json!(0); request["scale"] = json!(1); } + "text" => request["page"] = json!(0), + "setFills" => { + request["fills"] = + json!([{"field": "a", "value": true}, {"field": "b", "value": "x"}]) + } "cancel" => request["target"] = json!(1), _ => {} } @@ -321,6 +426,9 @@ mod tests { ErrorKind::TooComplex, ErrorKind::Cancelled, ErrorKind::Engine, + ErrorKind::Repaired, + ErrorKind::Encrypted, + ErrorKind::MissingGlyphs, ] { assert_eq!(serde_json::to_value(kind).unwrap(), kind.name()); } diff --git a/src-tauri/pdf/ipc/src/serve.rs b/src-tauri/pdf/ipc/src/serve.rs index fedff86d..af3d96d5 100644 --- a/src-tauri/pdf/ipc/src/serve.rs +++ b/src-tauri/pdf/ipc/src/serve.rs @@ -201,8 +201,29 @@ fn answer( })?; Ok((value, bitmap.rgba)) } + Op::Text { page } => { + if page >= doc.info().pages { + return Err(ErrorKind::BadRequest); + } + json(doc.text(page).map_err(failed)?) + } Op::Fields => json(doc.fields().map_err(failed)?), Op::Signatures => json(doc.signatures().map_err(failed)?), + Op::SetFills { fills } => { + let pages = doc.set_fills(&fills).map_err(failed)?; + json(crate::protocol::Filled { pages }) + } + Op::Save => { + let info = doc.info(); + if info.repaired { + return Err(ErrorKind::Repaired); + } + if info.encrypted { + return Err(ErrorKind::Encrypted); + } + let bytes = doc.save_incremental().map_err(failed)?; + Ok((serde_json::json!({}), bytes)) + } Op::Open { .. } | Op::Ping | Op::Cancel { .. } => Err(ErrorKind::BadRequest), } } diff --git a/src-tauri/pdf/ipc/tests/client.rs b/src-tauri/pdf/ipc/tests/client.rs index 7f30e340..6e8eb616 100644 --- a/src-tauri/pdf/ipc/tests/client.rs +++ b/src-tauri/pdf/ipc/tests/client.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use std::time::{Duration, Instant}; use blank_pdf_ipc::client::{HelperConfig, PdfError, Supervisor, Timeouts}; -use blank_pdf_ipc::protocol::{ErrorKind, Op, PageInfo}; +use blank_pdf_ipc::protocol::{ErrorKind, Field, Fill, FillValue, Op, PageInfo, PageText}; fn config(args: &[&str]) -> HelperConfig { HelperConfig { @@ -319,3 +319,51 @@ fn refuses_a_pdf_bigger_than_a_frame() { ); assert!(!pdf.failed()); } + +fn fill(field: &str, value: FillValue) -> Fill { + Fill { + field: field.into(), + value, + } +} + +#[test] +fn keeps_the_fills_for_a_new_helper() { + let pdf = supervisor(&["ok", "crash"]); + let fills = vec![ + fill("name", FillValue::Text("Ada".into())), + fill("agree", FillValue::Checked(true)), + ]; + assert_eq!(pdf.set_fills(fills.clone()).unwrap(), [0, 1]); + assert_eq!( + pdf.set_fills(vec![fill("name", FillValue::Text("中".into()))]) + .unwrap_err(), + PdfError::Engine(ErrorKind::MissingGlyphs) + ); + assert_eq!(pdf.fills(), fills, "a refused set keeps the last"); + + assert_eq!(render(&pdf, 1).unwrap_err(), PdfError::Crashed); + // the new helper has them again + let fields: Vec = pdf.ask(Op::Fields).unwrap(); + let values: Vec<&str> = fields.iter().map(|field| field.value.as_str()).collect(); + assert_eq!(values, ["Ada", "Yes"]); + let saved = pdf.save().unwrap(); + let opened = serde_json::to_vec(&serde_json::json!({ "pages": ["ok", "crash"] })).unwrap(); + assert!(saved.starts_with(&opened) && saved.len() > opened.len()); + + assert_eq!(pdf.set_fills(vec![]).unwrap(), [0, 1], "both changed back"); + assert_eq!(pdf.save().unwrap(), opened); +} + +#[test] +fn reads_a_pages_text() { + let pdf = supervisor(&["ok", "ok"]); + let text: PageText = pdf.ask(Op::Text { page: 1 }).unwrap(); + let words: Vec<&str> = text.words.iter().map(|word| word.text.as_str()).collect(); + assert_eq!(words, ["page", "1"]); + assert_eq!(text.rules, [[1.0, 4.0, 8.0, 0.5]]); + assert_eq!( + pdf.ask::(Op::Text { page: 2 }).unwrap_err(), + PdfError::Engine(ErrorKind::BadRequest) + ); +} From eb30265372e837e5c9e0884266581d1c0eb478f3 Mon Sep 17 00:00:00 2001 From: Florian Purchess Date: Mon, 12 Oct 2026 09:57:44 +0200 Subject: [PATCH 07/50] feat: ask a PDF tab's helper for page text, fields and fills, and give the signer the filled bytes --- .claude/rules/pdf.md | 4 +- src-tauri/src/lib.rs | 3 + src-tauri/src/pdf_view/commands.rs | 71 +++++++++++++- src-tauri/src/pdf_view/mod.rs | 144 ++++++++++++++++++++++++++++- src/pdf/ipc.test.ts | 28 ++++++ src/pdf/ipc.ts | 76 ++++++++++++++- 6 files changed, 319 insertions(+), 7 deletions(-) create mode 100644 src/pdf/ipc.test.ts diff --git a/.claude/rules/pdf.md b/.claude/rules/pdf.md index 7ea247c4..563a215f 100644 --- a/.claude/rules/pdf.md +++ b/.claude/rules/pdf.md @@ -31,14 +31,14 @@ Blank opens PDFs in tabs of their own, shows their pages as MuPDF draws them, an - **AGPL:** MuPDF is AGPL-3.0, like Blank. The helper's corresponding source is Artifex's tag plus our glue and shim in this repository (`src-tauri/pdf`); the FAQ says so. - **The shim** (`helper/shim/blank_mupdf.c`) is the only code that calls MuPDF: every entry point is inside `fz_try`, so an exception is a status, never an abort. It has its own `fz_context` with a counting allocator (the memory cap: past `--memory-limit-mb`, 1536 MB, an allocation fails and the request answers `tooComplex`), a cookie to cancel a render, our font loader, and writes the form's fields and signatures as JSON (`blank_form_json`, its walk capped in fields, depth and visits). A cancel is `cancelled` even after a refused allocation; a refused allocation, MuPDF's out of memory or its limits are `tooComplex`, and so is a page whose drawing swallowed an error after a refused allocation (`cookie.errors`), rather than showing it half drawn. Rust calls it through `helper/src/mupdf.rs` only, never mupdf-sys's bindings. - **Fonts:** a font a PDF names but doesn't embed comes from the system through `blank-system-fonts` (`src-tauri/system-fonts`, the lookup the app's `fallback_fonts` uses too): a family by name, or the CJK font of an Adobe ordering by script and language. Only fonts a PDF may embed (`embeddable`). -- **The protocol** (`src-tauri/pdf/ipc`, crate `blank-pdf-ipc`, no MuPDF, no Tauri): frames `"BPF1" | header len u32 LE | body len u64 LE | JSON header | body` (`frame.rs`) over the helper's stdin/stdout; version 1 (`protocol.rs`): the helper's first frame is a hello with `protocol` and `ops`; ops `open` (body: the PDF), `pages` (at most `PAGES_PER_REQUEST`, 4096, a request: `Supervisor::pages` batches them, since one answer for ~16k pages would outgrow the 1 MB header), `render` (answer body: RGBA, opaque, at most `MAX_PIXELS`; `serve` and the client both check the size against the pixels), `fields`, `signatures`, `cancel`, `ping`. `fields` and `signatures` (and the shim's form JSON) aren't asked by the app in this step: they are there for filling in forms and placing signature fields next, and the signature check reads the PDF itself (`signing/verify.rs`). An answer too big for a frame is sent as `tooComplex`, and `write_frame` refuses to write one. A breaking change raises `PROTOCOL` and the magic; a new op is optional within v1 (`unknownOp`); the app doesn't read the hello's `ops` yet. Positions are display points: from the top left of the crop box after `/Rotate`, UserUnit applied. `engine.rs` is the `PdfEngine`/`PdfDoc` traits MuPDF implements (a PDFium one would fit behind the same traits), `serve.rs` the helper's loop (a reader thread that takes `cancel` at once, the engine on the main thread), `client.rs` the app's side. +- **The protocol** (`src-tauri/pdf/ipc`, crate `blank-pdf-ipc`, no MuPDF, no Tauri): frames `"BPF1" | header len u32 LE | body len u64 LE | JSON header | body` (`frame.rs`) over the helper's stdin/stdout; version 1 (`protocol.rs`): the helper's first frame is a hello with `protocol` and `ops`; ops `open` (body: the PDF), `pages` (at most `PAGES_PER_REQUEST`, 4096, a request: `Supervisor::pages` batches them, since one answer for ~16k pages would outgrow the 1 MB header), `render` (answer body: RGBA, opaque, at most `MAX_PIXELS`; `serve` and the client both check the size against the pixels), `text` (`PageText`: the words of the page's contents and annotations, not the widgets' values, each box spanning at most 0.8 em above the baseline and 0.25 below, as MuPDF's quads span the font's line gap too; and `rules`, drawn thin horizontals ≥ 40 pt wide, ≤ 2 pt high, not white), `fields`, `signatures` (not asked by the app: the signature check reads the PDF itself, `signing/verify.rs`), `setFills` (stateless: the whole set, on a document opened afresh from the bytes, appearances regenerated by MuPDF with `pdf_update_page` on the pages loaded together; answers the pages whose fields' values changed; a refused set keeps the last), `save` (an incremental update of the filled document, from a document opened afresh, since MuPDF counts what it wrote as saved; refused `repaired`, also for bytes before the header, whose offsets `pdf_write_document` gets wrong, and `encrypted`), `cancel`, `ping`. **Fills** (`Fill`): a checkbox `true`/`false`, a radio group the `onValue` of a widget or "Off", a choice one of its options by what it shows (its export value is set) or any text in an editable combo box, a text field its text (within `/MaxLen`, one line unless multiline). MuPDF draws every field's value in the base 14 font its `/DA` names (else Helvetica) with Windows-1252, and anything else in fonts it doesn't embed or as a replacement character, so a value with characters outside that encoding or font is refused `missingGlyphs` (`shows` in the shim), never set. Read-only, push button and signature fields, unknown names and a name that only starts a field's are `badRequest`. The supervisor keeps the fills that held (`Supervisor::set_fills`, `fills`) and gives a new helper them again after `open`; `save`'s answer may be as big as the PDF and a page's pixels (`max_answer`). An answer too big for a frame is sent as `tooComplex`, and `write_frame` refuses to write one. A breaking change raises `PROTOCOL` and the magic; a new op is optional within v1: the client asks no op the hello's `ops` lacks (`unknownOp`), and an engine without one answers `unknownOp` (the traits' defaults). Positions are display points: from the top left of the crop box after `/Rotate`, UserUnit applied. `engine.rs` is the `PdfEngine`/`PdfDoc` traits MuPDF implements (a PDFium one would fit behind the same traits), `serve.rs` the helper's loop (a reader thread that takes `cancel` at once, the engine on the main thread), `client.rs` the app's side. - **The helper's stdout carries frames only:** at start it dups fd 1 to a private fd for the frames and points fd 1 at stderr (`main.rs`). Its stderr is null in release builds (MuPDF's warnings may quote the PDF), inherited in debug builds. - **Limits** (`client.rs`, `limits.rs`): the counting allocator first; then Linux's `RLIMIT_AS` of 4 GB (no `PR_SET_PDEATHSIG`: it fires when the *thread* that spawned the helper ends, and tokio's blocking threads retire when idle; stdin's end covers Blank going); on macOS a thread that kills a helper whose footprint passes 2.5 GB (it stops, under a lock, before the helper is reaped, so it never kills a reused PID); on Windows a job object (2.5 GB, killed with Blank) and no console window. Timeouts: start 10 s, open 20 s, render and the rest 15 s; then `cancel`, and 1 s later a kill (a hang). stdin's end makes the helper exit. A helper whose stdout closes or stops making sense is a crash; it gets 0.5 s to exit, then it's killed, so the client never waits on a live one. The client reads answers of at most `MAX_PIXELS × 4` bytes through a queue of 4. A PDF over 512 MB (`MAX_BODY`) is `tooComplex` before any helper starts. `idle_since` and `stop` never wait for a request that runs (`try_lock`): a helper in use is neither idle nor stopped. `pdf_open` puts the tab in `PdfTabs` before it opens, so a tab closed meanwhile is seen and its helper stopped. - **Restart policy** (`Supervisor`): a crash or a hang is a failure. After the first, the next request starts a new helper, which opens the PDF again (the bytes the supervisor holds, or the file read again and checked against its SHA-256); the request that failed isn't asked again. After the second, `Failed`: the tab shows "Blank can't show this PDF any more", and **Open again** makes a new supervisor. A helper unused for 2 minutes, or past 4 running, least recently used first, is stopped, which isn't a failure. ## The app's side (`src-tauri/src/pdf_view`) -- `PdfTabs` keeps each tab's supervisor, file and SHA-256; `pdf_open` reads the file, starts the helper and returns the pages (`Opened`); `pdf_render` returns the pixels with the width and height as two u32 LE at the end (raw RGBA through `tauri::ipc::Response`, no image protocol); `pdf_cancel`, `pdf_close`; `pdf_verify` reads the file again, checks its SHA-256, and checks its signatures (below); `pdf_print` reads it again (`read_checked`) and hands it to the system's print dialog through `print::prepare`/`print::send`, so a PDF of up to 512 MB never travels through the webview. +- `PdfTabs` keeps each tab's supervisor, file and SHA-256; `pdf_open` reads the file, starts the helper and returns the pages (`Opened`); `pdf_text`, `pdf_fields` and `pdf_set_fills` (src/pdf/ipc.ts: `pageText`, `pdfFields`, `setFills`, types `PageText`, `PdfField`, `Fill`) ask the tab's helper; `filled_bytes` gives the signer the file (read again and checked against its SHA-256: `FileChanged`/`FileMissing`) as it is without fills, else with them as MuPDF's incremental update, which must start with the file's bytes; words and values never reach the log; `pdf_render` returns the pixels with the width and height as two u32 LE at the end (raw RGBA through `tauri::ipc::Response`, no image protocol); `pdf_cancel`, `pdf_close`; `pdf_verify` reads the file again, checks its SHA-256, and checks its signatures (below); `pdf_print` reads it again (`read_checked`) and hands it to the system's print dialog through `print::prepare`/`print::send`, so a PDF of up to 512 MB never travels through the webview. - Failures reach the webview as a kind (`PdfError::kind`) and the log as `PDF: : `; the path of a PDF is logged at info when it opens (`PDF: opening `), never in a warning. Never what a PDF holds. A file read again that isn't the one opened is told apart: `missing` (it went), `changed` (it holds another PDF), `unreadable`: `pdf_render` turns `PdfError::Source` (a helper stopped while idle that can't open it again) into one of them (`file_problem`), `pdf_print` fails with the `PrintError` kinds `missing`/`changed`/`notOpen`, and `pdf_verify` says it in its message. - `helper_path()` finds the helper: on Linux first in `/../lib/blank/blank-pdf` (`/usr/lib/blank` of the packages, `usr/lib/blank` in the AppImage, `$SNAP/usr/lib/blank`; Tauri's `resource_dir` is the same folder), then next to the app (`current_exe().parent()/blank-pdf[.exe]`), where macOS's and Windows' packages have it and every build has it in `target//` (tauri-build copies the sidecar or the resource there); in debug builds `BLANK_PDF_HELPER` may point elsewhere. diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 1d2ae72d..8d7238f2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -105,6 +105,9 @@ pub fn run() { pdf_view::commands::pdf_close, pdf_view::commands::pdf_verify, pdf_view::commands::pdf_print, + pdf_view::commands::pdf_text, + pdf_view::commands::pdf_fields, + pdf_view::commands::pdf_set_fills, signing::commands::signing_certificate, signing::commands::signing_create_certificate, signing::commands::signing_import, diff --git a/src-tauri/src/pdf_view/commands.rs b/src-tauri/src/pdf_view/commands.rs index 552fa810..960733e7 100644 --- a/src-tauri/src/pdf_view/commands.rs +++ b/src-tauri/src/pdf_view/commands.rs @@ -2,7 +2,8 @@ //! a kind (`PdfError::kind`, "unreadable" for a file that can't be read, or //! for a file read again "missing" when it has gone and "changed" when it //! isn't the one opened) and the log as the same kind with what was asked: -//! never the PDF's text. The path of a PDF opened is logged at info. +//! never the PDF's text, nor its words or the values of its fields. The +//! path of a PDF opened is logged at info. use std::io; use std::path::{Path, PathBuf}; @@ -12,7 +13,7 @@ use std::time::Instant; use crate::print::{self, Meta, PrintError, PrintSetup, Sent}; use blank_pdf_ipc::client::{PdfError, Supervisor}; use blank_pdf_ipc::frame::MAX_BODY; -use blank_pdf_ipc::protocol::{ErrorKind, Op, PageInfo}; +use blank_pdf_ipc::protocol::{ErrorKind, Field, Fill, Op, PageInfo, PageText}; use serde::Serialize; use tauri::{AppHandle, State}; @@ -49,8 +50,21 @@ fn file_problem(path: &Path) -> &'static str { } } +/// the kind of a helper's failure, logged with `context`: for a helper that +/// stopped while idle and can't open the file again, why (`file_problem`) +fn helper_failed(context: &'static str, path: PathBuf) -> impl Fn(PdfError) -> String { + move |error| match error { + PdfError::Source => { + let problem = file_problem(&path); + log::warn!("PDF: {context}: {problem}"); + problem.to_string() + } + error => failed(context)(error), + } +} + /// what reading a PDF tab's file again found, by `read_checked`'s error -fn read_problem(error: &io::Error) -> &'static str { +pub(super) fn read_problem(error: &io::Error) -> &'static str { match error.kind() { io::ErrorKind::InvalidData => "changed", io::ErrorKind::NotFound => "missing", @@ -166,6 +180,57 @@ pub async fn pdf_render( Ok(tauri::ipc::Response::new(pixels)) } +/// the words and thin horizontal lines of page `page` (from 0) of tab +/// `tab`, in display points +#[tauri::command] +pub async fn pdf_text( + tabs: State<'_, PdfTabs>, + tab: String, + page: usize, +) -> Result { + let pdf = tabs.get(&tab).ok_or("closed")?; + tabs.stop_idle(Some(&tab), Instant::now()); + blocking(move || { + pdf.supervisor + .ask(Op::Text { page }) + .map_err(helper_failed("text", pdf.path.clone())) + }) + .await +} + +/// the form's fields of tab `tab`'s PDF, with the values its fills gave +#[tauri::command] +pub async fn pdf_fields(tabs: State<'_, PdfTabs>, tab: String) -> Result, String> { + let pdf = tabs.get(&tab).ok_or("closed")?; + tabs.stop_idle(Some(&tab), Instant::now()); + blocking(move || { + pdf.supervisor + .ask(Op::Fields) + .map_err(helper_failed("fields", pdf.path.clone())) + }) + .await +} + +/// fills the form of tab `tab`'s PDF in with `fills`, always the whole set, +/// which its helper keeps (a new helper gets them again); gives the pages +/// to draw again. A value refused ("missingGlyphs", "badRequest") leaves +/// the last set as it was +#[tauri::command] +pub async fn pdf_set_fills( + tabs: State<'_, PdfTabs>, + tab: String, + fills: Vec, +) -> Result, String> { + let pdf = tabs.get(&tab).ok_or("closed")?; + tabs.stop_idle(Some(&tab), Instant::now()); + blocking(move || { + pdf.supervisor + .set_fills(fills) + .map_err(helper_failed("fill in", pdf.path.clone())) + }) + .await +} + /// stops the render of `ticket` in tab `tab`, if it runs #[tauri::command] pub fn pdf_cancel(tabs: State<'_, PdfTabs>, tab: String, ticket: u64) { diff --git a/src-tauri/src/pdf_view/mod.rs b/src-tauri/src/pdf_view/mod.rs index 14236fc8..9b5e306f 100644 --- a/src-tauri/src/pdf_view/mod.rs +++ b/src-tauri/src/pdf_view/mod.rs @@ -12,9 +12,12 @@ use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; use std::time::{Duration, Instant}; -use blank_pdf_ipc::client::{HelperConfig, Source, Supervisor, Timeouts}; +use blank_pdf_ipc::client::{HelperConfig, PdfError, Source, Supervisor, Timeouts}; +use blank_pdf_ipc::protocol::{ErrorKind, Fill}; use sha2::{Digest, Sha256}; +use crate::signing::commands::SigningError; + /// a helper unused this long is stopped, which isn't a failure const IDLE: Duration = Duration::from_secs(120); /// the most helpers that run at once; the least recently used stops @@ -135,6 +138,71 @@ pub fn source(path: PathBuf, sha256: String, first: Arc<[u8]>) -> Source { ) } +/// the bytes to sign of tab `tab`'s PDF, the file at `path`, while it still +/// holds the PDF of `sha256` (else `FileChanged` or `FileMissing`): the file +/// as it is without `fills`, else the file with the fills appended as an +/// incremental update by the tab's helper (MuPDF writes R1, the signer then +/// appends R2). Waits for the helper: call it on a blocking thread +pub fn filled_bytes( + tabs: &PdfTabs, + tab: &str, + path: &Path, + sha256: &str, + fills: &[Fill], +) -> Result, SigningError> { + let bytes = read_checked(path, sha256).map_err(|error| { + let problem = commands::read_problem(&error); + log::warn!("PDF: read to fill in: {problem}"); + match problem { + "changed" => SigningError::FileChanged, + "missing" => SigningError::FileMissing, + _ => SigningError::Failed("Blank can't read this PDF again".into()), + } + })?; + if fills.is_empty() { + return Ok(bytes.to_vec()); + } + let pdf = tabs + .get(tab) + .ok_or_else(|| SigningError::Failed("This PDF isn't open any more".into()))?; + // the tab opened another version of the file meanwhile + if pdf.sha256 != sha256 { + return Err(SigningError::FileChanged); + } + let saved = (|| { + if pdf.supervisor.fills() != fills { + pdf.supervisor.set_fills(fills.to_vec())?; + } + pdf.supervisor.save() + })() + .map_err(|error| { + log::warn!("PDF: save the fills: {}", error.kind()); + SigningError::Failed(fill_refusal(error).into()) + })?; + // what MuPDF wrote must leave the file as it is, and only add to it + if !saved.starts_with(&bytes) { + log::warn!("PDF: save the fills: not an update of the file"); + return Err(SigningError::Failed( + fill_refusal(PdfError::Engine(ErrorKind::Engine)).into(), + )); + } + Ok(saved) +} + +/// why the fills couldn't be saved, in words for the user +fn fill_refusal(error: PdfError) -> &'static str { + match error { + PdfError::Engine(ErrorKind::Repaired) => { + "Blank can't fill in this PDF: it's damaged, and only a repaired copy could be read" + } + PdfError::Engine(ErrorKind::Encrypted) => "Blank can't fill in an encrypted PDF", + PdfError::Engine(ErrorKind::MissingGlyphs) => { + "A field's font can't show some of the characters filled in" + } + _ => "Blank couldn't fill in this PDF", + } +} + /// where the helper is: on Linux in `../lib/blank` beside the app's /// directory, where the packages put it (`bundle.resources` of /// `tauri.linux.conf.json`: `/usr/lib/blank` of the .deb, the .rpm and the @@ -230,6 +298,80 @@ mod tests { assert_eq!(to_stop(&many, Some("f"), now), ["a"]); } + /// the helper `make pdf-helper` built, which the app's build needs + fn built_helper() -> PathBuf { + let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("binaries"); + std::fs::read_dir(&dir) + .unwrap() + .map(|entry| entry.unwrap().path()) + .find(|path| { + let name = path.file_name().unwrap().to_string_lossy(); + name.starts_with("blank-pdf") && !name.ends_with(".stamp") + }) + .expect("make pdf-helper") + } + + #[test] + fn gives_the_signer_the_file_with_its_fills_as_an_update() { + use blank_pdf_ipc::protocol::FillValue; + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("form.pdf"); + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("pdf/fixtures/form.pdf"); + std::fs::copy(fixture, &path).unwrap(); + let original: Arc<[u8]> = std::fs::read(&path).unwrap().into(); + let sha = sha256(&original); + let config = HelperConfig { + program: built_helper(), + args: vec!["--memory-limit-mb".into(), "256".into()], + quiet: true, + timeouts: Timeouts::default(), + }; + let supervisor = + Supervisor::new(config, source(path.clone(), sha.clone(), original.clone())); + let tabs = PdfTabs::default(); + tabs.insert( + "t".into(), + Arc::new(PdfTab { + supervisor: Arc::new(supervisor), + path: path.clone(), + sha256: sha.clone(), + }), + ); + let fills = [Fill { + field: "name".into(), + value: FillValue::Text("Ada".into()), + }]; + + let plain = filled_bytes(&tabs, "t", &path, &sha, &[]).unwrap(); + assert_eq!(plain, &original[..], "no fills: the file as it is"); + let filled = filled_bytes(&tabs, "t", &path, &sha, &fills).unwrap(); + assert!(filled.starts_with(&original) && filled.len() > original.len()); + assert_eq!(tabs.get("t").unwrap().supervisor.fills(), fills); + assert!(matches!( + filled_bytes(&tabs, "other", &path, &sha, &fills), + Err(SigningError::Failed(_)) + )); + let refused = [Fill { + field: "name".into(), + value: FillValue::Text("中文".into()), + }]; + assert!(matches!( + filled_bytes(&tabs, "t", &path, &sha, &refused), + Err(SigningError::Failed(message)) if message.contains("font") + )); + + std::fs::write(&path, b"%PDF-1.7 another").unwrap(); + assert!(matches!( + filled_bytes(&tabs, "t", &path, &sha, &fills), + Err(SigningError::FileChanged) + )); + std::fs::remove_file(&path).unwrap(); + assert!(matches!( + filled_bytes(&tabs, "t", &path, &sha, &[]), + Err(SigningError::FileMissing) + )); + } + #[test] fn reads_the_file_again_only_while_it_is_the_same() { let dir = tempfile::tempdir().unwrap(); diff --git a/src/pdf/ipc.test.ts b/src/pdf/ipc.test.ts new file mode 100644 index 00000000..60ecbed0 --- /dev/null +++ b/src/pdf/ipc.test.ts @@ -0,0 +1,28 @@ +import { invoke } from "@tauri-apps/api/core"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { pageText, pdfFields, setFills } from "./ipc"; + +vi.mock("@tauri-apps/api/core", () => ({ invoke: vi.fn() })); + +describe("the PDF form's calls", () => { + beforeEach(() => { + vi.mocked(invoke).mockResolvedValue([]); + }); + + it("asks for a page's text, the fields and the fills by tab", async () => { + await pageText("t1", 2); + expect(invoke).toHaveBeenLastCalledWith("pdf_text", { tab: "t1", page: 2 }); + await pdfFields("t1"); + expect(invoke).toHaveBeenLastCalledWith("pdf_fields", { tab: "t1" }); + const fills = [ + { field: "name", value: "Ada" }, + { field: "agree", value: true }, + ]; + await setFills("t1", fills); + expect(invoke).toHaveBeenLastCalledWith("pdf_set_fills", { + tab: "t1", + fills, + }); + }); +}); diff --git a/src/pdf/ipc.ts b/src/pdf/ipc.ts index 7be9c012..51f746d0 100644 --- a/src/pdf/ipc.ts +++ b/src/pdf/ipc.ts @@ -24,7 +24,9 @@ export interface OpenedPdf { encrypted: boolean; } -// why a call failed, as the native side says it (`PdfError::kind`), or +// why a call failed, as the native side says it (`PdfError::kind`: e.g. +// "missingGlyphs" for a fill with characters the field's font can't show, +// "badRequest" for one the field can't take), or // "unreadable" for a file that can't be read, "closed" for a tab whose PDF // was closed meanwhile, and for a file read again (a helper that stopped // opens it again) "missing" when it has gone, "changed" when it holds @@ -41,6 +43,9 @@ export type PdfFailure = | "tooComplex" | "cancelled" | "engine" + | "repaired" + | "encrypted" + | "missingGlyphs" | "timedOut" | "crashed" | "hung" @@ -114,3 +119,72 @@ export const closePdf = (tab: string) => invoke("pdf_close", { tab }); */ export const verifyPdf = (tab: string) => invoke("pdf_verify", { tab }); + +// a box on a page, [x, y, width, height] in display points +export type PdfRect = [number, number, number, number]; + +// a page's text: its words in reading order, and the thin horizontal lines +// drawn on it (at least 40 pt wide, at most 2 pt high), where people sign or +// write. Never logged: it is what the PDF says +export interface PageText { + words: { text: string; rect: PdfRect }[]; + rules: PdfRect[]; +} + +/** + * pageText reads the words and lines of the page `page` (from 0) of the tab + * `tab` + */ +export const pageText = (tab: string, page: number) => + invoke("pdf_text", { tab, page }); + +export type PdfFieldKind = + "text" | "checkbox" | "radio" | "combo" | "list" | "button" | "signature"; + +// a field of a PDF's form, as protocol.rs's Field +export interface PdfField { + // its full name, which a Fill names + name: string; + kind: PdfFieldKind; + // its /TU, what it is called for people + label: string | null; + // its value: a checkbox's or radio button group's state ("Off" for none) + value: string; + // a choice's options + options: string[]; + flags: { + readOnly: boolean; + required: boolean; + multiline: boolean; + comb: boolean; + maxLen: number | null; + }; + // a signature field that holds a signature + signed: boolean; + // where it shows; `onValue` is a checkbox's or radio button's state when on + widgets: { page: number; rect: PdfRect; onValue: string | null }[]; +} + +/** + * pdfFields reads the form's fields of the tab's PDF, with the values its + * fills gave them + */ +export const pdfFields = (tab: string) => + invoke("pdf_fields", { tab }); + +// what a field (by its full name) is filled in with: a checkbox's true or +// false; text for a text field, a choice (one of its options, any text in a +// combo box that may be edited) and a radio button group (the `onValue` of +// the button chosen, "Off" for none) +export interface Fill { + field: string; + value: string | boolean; +} + +/** + * setFills fills the form of the tab's PDF in with `fills`, always the whole + * set: the pages to draw again. Refused with "missingGlyphs" or + * "badRequest", the last set stays. Values are never logged + */ +export const setFills = (tab: string, fills: Fill[]) => + invoke("pdf_set_fills", { tab, fills }); From abd6af420e48fb351ede7adfc5d52bdddb995b8c Mon Sep 17 00:00:00 2001 From: Florian Purchess Date: Mon, 12 Oct 2026 10:01:26 +0200 Subject: [PATCH 08/50] feat: sign a PDF tab with its form filled in, as MuPDF's revision under the signature, and share the PDF's field types --- src-tauri/src/signing/commands.rs | 78 +++++-------------- src-tauri/src/signing/commands/received.rs | 10 +-- src-tauri/src/signing/commands/tests.rs | 2 +- src/pdf/fields.ts | 55 +++---------- src/signing/__fixtures__/labels/form-pdf.json | 8 ++ 5 files changed, 43 insertions(+), 110 deletions(-) diff --git a/src-tauri/src/signing/commands.rs b/src-tauri/src/signing/commands.rs index 68ea948d..98c45440 100644 --- a/src-tauri/src/signing/commands.rs +++ b/src-tauri/src/signing/commands.rs @@ -12,7 +12,9 @@ use serde::{Deserialize, Deserializer, Serialize}; use tauri::{AppHandle, Manager}; use zeroize::Zeroizing; -use crate::pdf_view::{read_checked, PdfTabs}; +pub use blank_pdf_ipc::protocol::{Fill, FillValue}; + +use crate::pdf_view::{filled_bytes, PdfTabs}; use crate::signing::certificate::CertificateInfo; use crate::signing::keys::{self, Identity, OpenError}; use crate::signing::pdf::sign::{update_pdf, Other, PlacedStamp, Plan as UpdatePlan, Signer, Spot}; @@ -430,22 +432,6 @@ pub struct Plan { pub timestamp_server: Option, } -/// a form field's new value, which the PDF tab's helper writes (see -/// pdf_view): text, or whether a box is ticked -#[derive(Debug, Clone, Deserialize)] -#[serde(untagged)] -pub enum FillValue { - Text(String), - Ticked(bool), -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Fill { - /// the field's full name - pub field: String, - pub value: FillValue, -} - /// where the PDF to sign comes from #[derive(Debug, Deserialize)] #[serde(tag = "kind", rename_all = "camelCase")] @@ -672,37 +658,6 @@ fn parts(body: &[u8]) -> Option> { rest.is_empty().then_some(out) } -/// tab_bytes is the PDF of a PDF tab to sign: its file, read again, while -/// it is the one the tab opened, with the tab's fills written into it by -/// the tab's helper as a revision of their own. -/// -/// INTEGRATION POINT: the fills are `pdf_view::filled_bytes(tabs, tab, -/// path, sha256, fills)` of the helper's stream; until it lands here, a PDF -/// with fills isn't signed. -fn tab_bytes( - _tabs: &PdfTabs, - _tab: &str, - path: &Path, - sha256: &str, - fills: &[Fill], -) -> Answer> { - let bytes = read_checked(path, sha256).map_err(|error| { - log::warn!("failed to read a PDF tab's file to sign: {}", error.kind()); - match error.kind() { - std::io::ErrorKind::InvalidData => SigningError::FileChanged, - std::io::ErrorKind::NotFound => SigningError::FileMissing, - _ => SigningError::Failed("Blank can't read this PDF again".into()), - } - })?; - if !fills.is_empty() { - log::warn!("a PDF tab's fills can't be written yet"); - return Err(SigningError::Failed( - "Blank can't fill in this PDF's fields yet".into(), - )); - } - Ok(bytes.to_vec()) -} - /// signing_sign_pdf adds stamps, empty signature fields for others and a /// signature to a PDF, and returns it. Its header `blank-signing` holds, /// as JSON, the passphrase (null: no signature, the stamps and fields @@ -712,7 +667,6 @@ fn tab_bytes( #[tauri::command] pub async fn signing_sign_pdf( app: AppHandle, - tabs: tauri::State<'_, PdfTabs>, request: tauri::ipc::Request<'_>, ) -> Answer { let refused = || SigningError::Failed(SIGN_FAILED.into()); @@ -729,21 +683,29 @@ pub async fn signing_sign_pdf( log::warn!("a request to sign had a body that isn't parts"); return Err(refused()); } - let pdf = match &header.source { - Source::Body => None, - Source::Tab { - tab, - path, - sha256, - fills, - } => Some(tab_bytes(&tabs, tab, path, sha256, fills)?), - }; let p12 = match header.passphrase { Some(_) => Some(read_key(&app)?), None => None, }; let body = body.clone(); let signed = tauri::async_runtime::spawn_blocking(move || { + // a PDF tab's file as it still is, with its fills as MuPDF's + // revision; this waits for the tab's helper + let pdf = match &header.source { + Source::Body => None, + Source::Tab { + tab, + path, + sha256, + fills, + } => Some(filled_bytes( + &app.state::(), + tab, + path, + sha256, + fills, + )?), + }; let parts = parts(&body).ok_or_else(refused)?; let pdf = match &pdf { Some(pdf) => pdf.as_slice(), diff --git a/src-tauri/src/signing/commands/received.rs b/src-tauri/src/signing/commands/received.rs index 59227465..f1134e0e 100644 --- a/src-tauri/src/signing/commands/received.rs +++ b/src-tauri/src/signing/commands/received.rs @@ -768,21 +768,21 @@ fn reads_a_tabs_file_while_it_is_the_one_opened() { let path = written(dir.path(), "a.pdf", FORM); let sha = crate::pdf_view::sha256(FORM); let tabs = PdfTabs::default(); - assert_eq!(tab_bytes(&tabs, "t", &path, &sha, &[]).unwrap(), FORM); + assert_eq!(filled_bytes(&tabs, "t", &path, &sha, &[]).unwrap(), FORM); assert!(matches!( - tab_bytes(&tabs, "t", &path, "00", &[]), + filled_bytes(&tabs, "t", &path, "00", &[]), Err(SigningError::FileChanged) )); assert!(matches!( - tab_bytes(&tabs, "t", &dir.path().join("gone.pdf"), &sha, &[]), + filled_bytes(&tabs, "t", &dir.path().join("gone.pdf"), &sha, &[]), Err(SigningError::FileMissing) )); - // fills wait for the helper's revision (pdf_view::filled_bytes) + // fills need the tab's helper, which a tab that isn't open has not let fill = Fill { field: "Name".into(), value: FillValue::Text("Ada".into()), }; - assert!(tab_bytes(&tabs, "t", &path, &sha, &[fill]).is_err()); + assert!(filled_bytes(&tabs, "t", &path, &sha, &[fill]).is_err()); } #[test] diff --git a/src-tauri/src/signing/commands/tests.rs b/src-tauri/src/signing/commands/tests.rs index 9c86b37e..f2cbec11 100644 --- a/src-tauri/src/signing/commands/tests.rs +++ b/src-tauri/src/signing/commands/tests.rs @@ -623,7 +623,7 @@ fn reads_the_header_the_webview_encodes() { panic!("not a tab"); }; assert_eq!(sha256, "ab"); - assert!(matches!(&fills[1].value, FillValue::Ticked(true))); + assert!(matches!(&fills[1].value, FillValue::Checked(true))); assert!(read.plan.stamps[0].repeat); assert!(matches!( read.plan.signature, diff --git a/src/pdf/fields.ts b/src/pdf/fields.ts index 8edb3e64..7d18148c 100644 --- a/src/pdf/fields.ts +++ b/src/pdf/fields.ts @@ -1,50 +1,13 @@ -// What the helper reads of a PDF's pages for signing it: the words and drawn -// lines of a page (the `text` op), and the fields of its form (the protocol's -// `Field`, src-tauri/pdf/ipc/src/protocol.rs, as serde writes it). Every rect -// is `[x, y, width, height]` in display points, from the top left of the -// page as it shows. - -export type Rect = [number, number, number, number]; - -// the words of a page, in reading order as MuPDF gives them, and its drawn -// thin horizontals (strokes and filled rects at least 40 pt wide and at most -// 2 pt high) -export interface PageText { - words: { text: string; rect: Rect }[]; - rules: Rect[]; -} +import type { PdfField, PdfRect } from "./ipc"; -export type PdfFieldKind = - "text" | "checkbox" | "radio" | "combo" | "list" | "button" | "signature"; +// What the helper reads of a PDF's pages for signing it: the words and drawn +// lines of a page (the `text` op) and the fields of its form, as src/pdf/ipc.ts +// gets them. Every rect is `[x, y, width, height]` in display points, from the +// top left of the page as it shows. -export interface PdfFieldFlags { - readOnly: boolean; - required: boolean; - multiline: boolean; - comb: boolean; - maxLen: number | null; -} +export type { PageText, PdfField, PdfFieldKind } from "./ipc"; +export type Rect = PdfRect; +export type PdfFieldFlags = PdfField["flags"]; // where a field shows: one of its widgets, on the page `page` (from 0) -export interface PdfWidget { - page: number; - rect: Rect; - // a checkbox's or radio button's state when it is on - onValue: string | null; -} - -// a field of the PDF's form, with its full name and what it inherits -export interface PdfField { - name: string; - kind: PdfFieldKind; - // its `/TU`, what it is called for people - label: string | null; - value: string; - // a choice's options - options: string[]; - flags: PdfFieldFlags; - // a signature field that holds a signature - signed: boolean; - // its widgets on the pages; none for one that shows nowhere - widgets: PdfWidget[]; -} +export type PdfWidget = PdfField["widgets"][number]; diff --git a/src/signing/__fixtures__/labels/form-pdf.json b/src/signing/__fixtures__/labels/form-pdf.json index c9382f49..12f836af 100644 --- a/src/signing/__fixtures__/labels/form-pdf.json +++ b/src/signing/__fixtures__/labels/form-pdf.json @@ -10,6 +10,14 @@ "text": "A-17", "rect": [352, 128.82, 21.12, 9.25] }, + { + "text": "Apple", + "rect": [352, 205.82, 25.57, 9.25] + }, + { + "text": "Pear", + "rect": [352, 216.82, 21.12, 9.25] + }, { "text": "Green", "rect": [102, 228.82, 27.79, 9.25] From cd2b5b7375c4978dd08c825561800c5068b7aaa1 Mon Sep 17 00:00:00 2001 From: Florian Purchess Date: Mon, 12 Oct 2026 10:19:35 +0200 Subject: [PATCH 09/50] feat: one Sign dialog for exports and PDF tabs, with Sign as, initials on every page, a reason, and empty fields for the other signers of an export --- .claude/rules/signing.md | 13 +- docs/guide/signing.md | 23 ++- src/editor/commands/exportAs.ts | 12 +- src/editor/commands/signing.test.ts | 199 +++++++++++++++++++++-- src/editor/commands/signing.ts | 115 +++++++------ src/engine/pdf.test.ts | 42 +++++ src/engine/pdf.ts | 17 +- src/engine/pdfJob.test.ts | 19 +++ src/engine/pdfJob.ts | 13 ++ src/exporters/types.ts | 6 + src/scss/_signing.scss | 12 ++ src/signing/exportPlan.test.ts | 91 +++++++++++ src/signing/exportPlan.ts | 79 +++++++++ src/signing/initials.ts | 56 +++++++ src/signing/targets.ts | 1 + src/state/dialogs.ts | 56 ++++++- src/state/signing.ts | 5 + src/storage.ts | 9 + src/ui/SignDialog.test.ts | 244 +++++++++++++++++++++++++--- src/ui/SignDialog.vue | 197 ++++++++++++++++++---- src/ui/signDialogModel.test.ts | 67 +++++++- src/ui/signDialogModel.ts | 125 +++++++++++++- 22 files changed, 1253 insertions(+), 148 deletions(-) create mode 100644 src/signing/exportPlan.test.ts create mode 100644 src/signing/exportPlan.ts create mode 100644 src/signing/initials.ts diff --git a/.claude/rules/signing.md b/.claude/rules/signing.md index 1c2d7c66..461008ab 100644 --- a/.claude/rules/signing.md +++ b/.claude/rules/signing.md @@ -43,16 +43,23 @@ Blank signs what the user writes by exporting a signed PDF; a markdown file neve - The key and certificate are `key.p12` and `certificate.cer` in the app config's `signing` folder. The key never goes to the webview: the passphrase comes once per signing (`signing_check_passphrase` before anything is exported, then `signing_sign_pdf`, see below), and a signed PDF is checked with `verify` before it's returned (`check_signed`: valid and covering the whole file, and with a timestamp server a timestamp that holds, else `TimestampUnusable`). `key.p12` is written 0600 on unix, both files through temp files, under one lock (`FILES`) with removing and exporting them; a key there is replaced only when the call says `replace` (else `keyExists`, which the webview turns into `ConfirmDialog`'s "Replace your key?"), and if the certificate can't be put in place the old key comes back (`keep_files`). An email that isn't ASCII is refused with a sentence (rcgen can't hold it), in `certificateDialogModel.ts` and natively. The signature names the certificate's holder (its common name) as /Name, never the block's name. - Errors are typed, never matched by their text: `keys::OpenError` and `signing::Refusal` (`Restricted`, `Certified`, `NoTimestamp`, `TimestampRefused`, `TimestampUnusable`, whose text is the user's sentence). They're logged by context and kind only (`forTheLog`), never their text, which crypto crates may fill with what they got; the user gets fixed sentences (`SigningError`: `wrongPassphrase`, `noCertificate`, `keyExists`, `failed`). Passphrases are `Zeroizing`. - The crypto crates are optimized in debug builds (the dev profile in `Cargo.toml`), or opening a key takes minutes; the tests make keys with few rounds (`create_certificate_with`). -- Webview: `src/signing/ipc.ts` (every call; `signingMessage(error, fallback)` with a fallback per action, `signingKind` for the log), `src/signing/certificate.ts` (`signingCertificate`, read once; made, imported, exported, removed after `confirmRemoval`'s question in `ConfirmDialog.vue`, which offers Back up key… first; failures through `reportSigningFailure`), `CertificateDialog.vue` (request `certificateDialog`), `CertificateRows.vue` in Settings ▸ Signing, and the Sign dialog's choice of a digital signature with the passphrase. +- Webview: `src/signing/ipc.ts` (every call; `signingMessage(error, fallback)` with a fallback per action, `signingKind` for the log), `src/signing/certificate.ts` (`signingCertificate`, read once; made, imported, exported, removed after `confirmRemoval`'s question in `ConfirmDialog.vue`, which offers Back up key… first; failures through `reportSigningFailure`), `CertificateDialog.vue` (request `certificateDialog`), `CertificateRows.vue` in Settings ▸ Signing, and the Sign dialog's Sign as with the passphrase. - Making a key or checking a passphrase takes seconds: the Certificate dialog says so ("Creating…", a note in a live region) and the Sign dialog ("Checking…"), and neither can be cancelled meanwhile (Cancel and Esc wait), since the certificate or the PDF would still come. Removing the signature or initials asks nothing: they're made again in a moment. ## Export signed PDF -`exportSigned` (`export.signed_pdf`, `Mod-Alt-Shift-p`): no block, a notification; no signature yet, the signature dialog first; then `SignDialog.vue` (request `signDialog`, closed by `closeRequests` before a tab switch: which block (`targetsOf`/`chosenOf`, `src/signing/targets.ts`), the place, digital or a picture only and the passphrase, the Word warning for imported documents; a certificate made from it switches it to digital), then `runExport` (`exportAs.ts`, the save dialog first, " signed" before the extension of the file captured when the command ran) on the editor's state when the dialog closes, the block still at its place or else the one of its number, never another. It signs the tab that showed when it ran (`activeTabId`, checked before the Sign dialog opens and when it's submitted), and nothing once another shows; the Sign dialog stops after its passphrase check when its request was closed meanwhile. The signature and certificate dialogs stay open across a tab switch: they work on the user's profile, not the document. The PDF's /Name is the certificate's name, else Settings ▸ Signing's, never the block's (the dialog warns when the chosen block names someone else, `namedNote`); the place is the one the page shows, the block's own (the dialog then shows `placeNote` instead of the field) else the one typed, and that is what /Location gets and what is remembered. For a digital signature, its `finish` signs the exported PDF (`signExport`), the field over the block's box, which the export's engine gives (`signatureBox`, `PdfJob.signedPos`); no box or a failure writes nothing, and says so (`signingFailure`, with how to sign without a timestamp while a server is set). +`exportSigned` (`export.signed_pdf`, `Mod-Alt-Shift-p`): no block, a notification; no signature yet, the signature dialog first; then `SignDialog.vue` (request `signDialog` with `mode: "export"`, see below), then `runExport` (`exportAs.ts`, the save dialog first, " signed" before the extension of the file captured when the command ran) on the editor's state when the dialog closes, the block still at its place or else the one of its number, never another. It signs the tab that showed when it ran (`activeTabId`, checked before the Sign dialog opens and when it's submitted), and nothing once another shows; the Sign dialog stops after its passphrase check when its request was closed meanwhile. The signature and certificate dialogs stay open across a tab switch: they work on the user's profile, not the document. The PDF's /Name is the certificate's name, else Settings ▸ Signing's, never the block's (the dialog warns when the chosen block names someone else, `namedNote`); the place is the one the page shows, the block's own (the dialog then shows `placeNote` instead of the field) else the one typed, and that is what /Location gets and what is remembered, with the initials' choice (`signingInitials`, localforage like `signingPlace`, written once a signed PDF is). + +- **The boxes:** the export's engine gives the filled in block's box (`ExportResult.signatureBox`, `PdfJob.signedPos`) and those of the other blocks (`ExportContext.others` → `otherBoxes`, `PdfJob.otherPos`; null for one not on the pages), display points from the page's top left, read before the engine is freed, on the shared engine and in the worker alike. +- **The update** (`finishExport`, the plan from `exportRequest` in `src/signing/exportPlan.ts`, one `signing_sign_pdf` in body mode): `others` an empty field per other block named after its name, else its role; the initials (`src/signing/initials.ts`: `INITIALS_STAMP`, 56 × 28 pt, `INITIALS_MARGIN` 36 pt from the bottom and the side chosen, `initialsRect` on the layout's page size) one repeated stamp as part 1, made with `stampPdf` after `loadMarks`; and for a digital signature the field over the block (no box, nothing written). Without a passphrase it's the stamps-only update, and only with others or initials: else the PDF is written as krilla made it. A failure writes nothing, and says so (`signingFailure`, with how to sign without a timestamp while a server is set). + +## The Sign dialog (`SignDialog.vue`, `signDialogModel.ts`) + +One dialog for both flows, `SignDialogRequest.mode` `"export"` or `"pdf"` (a PDF tab), closed by `closeRequests` before a tab switch; it hands `SignChoices` (`pos`, null in a PDF tab, `place`, `reason`, `passphrase`, null for no digital signature, `initials`, `open`) to `submit`. Rows: **Sign as** (`#sign-dialog-how`, a `MenuButton` select: "‹name› · your certificate", "No digital signature (only the visible signature)", then Create your certificate… without one and Manage…, which closes the dialog for Settings ▸ Signing; its menu gives the focus to the passphrase when there is one; a certificate made from it switches to it), **Sign in** (export, several blocks), **Initials** (`#sign-dialog-initials`, On every page, with the corner below it; disabled with `initialsProblem`: the request's `initials.reason`, e.g. a PDF others signed, else no saved initials, with Add your initials…; a PDF tab follows it at once through `setInitials`), **Place**, **Reason** (`plan.reason`), **Passphrase** (selected on errors). Notes: the time source, a PDF tab's `stampNote` (an invisible signature without a signature stamp, or for no digital signature "Place your signature on the page first…", and Sign… waits) and `emptyFieldsNote`, the others' fields in the export. `confirmPages` (an imported Word document) adds "I checked every page", which Sign… waits for (`canSign`); a PDF tab has "Open the signed PDF" (on). ## Stamps and signing received PDFs (native, `signing/pdf/`) -- One command, `signing_sign_pdf`: the header `blank-signing` is percent-encoded JSON `{ passphrase, source, plan }` (`SignRequest` in `src/signing/ipc.ts`, `Header` in `commands.rs`), the body the parts (`encodeParts`: a little-endian u32 count, then each part as a little-endian u64 length and its bytes). `source` is `{kind:"body"}` (the export flow: part 0 is the PDF) or `{kind:"tab", tab, path, sha256, fills}` (read again, `fileChanged`/`fileMissing` when it isn't the file the tab opened; the fills come from the tab's helper as their own revision through `tab_bytes`, the one place that calls it). `plan.stamps[].part` and the signature's `stamp` index the parts. A `null` passphrase writes the stamps and fields alone, no signature. Pages are from 1, rects display points (`PageGeometry` converts). +- One command, `signing_sign_pdf`: the header `blank-signing` is percent-encoded JSON `{ passphrase, source, plan }` (`SignRequest` in `src/signing/ipc.ts`, `Header` in `commands.rs`), the body the parts (`encodeParts`: a little-endian u32 count, then each part as a little-endian u64 length and its bytes). `source` is `{kind:"body"}` (the export flow: part 0 is the PDF) or `{kind:"tab", tab, path, sha256, fills}` (read again, `fileChanged`/`fileMissing` when it isn't the file the tab opened; the fills come from the tab's helper as their own revision through `pdf_view::filled_bytes`, on the blocking thread). `plan.stamps[].part` and the signature's `stamp` index the parts. A `null` passphrase writes the stamps and fields alone, no signature. Pages are from 1, rects display points (`PageGeometry` converts). - `xobject.rs`: a stamp's one-page PDF (from Blank's engine) becomes a Form XObject, `/BBox [0 0 w h]`: loaded with bounded `LoadOptions`, every object its resources reach copied once with new ids (through a queue, so cycles end; page tree and structure objects become null), `/StructParent(s)` dropped, marked content with an `/MCID` turned into `/Artifact BMC`. For a PDF/A-1 target (`pdfa_part`, the XMP's `pdfaid:part`) the transparency goes: no `/Group`, soft masks of images drawn over white, graphics states opaque. - `stamp.rs`: `draw_on_page` wraps the page's content in `q … Q` and draws its stamps after it (`cm` from `PageGeometry::placement`, so /Rotate, the crop box's origin and UserUnit hold), with a direct copy of the page's effective (maybe inherited) `/Resources`. A repeated stamp is one XObject drawn on every page at the same place, moved (and made smaller) into smaller pages (`clamp`). `appearance` is a widget's appearance: empty, or a stamp fitted into it. - `sign.rs` (`update_pdf`): the stamps, then empty `/Sig` fields for the others who sign ("Signature of ‹name›", unique, no periods), then the signature: an existing empty field keeps its name and gets `/V` and the stamp as its `/AP /N`; a new `SignatureN` shows its stamp in the page content on a PDF nobody signed yet and as its appearance on signed or certified ones; or invisible. Without a signer, a signature's stamp goes into the page content. diff --git a/docs/guide/signing.md b/docs/guide/signing.md index 802fe64d..0ecdf857 100644 --- a/docs/guide/signing.md +++ b/docs/guide/signing.md @@ -33,30 +33,35 @@ A contract for two people has two blocks, one for each. On the pages and in a pr **Export signed PDF…** (`Mod` `Alt` `Shift` `P`) signs your document: 1. If you haven't made your signature yet, Blank asks for it first. -2. Choose which block is yours, if the document has several (the one with your name is chosen for you), and where you sign. Blank remembers the place for the next time. A block that gives its own place is signed there, and the dialog says so; a block with someone else's name gets a warning. The dialog also shows the date that goes under your signature: it's written in the document's language, which you choose in the status bar. +2. In the sign dialog: + - **Sign as:** your certificate, for a [digital signature](#digital), or **No digital signature (only the visible signature)**. + - **Sign in:** which block is yours, if the document has several (the one with your name is chosen for you). A block with someone else's name gets a warning. + - **Initials: On every page** puts your initials in a bottom corner of every page, **Bottom right** or **Bottom left**, as many contracts ask. Make your initials first ([above](#your-signature)), or with **Add your initials…** right there. + - **Place:** where you sign. A block that gives its own place is signed there, and the dialog says so. + - **Reason**, if you like, e.g. _I agree to these terms_: PDF apps show it with a digital signature. + + Blank remembers the place and the initials for the next time. The dialog also shows the date that goes under your signature: it's written in the document's language, which you choose in the status bar. 3. Choose where to save the PDF. Blank suggests the document's name with _signed_ after it (_Untitled signed.pdf_ for a document you haven't saved yet), so the unsigned file stays as it is. The block shows your signature, your name and the place and today's date, written as **Settings ▸ Signing ▸ Date** says: the long form in the document's language (e.g. _8 October 2026_ in British English), its short form, or _2026-10-08_. The markdown file itself never holds your signature. ### Two or more signers {#others} -You sign only your own block; the other blocks keep their empty lines. Send the signed PDF to the others, and they sign it one of two ways: +You sign only your own block. Every other block keeps its empty line and gets an empty signature field over it, named after who signs there (e.g. _Signature of Ben Sample_, or _Signature of Landlord_ for a block with a role only). Send the signed PDF to the others, and they sign it: +- **In any PDF app, or in Blank:** they click their field and sign with a signature of their own. Opened in Blank, the PDF shows their field as the place to sign. If you signed digitally, PDF apps then show that something was added after you signed, and your signature stays intact; an app that rewrites the whole file breaks it. - **On paper:** they print it, sign on their line by hand, and scan it or send it back. -- **In their PDF app**, with a signature of their own. If you signed digitally, PDF apps then show that something was added after you signed; an app that rewrites the whole file breaks your digital signature. -Blank can't sign PDFs you received yet, so the others can't sign your PDF in Blank. - -A Word document you opened in Blank is signed the same way. Blank lays the pages out a little differently from Word, so check every page before you send it, or ask the sender for a PDF. +A Word document you opened in Blank is signed the same way. Blank lays the pages out a little differently from Word, so check every page before you send it, or ask the sender for a PDF: **Sign…** waits until you switch on **I checked every page**. ## Digital signatures {#digital} A picture of a signature is easy to copy. A digital signature isn't: your certificate signs the PDF, so PDF apps show whether anything changed after you signed. That it was you, they show once the reader trusts your certificate, e.g. after comparing its fingerprint with you (see [below](#check)). -1. **Create your certificate** in **Settings ▸ Signing ▸ Certificate**, with your name and a passphrase that protects it (at least 8 characters), or from the sign dialog with **Create certificate…**. Creating the key takes a few seconds. Or **Import key file…** to use a key file (`.p12` or `.pfx`) you already have, e.g. from your employer or a certificate authority. -2. When you sign, choose **Digital, with your certificate** and type your passphrase. Blank checks it, then signs the PDF after exporting it; the signature block shows your signature as before. The PDF names you as on your certificate. +1. **Create your certificate** in **Settings ▸ Signing ▸ Certificate**, with your name and a passphrase that protects it (at least 8 characters), or from the sign dialog with **Sign as ▸ Create your certificate…**. Creating the key takes a few seconds. Or **Import key file…** to use a key file (`.p12` or `.pfx`) you already have, e.g. from your employer or a certificate authority. +2. When you sign, choose your certificate under **Sign as** (_‹your name› · your certificate_, chosen for you once you have one) and type your passphrase. Blank checks it, then signs the PDF after exporting it; the signature block shows your signature as before. The PDF names you as on your certificate. -If you forgot your passphrase, sign with **Picture only**, or **Remove** your certificate and create a new one. If anything goes wrong while signing, nothing is saved. +If you forgot your passphrase, sign with **No digital signature**, or **Remove** your certificate and create a new one. If anything goes wrong while signing, nothing is saved. Your key never leaves your computer and is kept in the `signing` folder, protected by your passphrase. Blank can't recover a forgotten passphrase: use **Back up key…** to keep a copy somewhere safe. **Save certificate…** saves your certificate alone (a `.cer` file), which you can send to people who should trust your signatures. **Remove** asks first, since Blank can't bring a removed key back: signatures you made with it stay valid, but you can't sign with it again. Back up your key before, if you may need it. diff --git a/src/editor/commands/exportAs.ts b/src/editor/commands/exportAs.ts index 4536cf40..c16289a8 100644 --- a/src/editor/commands/exportAs.ts +++ b/src/editor/commands/exportAs.ts @@ -11,7 +11,7 @@ import { type exporterFunc, } from "../../exporters"; import { describePaper, layoutWarnings } from "../../layout/describe"; -import { resolveLayout } from "../../layout/resolve"; +import { type Layout, resolveLayout } from "../../layout/resolve"; import { localeUnit } from "../../layout/paper"; import { frontmatterOf } from "../../markdown"; import { extname } from "../../paths"; @@ -59,14 +59,15 @@ export interface ExportRequest { suffix?: string; // more of the context: the pages chosen to print, or the signature block // a signed PDF fills in - context?: Pick; + context?: Pick; // told once the file is written, e.g. to remember where the user signed written?: () => void; // what the notification says after what was exported, e.g. that the PDF // is signed digitally done?: string; // what is written instead of the export's contents, e.g. the PDF signed - finish?: (result: ExportResult) => Promise; + // with the page it was laid out on + finish?: (result: ExportResult, layout: Layout) => Promise; } /** @@ -149,7 +150,10 @@ export const runExport = async ( ); const result = await exporter(state, { docPath, layout, ...context }); const { warnings, pages } = result; - await writeFile(dest, finish ? await finish(result) : result.contents); + await writeFile( + dest, + finish ? await finish(result, layout) : result.contents, + ); written?.(); // e.g. "Exported 3 A4 pages", or for Word, which lays out the pages diff --git a/src/editor/commands/signing.test.ts b/src/editor/commands/signing.test.ts index dc50b07d..434ea3e9 100644 --- a/src/editor/commands/signing.test.ts +++ b/src/editor/commands/signing.test.ts @@ -12,12 +12,15 @@ import { signatureDialog, signDialog, signingCertificate, + type SignChoices, + signingInitials, signingPlace, } from "../../state"; import { config } from "../../config"; import { flushPromises } from "../../test/async"; import { createState, createTestView, doc, p } from "../../test/editor"; import type { SavedMark } from "../../signing/profile"; +import { pageGeometry } from "../../layout/resolve"; import { editMark, exportSigned, openSignatureDialog } from "./signing"; const saveSettings = vi.hoisted(() => vi.fn()); @@ -45,6 +48,8 @@ vi.mock("../../signing/ipc", async (importOriginal) => ({ ...(await importOriginal()), ...ipc, })); +const stampPdf = vi.hoisted(() => vi.fn()); +vi.mock("../../pdf/stampEngine", () => ({ stampPdf })); vi.mock("../../signing/certificate", () => ({ loadCertificate: vi.fn(async () => {}), openCertificateDialog: vi.fn(), @@ -69,6 +74,22 @@ const run = (...blocks: Parameters) => { }; const BOX = { page: 0, x: 70, y: 600, width: 230, height: 70 }; +// what the Sign dialog hands on +const choices = ( + pos: number, + place: string, + passphrase: string | null, + more: Partial = {}, +): SignChoices => ({ + pos, + place, + reason: "", + passphrase, + initials: { on: false, side: "right" }, + open: true, + ...more, +}); + const withName = (name: string) => { config.value = { ...config.value, @@ -148,7 +169,7 @@ describe("signing", () => { run(p("Text"), signature({ role: "Tenant" })); await flushPromises(); const { targets } = signDialog.value!; - signDialog.value!.submit(targets[0].pos, "Hamburg", null); + signDialog.value!.submit(choices(targets[0].pos, "Hamburg", null)); await flushPromises(); expect(save).toHaveBeenCalledWith( expect.objectContaining({ defaultPath: "/docs/lease signed.pdf" }), @@ -173,7 +194,9 @@ describe("signing", () => { vi.mocked(save).mockResolvedValue(null); run(p("Text"), signature()); await flushPromises(); - signDialog.value!.submit(signDialog.value!.targets[0].pos, "Hamburg", null); + signDialog.value!.submit( + choices(signDialog.value!.targets[0].pos, "Hamburg", null), + ); await flushPromises(); expect(signingPlace.value).toBe("Berlin"); }); @@ -189,7 +212,7 @@ describe("signing", () => { const request = signDialog.value!; expect(request.chosen).toBe(request.targets[1].pos); // a block that names nobody is signed in the user's name - request.submit(request.targets[0].pos, "", null); + request.submit(choices(request.targets[0].pos, "", null)); await flushPromises(); expect(toPDF.mock.calls[0][1].signed.signing.name).toBe("Ada Lovelace"); }); @@ -200,7 +223,7 @@ describe("signing", () => { const { targets } = signDialog.value!; // typed meanwhile, before the block view.dispatch(view.state.tr.insertText("More ", 1)); - signDialog.value!.submit(targets[0].pos, "", null); + signDialog.value!.submit(choices(targets[0].pos, "", null)); await flushPromises(); const [state, context] = toPDF.mock.calls[0]; expect(state.doc).toBe(view.state.doc); @@ -220,11 +243,11 @@ describe("signing", () => { await flushPromises(); const request = signDialog.value!; expect(await request.check("wrong")).toMatch( - /^The passphrase is wrong\. Forgot it\? Sign with a picture only/, + /^The passphrase is wrong\. Forgot it\? Sign with no digital signature/, ); ipc.checkPassphrase.mockResolvedValue(undefined); expect(await request.check("right")).toBeNull(); - request.submit(request.targets[0].pos, "Hamburg", "right"); + request.submit(choices(request.targets[0].pos, "Hamburg", "right")); await flushPromises(); expect(ipc.signPdf).toHaveBeenCalledWith( { @@ -248,6 +271,150 @@ describe("signing", () => { ); }); + describe("with other signers", () => { + const OTHER = { page: 1, x: 300, y: 100, width: 200, height: 60 }; + const blocks = () => + run( + p("Text"), + signature({ role: "Tenant" }), + signature({ role: "Landlord", name: "Bea Sample" }), + signature({ role: "Witness" }), + ); + beforeEach(() => { + toPDF.mockImplementation(async (_state, context) => ({ + contents: new Uint8Array([1]), + warnings: [], + pages: 2, + signatureBox: BOX, + // the second one isn't on the pages + otherBoxes: context.others.map((_: number, at: number) => + at === 1 ? null : OTHER, + ), + })); + ipc.signPdf.mockResolvedValue(new Uint8Array([2])); + }); + + it("gives the others empty fields, named after who signs, beside the signature", async () => { + blocks(); + await flushPromises(); + const request = signDialog.value!; + expect(request.mode).toBe("export"); + request.submit(choices(request.targets[0].pos, "", "right")); + await flushPromises(); + // the export gives the boxes of the other blocks + const context = toPDF.mock.calls[0][1]; + expect(context.others).toEqual([ + request.targets[1].pos, + request.targets[2].pos, + ]); + const [asked, parts] = ipc.signPdf.mock.calls[0]; + expect(asked.plan.others).toEqual([ + { page: 2, rect: [300, 100, 200, 60], name: "Bea Sample" }, + ]); + expect(asked.plan.signature).toEqual({ + kind: "new", + page: 1, + rect: [70, 600, 230, 70], + }); + expect(parts).toEqual([new Uint8Array([1])]); + expect(writeFile).toHaveBeenCalledWith( + "/docs/lease signed.pdf", + new Uint8Array([2]), + ); + }); + + it("names a block without a name by its role, and adds the fields without a digital signature", async () => { + blocks(); + await flushPromises(); + const request = signDialog.value!; + request.submit( + choices(request.targets[1].pos, "", null, { reason: "I agree" }), + ); + await flushPromises(); + const [asked] = ipc.signPdf.mock.calls[0]; + // stamps and fields alone + expect(asked.passphrase).toBeNull(); + expect(asked.plan.signature).toEqual({ kind: "invisible" }); + expect( + asked.plan.others.map((other: { name: string }) => other.name), + ).toEqual(["Tenant"]); + expect(asked.plan.timestampServer).toBeNull(); + expect(writeFile).toHaveBeenCalledWith( + "/docs/lease signed.pdf", + new Uint8Array([2]), + ); + }); + }); + + it("puts the initials on every page, in the corner chosen, and remembers it", async () => { + signingInitials.value = { on: false, side: "right" }; + savedMarks.value = { signature: SIGNATURE, initials: SIGNATURE }; + stampPdf.mockResolvedValue(new Uint8Array([7])); + ipc.signPdf.mockResolvedValue(new Uint8Array([2])); + run(p("Text"), signature()); + await flushPromises(); + const request = signDialog.value!; + expect(request.initials).toMatchObject({ on: false, side: "right" }); + request.submit( + choices(request.targets[0].pos, "", null, { + reason: "I agree", + initials: { on: true, side: "left" }, + }), + ); + await flushPromises(); + expect(stampPdf).toHaveBeenCalledWith( + expect.objectContaining({ image: "initials" }), + 56, + 28, + ); + const { height } = pageGeometry(toPDF.mock.calls[0][1].layout); + const [asked, parts] = ipc.signPdf.mock.calls[0]; + expect(asked.plan.stamps).toEqual([ + { part: 1, page: 1, rect: [36, height - 64, 56, 28], repeat: true }, + ]); + expect(asked.plan.reason).toBe("I agree"); + expect(parts).toEqual([new Uint8Array([1]), new Uint8Array([7])]); + expect(signingInitials.value).toEqual({ on: true, side: "left" }); + signingInitials.value = { on: false, side: "right" }; + }); + + it("writes the PDF as the engine made it without a digital signature, others or initials", async () => { + run(p("Text"), signature()); + await flushPromises(); + signDialog.value!.submit( + choices(signDialog.value!.targets[0].pos, "", null), + ); + await flushPromises(); + expect(ipc.signPdf).not.toHaveBeenCalled(); + expect(writeFile).toHaveBeenCalledWith( + "/docs/lease signed.pdf", + new Uint8Array([1]), + ); + }); + + it("gives the reason to the digital signature", async () => { + toPDF.mockResolvedValue({ + contents: new Uint8Array([1]), + warnings: [], + pages: 1, + signatureBox: BOX, + }); + ipc.signPdf.mockResolvedValue(new Uint8Array([2])); + run(p("Text"), signature()); + await flushPromises(); + signDialog.value!.submit( + choices(signDialog.value!.targets[0].pos, "", "right", { + reason: "I agree to these terms", + }), + ); + await flushPromises(); + expect(ipc.signPdf.mock.calls[0][0].plan).toMatchObject({ + reason: "I agree to these terms", + stamps: [], + others: [], + }); + }); + it("writes nothing when signing fails, and says why", async () => { toPDF.mockResolvedValue({ contents: new Uint8Array([1]), @@ -261,7 +428,9 @@ describe("signing", () => { }); run(p("Text"), signature()); await flushPromises(); - signDialog.value!.submit(signDialog.value!.targets[0].pos, "", "right"); + signDialog.value!.submit( + choices(signDialog.value!.targets[0].pos, "", "right"), + ); await flushPromises(); expect(writeFile).not.toHaveBeenCalled(); expect(sendNotification).toHaveBeenCalledWith( @@ -284,7 +453,9 @@ describe("signing", () => { }); run(p("Text"), signature()); await flushPromises(); - signDialog.value!.submit(signDialog.value!.targets[0].pos, "", "right"); + signDialog.value!.submit( + choices(signDialog.value!.targets[0].pos, "", "right"), + ); await flushPromises(); expect(writeFile).not.toHaveBeenCalled(); expect(vi.mocked(sendNotification).mock.lastCall![0]).toMatchObject({ @@ -300,7 +471,7 @@ describe("signing", () => { await flushPromises(); const request = signDialog.value!; activeTabId.value = "two"; - request.submit(request.targets[0].pos, "", null); + request.submit(choices(request.targets[0].pos, "", null)); await flushPromises(); expect(save).not.toHaveBeenCalled(); expect(toPDF).not.toHaveBeenCalled(); @@ -332,7 +503,7 @@ describe("signing", () => { const { targets } = signDialog.value!; // the first block taken out: the second moves to another position view.dispatch(view.state.tr.delete(targets[0].pos, targets[0].pos + 1)); - signDialog.value!.submit(targets[1].pos, "", null); + signDialog.value!.submit(choices(targets[1].pos, "", null)); await flushPromises(); expect(toPDF).not.toHaveBeenCalled(); expect(sendNotification).toHaveBeenCalledWith( @@ -364,7 +535,7 @@ describe("signing", () => { const request = signDialog.value!; expect(request.signer).toBe("Ada Lovelace"); expect(request.targets[0].place).toBe("Paris"); - request.submit(request.targets[0].pos, "Hamburg", "right"); + request.submit(choices(request.targets[0].pos, "Hamburg", "right")); await flushPromises(); expect(toPDF.mock.calls[0][1].signed.signing).toMatchObject({ name: "Ada Lovelace", @@ -383,7 +554,9 @@ describe("signing", () => { it("never signs a PDF without the block's place for the signature", async () => { run(p("Text"), signature()); await flushPromises(); - signDialog.value!.submit(signDialog.value!.targets[0].pos, "", "right"); + signDialog.value!.submit( + choices(signDialog.value!.targets[0].pos, "", "right"), + ); await flushPromises(); expect(ipc.signPdf).not.toHaveBeenCalled(); expect(writeFile).not.toHaveBeenCalled(); @@ -396,6 +569,8 @@ describe("signing", () => { await flushPromises(); expect(signDialog.value!.title).toBe("offer"); expect(signDialog.value!.warnings[0]).toContain("Word"); + // Sign… waits for the user to say they checked every page + expect(signDialog.value!.confirmPages).toBe(true); }); it("asks for the signature first when there is none", async () => { diff --git a/src/editor/commands/signing.ts b/src/editor/commands/signing.ts index 9c30650c..8c66b633 100644 --- a/src/editor/commands/signing.ts +++ b/src/editor/commands/signing.ts @@ -17,9 +17,16 @@ import { signatureDialog, signDialog, signingCertificate, + type SignChoices, signingDates, + signingInitials, signingPlace, } from "../../state"; +import { type Layout, pageGeometry } from "../../layout/resolve"; +import { stampPdf } from "../../pdf/stampEngine"; +import { INITIALS_BOX, INITIALS_STAMP } from "../../signing/initials"; +import { type ExportSigning, exportRequest } from "../../signing/exportPlan"; +import { openSettings } from "./settings"; import { basename } from "../../paths"; import { PDF_FILTER } from "../../formats"; import { toPDF } from "../../exporters"; @@ -132,7 +139,7 @@ const signerName = () => */ const passphraseProblem = (error: unknown) => signingKind(error) === "wrongPassphrase" - ? "The passphrase is wrong. Forgot it? Sign with a picture only, or remove your certificate in Settings ▸ Signing and create a new one." + ? "The passphrase is wrong. Forgot it? Sign with no digital signature, or remove your certificate in Settings ▸ Signing and create a new one." : signingMessage(error, "Blank couldn't open your key"); /** @@ -184,6 +191,7 @@ export const exportSigned = (): Command => (state, dispatch, view) => { const name = config.value.signing.name; const named = file.path ?? file.importedFrom; signDialog.value = { + mode: "export", title: named ? basename(named).replace(/\.[^.]+$/, "") : "Untitled", targets, chosen: chosenOf(targets, name), @@ -202,6 +210,11 @@ export const exportSigned = (): Command => (state, dispatch, view) => { "Blank's layout of this Word document can differ from Word's. Check every page before you send it, or ask the sender for a PDF.", ] : [], + confirmPages: !!file.importedFrom, + initials: signingInitials.value, + // the block shows the signature + stamped: true, + emptyFields: 0, check: async (passphrase) => { try { await checkPassphrase(passphrase); @@ -219,15 +232,21 @@ export const exportSigned = (): Command => (state, dispatch, view) => { if (!signingCertificate.value) back(); }); }, - submit: (pos, place, passphrase) => { + createInitials: (opener) => + void openSignatureDialog("initials", focusBack(opener)), + manage: () => { refocus(); - if (!shown()) return; + openSettings("signing")(view.state, view.dispatch); + }, + submit: (choices) => { + refocus(); + if (!shown() || choices.pos === null) return; + const pos = choices.pos; signNow(view, file, request, { + ...choices, pos, index: targets.findIndex((target) => target.pos === pos), png: signature.png, - place, - passphrase, }); }, cancel: refocus, @@ -253,7 +272,8 @@ export const exportSigned = (): Command => (state, dispatch, view) => { /** * signNow exports the document as it is now, the dialogs closed, with the * block chosen filled in: the one still at `pos`, else the one of its - * number among them; with neither, it signs nothing + * number among them; with neither, it signs nothing. The other blocks get + * empty signature fields, named after who signs them. */ const signNow = ( view: EditorView, @@ -264,14 +284,10 @@ const signNow = ( index, png, place, + reason, passphrase, - }: { - pos: number; - index: number; - png: string; - place: string; - passphrase: string | null; - }, + initials, + }: SignChoices & { pos: number; index: number; png: string }, ) => { const state = view.state; const targets = targetsOf(state.doc); @@ -283,6 +299,7 @@ const signNow = ( }); return; } + const others = targets.filter((other) => other !== target); // the place the page shows: the block's own, else where the user signs const shownPlace = target.place || place; const signer = signerName(); @@ -307,57 +324,51 @@ const signNow = ( ...signingDates.value, }, }, + ...(others.length ? { others: others.map((other) => other.pos) } : {}), }, written: () => { if (shownPlace) signingPlace.value = shownPlace; + signingInitials.value = initials; announce("Signed PDF exported"); }, done: how, - ...(passphrase === null - ? {} - : { - finish: (result: ExportResult) => - signExport(result, passphrase, shownPlace), - }), + finish: (result: ExportResult, layout: Layout) => + finishExport(result, { + passphrase, + place: shownPlace, + reason, + timestampServer: config.value.signing.timestampServer, + signatureBox: result.signatureBox, + others: others.map((other, at) => ({ + name: other.name || other.role, + box: result.otherBoxes?.[at] ?? null, + })), + initials: initials.on ? initials.side : null, + page: pageGeometry(layout), + }), }); }; /** - * signExport signs an exported PDF with the user's key, its field over the - * signature block the export filled in, signed at `place`; the native side - * names the certificate's holder as who signed it. A failure stops the - * export with what went wrong, so nothing unsigned is written under the - * signed name. + * finishExport adds to an exported PDF what the engine didn't write: the + * user's initials on every page, the others' empty signature fields and, + * with the passphrase, the digital signature, its field over the block the + * export filled in. Without any of them the PDF stays as the engine wrote + * it. A failure stops the export with what went wrong, so nothing unsigned + * is written under the signed name. */ -const signExport = async ( - result: ExportResult, - passphrase: string, - place: string, -) => { - const box = result.signatureBox; - if (!box) throw new Error("the signature block isn't in the PDF"); - const timestampServer = config.value.signing.timestampServer; - try { - return await signPdf( - { - passphrase, - source: { kind: "body" }, - plan: { - // the block is in the page content already, from the engine - stamps: [], - signature: { - kind: "new", - page: box.page + 1, - rect: [box.x, box.y, box.width, box.height], - }, - others: [], - location: place || null, - reason: null, - timestampServer: timestampServer || null, - }, - }, - [result.contents], +const finishExport = async (result: ExportResult, signing: ExportSigning) => { + const asked = exportRequest(signing); + if (!asked) return result.contents; + const parts = [result.contents]; + if (asked.initials) { + await loadMarks(); + parts.push( + await stampPdf(INITIALS_STAMP, INITIALS_BOX.width, INITIALS_BOX.height), ); + } + try { + return await signPdf(asked.request, parts); } catch (error) { throw new Error(signingFailure(error), { cause: error }); } diff --git a/src/engine/pdf.test.ts b/src/engine/pdf.test.ts index c1b51683..6760d92f 100644 --- a/src/engine/pdf.test.ts +++ b/src/engine/pdf.test.ts @@ -248,6 +248,48 @@ describe("a signed PDF", () => { }, ); + it("says where the other signature blocks are, for their empty fields", async () => { + const state = EditorState.create({ + schema, + doc: parseMarkdown( + [ + "The tenant rents the flat.", + '', + "", + '', + ].join("\n\n"), + ), + }); + const blocks: number[] = []; + state.doc.forEach((node, offset) => { + if (node.type.name === "signature") blocks.push(offset); + }); + const { signatureBox, otherBoxes } = await toPDF(state, { + docPath: null, + layout: testLayout(), + signed: { + pos: blocks[0], + src: `data:image/png;base64,${IMAGES.png}`, + signing: { + name: "Ada", + place: "", + date: new Date(2026, 9, 8), + format: "iso", + language: "en", + }, + }, + others: [blocks[1], 9999], + }); + expect(signatureBox?.page).toBe(0); + expect(otherBoxes).toHaveLength(2); + // the landlord's block on the second page, where the signed one starts + expect(otherBoxes![0]).toMatchObject({ page: 1, x: signatureBox!.x }); + expect(otherBoxes![0]!.width).toBeGreaterThan(100); + expect(otherBoxes![0]!.height).toBeGreaterThan(20); + // a position without a block has no box + expect(otherBoxes![1]).toBeNull(); + }); + it("isn't written without the signature's picture", async () => { const state = signedState(); await expect( diff --git a/src/engine/pdf.ts b/src/engine/pdf.ts index 85f12dca..b5850d3c 100644 --- a/src/engine/pdf.ts +++ b/src/engine/pdf.ts @@ -66,6 +66,8 @@ interface Prepared { output: Output; // the signed block, if any, and how dates are written flattening: FlattenOptions; + // the positions of the other signature blocks, whose boxes it gives + others?: number[]; } /** @@ -80,6 +82,7 @@ const onSharedEngine = async ({ sizes, output, flattening, + others, }: Prepared): Promise => { const { signed } = flattening; const engine = await newEngine(true); @@ -119,6 +122,13 @@ const onSharedEngine = async ({ ...(signed ? { signatureBox: engine.boxes(signed.pos, signed.pos + 1)[0] } : {}), + ...(others + ? { + otherBoxes: others.map( + (pos) => engine.boxes(pos, pos + 1)[0] ?? null, + ), + } + : {}), }; } finally { engine.free(); @@ -189,7 +199,7 @@ const inWorker = async (prepared: Prepared): Promise => { }; const jobOf = ( - { doc, layout, fields, images, sizes, output, flattening }: Prepared, + { doc, layout, fields, images, sizes, output, flattening, others }: Prepared, fonts: Uint8Array[], ): PdfJob => ({ fonts, @@ -211,6 +221,7 @@ const jobOf = ( ? { sheets: JSON.stringify(output.sheets) } : { pages: output.pages }), ...(flattening.signed ? { signedPos: flattening.signed.pos } : {}), + ...(others ? { otherPos: others } : {}), }); /** @@ -315,7 +326,7 @@ export const describeWarnings = ( */ const write = async ( state: EditorState, - { docPath, layout, signed }: Omit, + { docPath, layout, signed, others }: Omit, output: Output, ): Promise => { const { images, failures } = await prepareImages(state.doc, docPath, [ @@ -346,6 +357,7 @@ const write = async ( sizes: sizesOf(images, layout), output, flattening: { signed: signed ?? null, dates: signingDates.value }, + ...(others ? { others } : {}), }; let result: PdfResult | null = null; if (!engineInstanceBroken()) { @@ -371,6 +383,7 @@ const write = async ( ], pages: result.pages, signatureBox: result.signatureBox, + ...(result.otherBoxes ? { otherBoxes: result.otherBoxes } : {}), }; }; diff --git a/src/engine/pdfJob.test.ts b/src/engine/pdfJob.test.ts index d71d0c84..09351e87 100644 --- a/src/engine/pdfJob.test.ts +++ b/src/engine/pdfJob.test.ts @@ -4,6 +4,7 @@ import { resolve } from "node:path"; import { describe, expect, it, vi } from "vitest"; import { documentFields } from "../layout/bands"; +import { schema } from "../markdown"; import { doc, h, p } from "../test/editor"; import { testEngine } from "../test/engine"; import { testLayout } from "../test/layout"; @@ -113,6 +114,24 @@ describe("writePdf", () => { expect(() => writePdf(LayoutEngine, jobOf().job)).toThrow("unreachable"); }); + it("gives the boxes of the signature blocks asked for", () => { + testEngine(); + const block = () => schema.nodes.signature.create({ role: "Tenant" }); + // the blocks at 0 and 1 + const { job } = jobOf(doc(block(), block())); + const result = writePdf(LayoutEngine, { + ...job, + signedPos: 0, + otherPos: [1, 9999], + }); + expect(result.signatureBox).toMatchObject({ page: 0 }); + expect(result.otherBoxes).toEqual([ + { ...result.signatureBox, y: expect.any(Number) }, + null, + ]); + expect(result.otherBoxes![0]!.y).toBeGreaterThan(result.signatureBox!.y); + }); + it("tells which characters no font has", () => { testEngine(); const { job } = jobOf(doc(p("中文"))); diff --git a/src/engine/pdfJob.ts b/src/engine/pdfJob.ts index a0f0fcdf..689914e2 100644 --- a/src/engine/pdfJob.ts +++ b/src/engine/pdfJob.ts @@ -28,6 +28,9 @@ export interface PdfJob { // the position of the signature block a signed PDF fills in, whose box // the result gives signedPos?: number; + // the positions of the other signature blocks, whose boxes the result + // gives, for the empty fields of the others who sign + otherPos?: number[]; } // what a PDF left out: an image it couldn't decode, which shows its alt @@ -47,6 +50,9 @@ export interface PdfResult { warnings: PdfWarning[]; // where the filled in signature block is, for the signature's field signatureBox?: PageBox; + // where the other signature blocks are, in the order of `otherPos`; null + // for one the PDF doesn't show + otherBoxes?: (PageBox | null)[]; } /** @@ -106,6 +112,13 @@ export const writePdf = ( ...(signed === undefined ? {} : { signatureBox: rectsOf(engine.boxes(signed, signed + 1))[0] }), + ...(job.otherPos + ? { + otherBoxes: job.otherPos.map( + (pos) => rectsOf(engine.boxes(pos, pos + 1))[0] ?? null, + ), + } + : {}), }; } finally { try { diff --git a/src/exporters/types.ts b/src/exporters/types.ts index fca0e286..f9431aa4 100644 --- a/src/exporters/types.ts +++ b/src/exporters/types.ts @@ -14,6 +14,9 @@ export interface ExportContext { pages?: number[]; // for a signed PDF: the signature block to fill in signed?: SignedBlock; + // for a signed PDF: the positions of the other signature blocks, whose + // boxes the result gives + others?: number[]; } export interface ExportResult { @@ -25,6 +28,9 @@ export interface ExportResult { // for a signed PDF: where the filled in signature block is, in points // from the top left of its page (counted from 0) signatureBox?: PageBox; + // where the blocks of `others` are, in their order, the same way; null + // for one the PDF doesn't show + otherBoxes?: (PageBox | null)[]; } export type exporterFunc = ( diff --git a/src/scss/_signing.scss b/src/scss/_signing.scss index 3c8ecae3..60c0149e 100644 --- a/src/scss/_signing.scss +++ b/src/scss/_signing.scss @@ -295,3 +295,15 @@ flex-basis: 100%; } } + +// the Sign dialog's switches and the button that makes the initials +.sign-dialog { + .dialog-body > .switch-row { + margin-bottom: var(--s2); + } + + .make-initials { + @include text-button; + margin-left: var(--s1); + } +} diff --git a/src/signing/exportPlan.test.ts b/src/signing/exportPlan.test.ts new file mode 100644 index 00000000..ce579555 --- /dev/null +++ b/src/signing/exportPlan.test.ts @@ -0,0 +1,91 @@ +import { describe, expect, it } from "vitest"; + +import { type ExportSigning, exportRequest } from "./exportPlan"; +import { initialsRect, isInitialsChoice } from "./initials"; + +const BOX = { page: 0, x: 70, y: 600, width: 230, height: 70 }; +const A4 = { width: 595.28, height: 841.89 }; + +const signing = (changes: Partial = {}): ExportSigning => ({ + passphrase: null, + place: "Berlin", + reason: "", + timestampServer: "https://tsa.example", + signatureBox: BOX, + others: [], + initials: null, + page: A4, + ...changes, +}); + +describe("a signed export's update", () => { + it("leaves a PDF without a digital signature, others or initials as it is", () => { + expect(exportRequest(signing())).toBeNull(); + }); + + it("signs over the block, with the time from the server", () => { + const { request, initials } = exportRequest( + signing({ passphrase: "secret", reason: "I agree" }), + )!; + expect(initials).toBe(false); + expect(request).toEqual({ + passphrase: "secret", + source: { kind: "body" }, + plan: { + stamps: [], + signature: { kind: "new", page: 1, rect: [70, 600, 230, 70] }, + others: [], + location: "Berlin", + reason: "I agree", + timestampServer: "https://tsa.example", + }, + }); + }); + + it("never signs without the block's box", () => { + expect(() => + exportRequest(signing({ passphrase: "secret", signatureBox: undefined })), + ).toThrow("the signature block isn't in the PDF"); + }); + + it("adds the others' fields and the initials without a signature", () => { + const { request, initials } = exportRequest( + signing({ + others: [ + { name: "Bea", box: { ...BOX, page: 2 } }, + { name: "Cy", box: null }, + ], + initials: "right", + }), + )!; + expect(initials).toBe(true); + expect(request.passphrase).toBeNull(); + expect(request.plan.signature).toEqual({ kind: "invisible" }); + expect(request.plan.timestampServer).toBeNull(); + // one not on the pages gets none + expect(request.plan.others).toEqual([ + { page: 3, rect: [70, 600, 230, 70], name: "Bea" }, + ]); + expect(request.plan.stamps).toEqual([ + { part: 1, page: 1, rect: initialsRect(A4, "right"), repeat: true }, + ]); + }); +}); + +describe("the initials on every page", () => { + it("go 36 pt from the bottom and the side chosen, 56 × 28 pt", () => { + expect(initialsRect({ width: 600, height: 800 }, "right")).toEqual([ + 508, 736, 56, 28, + ]); + expect(initialsRect({ width: 600, height: 800 }, "left")).toEqual([ + 36, 736, 56, 28, + ]); + }); + + it("are remembered only as the dialog chose them", () => { + expect(isInitialsChoice({ on: true, side: "left" })).toBe(true); + expect(isInitialsChoice({ on: "yes", side: "left" })).toBe(false); + expect(isInitialsChoice({ on: true, side: "top" })).toBe(false); + expect(isInitialsChoice(null)).toBe(false); + }); +}); diff --git a/src/signing/exportPlan.ts b/src/signing/exportPlan.ts new file mode 100644 index 00000000..756a5fdf --- /dev/null +++ b/src/signing/exportPlan.ts @@ -0,0 +1,79 @@ +import type { PageBox } from "../engine/boxes"; +import { type InitialsSide, initialsRect } from "./initials"; +import type { PageRect, SignRequest } from "./ipc"; + +// What a signed export adds to the PDF the layout engine wrote, in one +// incremental update (`signing_sign_pdf` with the PDF as the body's part 0): +// the user's initials on every page, empty signature fields for the others +// who sign, and the digital signature over the filled in block. + +export interface ExportSigning { + // the key's passphrase, or null for no digital signature + passphrase: string | null; + // the place and reason the signature carries; the native side names the + // certificate's holder as who signed it + place: string; + reason: string; + timestampServer: string; + // where the filled in block is + signatureBox?: PageBox; + // the other signature blocks, by who signs them, where the PDF shows them + others: { name: string; box: PageBox | null }[]; + // the corner the initials go in on every page, or null for none + initials: InitialsSide | null; + // the size of the pages, in points + page: { width: number; height: number }; +} + +const rectOf = (box: PageBox): PageRect => [ + box.x, + box.y, + box.width, + box.height, +]; + +/** + * exportRequest returns what to ask the signer for, with the body's parts + * after the PDF: the initials' stamp when they go on the pages (part 1). + * null when there's nothing to add, so the PDF is written as the engine + * made it: no digital signature, no other signers and no initials. + */ +export const exportRequest = ( + signing: ExportSigning, +): { request: SignRequest; initials: boolean } | null => { + const others = signing.others.flatMap(({ name, box }) => + box ? [{ page: box.page + 1, rect: rectOf(box), name }] : [], + ); + const digital = signing.passphrase !== null; + if (!digital && others.length === 0 && !signing.initials) return null; + const box = signing.signatureBox; + if (digital && !box) throw new Error("the signature block isn't in the PDF"); + return { + initials: !!signing.initials, + request: { + passphrase: signing.passphrase, + source: { kind: "body" }, + plan: { + stamps: signing.initials + ? [ + { + part: 1, + page: 1, + rect: initialsRect(signing.page, signing.initials), + repeat: true, + }, + ] + : [], + // the block is in the page content already, from the engine + signature: + digital && box + ? { kind: "new", page: box.page + 1, rect: rectOf(box) } + : { kind: "invisible" }, + others, + location: signing.place || null, + reason: signing.reason || null, + timestampServer: (digital && signing.timestampServer) || null, + }, + }, + }; +}; diff --git a/src/signing/initials.ts b/src/signing/initials.ts new file mode 100644 index 00000000..d4290464 --- /dev/null +++ b/src/signing/initials.ts @@ -0,0 +1,56 @@ +import type { StampSpec } from "../pdf/stamps"; +import type { PageRect } from "./ipc"; + +// The user's initials on every page of what they sign: one stamp, drawn at +// the same place on each page (`repeat`), in a bottom corner. + +// which bottom corner the initials go in +export type InitialsSide = "right" | "left"; + +// what the Sign dialog remembers of the initials: whether they go on every +// page, and where +export interface InitialsChoice { + on: boolean; + side: InitialsSide; +} + +export const INITIALS_DEFAULT: InitialsChoice = { on: false, side: "right" }; + +// the stamp's box, in points, and how far it is from the page's edges +export const INITIALS_BOX = { width: 56, height: 28 }; +export const INITIALS_MARGIN = 36; + +// the stamp: the initials alone, contained in the box +export const INITIALS_STAMP: StampSpec = { + image: "initials", + lines: [], + size: 9, + align: "center", +}; + +/** + * initialsRect returns where the initials go on a page of `page`'s size, in + * points from its top left: `INITIALS_MARGIN` from the bottom and from the + * side chosen + */ +export const initialsRect = ( + page: { width: number; height: number }, + side: InitialsSide, +): PageRect => [ + side === "right" + ? page.width - INITIALS_MARGIN - INITIALS_BOX.width + : INITIALS_MARGIN, + page.height - INITIALS_MARGIN - INITIALS_BOX.height, + INITIALS_BOX.width, + INITIALS_BOX.height, +]; + +/** + * isInitialsChoice tells whether a stored value is what the dialog + * remembers + */ +export const isInitialsChoice = (value: unknown): value is InitialsChoice => + !!value && + typeof value === "object" && + typeof (value as InitialsChoice).on === "boolean" && + ["right", "left"].includes((value as InitialsChoice).side); diff --git a/src/signing/targets.ts b/src/signing/targets.ts index 037836a3..6cec9dbb 100644 --- a/src/signing/targets.ts +++ b/src/signing/targets.ts @@ -21,6 +21,7 @@ export const targetsOf = (doc: Node): SignTarget[] => { name, place: node.attrs.place as string, dated: node.attrs.date === "signing", + role: node.attrs.role as string, label: label || `Signature ${targets.length + 1}`, }); }); diff --git a/src/state/dialogs.ts b/src/state/dialogs.ts index c3cbef85..6d2f774c 100644 --- a/src/state/dialogs.ts +++ b/src/state/dialogs.ts @@ -9,6 +9,7 @@ import type { BoxAnchor } from "./popups"; import type { Mark, MarkSource, SavedMark } from "../signing/profile"; import type { SignatureDate } from "../markdown/blocks/atoms"; import type { CheckedSignature } from "../signing/ipc"; +import type { InitialsChoice } from "../signing/initials"; export interface LinkDialogRequest { url: string; @@ -195,14 +196,38 @@ export interface SignTarget { place: string; // whether today's date goes under it when it's signed dated: boolean; + // as what, e.g. "Tenant", "" for none + role: string; // e.g. "Ada Lovelace · Tenant", or "Signature 2" for one that names none label: string; } +// what the user chose in the Sign dialog +export interface SignChoices { + // the signature block to fill in, by its position (the export flow); + // null for a PDF tab + pos: number | null; + // where the user signs, and why (the signature's /Reason), "" for none + place: string; + reason: string; + // the key's passphrase, or null for no digital signature (only the + // visible signature) + passphrase: string | null; + // the user's initials on every page, and in which corner + initials: InitialsChoice; + // a PDF tab: whether the signed PDF opens in a tab of its own + open: boolean; +} + +// The Sign dialog (SignDialog.vue), for both flows: "export" signs the +// document as a PDF with its signature block filled in, "pdf" signs a PDF +// tab with what the user placed on its pages export interface SignDialogRequest { + mode: "export" | "pdf"; // what is signed, e.g. the document's name title: string; - // the signature blocks the document has, the one to fill in chosen + // the export flow: the signature blocks the document has, the one to + // fill in chosen; none for a PDF tab targets: SignTarget[]; chosen: number; // where the user signed last @@ -215,15 +240,32 @@ export interface SignDialogRequest { // the notes the dialog shows, e.g. that an imported Word document's // layout can differ from Word's warnings: string[]; + // whether Sign… waits for the user to say they checked every page, e.g. + // of an imported Word document + confirmPages: boolean; + // the user's initials on every page as they start, and why they can't + // go on the pages, if they can't (e.g. a PDF others signed); without + // saved initials the dialog says so itself + initials: InitialsChoice & { reason?: string }; + // a PDF tab: changes the repeated initials stamp at once, as the switch + // and the corner change + setInitials?(initials: InitialsChoice): void; + // whether the user's signature shows on a page: the export flow's block, + // or a signature stamp placed on a PDF tab. Without it a digital + // signature is invisible and a visible one can't be made. + stamped: boolean; + // a PDF tab: the empty fields still to fill in + emptyFields: number; // whether `passphrase` opens the user's key: what's wrong, or null check(passphrase: string): Promise; - // opens the dialog that makes the user a certificate, which gives the - // focus back to `opener` + // open the dialog that makes the user a certificate, or their initials, + // which gives the focus back to `opener` createCertificate(opener: EventTarget | null): void; - // fills in the block at `pos` (signed in `place` when it says none), - // asks where to save the signed PDF and, with - // the key's passphrase, signs it digitally too - submit(pos: number, place: string, passphrase: string | null): void; + createInitials(opener: EventTarget | null): void; + // Settings ▸ Signing, once the dialog closed + manage(): void; + // signs as chosen: asks where to save the signed PDF and writes it + submit(choices: SignChoices): void; cancel(): void; } diff --git a/src/state/signing.ts b/src/state/signing.ts index 2dc47e8d..2819ffa8 100644 --- a/src/state/signing.ts +++ b/src/state/signing.ts @@ -6,6 +6,7 @@ import { language } from "./language"; import type { CertificateInfo } from "../signing/ipc"; import type { Mark, SavedMark } from "../signing/profile"; +import { INITIALS_DEFAULT, type InitialsChoice } from "../signing/initials"; // The user's signature and initials (src/signing/profile.ts), once read: // undefined until then, null for none. They're read when something first @@ -17,6 +18,10 @@ export const savedMarks = shallowRef< // where the user signed last, which a signature's place starts as export const signingPlace = shallowRef(""); +// whether the user put their initials on every page the last time they +// signed, and in which corner, which the Sign dialog starts with +export const signingInitials = shallowRef(INITIALS_DEFAULT); + // the user's certificate as the native side has it (src/signing/ipc.ts): // undefined until read, null for none export const signingCertificate = shallowRef< diff --git a/src/storage.ts b/src/storage.ts index e0ff9a56..02c2986c 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -15,6 +15,7 @@ import { currentViewAnchor, language, outlinePinned, + signingInitials, signingPlace, PAGE_VIEW_MODES, pageLayoutState, @@ -41,6 +42,7 @@ import { CommandIdentifier } from "./config"; import { logWarning } from "./log"; import { closeMarker, fenceFor, formatMarker, schema } from "./markdown"; import { isPrintSettings, PRINT_DEFAULTS } from "./print/printModel"; +import { INITIALS_DEFAULT, isInitialsChoice } from "./signing/initials"; import { sendNotification } from "@tauri-apps/plugin-notification"; localforage.config({ @@ -369,6 +371,13 @@ export const bootStorage = async () => { (value): value is string => typeof value === "string", "", ); + // whether the initials went on every page then, and in which corner + await restore( + "signingInitials", + signingInitials, + isInitialsChoice, + INITIALS_DEFAULT, + ); sessionKept = await ownSession(); if (sessionKept) { diff --git a/src/ui/SignDialog.test.ts b/src/ui/SignDialog.test.ts index 1b1c91ea..688cde60 100644 --- a/src/ui/SignDialog.test.ts +++ b/src/ui/SignDialog.test.ts @@ -2,6 +2,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { nextTick } from "vue"; import { + contextMenu, + type MenuItem, + savedMarks, + type SignChoices, signDialog, type SignDialogRequest, signingCertificate, @@ -11,6 +15,33 @@ import { createTestHandle } from "../test/editor"; import { bootApp } from "./mount"; const dialog = () => document.getElementById("sign-dialog"); +const byId = (id: string) => + document.getElementById(id) as T | null; +const submitForm = () => + dialog()! + .querySelector("form")! + .dispatchEvent(new Event("submit", { cancelable: true })); +// what the dialog hands on, with what a test changes +const chose = (changes: Partial): SignChoices => ({ + pos: 9, + place: "Berlin", + reason: "", + passphrase: null, + initials: { on: false, side: "right" }, + open: true, + ...changes, +}); +// runs the item of Sign as's menu with this id +const signAs = async (id: string) => { + byId("sign-dialog-how")!.click(); + await nextTick(); + const item = contextMenu.value!.items.find( + (line) => line !== "separator" && "id" in line && line.id === id, + ) as Exclude; + item.run!(); + contextMenu.value = null; + await nextTick(); +}; describe("the dialog that signs", () => { let dispose = () => {}; @@ -18,6 +49,7 @@ describe("the dialog that signs", () => { const open = async (changes: Partial = {}) => { request = { + mode: "export", title: "lease", targets: [ { @@ -25,17 +57,24 @@ describe("the dialog that signs", () => { name: "Bea", place: "", dated: true, + role: "Landlord", label: "Bea · Landlord", }, - { pos: 9, name: "", place: "", dated: true, label: "Tenant" }, + { pos: 9, name: "", place: "", dated: true, role: "", label: "Tenant" }, ], chosen: 9, place: "Berlin", signer: "Ada", today: { date: "10 October 2026", language: "English" }, warnings: [], + confirmPages: false, + initials: { on: false, side: "right" }, + stamped: true, + emptyFields: 0, check: vi.fn(async () => null), createCertificate: vi.fn(), + createInitials: vi.fn(), + manage: vi.fn(), submit: vi.fn(), cancel: vi.fn(), ...changes, @@ -68,7 +107,9 @@ describe("the dialog that signs", () => { it("offers no choice with one block, and shows its warnings", async () => { await open({ - targets: [{ pos: 3, name: "", place: "", dated: true, label: "Tenant" }], + targets: [ + { pos: 3, name: "", place: "", dated: true, role: "", label: "Tenant" }, + ], chosen: 3, warnings: ["Check every page."], }); @@ -76,13 +117,13 @@ describe("the dialog that signs", () => { expect(dialog()!.querySelector(".warning")!.textContent).toContain( "Check every page.", ); - expect(dialog()!.textContent).not.toContain("other signature blocks"); + expect(byId("sign-dialog-others")).toBeNull(); }); it("says the other blocks stay empty, and warns of a block for someone else", async () => { await open(); - expect(dialog()!.textContent).toContain( - "The other signature blocks stay empty.", + expect(byId("sign-dialog-others")!.textContent).toContain( + "The other signature blocks get empty signature fields.", ); expect(document.getElementById("sign-dialog-named")).toBeNull(); dialog()!.querySelector(".options button")!.click(); @@ -102,7 +143,14 @@ describe("the dialog that signs", () => { it("signs a block that fixes its place there, and says so", async () => { await open({ targets: [ - { pos: 3, name: "", place: "Paris", dated: false, label: "Tenant" }, + { + pos: 3, + name: "", + place: "Paris", + dated: false, + role: "", + label: "Tenant", + }, ], chosen: 3, }); @@ -118,7 +166,7 @@ describe("the dialog that signs", () => { .querySelector("form")! .dispatchEvent(new Event("submit", { cancelable: true })); // the dialog passes what was typed; the command signs in the block's - expect(request.submit).toHaveBeenCalledWith(3, "Berlin", null); + expect(request.submit).toHaveBeenCalledWith(chose({ pos: 3 })); }); it("signs in the chosen block, where the user says", async () => { @@ -131,7 +179,7 @@ describe("the dialog that signs", () => { .querySelector("form")! .dispatchEvent(new Event("submit", { cancelable: true })); // without a certificate, a picture only - expect(request.submit).toHaveBeenCalledWith(9, "Hamburg", null); + expect(request.submit).toHaveBeenCalledWith(chose({ place: "Hamburg" })); expect(signDialog.value).toBeNull(); }); @@ -144,6 +192,135 @@ describe("the dialog that signs", () => { fingerprint: "AB".repeat(32), }; + it("offers to make a certificate from Sign as", async () => { + signingCertificate.value = null; + await open(); + expect(byId("sign-dialog-how")!.textContent).toContain( + "No digital signature", + ); + await signAs("create"); + expect(request.createCertificate).toHaveBeenCalledWith( + byId("sign-dialog-how"), + ); + signingCertificate.value = undefined; + }); + + it("takes the reason into the signature", async () => { + await open(); + const reason = byId("sign-dialog-reason")!; + expect(reason.placeholder).toBe("Optional, e.g. I agree to these terms"); + reason.value = " I agree "; + reason.dispatchEvent(new Event("input")); + submitForm(); + expect(request.submit).toHaveBeenCalledWith(chose({ reason: "I agree" })); + }); + + describe("the initials", () => { + afterEach(() => { + savedMarks.value = undefined; + }); + + const MARK = { + source: { kind: "draw" as const, strokes: [] }, + png: "data:image/png;base64,AAAA", + width: 40, + height: 20, + }; + + it("asks for initials first, and puts new ones on every page", async () => { + savedMarks.value = { signature: MARK, initials: null }; + await open(); + const toggle = byId("sign-dialog-initials")!; + expect(toggle.disabled).toBe(true); + expect(byId("sign-dialog-initials-note")!.textContent).toContain( + "Add your initials in Settings ▸ Signing first.", + ); + const make = byId("sign-dialog-make-initials")!; + make.click(); + expect(request.createInitials).toHaveBeenCalledWith(make); + savedMarks.value = { signature: MARK, initials: MARK }; + await nextTick(); + expect(toggle.disabled).toBe(false); + expect(toggle.getAttribute("aria-checked")).toBe("true"); + expect(byId("sign-dialog-initials-note")).toBeNull(); + }); + + it("puts them in the corner chosen, and tells a PDF tab at once", async () => { + savedMarks.value = { signature: MARK, initials: MARK }; + const setInitials = vi.fn(); + await open({ initials: { on: true, side: "right" }, setInitials }); + const corners = [ + ...dialog()!.querySelectorAll( + "[data-row=initials-side] button", + ), + ]; + expect(corners.map((corner) => corner.textContent!.trim())).toEqual([ + "Bottom right", + "Bottom left", + ]); + corners[1].click(); + await nextTick(); + expect(setInitials).toHaveBeenLastCalledWith({ on: true, side: "left" }); + submitForm(); + expect(request.submit).toHaveBeenCalledWith( + chose({ initials: { on: true, side: "left" } }), + ); + }); + + it("can't put them on pages others signed, and says why", async () => { + savedMarks.value = { signature: MARK, initials: MARK }; + await open({ + mode: "pdf", + targets: [], + initials: { + on: true, + side: "right", + reason: "Initials would change pages the earlier signers signed.", + }, + }); + expect(byId("sign-dialog-initials")!.disabled).toBe(true); + expect(byId("sign-dialog-initials-note")!.textContent).toContain( + "Initials would change pages", + ); + expect(byId("sign-dialog-make-initials")).toBeNull(); + // and opens the signed PDF, unless switched off + byId("sign-dialog-open")!.click(); + await nextTick(); + submitForm(); + expect(request.submit).toHaveBeenCalledWith( + chose({ pos: null, open: false }), + ); + }); + }); + + it("signs a picture only in a PDF tab once the signature is on a page", async () => { + await open({ mode: "pdf", targets: [], stamped: false }); + const sign = dialog()!.querySelector( + 'button[type="submit"]', + )!; + expect(sign.disabled).toBe(true); + expect(byId("sign-dialog-stamp")!.textContent).toBe( + "Place your signature on the page first, or sign digitally.", + ); + submitForm(); + expect(request.submit).not.toHaveBeenCalled(); + }); + + it("signs a Word document once every page is checked", async () => { + await open({ warnings: ["Check every page."], confirmPages: true }); + const sign = dialog()!.querySelector( + 'button[type="submit"]', + )!; + expect(sign.disabled).toBe(true); + const checked = byId("sign-dialog-checked")!; + expect(checked.textContent!.trim()).toBe("I checked every page"); + checked.click(); + await nextTick(); + expect(sign.disabled).toBe(false); + submitForm(); + expect(request.submit).toHaveBeenCalled(); + }); + it("signs digitally with a certificate made from it", async () => { signingCertificate.value = null; await open(); @@ -203,7 +380,9 @@ describe("the dialog that signs", () => { request.check = vi.fn(async () => null); submit(); await flushPromises(); - expect(request.submit).toHaveBeenCalledWith(9, "Berlin", "wrong"); + expect(request.submit).toHaveBeenCalledWith( + chose({ passphrase: "wrong" }), + ); }); it("signs nothing when it's closed while the passphrase is checked", async () => { @@ -232,7 +411,14 @@ describe("the dialog that signs", () => { it("starts on the passphrase when the block fixes its place", async () => { await open({ targets: [ - { pos: 3, name: "", place: "Paris", dated: false, label: "Tenant" }, + { + pos: 3, + name: "", + place: "Paris", + dated: false, + role: "", + label: "Tenant", + }, ], chosen: 3, }); @@ -240,19 +426,39 @@ describe("the dialog that signs", () => { expect(document.activeElement).toBe(passphrase()); }); - it("signs with a picture only when chosen", async () => { + it("signs as the certificate, or with a picture only when chosen", async () => { await open(); - const options = [ - ...dialog()!.querySelectorAll( - "[data-row=how] button", - ), - ]; - options[1].click(); - await nextTick(); + expect(byId("sign-dialog-how")!.textContent).toContain( + "Ada Lovelace · your certificate", + ); + await signAs("visible"); + expect(byId("sign-dialog-how")!.textContent).toContain( + "No digital signature (only the visible signature)", + ); expect(document.querySelector("#sign-dialog-passphrase")).toBeNull(); submit(); await flushPromises(); - expect(request.submit).toHaveBeenCalledWith(9, "Berlin", null); + expect(request.submit).toHaveBeenCalledWith(chose({})); + }); + + it("manages the certificate in Settings ▸ Signing, the dialog closed", async () => { + await open(); + await signAs("manage"); + expect(signDialog.value).toBeNull(); + expect(request.manage).toHaveBeenCalled(); + }); + + it("says a PDF tab without the signature on a page is signed invisibly", async () => { + await open({ mode: "pdf", targets: [], stamped: false, emptyFields: 2 }); + expect(byId("sign-dialog-stamp")!.textContent).toContain( + "the signature will be invisible", + ); + expect(byId("sign-dialog-empty")!.textContent).toContain( + "This PDF still has 2 empty fields.", + ); + // no blocks in a PDF tab + expect(document.getElementById("sign-dialog-target")).toBeNull(); + expect(byId("sign-dialog-others")).toBeNull(); }); }); }); diff --git a/src/ui/SignDialog.vue b/src/ui/SignDialog.vue index 4ee1b328..f630c34d 100644 --- a/src/ui/SignDialog.vue +++ b/src/ui/SignDialog.vue @@ -1,5 +1,6 @@ @@ -109,12 +189,23 @@ const submit = async () => { @submit="submit" @cancel="cancel" > + + + + + + On every page + + +

+ {{ initialsBlocked }} + +

+ {

{{ fixedPlace }}

{{ named }}

{{ dated }}

- {

{{ warning }}

-

{{ others }}

+ + I checked every page + +

{{ stamp }}

+

+ {{ emptyFields }} +

+

{{ others }}

{{ note }}

+