Commit Graph
1132 Commits
Author SHA1 Message Date
mtlcl410andClaude Opus 5.5 36699aeaf0 Merge storytold/pdfcraft main into feat/edit-text-in-forms
Conflicts:
- crates/edit/src/images.rs, crates/engine/src/doc_state.rs, crates/engine/src/edit/labels.rs,
  crates/automation/src/dispatch.rs: main added Transform, Arrange and RemoveCrop next to this
  branch's Ungroup; kept all.
- crates/edit/src/lib.rs: kept `mod ungroup;` with main's export list (ArrangeOrder).
- crates/engine/src/edit/run.rs: main moved EditPageImage into run_edit_page_image; carried
  the Ungroup arm there, and Ungroup goes through change_image on the added-image path too.
- crates/automation/src/tools.rs: image_edit takes main's description and action list, with
  this branch's ungroup added to both.
- crates/ui-egui/src/tools/edit_text_ui/image.rs: main rebuilt the image context menu; took
  it and carried the Ungroup item (Form artwork only) before Replace image.
- crates/automation/tests/it/automation.rs, crates/edit/src/tests.rs,
  crates/ui-egui/tests/it/editing.rs: both sides appended tests; kept both.
- crates/ui-egui/src/i18n/{ca,es,fi,hi,id,ko,ro,sq}.tsv: both sides appended; kept both,
  main's lines first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-11-02 21:39:20 +08:00
