mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
Merge remote-tracking branch 'upstream/main' into fix/xfa-layout-drops-usage-rights
# Conflicts: # parity/acrobat-features.toml
This commit is contained in:
+3
-3
@@ -275,15 +275,15 @@ Every asset PdfCraft includes, bundles or uses to build its published material,
|
||||
| `assets/icons/thumbs-down.svg` | Lucide icon "thumbs-down" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/thumbs-down.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `assets/icons/message-square-plus.svg` | Lucide icon "message-square-plus" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/message-square-plus.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `assets/icons/circle-x.svg` | Lucide icon "circle-x" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/circle-x.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `crates/ui-egui/tests/data/form.pdf` | One-page AcroForm test fixture (text, check box, radio group, combo box) | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: hand-written PDF objects, no third-party content | Form-filling UI tests (crates/ui-egui/tests/forms.rs, commands.rs) |
|
||||
| `crates/ui-egui/tests/data/form.pdf` | One-page AcroForm test fixture (text, check box, radio group, combo box) | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: hand-written PDF objects, no third-party content | Form-filling UI tests (crates/ui-egui/tests/it/forms.rs, commands.rs) |
|
||||
| `assets/icons/pentagon.svg` | Lucide icon "pentagon" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/pentagon.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `assets/icons/message-square-quote.svg` | Lucide icon "message-square-quote" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/message-square-quote.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `crates/sign/tests/data/openssl-signed.pdf` | One-page PDF signed with OpenSSL CMS (adbe.pkcs7.detached) by the RSA test key | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: hand-written PDF objects; signature made with `openssl cms -sign` over its byte ranges (see crates/sign/tests/data/README.md) | Signature validation tests (crates/sign/tests/pdf.rs): a signature PdfCraft did not make |
|
||||
| `crates/sign/tests/data/openssl-signed.pdf` | One-page PDF signed with OpenSSL CMS (adbe.pkcs7.detached) by the RSA test key | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: hand-written PDF objects; signature made with `openssl cms -sign` over its byte ranges (see crates/sign/tests/data/README.md) | Signature validation tests (crates/sign/tests/it/pdf.rs): a signature PdfCraft did not make |
|
||||
| `assets/icons/clipboard-paste.svg` | Lucide icon "clipboard-paste" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/clipboard-paste.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `assets/fonts/DancingScript.ttf` | Dancing Script (variable) | The Dancing Script Project Authors (Pablo Impallari) | OFL-1.1 | https://github.com/google/fonts/blob/9710da1eacb3be272583c3224dcb70f9da6eadbb/ofl/dancingscript/DancingScript%5Bwght%5D.ttf | Fill & Sign typed signatures and initials: the typed name's glyph outlines, drawn as filled paths (the font file itself is not embedded in PDFs) |
|
||||
| `packaging/macos/dmg/background.svg` | PdfCraft macOS DMG window background | @XusBadia | MIT OR Apache-2.0 | Original work for this repository, derived from the PdfCraft app icon (assets/app-icon/pdfcraft.svg, referenced via an SVG image element, not copied) | Finder window of the macOS DMG; rendered to background.tiff by packaging/macos/dmg/generate.py |
|
||||
| `packaging/macos/dmg/background.tiff` | PdfCraft macOS DMG window background (rendered) | @XusBadia | MIT OR Apache-2.0 | Rendered from packaging/macos/dmg/background.svg by packaging/macos/dmg/generate.py | 1x + 2x HiDPI 16-colour palette TIFF copied into the macOS DMG by packaging/macos/package.sh; includes the app icon and the free LittleCMS built-in sRGB profile |
|
||||
| `crates/sign/tests/data/x509-rsa-sha1.pdf` | One-page PDF with a legacy adbe.x509.rsa_sha1 signature by the RSA test key | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: hand-written PDF objects; signature made with `openssl dgst -sha1 -sign` over its byte ranges (see crates/sign/tests/data/README.md) | Signature validation tests (crates/sign/tests/pdf.rs): the legacy adbe.x509.rsa_sha1 format |
|
||||
| `crates/sign/tests/data/x509-rsa-sha1.pdf` | One-page PDF with a legacy adbe.x509.rsa_sha1 signature by the RSA test key | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: hand-written PDF objects; signature made with `openssl dgst -sha1 -sign` over its byte ranges (see crates/sign/tests/data/README.md) | Signature validation tests (crates/sign/tests/it/pdf.rs): the legacy adbe.x509.rsa_sha1 format |
|
||||
| `assets/icons/monitor.svg` | Lucide icon "monitor" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/monitor.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `crates/ui-egui/src/i18n/vi.tsv` | Vietnamese interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels; PhotoCraft and VectorCraft Vietnamese catalogs used only as translation memory when English source and semantic context matched; original catalog reviewed by a native Vietnamese-speaking contributor; 2026-10-15 coverage additions are AI-assisted and not separately native-reviewed; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed | Vietnamese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `crates/ui-egui/src/i18n/sl.tsv` | Slovene interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed | Slovene UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
|
||||
+3
-3
@@ -3277,7 +3277,7 @@ source = "Contributor-original: hand-written PDF objects, no third-party content
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "document"
|
||||
usage = "Form-filling UI tests (crates/ui-egui/tests/forms.rs, commands.rs)"
|
||||
usage = "Form-filling UI tests (crates/ui-egui/tests/it/forms.rs, commands.rs)"
|
||||
sha256 = "c6f8b656e225101129aa36bd5467adc8dba16730915f19caca17f6a546d0b040"
|
||||
|
||||
[[asset]]
|
||||
@@ -3310,7 +3310,7 @@ source = "Contributor-original: hand-written PDF objects; signature made with `o
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "document"
|
||||
usage = "Signature validation tests (crates/sign/tests/pdf.rs): a signature PdfCraft did not make"
|
||||
usage = "Signature validation tests (crates/sign/tests/it/pdf.rs): a signature PdfCraft did not make"
|
||||
sha256 = "8b91c3b4e39c4a31717a0dd0685bd7d0468671df32aee5b9818d44dc72c2bc3c"
|
||||
|
||||
[[asset]]
|
||||
@@ -3403,7 +3403,7 @@ source = "Contributor-original: hand-written PDF objects; signature made with `o
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "document"
|
||||
usage = "Signature validation tests (crates/sign/tests/pdf.rs): the legacy adbe.x509.rsa_sha1 format"
|
||||
usage = "Signature validation tests (crates/sign/tests/it/pdf.rs): the legacy adbe.x509.rsa_sha1 format"
|
||||
sha256 = "b77bfb21166f4bd043597e45a1ea28b9d9f4ba6df0142b3a0fc8b7f8e0fc111b"
|
||||
|
||||
[[asset]]
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
</p>
|
||||
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/app-icon/hicolor/256x256/apps/ai.storyteller.pdfcraft.png" width="128" height="128" alt="PdfCraft icon: an engraved lion on a teal tile">
|
||||
</p>
|
||||
|
||||
<h1 align="center">PdfCraft</h1>
|
||||
|
||||
<p align="center">
|
||||
|
||||
@@ -24,7 +24,7 @@ pdfcraft-platform = { workspace = true }
|
||||
base64 = "0.22"
|
||||
|
||||
[dev-dependencies]
|
||||
# Decodes `render` output in tests/owner_password.rs; already a dependency of the engine.
|
||||
# Decodes `render` output in tests/it/owner_password.rs; already a dependency of the engine.
|
||||
png = "0.18"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
//! The crate's integration tests, linked into one test binary.
|
||||
|
||||
mod audit;
|
||||
mod automation_roots;
|
||||
mod batch;
|
||||
mod check;
|
||||
mod edit;
|
||||
mod extract;
|
||||
mod help;
|
||||
mod info_standards;
|
||||
mod mcp_compact;
|
||||
mod options;
|
||||
mod owner_password;
|
||||
mod render;
|
||||
mod root_arguments;
|
||||
mod run_report;
|
||||
mod run_root;
|
||||
mod split;
|
||||
mod stderr;
|
||||
mod stdout;
|
||||
mod text;
|
||||
mod ui_control;
|
||||
mod version_resource;
|
||||
mod xfa;
|
||||
@@ -780,7 +780,7 @@ mod tests {
|
||||
/// graphics libraries at a time. The test threads otherwise run them together, and on
|
||||
/// FreeBSD's CI machine the test process then died with SIGSEGV, in about half the runs:
|
||||
/// the first device was still being set up when the second test began its own. The UI
|
||||
/// tests take turns the same way (`crates/ui-egui/tests/view.rs`). Hold it for the whole
|
||||
/// tests take turns the same way (`crates/ui-egui/tests/it/main.rs`). Hold it for the whole
|
||||
/// test (declared before the instance, so it's released after the device is dropped).
|
||||
static GPU: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
//! The crate's integration tests, linked into one test binary.
|
||||
|
||||
// Compile winit's scroll policy against its real event type, without a compositor. The vendored
|
||||
// source (`winit_wayland_scroll`) names it as `crate::event`.
|
||||
#[cfg(target_os = "linux")]
|
||||
mod event {
|
||||
pub use winit::event::TouchPhase;
|
||||
}
|
||||
|
||||
mod winit_macos_emoji_picker;
|
||||
mod winit_macos_ime_commit;
|
||||
mod winit_macos_ime_discard;
|
||||
mod winit_macos_korean_first_key;
|
||||
mod winit_macos_korean_space;
|
||||
mod winit_macos_title_bar_drag;
|
||||
mod winit_wayland_scroll;
|
||||
mod winit_wayland_text_input;
|
||||
mod winit_windows_hidden_maximized;
|
||||
mod winit_windows_ime_cursor;
|
||||
mod winit_x11_dpi;
|
||||
mod winit_x11_ime_focus;
|
||||
+2
-5
@@ -3,11 +3,8 @@
|
||||
use egui::{Event, Modifiers, MouseWheelUnit, RawInput, TouchPhase, vec2};
|
||||
use wayland_client::protocol::wl_pointer::AxisSource;
|
||||
|
||||
// Compile the production policy against winit's real event type, without a compositor.
|
||||
mod event {
|
||||
pub use winit::event::TouchPhase;
|
||||
}
|
||||
#[path = "../../../vendor/winit/src/platform_impl/linux/wayland/seat/pointer/scroll.rs"]
|
||||
use crate::event;
|
||||
#[path = "../../../../vendor/winit/src/platform_impl/linux/wayland/seat/pointer/scroll.rs"]
|
||||
mod scroll;
|
||||
|
||||
fn egui_phase(phase: event::TouchPhase) -> TouchPhase {
|
||||
+10
-10
@@ -401,12 +401,12 @@ a.box { position: absolute; display: block; color: transparent; font-size: 1pt;
|
||||
</div>
|
||||
<h3>The families in this specimen</h3>
|
||||
<div class="families">
|
||||
<div><span style="font-family:'PC Playfair'">Ag</span><b>Playfair Display</b>Display</div>
|
||||
<div><span style="font-family:'PC Garamond'">Ag</span><b>EB Garamond</b>Body text</div>
|
||||
<div><span style="font-family:'PC Inter';font-weight:500">Ag</span><b>Inter</b>Labels</div>
|
||||
<div><span style="font-family:'PC Mono'">Ag</span><b>JetBrains Mono</b>Code</div>
|
||||
<div><span style="font-family:'PC Vibes'">Ag</span><b>Great Vibes</b>Script</div>
|
||||
<div><span style="font-family:'PC Math'">∑x</span><b>STIX Two Math</b>Mathematics</div>
|
||||
<div><span style="font-family:'PC Playfair', serif">Ag</span><b>Playfair Display</b>Display</div>
|
||||
<div><span style="font-family:'PC Garamond', serif">Ag</span><b>EB Garamond</b>Body text</div>
|
||||
<div><span style="font-family:'PC Inter', sans-serif;font-weight:500">Ag</span><b>Inter</b>Labels</div>
|
||||
<div><span style="font-family:'PC Mono', monospace">Ag</span><b>JetBrains Mono</b>Code</div>
|
||||
<div><span style="font-family:'PC Vibes', cursive">Ag</span><b>Great Vibes</b>Script</div>
|
||||
<div><span style="font-family:'PC Math', math">∑x</span><b>STIX Two Math</b>Mathematics</div>
|
||||
</div>
|
||||
<div class="folio"><span>Foreword</span><b>ii</b></div>
|
||||
</section>
|
||||
@@ -449,7 +449,7 @@ a.box { position: absolute; display: block; color: transparent; font-size: 1pt;
|
||||
<tr><td class="tag">onum</td><td class="lbl">Old-style figures</td><td class="off" style="font-feature-settings:'lnum' 1">Since 1885: 3,467,920</td><td class="on" style="font-feature-settings:'onum' 1">Since 1885: 3,467,920</td></tr>
|
||||
<tr><td class="tag">frac</td><td class="lbl">Fractions</td><td class="off" style="font-family:var(--ui)">1/2 cup, 3/4 inch, 7/8</td><td class="on" style="font-family:var(--ui);font-feature-settings:'frac' 1">1/2 cup, 3/4 inch, 7/8</td></tr>
|
||||
<tr><td class="tag">swsh</td><td class="lbl">Swash capitals</td><td class="off" style="font-style:italic">The Quiet River</td><td class="on" style="font-style:italic;font-feature-settings:'swsh' 1">The Quiet River</td></tr>
|
||||
<tr><td class="tag">ss01</td><td class="lbl">Stylistic set 1</td><td class="off" style="font-family:'PC Inter'">a 1 4 6 9 G ?</td><td class="on" style="font-family:'PC Inter';font-feature-settings:'ss01' 1">a 1 4 6 9 G ?</td></tr>
|
||||
<tr><td class="tag">ss01</td><td class="lbl">Stylistic set 1</td><td class="off" style="font-family:'PC Inter', sans-serif">a 1 4 6 9 G ?</td><td class="on" style="font-family:'PC Inter', sans-serif;font-feature-settings:'ss01' 1">a 1 4 6 9 G ?</td></tr>
|
||||
<tr><td class="tag">kern</td><td class="lbl">Kerning</td><td class="off" style="font-kerning:none;font-family:var(--sans)">AVATAR Type Wave</td><td class="on" style="font-kerning:normal;font-family:var(--sans)">AVATAR Type Wave</td></tr>
|
||||
<tr><td class="tag">tnum</td><td class="lbl">Tabular figures</td><td class="off" style="font-family:var(--ui);font-feature-settings:'pnum' 1">1111 / 8888</td><td class="on" style="font-family:var(--ui);font-feature-settings:'tnum' 1">1111 / 8888</td></tr>
|
||||
</table>
|
||||
@@ -519,9 +519,9 @@ a.box { position: absolute; display: block; color: transparent; font-size: 1pt;
|
||||
<p class="dek">A short greeting — roughly, “every letter deserves a good home” — in writing systems that need bidirectional layout, contextual shaping, reordering and vertical setting.</p>
|
||||
<div style="display:grid;grid-template-columns:1fr 1in;gap:16pt">
|
||||
<div class="scripts">
|
||||
<div class="sc" lang="ar" dir="rtl"><div class="nm" dir="ltr">Arabic <i>RTL · joining</i></div><div class="tx" style="font-family:'PC Amiri'">لكل حرف بيت</div><div class="en" dir="ltr">Amiri</div></div>
|
||||
<div class="sc" lang="ar" dir="rtl"><div class="nm" dir="ltr">Arabic <i>RTL · joining</i></div><div class="tx" style="font-family:'PC Amiri', serif">لكل حرف بيت</div><div class="en" dir="ltr">Amiri</div></div>
|
||||
<div class="sc" lang="he" dir="rtl"><div class="nm" dir="ltr">Hebrew <i>RTL</i></div><div class="tx" style="font-family:'PC Hebrew',sans-serif">לכל אות יש בית טוב</div><div class="en" dir="ltr">Noto Sans Hebrew</div></div>
|
||||
<div class="sc" lang="hi"><div class="nm">Devanagari <i>conjuncts</i></div><div class="tx" style="font-family:'PC Devanagari'">हर अक्षर का घर</div><div class="en">Noto Sans Devanagari</div></div>
|
||||
<div class="sc" lang="hi"><div class="nm">Devanagari <i>conjuncts</i></div><div class="tx" style="font-family:'PC Devanagari', sans-serif">हर अक्षर का घर</div><div class="en">Noto Sans Devanagari</div></div>
|
||||
<div class="sc" lang="th"><div class="nm">Thai <i>no spaces</i></div><div class="tx" style="font-family:'PC Thai',sans-serif">ทุกอักษรมีบ้าน</div><div class="en">Noto Sans Thai</div></div>
|
||||
<div class="sc" lang="el"><div class="nm">Greek <i>polytonic</i></div><div class="tx" style="font-family:'PC Garamond',serif">Κάθε γράμμα ἔχει σπίτι</div><div class="en">EB Garamond</div></div>
|
||||
<div class="sc" lang="ru"><div class="nm">Cyrillic</div><div class="tx" style="font-family:'PC Garamond',serif">Каждой букве — дом</div><div class="en">EB Garamond</div></div>
|
||||
@@ -541,7 +541,7 @@ a.box { position: absolute; display: block; color: transparent; font-size: 1pt;
|
||||
</div>
|
||||
|
||||
<h3>Mixed direction in one line</h3>
|
||||
<p style="font-size:12pt;margin:0">The word <span lang="ar" style="font-family:'PC Amiri'">كتاب</span> means “book”, and <span lang="he" style="font-family:'PC Hebrew'">ספר</span> does too — the bidi algorithm keeps each run in its own direction while the sentence stays left-to-right, even across numbers like <span lang="ar" style="font-family:'PC Amiri'">٣٤٥</span> and 345.</p>
|
||||
<p style="font-size:12pt;margin:0">The word <span lang="ar" style="font-family:'PC Amiri', serif">كتاب</span> means “book”, and <span lang="he" style="font-family:'PC Hebrew', serif">ספר</span> does too — the bidi algorithm keeps each run in its own direction while the sentence stays left-to-right, even across numbers like <span lang="ar" style="font-family:'PC Amiri', serif">٣٤٥</span> and 345.</p>
|
||||
|
||||
<h3>Colour emoji</h3>
|
||||
<div class="emoji">📄🖋️🎨📐🔍🌍🧮✅🦀📎</div>
|
||||
|
||||
@@ -128,7 +128,7 @@ claude mcp add pdfcraft -- pdfcraft-cli mcp --root <dir>
|
||||
1. Add the engine capability first, with its tests (the tool is a thin adapter).
|
||||
2. Add a `ToolDef` in `src/tools.rs`: a precise description, the schema, `ro()`/`destructive()`, and `cmd()` if a registry command exists.
|
||||
3. Handle it in `Automation::call`, validating pages and positions with the existing helpers.
|
||||
4. Add an end-to-end test in `tests/automation.rs`. `tool_table_is_well_formed` checks names, schemas and command links.
|
||||
4. Add an end-to-end test in `tests/it/automation.rs`. `tool_table_is_well_formed` checks names, schemas and command links.
|
||||
|
||||
|
||||
## Moving several page objects
|
||||
|
||||
@@ -539,7 +539,7 @@ fn tool_table_is_well_formed() {
|
||||
#[test]
|
||||
fn explicit_font_and_disjoint_ranges_save_reopen_through_tools() {
|
||||
let dir = workdir("font-ranges");
|
||||
std::fs::write(dir.join("inter.ttf"), include_bytes!("../../../assets/fonts/Inter-Regular.ttf")).unwrap();
|
||||
std::fs::write(dir.join("inter.ttf"), include_bytes!("../../../../assets/fonts/Inter-Regular.ttf")).unwrap();
|
||||
let mut a = auto(&dir);
|
||||
std::fs::write(dir.join("broken.otf"), b"not a font").unwrap();
|
||||
let found = ok(&mut a, "text_font_files", json!({}));
|
||||
@@ -0,0 +1,9 @@
|
||||
//! The crate's integration tests, linked into one test binary.
|
||||
|
||||
mod automation;
|
||||
mod automation_roots;
|
||||
mod batch;
|
||||
mod button_caption;
|
||||
mod form_bulk;
|
||||
mod hostile_args;
|
||||
mod optimize_content_boundaries;
|
||||
@@ -0,0 +1,8 @@
|
||||
//! The crate's integration tests, linked into one test binary.
|
||||
|
||||
mod bounded_writer;
|
||||
mod corpus;
|
||||
mod encryption;
|
||||
mod proptests;
|
||||
mod shared_streams;
|
||||
mod writer;
|
||||
@@ -0,0 +1,4 @@
|
||||
//! The crate's integration tests, linked into one test binary.
|
||||
|
||||
mod proptests;
|
||||
mod qpdf_literals;
|
||||
@@ -278,7 +278,7 @@ fn metadata_flag_from_outside_changes_the_file_key_it_certifies() {
|
||||
// `/EncryptMetadata` false folds four set bytes into a revision 4 key (Algorithm 2, step f) and
|
||||
// changes what revision 6 stores in `/U`. qpdf wrote both variants of each, so an implementation
|
||||
// that ignored the flag would give identical keys. Internally this is no longer untested -
|
||||
// `tests/proptests.rs` from #2358 varies the flag and asserts the keys differ - but internal tests
|
||||
// `tests/it/proptests.rs` from #2358 varies the flag and asserts the keys differ - but internal tests
|
||||
// still share our derivation, so what these rows add is a second implementation's own numbers.
|
||||
for (enc_label, plain_label) in [("r4-aes128-meta-enc", "r4-aes128-meta-plain"), ("r6-aes256-meta-enc", "r6-aes256-meta-plain")] {
|
||||
let a = CASES.iter().find(|c| c.label == enc_label).expect("encrypted-metadata fixture present");
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Document font selection and contextual glyph save/reopen acceptance on synthetic PDFs.
|
||||
mod support {
|
||||
pub mod font_fixture;
|
||||
}
|
||||
use crate::support;
|
||||
use pdfcraft_cos::{Dict, Document, Object, SaveOptions, Stream};
|
||||
use pdfcraft_engine::{BlockStyle, Edit, Session};
|
||||
use pdfcraft_render::{PageRenderer, RenderRequest};
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The crate's integration tests, linked into one test binary.
|
||||
|
||||
mod support {
|
||||
pub mod font_fixture;
|
||||
}
|
||||
|
||||
mod document_fonts;
|
||||
mod form_rotation;
|
||||
mod marked_content_isolation;
|
||||
mod ocr_edit;
|
||||
mod private_image_resize;
|
||||
mod private_text_glyphs;
|
||||
@@ -15,7 +15,7 @@ Every test that wanted an oracle used to probe for it by hand and decide on its
|
||||
it was missing. The common choice — print a line and return — reports as a **pass**, so a suite
|
||||
with no oracles installed looked exactly like one in which every oracle agreed.
|
||||
|
||||
That was not hypothetical. No CI runner installed `qpdf`, so `cos/tests/encryption.rs::qpdf_agrees`
|
||||
That was not hypothetical. No CI runner installed `qpdf`, so `cos/tests/it/encryption.rs::qpdf_agrees`
|
||||
had never executed on a runner, while `README.md` said saved output was "independently checked with
|
||||
qpdf". For R2–R4 that test is the *only* check of owner-password authentication (Algorithm 7) that
|
||||
does not go through our own code.
|
||||
@@ -50,10 +50,10 @@ Each returns `Result<Oracle, Absent>`. `Oracle::command()` hands back a fresh `C
|
||||
install the tools, so a skip notice there becomes a red build.
|
||||
|
||||
```
|
||||
$ cargo test -p pdfcraft-cos --test encryption qpdf_agrees
|
||||
$ cargo test -p pdfcraft-cos --test it encryption::qpdf_agrees
|
||||
test result: ok. 1 passed # qpdf absent: skipped, with a notice on stderr
|
||||
|
||||
$ PDFCRAFT_ORACLES_REQUIRED=1 cargo test -p pdfcraft-cos --test encryption qpdf_agrees
|
||||
$ PDFCRAFT_ORACLES_REQUIRED=1 cargo test -p pdfcraft-cos --test it encryption::qpdf_agrees
|
||||
oracle `qpdf` is required but is not on PATH (program not found): PDFCRAFT_ORACLES_REQUIRED is
|
||||
set, so this is a failure rather than a skip (apt install qpdf / brew install qpdf)
|
||||
test result: FAILED. 1 failed
|
||||
@@ -90,7 +90,7 @@ appearance we do draw.
|
||||
**Poppler is not a usable oracle for JBIG2.** The 96 `bitmap-*.pdf` corpus fixtures encode one
|
||||
picture through every corner of the format, so a correct decoder renders them all identically. We
|
||||
render 1 distinct image across all 96; Poppler renders 8, drawing a blank page for seven of them.
|
||||
`crates/render/tests/jbig2_invariants.rs` pins that invariant, and needs no oracle to do it.
|
||||
`crates/render/tests/it/jbig2_invariants.rs` pins that invariant, and needs no oracle to do it.
|
||||
|
||||
## Comparing text
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
//! The crate's integration tests, linked into one test binary.
|
||||
|
||||
mod inline_image_ei;
|
||||
mod jbig2_invariants;
|
||||
mod named_form_properties;
|
||||
mod search;
|
||||
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! Verification only, on public data, so plain affine arithmetic over `crypto-bigint` is enough:
|
||||
//! no secret is involved, and a verification takes a few milliseconds. Checked against
|
||||
//! signatures made by OpenSSL (`tests/crypto.rs`).
|
||||
//! signatures made by OpenSSL (`tests/it/crypto.rs`).
|
||||
|
||||
use rsa::BoxedUint;
|
||||
|
||||
|
||||
@@ -45,14 +45,14 @@ A synthetic one-page PDF signed the legacy way (`/SubFilter /adbe.x509.rsa_sha1`
|
||||
the key of `rsa-aes.p12`. Built by writing the objects with a fixed-width `/ByteRange` and zero-filled
|
||||
`/Contents`, patching the byte range, then `openssl dgst -sha1 -sign rsa.key` over the two ranges.
|
||||
|
||||
### RSA signature variants (hex constants in `tests/crypto.rs`)
|
||||
### RSA signature variants (hex constants in `tests/it/crypto.rs`)
|
||||
|
||||
Over SHA-256("hello") with the key of `rsa-aes.p12`:
|
||||
`openssl dgst -sha256 -sign rsa.key` (standard DigestInfo); `openssl pkeyutl -sign -pkeyopt rsa_padding_mode:pkcs1`
|
||||
over a hand-built DigestInfo without the NULL parameter, and over the bare digest;
|
||||
`openssl dgst -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:0 -sign rsa.key`.
|
||||
|
||||
### Other algorithms (hex constants in `tests/algorithms.rs`)
|
||||
### Other algorithms (hex constants in `tests/it/algorithms.rs`)
|
||||
|
||||
All sign the message `hello` with throw-away keys, OpenSSL 3.6. RSA ones use the key of `rsa-aes.p12`.
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ fn verdict(leaf: Party, embedded: Vec<Certificate>, anchor: &Certificate) -> (St
|
||||
let trust = TrustStore { certs: vec![anchor.clone()] };
|
||||
let s = signatures(&Document::open(Arc::new(signed.clone())).unwrap(), &signed, &trust).into_iter().rfind(|s| s.signed).unwrap();
|
||||
// The details as they read in English; what an interface says in another language is checked in
|
||||
// `tests/pdf.rs`.
|
||||
// `tests/it/pdf.rs`.
|
||||
(s.status, s.details.iter().map(|d| d.message.clone()).collect())
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ fn the_signed_content_type_is_the_type_of_the_encapsulated_content() {
|
||||
assert!(why.contains("not the encapsulated content type"), "{why}");
|
||||
|
||||
// Without a content-type attribute there is nothing to compare here. The type is kept, and a
|
||||
// PDF signature's validation refuses one that is not data (tests/pdf.rs).
|
||||
// PDF signature's validation refuses one that is not data (tests/it/pdf.rs).
|
||||
let sd = SignedData::parse(&signed_data(&id, SIGNED_DATA, std::slice::from_ref(&message_digest))).unwrap();
|
||||
assert_eq!(sd.econtent_type, SIGNED_DATA);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//! The crate's integration tests, linked into one test binary.
|
||||
|
||||
mod algorithms;
|
||||
mod ber;
|
||||
mod chain;
|
||||
mod cms;
|
||||
mod crypto;
|
||||
mod pdf;
|
||||
mod store_keys;
|
||||
@@ -2182,11 +2182,15 @@ fn appearance_label_outside_helvetica_is_refused_beside_an_embedded_font_name()
|
||||
/// the files. An interface then has these to translate and only these.
|
||||
#[test]
|
||||
fn wordings_are_complete() {
|
||||
let source = include_str!("../src/pdf.rs");
|
||||
let source = include_str!("../../src/pdf.rs");
|
||||
// The modules below `pdf` word whole sentences too: how a signature value had to be read, and
|
||||
// what a certificate does not allow.
|
||||
let elsewhere =
|
||||
[include_str!("../src/x509.rs"), include_str!("../src/cms.rs"), include_str!("../src/keys.rs"), include_str!("../src/rsa_pad.rs")];
|
||||
let elsewhere = [
|
||||
include_str!("../../src/x509.rs"),
|
||||
include_str!("../../src/cms.rs"),
|
||||
include_str!("../../src/keys.rs"),
|
||||
include_str!("../../src/rsa_pad.rs"),
|
||||
];
|
||||
/// A sentence said to the user, as opposed to an OID, a name or a tag: it reads as English.
|
||||
fn is_sentence(s: &str) -> bool {
|
||||
s.len() > 24 && s.ends_with('.') && s.contains(' ') && s.starts_with(|c: char| c.is_ascii_uppercase())
|
||||
@@ -499,7 +499,7 @@ mod tests {
|
||||
|
||||
/// `script_fallback` honours the documented installed-font switch, under which it returns
|
||||
/// nothing before order is ever consulted — so both loader tests and the macOS oracle skip when
|
||||
/// it is off, the way `tests/fonts.rs` skips for the same reason. Without this the suite goes red in exactly the
|
||||
/// it is off, the way `tests/it/fonts.rs` skips for the same reason. Without this the suite goes red in exactly the
|
||||
/// mode `cargo xtask screenshots` uses.
|
||||
fn installed_faces_switched_off() -> bool {
|
||||
std::env::var_os("PDFCRAFT_SYSTEM_FONTS").is_some_and(|v| v == "0")
|
||||
@@ -527,7 +527,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_script_fallback_is_the_first_candidate_with_the_probe() {
|
||||
// This one calls `script_fallback` too, so it fails under the documented switch that
|
||||
// published screenshots use — skip with a reason, as tests/fonts.rs does.
|
||||
// published screenshots use — skip with a reason, as tests/it/fonts.rs does.
|
||||
if installed_faces_switched_off() {
|
||||
eprintln!("skipping: PDFCRAFT_SYSTEM_FONTS=0 turns the loader off");
|
||||
return;
|
||||
|
||||
@@ -181,6 +181,7 @@ fn search_finds_collapsed_bookmarks_and_keeps_original_paths() {
|
||||
assert!(h.query_by_label("Other chapter").is_none());
|
||||
assert_eq!(h.state().session.save_full_bytes(id).unwrap(), before, "search doesn't edit the PDF");
|
||||
if let Ok(dir) = std::env::var("PDFCRAFT_BOOKMARK_SHOTS") {
|
||||
let _gpu = crate::gpu();
|
||||
h.render().unwrap().save(std::path::Path::new(&dir).join("filtered-bookmarks.png")).unwrap();
|
||||
}
|
||||
menu(&mut h, "Résumé results", "Delete");
|
||||
@@ -243,6 +244,7 @@ trailer << /Root 1 0 R >>
|
||||
h.get_by_label("Method");
|
||||
assert_eq!(h.state().session.get(id).unwrap().can_undo(), Some("New bookmarks from structure"));
|
||||
if let Ok(dir) = std::env::var("PDFCRAFT_BOOKMARK_SHOTS") {
|
||||
let _gpu = crate::gpu();
|
||||
h.render().unwrap().save(std::path::Path::new(&dir).join("bookmarks-from-structure.png")).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
//! The command registry drives menus, shortcuts and the palette: every registered command must
|
||||
//! be implemented, disabled commands must say why, and every surface must reach the same action.
|
||||
|
||||
mod support;
|
||||
use crate::support;
|
||||
|
||||
use egui::os::OperatingSystem;
|
||||
use egui::{Key, Modifiers};
|
||||
@@ -71,14 +71,14 @@ const PICKERS: &[&str] = &[
|
||||
#[test]
|
||||
fn every_registered_command_is_implemented() {
|
||||
for spec in COMMANDS {
|
||||
// Page-text Copy needs a loaded view and egui context, covered by ui_group::text_select.
|
||||
// Page-text Copy needs a loaded view and egui context, covered by text_select.
|
||||
if PICKERS.contains(&spec.id) || spec.id == "edit.copy" {
|
||||
continue;
|
||||
}
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.set_option("language", "en").unwrap();
|
||||
if spec.id.starts_with("form.") || spec.id == "comment.flatten" {
|
||||
app.open_bytes("form.pdf", None, include_bytes!("data/form.pdf").to_vec()).unwrap();
|
||||
app.open_bytes("form.pdf", None, include_bytes!("../data/form.pdf").to_vec()).unwrap();
|
||||
} else if spec.id == "protect.unlock" {
|
||||
// Only a permissions password: opens restricted, and can be unlocked.
|
||||
let mut doc = pdfcraft_cos::Document::open(std::sync::Arc::new(fixture(3))).unwrap();
|
||||
@@ -7,7 +7,7 @@ use egui_kittest::kittest::Queryable;
|
||||
use pdfcraft_render::Annotation;
|
||||
use pdfcraft_ui_egui::{PdfCraftApp, QuickTool};
|
||||
|
||||
mod support;
|
||||
use crate::support;
|
||||
|
||||
/// Two 300×200 pages of Helvetica text.
|
||||
const TEXT_FIXTURE: &[u8] = b"%PDF-1.7
|
||||
@@ -145,6 +145,7 @@ fn sidebar_comment_centres_the_annotation_at_high_zoom() {
|
||||
&& rotation == 0
|
||||
&& let Ok(dir) = std::env::var("PDFCRAFT_SHOTS")
|
||||
{
|
||||
let _gpu = crate::gpu();
|
||||
settle(&mut h);
|
||||
h.render().unwrap().save(format!("{dir}/comment-centred.png")).unwrap();
|
||||
}
|
||||
@@ -164,6 +164,7 @@ fn compare_filters_hide_text_or_visual_changes() {
|
||||
#[test]
|
||||
fn compare_panel_screenshot() {
|
||||
let Ok(out) = std::env::var("PDFCRAFT_SHOT") else { return };
|
||||
let _gpu = crate::gpu();
|
||||
let s = Session::new();
|
||||
let v1 = s.create_from_text("t", "Delivery within five days. Returns accepted.").unwrap().to_vec();
|
||||
let v2 = s.create_from_text("t", "Delivery within three days. Returns accepted for a week.").unwrap().to_vec();
|
||||
@@ -6,23 +6,14 @@ use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use pdfcraft_ui_egui::{PdfCraftApp, QuickTool};
|
||||
|
||||
/// Tests that render pixels (`h.render()`) take this first, so only one wgpu device exists at a
|
||||
/// time. Lavapipe crashes when another test creates its Vulkan instance meanwhile (segfault in
|
||||
/// `vkCreateSampler` during a concurrent `vkCreateInstance`). Declared before the harness, so it
|
||||
/// is released after the device is dropped.
|
||||
static GPU: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
|
||||
fn gpu() -> std::sync::MutexGuard<'static, ()> {
|
||||
// A test that panicked while holding it leaves nothing to clean up.
|
||||
GPU.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
}
|
||||
use crate::gpu;
|
||||
|
||||
fn harness() -> Harness<'static, PdfCraftApp> {
|
||||
harness_with_step_dt(0.25)
|
||||
}
|
||||
|
||||
fn harness_with_step_dt(step_dt: f32) -> Harness<'static, PdfCraftApp> {
|
||||
harness_bytes_with_step_dt(include_bytes!("data/form.pdf").to_vec(), step_dt)
|
||||
harness_bytes_with_step_dt(include_bytes!("../data/form.pdf").to_vec(), step_dt)
|
||||
}
|
||||
|
||||
fn harness_bytes(bytes: Vec<u8>) -> Harness<'static, PdfCraftApp> {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user