Commit Graph
4076 Commits
Author SHA1 Message Date
Grzegorz MartensandClaude Opus 5.5 85bcfea183 M8: a remembered parameters value is found before it is fetched
f5aa20f7a keyed what Names remembers by the object references end at,
but followed them all before looking: every hit fetched the object
again. With two large objects that push each other out of cos's caches,
each fetch decoded 17 MiB, and a 66 KiB file took 26 s where it took
0.6 s. Each reference is now looked up before it is fetched, and what
it leads to is remembered under every reference on the way, with the
key of the last: a hit fetches nothing, an alias or a chain only the
references before what is remembered. The test counts fetches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-02 14:27:36 +01:00
Grzegorz MartensandClaude Opus 5.5 f5aa20f7a9 M8: a shared parameters value is remembered by the object it ends at
Names kept what a value names by its first reference, so streams naming
one array each through an object of their own that refers to it missed
the memo every time: quadratic time, and now memory too (8000 such
streams with 8000 entries, 1.4 MB: 30 s, 504 MiB). It is keyed by the
object the references end at, so another reference, or a chain, finds it
(the same file: 0.29 s, 7 MiB, measured by the review). matches and
added use the same key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-02 14:03:42 +01:00
Grzegorz MartensandClaude Opus 5.5 4acc7e040f M8: what streams share is read once a run, and nothing large is copied for each
Three ways a small file could still make a redaction run long or large:

- direct_parms kept /JBIG2Globals as given, so a direct array there was
  copied for each entry (427 MiB from 32 KB). It is kept only when it is a
  reference, as Document::with_direct_parms keeps it; a direct value is
  no stream, so no decoder reads it.
- The entries copied were bounded only by /Filter's length, and the work
  repeated for every stream that uses a dictionary: time grew with the
  square of the file (117 s from 249 KB). A /Filter longer than any
  decoder takes (MAX_CHAIN, and a /Crypt) is read as no filter, so its
  parameters aren't read either; no decoder reads such a stream anyway.
- named() read every entry of every stream's parameters, through up to
  33 references each, with nothing shared between streams. Names keeps
  what each value the file refers to names, for the whole run, so streams
  sharing an array or an entry cost it once; unshare adds a shared list,
  and finds what it matches, once too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-02 13:33:36 +01:00
Grzegorz MartensandClaude Opus 5.5 4b57bd1eb6 M8: what is made direct to read a stream is small, however often it is named
direct_parms copied every value it followed, and direct() every /Filter
entry, so each reference to one large array copied it again: about 5.5 MB
a reference, 11 GB from a file of about 225 KB. Now, as
Document::with_direct_parms does, a size is a number, /Filter a name or
an array of names, and direct_parms() keeps of each entry the numbers and
flags decoders read (DECODE_PARMS_KEYS, now public in cos) and
/JBIG2Globals as given, for as many entries as there are filters.
Anything else is null.

A kept image whose own /DecodeParms come through a chain is stored again
with them, which 2eddb1bc9 made so: what_stays tests it now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-02 13:05:52 +01:00
Grzegorz MartensandClaude Opus 5.5 2eddb1bc98 M8: a copy is found whatever way its parameters are given
content() decoded a dictionary through with_direct_parms, which reads
/DecodeParms and its entries one reference deep, while named() follows
the chain. So a copy of the dictionary whose parameters came through a
reference to a reference decoded to other bytes, wasn't known for a copy,
and stayed in the file with Ok. readable() now gives /DecodeParms, each
entry and each value through followed, /JBIG2Globals aside, so a copy
decodes as a reader that follows references decodes it.

With the copy known, the image naming it has to be stored again, and
PdfCraft decodes that image's symbols with the copy's parameters one
reference deep, so it can't: the run is refused, where it went on with
the copy in the file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-02 12:20:23 +01:00
Grzegorz MartensandClaude Opus 5.5 f0ff1f7fe6 M8: values given through a chain of references are read as their object
/F, /Width, /Height, /Filter and its entries, /ColorSpace and an ICC
profile were resolved one reference deep, while /DecodeParms and
/JBIG2Globals follow a chain (followed). So /F through a reference to a
reference to null counted as data in another file and refused the run,
and a /ColorSpace chained to /DeviceRGB wasn't known to have three
components, so a JBIG2 image naming it was stored as one bit, where main
refused it. All now go through followed, which a ring ends.

