Merge origin/main into data-merge

Resolve conflicts keeping both sides: doc gains both `cjk` and
`datamerge` modules; file.open resolves data-merge sources and loads the
document's fonts; DocState keeps preview_stash/preview_record and fonts.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Brandon ThomasandClaude Opus 5.5 committed 2026-10-07 22:28:15 -04:00
commit 48dfbcfd19
105 files changed
+11338 -710

No files matched your search

+76
View File
@@ -0,0 +1,76 @@
# Arabic import and composition
Task: AR1. These changes repair verified gaps in the existing typesetting pipeline.
They do not claim full InDesign or vendor-specific Naskh composition parity.
## Architecture
Portable character controls live in `doc::arabic` and the inherited attribute
model. Story column direction and table column direction are independent of
paragraph direction. The IDML adapter translates serialized values at the boundary.
The font layer resolves script/direction runs and passes language and surrounding
text to the OpenType shaper. The composer retains complete cluster text for
paragraph-level bidi resolution, then applies line-level ordering and placement.
The renderer continues to consume placed glyphs without Arabic-specific policy.
Existing `type.char`, `type.para` and `table.options` commands edit the controls.
`story.setDirection` edits column flow with history and cache invalidation;
`story.get` reports it. Table cell indices remain logical even when their visual
positions are reversed. Physical left/right insertion commands account for direction.
## Implemented rules
| Area | Behavior |
| --- | --- |
| Mixed scripts | Separate Hebrew/Arabic script runs, resolved shaping direction, language tags and OpenType joining features |
| Style boundaries | Pre/post shaping context preserves joining when a character style changes within an Arabic word |
| Font fallback | Combining marks and join controls stay with the preceding base face |
| Bidirectional layout | Full paragraph context, cluster contents and Unicode controls survive line breaking; per-line UAX #9 ordering keeps marks attached, including lines with tab leaders |
| Character direction | Imported default/LTR/RTL overrides participate in bidi resolution; single-character punctuation mirroring is corrected against final paragraph levels |
| Digits | European, Arabic-Indic and Persian conversion in both directions, contextual defaults, language-sensitive native digits, original UTF-8 source ranges retained |
| Diacritics | Font GDEF classification and OpenType mark anchors; explicit X/Y offsets affect marks only, in thousandths of an em |
| Positional forms | Explicit isolated/initial/medial/final OpenType feature selection; automatic forms remain the default |
| Kashidas | Paragraph and character switches, actual font coverage, shaper-approved insertion boundaries, nonjoiner protection, whole-cluster movement to keep marks attached |
| Story direction | Independent right-to-left column flow, including span-column bounds |
| Table direction | Right-to-left cell placement, merged spans, physical edge styles/borders, hit testing and left/right column insertion |
| Serialization | Character controls, paragraph policy/width values, story/table directions survive native data and IDML round trips |
## Remaining limitations
- Vendor-specific Arabic/Naskh justification policies and Paragraph Kashida Width
presets are preserved but not reproduced. Preflight reports their use.
Automatic elongation still uses bounded, scaled tatweel insertion rather than
full line reshaping or a vendor allocation algorithm.
- Loose/Medium/Tight/OpenType-from-baseline diacritic presets are preserved and
reported. Composition uses font anchors and explicit offsets instead.
- Script-specific stretched alternates, contextual swash policies, complete RTL
tab-stop semantics and re-shaping at emergency intra-word line breaks require
further compatibility work. Tracking and arbitrary metric changes within a
cursive word can still open joining gaps.
- A font must contain the required glyphs and appropriate OpenType tables. This
change neither embeds proprietary fonts nor makes missing-font errors disappear.
- CJK compatibility boundaries remain documented in `cjk-typography.md`.
## Verification and sources
Regression tests cover independent IDML XML, round trips, forced line breaks,
character overrides, original byte ranges, mark placement, style-boundary joining,
kashida controls, story/table flow, merged cells, command validation and undo.
Font-dependent tests use available fonts; no proprietary font or user document
is committed. Synthetic Arabic documents provide visual smoke checks.
The AR1 validation run passed all six `cargo xtask ci` gates and 616 tests
(with two existing ignored tests). WASM checks used the installed Rust standard
library sources via `-Z build-std=std,panic_abort`, because a prebuilt WASM standard
library was unavailable. The synthetic Arabic proof was exported to IDML and
reopened in the desktop app: preflight reported zero errors and zero warnings.
The original Chinese IDML was also reopened; its four Mojikumi/kinsoku-priority
compatibility warnings remain, along with separate font/link/overset findings.
- [Unicode UAX #9](https://www.unicode.org/reports/tr9/) defines paragraph resolution
and line-level bidirectional ordering.
- [Adobe Middle Eastern scripting guide](https://developer.adobe.com/indesign/uxp/resources/recipes/rtl/)
documents the distinct direction, digit, kashida and diacritic controls.
- [Diacritic position enumeration](https://developer.adobe.com/indesign/uxp/dom/api/d/diacritic-position-options/)
and [positional forms](https://developer.adobe.com/indesign/uxp/dom/api/p/positional-forms/)
identify serialized options; their presence alone does not establish visual parity.
+64
View File
@@ -0,0 +1,64 @@
# CJK import and composition
Task: CJK1. This is incremental support, not a claim of InDesign composition parity.
## Data flow
The IDML adapter resolves font resources and named/custom kinsoku sets into portable
document data. `doc::cjk` owns those resources; `CharAttrs` and `ParaAttrs` retain
inheritance and local overrides. The composer resolves composite-font character
assignments before shaping, applies character metrics, then supplies break
constraints to both line breakers. Line placement applies em alignment, leading
references and hanging punctuation. The renderer consumes placed glyphs unchanged.
`style.compositeFont.list` and `style.compositeFont.set` expose resources through the
existing command channel. Editing uses normal document history and style cache
invalidation. Font listing, replacement and preflight resolve component fonts.
## Implemented behavior
| Rule | Behavior and coverage |
| --- | --- |
| Composite fonts | Character mappings, component family/style, relative size, scales and baseline shift; synthetic selection and command/undo tests |
| Leading/trailing aki | Explicit em spacing without outline scaling; inherited resets and IDML round trip |
| Kerning import | Automatic methods take precedence over inactive numeric values; the 1e11 sentinel cannot become manual spacing |
| Tsume | Compress nonnegative glyph sidebearings without scaling ink; full compression regression |
| Jidori | Fixed group width with protected internal boundaries and justification advances; both composers tested |
| Kinsoku sets | Custom character sets and independent common punctuation defaults for named sets; explicit disabling retained |
| Bunri kinshi / rensuuji | Protect repeated dash/ellipsis pairs and numeric sequences |
| Hanging punctuation | Regular/forced edge allowance integrated with line breaking and placement |
| Ideographic spaces | Paragraph switch controls whether fullwidth spaces participate in word spacing |
| Em alignment / leading | Em top/center/bottom alignment and baseline reference offsets; mixed-size regressions |
| Kenten / glyph forms | Preserve emphasis character, shape it with font fallback; map supported OpenType forms |
| Tate-chu-yoko offsets | Carry horizontal/vertical offsets through the existing vertical group placement |
## Unfinished behavior
- **Mojikumi**: definitions, base-set names, all override rows and paragraph references
survive import/export. The numeric character-class spacing tables and their
compression priorities are **not executed**. Preflight reports this explicitly.
- **Kinsoku push-in/push-out priorities**: retained, but not used in candidate
selection. Preflight reports this explicitly. Boundary prohibition alone is not
equivalent to implementing this priority policy.
- Named kinsoku defaults are clean-room common punctuation rules, not verified
reproductions of every vendor preset. Explicit custom tables take precedence.
- ICF alignment currently uses font ascender/descender metrics; exact ideographic
character-face metrics are not implemented. CenterDown currently shares the
center leading reference. These require separate compatibility work.
- Jidori groups are scoped to resolved shaping runs. Mixed component-font or style
changes inside one intended group need a group model above shaping.
- Frame-grid composition and detailed ruby/warichu controls are outside this change.
Preserving a parameter is not evidence that its visual rule has been implemented.
Missing fonts, missing links and overset remain separate preflight findings;
removing unsupported-rule warnings to produce a clean report is not acceptable.
## References
- [Adobe MojikumiTable API](https://developer.adobe.com/indesign/uxp/dom/api/m/mojikumi-table/)
describes the serialized override tuple, but does not define the numeric class mapping.
- [Adobe spacing priorities](https://helpx.adobe.com/hk_en/indesign/desktop/language-and-proofing/chinese-japanese-and-korean/set-spacing-priorities-in-mojikumi-character-classes.html)
describes ordered compression, which requires more than loading the table.
Tests use synthetic documents; user IDML files and proprietary application output
are not committed as fixtures.
+276
View File
@@ -0,0 +1,276 @@
# CJK typesetting plan
Goal: the Chinese, Japanese and Korean typesetting that InDesign offers with its East Asian
feature set (the "Japanese/CJK" feature set, `Feature Set Locale Setting` 257), so that a
DesignCraft document can be set to the standard of a Japanese, Chinese or Korean publisher,
round-trip through IDML, and export correct PDF and EPUB.
It builds on the CJK import and composition described in [cjk-typography.md](cjk-typography.md)
(composite fonts, kinsoku sets, aki, tsume, jidori, hanging punctuation, em alignment and leading
models, kenten characters, tate-chu-yoko offsets, preserved mojikumi tables) and plans what
remains.
## Sources (clean-room)
Behaviour comes from public specifications, not from InDesign's implementation:
- W3C *Requirements for Japanese Text Layout* (JLREQ): character classes, line composition,
spacing (aki), line breaking (kinsoku), ruby, kenten, warichu, tate-chu-yoko, grids.
- W3C *Requirements for Chinese Text Layout* (CLREQ) and *Korean* (KLREQ): punctuation placement
for Simplified and Traditional Chinese, Korean word-based line breaking.
- JIS X 4051 (published standard; JLREQ follows it).
- OpenType: `vmtx`/`vhea`/`VORG`, `BASE`, the `vert`/`vrt2`/`vkrn`/`vpal`/`palt`/`halt`/`vhal`,
`locl`, `ruby`, width (`fwid`/`hwid`/`pwid`/`qwid`/`twid`) and form (`jp78`/`jp83`/`jp90`/`jp04`,
`trad`, `expt`, `nlck`, `hojo`) features.
- The published IDML specification for attribute names and values.
InDesign itself is only observed black-box (feature names, which options exist); no wording,
presets or tables are copied. Our default mojikumi and kinsoku tables are built from JLREQ.
## Where we are (2026-10)
| Area | State |
|---|---|
| Vertical text, upright CJK, rotated Latin | done: story direction per story (frames of a thread agree; IDML `StoryOrientation`); glyphs turned by transforms; arrow keys follow the vertical lines |
| Vertical metrics and shaping | done: upright runs shaped top to bottom (`vert`; `vkrn`/`vpal` when asked) with `vmtx`/`VORG` advances and origins, centred on the ideographic em box (`BASE`, else OS/2); turned runs shaped horizontally, without `vrt2`; Horizontal Kana (`hkna`) takes the vertical kana (`vkna`) in upright runs. Default location of variable fonts only (no `VVAR`) |
| Kinsoku | named (hard, soft, Chinese, Korean) and custom kinsoku sets, bunri-kinshi, rensuuji, hanging punctuation (regular, force); without a set, fixed no-start / no-end lists and a break anywhere between CJK characters; kinsoku type (push in / push out priorities) kept but not applied |
| Korean line breaking | at spaces (Hangul everywhere, hanja in Korean text); per-paragraph character-based breaking (`koreanCharBreaks`); DesignCraft-only, not in IDML |
| Mojikumi | tables and paragraph references kept through IDML; not applied (Preflight says so) |
| Aki, tsume, jidori | done ([cjk-typography.md](cjk-typography.md)) |
| Character alignment, leading model | em box top / centre / bottom, ICF from ascender and descender, roman baseline; aki above / below, centre (centre down as centre) |
| Tate-chu-yoko | manual, with offsets; no auto, 3+ digits overflow the em |
| Ruby | group ruby only, fixed 50 % size, no options, doesn't affect leading |
| Kenten | the IDML kinds and a custom character, drawn as characters; no position, size, alignment, colour or font |
| Composite fonts | done: model, `style.compositeFont.*`, shaping, IDML, font list, replace and Preflight; no dialog |
| Languages | Japanese, Korean, Simplified and Traditional Chinese reach the shaper (`locl`), font fallback, line breaking and typographer's quotes; IDML language names are kept as written; locale codes (`ja_JP`, `ko-KR`, `de_DE_2006`) are their language and region, and Chinese is also recognised in its common spellings ("Simplified Chinese", "Chinese (Traditional)", `zh_CN`, `zh-Hant`) |
| Missing glyphs | as in InDesign: characters the applied font lacks are drawn as its missing-glyph box (screen and PDF) and listed by Preflight; the document setting Draw Missing Glyphs from Fallback Fonts (Preferences › Composition, off in new documents and IDML imports, on in documents saved before it existed) draws them from fallback fonts instead |
| Font menus | Western families first, then Japanese, Simplified Chinese, Traditional Chinese and Korean groups (from the font's `meta` languages, CJK family names, OS/2 code pages, else its coverage); CJK families under their native names unless Preferences › Type › Show Font Names in English; the order is recalled from InDesign, not observed |
| CJK fonts | Japanese faces (Shippori Mincho first) from the optional craft-fonts build input, in the font menus for users to apply; fallback by language (with fallback fonts on) (Japanese: the craft-fonts faces, then system fonts; Simplified and Traditional Chinese, Korean: system fonts); document fonts: the fonts of a `Document Fonts` folder beside an opened file belong to that document (composition, export, font menus), ahead of installed fonts of the same name, and are forgotten when it closes; Package copies every font file that draws text, fallback fonts included (licence permitting). Font files stay in memory for the session (reopening a document reuses them) and aren't read on the web |
| Everything else below | missing |
Known bugs to fix first:
- A justified CJK line has no stretch except Single Word Justification.
## Architecture
### Character classes
A `cjk::class_of(char, &Context) -> Class` in `designcraft-compose` with the JLREQ classes
(opening brackets, closing brackets, hyphens, dividing punctuation, middle dots, full stops,
commas, inseparable characters, iteration marks, prolonged sound mark, small kana, prefixed and
postfixed abbreviations, full-width ideographic space, hiragana, katakana, math symbols and
operators, ideographs, Western characters, numerals, ruby bases, warichu brackets, …). Language
and the paragraph's punctuation style decide the ambiguous ones (e.g. Chinese full-width
punctuation centred vs corner-placed).
### Spacing: mojikumi
A *mojikumi set* is a table indexed by (class before, class after) giving the space between them
as a min/optimum/max triple in em fractions, plus line-start and line-end rules. Sets are
document resources (`Styles::mojikumi_tables`, read from and written to IDML with their override
rows), referenced by name from the paragraph's `mojikumi`; what remains is applying them. We
ship a few built-in sets derived from JLREQ (no line-start indent, one-em indent, half-width
punctuation, full-width punctuation…), all original data, for the IDML built-in names.
Shaping keeps the glyphs' own advances; mojikumi is applied after shaping as *aki* glue between
glyphs: a full-width bracket's empty half becomes negative aki (compression) and the composer
can widen or narrow each gap within its range. This keeps `palt`-style proportional fonts
working (tsume removes the side bearings before mojikumi runs).
### Line breaking: kinsoku and the Japanese composer
- Kinsoku sets (`doc::cjk::Kinsoku`: characters not allowed at line start, at line end, that may
hang, and inseparable pairs), the paragraph's set, hanging (none / regular / force),
bunri-kinshi and rensuuji are in place: they constrain breaks in both breakers. Remaining: the
built-in hard and soft sets from JLREQ's classes (today's named sets are common punctuation
lists), and the kinsoku type (push in first / push out first / push out only / prioritise the
adjustment amount), which needs the Japanese composer's push-in and push-out candidates.
- A `Composer::Japanese` (paragraph) and `Composer::JapaneseSingleLine` beside the Western
composers. In `breaker::items` the Japanese composer emits:
- boxes per glyph,
- mojikumi glue between glyphs with priority tiers (compress punctuation first, then
expand inter-ideograph space, then Western/CJK spacing, then letter-spacing),
- kinsoku penalties (infinite inside prohibited pairs; push-in candidates scored by
compression needed),
- hanging punctuation as zero-width boxes allowed past the measure.
Knuth–Plass already takes tiered stretch (`TIER_COST`), so this plugs into `kp_pass`.
- Justification in `layout_line` distributes by the same tiers; the last line follows the
paragraph's last-line alignment; CJK lines justify by default (JLREQ).
- Korean: the Japanese composer with word-based breaking (spaces) by default; per-paragraph
option for character-based breaking.
### Vertical text, properly
- Read `vmtx`, `vhea`, `VORG` and the ideographic em box (`BASE` `ideo`/`idtp`, else OS/2 typo
metrics). `FontFace` gets `v_advance(gid)`, `v_origin(gid)` and `em_box()`.
- Shape upright runs with direction `TopToBottom` so `vert`, `vkrn` and `vpal` apply and the
shaper reports vertical advances; rotated (Latin) runs stay horizontal and turn as a whole.
`vrt2` only for runs that are rotated, never with an extra turn.
- Glyph placement in vertical lines uses the vertical origin, not a guessed centre.
- Story direction per story (IDML `StoryOrientation`); frames of one story agree.
- Arrow keys and carets follow the vertical line direction.
### Annotations and inline structures
All set after line layout like today's ruby, but able to affect line height:
- **Ruby**: per-character (mono), group and jukugo ruby; alignment (1-2-1, centre, left/right,
equal space, JIS rule), position (above/right, below/left), size, font, colour, x/y scale,
offsets, overhang onto neighbours (none / ½ / 1 ruby character / ½ base / unlimited), automatic
scaling, spacing to the base, "ruby adjusts leading".
- **Kenten**: position, size, alignment, colour, font (the kinds and a custom character are
there).
- **Warichu**: an inline run set in 2+ smaller lines inside one line height, with size, line
spacing, alignment and breaking rules.
- **Tate-chu-yoko**: scale-to-fit-em, automatic TCY for runs of N digits (with or without
Latin), rensuji (offsets are there).
- **Shatai** (oblique in the CJK sense: angle and magnification, keeping the em box),
**character rotation**.
- **Character alignment and leading model**: ICF top and bottom from the font's ideographic
character face (`BASE` `icft`/`icfb`) rather than its ascender and descender, and centre down
as its own model.
Jidori, tsume, aki before and after, em-box alignment and the other leading models are in place
([cjk-typography.md](cjk-typography.md)).
### Grids
- **Layout grid** (per page / parent): character size, characters per line, lines per column,
columns and gutter, origin; drawn like the baseline grid; snap target.
- **Frame grids**: text frames that show a character grid (horizontal or vertical), with a
character count, named grids as document resources (font, size, scale, aki between characters
and lines, alignment, view options), grid alignment of paragraphs (em-box centre, ICF top/bottom,
roman baseline, gyoudori: lines spanning N grid lines), grid tracking.
- Frame Grid tools (horizontal and vertical) and Object › Frame Type (text frame ↔ frame grid).
### Fonts
- **Composite fonts** (a named font made of a base font plus fonts for character sets, with size,
baseline, x/y scale and centre adjustments) are resolved before shaping, read from and written
to IDML and listed, replaced and preflighted by their member fonts; PDF embeds the member fonts.
Remaining: the Composite Fonts dialog, and the predefined character classes (kanji, kana,
full-width symbols, punctuation, Latin, numerals) beside custom sets.
- **Missing glyphs**: InDesign doesn't substitute fonts per character: a character the applied
font lacks shows as the font's missing-glyph box, prints that way and is a Preflight error.
DesignCraft does the same, with a document setting (Draw Missing Glyphs from Fallback Fonts)
that draws such characters from fallback fonts instead.
- **Language-aware fallback** (when that setting is on): Japanese, Simplified Chinese, Traditional
Chinese and Korean each have their own fallback chain of system fonts (Japanese: the craft-fonts
Japanese faces first); `locl` follows the character language (IDML language names).
- **No CJK fonts in the repo.** CJK fonts are large (15–25 MB each even subset) and documents
should name the fonts they use. The Japanese UI and document faces come from the optional
craft-fonts build input when DesignCraft is built with it (release builds are).
- **Document fonts**: fonts in a `Document Fonts` folder beside an opened `.designcraft` or
`.idml` file are available to that document only, ahead of system fonts, and unloaded when it
closes. File › Package copies the document's fonts into the package's `Document Fonts` folder
(font licences permitting: OS/2 `fsType` restricted-licence fonts are listed, not copied), so a
packaged CJK document opens with its fonts on another machine. On the web, the fonts of a
dropped or opened package folder load the same way.
### Editing and output
- Find/Change: kana-sensitive and width-sensitive matching; full/half-width and
hiragana/katakana conversion (Type › Change Case).
- Numbering styles: kanji, full-width, circled, parenthesised.
- Typographer's quotes per language (Simplified/Traditional Chinese, Korean).
- Character count (Info panel) by CJK conventions.
- EPUB/HTML: `writing-mode: vertical-rl`, `<ruby>`, `text-combine-upright`, `text-emphasis`.
- PDF: correct `ActualText` for ruby/warichu; vertical glyph placement as above.
- IDML: every attribute below, import and export, round-trip tested.
## Feature set (UI)
Preferences › Type gets a *Feature Set* choice (Western, CJK). CJK shows the East Asian
controls in the Character and Paragraph panels, styles dialogs and menus (Kinsoku, Mojikumi,
Composite Fonts, Named Grids, Layout Grid), makes the Japanese composer the default for new
documents and offers the frame grid tools. Western hides them; documents keep their settings
either way (opening a CJK document in Western mode loses nothing). Every feature stays reachable
as a command regardless of the feature set (agents, MCP).
## IDML attributes to support
Already read and written: `KentenKind`, `KentenCustomCharacter`, `RubyFlag`, `RubyString`,
`Tatechuyoko`, `TatechuyokoXOffset`, `TatechuyokoYOffset`, `Jidori`, `Tsume`, `LeadingAki`,
`TrailingAki`, `CharacterAlignment`, `LeadingModel`, `GlyphForm`, `OTFProportionalMetrics`,
`OTFHVKana`, `OTFRomanItalics`; paragraph `KinsokuSet`, `KinsokuType`, `KinsokuHangType`,
`BunriKinshi`, `Rensuuji`, `MojikumiTable`, `TreatIdeographicSpaceAsSpace`; resources
`KinsokuTable`, `MojikumiTable`, `CompositeFont`, `StoryPreference` direction.
Character: `KentenFont`, `KentenFontStyle`, `KentenSize`, `KentenPosition`, `KentenAlignment`,
`KentenCharacterSet`, `KentenXScale`, `KentenYScale`, `KentenFillColor`/`Tint`, `RubyType`,
`RubyAlignment`, `RubyPosition`, `RubyFont`, `RubyFontStyle`, `RubyFontSize`, `RubyXScale`,
`RubyYScale`, `RubyXOffset`, `RubyYOffset`, `RubyOverhang`, `RubyParentSpacing`,
`RubyParentOverhangAmount`, `RubyAutoAlign`, `RubyAutoScaling`, `RubyAutoTcyDigits`, `Rensuji`,
`CharacterRotation`, `ShataiMagnification`, `ShataiDegreeAngle`, `ShataiAdjustRotation`,
`ShataiAdjustTsume`, `Warichu`, `WarichuLines`, `WarichuSize`, `WarichuLineSpacing`,
`WarichuAlignment`, `WarichuCharsBeforeBreak`, `WarichuCharsAfterBreak`.
Paragraph: `Composer` (the Japanese composers), `GridAlignment` (all values),
`GridAlignFirstLineOnly`, `GridGyoudori`, `ParagraphGyoudori`, `CjkGridTracking`, `AutoTcy`,
`AutoTcyIncludeRoman`, `RotateSingleByteCharacters`, `ScaleAffectsLineHeight`.
Resources: `MojikumiTableSet`, `NamedGrid`, layout grid and frame grid preferences,
`StoryPreference` orientation.
Exact names and enumerations are checked against the IDML specification as each phase lands.
## Phases
Each phase lands as reviewable commits with tests (compose tests for layout, engine tests for
commands, IDML round trips, a rendered sample page) and `cargo xtask ci`.
| # | Phase | Contents | Estimate (agent hours) |
|---|---|---|---|
| 1 | Foundations | language passed to the shaper (`locl`); Simplified/Traditional Chinese and Korean languages; language-aware CJK fallback; document fonts folder; Korean word breaking; `vmtx`/`VORG`/em box and vertical shaping; story-level direction; vertical caret keys | 12–17 |
| 2 | Japanese composer | character classes; mojikumi applied (built-in sets and the document's tables) as aki glue; JLREQ kinsoku sets and kinsoku types (push in / push out); Japanese paragraph and single-line composers; CJK justification and last line; Chinese punctuation styles; IDML | 20–30 |
| 3 | Inline CJK | full ruby, kenten options, warichu, auto TCY and scale-to-fit, rensuji, shatai, character rotation, ICF alignment from the font; IDML | 20–30 |
| 4 | Grids | layout grid, frame grids and tools, named grids, grid alignment and gyoudori, grid tracking, character count; IDML | 20–30 |
| 5 | Fonts | Composite Fonts dialog and predefined character classes; CJK OpenType features in the OpenType menu | 5–8 |
| 6 | Editing and output | kana/width-sensitive Find/Change, width and kana conversion, CJK numbering, quotes, EPUB/HTML vertical text and ruby | 10–15 |
| 7 | Feature set UI | Preferences › Feature Set, panels and dialogs for every attribute, Japanese defaults for new documents, translations | 15–20 |
Total: roughly 100–150 agent hours.
### Phase 1 in detail
1. `CharProps.language` reaches `designcraft_fonts::shape` (harfrust buffer language) and the
word cache key; tests: a font with `locl` for JAN vs ZHS draws different glyphs for U+76F4.
2. Language list: "Chinese: Simplified", "Chinese: Traditional", "Korean", "Japanese" (IDML
names), with quotes and spelling/hyphenation off for them.
3. Fallback chains by language in `FontDb::fallback_for` (system fonts per platform; the
craft-fonts Japanese faces first for Japanese); tests with stand-in fonts and a fake catalog.
4. Document fonts: opening a document loads the fonts in its `Document Fonts` folder for that
document; Package copies used fonts there (licence permitting) and reports the ones it
couldn't copy; tests with a synthetic package folder.
5. Korean: `cjk_break_between` doesn't break between Hangul syllables unless the paragraph asks
for character breaking; test with a Korean sentence.
6. `FontFace::v_advance/v_origin/em_box` from `vmtx`/`vhea`/`VORG`/`BASE`; upright glyphs in
vertical lines use them; tests against synthetic fonts with and without `vmtx` (without, the
em box), and Shippori Mincho's tables when built with craft-fonts.
7. Upright runs shaped top-to-bottom (`vert`, `vkrn`, `vpal` apply); `vrt2` never on turned
runs; test that a full-width comma takes its vertical form and position.
8. Story direction at story level (frames of a story agree; IDML maps both ways); arrow keys move
along the vertical line.
## Decisions
Each follows what an InDesign user expects.
1. **Feature set gating.** East Asian controls are hidden in the Western feature set and shown in
the CJK feature set, as in InDesign. Every feature stays reachable as a command either way.
2. **Korean line breaking** is word-based by default (KLREQ), with a per-paragraph option to break
between syllables.
3. **PDF vertical text** keeps transformed horizontal glyphs (correct-looking, copyable via
ActualText) rather than vertical CID fonts (`Identity-V`).
4. **No CJK fonts in the repo**; documents carry theirs in `Document Fonts`, scoped to the
document as in InDesign. DesignCraft embeds Japanese UI and document faces only when built with
the optional craft-fonts input (release builds are).
5. **Package copies every font that draws text**, fallback fonts included (when the document
draws missing glyphs from them), so a package reproduces what is on screen.
6. **Korean fallback is serif first** (Myungjo), as Chinese and Japanese fall back to Song and
Mincho: the serif faces CJK editions of layout software default to.
7. **Language names on IDML import** accept the common spellings of Simplified and Traditional
Chinese, since the exact names InDesign writes are unconfirmed without a sample file.
8. **Missing glyphs** are drawn as the font's box and reported, as in InDesign; drawing them from
fallback fonts is a per-document choice, on only for documents saved before it existed.
+55
View File
@@ -0,0 +1,55 @@
# Contributors in the About window
**About ▸ Contributors** credits everyone who contributed to DesignCraft, and **About ▸ Models** credits
the AI models named in `Co-Authored-By` trailers. This follows the shared craftrules standard
[`standards/contributors.md`](https://github.com/storytold/craftrules/blob/main/standards/contributors.md);
this page is the local copy of the decision.
## Decision
- Two files, both **compiled into the binary** by the UI crate's `build.rs` (nothing is read from
disk or the network at run time; the web build has them too):
- [`contributors/contributors.json`](../contributors/contributors.json): commit stats per GitHub
username. **Generated** by `craftrules/scripts/contributors.py`; never hand-edit.
- [`contributors/people.toml`](../contributors/people.toml): the names people chose for
themselves. **Each contributor adds only their own entry**, in their own PR.
- Each contributor gets one line: GitHub username (always), display name (opt-in, or their synced
GitHub profile name), real name (only if they told us), merged PRs, commits, lines added, lines
deleted, binary assets added, binary assets removed, first and last commit date.
- The list is shown as a **grab bag** (names flowing as text) or a **table**, in the same order. It
sorts by name, PRs, commits, lines added, lines deleted, line delta, binary assets added/removed,
first or last commit date. The name toggle cycles **Username → Display name → Real name**; a
missing name falls back to `@username`. Alphabetical sorting is case-insensitive and ignores the `@`.
## In DesignCraft
- The About window is `crates/ui-egui/src/about.rs` (Help ▸ About DesignCraft); `help.about {"tab":"contributors"}` opens a tab.
## Credit yourself
Add your entry to `contributors/people.toml` in a PR you commit yourself (every field optional):
```toml
[people.your-github-username]
real_name = "Your Name"
display_name = "Nickname"
sync_github_name = true # use the name on your public GitHub profile as display name
```
or, with craftrules checked out next to `craft-apps/`:
```sh
python3 ../../craftrules/scripts/contributors.py --add-me . --real-name "Your Name" --sync-github-name
```
Names show after the next refresh: the script checks that each entry was committed by its own
user (or by a maintainer with a `consent_source`). Delete your entry to remove your names.
## Refreshing (maintainers)
```sh
python3 ../../craftrules/scripts/contributors.py . # regenerate + verify; needs git, Python 3.11+, gh
python3 ../../craftrules/scripts/contributors.py --check . # verify people.toml only
```
Commit the updated `contributors/contributors.json`. Git author names and emails are never recorded.
+11 -1
View File
@@ -3,6 +3,16 @@
Start the app with `--control <port>` (or `DESIGNCRAFT_CONTROL_PORT`). The server listens on
`127.0.0.1` only and speaks JSON lines: one request object per line, one reply per line.
**Only requests are read.** Every line must be a JSON object with a string `method` (`id` and `params`
are optional; blank lines are skipped). Anything else gets one error reply
(`{"ok": false, "error": "… closing the connection"}`) and the server **closes the connection**, so
nothing sent after it on that connection runs. That covers text that isn't JSON, a JSON array or number,
an object without `method`, invalid UTF-8, and a line longer than 4 MiB. An HTTP request (for example a
web page's cross-origin `fetch` to `127.0.0.1:<port>`) therefore can't smuggle a command in its body:
its request line is rejected first. At most 16 connections are served at once; further ones get an error
line and are closed. Clients that get an error reply should reconnect. The port has no authentication,
so only enable it while you use it. Transport: `apps/designcraft/src/control_server.rs`.
```json
{"id": 1, "method": "engine.execute", "params": {"command": "frame.create", "params": {"rect": [36, 36, 300, 200], "content": "text"}}}
{"id": 1, "ok": true, "result": {"id": 10, "story": 11}}
@@ -18,7 +28,7 @@ Start the app with `--control <port>` (or `DESIGNCRAFT_CONTROL_PORT`). The serve
| `ui.tool.select` | `{tool}` | Select a tool (`selection`, `type`, `rectangleFrame`, …) |
| `ui.pointer` | `{events:[{kind: down\|drag\|up\|move\|doubleclick, x, y, space?: "screen"\|"canvas"}], mods?}` | Drive the active tool through the same code path as the mouse |
| `ui.key` / `ui.text` | `{key, shift?, alt?, cmd?}` / `{text}` | Synthetic keyboard input (typing into a text frame) |
| `ui.move` / `ui.click` / `ui.drag` | screen points | Real egui pointer input — reaches every widget, menu and panel |
| `ui.move` / `ui.click` / `ui.drag` | screen points, `button?: left\|right\|middle` | Real egui pointer input — reaches every widget, menu and panel |
| `ui.set` | `{brightness?, panel?, rulers?, guides?, frameEdges?, baselineGrid?, textThreads?, screenMode?, zoom?, page?, fit?}` | UI state |
| `ui.dialog.open` | `{id, fields?}` | Open a dialog by id (e.g. `paragraphStyleOptions` with `{name, section}`) |
| `ui.dialog.set` / `ui.dialog.confirm` / `ui.dialog.cancel` | `{field, value}` | Fill and confirm the open dialog |
+1 -1
View File
@@ -93,4 +93,4 @@ key {"key": "Escape"}
## Community links
`app.links` returns the ArtCraft Discord (https://discord.gg/artcraft), the website (https://getartcraft.com), DesignCraft's app page (https://getartcraft.com/apps/designcraft), its GitHub repository (https://github.com/storytold/designcraft) and the issue tracker. In the running app, the UI commands `help.discord`, `help.appPage`, `help.github`, `help.issues`, `help.website`, `help.app {app}` open them in the browser and `help.about` shows the About splash. The server's `serverInfo.websiteUrl` is the app page.
`app.links` returns the ArtCraft Discord (https://discord.gg/artcraft), the website (https://getartcraft.com), DesignCraft's app page (https://getartcraft.com/apps/designcraft), its GitHub repository (https://github.com/storytold/designcraft) and the issue tracker. In the running app, the UI commands `help.discord`, `help.appPage`, `help.github`, `help.issues`, `help.website`, `help.app {app}` open them in the browser and `help.about {tab?}` shows the About window (tabs `about`, `contributors`, `models`). The server's `serverInfo.websiteUrl` is the app page.