Commit Graph
1507 Commits
Author SHA1 Message Date
aiongg bf3a199fbb Merge PR #2505: G14: Romanian (ro) interface catalog
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: both sides' language rows, arrays sized 28.
crates/ui-egui/tests/fonts.rs: both catalog lists.
ATTRIBUTION.md: both sides' rows; count regenerated after all merges.
ROADMAP.md: main's summary with Romanian added (15 more catalogs); both log lines.
docs/localization-parity.md: main's rows plus the ro row; 28 interface languages; both revision rows.
docs/localization.md: main's text with Română, the ro locales and code, and the PR's catalog-list edits.
2026-10-22 01:52:53 +00:00
aiongg 13b21c7f64 Merge PR #2227: M14: add Catalan interface and date localization
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: both sides' language rows, arrays sized 27.
crates/ui-egui/tests/fonts.rs: both catalog lists; Catalan added to main's comment.
ATTRIBUTION.toml/.md: both sides' assets; hashes and counts regenerated after all merges.
ROADMAP.md: both log lines.
docs/localization-parity.md: main's rows plus the ca row; 27 interface languages; both revision rows.
docs/localization.md: main's text with Català, the ca locales, code and catalog paragraph added.
2026-10-22 01:52:24 +00:00
aiongg 7848fc40db Integration: #2141 ATTRIBUTION entry for the Thai catalog 2026-10-22 01:51:35 +00:00
aiongg b94393400d Merge PR #2141: i18n: add Thai (th) interface language
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: both sides' language rows (fi, sq, th), arrays sized 26.
2026-10-22 01:51:26 +00:00
aiongg f0f4aeeb19 Merge PR #2079: i18n: add Albanian (sq) interface language
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: both sides' language rows (vi, fi, sq), arrays sized 25.
crates/ui-egui/tests/fonts.rs: both catalog lists; Albanian added to main's comment.
ATTRIBUTION.toml/.md: both sides' assets; hashes and counts regenerated after all merges.
ROADMAP.md: both log lines.
docs/localization-parity.md: main's rows plus the sq row; 25 interface languages; both revision rows.
docs/localization.md: main's text with Shqip, the sq locales and code added.
2026-10-22 01:51:09 +00:00
aiongg b4f762ce45 M14: complete the French interface catalog (267 entries) (#1594)
Squash of PR #1594 (its branch history holds a reverted rewording commit).
crates/ui-egui/src/i18n/fr.tsv: both sides' entries, no duplicate keys.
2026-10-22 01:50:12 +00:00
aiongg 3da841f3a4 Merge PR #1523: Ita Translate
crates/ui-egui/src/i18n/it.tsv: main's catalog plus the 11 PR rows for keys it lacked.
For 68 keys main already translates differently, main's text is kept; 133 PR rows
were identical to main's.
2026-10-22 01:49:56 +00:00
aiongg 51a5d4ef51 Integration: #3819 move name_sort_key above the test module 2026-10-22 01:49:30 +00:00
aiongg e529198ad4 Merge PR #3819: Preferences: list the languages alphabetically by their own name 2026-10-22 01:49:20 +00:00
aiongg 456b96d06b Merge PR #2454: M5: year-first slash dates (yyyy/mm/dd, yyyy/m/d) for Fill & Sign and headers 2026-10-22 01:49:17 +00:00
aiongg b7999e8766 Merge PR #3603: M14: Spanish catalog covers the bundled key union
ATTRIBUTION.toml: kept main's sha; rehashed after all merges.
ROADMAP.md: summary names Vietnamese, Brazilian Portuguese and Spanish against the current union; both log lines.
docs/localization-parity.md: main's status line with the Spanish change added; the PR's denominator paragraph naming Brazilian Portuguese too; the PR's lt row plus main's fi row; revision rows newest first.
2026-10-22 01:49:15 +00:00
aiongg 1acb402e0a Integration: #2452 Polish for the two snapshot notices; English for five panel labels
The five panel labels (Panels, Float panel, Move to group, Resize panels,
Resize panel window) stay English until a translator fills them, so that
polish_covers_ui_literals passes.
2026-10-22 01:48:52 +00:00
aiongg 0682211d66 Merge PR #2452: M14: Polish says what a signature says, and the rest of the interface
ATTRIBUTION.toml: kept main's sha; rehashed after all merges.
ROADMAP.md: both progress-log lines.
crates/ui-egui/src/i18n/pl.tsv: both sides' appended entries, no duplicate keys.
parity/acrobat-features.toml: main's single notes line with the PR's Polish clause applied.
2026-10-22 01:48:33 +00:00
aiongg 342b7e2243 Merge PR #3907: M14: Italian catalog: the 8-character digital ID password message
ATTRIBUTION.toml: kept main's sha; rehashed after all merges.
crates/ui-egui/src/i18n/it.tsv: both sides' appended entries.
2026-10-22 01:48:12 +00:00
aiongg ee092caf56 Integration: #3827 revert the unrelated vendor/README.md hunk 2026-10-22 01:48:06 +00:00
aiongg 470c3a4f8c Merge PR #3827: M14: complete Brazilian Portuguese catalog
ATTRIBUTION.toml: kept main's sha; rehashed after all merges.
ROADMAP.md: both progress-log lines, newest first.
docs/localization-parity.md: the PR's status line; both revision rows, newest first.
docs/roadmap.md: the PR's rows (main's rows plus the PR's wording).
2026-10-22 01:47:56 +00:00
aiongg 2670b6c607 Merge PR #2758: M14: complete and review the Czech catalog
ATTRIBUTION.toml: kept main's sha; rehashed after all merges.
ROADMAP.md: both progress-log lines, newest first.
crates/ui-egui/src/i18n/cs.tsv: both sides' entries, no duplicate keys.
docs/localization.md, docs/roadmap.md: main's single paragraph and row, with the PR's Czech wording applied.
2026-10-22 01:47:23 +00:00
aiongg 0792e0a8c7 Merge PR #1778: docs: keep one copy of the catalog paragraph in localization.md 2026-10-22 01:46:22 +00:00
aiongg e3afcaaae1 Integration: #2210 fill the 46 keys Finnish lacks with English text
Missing translations never block; the Finnish coverage tests need every key
the other catalogues carry. The keys stay English until a translator fills them.
2026-10-22 01:46:18 +00:00
aiongg 9f44f24448 Merge PR #2210: Add Finnish interface and date language
ATTRIBUTION.md: kept main's asset count; regenerated after all merges.
2026-10-22 01:45:16 +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
johnpetersa19andCopilot App 589a195117 Merge latest main and resolve roadmap conflicts
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-21 21:02:13 -03:00
johnpetersa19andCopilot App 1227ab72a5 docs: consolidate hayro-syntax vendor record
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-21 21:01:30 -03:00
johnpetersa19andCopilot App b8b09d315f docs: remove shipped Vietnamese from M14 missing lists
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-21 20:56:30 -03: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
johnpetersa19andCopilot App 52b4a4454f Merge origin/main and resolve localization conflicts
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-21 20:53:52 -03: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
kcobelli-pixelandClaude Opus 5.5 57d41762b5 M14: Italian catalog: the 8-character digital ID password message
Create a new digital ID rejects a password shorter than
sign::pkcs12::MIN_PASSWORD_CHARS (8) with "The password must have at
least 8 characters.", which the dialog shows through tl!. Italian only
had the older 6-character wording, so the message appeared in English.
Czech, Lithuanian and Vietnamese already carry the current key.

The other 67 keys some catalogs have and Italian lacks are not looked
up by the code: shortcut tooltips the code now fills from {key} rows,
older wordings, plain-string plurals and generic labels only ar/lt/vi
carry. The catalog's SHA-256 in ATTRIBUTION.toml is updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 21:44:49 +02: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
johnpetersa19andCopilot App 8ea9049809 M14: update Brazilian Portuguese parity notes
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-21 08:19:51 -03:00
johnpetersa19andCopilot App 4eaba2bbcd M14: complete Brazilian Portuguese catalog
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-21 08:14:32 -03:00
Nikolai Nyegaard ca56271678 Preferences: list the languages alphabetically by their own name
- English (and Auto where offered) stays first as the source language and fallback; the rest sort by native name with accents folded, so Čeština is under C and Magyar under M; Cyrillic and CJK names follow the Latin ones
- The list used the registry order, which is the order the languages were added
2026-10-21 12:01:37 +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
Janiz-zzzandClaude Opus 5.5 0a78f31fec Add Finnish interface and date language
Adds a Finnish (Suomi, `fi`) catalog with 2,592 entries: every key of
the other bundled catalogs, written in sentence case from the English
labels. Finnish uses the one/other plural rule with the partitive
singular after any number but 1 (1 sivu, 2 sivua, 0 sivua).

Finnish is also a date language: calendar headings use the nominative
month (Lokakuu 2026) and dates the partitive (14. lokakuuta 2026).
Any fi-* locale selects it.

Tests cover registration, locale tags, persistence, plurals, calendar
wording, commands/tools, scanned UI literals, every bundled catalog key
and the Latin UI fonts. The catalog has its ATTRIBUTION.toml entry
(ATTRIBUTION.md regenerated with `cargo xtask assets --write`).

Clean-room, AI-assisted (Claude Code) translation; not yet proofread
line by line by a native speaker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 09:10:16 +03: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