A simple font that a document names but doesn't embed (Arial,Bold, ArialMT, ...) is
drawn with a standard-font stand-in, and each glyph was scaled horizontally to its
/Widths advance, narrower or wider. With 600-unit widths, Helvetica-Bold's i and l
(278 units) were widened 2.16 times into solid blocks, and the line read as a heavy
fixed-pitch face. The advance already places the next glyph (ISO 32000-2 §9.2.4;
Widths, Table 109), so a substituted glyph is still narrowed to a narrower advance
but no longer widened. Exact standard-14 fonts are unchanged.
Vendored hayro-interpret patch 57 (StandardKind::outline_glyph), with tests in
crates/render/tests/it/substitute_widths.rs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
An annotation whose Invisible flag (bit 1) is set and whose /Subtype is none
of the standard types of ISO 32000-2 Table 171 is no longer drawn (Table 167)
or printed (Table 167: "do not print it even if the Print flag is set"). A
/Popup annotation's /AP is no longer painted as page content: per §12.5.6.14 a
popup has no appearance stream of its own and is shown by the viewer as a
window for its parent. Printing already left popups out.
Link appearances are painted as before (§12.5.5).
hayro-interpret patch (56), recorded in vendor/README.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
cargo deny refuses crypto-bigint 0.7.5, which pdfcraft-sign uses through
rsa and crypto-primes. 0.7.6 is a semver-compatible patch release with the
fix.
The on-screen drop-down button for combo boxes drawn display-only, the
narrowed text area beside it, and the Print as Image copy without it
(with_printed_field_appearances, printed_display_bytes) are removed.
Open PR #3567 draws the arrow for every combo box in the page view.
Check boxes, radio buttons, push buttons and comb lines are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
A form that leaves its appearances to the viewer had its text and choice
fields drawn for display (#3475), but not its buttons. Now, also for
display only, without changing the file:
- Check boxes and radio buttons with no normal appearance for their
state show their /MK background and border (Table 192), and when on,
the /MK /CA caption as a standard-14 ZapfDingbats character
(12.7.5.2.3), 4 or l by default, in the /DA colour, auto-sized for
0 Tf.
- Push buttons with none show their /MK background, their /BS border
(beveled per Table 168) and their centred caption.
- Comb fields with a border colour draw the MaxLen - 1 lines between
their positions (Table 228), up to 500 cells. This also applies to the
appearances PdfCraft saves for comb fields.
- A combo box drawn for display shows a drop-down button on screen, with
its text kept clear of it. Saved appearances and Print as Image leave
it out (with_printed_field_appearances, printed_display_bytes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
Main took hayro-syntax patches 26 and 27 (xref streams), so the patch
that keeps up to 256 operands per instruction moves to 28 in
vendor/README.md and in every comment that cites it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
ISO 32000-2 §8.6.6.5: "DeviceN colour spaces (PDF 1.3) may contain an
arbitrary number of colour components", and scn takes one operand per
component. The content parser (vendored hayro-syntax) held ten operands
per instruction; pushing the eleventh failed, and the failure ended the
whole content stream. A twelve-ink background fill therefore drew
nothing, and nothing after it was drawn either: a blank page, no error.
hayro-syntax patch (26): an instruction keeps up to 256 operands (enough
for the largest DeviceN space the interpreter reads, 255 inks, plus a
pattern name). Past that the oldest are dropped, in order, rather than
the stream ending: operators already read the operands nearest to them,
so this is how stray operands are treated below the cap too.
hayro-interpret patch (55): a Separation or DeviceN tint transform that
can't be read no longer makes the colour space unreadable (its paints
were drawn in DeviceGray from the first tint, white for a full tint);
it keeps the space, and like a transform that fails when evaluated it
paints that colour in the alternate space's initial colour. Those
fallbacks, and a cs/CS colour space that can't be read, were silent:
they are reported once per content stream as
InterpreterWarning::ColorFallback, which pdfcraft-render records as
RenderWarning::ColourFallback and pdfcraft-cli render prints.
Tests, each seen failing with its fix reverted: the report's twelve-ink
page paints black and draws what follows; a four-ink control; 300 stray
operands before rg still draw; a calculator tint transform that pops
too much, and an unknown function type, fall back with one warning and
the rest of the page drawn; hayro-syntax keeps all twelve scn operands
and, past the cap, the latest 256 in order (fails without the reorder).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
* chore: resolve SonarCloud maintainability issues
* fix(packaging): use verify_sha256 helper function
* style: cargo fmt
* fix: restore wildcard imports and fix method-reference type errors
Revert the glob-import expansions (they missed SomIndices and left unused
imports), revert char::is_ascii_digit where the iterator yields char by
value, and pass the content slice to prune_doomed_xobjects as &[Vec<u8>].
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep XObject decodability check without binding content in interp
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
Comments that arrive without /AP are drawn from their dictionaries by
appearance::build, which lost three entries:
- /Border [hr vr w [dash]] (Table 166): the dash array made the whole
array unreadable, so the width fell back to 1 and the border was solid.
The width is now element 2 whatever follows, and without /BS the
/Border dash array is drawn. /BS, when present, wins for both (Table
166: "the Border entry is ignored"), so a /BS without /W means 1.
- Dash arrays are drawn only when finite, non-negative, not all zero
(§8.4.3.6) and at most 64 long; anything else draws solid.
- Ink applies its /BS dash pattern (Table 168, Table 182), like the
other stroked shapes.
- A caret with /Sy /P (Table 183) shows a ¶ (Helvetica, WinAnsi 0xB6) to
its right in its colour, inside /Rect: in the room /RD leaves, or by
narrowing the caret. /Sy /None draws as before.
The Helvetica font resource, written out three times, is one helper.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
* fix(render): bound xref streams, JBIG2 referred-to counts, saved graphics states and /W2 runs
Input bounds and crash fixes in the vendored hayro crates, ported from
the snapshot craft-libs carries (storytold/craft-libs#176, #239, #241):
- hayro-syntax: an xref stream subsection must be backed by its decoded
data. All-zero /W widths and a count longer than the data are refused
before any entry is inserted; a tiny stream with a large /Size or
/Index count inserted that many entries.
- hayro-syntax: a subsection running past object number u32::MAX is
refused; start + i overflowed.
- hayro-syntax: the xref stream type field is read big-endian at its
full /W[0] width, and /W[0] over 8 bytes is refused like /W[1].
- hayro-jbig2: the long-form referred-to segment count is capped at
u16::MAX before its retention bytes are read; the 29-bit count went
straight to Vec::with_capacity (about 2 GiB) and aborted the process.
- hayro-interpret: at most 4096 saved graphics states holding at most
64 MiB together. A q past either limit is counted but not copied, and
its Q still removes the clips added since.
- hayro-interpret: a /W2 run whose first CID is past MAX_CID stops there,
as /W does; a first CID of 4294967295 discarded the whole /W2 table.
Each patch is marked in the source, has a regression test in its crate,
and is listed in vendor/README.md.
* fix(render): a binary usecmap counts against the CMap nesting limit; /W2 checks the CID first
- hayro-cmap: a binary CMap's usecmap passed its own depth to the nested
parse, so a self-referencing binary CMap recursed until the stack
overflowed. It now counts like a text usecmap (ledger hayro-cmap 2).
- hayro-interpret: the /W2 first-CID check runs before the run's metrics
are read, so an incomplete run past MAX_CID no longer discards the rest
of the table (ledger 52).
* fix(render): refuse xref-stream object numbers past i32::MAX
ObjectIdentifier stores an i32; a number between i32::MAX and u32::MAX
was cast to a negative object number (ledger hayro-syntax 26).
Review follow-up on #2195: the last view stored ViewDefaults::zoom_name(),
which rounds 62.5% to 63%. A fixed zoom is now written with its fraction
(with_zoom already reads it back); the fit tokens are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
CodeRabbit on #2195: a file opened while full screen was on entered the presentation
first, and the remembered view then replaced its one fitted page, while leaving full
screen went back to the display from before the restore; and a tab closed (or settings
saved) in full screen remembered full screen's temporary fit and display. The remembered
view is now applied before the presentation starts, and the view remembered is the
normal display (DocView::normal_display: the one presenting replaced) at the page
reached.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Catalan, Finnish, Hindi, Indonesian, Korean, Romanian and Albanian have coverage
tests the new preference failed after rebasing onto main; Norwegian Bokmål and Thai
get it too. AI-assisted, marked as not reviewed by a native speaker. Lao and Central
Kurdish fall back to English for now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file opened again shows the page, zoom and page display it was last
shown with, as Acrobat's "Restore last view settings when reopening
documents" does: remembered by path (the last 100 files) when its tab
closes or settings are saved with it open, applied over the file's own
initial view, on by default and switched off in Preferences ▸ Documents
and view.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An AP-less stamp whose /Name has no drawing got its placeholder frame in
the annotation's /C colour (/C [1 0 0] drew a red frame), while the doc
comment and parity note promise a neutral grey one. The frame now always
strokes PLACEHOLDER_GREY; the fourteen Table 184 names keep /C.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
dialog hides Two-sided while Booklet is chosen and prints the booklet two-sided itself
(Using Acrobat X Pro, Print dialog box options: "Acrobat automatically enables duplex
printing, if available, for booklet printing"; later manuals and the current help no
longer say so, and on a current Acrobat the Two-sided control is shown for Size and
hidden for Booklet with the same duplex-capable printer). print::booklet_duplex gives a
booklet's setting: both sides flip on the short edge, the fold of a 2-up sheet turned
sideways; front or back only is one pass of a manual duplex and prints one-sided. The
Print dialog hides Two-sided while Booklet is chosen and sends that setting, leaving the
Two-sided choice as it was for the other layouts (and as #2001 remembers it); doc_print
applies the same rule. This replaces the first version, which rewrote Two-sided to the
short edge when Booklet was chosen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A rubber stamp from another program with no normal appearance was not
drawn at all. The display copy now gives it one:
- the fourteen standard names of Table 184 draw that stamp with
PdfCraft's stamp drawing (eight from the palette, six with labels of
their own), fitted to /Rect, in /C or the stamp's colour;
- no /Name draws Draft, the table's default, unless /IT says the stamp
is an image or a snapshot;
- any other name (names match byte for byte, §7.3.5) draws a thin grey
frame with both diagonals, so the stamp stays visible and selectable.
The drawing is display-only, like the other comments without an
appearance: Save writes the file as it was, and build() still declines
these stamps, so restyling and flattening never write it into the file.
A stamp with its own /AP /N is never touched. stamp() now takes the
label and the sign-here flag instead of a StampKind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
The calendar under a date field had no way to pick today: from another
month the only way back was the arrows. A Today button now sits under
the days, centred under the grid. It picks today from whichever month
the calendar shows, exactly as a click on today's cell would: written
with the field's own format through format_date, then the same commit
path, so the field's scripts treat it as they treat a clicked day, the
pick is one undoable edit, and the field and its calendar close. A year the field's date can't hold offers
no button. Hovering it shows the full date. One UI string in all 33
catalogues, marked AI-assisted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
Review: copying an added JPEG 2000 image failed even within its own
document, because the exporter can't write JPEG 2000 yet. Add Image stores
such a picture as the file it was given, so that file is the copy: it
pastes into the same document as the same object and into another as one
embedded image. The notes now also say that a CMYK picture stored
losslessly turns to RGB in another document.
Co-Authored-By: Opus 5.5 medium ultracode <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEgu9NcTcPqKovsLyfPxK
Edit::PasteContent pastes an added text box or image copied in the same
document or another, as it was (a text box's style; an image's picture,
turns, flips and crop), as one undo step. A pasted image draws its picture
from the copied object while that is still the picture in the target, else
from an added image there that already holds it (an earlier paste from
another document), else the file is embedded. Document::added_image_file
gives a copied image's picture as a file and honours the copy permission.
pdfcraft_edit::added::paste_rect / paste_place place a paste where the item
was, 18 pt down and to the right past each copy of it on the page (an image
with the same box and transforms counts whichever object holds its
picture), or at a point, kept on the page.
The content_copy tool drives it headlessly, across pages and documents.
The desktop keys and menus come in follow-up PRs.
Co-Authored-By: Opus 5.5 medium ultracode <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEgu9NcTcPqKovsLyfPxK