d3384340a6 M7: make page-mark replacement transactional (#5657) (#5704)
* M7: make page-mark replacement transactional (#5657)

* ROADMAP: drop the header line (the log entry stays)

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 23:49:18 -05:00
FredApps 0cba87ae6f Merge pull request #5677 from Arch-amon/arclight/freehand-hotbar
feat(ui,annot): customizable hotbar strip, freehand highlight, and moveable navigation rail
2026-11-01 23:34:38 +01:00
FredApps 616ddca371 M8: images stored again without shared JBIG2 globals are stored as a cleared copy is
An image that shared a redacted image's /JBIG2Globals is now stored
again as the smallest of Flate, Group 4 or JBIG2 coded alone (smallest),
like a cleared copy, rather than always Flate, so the other pages of a
scan don't balloon either. The shared-globals test's second page is a
ragged scan and checks it is no longer Flate and is smaller.
2026-11-01 22:48:29 +01:00
FredApps 54d0ea7c59 Merge branch 'main' into feat/redact-bilevel-codec 2026-11-01 22:44:58 +01:00
FredApps 4d8eede62a Merge pull request #5472 from Piohrun/feat/mono-jbig2
M11: Monochrome Images reads JBIG2 scans: averaged like any, and stored again as JBIG2 (optimize.mono-compression)
2026-11-01 22:44:16 +01:00
FredApps 2850d52825 Merge pull request #5471 from Piohrun/feat/mono-indexed-polarity
M11: Monochrome Images averages indexed black-and-white images, the palette saying which sample is dark (optimize.downsample-images)
2026-11-01 22:43:06 +01:00
FredApps 99e63d4238 Merge pull request #5461 from Piohrun/feat/redact-jbig2
M8: redaction clears the covered pixels of JBIG2 scans instead of removing them (protect.redact-image-pixels)
2026-11-01 22:39:07 +01:00
FredApps a87c4fb6b7 M8: redaction stores again without them the images that share a redacted JBIG2 image's globals, so the globals leave the file (protect.redact-image-pixels)
A JBIG2 image's /JBIG2Globals hold the symbol dictionary of every image that
shares them (jbig2enc -s and most scanners give one to all of a scan's pages),
so the bitmaps of marks drawn only under a redaction stayed in the file while
another page's image used them. Every image using the globals of a removed or
replaced image (or of one in a removed or replaced form) is now decoded and
stored again in place as Flate without them; a full save then drops them. One
that can't be decoded for certain refuses the redaction
(RedactError::SharedJbig2Globals). Report::images_restored counts them.
2026-11-01 22:14:52 +01:00
archamon 53c79ca9b0 feat(ui,annot): customizable hotbar strip, freehand highlight, and moveable navigation rail
- Add Freehand-Highlight tool with 100pt line width support, Lucide highlighter icon, and PDF Multiply blending.
- Add customizable floating and docked hotbar with command picker, size slider, full-side strip chrome integration, and settings gear icon anchored at the bar end.
- Add left/right docking options for the navigation rail with toggle button and preferences setting.
2026-11-01 20:23:12 +00:00
Livio OswaldandClaude Opus 5.5 2242eb322f M10: Create ▸ Blank page uses the system region's paper (#5644) (#5651)
* M10: Create ▸ Blank page uses the system region's paper (#5644)

A new blank document was always US Letter. It now takes the paper of the
system region: Windows' own Region paper size (iPaperSize) first, then the
system locale on macOS or LC_ALL/LC_PAPER/LANG, A4 everywhere outside CLDR's
US-Letter regions, and Letter when the region can't be told (web).
The region parsing is shared with the Units preference.

* M10: the first locale variable set decides the paper; extensions are not regions (#5644)

LC_ALL, LC_PAPER and LANG are read in POSIX order and the first one set
decides, so LC_ALL=C no longer falls through to LANG's region; and a locale
tag's region is read from its base tag only, so en-US-u-nu-latn is US, not
NU (CodeRabbit on #5651).

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

* M10: the blank page test covers A4 as well as Letter (#5644)

The app keeps the paper Create ▸ Blank page makes (blank_paper, the system
region's at start), so the test makes a blank page on A4 and on Letter
whatever the host's region (CodeRabbit on #5651).

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-01 15:04:52 -05:00
VoiceLessQ 424ca0e972 M14: Preferences sets how many recent files to keep (#5572) (#5675)
The list was fixed at 12. Preferences ▸ Documents and view now takes a
number from 0 to 50, with 12 as the default. At 0 Home has no Recent
section and File ▸ Open Recent is disabled.

The list is cut when Preferences closes, so the number can pass through
0 on its way to another. Until then Home and saved settings already
leave out what is past it. A settings file can't ask for more than 50.

The control channel and launch options take it as recent-files. The two
new strings go in every catalog as English copies.
2026-11-01 13:52:47 -05:00
FredApps b3023b4b28 Merge pull request #5624 from Piohrun/feat/xfa-rotate
M12: an XFA draw placed by position turns about its anchor
2026-11-01 19:43:57 +01:00
FredAppsandCodex GPT-6.1 e7762568cc docs(text): define mixed-script normalization and undo policy
Document the existing policy: mixed plain and target-script text normalizes to that script, then to plain text, rather than reconstructing the original mixture. Undo restores exact prior text. Cover both scripts in engine and headless tool tests, including document-byte restoration, redo and save/reopen, and record the evidence in parity. The new tests pass against unchanged conversion logic; this is policy documentation and coverage, not a product behavior fix. Scoped Linux native, wasm, clippy, formatting, layers, assets and parity validation passed.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 16:12:39 +01:00
FredAppsandCodex GPT-6.1 faf85f5af9 fix(text): apply explicit sizes and preserve script characters
An explicit size commit normalizes mixed-size selections even when the value matches the displayed size. Caret-only script commands format the whole draft. Mathematical minus uses reversible Unicode forms and ASCII hyphen-minus remains unchanged. Correct the applied-selection comment and document the character policy in parity and the tool.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 14:51:27 +01:00
FredAppsandCodex GPT-6.1 1184bd02cb docs(edit): record safe crop and arrange semantics
Describe editor-owned crop removal, preserve authored clipping paths, and link the scope/state regressions in parity evidence. Record the maintainer changes without changing readiness estimates.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 13:00:26 +01:00
FredAppsandClaude Opus 5.5 8abd35cf67 feat(automation): text_script tool for superscript and subscript
Format text's Superscript and Subscript had no headless path (AGENTS §3):
text_script toggles them on a paragraph, or a byte range of its editor text,
through the engine's text_script and writes the paragraph as text_edit does.
The parity entry now lists the tool and its tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:34:56 +01:00
FredAppsandClaude Opus 5.5 b5ddf2cc80 feat(automation): image_edit transform action
A resize of a turned image sends ImageEdit::Transform, which had no
headless path (AGENTS 3). image_edit now takes action transform with
matrix: six numbers in top-left-origin points, like the tool's other
geometry, applied after the image's placement. The tool converts it to
user space through the page's view mapping, and refuses a matrix that is
not finite, is flat (a·d − b·c = 0) or moves the image out of range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:33:50 +01:00
FredAppsandClaude Opus 5.5 29913dcfca Merge current main into #2155
Brings the branch up to date with main (47f42ba0), which moved 37 commits since
the merge base, including page-edge shadows (#2088), full rewrite after page
delete (#4279) and the licence-check stage (#2212). No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:23:43 +01:00
Grzegorz MartensandClaude Opus 5.5 2afbf55cd2 M12: an XFA draw placed by position turns about its anchor
Nothing read rotate, so a turned line, picture or label was drawn as if it
stood upright: part of #5561. XFA gives the angle in degrees counter-clockwise
about the anchor, and Acrobat's flattened page for that issue's form puts the
three turned lines exactly there, which the test holds it to.

A turned draw is one item: a page break moves it whole, and what it holds is
written unturned under one matrix, so text, pictures and borders need nothing
of their own. The page count and link annotations look inside it, and its
container grows to the box it stands in. Fields, subforms and draws in a flow
don't turn yet.

That issue's own form is static, so it already shows Acrobat's page: this is
for dynamic forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-01 02:59:32 +01:00
FredApps f2e1a8ce29 Merge branch 'main' into fix/1577-optional-page-edge-shadows-integration 2026-10-31 23:38:40 +01:00
FredApps a471cb1664 Merge pull request #1433 from srt8hellcat2-ui/feature/two-paged-cli-1354
M3: add optional --two-paged CLI flag (fixes #1354)
2026-10-31 23:35:31 +01:00
FredApps 1d5ce9d7a9 Update shadow regression wheel events and retain upstream parity notes 2026-10-31 22:26:16 +01:00
FredApps bad4c81800 Merge current main into PR2088 and port shadow preference to split UI modules 2026-10-31 22:13:25 +01:00
FredApps 286a3c6eee Merge current main into PR1433 and preserve launch override across module splits 2026-10-31 22:12:05 +01:00
FredApps 7165ff1ace Merge current main into PR4279 and retain unsigned delete-save policy 2026-10-31 22:09:56 +01:00
Combiler01 fdc0e6404a fix: address review feedback on arrange, image flips, rotate knob, and format panel
- [P1] Arrange: track and replay inherited non-ExtGState drawing state (colors, line widths, etc.) across q/Q scopes for stencil and form artwork
- [P2] Flips: route ImageEdit::Flip through coordinate-aware transform logic to swap axes when rotated at odd quarter turns (90°/270°)
- [P2] Rotation knob: measure clockwise angle delta from drag start relative to the rotated image center to prevent reapplying existing angle
- [P2] Format panel: connect superscript/subscript controls to line editor and content draft selections, and update parity evidence
2026-10-31 23:18:58 +05:30
Combiler01 79eed72c08 Merge upstream/main into main preserving custom features
- Resolve all 13 merge conflicts between upstream/main and main
- Preserve full image editing suite: OBB, crop, proportional resize, arrange, and context menu actions
- Preserve text editing formatting tools: bullet/numbered lists, subscript/superscript, and line spacing
- Cleanly integrate upstream's canvas modularization, font updates, and styling changes
- Refactor arrange_image and run_edit_page_image helpers to satisfy clippy cognitive complexity limits
- All engine, edit, and UI integration test suites passing cleanly
2026-10-31 15:50:31 +05:30
mtlcl410andClaude Opus 5.5 b61661774e M7: Ungroup refuses a transparency group that blends in a colour space of its own (#2964, #3005)
A transparency group composites its contents in its own group colour space
(ISO 32000-2 11.6.6, Table 145 /CS) and the result is then converted to
its parent's; inlined, the same objects composite in the page group's
space (11.4.7). Colour conversion isn't linear, so translucent content and
even opaque colours can change, and Ungroup promises the same pixels.

A group /CS is now accepted only when it matches the page group's /CS, or
is DeviceRGB on a page that names none (the processor chooses that space;
viewers composite on screen in RGB). A group that names no space inherits
the page's and is fine. The tool description and the parity note say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-10-31 08:18:03 +08:00
Grzegorz MartensandClaude Opus 5.5 e0e40c6460 Merge the fixes to the PR below into this one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-10-31 01:06:26 +01:00
Grzegorz MartensandClaude Opus 5.5 6ad95f0aa4 M11: an indexed image's palette is read once it matters, within 4 KiB; a colour key keeps its image
From a review of the spellings an indexed image can take:
- The palette was read for every 1-bit indexed image, resampled or not, and twice each. Images
  share a table, so 1,000 images sharing one with 16 MiB of raw data took 16 to 60 s, none of
  them resampled. It is now read only for an image about to be resampled, and a table over
  4 KiB is refused before it is copied or decoded, since no palette needs that.
- A colour key can hide the dark sample a tie goes to, so a thin rule of the other was lost: a
  white rule on a hidden black went from 255 to the backdrop's 128. An image with a colour key
  now keeps its resolution. Grey had this since #5426 and is covered too.
- `/Indexed` by reference, and a base of `[/DeviceRGB]`, are the same colour space and are read
  so. `/I`, an inline abbreviation, and a real hival aren't, and such an image is left alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-10-31 01:05:58 +01:00
mtlcl410andClaude Opus 5.5 54b450da0e M7: Ungroup carries the form's placement into the patterns it fills with (#2964, #3005)
A pattern's matrix maps pattern space to the default space of the content
stream that uses it (ISO 32000-2 §8.7.2). Inside a Form XObject that is the
form's space at the Do; drawn inline on the page it became the page's, so a
form moved by its /Matrix or a cm before the Do lost its patterns' place:
tiles out of phase or at the wrong size, a shading outside its box.

Each pattern the form selects with scn/SCN is now drawn from a copy whose
/Matrix is the pattern's own, then the form's /Matrix, then the CTM at the
Do. The form's own pattern object is unchanged for its other placements, a
pattern selected twice is copied once, and a pattern that can't be copied
that way (unreadable matrix, non-finite result, a tiling stream past the
size limit, more than 256 patterns) is refused rather than drawn elsewhere.
Nested forms and patterns inside patterns keep their own parents.

Tests: ungroup_carries_the_placement_into_the_patterns_it_selects (edit) and
ungrouping_keeps_patterns_where_the_forms_placement_put_them (automation,
four synthetic cases rendered before and after save/reopen), both seen
failing with the fix switched off. The same four cases were checked in
Acrobat Pro DC, where the unfixed output drew differently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
2026-10-31 08:04:09 +08:00
mtlcl410andClaude Opus 5.5 6eea737e14 M7: Ungroup draws Form artwork in place, so its text can be edited (#2964, #3005)
Text drawn inside a Form XObject was one piece of artwork in Edit text &
images: HTML-to-PDF output that wraps a block with a translucent picture
in a transparency group showed a whole table as a single box with no
editable text (#3005).

Ungroup (artwork right-click menu; image_edit action ungroup) replaces
that placement's Do with the form's content, wrapped as
q <Matrix> cm <BBox> re W n ... Q, its resources merged into a copy of the
page's under fresh names where they clash (fonts, XObjects, graphics
states, colour spaces incl. inline images', patterns, shadings,
properties). The form object and its other placements are untouched; a
form in a layer keeps it as /OC ... BDC ... EMC.

Refused rather than changed: knockout groups, transparency groups that
blend inside (used gs, nested forms) or are drawn translucent, blended or
soft-masked at the Do (followed per parameter across q/Q and streams),
tagged form content, content that can't be decoded strictly or parsed,
and unbalanced Q. cos gains Stream::decoded_strict_within for that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-31 08:03:39 +08:00
Grzegorz MartensandClaude Opus 5.5 4d30af00a9 Merge the fixes to the PR below into this one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-10-31 00:20:54 +01:00
Grzegorz MartensandClaude Opus 5.5 d7bd36251b Merge the fixes to the PR below into this one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-10-31 00:20:28 +01:00
Grzegorz MartensandClaude Opus 5.5 7fbb40a2c1 Merge the fixes to #5461 into this PR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-10-31 00:20:10 +01:00
Grzegorz MartensandClaude Opus 5.5 5480dbe163 M2.6: shared JBIG2 globals are read once a document; Copy Image is tested on JBIG2
From CodeRabbit's review of the stack:
- Every JBIG2 image read its /JBIG2Globals afresh, within 32 MiB each but with no
  bound across an export or a redaction. A globals stream of the file, not edited since
  it was read and with its /DecodeParms direct, now has its filters undone once a
  document and is kept for the images that share it (in every clone, which share the
  file), up to 64 MiB in all. An edited one, one added in memory, or one with
  /DecodeParms by reference (another object, which can change under it) is read each
  time as before. This saves the filters only: the decoder reads the segments afresh for
  every image, so a real symbol dictionary still costs its decoding once an image.
- view.copy-image dropped "JBIG2 inside other filters" from its missing list on the
  strength of Export's test; Copy Image now has its own: a JBIG2 picture behind
  ASCIIHexDecode is found under the pointer and copied as its pixels and as a PNG, and
  one whose page information is in its globals is read through them.

A JBIG2 image's /Width and /Height are read as given directly; one by reference is an
error rather than an image of no rows.

A /JBIG2Globals reference to no object still reads as no globals: it is a reference
to null (ISO 32000-2 §7.3.10), which an entry may hold for being absent (§7.3.7), and
the renderer draws the page so. An image that needed those globals then mostly fails
to decode; in a probe of twelve pictures that decoded with them, eleven were refused
and one decoded to other pixels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-10-31 00:06:56 +01:00
Grzegorz MartensandClaude Opus 5.5 7f9554f29b M8: a cleared black-and-white scan is stored as Group 4, or JBIG2 again, when smaller
Redaction wrote every cleared copy as Flate, so a redacted Group 4 or JBIG2 scan
page grew several times over until the optimizer ran. A cleared one-bit image of one
component is now stored as the smallest of Flate, CCITT Group 4 and, when it was
JBIG2, JBIG2 (PDF 1.4, which such a file already is), as stored, dictionary included:
a tiny image can lose by Group 4's parameters. Both are coded from the
cleared samples, so nothing of the original's code is carried over; rows Group 4 or
JBIG2 can't code (too wide) leave Flate. Images of more bits stay Flate.

Tests: a cleared one-bit Flate scan of ragged word blocks (where Group 4 is smaller
than Flate) comes out Group 4 and an eight-bit image Flate, each decoding to the
cleared samples (the whole buffer); a tiny image whose Group 4 code is shorter but whose
stream isn't, and rows wider than Group 4 codes, stay Flate and clear; the redacted
JBIG2 scan of #5461's test comes out JBIG2, smaller than
Flate, with no globals and no stream holding the original code. The tests' image
helpers decode through Document::decoded_image, as image codecs need.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:08:32 +01:00
Grzegorz MartensandClaude Opus 5.5 39eb03a0b6 M11: Monochrome Images reads JBIG2 scans: averaged like any, and stored again as JBIG2
#5461 made Document::decoded_image read JBIG2, page exactly the image's size, and
kept the optimizer's monochrome step off it until it could weigh JBIG2 again. Now
one_bit reads JBIG2 last after any filters, within the raster's allowance, and mono
treats it as it does any black-and-white image:
- above the threshold it is averaged, and stored as the compression asks, the
  smallest of what that may store; under Retain it is stored as JBIG2 again, its kind
  of compression kept;
- not resampled, it is stored again as the compression asks when that is smaller: a
  generic region becomes symbols with JBIG2 asked for, and JBIG2 behind ASCIIHex is
  stored again alone (as Group 4 where that is smaller);
- a JBIG2 image stored again refers to no /JBIG2Globals; one whose page isn't its
  size isn't read, and stays.

jbig2_images_are_left_as_they_are becomes jbig2_scans_are_averaged_and_stored_again:
a JBIG2 scan at 600 ppi, alone, behind ASCIIHex and with globals, averages to the
same samples as its Flate copy under each compression (Retain keeping JBIG2); at 300
ppi a generic region of rendered text stays under Group 4 (larger) and Retain and
becomes symbols under JBIG2 (under half the size); behind ASCIIHex it is stored again
alone, as Group 4 where the hex is spaced out; a taller page stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:05:09 +01:00
Grzegorz MartensandClaude Opus 5.5 c412093dc7 M11: an indexed image's palette says which of its samples is dark, so it is averaged too
#5426 left indexed images at their resolution rather than guess which sample is
dark. The palette says: dark_is_one now maps samples 0 and 1 through the decode array
to palette entries (rounded and clipped to hival, ISO 32000-2 §8.9.5.2) and compares
how light those entries are (`lightness`: the grey, or Rec. 601 luma over DeviceRGB).
Only DeviceGray and DeviceRGB bases count, the table a string or a stream (within
4 KiB), directly or by reference: there the palette's values are the colour, and
under /DefaultGray or /DefaultRGB no polarity is asked for. Other bases (CMYK,
calibrated, ICC-based, a resource name), two entries alike, an entry the table
doesn't hold or a hival past 255 still say nothing; separations and DeviceN still
aren't averaged, their tint transforms unread. #5426's tone guard applies as to any
image.

#5426's indexed thin-rule test now checks what it was for: with the palette either
way round and the decode array either way, the averaged image holds the averaged
DeviceGray one's samples (flipped where its samples are) and renders the same. A unit
test covers each base, both orders, an inverting decode array, the weights of luma,
the table by reference and in a stream, and every refusal; the older polarity test's
doc says what is known now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:04:42 +01:00
songzhihui 9447cdc810 M2: merge main and resolve CJK viewer integration conflicts 2026-10-31 01:14:02 +08:00
Grzegorz MartensandClaude Opus 5.5 78fc1e5568 M8: redaction clears the covered pixels of JBIG2 images instead of removing them
Part of #283. A scan stored as JBIG2 (as the optimizer writes them) is decoded
through Document::decoded_image_within, its /JBIG2Globals included, and its covered
pixels cleared in a Flate copy that refers to no globals, as CCITT fax images are.
Before, any JBIG2 image under a mark was removed whole, blanking the page.

Still removed whole (fail-closed): a JBIG2 page of another size than the image,
which decoded_image refuses (the renderer draws the whole page into the image, so
the rows cleared wouldn't be the ones shown); globals that can't be read; damaged
data; an image claiming more than one bit or one component, even where its rows
take as many bytes; a page far past the image's raster, refused before it is
decoded.

Globals no other image uses leave the file with the original on the full rewrite
(tested); globals another image still uses stay, and the README and parity note say
they may hold the shapes of marks from under the area, not where they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 18:03:06 +01:00
Grzegorz MartensandClaude Opus 5.5 ea6c329731 M2.6: JBIG2 decoding in pdfcraft-filters; Document::decoded_image reads JBIG2 images
decode_jbig2 sits next to decode_ccitt, through hayro-jbig2 as Export ▸ Images
used it, and shares CCITT's row sink. The JBIG2 page has to be the image's size:
PdfCraft's renderer draws the whole page into the image (black box: a 4-row page
behind /Height 2 fills the image box with all four rows), so the rows of a taller
page aren't the image's. The decoder decodes a page whole, so the page is checked
before it is decoded: that size, within the caller's bound as rows of one bit, and
at most 2^28 pixels (MAX_JBIG2_PIXELS; the decoder keeps a bit a pixel, about 34 MB
there).

Document::decoded_image_within decodes a JBIG2 image as it does a fax image: the
filters before it undone first, then the decoder with the /JBIG2Globals of the
image's own /DecodeParms entry (/Crypt counts in the position; fax_params and the
globals now share last_filter_parms). Globals have a limit of their own,
MAX_JBIG2_GLOBALS (32 MiB, moved from Export), and globals that can't be read are an
error rather than ignored.

Export ▸ Images reads JBIG2 through it, so its own decoder, row sink and DecodeParms
reader go, and hayro-jbig2 leaves pdfcraft-create. JBIG2 behind another filter now
exports, and so copies (Copy Image uses the same function); a page taller than the
image, which Export cut to the image's rows, now skips the image, as one of another
width did. doc_export_all_images' description no longer lists JBIG2 inside other
filters as skipped.

The optimizer's monochrome step read images through decoded_image_within and
relied on JBIG2 failing there: it now leaves JBIG2 images alone explicitly, as
before (a resampled one would come out Flate under Retain), and the README and
parity note list them as not yet handled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 18:03:06 +01:00
Grzegorz MartensandClaude Opus 5.5 c0e3c6bec5 M11: the docs say which black-and-white images monochrome downsampling averages since #5426
The integration in #5426 made downsampling conservative: dark_is_one answers only for
DeviceGray and image masks (a decode array of two different numbers or none), and not
under /DefaultGray or /DefaultRGB, and a square of four new pixels that each cover both
samples keeps the image at its resolution. The README, the crate docs, mono()'s comment
and the optimize.downsample-images parity note still said separations and indexed
images were averaged, a tie going to full ink or entry 0.

Measured on main: a 600 ppi page of the ragged-text fixture is no longer averaged
(it is with the square guard switched off), so the docs say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 17:09:02 +01:00
FredApps 4791cacd2c test: qualify conservative tone retention and format review regressions 2026-10-30 14:55:44 +01:00
FredApps d7f3e67141 fix: bound backdrop edits and withhold unsafe integration behavior 2026-10-30 14:45:50 +01:00
FredApps 7d3d4830e5 Integrate remaining six PdfCraft parity PRs with conflict resolutions
Includes #4325 #1952 #4180 #2529 #4320 #4805, preserving their original histories.
Keep paragraph alignment and hard/soft wrap metadata together; retain CCITT/downsampling/RGB ownership guards with bounded JBIG2 export and exact-copy symbol encoding.
Combined interaction regressions added. Validation pending.
2026-10-30 11:02:07 +01:00
FredApps b21af3b969 Integrate 21 clean-merging PdfCraft parity PRs
Preserve each original PR head and its authorship as a merge parent.
Includes #4304 #4335 #4384 #4404 #4179 #5300 #4719 #4541 #2815 #4620 #3789 #3779 #2960 #5208 #4596 #3636 #4494 #4013 #5134 #2133 #2635.
Conflicting candidates skipped. Combined CI pending; see docs/integration-20261030.md.
2026-10-30 10:35:42 +01:00
FredApps ea66a642fe Reconcile Pages sidebar selection with current keyboard and panel modules
Preserve main's continuous text selection, page tools and module split. Carry only this branch's sidebar-focus feature and regression onto main, without resurrecting old common files from criss-cross merge bases. Hosted CI remains required.
2026-10-30 08:52:06 +01:00
FredApps 6b38a5ab47 Reconcile typed font sizes and Fill & Sign colours with current UI
Use the unified Format Text panel and current page-input context instead of restoring old monolithic UI code. Preserve main's pointer routing, editor accessibility and preferences; retain colour, save and input regressions. Hosted CI must validate this refreshed head.
2026-10-30 08:47:42 +01:00