Commit Graph
2702 Commits
Author SHA1 Message Date
moppeeer b6b1b86b0a feat(ui): Tint and Apply To in the Properties Fill and Stroke menus
InDesign's Fill and Stroke menus in Properties › Appearance have a Tint
field and, for a text frame, Apply To: Frame or Text. DesignCraft's
listed only the swatches, so a tint needed the Swatches panel and a
text frame's text colour needed a text selection.

The menus now have both: the tint applies to the fill or stroke of the
frame (object.fill / object.stroke) or of its text (type.char fillTint /
strokeTint); Apply To: Text colours the whole story. With text selected
the menu applies to the selected text, with its tint.

fix(ui): A tint keeps each object's swatch, and no stroke stays none

Changing the Tint in the Fill menu sent the first object's swatch with
it, so with several objects selected every fill became that swatch; and
a Stroke tint on a frame without a stroke gave it the default 1 pt black
stroke.

object.fill now takes a tint alone and keeps each object's swatch, and
object.stroke with only a tint leaves a [None] stroke alone; the menu
sends just the tint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:46:03 +02:00
moppeeerandClaude Opus 5.5 73d3be1290 fix: A selected graphic's Transform values are the graphic's
With a placed graphic selected, transform.set acted on the graphic but
the Control bar and Properties still read the frame's values: after
setting the image of a -45° frame to 0°, a spinner step sent -44, and a
200 pt frame holding a 400 pt image showed W 200, so 201 halved the
image. transform.info now reports the graphic's values at the top level
in that case, and sel_info gives the graphic's bounds.

