Skip to content

Edit: crop an existing page image with its handles - #2002

Merged
echelon merged 5 commits into
storytold:mainfrom
regendorf-ux:pr/image-crop
Oct 15, 2026
Merged

echelon merged 5 commits into
storytold:mainfrom
regendorf-ux:pr/image-crop

Conversation

@regendorf-ux

@regendorf-ux regendorf-ux commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What this changes

edit.image-crop was partial ("for added images: trim each side, clipped"). Images already on the
page can now be cropped too:

  • ImageChange::Crop([f64; 4]) (edit/src/images.rs): the part to keep, in user space, becomes a
    m l l l h W n clipping path - the box's corners mapped by the inverse of the scope's matrix - placed
    just inside the scope that holds the image (the private-crop scope from Resize exclusive image crops across nested states and content streams #1269), or a new q … Q
    around the Do. A clipping path already at the scope's start (the picture's earlier crop) is
    replaced, so a crop can grow back. The pixels are never scaled, and a later move or resize wraps the
    scope, so the crop travels with the picture.
  • Engine ImageEdit::Crop (undo label "Crop image"); tool image_edit action crop with rect
    (top-left-origin points, the part to keep).
  • Window (edit_text_ui.rs): right-click a selected picture ▸ Crop Image puts it in crop mode
    (CropMode, exported): eight handles trim (the inside pans), the part that goes is dimmed, Enter or
    Done applies, Esc or Cancel leaves; Remove Crop (enabled when the picture is cropped) shows the whole
    picture again.
  • Rows for the three new labels in all 21 languages, with their checksums in ATTRIBUTION.toml; parity
    edit.image-crop → shipped, with what is still missing.

How you verified it

Re-run on 2026-10-14 at 9beb327, after merging main at v0.8.0 (9931329) into the branch:

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets -- -D warnings: clean
  • cargo nextest run --workspace -E 'package(pdfcraft-automation) | package(pdfcraft-edit) | package(pdfcraft-engine) | package(pdfcraft-ui-egui)' (every test of the crates this PR changes): 1677 run, 1677 passed
  • cargo xtask assets, cargo xtask parity and cargo xtask layers: ok

Running every ui-egui test caught primary_ui_fonts_cover_latin_and_cyrillic_catalogs failing: the Bulgarian crop hint used "ѝ", which JetBrains Mono lacks. The hint is reworded without it (9beb327).

Tests. New behaviour, so the tests exercise APIs this PR adds. cropping_an_existing_image_clips_it_and_moves_with_it
(engine): the crop clips the rendered picture, a later move carries the crop, cropping again can grow it
back, and a box with no area is refused. cropping_a_picture_with_its_handles (ui-egui):
dragging the bottom-right handle and pressing Enter crops; Esc leaves crop mode without a change.
editing_page_images_through_tools (automation) gains a crop step.

Anything a reviewer should look at first

  • The labels in the 20 non-English language files were written by an AI model from the English
    wording; a native speaker's review of those rows would be welcome.
  • The Properties panel gets no Crop… button here: the right-click menu is the way in. A button there
    belongs with a larger Properties change.
  • Not done: handles for added images (they keep their % fields), and a clip shared by several objects in
    the original file is left as it is.
  • Overlap with feat: Acrobat UI parity for image editing, text formatting, and UI stabilization and Bug Fixes #2155 (opened after this one, +3739 lines): among its many changes it adds a 4-point
    image-specific interactive crop in the same files (edit/src/images.rs, edit_text_ui.rs). This PR is
    only the crop: ImageChange::Crop with its edit, tool and handles, tested on its own. Whichever you
    prefer is fine; if feat: Acrobat UI parity for image editing, text formatting, and UI stabilization and Bug Fixes #2155's crop lands first I'll rebase this down to what it adds, or close it.

Never-crash

  • No new unwrap/expect/panic! outside tests
  • The crop box must be finite (|v| ≤ 1e12) and have area; a placement that cannot be inverted is refused
  • No unsafe

Provenance

  • No asset added beyond translation rows; behaviour from ISO 32000's clipping paths and black-box
    observation of Acrobat's crop handles

Security

  • No network access

Parity and reach

  • parity/acrobat-features.toml updated with what is still missing
  • Headless through image_edit action crop
  • No documented behaviour changed

🤖 Generated with Claude Code

regendorf-ux and others added 5 commits October 13, 2026 16:53
ImageChange::Crop(rect) keeps the part of a page image inside rect (user space)
with a clipping path placed just inside the scope that holds the image (or a
new q … Q around its Do); a clipping path already at the scope's start, the
picture's earlier crop, is replaced, so a crop can grow back. The pixels are
never scaled, and a later move or resize wraps the scope, so the crop moves
with the picture. ImageEdit::Crop (undo label "Crop image"), image_edit
action crop. In the window, right-click ▸ Crop Image puts the selected
picture in crop mode: eight handles trim (the inside pans), the part that goes
is dimmed, Enter or Done applies, Esc or Cancel leaves; Remove Crop shows the
whole picture again. Rows in every language, their checksums in
ATTRIBUTION.toml, parity edit.image-crop shipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ATTRIBUTION.toml: the 21 language files' sha256 recomputed from the
merged files.

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

JetBrains Mono has no 'ѝ', so primary_ui_fonts_cover_latin_and_cyrillic_catalogs
failed; the hint is reworded ("with its handles" -> "with the handles").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	ATTRIBUTION.toml
#	crates/engine/src/tests.rs
#	crates/ui-egui/src/i18n/ar.tsv
#	crates/ui-egui/src/i18n/bg.tsv
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/de.tsv
#	crates/ui-egui/src/i18n/el.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/fr.tsv
#	crates/ui-egui/src/i18n/hu.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/kk.tsv
#	crates/ui-egui/src/i18n/lt.tsv
#	crates/ui-egui/src/i18n/pl.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/sl.tsv
#	crates/ui-egui/src/i18n/te.tsv
#	crates/ui-egui/src/i18n/tr.tsv
#	crates/ui-egui/src/i18n/uk.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
#	crates/ui-egui/tests/editing.rs
@echelon
echelon merged commit 6c22c78 into storytold:main Oct 15, 2026
3 of 5 checks passed
@echelon

echelon commented Oct 15, 2026

Copy link
Copy Markdown
Member

Thank you @regendorf-ux for cropping existing page images with handles! It's merged into main now.

If you haven't already, please come and join us on Discord: https://discord.gg/artcraft

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants