A story that ends with a paragraph return has an empty last paragraph. When
it didn't fit, DesignCraft marked the story overset at the end of its text
(Preflight: "Overset text: 0 characters"; the out port turned red).
InDesign doesn't: Montilla Design's Flex Layout file has a caption ending
with a return, fitted to its three lines, and InDesign reports no errors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Montilla Design's Flex Layout file names its body face "Regular " (with a
trailing space, as the designer's copy of Articulat CF did). Face lookup
already ignores spacing and case, so the text is drawn in Regular, but
font.list (Find Font) compared names exactly and reported the style
missing. It now uses the lookup's comparison (designcraft_fonts::same_style).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Guides in an IDML page (<Guide> children of <Page>) were dropped on import,
and DesignCraft's own guides weren't written. Montilla Design's Flex Layout
file keeps its 16 column guides on the B-Parent page; none came in.
Import: Location is measured from View Preferences › Ruler Origin (the
spread's top left, the page's, or the spine); Orientation, FitToPage (off:
a spread guide), Locked and ItemLayer are kept. Export writes each page's
guides under its <Page> in spread-origin coordinates, which is the ruler
origin DesignCraft writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
InDesign writes LeadingModel="LeadingModelAkiBelow" on [No paragraph
style] of every document, Western ones included, but only its Japanese
composers use the leading model. DesignCraft applied it to every paragraph,
so a 24 pt paragraph after a 32 pt one sat 34.53 pt below it instead of the
Roman 32.8 (4 pt space after + 28.8 auto leading) in Montilla Design's
Flex Layout exercise file, and its fitted captions came out overset.
The import now drops that default when no part of the package applies a
Japanese composer (HL Composer J / HL Single J). Leading models set on
text, other styles, or in documents that use a Japanese composer are kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
InDesign sets a text line when its baseline fits in the frame; the
descenders may hang below the frame's bottom. DesignCraft required the
baseline plus the descent to fit, so a frame sized to a heading's caps
oversets: Zero2Graphic's magazine tutorial draws 2.7682 cm frames for
103 pt headings (control bar, 4:12–5:44), and all three were overset.
The keep-lines IDML fixture's frames are now 236 pt tall, so they hold the
same 19 lines under either rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Tint row's value box was the egui Slider's own DragValue. The panel reads
the tint back from the document every frame, so egui dropped the text being
edited after the first digit (100 -> 6 -> 100 -> 1000, as #945 reports for
the CMYK fields), and a typed tint was never applied because the panel only
applied on the slider's drag_stopped().
The slider keeps applying on release, without its own value box; the value
is a widgets::NumField that buffers text while focused and applies on Enter
or focus loss, clamped to 0-100 %.
Part of #945.
The guard restored only the active document's content and selection, so a
command that edited through a nested successful command and then panicked
left the nested edit's undo entry (and no redo) behind, and a command that
opened or switched documents restored nothing at all. The guard now
snapshots every open document with its history and selection, the active
index and the journal, and restores them on a panic.
Regression tests: a nested edit followed by a panic leaves the history and
redo exactly as they were, and a command that opens a second document and
panics leaves only the original open.
A script whose later step failed kept the successful prefix in the document
but truncated the prefix's undo entries (and returned before the command's
aggregate undo entry was recorded), so the edits could not be undone at all.
A failed script now restores the document, selection and history from before
it started: all-or-nothing.
Regression test: frame.create followed by an unknown command is an error, the
document has no frame, nothing is left to undo, and the next edit undoes
normally. The one-undo-step success path still holds.
parse() expanded parameter entities with up to eight passes of whole-string
replacement, each pass cloning the text and re-inserting the entities'
original values. A pass count is not an output bound: a few hundred bytes of
nested entities inflate multiplicatively (a 449-byte DTD reaches ~4 MB here,
and larger chains would go to gigabytes), so a small file could exhaust
memory and CPU. Expansion now stops once it has added more than 1 MiB over
the input.
Regression test: a nested-entity DTD under 500 bytes is rejected with the
budget error, and a small inline entity still parses.
The command removed the section at a nonzero start with retain() and only then
looked it up to keep its properties, so the lookup always fell back to the
defaults: editing one field (a marker, a start number) silently reset the
style, prefix, include-prefix flag and start number. The existing section is
now read before the list is rebuilt.
Regression test: a Roman-numbered page-2 section with prefix A- and marker
First, then marker Second keeps startNumber, style, prefix and includePrefix.
A placement of a multi-page PDF carried no page number through IDML: graphic()
always made the asset with page 0 and keyed linked_assets by path alone, so
every placement of one file showed page 1 (InDesign's PDFAttribute PageNumber
was ignored). Import now reads PDFAttribute/@PageNumber (1-based) into
Asset.page, linked_assets is keyed by (path, page), and export writes
PDFAttribute back for pages past the first.
Regression test: the same file placed at pages 2, 1 and 2 imports as two
assets with pages 1 and 0 and round-trips through export.
Measured in Zero2Graphic's video: InDesign's placeholder paragraphs run 5,
10 (at 18 pt), 6 and 8 lines in a 5.9 cm column, about 2, 2, 3 and 3 of
DesignCraft's filler sentences. The rhythm started 3, 4, 2, 5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The panel was a row of text buttons (Add, Subtract, …, Make Compound,
Release). InDesign's has four rows of icon buttons: Paths (Join, Open,
Close, Reverse), Pathfinder, Convert Shape and Convert Point. The icons are
new drawings in icons.rs. Two engine commands back the rows that had none:
path.join (close an open path, or join two open paths at their nearest
ends) and path.convertPoints (plain, corner, smooth, symmetrical, on the
selected points or whole paths).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`math.svg` and `math.insert` take an unbounded `latex` string, and the parser's
arg/atom/list/list_until_right recursive descent recursed once per group: 100k
nested braces, `\frac`s or `\left(`s overflowed the stack (an abort the command
guard cannot catch). Past MAX_NESTING (256) groups stop descending, so the rest
of the input is consumed iteratively.
Regression test: 100k nested braces, `\frac`s and `\left(`s typeset to an SVG;
the old code aborted with a stack overflow.
InDesign's placeholder text comes in paragraphs; Zero2Graphic's magazine
tutorial styles its second paragraph as an 18 pt lead (2:16). DesignCraft
filled the frame with one paragraph, and the last filler sentence ran into
the first ("interit.Ovid"). Paragraphs now hold 2 to 5 sentences, and an
overset fill is trimmed to the last whole sentence.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
InDesign's Pathfinder result takes the attributes (and content) of the
frontmost object, except Subtract, which keeps the backmost. DesignCraft
kept the backmost for every op but Minus Back, so a photo placed over
strips and intersected with them was deleted and the black strips stayed
(Zero2Graphic's magazine tutorial, 7:19).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found while testing crates/doc/src/build.rs:377 (Document::insert_pages), called from crates/engine/src/cmd/layout.rs:91
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Conflicts in dialogs.rs: main's set_field kept (Dialog::s made pub(crate) for
new_document.rs), #325's New Document body re-seated in main's dispatch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Selecting an empty paragraph (a blank line) puts a collapsed caret on its
hidden paragraph mark, the only character on that line. Previously
type.char either deferred the formatting to the next typed character (in
a non-empty story) or dropped it on the floor for composition, so there
was no way to change a blank line's height — matching InDesign's ability
to select the pilcrow and adjust leading.
- engine/cmd/text.rs: recognize an empty paragraph's own caret position
(empty_para_mark) and apply character attributes straight to that
byte's run instead of deferring to typing format.
- compose/lib.rs: an empty paragraph has no glyphs of its own, so resolve
the hidden mark's run (if overridden) for the paragraph's base leading
instead of always falling back to the paragraph's plain default.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
delete_para and delete_char previously reassigned only the story text
runs/paragraphs using the deleted style, leaving other styles' Based
On, paragraph styles' nested/nested-line/GREP style refs, the drop cap
style, and the footnote reference style pointed at a name that no
longer exists.
Extract redirect_style_refs() from rename_style() (everything it does
except updating the registry entry's own name) and reuse it from both
delete commands, so deleting a style now redirects every reference
type to the replacement style before removing the registry entry.
Also teach rename_char_refs() about drop_cap_style, which it missed
even for renames.
Add validation: name must be non-empty and refer to an existing
style, and replaceWith (if given) must refer to an existing style and
can't be the style being deleted.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
InDesign's Preferences › Appearance of Black defaults to Display All
Blacks as Rich Black on screen, so [Black] text and strokes look pure
black (RGB 0,0,0), as in Adobe's magazine-layout tutorial. DesignCraft
defaulted to Display All Blacks Accurately (#343334). Settings saved
with an explicit choice keep it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Margins sit in Top/Bottom and Inside/Outside pairs (Left/Right on a
non-facing document) with a link button between them that makes all four
the same; Columns has Number and Gutter. Create Guides puts Rows and
Columns side by side, with Fit Guides to Margins/Page and Remove Existing
Ruler Guides under Options. Both have a Preview checkbox that previews the
change through a session interaction (one undo step on OK, nothing on
Cancel), and a measurement field works out arithmetic when it loses focus.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The object's name turns into a field: Enter or a click elsewhere renames it
(object.rename), Esc leaves it as it was, and renaming to nothing goes back
to the automatic label. Layer rows keep double-click for Layer Options
(#416).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adobe's "Set up a modern magazine layout" (Brian Wood, 2:12–4:23) locks a
page frame drawn round the spread (Cmd+L), then draws the text frames on
top of it with the Type tool. In DesignCraft the press landed in the
locked frame, which the Type tool turned into a text frame, putting the
caret in it; no new frame was drawn.
- The Type tool takes a hit only on text frames (locked or not, as
InDesign lets you edit their text) and unlocked empty frames; over a
locked empty frame, the press starts a new text frame.
- text.placeCaret refuses to convert a locked empty frame (or one on a
locked layer).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file dropped on the window is placed on the spread under the pointer,
or the spread under the view centre when the pointer isn't over the
canvas, a parent spread included while parents are being edited: a
graphic's top left goes to that point, text into a new frame on the
document page there. Before, every dropped file went to the first
spread.
Georgian is a Properties text language (IDML "Georgian" maps to ka). Georgian has no initial capitals: Mtavruli is only for all-caps text, so Change Case title and sentence case and index section heads leave Mkhedruli as it is (doc::capitalizes); upper case still gives Mtavruli.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
Adds i18n/ka.rs (1,734 entries: every key of the shared, Arabic, Brazilian Portuguese, Japanese, Italian and Ukrainian tables, plus labels only the code and command table use), registered as ქართული in LANGUAGES and the Interface Language menu. Count captions use the category-first form Ukrainian already uses (გვერდი: 5), since Georgian nouns stay singular after a numeral. Tests: every Georgian key resolves, placeholders and ellipses are preserved, no Mtavruli, every menu title, item and submenu is translated, and switching to ka renders. Docs: localization-parity.md and ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF