mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-09 10:46:38 +00:00
Refs #5989. Ctrl/Cmd+A selects every object on the page; copied objects paste as page content that draws the same, within a document or into another, with organize's bounded deep copy across documents. Headless: object_copy, object_paste, object_move all.
126 lines
7.8 KiB
Markdown
126 lines
7.8 KiB
Markdown
# Move several objects together
|
|
|
|
In **Edit a PDF → Edit text & images**, select several items on the same page:
|
|
|
|
- **Shift-click**, **Ctrl-click**, or **Command-click** toggles an item. An ordinary selected
|
|
paragraph, image, or added item becomes the first member when you extend its selection.
|
|
- Drag through empty page space to draw a selection rectangle. Items intersecting the rectangle
|
|
are selected; hold one of those modifiers to add to the selection.
|
|
- Drag the selection to move all its items by the same distance. Blue outlines preview the move;
|
|
releasing commits it as **one undo step**. Escape cancels the gesture or clears the selection.
|
|
- Switching tabs, opening Home or a modal dialog, or losing window focus cancels a drag preview.
|
|
Returning to the document keeps the selection without committing a release from elsewhere.
|
|
- Modifier-clicks prefer added items and text over background artwork, so labels remain selectable.
|
|
- Clear the selection or double-click an item to return to ordinary text editing and image resizing.
|
|
Finish or discard a text draft before starting a multi-selection.
|
|
- **⌘A / Ctrl+A** selects every item on the current page (paragraphs, artwork and added items),
|
|
as Acrobat's Edit PDF does, instead of the page's text. Inside a paragraph or text box open for
|
|
typing it still selects that text.
|
|
|
|
## Copy, cut and paste
|
|
|
|
With items selected together, **⌘C / Ctrl+C** (Edit ▸ Copy) copies them and **⌘X / Ctrl+X**
|
|
(Edit ▸ Cut) copies them, then deletes them as one undo step. **⌘V / Ctrl+V** (Edit ▸ Paste) pastes
|
|
them onto the current page of any open document, also after the original changed or closed, as
|
|
one undo step, and selects what it pasted. Onto the page they came from they land 18 points down
|
|
and to the right, as Acrobat offsets a paste onto the same page; elsewhere where they were; each
|
|
further paste onto a page steps another 18 points; a copy that would land off the page is centred.
|
|
|
|
A paste draws the same: it copies the items' content operators with the graphics state they were
|
|
drawn in (transformation, clip, colours, transparency, text state) and every resource they use
|
|
(fonts, images and Form artwork, ExtGStates, colour spaces, patterns, shadings), merged into the
|
|
target page's resources under names that don't clash, as Ungroup merges them. Within a document the
|
|
same font and image objects are drawn again. Into another document they are copied with what they
|
|
refer to, each once whatever the cycles, sharing objects it already holds; a paste whose resources
|
|
take more than 20,000 objects or 256 MB is refused with that reason. Pasted items are ordinary page
|
|
content: an added text box comes as page text, untagged and in no layer. The system clipboard gets
|
|
the copied text (or, without text, the first picture) for other applications; a newer copy there,
|
|
here or in another application, wins ⌘V. Text drawn as a clipping path before the selection is
|
|
refused, as is artwork without resources of its own that names one, where a paste would change it.
|
|
|
|
The supported items are the existing paragraph boxes, raster Image XObjects, whole Form artwork,
|
|
and text/images added by PdfCraft. A Form remains one item, including its nested artwork and
|
|
resources. Added items appear once in the selection inventory. Selections stay on one page and
|
|
contain at most 1,000 items; pages with more than 10,000 recognised objects are refused for group
|
|
selection. Content streams must decode completely within 64 MiB each and 128 MiB per page, with
|
|
a 1,024-level graphics-state limit and 100,000 content operations. These limits apply to both the
|
|
original page and the moved result; unreadable or missing streams are refused. Moving between pages,
|
|
arbitrary paths, inline images, group resizing, align/distribute,
|
|
and vertical or clipping text are outside this operation's scope. A refusal changes nothing.
|
|
|
|
Added streams move by changing their outer placement, preserving drawing bytes, fonts, crop paths,
|
|
and image transforms even if the page was rotated after the item was added. Their original local
|
|
coordinate basis is retained in `/PCAdded/MoveOrigin` so metadata and source placement agree after
|
|
repeated moves. Ordinary content updates replace that stream and reset the basis.
|
|
|
|
Existing text moves without retyping, font substitution, or reflow: its original encoded glyph
|
|
strings and kerning are replayed with temporary numeric `TJ` adjustments and text rise. The reader
|
|
performs the original glyph advances, so following unselected text keeps its position. Paragraph
|
|
indices can change when detection groups lines differently after a move. The UI remaps its own
|
|
selection only when the new geometry matches uniquely; other document edits clear stale selection.
|
|
|
|
## Engine
|
|
|
|
`Document::editable_objects(page)` returns a mixed inventory with zero-based `ObjectTarget`
|
|
references and user-space rectangles. Apply `Edit::MoveObjects { page, objects, offset }` through
|
|
`Session`. Targets resolve together before mutation. Permission checks, dirty state, history,
|
|
incremental saving, and refreshing use the same transaction as other edits.
|
|
|
|
`Document::object_move_offset(page, [dx, dy])` converts a displayed-page vector (points, right/down)
|
|
to the user-space vector required by the edit, accounting for the crop box, `/Rotate`, and `/UserUnit`.
|
|
A frontend with an additional view rotation converts its pointer positions to displayed page space
|
|
first. Duplicate, missing, non-finite, oversized, or unsupported targets fail atomically. Engine callers
|
|
re-list after changes; the automation generation guard detects stale references before the edit.
|
|
|
|
## CLI and MCP
|
|
|
|
Both frontends use the same tools. `object_list` returns a document `generation` and items with
|
|
`kind` (`added`, `text`, or `image`), 1-based `index`, `type`, displayed `rect`, and optional `text`.
|
|
Treat those references as belonging to that generation and pass it back when moving:
|
|
|
|
```json
|
|
[
|
|
{"tool":"doc_open","args":{"path":"input.pdf"}},
|
|
{"tool":"object_list","args":{"doc":1,"page":1}}
|
|
]
|
|
```
|
|
|
|
After choosing references from the returned list, use a move call like this in the same MCP
|
|
session. For a CLI script, include `doc_open` in that script before the move:
|
|
|
|
```json
|
|
[
|
|
{"tool":"object_move","args":{
|
|
"doc":1,"page":1,"generation":0,
|
|
"objects":[{"kind":"text","index":1},{"kind":"image","index":1}],
|
|
"offset":[24,18]
|
|
}},
|
|
{"tool":"doc_save","args":{"doc":1,"path":"moved.pdf"}}
|
|
]
|
|
```
|
|
|
|
`{"all": true}` instead of `objects` takes every item on the page, as ⌘A does. `object_copy` takes
|
|
the same references (with `cut: true` it deletes them afterwards, one undo step) and `object_paste
|
|
{doc, page}` pastes them, into the same or another open document, as the app does:
|
|
|
|
```json
|
|
[
|
|
{"tool":"object_copy","args":{"doc":1,"page":1,"all":true}},
|
|
{"tool":"object_paste","args":{"doc":2,"page":3}}
|
|
]
|
|
```
|
|
|
|
Use the actual generation and references from your document, rather than assuming these example
|
|
values. `offset` is in displayed points: positive x moves right, positive y moves down. The optional
|
|
generation guard rejects a changed document before interpreting references. Re-list after any edit,
|
|
undo, redo, or save; there is no claim that an index persists across generations. Run a script with
|
|
`pdfcraft-cli run --script steps.json --root DIR` or call these tools through the opt-in MCP server.
|
|
|
|
Regression coverage: `crates/edit/src/tests.rs` (glyph bytes, neighbours, split streams, shared Forms,
|
|
rotated/cropped/UserUnit pages and refusal), `crates/edit/src/objects/clip_tests.rs` (copies that
|
|
render the same pixels, renamed resources, hostile content), `crates/engine/src/tests.rs` and
|
|
`crates/engine/src/object_clip_tests.rs` (permissions, history, rendering, save/reopen, pastes into
|
|
another document, cycles and the size limit), `crates/automation/tests/it/automation.rs` (generation
|
|
guards, hostile references, copy/cut/paste tools), and `crates/ui-egui/tests/it/object_selection.rs`
|
|
(real-shell selection, gestures, ⌘A and the clipboard keys).
|