commit fa03b8e97ebe07dd5dd3fc6dfbeefd3952c6a297
parent 32a6a7c603aeae577619f1177cc35627ba1118ad
Author: Antoine A <>
Date: Tue, 23 Jun 2026 11:46:51 +0200
nexus: improve pdf generation
Diffstat:
4 files changed, 132 insertions(+), 130 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -333,17 +333,6 @@ dependencies = [
]
[[package]]
-name = "bstr"
-version = "1.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
-dependencies = [
- "memchr",
- "regex-automata",
- "serde",
-]
-
-[[package]]
name = "bumpalo"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1025,7 +1014,6 @@ version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
- "crc32fast",
"miniz_oxide",
"zlib-rs",
]
@@ -1348,7 +1336,7 @@ dependencies = [
[[package]]
name = "http-client"
version = "1.5.0"
-source = "git+git://git.taler.net/taler-rust.git/#5701a8fd93a4833e3cc5ab7b128648d03122d111"
+source = "git+git://git.taler.net/taler-rust.git/#e8a8977ada44fe6015d5a50eb8db63e3dfe693f2"
dependencies = [
"compact_str",
"futures-util",
@@ -1809,8 +1797,8 @@ dependencies = [
"flate2",
"futures-util",
"jiff",
+ "pdf-writer",
"pretty_assertions",
- "printpdf",
"rand 0.10.1",
"rcgen",
"reqwest",
@@ -1927,12 +1915,6 @@ dependencies = [
]
[[package]]
-name = "linked-hash-map"
-version = "0.5.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
-
-[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1977,23 +1959,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
-name = "lopdf"
-version = "0.31.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07c8e1b6184b1b32ea5f72f572ebdc40e5da1d2921fa469947ff7c480ad1f85a"
-dependencies = [
- "encoding_rs",
- "flate2",
- "itoa",
- "linked-hash-map",
- "log",
- "md5",
- "pom",
- "time",
- "weezl",
-]
-
-[[package]]
name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2016,12 +1981,6 @@ dependencies = [
]
[[package]]
-name = "md5"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
-
-[[package]]
name = "memchr"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2218,15 +2177,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
-name = "owned_ttf_parser"
-version = "0.19.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "706de7e2214113d63a8238d1910463cfce781129a6f263d13fdb09ff64355ba4"
-dependencies = [
- "ttf-parser",
-]
-
-[[package]]
name = "owo-colors"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2262,6 +2212,18 @@ dependencies = [
]
[[package]]
+name = "pdf-writer"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5e456864a7a304047bff84977dc6fb162bd956475d40ba50b2dcecaada7f753"
+dependencies = [
+ "bitflags 2.13.0",
+ "itoa",
+ "memchr",
+ "ryu",
+]
+
+[[package]]
name = "pem"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2326,15 +2288,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[package]]
-name = "pom"
-version = "3.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c972d8f86e943ad532d0b04e8965a749ad1d18bb981a9c7b3ae72fe7fd7744b"
-dependencies = [
- "bstr",
-]
-
-[[package]]
name = "portable-atomic"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2384,17 +2337,6 @@ dependencies = [
]
[[package]]
-name = "printpdf"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c30a4cc87c3ca9a98f4970db158a7153f8d1ec8076e005751173c57836380b1d"
-dependencies = [
- "lopdf",
- "owned_ttf_parser",
- "time",
-]
-
-[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3498,7 +3440,7 @@ dependencies = [
[[package]]
name = "taler-api"
version = "1.5.0"
-source = "git+git://git.taler.net/taler-rust.git/#5701a8fd93a4833e3cc5ab7b128648d03122d111"
+source = "git+git://git.taler.net/taler-rust.git/#e8a8977ada44fe6015d5a50eb8db63e3dfe693f2"
dependencies = [
"aws-lc-rs",
"axum",
@@ -3525,12 +3467,12 @@ dependencies = [
[[package]]
name = "taler-build"
version = "1.5.0"
-source = "git+git://git.taler.net/taler-rust.git/#5701a8fd93a4833e3cc5ab7b128648d03122d111"
+source = "git+git://git.taler.net/taler-rust.git/#e8a8977ada44fe6015d5a50eb8db63e3dfe693f2"
[[package]]
name = "taler-common"
version = "1.5.0"
-source = "git+git://git.taler.net/taler-rust.git/#5701a8fd93a4833e3cc5ab7b128648d03122d111"
+source = "git+git://git.taler.net/taler-rust.git/#e8a8977ada44fe6015d5a50eb8db63e3dfe693f2"
dependencies = [
"anyhow",
"aws-lc-rs",
@@ -3559,7 +3501,7 @@ dependencies = [
[[package]]
name = "taler-macros"
version = "1.5.0"
-source = "git+git://git.taler.net/taler-rust.git/#5701a8fd93a4833e3cc5ab7b128648d03122d111"
+source = "git+git://git.taler.net/taler-rust.git/#e8a8977ada44fe6015d5a50eb8db63e3dfe693f2"
dependencies = [
"proc-macro2",
"quote",
@@ -3569,7 +3511,7 @@ dependencies = [
[[package]]
name = "taler-test-utils"
version = "1.5.0"
-source = "git+git://git.taler.net/taler-rust.git/#5701a8fd93a4833e3cc5ab7b128648d03122d111"
+source = "git+git://git.taler.net/taler-rust.git/#e8a8977ada44fe6015d5a50eb8db63e3dfe693f2"
dependencies = [
"aws-lc-rs",
"axum",
@@ -3925,12 +3867,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
-name = "ttf-parser"
-version = "0.19.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1"
-
-[[package]]
name = "tungstenite"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4252,12 +4188,6 @@ dependencies = [
]
[[package]]
-name = "weezl"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
-
-[[package]]
name = "whoami"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/libeufin-ebics/Cargo.toml b/libeufin-ebics/Cargo.toml
@@ -38,4 +38,4 @@ roxmltree = "0.21.1"
rcgen = { version = "0.14.7", features = ["aws_lc_rs", "pem"], default-features = false }
x509-parser = { version = "0.18.1", features = ["verify-aws"] }
calamine = { version = "0.35.0" }
-printpdf = { version = "0.7", default-features = false }
+pdf-writer = "0.15.0"
diff --git a/libeufin-ebics/src/pdf.rs b/libeufin-ebics/src/pdf.rs
@@ -19,18 +19,43 @@
use aws_lc_rs::{encoding::AsDer as _, rsa::PublicKey, signature::KeyPair};
use jiff::Zoned;
-use printpdf::{BuiltinFont, Mm, PdfDocument};
+use pdf_writer::{Content, Finish, Name, Pdf, Rect, Ref, Str};
use crate::{
config::EbicsHostCfg, crypto::ebics_pub_key_hash, keys::ClientKeys, utils::hex_chunk_by_two,
};
-
pub fn generate_keys_pdf(keys: &ClientKeys, cfg: &EbicsHostCfg) -> anyhow::Result<Vec<u8>> {
let date = Zoned::now().date().to_string();
- let (doc, p, l) = PdfDocument::new("EBICS Client Keys", Mm(210.0), Mm(297.0), "Content");
- let reg = doc.add_builtin_font(BuiltinFont::Helvetica)?;
- let bld = doc.add_builtin_font(BuiltinFont::HelveticaBold)?;
+ // Assign structural IDs directly
+ let catalog_id = Ref::new(1);
+ let page_tree_id = Ref::new(2);
+ let font_reg_id = Ref::new(3);
+ let font_bld_id = Ref::new(4);
+
+ // Static mapping for the 3 EBICS pages
+ let page_ids = [Ref::new(5), Ref::new(6), Ref::new(7)];
+ let content_ids = [Ref::new(8), Ref::new(9), Ref::new(10)];
+
+ let mut pdf = Pdf::new();
+
+ // Catalog & Page Tree
+ pdf.catalog(catalog_id).pages(page_tree_id);
+ pdf.pages(page_tree_id)
+ .count(3)
+ .kids(page_ids.iter().copied());
+
+ // Register built-in fonts
+ pdf.type1_font(font_reg_id)
+ .base_font(Name(b"Helvetica"))
+ .encoding_predefined(Name(b"WinAnsiEncoding"));
+
+ pdf.type1_font(font_bld_id)
+ .base_font(Name(b"Helvetica-Bold"))
+ .encoding_predefined(Name(b"WinAnsiEncoding"));
+
+ let f_reg = Name(b"F1");
+ let f_bld = Name(b"F2");
let pages_data = [
(
@@ -50,37 +75,82 @@ pub fn generate_keys_pdf(keys: &ClientKeys, cfg: &EbicsHostCfg) -> anyhow::Resul
),
];
+ // Conversion factor for Millimeters to Points
+ const fn mm(v: f32) -> f32 {
+ v * 2.834_65
+ }
+
+ struct Page {
+ y: f32,
+ content: Content,
+ }
+
+ impl Page {
+ pub fn new() -> Self {
+ Self {
+ y: 270.0,
+ content: Content::new(),
+ }
+ }
+
+ pub fn line(&mut self, font: Name, size: f32, text: &str) {
+ let encoded: Vec<u8> = text
+ .chars()
+ .map(|c| match c {
+ 'Ö' => 0xD6,
+ 'ü' => 0xFC,
+ 'ä' => 0xE4,
+ 'ß' => 0xDF,
+ _ => c as u8,
+ })
+ .collect();
+
+ self.content.begin_text();
+ self.content.set_font(font, size);
+ self.content
+ .set_text_matrix([1.0, 0.0, 0.0, 1.0, mm(15.0), mm(self.y)]);
+ self.content.show(Str(&encoded));
+ self.content.end_text();
+
+ self.y -= size * 0.45;
+ }
+ }
+
for (i, (title, label, pub_key)) in pages_data.into_iter().enumerate() {
- let layer = if i == 0 {
- doc.get_page(p).get_layer(l)
- } else {
- let (p, l) = doc.add_page(Mm(210.0), Mm(297.0), "Content");
- doc.get_page(p).get_layer(l)
- };
-
- let mut y = 270.0;
- let line = |font, y: &mut f32, size, text: &str| {
- layer.use_text(text, size, Mm(15.0), Mm(*y), font);
- *y -= size * 0.45;
- };
+ let mut page = pdf.page(page_ids[i]);
+ page.parent(page_tree_id);
+ page.media_box(Rect::new(0.0, 0.0, mm(210.0), mm(297.0)));
+ page.contents(content_ids[i]);
+
+ // Map Fonts to Page Resources
+ let mut res = page.resources();
+ let mut fonts = res.fonts();
+ fonts.insert(f_reg).primitive(font_reg_id);
+ fonts.insert(f_bld).primitive(font_bld_id);
+ fonts.finish();
+ res.finish();
+ page.finish();
+
+ let mut p = Page::new();
// Write Header
- line(&bld, &mut y, 24.0, title);
- y -= 2.2;
- line(®, &mut y, 13.0, &format!("Datum: {date}"));
- line(®, &mut y, 13.0, &format!("Host-ID: {}", cfg.host_id));
- line(®, &mut y, 13.0, &format!("User-ID: {}", cfg.user_id));
- line(
- ®,
- &mut y,
- 11.0,
+ p.line(f_bld, 24.0, title);
+ p.y -= 2.2;
+ for header in [
+ format!("Datum: {date}").as_str(),
+ &format!("Host-ID: {}", cfg.host_id),
+ &format!("User-ID: {}", cfg.user_id),
&format!("Partner-ID: {}", cfg.partner_id),
- );
- line(®, &mut y, 13.0, "ES version: A006");
- y -= 5.0;
- line(&bld, &mut y, 13.0, label);
- y -= 5.0;
+ "ES version: A006",
+ ] {
+ p.line(f_reg, 13.0, header);
+ }
+
+ p.y -= 5.0;
+ p.line(f_bld, 13.0, label);
+ p.y -= 5.0;
+ // Write Public Key Fields
for (hdr, bytes) in [
(
"Exponent:",
@@ -92,20 +162,22 @@ pub fn generate_keys_pdf(keys: &ClientKeys, cfg: &EbicsHostCfg) -> anyhow::Resul
),
("SHA-256 hash:", ebics_pub_key_hash(pub_key).as_ref()),
] {
- line(&bld, &mut y, 13.0, hdr);
+ p.line(f_bld, 13.0, hdr);
for chunk in bytes.chunks(16) {
- line(®, &mut y, 11.0, &hex_chunk_by_two(chunk).to_string());
+ p.line(f_reg, 11.0, &hex_chunk_by_two(chunk).to_string());
}
- y -= 5.0;
+ p.y -= 5.0;
}
// Write Footer
- line(®, &mut y, 13.0, "Ort / Datum: ________________");
- line(®, &mut y, 13.0, "Firma / Name: ________________");
- line(®, &mut y, 13.0, "Unterschrift: ________________");
+ p.line(f_reg, 13.0, "Ort / Datum: ________________");
+ p.line(f_reg, 13.0, "Firma / Name: ________________");
+ p.line(f_reg, 13.0, "Unterschrift: ________________");
+
+ pdf.stream(content_ids[i], &p.content.finish());
}
- Ok(doc.save_to_bytes()?)
+ Ok(pdf.finish())
}
#[test]
diff --git a/testbench/src/main.rs b/testbench/src/main.rs
@@ -77,7 +77,7 @@ pub struct TestbenchCmd {
}
#[derive(Parser)]
-#[command(name = "shell", no_binary_name = true)]
+#[command(no_binary_name = true)]
/// Run integration tests on banks provider
pub enum NexusCmd {
#[clap(alias = "ebics-setup")]
@@ -103,7 +103,7 @@ pub enum NexusCmd {
}
#[derive(Parser)]
-#[command(name = "shell", no_binary_name = true)]
+#[command(no_binary_name = true)]
/// Run integration tests on banks provider
pub enum SyncCmd {
#[clap(alias = "ebics-setup")]