IDML names some languages by locale code
(AppliedLanguage="$ID/de_DE_2006", "$ID/nl_NL_2005") beside names such
as "English: USA" and "Spanish: Castilian". language_tag maps any locale
code (ll, ll_CC, ll-CC, with a reform year after them) to its BCP 47 tag
(de-DE, nl-NL; kept once per process, at most 512 distinct tags), ignores
IDML's $ID/ prefix, and knows Spanish: Castilian (es-ES) and Swiss French
and Italian. The stored name stays as written, so IDML round trips are
unchanged.
What depends on the language now goes by the tag: typographer's quotes
(by language subtag; Swiss regions take guillemets), English
hyphenation and spelling (is_english), Korean word breaking and the CJK
fallback chains (ja-JP, ko-KR are Japanese and Korean whatever the
region).
A character the applied font lacks is drawn as that font's missing-glyph
box (its .notdef, gid 0), on screen and in PDF, and Preflight lists the
characters by font ("Missing glyphs in <family> <style>: …", an error,
at most 20 named). A .notdef without an outline is drawn as a hollow box
across its advance, so a missing character never disappears. PDF draws
.notdef glyphs as paths, which PDF/A and PDF/UA accept. Kenten marks are
set in their text's font the same way.
The document setting Draw Missing Glyphs from Fallback Fonts
(DocSettings::glyph_fallback; Preferences > Composition,
document.preferences {glyphFallback}, undoable) draws them from fallback
fonts instead (the language-aware chains, then any font; kenten marks
from a fallback font, else a bullet). It is off in new documents and
IDML imports and on in files saved before it existed, which keep
drawing as they did. A missing font's substitute always uses fallback
fonts. The composition cache's signature includes the setting. Tests
whose Arabic comes from a fallback font turn the setting on.
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.
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.
In vertical frames upright runs are shaped TopToBottom (vert; vkrn/vpal
when asked), advance by their vertical metrics and hang from their
vertical origins on the em box centre, on screen and in PDF; their line
ascent and descent are the em box. Turned runs stay horizontal without
vrt2 (rather than every run taking vert and vrt2), and tate-chu-yoko
stays horizontal and centres on the em box, its offsets moving it along
and across the line. Horizontal Kana in upright runs takes vkna. Each
run is shaped with the text around it, as horizontal runs are.
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.
Characters the primary font lacks try a fallback chain by language
before the language-blind search: Japanese (the craft-fonts Japanese
faces first, Mincho first), Simplified Chinese (also plain Chinese),
Traditional Chinese and Korean each list their macOS, Windows and Linux
system families. Hangul, kana and Bopomofo pick their language's chain
whatever the character language; ideographs and CJK punctuation follow
it, and keep today's fallback without a CJK language. Chain families
load from the system font catalog the first time they are needed.
designcraft_fonts::testing (feature `testing`) builds synthetic fonts
with a given family name and coverage for tests.
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.
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.
expect() calls become let-else / fallbacks. A parent spread without pages
(possible in a .designcraft file) no longer underflows in parent_page_for,
and check() rejects a next_id so large that allocating ids would overflow.
Both crates deny the panicking lints.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- render: damage()/damage_with() — changed items and only the frames whose composition changed
(cache keeps the composition each edit replaced; Cache::composed_for matches a document
version exactly); conservative full repaint for styles, swatches, layers, parents, wrap,
effects and cross-story text (+ tests incl. pixel-identical patch vs full render)
- UI: canvas patches its texture after edits with the view unchanged (0.6 ms vs 22 ms on the
sample when typing); perf harness row for the damage repaint
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- doc: OBJECT_MARK with Story::objects (items normalized to the origin) kept in sync on edits,
copy/paste slices and threading
- compose: inline objects are wide glyphs sitting on the baseline (Y offset; auto leading grows);
above-line objects push their line down; placements per frame follow vertical justification
- render + PDF draw anchored items with their lines
- engine: anchored.insert/options/release/list; pasting copied items into text anchors them;
Options/Release act on the object at the insertion point (+ test)
- Object › Anchored Object menu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- doc: INDEX_MARK with Story::index_refs kept in sync (topics 1–4 levels, sort keys, current
page / to end of story / next n paragraphs / suppressed / See / See also); index builder with
section headings, nested or run-in entries, merged page ranges; Document::index for updates
- engine: index.addReference (topic from the selection), index.list, index.generate, index.update
with Index Title / Section Head / Level 1–4 styles (+ tests)
- IDML: PageReference markers and the Index topic tree with See / See also cross-references,
both ways (verified in InDesign 2026)
- Layout › Index menu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- doc: ANCHOR_MARK / XREF_MARK with Story::anchors / xrefs kept in sync on edits, 11 formats
(InDesign's building blocks + partial paragraph), paragraph anchors, typographic quotes
- compose: anchor pages via an XrefIndex (cached per document signature); cross-references are
resolved at composition, so they never go stale; '??' for missing destinations
- PDF: cross-references become link annotations to the destination page
- IDML: export/import as HyperlinkTextDestination / CrossReferenceSource / Hyperlink /
CrossReferenceFormat (+ XRef page variables); verified live in InDesign 2026
- engine: xref.insert/list/setFormat/defineFormat/formats, anchor.create/list (+ tests)
- UI: New Cross-Reference dialog (styles → paragraphs, format); menu pass adding TOC, sections,
text variables, hyperlinks, footnotes, step and repeat, spelling, fitting/content submenus,
effects, Data Merge, Preflight, EPUB; fixed-parameter menu variants; ui.menu.invoke opens
generated dialogs; every-menu-entry-is-a-command test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- doc: FOOTNOTE_REF marker + Story::notes (kept in sync on insert/delete/thread), FootnoteOptions,
NumberStyle::Symbols, footnote text addressed like cell text (CellAddr::footnote)
- compose: notes numbered (continuous / restart per page, spread, section), composed at column
width, body text makes room, stacked at the column bottom with the rule above
- render + PDF draw footnote text; IDML export/import in InDesign's structure (verified in
InDesign 2026), FootnoteOption preferences
- engine: footnote.insert/options/list/setText/delete/goToReference (+ tests)
- UI: caret/selection in footnote text, Footnote Options dialog, Type menu entries; menu items
labelled '…' with parameters open a dialog generated from the command's params doc
- sample magazine carries a footnote
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>