mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-13 06:04:59 +00:00
Merge upstream/main into feat/recent-reveal-5452
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
# signatures) and assets whose SHA-256 ATTRIBUTION.toml records (cargo xtask assets).
|
||||
* -text
|
||||
|
||||
# The craft-i18n pin: CI and the build read it as one SHA, so it stays LF everywhere (readers trim it too).
|
||||
craft-i18n.rev text eol=lf
|
||||
|
||||
# Not text at all: no textual diffs or merges either.
|
||||
*.pdf binary
|
||||
*.p12 binary
|
||||
|
||||
@@ -42,3 +42,4 @@ jobs:
|
||||
AZURE_SIGNING_ENDPOINT: ${{ secrets.AZURE_SIGNING_ENDPOINT }}
|
||||
AZURE_SIGNING_ACCOUNT: ${{ secrets.AZURE_SIGNING_ACCOUNT }}
|
||||
AZURE_CERT_PROFILE: ${{ secrets.AZURE_CERT_PROFILE }}
|
||||
LINUX_SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }}
|
||||
|
||||
@@ -52,14 +52,14 @@ Anything else is forbidden, including assets whose licence you cannot prove from
|
||||
- path or URL, title, author;
|
||||
- source (URL, with version or commit);
|
||||
- SPDX licence, and the licence file in the repo;
|
||||
- SHA-256, except for translation catalogs (see below);
|
||||
- SHA-256;
|
||||
- how it is used.
|
||||
- **Translation catalogs** (`crates/ui-egui/src/i18n/*.tsv`, `kind = "translation"`) are project files under the project licence. Their entries carry no `sha256`: git history records every change and who made it. Every other kind (fonts, images, data) is hashed.
|
||||
- **Translations** live in craft-i18n (`locales/<code>/common.tsv` and `pdfcraft.tsv`), embedded at build time from the checkout pinned in `craft-i18n.rev`; PdfCraft keeps no catalogs. `ATTRIBUTION.toml` records them as one `[[build_input]]` (`kind = "translation"`); craft-i18n's own `ATTRIBUTION.md` credits the translators. Every `[[asset]]` (fonts, images, data) is hashed.
|
||||
- **`ATTRIBUTION.md`** is generated from it (`cargo xtask assets --write`). Never edit it by hand.
|
||||
- **`NOTICE`** keeps the licence notices that the licences require.
|
||||
- **`cargo xtask assets`** runs in `cargo xtask ci`. It fails if:
|
||||
- an asset file in the repository has no entry;
|
||||
- an entry's hash is missing or does not match (translation catalogs carry none);
|
||||
- an entry's hash is missing or does not match;
|
||||
- a licence is not on the allowlist;
|
||||
- a licence file is missing;
|
||||
- an image, icon or font names Adobe as author or source, except a font licensed under OFL-1.1 alone.
|
||||
|
||||
+5
-37
@@ -2,40 +2,12 @@
|
||||
|
||||
<!-- Generated from ATTRIBUTION.toml by `cargo xtask assets --write`. Do not edit by hand. -->
|
||||
|
||||
Every asset PdfCraft includes, bundles or uses to build its published material, with its author, source and licence. The policy is in [AGENTS.md](AGENTS.md) §1. The machine-readable list, with the SHA-256 of every asset but the translation catalogs, is [ATTRIBUTION.toml](ATTRIBUTION.toml). Licence texts are kept beside the assets and summarised in [NOTICE](NOTICE).
|
||||
Every asset PdfCraft includes, bundles or uses to build its published material, with its author, source and licence. The policy is in [AGENTS.md](AGENTS.md) §1. The machine-readable list, with the SHA-256 of every asset, is [ATTRIBUTION.toml](ATTRIBUTION.toml). Licence texts are kept beside the assets and summarised in [NOTICE](NOTICE).
|
||||
|
||||
## In this repository (289)
|
||||
## In this repository (256)
|
||||
|
||||
| Asset | Title | Author | Licence | Source | Used for |
|
||||
|---|---|---|---|---|---|
|
||||
| `crates/ui-egui/src/i18n/el.tsv` | Greek interface translation catalog | Camthalion77 and contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft English UI labels; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Greek UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/zh-hans.tsv` | Simplified Chinese 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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/zh-hant.tsv` | Traditional Chinese (Taiwan) 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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/cs.tsv` | Czech 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; 2026-10-22 signature-check and standard-identification wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Czech UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/pt-br.tsv` | Brazilian Portuguese interface translation catalog | André Cardoso (andrecards), PdfCraft contributors | MIT OR Apache-2.0 | Clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources: the ~1,826-entry gist posted in issue #288 (https://gist.github.com/andrecards/ac388c25fba17312ce83207f8b7b467e), completed and reviewed by PdfCraft contributors; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table limit messages are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/es.tsv` | Spanish 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; the 2026-10-20 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; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Spanish UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/fr.tsv` | French 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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | French UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/de.tsv` | German 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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | German UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/lo.tsv` | Lao interface translation catalog | @bounkirdni-2025 | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Lao UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/te.tsv` | Telugu 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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Telugu UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/it.tsv` | Italian 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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Italian UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/pl.tsv` | Polish 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; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Polish UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/ru.tsv` | Russian 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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Russian UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/hu.tsv` | Hungarian 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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Hungarian UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/bg.tsv` | Bulgarian 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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/kk.tsv` | Kazakh interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of the English UI labels into Kazakh, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Kazakh UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/ja.tsv` | Japanese 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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Japanese UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/uk.tsv` | Ukrainian 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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/ar.tsv` | Arabic 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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Arabic UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/ckb.tsv` | Central Kurdish (Sorani) 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-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Central Kurdish UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/fa.tsv` | Persian (Farsi) 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-25 initial catalog covering the current 2,776-entry key union, AI-assisted translations from English, not separately native-reviewed | Persian UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/tr.tsv` | Turkish 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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Turkish UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/lt.tsv` | Lithuanian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels and user-facing messages; original catalog reviewed by a native Lithuanian-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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Lithuanian UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/hi.tsv` | Hindi 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-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Hindi UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/id.tsv` | Indonesian 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-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Indonesian UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/ko.tsv` | Korean 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-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Korean UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/ro.tsv` | Romanian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels and user-facing messages; AI-assisted and not yet reviewed line by line by a native Romanian speaker; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Romanian UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/fi.tsv` | Finnish interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: AI-assisted (Claude Code) clean-room translations of PdfCraft's English UI labels and user-facing messages; not yet proofread by a native Finnish speaker; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 calculation-order, text-format, print, thumbnail, layer, comment-reshape and signature labels (previously English placeholders) are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Finnish UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `assets/icons/accessibility.svg` | Lucide icon "accessibility" | Lucide Contributors (portions Cole Bemis, Feather) | ISC AND MIT | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/accessibility.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `assets/icons/align-left.svg` | Lucide icon "align-left" | Lucide Contributors (portions Cole Bemis, Feather) | ISC AND MIT | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/align-left.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `assets/icons/align-center.svg` | Lucide icon "align-center" | Lucide Contributors (portions Cole Bemis, Feather) | ISC AND MIT | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/align-center.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
@@ -231,6 +203,7 @@ Every asset PdfCraft includes, bundles or uses to build its published material,
|
||||
| `vendor/hayro-interpret/assets/FoxitSymbol.pfb` | FoxitSymbol (standard-14 substitute font) | Foxit Software Inc. / PDFium Authors | BSD-3-Clause | https://pdfium.googlesource.com/pdfium (via hayro-interpret 0.7.0 assets/) | Renders non-embedded standard-14 fonts (vendored hayro-interpret) |
|
||||
| `vendor/hayro-interpret/assets/CGATS001Compat-v2-micro.icc` | CGATS001Compat v2 micro ICC profile | Clinton Ingram (saucecontrol/Compact-ICC-Profiles) | CC0-1.0 | https://github.com/saucecontrol/Compact-ICC-Profiles | Default CMYK colour conversion (vendored hayro-interpret) |
|
||||
| `crates/fonts/src/std14_metrics.rs` | Standard-14 font advance widths and encodings (as Rust tables) | Adobe (Core14 AFM metrics, PDF specification encodings); tables generated by the PdfCraft contributors | MIT OR Apache-2.0 | ISO 32000-2 Annex D and the Adobe Core14 AFM metrics. Generated by joining the published WinAnsi, Symbol and ZapfDingbats encodings (code to glyph name) with the per-glyph advance widths, then spot-checked against the specification by hand in the tests of crates/fonts/src/std14.rs. Data only: the code that reads these tables lives in std14.rs, so that ordinary edits do not churn this file's hash. | Advance widths for the fourteen standard fonts, which a PDF may name without embedding. Non-visual data, not a typeface: the item AGENTS.md 1.1 permits, held first-party so generated appearance streams and the renderer agree. |
|
||||
| `crates/prc/src/acof/table.rs` | PRC Double prefix-code table (as a Rust table) | Kristof Ralovich (prc-rs); the code table itself is defined by the PRC format (ISO 14739-1, working draft clause 11.17.2) | MIT | Rows taken from prc-rs src/double.rs at commit 287b671bb5b31b05efb5ec1b98f7c359b9e4dd7d (https://github.com/ralovich/prc-rs), mechanically reformatted into (code length, code, entry) tuples; checked to be a complete prefix code by the tests in crates/prc/src/acof.rs. Data only: the code that reads the table lives in acof.rs. The MIT notice is kept in the file header. | Decodes every PRC Double (coordinates, transforms, colours) in 3D annotations' PRC streams. Non-visual data. |
|
||||
| `vendor/hayro-cmap/assets/cmaps.brotli` | Adobe CMap resources (predefined CJK CMaps) | Adobe | BSD-3-Clause | https://github.com/adobe-type-tools/cmap-resources (via hayro-cmap 0.1.0 assets/) | Character-code mapping tables required to read CJK PDFs. Non-visual data, allowed by AGENTS.md §1.1 |
|
||||
| `docs/brand/artcraft-logo-white.png` | ArtCraft brand: artcraft-logo-white | ArtCraft Team | LicenseRef-Storyteller-Trademark | ArtCraft Team brand assets (provided by the project owner, 2026-10-01; identical copies in storytold/photocraft) | ArtCraft branding next to the community links (About dialog, Help menu, home screen) and in docs. ArtCraft Team trademark, not open source; may be used only unmodified within PdfCraft; forks and modified versions must remove it. |
|
||||
| `docs/brand/artcraft-logo-white.svg` | ArtCraft brand: artcraft-logo-white | ArtCraft Team | LicenseRef-Storyteller-Trademark | ArtCraft Team brand assets (provided by the project owner, 2026-10-01; identical copies in storytold/photocraft) | ArtCraft branding next to the community links (About dialog, Help menu, home screen) and in docs. ArtCraft Team trademark, not open source; may be used only unmodified within PdfCraft; forks and modified versions must remove it. |
|
||||
@@ -286,17 +259,11 @@ Every asset PdfCraft includes, bundles or uses to build its published material,
|
||||
| `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/it/pdf.rs): the legacy adbe.x509.rsa_sha1 format |
|
||||
| `assets/icons/monitor.svg` | Lucide icon "monitor" | Lucide Contributors (portions Cole Bemis, Feather) | ISC AND MIT | 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; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Vietnamese UI strings, compiled in by crates/ui-egui/src/i18n.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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Slovene UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `vendor/vello_common/assets/probe.png` | vello_common probe reference image (rendered from probe.rgba) | Linebender vello contributors | MIT OR Apache-2.0 | vello_common 0.0.8 (crates.io), vendored in vendor/vello_common; regenerated by its vello_sparse_tests regenerate_probe_reference binary | Reference picture of the vendored vello_common probe module, for people only; the build reads probe.rgba, not this file |
|
||||
| `crates/ui-egui/src/i18n/nb.tsv` | Norwegian Bokmål 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-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Norwegian Bokmål UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `assets/icons/superscript.svg` | Lucide icon "superscript" | Lucide Contributors (portions Cole Bemis, Feather) | ISC AND MIT | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/superscript.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `assets/icons/subscript.svg` | Lucide icon "subscript" | Lucide Contributors (portions Cole Bemis, Feather) | ISC AND MIT | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/subscript.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `assets/icons/pilcrow.svg` | Lucide icon "pilcrow" | Lucide Contributors (portions Cole Bemis, Feather) | ISC AND MIT | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/pilcrow.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `assets/icons/move-horizontal.svg` | Lucide icon "move-horizontal" | Lucide Contributors (portions Cole Bemis, Feather) | ISC AND MIT | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/move-horizontal.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `crates/ui-egui/src/i18n/sq.tsv` | Albanian 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-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-appearance labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Albanian UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/th.tsv` | Thai 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-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Thai UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
| `crates/ui-egui/src/i18n/ca.tsv` | Catalan interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, written from their meaning; no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed | Catalan UI strings, compiled in by crates/ui-egui/src/i18n.rs |
|
||||
|
||||
## Compiled in through dependencies (6)
|
||||
|
||||
@@ -338,10 +305,11 @@ Fonts are fetched by `cargo xtask demo-pdf` into `target/demo-fonts/`, OCR model
|
||||
| `text-detection.onnx` | Ocrs text detection model | Robert Knight (ocrs-models; trained on HierText, CC-BY-SA-4.0) | CC-BY-SA-4.0 | https://github.com/robertknight/ocrs/blob/0c826441362a701c55560fefc17bf356b6d1037e/ocrs/examples/download-models.sh (author-published ONNX models; model card: https://huggingface.co/robertknight/ocrs/blob/df0edd170279ab971b53e094c627255a87e1a503/README.md, cc-by-sa-4.0) | Scan & OCR: finds words on page images (cargo xtask models) |
|
||||
| `text-recognition.onnx` | Ocrs text recognition model | Robert Knight (ocrs-models; trained on HierText, CC-BY-SA-4.0) | CC-BY-SA-4.0 | https://github.com/robertknight/ocrs/blob/0c826441362a701c55560fefc17bf356b6d1037e/ocrs/examples/download-models.sh (author-published ONNX models; model card: https://huggingface.co/robertknight/ocrs/blob/df0edd170279ab971b53e094c627255a87e1a503/README.md, cc-by-sa-4.0) | Scan & OCR: reads the words it finds (cargo xtask models) |
|
||||
|
||||
## Optional build inputs (1)
|
||||
## Optional build inputs (2)
|
||||
|
||||
Not in this repository and never downloaded by it: compiled in only when the build sets the option (official releases do). Each input attributes its own files.
|
||||
|
||||
| Input | Option | Title | Author | Licence | Source | Attribution | Used for |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `craft-i18n` | `CRAFT_I18N_DIR` | Interface translations: the craft-i18n catalogs (locales/<code>/common.tsv and pdfcraft.tsv) of the 34 languages PdfCraft offers besides English | The craft-i18n translators, including every translator of PdfCraft's former in-repo catalogs (crates/ui-egui/src/i18n/*.tsv, migrated at pdfcraft@ecdd1e6) | MIT OR Apache-2.0 | https://github.com/storytold/craft-i18n (pinned by commit in craft-i18n.rev, which CI, releases and the flatpak manifests read) | https://github.com/storytold/craft-i18n/blob/main/ATTRIBUTION.md | Interface text in languages other than English, embedded by crates/ui-egui/build.rs and looked up through craft-libs' craft-i18n crate; a build without CRAFT_I18N_DIR is English only. Clean-room translations from PdfCraft's English interface labels |
|
||||
| `craft-fonts` | `CRAFT_FONTS_DIR` | Japanese fonts: BIZ UDPGothic (Regular, Bold), Shippori Mincho, BIZ UDMincho; native Simplified Chinese font: LXGW WenKai Lite; Arabic font: Noto Sans Arabic | The BIZ UDGothic and BIZ UDMincho Project Authors (Morisawa Inc.), The Shippori Mincho Project Authors (FONTDASU), LXGW and the original Klee Project Authors / Fontworks (LXGW WenKai Lite), The Noto Project Authors (Noto Sans Arabic) | OFL-1.1 | https://github.com/storytold/craft-fonts (pinned by commit in .github/workflows/release.yml and .github/workflows/ci.yml); LXGW WenKai Lite from https://github.com/lxgw/LxgwWenKai-Lite/releases/tag/v1.530 (commit b712c83d98acf977bacfb7fb010207802808bda9) | https://github.com/storytold/craft-fonts/blob/main/ATTRIBUTION.md | Japanese fallback in the interface (BIZ UDPGothic first) and Japanese glyphs in edited PDF text (Shippori Mincho); Simplified Chinese interface fallback on native builds only (LXGW WenKai Lite Regular, font SHA-256 f799a538a3355877e597a0ae04050e897932c9ae7689eb9227c2953c5dd549ea, OFL SHA-256 0333a85fe077b6849f8e417acc44e8bd879442ceb681e271acf93e2d57fd4e2e, read from fonts/simplified-chinese-manifest.txt and never embedded on wasm); Arabic interface text, file names and document titles (Noto Sans Arabic); the web build embeds BIZ UDPGothic Regular and the pinned small Arabic, Hebrew, Thai and Devanagari faces; the selected craft-fonts revision contains no Telugu face. Other non-Japanese CJK faces retain the general 4 MiB cap; the reviewed native-only LXGW fallback has a separate 16 MiB cap. Open-source OFL releases are permitted under current AGENTS.md §1.1 |
|
||||
|
||||
+23
-341
@@ -1,296 +1,15 @@
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/el.tsv"
|
||||
title = "Greek interface translation catalog"
|
||||
author = "Camthalion77 and contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft English UI labels; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Greek UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
# ATTRIBUTION.toml: one entry per asset (AGENTS.md §1). The source of truth for provenance and licences.
|
||||
# ATTRIBUTION.md is generated from this file: `cargo xtask assets --write`. Checked by `cargo xtask assets` (part of `xtask ci`).
|
||||
#
|
||||
# [[asset]] a file in this repository (committed or vendored); `sha256` must match the file.
|
||||
# Translation catalogs (kind = "translation", crates/ui-egui/src/i18n/*.tsv) are project files
|
||||
# under the project licence and carry no `sha256`: git history records their provenance.
|
||||
# [[bundled]] a file compiled into PdfCraft by a Cargo dependency (its crate version must match Cargo.lock).
|
||||
# [[fetched]] downloaded at build time by xtask (never committed), pinned by `sha256`.
|
||||
# [[build_input]] an optional build input outside this repository (e.g. craft-fonts via CRAFT_FONTS_DIR);
|
||||
# [[build_input]] an optional build input outside this repository (craft-fonts via CRAFT_FONTS_DIR, craft-i18n via CRAFT_I18N_DIR);
|
||||
# never committed or downloaded here, its own repository attributes each file.
|
||||
#
|
||||
# licence: SPDX expression from the allowlist in AGENTS.md §1.2. adobe_data = true marks the closed list of
|
||||
# Adobe-authored non-visual technical data allowed by AGENTS.md §1.1.
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/zh-hans.tsv"
|
||||
title = "Simplified Chinese interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/zh-hant.tsv"
|
||||
title = "Traditional Chinese (Taiwan) interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/cs.tsv"
|
||||
title = "Czech interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 signature-check and standard-identification wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Czech UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/pt-br.tsv"
|
||||
title = "Brazilian Portuguese interface translation catalog"
|
||||
author = "André Cardoso (andrecards), PdfCraft contributors"
|
||||
source = "Clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources: the ~1,826-entry gist posted in issue #288 (https://gist.github.com/andrecards/ac388c25fba17312ce83207f8b7b467e), completed and reviewed by PdfCraft contributors; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table limit messages are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/es.tsv"
|
||||
title = "Spanish interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; the 2026-10-20 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; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/fr.tsv"
|
||||
title = "French interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "French UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/de.tsv"
|
||||
title = "German interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "German UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/lo.tsv"
|
||||
title = "Lao interface translation catalog"
|
||||
author = "@bounkirdni-2025"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Lao UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/te.tsv"
|
||||
title = "Telugu interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/it.tsv"
|
||||
title = "Italian interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/pl.tsv"
|
||||
title = "Polish interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Polish UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ru.tsv"
|
||||
title = "Russian interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/hu.tsv"
|
||||
title = "Hungarian interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/bg.tsv"
|
||||
title = "Bulgarian interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/kk.tsv"
|
||||
title = "Kazakh interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of the English UI labels into Kazakh, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Kazakh UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ja.tsv"
|
||||
title = "Japanese interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/uk.tsv"
|
||||
title = "Ukrainian interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ar.tsv"
|
||||
title = "Arabic interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ckb.tsv"
|
||||
title = "Central Kurdish (Sorani) interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Central Kurdish UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/fa.tsv"
|
||||
title = "Persian (Farsi) interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-25 initial catalog covering the current 2,776-entry key union, AI-assisted translations from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Persian UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/tr.tsv"
|
||||
title = "Turkish interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Turkish UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/lt.tsv"
|
||||
title = "Lithuanian interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels and user-facing messages; original catalog reviewed by a native Lithuanian-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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Lithuanian UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/hi.tsv"
|
||||
title = "Hindi interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Hindi UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/id.tsv"
|
||||
title = "Indonesian interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Indonesian UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ko.tsv"
|
||||
title = "Korean interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Korean UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ro.tsv"
|
||||
title = "Romanian interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels and user-facing messages; AI-assisted and not yet reviewed line by line by a native Romanian speaker; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Romanian UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/fi.tsv"
|
||||
title = "Finnish interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: AI-assisted (Claude Code) clean-room translations of PdfCraft's English UI labels and user-facing messages; not yet proofread by a native Finnish speaker; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 calculation-order, text-format, print, thumbnail, layer, comment-reshape and signature labels (previously English placeholders) are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Finnish UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "assets/icons/accessibility.svg"
|
||||
title = "Lucide icon \"accessibility\""
|
||||
@@ -2437,6 +2156,17 @@ adobe_data = true
|
||||
usage = "Advance widths for the fourteen standard fonts, which a PDF may name without embedding. Non-visual data, not a typeface: the item AGENTS.md 1.1 permits, held first-party so generated appearance streams and the renderer agree."
|
||||
sha256 = "f225e0b044875b8fa6375b6b5ad935f0d99fcc1ad03ecf9f0baae2cedbfca4d2"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/prc/src/acof/table.rs"
|
||||
title = "PRC Double prefix-code table (as a Rust table)"
|
||||
author = "Kristof Ralovich (prc-rs); the code table itself is defined by the PRC format (ISO 14739-1, working draft clause 11.17.2)"
|
||||
source = "Rows taken from prc-rs src/double.rs at commit 287b671bb5b31b05efb5ec1b98f7c359b9e4dd7d (https://github.com/ralovich/prc-rs), mechanically reformatted into (code length, code, entry) tuples; checked to be a complete prefix code by the tests in crates/prc/src/acof.rs. Data only: the code that reads the table lives in acof.rs. The MIT notice is kept in the file header."
|
||||
licence = "MIT"
|
||||
licence_file = "licenses/prc-rs-LICENSE.txt"
|
||||
kind = "data"
|
||||
usage = "Decodes every PRC Double (coordinates, transforms, colours) in 3D annotations' PRC streams. Non-visual data."
|
||||
sha256 = "8e705c5cde6a3ef778e8aca69759e38845b228b3f7f12949e587b19855d2114c"
|
||||
|
||||
[[bundled]]
|
||||
crate = "epaint_default_fonts"
|
||||
version = "0.37.0"
|
||||
@@ -3381,6 +3111,17 @@ kind = "model"
|
||||
usage = "Scan & OCR: reads the words it finds (cargo xtask models)"
|
||||
sha256 = "86c145c2edb96c8caed5b1ebb8f44d706408922211451c309c625157dd6061c5"
|
||||
|
||||
[[build_input]]
|
||||
name = "craft-i18n"
|
||||
option = "CRAFT_I18N_DIR"
|
||||
title = "Interface translations: the craft-i18n catalogs (locales/<code>/common.tsv and pdfcraft.tsv) of the 34 languages PdfCraft offers besides English"
|
||||
author = "The craft-i18n translators, including every translator of PdfCraft's former in-repo catalogs (crates/ui-egui/src/i18n/*.tsv, migrated at pdfcraft@ecdd1e6)"
|
||||
source = "https://github.com/storytold/craft-i18n (pinned by commit in craft-i18n.rev, which CI, releases and the flatpak manifests read)"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
attribution = "https://github.com/storytold/craft-i18n/blob/main/ATTRIBUTION.md"
|
||||
kind = "translation"
|
||||
usage = "Interface text in languages other than English, embedded by crates/ui-egui/build.rs and looked up through craft-libs' craft-i18n crate; a build without CRAFT_I18N_DIR is English only. Clean-room translations from PdfCraft's English interface labels"
|
||||
|
||||
[[build_input]]
|
||||
name = "craft-fonts"
|
||||
option = "CRAFT_FONTS_DIR"
|
||||
@@ -3414,26 +3155,6 @@ kind = "icon"
|
||||
usage = "UI icon, embedded via crates/ui-egui/src/icon_data.rs"
|
||||
sha256 = "d1a443233345724859de8e4fb48968ba91e885e12ddbd710701548bd91b39428"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/vi.tsv"
|
||||
title = "Vietnamese interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 signature-check wordings are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Vietnamese UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/sl.tsv"
|
||||
title = "Slovene interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "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; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Slovene UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
|
||||
path = "vendor/vello_common/assets/probe.png"
|
||||
@@ -3446,16 +3167,6 @@ kind = "image"
|
||||
usage = "Reference picture of the vendored vello_common probe module, for people only; the build reads probe.rgba, not this file"
|
||||
sha256 = "78c59a0cfd2c829909d77006f94e34dac1ae5d1f90e7f10f72e84de541fef697"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/nb.tsv"
|
||||
title = "Norwegian Bokmål interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Norwegian Bokmål UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "assets/icons/superscript.svg"
|
||||
title = "Lucide icon \"superscript\""
|
||||
@@ -3500,32 +3211,3 @@ kind = "icon"
|
||||
usage = "UI icon, embedded via crates/ui-egui/src/icon_data.rs"
|
||||
sha256 = "864ab2f19254e6ac7240bc6a98323197332e8f714d4d3f94831f41309e5ef2f7"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/sq.tsv"
|
||||
title = "Albanian interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 signature-appearance labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Albanian UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/th.tsv"
|
||||
title = "Thai interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Thai UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ca.tsv"
|
||||
title = "Catalan interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, written from their meaning; no proprietary localisation resources; 2026-10-22 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 text-field-table labels are contributor-original AI-assisted translations from English, not separately native-reviewed; 2026-10-22 secured-document notice labels are contributor-original AI-assisted translations from English, not separately native-reviewed; the 2026-10-22 reply-count label is an AI-assisted translation from English, not separately native-reviewed"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Catalan UI strings, compiled in by crates/ui-egui/src/i18n.rs"
|
||||
|
||||
@@ -23,7 +23,7 @@ PdfCraft is a clean-room, open-source, Rust-native PDF application targeting Ado
|
||||
- **Layering.** Nothing below L7 depends on egui/winit/eframe/rfd (`plan/architecture.md` §3). `cos`/`filters`/`crypt`/`arlington` stay standalone.
|
||||
- **Page input.** Page modules read `PagePointer` (`crates/ui-egui/src/page_pointer.rs`), never egui's raw pointer state (`hover_pos`, `interact_pos`, `latest_pos`, `press_origin`, button reads); `page_pointer::tests::page_modules_read_the_pointer_only_through_page_pointer` enforces it.
|
||||
- **Page cursor.** Page modules claim the cursor for what is under the pointer (`view.cursor.claim(Layer::…, icon)`, `crates/ui-egui/src/canvas/page_cursor.rs`), never set it (`set_cursor_icon`, `cursor_icon =`, `on_hover_cursor`); `document_area` sets it once, from the claim on the highest layer the active tool reaches. `page_pointer::tests::page_modules_claim_the_cursor_and_never_set_it` enforces it.
|
||||
- **Page presses.** A page module decides what of its own is under a point in one hit test, which returns a `Hit` (owner, layer, cursor, tip; `crates/ui-egui/src/canvas/page_router.rs`) and gives its cursor, its tip and its press. It acts on a press only when the router says the press is its own (`view.router.owner(page)`), never by testing the press point again or by asking another module whether it wants it. Escape cancels any press the pages hold (`cancel_on_escape`), and that press is ignored until the button is up. `page_modules_take_presses_from_the_router_without_repeating_hit_tests` guards the centralized hit calls and captured-owner handlers; runtime routing tests cover gesture behavior.
|
||||
- **Page presses.** A page module decides what of its own is under a point in one hit test, which returns a `Hit` (owner, layer, cursor, tip; `crates/ui-egui/src/canvas/page_router.rs`) and gives its cursor, its tip and its press. It acts on a press only when the press is its own or, for the active tool, nobody's: the page view tells it which (`PagePress`: its own, nobody's, or another page's), and it never tests the press point again or asks another module whether it wants it. Escape cancels any press the pages hold (`cancel_on_escape`), and that press is ignored until the button is up. `page_pointer::tests::page_modules_act_on_a_press_only_through_the_router` enforces it.
|
||||
- **Fidelity.** The PDF object graph is the model. Preserve unknown data. Saves are incremental unless a full rewrite is required. Never silently drop or repair data without recording it.
|
||||
- **Never panic.** Every PDF, script, MCP call, settings file and keystroke is untrusted input, and none of it may crash the app or lose the user's work. This outranks feature work. Return `Result` (the crate's error enum) and propagate with `?`, or fall back leniently and record it. In non-test code:
|
||||
- No `unwrap`/`expect`/`panic!`/`unreachable!`/`todo!` unless it is provably infallible, with a comment saying why.
|
||||
|
||||
Generated
+57
-36
@@ -4208,7 +4208,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"craft-update",
|
||||
@@ -4232,7 +4232,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-a11y"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"pdfcraft-content",
|
||||
"pdfcraft-cos",
|
||||
@@ -4244,7 +4244,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-annot"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"hayro-syntax",
|
||||
"pdfcraft-cos",
|
||||
@@ -4255,7 +4255,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-audit"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"pdfcraft-content",
|
||||
"pdfcraft-cos",
|
||||
@@ -4266,7 +4266,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-automation"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"flate2",
|
||||
@@ -4277,6 +4277,8 @@ dependencies = [
|
||||
"pdfcraft-model",
|
||||
"pdfcraft-organize",
|
||||
"pdfcraft-platform",
|
||||
"pdfcraft-prc",
|
||||
"pdfcraft-prc-render",
|
||||
"pdfcraft-render",
|
||||
"pdfcraft-xfa",
|
||||
"png 0.18.1",
|
||||
@@ -4288,7 +4290,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-cli"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"pdfcraft-audit",
|
||||
@@ -4306,11 +4308,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-compare"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-content"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"hayro-syntax",
|
||||
"pdfcraft-cos",
|
||||
@@ -4318,7 +4320,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-cos"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"hayro-syntax",
|
||||
@@ -4333,7 +4335,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-create"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"hayro-ccitt",
|
||||
@@ -4352,7 +4354,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-crypt"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"md-5",
|
||||
@@ -4364,7 +4366,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-edit"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"hayro-syntax",
|
||||
"kurbo 0.13.1",
|
||||
@@ -4382,7 +4384,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-engine"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"craft-docpath",
|
||||
@@ -4426,14 +4428,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-export"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-filters"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"fax",
|
||||
"flate2",
|
||||
@@ -4446,7 +4448,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-fonts"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"harfrust",
|
||||
"pdfcraft-cos",
|
||||
@@ -4456,7 +4458,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-forms"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"hayro-syntax",
|
||||
"pdfcraft-content",
|
||||
@@ -4469,11 +4471,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-geom"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-js"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"boa_engine",
|
||||
"chrono",
|
||||
@@ -4481,7 +4483,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-measure"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"pdfcraft-annot",
|
||||
"pdfcraft-content",
|
||||
@@ -4494,14 +4496,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-model"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"pdfcraft-cos",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-ocr"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"ocrs",
|
||||
"pdfcraft-fonts",
|
||||
@@ -4512,7 +4514,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-optimize"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"pdfcraft-annot",
|
||||
@@ -4528,11 +4530,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-oracle"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-organize"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"hayro-syntax",
|
||||
"pdfcraft-content",
|
||||
@@ -4543,7 +4545,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-platform"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"futures-lite",
|
||||
@@ -4551,9 +4553,27 @@ dependencies = [
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-prc"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"proptest",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-prc-render"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"pdfcraft-prc",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-preflight"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"pdfcraft-content",
|
||||
@@ -4564,7 +4584,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-print"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"craft-print",
|
||||
"pdfcraft-content",
|
||||
@@ -4576,7 +4596,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-redact"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"pdfcraft-annot",
|
||||
"pdfcraft-content",
|
||||
@@ -4593,7 +4613,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-render"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"flate2",
|
||||
@@ -4612,7 +4632,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-sign"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aws-lc-rs",
|
||||
@@ -4648,17 +4668,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-testkit"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-ui-egui"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"base64 0.23.1",
|
||||
"block2 0.6.2",
|
||||
"bytemuck",
|
||||
"chrono",
|
||||
"craft-i18n",
|
||||
"craft-keymap",
|
||||
"craft-menu",
|
||||
"craft-quit",
|
||||
@@ -4706,7 +4727,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-web"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
@@ -4718,7 +4739,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-xfa"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"image",
|
||||
@@ -4732,7 +4753,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pdfcraft-xfdf"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"pdfcraft-annot",
|
||||
"pdfcraft-cos",
|
||||
|
||||
+40
-34
@@ -5,7 +5,7 @@ exclude = ["vendor"]
|
||||
default-members = ["crates/*", "apps/pdfcraft", "apps/pdfcraft-cli", "xtask"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.95"
|
||||
@@ -19,6 +19,10 @@ craft-quit = { git = "https://github.com/storytold/craft-libs", version = "0.1.0
|
||||
# One menu definition for the native macOS menu bar and the in-window bar elsewhere.
|
||||
craft-menu = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "787a7a0f972fd1d7a7618fdbeca3a5267a0407ff" }
|
||||
craft-keymap = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "787a7a0f972fd1d7a7618fdbeca3a5267a0407ff", default-features = false }
|
||||
# Interface translations: lookup, fallback, CLDR plurals and language names over the craft-i18n
|
||||
# catalogs that `crates/ui-egui/build.rs` embeds from `CRAFT_I18N_DIR` (storytold/craft-i18n, at
|
||||
# the commit in `craft-i18n.rev`). Already in the tree through craft-ui.
|
||||
craft-i18n = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "787a7a0f972fd1d7a7618fdbeca3a5267a0407ff" }
|
||||
# Shared system spooler; document composition remains in pdfcraft-print.
|
||||
craft-print = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "787a7a0f972fd1d7a7618fdbeca3a5267a0407ff" }
|
||||
# Whether a path stored in a document may be opened (GoToR link targets).
|
||||
@@ -26,39 +30,41 @@ craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-l
|
||||
# craft-update: app-specific pretend versions apply to startup and manual checks.
|
||||
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "787a7a0f972fd1d7a7618fdbeca3a5267a0407ff", features = ["egui"] }
|
||||
# internal
|
||||
pdfcraft-geom = { path = "crates/geom", version = "0.17.0" }
|
||||
pdfcraft-filters = { path = "crates/filters", version = "0.17.0" }
|
||||
pdfcraft-cos = { path = "crates/cos", version = "0.17.0" }
|
||||
pdfcraft-crypt = { path = "crates/crypt", version = "0.17.0" }
|
||||
pdfcraft-organize = { path = "crates/organize", version = "0.17.0" }
|
||||
pdfcraft-measure = { path = "crates/measure", version = "0.17.0" }
|
||||
pdfcraft-annot = { path = "crates/annot", version = "0.17.0" }
|
||||
pdfcraft-audit = { path = "crates/audit", version = "0.17.0" }
|
||||
pdfcraft-fonts = { path = "crates/fonts", version = "0.17.0" }
|
||||
pdfcraft-model = { path = "crates/model", version = "0.17.0" }
|
||||
pdfcraft-content = { path = "crates/content", version = "0.17.0" }
|
||||
pdfcraft-redact = { path = "crates/redact", version = "0.17.0" }
|
||||
pdfcraft-print = { path = "crates/print", version = "0.17.0" }
|
||||
pdfcraft-xfdf = { path = "crates/xfdf", version = "0.17.0" }
|
||||
pdfcraft-sign = { path = "crates/sign", version = "0.17.0" }
|
||||
pdfcraft-optimize = { path = "crates/optimize", version = "0.17.0" }
|
||||
pdfcraft-a11y = { path = "crates/a11y", version = "0.17.0" }
|
||||
pdfcraft-oracle = { path = "crates/oracle", version = "0.17.0" }
|
||||
pdfcraft-testkit = { path = "crates/testkit", version = "0.17.0" }
|
||||
pdfcraft-ocr = { path = "crates/ocr", version = "0.17.0" }
|
||||
pdfcraft-js = { path = "crates/js", version = "0.17.0" }
|
||||
pdfcraft-compare = { path = "crates/compare", version = "0.17.0" }
|
||||
pdfcraft-preflight = { path = "crates/preflight", version = "0.17.0" }
|
||||
pdfcraft-export = { path = "crates/export", version = "0.17.0" }
|
||||
pdfcraft-edit = { path = "crates/edit", version = "0.17.0" }
|
||||
pdfcraft-create = { path = "crates/create", version = "0.17.0" }
|
||||
pdfcraft-forms = { path = "crates/forms", version = "0.17.0" }
|
||||
pdfcraft-xfa = { path = "crates/xfa", version = "0.17.0" }
|
||||
pdfcraft-render = { path = "crates/render", version = "0.17.0" }
|
||||
pdfcraft-engine = { path = "crates/engine", version = "0.17.0" }
|
||||
pdfcraft-platform = { path = "crates/platform", version = "0.17.0" }
|
||||
pdfcraft-ui-egui = { path = "crates/ui-egui", version = "0.17.0" }
|
||||
pdfcraft-automation = { path = "crates/automation", version = "0.17.0", default-features = false }
|
||||
pdfcraft-geom = { path = "crates/geom", version = "0.18.1" }
|
||||
pdfcraft-filters = { path = "crates/filters", version = "0.18.1" }
|
||||
pdfcraft-cos = { path = "crates/cos", version = "0.18.1" }
|
||||
pdfcraft-crypt = { path = "crates/crypt", version = "0.18.1" }
|
||||
pdfcraft-organize = { path = "crates/organize", version = "0.18.1" }
|
||||
pdfcraft-measure = { path = "crates/measure", version = "0.18.1" }
|
||||
pdfcraft-annot = { path = "crates/annot", version = "0.18.1" }
|
||||
pdfcraft-audit = { path = "crates/audit", version = "0.18.1" }
|
||||
pdfcraft-fonts = { path = "crates/fonts", version = "0.18.1" }
|
||||
pdfcraft-model = { path = "crates/model", version = "0.18.1" }
|
||||
pdfcraft-content = { path = "crates/content", version = "0.18.1" }
|
||||
pdfcraft-redact = { path = "crates/redact", version = "0.18.1" }
|
||||
pdfcraft-print = { path = "crates/print", version = "0.18.1" }
|
||||
pdfcraft-xfdf = { path = "crates/xfdf", version = "0.18.1" }
|
||||
pdfcraft-sign = { path = "crates/sign", version = "0.18.1" }
|
||||
pdfcraft-optimize = { path = "crates/optimize", version = "0.18.1" }
|
||||
pdfcraft-a11y = { path = "crates/a11y", version = "0.18.1" }
|
||||
pdfcraft-oracle = { path = "crates/oracle", version = "0.18.1" }
|
||||
pdfcraft-testkit = { path = "crates/testkit", version = "0.18.1" }
|
||||
pdfcraft-ocr = { path = "crates/ocr", version = "0.18.1" }
|
||||
pdfcraft-js = { path = "crates/js", version = "0.18.1" }
|
||||
pdfcraft-compare = { path = "crates/compare", version = "0.18.1" }
|
||||
pdfcraft-preflight = { path = "crates/preflight", version = "0.18.1" }
|
||||
pdfcraft-export = { path = "crates/export", version = "0.18.1" }
|
||||
pdfcraft-edit = { path = "crates/edit", version = "0.18.1" }
|
||||
pdfcraft-create = { path = "crates/create", version = "0.18.1" }
|
||||
pdfcraft-forms = { path = "crates/forms", version = "0.18.1" }
|
||||
pdfcraft-xfa = { path = "crates/xfa", version = "0.18.1" }
|
||||
pdfcraft-render = { path = "crates/render", version = "0.18.1" }
|
||||
pdfcraft-engine = { path = "crates/engine", version = "0.18.1" }
|
||||
pdfcraft-platform = { path = "crates/platform", version = "0.18.1" }
|
||||
pdfcraft-ui-egui = { path = "crates/ui-egui", version = "0.18.1" }
|
||||
pdfcraft-automation = { path = "crates/automation", version = "0.18.1", default-features = false }
|
||||
pdfcraft-prc = { path = "crates/prc", version = "0.18.1" }
|
||||
pdfcraft-prc-render = { path = "crates/prc-render", version = "0.18.1" }
|
||||
|
||||
# pdf
|
||||
hayro = "0.7.1"
|
||||
|
||||
@@ -88,6 +88,13 @@ destination = "licenses/egui-emoji-icon-font-mit-license.txt"
|
||||
sha256 = "b9d2c1d909aa149996fd4c91dcb92b2362a04431640c1d200959da94caf8cde1"
|
||||
origin = "https://raw.githubusercontent.com/emilk/egui/0.36.2/crates/epaint_default_fonts/fonts/emoji-icon-font-mit-license.txt"
|
||||
|
||||
[[document]]
|
||||
source = "licenses/prc-rs-LICENSE.txt"
|
||||
licences = ["MIT"]
|
||||
destination = "licenses/prc-rs-LICENSE.txt"
|
||||
sha256 = "ddb846513d92153d8fa16b18ed3e7c86953a582f0d2f49f02b29f1bd569dac58"
|
||||
origin = "https://raw.githubusercontent.com/ralovich/prc-rs/287b671bb5b31b05efb5ec1b98f7c359b9e4dd7d/LICENSE"
|
||||
|
||||
[[document]]
|
||||
source = "vendor/hayro-interpret/LICENSE-APACHE"
|
||||
licences = ["Apache-2.0"]
|
||||
|
||||
+19
-1
@@ -1,6 +1,10 @@
|
||||
# PdfCraft roadmap
|
||||
|
||||
> **Last reviewed:** 2026-11-01 | **Last updated:** 2026-11-01 | **Change:** minor (M3, #5452: native Recent rows gain Reveal in Finder / File Explorer; estimates unchanged); previously patch (M7, PR #2155 further review: inherited drawing/text state, affine image gestures, paste/selection ownership, no-op scripts and menu permissions; estimates unchanged)
|
||||
> **Last reviewed:** 2026-11-05 | **Last updated:** 2026-11-05 | **Change:** minor (M3, #5452: native Recent rows gain Reveal in Finder / File Explorer; estimates unchanged))
|
||||
|
||||
> **Last reviewed:** 2026-11-03 | **Last updated:** 2026-11-03 | **Change:** patch (M5 #5850 review: indirect typewriter intent and composer edits preserve outer whitespace; movement regression commits by outside click; estimates unchanged)
|
||||
|
||||
> **Last reviewed:** 2026-11-03 | **Last updated:** 2026-11-03 | **Change:** patch (M5 #5850: Enter adds Fill & Sign line breaks; outside clicks commit, Escape cancels, and saved multiline appearances retain their text and height; estimates unchanged)
|
||||
|
||||
> **Last reviewed:** 2026-11-01 | **Last updated:** 2026-11-01 | **Change:** patch (M7, PR #2155 further review: inherited drawing/text state, affine image gestures, paste/selection ownership, no-op scripts and menu permissions; estimates unchanged)
|
||||
|
||||
@@ -220,6 +224,18 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md
|
||||
|
||||
## Progress log
|
||||
|
||||
- **2026-11-03 (M11, shared JBIG2 dictionary):** With Monochrome Images set to JBIG2, Optimize has up to 16 JBIG2 images share one symbol dictionary (`/JBIG2Globals`), the letters found on more than one page stored once, when that is smaller (`pdfcraft_filters::encode_jbig2_shared`). Eight pages of 10 pt text rendered at 300 dpi take 41% less in Helvetica and 42% in Times than coded alone, 10% in Courier; groups of 16 save 45%, of 32 only 47%. Only images redaction can store again without the dictionary share one (image masks and DeviceGray named directly, at most 64 Mi pixels), and redacting one stores the rest of its group so, which the group bounds to 15.
|
||||
|
||||
- **2026-11-03 (M5, #5850 review):** Resolve indirect IT when recognizing typewriter contents and Fill & Sign marks. Comment listing and property lookup retain their existing direct-Subtype behavior. A real double-click Edit composer preserves the typewriter’s text as written; ordinary comment edits keep trimming. Regressions cover direct Subtype with indirect intent, save/reopen, raw posted contents and same-index comments on different pages. The existing live-movement test now commits its setup with an outside click rather than Enter. Estimates unchanged.
|
||||
|
||||
- **2026-11-03 (M5, #5850):** Fill & Sign Enter inserts a line break in a growing editor; clicking outside commits and Escape cancels. Nonempty text keeps leading, internal and trailing line breaks. The headless fill_sign_add path sizes its typewriter box for the same wrapped lines so saved appearances show every row. Production-order keyboard and engine save/reopen/render regressions exercise the UI and headless callers. Estimates unchanged.
|
||||
|
||||
- **2026-11-03 (M7, #5835):** In image crop mode, a right-click on the image, its crop box or its handles keeps the crop and opens no menu, so the menu no longer switches as the pointer moves. A right-click elsewhere clears the selection and opens the page menu. UI tests cover all four page rotations, overlapping images, a 45° image, and a check that the Done/Cancel/Remove Crop pills at a page edge still keep the crop.
|
||||
|
||||
- **2026-11-02 (M12, #1297):** `model_add` embeds a U3D or PRC file, recognised by its header, as a 3D annotation with a chosen activation and a poster (an image or PDF page fitted in the box, or a labelled frame); an older file's catalog `/Version` is raised to what the model needs. Views, a rendered poster and a UI command are later steps. Estimates unchanged.
|
||||
|
||||
- **2026-11-02 (M10, #5743):** macOS print jobs name their title and owner (`com.apple.print.JobInfo.PMJobName`, `PMJobOwner`), as the system print dialog does, because a Kyocera filter crashes on a job without them. Checked by a test and through `doc_print` against a stand-in `lp`; not yet on a physical printer.
|
||||
|
||||
- **2026-10-31 (M3, #5452):** Native Home Recent rows offer Reveal in Finder / File Explorer from their context menu. They reuse the tab action’s platform dispatch and missing-file notification without opening the PDF or changing Recent. Browser behavior and milestone estimates unchanged.
|
||||
- **2026-11-01 (M7, #5657):** Header/footer, watermark and background additions and replacements stage the complete call before committing. Synthetic later-page failures preserve exact incremental bytes, object count, dirty state and allocation cursor; success controls retain other marks and shared/inherited resources. Estimates unchanged.
|
||||
|
||||
@@ -747,6 +763,8 @@ Newest first: the date, what moved, and the new overall estimate.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-03 | patch | M5 #5850 review: indirect typewriter intent and composer edits preserve outer whitespace; movement regression commits by outside click; estimates unchanged. |
|
||||
| 2026-11-03 | patch | M5 #5850: Enter adds Fill & Sign line breaks; outside clicks commit, Escape cancels, and saved multiline appearances retain their text and height; estimates unchanged. |
|
||||
| 2026-11-01 | patch | M7 #5657: header/footer, watermark and background operations commit all selected pages together; later-page errors preserve marks, content, shared/inherited resources and pending edits; estimates unchanged. |
|
||||
| 2026-10-28 | patch | M2, PR #4172: integrate main 5af10276 and retain both roadmap histories; viewer-only CJK and print/output font policies and estimates unchanged. |
|
||||
| 2026-10-27 | minor | M10: OCR stages SHA-256-verified ONNX models after the upstream RTEN URL brownout; discovery prefers a complete ONNX pair per directory, falls back to a complete RTEN pair, and never mixes formats or directories; language/web limits and estimates unchanged. |
|
||||
|
||||
@@ -663,6 +663,29 @@ fn the_desktop_entry_names_the_window_class() {
|
||||
assert_eq!(class, Some(super::APP_ID));
|
||||
}
|
||||
|
||||
/// Both flatpak manifests build with the craft-i18n commit `craft-i18n.rev` pins: CI and
|
||||
/// releases read the file, and the manifests have to name the commit themselves. The pin may
|
||||
/// have a trailing newline or CRLF (a Windows checkout), so it is trimmed, as every reader does.
|
||||
#[test]
|
||||
fn the_flatpak_manifests_pin_the_craft_i18n_rev() {
|
||||
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
|
||||
let rev = std::fs::read_to_string(root.join("craft-i18n.rev")).unwrap();
|
||||
let rev = rev.trim();
|
||||
assert!(rev.len() == 40 && rev.bytes().all(|b| b.is_ascii_hexdigit()), "craft-i18n.rev holds one full SHA: {rev:?}");
|
||||
for manifest in ["ai.storyteller.pdfcraft.yml", "ai.storyteller.pdfcraft.bundle.yml"] {
|
||||
let text = std::fs::read_to_string(root.join("packaging/linux/flatpak").join(manifest)).unwrap();
|
||||
let source =
|
||||
text.split("url: https://github.com/storytold/craft-i18n").nth(1).unwrap_or_else(|| panic!("{manifest}: a craft-i18n git source"));
|
||||
let source = source.split("- type:").next().unwrap_or(source);
|
||||
let commit = source
|
||||
.lines()
|
||||
.find_map(|l| l.trim().strip_prefix("commit:"))
|
||||
.unwrap_or_else(|| panic!("{manifest}: the craft-i18n source pins a commit"));
|
||||
assert_eq!(commit.trim().trim_matches(['\'', '"']), rev, "{manifest}");
|
||||
assert!(text.contains("CRAFT_I18N_DIR: /run/build/pdfcraft/craft-i18n"), "{manifest}: the build names the catalogs");
|
||||
}
|
||||
}
|
||||
|
||||
// Regression test for #246: `eprintln!` panics when standard error is a broken pipe (a GUI
|
||||
// launch on Windows), and a panic inside the panic hook aborts the process. The start-up
|
||||
// path and the hook must write to standard error without it.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
575cfdef63227b14daf173b8cc87928bfdfa325e
|
||||
+78
-92
@@ -125,9 +125,9 @@ pub(crate) fn elements(doc: &Document) -> Vec<Element> {
|
||||
}
|
||||
|
||||
/// The marked-content ids an element owns directly.
|
||||
fn mcids(d: &Dict) -> Vec<i64> {
|
||||
fn mcids(doc: &Document, d: &Dict) -> Vec<i64> {
|
||||
let mut out = Vec::new();
|
||||
let mut push = |o: &Object| match o {
|
||||
let mut push = |o: &Object| match &*doc.resolve(o) {
|
||||
Object::Int(n) => out.push(*n),
|
||||
Object::Dict(m) if m.name(b"Type") == Some(b"MCR") => {
|
||||
if let Some(n) = m.get(b"MCID").and_then(Object::as_int) {
|
||||
@@ -323,7 +323,7 @@ pub fn figures(doc: &Document) -> Vec<Figure> {
|
||||
let mut wanted: HashMap<usize, HashSet<i64>> = HashMap::new();
|
||||
for (e, page) in &figures {
|
||||
if let Some(p) = page {
|
||||
wanted.entry(*p).or_default().extend(mcids(&e.dict));
|
||||
wanted.entry(*p).or_default().extend(mcids(doc, &e.dict));
|
||||
}
|
||||
}
|
||||
let bounds: HashMap<usize, HashMap<i64, [f64; 4]>> =
|
||||
@@ -333,7 +333,7 @@ pub fn figures(doc: &Document) -> Vec<Figure> {
|
||||
.map(|(e, page)| {
|
||||
let alt = e.dict.get(b"Alt").map(|o| doc.resolve(o)).and_then(|o| o.as_string().map(|s| s.to_text()));
|
||||
let on_page = page.and_then(|p| bounds.get(&p));
|
||||
let bbox = on_page.and_then(|b| mcids(&e.dict).iter().filter_map(|id| b.get(id)).fold(None, |acc, b| Some(union(acc, *b))));
|
||||
let bbox = on_page.and_then(|b| mcids(doc, &e.dict).iter().filter_map(|id| b.get(id)).fold(None, |acc, b| Some(union(acc, *b))));
|
||||
Figure { obj: e.object, page, alt, bbox }
|
||||
})
|
||||
.collect()
|
||||
@@ -363,52 +363,73 @@ pub fn set_alt(doc: &mut Document, obj: ObjRef, alt: Option<&str>) -> Result<(),
|
||||
}
|
||||
|
||||
/// The page's content entries: one stream, or the elements of a contents array.
|
||||
fn content_entries(doc: &Document, page: &Dict) -> Option<Vec<Object>> {
|
||||
let c = page.get(b"Contents")?;
|
||||
Some(match &*doc.resolve(c) {
|
||||
fn content_entries(doc: &Document, page: &Dict) -> Vec<Object> {
|
||||
let Some(c) = page.get(b"Contents") else { return Vec::new() };
|
||||
match &*doc.resolve(c) {
|
||||
Object::Array(a) => a.clone(),
|
||||
Object::Stream(_) => vec![c.clone()],
|
||||
_ => return None,
|
||||
})
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn marked(doc: &Document, props: &Dict, op: &pdfcraft_content::Op, ids: &HashSet<i64>) -> bool {
|
||||
op.op == b"BDC" && bdc_mcid(doc, props, op).is_some_and(|n| ids.contains(&n))
|
||||
}
|
||||
type DecodedStream = (Object, Option<(Stream, Vec<u8>)>);
|
||||
|
||||
/// Turn the figure's marked-content blocks into artifacts, rewriting only the streams that
|
||||
/// contain them. Other content streams, and unknown entries on a rewritten stream, stay.
|
||||
fn artifact_marked_content(doc: &mut Document, page: &pdfcraft_model::Page, ids: &HashSet<i64>) -> Result<bool, AltError> {
|
||||
let Some(entries) = content_entries(doc, &page.dict) else { return Ok(false) };
|
||||
let props = page_properties(doc, &page.dict);
|
||||
let mut streams: Vec<Option<(Stream, Vec<u8>)>> = Vec::with_capacity(entries.len());
|
||||
/// Decode the page's content streams within the page decode budget. `None` when a stream is
|
||||
/// rejected because the budget is exhausted.
|
||||
fn decode_page_streams(doc: &Document, page: &Dict) -> Option<Vec<DecodedStream>> {
|
||||
let entries = content_entries(doc, page);
|
||||
let mut streams = Vec::with_capacity(entries.len());
|
||||
let mut budget = DecodeBudget::page();
|
||||
for entry in &entries {
|
||||
match &*doc.resolve(entry) {
|
||||
for entry in entries {
|
||||
let decoded = match &*doc.resolve(&entry) {
|
||||
Object::Stream(s) => {
|
||||
let data = match budget.decode(s) {
|
||||
Ok(data) => data,
|
||||
// Content past the budget may hold more of the figure: editing the rest
|
||||
// would leave that part marked as a figure the tree no longer has.
|
||||
Err(_) if budget.exhausted() => return Err(AltError::TooLarge),
|
||||
// Content past the budget may hold more of the tag: editing the rest
|
||||
// would leave that part marked as a tag the tree no longer has.
|
||||
Err(_) if budget.exhausted() => return None,
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
streams.push(Some((s.clone(), data)));
|
||||
Some((s.clone(), data))
|
||||
}
|
||||
_ => streams.push(None),
|
||||
_ => None,
|
||||
};
|
||||
streams.push((entry, decoded));
|
||||
}
|
||||
Some(streams)
|
||||
}
|
||||
|
||||
fn marked_id(doc: &Document, props: &Dict, op: &pdfcraft_content::Op, ids: &HashSet<i64>) -> Option<i64> {
|
||||
(op.op == b"BDC").then(|| bdc_mcid(doc, props, op)).flatten().filter(|n| ids.contains(n))
|
||||
}
|
||||
|
||||
/// Turn the tag's marked-content blocks into artifacts, rewriting only the streams that
|
||||
/// contain them. Other content streams, and unknown entries on a rewritten stream, stay.
|
||||
/// With `require_all`, every id in `ids` must appear on the page before any stream is rewritten.
|
||||
fn artifact_marked_content(
|
||||
doc: &mut Document,
|
||||
page: &pdfcraft_model::Page,
|
||||
streams: Vec<DecodedStream>,
|
||||
ids: &HashSet<i64>,
|
||||
require_all: bool,
|
||||
) -> Result<bool, pdfcraft_cos::CosError> {
|
||||
let props = page_properties(doc, &page.dict);
|
||||
let decoded: Vec<&[u8]> = streams.iter().filter_map(|(_, s)| s.as_ref().map(|(_, bytes)| bytes.as_slice())).collect();
|
||||
let pieces = pdfcraft_content::Pieces::join(&decoded);
|
||||
let mut found = HashSet::new();
|
||||
let mut edits = Vec::new();
|
||||
for op in pieces.parse().ops {
|
||||
if let Some(id) = marked_id(doc, &props, &op, ids) {
|
||||
found.insert(id);
|
||||
edits.push((op.span, b"/Artifact BMC".to_vec()));
|
||||
}
|
||||
}
|
||||
let decoded: Vec<&[u8]> = streams.iter().flatten().map(|(_, bytes)| bytes.as_slice()).collect();
|
||||
let pieces = pdfcraft_content::Pieces::join(&decoded);
|
||||
let edits: Vec<_> =
|
||||
pieces.parse().ops.into_iter().filter(|op| marked(doc, &props, op, ids)).map(|op| (op.span, b"/Artifact BMC".to_vec())).collect();
|
||||
if edits.is_empty() {
|
||||
if if require_all { found != *ids } else { found.is_empty() } {
|
||||
return Ok(false);
|
||||
}
|
||||
let mut spliced = pieces.splice(edits).into_iter();
|
||||
let mut next = Vec::with_capacity(entries.len());
|
||||
let mut changed = false;
|
||||
for (entry, stream) in entries.into_iter().zip(streams) {
|
||||
let mut next = Vec::with_capacity(streams.len());
|
||||
for (entry, stream) in streams {
|
||||
let Some((stream, old)) = stream else {
|
||||
next.push(entry);
|
||||
continue;
|
||||
@@ -421,7 +442,6 @@ fn artifact_marked_content(doc: &mut Document, page: &pdfcraft_model::Page, ids:
|
||||
next.push(entry);
|
||||
continue;
|
||||
}
|
||||
changed = true;
|
||||
let mut dict = stream.dict;
|
||||
dict.remove(b"Length");
|
||||
dict.remove(b"Filter");
|
||||
@@ -431,28 +451,27 @@ fn artifact_marked_content(doc: &mut Document, page: &pdfcraft_model::Page, ids:
|
||||
// appear elsewhere in this array or on another page, where it must stay unchanged.
|
||||
next.push(Object::Ref(doc.add(stream)));
|
||||
}
|
||||
if changed {
|
||||
let contents = match next.as_slice() {
|
||||
[one] => one.clone(),
|
||||
_ => Object::Array(next),
|
||||
};
|
||||
doc.update_dict(page.obj, |p| p.set(b"Contents".to_vec(), contents))?;
|
||||
}
|
||||
Ok(changed)
|
||||
let contents = match next.as_slice() {
|
||||
[one] => one.clone(),
|
||||
_ => Object::Array(next),
|
||||
};
|
||||
doc.update_dict(page.obj, |p| p.set(b"Contents".to_vec(), contents))?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Decorative figure: its marked content on the page becomes an artifact, and the element
|
||||
/// leaves the structure tree (and the parent tree).
|
||||
pub fn mark_decorative(doc: &mut Document, obj: ObjRef) -> Result<(), AltError> {
|
||||
let d = check_figure(doc, obj)?;
|
||||
let ids: HashSet<i64> = mcids(&d).into_iter().collect();
|
||||
let ids: HashSet<i64> = mcids(doc, &d).into_iter().collect();
|
||||
let pages = pdfcraft_model::pages(doc);
|
||||
let pg = d.get(b"Pg").and_then(Object::as_ref).or_else(|| elements(doc).into_iter().find(|e| e.object == obj).and_then(|e| e.page));
|
||||
if let Some(page) = pg.and_then(|p| pages.iter().find(|x| x.obj == p)).filter(|_| !ids.is_empty())
|
||||
&& artifact_marked_content(doc, page, &ids)?
|
||||
{
|
||||
if let Some(page) = pg.and_then(|p| pages.iter().find(|x| x.obj == p)).filter(|_| !ids.is_empty()) {
|
||||
let streams = decode_page_streams(doc, &page.dict).ok_or(AltError::TooLarge)?;
|
||||
// The parent tree no longer points those ids at the figure.
|
||||
if let Some(key) = page.dict.get(b"StructParents").and_then(Object::as_int) {
|
||||
if artifact_marked_content(doc, page, streams, &ids, false)?
|
||||
&& let Some(key) = page.dict.get(b"StructParents").and_then(Object::as_int)
|
||||
{
|
||||
clear_parent_tree(doc, key, obj)?;
|
||||
}
|
||||
}
|
||||
@@ -484,54 +503,23 @@ pub fn mark_tag_artifact(doc: &mut Document, obj: ObjRef) -> Result<(), Artifact
|
||||
if has_child_structure(doc, &element.dict) {
|
||||
return Err(ArtifactError::HasChildren);
|
||||
}
|
||||
let ids: HashSet<i64> = mcids(&element.dict).into_iter().collect();
|
||||
let ids: HashSet<i64> = mcids(doc, &element.dict).into_iter().collect();
|
||||
if ids.is_empty() {
|
||||
return Err(ArtifactError::NoMarkedContent);
|
||||
}
|
||||
let pages = pdfcraft_model::pages(doc);
|
||||
let page_ref = element.page.ok_or(ArtifactError::NoPage)?;
|
||||
let page = pages.iter().find(|page| page.obj == page_ref).ok_or(ArtifactError::NoPage)?;
|
||||
let mut content = Vec::new();
|
||||
if let Some(c) = page.dict.get(b"Contents") {
|
||||
let mut budget = DecodeBudget::page();
|
||||
for data in budget.decode_contents(doc, c) {
|
||||
match data {
|
||||
Ok(data) => {
|
||||
content.extend_from_slice(&data);
|
||||
content.push(b' ');
|
||||
}
|
||||
// The page's /Contents is replaced with what was decoded: content past the
|
||||
// budget would be dropped from the page.
|
||||
Err(_) if budget.exhausted() => return Err(ArtifactError::TooLarge),
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut ops = pdfcraft_content::parse(&content).ops;
|
||||
let mut found = HashSet::new();
|
||||
for op in &mut ops {
|
||||
let id = (op.op == b"BDC")
|
||||
.then(|| match op.operands.get(1) {
|
||||
Some(Object::Dict(mark)) => mark.get(b"MCID").and_then(Object::as_int),
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.filter(|id| ids.contains(id));
|
||||
if let Some(id) = id {
|
||||
*op = pdfcraft_content::Op::new("BMC", vec![Object::name("Artifact")]);
|
||||
found.insert(id);
|
||||
}
|
||||
}
|
||||
if found != ids {
|
||||
let parent = element.dict.get(b"P").and_then(Object::as_ref).ok_or(ArtifactError::NoParent)?;
|
||||
let replacement = replacement_kids(doc, parent, obj)?;
|
||||
let streams = decode_page_streams(doc, &page.dict).ok_or(ArtifactError::TooLarge)?;
|
||||
if !artifact_marked_content(doc, page, streams, &ids, true)? {
|
||||
return Err(ArtifactError::ContentNotFound);
|
||||
}
|
||||
let stream = doc.add(Object::Stream(Stream::flate(Dict::new(), &pdfcraft_content::serialize_ops(&ops))));
|
||||
doc.update_dict(page.obj, |p| p.set(b"Contents".to_vec(), Object::Ref(stream)))?;
|
||||
if let Some(key) = page.dict.get(b"StructParents").and_then(Object::as_int) {
|
||||
clear_parent_tree(doc, key, obj).map_err(|e| ArtifactError::ParentTree(e.to_string()))?;
|
||||
}
|
||||
let parent = element.dict.get(b"P").and_then(Object::as_ref).ok_or(ArtifactError::NoParent)?;
|
||||
remove_from_parent(doc, parent, obj)?;
|
||||
doc.update_dict(parent, |p| p.set(b"K".to_vec(), replacement))?;
|
||||
// Keep the retired object valid if another object holds an out-of-tree reference to it.
|
||||
doc.update_dict(obj, |d| {
|
||||
d.remove(b"P");
|
||||
@@ -551,20 +539,18 @@ fn has_child_structure(doc: &Document, tag: &Dict) -> bool {
|
||||
tag.get(b"K").is_some_and(|items| child(doc, items))
|
||||
}
|
||||
|
||||
fn remove_from_parent(doc: &mut Document, parent: ObjRef, obj: ObjRef) -> Result<(), ArtifactError> {
|
||||
fn replacement_kids(doc: &Document, parent: ObjRef, obj: ObjRef) -> Result<Object, ArtifactError> {
|
||||
let kids = doc.get(parent).as_dict().and_then(|p| p.get(b"K").cloned()).ok_or(ArtifactError::NoParent)?;
|
||||
let replacement = match kids {
|
||||
match kids {
|
||||
Object::Array(items) => {
|
||||
if items.iter().filter(|item| item.as_ref() == Some(obj)).count() != 1 {
|
||||
return Err(ArtifactError::AmbiguousParent);
|
||||
}
|
||||
Object::Array(items.into_iter().filter(|item| item.as_ref() != Some(obj)).collect())
|
||||
Ok(Object::Array(items.into_iter().filter(|item| item.as_ref() != Some(obj)).collect()))
|
||||
}
|
||||
Object::Ref(r) if r == obj => Object::Array(Vec::new()),
|
||||
_ => return Err(ArtifactError::NoParent),
|
||||
};
|
||||
doc.update_dict(parent, |p| p.set(b"K".to_vec(), replacement))?;
|
||||
Ok(())
|
||||
Object::Ref(r) if r == obj => Ok(Object::Array(Vec::new())),
|
||||
_ => Err(ArtifactError::NoParent),
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace `obj` by null in the page's parent-tree array (`key`), in a flat /Nums tree.
|
||||
|
||||
@@ -1017,6 +1017,76 @@ fn named_property_lists_are_found_and_marked_decorative_like_inline_ones() {
|
||||
assert!(props.contains(b"P0") && props.contains(b"P1") && props.contains(b"Other"), "resources are left as they were");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mark_tag_artifact_finds_named_property_lists_and_indirect_mcrs() {
|
||||
use pdfcraft_cos::{Dict, ObjRef, Object};
|
||||
let mut doc = named_figures_doc();
|
||||
let mcr = doc.add(Object::Dict([(b"Type".to_vec(), Object::name("MCR")), (b"MCID".to_vec(), Object::Int(0))].into_iter().collect::<Dict>()));
|
||||
doc.update_dict(ObjRef::new(7, 0), |d| d.set(b"K".to_vec(), Object::Ref(mcr))).unwrap();
|
||||
assert_eq!(figures(&doc)[0].bbox, Some([10.0, 150.0, 50.0, 170.0]), "indirect MCR is resolved for figure bounds");
|
||||
|
||||
for (tag, gone) in [(ObjRef::new(7, 0), "/P0"), (ObjRef::new(8, 0), "/P1")] {
|
||||
mark_tag_artifact(&mut doc, tag).unwrap();
|
||||
let pages = pdfcraft_model::pages(&doc);
|
||||
let c = doc.resolve(pages[0].dict.get(b"Contents").unwrap());
|
||||
let Object::Stream(c) = &*c else { panic!("one content stream") };
|
||||
let text = String::from_utf8(c.decoded().unwrap()).unwrap();
|
||||
assert!(text.contains("/Artifact BMC") && !text.contains(gone), "{text}");
|
||||
}
|
||||
assert_eq!(tags(&doc).into_iter().map(|t| t.object.num).collect::<Vec<_>>(), [6]);
|
||||
let nums = doc.get(ObjRef::new(9, 0)).as_dict().and_then(|d| d.get(b"Nums").cloned());
|
||||
assert_eq!(nums, Some(Object::Array(vec![Object::Int(0), Object::Array(vec![Object::Null, Object::Null])])));
|
||||
let r = check(&doc, &Options { rules: [Rule::FiguresAltText, Rule::TaggedContent].into(), pages: None });
|
||||
assert_eq!(failed(&r), Vec::<Rule>::new(), "{:?}", r.results);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mark_tag_artifact_validates_parent_and_all_mcids_before_mutating_document() {
|
||||
use pdfcraft_cos::{ObjRef, Object};
|
||||
|
||||
// Missing /P on the tag must fail before touching /Contents or /ParentTree.
|
||||
let mut no_parent = figures_doc();
|
||||
let before_contents = no_parent.get(ObjRef::new(3, 0)).as_dict().unwrap().get(b"Contents").cloned();
|
||||
let before_pt = no_parent.get(ObjRef::new(9, 0)).as_ref().clone();
|
||||
no_parent
|
||||
.update_dict(ObjRef::new(7, 0), |d| {
|
||||
d.remove(b"P");
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(mark_tag_artifact(&mut no_parent, ObjRef::new(7, 0)), Err(ArtifactError::NoParent));
|
||||
assert_eq!(no_parent.get(ObjRef::new(3, 0)).as_dict().unwrap().get(b"Contents").cloned(), before_contents);
|
||||
assert_eq!(*no_parent.get(ObjRef::new(9, 0)), before_pt);
|
||||
|
||||
// Duplicate entry in parent's /K must fail with AmbiguousParent before touching /Contents or /ParentTree.
|
||||
let mut dup_parent = figures_doc();
|
||||
dup_parent
|
||||
.update_dict(ObjRef::new(6, 0), |d| {
|
||||
d.set(b"K".to_vec(), Object::Array(vec![Object::Ref(ObjRef::new(7, 0)), Object::Ref(ObjRef::new(8, 0)), Object::Ref(ObjRef::new(7, 0))]));
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(mark_tag_artifact(&mut dup_parent, ObjRef::new(7, 0)), Err(ArtifactError::AmbiguousParent));
|
||||
assert_eq!(dup_parent.get(ObjRef::new(3, 0)).as_dict().unwrap().get(b"Contents").cloned(), before_contents);
|
||||
assert_eq!(*dup_parent.get(ObjRef::new(9, 0)), before_pt);
|
||||
|
||||
// Partial MCID match (MCID 0 present, MCID 99 absent) must fail with ContentNotFound in
|
||||
// mark_tag_artifact without mutating /Contents, while mark_decorative still converts MCID 0.
|
||||
let mut partial = figures_doc();
|
||||
partial.update_dict(ObjRef::new(7, 0), |d| d.set(b"K".to_vec(), Object::Array(vec![Object::Int(0), Object::Int(99)]))).unwrap();
|
||||
assert_eq!(mark_tag_artifact(&mut partial, ObjRef::new(7, 0)), Err(ArtifactError::ContentNotFound));
|
||||
assert_eq!(partial.get(ObjRef::new(3, 0)).as_dict().unwrap().get(b"Contents").cloned(), before_contents);
|
||||
assert_eq!(*partial.get(ObjRef::new(9, 0)), before_pt);
|
||||
mark_decorative(&mut partial, ObjRef::new(7, 0)).unwrap();
|
||||
assert_ne!(partial.get(ObjRef::new(3, 0)).as_dict().unwrap().get(b"Contents").cloned(), before_contents);
|
||||
assert_ne!(*partial.get(ObjRef::new(9, 0)), before_pt);
|
||||
|
||||
// When none of a figure's MCIDs appear on the page, mark_decorative leaves /Contents and /ParentTree untouched.
|
||||
let mut absent = figures_doc();
|
||||
absent.update_dict(ObjRef::new(7, 0), |d| d.set(b"K".to_vec(), Object::Int(99))).unwrap();
|
||||
mark_decorative(&mut absent, ObjRef::new(7, 0)).unwrap();
|
||||
assert_eq!(absent.get(ObjRef::new(3, 0)).as_dict().unwrap().get(b"Contents").cloned(), before_contents);
|
||||
assert_eq!(*absent.get(ObjRef::new(9, 0)), before_pt);
|
||||
}
|
||||
|
||||
fn ref_(n: u32) -> Option<pdfcraft_cos::ObjRef> {
|
||||
Some(pdfcraft_cos::ObjRef::new(n, 0))
|
||||
}
|
||||
|
||||
@@ -99,14 +99,20 @@ fn shown_strings(ops: &[Op]) -> Vec<String> {
|
||||
|
||||
#[test]
|
||||
fn mark_decorative_leaves_the_other_stream_untouched() {
|
||||
let mut doc = tagged_figure_two_streams("0 0 1 rg 300 600 100 50 re f", "BT /F1 12 Tf 72 500 Td (After) Tj ET");
|
||||
let before = doc.get(ObjRef::new(11, 0)).clone();
|
||||
mark_decorative(&mut doc, ObjRef::new(8, 0)).expect("mark decorative succeeds");
|
||||
assert!(figures(&doc).is_empty());
|
||||
assert_eq!(*doc.get(ObjRef::new(11, 0)), *before, "the second stream holds no figure and keeps its object");
|
||||
let page = doc.get(ObjRef::new(3, 0));
|
||||
let contents = page.as_dict().unwrap().get(b"Contents").unwrap().as_array().unwrap();
|
||||
assert_eq!(contents[1].as_ref(), Some(ObjRef::new(11, 0)), "the untouched stream must not be replaced by a re-encoded copy");
|
||||
for use_artifact in [false, true] {
|
||||
let mut doc = tagged_figure_two_streams("0 0 1 rg 300 600 100 50 re f", "BT /F1 12 Tf 72 500 Td (After) Tj ET");
|
||||
let before = doc.get(ObjRef::new(11, 0)).clone();
|
||||
if use_artifact {
|
||||
mark_tag_artifact(&mut doc, ObjRef::new(8, 0)).expect("mark tag artifact succeeds");
|
||||
} else {
|
||||
mark_decorative(&mut doc, ObjRef::new(8, 0)).expect("mark decorative succeeds");
|
||||
}
|
||||
assert!(figures(&doc).is_empty());
|
||||
assert_eq!(*doc.get(ObjRef::new(11, 0)), *before, "the second stream holds no figure and keeps its object");
|
||||
let page = doc.get(ObjRef::new(3, 0));
|
||||
let contents = page.as_dict().unwrap().get(b"Contents").unwrap().as_array().unwrap();
|
||||
assert_eq!(contents[1].as_ref(), Some(ObjRef::new(11, 0)), "the untouched stream must not be replaced by a re-encoded copy");
|
||||
}
|
||||
}
|
||||
|
||||
/// A and B supply different operands to R's BDC. Reusing R must not let the first
|
||||
|
||||
@@ -278,7 +278,7 @@ fn ext_gstate(opacity: f64, multiply: bool) -> Option<Dict> {
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn form(bbox: [f64; 4], content: &[u8], resources: Dict) -> Stream {
|
||||
pub(crate) fn form(bbox: [f64; 4], content: &[u8], resources: Dict) -> Stream {
|
||||
let mut d = Dict::new();
|
||||
d.set(b"Type".to_vec(), Object::name("XObject"));
|
||||
d.set(b"Subtype".to_vec(), Object::name("Form"));
|
||||
@@ -288,6 +288,18 @@ fn form(bbox: [f64; 4], content: &[u8], resources: Dict) -> Stream {
|
||||
Stream::flate(d, content)
|
||||
}
|
||||
|
||||
/// The `cm` that draws a picture into `at`: an image (its unit square) or a form XObject whose
|
||||
/// `/Matrix` maps it to `size` points (`None`, or a side that is not positive, draws nothing).
|
||||
pub(crate) fn picture_place(at: [f64; 4], image: bool, size: Option<(f64, f64)>) -> Option<String> {
|
||||
let [x0, y0, x1, y1] = at;
|
||||
let (w, h) = (x1 - x0, y1 - y0);
|
||||
if image {
|
||||
return Some(format!("{} 0 0 {} {} {} cm", n(w), n(h), n(x0), n(y0)));
|
||||
}
|
||||
let (sw, sh) = size.filter(|(sw, sh)| *sw > 0.0 && *sh > 0.0)?;
|
||||
Some(format!("{} 0 0 {} {} {} cm", n(w / sw), n(h / sh), n(x0), n(y0)))
|
||||
}
|
||||
|
||||
/// The page `/Rotate` an image signature's picture is turned back by (`/PCPictureRotate`, written
|
||||
/// when the signature is added to a turned page): 0, 90, 180 or 270.
|
||||
pub(crate) fn picture_turn(d: &Dict) -> i64 {
|
||||
@@ -325,7 +337,7 @@ pub fn undrawable_text(d: &Dict) -> Option<(&'static str, char)> {
|
||||
}
|
||||
|
||||
/// `/Font` resources naming `/Helv`: standard Helvetica with WinAnsiEncoding.
|
||||
fn helvetica_resource() -> Object {
|
||||
pub(crate) fn helvetica_resource() -> Object {
|
||||
let mut font = Dict::new();
|
||||
font.set(b"Type".to_vec(), Object::name("Font"));
|
||||
font.set(b"Subtype".to_vec(), Object::name("Type1"));
|
||||
@@ -786,15 +798,12 @@ fn draw_stamp(context: &AppearanceContext<'_>, mut drawing: AppearanceDrawing) -
|
||||
// the standard counter-rotation as its form `/Matrix`: viewers fit the turned
|
||||
// bounding box to `/Rect` (ISO 32000-2 §12.5.5), so it reads upright as displayed.
|
||||
if let Some(pic) = d.get(b"PCPicture").and_then(Object::as_ref) {
|
||||
let [x0, y0, x1, y1] = rect;
|
||||
let (w, h) = (x1 - x0, y1 - y0);
|
||||
let image = matches!(d.get(b"PCPictureImage"), Some(Object::Bool(true)));
|
||||
let place = if image {
|
||||
format!("{} 0 0 {} {} {} cm", n(w), n(h), n(x0), n(y0))
|
||||
} else {
|
||||
let size = nums(d, b"PCPictureSize").filter(|s| s.len() == 2 && s[0] > 0.0 && s[1] > 0.0)?;
|
||||
format!("{} 0 0 {} {} {} cm", n(w / size[0]), n(h / size[1]), n(x0), n(y0))
|
||||
};
|
||||
let size = nums(d, b"PCPictureSize").and_then(|s| match s.as_slice() {
|
||||
[w, h] if *w > 0.0 && *h > 0.0 => Some((*w, *h)),
|
||||
_ => None,
|
||||
});
|
||||
let place = picture_place(rect, image, size)?;
|
||||
c.push_str(&format!("q {place} /Pic Do Q\n"));
|
||||
let mut xo = Dict::new();
|
||||
xo.set(b"Pic".to_vec(), Object::Ref(pic));
|
||||
|
||||
@@ -47,8 +47,12 @@ pub(crate) fn text_value(doc: &Document, d: &Dict, key: &[u8]) -> Option<String>
|
||||
Object::Name(n) => String::from_utf8_lossy(n).into_owned(),
|
||||
_ => return None,
|
||||
};
|
||||
let s = s.trim_matches('\0').trim().to_string();
|
||||
(!s.is_empty()).then_some(s)
|
||||
let s = s.trim_matches('\0');
|
||||
// Typewriter outer whitespace belongs to the text (keep in step with render's inspector).
|
||||
let name_is = |key: &[u8], expected: &[u8]| d.get(key).is_some_and(|o| doc.resolve(o).as_name() == Some(expected));
|
||||
let keep_breaks = key == b"Contents" && name_is(b"Subtype", b"FreeText") && name_is(b"IT", b"FreeTextTypeWriter");
|
||||
let s = if keep_breaks { s } else { s.trim() };
|
||||
(!s.trim().is_empty()).then(|| s.to_owned())
|
||||
}
|
||||
|
||||
/// Whether an annotation subtype is a comment. Links, form widgets, pop-ups and the non-markup
|
||||
|
||||
@@ -22,7 +22,9 @@ mod create;
|
||||
mod draw;
|
||||
mod info;
|
||||
mod lookup;
|
||||
mod model3d;
|
||||
mod modify;
|
||||
mod threed;
|
||||
mod types;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -36,11 +38,13 @@ use lookup::{annots, page_ref};
|
||||
pub use lookup::{
|
||||
has_visible_fill_sign, is_fill_sign, isolate_annotation, isolate_annotations, page_refs, picture_rotation, set_annots, signature_image,
|
||||
};
|
||||
pub use model3d::{Model3d, Model3dKind, model_3d, model_3d_annotations};
|
||||
pub(crate) use modify::n;
|
||||
pub use modify::{
|
||||
add_reply, add_text_replacement, deletable, delete_annotation, erase_ink, fillable, goes_with, lift_annotation, move_annotation, set_contents,
|
||||
set_fill, set_font_size, set_locked, set_marked, set_points, set_rect, set_review_state, set_style, with_replies_and_popups,
|
||||
};
|
||||
pub use threed::{Activation as Activation3d, MAX_MODEL_BYTES, ModelFormat, NewModel3d, PosterPicture, add_3d_model, check_model};
|
||||
pub use types::{
|
||||
AttachIcon, FillMark, LineEnding, Markup, Meta, NOTE_SIZE, NewAnnotation, NoteIcon, OverlayFont, OverlayLook, ReviewState, Rgb, Shape,
|
||||
StampGroup, StampKind, Style, StyleChange, TextReplacement, rect_quad,
|
||||
@@ -70,6 +74,12 @@ pub enum AnnotError {
|
||||
/// with a font that has those characters. The message names the character.
|
||||
#[error("{0}")]
|
||||
Undrawable(String),
|
||||
/// A 3D model file that is neither U3D (starts with `U3D` and a zero byte) nor PRC (starts `PRC`).
|
||||
#[error("not a 3D model: the file must be U3D (ECMA-363) or PRC (ISO 14739-1)")]
|
||||
NotAModel,
|
||||
/// A 3D model larger than `MAX_MODEL_BYTES`.
|
||||
#[error("the 3D model is {size} bytes; the largest accepted is {max}")]
|
||||
ModelTooLarge { size: usize, max: usize },
|
||||
#[error("{0}")]
|
||||
Cos(#[from] pdfcraft_cos::CosError),
|
||||
}
|
||||
|
||||
@@ -137,11 +137,11 @@ pub fn is_fill_sign(doc: &Document, d: &Dict) -> bool {
|
||||
if matches!(d.get(b"PCFillSign").map(|o| doc.resolve(o)).as_deref(), Some(Object::Bool(true))) {
|
||||
return true;
|
||||
}
|
||||
let subtype = d.name(b"Subtype").unwrap_or_default();
|
||||
if subtype == b"FreeText" && d.name(b"IT") == Some(b"FreeTextTypeWriter") {
|
||||
let name_is = |key: &[u8], expected: &[u8]| d.get(key).is_some_and(|o| doc.resolve(o).as_name() == Some(expected));
|
||||
if name_is(b"Subtype", b"FreeText") && name_is(b"IT", b"FreeTextTypeWriter") {
|
||||
return true;
|
||||
}
|
||||
if subtype == b"Stamp" {
|
||||
if name_is(b"Subtype", b"Stamp") {
|
||||
if matches!(d.name(b"Name"), Some(b"PCCheck" | b"PCCross" | b"PCDot" | b"PCLine" | b"PCTypedSignature")) {
|
||||
return true;
|
||||
}
|
||||
@@ -151,7 +151,7 @@ pub fn is_fill_sign(doc: &Document, d: &Dict) -> bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
subtype == b"Ink" && text_value(doc, d, b"Subj").as_deref() == Some("Signature")
|
||||
name_is(b"Subtype", b"Ink") && text_value(doc, d, b"Subj").as_deref() == Some("Signature")
|
||||
}
|
||||
|
||||
pub(crate) fn annotation_flags(doc: &Document, d: &Dict) -> i64 {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
//! Reading the model a 3D annotation shows (ISO 32000-2 §13.6.3 and §13.6.4): its `/3DD` is a
|
||||
//! 3D stream, or a 3D reference dictionary naming the stream another annotation shares.
|
||||
|
||||
use super::lookup::{annots, page_ref};
|
||||
use super::threed::MAX_MODEL_BYTES;
|
||||
use super::{AnnotError, Dict, Document, Object};
|
||||
|
||||
/// The format of a 3D stream, from its `/Subtype`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Model3dKind {
|
||||
/// Product Representation Compact (ISO 14739-1).
|
||||
Prc,
|
||||
/// Universal 3D (ECMA-363).
|
||||
U3d,
|
||||
}
|
||||
|
||||
/// A 3D annotation's model: its format and the decoded stream.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Model3d {
|
||||
pub kind: Model3dKind,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// The `/Annots` positions of the 3D annotations on 0-based `page`, in order.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`AnnotError::NoPageTree`] or [`AnnotError::NoSuchPage`].
|
||||
pub fn model_3d_annotations(doc: &Document, page: usize) -> Result<Vec<usize>, AnnotError> {
|
||||
let list = annots(doc, page_ref(doc, page)?);
|
||||
Ok(list.iter().enumerate().filter(|(_, a)| doc.dict(a).is_some_and(|d| d.name(b"Subtype") == Some(b"3D"))).map(|(i, _)| i).collect())
|
||||
}
|
||||
|
||||
/// The model of the 3D annotation at `/Annots` position `index` on 0-based `page`, decoded.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`AnnotError::NoSuchAnnotation`] when there is no annotation there; [`AnnotError::Invalid`]
|
||||
/// when it is not a 3D annotation, has no readable 3D stream, names a format other than PRC or
|
||||
/// U3D, or decodes past [`MAX_MODEL_BYTES`].
|
||||
pub fn model_3d(doc: &Document, page: usize, index: usize) -> Result<Model3d, AnnotError> {
|
||||
let list = annots(doc, page_ref(doc, page)?);
|
||||
let entry = list.get(index).ok_or(AnnotError::NoSuchAnnotation { page, index })?;
|
||||
let annot = doc.dict(entry).ok_or(AnnotError::NoSuchAnnotation { page, index })?;
|
||||
let at = || format!("annotation {} on page {}", index.saturating_add(1), page.saturating_add(1));
|
||||
if annot.name(b"Subtype") != Some(b"3D") {
|
||||
return Err(AnnotError::Invalid(format!("{} is not a 3D annotation", at())));
|
||||
}
|
||||
let stream = stream_of(doc, &annot).ok_or_else(|| AnnotError::Invalid(format!("{} has no 3D stream", at())))?;
|
||||
let Object::Stream(s) = stream.as_ref() else {
|
||||
return Err(AnnotError::Invalid(format!("{} has no 3D stream", at())));
|
||||
};
|
||||
let kind = match s.dict.name(b"Subtype") {
|
||||
Some(b"PRC") => Model3dKind::Prc,
|
||||
Some(b"U3D") => Model3dKind::U3d,
|
||||
other => {
|
||||
let name = other.map_or_else(|| "none".to_owned(), |n| String::from_utf8_lossy(n).into_owned());
|
||||
return Err(AnnotError::Invalid(format!("{}'s 3D stream is of an unknown format ({name})", at())));
|
||||
}
|
||||
};
|
||||
let data = doc
|
||||
.with_direct_parms(s)
|
||||
.decoded_within(MAX_MODEL_BYTES)
|
||||
.map_err(|e| AnnotError::Invalid(format!("{}'s 3D stream could not be decoded: {e}", at())))?;
|
||||
Ok(Model3d { kind, data })
|
||||
}
|
||||
|
||||
/// What an annotation's `/3DD` names: the 3D stream itself, or the `/3D` entry of a 3D reference
|
||||
/// dictionary. A reference is followed once; the caller refuses anything that is not a stream.
|
||||
fn stream_of(doc: &Document, annot: &Dict) -> Option<std::sync::Arc<Object>> {
|
||||
let obj = doc.resolve(annot.get(b"3DD")?);
|
||||
match obj.as_ref() {
|
||||
Object::Dict(d) if d.name(b"Type") == Some(b"3DRef") => Some(doc.resolve(d.get(b"3D")?)),
|
||||
_ => Some(obj),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -0,0 +1,88 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A one-page document whose page has `annots` as its `/Annots`, plus `extra` objects from 4 on.
|
||||
fn doc(annots: &str, extra: &[Vec<u8>]) -> Document {
|
||||
let mut objs: Vec<Vec<u8>> = vec![
|
||||
b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
|
||||
b"<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 612 792] >>".to_vec(),
|
||||
format!("<< /Type /Page /Parent 2 0 R /Annots {annots} >>").into_bytes(),
|
||||
];
|
||||
objs.extend(extra.iter().cloned());
|
||||
let mut out = b"%PDF-1.7\n".to_vec();
|
||||
let mut offsets = Vec::new();
|
||||
for (i, o) in objs.iter().enumerate() {
|
||||
offsets.push(out.len());
|
||||
out.extend_from_slice(format!("{} 0 obj\n", i + 1).as_bytes());
|
||||
out.extend_from_slice(o);
|
||||
out.extend_from_slice(b"\nendobj\n");
|
||||
}
|
||||
let xref = out.len();
|
||||
out.extend_from_slice(format!("xref\n0 {}\n0000000000 65535 f \n", objs.len() + 1).as_bytes());
|
||||
for o in offsets {
|
||||
out.extend_from_slice(format!("{o:010} 00000 n \n").as_bytes());
|
||||
}
|
||||
out.extend_from_slice(format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n", objs.len() + 1).as_bytes());
|
||||
Document::open(Arc::new(out)).expect("opens")
|
||||
}
|
||||
|
||||
fn stream(dict: &str, body: &[u8]) -> Vec<u8> {
|
||||
let mut s = format!("<< {dict} /Length {} >>\nstream\n", body.len()).into_bytes();
|
||||
s.extend_from_slice(body);
|
||||
s.extend_from_slice(b"\nendstream");
|
||||
s
|
||||
}
|
||||
|
||||
fn annot(dd: &str) -> Vec<u8> {
|
||||
format!("<< /Type /Annot /Subtype /3D /Rect [0 0 100 100] /3DD {dd} >>").into_bytes()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_plain_3d_stream_is_read_through_its_filters() {
|
||||
let d = doc("[4 0 R]", &[annot("5 0 R"), stream("/Type /3D /Subtype /PRC /Filter /ASCIIHexDecode", b"505243>")]);
|
||||
assert_eq!(model_3d(&d, 0, 0), Ok(Model3d { kind: Model3dKind::Prc, data: b"PRC".to_vec() }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_3dref_leads_to_the_stream_it_names() {
|
||||
let d = doc("[4 0 R]", &[annot("<< /Type /3DRef /3D 5 0 R >>"), stream("/Type /3D /Subtype /PRC", b"PRC")]);
|
||||
assert_eq!(model_3d(&d, 0, 0).map(|m| m.data), Ok(b"PRC".to_vec()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_u3d_stream_is_read_and_says_so() {
|
||||
let d = doc("[4 0 R]", &[annot("5 0 R"), stream("/Type /3D /Subtype /U3D", b"U3D")]);
|
||||
assert_eq!(model_3d(&d, 0, 0).map(|m| m.kind), Ok(Model3dKind::U3d));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_3d_annotation_without_a_stream_is_invalid() {
|
||||
let d = doc("[<< /Type /Annot /Subtype /3D /Rect [0 0 1 1] >>]", &[]);
|
||||
assert!(matches!(model_3d(&d, 0, 0), Err(AnnotError::Invalid(m)) if m.contains("no 3D stream")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_unknown_format_is_invalid() {
|
||||
let d = doc("[4 0 R]", &[annot("5 0 R"), stream("/Type /3D /Subtype /STL", b"x")]);
|
||||
assert!(matches!(model_3d(&d, 0, 0), Err(AnnotError::Invalid(m)) if m.contains("unknown format (STL)")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_3dref_is_followed_once_and_only_to_a_stream() {
|
||||
// A reference naming another reference, or one that names itself, is damage: no stream.
|
||||
let d = doc("[4 0 R]", &[annot("5 0 R"), b"<< /Type /3DRef /3D 6 0 R >>".to_vec(), b"<< /Type /3DRef /3D 5 0 R >>".to_vec()]);
|
||||
assert!(matches!(model_3d(&d, 0, 0), Err(AnnotError::Invalid(m)) if m.contains("no 3D stream")));
|
||||
// The stream is under `/3D`; a `/3DD` inside the reference is not the standard's key.
|
||||
let d = doc("[4 0 R]", &[annot("<< /Type /3DRef /3DD 5 0 R >>"), stream("/Type /3D /Subtype /PRC", b"PRC")]);
|
||||
assert!(matches!(model_3d(&d, 0, 0), Err(AnnotError::Invalid(m)) if m.contains("no 3D stream")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn another_annotation_is_not_a_3d_one() {
|
||||
let d = doc("[<< /Type /Annot /Subtype /Square /Rect [0 0 1 1] >> 4 0 R]", &[annot("5 0 R"), stream("/Subtype /PRC", b"PRC")]);
|
||||
assert!(matches!(model_3d(&d, 0, 0), Err(AnnotError::Invalid(m)) if m.contains("not a 3D annotation")));
|
||||
assert_eq!(model_3d(&d, 0, 2), Err(AnnotError::NoSuchAnnotation { page: 0, index: 2 }));
|
||||
assert_eq!(model_3d_annotations(&d, 0), Ok(vec![1]));
|
||||
assert_eq!(model_3d_annotations(&d, 1), Err(AnnotError::NoSuchPage(1)));
|
||||
}
|
||||
@@ -2250,3 +2250,162 @@ fn freehand_highlight_is_compatible_ink_with_marker_blending() {
|
||||
assert!((a - b).abs() < 1e-4, "expected [10, 90, 130, 110], got {rect_from_border:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A 3D annotation is PDF 1.6 and a PRC model 1.7 (ISO 32000-2 §13.6): an older document's
|
||||
/// catalog says so, and a newer one is left as it is.
|
||||
#[test]
|
||||
fn a_3d_model_raises_an_older_document_to_its_version() {
|
||||
let catalog_version = |doc: &Document| {
|
||||
let root = doc.root().unwrap();
|
||||
doc.get(root).as_dict().unwrap().name(b"Version").map(<[u8]>::to_vec)
|
||||
};
|
||||
let add = |doc: &mut Document, model: &[u8]| {
|
||||
let new = NewModel3d { page: 0, rect: [100.0, 100.0, 300.0, 300.0], model, activation: Activation3d::default(), poster: None, contents: "" };
|
||||
add_3d_model(doc, &new, &meta("m")).unwrap();
|
||||
};
|
||||
let mut doc = fixture();
|
||||
add(&mut doc, b"U3D\0rest");
|
||||
assert_eq!(catalog_version(&reopen(&doc)), None, "a 1.7 file needs nothing for U3D");
|
||||
for (model, version) in [(&b"U3D\0rest"[..], &b"1.6"[..]), (b"PRC rest", b"1.7")] {
|
||||
let mut doc = fixture();
|
||||
doc.set_version("1.4");
|
||||
add(&mut doc, model);
|
||||
assert_eq!(catalog_version(&reopen(&doc)).as_deref(), Some(version));
|
||||
}
|
||||
let mut doc = fixture();
|
||||
doc.set_version("2.0");
|
||||
add(&mut doc, b"PRC rest");
|
||||
assert_eq!(catalog_version(&reopen(&doc)), None, "a 2.0 file takes PRC as it is");
|
||||
}
|
||||
|
||||
/// Before 2.0, PRC is an Adobe extension to PDF 1.7 (§7.12): a PRC model declares it, keeping
|
||||
/// other extensions and a later ADBE level; U3D and a 2.0 file need nothing.
|
||||
#[test]
|
||||
fn a_prc_model_declares_the_adobe_extension_before_pdf_2() {
|
||||
let adbe = |doc: &Document| {
|
||||
let root = doc.root().unwrap();
|
||||
let ext = doc.get(root).as_dict().unwrap().get(b"Extensions").map(|e| doc.resolve(e).as_dict().unwrap().clone());
|
||||
ext.map(|e| {
|
||||
let adbe = e.get(b"ADBE").map(|a| a.as_dict().unwrap().clone());
|
||||
(e.get(b"XYZ").is_some(), adbe.map(|a| (a.name(b"BaseVersion").map(<[u8]>::to_vec), a.get(b"ExtensionLevel").and_then(Object::as_int))))
|
||||
})
|
||||
};
|
||||
let add = |doc: &mut Document, model: &[u8]| {
|
||||
let new = NewModel3d { page: 0, rect: [100.0, 100.0, 300.0, 300.0], model, activation: Activation3d::default(), poster: None, contents: "" };
|
||||
add_3d_model(doc, &new, &meta("m")).unwrap();
|
||||
};
|
||||
let mut doc = fixture();
|
||||
add(&mut doc, b"PRC rest");
|
||||
assert_eq!(adbe(&reopen(&doc)), Some((false, Some((Some(b"1.7".to_vec()), Some(1))))));
|
||||
let mut doc = fixture();
|
||||
add(&mut doc, b"U3D | ||||