image::components serves clear too, so an image whose colour space is
given that way is now cleared, where it was removed whole; clear's
/Decode for a stencil mask, read one reference deep, follows the chain
as well, so a chained [1 0] isn't missed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-02 11:57:43 +01:00
Grzegorz MartensandClaude Opus 5.5 1b39d9a00c M8: /F null is none, and no copy keeps the old data's parameters
A /F that is null, by reference to null or to no object, is as if absent,
so an image with one is stored again, not refused as data in another file.
The check is image::data_elsewhere.

A copy stored again kept /FFilter and /FDecodeParms, which named now
reads, so the dictionary stayed through the copy and the run refused,
blaming something else. A cleared copy kept those and /DP. Both drop them,
the cleared copy only when its data isn't in another file.

A dictionary that names itself isn't counted as something that uses one
when the run is refused, as it already wasn't when it goes on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-02 10:39:01 +01:00
Grzegorz MartensandClaude Opus 5.5 cb1a040731 M8: a shared JBIG2 dictionary is found in /FDecodeParms too
/FDecodeParms are the parameters of data in another file, which a reader
may draw instead of the stream's own. A dictionary that only a removed
image named there stayed in the file with Ok. It is now read with
/DecodeParms and /DP, and an image whose data is in another file counts
as one that can't be stored without the dictionary: readers draw that
file, not a copy stored here.

images_restored counts what was stored again. A dictionary that names
itself as its own globals (malformed) leaves with what used it but can't
be stored, and was counted.

The table's /Mask route gets a row in the routes test, as it was only
measured.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-02 09:57:33 +01:00
Grzegorz MartensandClaude Opus 5.5 14e5eec341 M8: a dictionary named beside a shared one stays, whatever the order
A kept image that named the dictionary added every dictionary it names to
what had to leave, in one pass. An image using only one of those, earlier
in the file, had been passed over, so it kept that dictionary and the
redaction refused, saying something else referred to the data. In the
other order it was stored again. Now only the names that matched have to
leave, so another dictionary stays with what uses it, in either order.

The variant's doc says what is counted: something that names the data,
JBIG2 or not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-02 08:39:00 +01:00
Grzegorz MartensandClaude Opus 5.5 b3b47b651d M8: the shared JBIG2 refusal says what keeps the data
When nothing that names the dictionary is stuck and something else refers
to it (a private key, say), the refusal said 1 item(s) couldn't be stored
without it. It now counts 0 and says something else refers to the data.

remove_hidden's refusal gets a test: the comment has gone by then, and the
text doesn't say nothing was changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-02 08:26:31 +01:00
Grzegorz MartensandClaude Opus 5.5 13422bf279 M8: the shared JBIG2 refusal says no more than it knows
apply works on a copy, so nothing changes when it refuses, but remove_hidden
isn't transactional: a direct caller's document is part changed by then. The
message said nothing was changed for both. The app's callers work on copies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-02 01:15:53 +01:00
Grzegorz MartensandClaude Opus 5.5 580abc51cf M8: a shared JBIG2 dictionary leaves with whatever used it, by any route
Redaction starts from the images a run replaces or removes and the forms
and soft masks under them, so a dictionary used by something that left
another way stays, with apply returning Ok: a stamp or widget appearance, a
Type 3 glyph, an /Alternates entry, a /Mask, or an image with /Filter by
reference or /DecodeParms through a reference to a reference. A
byte-identical copy stays too, which is what pdfseparate then pdfunite
leaves, and Remove Hidden Information never takes a dictionary out.

