Commit Graph
1660 Commits
Author SHA1 Message Date
Marco Arnulfo 7362530591 MCP: --automation-read-root and --automation-write-root may each be given more than once (#1552)
A path passes when it lies inside one of the roots of its kind. The roots'
common parent, a sibling named like a root, `..` or a link out of them and
the other kind's roots stay refused; a folder given twice counts once.
2026-10-23 20:30:07 +02:00
andeecollard da8d3a2937 PDF: accept forward xref links and reject cycles 2026-10-23 19:19:20 +01:00
moppeeer d2944377db Merge branch 'direct-select-anchor-points' into properties-anchor-points 2026-10-23 19:44:28 +02:00
moppeeer d3960f90df Merge branch 'direct-select-anchor-points' into direct-select-handles 2026-10-23 19:38:36 +02:00
moppeeerandClaude Opus 5.5 8daf036a3e Add Anchor: an invalid subpath is an error, not the last subpath
path.appendAnchor read `subpath` with as_u64, so -1 or "0" counted as omitted
and the point went onto the last subpath. Only an omitted (or null) subpath
means the last one now; any other value must be a valid index.

Found by CodeRabbit on #1657.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 19:38:24 +02:00
moppeeerandClaude Opus 5.5 c6821516e5 Edit › Clear with points selected deletes the points, not their paths
The Delete key already ran path.deletePoints under Direct Selection, but
Edit › Clear (the menu item, a native accelerator, or the control channel)
called edit.clear, which removed every path whose points were selected. With
points selected and no ids given, edit.clear now deletes those points, as
InDesign's Clear does.

Found by CodeRabbit on #1657.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 19:32:08 +02:00
Marco Arnulfo 85552ca0f3 Selection: a key object needs at least two selected objects 2026-10-23 19:03:26 +02:00
aiongg 4c29b60700 Print color: PDF/X-1a conformance level, intent profile and review fixes
- Write GTS_PDFXConformance (PDF/X-1a:2003) in the document information and
  XMP; check_pdfx1a requires it.
- A registered printing condition (Coated FOGRA39) has no DestOutputProfile,
  so no ICC v4 profile goes into the PDF 1.4 file; an unregistered condition
  needs an ICC v2 profile, else the export fails.
- Images the flattener can't read fail the export, naming the page and its
  graphics.
- Shadow mask coverage (with - without) / (255 - without), so partly
  transparent objects drawn over their shadow give the rendered pixel back.
- The dropped-inks warning says their tints were dropped and won't print.
- Type 3 fonts pass; their glyph procedures are checked like page content.
2026-10-23 16:52:05 +00:00
andeecollard 62a26d9e35 PDF: preserve catalogs and full references in layer variants 2026-10-23 17:47:18 +01:00
MarekSpodBiedry 708feab5cd find: search and change text inside table cells in document scope (#1624) 2026-10-23 17:37:45 +02:00
moppeeer db1e7b182c Merge branch 'direct-select-anchor-points' into properties-anchor-points
# Conflicts:
#	ROADMAP.md
2026-10-23 17:00:40 +02:00
Marco Arnulfo 8eab33eba1 Align: no key object once the selection is down to one object (#1637) 2026-10-23 16:54:21 +02:00
moppeeer f7197e1e0b Merge branch 'direct-select-anchor-points' into direct-select-handles 2026-10-23 16:51:43 +02:00
moppeeerandClaude Opus 5.5 9442f5d9c8 Selected points follow their path: Toggle Selection and Add Anchor at the start
- selection.toggle dropping an item from the selection now drops its selected
  points too. They stayed in selection.anchors, so the arrow keys still moved
  points of a frame that was no longer selected.
- path.appendAnchor at the start moves the sub-path's selected points up one
  place, as path.reverse remaps them, so they keep naming the same anchors.

Found by CodeRabbit on #1657.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 16:51:31 +02:00
moppeeer 3c2d4abeb0 Merge remote-tracking branch 'origin/main' into direct-select-handles
# Conflicts:
#	ROADMAP.md
2026-10-23 16:40:46 +02:00
Marco Arnulfo c764454ed3 Align: a click on one of several selected objects makes it the key object (fixes #1637) 2026-10-23 16:22:52 +02:00
Dylan M. Taylor bd7fb76d4c fix: annotate package output permissions (SonarCloud rust:S2612)
New packages are created with the ordinary 0666 request; the process umask
decides the effective mode. Annotated with that reason so the analyzer's
world-writable warning does not change the intended sharing behaviour.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-10-23 09:53:28 -04:00
moppeeerandClaude Opus 5.5 3fbaf8dfb9 Direct Selection: handles around the selected anchors only, and no bounding box
As InDesign: a selected anchor shows both its direction handles, and its
neighbours the handle that shapes the segment to it; with no anchor
selected no handle shows. Handles that don't show can't be grabbed (one
rule, select::handle_shown, for drawing and hit testing). The pen and the
anchor tools still show every handle. Under the anchor tools the selection
is drawn as its path and points, without the bounding box.

Found in the Learn Skills Daily course, chapter 43.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:28:48 +02:00
moppeeerandClaude Opus 5.5 a1d4b1bc27 Merge #902 (Direct Selection pulls a corner of an unselected object) into direct-select-anchor-points
Both change the Direct Selection tool: #902's corner pull now builds the
anchor drag with this branch's list of other selected anchors (none), so the
two build together; both sets of tests are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:13:33 +02:00
makesomethingshit 4f45a0127a #1620: Resize selected parent pages from page controls 2026-10-23 20:58:19 +09:00
moppeeer 5a07446487 Merge remote-tracking branch 'origin/main' into effect-blend-modes 2026-10-23 12:42:04 +02:00
moppeeer 6be1c70716 Merge remote-tracking branch 'origin/main' into insert-pages-dialog 2026-10-23 12:40:53 +02:00
Marco Arnulfo 0160b7af07 Convert to Text (all instances): a caret in a table cell or a footnote keeps its place after the converted value; gaps: 150 missing menu items 2026-10-23 10:56:22 +02:00
aiongg 0409bbf9aa Merge branch 'print-color' into print-color-x1a
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	xtask/src/pdf_deps.rs
2026-10-23 08:16:07 +00:00
aiongg 3dbbcf2d07 Merge branch 'print-color' into print-color-separations
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	crates/engine/src/cmd/export.rs
#	crates/ui-egui/src/lib.rs
2026-10-23 05:52:56 +00:00
aiongg a30b71cd36 Merge remote-tracking branch 'upstream/main' into print-color
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	xtask/src/pdf_deps.rs
2026-10-23 05:40:13 +00:00
Marco Arnulfo 3cd235ee5c Type › Text Variables › Convert Variable to Text: the selected instances, or every instance of one variable, become the text they show on their page 2026-10-23 03:40:11 +02:00
aiongg 3ee11391ae CI: one integration-test binary per crate 2026-10-23 01:17:48 +00:00
Marco Arnulfo 2caa39e130 Autoflow: fill the pages that follow before adding pages at the end
Autoflow (Shift-click, autoflow on Place, text.thread with autoflow) inserted
new pages right after the story's last frame, in front of the document's own
pages. As in InDesign, it now threads one frame per column on each page that
follows, whatever else the page holds, and only then adds pages at the end of
the document. It fills whole pages, so an estimate that stops inside a page
can't leave its later columns out of the thread. Frames it set past the end
of the text are removed again; the pages they were on stay. Smart Text Reflow
of the primary text frame keeps adding pages after the story's last page.

Shrinks gap 26: flowing into the primary text frame of the following pages
is left.
2026-10-22 23:28:22 +02:00
moppeeerandClaude Opus 5.5 b3896d8122 List buttons: track the hanging indent they set (listHang)
A paragraph attribute records that the Bulleted or Numbered List button set
the hanging indent; turning the list off takes the indent away only then
(and while it is still as applied), and clears the mark. A paragraph whose
own indents were 18/-18 before the list keeps them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 17:30:14 +02:00
moppeeer 79df355aad Merge remote-tracking branch 'origin/main' into global-units-preferences
# Conflicts:
#	ROADMAP.md
2026-10-22 16:43:16 +02:00
aiongg d2bdb0866f Paste: other apps' text goes into a text caret after copying objects (#1344)
* Paste: other apps' text goes into a text caret even after copying objects (#212)

* Paste: text that matches a copied frame still pastes; groups with text frames aren't anchored (#212)
2026-10-22 09:26:43 -04:00
aiongg 22a40fd57e Merge pull request #1144 from aiongg/land-402-404
Land the rebased #402 and #404
2026-10-22 09:26:38 -04:00
moppeeerandClaude Opus 5.5 99c47da919 List buttons: a selection ending at a paragraph start leaves it out; a style hanging indent stays
A selection ending right after a return (a whole paragraph, as a triple
click selects it) applies the list to that paragraph only, and the button
shows pressed for it; a whole frame still takes in its last, empty
paragraph. Turning a list off clears only a hanging indent set on the
paragraph itself: one the paragraph style gives stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:07:06 +02:00
Hamza 80cf719555 A type size outside 0.1 to 1296 pt is refused
Nothing checked type size. `type.char {"size": 0}` set type with no size at
all, a negative size went in the same way, and 1e9 pt asked the composer for a
line a million times the height of the page — all of it silent, and all of it
saved to the document.

The range is the one this engine already applies to a ruby annotation's size,
0.1 to 1296 pt, which is InDesign's: "size 0: 0.1 to 1296 pt".

It sits in `validate_char_fill`'s place in the chain, so every command that
takes character attributes gets it: `type.char`, `find.change`, the four
`style.character` / `style.paragraph` create and edit commands, and the two
table paths that already validated a fill.

The test covers five refused sizes, the three in range including both ends, and
two of the style commands. Without the check, `{"size": 0}` applies again.

Not from a ticket: found probing `type.char`.
2026-10-22 17:12:15 +05:00
Hamza c212c89bab A text anchor's name is the document's, so it is not reused
`anchor.create` took any name, including one another anchor already had, so a
document could hold two anchors called "Photo 1". Every reference to the name
then means whichever the search finds first: `xref.insert {"anchor": "Photo 1"}`
and `hyperlink.create {"anchor": "Photo 1"}` take the first, and an export
writes both destinations under the one name. A name nobody can resolve is not a
name.

A name already in the document is a usage error now, naming it and where to
look: "a text anchor named `Photo 1` is already in this document (anchor.list
names them)". A name that is empty or only spaces is refused too — a
cross-reference built from `<txtAnchrName />` would show nothing.

Case still distinguishes names, as it does elsewhere in the document, and
nothing about existing anchors changes: the check is on creation.

The test covers the same name in the same story and in another one, the empty
names, that only one anchor exists afterwards, and that another name and a
name differing in case are both fine. Without the check the duplicate is
created again.

Not from a ticket: found probing `anchor.create`.
2026-10-22 17:00:05 +05:00
Hamza 2946f2af62 A page count Insert Pages cannot mean is refused
Found probing `layout.pages.insert` on a two-page document:

    {"count": 0}      → one page inserted  (0 clamped up to 1)
    {"count": -1}     → one page           (`as_u64` fails, the default applies)
    {"count": 1.5}    → one page           (the same way)
    {"count": 10000}  → 9999 pages, quietly capped

A count that is given is a number of pages, so each of those is a usage error
now: "count 0: 1 to 9999 pages". A count that is absent still means one page.

`after` is left alone. My first attempt also refused a position past the last
page, and `insert_after_u64_max_appends_at_the_end` caught it: appending is
what an out-of-range `after` is meant to do, and the test now says so in the
same place.

The test covers the five counts, that none of them inserted anything, that a
position past the end still appends, and that `{"count": 2, "after": 0}` still
inserts two pages after the first. Without the check, `{"count": 0}` inserts a
page again.

Not from a ticket: I found it probing the command's parameters.
2026-10-22 16:41:11 +05:00
Hamza e7ef29d9d1 A page named twice in an export list is exported once (found while probing)
The same selection written two ways gave different files:

    file.exportPdf {"pages": "2,2"}   → 1 page
    file.exportPdf {"pages": [2, 2]}  → 2 pages, the second a copy
    file.exportPdf {"pages": [2,2,2]} → 3 pages

`parse_page_range` keeps the first of a repeat (`"2-2,2"` is one page too); the
array form appended whatever it was given. A list says which pages to export,
not how many times, so the array now drops a repeat as the string does. Both
`file.exportPdf` and `file.exportPptx` read the same parameter.

The test pins the agreement between the two forms over nine lists, in PDF and
PPTX, and the two facts I measured while checking it: the pages come out in the
document's order whichever order the list names them in, and `[2, 2]` is now
byte for byte the file `"2"` gives. A page that does not exist is still
refused, and an empty list still exports nothing.

Not from a ticket: I found it probing the page-range parameter.
2026-10-22 16:18:33 +05:00
Hamza a473793c07 Rotated cell text is settable and reported, not silently flattened (#1468)
A cell's text rotation (Cell Options › Text › Rotation) was already read from
IDML and written back, but only 0 is composed, so an imported form's narrow
header columns of vertical labels came in horizontal and wrapped mid-word
("Eingela / ngt am") — and nothing anywhere said why. The reporter's file has
30 such cells.

Composing rotated cell text is a larger change than this. What this does is
stop it being silent, and let the value be set from the API:

- the IDML import reports it once per file: "2 table cell(s) have rotated text
  (Cell Options › Text › Rotation): the angle is kept and written back, but the
  text is composed horizontally";
- `table.setCell` takes `rotation` (0, 90, 180 or 270, any other angle refused,
  negatives and multiples normalised), which nothing could set before;
- `table.get` reports each cell's rotation, so a round trip can be checked.

Tests: the angle through an export and back with the warning counted once for
the file (and no warning when no cell is rotated), and the command's accepted
and refused angles. Each fails without its own part.
2026-10-22 15:59:33 +05:00
moppeeerandClaude Opus 5.5 6f8a0bf09e Default units: a preset keeps both its units; bad increments are refused
A document made from a preset keeps the preset's units on both rulers; the
default origin and increments still apply. Preferences with no document open
passes the increments it reads to `prefs.set` unclamped: a Cursor Key of 0,
or text that isn't a measure or number, is refused and the dialog stays open
with the reason; a Kerning/Tracking of 0.5 is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 12:43:28 +02:00
moppeeerandClaude Opus 5.5 ebb428619a Insert Pages bounds test: an out-of-range placement is refused
`after` or `before` past the last page is refused without overflowing and
changes nothing; inserting after the last page still appends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 12:34:43 +02:00
moppeeerandClaude Opus 5.5 d744b760fe Direct Selection points: each moves in its own spread; Delete deletes them; Transform measures them
`path.moveAnchors` points may carry their own spread-space movement
(`[id, subpath, index, dx, dy]`). Dragging and the arrow keys send each
selected point's movement converted for its spread, so on a spread turned on
screen a point moves the way the pointer or arrow does. Delete and Backspace
with points selected run `path.deletePoints`. Properties › Transform for
selected points shows the points' bounds; X/Y move them and W/H spread or
gather them about the reference point, in one step (`transform_anchors`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 12:20:44 +02:00
moppeeer 250825e5b7 Merge branch 'direct-select-anchor-points' into properties-anchor-points
# Conflicts:
#	ROADMAP.md
#	crates/tools/src/tests.rs
2026-10-22 12:11:30 +02:00
moppeeerandClaude Opus 5.5 c87e9d261d Insert Pages: before and after must name a page of the document
An index past the last page was clamped to the end; now it is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:47:29 +02:00
moppeeer 3497163827 Merge remote-tracking branch 'origin/main' into package-pdf-preset
# Conflicts:
#	ROADMAP.md
2026-10-22 11:38:41 +02:00
moppeeerandClaude Opus 5.5 3537343622 Preferences: Units & Increments with no document open set new documents' units
As in InDesign, Preferences with no document open has Units & Increments:
the horizontal and vertical ruler units, the ruler origin and the cursor-key
and kerning increments. They are kept with the application preferences
(`prefs.set {defaultUnits}`) and new documents start with them; New Document
opens in those units, and its [Default] card uses them. A document keeps its
own Units & Increments once made, and Preferences with a document open still
edits that document's.

`file.new` without a preset or `units` uses the default units; units picked
in New Document set both rulers unless they are the default horizontal ones,
which keep the default vertical units.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:38:06 +02:00
moppeeer 8fb00c5a26 Merge remote-tracking branch 'origin/main' into spelling-common-words
# Conflicts:
#	ROADMAP.md
2026-10-22 11:35:43 +02:00
moppeeer dbfab9857f Merge remote-tracking branch 'origin/main' into insert-pages-dialog
# Conflicts:
#	ROADMAP.md
2026-10-22 11:33:45 +02:00
moppeeer e33398a3c4 Merge remote-tracking branch 'origin/main' into find-change-format
# Conflicts:
#	ROADMAP.md
2026-10-22 11:27:38 +02:00
moppeeerandClaude Opus 5.5 a1bbf1c7a1 Properties › Text Wrap: an offset or Invert edit keeps each object's wrap mode
`object.textWrap` takes `mode: null` for each object's own mode. Properties
sends it with an offset or Invert edit, so objects with another wrap mode
than the first keep theirs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:26:09 +02:00