Commit Graph
103 Commits
Author SHA1 Message Date
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
Brandon Thomas 8ce4c31bac Merge pull request #1078 from marcoarnulfo/feat/vt-forced-break
Text parameters: a vertical tab is a forced line break
2026-10-19 02:50:31 -04:00
aiongg 45178f283a Merge branch 'batch-tC' into next-integ 2026-10-16 04:48:45 +00:00
aiongg 1014d841ac Integration: a dropped file without an extension is recognised from its first bytes 2026-10-16 04:29:27 +00:00
aiongg 7979a417fe Integration: cargo fmt 2026-10-16 04:21:41 +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 2f948fa7e2 Merge PR #1079: Color Theme: extract a placed image palette and add it to Swatches
crates/ui-egui/src/dialogs.rs: the branch's dialog body is laid out differently; the Color Theme body goes into its match beside Text Frame Options, and its OK label into the branch's translated labels.
crates/ui-egui/src/menus.rs: keep the branch's Effects dialog opener and add the Color Theme opener.
crates/ui-egui/src/panels/swatches.rs: keep both menu rows (Swatch Options… from #416, Color Theme…).
crates/tools/src/tests.rs: the PR's Graphic literal names clip, wrap and fill.
docs/agents.md: keep both sections.
ROADMAP.md: the PR's line moves under "Newest first."; its copy of an existing 2026-10-13 line is dropped.
2026-10-16 04:12:13 +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
moppeeer c4772c3745 M2.2: extract image color themes into swatch groups 2026-10-15 21:57:26 +02:00
Marco Arnulfo ed218b9817 Text parameters: a vertical tab is a forced line break
frame.create, story.setText, text.insert and the other commands that take
text kept U+000B as a character, drawn as a missing-glyph box. It is the
forced line break of Word and PowerPoint text (Chr(11), Shift+Enter), and
what people porting scripts try first, so text_param now reads it as
U+2028, the forced line break, next to the CR handling it already has.
script --help and docs/agents.md say so.
2026-10-15 21:45:11 +02:00
moppeeerandClaude Opus 5.5 6fe0770f47 Flex Layout: containers that lay out their objects, as InDesign 2026's
A Flex Layout container is a frame holding its objects as nested items
(as Edit › Paste Into makes), with a `flex` record: direction, wrap,
justification, cross-axis alignment, Fixed/Auto width and height, padding
and horizontal/vertical spacing. designcraft_doc::flex lays the objects
out (a Flexbox subset: lines, justification, alignment, Auto sizing), and
the engine runs it after every edit, so the container keeps its objects
in place whatever changes.

Commands: flex.add (Add to Flex Layout; detects aligned objects: their
direction, Auto size, Space Between at their own spacing, as InDesign
does), flex.convert (a group or rectangle frame), flex.remove (objects
stay, grouped), flex.set (every panel setting, plus the alignment grid
cell), flex.reorder. Resizing a container by its handles or W/H changes
its box and makes that side Fixed instead of scaling its objects;
dragging an object inside one drops it in the nearest place.

UI: Object › Flex Layout (Add to, Convert to, Remove, Show Flex Controls),
a Flex Layout panel (Window › Panels) laid out as InDesign's (Create Flex
Layout; alignment grid, W/H with Auto/Fixed; justification menu, four
directions, wrap; padding with the link button; spacing, greyed as
InDesign does when the justification works it out or nothing wraps), and
the same section in Properties. Icons are new drawings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 01:20:50 +02:00
sanyamsingh001andCodex 44485e08da Merge tested main 0f4f382b0e into PR #845
Preserve the reviewed PR behavior and tested upstream base.

Co-Authored-By: Codex <codex@openai.com>
2026-10-14 19:33:57 +10:00
0f4f382b0e feat(text): improve editing, snapping, and threading
Add multi-click text selection, pending caret formatting and clipboard support, text-aware colour controls, guide/grid snapping, and frame threading with loaded-port interactions.

