Commit Graph
4380 Commits
Author SHA1 Message Date
f16b5d9331 M13 batch: test that results hold no planted secret, and record network-crate reachability (#6297)
* M13 batch: test that results hold no planted secret, and record network-crate reachability

* netcheck: list socket2, report a walk past the cap, test by the gate rule

socket2 can open sockets, so it belongs in the list. A walk past the cap now
fails instead of reading as unreachable. The baseline test follows check_rows,
so a dependency update that moves a path no longer fails cargo test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 20:52:06 +01:00
ee39c7e2d0 Issue #5081: assembly at revision 3 and later needs bit 11; high-quality print, accessibility and form fill follow the revision (#6309)
* Issue #5081: assembly at revision 3 and later needs bit 11; high-quality print, accessibility and form fill follow the revision

* Permissions: at revision 2, bits 11 and 12 mean nothing

Table 22 defines them for revision 3 and later. A revision 2 file with bit 4
clear and bit 11 set was given page changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 20:32:44 +01:00
eliets2andeliets2 bb9da1c1c2 Convert replaces a removed forbidden annotation, so it is not reported again (#6306)
Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
2026-11-05 20:32:39 +01:00
b3a90f07f3 Parity: titles and the PDF/X row say only what is implemented (#6300)
* Parity: titles and the PDF/X row say only what is implemented

* Parity: the PDF/X row says removal matches the pdfxid: prefix only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 20:32:34 +01:00
f9c150c17c Add IBAN matching to Search and Redact patterns (#4332)
* Add IBAN matching to Search and Redact patterns

The Pattern/PATTERNS/from_id interface is consumed generically by redact_mark and the UI pattern combobox through the engine re-exports, so one new variant and matcher exposes IBAN in Search and Redact everywhere. A unit test shows valid compact and spaced IBANs are marked while wrong check digits, short runs, unknown countries, letter check digits and token-glued IBANs are not.

Add an Iban variant to Pattern, list it in PATTERNS, handle it in label, id and find, and add an iban matcher with a country-code length table, compact or group-of-four-spaced acceptance, alphanumeric start and end boundaries and ISO 7064 mod-97 check-digit validation.

Test: iban_matches_compact_spaced_and_rejects_near_matches failed before the change and passes after.

* Address review for pull request 4332

* Address review for pull request 4332

* review: address CodeRabbit findings

- patterns.rs: fix SWIFT registry IBAN lengths EG 27->29, LY 27->25, SD 24->18 and add NI 28, BI 27, DJ 27, OM 23 (crates/redact/src/patterns.rs:545,549,551,558-561)
- patterns.rs: state table source as SWIFT IBAN registry without claiming full ISO 13616 coverage (crates/redact/src/patterns.rs:475)
- patterns.rs: regression test with valid registry examples for EG, LY, SD, NI, BI, DJ, OM and a wrong-length EG rejection (crates/redact/src/patterns.rs:892)

* Complete the IBAN country table and accept spaces only between groups

The country table now lists all 89 countries of the SWIFT IBAN registry
(release 99) as one sorted constant. Honduras, Russia, Yemen, Falkland
Islands, Mongolia and Somalia were missing.

A space is accepted only after a full group of four characters, so a code
followed by ordinary words ("BA37 leaves at noon") ends the run instead of
being tried against the check digits.

Tests: the registry example of every country matches compact and printed,
changes to one digit are rejected, and the table and the examples agree.

* Advertise the IBAN pattern in the redact_mark tool schema

The pattern enum in the advertised schema was kept by hand and still
listed phone, email, credit-card, ssn and date, so clients that obey the
schema could not select iban, which the runtime already accepted. The
enum is now built from the engine's pattern registry, so it cannot drift
from it again, and the description names iban.

Tests: tools/list advertises exactly the registry's pattern ids and
names each in the description, and an iban mark made through the tool
covers the spaced German sample and nothing else.

---------

Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 20:32:24 +01:00
eliets2andeliets2 ae893c1517 M13 batch: correct parity status and scope the no-replace sentence (#6299)
Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
2026-11-05 20:13:45 +01:00
eliets2andeliets2 0c744357cb M13 batch: an action step that fails now fails its file (#6307)
Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
2026-11-05 20:11:33 +01:00
eliets2andeliets2 9a95273c7c Fuzz targets for PDF/A verify and convert (#6308)
Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
2026-11-05 20:11:27 +01:00
eliets2andeliets2 c15c909745 Fuzz target for redaction apply (#6305)
Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
2026-11-05 20:11:21 +01:00
eliets2andeliets2 a2c6e63d0a Fuzz target for the JavaScript engine's script runner (#6304)
Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
2026-11-05 20:11:15 +01:00
eliets2andeliets2 0d7ae1b0fc Tests: a signed encrypted document's byte ranges cover its earlier revision (#6303)
Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
2026-11-05 20:11:10 +01:00
eliets2andeliets2 9ff881e533 The PDF/A panel reports its own checks, not conformance (#6302)
Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
2026-11-05 20:11:05 +01:00
eliets2andeliets2 b1c1dbdad3 One known-answer case per implemented clause (#6301)
Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
2026-11-05 20:11:00 +01:00
eliets2andeliets2 5aff7c83db M13 batch: test that failed folder runs leave no output or staging file (#6298)
Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
2026-11-05 20:10:55 +01:00
eliets2andeliets2 7fd6860f4a M13 batch: pin the command-line exit codes before any change (#6296)
Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
2026-11-05 20:10:49 +01:00
eliets2andeliets2 431a4e68ae Test that moving an image keeps its marked-content sequence intact (#4248)
* Test that moving an image keeps it inside its marked-content sequence

translate_images changes the image's own cm and leaves its Do where it was,
so a moved image stays in the BDC ... EMC sequence that tags it, whether
that is a tagged-PDF element or an optional-content layer. Nothing pinned
this: the image-move tests covered clips, transforms and repeated moves but
not marked content.

The test moves an image inside /P <</MCID 0>> BDC and inside /OC /L0 BDC
and checks that both sequences remain, the Do is still inside its own, and
the other sequence gained no image.

* Test image moves on a real optional-content layer

---------

Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
2026-11-05 20:10:43 +01:00
eliets2andeliets2 6f63f98717 docs: add comparative PDF workflow research reference (#2940)
Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
2026-11-05 20:10:37 +01:00
496e37d72b M5: text boxes and typed text take an oval or a circle for their border (#4543)
* Fill & Sign: a Border control for typed text, and a border colour of its own

Rebased onto main as one commit: main's Fill & Sign text colour (#2444) runs through the
same places as the border (the open box, typed(), the page input, the toolbar and the
preferences), so every step of the branch met it again. Text colour and border now travel
together as TypedLook (typed_with), which keeps typed() and the new function under Clippy's
argument limit; a border without its own colour takes the text's. Make Current Properties
Default on typed text also keeps its colour for new text now.

The branch's commits, in order:
- Typewriter text: a border change grows its box on every side, so the text keeps its place
- Fill & Sign: a Border control for typed text and dates
- Parity: the typewriter border's evidence
- i18n: the Border label in every catalog, recorded in ATTRIBUTION.toml
- Text boxes, typewriter text and callouts: a border colour of their own
- Fill & Sign: the toolbar follows the selected text; a Border colour button
- fill_sign_add and comment_edit take `border_color`
- Parity: the border colour and the toolbar following the selection
- i18n: Border colour and Same as text in every catalog, recorded in ATTRIBUTION.toml
- Typed text's border: /Border's width is read, a non-finite border colour refused, a saved Same as text restored
- Fill & Sign: the toolbar changes only the selected text; Make Current Properties Default keeps new text's border
- Review: Make Properties Default gives a text box's or callout's tool its border colour
- i18n: the new labels in the Persian catalog, recorded in ATTRIBUTION.toml
- Review: a test that dragging the Font size control resizes the selected text to where the drag ends
- Rebase: the typed text's border in main's annot and comments modules (Style's border colour, exports), and craft_ui's ComboBox

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* M5: text boxes and typed text take an oval or a circle for their border

Rebased onto #4146's new head as one commit (see there: main's Fill & Sign text colour runs
through the same places). The border shape goes into TypedLook's border with the width and
colour; a circle around typed text still takes the snug box.

The branch's commits, in order:
- Text boxes and typed text take an oval or a circle for their border
- A circle goes snug around its text
- Review: fill_sign_add takes an oval or a circle only with a border
- Review: a circle made on a quarter-turned page is sized from the text as shown
- Review: Border ▸ None takes an oval or a circle away with the line
- Review: the Border colour button shows the selected text box's own colour
- i18n: Border shape in the Persian catalog, recorded in ATTRIBUTION.toml
- Review: Border ▸ None on a circled text is one undo step
- Rebase: the border shape in main's annot, engine and comments modules; craft_ui's ComboBox; Fill & Sign's border controls in a function of their own (main's complexity limit)
- Review: Thickness 0 in Comment Properties takes an oval or a circle away, as Border ▸ None does

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Open the Font size popup first in the drag test: main moved the size into a popup

* Border shape per selected comment, and split the oval/circle border out of draw_free_text (clippy cognitive complexity)

* Re-run CI (the push of the main merge did not start it)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-05 17:32:01 +01:00
0839c1e761 Fill & Sign: a Border control for typed text, and a border colour of its own (#4146)
* Fill & Sign: a Border control for typed text, and a border colour of its own

Rebased onto main as one commit: main's Fill & Sign text colour (#2444) runs through the
same places as the border (the open box, typed(), the page input, the toolbar and the
preferences), so every step of the branch met it again. Text colour and border now travel
together as TypedLook (typed_with), which keeps typed() and the new function under Clippy's
argument limit; a border without its own colour takes the text's. Make Current Properties
Default on typed text also keeps its colour for new text now.

The branch's commits, in order:
- Typewriter text: a border change grows its box on every side, so the text keeps its place
- Fill & Sign: a Border control for typed text and dates
- Parity: the typewriter border's evidence
- i18n: the Border label in every catalog, recorded in ATTRIBUTION.toml
- Text boxes, typewriter text and callouts: a border colour of their own
- Fill & Sign: the toolbar follows the selected text; a Border colour button
- fill_sign_add and comment_edit take `border_color`
- Parity: the border colour and the toolbar following the selection
- i18n: Border colour and Same as text in every catalog, recorded in ATTRIBUTION.toml
- Typed text's border: /Border's width is read, a non-finite border colour refused, a saved Same as text restored
- Fill & Sign: the toolbar changes only the selected text; Make Current Properties Default keeps new text's border
- Review: Make Properties Default gives a text box's or callout's tool its border colour
- i18n: the new labels in the Persian catalog, recorded in ATTRIBUTION.toml
- Review: a test that dragging the Font size control resizes the selected text to where the drag ends
- Rebase: the typed text's border in main's annot and comments modules (Style's border colour, exports), and craft_ui's ComboBox

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Open the Font size popup first in the drag test: main moved the size into a popup

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-05 17:08:08 +01:00
42bc4f117d M5: copy and paste comments between documents, select several, finish connected lines from the menu (#3484)
Comments copy and paste, within and between documents. ⌘C with comments selected (and no text
selected) copies them, each with its replies, as XFDF: into the app's clipboard (every tab and
window) and as text onto the system clipboard. ⌘V, Edit ▸ Paste comments or the page's context
menu pastes them onto the current page at their own positions, as new comments under names of
their own, so nothing is replaced and pasting twice gives two sets; the copies are selected.
XFDF with comments from the system clipboard pastes too, so comments travel between processes.

pdfcraft_xfdf::export_xfdf_comments writes a selection with its replies; import_comments takes
a target page and a name prefix and imports copies (the usual import still replaces same names).
Engine: Edit::PasteComments { xfdf, page } (one undo step, comment permission) and
Session::export_comments. Tools: comments_copy (read-only) and comments_paste.

Several comments select at once: ⌘-click (Ctrl on Windows and Linux) or ⇧-click adds one or
takes it out, on the page and in the Comments panel; the one clicked last has the handles.
Delete removes them all as one step, a drag moves them together, the context menu copies them.

Connected lines, polygons and clouds finish from the right-click menu's Complete (with Undo last
point and Cancel drawing), as other editors do, besides Enter and a double-click.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-05 16:41:45 +01:00
6427357fcb Forms: date fields name months in the date language, not always English (#5303) (#5464)
* Forms: date fields name months in the date language, not always English (#5303)

`AFDate_FormatEx("dd-mmm-yy")` wrote "18-Oct-26" in an Italian interface,
where a localized Acrobat writes "18-ott-26". Form date formats,
`util.printd` and `util.scand` now take their month and weekday names
from the date language (Preferences > Date format > Language, which
follows the interface language until one is picked), and still read
English names typed in. The names are process-wide, so their test runs
in its own binary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Forms: dates read back in every date language (#5303)

Trying every date language showed what a field wrote not always reading
back: names holding digits ("10月"), spaces ("tháng 10"), dots ("ม.ค.")
or combining vowel signs (Thai, Hindi, Telugu) fell apart word by word,
and Italian, Spanish, Romanian and Albanian share some short weekday and
month names ("mar"). The language's names are now found whole in the
text first, the longest at each place, and read as their English names;
the format's order of `mmm` and `ddd` settles a shared name. Both
AFDate parsing and util.scand do this, on text up to 1 KB. A test reads
back every language, each name format, and hostile text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Forms: date names no field font can draw are drawn in English (#5303)

With the names in the date language, a Russian dd-mmm-yy field drawn in
Helvetica showed "18-????.-26": no /DR font held "окт." and the WinAnsi
stand-in drew each letter as ?. The same held for Japanese, Chinese,
Greek, Arabic, Thai and others, and for Latin names outside WinAnsi
(Czech "říj"). Before, the names were English and always drew. An
AFDate/AFTime field now formats its value again with English names for
a widget whose fonts can't draw the localized text; a custom Format
script is run again with util.printd writing English names for such a
widget, and its result kept only when it draws whole. A field whose
font holds the names keeps them, and scripts otherwise still print the
date language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Forms: an English weekday before the month keeps the month (#5303)

With Italian dates, "Tue 17 Mar 2026" in a "ddd d mmm yyyy" field read
as no date: the language's names were found in the text, but not
English ones, so "Tue" left the format's weekday unfilled and "Mar",
which Italian shares between Tuesday and March, was taken as the
weekday. Both AFDate parsing and util.scand lost the month, and
Spanish, Romanian and Albanian ("mar") did the same. English names are
now found too and fill their field of the format as localized ones do.
A test reads English and mixed weekdays before the month in those
languages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Forms: an English date run is drawn even when it prints the value (#5303)

A Helvetica field storing "18-Oct-26" whose Format script prints it
again as dd-mmm-yy still showed "18-???.-26" in Russian. The English
run printed "18-Oct-26", but it was dropped for equalling the stored
value, which was how a failed run was told apart: a failing script
handed back the value unchanged. The English run now says whether it
ran (`None` when the script failed or the runner can't write English
dates), and what it made is drawn whenever it draws whole. A test
reprints the stored value under Russian, and checks that a failing
script keeps its text and is reported undrawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Forms: a short weekday typed without its dot is the weekday (#5303)

With French dates, "mar 6 janv 2026" in a "ddd d mmm yyyy" field read
as no date through util.scand, and "mar 06/01/2026" in "ddd dd/mm/yyyy"
through AFDate too. French writes Tuesday as "mar.", which a name
search only found with its dot; since English names are found as well,
the English "Mar" (March) was the only name of that length, so the
weekday became a month. Romanian "mar" did the same. A short name
ending in a dot is now also found typed without it, as the same kind of
name, so the format's order of name fields decides between it and an
equal name of the other kind. Both copies of the name search change. A
test reads French and Romanian weekdays typed without their dot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Forms: a shared name is the month when no weekday is typed (#5303)

With Italian dates, "17 mar 2026" in a "ddd d mmm yyyy" field read as
no date, nor did "mar 17, 2026" in "dddd, mmmm d, yyyy". AFDate leaves
weekdays out of what it reads (they are shown, not typed), but the name
search still expected the format's weekday first, so "mar", which
Italian shares between Tuesday and March, filled it and left no month.
Spanish and Albanian "mar" and Albanian "sht" (Saturday, September) did
the same, and the keystroke check refused those dates. Where such a
read finds no date, the names are now read again with no weekday
expected, so the shared name is the month; a typed weekday still comes
first ("mar 17 mar 2026"). util.scand reads its fields in order and
needs the weekday, so it is unchanged. A test types those dates with the
weekday left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Forms: Turkish names typed in capitals are read (#5303)

With Turkish dates, "1 MAYIS 2026" and "1 EKİM 2026" in a d mmmm yyyy
field read as no date through AFDate and util.scand, and "SALI 6 OCAK
2026" through util.scand. Names were compared lower-cased, which turns
I into i, not the dotless ı of "Mayıs" and "Salı", and İ into i with a
combining dot above, which "Ekim" doesn't have. Names and typed text
are now folded alike, the capital I and the dotted and dotless i all as
i and the combining dot dropped, one piece for each char so positions
in the text stay aligned: in both copies of the name search, the AFDate
month reader and util.scand's name matcher. English reads as before. A
test types Turkish names in capitals and English ones as a check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Forms: Greek names typed in capitals are read again (#5303)

Folding names char by char turned a capital Σ into σ, so "ΙΑΝΟΥΆΡΙΟΣ"
no longer matched "Ιανουάριος", whose last letter is the final ς. The
fold now makes ς a σ on both sides, in the forms and JS parsers alike.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 16:12:04 +01:00
c3c36f77cf M5: a comment's text, border or line, and fill each at an opacity of its own (#4542)
* A comment's text, border or line, and fill each at an opacity of its own

Comment Properties showed one opacity for a whole comment, so a text box's
see-through background took its text and border with it, and a rectangle
marking an area on a plan couldn't have an opaque line over a faint fill.
Now a text box, typewriter text or callout has a Text, Border and Fill
opacity, a rectangle, oval, polygon or cloud a Line and Fill opacity (a part
not drawn is greyed); everything else keeps one Opacity.

The line's stays /CA, the fill's is /ca (PDF 2.0's opacity for what is
filled) and the text's /PCTextOpacity, each only where it differs from the
one it would follow, and the appearance draws them with /GS0 (strokes at the
line's, fills at the fill's) and /GS1 for the text, so every viewer shows
them. The quick bar's Opacity and StyleAnnotation's set the whole comment's.

Engine: Style::{fill_opacity, text_opacity}, Edit::PartOpacity,
annot::{set_part_opacity, appearance::part_opacity}, Props::{fill_opacity,
text_opacity}. Make Properties Default and the settings carry the parts.
Headless: comment_add / comment_edit fill_opacity, text_opacity; comment_list
reports opacity, fill_opacity, text_opacity. Four labels in every catalog
(AI-assisted translations from English, noted in ATTRIBUTION.toml).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review: the text's opacity slider shows what the text is drawn at; comment_list skips replies' properties; ui-parity's change record

A text box without fill and border whose text kept an opacity of its own showed the border's
in its Text opacity slider; it now shows the text's, and a change sets the whole comment's.
The opacity sliders are named by their labels. comment_list reads a comment's properties only
for comments, not for every reply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rebase: comment_list tells replies by is_reply, as main now threads them

main threads replies by their /IRT reference (#4061); the opacity fields skip a reply the
same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review: a text-only text box made default gives the tool its text's opacity

With neither a border nor a fill, a text box's text is the whole comment:
Comment Properties shows its drawn opacity (the text's own, when set), and a
change there sets the whole comment's. Make Properties Default, and the
context menu's Make Current Properties Default, stored /CA instead, so a
text box whose text was at 50 % gave the tool 100 %. The default is now the
text's drawn opacity in that case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rebase: the part opacities in main's annot layout (Style's fields, the text box drawing's parts, set_part_opacity exported)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rebase: main's new comment test builds a Style with the part opacities

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 14:39:36 +01:00
983e2f2ad6 M3: → / ← scroll a page wider than the window before turning it (#4540)
* → / ← scroll a page wider than the window sideways before turning it

On a plan zoomed in, the arrow keys turned the page (#185) and never showed
its other side: the view moved sideways only by the scroll bar, the wheel or
the Hand. Now → / ← scroll a line that way while there is more of the page,
and turn the page at its edge, as ↓ / ↑ do in single-page view. ⌘→ / ⌘←
turn it at once; a page that fits across turns as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review: → / ← read the current page's or spread's edges, not the widest page's; ui-parity's change record

In continuous view the content is as wide as the widest page, so on a narrower current page
that shows whole → scrolled toward the wide page's edge instead of turning. The edges are now
the current page's (or spread's) as drawn, or the content's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review: a spread's second page off screen still counts for → / ←

The row's edges came from the pages' screen transforms, and a page gets one
only while it meets the window. With a zoomed two-page spread scrolled so its
second page was still wholly to the right of the window, the row ended with
the first page, so the next → turned the spread instead of scrolling to the
second page. The edges are now taken from the layout rectangles of every page
in the row, placed as the drawn pages are.

A TwoUp case in the viewer tests zooms a spread until its page gap sits at the
window's right edge and checks that → scrolls on rather than turning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review: → / ← read the row after continuous view has moved the current page

In continuous view the current page is updated while the pages are laid
out, after the row's extent was taken, so the edges could describe the
previous row and the next → or ← turn the page instead of scrolling the
page that had just become current. The row is now read after that update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review: a page turned by → / ← at its edge starts at the edge the reader comes from

Turning at a wide page's right edge with →, the next page kept the
horizontal scroll (the same column, as other jumps keep it), so an equally
wide page opened at its right edge and the next → turned again without
showing its left side; ← had the same in reverse. When plain → or ← turns
the page, a page or spread wider than the window now starts at its left
(after →) or right (after ←). ⌘→ / ⌘←, Page Down / Page Up and every other
jump keep the column.

The sideways-arrow test now goes across two wide pages both ways: → scrolls
the next page from its left edge, ← turns back to the previous page shown
from its right edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rebase: the sideways arrow keys through main's PageEnv, PageOut and GlideCx, and shown_row with its layout

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 14:25:56 +01:00
FredApps c56a63bcb6 Merge pull request #4732 from helenkwok/web/host-page
feat(web): page host mode, so a page can host PdfCraft in its own document (#4731)
2026-11-05 14:20:12 +01:00
96fe832e12 M5: Shift holds lines level or plumb while drawing (ortho) (#4285)
* M5: Shift holds lines level or plumb while drawing (ortho)

With Shift held, a new line or arrow runs level or plumb from where it
starts, whichever is nearer to the pointer (a CAD drawing's ortho mode),
and each new side of connected lines, a polygon or a cloud is held the
same way from the corner before, rubber band included. Dragging an end
or corner of a drawn one with Shift is held the same way; it turned to
45° steps before. The measuring tools keep their 45° steps.

measure::snap::ortho next to constrain; comment_add takes `ortho` for
line, arrow, polygon, cloud and polyline. Parity comment.draw-ortho.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Ortho: over the first corner the rubber band shows the closing side, Shift or not

From review: a click there closes the polygon or cloud to its first
corner, while the preview with Shift held showed a level or plumb side.
The click and the preview now share one rule (closes_at).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rebase: Shift is read where main's split put the drawing input, and the ortho helpers are imported there

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rebase: Shift holds a drawn line, not a freehand stroke (a hunk had landed in the wrong branch)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Ortho: the parity note names the closing side's exception; the test's doc comment is back above its test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 14:01:18 +01:00
FredApps 419b267ee5 Merge pull request #5125 from huppiflupp/feat/scan-clean
M10: Enhance scanned file cleans scanned pages in place
2026-11-05 13:36:55 +01:00
ohagmannandClaude Opus 5.5 04cee34bfe M5: a double-click on a comment card's text edits it (#4544)
* A double-click on a comment card's text edits it

On the selected card in the Comments panel only the header took the
double-click: the text is a selectable label, so a double-click there
selected a word and the editor stayed closed (a tester's report). It now
opens the editor too; dragging over the text still selects it for copying.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Tests: the double-clicked card's editor is drawn too

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review: the double-click test also starts from a card that isn't selected

It selected the card before any input, so it never covered what the change
is for in the Comments panel as one uses it: a first click on the text of an
unselected card selects it, the second edits it. The test now runs both ways.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:21:29 +01:00
FredApps 65aa495c98 Enhance scanned file: re-render the page shown after a successful run 2026-11-05 13:19:49 +01:00
ohagmannandClaude Opus 5.5 8d569066b8 M5: Escape leaves a Fill & Sign tool, as it leaves a comment tool (#4539)
* Escape leaves a Fill & Sign tool, as it leaves a comment tool

The first Escape closes the open type box, the next one goes back to
Select; Fill & Sign's Text tool otherwise opened a box at every click with
no way out by the keyboard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fill & Sign: a new text box deselects, so two presses of Escape close it and leave the tool

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:13:56 +01:00
FredApps 9cc1736dc9 Enhance scanned file: decode bi-level images within the budget, DecodeParms resolved 2026-11-05 12:56:10 +01:00
FredApps 9927a1bd12 M10: page_enhance_scan as its own dispatch method
Main split Automation::call into per-tool methods and its clippy
cognitive-complexity limit (30) now fails with the inline arm.
2026-11-05 12:47:19 +01:00
FredApps 2f2d69be17 M10: merge main into feat/scan-clean; classify mixed scan pages in one pass
Merge origin/main. Conflicts: docs/changelog headers keep both histories;
edit/run.rs keeps EnhanceScan beside main's run_edit_page_image; optimize
images.rs keeps main's scaled() and the crate-visible jpeg(); the German
strings in ui-egui/src/i18n/de.tsv go with that file, which main moved to
craft-i18n. decode_samples now takes main's ImageBudget; a scan clean-up
run shares one sized by the file.

Review (CodeRabbit, scan_clean.rs): a page whose own images and a
scan-holding Form only together cover it now cleans the images and reports
the Form; a page whose images alone cover it reports its Form too. New
regression fails on the old classification.
2026-11-05 12:14:01 +01:00
Helen Kwok 3df903d516 pdfcraft-web: speak the host messages through craft-web-host
The transports, the message types and the origin checks come from the shared crate (storytold/craft-libs#420); PdfCraft keeps the save and dirty hooks, the open size limit and the author.
2026-11-05 17:51:00 +08:00
Helen Kwok c79e48d608 web host mode: refuse an oversized host file before copying it, cap the name, and document trust and limits 2026-11-05 17:08:15 +08:00
Helen Kwok 43efa9a9e3 packaging/web: say that ?host=parent takes precedence over data-host="page" 2026-11-05 17:08:15 +08:00
Helen Kwok 5574a8bf4a ROADMAP: a framed ?host=parent selects the parent bridge; only other requests keep downloads 2026-11-05 17:08:14 +08:00
Helen Kwok d5b8a35248 feat(web): page host mode, so a page can host PdfCraft in its own document (#4731)
data-host="page" on the canvas opts a page that loads the module into its own document into the host bridge. The ?host=parent messages travel as non-bubbling CustomEvents on the canvas; host::Bridge holds a Transport (Parent or Page) with one send, and save, dirty, ready and failure reporting are shared. ?host=parent is checked first and unchanged. Hosting guide documents it.
2026-11-05 17:08:14 +08:00
Helen Kwok 0f99608112 Contributors: credit helenkwok as Helen Kwok 2026-11-05 17:08:14 +08:00
863af864a7 M1: respect generations in PDF object references and full saves (#1143)
* M1: respect generations in COS object references and full saves

Resolve stale references to null instead of replacement objects with the
same number. Validate complete identities in writer readers, indirect
stream lengths and catalog checks, and keep generation-aware length caches.

Cover cache reuse, edit overlays, snapshots, incremental and packed/classic
full saves, and stream lengths with synthetic regressions and independent
hayro checks.

Co-Authored-By: Codex <noreply@openai.com>

* M0: clarify refreshed CI roadmap note

Co-authored-by: OpenAI Codex <noreply@openai.com>

* M1: repair contradictory xref generation references

Address maintainer feedback on the existing PR. Preserve upstream work and document the tested scope and remaining validation limits.

* M1: cover repaired generations and freed-slot round trips

Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.

* M1: document generation repair and bounded diagnostics

Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.

* M1: record generation repair regression evidence

Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.

* M1: document bounded COS generation repair

Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.

* M1: record generation repair parity evidence

Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.

* fix(cos): preserve repaired generations through preflight and saves

Keep accepted xref/header aliases across repair-aware edits and snapshots, canonicalize full-save identities, and retain bounded generation diagnostics from older revisions. Add COS, writer/encryption, and real preflight regression coverage.

* test(preflight): assert repaired aliases share saved identity

Address the review nitpick by comparing reopened HeaderImage and RowImage references after damaged full saves, for classic/packed output and edited/restored snapshots. Focused preflight regression and rustfmt check pass.

* M14: update certificate dependencies for RustSec advisories

Bump rustls-webpki to0.103.16 and webpki-root-certs to1.0.10. Preserve all other locked packages and dependency lists, manifests, Git pins, and advisory policy.

---------

Co-authored-by: MMexicann <MMexicann@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-05 09:28:43 +01:00
FredApps 15c505d1c6 Merge pull request #3858 from Kronbii/fix/scan-image-resampling
M2: scans stay sharp at screen size and smooth when zoomed (area-average shrink, bilinear enlarge)
2026-11-05 06:03:58 +01:00
FredApps 84e85c1ff3 Merge pull request #5758 from hasannadeem/feat/update-check-preference
M14: Preferences can turn off the update check at start (#5737, #5460)
2026-11-05 05:39:12 +01:00
FredApps f3dc867651 Merge remote-tracking branch 'origin/main' into w
# Conflicts:
#	vendor/README.md
2026-11-05 05:14:13 +01:00
FredApps 21540829cd Merge pull request #5754 from agent-reed/fix/fill-sign-quick-bar-controls
M5.7: the Fill & Sign text controls use a colour dot and a size flyout
2026-11-05 04:48:17 +01:00
FredAppsandClaude Opus 5.5 943718ea98 fix(ui): About credits tables fit without side-scrolling and their columns resize (#6052)
* fix(ui): About credits tables fit without side-scrolling and their columns resize

The Contributors table and the Models table were egui Grids inside a
two-way scroll area: they opened wider than the dialog, so they scrolled
sideways, and Company and Model sat almost touching. Both are now
egui_extras tables. Each column opens at the width of its header and widest
cell, the spare width goes to the text columns (Name; Company and Model),
and every column edge can be dragged. Widening a column grows the table
like a spreadsheet, and only then does it scroll sideways; the header stays
put while the rows scroll. Counts and dates are right-aligned and every
cell is vertically centred.

The sorted column stays highlighted and its direction shows on the
toolbar's sort-order button, so headers no longer reserve room for an
arrow. The opening widths come from fit_widths, a pure function with unit
tests that a fitted table fills its room exactly and that only the text
columns give way when room is short.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Let every credits column be dragged, and refit changed tables

The last column was egui's remainder column, which has no drag handle, so
it could only be resized from the edge before it. It is now an ordinary
column with its own handle.

A table keeps its column widths, so switching to a longer name left the
Name column at its old width and clipped the text. The table now goes
back to the opening fit whenever that fit changes: a longer widest cell
or a resized window. Dragged widths are dropped at that point.

fit_widths could overflow the room when one column hit the minimum,
because the other columns did not take up its share. Columns now give
way in proportion to their width above the minimum.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refit credits tables on what they measure, not the fitted widths

Comparing the fitted widths missed the case it was meant for. Name is the
only growing column, so a longer name takes its extra width from the
spare room and the fit comes out the same: a dragged Name width stayed,
clipping the longer name.

It could also drop dragged widths by accident. The fit comes from the
available width inside the scroll area, which scrolling can nudge, so
scrolling a widened table could reset it.

The table now compares the natural widths and the clip rect's width,
which scrolling does not move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 04:33:21 +01:00
FredApps 13cb547434 Merge remote-tracking branch 'origin/main' into fix5754
# Conflicts:
#	crates/ui-egui/tests/it/fill_sign.rs
2026-11-05 04:06:56 +01:00
FredApps 713c61548f Merge pull request #5435 from agent-reed/feat/fill-sign-rail-default
M5.7: the quick bar's Fill & Sign button shows the tool it picks
2026-11-05 04:06:42 +01:00
Dylan M. Taylor d22641a875 Pass the Linux signing key to the release workflow (#6200) 2026-11-04 17:44:52 -05:00
FredApps 083b3aca44 Merge pull request #6087 from KenM76/feat/model3d-render
M12: model_render, a page's 3D model drawn to a PNG (stacked on #6069)
2026-11-04 20:19:35 +01:00
FredApps 52d68d7844 annot: share threed's MAX_MODEL_BYTES with model3d
main's 3D-embed change (#5808) added crates/annot/src/threed.rs with its own
pub MAX_MODEL_BYTES (same 256 MiB value), so both were re-exported from the
crate root and pdfcraft-annot no longer compiled after merging main. model3d
now uses threed's constant.
2026-11-04 19:52:51 +01:00
FredApps 697a02e2ef Merge storytold/main into feat/model3d-render
# Conflicts:
#	crates/automation/src/lib.rs
#	crates/automation/tests/it/main.rs
2026-11-04 19:52:51 +01:00