Commit Graph
4147 Commits
Author SHA1 Message Date
KenM76andClaude Opus 5.5 ebd09e6649 M12: PRC reader: map the prc-rs licence for license-check (#1297)
The Double code table's attribution pointed its licence file at the
table's own source. `cargo xtask license-check` requires every asset's
licence file to be a LICENSES.toml document, so the gate failed.

Add prc-rs's LICENSE at the same commit as `licenses/prc-rs-LICENSE.txt`,
fetched unchanged, with its origin and hash in LICENSES.toml, and point
the attribution entry at it. The MIT notice stays in the table's header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 15:03:39 -05:00
KenM76andClaude Opus 5.5 cb317560fe M12: PRC reader: answer review (#1297)
- Schema programs draw on one step allowance per reading pass:
  MAX_SCHEMA_STEPS (2^22) moves into the BitReader's budget next to the
  decoded-element allowance, and the tree walk carries both from reader
  to reader. A per-call ceiling let a cheap entity repeated many times
  run ~2^22 steps each.
- The vertex-colour pre-check charges the minimum encoding (one full
  colour, then one reuse bit each), so a run of reused colours may end a
  section.
- placements() reports unreadable globals when an entity reference, not
  only an item, names a reference coordinate system.
- chunks_exact -> as_chunks for clippy 1.99 (chunks_exact_to_as_chunks).

Tests: programs_on_one_stream_share_the_step_allowance,
one_step_allowance_covers_the_whole_walk,
reused_vertex_colours_may_be_the_last_data,
unreadable_globals_are_an_error_when_a_reference_names_a_coordinate_system;
each fails with its fix reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 14:38:13 -05:00
KenM76andClaude Opus 5.5 5eb484c235 M12: PRC reader: a test for each input guard (#1297)
Disabling each input guard in turn left several with no failing test.
Each now has one that pins the guard's own refusal, not a later error:

- a zero Huffman code-length width, and a zero code length;
- a boolean array longer than the data, refused before any bit is read;
- a character array count past the data;
- no file structures, or more than MAX_FILE_STRUCTURES;
- a schema entry count past the data;
- normal angles wider than 16 bits;
- more texture sets than a face may carry (now by message);
- representation sets nested past the ceiling (and at it, accepted);
- a prototype chain past the ceiling;
- the element allowance, at each place it is charged: an entity's
  counted array, uncompressed character and short arrays, a schema
  program, and across one walk's readers (walk_within takes the
  allowance so a test can set it; an allowance that fits one file
  structure does not fit two).

Each fails with its guard disabled (the texture-set one by aborting on
the allocation the guard prevents).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 13:52:20 -05:00
KenM76andClaude Opus 5.5 a7cc7586d1 M12: PRC reader: cap decoded array elements per reading pass (#1297)
The inflation cap bounds bytes, but one bit can decode to a 4-8 byte
element, so a 512 MiB section could ask for about 16 GiB. Every array
count is now charged against MAX_DECODED_ELEMENTS (2^24), shared by
all readers of one tree walk; over the allowance is TooManyElements.
Huffman pre-sizing is capped at 1024.

decoded_elements_are_charged_against_one_allowance fails with the
charge in BitReader::take_elements removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 13:27:05 -05:00
KenM76andClaude Opus 5.5 c566ca6144 M12: PRC reader: tree markups and views read past (#1297)
CAD 3D files carrying PMI failed model_tree() and placements() with
"tree markups (PMI) is not supported": the tree section has no length
prefix, so MarkupData and views had to be parsed to be skipped.

Now read past, not modelled (new tree/markup.rs):
- MarkupData [WD 7.3.10.4]: markup linked items (204), leaders (503),
  markups (502), annotation items/sets/references (504-506); sets
  nest under the MAX_DEPTH ceiling.
- Views (501) [WD 7.7.3], with their annotation plane (SURF_Plane 86),
  optional scene parameters, and from 8137 their flags, linked items
  and filters.
- Scene lights (731-734) and clipping planes, so
  SceneDisplayParameters no longer refuses them.
ReferenceData and ReferenceOnPRCBase are shared readers now, and skip
their schema-added fields.

Field orders the working draft leaves open follow prc-rs's format
description (MIT); each such reader carries a `prc-rs:` marker.

Tests: one synthetic test per entity kind (each reads to a sentinel
and every truncation errors), nesting ceiling, wrong annotation type,
and a tree with markups and views on a part and an occurrence that
still places its mesh. Each shown failing with its reader reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 12:26:44 -05:00
KenM76andClaude Opus 5.5 c571b13469 M12: PRC reader: product tree and styles (#1297)
Walk the PRC product tree into placed, coloured tessellations.

- PrcFile::placements / placements_with(StyleAlpha, EntityOverrides):
  every drawable tessellation reached from the model file's roots, placed
  by its occurrence path's locations and then the representation item's
  local coordinate systems (a 4x4 Matrix) [WD 7.3.10.1, 7.6.3.2].
- Colours: styles and materials from the file structure globals, the
  father/son inheritance bits [WD 7.2.4.2], style transparency combined
  with material alpha by StyleAlpha, per-face styles and topology
  references via Placement::triangle_colours. A prototype's part is
  coloured by its own structure's styles. A colour index counts the colour
  array's doubles (pdf-issues #816).
- Occurrences' entity references: colour, hide or relocate the entity
  they name, in the occurrence's subtree, everywhere, or not at all.
- PrcFile::model_tree: the assembly tree as a model-tree panel lists it,
  with name sources, visibility and each node's placement range.
- TriangleMesh::transformed applies a Matrix: positions, cofactor-mapped
  normals, winding flipped under a mirror.

Never panics: occurrence and prototype cycles are Malformed; depth 64,
2^20 visits, MAX_PLACEMENTS and a face-override ceiling bound the walk;
counts are checked against the remaining data before allocating.
Markups, views, scene lights and clipping planes are Unsupported;
textures are read past.

Field orders the working draft leaves open follow prc-rs (MIT); each is
marked `// prc-rs:`, as are the step-1 container, Double and schema
choices that did.

Adapted from pdfcer (MIT, same author), crates/pdfcer-3d/src/tree and
vec3.rs at 0c9742b5936232af89cd707138ef149dc1f4a660.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 12:26:44 -05:00
KenM76andClaude Opus 5.5 7f30266790 M12: PRC reader: tessellation and arrays (#1297)
Read the tessellation section of a PRC file structure:
FileStructure::tessellations() returns each entity as a Tessellation.

- TESS_3D becomes a TriangleMesh: positions, triangles from the
  triangle/fan/strip blocks (one-normal and textured variants included),
  one triangle range per face, stored normals and texture coordinates
  with per-corner indices, and per-triangle line-attribute styles.
- TESS_3D_Wire becomes polylines (closing and continuous flags); vertex
  colours are read past, sized by points (closing point included).
- TESS_Markup is read past.
- TESS_3D_Compressed is read field by field to its end so the entities
  after it decode; its triangles are not rebuilt (later step).
- The compressed Character/Short/Integer/Indice arrays and the Huffman
  container they use (WD 11.6-11.9, 12.2) live in arrays.rs; the base
  fields every entity shares (attributes, name, user data) in entity.rs.

Never-panic hardening beyond the source this is adapted from:
- Huffman symbol width must be 1..=16; the leaf table and element count
  are bounded by the payload before anything is allocated.
- A face may carry at most 8 texture-coordinate sets (an unbounded
  count allocated per set and padded per triangle).
- Every count is checked against the remaining bits; indice sums and
  deltas use checked arithmetic.

A few reading choices (Huffman bit order and code-length width, indice
running sums, 64-bit attribute order, three version gates, the
is_segment_color bit, the compressed field order) follow prc-rs (MIT);
each is marked `// prc-rs:` in the source.

Tests are synthetic, written with the crate's test bit writer. Each
fix was reverted and its test seen failing (17 reversions).

Adapted from pdfcer (MIT, same author), pdfcer-3d at
0c9742b5936232af89cd707138ef149dc1f4a660.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 12:26:43 -05:00
KenM76andClaude Opus 5.5 ab552430a7 M12: PRC reader: the Double code table from prc-rs (MIT) (#1297)
Every PRC Double decodes through a fixed 2077-row prefix-code table
(working draft 11.17.2). The rows come from prc-rs src/double.rs at
287b671bb5b31b05efb5ec1b98f7c359b9e4dd7d under its MIT licence, whose
notice stays in the file header; ATTRIBUTION.toml records it.

The table is a complete prefix code, so the empty-table stub test is
replaced by a truncation test. The exponent lookup masks the 11 exponent
bits, because the last exponent row's upper word is 0x7ff80000.

Revert proof: with the table emptied, eight Double tests fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 12:26:43 -05:00
KenM76andClaude Opus 5.5 83b230ae73 M12: PRC reader crate: container, bit primitives, schema (#1297)
New standalone L0 crate pdfcraft-prc: the PRC container walk (file
header, file-structure descriptions, per-section zlib inflation under a
512 MiB ceiling), the bit-level primitives of the compressed sections,
and the schema interpreter a newer producer uses to describe fields it
added (step and nesting ceilings).

The Double prefix-code table is not in this commit: Double reads
return UnknownDoubleCode, and the tests that need the table are
ignored with that reason.

Adapted from pdfcer (MIT, same author), crates/pdfcer-3d at
0c9742b5936232af89cd707138ef149dc1f4a660.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-02 12:26:43 -05:00
FredApps 168a3cb91e Merge pull request #5740 from Combiler01/fix/panel-resize-scrollbar-5730
fix(ui): prevent panel resize handle collision with scrollbar (#5730)
2026-11-02 17:47:35 +01:00
FredApps 8f1441a3f2 fix(ui): keep pane resize edges right of the border, off the page scrollbar (#5730)
The centred 4 pt strip still covered the right 3 pt of the page's
scrollbar beside a right-docked pane. Register the host's resize edge
again from the border rightwards, on the host's own layer, and leave an
inert edge inert. The test now hovers and drags both scrollbars beside a
right-docked Tools pane (CodeRabbit) and checks the width is unchanged.
2026-11-02 16:59:26 +01:00
FredApps 1997b8ed94 Merge remote-tracking branch 'origin/main' into pr5740 2026-11-02 16:29:14 +01:00
FredApps e261518727 Merge pull request #5569 from Piohrun/feat/jbig2-run-allowance
M2.6: the images of a run, and a page's JBIG2 images, share one allowance of decoding
2026-11-02 15:11:32 +01:00
FredApps 1303684851 Merge pull request #5639 from RiceDee/fix/rail-page-zoom-fields
fix(ui): the rail's page box fits, and the zoom field looks like a field (#5636)
2026-11-02 14:46:11 +01:00
FredApps 2d4e317a58 Merge pull request #5638 from RiceDee/fix/quick-bar-centred-in-margin
fix(ui): centre the quick-tools bar in the margin beside the page (#5635)
2026-11-02 14:45:58 +01:00
FredApps 52574b4fe5 Merge pull request #5637 from RiceDee/fix/format-text-fields-fit-pane
fix(ui): Format text fields read as fields, line up, and fit the Tools pane (#5634)
2026-11-02 14:45:46 +01:00
FredApps ec25e7d327 M2.6: images decode outside the renderer only through an ImageBudget
`ImageBudget::decode` is now the one public way to decode an image's
CCITT fax or JBIG2 data: `Document::decoded_image` and
`decoded_image_within`, which decoded with no limit on the work, are
test-only in pdfcraft-cos, and pdfcraft-filters exports only
`decode_ccitt_within` and `decode_jbig2_within`, which spend from a
caller's allowance (`decode_jbig2` is gone, `decode_ccitt` private).
So an operation over many images can't decode them without sharing an
allowance; the compiler holds new callers to it. Doctests that must not
compile check both crates; with the old entry points public they fail.

Tests outside the crates decode through an unlimited ImageBudget.
Streams in general (`Stream::decoded`) are left for a follow-up.
2026-11-02 14:24:21 +01:00
FredApps 4ffe5908c8 Merge origin/main into feat/jbig2-run-allowance
# Conflicts:
#	vendor/README.md
2026-11-02 14:15:33 +01:00
RiceDee ecd1822525 fix(ui): the size field shows "Mixed" whole in a tooltip (#5634)
A long translation scrolls in the capped field, so the tooltip gives the full status.
2026-11-02 23:37:29 +11:00
RiceDee 89efc79ecd fix(ui): the size field stays within its room in the row in every language (#5634)
It reserves room for a translated "Mixed", and in Czech or French that pushed the alignment
button up to 54 pt past the Tools pane. The field is now no wider than SIZE_FIELD, and a longer
translation scrolls in it. The language test checks the size row too.
2026-11-02 23:23:20 +11:00
Harsh Verma ec2f59305b Merge branch 'storytold:main' into fix/panel-resize-scrollbar-5730 2026-11-02 12:23:11 +00:00
RiceDee 808ba71287 Merge main into fix/rail-page-zoom-fields
Conflict with main's rail docking, resolved by keeping both: the rail follows the side chosen
for it (main) at 49 pt with 1 pt padding, and main's dock button stays above the page display
button, while zoom out and in stay side by side under the zoom field.
2026-11-02 23:11:59 +11:00
RiceDee bcff9a3948 Merge main into fix/quick-bar-centred-in-margin
Conflict with main's hotbar, resolved by keeping both: the bar keeps main's offset past a hotbar
docked on the left or top, and centres in the margin that starts after it.
2026-11-02 22:58:09 +11:00
RiceDee f711b163c6 Merge main into fix/format-text-fields-fit-pane
Conflicts with main's font menu, size field and accessible names, resolved by keeping both:
the font list keeps main's searchable installed-fonts menu inside its outline; the line spacing
list and number fields keep their names. The size field now reserves room for "Mixed", so
MIN_ROW counts it (62 pt, measured for English), and the spacing columns take their width from
the font list so a longer translation of "Mixed" can't widen them.
2026-11-02 22:43:31 +11:00
Combiler01 8baae14c34 fix(ui): prevent panel resize handle collision with scrollbar (#5730) 2026-11-02 16:36:17 +05:30
FredApps f47618f59b Merge pull request #5710 from Combiler01/feat/preferences-cards-refresh
ui: modernize preferences dialog with card-based layout
2026-11-02 11:38:28 +01:00
FredApps 2f8a8fb625 Preferences: reconcile current main before final validation 2026-11-02 11:23:23 +01:00
NetterSilverandClaude Haiku 5.5 d7dd1de647 Reject '+' sign in page selection numbers (#5715)
Found while testing crates/ui-egui/src/panels/page_selection_ui.rs:24-27 (parse, `number` closure)

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-11-02 05:07:42 -05:00
4a0b1dd158 Repair scan: an object whose header names another generation is still found (#5580) (#5584)
* A rebuilt xref finds an object whose header names another generation (#5580)

`poppler-85140-0.pdf` is 340 bytes with no cross-reference table, and the
header of its one page reads `3 18446744073709551616 obj` while the page tree
points at `3 0 R`. Before #427 the digits wrapped and 2^64 wrapped to exactly
0, so the header read as `3 0 obj` and the file opened by accident. Once an
overflowing integer became a real, the object was unreachable and the file
stopped opening at all, which `cargo xtask check` reports as a regression.

The generation is not really the point. `3 99999 obj` with the same page tree
is refused in exactly the same way on main -- the control for the new test
fails on that row first, with "page 1 does not exist" -- so this is a
mismatched generation in a rebuilt table, not an integer overflow. A table
rebuilt by scanning the file has no revision history to tell two generations of
an object apart, and a file that needs rebuilding references everything as
`N 0 R` whatever its headers say.

So, in the reconstruction path only:

  - a generation number that cannot be read is taken as 0, the value such a
    file's references use (`read_object_header`). A generation is at most
    65,535 (ISO 32000-2 §7.5.4), so there is nothing to lose by it;
  - every scanned object is also filed under generation 0, with `or_insert` so
    a real `N 0 obj` is never displaced by the alias;
  - the object is read back behind the same lenient header, since reading it
    parses the header again and the strict identifier check rejected it
    (`read_repaired_body`, gated on `repaired`).

Reading numbers anywhere else is unchanged, and a table read from the file's
own xref keeps the exact identifier match. The scan already files objects found
in object streams under generation 0, and cos's table parser already takes an
unreadable generation as 0, so this follows what the code does elsewhere.

The test covers generations 0, 99999 and 2^64 with no table and no `startxref`,
and asserts the page is found at its own size. Its fixture is built in the test
rather than taken from the corpus, so it holds without the pinned checkout.

* The generation-0 stand-in is one key, not a second object

Two consequences of filing a scanned object under generation 0 as well,
both from review.

`XRef::objects` walks the table's keys, so an object with a stand-in came out
once per key: a document whose page tree cannot be read is recovered by looking
for objects that are pages, and it reported the page twice. Enumeration now
yields one object per location, after the sort it already does. Both keys stay
for lookups; only this walk is deduplicated.

And `or_insert` never replaces, so a later `3 1 obj` moved the exact `3 1` key
to the later body while the `3 0` stand-in stayed on the earlier one -- `3 0 R`
and `3 1 R` would then read different objects. The scan now tracks which
generation-0 keys are stand-ins: a real `N 0 obj` takes the key and keeps it,
and a later header for the same number replaces a stand-in.

The tests check both through what comes out: page 2 must not exist for a
single-page file recovered without a page tree, and with two `3 1 obj` headers
the page comes back at the later body's 300x150 rather than the earlier
200x100.

* M2: the repair scan keeps its place on a non-header, and an object stream's first member is enumerated (#5580)

- read_object_header's lenient path works on a clone, so `42 3 0 obj` no longer costs the
  header after the stray number (CodeRabbit, minor).
- XRef::objects deduplicates by location and object number: an object stream and its first
  member share (offset, 0) and are two objects (CodeRabbit, minor).
- vendor/README.md: hayro-syntax patch (34) and its tests.

Both new tests fail with their fix reverted.

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-02 05:02:12 -05:00
Combiler01 d266527b98 ui: fold dotted and dotless i in preferences text search 2026-11-02 14:41:41 +05:30
Combiler01 b5e847d3b9 ui: scroll to matches in segmented pill options, toggle pills, and theme cards 2026-11-02 14:18:21 +05:30
Combiler01 7f42c375dd ui: translate search match-count text and remove unused toggle_pill 2026-11-02 12:45:46 +05:30
Combiler01 80ffea6df4 ui: remove misleading cancel button from preferences dialog 2026-11-02 12:28:19 +05:30
Combiler01 0c822af3b1 ui: implement browser-style in-situ text search and highlight in preferences 2026-11-02 12:00:53 +05:30
Harsh Verma fa818ccc08 Merge branch 'storytold:main' into feat/preferences-cards-refresh 2026-11-02 06:00:41 +00:00
Combiler01 6aa88171d3 ui: register theme card focus upfront and assert TwoUp continuous preservation 2026-11-02 11:24:53 +05:30
Combiler01 dac706f3b2 ui: fix vertical layout containment for theme preview cards 2026-11-02 11:02:02 +05:30
Combiler01 8867d15d18 ui: align navigation rail accessibility labels with existing integration test 2026-11-02 10:46:10 +05:30
Combiler01 ec533af1bb ui: fix two-page continuous scrolling and add keyboard focus indicators 2026-11-02 10:43:27 +05:30
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
8faebd0d96 Fix Matrix::invert rejecting valid matrices with a small scale (#5703)
* Fix Matrix::invert rejecting valid matrices with a small scale

Found while testing crates/content/src/lib.rs:732-735 (Matrix::invert)

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

* Matrix::invert: a matrix whose inverse overflows has none

A subnormal determinant passes the relative check, and dividing by it
overflows (CodeRabbit on #5703).

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

---------

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-01 23:49:14 -05:00
f035218cb4 fix(ui): don't check recent files on the UI thread at startup (#5682) (#5692)
* fix(ui): don't check recent files on the UI thread at startup (#5682)

Restoring settings dropped recent files whose files no longer exist by
asking the file system about each one on the UI thread, before the first
frame. On a network mount that has stopped answering (davfs2 in the
report) that call blocks in the kernel, so the window never appears.

Settings now keep every recent entry when they load. Once the window is
up, a worker checks the restored paths and each frame drops the ones it
has found gone. An entry it hasn't reached, or can't tell about (the
mount answers with an error), stays listed. A worker stuck on a dead
mount stays stuck, but nothing waits for it. This follows the pinned
folders' worker.

Fixes #5682

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

* Recent files: a file opened while the check is pending stays listed

The worker's answer that a restored path is gone is out of date once the
user opens that path; keep the new entry (CodeRabbit on #5692).

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 23:49:10 -05:00
Combiler01 5f07da4419 ui: modernize preferences dialog with card-based layout
- Replace cluttered settings list with clean, modular card containers
- Integrate interactive real-time search box flush with card boundary
- Combine page display and continuous scrolling into a unified segmented pill
- Enable smooth scrolling with dedicated animation duration
- Enhance vertical scrollbar spacing and layout geometry
- Update unit and integration tests across layout, appearance, and theme
- Credit @Combiler01 in contributors/people.toml
2026-11-02 09:58:24 +05:30
FredApps f89a5917ed M2.6: streams made in memory enlarge a run's image allowance as a file's bytes do 2026-11-01 23:52:37 +01:00
FredApps 5eb82d1f68 M2.6: an image pays for the pixels its data holds, not those its size declares 2026-11-01 23:52:07 +01:00
FredApps 74d51ebf4a M2.6: a JBIG2 image stored again without its globals decodes within the run's allowance 2026-11-01 23:50:39 +01:00
FredApps bc4acf7bfc Merge origin/main into feat/jbig2-run-allowance 2026-11-01 23:50:04 +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