* 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
footnote.delete and endnote.delete removed the reference character from
the story but left a caret or selection in the story text at its old
byte offsets. Everything after the reference moved back by the
reference's length, so the caret ended up three bytes further along the
text. A caret or selection after the reference now moves back with the
text, and one before it stays where it is.
footnote.setText left a caret in the replaced footnote at its old
offset, which could fall past the end of the new text or inside a
character. Backspace there then failed with an internal error. The
caret now goes to the end of the new text.
Go to Footnote Reference, Insert Footnote and Delete Footnote (when the
caret is in the deleted footnote) moved the caret without clearing the
pending typing format. Formatting chosen at the old insertion point and
not yet typed then came back when the caret returned there, for example
in a new footnote that takes a deleted footnote's id. The three
commands now clear it, as text.select and text.move do.
Found while testing crates/compose/src/notes.rs:133 (also crates/doc/src/notes.rs:265 footnote_start and crates/doc/src/endnotes.rs:150 endnote_start)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Conflict: ROADMAP.md: both progress-log lines
Conflict: crates/engine/src/cmd/table.rs: next's setCell (StrokeEdit/EdgeSel, edit_table with ID) with the PR's text_param for the cell text
frame.create and text.insert turned \r and \r\n into paragraph returns,
but story.replaceRange, story.setText, note.new, footnote and endnote
text, type on a path and table cells kept the CR in the text, where it
drew as a missing glyph and joined the paragraphs. They all use
text_param now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kanji numbering style (一, 二, 三, …, written digit by digit: 10 is 一〇)
for sections, footnotes, endnotes and lists. IDML PageNumberStyle and
FootnoteNumberingStyle read and write Kanji and the leading-zero styles;
the list format "一, 二, 三, 四..." maps to it.
Conflicts: fonts fontdb.rs (the branch's em box with its from-BASE flag beside next's ICF cache); idml
tests.rs (the branch's copies of tests already on next, and the mojikumi test #129 replaced, dropped;
the Em Box Height test kept). German entry for Em Box Height.
The first baseline sits the em box top below the frame's top inset: the
font's BASE em box (idtp, else ideo + 1 em), else 0.88 em, for the
largest size on the first line. IDML FirstBaselineOffset="EmboxHeight"
imports and exports as itself; the footnote options list it and the
frame and footnote option commands take emboxHeight.
Cancelling a drag, a preview or an IME composition restores the
document, its selection and now its revision. Revisions come from one
counter, so the numbers a cancelled preview used are never handed out
again and caches keyed by (uid, revision) never show a cancelled
preview.
- doc: FOOTNOTE_REF marker + Story::notes (kept in sync on insert/delete/thread), FootnoteOptions,
NumberStyle::Symbols, footnote text addressed like cell text (CellAddr::footnote)
- compose: notes numbered (continuous / restart per page, spread, section), composed at column
width, body text makes room, stacked at the column bottom with the rule above
- render + PDF draw footnote text; IDML export/import in InDesign's structure (verified in
InDesign 2026), FootnoteOption preferences
- engine: footnote.insert/options/list/setText/delete/goToReference (+ tests)
- UI: caret/selection in footnote text, Footnote Options dialog, Type menu entries; menu items
labelled '…' with parameters open a dialog generated from the command's params doc
- sample magazine carries a footnote
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>