Text typed into a pop-up's box and not yet saved is saved when the window's
x is clicked: the click takes the focus from the box and closes the window
in one frame, as one undo step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
- The render path reads a pop-up as none unless its /Rect is four numbers,
as pdfcraft_annot::summaries does; set_popup gives a comment's own pop-up
without one the place a new pop-up gets, so opening it shows it again.
- The composer's edit and a pop-up's, landing in one frame, are queued
together in one batch instead of the pop-up's replacing the composer's.
- A pop-up's unsaved text, reply draft, drag and resize follow their comment
when a deletion moves it, are dropped on undo/redo, and are dropped after
any other edit that leaves their (page, index) naming another comment
(pages moved, inserted or deleted).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
The pop-up window now reads as the common readers' do:
- a one-line title bar with the comment's kind and author, its date and x,
in the comment's colour; the body in a lighter tint, with fixed ink so it
reads in either app theme;
- the comment's text, and each reply's, in a box typed in place;
- the replies as panels in thread order, each inside the panel of the reply
it answers (/IRT), nesting at most eight deep;
- a Reply button under the comment and each reply, opening a box that
answers that comment or reply (Enter starts a new line; Post or
Ctrl/Cmd+Enter posts; Cancel closes it).
The window keeps the size of its pop-up's /Rect in points on screen as the
page zooms, instead of growing with the zoom; the text and replies scroll
inside it, with a scroll bar shown whenever there is more. Dragging its
lower right corner resizes it; the new size is saved as the pop-up's /Rect,
the window's upper left corner kept in place on screen (on a page turned a
quarter too), in one undo step. A /Rect with non-finite numbers gives the
least size.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
The connector from a comment to its open pop-up window was a one-pixel line.
It is now a faint wedge in the comment's colour: a point on the comment's
edge nearest the window, widening to a base on the window's side that faces
the comment, as the common readers draw it. Shown, as before, while the
pointer is on the comment or the window, or the comment is selected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
Review on #2137 (nitpick): a SetAnnotationPopup edit marked its page
stale, so its tiles were rendered again, its text cache dropped and its
selection cleared, though nothing in the raster changes: the window is
drawn over the page and a /Popup annotation paints nothing. Edits that
also change the comment (a batch) still invalidate as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
Review on #2137: the cited test opened, moved and closed a pop-up but
never looked at the connector line. The new test reads the frame's
shapes: a line in the comment's colour from the comment's edge to the
window's, nearest points at each end, shown with the pointer on the
comment or the window or with the comment selected, and not otherwise.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
Review on #2137: when /Annots holds 5 0 R and object 5 is only a
reference to the comment 6 0 R, the pop-up's /Parent names 6 0 R, but
the ownership check compared it with 5 0 R. The comment's pop-up was
then missing from both the on-open listing (render inspect) and the
summaries after an edit (annot). Both now compare the references they
resolve to.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
Rebased onto main's reference-based threading (#4061, #4566). The pop-up
matched replies by /NM (in_reply_to == name), which need not be unique:
two notes sharing PyMuPDF's fitz-A0 both showed one note's replies, and a
reply to a reply was missing. It now files replies under a comment with
thread_tops over the settled parent links, as comments_panel does, and
has_popup asks Annotation::is_comment rather than in_reply_to (a reply
whose parent has no /NM read as a comment of its own).
Regression test a_pop_up_lists_its_replies_by_reference_when_names_repeat,
seen to fail with the /NM filter restored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
The pop-up's reply list kept every annotation in reply to the comment,
so a review status (/StateModel /Review) and the private checkmark reply
Mark writes (/StateModel /Marked) showed as replies with their generated
text. It now lists replies without a /State, as the Comments panel does
(comments_panel.rs).
Raised by CodeRabbit on #2137.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3niW7kVeT8mi7TpWc8eAJ
Review on #2137. set_popup accepted a pop-up whose /Parent was present but
not a reference, and the comment list (pdfcraft_annot::summaries and the
inspector) reported whatever /Popup named: another annotation's rectangle
and open state. One check, own_popup, now decides for both: /Subtype
/Popup, and /Parent absent or a reference to the comment (ISO 32000-2:2020
12.5.6.14, Table 186: "Optional; shall be an indirect reference").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
A double-click on a comment opens its pop-up window as in Acrobat:
author, date, the text edited in place, the replies; the title bar
drags it, x closes it, each an undo step saved in /Popup (/Open, /Rect)
and a note's own /Open. A connector line in the comment's colour joins
the comment to its open window on roll-over or selection. comment_popup
does the same headlessly; comment_list reports the pop-up. Three undo
labels in 22 catalogues (Vietnamese included).
Ported onto main's split modules: set_popup lives in annot's modify.rs
and PopupInfo in info.rs; the pop-up windows are their own module,
ui-egui tools/comments/popups.rs, listed with the page modules that
read the pointer and set the cursor themselves; the hover tip filter is
in comments::describe. PageCx's pointer and redaction look, the
tests/it evidence paths and the Annotation test fixtures follow main
(formerly fcd33e131, c68e5d902, 0a7fb530b, 8af2c338e, 345c1573d), and
labels are logical text now that egui shapes bidi itself.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
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.
Use a named compact preset arrow beside the field, retain the row alignment/width assertions for the editable control, and open the current colour flyout in the saved-text regression. No accessibility or layout checks are disabled.
Preserve caption editing and current form/Prepare module boundaries. Carry export-only appearance preservation, validation and data round-trip tests to their current paths, and retain current attribution metadata. Hosted CI must validate this refreshed head.
Preserve main's continuous text selection, page tools and module split. Carry only this branch's sidebar-focus feature and regression onto main, without resurrecting old common files from criss-cross merge bases. Hosted CI remains required.
Use the unified Format Text panel and current page-input context instead of restoring old monolithic UI code. Preserve main's pointer routing, editor accessibility and preferences; retain colour, save and input regressions. Hosted CI must validate this refreshed head.
UPDATE_APP (native builds; craft-update is a native-only dependency) carries our 128 px icon and accent (#b70228, its red, from the icon (#5245)). The updater, the self-update
helper and craft-update's startup prompt all get them through crate::UPDATE_APP; the
prompt draws the icon, the accent and "by ArtCraft" (craft-update's default developer).
craft-update is pinned to storytold/craft-libs main at 1065653e7 (#434).
Clippy fixes for Rust 1.99 in untouched code (same logic):
- crates/edit/src/images.rs
- crates/forms/src/appearance.rs
- `zip`: 9.1.0 → 9.1.3
Requirement `roxmltree` 0.20 → 0.21: `ParsingOptions` gained `entity_resolver` (initializers take `..Default::default()`, i.e. none, as before) and is no longer `Copy` (two functions build it with a closure, the pattern merge.rs already used).
Packing makes an RGB image grey for every use at once, but /DefaultGray and
/DefaultRGB were checked only where the walk reads: page content and forms.
An annotation's appearance, a pattern, a Type3 glyph or a soft mask's group
drawing the same image can have other default colour spaces, so its colours
could change there.
The walk now records, by object number, every reference in the resources it
reads (each /XObject entry, drawn or not, an /XObject dictionary under its
resources, resources under a page, page-tree node or form), reading the
resources of every page and of every node above one, each page and node a
root. An image the walk reached stays covered unless an object reachable
from the trailer refers to it, or to an object on its way from a page, other
than through those references, or it is held by one that isn't covered; an
eight-bit DeviceRGB image that isn't covered stays RGB. So resources, an
/XObject dictionary or a form a page shares with an appearance keep it RGB,
as does a reference with another generation, while LibreOffice's layout (one
resources object for a page, its /Pages node, and a form inside it that
names it again) still packs. The scan runs only when packing is on and there
is such an image.
The images drawn or listed where the defaults are in force are kept by
object number too, so one named with another generation there stays RGB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The optimizer now writes JBIG2 images as a symbol dictionary and text
regions, and Export ▸ Images has to read them back. A page of three
repeated letters, symbol coded (smaller than the generic coding),
exports as its picture. The test is added to create.export-all-images'
parity evidence. Asked for in pre-push review.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P6yUtvWGPsJpMLyh6pxSXG