Also: `id` names the target explicitly, like `ids`, so it isn't taken
as a content selection; and a content selection of locked frames
records no empty undo step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:42:39 +02:00
moppeeerandClaude Opus 5.5 0eb0aaa53c fix(engine): Transform values act on a selected placed graphic, not its frame
With a frame's content selected (Direct Selection, or selection.set
{content: true}), transform.set still rotated, scaled and moved the
frame. It now transforms the placed graphic inside the frame and leaves
the frame alone, as InDesign's Transform panel does: rotation, shear and
scale are absolute and measured as transform.info reports the content;
x, y, width and height are the graphic's bounds on the page. A
`content` parameter picks either explicitly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:42:39 +02:00
moppeeerandClaude Opus 5.5 812df5b3d3 fix(tools): Fit along the frame's own axes; skip locked frames; fit text by its layout
Side handles map to the frame's local axis through spread and frame
rotation. Graphic frames honour the axis. Locked frames and frames on
locked layers are left alone. Text frames fit by searching the frame
height with its own columns and vertical justification, so two columns
stay two columns and Bottom/Center/Justify don't inflate it. A mouse
double-click on a handle fits (main's canvas counts clicks on the press);
a UI test covers it with one undo step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:42:26 +02:00
moppeeerandClaude Opus 5.5 45d803c6a4 feat(tools): Double-click a frame handle to fit the frame to its content
In InDesign double-clicking a selection handle fits the frame to its
content: a corner fits both sides, a top or bottom handle the height, a
side handle the width. LYH Studio's tutorial uses it at 9:30 to fit the
INDESIGN text frame before moving it to the corner. In DesignCraft the
double-click switched to the Type tool.

- Selection tool: a double-click on a handle runs object.fit
  {mode: fitFrameToContent, axis: both|height|width}.
- object.fit fitFrameToContent fits text frames to their composed text:
  the bottom to the last line's descent, the right side to the longest
  line (one column), plus the insets, keeping the top-left. Overset text
  grows the frame first. Before, it fitted graphic frames only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:39:42 +02:00
moppeeerandClaude Opus 5.5 a4c2b3031c 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-13 09:34:32 +02:00
moppeeerandClaude Opus 5.5 a805a5f4e6 feat(ui): The Effects dialog, as in InDesign
Object > Effects (Transparency..., Drop Shadow... and the rest, and their
shortcuts) and the Effects panel's fx button open the Effects dialog on
the chosen effect, turned on: the effect list with checkboxes on the left
and a summary line, the chosen page's settings on the right in the
document's units (Blending, Position with X/Y offsets, Options...),
Preview on the page while it is open, Cancel putting the page back, and
OK as one undo step.

- engine: object.effects {effects (merged, kept in range), opacity, blend}
- doc: drop and inner shadows default to InDesign's 120 degree light
- ui: dialogs::cancel (a previewing dialog reverts)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:46 +02:00
moppeeer d68b59e538 Integrate Windows window controls into the menu bar 2026-10-13 09:33:23 +02:00
moppeeerandClaude Opus 5.5 65bb0e4823 docs: Compact Control panel in the progress log
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:22 +02:00
moppeeerandClaude Opus 5.5 b57d96a583 Label the object Control panel's captions and chips for screen readers
Put the compact object groups inside the Control bar's existing sideways
scroll area, and give the painted captions (X:/Y:/W:/H:, icon captions,
Columns) and the Fill/Stroke chips accessible labels so tests and screen
readers find them. The Fill and Stroke chips open with nothing selected,
as before, so a popup opens at a scrolled control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:22 +02:00
moppeeer f2a4a1ce97 Compact the Advanced object Control panel into aligned rows 2026-10-13 09:33:22 +02:00
moppeeerandClaude Opus 5.5 f4a980a201 docs: New Document in the progress docs
Rebased on the progress-docs restructure (#355): the ROADMAP bullets this
PR added moved to the inventory in docs/target-app-parity.md, and the
ROADMAP progress log gets a line for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:21 +02:00
moppeeerandClaude Opus 5.5 c05a2e922f fix(ui): Keep Enter and Escape in the Save Document Preset popup; saved presets in the engine
Review follow-up. Enter in the Save Document Preset popup created the
document and Escape closed the whole dialog: the dialog footer took
both keys while a popup was open. The dialog now leaves Enter and
Escape to an open popup (read before it draws, as egui closes a popup
on Escape that frame): Enter in the preset name saves the preset,
Escape closes only the popup. Clicking the name field no longer closes
the popup either (menus close on any click; this one only outside), and
the field takes focus when the popup opens.

Saved presets lived only in the UI state, so `file.new {preset: name}`
refused them. They are now engine preferences (saved with prefs.json):
`file.savePreset {name, …the file.new settings}` replaces a preset of
that name and refuses built-in names, `file.deletePreset {name}` removes
one, `file.presets` lists them after the built-in ones (`saved: true`),
and `file.new {preset}` makes documents from them by name. The Saved tab
reads and writes them through those commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:20 +02:00
moppeeerandClaude Opus 5.5 f84c2537f8 feat(ui): Lay out the New Document window like InDesign's
File > New > Document now shows Recent, Saved, Print, Web and Mobile
tabs of page cards (drawn to the preset's proportions; double-click
creates) beside the preset's details: document name, width and height
with arrows, units, orientation, pages, start page #, facing pages,
primary text frame, columns and gutter, and collapsible margins and
bleed and slug per edge with link toggles. Save Document Preset keeps
presets by name; documents made are remembered for the Recent tab.
Small windows stack the details above the presets and scroll; the
layout mirrors for right-to-left languages. New strings go through the
translation tables (Italian, German, French, Spanish, Japanese, Chinese).

file.new takes intent, units, startPage and per-edge bleed and slug.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:20 +02:00
moppeeerandClaude Opus 5.5 9d666f3dc8 fix(ui): Show the Pencil stroke while drawing
The Pencil tool's stroke only appeared once the mouse button was
released. The tool reported the stroke so far as a path overlay, but the
canvas skipped path overlays (they were never drawn), so nothing showed
while drawing. The canvas now draws them: curves flattened to about a
quarter of a screen pixel, dashed when asked. That also brings back the
Smooth and Erase tools' dashed drag previews.

The Pencil's preview was black, lost on a dark pasteboard; it is drawn in
the colour of the layer the path goes on, as InDesign does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:18 +02:00
moppeeerandClaude Opus 5.5 e1936cbfd0 fix(ui): Highlight swatch list rows and keep panels clear of scroll bars
Swatch lists (Character panel, Properties, task bar) shaded the row under
the pointer with the theme's hover grey, a few levels from the popup's
own background, so it looked as if nothing changed. The row under the
pointer is now white on blue, like a menu; the current swatch keeps its
shading. The lists also grow to fit their longest name, and their rows
stop short of the scroll bar that floats over the list's edge, so names
like C=15 M=100 Y=100 K=0 are no longer cut by it. The four copies of
the list are one shared function now.

The Glyphs panel counted its columns from the panel's whole width, so
the last column ran under the floating scroll bar and was cut off; the
columns now leave the bar room.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:18 +02:00
moppeeerandClaude Opus 5.5 540317310d fix(ui): Document Setup keeps each page's margins unless they're edited
OK sent the first page's margins every time, and the engine writes them to
every page and parent page, so changing only the page count overwrote
each page's own margins. The margins now go only when they were edited.

Measures are read as every dialog reads them again, leaving bare-number
units to the shared change in #468.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:17 +02:00
moppeeerandClaude Opus 5.5 f1e6c379c0 feat(ui): Lay out Document Setup like InDesign's
File > Document Setup now follows InDesign's layout: Intent, Number of
Pages and Start Page # with Facing Pages and Primary Text Frame (shown,
set when the document is made) beside them, Binding; a Page Size group
(preset menu keeping the orientation, width and height with arrows,
orientation icons); a Margins group (top, bottom, inside, outside, or
left and right without facing pages, with a link that keeps them equal);
Bleed and Slug (collapsible, a column per edge, a link per row); and an
Adjust Layout box whose button applies the changes with objects
following the new page size and margins.

Measures are read in the document's units (a plain 12 is 12 mm in a
metric document, not 12 picas). OK sends one layout.documentSetup (one
undo step); a value that can't be read keeps the dialog open with the
reason in the status bar. New strings are translated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:17 +02:00
moppeeerandClaude Opus 5.5 722ebf4829 Repaint a moved text frame's whole text box
Text is composed in the box around a frame's path in the frame's own
coordinates. A sheared then rotated frame turns that box past its path's
bounds, which was all a move repainted, so its text left trails behind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:16 +02:00
moppeeerandClaude Opus 5.5 e6e2ff50ed 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-13 09:33:16 +02:00
moppeeerandClaude Opus 5.5 6f47f10b4b fix(render): Blend modes don't blend with the paper
The renderer painted the paper, then blended page items with it: black
text in Color Dodge over blank paper dodged the white to white and
vanished, and other modes showed the same way. In InDesign (and in PDF,
where the page group starts transparent) the paper is not a backdrop:
over blank paper an object in any blend mode shows as it is, and only
the objects below it are blended with. On spreads that use a blend mode
the items are now drawn in their own group, laid over the paper once;
other spreads render as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:13 +02:00
moppeeerandClaude Opus 5.5 5b74bd71e4 fix(render): A group's gradient feather masks everything the group draws
The DestIn mask now covers the whole render target (and the PDF's
exported area), with the gradient kept in item space through the paint
transform, since past the gradient's ends the end opacity carries on: a
child's long drop shadow or thick stroke is faded, not cut off at a box.
feather_bounds counts only shown children and active states, walking
nested groups without recursion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:12 +02:00
moppeeerandClaude Opus 5.5 6f4fa3e56d fix(render): A group's gradient feather fades the group, on screen and in PDF
A gradient feather on a group (Gradient Feather tool over grouped text, as
in LYH Studio's poster tutorial) did nothing on screen: groups never went
through the effects path. In PDF the group's mask was the group's own path
bounds, and a group has no path, so the mask covered a box of zero size and
hid the group.

- Item::feather_bounds: the box the feather spans, the objects' bounds for
  a group.
- Render: a feathered group draws its objects in one layer, then fades it
  with the same mask as other objects (fx::gradient_feather_mask).
- PDF: the group's mask spans its objects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:12 +02:00
moppeeerandClaude Opus 5.5 048d8843bf perf(render): CMYK blending in two ink passes, for every blended object
Separable blend formulas work channel by channel, so the spread is drawn
twice through the ordinary renderer: C, M and Y complements into RGB, K
into a second raster, with the existing transparency groups, masks,
effects and parent-page placement. The inks are converted to sRGB once and
put over the paper. Two passes regardless of how many objects blend;
parent items, nested blends and effect blends (Multiply satin, shadows)
blend in CMYK too, and the pass covers the whole view, so blending
continues past the trim. An unchanged view is reused from a one-frame
cache (64 MiB cap). Spreads with Hue, Saturation, Color or Luminosity keep
the RGB-then-proof approximation. Effect blend layers sit outside their
clips and offscreen filters, so they see the object's backdrop (RGB too).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:11 +02:00
moppeeerandClaude Opus 5.5 eb89eef4ad docs: CMYK blending on screen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:11 +02:00
moppeeerandClaude Opus 5.5 f51aba86a8 perf(render): look the colour tables up once per pass
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:10 +02:00
moppeeerandClaude Opus 5.5 803c898d72 feat(render): Blend modes blend in CMYK on screen in a CMYK blend space
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:10 +02:00
Brandon ThomasandClaude Opus 5.5 e862e0a613 fix: integrate batched community PRs (#572 with #662's fallible spread_param; #524's test helper back in its module)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:28:50 -04:00
Brandon Thomas 777d8c3251 Merge branch 'pr/572' into batch1
# Conflicts:
#	crates/tools/src/tests.rs
#	crates/ui-egui/src/i18n.rs
#	crates/ui-egui/src/i18n/ar.rs
#	crates/ui-egui/src/i18n/it.rs
#	crates/ui-egui/src/i18n/ja.rs
#	crates/ui-egui/src/i18n/pt_br.rs
#	crates/ui-egui/src/i18n/uk.rs
2026-10-13 02:24:24 -04:00
Brandon Thomas d1452a3e3f Merge branch 'pr/524' into batch1
# Conflicts:
#	crates/ui-egui/src/panels/properties.rs
2026-10-13 02:23:58 -04:00
Brandon Thomas 99c229486d Merge branch 'pr/575' into batch1
# Conflicts:
#	crates/ui-egui/src/i18n.rs
#	crates/ui-egui/src/i18n/ar.rs
#	crates/ui-egui/src/i18n/it.rs
#	crates/ui-egui/src/i18n/ja.rs
#	crates/ui-egui/src/i18n/pt_br.rs
#	crates/ui-egui/src/i18n/uk.rs
2026-10-13 02:23:57 -04:00
Brandon Thomas 89e9abbc8d Merge branch 'pr/662' into batch1
# Conflicts:
#	crates/engine/src/cmd/object.rs
2026-10-13 02:23:56 -04:00
Brandon Thomas a390905beb Merge branch 'pr/647' into batch1
# Conflicts:
#	crates/tools/src/lib.rs
#	crates/tools/src/tests.rs
#	crates/ui-egui/src/canvas.rs
2026-10-13 02:23:33 -04:00
Brandon Thomas bd9974a420 Merge branch 'pr/552' into batch1 2026-10-13 02:23:12 -04:00
Brandon Thomas ea8dc959d1 Merge branch 'pr/573' into batch1 2026-10-13 02:23:12 -04:00
Brandon Thomas 41ce560ce3 Merge branch 'pr/541' into batch1 2026-10-13 02:23:12 -04:00
Brandon Thomas 0a14d9fb97 Merge branch 'pr/539' into batch1 2026-10-13 02:23:12 -04:00
Brandon Thomas 2103af784a Merge branch 'pr/537' into batch1 2026-10-13 02:23:11 -04:00
Brandon Thomas 055da9f788 Merge branch 'pr/536' into batch1 2026-10-13 02:23:11 -04:00
Brandon Thomas 160ae04b8b Merge branch 'pr/527' into batch1 2026-10-13 02:23:11 -04:00
Brandon Thomas d5613411ca Merge branch 'pr/522' into batch1 2026-10-13 02:23:11 -04:00
Brandon Thomas d6d26d2b38 Merge branch 'pr/519' into batch1 2026-10-13 02:23:11 -04:00
Brandon Thomas 1f6ceea657 Merge branch 'pr/634' into batch1 2026-10-13 02:23:11 -04:00
Brandon Thomas eddf681df1 Merge branch 'pr/677' into batch1 2026-10-13 02:23:11 -04:00
Brandon Thomas 6e551af3e7 Merge branch 'pr/663' into batch1 2026-10-13 02:23:11 -04:00
Brandon Thomas b758c6180a Merge branch 'pr/649' into batch1 2026-10-13 02:23:11 -04:00
Brandon Thomas 9bd099c0ae Merge branch 'pr/639' into batch1 2026-10-13 02:23:11 -04:00
Brandon Thomas bff290c9bd Merge branch 'pr/607' into batch1 2026-10-13 02:23:11 -04:00
Brandon ThomasandClaude Opus 5.5 b70b586a16 fix(path-type): NaN-safe bracket clamping (f64::clamp panics on a NaN path length)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:22:34 -04:00
Brandon Thomas 878169130e Merge branch 'pr/600' into batch1 2026-10-13 02:21:53 -04:00