They were left behind in place_paths when the PDF's Import Options opened, so the
PDF was placed alone and the rest of what was chosen never loaded. They now wait in
the dialog and carry on when it is confirmed; Cancel ends the batch.
Appending only checked each batch against the cap, and a single image or a PDF
added with append wasn't checked at all, so repeated appends could grow the
cursor without limit. Refused before decoding a batch or adding an asset.
With a graphic frame selected, `file.place` chose that frame before it looked at the place
cursor, so a PDF confirmed with `append` filled the frame and left the images around it queued.
An appended file now skips the selected-frame fallback; an explicit `frame` still applies, and a
single placed file still fills the selected frame.
Esperanto in Character › Language (panel, Properties, style options),
mapped to the BCP 47 tag eo, with English-style typographer's quotes.
IDML keeps the name as written ($ID/Esperanto). Not hyphenated yet:
morpheme-based hyphenation follows in #1201.
Claude-Session: https://claude.ai/code/session_01Y9j7XJDCmPc8UZLs9cTxtu
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
From review: a copied graphic keeps no story, so there is nothing to scan; and anchored_items already lists a group's members, so walking each item again visited them twice through unbounded recursion. A text frame inside an anchored group (as IDML import anchors an InDesign group) is tested.
clip_doc kept the stories of the text frames among the copied items, and a text frame anchored in a frame's text (as IDML import makes them) is in its host's story, not among them. Its story was dropped from the clipboard, and the paste gave the anchored frame a new empty story. The stories of text frames anchored in the kept stories, and in theirs, are kept too.
* Pages: more parents and alternate layouts as columns in the Pages panel
Parents
- layout.parents.duplicate copies a parent spread (pages and items) under the next free prefix.
- layout.parents.options also renames the prefix (1-4 characters, no "-") and the name; basedOn is now optional.
- layout.parents.new validates prefix and name and takes basedOn.
- Pages panel: a "+ New Parent" row adds B, C… in one click. The parents' context menu and the panel menu have New Parent…, Duplicate Parent Spread, Parent Options… and Delete Parent, named after the selected parent, or a submenu of the parents when none is selected. A new Parent Options dialog has Prefix, Name and Based on Parent.
Alternate layouts
- Section.layout marks the section that starts a layout; Document::layouts() lists them.
- layout.createAlternate copies one layout (from), not every page; new layout.alternate.duplicate, .delete and .options (rename, resize).
- Each layout starts a spread of its own; sections now follow their pages when pages are inserted or deleted.
- Pages panel: with more than one layout, each is a column side by side with a header (name, size, ▾ menu: create, duplicate, options, delete) and a + column to add one.
Tests cover the commands' happy and error paths, undo, serde, and the panel's menus and columns.
* Pages: address review of parents and alternate layouts
- Deleting a layout's first page keeps the layout's name when another section lands on the same page.
- A parent's full label (prefix-name) must be unique; parents may still share a prefix.
- Alternate layout names round-trip through IDML (Section AlternateLayout / AlternateLayoutLength).
- The Pages panel's selected parent is per document and no longer saved in ui.json.
- Layout ▸ Parent Options… and Alternate Layout Options… open the Pages panel's dialogs.
- The Pages panel computes the layouts once per frame.
* Export: layouts and versions in one export, with presets; print chosen layouts
- file.exportBatch writes the chosen alternate layouts (a file per layout, or all in one PDF) in several versions at once: PDF with any marks, bleed and standard, PNG or JPEG at a resolution. Each version goes in a folder of its own, and file names come from a template ({document}, {layout}, {version}, {date}). Names are made safe and unique. dryRun lists the files; without a dir the files come back as base64 (web).
- Export presets: file.exportPresets, file.saveExportPreset, file.deleteExportPreset, kept in prefs.json. Built-ins: Print + Screen, Press PDF per Layout, Images per Layout, All Layouts in One PDF.
- file.print takes layouts and separateJobs (a job per layout, titled after it).
- File ▸ Export Layouts and Versions… dialog: preset, layouts, versions (add from templates, duplicate, remove, quick PDF choices or custom marks), name template, and a live list of the files it will write. It opens from a layout column's ▾ menu as "Export Layout…". The desktop asks for a folder; the web downloads each file.
- The Print dialog prints chosen layouts, together or separately; the Export PDF dialog fills the page range from a layout.
- crate::io::create_dir_all keeps folder creation inside the automation write root.
* Export: address review of layouts and versions
- An exported image is capped at 80 million pixels, the limit EPUB page images already use.
- The Print dialog stays open and says why when no layout is chosen.
- Clicking a layout column's header leaves Edit Parents before going to the layout's first page.
* Pages, Export: address maintainer review of layouts and versions
- Sections (and the layouts they start) follow a moved page, as they do inserted and deleted
ones: moving a page across a layout boundary no longer moves the other pages between layouts.
Delete and move share `settle_sections`.
- Alternate Layout Options resizes only pages whose size changes: renaming a layout no longer
forces every page to the first page's size.
- Export file and folder names that are Windows device names (CON, NUL, COM1…) get a leading `_`.
- An export without `dir` refuses past 512 MB of files instead of building them all in memory.
- A file that fails partway through an export to a folder says how many were already written.
- Parent Options and Print keep the dialog open, with what was typed and why, when the engine
refuses.
- A duplicated layout's default name stays within the 100-character limit.
- rustfmt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Pages: Alternate Layout Options sends the size only when it changed
The dialog starts at the layout's first page's size, so the engine couldn't tell a rename from a
resize to that size. The dialog now leaves out a width or height still at its starting value, and
`layout.alternate.options` resizes every page of the layout when a size is given, as before.
The test moves to the UI, where the size was coming from.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* About: credits tables fit without side-scrolling and their columns resize
The Contributors table and the Models table were egui Grids inside a
two-way scroll area: they opened wider than the dialog, so they scrolled
sideways, and Company and Model sat almost touching. Both are now
egui_extras tables. Each column opens at the width of its header and widest
cell, the spare width goes to the text columns (Name; Company and Model),
and every column edge can be dragged. Widening a column grows the table
like a spreadsheet, and only then does it scroll sideways; the header stays
put while the rows scroll. Counts and dates are right-aligned and every
cell is vertically centred.
The sorted column stays highlighted and its direction shows on the
toolbar's sort-order button, so headers no longer reserve room for an
arrow. The opening widths come from fit_widths, a pure function with unit
tests that a fitted table fills its room exactly and that only the text
columns give way when room is short.
The About dialog opens 820 points wide, so every column fits at its natural width.
The workspace egui_extras entry, which no crate used, drops its svg feature, so the table
brings in no second SVG renderer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Apply review fixes
- crates/ui-egui/src/about.rs
- crates/ui-egui/src/credits.rs
* Let every credits column be dragged, and refit changed tables
The last column was egui's remainder column, which has no drag handle, so
it could only be resized from the edge before it. It is now an ordinary
column with its own handle.
A table keeps its column widths, so switching to a longer name left the
Name column at its old width and clipped the text. The table now goes
back to the opening fit whenever that fit changes: a longer widest cell
or a resized window. Dragged widths are dropped at that point.
fit_widths could overflow the room when one column hit the minimum,
because the other columns did not take up its share. Columns now give
way in proportion to their width above the minimum.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Refit credits tables on what they measure, not the fitted widths
Comparing the fitted widths missed the case it was meant for. Name is the
only growing column, so a longer name takes its extra width from the
spare room and the fit comes out the same: a dragged Name width stayed,
clipping the longer name.
It could also drop dragged widths by accident. The fit comes from the
available width inside the scroll area, which scrolling can nudge, so
scrolling a widened table could reset it.
The table now compares the natural widths and the clip rect's width,
which scrolling does not move.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
The preview's 6 pt duplicated place.grid's default, so changing one would leave the preview out of step with what lands. The tool now sends its gutter with the command.
Confirming the PDF's Import Options placed it without append, so in a batch such as [a.png, b.png, multi.pdf] it replaced the images already in the cursor. The dialog now carries append, on both the native and the delivered-file paths. A single multi-page PDF still replaces what the cursor holds.
The preview divided the dragged rectangle on the canvas and the release divided it on the spread, so on a rotated spread two columns previewed across the canvas and landed down it. The preview now takes the release's cells and shows each where it lands on the canvas.
Review fix for #2309: set_active, add_document and close_document emptied the place cursor but
kept the placeGun tool, so the next canvas click failed with 'nothing loaded in the place
cursor'. One helper, Session::unload_place_cursor, empties the cursor and its queue and goes back
to the Selection tool when the place cursor's tool is active; validate_place_cursor uses it too.
Test: changing_documents_unloads_the_place_cursor_and_its_tool.
* M5: preserve out-and-back cubics in striped strokes
A cubic can travel out and back when its endpoints and first control point coincide. Include the second control point in the zero-length guard and cover its offset extent and both stripe bands.
* M5: record striped cubic regression coverage
Record the scoped cubic stroke correction without changing readiness estimates or other progress entries.
package.sh built its own Arch package with nfpm while the shared release
workflow also built one with makepkg, because has-arch defaults to true. Both
wrote designcraft-<version>-linux-<arch>.pkg.tar.zst, the draft release merges
the release-* artifacts last-writer-wins, and 0.17.0 shipped the nfpm copy,
which declared fewer dependencies than the makepkg template.
Drop arch from the formats this script builds and remove the archlinux section
from nfpm.yaml, so the shared action is the single builder. It names the
package after the .desktop bundle id, declares the same provides/conflicts
against the AUR packages, and adds the optdepends nfpm could not express.
The binaries load their windowing libraries at run time, and xkbcommon's X11
keyboard module is a separate library from the core one. Nothing declared it,
so a package installed on a system without it crashes before opening a window:
thread 'main' panicked at xkbcommon-dl-0.4.2/src/x11.rs:59:28:
Library libxkbcommon-x11.so could not be loaded.
winit's X11 backend cannot fall back, so the dependency is required for deb,
rpm and Arch rather than recommended.
- Arabic, Czech, Hungarian, Italian, Polish, Portuguese (Brazil), Russian and
Ukrainian read only their own catalogs, so File › Place Contact Sheet… and
its dialog showed in English there. They now have the four new strings.
- Spanish Columns, Rows and Gutter were still English in the shared table:
Columnas, Filas, Medianil, as the Spanish catalog already says for
"Columns:", "Rows:" and "Gutter:".
Split out of #2073. A frame holding its image as an item pasted into it (how a
document from InDesign keeps a photo) takes a dropped graphic as a graphic
frame does: `canvas::drop_frame` uses `Item::takes_graphic` (#2226).
rename_swatch_refs visited every page item's fill and stroke but not the swatch applied to a placed greyscale or bitmap image (Graphic::fill), framed or in a table cell. Those images kept the old name, found no swatch, and drew in black.
The place cursor's grid preview fell back to the raw drag rectangle when the
cells had no positive size, while the release still sent `place.grid`, which
rejected the area. Both now use the same cell calculation (`grid_cells`), and
a grid with no room for its cells shows and places the single proportional
frame of an ordinary drag.
* warichu: split long runs in one pass instead of trying every length
The exact split tries every line length for every start, so its work grew with
the square of the run's length, and a document can set warichu on a run of any
length. Runs longer than 512 glyphs are now split where the running advance
reaches each line's share, keeping the break minimums. The glyphs drawn with the
run find their anchor by binary search instead of a scan per glyph.
* address review: warichu split picks the break closest to the target
Split out of #2073. Port of #1392's contact sheet.
- `place.contactSheet` places every image in the place cursor on new pages
after the last one, in a grid of equal frames (columns, rows, gutter,
page margin), as one undo step; a bad grid or margin changes nothing.
- The dialog reads bare numbers in the document's unit, from the menu and
from `ui.dialog.open` alike; a mistake keeps it open with what was typed
and why. Its fields have accessible names; strings in every language.