Commit Graph
3518 Commits
Author SHA1 Message Date
nlevaandOpus 5.5 medium ultracode 5515f321c8 M7: copy added JPEG 2000 images as their file (#2636)
Review: copying an added JPEG 2000 image failed even within its own
document, because the exporter can't write JPEG 2000 yet. Add Image stores
such a picture as the file it was given, so that file is the copy: it
pastes into the same document as the same object and into another as one
embedded image. The notes now also say that a CMYK picture stored
losslessly turns to RGB in another document.

Co-Authored-By: Opus 5.5 medium ultracode <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEgu9NcTcPqKovsLyfPxK
2026-10-24 01:17:34 +02:00
nlevaandOpus 5.5 medium ultracode 302ad68502 M7: paste added text boxes and images in the engine and through content_copy (#2636)
Edit::PasteContent pastes an added text box or image copied in the same
document or another, as it was (a text box's style; an image's picture,
turns, flips and crop), as one undo step. A pasted image draws its picture
from the copied object while that is still the picture in the target, else
from an added image there that already holds it (an earlier paste from
another document), else the file is embedded. Document::added_image_file
gives a copied image's picture as a file and honours the copy permission.

pdfcraft_edit::added::paste_rect / paste_place place a paste where the item
was, 18 pt down and to the right past each copy of it on the page (an image
with the same box and transforms counts whichever object holds its
picture), or at a point, kept on the page.

The content_copy tool drives it headlessly, across pages and documents.

The desktop keys and menus come in follow-up PRs.

Co-Authored-By: Opus 5.5 medium ultracode <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEgu9NcTcPqKovsLyfPxK
2026-10-24 01:17:34 +02:00
Philip 0ad7396cf7 Merge remote-tracking branch 'origin/main' into feat/edit-scanned-page-offers-ocr 2026-10-24 00:02:56 +01:00
Osvaldo Duran MelendezandClaude Opus 5.5 8b2cefc04c Merge upstream main a2ec27b4 into pr/attachments: translation catalogs and notes kept from both sides, ATTRIBUTION hashes recomputed (conflicted: nothing)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:16:24 -04:00
alehelandClaude Opus 5.5 3485e92a95 fix(edit): backdrop copies keep no reference that outlives a redaction
The tag on a page's backdrop copy, `/PCBackdrop << /Original ref /At /Len >>`, kept the
page's original content stream reachable. After Add background, then Redact (or Remove
Hidden Information, or Edit text) and a full save, the text taken off the page was still in
the file, in the original stream the tag pointed at. Text extraction didn't show it; reading
the file's bytes did. Restore also gave up after any edit, so the fill stayed off without a
word.

The tag is now `/PCBackdrop << /Index n /Op (f) /Before <SHA-256> /Was [num gen] >>`: how many
of the copy's operators come before the `n`, the fill it stands in for, a hash of those
operators by value (redaction writes the stream out again with other spacing), and the
original's number as two integers, which keep nothing alive.

Remove background turns the fill back on in the copy when the operators before it still
hash the same and a scan with the fill restored finds it painted first, covering the page.
Edits and redactions after the fill are kept. When the original stream is still there with
the same bytes, the page points at it again. Otherwise the copy stays as it is, and
Remove background says which pages keep their own backdrop off: `EditNotice::BackdropsLeftOff`
(engine), a toast in the app, `warnings` from `doc_remove_marks`. `edit_notices` holds
`EditNotice` now, with the undrawn-text notice as one of its kinds. The two new UI strings
are in all 33 catalogues.

`sha2` (already in the lock file) becomes a dependency of `pdfcraft-edit`.

Tests, each seen failing with its piece of the fix reverted:
- `the_tag_holds_no_reference_and_a_full_save_keeps_no_second_copy_of_the_page` and
  `removed_text_leaves_the_file_when_a_background_replaced_the_pages_backdrop` (automation,
  redaction and Remove Hidden Information through a full save): fail with `/Original` back.
- `a_fill_that_would_cover_content_painted_before_it_stays_off`: fails without the re-scan.
- `a_copy_written_out_again_with_other_spacing_still_comes_back` and the automation test:
  fail when the raw bytes are hashed instead of the operators.
- `an_edit_after_the_fill_is_kept_and_the_backdrop_still_comes_back`: fails under the old
  rule (only an unchanged copy comes back).
- `remove_background_names_the_pages_whose_own_backdrop_stays_off` (engine),
  `remove_background_warns_about_pages_whose_own_backdrop_stays_off` (automation),
  `remove_background_says_which_pages_keep_their_own_backdrop_off` (UI): fail without the
  notice.
- `a_change_before_the_fill_leaves_it_off_and_names_the_page`,
  `a_tag_that_does_not_add_up_is_left_alone` (hostile and old-format tags, never a panic),
  `operators_hash_by_value_not_by_spelling`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 23:58:42 +02:00
Kyle Kassen e373dffa20 M8: redact labelled Italian international slash numbers 2026-10-23 16:37:10 -05:00
Kyle Kassen 50b5e90d63 M10: apply soft-mask Decode intervals to image exports 2026-10-23 16:37:10 -05:00
Kyle Kassen 9dc4bd598c M10: preserve table text from covered export slots 2026-10-23 16:37:10 -05:00
Kyle Kassen 1a69e50826 M10: document annotation resource collision regression 2026-10-23 16:37:10 -05:00
Kyle Kassen 30d85bbf15 M10: write DOCX cell widths from their grid columns 2026-10-23 16:37:10 -05:00
Kyle Kassen a8839bc5b1 M10: preserve page artwork when printing annotations 2026-10-23 16:37:10 -05:00
Kyle Kassen bf86689fd7 M11: measure optimizer image resolution in physical page units 2026-10-23 16:37:10 -05:00
Eric Kaden 352ebb1477 M12: a backslash ends a web link's host, as web addresses read it 2026-10-23 17:28:22 -04:00
Eric Kaden 3355edad42 M12: only a link's own underline joins its pieces, and web links need a host 2026-10-23 17:19:25 -04:00
Eric Kaden 3cf6e5f716 M12: a link's pieces don't join across a rule, picture or field between them 2026-10-23 17:05:18 -04:00
Eric Kaden ac181479ef M12: a round check button's underline border stays an underline 2026-10-23 17:03:53 -04:00
Eric Kaden ba37d412e8 test(ui): recovery idle tests make folders that never share a name 2026-10-23 17:02:14 -04:00
Eric Kaden d91028a990 M12: a link's address is shared by its runs, words and spans, not copied 2026-10-23 16:47:19 -04:00
Lcoding15 0b3b243e9a M7: a bold paragraph's stand-in font no longer turns earlier edits bold (#4280)
Edit text set a paragraph whose font lacks a typed character (an accent
missing from a subset) in a standard font, always under the resource
name PCEdHelv, whatever face it stood for. Rewriting a bold paragraph
after a regular one replaced PCEdHelv with Times-Bold, so every
paragraph set in the earlier stand-in turned bold, and the editor's
draft measurement saw those paragraphs change and grew its frame over
them.

The stand-in is now named after its face (PCEdTimesBold, ...), as the
line and region rewrites already name theirs.

Fixes #4280
2026-10-23 22:46:56 +02:00
Eric Kaden 21f3241e6d M12: a round border as wide as its check button fills it as a disc 2026-10-23 16:45:40 -04:00
alehel e38b129450 Merge remote-tracking branch 'origin/main' into fix/background-hidden-by-page-backdrop
# Conflicts:
#	ATTRIBUTION.toml
2026-10-23 22:44:29 +02:00
Eric Kaden 0008a18345 M12: XFA borders and rectangles with rounded corners are drawn rounded 2026-10-23 16:41:22 -04:00
srt8hellcat2-ui 30438bf595 Format the nested batch the way rustfmt wants 2026-10-23 16:23:04 -04:00
Eric Kaden 1c4a481ea3 M12: round XFA check buttons draw a round background and border 2026-10-23 16:19:43 -04:00
Eric Kaden 0b8758eda3 M12: links in XFA rich text become clickable links 2026-10-23 16:16:40 -04:00
alehel 7e469b9961 Merge remote-tracking branch 'origin/main' into feat/all-tools-expand-view
# Conflicts:
#	crates/ui-egui/src/i18n/mod.rs
2026-10-23 22:02:03 +02:00
Guillem Hermida a1f6a64d4a tests: the pending-open control test waits for the worker by time, not by 30 frames 2026-10-23 21:50:24 +02:00
Guillem Hermida 7668a2c378 tests: each forms test saves into a folder of its own
tabbing_back_to_the_same_field_then_saving_keeps_the_committed_value (its
same-frame case) and typing_and_saving_in_the_same_frame_saves_the_last_keystrokes
both asked scratch() for "same-frame". The tests of the it binary share one
process id and run in parallel, so they got the same folder, and one could
clear it while the other was saving into it: the save then failed to read
back with NotFound. scratch() now adds a number taken per call.
2026-10-23 21:50:24 +02:00
Guillem Hermida bf81191122 tests: the pending-open control test waits for the worker by time, not by 30 frames 2026-10-23 21:50:24 +02:00
aiongg 8d99a5ce1f Translations: a missing string never fails a test; xtask ci prints the gaps (#4302)
Delete the translation coverage tests and the tests of one language's
wording. Keep catalog validation (format, duplicates, placeholders, plural
forms, command ids) and the tests that use a language (registration,
plural rules, RTL order, glyphs, casing, font fallback).

cargo xtask i18n-report runs i18n::tests::translation_gaps_report, which
prints each language's missing count and first few keys and always
passes; xtask ci runs it after the tests.
2026-10-23 15:42:18 -04:00
alehelandClaude Opus 5.5 ba33257fa3 fix(sign,cos): signing leaves out what was added and removed again
The signing saves wrote with default options, so a redaction mark added
and cleared in the session (or a comment added and deleted) reached the
file through a signature, a timestamped signature, embedded LTV evidence
or a document timestamp, with its QuadPoints, overlay text and authors.
A signed revision can't be changed afterwards, so nothing could take it
out again.

`sign`, `sign_with_timestamp` and `dss::embed` now save with
`drop_unlinked`: everything they add is reachable (the signature through
the field's `/V`, the store from the catalog), and the ByteRange
placeholder is found in the written bytes.

`timestamp_document` can't: its `/DocTimeStamp` dictionary has no field
pointing to it, so the option would drop it and every document timestamp
would fail with "ByteRange placeholder not found". It frees the leftovers
first with the new `Document::free_unlinked_additions`, which frees
exactly what the option leaves out, and then adds the dictionary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 21:32:46 +02:00
Marco Arnulfo c5b0e6aa61 M6: Actions tab: replacing a trigger's first action keeps its chain, and the row shows it (#4006)
The actions chained after a trigger's first one (/Next) are kept
whatever replaces it, not only another script, and the row reads
"…, then N more actions". Edit is disabled on every row while a
script is open, so it can't be overwritten.
2026-10-23 21:02:47 +02:00
NetterSilverandClaude Haiku 5.5 afcc54fddc Omit minus sign when FormCalc Format() rounds to zero
Found while testing crates/js/src/formcalc.rs:2328-2330 ("format" number-picture branch, `if num < 0.0 { text = format!("-{text}") }`)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 20:53:02 +02:00
Lcoding15andClaude Opus 5.5 21bbae39b9 M3: a link without a position keeps the reader's place (ISO 32000-2 §12.3.2.2)
A /XYZ destination with a null (or unusable) left or top went to the top of the
target page. ISO 32000-2 §12.3.2.2 says a null value keeps the current one, and
Acrobat Reader, observed on a synthetic two-page fixture at 400 %, keeps the
user-space point at the window's top-left on the new page at the same zoom.
CAD exports (WSCAD) write every cross-reference as [page /XYZ null null 0], so
following one zoomed in landed on the sheet's title row instead of where the
reader was looking.

go_to_dest now reads the place at the window's top-left before the jump and
uses it for the null coordinates, along each axis the page overflows; a page
that fits keeps the layout's own placement. Null /FitH top and /FitV left
still go to the page edge.

Tests: a unit test (zoomed, partly given, view turned a quarter, page that
fits) and an end-to-end click on such a link at 400 %, both seen failing with
the fix reverted. The malformed-bookmark case in
bookmark_destinations_scroll_to_their_position now expects the kept place, as
DestView documents unusable operands as null.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 20:41:06 +02:00
midasmatterhorn 1f41c0ccaa organize: preserve widget actions and inherited field state 2026-10-23 21:31:13 +03:00
alehelandClaude Opus 5.5 c7715a09e7 fix(ui): name the pages in the Background dialog's backdrop and scan messages
"A picture covers this page (a scan?)" didn't say which page, so on a
range of several pages the user couldn't tell where the background
would stay hidden. Both messages now list the pages as 1-based runs
("Page 3", "Pages 1-3, 6"), stopping with "…" after eight runs.
The four strings are updated in every catalogue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 20:29:39 +02:00
Marco Arnulfo 61fe4b56a7 M6: Field Properties ▸ Actions: Edit opens an existing script (#4006)
Edit on a Run a JavaScript action loads its script into the editor;
Update replaces it on the same trigger, Discard leaves it. Saving the
tab now keeps actions that did not change as they are, and an edited
script keeps the actions chained after it (/Next).
2026-10-23 20:28:13 +02:00
aiongg c9ac28a048 test(automation): give sign_document a real digital ID and document in the hostile-arguments sweep (#4277)
* test(automation): give sign_document a real digital ID and document in the hostile-arguments sweep

sign_id_create, earlier in the sweep, wrote a digital ID to the in.pdf
placeholder that sign_document then took as its id. Almost every
sign_document call failed to decrypt it and never reached the hostile
value under test, and the decrypts took most of the test's time.

Each tool now starts from an empty root, and sign_document gets a test
ID with its password and a fresh blank document per call. 370 of its
440 calls get past the ID (1 before) and 340 past the document (0
before); the sweep takes 7 s instead of 89 s.

* test(automation): empty the sweep's root through its canonical path

form_merge_data, given the hostile name " ", writes a file of that name
into the root through the canonical \\?\ path, where Windows takes it
literally. Emptying the root through the plain path dropped the trailing
space, so the entry named the root itself and removing it deleted the
root. Entries are now reached through the canonical path, their type is
read from the directory entry, and a failed removal is reported.

* test(automation): fail the sweep when the root can't be emptied

Listing, typing or removing an entry left in the root now fails the test
with the entry's path, so no tool runs against an earlier tool's files.
2026-10-23 14:22:19 -04:00
Eric Kaden 441d9005e3 M12: XFA text is measured at its standard-14 font's own widths 2026-10-23 14:08:42 -04:00
Marco Arnulfo 6062535e0e Test: the check strokes and the circle fills in the field's colour (#4261) 2026-10-23 20:06:20 +02:00
Marco Arnulfo 8b9d5017fe Check boxes and radio buttons draw their mark in the field's text colour (fixes #4261) 2026-10-23 19:59:13 +02:00
srt8hellcat2-ui f3e8a9775a Nest the deleting batch in the regression test 2026-10-23 13:53:47 -04:00
srt8hellcat2-ui 12d89c551c Test that a batch deleting pages also saves in full 2026-10-23 13:31:10 -04:00
srt8hellcat2-ui cb17f8490d Also rewrite in full when a batch edit deletes pages 2026-10-23 13:30:10 -04:00
Marco Arnulfo 5af594e7df Added text editor: wrap width gives up the trailing character spacing (#4245)
The page counts the spacing after every character, the last of a line
included; epaint only puts it between characters, so a line could fit in
the editor and wrap in the saved text.
2026-10-23 19:27:10 +02:00
srt8hellcat2-ui 29d12c39e9 Add blank line between tests 2026-10-23 13:12:53 -04:00
srt8hellcat2-ui 143dc669f1 Test saving after deleting pages (#3407) 2026-10-23 13:11:56 -04:00
srt8hellcat2-ui d149d23320 Rewrite an unsigned file in full when saving after deleting pages (#3407) 2026-10-23 13:10:19 -04:00
aiongg cf0f80604f Allow OFL typefaces, Noto CJK and Source Han included (AGENTS.md §1.1) (#3908)
* Allow OFL typefaces whoever designed them (AGENTS.md §1.1)

Noto CJK and Source Han from their open-source releases are allowed like any
other OFL font. The fonts build no longer skips them, the asset check accepts
an OFL font credited to Adobe, and the docs describe the Chinese faces that
result. The ban on Adobe icons, artwork, UI graphics and the fonts Adobe
products install stays.

* Leave out whole CJK faces other than the Japanese ones for size

A Chinese or Korean face is embedded whole only under 4 MiB, so the 16.4 MB
Noto Sans CJK SC in craft-fonts stays out of the binary and the embedded
total against the pinned craft-fonts is unchanged (25,204,828 bytes).

* docs: replacement boxes for Chinese without craft-fonts only on the web or without an installed Chinese face

* xtask assets: the OFL font exemption needs at least one licence ID

* fonts build: count a CJK script tag with a region (Hant-HK) as CJK, shared with the embedding test

* AGENTS.md: the asset gate exempts OFL fonts from the Adobe-author check

* fonts build: count a Hangul-only face (Hang) as CJK for the size rule

* fonts: the CJK embedding test checks the size rule, sharing its constants with build.rs

* docs: the missing Hans face is a property of the pinned craft-fonts, not of every build
2026-10-23 13:02:56 -04:00
aiongg 487907adfb fix(links): resolve remote document paths with craft-docpath's resolve_absolute (#3730)
* fix(links): use resolve_absolute for remote document paths

* fix(ui): refuse output file names that end in a dot or a space

* ci: check that git dependencies come from storytold

* ci: release only with git dependencies from storytold

* ci: check git dependency sources on a locked, parsed Cargo.lock

The git-sources step in ci.yml and release.yml fails when Cargo.lock is
missing, when .cargo/config.toml replaces or patches a source, when
Cargo.lock is out of date with the manifests (cargo metadata --locked),
and when any package source parsed from Cargo.lock as TOML is a git
source outside github.com/storytold/. A grep on Cargo's usual spelling
missed single-quoted strings and stale lockfiles.

* fix(links): a Windows path on Unix is refused, not reported as needing a source

resolve_remote_document_path reported MissingSourcePath for any path that
resolved but not to an absolute one. With an absolute source, such a path
is one this platform cannot open (C:\x.pdf on Unix): it is now
NotLocal(Invalid). A relative target from a missing or relative source
still gives MissingSourcePath.

* ci: check the legacy .cargo/config for source replacement too

Cargo reads .cargo/config as well as .cargo/config.toml, so the
git-sources step greps both; grep -s keeps a missing file from
failing the step.

* ci: parse cargo config for source replacement, patches and path overrides
2026-10-23 13:02:41 -04:00