Commit Graph
353 Commits
Author SHA1 Message Date
songzhihui 9447cdc810 M2: merge main and resolve CJK viewer integration conflicts 2026-10-31 01:14:02 +08:00
FredApps 7fe33d1cbd test: verify conservative review fixes across rendering and automation 2026-10-30 15:32:44 +01:00
FredApps 4791cacd2c test: qualify conservative tone retention and format review regressions 2026-10-30 14:55:44 +01:00
FredApps d7f3e67141 fix: bound backdrop edits and withhold unsafe integration behavior 2026-10-30 14:45:50 +01:00
FredApps 7d3d4830e5 Integrate remaining six PdfCraft parity PRs with conflict resolutions
Includes #4325 #1952 #4180 #2529 #4320 #4805, preserving their original histories.
Keep paragraph alignment and hard/soft wrap metadata together; retain CCITT/downsampling/RGB ownership guards with bounded JBIG2 export and exact-copy symbol encoding.
Combined interaction regressions added. Validation pending.
2026-10-30 11:02:07 +01:00
FredApps b21af3b969 Integrate 21 clean-merging PdfCraft parity PRs
Preserve each original PR head and its authorship as a merge parent.
Includes #4304 #4335 #4384 #4404 #4179 #5300 #4719 #4541 #2815 #4620 #3789 #3779 #2960 #5208 #4596 #3636 #4494 #4013 #5134 #2133 #2635.
Conflicting candidates skipped. Combined CI pending; see docs/integration-20261030.md.
2026-10-30 10:35:42 +01:00
alehelandClaude Opus 5.5 acbc746a57 Merge origin/main into fix/delete-single-reply
No conflicts. main's changes to the files this branch touches (the page
cursor router in canvas.rs, hover tips in tools/comments, new tools and
document methods) don't meet this branch's hunks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:10:59 +01:00
Oliver HagmannandClaude Opus 5.5 67b5b2dcf6 A moved comment is drawn where it goes while it is dragged
Moving a rectangle, an oval or any other comment showed only its blue
frame going, the shape staying where it was until release. The comment is
now rendered alone once as the drag starts (Document::comment_layer: a
read-only copy with only that comment, no page content) and laid over the
page where it goes; the page beneath keeps showing it where it was until
the move is applied, as a CAD program's move does. Rendering the page
without it would cost a whole page's render at every drag on a heavy plan.
Stamps and Fill & Sign marks keep their own layers. annot: isolate_annotations
for several comments of a page, so a group can be drawn together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:50:58 +01:00
alehel 9aaac56417 Merge remote-tracking branch 'refs/remotes/origin/main' into fix/background-hidden-by-page-backdrop
# Conflicts:
#	crates/engine/src/tests.rs
#	crates/ui-egui/src/dialogs/marks_ui.rs
#	parity/acrobat-features.toml
2026-10-28 23:18:52 +01:00
aiongg dc943eeb13 Merge pull request #4718 from alehel/fix/marks-update-preview-without-old-marks
fix(ui): Update previews the page without the marks it replaces
2026-10-28 17:59:41 -04:00
.CooCoo 222a124650 M5: Import comments takes another PDF and copies its comments onto the same pages (#4595)
* M5: Import comments takes another PDF and copies its comments onto the same pages

Import comments read only XFDF, FDF, XML, XDP, CSV and text data files.
It now also takes a PDF: each comment of that PDF is copied onto the
page with the same number, at the same place, with its appearance
stream, its pop-up, its replies and its layer. Comments on pages past
the document's last page are left out, together with replies to them.
Links, form fields and redaction marks are not copied. A comment named
like one already on its page replaces it, as an XFDF or FDF import
does. A PDF without comments is refused with "nothing in the file
matches this document", and a PDF that needs a password is refused.

- organize: import_comments copies the comments with the page copier
  that Insert Pages uses, and registers the layers they use.
- engine: Edit::ImportData reads a file that starts with %PDF- as a PDF.
  Any other file goes to the data importer first, and is read as a PDF
  when that fails, %PDF- occurs in its first 1,024 bytes and the PDF
  reader opens it.
- ui: Comments > Import comments lists .pdf files; form.import_data
  lists the same files as before.
- automation: the doc_import_data description names the PDF case.
- parity: comment.import-from-pdf is shipped.

* Import comments: an annotation that is no comment keeps its name

A comment imported from a PDF replaced any annotation of the same /NM
on its page that was not locked, a redaction mark or a widget, so a
link, screen or 3D annotation of that name was overwritten. Only an
annotation whose subtype is one of the copied comment subtypes is
replaced now; for any other, the copy is added beside it without the
name. The subtype is read through an indirect reference.

* Import comments: index each page's annotations instead of scanning them per comment

* Import comments: a layer that BaseState OFF hides stays hidden

* Import comments: a PDF with binary bytes before its header imports on an XFA form, and two comments with one name both land

On an XFA form, a decode failure no longer stops the import when a PDF
header is in the first 1,024 bytes; the PDF's comments are copied. Two
incoming comments on one page with the same /NM no longer replace the
same comment: the first replaces it, and the second is added without
the name.

* Import comments: an inline annotation on the page is replaced by name

An annotation written inline in a page's /Annots is made an indirect
object before names are matched, so an incoming comment with its /NM
replaces it instead of being added beside it.

* Import comments: two new comments with one name keep it once

When no comment on the page has the name, the first incoming comment
keeps its /NM and each later one with the same name is added without it.
2026-10-28 17:53:03 -04:00
aiongg 4d8dc3a1b7 Merge pull request #4560 from Lcoding15/feat/bookmarks-from-text-styles
M4: New bookmarks from text styles, for headings that aren't tagged (#1693)
2026-10-28 17:50:47 -04:00
Eric Kaden 21f6c29d3e M6: form script messages keep their title, icon and buttons, and every one is shown (#4293, #4294) (#4306)
* M6: form script messages keep their title, icon and buttons, and every one is shown (#4293, #4294)

* M6: a form script message waits for a notice the toast already shows

* M6: form script messages past the queue's room are counted and reported

* M6: a sentence break in the merged parity note

* M6: an object that names no parameter is a message's text, as a String object or a caught error

* M6: the count of left-out script messages is said again if another notice replaces it before its time

* M6: a full stop between two sentences of the FormCalc parity note

* chore: re-run CI (a frame-budget timing test missed on a shared runner)
2026-10-28 17:46:11 -04:00
alehelandClaude Opus 5.5 dcec1cecff fix(ui): Update's preview parses its unmarked copy once and keeps a page's failure to itself
The page shown was drawn through a fresh renderer each time, which parsed
the whole unmarked document on the UI thread; UnmarkedPages now parses it
once and keeps it. An error from rendering a page ahead (the pool retiring
its parser, a cancelled request) no longer marks that page failed: only
drawing it when shown decides, and a page that has a picture keeps it. On
the web, where the pool renders on the UI thread, nothing renders ahead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:00:34 +01:00
alehelandClaude Opus 5.5 28a31aebc0 fix(comments): previews lift a stamp whatever its replies' locks; Delete greys out over a locked reply
The locked-reply check in delete_annotation also reached stamp_preview
and signature_image, which only take the annotation off a copy to draw
the page beneath it: an unlocked stamp with a locked reply lost its drag
preview. They now use lift_annotation, which checks no locks.

The Comments panel's Delete (comment and reply menus) and the page's
context menu are greyed out while a reply below is locked
(comments::locked_below), rather than refusing after the click.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:33:14 +01:00
alehelandClaude Opus 5.5 1e01497649 fix(ui): Update prepares the unmarked pages once, and shows the thumbnail while one renders
The preview cloned and wrote the whole document on the UI thread for every
page it stepped to, and showed a blank sheet while the page rendered.
Document::without_marks now removes the marks of a kind from every page
once per dialog (UnmarkedPages), each page shown renders from it off the
UI thread, and the page's thumbnail stays until it arrives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:09:37 +01:00
alehelandClaude Opus 5.5 b3cbb77681 Merge origin/main into fix/marks-update-preview-without-old-marks
main split lib.rs: the unmarked_page field stays on PdfCraftApp in lib.rs,
its init and the render_pending check moved to app.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:50:04 +01:00
RiceDee 43f848d532 Merge main into feat/align-selected-lines
Main split and refactored the text-rewrite code this PR changes
(automation dispatch.rs, edit added/ and text/, text_regions/styled.rs).
Conflicts in added/store.rs, text/rewrite.rs and text_regions/styled.rs
take main's structure, with per-row alignment applied in it:

- rewrite.rs: LineWriter keeps the paragraph's alignment and the rows'
  own (align, aligns); each line looks up its row's alignment
  (align_of) for justify and for its offset. Alignment::offset takes the
  row's alignment. Wrapped lines carry their row, as wrap returns it.
- styled.rs: wrap_styled_rows also returns each row's draft line
  (row_lines), which the kept-rows check counts aligns by.
- Imports for the moved modules (alignment_frame, BlockStyle,
  align_code, align_from, content in dispatch.rs).
2026-10-28 19:34:21 +11:00
Claude 03fdf97cde Merge remote-tracking branch 'upstream/main' into feat/bookmarks-from-text-styles
# Conflicts:
#	crates/ui-egui/src/commands.rs
#	crates/ui-egui/src/dialogs.rs

Main split the command and dialog dispatch for its stricter clippy
rules; New bookmarks from text styles goes into the command list after
New bookmarks from structure, and its dialog into the dialog dispatch
next to the other dialogs with their own buttons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbuSKAS2k5248LGFEapMUR
2026-10-28 06:07:14 +00:00
Dylan M. Taylor 166f706e3e Refactor engine for strict Clippy checks 2026-10-27 23:12:51 +00:00
songzhihui 42aeec616d M2: resolve CJK viewer PR roadmap conflicts with main 2026-10-28 06:31:50 +08:00
Claude cace97e7b2 M4: one text style can't be given to two bookmark levels
A line goes to the first level whose style it is in, so the same style
for two levels, or one within the size tolerance of the other, left the
later level empty without a word. The edit now refuses it, for the dialog
and automation alike, naming the two levels (CodeRabbit on #4560).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbuSKAS2k5248LGFEapMUR
2026-10-27 21:02:46 +00:00
Claude 65d8385616 Merge remote-tracking branch 'upstream/main' into feat/bookmarks-from-text-styles
# Conflicts:
#	crates/automation/src/lib.rs
#	crates/engine/src/lib.rs
#	crates/ui-egui/src/lib.rs
#	crates/ui-egui/src/panels.rs

Main's module layout (#4889) moved what this branch changed: the tools to
automation's dispatch.rs (the levels parser to json.rs), the edit to
engine's edit.rs, edit/labels.rs and edit/run.rs, Document::text_styles
to document.rs, the dialog state to state.rs and app.rs, and the menu
item to panels/right.rs and panels/bookmarks.rs. The dialog module joins
the others in dialogs/. Main's craft-menu bar (#4752) lists its commands
by hand, so bookmark.from_text_styles is added after
bookmark.from_structure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbuSKAS2k5248LGFEapMUR
2026-10-27 20:46:00 +00:00
NetterSilverandClaude Opus 5.5 37bf4ee986 perf(engine): export page images on worker threads
Export > Image spent its time in the PNG, JPEG and TIFF encoders, one
page after another: on a 55-page manual at 150 dpi, drawing every page
took about 0.34 s and encoding it 1.1 s (PNG) to 2.0 s (JPEG).

Exporter::images exports a list of pages, drawing and encoding several
at once on worker threads, each with its own PageRenderer (as
automation's extract_parallel does), and hands the files to a sink in
page order on the calling thread. Workers draw at most one page ahead
of the sink and fewer run when 1 GiB can't hold that many of the
largest page. The first page or sink error stops the export with every
earlier page handed over, as before. A page panic becomes that page's
error, and a panicking sink stops the workers. One page, one core or
wasm keeps the per-page loop. Export > Image and the doc_export_images
tool use it.

Files are byte-identical. One core is left for the interface (at most
8 workers). With examples/export_bench, on a Ryzen with 16 threads
(shared), mean of two medians of 21 runs:
  PNG 150 dpi   1874 -> 340 ms  (-82%)
  JPEG 150 dpi  2106 -> 340 ms  (-84%)
  TIFF 150 dpi  2032 -> 375 ms  (-82%)
  PNG 300 dpi   3804 -> 590 ms  (-84%)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:41:38 +01:00
RiceDee 6733d9eaee Merge main into feat/align-selected-lines 2026-10-27 17:09:44 +11:00
Cursor Agentandjonathanwxh-cell b5b42a3318 Merge upstream main into prep/4013-merge-main
The only content conflict was crates/engine/src/lib.rs: main moved
EditCtx and run_edit into edit.rs, while this branch taught Protect
to draw keys and salts from getrandom and fail closed. Keep main's
module split and apply that CSPRNG behaviour on EditCtx in edit.rs.
The regression test imports EditCtx and run_edit from the edit module.

Co-authored-by: jonathanwxh-cell <jonathanwxh-cell@users.noreply.github.com>
2026-10-27 01:16:56 +00:00
songzhihui 43d2491f5e M2: preserve CJK viewer fallback across engine split 2026-10-27 09:00:39 +08:00
alehel fd1382af6e Merge remote-tracking branch 'refs/remotes/origin/main' into fix/background-hidden-by-page-backdrop
# Conflicts:
#	crates/engine/src/lib.rs
2026-10-26 21:20:09 +01:00
RiceDee a33a8bba38 Merge main into feat/align-selected-lines
Main moved to egui 0.37 (#4591) and took the menus change (#4752).
Conflicts:
- edit_text_ui.rs: the paragraph box keeps the shared input id, now
  Id::unique; the added-text box's shared id and the unit tests' ids
  follow.
- content_ui.rs: the added-text box keeps its per-paragraph layouter
  (which places each line itself, so main's .align(halign) is left out)
  and takes main's inset, margin(4, 2), and its "Added text"
  accessibility label.
2026-10-26 21:02:08 +11:00
jonathanwxh-cellandjonathanwxh-cell 0a6d0f3689 fix(crypt): derive a new file id from the encryption seed
/ID is SHA-256 of the seed under the label id, not an unkeyed DefaultHasher digest. The salts test is a characterization; failing closed when randomness is missing stays the regression guard.

Co-authored-by: jonathanwxh-cell <jonathanwxh-cell@users.noreply.github.com>
2026-10-26 10:00:14 +00:00
songzhihui 9107ada7e7 M2: resolve CJK viewer PR conflicts with main 2026-10-26 13:37:48 +08:00
songzhihui 46fbb73d1a M2: address CJK viewer review and sync main 2026-10-26 09:03:41 +08:00
alehelandClaude Opus 5.5 8473403a5d refactor(engine): move layer visibility to layers.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:08:38 +01:00
alehelandClaude Opus 5.5 ff361fe138 refactor(engine): move Document and its display helpers to document.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:05:07 +01:00
alehelandClaude Opus 5.5 5b75180759 refactor(engine): move Session and its operations to session.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:02:51 +01:00
alehelandClaude Opus 5.5 2b83ae6a7a fix(ui): Update previews the page without the marks it replaces
Update Background, Update Watermark and Update Header and Footer
previewed the new marks on the page's thumbnail, which still has the
marks Update replaces. A pale yellow background updated to light blue
showed a muddy teal (the blue multiplied onto the yellow), and a new
watermark or header was drawn over the old one. The applied result was
right; only the preview was wrong.

In Update, the preview now draws the page without its marks of that
kind. The engine removes them on a copy of the document
(Document::page_without_marks, read-only like the other preview layers),
and the dialog renders that page off the UI thread at the preview's
size, showing a blank sheet until it arrives. Add, and pages without
marks of that kind, keep the thumbnail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:23:17 +01:00
8708f35b37 M6: with NeedAppearances, check boxes and radio buttons draw their /MK caption (ISO 32000-2 Table 224) (#4454)
* M6: with NeedAppearances, check boxes and radio buttons draw their /MK caption (ISO 32000-2 Table 224)

A form that sets /NeedAppearances asks the viewer to construct the
appearances of its widgets (ISO 32000-2 Table 224; deprecated in PDF 2.0).
The display copy already redraws text and choice fields for it, but check
boxes and radio buttons kept the file's own appearance streams, so a box
whose /MK /CA is ZapfDingbats 4 (a check mark) showed the filled square
its /AP carried.

With /NeedAppearances, every check box and radio button that has an /MK
is now drawn in its current state through button_state_shown (merged in
#4368): /MK /BG and /BC, and when on the /MK /CA ZapfDingbats character
(Table 192) in the /DA colour, sized to the box. A widget without /MK
keeps its own appearance, and without /NeedAppearances nothing changes.
A radio button with no /AS now takes its state from /V when /V names its
own on state. Display and print only: the file is saved as it was.

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

* chore(forms): wrap button_state_shown's doc comment

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-25 10:11:04 -04:00
mtlcl410andClaude Opus 5.5 6918d0be21 M6: a static XFA form whose data fills thousands of fields opens in seconds (#4638)
Opening a static XFA form gives its fields the values in its datasets
(a form filled in by another application). Each value went through
forms::set_value, which reads the whole field tree to find the field,
writes it, and recalculates, reading the tree again. With n values that
is about 2n readings of n fields: quadratic. A public 136-page form with
about 6,000 fields took 261 s to open, a 95-page one with about 4,000
took 112 s; 8,000 synthetic fields took over ten minutes.

forms::set_values fills a batch as set_value fills each in turn, reading
the form once and recalculating once after the last value (the
calculation order, ISO 32000-2 Table 224 /CO, runs once over the final
values). Fields with the same fully qualified name (12.7.4.2) still share
the value. A name the batch already wrote, or a twin of it, makes the
form be read again first, so each write starts from what the earlier one
left, as one by one. Each value reports its own result; a failed
recalculation fails the batch, as it fails set_value. The engine collects
the changed values and fills them with set_values, finding fields by name
through a map rather than a scan.

The two forms now open in 4 s and 3 s, with the same fields, values,
warnings and document information as before; 8,000 synthetic fields open
in 1.5 s.

Tests: set_values matches set_value one by one (values, twins, the
calculated total, every appearance, saved; refusals for an unknown name,
a read-only field, an unknown option and clearing a no-toggle-off radio
group the batch selected); it reads the form a fixed number of times; the
engine takes 59 data values on a 60-field static form exactly as filling
them one by one does (the read-only one refused); and opening 800 fields
takes under 16 times as long as 100 (about 4x now, about 48x before).


Claude-Session: https://claude.ai/code/session_01CBVyQEwtmEw3mS1MoAiwrK

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:02:35 -04:00
.CooCoo 0db7d57619 Tests: XFA calculation tests no longer fail when the machine is busy (#4535)
Five XFA tests failed at random on a busy machine. The scripts of an
opening or a change share a 3 s wall-clock budget (a click's share 5 s),
each script has 1 s or 3 s, and the engine has 10 s to start; under load
the tests went past one of them, and the scripts they check were skipped
or did not start.

Session::with_xfa_time_scale (and Automation::with_xfa_time_scale)
multiplies each of these limits for a test; the default stays 1. The
script session counts the engine threads it starts on the calling thread
(xfa_session::engines_started).

The hundreds-of-calculations tests run with the limits raised, and the
engine's test now checks that opening starts one engine and a change one
more. A new test sets the factor to 0 and checks that the budget skips
all 210 calculations and says so. Three more tests that failed the same
way under load run with the limits raised.
2026-10-25 10:02:00 -04:00
Eric Kaden 3f5d8d06fe M9: name a laid-out XFA form's signature fields by their SOM path, so other viewers find the signature (#4507)
* M9: name a laid-out XFA form's signature fields by their SOM path, so other viewers find the signature

* M9: doc comments on the functions the signature naming touches

* M9: say other viewers in the signature naming's comments

* M9: a new layout keeps a signature a generated signature box holds

* M9: the re-layout test checks the signature's picture is kept too

* M9: a signature field named by its SOM path doesn't repeat a name a field already has

* M9: every name a field of the pass takes is known before a signature field is named, in either order

* M9: the signed-box test's doc comment is back on its test

A merge had left it above the next test, which has its own.
2026-10-25 10:01:53 -04:00
b6fedc09f0 M7.5: crop an added image by more than half from one side (#4521)
* M7.5: crop an added image by more than half from one side

Each side of an added image's crop was capped at 45 %, so keeping its right
third (70 % off the left) wasn't possible, and the drawing silently cut a
larger crop down. The limit is now per axis: a side may trim up to 95 % as
long as left and right, and bottom and top, keep at least 5 % together.
AddedImage::crop_valid checks input (content_update refuses the rest with
the rule), clamp_crop brings a crop read from a file into range keeping the
sides' proportion, and the Edit panel lets a side go up to what the opposite
leaves.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Marco Hagspiel <m.hagspiel@lcs-group.com>

* M7.5: accept crops that sum to the limit; keep untouched crop sides exact

`0.05 + 0.9` is just above `0.95` in floating point, so `crop_valid` rejected a crop that
keeps 5 %. Sums now pass within a rounding tolerance, and `clamp_crop` leaves such a crop as
it is. The image panel no longer rounds the stored crop to whole percent or clamps sides the
user doesn't touch: only an edited side is written back.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Marco Hagspiel <m.hagspiel@lcs-group.com>

* M7.5: an excessive crop from a file keeps its proportion

`clamp_crop` capped each side before fitting the pair, so `[1.5, 0, 0.5, 0]` lost its 3:1
proportion. The pair is now scaled first, from its halves so two huge sides can't overflow;
a pair within the limit keeps each side within it.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Marco Hagspiel <m.hagspiel@lcs-group.com>

---------

Signed-off-by: Marco Hagspiel <m.hagspiel@lcs-group.com>
Co-authored-by: Marco Hagspiel <m.hagspiel@lcs-group.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-25 10:01:48 -04:00
RiceDee 6901f66876 Merge main into feat/align-selected-lines
Main took integration batch 6 (#4566). Conflict in edit_text_ui.rs: the
overlay keeps this PR's view_id with main's interact_pos comment. The
two TextDraft literals from #4441's tests take selection and
typing_from.
2026-10-25 15:25:11 +11:00
Claude 4c09747773 M4: bookmarks from text styles need copying permission and keep columns apart (#1693)
CodeRabbit on #4560:
- The bookmark titles are the document's text, so the edit now needs
  copying permission as well as assembly, and Document::text_styles (the
  dialog's samples) is empty when copying is withheld; the automation
  text_styles tool already checked it.
- A heading went on to the next row whenever it was close below in the
  same style, so same-style headings in two columns on staggered rows ran
  together into one bookmark. A line continues a heading only when it lies
  under it.
- Lines found their style by a scan over every style so far; they are now
  indexed by font, weight, slant and quarter-point size, so a page with
  thousands of styles no longer makes the scan quadratic.

Regression tests for the first two (each fails without its fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LymhVaGZCWwB9HNEbLzeMM
2026-10-25 01:14:19 +00:00
jonathanwxh-cell 24edde6563 forms: clear typed XFA password field values from datasets on save (#3944) (#4506) 2026-10-24 21:08:21 -04:00
aiongg e0c319d273 Integration: a comment listed through a reference chain keeps its replies after an edit (#4061) 2026-10-24 23:18:59 +00:00
Lcoding15 4ce68f517b M4: New bookmarks from text styles, for headings that aren't tagged (#1693)
Bookmark options > New bookmarks from text styles... lists the styles the
document's text is set in (font without its subset tag, the size shown,
bold, italic, how many lines, a sample), the largest first, and takes one
style per bookmark level. Every line in a level's style becomes a
bookmark of that level under a new first "Untitled" bookmark, as from
structure; a heading set on several rows is one bookmark, and each goes
to the top of its heading.

The detection lives in engine::text_style_bookmarks on top of
edit::text_lines, reading each page top to bottom then left to right, so
two headings on one row stay two. text_styles and
bookmark_from_text_styles reach it headlessly, and the command
bookmark.from_text_styles opens the dialog. OutlineEntry gets an
optional top for the /XYZ destination; structure bookmarks keep theirs.
2026-10-25 01:03:08 +02:00
aiongg d821f91001 Integration: fix(comments): thread replies by their /IRT reference, count comments like Acrobat (#4061) 2026-10-24 22:48:11 +00:00
aiongg f94b34adc9 Integration: fix(redact,cos): cleared redaction marks leave nothing behind (#4226) 2026-10-24 22:48:11 +00:00
alehel ad8c2f46dd Merge origin/main into fix/comment-threads-by-irt
Conflicts:
- crates/render/src/inspect.rs: this branch's reply-linking methods and main's NoRotate
  rectangles (#3016) both go in the Annotation impl; the annotation reader takes this
  branch's loop with main's no_rotate field.
- crates/automation/src/comments.rs: this branch's HashMap import, main's import list.
- ATTRIBUTION.toml: each catalogue's source note keeps main's secured-document clause and
  this branch's reply-count clause; ATTRIBUTION.md regenerated (cargo xtask assets --write).
- The 33 catalogues: main's new rows, then this branch's reply-count row.

main's #3016 tests build Annotation field by field; they now set parent and grouped.
2026-10-24 20:42:11 +02:00
RiceDee 25da4b1894 Merge main into feat/align-selected-lines
Conflict in automation/tests/it/automation.rs: this PR's two aligns
tests and main's editable combo box test (#4451) were added at the same
place; all three are kept.
2026-10-25 02:08:55 +11:00