mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-27 11:37:59 -06:00
4b74f655de75af304e7ebb3df6bcda3180c8a543
143
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5ab928d529 |
fix(comments): include replies to replies in the comment summary (#820) (#840)
The comment summary gathered a comment's direct replies only, matching /IRT against the comment itself. A reply to a reply points at the direct reply, so its text never reached the generated summary even though the stored discussion kept it. Replies are now walked to any depth, with a bound, and indented one level per reply. The existing output for direct replies is unchanged. The new test builds a rectangle, a reply and a reply to that reply, then checks the summary carries both texts with the nested one indented under its parent. It omitted the nested reply before the fix. Co-authored-by: Big Pickle <noreply@opencode.ai> |
||
|
|
c7fcee4ed9 |
OCR: a scanned euro sign reads as €, not E (#796)
The ocrs crate's default alphabet has an E where its recognition model was trained with € (ocrs-models DEFAULT_ALPHABET), so a scanned "€250" was recognised as "E250". pdfcraft-ocr now gives ocrs the alphabet the model was trained with. the_alphabet_is_the_trained_one checks the alphabet without the models; recognize_text_reads_the_euro_sign reads a scan end to end when they are installed. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f3f4019fad | Autosave: snapshot a new unsaved document before its first edit (#815) (#850) | ||
|
|
9cd07e4793 |
M8: redact lists of words or phrases (protect.redact-word-lists) (#757)
* M8: redact lists of words or phrases Find text and redact gains "Multiple words or phrases": a list with one word or phrase per line, typed or imported from a .txt file (Import list…), whose every match is marked in one undo step. Lists are trimmed, deduplicated and bounded (1,000 entries of 256 characters; files up to 1 MB are read). redact_mark takes words and reports matched_words, the marks each entry made. protect.redact-word-lists is shipped; whole-word and case-sensitive matching and exporting the list are still missing. * M8: translate word-list labels into Arabic The Arabic catalog arrived on main after this branch, so the new Find text and redact strings were still English and failed arabic_covers_ui_literals. * Tests: drop sharp grid renders when the thumbnail will cover the cell Zooming the organize grid back out left the sharp page raster in place until a thumbnail texture existed, so the zoom test failed when that texture had not arrived yet. * M8: Czech rows for the word-list redaction labels 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> |
||
|
|
aa6242d0c6 |
M4: bookmarks from tagged headings (organize.bookmarks-from-structure) (#754)
* Parity: merge the duplicate form.field-list evidence and notes #649 and #650 each added `evidence` and `notes` to the same entry, so the TOML has duplicate keys and `cargo xtask parity` fails on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M4: bookmarks from tagged headings (organize.bookmarks-from-structure) Bookmark options > New bookmarks from structure (command bookmark.from_structure, tool bookmark_from_structure) turns a tagged document's headings into bookmarks, as Acrobat's New Bookmarks From Structure does for headings: H1-H6 through the role map, a plain H by its Sect/Part/Art nesting, nested by level under a new first "Untitled" bookmark, each linked to its structure element (/SE, ISO 32000-2 §12.3.3). Titles come from /ActualText, the marked text drawn on the page (decoded through the font's ToUnicode or encoding), /Alt or /T. The structure walk is bounded (depth, visits, heading and title caps, seen-set) and survives self-referencing elements; an untagged document gets an actionable error; the edit is one undo step under the assemble permission. Not yet: choosing other element types, and destinations at the heading's position rather than its page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: size the shared raster allowance from thumbnail demand The canvas test planted a 132×171 thumbnail whose tag did not match the request finish_render_frame computes. On FreeBSD that real render finished in the same frame, so the retained raster exceeded 132×171×4. Plant a thumbnail that already satisfies demand and compare against that size. * M4: a heading's text stops gathering once it is far past the title length A heading whose marked-content list repeats one mark up to the visit cap could otherwise collect about a gigabyte before the title is cut to 256 characters. 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> |
||
|
|
49a8d04f24 |
M9: sign and timestamp encrypted documents (#753)
* M9: sign and timestamp encrypted documents Signing a password-protected PDF failed with "not supported: signing encrypted documents". The writer encrypted every string, including the signature's /Contents placeholder, which the signer patches in place after hashing. - cos: a signature dictionary's /Contents is never encrypted or decrypted (ISO 32000-2 §7.6.2), identified by /Type /Sig|/DocTimeStamp or by /Contents + /ByteRange. Existing signatures in encrypted files now read their real CMS and survive rewrites of their dictionary. - sign: the blanket refusal is replaced by the document's permissions (Table 22) when opened with the user password: existing fields and document timestamps need fill-forms, new fields need annotate + modify, certifying needs modify. Refusals name the permissions password. - Tests for all four algorithms, counter-signing, tampering, permissions, document timestamps and the sign_document tool; parity entry sign.encrypted-documents; ROADMAP log. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Hadi Chokr <hadichokr@icloud.com> * M9: signing an encrypted document waits for a pending security change to be saved Otherwise the incremental save becomes a full rewrite that applies or removes protection while signing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Signed-off-by: Hadi Chokr <hadichokr@icloud.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
e4e39f4bef |
M4: keep the render pool across metadata edits (#750)
* M4: keep the render pool across metadata edits Metadata edits (SetInfo) no longer rebuild the render pool or the display bytes. The engine refreshes the document-information fields, the file size, the signatures (when present) and the working bytes, and keeps the renderer. Undo and redo of metadata edits reuse the same pool. Comment and form edits still rebuild the renderer and their view data. Tests: - metadata_edits_and_history_reuse_the_render_pool - field_and_comment_edits_still_rebuild_the_render_pool - metadata_edit_keeps_the_renderer_without_changing_page_content (the metadata invalidation audit; its assertions now expect the kept renderer) Supersedes #711 and #712. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * M4: a metadata edit reads the other Info entries as the full inspection does Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
1c33e6f851 |
M14: Arabic interface language (#692)
* M14: Arabic interface language A 2,136-entry Arabic catalog (six CLDR plural forms, Arabic month and weekday names), selected by `ar` and every `ar-*` locale. egui joins Arabic letters but places words left to right, so the catalog is put into display order when it loads (bidi::display_rtl); placeholders, keyboard shortcuts and caption colons stay in place. The command search matches a multi-word Arabic query word by word. The craft-fonts pin moves to the revision with Noto Sans Arabic. crates/fonts/build.rs now refuses to embed Adobe type designs, so the Noto Sans CJK SC in that revision is never embedded (AGENTS.md §1.1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M14: Arabic catalog follows the {key} shortcut labels and has Redaction code 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> |
||
|
|
ebc5ccd4c0 |
M14: View menu, tooltips and Keyboard shortcuts show Ctrl, not ⌘, off macOS (#695)
View ▸ Zoom and Page navigation were hand-written with "⌘1", "⌘[" … while
the registered items below them were formatted by Shortcut::label, so
Linux showed both ⌘1 and Ctrl+K in one menu. Ten tooltips and the view-key
rows of Help ▸ Keyboard shortcuts hard-coded ⌘ the same way.
- The keys the document view handles itself are Shortcut constants in
ui-egui commands.rs, written with the same Shortcut::label.
- Labels follow egui's detected OS (ctx.os()) instead of cfg!, so the web
build in a Linux or Windows browser shows Ctrl too, and tests can check
both platforms.
- Translated tooltips take a {key} placeholder (13 catalogs; only the
shortcut token changed, hashes updated in ATTRIBUTION.toml).
- Fit visible is listed once in View (in Zoom), not also in the generated
list; Ctrl+3 and the palette still run it.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
|
||
|
|
bfa65a9bfc |
M8: redaction code sets (U.S. FOIA, Privacy Act) as overlay text (#741)
* M8: redaction code sets (U.S. FOIA, Privacy Act) as overlay text Redaction properties offers "Custom text" or "Redaction code" under "Use overlay text". Picking the U.S. FOIA (5 U.S.C. 552(b)) or U.S. Privacy Act (552a) set and ticking codes makes new marks carry them as overlay text, in the set's order. redact_mark takes code_set and codes and rejects unknown sets or codes with the allowed list. Also merges a duplicated evidence/notes pair in form.field-list that made parity/acrobat-features.toml fail to parse on main. * M8: say why the default redaction code set can't fail Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: pin the clock in form_bulk (saves a second apart differed in /ModDate on Windows CI) --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3d45b598ee | test(engine): audit edit history side effects (#713) | ||
|
|
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> |
||
|
|
a99b39396f |
M6: bulk form-field properties and selection (#95) (#650)
* M6: bulk-edit shared form field properties (#95) Add opt-in shared properties, partial appearance patches and atomic CLI/MCP batches. Complete current-page, rectangle and Fields-panel selection, with 20 synthetic regressions for preservation, rollback and dialog lifecycle. Co-Authored-By: Codex <noreply@openai.com> * Bulk field properties: translated messages, a catalog check, and in-place /DA patches - The bulk dialog's flag label, errors and notices are in all 13 full catalogs; the missing-field error is a template (fmt + tl!) instead of format!. New test bulk_field_properties_labels_are_in_every_full_catalog. - patch_da rewrites the last Tf and fill colour in place (appending only when there is none), so /DA doesn't grow per edit; sizes clamp to 300 like parse_da. - A size- or font-only edit leaves widgets untouched; an indirect /MK or /BS is updated where it lives instead of inlined into each widget. - Draft::new says why: a widgetless field gets its own message, not 'could not be found'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: pin the session clock in the bulk-fields harness (save_bytes stamps /ModDate) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Tevinhead <173904641+Tevinhead@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a2f0fb7363 |
Forms: fields listed only as page widgets join the form (#649)
A PDF whose AcroForm /Fields array is empty or missing listed no fields, so none were fillable even though every widget sat on a page with its name and value: Acrobat and the browsers fill those. The page widgets are now adopted as fields — after the tree-listed ones, through each widget's topmost unlisted /Parent so a field split across several widgets stays one — and the leniency is recorded, never silent. - forms: fields() walks /Fields as before, then adopts the unlisted page widgets; adopted_page_fields() counts them for the note. - render: the inspector's field list adopts them the same way, so the Fields panel, the field-count notice and menu gating follow. - engine: opening such a file notes "the form's /Fields list omits N fields; they were read from the page annotations" in the repair log (Document Properties ▸ Advanced). Co-authored-by: GLM-5.3 <noreply@z.ai> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
b8b6fb2f45 |
Never crash & persist: Make Current Properties Default and page-display choices survive a restart (#340, #266) (#365)
* Never crash & persist: Make Current Properties Default and page display choices survive a restart #340: - Expose per-tool comment styles via CommentPrefs::styles(). - persist() writes a `comment_styles` array (tool, color, opacity, width, fill). - restore() reads each entry through the clamping setters; a new rgb_of helper at the read boundary refuses malformed colors whole (string, wrong length, null, non-finite) and clamps finite out-of-range values into 0..=1, so a hostile settings file can't smuggle a NaN or saturated color. #266: - The page display chosen in a document (view.layout.*, view.fit_one_page, view.fit_width_scrolling) now mirrors into view_defaults, which the existing default-layout/default-zoom keys already persist. New documents and the next launch open in the persisted display; transient zooms do not rewrite the default (matches Acrobat). Tests: - make_current_properties_default: persist + fresh app reuses the style. - the_page_display_you_choose_becomes_the_default: choice mirrors, persists, new document opens in the persisted display; Acrobat fit modes persist too. - comment_style_settings_clamp_and_refuse_hostile_values: hostile JSON is clamped or refused. cargo fmt --check and cargo clippy -p pdfcraft-ui-egui --all-targets are clean. * Fix clippy: chunks_exact(4) -> as_chunks::<4>() in signature_image.rs * 365: keep the comment-style persistence (#340) only; a saved no-fill clears a default fill The #266 half (every View choice rewriting the default page display) is dropped: Preferences already sets that default explicitly. rgb_triple moves out of the module declarations with one doc comment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fill & Sign dates: Italian names, since Italian is now an interface language (#494) date_languages_are_the_interface_languages requires every interface language to have date names; the unknown-language example in the session test moves from it to xx. --------- Co-authored-by: Mawfyy <mawfyy@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6e13a5fc54 | Fill & Sign: remove white background from imported signature images (#637) | ||
|
|
4fc99e4845 |
Tab strip: shrink tabs before scrolling, keep Open in sight; ignore about:blank titles (#612)
* M3: tabs shrink to fit before they scroll, and Open stays in sight With more documents open than fit, the tabs kept their full width: they ran under the buttons on the right (#416 made them scroll) and the Open button scrolled away with them. Now the tabs shrink first, as browsers do: narrow tabs keep their width, wider ones share the rest equally and end their names in "…" (the tooltip has the whole name). Only once they are at their narrowest does the strip scroll. Open sits after the tabs, outside the scrolling area, so it is always in sight next to the controls on the right. Refs #397. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M3: a placeholder title like about:blank leaves the file name on the tab A browser's "Save as PDF" of a blank page or pop-up titles the document "about:blank" and asks viewers to show the title, so its tab and window said "about:blank". A one-word browser address (about:, blob:, data:) or a bare "Untitled" now counts as no title, and the file name is shown. A tab that does show a document title names its file on hover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M3: arrows step through tabs that don't fit, and tabs keep most of their name Tabs shrank to 110 points before the strip scrolled, so with many open they became a few characters each and nothing showed that more were hidden. Tabs now stop at 180 points (most of a name), and once even that doesn't fit, arrows appear either side of them: each steps about one tab, and an arrow is dimmed at its end, so it is clear whether more tabs lie that way. The mouse wheel still scrolls them, and the active tab is still brought into view. New labels "Scroll tabs left" / "Scroll tabs right" are translated in every catalog (machine-written; native review welcome). Refs #397. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: William Max & Co. <info@williammax.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
e7a15783f3 |
Export to Word/HTML/RTF: read text and images drawn by form XObjects (fixes #314) (#406)
* Export to Word/HTML/RTF: read text and images drawn by form XObjects (fixes #314) The office export built its paragraphs from text_blocks and its images from page_images. Both read only the page's own content streams (they back Edit text and Edit image, which rewrite those streams), so a page drawn through `/Fm1 Do`, as many print-to-PDF tools emit, exported as an empty document. Add read-only reading_blocks and reading_images to pdfcraft-edit. They follow Do into form XObjects (nested ones too), apply each form's /Matrix, use its own /Resources (or the caller's when it has none), and start it from the graphics state at the Do. Depth is capped at MAX_FORM_DEPTH and a form already being read is skipped, so self-referencing and mutually recursive forms are read once. export_pages uses them; editing is unchanged and still offers only the page's own text and images. * Export: a page's walk through form XObjects has a total visit budget, so forms that fan out can't hang it * Export: keep the image walk within clippy's argument limit --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
1ba8b194b2 |
XFA: choice lists, signature, password, image and barcode fields; PNG and GIF pictures (#414)
* 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> * 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> --------- 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: Flowershangfromthebranches <152056395+Flowershangfromthebranches@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Streetblock <github@streetblock.de> Co-authored-by: lloyd <lloyd@stackworx.io> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
37076662a7 |
M7: select and edit grouped PDF figures (Form XObjects) (#450)
* 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> * 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 --------- 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: Codex <noreply@openai.com> Co-authored-by: Streetblock <github@streetblock.de> Co-authored-by: lloyd <lloyd@stackworx.io> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
d592dbb9c4 |
Fill & Sign: date format and date language in Preferences (refs #158) (#505)
* M5.7: date format and date language for Fill & Sign in the engine * M5: flatten Fill & Sign marks when saving Saving can now bake typed text, marks and signatures into the page, from Preferences or doc_save. The option defaults off, so a normal save still leaves them editable, and other comments and form fields stay. * M4: zoom the Organize page grid The page grid draws its pages at 50% to 300%: toolbar buttons with a percentage that resets to 100%, trackpad pinch or Ctrl/⌘ + wheel about the page under the pointer, and ⌘+ / ⌘− / ⌘0 (which no longer change the hidden page view's zoom while the grid shows). Thumbnails are re-rendered sharper in a few steps as the grid grows, capped so one document's thumbnails stay within 256 MB. The page under the pointer (or the first one in view) stays where it is across a zoom. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M4: zoomed grid pages are rendered at the size drawn A zoomed page grid stretched 132 pt thumbnails, and the stepped detail that was meant to help was capped by a memory budget, so a long document stayed blurry at any zoom. Pages drawn larger than their thumbnails now get a render at exactly the size drawn while they are in view; the thumbnail stands in until it arrives. Only pages in view keep one, so memory no longer depends on the page count, and thumbnails go back to their one fixed size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M5.7: fill_sign_date_format tool; format and language on fill_sign_add * M5.7: Preferences ▸ Date format for Fill & Sign dates (refs #158) * M5.7: refuse Fill & Sign dates the PDF text font can't hold * M5.7: Spanish labels for Preferences ▸ Date format * View: the middle button pans instead of drawing with markup tools egui's drag responses accept every pointer button, so on Windows and macOS a middle-button drag reached the page tools: with Draw (ink) selected it drew a stroke, and other tools cropped, selected or moved. Linux was unaffected because its middle-click auto-scroll already blocks page input. Outside Linux a middle-button drag that starts in the viewport (document or Organize Pages) now pans 1:1 while the button is held, with a Grabbing cursor, and page input is blocked from the press through the release. Linux keeps its latched auto-scroll. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M3: a short page gone to from the Pages panel stays current The current page was whichever page showed most of itself. Going to a short page (a cheque) above a tall one scrolls it to the top, where the tall page below shows more, so the current page and the Pages panel's highlight moved to the tall page instead of the one clicked. The page navigated to now stays current while it is wholly on screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M4: page operations from the Pages panel: context menu and drag to reorder Right-clicking a thumbnail offers Extract pages, Cut, Copy and Paste after, on the selection or on the page clicked (which becomes the selection), as the organize grid does. Dragging thumbnails moves the selection, or the page grabbed, to the gap it is dropped on: a bar marks the gap, the page count follows the pointer, and the panel scrolls near its edges. The move is one undoable step; the moved pages stay selected and the current page follows its page. Documents that forbid assembly keep click-only thumbnails. Parity: view.thumbnail-context-ops is shipped, with what it still lacks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pages panel: only the primary button drags thumbnails; a drag left over after the panel closed or the document changed is dropped * Fill & Sign dates: German, Bulgarian, Hungarian and Ukrainian month and weekday names, matching the interface languages --------- Co-authored-by: Mark Bartalos <bartalos86@gmail.com> Co-authored-by: samehelhawary <basio20044@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cody Vogt <Cody.Vogt@caltechsurveys.com> Co-authored-by: William Max & Co. <info@williammax.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
8e3782d5ca |
Flatten Fill & Sign marks when saving (#464)
* M5: flatten Fill & Sign marks when saving Saving can now bake typed text, marks and signatures into the page, from Preferences or doc_save. The option defaults off, so a normal save still leaves them editable, and other comments and form fields stay. * M4: zoom the Organize page grid The page grid draws its pages at 50% to 300%: toolbar buttons with a percentage that resets to 100%, trackpad pinch or Ctrl/⌘ + wheel about the page under the pointer, and ⌘+ / ⌘− / ⌘0 (which no longer change the hidden page view's zoom while the grid shows). Thumbnails are re-rendered sharper in a few steps as the grid grows, capped so one document's thumbnails stay within 256 MB. The page under the pointer (or the first one in view) stays where it is across a zoom. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M4: zoomed grid pages are rendered at the size drawn A zoomed page grid stretched 132 pt thumbnails, and the stepped detail that was meant to help was capped by a memory budget, so a long document stayed blurry at any zoom. Pages drawn larger than their thumbnails now get a render at exactly the size drawn while they are in view; the thumbnail stands in until it arrives. Only pages in view keep one, so memory no longer depends on the page count, and thumbnails go back to their one fixed size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * View: the middle button pans instead of drawing with markup tools egui's drag responses accept every pointer button, so on Windows and macOS a middle-button drag reached the page tools: with Draw (ink) selected it drew a stroke, and other tools cropped, selected or moved. Linux was unaffected because its middle-click auto-scroll already blocks page input. Outside Linux a middle-button drag that starts in the viewport (document or Organize Pages) now pans 1:1 while the button is held, with a Grabbing cursor, and page input is blocked from the press through the release. Linux keeps its latched auto-scroll. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M3: a short page gone to from the Pages panel stays current The current page was whichever page showed most of itself. Going to a short page (a cheque) above a tall one scrolls it to the top, where the tall page below shows more, so the current page and the Pages panel's highlight moved to the tall page instead of the one clicked. The page navigated to now stays current while it is wholly on screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M4: page operations from the Pages panel: context menu and drag to reorder Right-clicking a thumbnail offers Extract pages, Cut, Copy and Paste after, on the selection or on the page clicked (which becomes the selection), as the organize grid does. Dragging thumbnails moves the selection, or the page grabbed, to the gap it is dropped on: a bar marks the gap, the page count follows the pointer, and the panel scrolls near its edges. The move is one undoable step; the moved pages stay selected and the current page follows its page. Documents that forbid assembly keep click-only thumbnails. Parity: view.thumbnail-context-ops is shipped, with what it still lacks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pages panel: only the primary button drags thumbnails; a drag left over after the panel closed or the document changed is dropped * M5: translate Fill & Sign flatten labels into Ukrainian The coverage test failed because the new preference strings had no Ukrainian entries. * Fix main CI: move the rotated-field render test out of pdfcraft-forms (#591) #516 added pdfcraft-render as a dev-dependency of pdfcraft-forms for one rendering test. Both crates are layer 3, so `cargo xtask layers` (run in CI) rejects the sideways edge and main went red. The test now lives in crates/engine/tests/form_rotation.rs, where both crates are already dependencies, and forms drops the dev-dependency. The parity evidence points at the new location. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * uk: the author's wording for the flatten setting --------- Co-authored-by: samehelhawary <basio20044@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cody Vogt <Cody.Vogt@caltechsurveys.com> Co-authored-by: William Max & Co. <info@williammax.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
f1af1e2e79 |
Issue #125: page text the standard fonts can't draw is refused, not written as ? (#510)
Add text, headers/footers and watermarks wrote every character outside
WinAnsiEncoding as `?` and reported success. They now refuse it with a
specific error ("the standard fonts can't draw "中" (U+4E2D) ...") before
changing anything, as the maintainer asked on #327. Form fields and
text-box comments are unchanged (they keep the value).
- fonts: win_ansi_encodable / first_non_win_ansi, defined through
win_ansi itself so a fuller table widens them too; the edited-text
Type 3 decision uses the same check (same character set as before).
- edit: one drawable() check, fed the lines exactly as each tool draws
them, so a CR that would become `?` is refused and CRLF in headers is not.
- ui: the Add-text editor keeps such text open with an inline warning.
Done, a click elsewhere or a double-click on another item don't take
or replace it; it counts as unsaved work; Save stops and shows it; a
deleted page takes its draft with it.
- Arabic (#403): one public check, first_undrawable, follows the drawing:
Arabic shaped with the craft-fonts face is drawable, and only what's left
to the standard font counts. Without the face, #403 still decides for the
Arabic letters (new items refused with what's missing, existing ones stay
movable), but nothing else in the item may become `?`. The editor uses
the same check, cached per draft.
- #403's odd_arabic_text_never_panics: its U+2029/U+0085 input, which was
drawn as `?`, now expects a refusal naming the character.
- The warning is translated (fr, ru, zh-hans, zh-hant, es, ja, bg, de); the
catalogs' SHA-256 entries in ATTRIBUTION.toml are updated.
Claude-Session: https://claude.ai/code/session_01VNJqS2dEQawHJn1qTRJiKp
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
|
||
|
|
3af93c7585 |
M5: draw all ten PDF line-ending styles (#520)
* M5: draw all ten PDF line-ending styles Imported lines, polylines and callouts kept Square, Circle, Diamond, Butt, Slash and reverse arrows in /LE but would not redraw them. Those styles now draw, and comment_add and Comment properties can set them. * hu, uk: Line ending --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
5ddf33ccf5 |
Fix #299: typed and drawn signatures stay upright on rotated pages (#562)
On a page with /Rotate 90, 180 or 270, Fill & Sign placed typed and drawn signatures and initials sideways or upside down: their box and their strokes/outlines were laid out along user-space axes, which the page turns, while the click (or fill_sign_add's "at") is a displayed point. Only image signatures were counter-rotated (#485). Both now use the same displayed axes as image signatures (pdfcraft_model::view_matrix_for without its offset): the box's left edge and vertical centre are at the displayed point, a drawn signature's ink points are mapped through the displayed right/up vectors, and a typed signature's normalised outlines are turned back inside a user-space box laid out along the turned page (the shared signature_image::upright_box, now also used by SignatureImage::rect). The appearance is plain geometry, so every viewer shows it upright without relying on a form /Matrix. Unturned pages are unchanged (the mapping is the identity there). typed_signature_shape and fill_sign::{signature_at, typed_signature_at} take the page rotation; fill_sign::place and fill_sign_add pass it. Tests: a kittest-file test places a drawn, typed signature and typed initials on /Rotate 0/90/180/270 pages through fill_sign::place, renders them and checks each displayed ink box is wider than tall, anchored at the point, identical to the unturned page, and that an asymmetric drawn stroke is not mirrored; an automation test does the same for fill_sign_add typed signatures and initials, before and after save/reopen. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
28804d55d7 | Issue #430: clear the recent-files list, or remove one file from it (#557) | ||
|
|
2aca0692fc |
Fix #526: keep text colour in Word, HTML and RTF export (#547)
The export model (`pdfcraft_export::Block`/`Cell`) had no colour, so `export_pages` dropped the fill colour that `edit::TextBlock` already carries and every run came out black. Block and Cell now carry an sRGB colour, mapped from TextBlock.color. Word runs get `<w:color w:val="RRGGBB"/>`, HTML paragraphs, headings and cells a `style="color:#RRGGBB"`, and RTF a colour table with `\cfN`. Black text keeps each format's default; NaN and out-of-range components clamp. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
32fbef684a |
M5.7: keep natural image stamps upright on rotated pages (#473)
Refs #434 (partial). Preserve displayed bounds and standard appearance orientation through stamp edits; image signatures and explicit rectangles remain outside this fix. Assisted-by: OpenAI GPT-6.1 (gpt-6.1-sol) Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
5c5f4d8b49 |
Phone photos follow EXIF orientation; image signatures stay upright on rotated pages (#434) (#485)
* Stamps and added images follow a JPEG's EXIF orientation (part of #434) Phones store a portrait photo as landscape pixels plus an EXIF Orientation tag. A JPEG went into the PDF as it was (DCTDecode), and PDF image XObjects have no such tag, so a photo used as a custom stamp, an added or replaced image, a watermark or background, or a button image showed up turned 90 degrees. Before these images are embedded, a JPEG whose Orientation is not 1 is decoded, turned the way the tag says and re-encoded (quality 92). Its JFIF resolution is kept, swapped for quarter turns, so the natural size on the page stays right. Other images, JPEGs without the tag, JPEGs that fail to decode and photos over 64 megapixels are embedded unchanged, as before. Fill & Sign image signatures already applied the tag. * Image signatures stay upright and anchored on rotated pages (part of #434) On a page with /Rotate 90, 180 or 270 an image signature turned with the page, because its picture was drawn along user-space axes while the click or the "at" argument is a displayed point. It was also anchored at the wrong point: its left edge and vertical centre were taken in user space, so on a 90 degree page the image centre ended up elsewhere. The signature's appearance now carries the counter-rotation as its form /Matrix, which viewers fit to /Rect, so the picture reads upright as displayed. The page's rotation is kept in /PCPictureRotate, so restyling regenerates the same matrix and moving and resizing keep it. The box of the signature runs along the displayed axes, so its left edge and vertical centre are where they were asked for. The page view matrix in pdfcraft-model is split out as view_matrix_for. The Fill & Sign preview and the live move/resize preview draw the picture the way the appearance does, and the resize handles keep the image's proportions as displayed. comment_image_preview reports image_rotation, how far the image is shown turned (0 for one placed upright). Custom image stamps are left to #473. * Use is_multiple_of for the quarter-turn checks (clippy) (#434) * Probe the pointer preview away from egui_kittest's drawn cursor (#434) The kittest render paints a mouse cursor (a 16 px triangle with a black outline) at the hover point, so the 2 pt probe beside the pointer landed on its outline when zoom and view rotation made 2 pt only a pixel or two. Put the stroke's block at the picture's right end and probe there and at its far margin instead. |
||
|
|
0afe0b7094 |
Keyboard shortcut to enter page number to jump to (#393)
* added view.read_mode command * added view.focus_page_input * Go to page…: Ctrl+Shift+N / ⇧⌘N (Acrobat's), no indexing, rustfmt, catalog entries Shift+Y alone (the first proposal) would have typed a capital Y in text fields on Windows and Linux. 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> |
||
|
|
31f3631868 |
Home: pin folders and list their newest PDFs (#383)
* Home: pin folders and list their newest PDFs
File > Pin folder to Home... (or "Pin a folder..." on Home) pins up to 8
folders. Each lists its newest PDFs with how long ago and size, opens one
with a click, and can be unpinned. Folders are read on a worker thread and
listed again every few seconds while Home shows, so new scans appear by
themselves; an unreadable folder (moved, offline synced drive) stays pinned
and says so. A folder is described by where it is ("iCloud Drive",
"OneDrive - Contoso", "Documents > Work") instead of its full path.
Cloud drives work through their local synced folder; nothing goes through a
provider API. Settings keep absolute paths only, deduplicated and capped.
Tests: crates/ui-egui/tests/folders.rs (8) and folders_ui unit tests (4).
Command label translated in every catalog; Home strings in fr, es, ja,
zh-hans and zh-hant; catalog hashes updated in ATTRIBUTION.toml.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add my contributor name (amitabhthakur)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Home pinned folders: Russian strings; regenerate ATTRIBUTION.md
The Russian catalog (#375) landed while this branch was open: its coverage
tests need the new command label and Home strings. ATTRIBUTION.md is
regenerated (cargo xtask assets --write), which also adds the ru.tsv row
that #375 left out.
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>
|
||
|
|
07b95409bf |
Comments without an appearance stream are drawn (display only, never saved) (#371)
* M5: comments without an appearance stream are drawn (display only, never saved) #260 (page 7): a FreeText box with /DA and an orange /C background, a Text note and an Ink stroke, all without /AP, were not drawn; Acrobat, MuPDF, PDFium and Poppler draw them. The renderer only draws /AP (plus the Highlight fallback of #274), and pdfcraft-annot, which already draws all of these for PdfCraft's own comments, can't be used from the renderer (both are L3). pdfcraft_annot::with_missing_appearances returns a copy of the document in which every annotation without /AP /N that appearance::build can draw (FreeText, Ink, Text, FileAttachment, stamps, shapes, lines, markup, …) has the appearance build draws from its dictionary; links, form fields and pop-ups are left alone, as is anything build declines. The engine keeps that copy, written as an in-memory incremental update, in a new Document::display: the page view's render pool, page_render and page images (export_source) read it. Document::bytes, what Save, signatures and the inspector use, is unchanged. display is rebuilt wherever the renderer was (open, refresh, scoped refresh) and is bytes itself when nothing is missing or the document can't be edited. Tests: annot tests::comments_without_appearances_get_one_for_display_only (FreeText, Ink and Text get /AP /N in the copy, an existing /AP is kept, Link, Widget and an unknown subtype aren't touched, the original is untouched, none means no copy) and engine tests::comments_without_appearances_are_drawn_but_not_saved (the page view draws the FreeText background and the Ink stroke, before and after an edit; the working bytes stay the opened file; Save writes no /AP). On main the page view shows white where the FreeText box is. Parity: view.render-annotation-appearances cites the test and notes what's left (Link borders without /AP, printing). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test the Ink stroke on its apex row: Ink is now a curve through its points (#374) 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> |
||
|
|
2a35a1a76f |
M11: Optimize PDF and Reduce File Size run in the background with a progress bar (#370)
* M11: Optimize PDF and Reduce File Size run in the background with a progress bar
Advanced optimization and Reduce File Size did all their work on the UI
thread, so on a large file the window stopped repainting until they were
done and looked as if it had crashed.
- pdfcraft-optimize: optimize_with_progress reports each image and the
clean-up; returning false stops with OptimizeError::Cancelled.
- engine: Session::optimize_job captures a copy of the document and the
choices; OptimizeJob::run reports each OptimizeStage (removing user data,
images, cleaning up, merging, writing), stops on request with
EditError::Cancelled and turns a panic into an error so a worker always
reports back. optimized_bytes is the job run in place.
- ui: both commands run the job on a worker thread (inline in tests and on
the web), like OCR and Export. A card at the bottom centre shows the
stage ("Optimizing... image 14 of 40"), a bar that eases towards the
fraction with a light sweeping across it, the percentage and Cancel;
notices stack above it. A second run is refused while one is going. The
control channel's state includes the progress.
The headless doc_optimize and doc_reduce tools stay synchronous. New
strings are translated for es, fr, ja, zh-hans, zh-hant.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Clippy: as_chunks in the signature image background test (follow-up to #355)
#360 switched the image test to as_chunks; #355 added another chunks_exact(4)
in the background test, which clippy's chunks_exact_to_as_chunks rejects.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Optimize: report a failed worker spawn instead of waiting forever
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>
|
||
|
|
f88a59f762 |
M10.2: Create PDF from multiple files, edited as pages in the Organize grid (#361)
* M10.2: create PDF from multiple files Create a PDF ▸ Multiple files takes PDFs, images and .txt files, lets the user reorder them, and makes one combined PDF (a bookmark and an optional page range per file) or one PDF per file in a chosen folder, never overwriting what is there. - create: source_kind() tells PDFs, images and text apart (moved from the shell) - engine: Session::convert_to_pdf, shared by Open, the dialog and the tool - automation: doc_create_multiple (combine or separate) - ui: Multiple files dialog reusing the Combine files list; translations - parity entry and ROADMAP updated; catalog hashes refreshed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M10.2: multiple files open in the page grid; insert files at any gap Create a PDF ▸ Multiple files now opens the picked files as one unsaved document in the Organize page grid instead of a file-list dialog: pages are removed and reordered there, and Save pages writes what is shown. - grid: a "+" at every gap inserts files right there; Save pages button - insert from file: several files in order, images and text converted, inserted pages selected - the file-list dialog is removed; one PDF per file stays in the doc_create_multiple tool - translations, catalog hashes, parity notes and ROADMAP updated Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M4: insert files by dropping them on the page grid On desktop, PDFs, images and .txt files dropped on the Organize page grid are inserted at the gap under the pointer, in order, and selected; a dropped file still opens as a document everywhere else. winit does not report the pointer while files are dragged over the window on every platform, so a drop waits (up to a second) for the pointer to show up and otherwise appends at the end. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M4: files dragged over the page grid follow the pointer (macOS) winit sends no pointer moves while files are dragged over the window, so egui kept the place the pointer entered at: the insertion bar froze there and the files were inserted there. During a file drag the pointer is now read from AppKit (safe objc2 calls, crates already in the tree) and fed to egui, so the bar and a "+" badge follow the pointer and the files go where they are released. 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> |
||
|
|
acb8e96aff |
Update image signatures live during resize and movement (#355)
* M5.7: update image signatures live during resize and movement * M5.7: preserve image signature proportions when resizing corners --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
b5edb59d84 |
Clippy: as_chunks in the signature image test (follow-up to #335) (#360)
Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com> |
||
|
|
dbe4b2dddb |
Issue #290: File ▸ Open Recent lists the recent files (#323)
* Issue #290: File ▸ Open Recent lists the recent files The Home view kept a recent-files list but the File menu had no way to reach it; Acrobat shows File ▸ Open Recent and the request asked for exactly the menu wiring. A new command file.open_recent renders as a submenu of the live list right under Open… (disabled while the list is empty); each entry opens its file or focuses the tab already showing it, which is now shared with the Home view's list through the new open_recent() instead of a second copy of that check. The command palette runs it as "open the most recent file", or says there are none. Translations added to every catalog (Japanese, Czech, Spanish, both Chinese and Brazilian Portuguese are test-covered). Test: commands.rs::the_open_recent_menu_lists_files_and_opens_one (submenu lists a seeded recent file and clicking it opens a new active tab on that path) and commands.rs::the_open_recent_menu_is_disabled while_the_list_is_empty. Both failed before the change (no such menu). Co-authored-by: Mawfyy <mawfy12@gmail.com> * Build: cap cargo jobs and lighten the dev profile On a 7.1 GiB machine the default one-job-per-core (12) with every dependency at opt-level 2 plus full debuginfo swap-thrashes the box frozen mid-build. jobs = 4 bounds peak memory to a few GiB and matches GitHub runners (4 vCPUs), so CI is unaffected; dependencies drop to opt-level 1 with debug = false, which halves their build cost and keeps them fast enough for a usable debug app. Own crates keep their opt-level and full debuginfo. Verified: a from-scratch rebuild of all dependencies peaked at ~2 GiB used with the machine responsive throughout; the perf.rs frame-budget tests still pass at the lower dependency optimization. Co-authored-by: Mawfyy <mawfy12@gmail.com> * Keep the build configuration as on main; French for Open Recent The jobs and dependency opt-level changes are a separate build-policy decision. French is coverage-checked since #291; catalog checksums refreshed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * French for Open Recent again, and catalog checksums refreshed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Mawfyy <mawfy12@gmail.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e17833a64e |
Add image signatures with placement preview and resize controls (#335)
* M5.7: import images as Fill & Sign signatures and initials * M5.7: preview image signatures and select after placement |
||
|
|
bb8fcd006e |
Security: refuse mailto attachments, show link hosts in punycode (follow-up to #104) (#337)
* Security: refuse email links that attach or insert a local file Follow-up to #104. A document's mailto: link could ask the email app to attach a file from the user's computer (Evolution, KMail and Claws Mail honour attach=, Evolution and KMail also attachment=) or read one into the body (Claws Mail's insert=). document_url now refuses a mailto: address in which any word starting with attach or insert (any case) is followed by =, wherever it stands and after each layer of percent-decoding. It also refuses text that some email tools decode before reading the address: RFC 2047 encoded words, which KMail decodes across the whole address, and backslash escapes, which xdg-email's echo decodes. The link is refused rather than rewritten: PdfCraft doesn't change what a document asks to open, and the email apps' parsers disagree with one another. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Security: show a link's host as the browser receives it, in punycode Follow-up to #104. The link prompt's host now comes from links::display_host, which reads it with url::Url: the webbrowser crate hands the OS that crate's serialisation, and the web build's browser parses by the same WHATWG standard. International hosts are shown in punycode with a note, a label mixing writing systems (UTS #39 resolved script sets) gets a red warning, numeric hosts such as 3232235777 show as 192.168.1.1, and a host the browser would reject is refused as malformed. The Unicode form isn't repeated in the prompt, so a whole-script lookalike can't read as the real site. url, idna and icu_properties become direct dependencies of pdfcraft-engine; all three were already in the graph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Security: refuse document addresses the URL parser rejects Follow-up to #104. egui opens links through the webbrowser crate, which parses the address with url::Url and, when that fails, treats it as a local file path and opens file:///<cwd>/<address> in the browser. document_url accepted addresses that don't parse, such as mailto://a^b/x or https://a^b/ (a forbidden host code point), so a document could still get the browser to open a local path. document_url now also requires url::Url to accept the address with the same scheme, which is exactly the parse webbrowser does first. 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> |
||
|
|
9140b44792 |
M4: Combine files in its own tab, with a sortable table and unlocking of protected files (#321)
* M4: Combine files in its own tab, with a sortable table and unlocking of protected files Combine files was a modal dialog with a cramped list and up/down buttons on every row. It is now a tab next to Home and the documents, showing a table of the files: File name | Pages | Size | Modified | Warnings. - Columns sort when their heading is clicked (anywhere in the cell; again to reverse; names in natural order), resize from their edges and move by dragging the heading. Order and widths are kept in the settings; a right-click on a heading resets them. - Rows select like a file manager (click, Ctrl/Cmd+click, Shift+click, Shift+arrows, Ctrl/Cmd+A). One toolbar moves, removes and unlocks the selection; Alt+arrows and dragging move it too. Every change to the list undoes and redoes with Cmd/Ctrl+Z (also toolbar buttons). - Add files, Add folder, Add folder and subfolders (a split button), Add open documents, or drop files and folders on the tab. - Problems show before Combine: password-protected files, security settings that withhold page assembly, page ranges (checked as typed, with the pages they take), duplicates. Files that open but are protected say that the combined file won't keep their security; the result is never encrypted. - Unlock... asks for a file's open or permissions password (one password can be tried on several files). Passwords stay in memory, are never printed, and reach the engine through combine_unlocked; doc_combine takes them as `passwords`. The engine decides whether a password works, since the inspector rejects some it accepts (RC4 owner passwords). organize.combine-sort is shipped and organize.combine-options partial (include open files). Strings are translated for es, fr, ja, zh-hans, zh-hant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Credit Marlon Konrad (mlkonrad) in the About window Adds my own entry to contributors/people.toml, as docs/contributors.md asks. 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> |
||
|
|
87f635cf65 |
XFA: run FormCalc scripts (#301)
* XFA: run FormCalc scripts A FormCalc interpreter (XFA 3.3 part 8) in the js crate, over the same form table, effects and budgets as the XFA JavaScript model, so the engine runs both languages alike: expressions and FormCalc coercions, var, if/while/for/foreach/func, accessors ($, $host, $form, $record, $layout, $event, xfa., .., [n], [*], _name instance managers) and the arithmetic, logical, string, date/time, financial and unit built-ins. Get/Post/Put refuse. The JavaScript model's XHost, HNode, SOM resolution and instance operations are shared (pub(crate)) with Context-free add_instance and remove_instance that the boa wrappers call. Bounded like the JavaScript path: steps, loop iterations, recursion, parse depth, string and list sizes, clamped dates, the instance cap, and the per-activity time budget with abandonment. Tests: language, built-ins, control flow, form objects and hostile scripts in the js crate; a FormCalc fixture form in the engine that initializes, calculates, validates, adds and removes rows, shows a subform and round-trips through save; hostile scripts at the engine level. parity form.xfa-formcalc planned -> partial. Hardened after two adversarial review passes: statement nesting and chain length count against the limits (verified on a 1 MiB stack), the script honours the engine's deadline itself, strings are capped after every built-in and messages/values clipped and noted (shared host), lists hold each object once and the shared SOM resolver deduplicates, values go into fields only (shared host, JavaScript too), saturating Stuff and time arithmetic, FormCalc-specific size check instead of the JavaScript refuse scan. Semantics: bare row[*] and row[n] from inside a row see every row, relative [-n]/[+n] indexes, Count of non-null, Parse with a date picture gives the ISO date, assignment is an expression. * form.xfa-formcalc: bound FormCalc values, results, errors and copying (#301 review) - XHost::set_value cuts a value to 64 Ki chars before storing it, so later reads match what is saved and the snapshot stays bounded. - A script's completion value (calculate result) is cut to 64 Ki chars with a note, in both the FormCalc and the JavaScript paths. - throw clips its value like any message. - Copying, dereferencing and comparing long strings is charged to the step budget (len/256), so wasm (no clock) can't spend unbounded time on it; OneOf charges per comparison. - Exists propagates step/deadline/depth/size limits, answering 0 only for lookup failures. - IPmt/PPmt charge a step per period. - The parser counts each top-level expression's chain from zero, so an elseif condition no longer inherits the previous arm's count. - Comment on the interpreter's depth limit fixed; parity notes say memory isn't capped and Uuid/Date/Time are deterministic on wasm. 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> |
||
|
|
22752dc915 |
Run set-layer-visibility actions from links and push buttons (#292)
* Layers: run set-layer-visibility actions, keeping radio-button groups The engine can apply a set-layer-visibility action (SetOCGState, ISO 32000-2 §12.6.4.13) to a document's layers: each ON, OFF or Toggle in order, naming the layer by its optional content group, so a toggle sees the state the changes before it left. With /PreserveRB (the default), a layer turned on turns off the other layers of its radio-button groups, now read from /OCProperties /D /RBGroups; turning one off leaves them alone. The renderer is rebuilt once per action, not once per layer. Like the Layers panel, this changes the view, not the document. Tests: engine set_layer_actions_toggle_in_order_and_keep_radio_groups and set_layer_actions_change_what_renders (pixels before and after), render radio_button_layer_groups_are_read. * Links: run set-layer-visibility actions Clicking a link whose action is SetOCGState showed "SetOCGState actions run in the JavaScript engine (M6)" and did nothing. Links now read the action (/State, also when indirect, and /PreserveRB) and clicking runs it; the Layers panel follows. Hovering shows "Set layer visibility" (translated in the ja, es, zh-hans and zh-hant catalogs), and doc_info describes the action in its link list. Tests: render links_read_set_layer_actions, automation doc_info_describes_set_layer_links and ui-egui links_set_layer_visibility. * Forms: push buttons run set-layer-visibility actions A push button whose mouse-up action is SetOCGState counted as having no action, so clicking it did nothing. Buttons now read the action as links do, and clicking runs it. Tests: forms push_buttons_read_set_layer_actions and ui-egui push_buttons_set_layer_visibility. parity: edit.action-set-ocg-state is partial. The notes say what is missing: creating the action, bookmarks and other triggers, /Next chains, JavaScript ocg.state, and the Layers panel keeping radio-button groups itself. * French: Set layer visibility The French catalog is coverage-checked since #291. 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> |
||
|
|
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> |
||
|
|
776c8f0742 |
Issue #253: comment_add anchors note and attachment icons at the displayed top-left on rotated pages (#254)
`comment_add` converted a note's or attachment's `at` (the icon's top-left as displayed) as a single point through `PageInfo::view_to_user`, and the engine's `rect_for` extends the icon square from `at` toward +x/-y in user space. On an unrotated page the displayed and user-space top-left are the same corner; under `/Rotate` they are different corners of the square, so the icon landed one icon width (20 pt) away from the requested point. `icon_anchor` converts the whole displayed square to user space and takes its user-space top-left, so the `/Rect` covers the requested square under any rotation. `PageInfo::view_rect_to_user` is the render-side counterpart of `view_rect_to_quad`, and `NOTE_SIZE` is re-exported from the engine. Test: `note_icons_anchor_at_the_requested_corner_on_rotated_pages` adds a note and an attachment on pages rotated 0/90/180/270 and asserts the `comment_list` rectangles; before the fix the 90° page reports [52, 72, 72, 92] for a note asked for at [72, 72]. 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> |
||
|
|
8407c87ef9 |
#214: wheel paging, Page display menu, default layout and zoom (#241)
- The wheel turns pages in single-page view: one per notch, one per swipe. - The rail's Page display menu offers Acrobat's view choices. - Preferences: default page display and default zoom. - Fit page and Fit height no longer jump across mixed page sizes. |
||
|
|
11ffedcc98 |
XFA: run the forms' JavaScript: calculations, validation, rows, visibility (#60) (#230)
* M12: run XFA forms' JavaScript: calculations, validation, rows, visibility (#60) Laid-out XFA forms had their fields but none of their behaviour: totals didn't compute, "Add row" buttons did nothing, and subforms a check box should reveal stayed hidden. The js crate gains the XFA scripting object model as native boa objects over a snapshot of the form tree: xfa.form navigation by name, SOM resolution (absolute, relative with the containing-subform search, descendants, indexes and wildcards), rawValue/presence/access, instance managers (_name, addInstance, removeInstance, setInstances, count), xfa.host (messageBox, resetData, print, setFocus, gotoURL), xfa.layout page numbers, xfa.event, app, console and util, plus unqualified sibling names as XFA resolves them. Scripts run with `this` bound to the node and yield their completion value; everything they change or ask for comes back as effects. The xfa crate builds the live form tree (instances from the data, values, presence and access with overrides) and the event table from the template, keeps presence and access overrides on the AcroForm (/PCXfaOverrides) so undo and reopening preserve them, adds and removes row instances in the datasets packet through the existing merge pipeline, and lays the form out again on demand. The engine runs initialize and calculate scripts on open (an appended revision, nothing dirty), exit, validate and calculate after a field changes (a failing validate shows its message and keeps the value, as Acrobat does), and click scripts for buttons through run_javascript, so the app's buttons and the js_run tool work unchanged. Values a script sets go to the fields and the data at once; rows and visibility changes re-lay the form out. All of it is one undo step. JavaScript off turns it off. FormCalc scripts are recognised and reported, not run. Bounds: script length and nesting as before, loop and recursion limits, at most 1000 instances added per run, 2000 scripts and 50 re-layouts per event; a calculate script that adds rows, or a click script looping on addInstance/setInstances, ends quickly with a bounded form (tested). Checked on IRCC's IMM 5669: opens with its page-number scripts, the Print button's xfa.host.print runs through js_run, fill and save keep the original bytes. * M12: Save and SaveAs named actions run the file commands (#60) XFA forms' Save buttons are app.execMenuItem("SaveAs"), which the layout turns into a SaveAs named action; the shell answered it with "the SaveAs action isn't supported yet". Named Save and SaveAs now run file.save and file.save_as, like Named Print opens the print dialog. The buttons test covers Save against a file on disk (SaveAs opens the native dialog, which a headless test must not). * M12: bound XFA form scripts against hostile forms (#60) Review fixes for the XFA scripting PR (#230): - Effects: values, presence and access set again on the same object merge (last wins, behind order-dependent effects); 10k effects, 100 message boxes and 1000 console lines per script and per event, reported when hit. An event's data writes are kept in memory and written as one rewrite of the datasets packet; later writes in the same edit replace that stream in place. - form_tree stops at 200k objects and says so; forms without scripts build no live form and run nothing. - Only click scripts may print, save, open links or move the focus; others are noted in the console. Open-time messages and errors show right after open. - Lower loop limits for initialize/calculate/validate, a time budget per event after which remaining scripts are skipped and reported, and a per-script time limit after which a runaway script is abandoned and the document's scripts turned off. - What open-time scripts change is noted in the XFA warnings. - A document's Named /Save no longer writes the file on click (SaveAs opens the Save As dialog, as a script's execMenuItem does). - XFA click scripts are no longer written as /S /JavaScript actions: the button carries /PCXfaClick and runs by SOM path. Private keys documented. 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> |
||
|
|
42078a8365 |
Fix theme controls with shared System, Light and Dark preferences (#212)
* M3: unify toolbar and menu theme preferences Co-authored-by: Codex <noreply@openai.com> * M3: use the current egui style API in theme regression Co-authored-by: Codex <noreply@openai.com> --------- Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
6974c6550d |
Measure: add distance, perimeter and area tools (#208)
* M12: add calibrated 2D measurement tools * M12: harden measurement tools for landing (#208) Bounded snap targets (endpoints only for accepted segments, page-wide cap), lenient extraction (deep q nesting and undecodable streams are skipped and reported), per-page snap cache, measure::list reports unsupported entries instead of failing, closed polygons with a repeated vertex, printable unit labels (m²), cached panel listing, WinAnsi captions, indirect /VP kept, measure.export skipped in the command test like other pickers, missing translations, honest parity (scale and distance partial) and ROADMAP. 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> |
||
|
|
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> |
||
|
|
607d3d1c0e |
#190: choose DPI when creating PDFs from images (#206)
Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |