mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
b8ddf71e30959dc8a5ca4b66c40b8fcaea68cf76
21
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
306bbcedf9 |
M3: harden render and CLI failure surfaces (#669)
* M3: harden render and CLI failure surfaces Make content truncation observable, reject unknown CLI options, expose deterministic render work counters, and scope render failures to exact requests. Refuse silently reduced whole-page export rasters while preserving tiled rendering, and add heavy-vector and CLI regression coverage. Co-authored-by: Codex <noreply@openai.com> * Render: remember a failed render by page and kind again A key with scale and tag made every zoom level and tile a new key: a pathological page tied up a worker for the watchdog limit per request (exhausting worker replacements) and was re-parsed each time. The repeated-parsing check asks for a new generation and scale again. * Export: pages too large at the chosen dpi export at the most the renderer allows, and say so #669 made every Exporter refuse oversized whole-page renders, which reached File > Export to image, doc_export_* and OCR: at 600 dpi any page side over 13.65 in (Legal, A3, Tabloid) failed instead of exporting. The exporter clamps again; Exporter::dpi_used reports the resolution a page is drawn at, which the Export dialog's message and doc_export_png's lower_dpi list mention. A source with reject_oversize still refuses (CLI render keeps its strict config). * doc_export_images: describe lower_dpi * Render: ContentTruncated is reported once per page; the hayro-interpret patch is recorded Nested forms, Type 3 glyphs and patterns are interpreted again and each reported the exhausted budget; pdfcraft-render now keeps one. The warning is part of hayro-interpret patch (18) (the page content budget): marked in the code and described in vendor/README.md. * CLI render: print render warnings (skipped content) to stderr * CLI: the option check refuses unknown options only; commands report missing values themselves text's own --page parsing (and its test on main) says what a page value must be; the generic "needs a value" pre-empted it. --------- Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
994b42c15c |
CLI: pdfcraft-cli.exe carries a Windows version resource (#598)
pdfcraft-cli.exe had no VERSIONINFO, so Explorer's Details tab, (Get-Item pdfcraft-cli.exe).VersionInfo and inventory tools showed no version or publisher for it. A build.rs like the app's embeds one with winresource (already a build dependency of pdfcraft): the same product name and copyright, its own description and file names, and the package version. PDFCRAFT_REQUIRE_WINRES=1, which the Windows packaging already sets, makes a missing resource compiler an error; otherwise it's a warning, and other targets are unaffected. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
74e30e5803 |
CLI: pdfcraft-cli ui refuses a control file another user could plant (#597)
* Security: pdfcraft-cli ui refuses a control file another user could plant `pdfcraft-cli ui --control FILE` sent the token and every command, typed text included, to whatever port FILE named, without checking who owned it. The README suggested `/tmp/pc.json`; in a shared /tmp another local user can create that file first, so the CLI talked to their listener. The CLI now refuses a symbolic link and anything but a regular file, a file over 64 KiB, and on Unix a file owned by another user or with any group or other permission bits (the app writes 600). The owner and mode are read from the opened file, and its device and inode must match the path's, so a swap after the check is refused too. std has no geteuid and unsafe is forbidden, so the effective uid is the owner of a file the CLI creates in the temp folder and removes. The README now uses a per-user path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Security: stop the app when --control can't be published A control file that couldn't be written was only logged, and the app ran on with a listener nobody could reach. The failure can mean another user already created a file at a shared path to receive the commands, and a script started with --control would otherwise drive nothing without being told. The app now exits with the error, before any document opens, so no work is lost. The README and the app's usage comment say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * CLI: pdfcraft-cli ui refuses a control-file port above 65535 `ui` truncated the control file's port with `as u16`, so 65536 + N reached port N, and the token and command went to whatever listened there. A port that doesn't fit is now refused, before connecting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * CLI: learn the effective uid from rustix, not a temp-file probe Review on #597: the probe made `pdfcraft-cli ui` depend on a writable temp folder to judge a control file somewhere else, created and removed a file on every call, and could leave one behind. rustix's `geteuid` is safe and can't fail, and rustix is already in Cargo.lock; it is a Unix-only dependency of the CLI, so Windows builds don't compile it. A control file of your own now works with TMPDIR pointing at a folder that doesn't exist; the probe refused it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
+2 |
db1b3d065d |
MCP: common command tools, session resources and client hints (#451)
* XFA: choice lists, signature, password, image and barcode fields; PNG and GIF pictures The xfa crate lays out the field kinds that were left blank or shown as text fields: - Choice lists become combo boxes (editable with textEntry) or list boxes (open="always"; multi-select with open="multiSelect"), with the shown items and the saved values of <items save="1"> as /Opt pairs, /V, /I and /DV as saved values, several on separate lines in the data. Field::saved_value maps shown text to saved values for the layout and the script tree alike, so scripts read what is saved. - Password fields are password text fields; signature fields are /Sig fields the app can sign. - Image fields show the picture the data (base64) or the template holds; barcode fields show their value as text, with a warning that no bars are drawn. - PNG and GIF pictures (fields and draws) are decoded to Flate image XObjects with a soft mask for transparency; JPEGs stay as they were. Sizes come from the headers, so layout never decodes; decoding is capped at 40 million pixels and failures are warnings, not errors. Engine: multi-select list values from scripts and data are one per line. README and parity notes updated (form.xfa-dynamic). Hardened after an adversarial review pass: picture sizes refused from the header past 20 million pixels or 65,535 a side, the GIF decoder given limits (a frame far larger than its screen is refused before allocation), template pictures read whole up to 32 MB, pictures decoded once and remembered in the catalog by content hash so re-layouts reuse them, data pictures that aren't images reported by field; choice values matched against saved values first, several values deduplicated and capped, /I ascending and unique, /DV through the same mapping, <value> children read, the item cap reported; the reopen path takes another viewer's choice values (several per line); CMYK JPEGs inverted only with Adobe APP14. * D5: README for cos and engine, and an honest cos status line Thirteen crates have no README, although the session protocol tells every session to read the README of the crate it is touching. These are the two biggest: cos is where every hostile file lands first, and engine is the whole API surface a frontend gets. Each states its layer and the rules that bind it, sketches the API against the real signatures, and ends with what is deliberately not done. The cos README spells out the clone-is-a-snapshot property and why both writers return bytes rather than taking a sink; the engine README spells out the four-step editing model that makes what you see what Save writes. Also corrects the module doc on cos/src/lib.rs, which still said encryption and object-stream output for full saves were not done. Both shipped: security.rs handles R2-R6 with crypt filters and /EFF, and SaveOptions::object_streams defaults to true. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Preserve alternate appearances and indirect push-button values Keep rollover, down and custom appearance entries when normal appearances are regenerated, without modifying shared dictionaries. Resolve indirect button caption and layout values. Cover full and incremental saved output, and wait for raster readiness in the existing Linux scroll test. Co-authored-by: Codex <codex@openai.com> * M13: common MCP tools through the confined automation path Preserve task tools and compact mode, add complete hints and strict argument-key errors, and catch escaped tool panics. Co-Authored-By: OpenAI Codex <noreply@openai.com> * M13: session resources and version-aware MCP result hints Keep per-document resources and legacy response shapes; add document/command resources and private cache hints for modern clients. Co-Authored-By: OpenAI Codex <noreply@openai.com> * M7: select and edit grouped Form XObject figures Co-Authored-By: Codex <noreply@openai.com> * M14: let neutral Windows locales fall through to display language Co-authored-by: Codex <codex@openai.com> * pdfcraft-cli --help lists `mcp` only when the build has it Follow-up to #540: a build without the `mcp` feature (`--no-default-features`) still listed `pdfcraft-cli mcp` in its usage, and `pdfcraft-cli mcp` then said "unknown command". USAGE is now put together with `concat!`, and its `mcp` lines come from a macro that is empty without the feature. In such a build `pdfcraft-cli mcp` says the MCP server was left out instead of calling it an unknown command. - tests/help.rs: usage_lists_mcp_only_when_built_with_it (runs in both feature sets) and mcp_without_the_feature_says_it_was_left_out (without the feature) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * engine README: the execution plan is local-only, so don't link a path that doesn't exist * Merge land/450 into land/414; XFA pictures are found again by a SHA-256 on their XObject The image cache was a /PCXfaImages map in the catalog, keyed by DefaultHasher (not stable across Rust releases) and read back from the file. Each image XObject now records the SHA-256 of its picture's bytes, and a re-layout finds the XObjects on the pages it replaces by that hash; nothing goes in the catalog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Merge land/414 into land/451; keep main's MCP protocol versions, cap command_batch at 1000 steps The 2026-07-28 revision and its resultType/ttlMs/cacheScope fields are dropped: main doesn't advertise them and they couldn't be checked against the spec. command_batch refuses more than 1000 steps. docs/mcp.md says that unknown argument keys are now refused with -32602 (a behaviour change for clients that sent extra keys). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Yuvraj Singh <singh.yuvraj31@icloud.com> Co-authored-by: Tanish Manku <tmanku.dev@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com> Co-authored-by: Codex <codex@openai.com> Co-authored-by: storybored379 <storybored379@users.noreply.github.com> Co-authored-by: OpenAI Codex <noreply@openai.com> Co-authored-by: Flowershangfromthebranches <152056395+Flowershangfromthebranches@users.noreply.github.com> Co-authored-by: Streetblock <github@streetblock.de> Co-authored-by: lloyd <lloyd@stackworx.io> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
fce60eacb4 |
pdfcraft-cli --help lists mcp only when the build has it (#574)
Follow-up to #540: a build without the `mcp` feature (`--no-default-features`) still listed `pdfcraft-cli mcp` in its usage, and `pdfcraft-cli mcp` then said "unknown command". USAGE is now put together with `concat!`, and its `mcp` lines come from a macro that is empty without the feature. In such a build `pdfcraft-cli mcp` says the MCP server was left out instead of calling it an unknown command. - tests/help.rs: usage_lists_mcp_only_when_built_with_it (runs in both feature sets) and mcp_without_the_feature_says_it_was_left_out (without the feature) Co-authored-by: lloyd <lloyd@stackworx.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
5deb42199c |
Issue #537: pdfcraft-cli --help prints the full usage (#540)
The per-command options lived only in main.rs's doc header, so a packaged binary had no way to show them. They move into a USAGE constant (one copy, no drift) that `--help`, `-h`, `help` and `<command> --help` print to stdout, exiting 0. No command prints the usage to stderr and fails; an unknown command is named and points to --help. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9c63f3e8f2 |
fix(cli): validate page positions and preserve command results (M13) (#391)
Reject invalid page positions before input I/O and keep diagnostic stderr failures from overriding rendering, text errors or MCP startup. Preserve real file and protocol output failures and upstream compact MCP behavior. Co-authored-by: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com> Co-authored-by: Codex <codex@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
868bd727d1 |
M13: pdfcraft-cli mcp --compact lists 12 tools and finds the rest by search (#353)
`tools/list` returns all 132 tools (109,578 bytes, ~27k tokens), which an agent pays on every session. With `--compact` (library: `McpServer::with_compact(true)`) it returns the 10 core tools (`mcp::COMPACT_CORE_TOOLS`) plus `tool_search` (query / category / name with the full schema) and `tool_call` (runs any tool through the same `Automation::call`): 12 tools, 7,697 bytes. `tools/call` by name keeps working for every tool; without the flag nothing changes. Same shape as `lightcraft-cli mcp --compact`; the meta tools are not called `*_command` because "command" already means a registry command here. Both meta tools reject unknown outer keys and report failures as `isError` results; `tool_call` honours `--root` exactly like a direct call. Tests: automation integration tests (compact list, discovery, dispatch, errors, root confinement) and a CLI test that launches the binary with and without `--compact`. Co-authored-by: Oldrich333 <184516902+Oldrich333@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
f70c4caac9 |
Security: create save staging files with create_new (follow-up to #164) (#319)
* Security: create the --root staging file with create_new Follow-up to #164. write_atomic staged every save at the fixed name .<name>.pdfcraft-tmp and wrote it with fs::write, which follows a symbolic link and writes through a hard link. So a link planted there, in a folder someone else can write to, redirected the bytes to any file the link pointed at. The staging file now gets a random 64-bit suffix (std's RandomState, keyed from the operating system's random source) and is opened with OpenOptions::create_new, which fails if anything already has the name (a file, a hard link, a symbolic link even when dangling), on Windows (CREATE_NEW) as on Unix (O_EXCL). A taken name is skipped, never opened; Windows reports a folder at the name as "access denied", which counts as taken when something is there. After 16 taken names the save fails and nothing is overwritten. A failed write or rename removes only the file this save created. At most 128 bytes of the target's name go into the staging name, so it fits every system's name limit. The #164 tests that planted a sentinel at the old fixed name now also compare folder listings, and say that the "is a folder" refusal is what they check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Security: create the desktop app's save staging file with create_new The same fix as the previous commit, for the desktop app. editing::write_atomically, which Save, Save As, Export, Print to PDF, signing and crash recovery all use, staged at .<name>.pdfcraft-<pid>.tmp and opened it with File::create, which follows a symbolic link and writes through a hard link. A process id is easy to guess, so a link planted beside a document in a shared folder could redirect a save into another file. The staging file now gets a random 64-bit suffix and is opened with OpenOptions::create_new; a taken name (on Windows, a folder reports "access denied") is skipped, never opened, and after 16 the save fails without overwriting anything. At most 128 bytes of the document's name go into the staging name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
4dd738b60f |
CLI text tests: each protected-PDF test writes its own files (#267)
text_of_a_protected_document_without_the_password_fails failed on main: both protected tests wrote to-protect.pdf / protected.pdf / protect.json at the same paths, and tests run in parallel. Each now uses its own names. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2627081935 |
Issue #249: pdfcraft-cli split creates --out-dir when it does not exist (#256)
`split --out-dir DIR` wrote each part with `std::fs::write` and never
created DIR, so a folder that did not exist yet failed on the first part
with the part's path and a raw OS error ("The system cannot find the path
specified. (os error 3)") — easy to read as the split itself failing.
The folder is now created with `create_dir_all` once the parts have been
computed, before any is written; a folder that can't be created is
reported as `split: --out-dir DIR: <error>`, naming the folder rather
than the first part.
Tests (apps/pdfcraft-cli/tests/split.rs): a nested folder that does not
exist is created and holds every part listed on stdout; an `--out-dir`
that is an existing file fails with a message naming the option and
leaves the file as it was.
Co-authored-by: wstock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
|
||
|
|
505c4a2fa3 |
Issue #248: pdfcraft-cli render writes the format its --out name says (.png, .jpg, .tif or .pam) (#255)
`render --out p1.png` wrote a netpbm PAM stream (P7) whatever the name, so a `.png` could not be opened by any PNG reader. The CLI already links the engine, whose export module encodes PNG, JPEG and TIFF, so the output format now follows the extension: `.png`, `.jpg`/`.jpeg` (quality 90), `.tif`/`.tiff`, and `.pam` for the raw stream as before. Any other name is refused up front, before the page is rendered, with the accepted list. `export::encode_image` wraps the three encoders behind `ImageFormat` so the CLI and `Exporter::image` share one match. Tests (apps/pdfcraft-cli/tests/render.rs): a `.png` gets the PNG signature and an IHDR with the rendered size; `.JPG` and `.tif` get JPEG/TIFF magic; `.pam` still gets the P7 header and the right number of bytes; `.bmp` fails with the message and writes nothing. Co-authored-by: wstock <1394687+wstock@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
1e54e709b6 |
CLI: text fails when the document cannot be opened (#132) (#233)
`PageRenderer::new` keeps a failed open as `None`, so `page_count()` is 0 and `text` without `--page` iterated nothing, printed nothing and exited 0 on a password-protected file. When the renderer has no pages, ask `inspect` why and fail with its message (the same wording `info` gives); a document that inspects fine but still renders no pages is reported as unparseable. A genuinely empty document still succeeds. Fixes #132 Co-authored-by: wstock <1394687+wstock@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
7bed2d393c |
CLI: edit rejects unknown options instead of writing an unchanged copy (#133) (#227)
A misspelled option such as `--rotat` fell through the argument loop's
`_ => {}`, so `pdfcraft-cli edit` wrote a byte-identical copy of the input
and exited 0: an apparently successful edit that did nothing.
Every argument is now parsed before the file is opened. An unknown `--option`
fails with the list of accepted ones, an option without its value is refused,
and a second positional argument is reported. The documented options behave
as before (`--out`, `--password`, `--full` and the edits), and nothing is
written for a rejected command line.
Co-authored-by: William Stock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
|
||
|
|
932967a30d |
Restore #196, #202, #203 and the macOS release-runner change, reverted by the i18n chain (#229)
* Release CI: macOS job on macos-15-xlarge Custom macOS larger runners in a runner group do not pick up jobs; the built-in M2 Pro label does. Only release.yml uses it, and the actions_macos_xl budget caps the monthly spend. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit |
||
|
|
42ddb2d546 |
XFA: lay dynamic forms out from their template and keep their data in step (#60) (#121)
* M12: lay dynamic XFA forms out from their template (#60) A dynamic XFA form is a PDF shell around an XML template: one placeholder page ("requires Adobe Reader") and /NeedsRendering true. Every viewer without an XFA engine shows the placeholder, and so did PrintCraft. New crate printcraft-xfa (L3, already reserved in the layering table). It reads the XDP packets, parses the template (XFA 3.3: subforms, areas, fields, draws, exclusion groups, page sets, fonts, paragraphs, borders, captions, rich text, inline JPEGs, occurrences, breaks, overflow leaders, column widths), lays it out (positioned, tb, lr-tb, table/row; containers without a width size to their content; flow across content areas and page masters; header rows repeat after a break; hidden takes no space; page number and count fields resolve) and writes real pages and AcroForm widgets into the document: text, date (AFDate format and keystroke actions from the picture clause), check boxes, radio groups, push buttons with reset/print/save/URI actions. Standard-14 fonts stand in for Arial, Courier New and friends; nothing is embedded. The engine lays a dynamic form out when it opens it. The original bytes stay: the layout is one appended revision, the widgets get appearances from the forms crate, and the XFA packets remain so Adobe's viewers keep rendering the form their way. A marker on the AcroForm (/PCXfaLayout) stops a saved form from being laid out twice; each field keeps its SOM path (/PCSom) for the data round trip to come. doc_info, the CLI's info and the notice bar report the layout. Checked on IRCC's IMM 5669 (Schedule A, Designer 6.2, 464 KB template, AES-encrypted, usage rights): four pages and 308 fields in 27 ms, the same page structure as the form, filled through form_fill, saved in place with the original bytes intact and reopened with the values. Not done, and said so in the parity notes: the datasets packet is neither read nor written, so Adobe's viewers don't show values filled in here; no FormCalc or XFA JavaScript (rows don't grow, nothing calculates); choice lists are text fields; signature, image and barcode fields are blank; metrics are approximate and fidelity is eyeballed, not measured. Tests: crates/xfa (parsing, sizing, pagination with repeated headers, PDF writing, hostile input), the engine (open, fill, incremental save, reopen without a second layout), the automation tools (open, info, render, fill, save, reopen) and the UI notice (kittest). * M12: XFA forms read and write their datasets packet; static forms too (#60) With the layout alone, a form filled in PrintCraft opened blank in Adobe Reader, and one filled in Acrobat opened blank here: Adobe's viewers keep the values in the XFA datasets packet, not in the AcroForm fields. The xfa crate's new data module reads the datasets packet (normal binding: a field's node is named after it under its named ancestor subforms, repeated instances as repeated siblings) and writes it back from the fields. Laying a form out now takes each field's value from its data node (dates in ISO form shown in the field's pattern, check boxes and radio groups by their on values) and gives a repeating subform or table row as many instances as the data has, so rows another viewer added appear. Every laid-out field carries its SOM path and its on/off items, so the packet can be rebuilt from the AcroForm at any time. The engine writes the datasets after every form edit (one revision with the field change), and on open applies the datasets to the fields of a static XFA form, or of a dynamic one laid out earlier, when another viewer changed them since. The static-form notice now says the two are kept in step instead of warning that Acrobat may disagree. Checked on IRCC's IMM 5669: fill, save in place (original bytes intact), and the file holds <IMM_5669><page1><familyName>SINGH</familyName>…, the structure Acrobat writes for this form; reopening shows the values. Not done: explicit bind refs, global binding and data descriptions are not followed; no import or export of standalone XML or XDP data files. * M12: XFA layout memoizes container sizes, bounding measurement work (#60) A container without a width measured its content for its width and again for its height at every level, so nested width-less subforms took 2^depth measurements, all synchronously in Session::open. Container sizes are now cached per (node, width, depth), the root and page-area items keep stable addresses instead of being cloned per measurement, and a measurement budget turns anything left into TooLarge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M12: rustfmt the merged XFA notice (#60) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M12: XFA datasets are merged into, not replaced; capped; UTF-16 kept; rewrites recorded (#60) - write_datasets merged nothing: it replaced <xfa:data> with the AcroForm-bound values alone, dropping unbound, namespaced and deep data. It now edits the existing packet text in place: a bound node's text is replaced only when it differs, missing nodes are added below the deepest existing one, and everything else (unbound data, other namespaces, attributes, comments, layout) stays byte for byte. A packet of an XFA array that relies on the envelope's prefixes is read inside a wrapper declaring them. - What can't be written (a node holding structured content, too-deep paths or absurd SOM indices, conflicting fields, an unreadable or non-UTF packet) comes back as DatasetsWrite.warnings; the engine keeps them in Document.xfa_warnings and info.warnings (surviving re-reads), and doc_info reports xfa_warnings. - Building data counted same-name siblings for every one it added (quadratic in an untrusted index) with no total cap. Children are now indexed by name, indices above 10,000 and paths deeper than 64 are refused, and one write creates at most 20,000 nodes. - Packets are decoded exactly (UTF-8, UTF-16 LE/BE with or without BOM) and written back in the encoding they came in; anything else is reported and left untouched instead of being rewritten from a lossy UTF-8 reading. - A static XFA form whose fields are rewritten from the datasets on open now says so in info.warnings, naming the fields. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
41734376be |
i18n: add Brazilian Portuguese (pt-br) UI translation (#165)
* M14: translation catalogs, same system as PhotoCraft Move the interface translations to PhotoCraft's i18n system: one TSV catalog per language (crates/ui-egui/src/i18n/ja.tsv), the tl! macro, entries keyed by command id, context or plural forms, and catalog tests (format, duplicates, placeholders, command ids). The 49 existing Japanese strings move over unchanged, plus "Auto". The language preference now defaults to "auto" and follows the system language (English when it has no catalog); "en" and "ja" still persist and unknown values are rejected. Update docs/localization.md, ROADMAP.md and the misc.prefs-language parity entry. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * i18n: add Brazilian Portuguese (pt-br) UI translation Adds Language::PtBr (code pt-br, endonym Português (Brasil)) with serde rename so it persists as "pt-br", registers it in Language::ALL (Preferences ▸ Interface language and ui.set/ui.state) and translates the same 49 menu/command keys as the Japanese table. Extends the i18n tests (table parity, codes, persistence) and updates docs/localization.md. * M14: strict catalog validation, one-pass fmt, translation assets Fold in the infrastructure parts of #175: - `fmt` fills placeholders in one pass, so inserted values (a file name containing `{n}`) are never substituted again. - Catalog validation rejects unknown escapes, unknown `@` contexts, placeholder, ellipsis and plural-form mismatches and duplicates. The bundled catalogs must have no errors (tests); at run time a bad line is still only skipped and logged, so the rest of the catalog loads. - `.tsv` files count as assets in `cargo xtask assets`, with a `translation` kind that may never come from Adobe; ja.tsv gets its ATTRIBUTION.toml entry. - Control-channel test: switching the language keeps documents, their dirty state and command ids. Also make the drawing language thread-local, as in PhotoCraft, so parallel kittest harnesses can't change each other's language. Co-Authored-By: 才倾梦华V <185041488+CQMHV@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M14: Brazilian Portuguese as a TSV catalog Move #165's Portuguese table into crates/ui-egui/src/i18n/pt-br.tsv (same 49 strings, plus Auto) and register it in LANGUAGES with PhotoCraft's Portuguese plural rule (0 and 1 singular). Any Portuguese locale (pt_BR, pt_PT, pt) follows the Brazilian catalog, as in PhotoCraft. The catalog is an attributed asset (ATTRIBUTION.toml), and the docs and parity notes list the new code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Simon Leblanc <contact@leblanc-simon.eu> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: 才倾梦华V <185041488+CQMHV@users.noreply.github.com> |
||
|
|
1cf0d319e4 |
M13: exit cleanly when stdout pipe closes (#203)
Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
85e8e34547 |
CLI: text exits non-zero when a selected page cannot be read (#131) (#139)
* CLI: text exits non-zero when a selected page cannot be read (#131) printcraft-cli text --page N reported the failure on stderr and still exited 0, so a script checking the status could not tell a failed extraction from a successful one. The failing pages are collected and the command now fails with them named, pinned by a test for the nonexistent-page case and one for a normal extraction. * Move the text exit-code test to the renamed pdfcraft-cli app Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Mohammed Munzir <Mohammed.Munzir@iga.gov.bh> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
70da0179a6 |
Security: --root confinement leaks and escapes (#136, #137) (#164)
* Automation: --root refuses every outside path the same way (#136) With a root set, `resolve` canonicalized a path before checking it against the root, so an existing outside path got "is outside the allowed directory" while a missing one got the OS error ("No such file or directory", "Not a directory", "invalid path"): a confined MCP client or script could probe which files and folders exist anywhere on the disk. Containment is now decided before anything about an outside path is reported: - `..` is resolved by name first (as Windows does), so a path can't pass through an outside folder on its way back in; - on Windows another network share or device namespace (`\\host\share`, `\\?\UNC\...`, `\\.\...`) is refused by name, without contacting it; - the deepest existing ancestor, links followed, must be inside the root before anything is said about the rest; - a broken link is refused the same way, so whether its target exists stays hidden too. Every outside path now gets `<path> is outside the allowed directory <root>`, existing or not, file or folder, read or write. Filesystem errors such as "not found" are still reported for paths inside the root. Behaviour change: on Unix, `..` now means the parent by name, as on Windows (`link/../x` is `x` beside `link`), and a write such as `new/../x.pdf` that stays in the root now succeeds instead of failing with "'..' is not allowed here". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * CLI: script images stay inside --root (#137) `pdfcraft-cli run --script steps.json --root DIR` wrote each step's `"out"` PNG with `std::fs::write`, never through the root check the tools use: an absolute `"out"` created or replaced any file the process could write, and a relative one landed in the working directory instead of the root. The image is now saved through the new `Automation::write_output`, which uses the same resolver and atomic write as a tool's own output: outside paths get the usual "is outside the allowed directory" refusal (the step fails, nothing is written), relative paths resolve inside the root, and without --root the path is written as given, in place as before (so `/dev/stdout` still works). `run <tool> --out` goes the same way. Found while testing this, the same "." problem in two tools: - a write path naming a folder, such as "." (the root itself), staged its temporary file beside that folder, i.e. outside the root for ".", overwriting and then deleting any file there called `.root.pdfcraft-tmp`. `write_atomic` now refuses a folder before writing anything; - `image_save` adds an extension to a path without one, so "." became `root.png` beside the root. It now refuses a folder first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Automation: files named after a document stay in the output folder Found while reviewing the --root fixes for #136 and #137. Folder outputs name their files after the document and joined that name straight onto the resolved folder, without checking the result against the root: - `doc_export_images` and `doc_export_all_images` used the whole document name, so `doc_create` with `name: "../../escape"` followed by `doc_export_images` wrote `escape_page_1.png` outside the root (an absolute name went anywhere); - `page_extract` (separate files), `doc_split`, `ocr_recognize_files` and `action_run` took the last component, but on Windows a name such as `x/C:evil.pdf` gives `C:evil`, and `folder.join("C:evil-page1.pdf")` replaces the folder with a path relative to the current directory on C:. Every such name now goes through `child()`, which turns separators, colons and control characters into `_` (and prefixes a name of only dots), so the file is always a direct child of the folder. `with_root` also refuses a root that is not a folder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d3db99818f |
Rename PrintCraft to PdfCraft (#174)
The app, crates, binaries, bundle id, packaging, icons, screenshots and docs are now PdfCraft / pdfcraft (repo storytold/pdfcraft). On first launch the desktop app moves the PrintCraft settings and crash-recovery folders to the new name, and both apps read settings saved under the old storage key, so upgrades keep recent files, preferences and unsaved work. The signing test fixtures keep their PrintCraft subject names (they are binary). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |