Commit Graph
4320 Commits
Author SHA1 Message Date
PerchAnton caaae7a8c7 M7: retain product notes while integrating current main 2026-10-18 15:50:34 +03:00
PerchAnton e0e34a81b6 M7: preserve rotation and index regressions on current main 2026-10-18 15:39:47 +03:00
PerchAnton c378af9ea8 M1: reject partial resource-fork copies and integrate current main 2026-10-18 15:13:56 +03:00
MarekSpodBiedry cf1305fa4a Format the shading alpha test (#3176) 2026-10-18 14:05:26 +02:00
MarekSpodBiedry 7420af6751 Draw a note without an appearance as a fixed-size icon at the corner of its Rect (#3175) 2026-10-18 13:53:26 +02:00
MarekSpodBiedry b744866dee Shading patterns use the stroke or fill alpha current at paint time (#3176) 2026-10-18 13:52:43 +02:00
mtlcl410andClaude Opus 5.5 1c338b3ea6 M8: a phone number takes one slash, right after its area code (#3205)
Review follow-up: "0426/12/3456" and "0426 12/3456" matched, since any
slash between digits counted. Only one slash is allowed now, and only
right after the first (area code) group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-18 19:48:53 +08:00
mtlcl410andClaude Opus 5.5 0b731453d3 M3: extract text from glyph names outside the Adobe Glyph List (#3169, #3170)
A simple font without a usable /ToUnicode reads its text from glyph
names, and a name missing from the glyph list dropped the glyph from the
extracted text while the page still drew. An 821-page book set in Type1C
fonts named C39, C40, ... extracted 4 KB of front matter and nothing else.

- glyph_name_to_text applies the Adobe Glyph List Specification's full
  mapping: drop from the first '.', split on '_', map each component
  through the list or as uniXXXX / uXXXX. f_i, f_l and hyphen.case now
  read as fi, fl and '-'. Type 1, CFF, standard and embedded TrueType
  simple fonts use it.
- A Type 1 / CFF / standard font name that only numbers the glyph (one
  or two letters then digits, ZapfDingbats' aN excepted) reads as the
  code's StandardEncoding character, so C174 is the fi ligature and C208
  an em dash.

The book now extracts 1.9 MB (pdftotext: 1.85 MB), with fi and em dashes
where pdftotext writes Latin-1 mojibake.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-18 19:46:39 +08:00
mtlcl410andClaude Opus 5.5 73aea6e0ac M8: phone pattern finds slashed and labelled unseparated numbers (#3205)
Italian documents write landlines with a slash between area code and
number (0426/123456), and mobiles without separators (3451234567,
+393451234567). The phone pattern needed a separator other than a slash,
so these stayed readable after redaction.

- A slash joins two digits in a number with a trunk-0 area code; a
  North American or + number with a slash still doesn't match, so
  reference numbers like 2024/123456 stay unmarked.
- A number without separators matches only right after a phone label
  (Tel., cell., Fax:, telefono, mobile, ...). Unlabelled runs of digits
  (ids, VAT numbers) still don't match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-18 19:37:27 +08:00
wstock 1c12615032 lopdf: a rebuild takes the trailer from a cross-reference stream (PdfCraft patch 6)
Document Properties are read through the inspector: the lazy cos
reader refuses a file that needed repair, and lopdf's rebuild only
looked for the trailer keyword, so a file whose only trailer is a
cross-reference stream failed to load there and every field showed
empty. lopdf's rebuild now takes a /Type /XRef stream dictionary as
the trailer, the latest in the file winning as a later trailer
keyword would.
2026-10-18 11:25:48 +00:00
wstockandClaude Fable 5.1 227b4bfcf0 Issue #2365: a rebuilt file keeps the /Info and /ID of its cross-reference stream
When the cross-reference chain is unusable (here: a second, truncated PDF
appended after the first, so no startxref is in reach), the index is rebuilt
from object headers and the trailer taken from the last `trailer` dictionary
with a usable /Root. A file written with cross-reference streams has no
`trailer` keyword, so the rebuild fell through to the catalog alone and /Info
(and /ID, /Encrypt) were lost: Document Properties showed nothing, and a full
save after editing one Info field wrote only that field and /ModDate.

reconstruct() now also considers every /Type /XRef stream dictionary with a
usable /Root, taking whichever trailer comes last in the file.

Regression test: document::tests::reconstruction_takes_the_trailer_from_a_cross_reference_stream.
Verified on the issue's fixture (openpreserve PDF-HUL-138/6.2017-0960.pdf):
Creator, Producer, CreationDate and ModDate are read and kept by a full save.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011K1peyrh5MdZ6ttHkVq8Gh
2026-10-18 11:25:48 +00:00
Marco Arnulfo a61bd92fdd Fonts: measure a remapped standard-14 code by the glyph it draws
A standard-14 font with no /Widths was measured by code in the WinAnsi-indexed
table, so a /Differences remap took the width of the glyph WinAnsi gives that
code: Helvetica with [65 /W 87 /A] laid out "AAAA" four W's wide. The code is
now looked up through the font's encoding first; /Widths still go by code, and
Symbol, ZapfDingbats and glyphs WinAnsi has no code for keep the code itself.
2026-10-18 13:21:34 +02:00
Charan Rathore f6521cf64e hayro-cmap patch: tests for the unresolved-usecmap failure guard 2026-10-18 16:44:19 +05:30
Charan Rathore dd64f1797e hayro-cmap patch: an unresolved usecmap with no mappings reports failure 2026-10-18 16:43:48 +05:30
jonathanwxh-cell 0f28c6a902 M11: target PDF version and object streams option in PDF Optimizer (#3090) 2026-10-18 18:02:10 +08:00
Charan Rathore fb1f72e9ed Attribute the vendored cmaps.brotli as a repository asset
With hayro-cmap vendored, its Adobe-authored CMap data no longer
arrives through a crates.io dependency: record it as an [[asset]] (same
SHA-256, licence_file at vendor/hayro-cmap/assets/LICENSE.txt) instead
of the [[bundled]] entry, and add the vendored path to the closed
first-party list in xtask so cargo xtask assets passes.

Signed-off-by: Charan Rathore <180254320+charan-rathore@users.noreply.github.com>
2026-10-18 15:29:20 +05:30
Mex a694e5dbba M4: add duplicate-index rotation parity evidence
Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.
2026-10-18 11:52:04 +02:00
Mex 9bda202dba M4: document repeated rotation preservation controls
Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.
2026-10-18 11:51:55 +02:00
Mex 2a60b2fb0e M4: record repeated rotation regression evidence
Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.
2026-10-18 11:51:40 +02:00
Mex 19d138e951 M4: accumulate repeated page rotation indices
Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.
2026-10-18 11:51:29 +02:00
Mex 0897789788 Merge branch 'main' into fix/csv-tab-field-names 2026-10-18 11:14:16 +02:00
Mex 5af8e88fca Merge branch 'main' into fix/organize-resolve-indirect-rotation 2026-10-18 11:12:44 +02:00
Guillem Hermida 58d3211e76 Merge remote-tracking branch 'origin/main' into fix/icc-profile-cache-key-by-content
# Conflicts:
#	vendor/README.md
2026-10-18 10:47:59 +02:00
Guillem Hermida a890ecaaf3 Merge remote-tracking branch 'origin/main' into fix/1201-default-colour-spaces
# Conflicts:
#	vendor/README.md
2026-10-18 10:47:56 +02:00
Guillem Hermida ec8e30ad80 Merge remote-tracking branch 'origin/main' into fix/2583-note-icon-no-rotate
# Conflicts:
#	ROADMAP.md
#	crates/ui-egui/src/comments_panel.rs
#	vendor/README.md
2026-10-18 10:47:54 +02:00
Guillem Hermida 21909d4ec3 Merge remote-tracking branch 'origin/main' into fix/1201-optimize-keeps-jpeg-colortransform
# Conflicts:
#	ROADMAP.md
2026-10-18 10:47:53 +02:00
Charan Rathore e08950c8e6 hayro-cmap patch: truncated metadata-only streams report failure
Character-collection metadata is not a mapping: a truncated stream that
parsed only /Registry //Ordering //Supplement produced an empty CMap,
which callers accept and which then blocks the encoding fallback paths.
Drop the metadata exemption so a truncated stream with no mappings
returns no CMap, exactly as a full parse failure did before.

Signed-off-by: Charan Rathore <180254320+charan-rathore@users.noreply.github.com>
2026-10-18 13:34:49 +05:30
Charan Rathore e704504204 hayro-cmap patch: fix guard, entry alignment and array drain
- The truncated-stream guard was dead: completed was set on the break
  path too.  Detect truncation from the scanner position so a stream
  that yields nothing still reports failure and callers fall back to
  the font encoding.
- A skipped bfchar/bfrange entry consumed a different number of
  objects than a parsed one, shifting every later pair onto the wrong
  code.  Skip the entry's remaining objects as well.
- A bfrange array that runs out or breaks cannot be resynced to a
  member boundary; stop the loop instead of spinning over the rest of
  the range.

Signed-off-by: Charan Rathore <180254320+charan-rathore@users.noreply.github.com>
2026-10-18 13:21:57 +05:30
Marco ArnulfoandBrandon Thomas 50f261d70c M6: detect a row of comb boxes as one comb text field (#2111)
* M6: detect a row of comb boxes as one comb text field (fixes #1887)

* M6: keep empty tables of wide cells out of comb detection (#1887)

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:50:00 -04:00
EnricoandClaude Opus 5.5 eeadc79d15 M4: reverse page order (organize.reverse-pages) (#1720)
Organize pages ▸ More ▸ Reverse pages (and the command palette) reverses
the selected pages among their own positions, or the whole document when
fewer than two pages are selected. It is one undoable step, needs the
assemble permission like the other page edits, and is available headless
as the page_reverse automation tool (pages optional, default all).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:48:47 -04:00
Marco Arnulfo bd0b3fdb65 Save As to another file writes a clean copy without earlier revisions (fixes #2165) (#2174) 2026-10-18 03:48:35 -04:00
wstockandBrandon Thomas 72dac33f8e Issue #2367: an object stream whose entry cannot place it is found by its header (#2398)
* Issue #2367: an object stream whose entry cannot place it is found by its header

qpdf's xref-compressed-in-compressed.pdf marks object stream 69 as itself
compressed inside stream 81, which the format forbids and which does not
exist. objstm() loaded the container through its cross-reference entry only,
so every object inside (the outlines, the page labels) read as null and a
full save wrote `/Outlines null /PageLabels null`, while an incremental save
(which copies the original bytes) kept them, and MuPDF recovers them.

The container is now found by its own `N G obj` header when its entry cannot
load it (scan_for, the same last resort a plain object with a wrong offset has).

Regression test: document::tests::an_object_stream_marked_as_compressed_is_found_by_its_header.
Verified on the fixture: objects 70 and 82 load (Count 6; 12 page-label
ranges) and a full save keeps both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011K1peyrh5MdZ6ttHkVq8Gh

The fallback is for file entries only: once the stream is freed or replaced in the overlay,
its objects must not come back from the file's copy (sign's
object_stream_changes_cannot_reuse_old_candidate_membership caught this after the rebase);
a cos test covers both cases.

* cos: a container the saved table marks free is not found by its header either

Freeing an object stream, saving incrementally and reopening left the
overlay empty and the table saying the stream is free; the header
fallback then rescanned the file and the stream's objects came back on
the next full save. The fallback now also stops at an on-disk free
entry.

* M1: put the #2367 test's description on the test, not its fixture

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:48:23 -04:00
Ridwan 5827afdcde M8: measure fonts without /Widths with their embedded program (#2130) (#2232)
Find and the renderer place glyphs of a simple font that has no /Widths
with the embedded font program's own advances, but redaction guessed the
widths from the font's name (a standard-14 face, Helvetica by default).
When the guess was wider than the real font, the end of a line under a
mark seemed to lie past it and was kept in the content stream, and the
verify pass, using the same guess, agreed.

Metrics now fills the width table from an embedded sfnt program
(FontFile2, or OpenType FontFile3) when /Widths is missing, looking the
glyph up as the renderer does. A subset's unmapped codes get width 0 so
has_glyph stays false for them. Fonts with /Widths, and bare CFF or
Type 1 programs, are unchanged.

Fixes #2130
2026-10-18 03:48:00 -04:00
94f1a0d157 fix(engine): a new or recovered document stays unsaved through undo (#2346)
* fix(engine): a new or recovered document stays unsaved through undo

#1435 made undo back to the last saved bytes clean. For a document
created by Combine, Extract or Create, or restored from recovery, those
bytes were never written where the document belongs, so edit then Undo
made it clean: Close no longer asked to save and autosave stopped keeping
it. Editor::unsaved_file, set by open_new and mark_recovered and cleared
by mark_saved, keeps such a document unsaved until it is saved; Revert
follows the same rule.

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

* fix(engine): test that doc_close refuses a recovered or new document after undo

Review follow-up on #2346: a recovered document and a new one, rotated
and undone, are refused by doc_close without discard_changes, and close
with it.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:47:43 -04:00
komo1iddinandClaude Opus 5.5 36d239d2b1 M7: fold repeated group moves of a cropped image into one clip cm (#2476)
Moving an image whose crop is its own (`q re W n cm Do Q`) as part of a
group put a fresh `cm` before the clip path on every move, so twenty
moves left twenty `cm` operators in front of one `re`. It never nested
deeper, but the content stream grew with every drag.

When a `cm` already sits right before that clip (one an earlier move
added, or the file's own), `translate_images` now replaces it with the
product of the new and old matrices. The CTM at the clip is
`c · prev · outer` either way (ISO 32000-1 8.3.4, 8.4.4). A previous
`cm` that is not finite, or a product larger than 1e12, keeps the old
behaviour and inserts a separate `cm`.

The new test moves an image cropped smaller than itself twenty times and
checks that the image and its visible crop both moved by the full offset,
that the operator count stays at its value after the first move, and that
the caption after it is untouched. It fails without this change
(32 operators against 13).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:47:08 -04:00
7c8ca0bde8 M7: keep the text blocks cache after the document is edited (#2504)
The engine caches a page's text blocks for opening a paragraph and
applying its edit, but only while the document is unmodified: it was
keyed by the source identity, which edits keep. After the first edit the
cache never hit again, and the page was grouped again on every call (the
inline editor and the panel each call it after an apply).

pdfcraft_cos::Document::read_state() returns what the document reads as:
its source identity, its trailer and every changed object, compared as
allocations that the state keeps alive. A clone or a snapshot restored by
undo has the same state; any set, add, free or trailer change makes a
different one. The cache is keyed by it, so it hits after edits and never
returns another state's blocks.

On a page of 120 rows, text_blocks after an edit took 0.215 ms per call
and now 0.006 ms (release build, best of three; a throwaway timing test).

Tests: a read state matches a clone and not a document changed by set,
free or a trailer change, nor another document; after an edit the engine
groups the page once and undo and redo get their own blocks. The engine
test fails without this change.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:46:56 -04:00
Marco Arnulfo 946c6fdfc4 Comments: an attachment comment's menu saves or opens its file (fixes #2565) (#2585) 2026-10-18 03:46:10 -04:00
Marco Arnulfo cef2255da5 Edit text: a loaded font is applied to the paragraph being edited (part of #2591) (#2599) 2026-10-18 03:45:58 -04:00
mtlcl410andClaude Opus 5.5 2c4b84f6af M8: redaction removes the replies of the annotations it removes (#2659) (#2697)
* M8: redaction removes the replies of the annotations it removes (#2659)

A reply (/IRT) placed away from the mark survived, and its /IRT kept the
removed annotation - its comment text and the QuadPoints of the redacted
words - in the saved file. Replies to a removed annotation, and replies to
those, now go with it.

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

* M8: test replies listed before their parent, with a pop-up on a reply (#2659)

Review of #2697: the replies precede the annotation they answer in
/Annots, deepest first, and a reply has a pop-up. A single pass in
/Annots order would keep them; the test fails with the loop cut to one
pass, which the first test does not notice.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:45:46 -04:00
Charan Rathore bf27cb259a Vendor and patch hayro-cmap for tolerant CMap parsing
A /ToUnicode CMap with any unparsable part made the reading path
discard the whole map and fall back to the font encoding, so search
and redaction missed text the edit layer could read.  Vendor
hayro-cmap 0.1.0 and patch it to skip malformed bfchar/bfrange
entries, unresolvable usecmap references and unparseable tails,
keeping every readable entry, matching MuPDF/pdftotext and
PdfCraft's own edit-layer parser.

Fixes #3177.

Signed-off-by: Charan Rathore <180254320+charan-rathore@users.noreply.github.com>
2026-10-18 13:07:15 +05:30
mtlcl410andClaude Opus 5.5 0f870c7b7d fix(forms): read dynamicRender only at config/acrobat/acrobat7, in UTF-8 or UTF-16 (#3180)
Review follow-up. The config packet is decoded as UTF-16 (byte order mark,
or `<` written as two bytes) or UTF-8 before the scan, as crates/xfa reads
packets. The scan walks tags, skipping comments, CDATA sections, processing
instructions and declarations, and takes the value only at
config/acrobat/acrobat7/dynamicRender, so the same name in form data, in a
comment or elsewhere in the config no longer counts.

The engine test now adds the element to the fixture's own config packet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-18 15:33:57 +08:00
NetterSilverandClaude Haiku 5.5 5377a223e9 Extract RTL lines with numbers and punctuation in logical order (#2736)
Fixes #2667

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 03:14:51 -04:00
mtlcl410andClaude Opus 5.5 e827a2a17c M6: a box drawn with points along its sides is detected as a box (#2942) (#2947)
Field detection read a path as a rectangle only when it had exactly four
corners, and gave up on a subpath once it reached five points. The French
income tax form 2042 draws its letter cells with a midpoint on each
vertical side (six points), so most of its cells were not found.

A subpath now keeps up to 16 points, and a point lying on a straight side
between its neighbours is dropped before the four-corner test. A point
that turns back along a side is not between its neighbours and stays, so
a path that doubles back is still not a box.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:14:36 -04:00
NetterSilverandClaude Haiku 5.5 43f8ddf264 Apply JPEG EXIF orientation when creating PDFs from images (#2951)
Fixes #2944

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 03:14:25 -04:00
NetterSilverandClaude Haiku 5.5 17ddc8707d Fix overflow panic for i64::MIN index in content_update/delete (#2979)
Found while testing crates/automation/src/content.rs:141

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 03:14:13 -04:00
Kurtulus GemiciandClaude Opus 5.5 70d7df548e fix(i18n): the web build's Auto language follows the browser (#2954) (#2998)
On wasm, detect_system_lang() always returned English, so Auto showed
English whatever the browser's language. It now reads
navigator.languages in order, then navigator.language, and takes the
first tag with a catalog, as the desktop builds do with the system
languages and as VectorCraft and FilmCraft do in their browser builds.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:14:01 -04:00
Guillem Hermida 233767d181 fix(ui): the Hand tool moves the page with the pointer (#924) (#3017)
The Hand tool scrolled by the page widget's drag delta from inside the
page view's ScrollArea. egui applies that delta when the area ends, after
the frame's pages are laid out and drawn, so the page showed each move a
frame late and trailed behind the pointer. (Until the page view stopped
animating its scrolls, the delta was also eased in over several frames,
which is the stutter #924 and #1992 describe.) On top of that, the points
the pointer moved before egui tells a drag from a click were dropped, so
the page never caught up with the grab point.

Pan before the area lays the pages out instead: when the page widget is
being dragged with the Hand tool, set the scroll offset from the last
offset minus this frame's pointer delta, or minus everything moved since
the press on the frame the drag is recognised. The page now moves point
for point with the pointer in the frame it moves, and still stops where
it is let go (no fling). Other scrolls (go to page, links, zoom anchors,
compare sync) are untouched and still win when asked for in the same
frame.
2026-10-18 03:13:49 -04:00
skylar taylor-barrick 7cce65917d Control channel: don't lose the refusal reply to a TCP reset (fixes deadline test flake) (#3165)
* Control channel: don't lose the refusal reply to a TCP reset

When the control channel refused an unauthenticated connection (deadline
passed, overlong line, bad first request) it wrote the error line and
closed the socket at once. If the client had sent input the server had
not read, Linux answers that close with a reset instead of a FIN, and the
reset can discard the reply before the client reads it. That is the
flake in an_unauthenticated_connection_is_closed_at_the_deadline: the
dripping client's bytes were still unread when the server closed.

Now the server shuts down its write side after the reply and reads and
discards input until the client closes or a 1 s linger passes, and only
then closes. The new test a_client_refused_with_input_unread_gets_the_reason
pins it deterministically.

* Control channel: refused connections linger outside the auth places

A connection refused before auth used to keep its place among
max_unauthenticated while it lingered (up to 1 s), so refusals could
keep real clients out. Now `refuse` gives that place back and takes one
of a separate, smaller set of lingering places (Limits::max_lingering,
8) before the reply is written. When none is free the connection is
closed right after the FIN instead of lingering, so threads stay
bounded as #1806 intended.

Tests: a_lingering_connection_holds_no_place_to_authenticate and
only_so_many_refused_connections_linger.

* Control channel tests: a 5 s linger for the two linger tests

Their timing checks (the auth round trips finishing within the linger,
and a non-lingering connection failing a write within half of it) had
only 1 s and 500 ms of margin with TEST_LIMITS. Each now serves with its
own Limits and a 5 s linger; the assertions are unchanged and neither
test waits for the linger on its passing path.
2026-10-18 03:13:07 -04:00
mtlcl410andClaude Opus 5.5 15af2860f1 fix(ocr): the text layer restores the rendering mode it sets (#3029) (#3033)
text_layer wrote '/OCR BMC BT 3 Tr /PCHelv 1 Tf ... ET EMC'. Tr and the
font are graphics state, and ET doesn't reset them, so every content
stream after the layer on the page inherited 3 Tr: a header or footer
added after OCR was in the file but drew invisibly, in PdfCraft and in
MuPDF. The layer is now wrapped in q ... Q inside its marked content.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:12:37 -04:00
mtlcl410andClaude Opus 5.5 4ff53a8d8e fix(engine): printing keeps layers hidden in the Layers panel hidden (#3046) (#3050)
The print-ready PDF copied the document's /OCProperties as they are, so it
printed the layers in their default state: a layer hidden in the Layers
panel disappeared on screen and came back on paper. Print as image already
used the view's layer choices (it renders with the document's config).

Once the layers have been changed, the copy imposed for printing now has
its default configuration (/D) set to them: /BaseState /ON, /ON the
visible layers, /OFF the hidden ones, its other entries kept. With the
layers untouched, the document's own configuration prints as before.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:12:26 -04:00