Found while testing crates/edit/src/lib.rs:445
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Holding Shift while dragging a corner handle now preserves the annotation's
current proportions, unless the annotation has its own fixed aspect ratio
(image signatures). This matches Acrobat behaviour for comments, stamps,
and form fields.
- Added helper to compute the effective ratio from fixed
aspect, current proportions with Shift held, or none.
- Updated in comments.rs and in prepare.rs to
pass the Shift modifier.
- Updated signature_drag.rs to read and pass Shift.
- Added tests for the new Shift behaviour in comments.rs and prepare.rs.
- Updated docs/ui-parity.md and parity/acrobat-features.toml.
Fixes#2199
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M4: lay out Organize pages as Acrobat's panel
Organize pages gets Acrobat's panel: Pages selected (typed, or by kind
from its menu: all, even, odd, landscape, portrait); page options as a
row of icon buttons (rotate both ways, delete, extract) with Insert (a
blank page, pages from a file, duplicates), Replace, Split and Bates
numbering; page setup (boxes, crop, transitions, templates, printing);
and page labels and properties. Items PdfCraft doesn't have yet are
listed muted and say they are planned.
The panel shows whenever the grid does, however Organize was opened (the
tool, the command, --organize), and its Close ends Organize. The grid's
own toolbar keeps the selection count, moving pages, their size, Save
pages and Close; the page actions moved into the panel with the same
labels, and queue on the view as before. Two new strings are translated
in every catalogue.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M4: Pages selected shows the current page when nothing is selected
The empty field showed the example "1, 12, 16-18" in grey, which read as a
selection. It now shows the page the actions apply to, as Acrobat does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(i18n): refresh the catalogue hashes in ATTRIBUTION.toml
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M4: leaving Organize goes back to the workspace's panel
Opening a page from the grid (or the grid's Close, or the panel's Close)
always showed All tools, so Organize opened from the Edit panel dropped
the user out of Edit. It now goes back to the workspace's own panel: Edit
under the Edit tab, Convert's and E-Sign's under theirs, All tools
otherwise. Back still goes to All tools.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M4: box the Organize panel's page edit (clippy large_enum_variant)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M4: parity notes for the Organize pages panel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Refresh catalog hashes after merging main
---------
Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
A page turned a quarter (or any landscape page) was fitted with the
portrait margins and pinned to the top of its cell, so it showed as a
small strip with empty space below. The page picture is now as large as
the cell's picture area allows and centred in it, as Acrobat draws it;
the sides narrow only when a picture that wide would reach the selection
checkbox. Damaged page sizes (zero, NaN, infinite) still give a finite
rectangle inside the cell.
Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The boxes round paragraphs and images were drawn by the same call that
takes the page's pointer input, and that call was skipped whenever
something else owned the input: selected objects, added content, or page
input blocked because the window lost focus or a dialog or the palette
was open. Switching to another window made every box vanish until the
pointer came back. The boxes are now drawn whenever the tool is on; only
the input waits.
Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M7: the Edit tab turns on Edit text & images, as in Acrobat
Entering Edit boxes every paragraph and image on the page at once, so text
is editable with one click instead of first finding Edit text & images in
the tool list. The floating toolbar gets an Edit text & images button while
Edit is open or the tool is in use. Leaving Edit (or turning the tool off
there) applies a paragraph still open in the editor, as clicking away from
it does; left open, its page kept showing the paragraph's text removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M7: parity notes for the Edit tab turning on Edit text & images
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M7: the panel-reveal test looks for the Edit panel's own row
With Edit text & images on, its name is on the floating toolbar as well as in the panel, so
the label alone matched twice and no longer showed that the Tools tab was revealed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
unsaved_changes_are_kept_until_reload_is_chosen looked for the English notice while the app
followed the developer's system language (Auto), so it failed on a non-English Windows.
Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* fix(redact): size image clearing with checked arithmetic (#2177)
An ICCBased image's /N comes from the file and was cast with `as usize`, then multiplied
unchecked into the row size. /N 2^60 overflowed it (debug: overflow panic; release: a zero row
emptied the buffer and the write loop indexed out of bounds), and a negative /N wrapped.
/N is now limited to 1, 3 or 4 (ISO 32000-2 §8.6.5.5), every size product is checked, and the
pixel writes use `get_mut`. An image that fails any of these is refused, and the caller already
removes it whole (fail-closed). Regression test covers /N 2^60, -1 and 2.
* redact tests: pass the pixel array by value (clippy needless_borrows_for_generic_args)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M3: Save asks before writing over a changed file, one tab per file, locked autosaves (#2166)
Save compares the file on disk with the version the document came from
(its open, reload or last save) and asks before writing over another
one, with Save as... to keep it; the changed-on-disk notice says what
Reload and Save each do. A file that is open already comes to the front
instead of opening twice. A running instance holds an exclusive lock
file in the recovery folder and names it in its snapshots, so a second
instance neither offers nor discards what the first is autosaving.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
* M3: the pending save exists only where files do (#2166)
The web build has no file to compare against, so the struct and field
are desktop-only; the wasm check gained no warning.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
* Refresh catalog hashes after merging main
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
PdfCraft's RPM requires Fedora package names: `libxkbcommon`, `(vulkan-loader or libglvnd-egl)`.
openSUSE has no `libxkbcommon`, `vulkan-loader` or `libglvnd-egl` package, so zypper refuses it with
`nothing provides 'libxkbcommon'` (exit 4, which the ArtCraft Launcher shows as "error 4";
storytold/craft-launcher#341). Fixes#1472.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M9: harden .p12 digital IDs (#1847)
New digital IDs are written with 600,000 PBKDF2-HMAC-SHA-256 rounds (OWASP)
for both encrypted parts and the MAC, instead of 2048. The MAC gets the same
count because checking it alone tells whether a guessed password is right.
Opening a .p12 now spends at most 10 million rounds of key derivation across
the whole file, charged before any hashing. The file chose its own iteration
counts unchecked (`u64 as u32`), so a crafted file could hang the password
check for about an hour, or wrap 2^32+1 to one round.
Creating an ID needs at least 8 characters (NIST SP 800-63B), from one
constant, pkcs12::MIN_PASSWORD_CHARS, shared by the app, sign_id_create and
its MCP schema. Opening an existing ID takes any password.
* Refresh catalog hashes after merging main
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
New files from Combine, Extract, Split and Create, signed, timestamped and
LTV-embedded files and the OCR batch output reopen with their pages before
anyone writes them, as saves already did. The desktop app, the automation
tools (with or without --root) and the CLI's edit, combine, extract and
split write through pdfcraft_platform::staging::write_atomically, so a
failed write or --out naming the input never leaves a half-written file.
Special files such as /dev/stdout are still written as given.
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M6: sort multi-select choice indices
Write ascending /I option indices without reordering /V selections.
Add forms and engine regressions for reverse selection and save/reopen.
Co-authored-by: Codex <codex@openai.com>
* M0: update rustls to 0.23.46
Update only the resolved rustls version and registry checksum.
Keep the existing choice-index feature changes and advisory policy.
Co-authored-by: Codex <codex@openai.com>
---------
Co-authored-by: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* fix(optimize): count what form fields and comments draw with by kind
The space audit claimed the AcroForm and annotations before any page
resources, so the fonts in /DR and in widget and comment appearance
streams were counted as Forms or Comments. A fillable form with an
embedded field font showed mostly "Forms" and almost no "Fonts".
Resources are now sorted by kind first, wherever they are used: page
resources, the form's default resources and every annotation
appearance (one stream or a dictionary of states). A form XObject's
other entries now count as X Object Forms instead of overhead.
Sorting nested forms' resources recursed once per form, so a long
chain of nested forms overflowed the stack. The recursion is now
bounded at 64 levels; deeper forms are still counted as X Object
Forms through the iterative walk.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): line up the Space Audit columns and say what it shows
The percentages were drawn at the dialog's far edge, away from their
header and outside the striped rows: egui gives a grid's last column
the rest of the window, and the right-to-left layout used to
right-align the numbers filled all of it. Every cell is now laid out
first and given exactly its column's width; numeric columns and
their headers are right-aligned, and the description column takes
the rest, so the stripes span the dialog.
A caption under the title says the table is what takes up the file's
space now (not what optimizing would save), translated in every
catalogue. The parity notes no longer call the space audit missing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Refresh catalog hashes after merging main
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
A rewritten resources dictionary, font, XObject or graphics state of a
signed page changes what the page shows. It was judged by the keys its
new version carried, so adding /FT or /Subtype /Widget made it a
permitted form fill. The roles now come from the signed revision's own
object graph, walked from each page's resources with bounds; a category
dictionary that only gained a name is judged as before.
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Add text and page_add_text draw Hebrew with the craft-fonts Hebr face
(Noto Sans Hebrew) through the shaped path Arabic and Thai already use:
- pdfcraft-fonts: ShapedScript::Hebrew, shape_hebrew and hebrew_has.
Grapheme clusters keep points and cantillation marks on their letter.
- pdfcraft-edit: Hebrew paragraphs are laid out with UAX #9 like Arabic;
spaces and punctuation between Hebrew letters take the Hebrew face.
first_undrawable refuses Hebrew the face can't draw (or without the
face) instead of writing "?"; existing items stay movable without it.
- Type 3 glyphs with ToUnicode and per-line ActualText, so extraction,
search and the editor get typing order back.
Regular style only, as for Arabic and Thai.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The dialog previews in the interface font, which draws Cyrillic, while
applying draws with the standard fonts and refused it after OK.
HeaderFooter::first_undrawable and Watermark::first_undrawable are now
the one check: applying refuses through them, and the dialog shows the
Edit Text warning and disables OK on the same answer.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A BDC may name its property list in /Resources /Properties instead of
writing it inline. Marking a figure decorative and placing figures only
matched inline lists, so a figure tagged through a named list kept its
MCID content and parent-tree entry while its element was removed.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Pair coordinate press and release after hover warmup so frame delays do not turn clicks into holds. Preserve text multi-click selection for coalesced pointer events.
Co-authored-by: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Hovering a check box or radio button that sits on page text showed the
text-selection I-beam. The form layer set the pointing hand, but on a
plain hover it reported the pointer as not consumed, so the text layer
ran afterwards and replaced the cursor whenever a glyph was under it.
Character sheets hit this on every dot: the boxes are set on printed
"OOOOO", and boxes whose checked look is a font glyph (an "X") count as
text too, since page text includes annotation appearances.
forms_ui::page_input now says whether the pointer is merely over a field
or the field owns the press, and the text layer leaves the cursor alone
while a field is under the pointer. Presses, clicks and drags behave as
before; text fields keep their I-beam.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Without the mark, 5.1 reads the file in the system code page and the
ellipsis on line 136 decodes into something that swallows a quote, so the
script does not parse (MissingEndCurlyBrace at line 133) and package.ps1,
which runs it with the shell it was started from, fails after building
the MSI. pwsh reads UTF-8 either way.
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* M3: a document that asks to open in Full Screen mode asks first (/PageMode /FullScreen)
The Initial View option "Open in Full Screen mode" was written and read
but never acted on when opening. Now the app asks, as Acrobat's alert
does: Full Screen enters it, Not now leaves the window as it is. A
document cannot take the screen on its own.
Refs #379, #1459.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
* Refresh catalog hashes after merging main
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
pdfcraft_render::transition gives the picture of a page transition at a
moment of its run: which parts of the outgoing and incoming page to paint,
where, and how opaque, in page fractions, for all twelve styles and every
direction. Pure and tested; the full-screen presentation will paint from
it when it plays transitions.
Refs #379.
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M4: set page transitions and display times (/Trans, /Dur) through the engine and the tools
organize::set_transition writes or removes a page's /Trans dictionary
(only the entries the style reads and that differ from the defaults) and
/Dur, with every value checked before any page changes; Edit::SetTransition
is one undo step ("Set page transitions", translated in every catalogue);
page_transition_set is the tool. The counterpart of reading them (#2074);
playing them and the Organize dialog stay planned.
Refs #379.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
* Refresh catalog hashes after merging main
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Each page's presentation settings of ISO 32000-2 s12.4.4 are read with the
spec's defaults for what is missing or out of range: the transition style,
duration, dimension, motion, direction, Fly scale and backdrop, and the
/Dur display time. doc_info lists them; pdfcraft-cli info counts them.
This is the data full screen needs to play transitions and auto-advance;
playing and editing them stay planned.
Refs #379, #2070.
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* i18n(es): translate the new print and Add text strings, Type on the signature pad, tú register
Seven tl! literals reached the Spanish UI in English: the PDF page size,
Match page at 100%, PDF scale and the two scale hints in the print dialog,
and the Done / Discard buttons of Add text. The signature pad's Type
button read "Tipo" (the noun) because Spanish had no `signature pad`
entry; it now reads "Escribir", beside Dibujar, Imagen and Escanear.
Four rows used the formal usted register the catalog header rules out
(Elija, Cámbielo, the signature-photo hint, the no-signature notice);
they now use tú like the rest.
Spanish gains `spanish_covers_ui_literals`, the check Japanese, Italian,
French, German and others already have, and its command test checks
menu names too, so a new string can't ship in English by accident.
`contextual_labels_fall_back_to_the_plain_translation` used Spanish's
missing `signature pad` entry as its example; it now uses a context no
catalog defines, so it keeps testing the fallback as catalogs fill in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Refresh catalog hashes after merging main
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M2: heavy-sheet generator and page timing harness (item 8 of storytold/pdfcraft#623)
Adds two examples to pdfcraft-render. synth_sheet writes a synthetic A1 sheet whose content
follows the sample's layout, so timings on it can be compared with the sample. page_timing
replays the canvas's requests for page 1 and prints times, counts and an output hash. The
renderer is unchanged. Budgets are reported, not enforced.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
* M2: sort sweep results by tile size too (item 8 of storytold/pdfcraft#623)
Tiles at the same origin but different sizes compared equal under the sort key, so the
sweep hash depended on completion order. Including the size makes the key unique.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
document_scripts_named walked the catalog's /Names /JavaScript tree by
resolving and deep-cloning every kid onto its stack before looking at it.
The 10,000 cap counted nodes taken off the stack, not what went on it, so a
node whose /Kids lists itself k times pushed k full copies of itself (each
holding its k-entry /Kids array) per visit. A 6.5 KB file with k = 1000 made
`pdfcraft-cli run` (doc_open, js_document_scripts) exhaust an 8 GB address
space in 8 seconds and abort; the same walk runs when a field edit sets up
the script runner. A tree that lists a leaf twice returned its script twice.
Keep kids unresolved on the stack and skip references already read, as the
/Dests walker in organize does. The cap stays.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The single-instance handoff (#282, #715) ran only on Windows; every other platform returned
Claim::Alone, so on Linux each xdg-open or file-manager open started a second process and window.
Linux and the BSDs now use the same Unix-socket exchange through std's UnixListener/UnixStream,
with the socket in $XDG_RUNTIME_DIR/pdfcraft (never /tmp) and named after WAYLAND_DISPLAY or
DISPLAY, so a launch on another display opens its own window. No XDG_RUNTIME_DIR: as before.
macOS is unchanged (Apple events).
winit ignores Focus on Wayland, so on a native Wayland window the handoff also requests user
attention (xdg-activation), which desktops show in the taskbar.
The hand-off, together and stale-socket tests now run on Linux/BSD too; a new test covers the
display socket names.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Highlight appearances written by other editors are a transparency group whose
content is filled with `/BM /Multiply`. Every `/Group` form got a layer of its
own, so the Multiply blended with that layer's transparent backdrop and the
layer then covered the page as an opaque block.
Vendored hayro-interpret patch (28): a group that is neither isolated nor
knockout composites its elements with the backdrop (ISO 32000-2 §11.4.7), so
when it is entered at full opacity with no soft mask and Normal blending there
is nothing for a layer to add, and its content is drawn straight onto the page.
`/I` and `/K` are read from the group dictionary. An isolated group keeps its
blank backdrop; a knockout group or one entered at partial opacity, under a
soft mask or with a blend mode keeps its layer.
Test: `raster::tests::a_multiply_highlight_inside_a_transparency_group_keeps_the_text`
(the appearance drawn as an annotation and from page content, with `/I false
/K false` spelled out and with no group at all; an isolated group as a control;
a group entered at half opacity still composited as one). It fails before the
fix with the text pixel yellow instead of black. parity: view.render-transparency
cites it and says what is still approximated.
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M2: clip intersection starts its row walk at the first shared row
clip::intersect in vello_common, the step every clipped path passes
through, began its walk over strip rows at the earlier of the two paths'
first rows. Each path therefore visited every row above it, although rows
where one path has no strips produce no output. Every page is clipped (the
crop box is a clip), so this ran for every stroke of every render: on the
A1 engineering sample it was the hottest function in a full-page render.
Start at the later first row, as vello_common 0.1 does. The strips and the
pixels are unchanged; a test counts the rows the walk visits and fails
without the change.
vello_common 0.0.8 is vendored with that one-line PdfCraft patch (marked
in the source), its unit test, the vendor/README.md row and an
ATTRIBUTION.toml entry for its reference image. Cargo.lock records it as a
path crate.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
* Refresh catalog hashes after merging main
---------
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Wrap text in Document Properties rows within the 420 pt content column so documents with long filenames or metadata do not stretch the modal dialog card beyond the viewport boundaries.
Fixes#2011.
* Redaction Tool Properties: the mark's appearance, and Make properties default
Acrobat's third group in the Redaction Tool Properties dialog: the mark's
outline colour (/C), a fill colour shown while the pointer is over a mark (a
viewing preference, never written), and the mark's opacity (/CA). Make
properties default: ticked, OK keeps the values for the next run (saved with
the settings as redact_prefs); unticked, they hold for this run only and the
saved defaults stay; Cancel puts back what the dialog opened with. The dialog
is laid out in two columns so it fits a laptop screen. Rows in every
language, their checksums in ATTRIBUTION.toml, parity
protect.redaction-properties shipped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Refresh catalog hashes after merging main
---------
Co-authored-by: Osvaldo Duran <338149330+regendorf-ux@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Edit: crop an existing page image with its handles
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>
* Image crop: Bulgarian hint without a character the monospace UI font 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>
* Refresh catalog hashes after merging main
---------
Co-authored-by: Osvaldo Duran <338149330+regendorf-ux@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
calculate_js wrote a field's simplified field notation as
"/** BVCALC expr EVCALC **/ event.value = AFMakeNumber(0);". PdfCraft reads the
comment, but a viewer that runs the script instead (Acrobat among them) computed
0. notation_js now writes Acrobat's own translation after the comment: each
field name becomes AFMakeNumber(this.getField("name").value), with \ escapes
in names honoured, numbers and operators kept.
Co-authored-by: Osvaldo Duran <338149330+regendorf-ux@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>