Compare what a full save would keep before and after the run instead, the
same walk write_full makes, now cos::reachable. What left names the
dictionaries, in any entry of its parameters; what stays that names one, or
a copy, is stored again without it; and the walk is taken once more, so a
dictionary still kept refuses the run, whatever keeps it. An image with
/Width, /Height or /Filter by reference is read, and is one bit in the copy,
as it was when drawn. /Alternates stays with what is stored again, and a
stream that isn't JBIG2 refuses rather than being stored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-02 00:45:31 +01: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 85c8a2a0bf Merge pull request #5474 from Piohrun/feat/redact-bilevel-codec
M8: a redacted black-and-white scan is stored as Group 4, or JBIG2 again, when smaller (protect.redact-image-pixels)
2026-11-01 23:09:46 +01:00
FredApps 0b2f93538d test(ui): target the Bookmarks submenu item explicitly (#5677) 2026-11-01 22:57:53 +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 d35be6fdac fix(ui): use the shared rail viewport fix (#5677) 2026-11-01 22:40:04 +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
FredApps 8e035f2e34 fix(ui): reserve the navigation rail footer height (#5677) 2026-11-01 22:14:38 +01:00
archamon faebcfd264 fix(annot,ui): stabilize ink width bounds, search translated tool labels, and fix hotbar settings label 2026-11-01 20:58:16 +00: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
b36bdaeac6 M2: a shrunk image on a turned or mirrored page snaps to whole pixels (#5530) (#5640)
* M2: a shrunk image on a turned or mirrored page snaps to whole pixels (#5530)

hayro patch 14 (`snap_minified_image`) shrinks an interpolated image once
to its whole-pixel device rectangle and draws that 1:1, instead of
shrinking to a rounded-up size and resampling again at a fractional
scale and offset. It only accepted an upright transform (a > 0, d > 0,
b = c = 0), so every image on a page under /Rotate 90, 180 or 270 -- and
any image placed turned or mirrored by its own matrix -- still took the
double resample. That is #5530: a scanned page that matches PDFium
upright drifts and softens when the same page is rotated.

The snap now accepts every transform whose axes lie along the device's
(the eight quarter-turns and mirrors) and returns the 1:1 transform
that draws the shrunk raster from the snapped corner, one source-axis
step a whole device pixel, in whichever direction that axis runs.
Skewed and freely rotated images keep the old path.

On a synthetic page (crop box and scale on whole pixels, image at
fractional coordinates), /Rotate 90, 180 and 270 renders turned back
differed from the upright render in 72% of pixels, by up to 200 grey
levels; now they are identical. Non-interpolated images were already
identical and stay so.

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

* M2: snap only when the dropped skew is invisible, and only a resize that succeeded

Review of #5640:

- The snap judged an image axis-aligned by a tolerance on its
  coefficients (1e-4 of the scale). For a very wide image a skew that
  small still moves the far edge by more than a pixel (40 000 samples
  at 4e-5: 1.6 px), and the snap flattened it. It now measures what it
  drops: how far each off-axis term moves the far edges, at most a
  hundredth of a device pixel (SNAP_SKEW). Upright and turned alike.
- A resize that fails hands back the source raster at source size; the
  1:1 snapped transform then drew it at that size. The snapped
  transform is now used only when the shrink produced the snapped
  size; otherwise the full transform, as before.
- The mirrored case of the edge test drew a uniform image, which looks
  the same mirrored; it now also mirrors a half-black image and checks
  which side the black lands on.

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

* M2: the turned-page test draws every kind of image the snap reaches

The /Rotate oracle test drew one 8-bit grey image. Stencil masks (CCITT
scans often are one), 1-bit images and images with a soft mask reach
the snap through other paths, so it now draws all five: 8-bit and
1-bit grey, a stencil mask, a soft mask of the image's size and one of
another size. On main, every interpolated kind is off by 113-217 levels
over 63-73% of the image when turned, and so is a non-interpolated
image with a smaller soft mask (110 levels, 16%): hayro patch 15
filters such a mask whatever /Interpolate says, so it is snapped
upright and was not when turned. All are identical with the fix; the
other non-interpolated kinds were already identical and stay as guards.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-01 15:07:29 -05:00
07582e9ed4 Make MCP aligns enums compatible with Kimi (#5592)
* M13: make MCP aligns enums Kimi-compatible

Fixes #5588

* Drop the progress-log lines that conflict with main

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 15:05:43 -05:00
PiohrunandClaude Opus 5.5 036811368b M2: both ends of a /Decode array have to be numbers to invert an export (#5678)
An end that is missing or isn't a number sorted below any number, so [1],
or [1 7 0 R] with 7 leading nowhere, read as inverted on a JPEG or a stencil
mask, and so did an end too long to hold, which parses as infinite. Such an
array now leaves the default mapping, as it does on the lossless path.


Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:05:26 -05:00
Livio OswaldandClaude eafe9e2c0b M8: typing the last page of a range keeps what is typed (#5574) (#5579)
* M8: typing the last page of a range keeps what is typed (#5574)

The page-range dialogs (Redact pages, Rotate pages, Duplicate field,
Number pages) raised the end to the start on every frame, so the
half-typed "1" of "12" became the start before the "2" arrived.
The end is now ordered only once its field is no longer being typed
into. Number pages no longer lowers the start to a half-typed end, and
its preview cannot underflow while the end is below the start.

* M8: a test types the last page of Rotate Pages' range (#5574)

Rotate Pages' From and to fields are labelled, and a test types "12" after a
first page of 5 (pages 5-12 are turned) and "3" left by clicking OK (page 5
alone), as croquete1 asked on #5579.

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

* M8: page ranges stay typed while the window is in the background, and OK orders them (#5574)

Review follow-up. The end field's typing check used Response::has_focus,
which is false while another window is in front, so switching apps
halfway through "12" still raised the typed "1" to the start. It now
asks egui's memory (widgets::typing_into). OK pressed while the end is
still focused (a screen reader's click) orders the range first in Rotate
pages, Duplicate field and Redact pages, and Number pages builds its edit
from the ordered end, so a reversed range never reaches the edit.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-01 15:05:01 -05:00
Livio OswaldandClaude Opus 5.5 901050a31f M14: the comments panel lays out only the cards in view (#5488) (#5583)
* M14: the comments panel lays out only the cards in view (#5488)

With more than 100 comments, a card or page header out of view that
was measured at the current width only takes up its height instead of
being laid out every frame. The selected card is always drawn, so it
still scrolls itself into view. Shorter lists are laid out whole and
keep every card in the accessibility tree.

The 500-comment panel in tests/perf.rs drops from about 23 ms to 5 ms
per frame locally, well inside the 40 ms budget it overran on CI.

* M14: the comments panel measures a card again when it changes (#5488)

A card's kept height is now per document and per what the card shows (its
thread's text, author, date, status, colour and lock, and whether it is the
selected card), so another document at the same width, an edit, an undo or a
redo never lays the cards below it out at a stale height (CodeRabbit on #5583).

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

* M14: a comment card with an editor open is never skipped (#5488)

A card with an inline editor open on it or on one of its replies is laid out
wherever it is, and its kept height is apart from the one without the editor,
so the cards below make room for the editor (CodeRabbit on #5583).

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:57 -05: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
Livio Oswald a74e3d4715 M3: the wheel scrolls right after Alt+Tab back, before the mouse moves (#1518) (#5608)
After switching back to the window, the windowing layer sends no
pointer move until the mouse moves, so egui knew no pointer and the
wheel scrolled nothing. When a frame turns the wheel with no known
pointer and no move of its own, the raw-input hook now asks the system
where the pointer is (drag_pointer::in_window, already used for file
drags) and puts that move first.
2026-11-01 15:04:47 -05:00
Guillem Hermida 778aa58522 tests(engine): the XFA opening scale test times its small and large forms in the same rounds (#5586)
The test timed the 100-field form three times, then the 800-field form
three times, and compared the best of each. A busy spell on a shared
runner that fell on the second half slowed only the large form, and the
ratio went past 16x (16.6x on a gate run) though it is about 7x.

Each of five rounds now opens the small form and then the large one, and
the best round's ratio is checked against the same 16x, after one
untimed opening of each. With the values filled one by one again (the
quadratic code before #4638) it still fails, at about 48x.
2026-11-01 15:04:42 -05:00
NetterSilverandClaude Haiku 5.5 2289b5755d Ignore a negative trailer /Size in incremental saves (#5566)
Found while testing crates/cos/src/writer.rs:248

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-11-01 15:04:36 -05:00
NetterSilverandClaude Haiku 5.5 fdaa502f27 Reject signed and empty arcs in try_oid (#5581)
Found while testing crates/sign/src/der.rs:467-472 (try_oid)

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-11-01 15:04:32 -05:00
FredApps 2cd50b8d39 Merge pull request #5491 from Piohrun/fix/jbig2-retained-bitmaps
fix(render): a JBIG2 page's segments are bounded in what they hold and refer to (hayro-jbig2 patches 5 to 9, hayro-syntax 29)
2026-11-01 21:04:29 +01:00
Grzegorz MartensandClaude Opus 5.5 b57ddb0a22 Merge upstream/main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-01 20:10:05 +01:00
aiongg 41652972ac Merge pull request #5539 from Piohrun/fix/indexed-lookup-by-reference
M2: a reference in a colour space or array isn't lost: Indexed tables, Pattern spaces, /Decode, /W and functions
2026-11-01 13:59:05 -05:00
Brandon ThomasandClaude Opus 5.5 7d78f3e144 Release 0.17.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.17.0
2026-11-01 13:58:27 -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
aiongg 4238670dd5 Merge remote-tracking branch 'o/main' into fix-5539
# Conflicts:
#	crates/create/src/tests.rs
2026-11-01 18:48:28 +00:00
aiongg 6818995a3c Merge pull request #5673 from Piohrun/fix/jbig2-region-location
fix(render): JBIG2 arithmetic near the end of an i32 doesn't panic (hayro-jbig2 patch 12)
2026-11-01 13:45:39 -05:00
FredApps 67e86f2b27 Merge pull request #5620 from Piohrun/fix/mask-resample-allocation
fix(create,optimize): a mask of other proportions doesn't take the process down
2026-11-01 19:44:58 +01: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
FredApps 61140ed807 Merge pull request #2155 from Combiler01/main
feat: Acrobat UI parity for image editing, text formatting, and UI stabilization and Bug Fixes
2026-11-01 18:50:01 +01:00
Grzegorz MartensandClaude Opus 5.5 10107d943a M2: JBIG2 arithmetic near the end of an i32 doesn't overflow
A region's location, a refinement's offset, a symbol's corner, a Huffman
range line and a decoded integer of 2^31 could each overflow: a panic
wherever overflow is checked. The last four wrap, as a release build did.
Bitmap::combine saturates, which comes to the same: a side is at most
65,535, so a location that overflows is past the end of any page. What is
drawn doesn't change. Patch (12), after the numbers #5491 and #5569 take.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-01 18:32:33 +01:00
Dylan M. Taylor 7ca642980a docs: sync Craft App Suite list with ArtCraft Launcher 2026-11-01 12:29:59 -05:00
FredAppsandCodex GPT-6.1 a9de3db628 docs: record image editing review fixes and movement semantics
Validated with 3231 passing scoped tests (10 skipped), clippy, wasm checks, repository gates, and 42 CLI smoke steps including saved-document verification.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 17:59:35 +01:00
FredAppsandCodex GPT-6.1 6816d14e02 Fix context image placement and canvas selection ownership
Convert context-paste user coordinates into AddImage display coordinates and
identify the inserted image through its new owning stream and XObject. Keep
legacy added-image metadata aligned with the active image controller, route
Delete to the visible selection, and clear hidden image selections when leaving
the controller while preserving text selections and drafts.

Separate selection-dependent Copy from whole-page Snapshot and document Export
permissions. Add synthetic UI regressions covering transformed page placement,
identity collisions, shared image references, saved deletion results, tool
transitions, and permission controls.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 17:54:15 +01:00
FredAppsandCodex GPT-6.1 75e012de9a fix(edit): compose inside-scope group translations in order
Fold a leading transform inside the image scope as cm followed by the local transform; an enclosing transform uses the opposite multiplication order. This preserves the complete requested image and crop displacement under an outer scale. The exact crop-arrange-group-move fixture previously moved only five points for a ten-point request; saved geometry and rasters now match an independent direct Transform control across both axes and signed offsets. Full scoped Linux native, wasm and repository gates pass.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 17:41:13 +01:00