Commit Graph
2702 Commits
Author SHA1 Message Date
aiongg c154b4325b pdf: right-to-left tests read UTF-16 units with as_chunks (clippy 1.99) 2026-10-13 18:46:47 +00:00
aiongg b4f7286482 Merge text-autosize: opening a file keeps its auto-size frame sizes (#632)
# Conflicts:
#	ROADMAP.md
#	crates/engine/src/cmd/interchange.rs
#	docs/gaps.md
#	docs/target-app-parity.md
2026-10-13 18:40:15 +00:00
aiongg 935741d47c Text frames: opening a file keeps its auto-size frame sizes 2026-10-13 18:40:01 +00:00
moppeeerandClaude Opus 5.5 dc712baeca fix(color): gradients between process colours blend as inks, as InDesign shows them
LYH Studio episode 6 builds every gradient from C=0 M=100 Y=0 K=0 to
C=100 M=0 Y=0 K=0. InDesign blends a gradient's process stops as inks:
halfway is C 50 M 50, a light lilac. DesignCraft converted both ends to
RGB and blended there, so the middle came out a darker violet (117, 88,
189 instead of 135, 119, 181), and every Episode 6 gradient looked
different. A midpoint between CMYK stops also became an RGB colour, so
the PDF shading turned RGB with a conversion warning.

- color: `Color::lerp` blends CMYK with CMYK (or grey), and grey with
  grey, in ink space; anything with RGB still blends in RGB.
- `Gradient::display_stops`: the expanded stops plus samples through
  each span that isn't RGB at both ends, for renderers that interpolate
  in RGB; the screen renderer uses it.
- pdf: a midpoint's expanded stop now keeps the stops' space; the
  colour-space test expects no conversion for CMYK or grey midpoints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:38:38 +02:00
noah1400 ba4519c382 Preferences: Story Editor size stays finite and in range (#821)
Preferences OK stored whatever number the dialog held as the Story
Editor font size. One that is not finite ("nan", "inf", or a number
too large for an f32) was written to ui.json as null; the next launch
could not read the file, started with the default UI settings and
saved them over it on exit. The size is now kept between 8 and 36 pt,
the slider's range, and a value that is not a finite number leaves
the size as it was.
2026-10-13 20:31:54 +02:00
Govinda RoyandClaude Opus 5.5 677cafbf9c Images: EPS shows through its proxy at display time; simple PostScript (QR codes) is drawn
An EPS that came in with a document (InDesign embeds its QR codes as
EPS) drew nothing: only File › Place converted EPS to a proxy. Now
`decode_rgba` draws any EPS through `eps_proxy` and `natural_size` measures
it by its bounding box in points, so screen, PNG and PDF show it while the
document keeps the EPS itself.

`eps_proxy` also draws PostScript that only fills rectangles in solid
colours (setcmykcolor, setrgbcolor, setgray, rectfill and procedures that
stand for them, as InDesign's QR codes are written), at 8 px per point so
modules stay crisp. Anything else keeps the preview or placeholder. Input is
bounded: tokens, rectangles, painted area and procedure definitions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:26:02 +06:00
moppeeerandClaude Opus 5.5 d49d7c4ea0 i18n: keep the new Window menu strings beside Workspace
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:18:14 +02:00
moppeeerandClaude Opus 5.5 0a34316b82 feat(ui): Window menu in InDesign's order; Window > Workspace with Essentials Classic
LYH Studio episode 6 starts with "Window > Workspace > Essential
Classic" and opens panels with "Window > Color > Gradient & Swatches".
DesignCraft's Window menu had no Workspace or Color submenu (panels sat
in one "Panels" list) and no Essentials Classic workspace.

- Window menu: Arrange, Workspace, then the panels alphabetically with
  InDesign's groups (Color: Color, Gradient, Swatches; Editorial;
  Interactive; Object & Layout; Output; Styles; Type & Tables;
  Utilities). Every panel stays reachable; Next/Previous Document moved
  into Arrange.
- Window > Workspace: the built-in workspaces as "[Essentials]", the
  current one checked, Reset <current>, New Workspace. The app bar's
  switcher uses the same list and order.
- Essentials Classic: the Control panel, no Properties stack, and an
  icon column of named panel groups ([Pages, Layers, Links] [Stroke,
  Color] [Swatches] [Effects] [Paragraph] [Text Wrap] [Character
  Styles, Paragraph Styles] [Gradient]); Window > Pages opens Pages
  from its group. Saved workspaces keep the groups.
- Flyouts and floating panels name Pages, Layers and Properties.
- Translations for Editorial, Object & Layout, Styles and Essentials
  Classic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:16:51 +02:00
Govinda RoyandClaude Opus 5.5 7e344e85f1 Compose: spaces at the end of a paragraph take no room on the line
The paragraph composer counted a trailing space (or several) before the
paragraph end or a forced line break in the line's width, so "2 VOOR " in a
frame exactly as wide as "2 VOOR" broke before "VOOR" and the frame went
overset. InDesign lets those spaces hang past the edge. They now add no
width; the line's visible range already left them out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:16:42 +06:00
moppeeerandClaude Opus 5.5 bdf66d7749 feat(ui): Gradient panel in InDesign's layout; swatches drop onto its stops
LYH Studio episode 6 (1:36-2:05) builds its gradient in the Gradient
panel: it drags C=0 M=100 Y=0 K=0 from the Swatches panel onto the left
stop, drops C=100 M=0 Y=0 K=0 on the ramp (a new stop at 68.636 %), then
drags the black stop off. DesignCraft's Gradient panel took no swatches,
showed only a hint until the fill was a gradient, and laid out its
fields differently.

- Gradient panel: InDesign's layout (thumbnail; Type; Location % and
  Angle; Reverse; the ramp with midpoint diamonds above and stops below).
  Until the fill is a gradient it shows the default White -> Black ramp;
  clicking the thumbnail or using the ramp starts one. Midpoints drag
  and take a Location. A stop's colour picker moved to its right-click
  menu, so selecting a stop no longer grows the panel.
