Commit Graph
267 Commits
Author SHA1 Message Date
aiongg cf0f80604f Allow OFL typefaces, Noto CJK and Source Han included (AGENTS.md §1.1) (#3908)
* Allow OFL typefaces whoever designed them (AGENTS.md §1.1)

Noto CJK and Source Han from their open-source releases are allowed like any
other OFL font. The fonts build no longer skips them, the asset check accepts
an OFL font credited to Adobe, and the docs describe the Chinese faces that
result. The ban on Adobe icons, artwork, UI graphics and the fonts Adobe
products install stays.

* Leave out whole CJK faces other than the Japanese ones for size

A Chinese or Korean face is embedded whole only under 4 MiB, so the 16.4 MB
Noto Sans CJK SC in craft-fonts stays out of the binary and the embedded
total against the pinned craft-fonts is unchanged (25,204,828 bytes).

* docs: replacement boxes for Chinese without craft-fonts only on the web or without an installed Chinese face

* xtask assets: the OFL font exemption needs at least one licence ID

* fonts build: count a CJK script tag with a region (Hant-HK) as CJK, shared with the embedding test

* AGENTS.md: the asset gate exempts OFL fonts from the Adobe-author check

* fonts build: count a Hangul-only face (Hang) as CJK for the size rule

* fonts: the CJK embedding test checks the size rule, sharing its constants with build.rs

* docs: the missing Hans face is a property of the pinned craft-fonts, not of every build
2026-10-23 13:02:56 -04:00
Jayesh Prajapati 70704ff260 M2: text extraction finishes a column before the next one across a paragraph gap (#3658) (#4214)
* M2: text extraction finishes a column before the next one across a paragraph gap (#3658)

The block order took the top-most unread block next, so after the first
paragraph of a left column it jumped to the top of the right column and
read the rest of the left column last. Now the nearest block below the
one just read, overlapping it horizontally and at most 2.5 heights of
its last line below it, comes next, unless an unread block sits above it
in its own span (a column above a full-width heading or footnote),
beside it on its left, or shares its first line (the first lines overlap
by at least 30% of the taller one: a table or form row, which still
reads across, also with labels and values in different sizes or on
shifted baselines; a line that only touches it or a small footnote
marker is not a row). Footers, footnotes and captions further away keep
their old place. Extraction, Find and copy share this order.

Known limit: when a paragraph in the next column starts on the same line
as the continuation (text on a baseline grid), it counts as a row and
the top-most rule applies, so the order of #3658 comes back there.
Telling prose from a row in that case needs more evidence.

Fixes #3658

* M14: update ctutils to 0.4.5 for RUSTSEC-2026-0385

The dependency-advisory gate rejects ctutils 0.4.2 (non-constant-time
mask generation and u128 comparisons, fixed in 0.4.4). It comes in
through crypto-bigint for the signing crates. Only its version and
checksum change in Cargo.lock.

(cherry picked from commit 1f8811761a)
2026-10-23 10:43:10 -04:00
mtlcl410andClaude Opus 5.5 e6b9792539 M4: bookmarks show and set their title colour and style (ISO 32000-2 Tables 151, 152) (#4171)
* M4: the Bookmarks panel shows each bookmark's colour and style (#3193)

ISO 32000-2 §12.3.3: an outline item's /C is "An array of three numbers
in the range 0.0 to 1.0, representing the components in the DeviceRGB
colour space of the colour that shall be used for the outline entry's
text" (Table 151), and its /F flags say italic (bit 1) and bold (bit 2)
(Table 152). Both were ignored: every title was drawn in the theme's
text colour and face.

The inspector (OutlineItem) and the bookmark list (organize::Bookmark,
used once a document is edited) now read them: each component is kept
within 0-1, and black (the default) or a /C that isn't three numbers
reads as no colour. The panel draws bold titles in the semibold face,
italic ones slanted, and the colour as given, lightened towards the text
colour only as far as it needs to read on the panel (3:1), so dark blue
stays visible in the dark theme. bookmark_list reports color, bold and
italic where they differ from the defaults.

Mutants, each failing a test: italic or bold bit ignored (both readers,
the panel), black kept as a colour, no clamping, a four-number /C
accepted, colour ignored or truncated in the panel, the contrast floor
removed, always jumping to the text colour. Checks for non-finite
numbers were equivalent (clamping already covers any size), so they
are gone.

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

* M4: set a bookmark's style and colour from its menu or bookmark_set_style (#3193)

#3193 asks for a way to mark bookmarks by colour, for example chapters
that are rarely used. ISO 32000-2 §12.3.3 gives every outline item a
title colour (/C, Table 151, DeviceRGB, default black) and style flags
(/F, Table 152: bit 1 italic, bit 2 bold, default 0); the previous
commit shows them, and this one lets the user change them.

- organize: set_bookmark_color writes /C, removing it for black (the
  default) and refusing a component outside 0-1; set_bookmark_style
  sets or clears the bold and italic bits, keeps any bit the table
  doesn't define, and removes /F when no bit is left.
- engine: SetBookmarkColor and SetBookmarkStyle edits, undoable, under
  the same assemble permission as the other bookmark edits. Their undo
  labels are the menus' own names, already translated in every
  catalogue.
- ui-egui: the bookmark menu gains Style (Bold and Italic check boxes)
  and Colour (the comment swatch grid, with Other colour...). Only
  existing UI strings are used.
- automation: bookmark_set_style takes any of color ([r, g, b], 0-1),
  bold and italic, and changes only what it is given.

Mutants, each failing a test: no range check (library and tool), black
kept as /C, a bit not set, a bit not cleared, an untouched bit cleared,
/F 0 written, the bits swapped (library and menu), the tool accepting
nothing to change or ignoring bold/italic, the menu's colour ignored.

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

* M4: a colour and a style set in one bookmark_set_style call are one undo step (#3193)

Review on #4171 (CodeRabbit). Given both, the tool applied two edits,
so undo took back only the style and left the colour, and a failing
second edit left the first applied. They now go in one Edit::Batch
named "Style", all or nothing. "Colour" and "Style" join EDIT_LABELS,
and "Style" BATCH_LABELS, so the history can translate them.

The test sets colour and style together and undoes once; with the two
edits applied separately it fails.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:42:59 -04:00
mtlcl410andClaude Opus 5.5 ac41758cf3 M3: a Launch link that names a PDF opens it in PdfCraft (ISO 32000-2 §12.6.4.6) (#4185)
* M3: a Launch link that names a PDF opens it in PdfCraft (#4168)

Documents that cross-reference each other often use Launch actions to
open another PDF next to them (#4168: 268 links such as
..\se\se_ST.pdf). PdfCraft follows no Launch action, so clicking them
did nothing, while the same link written as a GoToR opens.

ISO 32000-2 §12.6.4.6: a Launch action "launches an application or
opens or prints a document". Starting programs is not something a
document should be able to do, but opening a PDF is what a remote
Go-To already does. So a Launch action whose /F names a .pdf file, and
that has no /Win launch parameters (application, operation,
arguments), is read as the remote Go-To to that file's first page
(LinkTarget::RemoteGoTo with launch: true). It then goes through the
same path as one: PDF only, --root confinement, confirmation for
absolute paths, opened as a document and never handed to the OS.
Windows writers separate such paths with \, where §7.11.2.1 uses /, so
a path without any / has its \ read as /. doc_info's link target says
"action": "Launch" or "GoToR".

Tests: launch_actions_that_name_a_pdf_open_it_like_a_remote_link
(relative with \, a file specification, an executable, /Win, a bare
.pdf, a mixed path, a URL file system) and
a_launch_link_to_a_pdf_opens_it_like_a_remote_link (link_open opens
the PDF in the root; a program link opens nothing). Mutants, each
failing: the /Win check dropped, \ always or never read as /, any
extension, a bare ".pdf", case-sensitive ".pdf", Launch not read.

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

* M3: a Launch link with a stray /D still opens its PDF (#4168)

Launch (ISO 32000-2 Table 207) has no /D, but launch_pdf went through
remote_go_to, which rejects the action when /D isn't a valid GoToR
destination. A Launch naming c.pdf with /D 0 stayed Other(Launch).
The /F parsing is now shared (action_file) and Launch ignores /D.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:42:32 -04:00
Dylan M. Taylor 8ee1ac0c58 Merge pull request #4117 from aiongg/fix/coderabbit-b-caps
fix(limits): bound the decoding and listing work that repeated references and small inputs multiply
2026-10-22 21:38:59 -04:00
aiongg 4362449fb2 fix(render): find an embedded file by key without the listing's budget
Extraction walked the name tree again with a fresh key budget, so keys
the listing had left out could fill it and a listed embedded file came
back as not found. The lookup compares each key with the one wanted and
keeps none, so it needs no budget; a key object or /Names array that
several places reference is converted once.
2026-10-22 23:32:42 +00:00
aiongg 8bcf83faef fix(render): spend the raw length of text that converts shorter
A kept web address or attachment text spent only its converted length,
so a long raw value that trimmed short was converted in full for every
entry sharing it. A kept conversion now spends the longer of its raw
and converted lengths.
2026-10-22 23:31:43 +00:00
aiongg 7a73c2ec3a fix(render): keep embedded-file keys within the text budget
The embedded-files name tree copied every key, and an attachment
without a file name copied its key again as the name, so many entries
sharing one long key string multiplied it. Keys, and a key kept again
as the name, now spend the 16 MiB link and attachment text budget; an
embedded file whose key does not fit is not listed.
2026-10-22 22:03:43 +00:00
aiongg a0a66b982b fix(render): read an annotation attachment's Contents only when it names it
An annotation attachment's /Contents was read and charged to the text
budget before the file specification's /UF or /F, which then named the
attachment, so the discarded text could leave a later web address
unlisted. /Contents is now read only when neither name is there.
2026-10-22 22:02:10 +00:00
aiongg aaa4b99eb9 fix(render): spend the text budget on a rejected conversion
A web address or attachment text that converted to more than the budget
left spent nothing, so every link or attachment sharing it converted it
again. A conversion that comes out too long now spends the raw length,
and so does attachment text that trims to nothing.
2026-10-22 21:54:39 +00:00
aiongg 426a609795 fix(render): keep link addresses and attachment text within 16 MiB
The link and attachment listings copied a link's web address and an
attachment's name and description for every annotation or name-tree
entry, so many entries sharing one long string multiplied it in
memory. Together they keep at most 16 MiB of that text, and a string
past what is left is not converted. A link past it lists as a URI
action without its address, an attachment under its generic name
without a description.
2026-10-22 21:35:23 +00:00
aiongg c82dc91abf fix(render): list an annotation reached through a reference chain once
The annotation listing skipped a repeated /Annots entry by its raw
reference, so an indirect object holding only a reference to a link
listed that link a second time. Entries and page arrays are now told
apart by the object their reference chain ends at. The lopdf reader
reported the first object of a chain, because lopdf's get_object
already follows it; it now reports the last, as the lazy reader does.
2026-10-22 21:33:20 +00:00
aiongg 071130d498 fix(render): list links and attachments after the comment budget fills
Reaching the 64 MiB comment budget ended the whole annotation scan, so
links and file attachments listed after it were missing, and a later
JavaScript link left the document unmarked. Past the budget, comments
are no longer built, and the scan goes on for links and attachments.
2026-10-22 20:05:05 +00:00
aiongg a0e82da864 fix(render): bound the replacement text copied into redraws
Each unmarked redraw of an /ActualText span's glyphs took its own copy
of the replacement text. The work budget counts comparisons, not
bytes, so a long replacement around one glyph followed by many redraws
at the same place copied it once per redraw: gigabytes from a small
page. Redraws on one page share 4 MiB of replacement text; past it a
redraw keeps its own glyphs, as it does past the work budget.
2026-10-22 17:43:04 +00:00
aiongg dfde90787f fix(render): list a repeated annotation once and cap what the listing keeps
Inspection built an annotation record, with owned copies of its text,
for every /Annots entry on every page, so one comment referenced many
times, a page-shared /Annots array, or many small comments sharing one
long /Contents string multiplied that text in memory. Pages are read in
order, an annotation or /Annots array already listed is skipped, and
the listing stops once the kept text and geometry pass 64 MiB.
2026-10-22 17:08:54 +00:00
aiongg 9526363152 Build each crate's integration tests as one binary (#4087)
* Build each crate's integration tests as one binary

Every tests/*.rs file is its own executable that links the whole crate
graph. Move each crate's test files into tests/it/ as modules of one
tests/it/main.rs, for apps/pdfcraft, apps/pdfcraft-cli, automation, cos,
crypt, engine, render, sign and ui-egui. ui-egui's ui_group binary and
the support modules move into tests/it/ too. Crates with a single test
file keep it. The test executables go from 150 to 52, and every crate
runs the same number of tests as before.

ui-egui perf keeps its own binary (own process): its frame-time budgets
would charge each measurement for the other tests running beside it.

What the move needs:
- include_str!/include_bytes! and #[path] paths gain one "../".
- ui-egui: one GPU lock in tests/it/main.rs replaces the five per-file
  ones (content, document_links, editing, fill_sign, view), so one
  process creates one wgpu device at a time. The drop-line test in ui.rs
  and the screenshot blocks behind PDFCRAFT_*_SHOT(S) and
  CRAFT_UI_DOCKING_DIR, which render without holding it, take it too.
- pdfcraft winit_wayland_scroll: the vendored winit source it compiles
  names crate::event, so that shim lives in main.rs.
- parity evidence names the new paths.

* Name the one-binary test paths in docs and comments

- READMEs, docs, ATTRIBUTION and source comments name tests/it/<file>.rs.
- The oracle README's example filters with --test it encryption::qpdf_agrees.
- pdfcraft's GPU lock comment points at the UI tests' shared lock in
  crates/ui-egui/tests/it/main.rs.
2026-10-22 11:28:58 -04:00
aiongg dcf2802f96 Merge r2 track t15: mixed: accessibility-tags, dialogs-prefs, javascript, organize-pages
# Conflicts:
#	crates/edit/src/tests.rs
#	crates/engine/src/tests.rs
2026-10-22 02:23:22 +00:00
aiongg 8bb2eef740 Merge r2 track t13: cos-parse, vendored-crate fixes
# Conflicts:
#	ATTRIBUTION.md
#	crates/annot/src/tests.rs
#	crates/forms/src/tests.rs
#	crates/organize/src/tests.rs
2026-10-22 02:21:53 +00:00
aiongg 123b214180 Merge r2 track t10: xfa 2026-10-22 02:21:06 +00:00
aiongg 69e755c439 Merge r2 track t08: fonts
# Conflicts:
#	crates/automation/src/tools.rs
#	vendor/README.md
2026-10-22 02:20:50 +00:00
aiongg 4d5025c6da Merge r2 track t07: text-extract-select
# Conflicts:
#	crates/edit/src/tests.rs
#	crates/render/src/raster.rs
#	vendor/README.md
2026-10-22 02:20:03 +00:00
aiongg a2c46a9298 Merge r2 track t06: annotations-comments
# Conflicts:
#	crates/automation/tests/automation.rs
#	crates/render/src/raster.rs
2026-10-22 02:19:23 +00:00
aiongg 0bba8dcafb Merge r2 track t04: render 2026-10-22 02:18:22 +00:00
aiongg e1652d5dbe Merge PR #3540: Render: radial luminosity fades have regression coverage
ROADMAP.md: progress log union, newest first
docs/roadmap.md: main's M2 notes with the PR's sentence appended
2026-10-22 01:52:42 +00:00
aiongg 07a9f286c4 Merge PR #3948: Reject rounded page rasters beyond the pixel budget 2026-10-22 01:52:27 +00:00
aiongg b2f913a7b6 Merge PR #3438: fix(render): use unfiltered inline image byte length before EI heuristic (#3107)
docs/roadmap.md: main's M2 notes with the PR's sentence appended
ROADMAP.md: the PR's progress-log line moved to the top (newest first)
2026-10-22 01:52:27 +00:00
aiongg ec6a8166bb Integration: #3020 number its hayro-interpret patch 48 (40 is #2987's) 2026-10-22 01:51:55 +00:00
aiongg e2d3f30c63 Merge PR #3020: fix(render): draw each ICC profile through itself when two have the same stream dictionary (vendored hayro-interpret patch 40)
vendor/README.md: main's row plus the PR's hayro-interpret patch and tests, numbered 48 (40 is #2987's)
2026-10-22 01:51:47 +00:00
aiongg ad23e6e6e9 Merge PR #3184: fix(render): apply the JP2 palette of a JPX image with no /ColorSpace (#3179) 2026-10-22 01:51:22 +00:00
aiongg e61515f324 Merge PR #2987: fix(render): a visibility expression decides what an OCMD shows (#2986)
vendor/README.md: main's row plus the PR's hayro-interpret patch 40 and its test
2026-10-22 01:51:22 +00:00
aiongg b84121e978 Merge PR #3327: Render: a five-point polyline is no longer painted as its bounding box
vendor/README.md: main's row plus the PR's hayro-interpret patch and tests, numbered 47 (35 is #3296's)
2026-10-22 01:51:10 +00:00
aiongg 6fc4a18d9c Merge PR #2972: fix(render): blend modes over an empty part of the page keep their colour (#2970)
crates/render/src/raster.rs: main's tests plus the PR's #2970 test after the knockout test
vendor/README.md: main's rows with the PR's rewording of hayro patch 18
2026-10-22 01:50:51 +00:00
aiongg 554d9105f3 Merge PR #2974: fix(render): knockout transparency groups knock out (#2971)
crates/render/src/raster.rs: both new tests (#2975 stencil masks, #2971 knockout groups)
vendor/hayro/src/renderer.rs: set_paint returns main's PaintSetup; the PR's knockout erase early return wraps its clip in PaintSetup::Clip
vendor/README.md: main's rows plus the PR's hayro-interpret patch 39 and hayro patch numbered 22 (19 is #3296's), with tests
2026-10-22 01:49:59 +00:00
aiongg 61cbaf3e23 Merge PR #3246: Shading patterns use the stroke or fill alpha current at paint time (#3176)
vendor/hayro-interpret/src/pattern.rs: ShadingPattern::new takes the default colour spaces (#3018) and no opacity (this PR); without_opacity (#2977) and set_alpha both kept
2026-10-22 01:49:02 +00:00
aiongg 4949724ec8 Merge PR #3875: M4: a new bookmark goes to where the reader is (#3865) 2026-10-22 01:48:54 +00:00
aiongg a447dd3db5 Merge PR #2731: Layers panel: Reset to Initial Visibility
Conflicts:
- ATTRIBUTION.toml: ours (sha256 lines only); hashes recomputed at the end of the track
- crates/engine/Cargo.toml: main's workspace chrono entry, plus the PR's pdfcraft-testkit dev-dependency
2026-10-22 01:48:51 +00:00
aiongg 1ddbb5f3ff Merge PR #2977: fix(render): a stencil mask is drawn at the fill opacity once (#2975)
vendor/hayro/src/renderer.rs: main's PaintSetup::Clip match with the PR's fill paint (is_stroke false) and comment
vendor/README.md: main's row plus the PR's hayro-interpret patch and test, numbered 46 (35 is #3296's)
2026-10-22 01:48:34 +00:00
aiongg 7bcf25991d Merge PR #2397: Issue #2365: a rebuilt file keeps the /Info and /ID of its cross-reference stream
crates/cos/src/document.rs: #2220 moved the header scan into object_headers; it also returns the position after `obj`, and reconstruct applies this PR's /XRef candidate check (same 1024-byte window) to each header there. Tests: kept both sides.
2026-10-22 01:47:39 +00:00
aiongg a8b7d14709 Merge PR #3035: fix(render): mesh triangles that share an edge leave no seam
vendor/hayro-interpret/src/encode.rs: both sides; a pixel sampled by either test (corner held, or centre covered) widens the mesh extent from #3296
vendor/README.md: main's row plus the PR's patch 36 and its test
2026-10-22 01:47:35 +00:00
aiongg e47b64b009 Merge PR #2949: M2: a post table version with swapped halves doesn't make a font unusable (#2946)
vendor/README.md: hayro-interpret row keeps #2914's patch (26) extension and #2949's new patch (38) with both tests
2026-10-22 01:46:55 +00:00
aiongg 5a13228b57 Merge PR #3520: M5: a stamp grows and shrinks with its handles before release (#3518) 2026-10-22 01:46:28 +00:00
aiongg b1c8ae231a Merge PR #3643: fix(render): draw missing spacing glyphs in Japanese CID substitutes as spaces (#3601)
ROADMAP.md: progress log keeps both sides' lines
2026-10-22 01:46:04 +00:00
aiongg fd0c47f7be Merge PR #3637: Fix named properties in forms without resources
ROADMAP.md: progress log union, newest first
2026-10-22 01:45:57 +00:00
aiongg 498a7d60b9 Merge PR #3955: perf(render): read PDFCRAFT_TEXT_DEBUG once instead of per glyph 2026-10-22 01:45:49 +00:00
aiongg 6bb89022d0 Merge PR #2483: fix(content): an inline image ends at its own EI, not a later one
vendor/README.md: hayro-syntax row already has a (22) on main; this PR's patch and tests are numbered (23).
2026-10-22 01:45:49 +00:00
aiongg 2a17980645 Merge PR #2914: fix(render): draw comma-below letters in non-embedded standard fonts … 2026-10-22 01:45:44 +00:00
aiongg 0b51ea4baa Merge PR #3018: fix(render): draw device colour in the resources' /DefaultGray, /DefaultRGB and /DefaultCMYK (vendored hayro-interpret patch 38)
vendor/README.md: main's single hayro-interpret row plus the PR's patch 38 and its tests, placed before 41
2026-10-22 01:45:41 +00:00
aiongg add6907f6a Merge PR #3239: M3: extract text from glyph names outside the reference-app Glyph List (#3169, #3170) 2026-10-22 01:45:33 +00:00
aiongg dbb71754d0 Merge PR #3497: M2: Type 0 fonts with /ToUnicode /Identity-H read their codes as Unicode
contributors/people.toml: kept both new contributor entries.
2026-10-22 01:45:33 +00:00
aiongg bc857aa60c Merge PR #3686: fix(a11y): resolve /MarkInfo /Marked when it is an indirect reference (#3671) 2026-10-22 01:45:25 +00:00