Add a clean-room Romanian translation of the UI catalog (2,670 entries,
every bundled key), the CLDR plural rule for Romanian (one / few /
other, "20 de pagini"), the ATTRIBUTION.toml entry, coverage and plural
tests, and the localization docs and roadmap entries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JadgGo4G2GHaAMNb3qyygk
crates/xfdf/README.md, parity/acrobat-features.toml: next's text, which has the same sentences without the formula-cell one the #2688 integration fix removed.
crates/xfdf/src/tests.rs: the XML comments test once, where next has it.
ROADMAP.md, docs/roadmap.md: union of both sides' lines, each once.
ROADMAP.md, docs/roadmap.md: union of both sides' log lines and milestone rows, each line once.
parity/acrobat-features.toml, crates/xfdf/README.md: next's text, a superset of main's (#1917, #2686 plus #2688).
crates/edit/src/tests.rs: both sides' new tests (#2558 and #3084).
crates/ui-egui/src/editing.rs: the picked picture is cleared on every edit, and a new drawing commits its raster (#3149).
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
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
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
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.
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
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.
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>
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>
- 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>
* 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>
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>
* 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>