page_render's hide_text_block used the paragraph number as a position in the page's current
paragraphs, so after an edit regrouped the page it hid a different paragraph from the one
text_edit would rewrite. It now resolves the number against the text_paragraphs listing and is
refused when the paragraph is gone.
text_edit also accepted any overlapping paragraph that contained the requested text as the
edited one. When the edit merged the paragraph into a neighbour, the merged paragraph contains
that text too, so the number followed it and a second edit by that number dropped the
neighbour's text. The edited paragraph must now show exactly the requested text; a merged one
leaves the number stale.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Paragraph numbers are positions in a list regrouped from geometry after
every edit, so an edit that merged a paragraph into its neighbour made the
next edit by number rewrite another paragraph and lose its text.
After text_paragraphs, text_edit and text_edit_layout find the listed
paragraph again by its text and box, and refuse one that no longer exists on
its own (list the page again). An edit's result names the numbers that are
gone in stale_paragraphs. The inline editor's switch-paragraph click holds
the same key instead of an index. Without a listing a number is a position,
as before. An identity that survives save and reopen is still #2508.
attachment_add compared every attachment name, including files attached to pages as comments, so a page comment with the same file name made the reported key empty. It now compares only the document-level name-tree keys. Deleting the last document attachment when the catalog's /Names is an indirect dictionary left an empty /Names behind; it is now removed from the catalog, as the direct case already did.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A font naming a Latvian comma-below letter by its code point (/uni0136) or by
the glyph list's name for it (/Kcedilla, /Gcedilla...) still drew nothing:
the name resolves to the cedilla name the glyph list prefers, which the
composite table does not hold. The thirteen comma-below letters are now
looked up by character, which also replaces the special case for U+021A and
U+021B. T and t with a cedilla (U+0162, U+0163) are not in that table and
keep their cedilla.
Raised by CodeRabbit on the PR.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`[(FIRSTARR)] [(LASTARR)] TJ` is malformed (TJ takes one array
operand), but the operator still shows the one immediately before it,
as the renderer, MuPDF and pdftotext agree. Three readers here counted
operands from the front instead and showed the discarded first array:
- crates/edit/src/text.rs (text_lines/text_paragraphs, and the Office
export through reading_lines)
- crates/redact/src/interp.rs (search-based redaction's content
interpreter)
- crates/a11y/src/headings.rs (autotagged heading detection)
So the editor could offer a word the page never draws, a redaction
search could miss it, and a heading could read as text nobody sees.
Take the last array for TJ in all three, matching the `_ =>
op.operands.last()` arm already used for Tj/'/" alongside it.
Added a regression test per crate (`a_tj_with_a_surplus_array_reads_
the_last_one`, `a_tj_surplus_arrays_undrawn_glyphs_are_not_seen_
under_a_mark`, `a_heading_with_a_tj_surplus_array_reads_the_one_the_
page_draws`), confirmed each fails without its fix and passes with it.
cargo test -p pdfcraft-edit (196 passed) -p pdfcraft-redact (81
passed) -p pdfcraft-a11y (44 passed), fmt, clippy -D warnings, xtask
layers and xtask assets all clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
load_certificates now reads PKCS #7 certificate bundles (degenerate
SignedData, RFC 5652 §5.2) in DER and as PEM PKCS7/CMS blocks, the way
Windows, Acrobat and certificate authorities export chains. PEM END
lines must match their BEGIN label.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The stand-ins for the standard fonts (PDFium's Foxit fonts) have neither the
comma-below letters nor a commaaccent glyph, and the composite-letter patch
left comma accents out. A non-embedded font naming /scommaaccent therefore
drew nothing, which is the blank "si" left in Romanian XFA forms, and
/tcommaaccent drew a cedilla through the glyph list's alias to U+0163.
Romanian s/S/t/T and Latvian k/K/l/L/n/N/r/R/G with a comma below are now
built from the letter and the font's comma, scaled to three quarters and set
just under the baseline, moved back along the slant in italics. The uni021A and
uni021B names of T and t with a comma below, which the glyph list
does not name, resolve by code point. Lowercase g with its turned comma above
is still not built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New settings start with Highlight fields on. A stored choice is kept; a missing or
malformed value falls back to on. Preferences > Appearance gets the checkbox.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Byte-only drops (no filesystem path) have nothing for tab_showing to
match and open another tab, pinned by
opening_documents_without_a_path_still_open_twice; the drag-drop-open
note claimed focus for "a dropped document that is already open"
without that limit.
The harness window is live, so open_path hands the read to the background
worker and run_steps(2) could not promise the worker had finished: under
load the file could be deleted before it was read, failing the test with
"no second tab". wait_until polls for the tab the way the drop tests
already do. Proven by delaying the worker 300 ms: the old test fails at
views.len() == 0, this one passes.
Conflicts: crates/automation/tests/automation.rs (code resolved by hand: crates/automation/tests/automation.rs). Catalogs keep upstream's rows, then this branch's rows upstream lacks; ATTRIBUTION.toml is upstream's with every sha256 recomputed; progress lines keep both sides, upstream's first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A content-type attribute value or an eContentType that was not an OID
was read as missing, which skipped the check that the two match. Such a
CMS is now not read, and neither is one without an eContentType.
A PDF signature's eContentType must also be data. With a content-type
attribute this already follows from the checks on the attribute.
Without one, a signature whose eContentType named another type could
still validate as a signature of the document. It is now invalid.
The PDF test now covers a wrong eContentType and a repeated
content-type attribute, and the README says that a missing
content-type attribute is read anyway, with a note.
RFC 5652 allows one content-type and one message-digest attribute in
the signed attributes, each with one value. The parser kept the last
attribute and the first value, so a CMS with two of either could mean
one thing here and another in a different reader. Such a CMS is now
not read.
The content-type attribute must also name the type of the encapsulated
content. The signature covers the attribute but not eContentType, so a
CMS where the two differ is not read either.