Commit Graph
679 Commits
Author SHA1 Message Date
hcchung1 3909bebeec M7: a paragraph number keeps naming the paragraph text_paragraphs listed (#2516)
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.
2026-10-20 10:02:06 +08:00
Eric Kaden 74326b455c M6: a field's own script may not open Save As 2026-10-19 21:02:37 -04:00
Eric Kaden e8eb840ec5 M6: form buttons show the side panels (execMenuItem ShowHide…) 2026-10-19 20:59:14 -04:00
Osvaldo DuranandClaude Opus 5.5 e76adf812a Merge upstream main ec0a9650 into pr/attachments
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>
2026-10-19 18:03:56 -04:00
Eric Kaden 3a5bcbdf97 M12: a date typed by another of an XFA field's edit patterns is kept in its first 2026-10-19 17:21:08 -04:00
Eric Kaden bf88255c15 Merge remote-tracking branch 'upstream/main' into fix/xfa-date-bind-picture 2026-10-19 16:52:36 -04:00
ec0a965074 M5.6: preserve nested annotation values in FDF exports (#1971)
* M5.6: preserve nested annotation values in FDF exports

Copy and renumber retained object graphs without expanding aliases or following
indirect cycles recursively. Refuse excessive work and propagate export errors
before the destination is written. Cover saved geometry, colour and borders,
shared graphs, streams, stale references and refusal through automation tools.

Co-Authored-By: Codex <noreply@openai.com>

* Bound FDF annotation copying before allocation

Keep selected aliases shared, budget retained keys and values before copying, and add direct-payload and alias-count regressions. Retain copied-graph limits and document source-memory scope.

* M5.6: bound FDF cache-eviction discovery and field values

Extend the concurrent annotation-budget correction while retaining its direct-payload and alias regressions. Record annotation positions instead of resolved bodies so cache eviction cannot pin repeated parsed copies; borrow direct values and budget retained entries before cloning. Count every emitted multi-select field value before allocating its output array.

Observed pre-fix allocation-cap and field-limit failures, then fixed tests passed. Final validation: 549 scoped XFDF/engine/automation tests passed, three existing ignored; strict all-targets Clippy, WASM, fmt, layers, assets, parity and diff checks passed.

AI assistance: OpenAI Codex for @MMexicann.

* M5.6: record bounded FDF export regression evidence

Record comment cache-eviction discovery and emitted-field-value budget regressions without changing feature scores or status. Describe copied-graph limits and existing source-parser limits precisely. Final 549 scoped tests, strict Clippy, WASM, fmt, layers, assets and parity passed.

AI assistance: OpenAI Codex for @MMexicann.

* M5.6: align FDF progress with budgeted discovery

Document lazy annotation positions, cache-eviction coverage and copied-graph limits while retaining the original nested-value progress entry and all upstream notes. Estimates unchanged.

AI assistance: OpenAI Codex for @MMexicann.

* M5.6: describe FDF discovery and copied-graph limits

Align exchange documentation with the tested lazy-position discovery implementation and its copied-graph limits. Source parsing/page discovery keep existing limits.

AI assistance: OpenAI Codex for @MMexicann.

* M5.6: align milestone notes with FDF discovery limits

Retain upstream milestone history and describe the tested discovery/copy budgets without increasing estimates or claiming a total source-memory bound.

AI assistance: OpenAI Codex for @MMexicann.

* M5.6: keep nested page links out of FDF exports

Preserve graph aliases and existing budgets; exclude direct and indirect Page/Pages type names. Add saved-PDF regression and bounded type-resolution controls. Both pre-fix failures observed; 46 scoped tests, strict Clippy, WASM and formatting passed.

* M5.6: document bounded nested page exclusion

Address maintainer feedback on the existing PR. Preserve upstream work and document the tested scope and remaining validation limits.

* M5.6: record nested page-reference regression evidence

Address maintainer feedback on the existing PR. Preserve upstream work and document the tested scope and remaining validation limits.

* M5.6: retain FDF limits in milestone evidence

Address maintainer feedback on the existing PR. Preserve upstream work and document the tested scope and remaining validation limits.

* M5.6: cite nested page-reference exclusion tests

Address maintainer feedback on the existing PR. Preserve upstream work and document the tested scope and remaining validation limits.

* M5.6: respect generations while identifying page references

Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.

* XFDF: document stale Type preservation regression

Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.

* M5.6: record generation-aware page exclusion evidence

Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.

* M5.6: propagate FDF export errors in usage example

Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.

* M5.6: consolidate roadmap change and target fields

Address maintainer feedback on the existing PR. Preserve upstream work and document tested scope and validation limits.

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: skylar taylor-barrick <hi@skylar.cc>
2026-10-19 12:21:37 -06:00
Joel Tony 99c78557eb M5: a stamp grows and shrinks with its handles before release (#3518)
While a handle of a Fill & Sign line, check, cross, dot, typed signature
or comment stamp was dragged, only the selection frame followed the
pointer; the stamp kept its old size (or place) until release. Image
signatures already had a live layer. Every stamp now gets one: the page
without the stamp, and the stamp drawn alone on a transparent page,
whose pixels are stretched with the frame. Resizing a stamp only changes
its /Rect, which its appearance is stretched to fit, so the preview is
what the release produces.

- render: RenderConfig.transparent clears the page to transparent.
- annot: isolate_annotation leaves a page showing one annotation alone.
- engine: Document::stamp_preview builds the two read-only layers; the
  page beneath gets the appearances drawn for comments without one.
- ui-egui: the signature drag layer covers every stamp, rendering the
  stamp's own tile sharp at the current zoom.
2026-10-19 23:42:32 +05:30
Philip ae0fcbeada Merge remote-tracking branch 'origin/main' into feat/edit-scanned-page-offers-ocr 2026-10-19 18:26:08 +01:00
Eric Kaden cddc327264 Merge branch 'main' of github.com:storytold/pdfcraft into fix/xfa-script-error-keeps-effects
# Conflicts:
#	parity/acrobat-features.toml
2026-10-19 13:23:34 -04:00
Eric Kaden d022254ce7 Merge branch 'main' of github.com:storytold/pdfcraft into fix/xfa-layout-drops-usage-rights
# Conflicts:
#	parity/acrobat-features.toml
2026-10-19 13:18:04 -04:00
5602fbba12 M10: Office export follows forms 50 deep and up to 50 000 a page and says where it stops (#3120) (#3426)
* M10: Office export reads forms as far as the renderer and says where it stops (#3120)

Export to Word, PowerPoint, HTML and RTF stopped at 12 nested forms and
4 096 forms a page and left the rest out silently. It now follows forms
50 deep, as the renderer does, and up to 50 000 a page (the renderer's
budget for nested paints), still bounded on hostile files.
doc_export_office lists pages past those limits in truncated_pages, with
a warning. New: reading_blocks_checked and export_office_checked;
reading_blocks and export_office keep their signatures.

Fixes #3120

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

* Tests: pin the form depth limit to the renderer's 50 (#3120)

The depth test built its chain from MAX_FORM_DEPTH, so it moved with the
constant. Write the renderer's limit out: depth 50 is read, depth 51 is
left out and reported. Fails with the constant at 49 and at 51.

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

* Export: say which forms count toward the page limit (#3120)

The page budget also counts forms the page draws itself, which the
renderer does not charge, so a page can be reported as cut although the
viewer shows it. The warning and the tool description now say so.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 13:02:44 -04:00
Nyanpasuwu 14fcc82a24 i18n: add Central Kurdish (Sorani) as an interface language
2,670 entries in ckb.tsv, written in logical order, with the same
(context, source) key set as ar.tsv. Registered with rtl: true and
plural_one_other (CLDR ckb: one, other); Sorani keeps the noun singular
after a numeral, so the 13 @plural rows usually read the same in both
forms.

RTL reuses the Arabic path: the catalog is put into display order by
bidi::display_rtl when it loads, so Kurdish has the same four limits
docs/localization.md lists for Arabic. No new layout or bidi code.

DATE_LANGUAGES gains Kurdish month and weekday names, because
date_languages_are_the_interface_languages keeps both registries in
step.

ku-* locales are not mapped to ckb on purpose: ku is the macrolanguage
and means Kurmanji in Latin script. Only ckb* tags resolve.
2026-10-19 19:32:21 +03:00
PerchAnton 73a98ffd79 M12: resolve main integration for PR #2648 2026-10-19 19:12:49 +03:00
PerchAnton 4bba9e08c6 M12: resolve main integration for PR #2624 2026-10-19 19:07:47 +03:00
PerchAnton 0d502f028b M12: resolve main integration for PR #2619 2026-10-19 19:05:12 +03:00
Philip 41ee1460d6 Merge remote-tracking branch 'origin/main' into feat/edit-scanned-page-offers-ocr
# Conflicts:
#	ROADMAP.md
2026-10-19 16:08:07 +01:00
mtlcl410andClaude Opus 5.5 da351cda8f fix(forms): cache field appearances drawn for display, redraw only what changed (#3470)
Review follow-up (CodeRabbit on #3475). A form edit refreshes the display
copy on every keystroke, and with /NeedAppearances that redrew every field
of the form: 68 ms per edit on a 1000-field form against 7 ms on main.

FieldAppearanceCache keeps each widget's drawing with the objects it was
drawn from: the widget and its field ancestors, the form dictionary and
the fonts of its /DR, each with the objects its entries refer to. An
entry is used only while each of them is still the same object
(Arc::ptr_eq); the document replaces an object it edits, so a changed
input is a miss, never a stale hit. The engine keeps one per editor. A
field edit on that form now takes 13 ms.

The repair note on a failed display write now names field appearances
too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-19 22:26:31 +08:00
mtlcl410andClaude Opus 5.5 b661ba4584 fix(forms): draw fields the file leaves to the viewer, for display only (#3470)
A form with /NeedAppearances true (ISO 32000-2 Table 224: the writer
"has not provided appearance streams for all visible widget
annotations") showed its text fields empty: PdfCraft only read the flag
when writing, and the display copy that draws missing appearances skips
widgets. Filled forms from tools that leave drawing to the viewer looked
blank, while Acrobat, Firefox and MuPDF show the values.

pdfcraft_forms::with_field_appearances returns a copy with text and
choice widgets drawn from their values (every one when the flag is set,
otherwise those with no /AP /N), with the generator a typed value uses.
The engine's display copy (page view, page images, Print as Image, CLI
render) applies it after the comment appearances. The document itself
is not changed, so saving writes the file as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-19 21:53:42 +08:00
mtlcl410andClaude Opus 5.5 23a032ef3b fix(engine): a reload keeps reading colours and hidden comments (#3443)
`Session::reload` (the file changed on disk) opens the new bytes with a
default render config, so a document shown white on black, or with its
comments hidden, went back to the file's own colours and comments until
the preference was toggled. The UI re-applies the preference only on
open. Reload now carries both display settings over and rebuilds the
renderer with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-19 19:17:21 +08:00
Eric Kaden 8f0557d2bc M12: an XFA script that fails partway keeps what it changed before the error 2026-10-19 01:13:09 -04:00
Eric Kaden 38d3a6c843 M12: each XFA date field uses its own data picture, and only a whole-date one
The template field behind a SOM path is now found by its number, as the
form numbers same-named siblings and repeated instances; the live form's
value refresh uses the same lookup. A data picture without a year, month
and day (date{YYYYMM}) keeps ISO dates in the data, since it can't hold
the date the field shows.
2026-10-19 00:18:03 -04:00
jonathanwxh-cell d16c94a703 M12: follow rewritten figure stream in engine regression 2026-10-19 12:12:12 +08:00
Eric Kaden c392d93ba6 M12: laying out a dynamic XFA form takes off the usage rights it voids
The layout revision replaces the placeholder pages and adds the fields,
so usage rights (/Perms /UR3) signed over the form as published no
longer match it, and a viewer may then open the form with every field
locked. write_form now takes them off with the helper strip already
needed, and the layout report says so.
2026-10-19 00:06:32 -04:00
Eric Kaden 93f90ef760 M12: XFA dates are written and read in the field's data picture
A field's data picture (<bind><picture>, such as date{YYYYMMDD}) is how
its value is written in the XFA data, but PdfCraft always wrote dates
there as ISO (2026-10-18) and read them back as ISO, so a form that keeps
its dates as 20261018 got a different format from PdfCraft than from
other viewers, and data those viewers wrote was shown as stored.

The parser keeps <bind><picture> as Field::data_picture. A typed date is
still read in the field's own pattern and turned into ISO; it is then
written in the data picture when the field has one. Reading goes the
other way, on layout and when values are taken from the data of a form
laid out before. A value that doesn't match its data picture is read as
ISO, so data written by earlier versions still reads. The date-letters
rule for XFA picture clauses moves to one helper (date_letters) that the
layout's date_pattern now uses too.
2026-10-18 23:46:43 -04:00
vangel76andClaude Sonnet 5.5 f3b8654b23 Merge upstream/main into feat/image-rotate-snap
Resolve the ROADMAP.md progress-log conflict by keeping both sides' entries.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 02:08:53 +02:00
DjPeepjay 7137c41f47 feat(viewer): add optional gapless continuous page display
M3: preserve reading position while joining page edges, with persisted defaults and opt-in UI control. Translate both labels in all 22 catalogs, update attribution, and cover catalog completeness with a regression test. Rebase PR #3101 onto current main while retaining navigation and rendering fixes.
2026-10-18 17:27:58 -05:00
Osvaldo DuranandClaude Opus 5.5 c257793c2c Merge upstream main dab31491 into pr/attachments
On top of the merge with 932bf9c6; no conflicts. ATTRIBUTION.toml is upstream's with every sha256 recomputed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:27:27 -04:00
Morten Husted Lochmann f280ca8e6d M10: exported TIFF and JPEG pages record the resolution they were drawn at (#3332)
* M10: exported TIFF and JPEG pages record the resolution they were drawn at

A page exported as TIFF said 1 x 1 with no unit, whatever the dpi, and
a JPEG export said nothing, so other programs showed and printed them at
a default (a 300-dpi page shown at 72 dpi is four times too big).
encode_image now takes the dpi: TIFF X/YResolution in inches, the JFIF
density in dots per inch.

PNG stays without pHYs. It counts pixels per metre, so 72 dpi would read
back as 72.009, and a page made from an exported PNG (doc_create from
images, as enhance_camera_image_through_tools does) would come out 0.01%
small.

Exporter records the resolution a page was actually drawn at (dpi_used),
so a page too large for the renderer says the lower one. render records
--dpi, since it refuses oversized pages. The TIFF is written with the
tiff crate, already in the graph, because image's TiffEncoder can't set
a resolution.

* M10: TIFF page exports are LZW-compressed, as documented

encode_tiff's comment said "LZW-compressed by the encoder's default",
but the TIFF was written uncompressed: a Letter page at 300 dpi came to
25 MB. It is now LZW without a predictor, the compression TIFF readers
support most widely after none.

* Contributors: add my name for the About window
2026-10-18 17:06:41 -04:00
Ahmad MuandBrandon Thomas 38999b3e90 fix(edit): isolate added marks from unfinished marked content (#3279)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 17:05:44 -04:00
Osvaldo DuranandClaude Opus 5.5 f24a26b951 Merge upstream main 932bf9c6 into pr/attachments
Conflicts: ATTRIBUTION.toml, crates/organize/src/tests.rs (code resolved by hand: crates/organize/src/tests.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>
2026-10-18 14:59:30 -04:00
jonathanwxh-cell 9b0aeb0f69 M11: integrate main b0e5c6eb into PR #3227 and fix version parser lifetime 2026-10-19 02:35:26 +08:00
Marco Arnulfo a4cdbb6235 XFA: decode BMP and TIFF pictures (fixes #1644)
Pictures in templates and data that are BMP or TIFF were left out with a
warning. They are now recognised by their signature, sized from their
headers, and decoded through the same bounded reader as GIF (first page
of a TIFF). A BMP needs a valid info-header size after "BM", so text
starting with those two letters is still not taken for one.

A BMP or TIFF chosen for an XFA image field is kept in the data as
image/bmp or image/tiff, since the layout can now show it.
2026-10-18 19:21:49 +02:00
alehel 9b6e74928f Merge remote-tracking branch 'origin/main' into fix/background-hidden-by-page-backdrop
# Conflicts:
#	ROADMAP.md
2026-10-18 19:17:34 +02:00
Mawfyy 932bf9c6bf M4: Ctrl-drag copies pages in the Organize grid (#2997) (#3092)
* M4: Ctrl-drag copies pages in the Organize grid (#2997)

Dragging thumbnails in the Organize page grid with Ctrl held (Option on
macOS) drops copies at the gap instead of moving the pages: a "+" badge
by the pointer, the copies land at the gap selected, and one undo step
takes them out. The insertion point joins the engine edit
(Edit::DuplicatePages { at }) so the copy is one undo step rather than a
duplicate plus a move.

page_duplicate takes a 1-based "at" (the first copy becomes page "at"),
so agents can drive the same thing, and the UI control channel's
ui.drag modifiers now reach the input state -- which gestures that read
it (this copy-drag, and Alt-drag column select) need.

Closes #2997.

* fix(ui): take the grid's copy modifier from the runtime OS (#3092 review)

Option-drag in the Organize grid moved the pages instead of copying them in a Mac
browser: `cfg!(target_os = "macos")` is false in the wasm build, and eframe sets the
context's OS from the browser. `copy_modifier` now takes the context and asks
`commands::mac_shortcuts`, as the shortcut labels do.

The drag helpers in `tests/editing.rs` and `tests/control.rs` picked the modifier the
same compile-time way, so they would have sent Ctrl on a Mac host; both read the
context's OS now. The new regression test
`option_copies_and_ctrl_moves_when_the_context_reports_macos` drives a Mac context —
Ctrl moves, Option copies — and fails without this change.
2026-10-18 11:13:22 -06:00
Marco Arnulfo fcdf2a2146 Layers panel: Reset to Initial Visibility
Layer options in the Layers panel header resets every layer to the state the
file's default configuration (/OCProperties /D) gives it, as Acrobat does.
Visibility is view state, so the reset doesn't mark the document changed and
doesn't enter Undo. doc_info now reports each layer's initial state.
2026-10-18 18:56:40 +02:00
Marco Arnulfo 3468c8543f fix(print): disable Print on documents whose security forbids printing (part of #2669) 2026-10-18 18:56:08 +02:00
PerchAnton 4119359c7d M12: retain accepted whitespace when structured XFA data cannot sync 2026-10-18 19:54:41 +03:00
enes yıldızandClaude Opus 5.5 882b5a33bc M1: merge upstream main into claude/fix/2670-cli-owner-password
Merges #2813 (dynamic-XFA layout in the CLI) into the owner-password branch. In
info, text and render the engine Session and the cos document keep the typed
password, while anything handed to `inspect` or `PageRenderer` gets the render
password (the user password recovered from an R2-R4 owner password), including
the dynamic-XFA detection in render and the original-bytes check in text.

Checked: cargo fmt --check; clippy -p pdfcraft-cli -p pdfcraft-engine --all-targets
-D warnings; cargo test -p pdfcraft-cli (75 passed) and -p pdfcraft-engine (283 lib
+ integration passed); cargo xtask layers, assets, parity; a throwaway RC4-40,
RC4-128 and AES-128 dynamic-XFA check with the owner and user passwords through
info, text and render (not committed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:44:21 +03:00
PerchAnton b7b4ab0c8d M12: retain product notes while integrating current main 2026-10-18 19:39:17 +03:00
PerchAnton c3df50e8dd M12: retain product notes while integrating current main 2026-10-18 19:36:34 +03:00
PerchAnton d39de3d31c M12: retain product notes while integrating current main 2026-10-18 19:33:52 +03:00
alehel dc27841010 Merge remote-tracking branch 'origin/main' into fix/background-hidden-by-page-backdrop
# Conflicts:
#	ROADMAP.md
#	crates/ui-egui/tests/ui_group/marks.rs
2026-10-18 17:34:42 +02:00
PerchAnton 8a786ab2d9 M12: retain product notes while integrating current main 2026-10-18 18:11:02 +03:00
sanches 2b445950df Merge remote-tracking branch 'origin/main' into i18n-id-vi 2026-10-18 18:08:27 +03:00
PerchAnton 0910b8314a M12: retain product notes while integrating current main 2026-10-18 18:07:15 +03:00
PerchAnton 4f4f589365 M12: retain product notes while integrating current main 2026-10-18 18:02:45 +03:00
aiongg 40d7b0bc47 Merge upstream main a12fe8f41 into next 2026-10-18 14:56:01 +00:00
aiongg eedf250b4c Merge upstream main ecffce69e into next
crates/cos/src/document.rs: both new accessors, header_offset (#2525) and reconstructed (#2029).
parity/acrobat-features.toml: the malformed-xref entry has both sides' evidence and notes (#2029 full save of a repaired file, #2717 table numbered from 1).
2026-10-18 14:50:20 +00:00
a12fe8f411 M4: custom document properties through the engine and the tools (#2076)
* M4: custom document properties through the engine and the tools (#organize.custom-properties)

/Info entries beyond the ones the spec names are custom properties:
organize lists them (custom_info) and set_info accepts any such name
after Acrobat's checks (no standard name, no empty name, no spaces,
delimiters, / or #, at most 127 bytes); inspection lists them too
(DocInfo::custom); doc_info reports them under custom and doc_set_info
sets or clears them. The Custom tab in Document Properties and the XMP
mirror stay planned.

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

* M4: custom document properties list the same before and after an edit

Review follow-up. The open-time reader (render/inspect.rs) now uses the
engine's rule (pdfcraft_organize::custom_info, trimmed, empty values
dropped): text strings only, NUL/whitespace trimmed, empty values left out,
and the 256 cap counting listed entries. Before, it also listed name
values (`/Dept /RnD`) and capped before filtering, so the list could change
after the first unrelated edit. An automation test opens a file with a name
value, blank, NUL-padded, empty and numeric entries and checks doc_info's
custom list is the same before and after setting the Title.

Drops the ROADMAP.md log line, which conflicted with main.

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

* M4: custom properties drop NUL/whitespace padding in one pass, before the cap

Review follow-up. custom_info now trims NULs and whitespace in any mix
(" \0Lab\0 " is "Lab"), leaves out values that are only padding, and does
both before taking 256, so blank entries can't crowd real ones out. It is
the one rule: the engine uses it as it is, and the open-time reader trims
the same way. Tests: 300 blank entries before a real one, and mixed
padding, in organize and through doc_info.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:49:30 -04:00