Commit Graph
461 Commits
Author SHA1 Message Date
aiongg 1ff299adf2 Merge place-no-autofit update: the fitting test names its fitting 2026-10-11 19:18:07 +00:00
aiongg 54b607525e Place: the fitting test names its fitting, since a placed image no longer auto-fits 2026-10-11 19:18:07 +00:00
aiongg 13fb00fd08 Integration: transform.move on content reports the ids it moved (#521)
#521 makes transform.move return {moved, ids}; next's move_content (the content-selection path, content?) returned only {moved}. It returns the ids of the items whose content or frame moved, and copies_report_their_ids checks it.
2026-10-11 19:11:53 +00:00
aiongg 3af8c6275d Merge #521: Copies report their ids
Conflict: ROADMAP.md: both progress lines kept, the PR's first
Conflict: crates/engine/src/cmd/object.rs: transform.move params doc is next's (with content?) plus the PR's return text; next's test modules and the PR's copy_ids_tests both kept
2026-10-11 19:11:43 +00:00
aiongg fc05e814df Merge #520: Object fills and strokes must name a swatch
Conflict: ROADMAP.md: both progress lines kept, the PR's first
Conflict: crates/engine/src/cmd/object.rs: next's reorder/group test modules and the PR's swatch_name_tests both kept
2026-10-11 19:10:39 +00:00
aiongg d8f1e69882 Merge place-no-autofit: an image placed into a frame leaves Auto-Fit off
Conflict: crates/engine/src/cmd/file.rs: next's natural-size (bw, bh) fill with the branch's comment; both place-into-frame paths set auto_fit to the default
2026-10-11 19:10:20 +00:00
aiongg 53d57e7e17 Merge #84 update: paste text with no insertion point makes a text frame
Conflict: crates/engine/src/cmd/edit.rs: edit.paste params doc joins next's `spread?: index (0)` with the branch's text-frame and `at?` text; enablement is the branch's has_doc
Conflict: crates/ui-egui/src/canvas.rs: next's current_spread and the branch's view_centre both kept
2026-10-11 19:10:07 +00:00
aiongg 1da2bb9082 Place: an image placed into a frame fills it once and leaves Auto-Fit off 2026-10-11 18:59:08 +00:00
aiongg 162a7f14bb Paste: text from another app with no insertion point becomes a new text frame (#518)
Paste with no text insertion point and text on the system clipboard that
does not stand for copied objects creates a text frame holding it,
selected as an object: as wide as the first column of the page's margin
area, as tall as the text (at most the margin area) and centred in the
view. One undo removes it.

edit.paste takes optional spread and at params and is available whenever
a document is open, since the engine cannot see the system clipboard.
The paste keys without a native menu pass the paste event's text; menu
Paste reads the system clipboard. With a native menu, an object copy
records the system clipboard's text at that moment, so older text from
another app still pastes the copied objects.
2026-10-11 18:55:50 +00:00
moppeeerandClaude Opus 5.5 b837371f50 feat(engine): Copies report their ids
transform.move with copy, edit.duplicate and edit.stepAndRepeat
returned only a count, so a script (or an agent) couldn't act on the
copies it had just made. They now return `ids`: the objects moved, or
the copies. The counts are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:09:00 +02:00
moppeeerandClaude Opus 5.5 6f053fc7b3 fix(engine): Object fills and strokes must name a swatch
object.fill and object.stroke stored any string as the swatch, so a
name like `Paper` or `Black` (the swatches are `[Paper]`, `[Black]`)
painted nothing without an error. They now refuse an unknown name,
change nothing, and suggest the swatch it was probably meant to be.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:06:43 +02:00
aiongg c99dbd7d6b Integration: Document Setup margins let matching pages follow their parent page (#469)
#469's Document Setup sets every page's and parent page's margins; next's parent-margins model (470aac7d) keeps pages marked local_margins out of later parent edits. After a Document Setup margin edit, pages that now match their parent page (margins and columns) lose the local mark, and parent margins are synced; pages with their own columns stay local.
2026-10-11 18:03:46 +00:00
aiongg 76450c630a Merge #469: feat(ui): Lay out Document Setup like InDesign's
Conflict: ROADMAP.md: progress log keeps both
Conflict: docs/target-app-parity.md: next's New Document line, the PR's Document Setup line
Conflict: crates/engine/src/cmd/layout.rs: setup_tests keep both tests
Conflict: crates/ui-egui/src/dialogs.rs: dispatch takes the PR's documentSetup block and keeps next's numberingSection; tests drop the PR's copies of dialog_context, dialog_frame, visible_label, click_dialog and scroll_dialog (next has them)
Conflict: crates/ui-egui/src/i18n.rs: TABLE keeps both; the PR's Top:/Bottom:/Left:/Right: rows dropped (next has them)
Conflict: crates/ui-egui/src/i18n/it.rs: keeps both; the PR's Top:/Bottom:/Left:/Right: rows dropped (next has them)
2026-10-11 18:03:16 +00:00
aiongg 5bc9594679 Merge #511: Place: PDF pages and SVGs keep their exact size
Conflict: crates/engine/src/cmd/file.rs: file_place keeps next's PdfCrop block (pdf_crop, Asset, crop_pdf, (nw, nh) fallback) with the PR's exact natural size for (nw, nh); the PR's crop_box match dropped; pdf_crop_tests keep both tests
2026-10-11 18:01:34 +00:00
aiongg 3fedc862f6 Merge #510: Color parsing: one scale per colour, not per component
Conflict: crates/engine/src/cmd/style.rs: parse_color object arm keeps next's early gray branch (per-value scale), then the PR's shared-scale CMYK
2026-10-11 18:01:14 +00:00
aiongg 1f6004505e Merge #506: Recovery: each running app keeps its own recovery data
Conflict: crates/engine/src/recovery.rs: run_dir takes the PR's DirBuilder (0o700 on Unix, create_dir elsewhere); rest is next's
2026-10-11 18:00:49 +00:00
noah1400 8119eece43 Recovery: run_dir passes clippy on Windows (#496)
The builder for a run's folder was `mut` only for the mode that Unix
sets, so `-D warnings` rejected it everywhere else. The folder is now
made the way `secure_recovery_dir` makes the recovery folder: with its
mode on Unix, and with `create_dir` elsewhere.
2026-10-11 19:39:09 +02:00
moppeeerandClaude Opus 5.5 7234fd9080 feat(engine): Document Setup sets every page's margins; refuses impossible sizes
layout.documentSetup takes `margins` (a number or {top, bottom, inside,
outside}) for every page and parent page, in the same undo step as the
rest of the setup; with adjustLayout objects follow the new margins too.
It reports the first page's margins and whether the document has a
primary text frame. Widths and heights outside 0-216 in, and margins it
can't read or outside 0-216 in, are refused before anything changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:53:40 +02:00
japbcoelhoandClaude Opus 5.5 72bdc0a919 Place: PDF pages and SVGs keep their exact size
file.place sized a placed PDF page (and SVG) from its size rounded to
whole points, while the export draws the page into that size: a page of
91.774 x 613.276 pt became 92 x 613 and was drawn 0.25 % wider and
0.3 % shorter, so pieces laid edge to edge left gaps of up to 1.9 pt.
A 100.4 x 50.4 pt page placed 100.4 pt wide came out 50.2 pt tall:
`Rect { x0: 0.0, y0: 0.0, x1: 100.4, y1: 50.2 }`.

The graphic's natural size is now the exact page (crop box) or SVG size;
the rounded pixel size stays for display and preflight. place.load (the
place cursor) gets the same exact size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:52:46 -03:00
japbcoelhoandClaude Opus 5.5 5a1e9de92e Color parsing: one scale per colour, not per component
A CMYK component of 1 or less was read as a fraction even when the other
components were percentages, so {"c": 0.7, "m": 0, "y": 12.2, "k": 0}
(a pale cream) became C 70 % Y 12.2 % and exported as
`0.7019608 0 0.12156863 0 k`. The same applied to RGB arrays.

Now a colour is read as percentages (0..100, or 0..255 for RGB) when any
component is above 1, and as fractions only when every component is
within 0..1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:52:46 -03:00
aiongg 933cfb11e6 Integration: recovery folders build on Windows without an unused mut (#506) 2026-10-11 16:40:05 +00:00
aiongg ee2f4dffd6 Merge #506: Recovery: each running app keeps its own recovery data 2026-10-11 16:36:07 +00:00
noah1400 257fb08dbd Recovery: each running app keeps its own recovery data (#496)
Document uids start at 1 in every process, and every running app wrote
its recovery entries into the one recovery folder. Two apps wrote over
each other's `1.designcraft`, and an app that started reopened and
removed the entries of one still running.

A run now has a folder of its own in the recovery folder and holds a
lock in it for as long as it runs. An app that starts moves the entries
of runs that have ended, and those an earlier version wrote, into its
folder and reopens them. A locked folder is left alone.
2026-10-11 16:55:39 +02:00
aiongg a5b8d99164 Merge parent-margins: Layout: parent pages pass margins and guides to their pages
Conflict: crates/doc/src/page.rs: next's Spread::parent_page_for_side is the one parent-page choice (single-sided pages take the first page); the branch's parent_page_index is dropped and its IDML import caller (crates/idml/src/import.rs) uses parent_page_for_side
Conflict: crates/doc/src/build.rs: the branch's parent_page_of/parent_page/parent_guides/margin sync on next's parent_page_for_side; tests both
Conflict: crates/engine/src/cmd/layout.rs: test modules both
Conflict: crates/engine/src/tests_idml.rs: tests both
Conflict: crates/format/src/lib.rs: next's upgrade and duplicate-asset merge, then the branch's mark_differing_margins_local, in both loaders
Conflict: ROADMAP.md: both log lines
Conflict: docs/target-app-parity.md: next's snapping line plus the branch's parent-page guides
2026-10-11 13:12:06 +00:00
aiongg 1daf0a8ec8 Integration: custom wrap boundaries with graphic text wrap
wrap-boundary gives TextWrap a custom path (so it is no longer Copy) and drops it when
object.textWrap sets an offset or contour; next lets object.textWrap edit a placed graphic's
own wrap. The command drops the boundary of the wrap it edits, and next's graphic-wrap IDML
test clones the wrap it compares.
2026-10-11 13:11:02 +00:00
aiongg b6cc59d349 Merge wrap-boundary: IDML: import and wrap around custom text-wrap boundaries
Conflict: crates/compose/src/lib.rs: the custom boundary goes into next's shared wrap_exclusion(); free_slots keeps next's JumpObject handling outside it and takes the branch's combined BoundingBox/Contour arm
Conflict: crates/engine/src/cmd/object.rs: params doc has next's content flag and the branch's custom-boundary note
Conflict: crates/engine/src/tests.rs: both tests
Conflict: crates/idml/src/export.rs: the boundary goes into next's text_wrap_pref()
Conflict: crates/idml/src/import.rs: next's object-style-aware text_wrap() plus the branch's boundary parsing for the item's own wrap; pdf_page_in and wrap_path_of both kept
Conflict: crates/idml/src/tests.rs: both sides' tests
2026-10-11 13:08:12 +00:00
aiongg 25aa2735b8 Merge copy-notes: Text: footnotes insert into a story whose references lack footnotes
Conflict: crates/doc/src/slice.rs: tests module keeps next's append_story overflow test and the branch's copy/paste and hostile-marker tests
2026-10-11 13:04:05 +00:00
aiongg 470aac7d10 Layout: parent pages pass margins and guides to their pages 2026-10-11 13:02:23 +00:00
aiongg fe8a724da4 Text: footnotes insert into a story whose references lack footnotes 2026-10-11 12:54:42 +00:00
aiongg 47d17ca6f5 IDML: import and wrap around custom text-wrap boundaries 2026-10-11 12:53:50 +00:00
aiongg 6cf1933a13 Text: notes insert into a story whose markers lack payloads 2026-10-11 12:43:59 +00:00
aiongg d6309bae32 Text: copied text keeps its endnotes and editorial notes 2026-10-11 12:31:59 +00:00
aiongg 8acb2fbdce Merge #463: Docs: explain hyphenation defaults and image fitting modes to agents
Conflict: crates/engine/src/cmd/text.rs: text.formatParagraph params doc keeps next's drop-cap fields and adds the PR's "hyphenate?: bool (default true)"
2026-10-11 12:24:26 +00:00
aiongg 9ed4aba3da Merge #456: Wrap saved story revision counters consistently
Conflict: crates/doc/src/edit.rs: next's thread_frames (append_story); every rev += 1 is rev.wrapping_add(1)
Conflict: crates/engine/src/cmd/fonts.rs: next's replace (the local-formatting pass moved out of this hunk)
Conflict: crates/engine/src/cmd/text.rs: next's format_chars (apply_char_attrs, typing format); rev += 1 is rev.wrapping_add(1)
2026-10-11 12:20:08 +00:00
KnottyDyes 97b753619a Docs: explain hyphenation defaults and image fitting modes to agents
Command descriptions left the new-text hyphenation default and image fitting outcomes implicit. State the default and each fitting mode's semantics, and add an MCP example that disables hyphenation. No behavior changes.
2026-10-11 01:26:26 -06:00
sanyamsingh001andCodex f58c212289 QA1: wrap saved story revisions consistently
Co-Authored-By: Codex <codex@openai.com>
2026-10-11 16:30:58 +10:00
aiongg c94d7ce8c8 Merge mojikumi-base: CJK: mojikumi tables based on Nothing and the one-em line-end set apply their rules
Conflict: crates/compose/src/tests.rs: next's tests, plus the branch's one-em line-end test after its neighbour
2026-10-11 05:18:58 +00:00
aiongg b77ed8c300 Merge fix/idml-interchange: PDF: Bounding Box crops are measured from the page's drawing, not a render 2026-10-11 05:18:36 +00:00
aiongg 2fae23fcc6 Merge atomic-save: MCP: exported files replace the target atomically (#436)
Conflict: ROADMAP.md: both progress-log entries, next's newer ones first
Conflict: apps/designcraft/src/gpu.rs: next's side; the branch's test fix targets the startup record tests next replaced
Conflict: apps/designcraft/src/main.rs: next's gpu startup and macOS IME helper; config_dir() dropped for the branch's profile::config_dir(), recovery_dir from profile::recovery_dir()
Conflict: crates/ui-egui/src/canvas.rs: next's guard (menu_open, keyboard_pane already limits keys to the focused pane)
Conflict: crates/ui-egui/src/lib.rs: modules from both sides
2026-10-11 05:16:10 +00:00
aiongg 0437ab27f5 PDF: Bounding Box crops are measured from the page's drawing, not a render
Finding a placed PDF's Bounding Box crop rendered its page at 2048
pixels and took the bounds of the painted pixels. PDF export measures
every such crop that import left unmeasured, so exporting a 17-page
document that places one complex illustration took about 4 s longer
than before crops were supported. The box is now the union of the
page's painted paths (strokes included), glyphs and images, each cut
to its clip, collected by interpreting the page without rasterizing
it: the same box, exact instead of to the nearest pixel, in
milliseconds.

Test: content_crop_box_is_measured_without_rendering_the_page.
2026-10-11 04:56:32 +00:00
aiongg 45032b527f CJK: mojikumi tables based on Nothing and the one-em line-end set apply their rules 2026-10-11 04:38:04 +00:00
aiongg 11f34a07f3 Files: saves and exports replace the file atomically
Documents, books, libraries, snippets, swatches and every export are
written to a temporary file beside the target, flushed, renamed over it
and the folder flushed, so a crash, kill or full disk mid-write leaves
the old file intact. Replaced files keep their permissions, symbolic
links keep pointing at their file, and Windows retries a rename that a
scanner briefly blocks.

Fixes #436
2026-10-11 04:01:22 +00:00
aiongg 1e1b0bcce0 Merge #362 update: placed PDFs cropped to their bounding box render only when drawn or exported 2026-10-11 03:01:04 +00:00
aiongg 607b2f4a34 Merge live-corners: Tools: live corners round a frame's corners by dragging
Conflicts:
- crates/tools/src/lib.rs: Overlay keeps next's ContentGhost and adds
  LiveCornerWidget and LiveCornerDiamond.
- crates/tools/src/select.rs: the module doc keeps next's threading and
  content paragraphs and adds the live-corner one. Drag keeps Place and
  adds Corner; SelectionTool holds both `loaded` and `live`. pointer()
  first ends live-corner editing when the selection changed, then runs
  next's loaded-text-cursor and double-click handling. An empty-canvas
  press clears `live` and keeps next's `content: false` Pending. drag()
  handles Place and Corner. overlays() keeps next's Place marquee and
  content ghost and appends the widget or diamonds.
- crates/ui-egui/src/canvas.rs: the overlay pass keeps next's doc/layout
  for ContentGhost and next's Path drawing, and adds the two live-corner
  arms (the branch's no-op Path arm is dropped). The live-corner render
  test goes before next's fast_harness tests.
- crates/engine/src/cmd/object.rs: next's text_wrap stays; the branch's
  corner_options function follows it.
- crates/engine/src/tests.rs, crates/tools/src/tests.rs: both sides'
  tests kept.
- ROADMAP.md: the live corners progress-log line goes first; the status
  line names it too.

Action::Handled is matched in engine tooling.rs; no other crate matches
Action or Overlay exhaustively.
2026-10-11 02:50:45 +00:00
aiongg 0f3f4e0634 Import: placed PDFs cropped to their bounding box render only when drawn or exported
IDML import found every Bounding Box crop by rendering the PDF page at
2048 pixels, once per placement, so opening a 17-page document that
places one Illustrator file 16 times took about 54 s of CPU. Import now
measures the box only for a graphic without GraphicBounds; drawing finds
it through designcraft_render::shown_box (cached per file, page and crop)
and PDF export through with_pdf_boxes.
2026-10-11 02:46:07 +00:00
aiongg 037771057e Merge #316 update: GREP styles with a pattern that can't compile are rejected, and imports warn
Conflicts:
- engine cmd/file.rs, cmd/interchange.rs: kept next's file.open and
  file.openIdml docs (content detection, INDD conversion, package parts)
  and added "GREP styles whose pattern can't compile" to their warnings.
  open_idml keeps next's conversion and import-report warnings and appends
  grep_style_warnings after the font warnings.
- engine cmd/style.rs: style.paragraph.create/edit docs combine next's
  startAt and kinsoku notes with the grepStyles rule. create_para and
  edit_para keep next's set_json_over, continue_numbering and
  explicit_default_kinsoku, then call check_grep_styles. The branch's
  grep_style_error, check_grep_styles and grep_style_warnings follow
  explicit_default_kinsoku. Kept next's delete_tests and
  swatch_options_tests; grep_style_tests goes at the end of the file.
- ui-egui dialogs.rs: kept next's style_section_edit tests and added the
  branch's bad-GREP-pattern test.

confirm_paragraph_style_options, grep_error and the dialog status line
merged without conflicts; next had not changed the OK arm they replace.
2026-10-11 02:19:10 +00:00
aiongg 3bda745a52 Tools: live corners round a frame's corners by dragging
A selected, unlocked rectangle frame at least 40 px on screen shows the
yellow widget near its top-right corner. Clicking it shows a diamond at
each corner. Dragging a diamond along its edge sets the size of all four
corners as one undo step (Shift: that corner only), clamped to half the
shorter side. Alt-click steps that corner's shape; Shift+Alt-click steps
every corner. Escape, a click on empty canvas, another selection or
another tool ends corner editing. Positions follow the frame's transform.

object.cornerOptions takes corners: four {shape, size} entries. Tools
gain Action::Handled and the LiveCornerWidget/LiveCornerDiamond overlays,
drawn by the canvas in the live_corner theme token. Fixes #186.
2026-10-11 02:16:21 +00:00
aiongg cf0ff4a034 Merge group-nested-panic: Objects: group siblings inside a group; refuse mixed levels and spreads
Conflicts in crates/engine/src/cmd/object.rs:
- Helpers: kept next's opt_u64, siblings_mut(d, spread, parent), reorder,
  set_layer_deep and set_layer, then added the branch's GroupPlan,
  group_plan, GROUP_SAME_LEVEL and can_group. The branch's
  siblings_mut(d, &GroupPlan) is dropped; group() calls next's helper as
  siblings_mut(d, plan.spread, &plan.parent).
- Tests: kept next's reorder_tests and the other test modules next added,
  followed by the branch's group_tests module.

group() and ungroup() were unchanged on next, so group() is the branch's
version. cmd/mod.rs (CANT_GROUP_SELECTION, selection_reason) and lib.rs
(execute_unguarded) merged without conflicts.
2026-10-11 02:15:58 +00:00
aiongg ae50689e27 GREP: styles with a pattern that can't compile are rejected, and imports warn
style.paragraph.create, style.paragraph.edit and type.para return the
compile error (bad GREP: ...) for a GREP style pattern that can't compile
and change nothing. Paragraph Style Options shows the error under the
pattern as it is typed, and on OK keeps the dialog open on the GREP Style
section with the command's error.

Opening an IDML or native document keeps such a style and reports a
warning naming the style (or paragraph) and the pattern; composition
keeps skipping it. Adds a lookbehind GREP style test.
2026-10-11 01:51:51 +00:00
aiongg 21a230e426 Merge pdf-effects-wrap: PDF: items with soft effects keep their text wrap 2026-10-11 01:47:49 +00:00