Commit Graph
1054 Commits
Author SHA1 Message Date
aiongg 76c0b79760 CJK 1.10: Package copies the fallback fonts that draw text
Composition draws the characters a named font lacks with fallback faces
(the language's CJK chain, then the generic fallbacks). Package now also
copies their font files: it collects the distinct faces of the visible
glyphs of every composed story (table cells and footnotes included) and
copies those that aren't a named font's face by the same rules (restricted
licence listed, bundled included with DesignCraft, no file noted, each file
once). The report lists them under Fallback fonts.
2026-10-07 10:44:46 -04:00
aiongg 5f52150b31 CJK 1.9: document fonts belong to their document
The fonts in a document's Document Fonts folder are registered under a
font scope that the document holds while it is open (Document::font_scope,
runtime only; DocState keeps the scope alive and closes it when the last
copy of the state goes: file.close, closing a tab, revert, a book
chapter's export, the session ending). Composition, preflight, font.list,
Package and the font menus, Glyphs panel and previews look fonts up
through FontDb::scoped(scope): the document's own faces first, then the
shared ones. Other open documents don't see them, and two documents with
fonts of the same name each set text in their own. Composite fonts'
member fonts and the fallback for kenten marks are looked up the same
way.

Font files stay in memory for the session (FaceRef keeps faces for the
process); a document opened again reuses the faces of its unchanged files
(same path, length and modification time) instead of reading them again.
The composition cache's signature includes the scope; the word cache and
the outline and PDF font caches are keyed by face. Variable font
instances are kept per base face.

A family's installed faces join the shared fonts all at once, and a
face two lookups load at the same time is kept once, so a parallel
fallback lookup never takes a family's bold before its regular has
loaded.
2026-10-07 10:44:46 -04:00
aiongg e6e843dc26 CJK 1.8: arrow keys follow vertical lines
In vertical text Down and Up move along the line by a character (by a
word with Alt), Left and Right to the next and previous line keeping the
position along it, and Shift extends, as the arrows do in horizontal
text turned a quarter. The caret is a bar across the vertical line.
2026-10-07 10:44:46 -04:00
aiongg 6df33fa344 CJK 1.7: story direction belongs to the story
Vertical is a story property: every frame of a thread is vertical or
horizontal together, a frame threaded onto a story takes its direction,
and frames split off by threading or breaking a thread keep theirs.
Type > Story Direction is type.storyDirection {vertical} (checked in the
menu) for the stories of the text selection or selected frames;
object.textFrameOptions {vertical} and frame.create {vertical} set the
frames' stories. Documents that stored Vertical on the frame open with
each story taking its first frame's direction. IDML StoryOrientation is
read into and written from the story, beside its column direction
(StoryDirection), which stays a separate setting.
2026-10-07 10:44:46 -04:00
aiongg e0fb78a230 CJK 1.4: Korean breaks at spaces
Korean is set word by word (KLREQ): Hangul, in any language, and Han
ideographs in Korean-language text take no line breaks between
characters, so lines break at spaces. Glyphs carry their language tag
for this. A paragraph attribute, koreanCharBreaks (type.para, paragraph
styles, Paragraph Style Options > Justification), breaks between
syllables instead. It is DesignCraft-only; IDML doesn't carry it.
A paragraph's kinsoku set decides which breaks it forbids, and Korean
still breaks at spaces under one.

Kinsoku still holds, also when the single-line composer has to split a
word wider than the measure: the split moves back so no line starts
with closing punctuation.
2026-10-07 09:33:52 -04:00
aiongg 832939691c CJK 1.3: documents bring their own fonts
Opening a .designcraft or .idml file from disk (file.open, file.openIdml,
book chapters, recovered documents) loads the fonts in the "Document
Fonts" folder beside it. Their faces come before installed faces of the
same family and style. The files are untrusted: at most 200 are read,
256 MB each (full CJK collections fit) and 512 MB in all; files over a
limit by their listed length are skipped unread, and damaged, oversized
and surplus files are listed in the open result's warnings. Document fonts stay
loaded for the session (the font database is process-wide).

Faces record their source (bundled, installed file, document file,
memory); font.list reports it. File > Package copies the font files the
document uses into the package's Document Fonts folder unless OS/2
fsType restricts the licence, and the report says which fonts weren't
copied and why.
2026-10-07 09:33:52 -04:00
aiongg 2a516761ef CJK 1.1: the character language reaches the shaper
The character's language (as IDML names it) maps to a BCP 47 tag
(designcraft_doc::language_tag) and is the shaping context's language
on the harfrust buffer, so OpenType locl applies for every language in
the list (Japanese, Chinese, Korean, Turkish, ...), not only Arabic,
Persian, Urdu, Hebrew and English: other names used to reach the
shaper as they were written, which it doesn't know, so their locl never
applied. designcraft_fonts no longer maps names itself. The word-shaping
cache is keyed by the tag. Chinese: Simplified and Chinese: Traditional
join the language list (plain Chinese stays, tagged zh); Simplified
Chinese takes curly quotes, Traditional Chinese corner brackets.
2026-10-07 09:33:40 -04:00
aiongg c6b00bc03d Text: drop caps align their left edge and count characters by code point
Drop caps get the Drop Cap Detail options as paragraph attributes: Align
Left Edge (on by default) and Scale for Descenders (kept and
round-tripped; no layout effect yet). With Align Left Edge the drop
cap's origin moves left by its first letter's left side bearing so its
ink starts at the indent, and the lines beside it start where its
advance ends. Right-to-left drop caps are unchanged.

Drop cap characters are counted as code points, extended to the end of
the grapheme cluster so a letter keeps its combining marks.

IDML imports and exports DropcapDetail (bit 1 Align Left Edge, bit 2
Scale for Descenders). DropCapStyle is still imported but no longer
exported: IDML applies the drop cap's character style through a Dropcap
nested style instead.

Format 1 documents keep their drop caps unaligned where nothing set the
option. Paragraph Style Options > Drop Caps has Align Left Edge and Scale
for Descenders checkboxes.
2026-10-07 09:05:00 -04:00
aiongg 3daf7fa1cf IDML: Keep Lines Together defaults to start/end of paragraph
Keep Lines Together now defaults to "At Start/End of Paragraph" (2/2):
new paragraphs, and IDML that sets Keep Lines Together without
KeepAllLinesTogether anywhere in the style chain, keep lines at the
start and end instead of moving the whole paragraph.

The native format is now version 2. Loading a version-1 document pins
"all lines" where that document resolved it from the old default (the
start of each paragraph style chain that leaves it unset, and
paragraphs whose style is missing), so saved documents keep their
layout.
2026-10-07 09:05:00 -04:00
aiongg 47d3f5b4bf Text: drop caps 2026-10-07 09:04:59 -04:00
Cameron Gilroy b41eba1cba Data merge: merge into a new document, with preview and tiling
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.
2026-10-07 21:22:06 +11:00
Shao Wenbin c861a9f75e TYPE1: Reapply Chinese and Arabic typesetting rules on main
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.
2026-10-07 18:16:32 +08:00
aiongg 7861297237 Threading: load text from a port and click to thread 2026-10-07 00:16:50 -04:00
aiongg 6104f2ced1 Threading: thread and unthread commands 2026-10-07 00:16:33 -04:00
aiongg 576fde980b Snap: the nearer of a margin and the baseline grid wins; Type tool frames snap
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).
2026-10-07 00:16:33 -04:00
aiongg 2d6213d40d Text editing: clicks, trailing spaces, bold/italic, clipboard, formatting affects text (#23)
- 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.
2026-10-07 00:16:27 -04:00
Cameron Gilroy 78605f2944 Snap: visible bounds and constrained gestures 2026-10-06 09:05:59 -04:00
Cameron Gilroy 09c8dc3f06 Snap: pen and anchor points 2026-10-06 09:05:59 -04:00
Cameron Gilroy 40f3b6454a Snap: dimensions on resize and create 2026-10-06 09:05:59 -04:00
Cameron Gilroy 253f1a7a10 Snap: Alt-drag skips preview copies and Shift follows the turned view 2026-10-06 09:05:59 -04:00
Cameron Gilroy a2451243ad Snap: Shift and Command on a move 2026-10-06 09:05:59 -04:00
Cameron Gilroy 4de93d1345 Snap: carry SnapView on the tool context 2026-10-06 09:05:59 -04:00
japbcoelhoandClaude Opus 5.5 b8e3b2c029 Preflight: placed PDF and SVG graphics have no resolution to check
preflight.run measured every placed graphic's "pixels", which for a PDF or
SVG is only the size of its screen preview, so a vector logo placed at its
own size was reported as low resolution:

  {"kind": "lowResolution", "message": "logo.pdf: effective 72 ppi (< 150)", "severity": "warning"}

Placed PDF and SVG pages export as vectors; the effective-resolution check
now applies to raster images only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:05:28 -04:00
Brandon ThomasandClaude Opus 5.5 e7ad37517e PDF/X-4: warn when a transparency group still blends in RGB
The CMYK rewrite matches krilla's exact output; if that ever changes,
the export says so instead of silently writing RGB groups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:05:03 -04:00
japbcoelhoandClaude Opus 5.5 0ce21b476c PDF: transparency groups blend in CMYK in print exports
krilla gives every transparency group it writes a DeviceRGB blending space,
including the group around a placed PDF page:

  /Group<</Type/Group/S/Transparency/I true/CS/DeviceRGB>>

A print file therefore carried DeviceRGB (forbidden by PDF/X-4 under a CMYK
output intent) and blended CMYK art through RGB: a placed logo's black,
C91 M78 Y61 K97 in its own file, printed as C72 M67 Y67 K88. When the
document's transparency blend space is CMYK (the default for print) or the
export is PDF/X-4, the groups are rewritten to /CS/DeviceCMYK in place and
at the same length (the optional /Type/Group gives the room, the rest is
padded with spaces), only inside object dictionaries found through the
cross-reference table, so every offset stays valid. Booklets get the same.
Books merged by merge_pdfs are not covered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:05:03 -04:00
japbcoelhoandClaude Opus 5.5 9fcba1e3db PDF: placed PDF 1.7 files go into PDF/X-4 exports
A PDF/X-4 export (PDF 1.6) of a page with a placed PDF 1.7 (VectorCraft's
default, and most tools') failed at the very end with

  designcraft-cli: PDF writer error: Pdf(PdfDocument(PdfDocumentRepr { .. }), VersionMismatch(Pdf17), None)

krilla refuses to embed a PDF newer than the file it writes. PDF 1.7 changed
nothing in how a page draws, so a 1.7 file now goes in as 1.6 (its header
relabelled, as qpdf --force-version=1.6 does) with a warning. A PDF 2.0
file, or one whose catalog declares 1.7, fails the export with a message
naming it. hayro-syntax moves from dev-dependencies to dependencies (krilla
already builds it) because the check names its PdfVersion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:04:49 -04:00
Brandon ThomasandClaude Opus 5.5 f1767a84fd Engine: an empty ids names nothing; states.create and conveyor.collect honour id
named_targets reads ids/id as targets() does, so {ids: [], id} can't
pass the gate and then run on nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:04:22 -04:00
japbcoelhoandClaude Opus 5.5 7e69c82b89 Engine: commands given ids run without a selection
A command that documents ids (object.textFrameOptions, transform.rotate, ...)
acts on the objects a call names, yet its enabled check looked only at the
selection. With a text caret or nothing selected, {"ids": [5]} failed with:

  command `object.textFrameOptions` is not available right now: nothing selected

Execute now lets such a call through when every named object exists, and
says "no object with id N" when one doesn't. transform.set, object.arrange
and edit.stepAndRepeat, which already acted on ids, now document them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:04:22 -04:00
japbcoelhoandClaude Opus 5.5 b54fa8d9bb Text: carriage returns in typed or created text start paragraphs
frame.create's text and text.insert kept CR and CR LF as characters, so text
written with InDesign's, classic Mac or Windows line ends ("One\rTwo") became
a single paragraph, and paragraph formatting (spaceAfter) never applied
between the parts. They now become \n, the paragraph separator, as edit.paste
and text import already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:04:08 -04:00
japbcoelhoandClaude Opus 5.5 316e1ffe62 Text: text.select documents its offsets as UTF-8 bytes
anchor and focus are byte offsets into the story text, as find.find reports
them, but the params doc said only {story, anchor, focus}. A script that
counts characters (Python's len()) selects too little after the first
accented letter: with "Matemática Básica" and focus 17, type.char left the
last two letters out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:03:56 -04:00
storybored379andOpenAI Codex 6e62155108 Guides: honor layer protection in move and delete commands
Co-Authored-By: OpenAI Codex <noreply@openai.com>
2026-10-06 00:03:03 -07:00
Brandon ThomasandClaude Opus 5.5 02f187cc5d Text: story.replaceRange keeps the caret on a character boundary
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 17:32:57 -04:00
storybored379andOpenAI Codex e47f65a595 Text: keep selections on UTF-8 character boundaries
Use the document model boundary helper for explicit selections and retained selections after story replacement. Cover editing and caret movement through the command API.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
2026-10-05 17:32:57 -04:00
Brandon Thomas afdae89d68 Merge pull request #14 from storytold/pr/never-crash-guard
Never crash: last-resort guard around every command
2026-10-04 14:20:03 -04:00
Brandon ThomasandClaude Opus 5.5 f1662185de Never crash: last-resort guard around every command
Session::execute runs each command inside catch_unwind: a panic that escapes
(any edit, import or export, from the UI, CLI, MCP or control channel)
becomes EngineError::Internal, the document and selection are restored to
before the command, and crash recovery is saved when enabled. The first
Session installs a panic hook that logs the message and location.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:55:07 -04:00
Brandon ThomasandClaude Opus 5.5 f05bbfeea2 Never crash: engine, MCP and tools
Commands return errors instead of expect()/unreachable!() (book, library,
undo/redo, paste, find/replace, Pathfinder, tables, QR, select, swatches,
anchors, articles, transform again, XML import). The sample document builder
propagates errors. Commands that add a key to their parameters no longer
panic when a caller (control channel, MCP, script) passes an array or a
string, and corrupt crash-recovery metadata is ignored. engine, mcp and
tools now deny unwrap/expect/panic/unimplemented/todo/unreachable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:43:33 -04:00
Brandon ThomasandClaude Opus 5.5 8490965e71 CI: fix new Rust 1.99 clippy lints (as_chunks, ?, redundant ref)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:37:09 -04:00
storybored379andClaude Opus 5.5 eee64ae7c9 Layers: the active layer falls back to the top layer when it is removed
The active layer lives on DocState, outside the undo snapshots, and
nothing re-checked it when the document's layers changed. After New
Layer → Undo (or Delete Layer / Delete Unused Layers / Merge Layers on
the active layer) it kept pointing at a layer that no longer exists, and
every object created next (frames, lines, placed images, pasted items)
was put on that phantom layer: not drawn on the canvas, missing from PDF
export, ItemLayer referencing an undefined layer in IDML, and preflight
reported nothing.

After each command, if the active layer is gone, make the top layer
active (as on opening a document).

Test: active_layer_survives_undoing_new_layer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 09:52:29 -04:00
storybored379andClaude Opus 5.5 01753f90f3 File: file.serialize returns the file's bytes (base64) — web Save wrote corrupt documents
file.serialize returned {json: String::from_utf8_lossy(to_bytes(doc))},
but since M6.1 the native format is a zip (deflated document.json + raw
assets), not JSON text. from_utf8_lossy turns every invalid UTF-8
sequence into U+FFFD, so the result is not the file: on the web, File ›
Save downloads exactly those bytes (download_document), and the
downloaded .designcraft can't be opened again ("invalid Zip archive").
Agents calling file.serialize got the same unusable string.

Return {base64, bytes} like the other byte-producing commands
(file.exportIdml, file.exportPdf, snippet.export) and decode it in
download_document.

Test: serialized_document_opens_again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 09:51:34 -04:00
Brandon ThomasandClaude Opus 5.5 5294c8f677 T: ruby and kenten
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 02:47:55 -04:00
Brandon ThomasandClaude Opus 5.5 558e798dfc E: CMYK transparency blend space on screen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 02:09:26 -04:00
Brandon ThomasandClaude Opus 5.5 e856b849bb M: video and sound in interactive PDF (screen annotations, renditions)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:44:50 -04:00
Brandon ThomasandClaude Opus 5.5 5b7969901c T: right-to-left carets and selections; Binding: Right to Left
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:34:47 -04:00
Brandon ThomasandClaude Opus 5.5 f888e6b061 X: DTDs — load, validate the structure, Tags panel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:57:18 -04:00
Brandon ThomasandClaude Opus 5.5 fe40d4b80b T: tate-chu-yoko in vertical frames
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:46:52 -04:00
Brandon ThomasandClaude Opus 5.5 b13135472a V: Rotate Spread per spread — canvas layout through per-spread transforms
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:25:16 -04:00
Brandon ThomasandClaude Opus 5.5 b23f19f0c9 T: drag selected table rows and columns
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:14:35 -04:00
Brandon ThomasandClaude Opus 5.5 d30285c542 L: alternate layouts keep their stories linked to the originals
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:10:48 -04:00
Brandon ThomasandClaude Opus 5.5 79f266c04c T: text language — hyphenation, spelling and quotes follow it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:01:50 -04:00
Brandon ThomasandClaude Opus 5.5 7b636fb9f5 C: separations from source colours
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:54:54 -04:00