Include the layout, drop-cap and IDML updates carried from #83, with safeguards for generated and linked stories, external text references, local IDs, locked frames and atomic colour edits.

Fixes #23.

Co-authored-by: Sean Hammond <sean.hammond@und.edu>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:41:46 -06:00
sanyamsingh001andCodex 4760a000db PARITY-FIND-SCOPE: respect search scope and first-match limits
Co-Authored-By: Codex <codex@openai.com>
2026-10-14 06:16:28 +10:00
aiongg 0278f6bbea Integration: cargo fmt 2026-10-13 20:16:12 +00:00
aiongg b9060d0350 Merge #803: Refuse command params the description doesn't name
# Conflicts:
#	ROADMAP.md
#	crates/engine/src/cmd/file.rs
#	crates/engine/src/cmd/mod.rs
#	crates/engine/src/cmd/object.rs
#	crates/engine/src/cmd/prefs.rs
#	crates/engine/src/cmd/table.rs
#	crates/engine/src/lib.rs
2026-10-13 20:13:51 +00:00
aiongg c6921a77cb Merge #802: File › Open Recent: recent documents in the menu and on the start screen
# Conflicts:
#	ROADMAP.md
#	apps/designcraft/src/native_menu.rs
#	crates/engine/src/cmd/file.rs
#	crates/engine/src/cmd/mod.rs
#	crates/engine/src/cmd/prefs.rs
#	crates/engine/src/lib.rs
#	crates/format/src/lib.rs
#	crates/ui-egui/src/dialogs.rs
#	crates/ui-egui/src/menus.rs
#	docs/target-app-parity.md
2026-10-13 20:06:29 +00:00
Gert Pellin 68596ea696 Refuse command params the description doesn't name
Callers from outside the app now get a parameter error for a key a
command's params description doesn't name, instead of the command
running with its defaults: `text.select {story, start, end}` and
`style.list {bogus: 1}` are refused with "unknown parameter `…`" and the
nearest or the accepted keys.

- engine: `cmd::params` (ported from VectorCraft #1442) reads the keys
  from each params description; `check_params` / `check_keys`.
  `Session::execute` itself stays lenient so old journals replay; in the
  engine's own tests it checks too, as the audit of the descriptions.
- MCP: tools/call answers -32602 for an argument a tool doesn't take and
  for params a command doesn't take (`execute`, every `batch` step before
  the first runs); the headless backend checks as well.
- Control channel (`engine.execute`) and `designcraft-cli run --cmd`.
- Descriptions now name every key their commands read (table target
  cells, anchored objects, gap.move, file.place, data merge, prefs.set,
  …); type.char / type.para stay unchecked (their attributes may be the
  params object itself; the setters refuse unknown attributes).
- Tests that passed keys the commands never read are fixed, and the pen
  tool no longer sends the unread `notifyPen`.