- Swatches panel rows are drag sources (the chip follows the pointer):
  dropped on a stop a swatch colours it, elsewhere on the ramp it adds
  a stop there.
- Stops keep their colour model through panel edits (CMYK used to turn
  into hex RGB whenever a stop moved).
- engine: `object.gradient` stops accept `swatch` (its colour; gradient
  swatches and [None] are refused) and colours accept `{gray}`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:02:45 +02:00
moppeeerandClaude Opus 5.5 ac54c8faf7 feat(text): gradient swatches on text, laid across the frame as in InDesign
LYH Studio episode 6 (8:28) gives the "1969" headline the "New Gradient
Swatch" (magenta → cyan) from the Swatches panel. InDesign runs a gradient
on text across its text frame's box. DesignCraft resolved a character
fill to one solid colour, so a gradient swatch drew no text at all.

- render: a character fill or stroke that names a gradient swatch is
  painted with that gradient over the frame's box in text space (type on
  a path: the path's box).
- pdf: text runs take a gradient paint: the shading is laid out in the
  frame's text space and mapped into each run's origin, so skew and
  horizontal scale don't bend it; outlined runs and type on a path too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:30:55 +02:00
Gert Pellin a2e67bd1d9 Contributors: credit switch87 as Gert Pellin 2026-10-13 19:25:39 +02:00
Gert Pellin 6ae1d6dc31 README: install on Gentoo from the ::snakebyte overlay 2026-10-13 19:25:33 +02:00
Tomasz LewandowskiandClaude Opus 5.5 99efe4c5ec contributors: add Tomasz Lewandowski
My own entry, so the About window can show my name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DukHNvUY45rjRnvtz91x33
2026-10-13 19:16:32 +02:00
Tomasz LewandowskiandClaude Opus 5.5 226991cb28 i18n: add Polish interface language
Adds a Polish catalog (i18n/pl.rs, 1,698 strings), registers Polski in
Edit › Interface Language, uses category-first count captions for Polish
(as for Ukrainian) and adds catalog and language-switch tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DukHNvUY45rjRnvtz91x33
2026-10-13 19:16:32 +02:00
moppeeerandClaude Opus 5.5 370dca2ca2 feat(render): gradient strokes, on screen, in PDF and in the Gradient panel
A gradient swatch on a stroke drew nothing: strokes were painted with a
single resolved colour. LYH Studio episode 6 gives the loop on the right
page (9:13) and, at first by accident, the circle (1:19, "right now it
is on the stroke") a gradient stroke.

- doc: Stroke::gradient_angle / gradient_vector (as Fill's) and
  Stroke::as_fill(), the stroke's paint as a fill.
- render and PDF: a gradient stroke is painted like a gradient fill,
  across the path's box (dash gaps and arrowheads keep their paints).
- engine: object.gradient {target: stroke} edits the stroke's
  gradient (kind, stops, angle, the Gradient Swatch tool's from/to);
  scaling moves the stroke's vector with the shape.
- IDML: GradientStrokeStart/Length/Angle round-trip.
- Gradient panel: acts on the stroke when the toolbox's Stroke box is
  in front, as InDesign's follows the Fill/Stroke proxy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:11:58 +02:00
Saad Abdullah 3c070a8cdd fix(geom): decompose of a flattened transform reports 0 shear instead of NaN 2026-10-13 16:49:51 +00:00
MarekSpodBiedry 79b9971939 fix(pdf): leave form fields and media out of PDF/A-2b exports (#743) 2026-10-13 18:44:45 +02:00
MarekSpodBiedry 6599521305 fix: drop the background render when a display option changes (#730) 2026-10-13 18:43:26 +02:00
MarekSpodBiedry ec4ff485df Keep a missing graphic missing when it is pasted into another document (#720) 2026-10-13 18:43:16 +02:00
MarekSpodBiedry cab9200975 fix: eyedropper ignores inactive states of a multi-state object (#737) 2026-10-13 18:43:09 +02:00
MarekSpodBiedry dfb41f5aea fix(cli): reject --pdf-options that is not a JSON object instead of panicking (#731) 2026-10-13 18:42:57 +02:00
MarekSpodBiedry b8e7e8ede6 fix(render): cap the failed-image cache at 64 entries (#728) 2026-10-13 18:20:29 +02:00
MarekSpodBiedry e329cb8023 Fix overset preflight page for frames inside translated groups (#738) 2026-10-13 18:20:01 +02:00
aiongg 7ad3d12883 Merge indd-import: open .indd and .indt files through indd-utils 2026-10-13 16:18:53 +00:00
noah1400 a64c1350f6 Preferences: OK without vertical units keeps them (#806)
Preferences OK read the vertical units by indexing the dialog's
fields. A dialog opened through `ui.dialog.open` with horizontal
units only made that panic on the UI thread, outside the crash
guard, and the app exited. The units are now read with `get`, and a
ruler the dialog doesn't name keeps its units.
2026-10-13 18:18:08 +02:00
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 843d471451 Merge menu-wake: macOS menu-bar commands run at once (#784)
# Conflicts:
#	ROADMAP.md
#	apps/designcraft/src/native_menu.rs
2026-10-13 14:58:34 +00: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
aiongg 572f9d3d9e Merge editor-undo: story editor test finds the editor by its text input role 2026-10-13 14:58:08 +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
aiongg 368958ee2f fix(macos): a menu-bar command runs at once instead of on the next input (#784) 2026-10-13 14:51:52 +00:00
aiongg 6d9f6db23d test(story-editor): find the editor by its text input role 2026-10-13 14:50:21 +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