Commit Graph
2291 Commits
Author SHA1 Message Date
MarekSpodBiedry 0820ef857c MCP: render_page and screenshot are not read-only because they can write to path (#733) 2026-10-13 18:17:59 +02:00
MarekSpodBiedry ac8f44d749 Images: reject EPS bounding boxes with nonfinite corners or size (#740) 2026-10-13 18:17:58 +02:00
aiongg a7c3e330ec INDD: open .indd and .indt files through indd-utils (#21, #114)
.indd and .indt documents open through File › Open, drop, the web inbox,
file.open, file.openBytes, file.openIdml and the CLI. The engine converts
them to IDML in memory with storytold/indd-utils (pinned by rev, MIT OR
Apache-2.0, no dependencies, builds for wasm) and imports the result
through the existing IDML path. The converter's warnings come first in the
open result's `warnings`, before the IDML importer's and the font
warnings. A file the converter can't read is an open error, shown in the
Can't Open the File alert.

Routing: a path or name ending in .indd/.indt, or bytes starting with the
INDD signature (the web inbox passes the name without its extension).
opens_as_document and the desktop and web Open dialogs list indd/indt.

Tests use synthetic bytes only: signature-bearing junk, a zero-filled
.indd and a short .indt all fail as open errors with no document added;
a dropped unreadable .indd raises the open alert.

(cherry picked from commit bb8a6a23fa)
2026-10-13 16:15:15 +00:00
Nick MarelloandClaude Opus 5.5 2d5abba78f #21: opening or dropping an InDesign file says how to bring it over
file.open on a .indd reported "not a DesignCraft document: expected value at
line 1 column 1"; file.openBytes with a .indd name failed the same way, and a
dropped .indd was placed as a graphic. All now say InDesign documents and
templates can't be opened yet and point to File > Export > InDesign Markup
(IDML), which DesignCraft already opens. The check is by extension, before
anything is read.

Also adds my contributors/people.toml entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:02:49 -07:00
Jeffrey ThayneandClaude Opus 5.5 0541f36795 Links: don't trust a linked text file's time while it is still changing
File times are whole seconds (as in Data Merge's fingerprint), so a
same-size change saved within the same second as the last check kept
both size and time, and the Links panel said OK. A size and time are now
only remembered (on place, update, relink, and in the session's check
cache) once the file has been unchanged for two seconds; a file changed
more recently is checked by content on each poll. Found by the Update
Link dialog test, which rewrites a file within the same second; the
polling test now also changes a file twice in one second.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:38:37 -06:00
Jeffrey ThayneandClaude Opus 5.5 7b33c981dc Links: only read a linked text file when its size or time changed
The Links panel lists links about twice a second, and each listing
hashed every linked text file. A linked text file now also remembers
its modification time (as Data Merge does for its source, through the
same size-and-time fingerprint): while size and time match, it isn't
read. A different size is Modified without reading. When only the time
changed, the content hash still decides (a file saved or synced again
without changes stays OK), and the session remembers that check by
size and time, so the next poll doesn't read the file again. Update and
Relink record the size and time from before their read. Test: polls of
an unchanged file don't read it (it is made unreadable), a touched but
unchanged file is read once and stays OK, a changed file is Modified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:30:40 -06:00
Jeffrey Thayne 38bf116604 Merge remote-tracking branch 'origin/main' into linked-docx 2026-10-13 09:25:53 -06: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 fca75a3302 Merge locked-paths: tests for path edits on locked layers and groups (#735)
# Conflicts:
#	crates/ui-egui/src/panel_docking_tests.rs
2026-10-13 14:58:15 +00:00
Gert Pellin b0c364ee60 EPUB: one content file per chapter, split before each h1 (#768)
The whole book was one content.xhtml, which is slow on e-readers and
loses chapter starts. The body is now split before each h1 into
content.xhtml, content2.xhtml, …: a section a split goes through is
closed and reopened, and a story that opens with its h1 starts in the
new file with its id. The manifest and spine list the files in order,
and the navigation links each heading to the file it is in. HTML export
stays one page.

Fixes #768
2026-10-13 16:56:17 +02:00
aiongg 2be4588658 test(properties): the Stroke link test starts with Properties active (#736) 2026-10-13 14:51:54 +00:00
aiongg e08f3c5997 test(paths): path edits on a locked layer change nothing (#735) 2026-10-13 14:51:53 +00:00
Gert Pellin 2a3d1dc183 Styles: name the attributes in the params docs, and a wrong one in the command's error (#777)
- describe style.paragraph.create (and .edit, style.character.create,
  .edit) gave chars?: {…} and para?: {…}. The descriptions now say they
  take type.char's and type.para's attributes and name the common ones.
- A wrong attribute gave "invalid parameters for `style`: unknown
  attribute `font`". The error now names the command and the group,
  and the attribute meant (nearest name, or one the key starts or ends)
  or else lists them all: "invalid parameters for
  `style.paragraph.create`: chars: unknown attribute `font`; did you
  mean `fontFamily`?". The suggestion comes from set_json, so
  type.char, type.para and find.change get it too.
- chars or para that isn't an object is an error instead of ignored.

Fixes #777
2026-10-13 16:48:27 +02:00
aiongg a2ec9b46ae Integration (locked-paths): Stroke link test shows Properties, which floating Layers leaves behind Pages 2026-10-13 14:47:24 +00:00
aiongg d9c766dd16 Integration (editor-undo): Story Editor test focuses the text input, not its text run; paste test sets its stale selection after Undo restores the selection 2026-10-13 14:47:23 +00:00
Gert Pellin 6fafa9b0a3 EPUB/HTML: generic font fallback from the font's class, and optional font embedding (#775)
- Every font-family fell back to serif, sans-serif typefaces included.
  The fallback now comes from the font's classification (PANOSE, OS/2
  class, name: serif, sans-serif, monospace, cursive, fantasy), passed
  in by the engine; without one, from the family name.
- file.exportEpub takes embedFonts: the faces the stylesheet names are
  packaged (OEBPS/fonts, @font-face, manifest items with font/ttf, otf,
  woff or woff2). Faces that aren't available, whose licence (OS/2
  fsType) forbids embedding, or that sit in a font collection are left
  out and listed in the new warnings array. Fonts are packaged whole;
  subsetting is a follow-up.

Fixes #775
2026-10-13 16:41:08 +02:00
Gert Pellin f420639276 EPUB/HTML: no file name as alt text, readable image names in the package (#774)
- An image without alt text (Object Export Options) got its file name as
  alt, which a screen reader then reads aloud. It is now decorative
  (alt=""), and file.exportEpub lists those images in a new warnings
  array so they can be given alt text.
- Images and media were packaged as images/<asset id>.<ext>. They are
  now <name>-<asset id>.<ext>, the name from the original file name
  (lowercase ASCII); the id keeps two files of the same name apart.

Fixes #774
2026-10-13 16:35:36 +02:00
aiongg 4a349ce1c6 Integration: format 2026-10-13 14:33:51 +00:00
Gert Pellin 7be88b4484 EPUB: real modified time, a stable unique identifier, and a cover image file (#771, #773)
- dcterms:modified was always 2026-01-01T00:00:00Z. It is now the
  export time (from the command; on the web, and in the crate without
  one, the document's last save or creation time).
- The default dc:identifier was urn:designcraft:<title>, shared by
  books with the same title. It is now a urn:uuid derived from the
  title and the document's creation time: stable from export to export,
  different between documents. file.exportEpub takes identifier to set
  it (an ISBN, say).
- file.exportEpub takes coverImage, a PNG or JPEG file used as the
  cover (cover-image); a JPEG is packaged as cover.jpg/image/jpeg.

Fixes #771
Fixes #773
2026-10-13 16:33:34 +02:00
aiongg d087c035b2 Merge recovery-dirs: fix(recovery): an explicit recovery folder never overwrites or deletes another run's checkpoints (#715) 2026-10-13 14:32:52 +00:00
aiongg 67b53772ff Merge pdf-hidden-fields: fix(pdf): form fields, buttons and media that don't print stay out of the PDF (#726) 2026-10-13 14:32:17 +00:00
aiongg a4f4a95901 Merge idml-overrides: fix(idml): export keeps overridden parent items and their local copies' links (#725) 2026-10-13 14:31:45 +00:00
aiongg e04cce9838 Merge epub-tables: fix(epub): EPUB and HTML export include tables, footnotes and endnotes (#724) 2026-10-13 14:31:31 +00:00
aiongg 4e3a5364e5 Merge docx-tables: fix(textimport): Word table cells skip deleted text and field codes; Normal comes in as its own style (#721, #722) 2026-10-13 14:31:03 +00:00
aiongg c538979fc7 Merge cache-keys: fix(compose): text recomposes when the baseline grid, a wrap layer's visibility or section numbering changes (#718) 2026-10-13 14:29:08 +00:00
aiongg a39bb70c5b Merge locked-paths: fix(paths): path edits refuse a path on a locked layer or in a locked group (#735); fix(properties): links to other panels open them when panel docking is on (#736) 2026-10-13 14:28:19 +00:00
aiongg a418a8a309 Merge editor-undo: fix(editing): Story Editor stays with its document; Undo restores the selection from before the command (#729, #716) 2026-10-13 14:27:42 +00: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
Gert Pellin 1fc302f695 EPUB: the navigation lists the headings, with a title in the book's language (#769, #770)
nav.xhtml had one entry per placed story, labelled with its first
paragraph (body text too), and always the title "Contents".

- Headings (h1-h6) get an id in the EPUB, and the navigation lists the
  h1-h3 headings of the stories in reading order, nested by level and
  linked to them. A document without headings keeps one entry per
  story (its first paragraph).
- The navigation's title follows the language (Inhoud, Inhalt, Table
  des matières, Índice, Indice, 目次, 目录; Contents otherwise), and
  file.exportEpub takes tocTitle to set it.

Fixes #769
Fixes #770
2026-10-13 16:26:32 +02:00
aiongg debec75166 fix(textimport): Word table cells skip deleted text and field codes; Normal comes in as its own style (#721, #722)
Table cell paragraphs go through the same run and inline path as body paragraphs, so deleted and moved-from text and field instructions stay out of cells, and field results, tabs and breaks come in.

Word's Normal imports as a paragraph style named Normal whose definition is w:docDefaults with Normal's own properties on top; paragraphs without a style use Word's default paragraph style. A styleMap entry can still map Normal to [Basic Paragraph].
2026-10-13 14:23:08 +00:00
aiongg 4eb786e880 fix(recovery): an explicit recovery folder never overwrites or deletes another run's checkpoints (#715) 2026-10-13 14:22:58 +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 f606992782 fix(properties): links to other panels open them when panel docking is on (#736) 2026-10-13 14:19:57 +00:00
aiongg 8817e07013 fix(paths): path edits refuse a path on a locked layer or in a locked group (#735) 2026-10-13 14:19:56 +00:00
aiongg c91b9af16d fix(epub): EPUB and HTML export include tables, footnotes and endnotes (#724) 2026-10-13 14:19:45 +00:00
aiongg 2c7e3e53f0 fix(editing): Story Editor stays with its document; Undo restores the selection from before the command (#729, #716) 2026-10-13 14:19:00 +00:00
aiongg e3d7b44f4c fix(pdf): form fields, buttons and media that don't print stay out of the PDF (#726)
Fields, buttons and video or sound are collected with the page content's
inclusion rule: the item's layer is visible and printable, and neither the
item nor any group around it is hidden or non-printing.
2026-10-13 14:18:35 +00:00
aiongg d6f4c0133a fix(compose): text recomposes when the baseline grid, a wrap layer's visibility or section numbering changes (#718) 2026-10-13 14:18:20 +00:00
seanth b676d9aebe Merge branch 'storytold:main' into small-frame-wrap 2026-10-13 10:18:17 -04:00
aiongg 06762c3bfc fix(idml): export keeps overridden parent items and their local copies' links (#725) 2026-10-13 14:17:13 +00:00
Gert Pellin 553a04311a EPUB/HTML: bool::then_some for the list tag (clippy)
Refs #767
2026-10-13 16:16:35 +02:00
Gert Pellin f51e896802 EPUB/HTML: list paragraphs share one ul/ol, and pre lines one block (#767)
story_html wrote every paragraph on its own, so li paragraphs had no
list around them and each line of a pre style was its own <pre>.
Consecutive li paragraphs now share one list: ol for a numbered style,
ul for a bulleted one, and an unmarked ul (list-style:none) when the
style has no list type of its own (typed bullets). Consecutive pre
paragraphs of one style (and direction) become one <pre> with their
lines separated by newlines.

Fixes #767
2026-10-13 16:16:00 +02:00
LosminekandClaude Opus 5.5 19eb0b8fbd i18n(cs): 16 strings added on main, drop 3 stale Type/ keys
Czech for Step and Repeat (Repeat, Count:, Rows:, Create as a grid),
transparency (Isolate Blending, Knockout Group, Object:, Group:),
fill/stroke commands, Reverse Path, Set Anchor, Sample Color, the
Presentation view mode and Invalid value. Removes Type/Proof Setup,
Type/Rotate Spread and Type/Separations Preview, which main no longer
uses (czech_catalog_has_no_stale_keys).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:13:23 +02:00
Gert Pellin 029a911bf4 EPUB/HTML: anchored objects are exported where they are anchored (#766)
story_html left out each anchored object's mark and the object with it,
and reading_order only sees page items, so an image anchored in a story
was in neither the EPUB nor the HTML export. The story's text is now
split at the object marks, and each anchored graphic is written in
place: inline in the paragraph, an above-line object as a block span
with its alignment. The graphic markup (and image or media file
registration) is shared with free graphics (graphic_html).

Fixes #766
2026-10-13 16:11:39 +02:00
moppeeerandClaude Opus 5.5 7a30e5427e fix(render): radial gradients stretch with the object's box, as in InDesign
InDesign draws a radial gradient in the object's bounding box: on a
non-square frame it's an ellipse reaching the frame's sides, and a
Gradient Swatch tool drag keeps those proportions. LYH Studio episode 6
(3:16) gives the left page's background a radial gradient: magenta in
the middle, cyan out to every edge. DesignCraft drew a circle reaching
only the longer side, so a portrait page stayed purple at its sides.

- doc: `Fill::radial_ellipse(bounds)`: centre and radii (default: the
  box's centre and half its width and height; with a tool vector, its
  start, and its length measured with the box made square).
- render: the radial paint is that circle, stretched by the paint
  transform; PDF: the shading's transform does the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:58:05 +02:00
moppeeerandClaude Opus 5.5 19aa1c6f4c feat(swatches): New Swatch names a gradient "New Gradient Swatch"; gradient swatches from colour swatches
InDesign's Swatches panel New Swatch takes what is current: a gradient
built in the Gradient panel becomes "New Gradient Swatch" (applied, and
listed), an unnamed colour a listed swatch, a selected named swatch a
"… copy". DesignCraft's button always added the same orange swatch, and
gradient swatches could not be created at all (LYH Studio episode 6,
2:12: "this is our Swatch … create a new swatch for it").

- engine: `swatch.new {swatch?, target?}` does the above for the
  selection's fill (or stroke) or a named swatch; `swatch.create` also
  takes `gradient: {kind?, stops: [{location, swatch | color, opacity?,
  midpoint?}]}`, its stops taking colour swatches by name, as swatches
  dragged onto the Gradient panel's stops.
- Swatches panel: New Swatch runs `swatch.new`, and with nothing
  selected still adds a colour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:46:31 +02:00
moppeeerandClaude Opus 5.5 1527987a8e perf(render): CMYK ink passes cover only what transparency can change, with the render threads
- The two ink passes run over the area the spread's transparent objects
  (opacity, blend, effects, knockout, nested or anchored) and their
  effects' reach can change, not the whole view; the rest is drawn once,
  through the ordinary renderer, clipped out of that area. Objects the
  area touches join it whole, so no object is drawn partly through each
  colour conversion (no seam). A transparent parent-page item still
  takes the whole view.
- The ink passes get the render threads (effects keep their backdrop:
  with_blended_filters composites the offscreen filters with the blend).
- The view is kept in the 64 MiB cache only when the ink passes ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:26:38 +02:00
moppeeerandClaude Opus 5.5 0ba90118c7 fix(ui): the whole empty app bar moves the window; caption colours are theme tokens
- The window drags from any empty part of the bar: the gap between the
  menus and the tools, and the 7 pt lower band, which is always there,
  so at the 900 px minimum width (menus and tools filling the row) the
  window can still be moved. Menu titles and tools keep their clicks; a
  menu title pressed and dragged doesn't move the window.
- The Close button's hover fill and glyph are Tokens::caption_close and
  caption_close_text instead of fixed RGB values.
- BAR_H and LOWER_BAND replace the literal 36 (drag area, caption
  buttons, title centre).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:21:34 +02:00
PiMinusMax 1c82a5b892 File › Open Recent: recent documents in the menu and on the start screen (#706)
Opening or saving a document moves it to the front of File › Open Recent
(no duplicates; Save a Copy and exports don't count). The submenu lists the
files by name with the full path as tooltip, then Clear Recent Files; the
start screen shows the last eight with their folders. A file that is gone is
dimmed and, when chosen, says so in an alert and stays listed (a disk that
isn't connected comes back). Preferences › File Handling sets how many are
kept (default 20, 0–100).

Engine: Prefs.recent_files / recent_files_max (saved in prefs.json),
file.openRecent {index}, file.recentFiles, file.clearRecent. UI:
app.openRecent; the macOS menu bar rebuilds the submenu when the list
changes.
2026-10-13 15:13:18 +02:00