Commit Graph
1642 Commits
Author SHA1 Message Date
kuseidos bb54c44ad9 Command API: refuse a page index past the end, and say how many pages went (#2460)
* Command API: refuse a page index past the end, and say how many pages went

layout.pages.delete answered ok for an index past the last page and deleted nothing for it, so a caller off by one removed a different range and read a success; on a 336-page book that is page 337 instead of 336. Both commands now check every index against the document and refuse it with the sentence the MCP export commands have used since #70, return how many pages they touched so a no-op and a success are told apart, and state in their params that the indices count from 0.

* Pages: count the pages a delete will remove, not the indices it was given

delete_pages refused [0, 0, 1, 2] on a four-page document as if it would leave none, because the guard measured the length of the list instead of the pages it names. Same family as the index check next to it: the count was of the wrong thing. A test in the engine pins it.

* Pages: refuse a malformed page list instead of dropping what it cannot read

Review finding: page_indices used filter_map, so a list holding a string, a fraction or nothing at all was silently shortened — the caller was told a page was handled when it never was, which is the same mistake the command now refuses one level down. Every entry must be a whole number, and pages must be a list. Tests cover all three shapes, and the new test functions carry docstrings so the coverage check has nothing to report.
2026-11-09 04:05:22 +00:00
aiongg 8e34117922 Apply review fixes
- crates/engine/src/cmd/file/new.rs
2026-11-08 14:29:02 +00:00
NetterSilverandClaude Haiku 5.5 4a7fc340f8 Clamp negative gutter in file.new and file.savePreset to 0
Found while testing crates/engine/src/cmd/file/new.rs:179 (apply_settings: `nd.gutter = f64_or(p, "gutter", nd.gutter)`); compare crates/engine/src/cmd/layout/margins.rs:101 (`g.max(0.0)`)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 15:10:20 +01:00
Kushagra Kumar f9cfd99adc Text: adjust hyperlink and placeholder source ranges on story edits (#1219) (#1229)
* Text: adjust hyperlink and placeholder source ranges on story edits (#1219)

Text-anchored hyperlinks and data-merge placeholders store byte ranges
(`start..end`) pointing into story text. When text earlier in the story is
inserted or deleted, these offsets remained unchanged, causing hyperlink
targets and data-merge placeholders to drift across unrelated text or
point to invalid UTF-8 boundaries.

This change:
- Implements `Document::adjust_text_ranges` in `designcraft-doc` to shift
  or resize text hyperlinks and placeholders when story text is edited.
  Pure insertions at or before the link shift it forward; insertions
  inside expand the link's span; deletions before shift it back; deletions
  consuming or overlapping the link clamp to surviving text or prune it if
  entirely consumed.
- Cleans up obsolete hyperlinks and placeholders in `set_story_text`.
- Wires range adjustment into engine text commands: typing, deleting,
  selection replacement, autocorrect, `story.replaceRange`, find/change,
  and spelling change.
- Adds comprehensive unit tests in `designcraft-doc` and 7 integration
  regression tests in `crates/engine/tests/hyperlink_offsets.rs`.

* fix(engine): adjust hyperlink and placeholder ranges for physically removed text under track changes

* fix(engine): adjust text ranges for formatted paste, case change, and drag move

* fix(engine): record change case adjustments per character to preserve enclosed hyperlinks

* fix(engine): adjust text ranges for track changes, inline markers, and preserve zero-length ranges

- Route Track Changes accept/reject deletions through adjust_text_ranges, and transfer replaced text hyperlinks to inserted text so links survive accept.
- Hook inline marker operations to adjust_text_ranges (notes, endnotes, xref, anchored objects, index markers, tables, editorial notes, unvar, and place text).
- Replace datamerge::shift_after with adjust_text_ranges.
- Only prune ranges that the edit emptied (s < e && s_new >= e_new), preserving zero-length ranges.
- Drop inverted ranges first and use saturating arithmetic in remap_range.
- Preserve doc.hyperlinks and placeholders ordering in move_text_ranges by mutating in place.
- Remove dead wrapper mark_deleted and call mark_deleted_reporting.
- Fix changes.track invocation parameter to 'on'.
- Add comprehensive regression tests in doc and engine covering all review items.

* fix(text): guard cell find/change, pre-record tracked replaces, shift para xref anchors

* fix(text): record stable IDs for tracked replacement redirects

* fix(text): reconcile range adjustments for review feedback

* fix(text): adjust ranges on delete surrounding and clean up dead auto edit

* Prune text ranges on TOC index and live caption regeneration

* test(engine): format regenerating_index_prunes_story_hyperlinks line wrap
2026-11-07 19:54:00 -06:00
FredApps 7e32b5ffd5 Merge pull request #2412 from ZIDAN44/fix/2401-white-overprint-preflight
Preflight: white or [Paper] set to overprint is reported (#2401)
2026-11-07 15:38:00 +01:00
aiongg e977f82558 Fix [Bug Report] [DesignCraft] Image content and frame don't move together (#2423)
Fixes #2422.

- crates/engine/src/cmd/object.rs
- crates/engine/tests/autocraft_regression_2422.rs
2026-11-06 18:21:30 -05:00
Zinadin Zidan cd315908aa Preflight: a frame with states is looked at as PDF export draws it (review)
Flex Layout's convert turns a multi-state group into a frame that keeps
its states. PDF export draws every object pasted into a frame, but the
white overprint check walked only the active state, so a white
overprint in another state printed unreported. Which children export
draws is now one function, drawn_children, used by both.
2026-11-07 03:07:28 +06:00
Zinadin Zidan 231ee6d4c3 Preflight: white overprint is looked for where PDF export prints (review)
Ruby and kenten in a threaded story were reported against the story's
first frame, on its page, wherever the text was; they are now reported
against the frame that shows them, and text no frame has room for isn't.
Parent pages' items and anchored objects print too and weren't looked
at; items on a hidden or nonprintable layer, or in a hidden or
nonprinting group, don't print and were reported. The check now walks
the items as PDF export draws them, with its rules (the parent page
items a page shows now come from one function both use), and a frame
PDF export leaves out reports none of its ruby and kenten.
2026-11-07 02:09:42 +06:00
Zinadin Zidan 085a120606 Preflight: white or [Paper] set to overprint is reported (#2401)
A fill, stroke or stroke gap set to overprint in [Paper] or in a process
colour that lays down no ink (C=0 M=0 Y=0 K=0, gray 0, RGB white, a 0%
tint) disappears on press. Preflight now reports it as whiteOverprint, and
ruby and kenten set to overprint in such a colour too. A spot ink (a white
ink or a varnish) prints whatever colour stands in for it, so it isn't
reported. Text has no overprint of its own, so white text knocks out and
prints, and isn't reported. The old Preflight note that PDF export drops
ruby and kenten overprint is gone: export writes it.
2026-11-07 00:27:10 +06:00
Hamza Naveed 4b63d26ac6 A hyperlink can point at a text anchor (#1446) (#1555)
* A hyperlink can point at a text anchor (#1446)

A frame or a text range could link to a URL, an email address or a page
number. A page number is the wrong thing to store for "this thumbnail links to
that photo": the photo moves and the link does not. InDesign stores the
destination, and IDML files do too — a `HyperlinkPageItemSource` pointing at a
`HyperlinkTextDestination` is how a report links its thumbnails to the
full-size pictures (#1446).

`HyperlinkDest::Anchor(id)` is a text anchor, the same destination a
cross-reference targets, and the PDF export resolves it the same way: through
the cross-reference index, to whichever page the anchor's text has landed on.
A link to an anchor that no longer exists is left out of the file rather than
pointing somewhere wrong.

`hyperlink.create` and `hyperlink.edit` take `anchor` — an anchor's id or its
name — and a link that is given no name of its own takes the anchor's.

Measured: a thumbnail on page 1 linked to an anchor in text on page 3 exports
`/Dest [<page 3> 0 R /XYZ 0 300 0]`; insert a page in front and the same link
exports page 4, with no edit to the link.

This is the groundwork for the IDML half of #1446 (import and export of
page-item and text hyperlinks), which is the next change and needs a
destination to put them in.

* An anchor a hyperlink cannot mean is refused, not ignored

This PR added anchors as link destinations and did not check that the anchor
exists, so the feature was wrong on its own and review was right not to take the
fix from a PR stacked above it.

`dest_param` returns `Result<Option<_>>` now. `None` still means no destination
was given, which `hyperlink.edit` needs for an edit that only renames; an
`anchor` that is given and names none is an error.

Two behaviours follow. An unknown *id* was accepted outright -- `as_u64`
answered for any number -- so a link was stored to an anchor nobody has and the
export quietly left it out. And on `hyperlink.edit` the `None` read as "no
destination given", so the command reported success and kept the old
destination, which is the case review reported.

An unknown *name* did already fail on `hyperlink.create`, but with "give `url`,
`email`, `page` or `anchor`", which named the wrong problem; it says "no text
anchor X (anchor.list names them)" now.

The test covers the id, the name, both on `edit`, that a refused edit leaves the
destination, the name and the exported page alone, and that a rename naming no
destination still works -- that last one is what `None` is for and it must not
have become an error on the way.

The page-number validation #1564 adds stays there; this is the anchor half,
which belongs with the anchor feature.

* A name two anchors share cannot pick one of them

`anchor.create` does not require an anchor's name to be unique, and an IDML
import can bring a document that already has two of the same. The lookup took
the first match, so a link made on a shared name went to whichever anchor came
first -- a different page, with nothing to say so (noted in review).

An id still names one anchor or none, and is checked as before. A name that two
anchors answer to is refused, with their ids in the message so the caller can
say which one they meant.

Not uniqueness at creation, which was the other way offered: a document can
already hold duplicates, so enforcing it there would refuse to open files that
are fine today, and the ambiguity only matters where a name is being resolved.

The test builds two anchors of one name on different pages, asserts both
`hyperlink.create` and `hyperlink.edit` refuse it and say what to do instead,
that the refused create made no link and the refused edit left the destination
alone, and that each anchor is still reachable by its id. It is a test of its
own because the twin anchor outlives the deletion the other test ends with.

* Test: two anchors sharing a name come from a file, now that anchor.create refuses a name in use

main's anchor.create now refuses a second anchor of a name, so the test makes the first and renames the second in the document, as a file opened with two can hold them. The comment on the guard says the same.
2026-11-05 22:24:10 +01:00
Hamza Naveed 59501a26f9 An item a parent overrode and deleted is gone from the pages that use it (#1469) (#1558)
* An item a parent overrode and deleted is gone from the pages that use it (#1469)

In InDesign, removing an inherited item from a parent page means overriding
it there and deleting the override: the parent's page keeps the item in its
override list with no copy of its own. Only the page being drawn was checked,
so the pages that use that parent still drew the base's item -- a signature
block from parent B over the notice text of every page using parent E.

The override lists of the whole parent chain are checked, not just the page's
own. An override always names an item of a parent further back, so one set
covers the chain.

Rebased onto the post-split main: the parent-item loop moved from render's
lib.rs to draw.rs and the tests to tests.rs.

One thing this does not touch: blendspace.rs checks `page.overridden` the same
way in two places, for deciding which spreads hold transparency. A deleted
override there would count an absent item as present, which can only cost an
unnecessary flatten rather than draw the wrong thing, so it is left for a
maintainer to judge rather than widened into this fix.

* Parent items: one set of overridden items for drawing, PDF and PPTX export, hit-testing, snapping and frame edges

* Override All: an item a parent removed is not brought back as a copy
2026-11-05 22:22:47 +01:00
AxelandClaude Opus 5.5 0e85e9e5ec Esperanto as a text language (#1142) (#1204)
Esperanto in Character › Language (panel, Properties, style options),
mapped to the BCP 47 tag eo, with English-style typographer's quotes.
IDML keeps the name as written ($ID/Esperanto). Not hyphenated yet:
morpheme-based hyphenation follows in #1201.


Claude-Session: https://claude.ai/code/session_01Y9j7XJDCmPc8UZLs9cTxtu

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:16:42 +01:00
aiongg 97e75f8fc4 Apply review fixes
- crates/engine/src/cmd/edit.rs
2026-11-05 11:42:55 +00:00
Hamza 3c8a3230ff Copy: the anchored-frame scan skips a copy that keeps no story, and reads each anchored item once
From review: a copied graphic keeps no story, so there is nothing to scan; and anchored_items already lists a group's members, so walking each item again visited them twice through unbounded recursion. A text frame inside an anchored group (as IDML import anchors an InDesign group) is tested.
2026-11-05 16:20:38 +05:00
Hamza 9083bd5adc Copy: a text frame anchored in a copied frame keeps its text
clip_doc kept the stories of the text frames among the copied items, and a text frame anchored in a frame's text (as IDML import makes them) is in its host's story, not among them. Its story was dropped from the clipboard, and the paste gave the anchored frame a new empty story. The stories of text frames anchored in the kept stories, and in theirs, are kept too.
2026-11-05 15:32:23 +05:00
4f3bb5340e Pages: parents and alternate layouts; export and print layouts and versions (#1342)
* Pages: more parents and alternate layouts as columns in the Pages panel

Parents
- layout.parents.duplicate copies a parent spread (pages and items) under the next free prefix.
- layout.parents.options also renames the prefix (1-4 characters, no "-") and the name; basedOn is now optional.
- layout.parents.new validates prefix and name and takes basedOn.
- Pages panel: a "+ New Parent" row adds B, C… in one click. The parents' context menu and the panel menu have New Parent…, Duplicate Parent Spread, Parent Options… and Delete Parent, named after the selected parent, or a submenu of the parents when none is selected. A new Parent Options dialog has Prefix, Name and Based on Parent.

Alternate layouts
- Section.layout marks the section that starts a layout; Document::layouts() lists them.
- layout.createAlternate copies one layout (from), not every page; new layout.alternate.duplicate, .delete and .options (rename, resize).
- Each layout starts a spread of its own; sections now follow their pages when pages are inserted or deleted.
- Pages panel: with more than one layout, each is a column side by side with a header (name, size, ▾ menu: create, duplicate, options, delete) and a + column to add one.

Tests cover the commands' happy and error paths, undo, serde, and the panel's menus and columns.

* Pages: address review of parents and alternate layouts

- Deleting a layout's first page keeps the layout's name when another section lands on the same page.
- A parent's full label (prefix-name) must be unique; parents may still share a prefix.
- Alternate layout names round-trip through IDML (Section AlternateLayout / AlternateLayoutLength).
- The Pages panel's selected parent is per document and no longer saved in ui.json.
- Layout ▸ Parent Options… and Alternate Layout Options… open the Pages panel's dialogs.
- The Pages panel computes the layouts once per frame.

* Export: layouts and versions in one export, with presets; print chosen layouts

- file.exportBatch writes the chosen alternate layouts (a file per layout, or all in one PDF) in several versions at once: PDF with any marks, bleed and standard, PNG or JPEG at a resolution. Each version goes in a folder of its own, and file names come from a template ({document}, {layout}, {version}, {date}). Names are made safe and unique. dryRun lists the files; without a dir the files come back as base64 (web).
- Export presets: file.exportPresets, file.saveExportPreset, file.deleteExportPreset, kept in prefs.json. Built-ins: Print + Screen, Press PDF per Layout, Images per Layout, All Layouts in One PDF.
- file.print takes layouts and separateJobs (a job per layout, titled after it).
- File ▸ Export Layouts and Versions… dialog: preset, layouts, versions (add from templates, duplicate, remove, quick PDF choices or custom marks), name template, and a live list of the files it will write. It opens from a layout column's ▾ menu as "Export Layout…". The desktop asks for a folder; the web downloads each file.
- The Print dialog prints chosen layouts, together or separately; the Export PDF dialog fills the page range from a layout.
- crate::io::create_dir_all keeps folder creation inside the automation write root.

* Export: address review of layouts and versions

- An exported image is capped at 80 million pixels, the limit EPUB page images already use.
- The Print dialog stays open and says why when no layout is chosen.
- Clicking a layout column's header leaves Edit Parents before going to the layout's first page.

* Pages, Export: address maintainer review of layouts and versions

- Sections (and the layouts they start) follow a moved page, as they do inserted and deleted
  ones: moving a page across a layout boundary no longer moves the other pages between layouts.
  Delete and move share `settle_sections`.
- Alternate Layout Options resizes only pages whose size changes: renaming a layout no longer
  forces every page to the first page's size.
- Export file and folder names that are Windows device names (CON, NUL, COM1…) get a leading `_`.
- An export without `dir` refuses past 512 MB of files instead of building them all in memory.
- A file that fails partway through an export to a folder says how many were already written.
- Parent Options and Print keep the dialog open, with what was typed and why, when the engine
  refuses.
- A duplicated layout's default name stays within the 100-character limit.
- rustfmt.

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

* Pages: Alternate Layout Options sends the size only when it changed

The dialog starts at the layout's first page's size, so the engine couldn't tell a rename from a
resize to that size. The dialog now leaves out a width or height still at its starting value, and
`layout.alternate.options` resizes every page of the layout when a size is given, as before.
The test moves to the UI, where the size was coming from.

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

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 06:02:27 +01:00
Hamza Naveed b48a9507b1 Swatches: renaming a swatch renames it on the images it colours (#2306) (#2311)
rename_swatch_refs visited every page item's fill and stroke but not the swatch applied to a placed greyscale or bitmap image (Graphic::fill), framed or in a table cell. Those images kept the old name, found no swatch, and drew in black.
2026-11-04 10:39:14 -05:00
aiongg b1f67ee4e4 Merge pull request #2211 2026-11-04 04:14:12 +00:00
Dylan M. Taylor 64c956f09e fix(render): index line style groups, stop futile glyph-cache eviction, read PDF page sizes once (#2217) 2026-11-03 22:23:11 -05:00
aiongg b18ed2e7ad Merge pull request #1294 from KnottyDyes/squad/place-never-into-line-v2
Place: implicit image placement preserves lines and open paths
2026-11-03 21:17:15 -05:00
KnottyDyesandClaude Opus 5.5 b032aa26e8 PDF: test that link stand-ins add only the link to tagged PDF
The nonprinting stand-in for a rasterized linked frame keeps the frame's
story, alt text and effects for link bounds and text composition. A test
now checks that a tagged PDF of a linked frame differs from the same frame
without the link only by the link annotation: no extra structure elements
or alt text. Note why the stand-in keeps the frame's ItemId.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 18:20:58 -07:00
KnottyDyesandClaude Opus 5.5 5cd8d99db7 Package: report PDF warnings and keep hyperlinks on rasterized frames
Include the PDF export warnings in the package result and report. Cover
shadow and feather output in packaged PDFs with a synthetic regression test.

Preserve frame and text-range hyperlinks on rasterized frames, including
inside groups, at their original source bounds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 18:20:58 -07:00
KnottyDyesandClaude Opus 5.5 5bd8ada171 Place: lines don't become frames when content is placed
Placing an image or media with no placement parameters used the selected
item as the frame, and so did a click of the loaded place cursor. On a
line that made a zero-height frame that kept its stroke. All three now
skip items that enclose no area (PathData::encloses_area): lines and
paths whose points all lie on one line get a new frame instead. Frames,
and open paths that enclose an area, take the content as before.

An explicit frame, x, y, spread or width behave as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:04 -07:00
Dylan M. Taylor 4071bd40e6 address review: skip a graphic whose PDF crop is too large to trace 2026-11-04 00:39:05 +00:00
Tex Jernigan e1910529a7 Frames with a nested image: hit inside the frame, take a placed graphic; replacing keeps the crop; open on a usable layer (#2226)
* Frames holding pasted-in images are hit inside the frame, take a placed graphic; documents open on a usable layer

A frame with items pasted into it (how a document from InDesign keeps a cropped photo) was hit
through its children like a group, so the whole uncropped image counted: a click on the empty
page beside such a photo selected it, and nothing could be deselected there. It is now hit by
its own area, as it is drawn clipped. Such a frame holding only graphics also takes a placed or
dropped graphic (Item::takes_graphic), which fills it as an empty or graphic frame is filled.

A document opened with new objects going to its top layer even when that layer was hidden and
locked (a dieline), so a placed graphic showed as an empty frame outline. It now opens on the
topmost layer that is shown and unlocked (Document::working_layer).

* Place: a graphic replacing the one a frame shows keeps the frame's crop and zoom

Placing or dropping into a frame that shows a graphic (in it, or as the one graphic pasted into
it) filled the frame afresh, so the crop and zoom chosen for the old picture were lost. The new
graphic now covers the old one's area, centred on it: an edited copy of the same picture lands
exactly where the old one was.

* Move the replace-keeps-crop and open-on-a-usable-layer changes to their own PRs

Review asked for them to be checked against reference app behaviour on their own. This PR keeps
the frame hit area and Item::takes_graphic. Reverts 8ea0bc16 and drops Document::working_layer.

* Move Document::working_layer and its test to #2267
2026-11-03 17:21:35 -05:00
Tex Jernigan ae5be94865 Layers: a document opens on its topmost shown, unlocked layer (#2267)
A document opened with new objects going to its top layer even when that layer was hidden and
locked (a dieline), so a placed graphic showed as an empty frame outline. Document::working_layer
picks the topmost layer that is shown and unlocked (the top layer when there is none); DocState::new
and the fallback after the active layer is deleted use it.
2026-11-03 17:15:53 -05:00
aiongg 0867f82961 Merge pull request #1666 from MarekSpodBiedry/issue-1646
Fill the empty or graphic frame under the pointer when a file is dropped on it (#1646)
2026-11-03 17:00:02 -05:00
aiongg c1bf2c769b Merge pull request #1968 from VoiceLessQ/fix/1522-anchored-copy-ids
Give anchored objects fresh ids when copying text frames (fixes #1522)
2026-11-03 16:48:53 -05:00
aiongg d400167537 Apply review fixes
- crates/engine/src/cmd/file/place.rs
2026-11-03 21:46:56 +00:00
aiongg 6c4723e1e8 Merge pull request #184 from storybored379/feat/mcp-conventions
MCP: complete tool hints and check nested argument keys
2026-11-03 16:35:55 -05:00
aiongg cb5c7ca309 Merge main into feat/mcp-conventions 2026-11-03 21:23:14 +00:00
aiongg c10bba5f4d Merge pull request #1900 2026-11-03 21:15:06 +00:00
aiongg 1967903244 Merge pull request #2102 2026-11-03 20:24:26 +00:00
aiongg 1da269b7f5 Merge main into feat/mcp-conventions 2026-11-03 19:40:25 +00:00
aiongg 6914f12209 Apply review fixes
- crates/engine/src/script.rs
2026-11-03 19:08:01 +00:00
aiongg 78139b693e Merge pull request #2193 2026-11-03 19:07:43 +00:00
aiongg 671114f5fc Merge pull request #2024 2026-11-03 19:07:43 +00:00
aiongg 474ecd61d4 Merge main into fix/1522-anchored-copy-ids 2026-11-03 19:01:23 +00:00
aiongg acf478e6e7 Apply review fixes
- crates/engine/src/cmd/file/open.rs
2026-11-03 18:35:07 +00:00
aiongg 1e3c5fb0ef Apply review fixes
- crates/engine/src/cmd/file/open.rs
2026-11-03 17:56:11 +00:00
Dylan M. Taylor edbea6036f fix: bound recent-file preview decoding and PDF crop allocation 2026-11-03 12:28:30 +00:00
JonRC 7bc3a26e4a type.char: warn about the font the text resolves to (follow-up to #2190)
The warning from #2190 looked only at the attributes passed. A composite
font made by style.compositeFont.set isn't an installed family, so
applying one warned "Missing font: Mixed" even when its entries were
installed. And a fontStyle-only call, or a family change that kept a
style the new family lacks, said nothing.

After a call that sets or clears fontFamily or fontStyle, type.char now
tallies the fonts the formatted text resolves to, with the code
font.list uses (composite entries per character), and warns about the
missing ones and substitute styles. At a caret it checks the font the
next typing gets. tally_story becomes tally_range over a byte range,
and describe_fonts is the part of used_fonts that type.char reuses.
2026-11-03 01:51:54 -03:00
Brandon Thomas 312eefac9b Merge pull request #1493 from Hamza0101/fix/overset-export-warnings
An export says what text its pages had no room for (#1465)
2026-11-02 23:24:29 -05:00
Brandon Thomas 1e2d75d0b0 Merge pull request #2125 from Tom-R-Main/openbytes-title
File import: keep native and IDML bytes out of the INDD converter
2026-11-02 23:24:12 -05:00
Brandon Thomas a4a4cd6ee9 Merge pull request #2141 from Silvertree2010/fix/h5392-data-merge-record-range-accepts
Reject signed record numbers in data merge record ranges
2026-11-02 23:24:08 -05:00
Brandon Thomas 3eb8091bac Merge pull request #2142 from TrevorBrown-dev/fix/variables-numeric-text
M3: a Custom text variable defined with a number shows its digits (#2093)
2026-11-02 23:23:59 -05:00
Brandon Thomas af9706176a Merge pull request #2165 from 4waymedia/fix/links-status-by-date
Links: an unchanged linked image is not read again on every poll (#2164)
2026-11-02 23:23:46 -05:00
Brandon ThomasandClaude Opus 5.5 4c2296491e Links: key the image check cache on the resolved file, not the link text
A relative link names another file once the document is saved elsewhere,
so a status remembered for the old file must not answer for the new one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:09:34 -05:00
JonRC 6abeb8a1ca type.char: warn when the font family or style isn't available (#2189)
A fontFamily the app doesn't have was applied with a null result, so a
script that asked for Courier Prime exported the PDF in the substitute
and exited 0. The font is still applied, since documents can carry
missing fonts and Find Font and preflight list them, but type.char now
returns {"warnings": ["Missing font: Courier Prime (using Source Sans 3)"]},
or "Missing font style: ..." for a style the family doesn't have, in
preflight's words. Other calls still return null.
2026-11-03 01:00:19 -03:00
Paul Gemignani 7d0f141b52 Links: an unchanged linked image is not read again on every poll (#2164)
links.list decided a placed graphic's status by reading the whole file and comparing it with the asset's bytes whenever the sizes matched, and the Links panel polls it twice a second, so a document of press-size images read every one of them in full on every poll. Image links now have the size-and-time fast path linked text files already had: a file of the remembered size and time isn't read, a new time is read once and remembered (unless the file changed in the last two seconds), a different size is modified without a read. The answer is keyed by the asset's bytes' identity, so a relink, update or undo can never be served a stale one; status() stays as the exact check for callers that want it.

Verified locally on a288a87b: fmt, clippy, engine link tests including the new unchanged_linked_images_are_not_read_again.
2026-11-02 16:40:46 -08:00