A line clears a Jump Object wrap with its leading box, [baseline - leading,
baseline]. It stays above the object while its baseline is at or above the
wrap top, so its descenders may hang into the wrap. The next line moves down
by whole leadings from the line above, after any space before or after,
until its leading box clears the wrap bottom. A line that would start a
column under an object starts as if the frame top were the wrap bottom (the
frame's first baseline offset below it). Wrap offsets, negative ones
included, set the wrap's edges.
Other wraps that leave no usable slot, such as a full-width bounding box,
push the line so its top lands exactly on the wrap bottom: the search jumps
from one exclusion bottom edge to the next. Lines aligned to the baseline
grid snap to the next grid line after a push and are re-checked, so they
stay on the grid. The breaker's slot estimate uses the same rules.
Remove links to the deleted localization-terminology.json from ROADMAP.md
and the Chinese/Arabic translation module comments. Preserve the scope
statement about verified Adobe help terminology and original labels.
Validation: cargo xtask ci passed fmt, clippy, tests, assets and layers.
WASM checks remain blocked by the missing wasm32-unknown-unknown standard
library in the local toolchain. Repository search finds no remaining
references to localization-terminology.json.
Reuse wrapped and shaped RTL galleys across frames using a bounded,
context-local cache keyed by the complete LayoutJob. Invalidate cached
results when egui replaces its font-cache token, including font, DPI and
atlas changes. Preserve existing wrapping and elision behavior.
Add regression coverage for cross-frame reuse, layout parameter changes,
font/scale/atlas invalidation and cache eviction, plus an opt-in benchmark.
Validation: cargo xtask ci passed fmt, clippy, 590 tests, assets and layers.
WASM checks are blocked by the missing wasm32-unknown-unknown standard
library in the local Homebrew Rust toolchain (E0463).
Arabic menu screenshots remained identical after ten additional frames.
Manual benchmark: 200 wrapped labels took 31.46 ms uncached vs 0.082 ms
with final-layout cache hits on this machine.
tr() uses a OnceLock<HashMap>, initialized once, changing lookups from a linear scan to average O(1).
Labels without RTL text, such as Chinese, Japanese, and Western-language labels, skip BidiInfo. Arabic, Hebrew, and directional controls retain their existing handling.
Window, Utilities, Data Merge lists the linked fields and previews a
record without merging. Create Merged Document runs the merge. The
desktop open dialog offers CSV, TSV, tab, text, and Excel files. The
roadmap records this phase of the feature.
Select, update, and remove a linked source. Placeholders cover text,
images, QR codes, and hyperlinks. Preview fills one record in the
session and is left out of save and recovery. Merge copies the template
into a new document, one record per page, or tiles frames on a single
non-facing page. A missing or changed file is marked and the cached
rows are still used. The template undo stack is left as it was.
Records keep empty cells inside the header width and cap the table at
100,000 rows and 200 columns. Place as a table still uses the existing
Excel import, which drops empty cells.
A document keeps one data source, the placeholders that point at its
fields, and the last merge layout. Empty merge state is omitted from
the file. New documents and IDML imports start with that state empty.
Port the CJK1 and AR1 changes from 3ecdb78 and de83cdc onto current main. Resolve translation and font-stack conflicts by retaining main localization and craft-fonts integration while preserving all typesetting code, tests, and documentation.
Validation: all six CI gates passed, including 616 tests and WASM checks.
Scale: a small square grown into a larger one from their shared corner,
with a diagonal arrow. Shear: a rectangle slanted into a parallelogram
along its base, with an arrow. Both are drawn in code in the icon set's
stroke weight; Free Transform keeps its drawing.
Resizing or drawing at a zoom where the snap zone covers the baseline
grid's spacing (66% and below with a 12 pt grid) always snapped to a
baseline line, so frames could not reach the top or bottom margin. The
baseline grid and the guides (ruler guides, margins, columns) now
compete by distance and a tie goes to the guide.
Frames drawn with the Type tool snapped nothing; they now snap like the
frame tools' (press point and moving edges, with Shift and Alt).
- Double-clicking a text frame with the Selection tool edits its text. The
canvas counts presses itself (egui reports double clicks only on release,
so the press never saw them): 2 clicks select the word or run of spaces
under the pointer, 3 the line, 4 the paragraph, 5 the story
(`text.selectAt`; pointer events carry `clicks`).
- The caret moves past spaces typed at the end of a line: trailing spaces
follow the line undrawn instead of being dropped.
- Bold and Italic (Shift+Cmd+B / Shift+Cmd+I, B and I in the Character
panel) toggle the family's own styles per run, keeping the other trait
(Italic + Bold = Bold Italic). Formatting chosen at a caret applies to
what is typed next.
- Copy, Cut and Paste reach the system clipboard however they are chosen,
including from the native macOS menu; without a native menu the copy and
paste keys work for objects too.
- Formatting Affects Text (J, Tools panel) sends swatches, colours, tints,
None, Swap and Default to the text of the selected frames; the proxy and
the Swatches and Color panels show the text's colours.
.indd and .indt files (recognised by their published 16-byte signature)
failed to open or place with a confusing format error. They now explain
that the format is closed and how to export IDML from InDesign instead.
File › Open offers .indd and .indt files (desktop and web), and dropping
one on the window opens it like a .designcraft or .idml file. A file that
can't be opened or placed from File › Open, Place, a drop, the command
line or the control channel's app.open shows an alert with the reason, as
well as the status line.
A line that is not a JSON object with a string `method` (an HTTP request
line, other non-JSON text, a JSON array, invalid UTF-8, a line over 4 MiB)
now gets one error reply and the connection is closed, so a browser's
cross-origin fetch can't smuggle a command in its body. Connections are
capped at 16. Same transport as LightCraft (storytold/lightcraft#129) and
FilmCraft (storytold/filmcraft#123).
Each formatting run was shaped on its own, so a pair split by a colour,
underline or other paint-only change lost its kerning (and ligatures and
Arabic joining across the change). Pieces of a paragraph whose shaping
attributes are equal (everything but fill, stroke, underline and
strikethrough options, skew, ruby, kenten, conditions, tracked insertion
and XML tags) are now shaped as one run, and each glyph takes the style
of its cluster. A cluster that would span two styles (a ligature over a
colour change) is shaped apart, as before, so each letter keeps its colour.
A parameter written as `key (description)` was dropped by the command
form parser, so Object › Transform › Rotate… and Shear… (and Basic
Feather) had no required field, opened no dialog and ran with defaults.
The key is now the leading word and the parenthetical becomes its hint.
Double-clicking the Selection, Rotate, Scale or Shear tool opens Move,
Rotate, Scale or Shear.
Decode the merged composite of .psd/.psb files in designcraft-images
(public file format spec) instead of the psd crate, which mapped
channels by count: grayscale with extra alpha/spot channels rendered
yellow, blue or invisible, and RGB + one spot channel was read as RGBA.
Channels now map by colour mode (Bitmap, Grayscale, Duotone, Indexed,
RGB, CMYK via the uncalibrated CMYK formula, Lab; 8 and 16 bit; raw and
PackBits). The first extra channel is the alpha only when the layer
count is negative (also via Lr16/Lr32), un-matted from white; other
extra channels are ignored. Damaged or oversized files return None
without panicking. Drops the psd dependency and its panic paths.
check_pdfx4 didn't look at colour spaces, so a placed RGB PNG (or a CMYK
TIFF converted to RGB, #35) went into a PDF/X-4 file as DeviceRGB under the
CMYK output intent with an empty warnings list. PDF/X-4 lets RGB in only
when it is colour-managed: ICCBased, or DeviceRGB remapped by a DefaultRGB
colour space. DeviceCMYK and DeviceGray are the intent's own values.
The check now reads the file with hayro-syntax and, when the PDF/X output
intent's profile has four channels, walks each page: its content, the
images, forms (placed PDFs), patterns, shadings and soft masks it uses, and
the transparency groups. It reports DeviceRGB (or Indexed over it) images
with their object number and pixel size, RGB fills and strokes, RGB
gradients, RGB inline images and groups blending in RGB, each with the pages
it is on, unless a DefaultRGB is in scope. Like the other PDF/X findings
these are export warnings. The separate "some transparency groups still
blend in RGB" warning is gone: the group check covers it, with the page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A CMYK TIFF went into every PDF, PDF/X-4 included, as a DeviceRGB image:
load_image sent anything that wasn't JPEG, PNG, GIF or WebP through
designcraft_images::decode_rgba, and `image` decodes CMYK TIFFs to RGB. 100%
K came out as RGB black, which the press separates as four-colour black
(#35).
designcraft-images now reads a CMYK TIFF's ink values with the tiff crate
(the version `image` already uses): 8- or 16-bit, interleaved, opaque or
with unassociated alpha. PDF export writes them through krilla's CustomImage
as DeviceCMYK, as it does a CMYK JPEG, and leaves out the TIFF's own ICC
profile so the numbers go to the output intent's press unchanged. PDF/A
exports stay RGB. A CMYK TIFF this can't read (planar, premultiplied alpha,
another ink set, over 256 MiB) is still converted, now with a warning. The
screen keeps decoding to RGB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Noto Sans CJK SC and Noto Sans Arabic now live in storytold/craft-fonts
(byte-identical to upstream); the UI picks them up by script (Hans,
Arab) from CRAFT_FONTS, Chinese forms first in the zh interface, and
the fonts reinstall when the interface language changes.
- Rebased onto main: zh column for the vertical-type and smart-guide
rows added since; duplicate rows removed; ROADMAP merged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Extend interface language selection with Chinese and Arabic translations
across menus, dialogs, panels, tooltips, and common application controls.
Keep English fallback for untranslated strings, return borrowed translation
text, and validate translation key uniqueness and language switching.
Bundle redistributable Noto CJK and Arabic fonts with OFL license files and
ASSETS.md attribution so native and WASM builds have portable glyph coverage.
Register Arabic regular and semibold families while retaining existing font
fallbacks for proportional and monospace controls.
Render Arabic labels using Unicode bidirectional runs and contextual shaping.
Wrap logical text at grapheme boundaries before ordering each visual line,
mirror neutral punctuation, preserve logical source text for accessibility,
and handle truncation, empty lines, and mixed Latin text and numbers. Keep
ASCII labels on the normal rendering path. Disable selection on shaped RTL
labels because egui's default label cursor mapping assumes LTR text.
Adapt welcome controls, menu ordering, popup alignment, and new-document rows
for RTL layout. Bound popup geometry and avoid unsupported RTL grid behavior;
keep numeric inputs LTR. Isolate user-provided document names from Latin
application and zoom metadata. Interface language changes leave document
binding, paragraph direction, editable text, and engine state unchanged.
Add regressions for contextual Arabic joining, bidirectional punctuation,
mixed document names, logical wrapping, truncation, language switching, and
finite widget geometry. Update the roadmap with localization coverage.
Apply equivalent chunk-iteration and PDF Option/borrow simplifications needed
by the current Rust toolchain's strict Clippy checks.
Validation:
- cargo xtask ci: fmt, Clippy, native tests, assets, and layers passed;
native tests total 517 passed and 2 ignored.
- UI regression suite: 26 passed.
- Headless UI screenshots verified Arabic welcome/new-document creation,
menus, mixed-script document names, and switching back to Chinese/English.
- Default WASM gate is blocked by this host's missing precompiled wasm32
standard library. All 15 WASM crates pass using the installed Rust sources:
RUSTC_BOOTSTRAP=1 CARGO_UNSTABLE_BUILD_STD=std,panic_abort
target/debug/xtask wasm.
Dragging with the middle mouse button pans the canvas with any tool, like
Space-drag, and shows the grabbing hand. Tools still only see the primary
button. ui.click / ui.drag accept "button": "middle".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shippori Mincho (8.7 MB) and its licence leave assets/fonts. Japanese fonts now
come from storytold/craft-fonts when built with CRAFT_FONTS_DIR (never a
Cargo.toml dependency): crates/fonts/build.rs embeds its manifest as
designcraft_fonts::CRAFT_FONTS. Native builds embed all four fonts; wasm32
embeds BIZ UDPGothic Regular only (web size budget).
- UI (egui): the Jpan faces at the end of every family, BIZ UDPGothic first
(Bold first in the semibold family).
- Documents: FontDb loads them after the bundled fonts, Mincho first, so they
are the Japanese fallback ahead of system fonts; SVG text gets them too.
- Vertical alternates test runs on every craft-fonts face and skips when
CRAFT_FONTS is empty; new UI no-tofu test and without-craft-fonts tests.
- FreeBSD CI tests with craft-fonts; release.yml builds every platform with
CRAFT_FONTS_DIR + CRAFT_FONTS_REQUIRED=1 at a pinned commit, packages carry
OFL-<family>.txt; NOTICE, ASSETS.md, AGENTS.md/CLAUDE.md, README, ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Release builds add an arm64 MSI and portable zip, cross-compiled on the x64
runner (signing and WiX as for x64). package.ps1 checks each binary's PE
header before packaging: Machine must match -Arch, the app must be a GUI
program and the CLI a console program. It skips the CLI smoke run when an
ARM64 build can't run on the host. A new Windows ARM64 workflow packages the
ARM64 MSI, installs it on a Windows 11 ARM64 runner, checks both installed
exes are ARM64, runs the CLI, and uninstalls. Ported from printcraft#66.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- view.snapPreferences {alignEdges, alignCenters, smartDimensions,
smartSpacing, zone} sets the smart guide options; the Preferences
dialog goes through it, so the control channel and MCP reach them.
- Smart guide, gap and measurement colours come from theme::Tokens.
- Remove the unused closest-target helper (snap_rect and friends).
- Drop the agent planning docs (docs/superpowers) and the .worktrees
ignore line; ROADMAP entry for smart guides and snapping.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shift plus Alt restores the aspect from the original center after the size snaps. A line keeps the runner delta on both axes. Dimension guides are redrawn on the committed side. Grouped own size decomposes the absolute transform once.