Commit Graph
1512 Commits
Author SHA1 Message Date
aiongg ca2e7f8ffd Merge PR #2840: M6: detected form fields get no border or fill
crates/engine/src/js.rs: auto_detect_fields keeps main's comb and turn handling, builds props with no border and no fill for every field, and pushes SetFieldProps unconditionally (the review's fix)
parity/acrobat-features.toml: form.detect-fields keeps main's comb evidence and notes and adds the no-border test and sentence
2026-10-22 01:47:29 +00:00
aiongg b4831565be Merge PR #2294: Keep Auto font size in the field properties Appearance tab 2026-10-22 01:47:03 +00:00
aiongg 0965bc124d Merge PR #2900: Forms: measure a field's own simple font, not Helvetica, when laying out its text 2026-10-22 01:46:58 +00:00
aiongg 50a9896ead Merge PR #3039: fix(ui): Fill & Sign font size resizes the selected text (#1274) 2026-10-22 01:46:58 +00:00
aiongg 7fb6bc221a Merge PR #2279: Preferences: field highlights on by default, toggle in Appearance (#2273)
parity/acrobat-features.toml: form.highlight-fields evidence and notes from both sides (the renamed remembered test, the read-only test)
docs/roadmap.md: M6 row keeps both added sentences
ROADMAP.md: union of the progress-log lines
2026-10-22 01:46:58 +00:00
aiongg 3463d0e0f9 Integration: #3323 drop docs/form-maxlen-compatibility.md 2026-10-22 01:46:20 +00:00
aiongg c516738a1b Merge PR #3323: M6: Treat AcroForm /MaxLen 0 as unlimited
parity/acrobat-features.toml: form.fill-text evidence and notes from both sides
2026-10-22 01:46:16 +00:00
aiongg 43b9a17603 Merge PR #3856: M6: hovering a form field shows its tooltip (#3854) 2026-10-22 01:46:04 +00:00
aiongg 5a33dfac23 Merge PR #3584: Forms: Highlight fields marks only the fields that can be filled 2026-10-22 01:45:58 +00:00
aiongg 5a351f658d Merge PR #2235: fix(forms): Tab and Space reach check boxes and radio buttons, and a click focuses them 2026-10-22 01:45:58 +00:00
aiongg 47ecd2ebc7 Merge PR #3625: fix(forms): preserve comments across content streams
ROADMAP.md, docs/roadmap.md: union of the progress-log and revision lines
2026-10-22 01:45:49 +00:00
aiongg 8cdfe0f4e8 Merge PR #3475: fix(forms): draw fields the file leaves to the viewer, for display only (#3470) 2026-10-22 01:45:38 +00:00
aiongg 12ce3bfa4c Merge PR #2621: feat(forms): nudge the selected field with the arrow keys in Prepare a form (#2608)
# Conflicts:
#	ROADMAP.md
ROADMAP.md: union of the progress-log lines
2026-10-22 01:45:38 +00:00
aiongg 01a930293f Merge PR #3013: fix(forms): renaming a nested field checks the full name it would take 2026-10-22 01:45:21 +00:00
aiongg 64e915a156 Integration: #3543 merge the duplicated doc comment on button_appearance 2026-10-22 01:45:19 +00:00
aiongg 3d8b906870 Merge PR #3543: M6: a push button's caption uses the font, size and color of its /DA 2026-10-22 01:45:06 +00:00
aiongg bcad9a2e4c Merge PR #3814: fix(xfdf): a field name full of periods no longer overflows the stack on export 2026-10-22 01:45:05 +00:00
aiongg caffb259f1 Merge PR #2280: Draw the widget border inside /Tx BMC so the reference app keeps it on focus changes (#1613) 2026-10-22 01:45:03 +00:00
ffaf312358 M6: Prepare a form ▸ Calculation order: a page to reorder, check and fix the order fields calculate in (#3733)
* M6: renaming a field renames it in the calculations that read it

A rename silently broke every formula that used the field: a sum dropped
it, notation read it as 0 and a script kept a stale value. set_props now
rewrites the name in each field's Calculate script, in the same edit:
AFSimple_Calculate field lists, simplified field notation (the BVCALC
comment and its translation) and getField calls naming it in one string
literal. The rest of each script is kept as written; names built at run
time, non-UTF-8 script streams and a name another field still has are
left alone, and a self-check keeps a script whose meaning would change.

Notation is read by one lexer shared by notation_js, eval_notation and
the rename. form_set_props returns formulas_updated; Field Properties
says how many calculations now use the new name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z

* M6: form_set_props leaves the renamed field out of formulas_updated by object

The renamed field was matched by the name given, but set_props stores it trimmed and under
its parent's prefix. When the same call changed the field's own formula, formulas_updated
listed the field as one the rename had rewritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z

* M6: calculation order: read, check, fix and set the order fields calculate in

A form's calculations run in its calculation order (/CO), which a field
joins when it gets a calculation, so a chain defined in the wrong order
shows stale values. calculation_order reads the order against the fields,
with what each formula reads (field lists, notation names, literal getField
names) and what is wrong: a field reading one that calculates later, a loop
(strongly connected components, found without recursion), a field /CO
leaves out, and /CO entries that calculate nothing. Fix order is a stable
topological sort that moves only what must move and leaves loops alone.
set_calculation_order writes /CO afresh; a name two fields share (a
malformed form) stands for the next of them in the current order.

Edit::SetCalculationOrder sets it in one undo step and recalculates, and
form_calculation_order reads, sets, moves one field or fixes the order
headlessly. An edited formula now keeps its field's place in /CO, a field
/CO lists twice calculates once, and recalculation runs each field by
object, so two fields sharing a name both calculate. The /CO code of the
Calculate tab, field scripts and recalculation goes through one place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z

* M6: Prepare a form ▸ Calculation order: a page to reorder, check and fix the order fields calculate in

The calculated fields in the order they calculate (/CO), each with its
formula and the value it shows, moved with Up/Down arrows (as Acrobat's Set
Field Calculation Order), Alt+Up/Down or by dragging. A card says which
fields read one calculated later, which read each other in a loop and which
/CO leaves out, and holds Fix order; a loop's card opens one of its
formulas. On the page, arrows show what the field in focus reads and what
reads it, with each linked field's step. The order is a page of the Prepare
a form panel, opened from its Fields tools, with a back arrow to Prepare a
form; opening it ends Preview and closes the page grid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z

* M6: form.calculation_order runs form_calculation_order headlessly

The command had no tool linked, so command_list showed none for it and
command_run refused it. form_calculation_order is now its tool: run without
a change, it reads the order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
2026-10-21 20:30:58 -04:00
Dylan M. Taylor dbc7130b36 ci: pin GitHub Actions to full commit SHA (SonarCloud githubactions:S7637) (#3932)
Pin third-party actions to full-length commit SHAs (with version comments)
instead of mutable tags/branches to prevent supply-chain tampering via
retagged releases. Resolves SonarCloud rule githubactions:S7637.
2026-10-21 20:14:11 -04:00
Dylan M. Taylor 78ef66636d security: enforce HTTPS on curl downloads (SonarCloud S6506) (#3933)
Add --proto '=https' to curl invocations so redirects cannot downgrade to
plaintext HTTP. Resolves SonarCloud S6506 (shell/docker/githubactions).
2026-10-21 20:13:46 -04:00
e451934f72 M6: calculation order: read, check, fix and set the order fields calculate in (#3708)
* M6: renaming a field renames it in the calculations that read it

A rename silently broke every formula that used the field: a sum dropped
it, notation read it as 0 and a script kept a stale value. set_props now
rewrites the name in each field's Calculate script, in the same edit:
AFSimple_Calculate field lists, simplified field notation (the BVCALC
comment and its translation) and getField calls naming it in one string
literal. The rest of each script is kept as written; names built at run
time, non-UTF-8 script streams and a name another field still has are
left alone, and a self-check keeps a script whose meaning would change.

Notation is read by one lexer shared by notation_js, eval_notation and
the rename. form_set_props returns formulas_updated; Field Properties
says how many calculations now use the new name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z

* M6: form_set_props leaves the renamed field out of formulas_updated by object

The renamed field was matched by the name given, but set_props stores it trimmed and under
its parent's prefix. When the same call changed the field's own formula, formulas_updated
listed the field as one the rename had rewritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z

* M6: calculation order: read, check, fix and set the order fields calculate in

A form's calculations run in its calculation order (/CO), which a field
joins when it gets a calculation, so a chain defined in the wrong order
shows stale values. calculation_order reads the order against the fields,
with what each formula reads (field lists, notation names, literal getField
names) and what is wrong: a field reading one that calculates later, a loop
(strongly connected components, found without recursion), a field /CO
leaves out, and /CO entries that calculate nothing. Fix order is a stable
topological sort that moves only what must move and leaves loops alone.
set_calculation_order writes /CO afresh; a name two fields share (a
malformed form) stands for the next of them in the current order.

Edit::SetCalculationOrder sets it in one undo step and recalculates, and
form_calculation_order reads, sets, moves one field or fixes the order
headlessly. An edited formula now keeps its field's place in /CO, a field
/CO lists twice calculates once, and recalculation runs each field by
object, so two fields sharing a name both calculate. The /CO code of the
Calculate tab, field scripts and recalculation goes through one place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
2026-10-21 19:56:27 -04:00
Dylan M. Taylor 606c560d24 Use chrono instead of hand-rolled UTC conversion (#3824)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the hand-rolled civil_from_days/days_from_civil copies and the
leap-year/month math around them (log timestamp, PDF /Info dates, ASN.1
UTCTime/GeneralizedTime, FormCalc Date/Num2Date, the Combine modified-time
stamp, the form date picker, and the demo-PDF timestamps) with chrono as a
workspace dependency. The engine's existing chrono (clock) is lifted to the
workspace. Every output format is unchanged and pinned by tests. CLAUDE.md now
says time goes through chrono.

Follow-up to storytold/cadcraft#1785

* Guard chrono date paths: no panic on overflow, no '+' on 5-digit years

Systemic chrono follow-ups to the UTC/calendar PR:

- logging::timestamp and combine_ui::utc_stamp dropped chrono's
  From<SystemTime> (which unwraps internally and panics on a clock past
  year 262143) for the fallible DateTime::from_timestamp, reading an
  out-of-range clock as the epoch, as the old clamping code did.
- timestamp, utc_stamp, pdf_date and the xtask Stamp now write the year
  with {:04} instead of %Y, which prefixes a '+' before years past 9999;
  the old hand-rolled code wrote the plain zero-padded year.
- forms_ui calendar navigation and length, and formcalc days_in, use
  checked_add for the December year successor so y + 1 can't overflow at
  i32::MAX / i64::MAX (reachable from a parsed or persisted date).

Docstrings added to the chrono-touched civil_from_days and Time::from_unix
for the docstring-coverage gate. Tests cover the out-of-range-epoch and
10000-01-01 cases for each.

* Don't build an unrepresentable SystemTime in the far-future test

UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.

* Keep FormCalc dates exact past chrono's range

MAX_DAYS accepts day numbers up to 1e8 (years past 270000), but NaiveDate
stops near ±262143, so Num2Date(99000000) and Format fell back to
1900-01-01. days_from_civil, civil_from_days and days_in now have chrono
convert within the date's 400-year Gregorian cycle and add whole cycles
back (via i128), which gives the pre-chrono results for every year. Tests
cover accepted inputs past chrono's range on both sides of the epoch,
through Num2Date and Format, with values from the old formula.

* Bump rustls-webpki and webpki-root-certs for RUSTSEC-2026-0382/0383

rustls-webpki 0.103.15 -> 0.103.16 and webpki-root-certs 1.0.9 -> 1.0.10
(both semver-compatible, lockfile only) drop the distrusted emSign
intermediates and root. Only these two entries change; cargo deny check
advisories passes. Upstream main fails the same check.

* logging/cos/sign/combine/demo: keep extended-year dates, no epoch fallback

For consistency with the stricter no-epoch-fallback rule now agreed across the sibling
chrono PRs (wordcraft#3427, filmcraft#2662): `DateTime::from_timestamp(..).unwrap_or_default()`
changes a timestamp the old code converted past chrono's year 262143 range — it collapses
to 1970-01-01 instead of the real proleptic-Gregorian date. The FormCalc Num2Date path was
already fixed (FredApps approved); this applies the same fix to every remaining conversion.
Each site uses the 400-year Gregorian cycle (146_097 days) with chrono as the only calendar
logic: chrono converts the day within one cycle, whole cycles carry in the year (i128,
saturated into i64). PDF/ASN.1 format-specific year handling is kept exactly as the old code
had it. Verified the cycle result equals the old formula across a wide range and at the extremes.

- apps/pdfcraft/src/logging.rs `timestamp`: cycle `civil_from_days(u64)`; pre-1970 epoch clamp
  and ms/time-of-day format kept. Pins: +10_000_000_000_000 s -> 318857-05-20T17:46:40.000Z
  (was 1970); checked_add u64::MAX/2 s -> 292277026596-12-04T15:30:07.000Z (was epoch).
- crates/cos/src/writer.rs `pdf_date` (i64): cycle converter, no year clamp (old code wrote `y`
  as-is, so a negative year keeps its sign). Pins: pdf_date(10_000_000_000_000)="D:3188570520174640Z",
  pdf_date(-10_000_000_000_000)="D:-3149180813061320Z".
- crates/sign/src/der.rs `Time::from_unix` (i64): cycle converter; the `year` field is a u32, so
  the year is cast `as u32` (wrapping) exactly as the old code did — ASN.1 year width preserved
  byte-for-byte. Pins: from_unix(10e12).year=318857, from_unix(-10e12).year=4_294_652_378,
  from_unix(i64::MAX).year=219_250_468, from_unix(i64::MIN).year=4_075_720_767.
- crates/ui-egui/src/combine_ui.rs `utc_stamp` (u64): cycle converter; pre-1970 epoch clamp kept.
  Pins: utc_stamp(10e12)="318857-05-20 17:46 UTC", u64::MAX/2 s="292277026596-12-04 15:30 UTC".
- xtask/src/demo_pdf/pdf.rs `Stamp::from_unix` (i64): cycle converter, no year clamp. Pins:
  from_unix(10e12).iso="318857-05-20T17:46:40Z"/.pdf="D:3188570520174640Z",
  from_unix(-10e12).iso="-314918-08-13T06:13:20Z".

cos/sign/xtask built, tested (pdf_dates_are_utc_and_byte_identical, der::tests::times,
stamps_round_trip), fmt and clippy clean. pdfcraft-ui-egui and the pdfcraft app can't link
here (unrelated LLVM intrinsic bug in the rten-gemm dependency, 1.98.1), so their logging and
combine_ui functions + tests were copied verbatim into a scratch crate with chrono 0.4.45 and
pass there; fmt --check clean on all five packages. CI builds these on all platforms.

* Don't build a year-318857 SystemTime unchecked in the logging test

Windows' SystemTime only reaches about year 30828, so
UNIX_EPOCH + Duration::from_secs(10_000_000_000_000) panicked before
timestamp() ran and failed the Windows test job. Build it with
checked_add and assert where the platform can hold it. The year-10000
case fits everywhere and stays unconditional.

* Name the 400-year range extension as the one exception to the time rule

The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.

* Say the library computes month and day; time-of-day is plain arithmetic

Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
2026-10-21 19:36:46 -04:00
6898094e77 M6: renaming a field renames it in the calculations that read it (#3550)
* M6: renaming a field renames it in the calculations that read it

A rename silently broke every formula that used the field: a sum dropped
it, notation read it as 0 and a script kept a stale value. set_props now
rewrites the name in each field's Calculate script, in the same edit:
AFSimple_Calculate field lists, simplified field notation (the BVCALC
comment and its translation) and getField calls naming it in one string
literal. The rest of each script is kept as written; names built at run
time, non-UTF-8 script streams and a name another field still has are
left alone, and a self-check keeps a script whose meaning would change.

Notation is read by one lexer shared by notation_js, eval_notation and
the rename. form_set_props returns formulas_updated; Field Properties
says how many calculations now use the new name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z

* M6: form_set_props leaves the renamed field out of formulas_updated by object

The renamed field was matched by the name given, but set_props stores it trimmed and under
its parent's prefix. When the same call changed the field's own formula, formulas_updated
listed the field as one the rename had rewritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
2026-10-21 19:25:43 -04:00
6dec57bf80 M7: lay out Format text as Acrobat's panel (#2249)
* M7: lay out Format text as Acrobat's panel

Format text was a row of separate combos, a Load font button, an
Installed fonts combo, a swatch grid and labelled drag values. It now
follows Acrobat's arrangement: the font across the panel (the page's
fonts, the standard families, Load font… and Installed fonts in one
list, showing the paragraph's own font by name); size and a colour
swatch, then lists and an alignment flyout; bold, italic, underline,
superscript and subscript; line and paragraph spacing; horizontal scale
and character spacing. One panel serves an edited paragraph and added
text. What PdfCraft can't apply yet (lists, superscript and subscript,
paragraph spacing, spacing for added text) is shown disabled and says
it is planned.

Icons stay Lucide's: superscript, subscript, pilcrow and
move-horizontal are added from lucide-static 1.49.0 with attribution.
Eight new strings are translated in every catalogue.

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

* M7: parity notes for the Format text panel

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

* i18n(vi): Superscript and Subscript for Format text

The Vietnamese catalogue came from main without the Format text panel's
Superscript and Subscript labels; vietnamese_covers_ui_literals lists them.

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

* M7: Format text takes any horizontal scale and character spacing again

Review of #2249: the panel's preset lists narrowed what could be set to
50-200 % and -1..5 pt, where the old fields took 10-400 % and -5..50 pt.
Each now has a number field over the old range again, with the list of
common values beside it.

Test: the_format_panel_takes_any_scale_and_character_spacing types values
between the listed ones and beyond them, saves and reopens, and compares
the paragraph with the engine's own rewrite. It fails without the field.

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

* M7: Format text's rows line their controls up and fit the panel

Raised in #3037: the size list sat lower than the colour swatch and the
alignment button beside it. egui lays a list out from the default control
height and grows it downward, so a row's shorter controls ended up off its
middle. Each row is now one control height and centres what it holds, at
a list's own height.

The spacing rows' columns are measured from what they hold, so the second
list no longer runs past the panel; the number fields' lists open from a
small arrow, which leaves the field the room its value needs.

Test: format_text_rows_line_up_and_fit_the_panel checks the size list is
centred with the swatch and alignment button and as tall as the font list,
that each spacing row shares one line, and that nothing passes the panel.
It fails without the change.

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

* Format text: choosing the family a font name reads as still changes the font

CodeRabbit on #2249: a Calibri paragraph is shown as Helvetica, so picking Helvetica left
look.family equal to look0.family and no family change was sent. The pick is now tracked
(LineEditor::choose_family) and counts as a change in style(), font_shown(), has_changes()
and the editor preview; Keep source fonts and an applied edit clear it.

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

* Format text: the reusable text style follows a selection only when its edit was applied

A refused UpdateContent used to leave the new style in app.text_style, so the next
added text inherited formatting the selected text never received.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jk4iYSh3mEd145niec77LF

---------

Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 18:26:28 -04:00
193754502f M3: the Tools panel has no tab strip while docked alone (#2244)
A "Tools" tab sat over the tool panel although nothing else shared its
group; Acrobat's tool pane has no such strip. Tools docked on its own is
now drawn bare. Grouped with another panel, or floating, it keeps its
tab so it can be dragged away again, and its side stays a preference
and a dock command. The docking test groups Inspector with Tools by
command, since there is no tab to drop on.

Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
2026-10-21 18:11:05 -04:00
18b61e2401 M4: Organize pages remembers its page size, as Acrobat does (#2282)
The grid's size was kept per document view and reset to 100% for every document and every
start. Acrobat keeps the size last chosen, observed in its Organize Pages. It is now one value
for the app, saved with the other settings like Combine's zoom, and --grid-zoom sets it too.
A damaged saved value is ignored or clamped to 50-300%.

Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
2026-10-21 18:06:54 -04:00
PiohrunandClaude Opus 5.5 d90cee4669 fix(render): read each object stream's index once (vendored hayro-syntax patch 22) (#2427)
* fix(render): read each object stream's index once (vendored hayro-syntax patch 22)

`XRef::get_with` parsed an object stream's whole index of `objnum offset`
pairs again for every object it fetched from that stream, so reading all of a
stream of N objects took N² steps. Firefox keeps every object of a page in one
stream: the pdf.js corpus file bug1978317.pdf (65,542 objects in one stream)
took 37 s to draw and 40 s to extract its text, and was a known timeout in the
robustness baseline.

The parsed index is now kept per stream, beside the decoded stream data it
indexes (`ObjectStreamIndexes`), and never cleared, as that data isn't. A
malformed index is remembered as malformed; `/N` or `/First` that can't be
read (a reference that can't be followed while another stream loads) is not
remembered. An offset past `usize` is malformed instead of wrapping. A poisoned
lock is still used: entries are inserted whole.

Release build, Linux: bug1978317.pdf draws in 0.5 s (was 37 s) and extracts
its text in 1.9 s (was 40 s), and leaves the baseline's known timeouts;
40,000 pages in one stream open in 0.2 s (was 54 s).
`raster::tests::objects_in_one_large_object_stream_are_read_in_linear_time`
times out at 20 s with the fix reverted and passes in 0.2 s with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MoEx9Nj8KC4WKDcf5YaokQ

* test(render): an object stream offset past usize is malformed, not a panic

Review follow-up on #2427. Mutation testing by mtlcl410 found that replacing
patch (22)'s `checked_add` in `read_object_stream_index` with a plain `+`
survived every test, although in a debug build that `+` panics on the very
input it guards against (and wraps in a release build). Their test pins it:
it fails with "attempt to add with overflow" under that mutant and passes on
this branch.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 18:01:35 -04:00
583c1ec68c M3: the mode bar has no Read tab, as in Acrobat (#2240)
* M3: the mode bar has no Read tab, as in Acrobat

Acrobat's mode bar is All tools, Edit, Convert and E-Sign: reading is what
the viewer does with no tool in use. Read mode (the panes hidden) stays a
View command and a default workspace. Tests that clicked the Read tab as an
ordinary mode-bar button use Convert instead.

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

* M3: parity notes for the mode bar without a Read tab

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

* M3: the Read-mode tests enter through the View command

The mode bar no longer has a Read tab, so `read_mode_hides_the_quick_toolbar`
(ui.rs) and `switching_modes_keeps_the_current_page_at_fixed_zoom` (view.rs)
clicked a button that is gone, failing the test jobs on ubuntu-latest and
windows-latest. Both run `view.read_mode` now, as the other read-mode tests in
this PR do; the second is the test the `view.read-mode` parity entry cites.

`cargo test -p pdfcraft-ui-egui --test ui --test view`: 56 and 55 passed.

---------

Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 17:34:16 -04:00
PiohrunandClaude Opus 5.5 8f3456f29c fix(engine): PNG export writes opaque pages as RGB at deflate level 3 (3–5× faster, same size) (#2425)
* fix(engine): PNG export writes opaque pages as RGB at deflate level 3

`encode_png` wrote every raster as RGBA at the png crate's default level
(zlib level 6). A rendered page is always opaque, so the alpha channel only
made the file bigger and slower to write. An opaque raster is now written as
RGB, at deflate level 3 with adaptive filtering; a raster with translucent
pixels is still written as RGBA with straight alpha.

The files come out the same size, 3-5x faster (release build, Linux):

  300-dpi scan drawn at 220 dpi   805 -> 167 ms   4.46 -> 4.47 MB
  text page at 220 dpi             70 ->  40 ms   0.85 -> 0.89 MB
  annual-report page at 150 dpi   179 ->  64 ms   1.41 -> 1.38 MB

This is the encoder behind Export > Image (PNG), `pdfcraft-cli render` and
Print as Image; the automation tools (`page_render` and the other page
previews, MCP included) had a copy of it and now call it instead, so png is
only a dev-dependency of pdfcraft-automation. Two automation tests that read
`page_render` output as RGBA now accept the RGB a page renders as.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MoEx9Nj8KC4WKDcf5YaokQ

* fix(engine): PNG export rejects a raster of the wrong length

encode_png now checks that the buffer is exactly width × height × 4 bytes
(checked arithmetic) before choosing RGB or RGBA. The opaque path's
as_chunks::<4>() dropped a partial trailing pixel, so encode_png(1, 1,
&[10, 20, 30, 255, 0]) wrote a valid 1×1 PNG where the old RGBA encoder
returned an error.

Regression test export::png_tests::rasters_of_the_wrong_length_are_rejected
covers opaque and translucent buffers with a partial pixel, a pixel short,
a pixel too many, an empty buffer and a size whose byte count overflows; it
fails on the previous head at the reviewer's case.

Review: https://github.com/storytold/pdfcraft/pull/2425#discussion_r4281603405

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MoEx9Nj8KC4WKDcf5YaokQ

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 17:34:11 -04:00
322a1b2ac0 M7: lay out the Edit panel as Acrobat's (#2245)
* M7: the Edit tab turns on Edit text & images, as in Acrobat

Entering Edit boxes every paragraph and image on the page at once, so text
is editable with one click instead of first finding Edit text & images in
the tool list. The floating toolbar gets an Edit text & images button while
Edit is open or the tool is in use. Leaving Edit (or turning the tool off
there) applies a paragraph still open in the editor, as clicking away from
it does; left open, its page kept showing the paragraph's text removed.

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

* M7: parity notes for the Edit tab turning on Edit text & images

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

* M7: the panel-reveal test looks for the Edit panel's own row

With Edit text & images on, its name is on the floating toolbar as well as in the panel, so
the label alone matched twice and no longer showed that the Tools tab was revealed.

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

* M7: lay out the Edit panel as Acrobat's

Under the Edit tab the panel is titled Edit, with Preferences and Close;
Modify page is a row of icon buttons (rotate, insert, delete, extract) and
Organize pages; Add content lists Text, Image, Header and footer, Watermark
and Link, with the rest under More; Other options links Combine files,
Redact a PDF and Prepare a form. The milestone banner and Ready chips are
gone; a planned item is muted and says when it ships. Every row still
comes from the catalogue. Edit text & images is on the floating toolbar.

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

* M7: Organize pages in the Edit panel opens the Organize workspace

It ran page.organize, which showed the page grid behind the Edit panel;
Acrobat replaces the panel with Organize pages' own. It now selects the
Organize tool, so its panel and the grid open together.

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

* M7: translate the Edit panel's Less and Other options

Every catalogue gets the two strings the Acrobat-style Edit panel adds.

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

* chore(i18n): refresh the catalogue hashes in ATTRIBUTION.toml

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

* M7: no idle selection hint above Format text in the Edit panel

"Shift-click to select several objects. Drag empty space to select an
area." sat at the top of the Edit panel whenever Edit text & images was
on, pushing Format text down; Acrobat's panel has no such line. The
selection count and how to move it still show while objects are
selected. Tests that read the hint as "nothing selected" check that no
selection count shows instead.

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

* M7: let auto-deref find the Edit panel's buttons (clippy)

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

* M7: the Edit panel keeps main's Add / Update / Remove menu

Main gave Header and footer, Watermark and Background a menu of Add,
Update and Remove in the tool panel (#1857). This branch draws the Edit
group in its own panel, so those rows ran only Add and Update / Remove
were unreachable there. Both panels now share one helper for the caret
and the menu.

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

* Edit panel: Modify page buttons follow command enablement; Organize pages with no document asks for a file

CodeRabbit on #2245: the four Modify page buttons were enabled from the catalogue
(always Ready) while execute() refuses them on a document that forbids page assembly;
they now use command_enabled. Organize pages with no document open selected a panel
with no grid to show; it now runs page.organize, which asks for a file first.

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

---------

Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 17:33:59 -04:00
Dylan M. Taylor b1bf1cf01f ci: add sonar-project.properties for CI-based analysis
Marks the project ineligible for SonarCloud Automatic Analysis, resolving the
CI-vs-automatic-analysis conflict so the SonarCloud workflow succeeds.
2026-10-21 16:00:45 -04:00
Dylan M. Taylor 30b8c60b55 ci: add SonarCloud scan via shared reusable workflow
Calls storytold/craft-common-actions/.github/workflows/sonarcloud.yml@main
with secrets: inherit. Adds SonarCloud badge to README where missing and
removes the redundant sonar-project.properties (config now passed by the
reusable workflow).
2026-10-21 15:47:03 -04:00
mtlcl410andClaude Opus 5.5 81c08be3ea M6: a field without /TU shows no tooltip of its own (#3854)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3niW7kVeT8mi7TpWc8eAJ
2026-10-22 03:37:01 +08:00
mtlcl410andClaude Opus 5.5 33150203a8 M6: test the read-only and blank-/TU tooltip cases (#3854)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3niW7kVeT8mi7TpWc8eAJ
2026-10-22 03:37:01 +08:00
mtlcl410andClaude Opus 5.5 7ddb6226b8 M6: hovering a form field shows its tooltip (#3854)
The field's /TU is read into FormField::tooltip and shown in the Fields
panel, but resting the pointer on the field on the page showed nothing.
page_input now attaches it to the hover, as Acrobat does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3niW7kVeT8mi7TpWc8eAJ
2026-10-22 03:37:01 +08:00
Brandon ThomasandClaude Opus 5.5 3e3064532f Release: PdfCraft v0.11.0 (#3893)
* Release: PdfCraft v0.11.0

- Bump the workspace to 0.11.0.
- Fix main's red CI (cargo-deny): update rustls-webpki 0.103.15 -> 0.103.16
  (RUSTSEC-2026-0382) and webpki-root-certs 1.0.9 -> 1.0.10 (RUSTSEC-2026-0383),
  both reached through winprint's sigstore build dependency.
- `cargo xtask version set` now moves the workspace crates' pins in
  [workspace.dependencies] with the version; before, `cargo update --workspace`
  refused the bump and the pins had to be edited by hand.

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

* Tests: a healthy private retry may answer during the watchdog pass that scheduled it

shared_timeouts_retry_healthy_pages_privately_before_blacklisting failed on
CI (PR #3893): the try_recv that runs the watchdog spawns the private retries,
and page 1's retry has no gate, so on a loaded runner its healthy result
arrived before that same call read the channel. Accept that early answer
(page 1, no error) and use it as the healthy result; an error still fails.
Reproduced by sleeping 300 ms after the watchdog in try_recv: the old test
fails every time, the new one passes.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
v0.11.0
2026-10-21 15:27:33 -04:00
Unhumanje d335ff375d fix(xfdf): a field name full of periods no longer overflows the stack on export
The XFDF export nested one <field> element per period-separated part of a field's full name. A /T can hold periods of its own, so a name can have far more parts than the field tree has levels. Past 64 levels the import refuses the file the export wrote, and around 100,000 parts the recursive write overflows the main thread's stack and aborts the process.

The export now nests at most as many levels as the import reads back. The parts past the last level stay in the deepest element's name, and the import joins them back into the same full name.
2026-10-21 11:17:56 +02:00
Dylan M. Taylor 4164951453 Merge pull request #2753 from dylanmtaylor/ci/shared-nix-workflow
ci: switch to shared nix workflow from craft-common-actions
2026-10-21 03:59:40 -04:00
Dylan M. Taylor d7481cca6e Merge remote-tracking branch 'origin/main' into pr-2753
# Conflicts:
#	.github/workflows/nix.yml
2026-10-21 03:41:36 -04:00
skylar taylor-barrick d695bc5654 fix(redact): evaluate OCMD visibility during sanitization (#3522)
* fix(redact): evaluate OCMD visibility during sanitization

Fixes #3499 by evaluating OCMD policies and bounded visibility expressions against the default optional-content configuration before removing hidden groups. Unknown or malformed visibility data is handled conservatively.

* fix(redact): preserve OCMD visibility during sanitization

* fix(redact): count hidden XObjects during verification

* test(redact): cover hidden form depth limit
2026-10-20 18:17:34 -06:00
Brandon ThomasandClaude Opus 5.5 22bedbf2af Skip CodeRabbit reviews on Dependabot PRs (#3692)
Adds .coderabbit.yaml so CodeRabbit no longer reviews or comments on PRs opened by dependabot[bot].

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:38:59 -04:00
Brandon ThomasandClaude Opus 5.5 b77d57000c Release 0.10.1: rebuild with the shared workflow's exact Linux tarball pick
0.10.0's Arch Linux and Flatpak aarch64 jobs picked up the
pdfcraft-cli tarball (storytold/craft-common-actions#35).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.10.1
2026-10-20 11:38:32 -04:00
Dylan M. Taylor 59a5a83774 docs: add SonarCloud and CodeRabbit badges to README (#3271)
* docs: add SonarCloud and CodeRabbit badges and standardize to flat-square

* docs: put SonarCloud and CodeRabbit badges on their own line
2026-10-20 11:29:59 -04:00
Eric Kaden ad453129f0 M5: doc comments for the functions around this change 2026-10-20 09:23:23 -04:00
Ahmad Mujahid 25eb568839 fix(forms): preserve content stream comment boundaries (#3502) 2026-10-20 13:30:02 +04:00
Eric Kaden 56b316a7f4 Forms: a radio group's read-only button can't be chosen by a click 2026-10-20 00:35:54 -04:00
Eric Kaden 0a77db066c Forms: a radio group's read-only button is left unhighlighted on its own 2026-10-20 00:20:02 -04:00
Eric Kaden 77eb328c83 Forms: doc comments for the functions this change touches 2026-10-19 23:42:37 -04:00
Eric Kaden 465bd0a717 M5: doc comments for the functions this change touches 2026-10-19 23:41:47 -04:00