Reject finite but singular or overflowing determinants at the engine boundary. Promote added images for arbitrary transforms, as RotateBy already does, while preserving editable quarter turns and reflections. Regressions verify transformed placement, promotion, undo and refusal without mutation.
Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
An original image and an added item may share an XObject without sharing editable metadata. Require the selected drawing stream to own the added item instead of falling back to any matching resource. The regression actually moved the added sibling before the fix; scoped native and wasm gates now pass.
Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
run_edit_page_image carried its own inverse, bbox and multiply; Matrix
already has invert, bbox and then with the same formulas and the same
degenerate-determinant guard. Also drop the second finite check on Move
(checked on entry), match the added item with two finds instead of a
four-way chain, and toggle flips in one place for both the parallel and
perpendicular cases. No behaviour change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- [P1] Arrange: track and replay inherited non-ExtGState drawing state (colors, line widths, etc.) across q/Q scopes for stencil and form artwork
- [P2] Flips: route ImageEdit::Flip through coordinate-aware transform logic to swap axes when rotated at odd quarter turns (90°/270°)
- [P2] Rotation knob: measure clockwise angle delta from drag start relative to the rotated image center to prevent reapplying existing angle
- [P2] Format panel: connect superscript/subscript controls to line editor and content draft selections, and update parity evidence
- Resolve all 13 merge conflicts between upstream/main and main
- Preserve full image editing suite: OBB, crop, proportional resize, arrange, and context menu actions
- Preserve text editing formatting tools: bullet/numbered lists, subscript/superscript, and line spacing
- Cleanly integrate upstream's canvas modularization, font updates, and styling changes
- Refactor arrange_image and run_edit_page_image helpers to satisfy clippy cognitive complexity limits
- All engine, edit, and UI integration test suites passing cleanly
* 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.
# 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
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
# 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
Two content conflicts, both from main splitting the files this branch edits.
crates/cos/src/document.rs: main moved Document into document/*.rs.
Keep that split. A missing /ID is still pdfcraft_crypt::file_id of the
encryption seed (document/open.rs); generated_id is gone. The regression
lives in document/tests.rs.
crates/engine/src/edit.rs: main moved EditCtx and run_edit into
edit/run.rs. Keep that split. Protect still draws its seed and default
owner password from getrandom on EditCtx and fails closed
(EditError::Protection) when the CSPRNG errors or returns all zeros.
Co-authored-by: jonathanwxh-cell <jonathanwxh-cell@users.noreply.github.com>