Fixes #776
2026-10-13 17:25:27 +02:00
PiMinusMax 0fb3d677cc Start screen: recent files show a picture of the first page (#706)
Save and Save a Copy put a picture of the first page in the .designcraft
zip as preview.png (320 px on the longer side). The start screen reads
only that entry and shows the last eight recent files as cards with the
picture and the name, below the community card. Files saved before have
a blank page until they are saved again.

The package fallback-font test writes its fixture without Save's preview:
drawing it before the document's fonts load pulled in a system font that
covers the test's characters (Unifont CSUR) ahead of the document's own.
2026-10-13 17:01:57 +02:00
aiongg 3d3f02e621 Merge save-result: fix(file): saving never reports success without a file (#741, #708) 2026-10-13 14:27:03 +00:00
aiongg eb83d23040 fix(file): saving never reports success without a file (#741, #708)
to_bytes returns a Result and every caller propagates it, so a document
that can't be written is an error: the existing file, the unsaved state
and the recovery checkpoint stay. Asset entries near or over 4 GiB are
written with zip64 sizes. On the web, Save marks the document saved only
after the browser download has started.
2026-10-13 14:22:27 +00:00
aiongg 13f4c37153 Integration (#662 x next): a null spread is absent (spread 0), as Paste in Place forwards its optional spread and file.place documents null as absent 2026-10-13 00:33:00 +00:00
aiongg 7e4c75fce9 Merge pr/662: Commands: reject unknown spread values instead of falling back to page 1 (#662)
# Conflicts:
#	crates/engine/src/cmd/file.rs
#	crates/engine/src/cmd/guides.rs
#	crates/engine/src/cmd/object.rs
2026-10-12 23:59:28 +00:00
Marco Arnulfo 3d59dd5d97 fix(cmd): reject unknown spread values instead of falling back to page 1 (part of #651) 2026-10-13 00:38:54 +02:00
aiongg 09e8118b21 Merge #618: Paste vector art from Illustrator, VectorCraft or Figma as editable paths
Conflict: crates/engine/src/cmd/mod.rs: next's file/layout exports + is_svg_markup
Conflict: crates/ui-egui/src/lib.rs: next's logic(); the PR's RequestPaste hook and request_system_paste field dropped (next reads the system clipboard through Services::clipboard_text); run() routes edit.paste with SVG markup and no insertion point to edit.pasteSvg at next's view centre (with_system_clipboard's spread/at)
Conflict: crates/ui-egui/src/menus.rs: next's activate/menu_enabled; pastes_from_system dropped
Conflict: crates/ui-egui/src/canvas.rs: PR's view_centre dropped (next's canvas::view_centre); PR's Event::Paste SVG arm dropped (the paste event reaches run() through edit.paste); tests both, the PR's two UI tests rewritten against run() (paste event in the middle of the view; native-menu Edit > Paste reading SVG from the system clipboard, markup stays text at an insertion point)
Conflict: docs/control-protocol.md: ui.ime and ui.paste rows
Conflict: ROADMAP.md: logs both
2026-10-12 21:47:29 +00:00
moppeeerandClaude Opus 5.5 f25cae4780 feat(edit): Paste vector art from other apps as editable paths
Illustrator, VectorCraft, Figma and Inkscape put copied objects on the
system clipboard as SVG markup. Pasting it now creates editable paths, as
InDesign does with Illustrator art: fills, strokes (weight, caps, joins,
dashes) and opacity come through as unnamed colours, groups stay groups,
text comes in as outlines, CSS pixels become points (0.75 pt/px) and the
art is centred in the view. One undo step; garbage is refused.

- engine: edit.pasteSvg {svg, spread?, at?}
- canvas: Ctrl/Cmd+V with SVG on the clipboard (not while typing)
- Edit > Paste with nothing copied here asks for the system clipboard
- control channel: ui.paste {text}

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:23:47 +02:00
aiongg 8cd0e74a33 Merge #571: Styles: Load Styles from another document or IDML file
Conflict: crates/ui-egui/src/lib.rs: import_file takes the PR's `styles` early return first, then next's content-sniffing `layout` line
Conflict: ROADMAP.md: kept both progress-log lines
2026-10-12 12:24:34 +00:00
noah1400 d298555e43 Styles: Load Styles from another document or IDML file (#405)
Styles could not be taken from one document into another; a client's
styles had to be rebuilt in every new file. The Paragraph Styles and
Character Styles panels now have Load Paragraph Styles, Load Character
Styles and Load All Text Styles: pick a .designcraft or .idml file,
tick the styles to load and choose what happens to one the document
defines differently (redefine, keep, or load under a new name). The
styles, swatches and lists a loaded style names come along.
`style.load` does it in one undo step.
2026-10-12 10:41:37 +02: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 c014832572 Objects: group siblings inside a group; refuse mixed levels and spreads
Grouping objects that were not top-level items of the first object's
spread panicked (index out of bounds), through object.group, Group on a
content selection, and states.create. Objects nested at different levels
or on different spreads were silently left out of the new group.

Objects that share a spread and a parent group now group into a nested
group in that parent, keeping their stacking order and position. Objects
on different spreads or at different levels return an error and the
document is unchanged, as do objects inside a multi-state object. Group
is disabled in the menu for a selection it can't group; a call that
names its ids still runs whatever is selected.

Fixes #412
2026-10-11 01:46:19 +00:00
aiongg 62df5e764a Merge file-open-os: Save adds .designcraft, and the OS opens DesignCraft and IDML files
- apps/designcraft/Cargo.toml: the macOS target keeps objc2 0.6 / objc2-app-kit 0.3 (IME) and
  adds fmv-macos-events =0.1.0, which uses the same objc2 and objc2-app-kit versions.
- apps/designcraft/src/main.rs: the shared Arc<gpu::Startup>, catch_unwind and restart flow
  stay; the Apple-event handlers are installed before run_native (outside catch_unwind, held
  until the window ends), the app drains the inbox each frame, and documents that arrived are
  noted on the Startup. The open dialog keeps the INDD/INDT filter and ends with All files.
  The open-filter test checks both; the save-dialog test is added.
- apps/designcraft/src/gpu.rs: Startup::opening records the documents the OS handed this run;
  a start again after a graphics failure (both finish and the first-frame watch) passes them
  as file arguments, since Finder doesn't send them twice.
- apps/designcraft/src/apple_events.rs: poll returns the documents it handed the app, and
  queued() returns those still in the inbox when the window ends.
- crates/engine/src/cmd/file.rs: file.open decides by content first (IDML by its mimetype
  entry, then a DesignCraft package or JSON), then an InDesign file by its signature or
  .indd/.indt extension, then a .idml or .designcraft extension for the reader's own error;
  anything else is "not a DesignCraft or IDML file". INDD stays in file.openBytes. Save
  commands add .designcraft as on file-open-os; the params docs describe all of it.
- crates/engine/src/cmd/mod.rs: re-exports from both sides (is_document,
  with_document_extension and layout::chapter_numbering).
2026-10-10 23:21:12 +00:00
aiongg 11532d961e Documents: Save adds .designcraft, and the OS opens DesignCraft and IDML files
- file.save / file.saveAs / file.saveACopy add the .designcraft extension to a
  given path that lacks it and report the path written; saving back to the
  document's own file keeps its name.
- file.open tells DesignCraft and IDML packages apart by content, so files
  without an extension open; anything else is "not a DesignCraft or IDML file".
- Dropped or web-picked files without an extension open as documents when their
  content is one.
- Desktop save dialogs filter on the suggested extension and add it to a name
  typed without it, asking before replacing an existing file; the open dialog
  gains "All files".
- macOS: open-documents Apple events (Finder, Dock, Open With) through
  fmv-macos-events, queued until the window is ready; Info.plist declares the
  .designcraft type and an imported .idml type with CFBundleDocumentTypes.
- Windows MSI: .designcraft ProgId with an Open verb, IDML under Open with.
- Linux: shared-mime-info for both types and MimeType in the .desktop file.

Fixes #158, #211, #375
2026-10-10 22:40:06 +00:00
aiongg c93664943b Merge feat/ruler-zero-point: a document zero point and ruler origin you can drag, reset and lock
Conflict: crates/idml/src/tests.rs: both sides' tests
2026-10-10 16:28:07 +00:00
aiongg 209f758727 Rulers: a document zero point you can drag, reset and lock
The zero point is document state (DocSettings.zeroPoint, zeroPointLocked):
an offset from the top-left corner of each spread's pages, so every
spread measures from the same relative place and split views share it.
view.zeroPoint {at, reset} and view.lockZeroPoint {on} set it; moving it
is not an undo step and undo/redo keep it. Values are kept on the
pasteboard, from commands, files and IDML.

Rulers and the X/Y fields (Control, Properties, Info, transform.set)
measure from it. IDML reads and writes Document ZeroPoint.

Dragging from the ruler corner places it where released, with crosshair
lines while dragging; a click that stays in the corner does nothing; a
double-click resets it; the corner's context menu locks it.
2026-10-10 15:34:12 +00:00
aiongg 816ce1b756 Merge #342: feat(ui): Lay out Numbering & Section Options like InDesign's
Conflict: ROADMAP.md: kept both entries (New Document, then Numbering & Section Options).
Conflict: crates/ui-egui/src/dialogs.rs: width match keeps "newDocument" => 960.0 and adds "numberingSection" => 560.0, dropping the PR's "newDocument" if is_rtl => 380.0; tests keep next's helper block and add only the PR's numbering_and_section_options_keep_their_actions_in_view.
Conflict: crates/ui-egui/src/i18n.rs: kept both TABLE tail blocks (File › New › Document, then Layout › Numbering & Section Options).
Conflict: crates/ui-egui/src/i18n/it.rs: kept both tail blocks.
2026-10-10 14:56:15 +00:00
aiongg 33a26228f5 Merge #343: Tabs: commands, Tabs panel and a Tabs section in Paragraph Style Options
Conflict: ROADMAP.md: both lines kept (next's IDML1 line, then the Tabs panel line)
Conflict: crates/engine/src/cmd/mod.rs: both spec lists kept (thread::specs, then tabs::specs)
Conflict: crates/ui-egui/src/dialogs.rs: next's delete_paragraph_style and the PR's style_tabs both kept, without the section_list doc comment that next removed; in the tests the PR's copies of draw, ruled_paragraph, undo_steps, the rule tests, command_app, the generic command tests and the text frame options test duplicated next's identical ones and were dropped, keeping only the PR's new Tabs section test
Conflict: crates/ui-egui/src/i18n/it.rs: both blocks kept (next's machine-translated rows, then the Tabs rows)
Conflict: crates/ui-egui/src/i18n/uk.rs: both blocks kept (next's machine-translated rows, then the Tabs rows)
2026-10-10 14:22:16 +00:00
aiongg 0f94bbb1ac Text: commands to add, move, change, delete, clear and repeat tab stops
type.tabs.* edit the stops of the selected paragraphs or, with a style
parameter, of a paragraph style. Positions are finite and clamped, a
paragraph holds at most 100 stops, leaders up to 8 characters and the
align-on character is one character. type.tabs.get reports the stops,
indents and the text column they are measured in.
2026-10-10 14:05:09 +00:00
moppeeerandClaude Opus 5.5 9c683f3ef8 feat(ui): Lay out Numbering & Section Options like InDesign's
Layout > Numbering & Section Options now opens its own window instead of
the generated command form, on the section of the page in view: Start
Section (always on for page 1), Automatic Page Numbering or Start Page
Numbering at, and a Page Numbering group with Section Prefix, Style,
Section Marker and Include Prefix when Numbering Pages; then Document
Chapter Numbering with its style, Automatic Chapter Numbering, Start
Chapter Numbering at, Same as Previous Document in the Book (only in a
book) and the book's name. OK and Cancel sit beside the options, out of
the scrolling body.

OK runs layout.section and layout.chapterNumbering; a refused value
(a prefix over eight characters or with + or a comma, a start number
below 1) keeps the window open and changes nothing. Opening the window
reads the chapter numbering without running a command, so it doesn't
stop a Data Merge preview. New strings are translated (German, French,
Spanish, Japanese, Chinese, Italian).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:58:33 +02:00
Brandon ThomasandClaude Opus 5.5 fc6101d10f Merge origin/main into feat/text-editing-and-frames
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:58:36 -04:00
Brandon ThomasandClaude Opus 5.5 962234fbdf Merge origin/main into feat/text-editing-and-frames
Resolve conflicts with the CJK/Arabic work: trailing spaces follow the line
only in left-to-right paragraphs without right-to-left glyphs (layout_line
now takes the bidi info); style linking moves to ScopedFonts so the
document's own fonts count (FontDb delegates to scope 0); threading keeps
the receiving story's direction and Vertical Type, and frames split off by
threading or removed from a thread keep theirs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:41:43 -04:00
Brandon ThomasandClaude Opus 5.5 48dfbcfd19 Merge origin/main into data-merge
Resolve conflicts keeping both sides: doc gains both `cjk` and
`datamerge` modules; file.open resolves data-merge sources and loads the
document's fonts; DocState keeps preview_stash/preview_record and fonts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:28:15 -04:00
aiongg 832939691c CJK 1.3: documents bring their own fonts
Opening a .designcraft or .idml file from disk (file.open, file.openIdml,
book chapters, recovered documents) loads the fonts in the "Document
Fonts" folder beside it. Their faces come before installed faces of the
same family and style. The files are untrusted: at most 200 are read,
256 MB each (full CJK collections fit) and 512 MB in all; files over a
limit by their listed length are skipped unread, and damaged, oversized
and surplus files are listed in the open result's warnings. Document fonts stay
loaded for the session (the font database is process-wide).

Faces record their source (bundled, installed file, document file,
memory); font.list reports it. File > Package copies the font files the
document uses into the package's Document Fonts folder unless OS/2
fsType restricts the licence, and the report says which fonts weren't
copied and why.
2026-10-07 09:33:52 -04:00
Cameron Gilroy b41eba1cba Data merge: merge into a new document, with preview and tiling
Select, update, and remove a linked source. Placeholders cover text,
images, QR codes, and hyperlinks. Preview fills one record in the
session and is left out of save and recovery. Merge copies the template
into a new document, one record per page, or tiles frames on a single
non-facing page. A missing or changed file is marked and the cached
rows are still used. The template undo stack is left as it was.
2026-10-07 21:22:06 +11:00
aiongg 6104f2ced1 Threading: thread and unthread commands 2026-10-07 00:16:33 -04:00
Brandon ThomasandClaude Opus 5.5 f1767a84fd Engine: an empty ids names nothing; states.create and conveyor.collect honour id
named_targets reads ids/id as targets() does, so {ids: [], id} can't
pass the gate and then run on nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:04:22 -04:00
japbcoelhoandClaude Opus 5.5 7e69c82b89 Engine: commands given ids run without a selection
A command that documents ids (object.textFrameOptions, transform.rotate, ...)
acts on the objects a call names, yet its enabled check looked only at the
selection. With a text caret or nothing selected, {"ids": [5]} failed with:

  command `object.textFrameOptions` is not available right now: nothing selected

Execute now lets such a call through when every named object exists, and
says "no object with id N" when one doesn't. transform.set, object.arrange
and edit.stepAndRepeat, which already acted on ids, now document them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:04:22 -04:00
japbcoelhoandClaude Opus 5.5 b54fa8d9bb Text: carriage returns in typed or created text start paragraphs
frame.create's text and text.insert kept CR and CR LF as characters, so text
written with InDesign's, classic Mac or Windows line ends ("One\rTwo") became
a single paragraph, and paragraph formatting (spaceAfter) never applied
between the parts. They now become \n, the paragraph separator, as edit.paste
and text import already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:04:08 -04:00
Brandon ThomasandClaude Opus 5.5 f05bbfeea2 Never crash: engine, MCP and tools
Commands return errors instead of expect()/unreachable!() (book, library,
undo/redo, paste, find/replace, Pathfinder, tables, QR, select, swatches,
anchors, articles, transform again, XML import). The sample document builder
propagates errors. Commands that add a key to their parameters no longer
panic when a caller (control channel, MCP, script) passes an array or a
string, and corrupt crash-recovery metadata is ignored. engine, mcp and
tools now deny unwrap/expect/panic/unimplemented/todo/unreachable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:43:33 -04:00
Brandon ThomasandClaude Opus 5.5 0d02222dab W: Scripts panel and script.run (one undo step)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:48:58 -04:00
Brandon ThomasandClaude Opus 5.5 84476ffef4 P: live captions that follow their source's metadata
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:48:58 -04:00