Commit Graph
418 Commits
Author SHA1 Message Date
aionggandHamza a912438ab8 Layout: autoflow, inserted pages and new parents follow dragged columns, mirrored across sides (#1096) (#1173)
* IDML: keep column guides that were dragged off centre (#1096)

InDesign records dragged column guides in `MarginPreference ColumnsPositions`,
as pairs of offsets from the margin box's left edge. The importer discarded them
and divided the columns evenly, so a page whose gutter the designer had moved
came back with it in the middle: on a 450 pt margin box, `0 150 170 450` was
read as columns at 215 and 235.

`Columns::positions` already existed, documented as "Custom column positions
(after dragging column guides)", but nothing set it and nothing read it. Now
`Page::column_rects` returns the custom rects when they are set — that one
method is what the canvas guides, snapping, threading and the first frame of a
placed story all go through, so they follow the dragged gutter — the importer
fills it, and the exporter writes the page's real positions instead of
recomputing an even split, so the round trip keeps them.

`layout.marginsAndColumns` clears them when a count or gutter is set, which is
what InDesign's Margins and Columns does. Without that the stale positions would
outrank the new value and the dialog would look broken.

Verified against InDesign-authored IDML rather than assumed, which settled two
things. The attribute is written whether or not anything was dragged — a real
page's values are exactly its even split — so an even set is dropped rather than
marking every imported page custom and freezing its columns; the tolerance for
that comparison only absorbs float noise, about 1e-14 pt on that page. And
`<Guide>` is a `<Page>` child, not a `<Spread>` one, which is where this
importer already looks: the reporter's missing ruler guide was 099fbd2, landed
the day the ticket was filed, not a second bug.

That commit left `lib.rs` still listing guides as ignored on import; corrected.

* IDML: custom column positions are validated and round-trip (#1096)

Columns::positions are used only when they are 2 x count finite edges,
ascending from 0 to the margin box width (within 0.01 pt); anything else
is the even split, in Page::column_rects, on IDML import (bounded parse,
checked against the page's margin box) and on IDML export.
Columns::with_gutter_moved moves one gutter's pair of guides between its
neighbours for the coming column-guide command.

ROADMAP: drop four progress-log lines the #1121 merge duplicated and move
its line to the top.

* Layout: autoflow, inserted pages and new parents follow dragged columns (#1096)

* IDML: a page without its own margins checks the default's column positions against its width (#1096)

* Layout: dragged columns mirror when a page changes side (#1096)

* Layout: pages that follow a one-page parent take its dragged columns mirrored (#1096)

* ROADMAP: the progress line lands in one commit after the guide PRs

---------

Co-authored-by: Hamza <mail.hamzanaveed@gmail.com>
2026-10-22 02:26:38 -04:00
aiongg d7b9f716f1 View: Lock Guides and Lock Column Guides are commands (#1096) (#1174)
* View: Lock Guides and Lock Column Guides are commands (#1096)

view.lockGuides {on?} (Cmd+Alt+;) sets the Lock Guides UI state the Properties panel and ruler-guide drags use. view.lockColumnGuides {on?} is a document setting, on by default, saved with the document, not an undo step, and read from and written to IDML ColumnGuideLocked. Both are in View > Grids & Guides with checkmarks.

* View: Lock Column Guides is translated wherever Lock Guides is (#1096)

* View: Lock Column Guides is undoable (#1096)

* ROADMAP: the progress line lands in one commit after the guide PRs
2026-10-22 02:12:42 -04:00
aionggandHamza 6f7fc7134a IDML: dragged column guides import, validated, and round-trip (#1096) (#1171)
* IDML: keep column guides that were dragged off centre (#1096)

InDesign records dragged column guides in `MarginPreference ColumnsPositions`,
as pairs of offsets from the margin box's left edge. The importer discarded them
and divided the columns evenly, so a page whose gutter the designer had moved
came back with it in the middle: on a 450 pt margin box, `0 150 170 450` was
read as columns at 215 and 235.

`Columns::positions` already existed, documented as "Custom column positions
(after dragging column guides)", but nothing set it and nothing read it. Now
`Page::column_rects` returns the custom rects when they are set — that one
method is what the canvas guides, snapping, threading and the first frame of a
placed story all go through, so they follow the dragged gutter — the importer
fills it, and the exporter writes the page's real positions instead of
recomputing an even split, so the round trip keeps them.

`layout.marginsAndColumns` clears them when a count or gutter is set, which is
what InDesign's Margins and Columns does. Without that the stale positions would
outrank the new value and the dialog would look broken.

Verified against InDesign-authored IDML rather than assumed, which settled two
things. The attribute is written whether or not anything was dragged — a real
page's values are exactly its even split — so an even set is dropped rather than
marking every imported page custom and freezing its columns; the tolerance for
that comparison only absorbs float noise, about 1e-14 pt on that page. And
`<Guide>` is a `<Page>` child, not a `<Spread>` one, which is where this
importer already looks: the reporter's missing ruler guide was 099fbd2, landed
the day the ticket was filed, not a second bug.

That commit left `lib.rs` still listing guides as ignored on import; corrected.

* IDML: custom column positions are validated and round-trip (#1096)

Columns::positions are used only when they are 2 x count finite edges,
ascending from 0 to the margin box width (within 0.01 pt); anything else
is the even split, in Page::column_rects, on IDML import (bounded parse,
checked against the page's margin box) and on IDML export.
Columns::with_gutter_moved moves one gutter's pair of guides between its
neighbours for the coming column-guide command.

ROADMAP: drop four progress-log lines the #1121 merge duplicated and move
its line to the top.

* IDML: a page without its own margins checks the default's column positions against its width (#1096)

* ROADMAP: the progress line lands in one commit after the guide PRs

---------

Co-authored-by: Hamza <mail.hamzanaveed@gmail.com>
2026-10-22 01:59:10 -04:00
aionggandSBaksa 3be2736d0a Guides: select ruler guides and set their position (#1282) (#1445)
* Guides: a dragged guide shows where it is (#1282)

While a guide is dragged out of a ruler or moved, a label beside the
pointer reads X: or Y: and the guide's place, in the ruler units and
measured the way the rulers measure (from the zero point, and from the
page origin when rulers restart per page).

* Guides: keep the drag readout inside the canvas

Near the canvas's right or bottom edge the readout flips to the other
side of the pointer, and it is kept inside the canvas in every case
(readout_at).

* Guides: the drag readout measures the spread under the pointer

The readout measured from the spread in the middle of the view (where the
rulers measure), but a guide is dropped on the spread under the pointer,
so with spreads above one another the number shown could differ from the
guide's place. It now measures that spread (ruler_pieces_of).

* Guides: no readout for an existing guide over another spread

An existing guide moves only on its own spread (guide_drag), so dropped
over another it stays where it was: the readout no longer shows a place
there it won't go.

* #1282: guide selection commands (guide.select, guide.selectAll, guide.deselect, guide.moveBy)

* #1282: select ruler guides on the canvas, X/Y in the Control bar and Properties

* #1282: guides take clicks over objects, a new guide is selected, group drag preview

* ROADMAP: the progress line lands in one commit after the guide PRs

---------

Co-authored-by: SBaksa <sabaksa11@gmail.com>
2026-10-22 01:47:49 -04:00
Zuujis 438d7b6d79 Dedupe the 400-year-cycle UTC conversion and the EPUB package writer (#1537)
* Dedupe the 400-year-cycle UTC conversion and the EPUB package writer

SonarCloud flagged new-code duplication on main (17%): five copies of the
"move the instant into chrono's range by whole 400-year cycles" conversion
and two copies of the EPUB OPF/container/zip writer.

- designcraft-doc gains a `time` module: `date_parts(days)` and
  `utc_parts(secs, nanos)`. App logging, EPUB `iso8601`, PDF `civil`, PPTX
  `civil` and text-variable `civil` are now thin callers; each keeps its own
  format string and year clamp. The app reaches it through
  `designcraft_engine::doc`, so its direct chrono dependency is dropped.
- EPUB: `package_opf` + `write_package` shared by fixed-layout and
  reflowable export (same entry order and compression).
- Boundary tests on the helper: pre-1970, year 9999 -> 10000, i64 extremes,
  nanos past a second.

Output is unchanged: existing date tests pass, and fixed/reflowable EPUBs
(with/without cover, author, pre-1970 and i64::MAX dates) are byte-identical
to origin/main.

* time: drop the unreachable i128 clamp and match arms in utc_parts

Whole 400-year cycles times 400 stay under 2.6e16 for any i64 day count,
so the year can't overflow i64 and the i128 + saturate was dead code
(carried over from the copies this replaced). The date and time lookups
can't fail either; each keeps one unwrap_or_default instead of a match
with a fake 1970 fallback. Output and tests unchanged.
2026-10-21 21:46:38 -04:00
Dylan M. Taylor 3b985e3813 Use chrono instead of hand-rolled UTC conversion (#1479)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the hand-rolled civil / civil_from_days / days_from_civil copies (the
146_097 / 719_468 magic numbers) across the logging timestamp, text-variable
dates, EPUB dcterms:modified, PDF metadata dates and PPTX core.xml dates with
chrono, added as a workspace dependency (default-features = false, std only, so
wasm is unaffected).

- apps/designcraft logging `timestamp` (private civil_from_days deleted).
- doc text variables: `civil` and `days_from_civil` use chrono::NaiveDate;
  `format_date` and `parse_iso_date` keep their own format/parse logic and
  still call these helpers.
- epub `iso8601`, pdf `civil`, pptx `civil`: UTC datetime via
  chrono::DateTime::from_timestamp.

Output is byte-identical and pinned by the existing date, ISO-date, EPUB,
image, EPS and logging tests. The OOXML/EPUB dcterms timestamps keep their
exact strings. Each converted helper keeps a proleptic-Gregorian fallback for
dates outside chrono's NaiveDate range (and for the serial/overflow dates
parse_iso_date can hand days_from_civil), so no input changes or panics.
AGENTS.md now says time goes through chrono.

Follow-up to storytold/cadcraft#1785

* Never panic on a clock outside chrono's range; keep 5-digit years unprefixed

logging.rs timestamp(): chrono's From<SystemTime> unwraps, so a clock past
year 262143 panicked inside the logger. Use the fallible DateTime::from_timestamp
and read such a clock, or a pre-epoch clock, as the epoch like the old saturating
code did. Years past 9999 are written with {:04} like before, because %Y adds a
'+'. Tests cover both cases.

epub iso8601(): write the year with {:04} instead of %Y, matching the old
{year:04} output, so years past 9999 are not prefixed with '+'. Test added.

AGENTS.md: the "Time goes through chrono" rule now notes the narrow fallback
for dates outside chrono::NaiveDate's range, which crates/doc/src/vars.rs
retains (Howard Hinnant's algorithm).

* Don't build an unrepresentable SystemTime in the far-future test

UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.

* Route out-of-range dates through chrono's 400-year cycle, not hand-rolled copies

doc/vars.rs kept hand-rolled Hinnant `civil_fallback`/`days_from_civil_fallback`
copies for dates outside chrono's ±262143-year range, and AGENTS.md carved a
"narrow fallback" exception for them, which left calendar arithmetic outside
chrono after all.

Replace both copies with the Gregorian 400-year cycle (146_097 days): chrono
converts the day/year within one cycle and whole cycles are added back via i128
(so i64::MIN/MAX can't overflow). `civil` is reached by the system clock through
`format_date(now(), ..)`, so it must stay exact; `days_from_civil` only ever
sees a 4-digit parsed year via `parse_iso_date`, but it uses the same cycle form
for consistency. Both reproduce the pre-chrono proleptic Gregorian result, so
outputs are unchanged (verified against the old formula over wide ranges and at
the i64 extremes).

New test pins dates past chrono's range on both sides of the epoch, through the
public `format_date` and round-tripping `days_from_civil`/`civil`, with values
from the old formula. Removed the AGENTS.md "narrow fallback" exception.

epub/pdf/pptx `civil`/`iso8601` are left as-is: the PR already dropped their
hand-rolled copies for pure chrono, and their remaining epoch (`unwrap_or_default`)
fallback only triggers for a metadata/clock timestamp past year 262143 -- the
live-clock/file-mtime case FredApps accepted, with outputs that clamp the year
anyway (pptx 0..9999, PDF DateTime). No hand-rolled copy remains there to replace.

Tested: cargo test -p designcraft-doc; fmt --check; clippy --all-targets -D warnings.

* Carry an overflowing day into the next month again in parse_iso_date

parse_iso_date accepts any day 1..=31, and the old days_from_civil
carried a day past the end of its month into the next one (2019-02-30 is
2019-03-02). from_ymd_opt rejects such a day, and unwrap_or(0) turned it
into 1970-01-01. chrono now places the 1st of the month and the day is
added on, which restores the old normalization. Tested through days_from_civil
and parse_iso_date.

* time: preserve old out-of-range date conversion (no epoch fallback)

`DateTime::from_timestamp(...).unwrap_or_default()` silently reset any
instant past chrono's year range to 1970-01-01, changing output the old
hand-rolled Howard-Hinnant code converted exactly (e.g. 10_000_000_000_000 s
was 318857-05-20 17:46:40, not the epoch). Restore the old conversion domain
with a cycle-based helper that routes the calendar through chrono (the
calendar repeats every 400 years = 146_097 days, so month/day/time come from
chrono and only the year is carried, keeping every i64 exact), then apply each
format's existing year clamp afterwards.

- apps/designcraft/src/logging.rs: `timestamp` via `utc_parts`; keep the
  pre-1970 epoch clamp; u64 secs past i64::MAX saturate, not epoch.
- crates/epub/src/lib.rs: `iso8601` via `utc_parts`.
- crates/pdf/src/export.rs: `civil` cycle-based; `date_time` keeps its
  0..=9999 year clamp.
- crates/pptx/src/package.rs: `civil` cycle-based, keeps its 0..=9999 year
  clamp on the year only (month/day/time keep the real instant).

Pinned old outputs in tests (through the public helpers):
  logging u64::MAX/2 s -> 292277026596-12-04T15:30:07.000Z (was epoch)
  epub 10_000_000_000_000 -> 318857-05-20T17:46:40Z
       -10_000_000_000_000 -> -314918-08-13T06:13:20Z
       i64::MAX -> 292277026596-12-04T15:30:07Z
       i64::MIN -> -292277022657-01-27T08:29:52Z
  pdf  civil(10_000_000_000_000) -> (318857,5,20,17,46,40)
  pptx civil(10_000_000_000_000) -> (9999,5,20,17,46,40) (year clamped)

* Don't build a year-318857 SystemTime unchecked in the logging test

Windows' SystemTime only reaches about year 30828, so
UNIX_EPOCH + Duration::from_secs(10_000_000_000_000) panicked before
timestamp() ran and failed the Windows test job. Build it with
checked_add and assert where the platform can hold it. The year-10000
case fits everywhere and stays unconditional.

* Name the 400-year range extension as the one exception to the time rule

The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.

* Say the library computes month and day; time-of-day is plain arithmetic

Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
2026-10-21 19:36:27 -04:00
Brandon Thomas f78bf721e8 Merge storytold/main into codex/delete-parent-pages 2026-10-19 02:52:27 -04:00
Brandon Thomas fcb83fd12d Merge storytold/main into codex/delete-parent-pages 2026-10-19 02:51:25 -04:00
Brandon Thomas 5e94f78c2e Merge storytold/main into screen-mode-bleed-slug 2026-10-19 02:51:20 -04:00
Brandon Thomas 992a171b78 Merge pull request #1117 from Silvertree2010/fix/h1992-table-moverow-table-dropcells-ca
Refuse table row moves across the header/footer boundary
2026-10-19 02:50:34 -04:00
Hamza b8ae284a4a View: Bleed and Slug screen modes show their area; draw the slug guide
Bleed and Slug were selectable from the toolbar, the View menu and
`view.screenMode`, and were stored, but the canvas only ever asked whether the
mode was Preview or Presentation. Both therefore drew exactly what Normal drew:
guides, frame edges, the other non-printing marks, and content reaching across
the pasteboard.

They are preview modes now, and what each spread shows reaches out to its box:
the bleed for Bleed, the bleed and the slug for Slug (Slug shows every printing
element, and a slug narrower than the bleed does not pull the edge back in).
Preview and Presentation still stop at the trim.

The slug guide was never drawn either, so the Slug colour in Preferences ›
Guides & Pasteboard was stored, exported and round-tripped while nothing read
it. It is drawn beside the bleed guide, in that colour.

`Spread::bleed_rect`'s body becomes `outset_rect`, which measures either offset
from the page edges, with `bleed_rect` and `slug_rect` as its named callers.
2026-10-18 20:47:01 +05:00
harrisonoconnorhover 9434e96dbf fix: integrate parent deletion with current main (#956) 2026-10-17 17:52:04 -04:00
Aykut FiratandClaude Opus 5.5 08ce444054 Tables: cell First Baseline offset (Cell Options › Text › First Baseline)
Cells and cell styles had no first-baseline setting, so every cell set its first line by the
Ascent offset (the font's typographic ascender). IDML documents that set FirstBaselineOffset on a
cell style (CapHeight is common, often through a table style's body region) composed every row
taller than the document asked for. With a font whose typographic ascender is a full em (Avenir),
each row grew about 2.6 pt and long tables overset, losing their last rows.

- doc: Cell.first_baseline / first_baseline_min; CellStyle.first_baseline / first_baseline_min,
  applied by CellStyle::apply_to and carried through resolve_cell_style's BasedOn chain.
- idml: FirstBaselineOffset and MinimumFirstBaselineOffset read and written on cells and cell styles.
- compose: a cell's text frame uses the cell's first baseline offset and minimum.
- engine: table.setCell, style.cell.create/edit take firstBaseline / firstBaselineMin; table.get
  reports them; style.cell.create fromSelection captures them.
- tests: import through BasedOn and a table style's body region, a cell's own override, IDML round
  trip; composer cap height vs ascent and a fixed minimum; command parameters and validation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:07:46 -04:00
harrisonoconnorhover 91a32730ef fix: allow deleting parent pages (#956) 2026-10-16 08:02:23 -04:00
NetterSilverandClaude Haiku 5.5 230b29be60 Refuse table row moves across the header/footer boundary
Found while testing crates/doc/src/table.rs:882 (Table::move_row, called from crates/engine/src/cmd/table.rs:1437 and :1369)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 05:54:41 +00:00
aiongg 21a6e6d90f Merge branch 'batch-tB' into next-integ
# Conflicts:
#	docs/gaps.md
2026-10-16 04:48:56 +00:00
aiongg 833db788af Merge branch 'batch-tA' into next-integ 2026-10-16 04:48:48 +00:00
aiongg 3021a8f893 Merge PR #1075: Thread the loaded text cursor into a text frame inside a group
crates/tools/src/tests.rs: the branch's tests, plus the PR's new loaded_text_cursor_threads_into_a_frame_inside_a_group.
2026-10-16 04:26:31 +00:00
aiongg d04d4de8fe Integration: coordinates must be finite (#1050)
file.place, place.drop, placed media and Fixed Width text frame columns refuse a rect that overflows to infinity or NaN. Saving refuses a document with non-finite item geometry before writing anything, so a file that won't load is never written.
2026-10-16 04:18:29 +00:00
aiongg 52cf755179 Merge PR #1070: Table of Contents: page number and separator character styles, Sort Entries in Alphabetical Order
crates/engine/src/tests_idml.rs: keep both new tests.
crates/ui-egui/src/i18n.rs: keep both sides' rows.
2026-10-16 04:10:56 +00:00
aiongg 42eaf774e9 Merge PR #1068: Table › Convert Rows › To Header, To Body and To Footer
crates/engine/src/cmd/table.rs: add the PR's three commands before the branch's select commands, which keep their target descriptions (#803).
docs/gaps.md: keep the branch's text; Convert Rows leaves the list of missing menu items.
2026-10-16 04:08:22 +00:00
aiongg ae544f8c05 Integration: cargo fmt 2026-10-16 04:08:00 +00:00
aiongg 6e4f122b8c Merge PR #1100: Placed greyscale and bitmap images take the swatch applied to them (IDML, screen, PDF)
Graphic literals (cli perf, doc assets/item, engine datamerge/export/file/links/mathexpr/media/table/sample, epub, format, pdf, pptx, render, tools, ui-egui properties, and tests): name the branch's clip and wrap (#561, #381) and the PR's fill.
crates/doc/src/item.rs: Graphic keeps wrap and gains fill.
crates/idml/src/import.rs: read the image swatch, then the branch's styled FrameFittingOption lookup; the graphic keeps its wrap.
crates/idml/src/tests/assets.rs: keep both new tests.
crates/render/src/lib.rs: keep #362's crop-box sizing of placed PDFs and colour the resulting pixmap.
crates/render/tests/colorize.rs, crates/epub/tests/grouped_reading_order.rs, crates/compose/src/tests.rs: literals name the branch's Asset and Graphic fields.
crates/pdf/src/export.rs: the soft mask interpolates by the standard's raster mode (the branch has no per-exporter interpolate switch).
docs/gaps.md, docs/target-app-parity.md: status lines list both sides' changes; gap text keeps the clipping-path items and adds the image swatch.
2026-10-16 04:07:57 +00:00
aiongg ca3929d0df Merge PR #1040: Reject threading that would create linked-story ancestor cycles 2026-10-16 03:59:56 +00:00
aiongg 57d083216a Merge PR #1081: Fill and stroke with nothing selected set the colours of new shapes, as InDesign's document defaults
Conflicts:
- crates/doc/src/lib.rs: DocSettings keeps the ruler origin and zero point fields and adds new_object; both defaults kept.
- crates/engine/src/cmd/style.rs: object.color keeps the batch's colour-model description, adds the nothing-selected sentence and the has_doc enable rule.
- crates/ui-egui/src/toolbar.rs: kept both tests (stroke gradient undo, new-shape colours from the proxy).
- ROADMAP.md: progress-log line moved under "Newest first." in the log's format.
2026-10-16 03:58:51 +00:00
aiongg 6f38f5a7b0 Merge PR #1061: Styles: captured text follows later style edits 2026-10-16 03:57:03 +00:00
aiongg ddac0751ff Integration: #1058 delete_pages keeps its doc comment, page_has_items has its own 2026-10-16 03:56:59 +00:00
aiongg f1406df005 Merge PR #1058: Pages: deleting pages that contain objects asks first, with Don't show again
Conflicts:
- crates/ui-egui/src/dialogs.rs: kept the branch's dialog code (wider New Document, Crop Amount, Cell Options) and added #1058's deletePages title, width, body, confirm arm and delete_pages() at the branch's indentation.
- crates/ui-egui/src/i18n.rs, i18n/{ar,de,it,ja,ka,pt_br,uk}.rs: kept both sides.
2026-10-16 03:56:53 +00:00
aiongg ec3593e4f9 Integration: #1034 the canvas draws the bleed from Spread::bleed_rect, which uses the outer pages; ROADMAP line matches the diff 2026-10-16 03:56:20 +00:00
aiongg 44e529ad6f Merge PR #1034: Objects snap to the bleed guide 2026-10-16 03:55:05 +00:00
aiongg 894bbe17e4 Integration: #1099 keep the text-threads status line, progress-log line under Newest first, A0 preset test 2026-10-16 03:54:26 +00:00
aiongg bb6b16fedc Merge PR #1099: Add A0, A1 and A2 poster page presets 2026-10-16 03:53:35 +00:00
aiongg 1061c44763 Integration: #325 New Document: mobile presets don't turn on the primary text frame
Primary Text Frame autoflow is still an open gap, and file.new {preset} goes through the same
builder. The TABS doc comment goes back onto TABS; the de and ja rows of the removed
"No saved presets yet" text go.
2026-10-16 03:48:21 +00:00
aiongg a59edb861a Merge PR #325 (new head c96d3e1fa): New Document window
Next keeps its tree (the PR's earlier commits, with #683's Preview on top) plus the PR's two new commits, 3d91e6460 (a tab selects its first preset; Saved starts with [Default]) and c96d3e1fa (the eight-unit Units list; mobile presets turn on the primary text frame).
crates/engine/src/cmd/file.rs, crates/ui-egui/src/dialogs.rs, crates/ui-egui/src/lib.rs: keep next's side (Preview, new_document::create).
crates/ui-egui/src/new_document.rs: next's side plus the two new commits.
crates/ui-egui/src/i18n.rs, i18n/it.rs: next's rows minus the removed "No saved presets yet" text.
docs/target-app-parity.md: keep next's New Document line (with Preview).
ROADMAP.md: the New Document line was already there; keep one.
2026-10-16 03:48:01 +00:00
aiongg b1421d53e4 Integration: slices give every endnote and editorial marker a payload
next's extract fills missing payloads with empty notes so the slice is a valid
story; main's test expected the slice to hold only the payloads the source had.
The pasted result is the same either way.
2026-10-16 03:28:14 +00:00
aiongg 18ae8e4761 Merge upstream main a184db6f8 with our 15 PRs (batch-0) into next
Conflicts resolved per file (both sides kept unless noted):
crates/engine/src/atomic.rs | 1 | main's: `write_file` removed (main routes command writes through `crate::io::write_file`); its one caller in file.rs now uses `crate::io::write_file`
crates/engine/src/cmd/datamerge/fill.rs | 1 | next's `Graphic` literal (has `clip` and `wrap`, which next's `Graphic` carries)
crates/engine/src/cmd/datamerge/mod.rs | 1 | main's `read_path_capped` (crate::io metadata/read_capped, roots checked); next's capped read is the same behaviour
crates/engine/src/cmd/datamerge/parse.rs | 1 | main's (`crate::io::metadata`, `crate::io::Metadata`)
crates/engine/src/cmd/edit.rs | 1 | next's `spread_param(p, "edit.paste")` (the second argument is the command id on both sides)
crates/engine/src/cmd/export.rs | 3 | main's: EPUB params doc, `toc_title` once (main's position; next's duplicate dropped)
crates/engine/src/cmd/file.rs | 7 | place doc: next's (lists page/rect/autoflow params #803 checks); next's recent-file functions + main's `foreign_document`; next's open (load_bytes, warnings, non-DesignCraft refusal) with main's checked `crate::io::read_file` for the bytes; main's `check_write` be
crates/engine/src/cmd/find.rs | 7 | main's Find scope (FindContext, scope/selection/story, continuation, UTF-8 repair) on next's `designcraft_compose::grep::Grep` (dialect GREP): find uses `find_all` capped at MAX_MATCHES, change uses `replacements`/`find_all` (literal change text not expanded), fin
crates/engine/src/cmd/fonts.rs | 1 | next's (`has_style`, resolved/replacement face); `has_style` already compares styles ignoring spacing and case, which is main's `same_style` fix; main's test kept
crates/engine/src/cmd/interchange.rs | 1 | next's `import_idml_report_with_previews` (previews for unreadable links)
crates/engine/src/cmd/layout.rs | 8 | pages.insert: main's overflow-safe `after` + next's checked `parent_ref`; main's duplicateSpread doc; layout.section: next's `SectionEdit` (already keeps unnamed settings, #717) with main's `d.repaginate()` moved into `SectionEdit::apply` (remove and set paths),
crates/engine/src/cmd/links.rs | 5 | next's links.list (no doc clone); main's `text_status` via crate::io; `use std::io::Read` dropped (unused after main's read_capped); test literals main's. `link_missing` (next) uses `crate::io::exists` for the file-guard scan
crates/engine/src/cmd/object.rs | 4 | object.fill/stroke tint-alone: main's (23e1680e7; out-of-range tint is an error, which main's test asserts); object.fit doc: next's (lists `axis`)
crates/engine/src/cmd/paths.rs | 1 | next's (shatai transform on outlines)
crates/engine/src/cmd/place_text.rs | 1 | next's `add_flow_frame` (used by autoflow)
crates/engine/src/cmd/preflight.rs | 2 | next's overset/unplacedStory issue (its `overset_location` already applies the parent transform main's #738 fix adds); next's two test modules kept with main's
crates/engine/src/cmd/scripts.rs | 1 | combined: next's `in_undo_step = false` (#406) + main's full rollback of a failed script (#709)
crates/engine/src/cmd/style.rs | 14 | param docs next's (gradient swatch.create, swatch stops, model colours); create_para next's; rename_style main's structure (rename entry, then `redirect_style_refs`), redirect has next's full reference set (cells, defaults, notes, TOC) + main's never-based-on-it
crates/engine/src/cmd/text.rs | 10 | main's selectSentence/Paragraph/Story + select_around; next's selectionAttrs (mixed) + main's openTypeFeatures/type.feature; main's comments; tracked typing: next's `reserve_command_text` + main's `insert_format_at`; empty-paragraph mark formatting main's (with `
crates/engine/src/cmd/thread.rs | 1 | next's `spread_param(p, "text.thread")`
crates/engine/src/dtd.rs | 1 | next's linear, depth-bounded `expand_parameter_entities` with main's budget (input + 1 MiB, `MAX_EXPANDED_BYTES`) and message ("expand the DTD beyond"); 16 MiB constant removed; both sides' tests pass on message
crates/engine/src/guard.rs | 2 | main's whole-transaction snapshot (docs, active, journal; #742) + next's `in_undo_step` restore; tests both
crates/engine/src/lib.rs | 5 | LoadedGraphic doc next's; next's `new_document_preview`; execute: main's `discard_stale_find_scope()` before next's nested-undo run; modules both (tests_autosize, tests_file_guard, tests_params, tests_roots)
crates/engine/src/recovery.rs | 1 | next's test (superset of main's assertion)
crates/engine/src/tests.rs | 4 | moved tests deduplicated (identical on both sides); next's preflight and direct-selection/kerning tests + main's style-delete, locked-frame and baseline-fit tests
crates/engine/src/tests_idml.rs | 3 | main's order (drop cap and margins tests identical on both sides, once each)
crates/ui-egui/src/canvas.rs | 3 | next's screen_options/DisplayKey block and tests kept; two tests that auto-merge had added twice (parent_guides_on_a_page_are_not_picked_there, baseline_grid_lines_are_the_ones_on_the_page) kept once; main's `typing`/`screen_at`/`click` helpers and its tests clicks
crates/ui-egui/src/control.rs | 3 | main's `count` check and pending budget (control queue + synthetic queue); exact-budget test runs both sides' cases (click 255 on 9,492 pending; drag 9492×255)
crates/ui-egui/src/cursors.rs | 1 | main's (same Pen cursor set)
crates/ui-egui/src/dialogs.rs | 14 | dialog body (newDocument … alert match) is next's text, with main's Find/Change scope reset (`set_field`, `reset_find_scope`, `find_change_params`) and main's Create Guides / Margins and Columns arms ported in; `Dialog::new` ruby section (next) + main's `set_fi
crates/ui-egui/src/i18n.rs | 7 | ka and pl modules, languages, statics and lookups both; `category_first` covers uk, ru, pl, ka and `count_label` keeps next's Czech plurals; shared TABLE union; next's duplicate rows for Auto, Number:, Gutter:, Top:, Bottom:, Inside:, Outside:, Left:, Right: dropped
crates/ui-egui/src/i18n/ar.rs | 2 | union of rows, no duplicate keys
crates/ui-egui/src/i18n/it.rs | 1 | union of rows, no duplicate keys
crates/ui-egui/src/i18n/ja.rs | 1 | union of rows, no duplicate keys
crates/ui-egui/src/i18n/pt_br.rs | 1 | union of rows, no duplicate keys
crates/ui-egui/src/i18n/uk.rs | 1 | union of rows, no duplicate keys
crates/ui-egui/src/icons.rs | 2 | both tests (main's typography icon, next's distribute icons)
crates/ui-egui/src/lib.rs | 11 | UiState: next's tabs_panel + main's last_color/last_gradient; `cursors` field once; next's SVG paste and paste-on-spread-in-view; next's extensionless document open; opens_as_document next's comment; tests both (next's control bar / key-name tests, main's rich black
crates/ui-egui/src/menus.rs | 15 | app.language doc next's text with pl and ka; UI_COMMANDS both (next's Tabs, Toggle Presentation, Apply None text; main's panel commands, Apply Color, Apply Gradient); Edit › Interface Language stays out (next moved it to Preferences); Type menu main's order with
crates/ui-egui/src/panels/glyphs.rs | 1 | next's (already has main's scroll-bar fix); the scroll-bar test module auto-merge had added twice kept once
crates/ui-egui/src/panels/layers.rs | 3 | next's drag-and-drop `object_rows`, with main's features ported into it: selection reveal (layer opens, row scrolls into view once), selected rows highlighted (also a group whose member is selected), double-click rename in place (`object.rename` queued like
crates/ui-egui/src/panels/mod.rs | 6 | tabs + typography modules and color_panel_tests; SelInfo has wrap_side/wrap_contour and wrap_offsets (offsets from the graphic's own wrap when it is selected); next's swatch_picker returning the chip response; next's content_selection_tests
crates/ui-egui/src/panels/pages.rs | 1 | next's (parents dragged by id)
crates/ui-egui/src/panels/properties.rs | 4 | next's swatch popup (Apply To, Tint); main's four wrap offsets then next's Wrap To / Contour Type; test fixtures main's (`..Default::default()`, no unused builder)
crates/ui-egui/src/panels/swatches.rs | 3 | main's Delete Swatch button + next's New Swatch comment; next's draggable rows; click: main's PICKED + fill/stroke in front, with next's no-apply on double-click
crates/ui-egui/src/toolbar.rs | 2 | main's Apply Color / Gradient / None wells and functions; next's apply_none doc line
crates/compose/src/lib.rs | 5 | next's line loop kept (mark_leading + last_line_mark, wrap segments/carry, grid gyoudori); main's mark-chars base_leading block (auto-merged) stays beside it. `fits`: next's #106 rule (baseline fit for horizontal frames, full descent for paths, vertical text, footnote
crates/compose/src/tests.rs | 5 | next's tests (tombstone set, sliver/one-sided/contour/clipping wrap tests, borders, lists, kenten, grid, anchored wrap, balance, shaped frames) plus main's an_empty_last_paragraph_isnt_overset; main's wrap_object_beside_one_narrow_column_leaves_the_other_alone re-ad
crates/doc/src/assets.rs | 1 | main's doc comment (same meaning)
crates/doc/src/build.rs | 3 | parent_page_for: next's parent_page_for_side (RTL-aware); single_sided_page_shows_first_parent_page test kept once, at main's position
crates/doc/src/item.rs | 1 | next's rotated_group_bounds_follow_the_children_outlines test kept
crates/doc/src/lib.rs | 1 | next's kerning_increment field kept; main's "Bulleted & Numbered" wording
crates/doc/src/page.rs | 1 | next's outer_page_indices + parent_page_for_side (integration of parent-margins with rtl-master-sides); main's parent_page_index dropped, its only caller (build.rs) uses parent_page_for_side
crates/doc/src/slice.rs | 5 | extract: next's `marked()` (get-based, fix_notes/fix_endnotes/fix_marks; main's version indexed slices and could panic); insert_story: main's take() limit on endnote/editorial payload copies with next's id comment, redundant `let ne`/`let nn` dropped; main's excess_note
crates/geom/src/corners.rs | 1 | next's box_outline_rounds_only_closed_corners test kept
crates/render/src/damage.rs | 1 | next's wrapping-anchored-object damage test kept
crates/render/src/fx.rs | 2 | feather: next's choke/corners sigma+join inside main's structure (body and clip at device resolution, with_filters(.., None)); inner glow centre: next's blend `mix` with main's clip argument (bp, xf)
crates/render/src/lib.rs | 3 | next's layer comment; next's front baseline grid draw after items; gradient match on g.kind (main's tuple scrutinee did not match the single-pattern arms)
crates/render/src/text.rs | 5 | paint_glyphs: main's paint_stroke closure structure with next's stroke overprint (overprinting helper, stroke `(TextPaint, bool)`); callers pass next's overprint_fill/overprint_stroke
crates/render/tests/placed_pdf.rs | 1 | main's `near` helper version
crates/tools/src/lib.rs | 1 | main's (PenConvert already declared below, avoids a duplicate variant)
crates/tools/src/pen.rs | 1 | main's module doc
crates/tools/src/select.rs | 1 | next's Drag::Corner variant (live corners) kept
crates/tools/src/tests.rs | 4 | next's tests (content drag, live corners, table border drag, eyedropper matchFrom, fitting handles, pen cursors once); eyedropper_ignores_inactive keeps next's samples_image check (main's exact-JSON compare lacks matchFrom) with main's `..Default::default()` Asset lit
crates/tools/src/text.rs | 2 | next's table border drag (BorderDrag, border_at) kept, then main's comment on locked text frames
crates/tools/src/xform.rs | 2 | main's explicit `Vec<&Item>` for shown children; next's eyedropper matchFrom (integration 796e9d713)
Cargo.toml | 2 | main's craft-pdf workspace dep added (craft-print stays once, in next's craft-libs block); [patch.crates-io] keeps next's winit (rust-windowing #4694 backport) and adds main's krilla patch; main's skylartaylor winit left out (see UNSURE 1)
Cargo.lock | 1 | union: next's craft-atomic and craft-cms entries plus main's craft-pdf entry; needs `cargo` normalisation
README.md | 1 | Open bullet combines INDD import via indd-utils (next) and editable PowerPoint export (main); main's paragraph break kept
contributors/people.toml | 1 | both people kept (switch87 from next, Nmarello from main)
apps/designcraft-cli/src/main.rs | 4 | next's `Export` enum dispatch kept; main's .pptx export added as `Export::Pptx` (run prints slides and warnings, script runs file.exportPptx); main's `--pdf-options` null-as-{} parse; usage lines list .pptx
apps/designcraft/src/main.rs | 3 | `startup.leave()` right after `app.ui` (main #380) and next's macOS `sync_movable` kept after the IME discard; next's Apple Events install kept; main's macOS Dock Quit `with_close_requested_on_terminate` block left out (needs the skylartaylor winit; UNSURE 1)
crates/fonts/src/fontdb.rs | 9 | FONT6 aliases (next) combined with #911 WWS names (main): ScannedFace and both CatalogEntry constructors carry `wws` and `aliases`; `canonical_family` falls back to WWS family names; `exact_style_traits` canonicalises then tries typographic, then WWS names (`named_st
crates/format/src/lib.rs | 1 | tests: both sides kept (next's r7a nullable-default tests; main's oversized ZIP entry/asset/entry-count tests)
crates/idml/src/import.rs | 11 | main's bounded `Package` reader under next's `import_idml_report_with_previews` (metadata read through `pkg.part`); next's `tab_list_repaired` kept; ruler origin: next's `settings.ruler_origin`, and main's guide import reads it (main's private `RulerOrigin` enum and
crates/idml/src/tests.rs | 6 | item-level union: all next tests plus main's new ones (ruler guides, package limits, CJK leading model, liquid page rules module, mojikumi); no test dropped
crates/mcp/src/backend.rs | 1 | test comment merged from both
crates/pdf/src/tests.rs | 1 | tests: both sides kept
crates/pdf/src/text.rs | 4 | next's (skew `tan`, cluster `start`, ruby overprint warning); main had no change here beyond base
crates/textimport/src/docx.rs | 5 | next's (ListCounter integration for list levels, page-break test position); main's side was the older form of the same code
crates/epub/src/lib.rs | 30 | non-test code is next's (StoryWriter holds img_src/images/no_alt/heading_ids; covers main's #787/#791/#782 fixes, plus #314 group transforms, anchored images in images_without_alt, endnote story skipped in the TOC); tests: next's set plus main's extra css_weight_style a
docs/control-protocol.md | 2 | next's rows (ui.paste, count 1–255, documentGrid) and wording
docs/file-format-parity.md | 1 | rows combined (next's ICC notes, main's TIFF indexed colour)
docs/gaps.md | 2 | status change notes unioned; Flex Layout removed from missing (main implemented it), next's Parent Options sentence kept
docs/localization-parity.md | 4 | Polish and Georgian rows; 5 languages beyond the twelve; file list includes cs and ka; both history rows
docs/roadmap.md | 1 | M1 next's (#186 fixed on next), M2 main's IME wording
docs/target-app-parity.md | 6 | next's status line; File formats row next's, UI/UX row main's (IME no longer failing); UI line next's plus main's Swatches detail; one Page tool line (main's fuller one); Load styles and main's fuller Pathfinder line both kept, main's duplicate short Pathfinder line r
docs/ui-parity.md | 1 | IME row main's, Font menu row next's

i18n/fr.rs: five rows dropped that main's shared table now has (Row, Open Path, Flex Layout, Current Page Number, Rulers).
2026-10-16 03:09:22 +00:00
aiongg 953b411342 Merge PR #501: Layout: parent pages pass margins and guides to their pages 2026-10-16 02:51:49 +00:00
aiongg df46ccf1c6 Merge PR #330: Compose: the CJK leading model and character alignment apply only under the Japanese composers 2026-10-16 02:51:48 +00:00
aiongg 2f2f9d6461 Merge PR #362: IDML: $ID/ font names, placed PDF page and crop, package IDML without embedded images 2026-10-16 02:51:48 +00:00
aiongg 845395a8ce Merge PR #993: Type: formatting set on a blank line applies to the text typed there 2026-10-16 02:51:47 +00:00
aiongg 7c6aedf22c Merge PR #992: Sections: consecutive right pages get separate spreads 2026-10-16 02:51:47 +00:00
Marco Arnulfo 2b9aae64a4 Placed greyscale and bitmap images take the swatch applied to them, as in InDesign (IDML, screen, PDF) 2026-10-16 03:07:52 +02:00
harrisonoconnorhover fbf3132ad0 fix: add poster page presets (#1091) 2026-10-15 20:26:13 -04:00
moppeeerandClaude Opus 5.5 528c7ea489 Fill and stroke with nothing selected set the colours of new shapes, as InDesign's defaults
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:32:22 +02:00
Hamza 39ef7799c0 Thread the loaded text cursor into a text frame inside a group (gaps 27)
A click with the loaded text cursor on a text frame inside a group showed the
not-allowed cursor and did nothing: `Document::hit_item` walks the spread's
top-level items, so the hit was the group, and the group is not a text frame.
`item_hit` already looks inside a group, but only to answer whether the group
was hit at all -- the frame in it was never named. InDesign threads into it.

`hit_descendant` names it: the frontmost descendant under a point that a
predicate accepts, through nested groups, front to back and skipping hidden
children. `ToolContext::hit_text_frame` asks for a text frame, and the thread
tool resolves its target through it, falling back to the plain hit so a click on
anything else means what it meant before. `hit` is untouched, so selection still
picks the group.

One fix covers the cursor and the click, since `click` resolves through
`target`. The test asserts the cursor, that the click threads to the frame and
not to the group, that `hit` still returns the group, and that a group holding
nothing threadable is still refused.
2026-10-16 00:27:53 +05:00
moppeeerandClaude Opus 5.5 c96d3e1fab New Document: InDesign's Units list; mobile presets start with a primary text frame
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:56:57 +02:00
Gert Pellin 801ab7d1f3 Table of Contents: page number and separator character styles, Sort Entries in Alphabetical Order (#1069)
Each included style gets pageNumberStyle, separatorStyle and sort.
toc.generate and toc.update apply the character styles to the page
number and the separator of every entry, and list the entries of a
sorted style alphabetically among their siblings, each with the
lower-level entries under it. Unknown character styles are errors.
Renaming or deleting a character style updates the definition. IDML
reads and writes PageNumberStyle, SeparatorStyle and Sort.
2026-10-15 20:32:20 +02:00
Marco Arnulfo f65eb0fd42 Table › Convert Rows › To Header, To Body and To Footer, as in InDesign 2026-10-15 20:20:57 +02:00
moppeeer 4f3a55a190 TUT3.1: let captured text inherit later style edits 2026-10-15 19:01:13 +02:00
moppeeerandClaude Opus 5.5 5ab0abaf6f Pages: deleting pages that contain objects asks first, with Don't show again
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 18:02:04 +02:00