Commit Graph
241 Commits
Author SHA1 Message Date
aiongg 2fae23fcc6 Merge atomic-save: MCP: exported files replace the target atomically (#436)
Conflict: ROADMAP.md: both progress-log entries, next's newer ones first
Conflict: apps/designcraft/src/gpu.rs: next's side; the branch's test fix targets the startup record tests next replaced
Conflict: apps/designcraft/src/main.rs: next's gpu startup and macOS IME helper; config_dir() dropped for the branch's profile::config_dir(), recovery_dir from profile::recovery_dir()
Conflict: crates/ui-egui/src/canvas.rs: next's guard (menu_open, keyboard_pane already limits keys to the focused pane)
Conflict: crates/ui-egui/src/lib.rs: modules from both sides
2026-10-11 05:16:10 +00:00
aiongg 607b2f4a34 Merge live-corners: Tools: live corners round a frame's corners by dragging
Conflicts:
- crates/tools/src/lib.rs: Overlay keeps next's ContentGhost and adds
  LiveCornerWidget and LiveCornerDiamond.
- crates/tools/src/select.rs: the module doc keeps next's threading and
  content paragraphs and adds the live-corner one. Drag keeps Place and
  adds Corner; SelectionTool holds both `loaded` and `live`. pointer()
  first ends live-corner editing when the selection changed, then runs
  next's loaded-text-cursor and double-click handling. An empty-canvas
  press clears `live` and keeps next's `content: false` Pending. drag()
  handles Place and Corner. overlays() keeps next's Place marquee and
  content ghost and appends the widget or diamonds.
- crates/ui-egui/src/canvas.rs: the overlay pass keeps next's doc/layout
  for ContentGhost and next's Path drawing, and adds the two live-corner
  arms (the branch's no-op Path arm is dropped). The live-corner render
  test goes before next's fast_harness tests.
- crates/engine/src/cmd/object.rs: next's text_wrap stays; the branch's
  corner_options function follows it.
- crates/engine/src/tests.rs, crates/tools/src/tests.rs: both sides'
  tests kept.
- ROADMAP.md: the live corners progress-log line goes first; the status
  line names it too.

Action::Handled is matched in engine tooling.rs; no other crate matches
Action or Overlay exhaustively.
2026-10-11 02:50:45 +00:00
aiongg f12b3443a0 Merge #427: Text: Optical kerning spaces pairs from their outlines
Conflicts:
- Cargo.lock: designcraft-compose depends on both unicode-script (#427)
  and unicode-segmentation (next).
- ROADMAP.md: the optical kerning progress-log line goes above next's two
  2026-10-11 lines. Status line keeps next's Last reviewed date and names
  both changes.
- docs/gaps.md, docs/typography-parity.md: same status-line merge.
2026-10-11 02:30:53 +00:00
aiongg 5e51392ac4 Merge mac-font-activation: macOS fonts that font services and managers activate are listed (#261, #327)
Conflict in ROADMAP.md: kept both 2026-10-11 progress-log lines (New
Document from next, then the font activation line).
2026-10-11 02:20:28 +00:00
aiongg 3bda745a52 Tools: live corners round a frame's corners by dragging
A selected, unlocked rectangle frame at least 40 px on screen shows the
yellow widget near its top-right corner. Clicking it shows a diamond at
each corner. Dragging a diamond along its edge sets the size of all four
corners as one undo step (Shift: that corner only), clamped to half the
shorter side. Alt-click steps that corner's shape; Shift+Alt-click steps
every corner. Escape, a click on empty canvas, another selection or
another tool ends corner editing. Positions follow the frame's transform.

object.cornerOptions takes corners: four {shape, size} entries. Tools
gain Action::Handled and the LiveCornerWidget/LiveCornerDiamond overlays,
drawn by the canvas in the live_corner theme token. Fixes #186.
2026-10-11 02:16:21 +00:00
aiongg 833c0ec8d6 Fonts: macOS fonts that font services and managers activate are listed
The system font scan also reads the font files CoreText has available
(core-text's collection of all available fonts, safe API), so fonts that
Adobe Fonts, RightFont and other font managers register from their own
folders are cataloged. Files the font folders already hold are read once;
paths that lead to no file are skipped. FontDb::global serves the desktop
app and designcraft-cli alike; the scan stays in the background.

Fixes #261, #327.
2026-10-11 01:50:32 +00:00
Abdelrehman Fouad 62faa8eee2 Text: Optical kerning spaces pairs from their outlines
Optical kerning was treated exactly like Metrics: the font's kern pairs
and nothing else, so text set with Optical in InDesign came out wider,
rewrapped and went overset in tightly fitted frames.

Now the font's pairs are turned off for Optical text and each pair is
spaced from its outlines (crates/compose/src/optical.rs): every glyph's
ink is sampled into 28 bands across the em, the pair's mean and smallest
ink gap over the bands both glyphs ink are compared with the font's
stem-to-stem gap (the pair nn, or HH then oo), and the pair moves 60% of
the way towards it, keeping 30% of that gap and moving at most -0.12 or
+0.06 em. Latin, Greek and Cyrillic letters, digits and Western
punctuation take part; spaces, marks, missing glyphs, right-to-left
runs, upright and tate-chu-yoko glyphs and pairs across a fallback font
keep their font spacing. Profiles and references are cached, bounded.

Tests: AV, To and r. close up and nn stays; HH closes up under Optical
but not Metrics (fails on main); every change on a pangram stays within
the bounds and spaces keep their width; profile, gap, adjustment and
character-class unit tests.

Docs: typography parity Kerning row and remaining effort, gap 4,
ROADMAP milestone 6 and progress log.

Fixes #181.
2026-10-11 03:25:01 +02:00
aiongg ce144a614a Merge remote-tracking branch 'upstream/main' into next
# Conflicts:
#	ROADMAP.md
2026-10-11 00:30:35 +00:00
skylar taylor-barrick a77bf8bff5 UI.9: reconcile upstream before shared UI landing 2026-10-10 18:00:07 -06:00
Brandon ThomasandClaude Opus 5.5 a9124a0517 Release prep: version 0.6.0; Linux riscv64 tar.gz in releases (#398)
- release.yml: new linux-riscv64 job (cross-compiled on ubuntu-24.04 against
  ports.ubuntu.com riscv64 libs, tar.gz only, CLI smoke-tested under QEMU),
  ported from GridCraft; the draft release job needs it, so SHA256SUMS covers it.
- packaging/linux/package.sh: CROSS_ARCH/CROSS_TARGET/CROSS_COMPILE/EMULATOR
  support, as in GridCraft.
- cargo xtask version set 0.6.0; README, hardware parity and ROADMAP log note
  the new platform.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:33:30 -04:00
aiongg 0430763918 Merge #325 update: New Document in the progress docs
The PR was rebased onto upstream main; its code commits are unchanged
(range-diff shows only ROADMAP lines moved into the new docs commit), so
next already carries them.

Conflicts: crates/ui-egui/src/{dialogs.rs,i18n.rs,i18n/it.rs,lib.rs}:
next's versions (the old PR code plus next's own dialogs, findFont and
numberingSection work). ROADMAP.md and docs/target-app-parity.md take the
PR's New Document entries.
2026-10-10 23:22:41 +00:00
moppeeerandClaude Opus 5.5 543fc41016 docs: New Document in the progress docs
Rebased on the progress-docs restructure (#355): the ROADMAP bullets this
PR added moved to the inventory in docs/target-app-parity.md, and the
ROADMAP progress log gets a line for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:06:58 +02:00
skylar taylor-barrick c62866443c UI.9: integrate shared panel docking and preserve workspace behavior 2026-10-10 14:42:40 -06:00
aiongg 279336c445 Startup: pick the GPU that drives the display, restart on the next adapter or backend, and log startup errors
Ported from VectorCraft's start-up fixes (#502, #545, #651, the display-GPU
choice, #806, #964) and reconciled with the backend fallback of #297:

- The app picks the window's adapter itself (eframe's native_adapter_selector,
  apps/designcraft/src/gpu.rs): adapters that can't present never, then
  WGPU_ADAPTER_NAME, hardware before software, native backends before OpenGL,
  the GPU that drives the primary display (Windows EnumDisplayDevices through
  winsafe, Linux /sys/class/drm), the power preference (WGPU_POWER_PREF;
  otherwise power saving on Windows and macOS, the system's order elsewhere),
  then DX12 before Vulkan. Every adapter and the choice are logged.
- wgpu's indirect-call validation is off (WGPU_VALIDATION_INDIRECT_CALL=1 turns
  it back on): its compute shader failed to compile on an Intel Mac and lost
  the Metal device (#334, first error).
- run_native runs under catch_unwind. A wgpu error or a panic in wgpu,
  egui-wgpu or naga during the first frames, or a first frame that doesn't
  reach the screen in 20 s, starts the app again without that adapter
  (DESIGNCRAFT_GPU_SKIP; exec on Unix, a new process on Windows). When no
  adapter of the start's backends is left, it starts again with the next
  backend. Each restart leaves out one more adapter or backend, so they end.
  Adapters without PCI ids (Metal) are keyed by name.
- The backend fallback (gpu/backend.rs, gpu.json) stays for driver faults that
  take the process down. Windows tries DirectX 12, then OpenGL, then Vulkan,
  each in its own instance: Vulkan last because creating its instance loads
  every installed Vulkan driver, and OpenGL kept out of the DX12 instance
  because creating it crashed in AMD's atio6axx.dll (#167).
- main returns an ExitCode and logs why the app stopped to standard error and
  the log file, which is all a Windows start without a console leaves (#273).

README, architecture, gaps, hardware and target-app parity docs and ROADMAP
describe the choice, the restarts and the variables.
2026-10-10 20:26:06 +00:00
Brandon ThomasandClaude Opus 5.5 a127c6d5c3 Progress docs: target-app parity, gaps, alpha gate, readiness by audience (#355)
Progress docs per craftrules standards/progress-docs.md: target-app parity, gaps, roadmap with alpha gate, readiness by audience (full / mainstream / essentials) with Opus 5.5 hours, and feature, format, hardware, UI and localization parity docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:09:17 -04:00
c657d8545e PDF export: options dialog with remembered settings (#196)
* PDF export: Export Adobe PDF options dialog with remembered settings

File > Export PDF... now opens an options dialog (General / Compression /
Marks and Bleeds / Advanced) that exposes only the parameters the engine's
file.exportPdf already supports: PDF standard, image compression, flattener
preset, spreads, document bleed, crop/bleed/page-info marks with weight and
offset, tagged PDF, page range, and the title. Choosing a preset fills the
dependent fields; editing any field switches the preset to Custom.

The last-used settings persist in ui.json (UiState.pdfExport) and are reused
across exports and sessions. Programmatic callers that pass explicit params
(path, options) still export directly without the dialog; cancelling the save
picker writes nothing.

Adds English and ar/pt-br strings, unit tests for the settings serde, the
dialog defaults and preset mapping, the open/confirm flow (stub writer), and
the menu dispatch, plus a ROADMAP note.

* PDF export: rename dialog title to "Export PDF"

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:23:32 -04:00
736acffe5f Desktop: pick the graphics backend before the window exists; DirectX 12 on Windows (#297)
* Desktop: pick the graphics backend before the window exists; DirectX 12 on Windows

On an AMD hybrid-graphics laptop (Radeon RX 6800M beside an integrated
Radeon, with two versions of amdvlk64.dll loaded into one process) the
app died a few seconds after launch with an access violation inside
AMD's Vulkan driver at the first swapchain present. Nothing reached the
log: a fault inside a driver is a native crash the engine's panic guard
never sees. wgpu's default backend set (PRIMARY | GL) enumerates Vulkan
first, so every start on that machine went down the same path, while
WGPU_BACKEND=dx12 and WGPU_BACKEND=gl ran fine and the headless CPU
renderer was never affected.

The desktop app now chooses the backend before creating the window
(apps/designcraft/src/gpu.rs): DirectX 12 first on Windows, then Vulkan,
then OpenGL; Vulkan then OpenGL on Linux; Metal on macOS. OpenGL stays
in the set as the in-process fallback when the chosen backend has no
adapter. WGPU_BACKEND still overrides everything and turns the fallback
off.

Start-up fallback: gpu.json in the settings directory records the
backend being tried until the third frame begins (the first has been
presented by then) or the app exits normally. A record left behind means
that backend never showed a frame, so the next start skips it, tries the
next candidate and says so in the status bar. The list starts over when
every candidate has failed or the app version changes;
DESIGNCRAFT_NO_PREFS records nothing. The adapter, backend and driver
version are logged at start-up for bug reports.

Verified on the affected machine: a default start runs on DirectX 12; a
simulated crashed DX12 start moved to Vulkan, which faulted for real,
and the start after that came up on OpenGL with the status line. Nine
unit tests cover the chooser and the record; cargo xtask ci passes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Merge origin/main; on Windows OpenGL isn't added to the DX12 or Vulkan instance (#167)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 04:11:47 -04:00
d0d4812ada Set a text selection as warichu inside the parent line (#280)
* Set warichu as smaller lines inside the parent line.

Type › Warichu stacks the selected run after the line is broken, closes up the rest of the line, and round-trips the IDML warichu properties. The line break itself still sees the full-size run.

* Keep clicks, the caret, and Up/Down on each warichu row.

The glyphs were already stacked inside the parent line, but hit testing, the caret, and vertical movement still used that parent line. Break minimums, row widths, spacing, and tab leaders now follow the small lines, and underlines sit on the small baseline.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:47:14 -04:00
9e4f69428f I18n italian (#218)
* i18n: add Italian (it) UI translation

Adds an Italian interface catalog (crates/ui-egui/src/i18n/it.rs) covering every
key of the shared table and of the Arabic table (1,430 strings), written from the
English source using ordinary Italian desktop-publishing vocabulary.

Registers "Italiano" in Edit > Interface Language, wires it into tr(), and extends
the i18n and menu tests (lookup, no duplicates, full key coverage, translated menu
titles, language switch frames).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* contributors: add emanuelebonaventura (display name insanityxoff)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 03:24:12 -04:00
60ad82497b Text frames: column rules on screen, in PDF and in IDML; Column Rules in Text Frame Options (#274)
* Text frames: column rules draw between columns on screen and in PDF

A frame with Column Rule on gets a bar centred in each gutter, inside the
insets, broken around paragraphs that span columns; right-to-left and
vertical frames included. object.textFrameOptions takes columnRuleWeight
and columnRuleColor (unknown swatches are an error) and an inset array
whose null sides stay; document.inspect reports the rule.

* Text Frame Options: Column Rules fields; OK changes only edited fields

The dialog shows the selected frame's options, with an inset field per
side, and sends only what the user changed, so unequal insets survive.
The rule weight is read in points.

* Column rules: tint, horizontal offset and top/bottom insets; IDML import and export

Text Frame Options gains Tint %, Top Inset, Bottom Inset and Horizontal
Offset for the rule, and object.textFrameOptions and document.inspect
take and report them. IDML reads the ColumnRule attributes of
TextFramePreference on frames and object styles and writes them when a
rule is set.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 02:49:00 -04:00
01500ceb04 Paragraph Rules: a dialog for the selected paragraphs and a section in Paragraph Style Options (#263)
* Paragraph rules: a partial ruleAbove/ruleBelow edit keeps the other rule fields; Rule Above honours Text width

* Paragraph Rules: dialog for the selected paragraphs and a Paragraph Style Options section

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 02:28:09 -04:00
464a442802 PDF/X-4: RGB images carry sRGB, no image interpolation, pdf:Trapped in XMP (#223)
- An RGB image with no ICC profile of its own was written as DeviceRGB
  under the CMYK output intent, and the export only warned. In PDF/X-4
  it now carries the sRGB profile (assets/icc/sRGB-v2-magic.icc, ICC
  v2.1, CC0). JPEGs stay passed through. Untagged PNG, GIF and WebP,
  and the decoded TIFF, BMP and PSD fallback, go in as RGB with the
  profile. A re-encoded JPEG keeps the source's RGB profile or gets
  sRGB. Grey images and images with their own profile are unchanged.
- Every image constructor asked for interpolation. PDF/X-4 and PDF/A
  exports now write images without it, which also removes the
  ImageInterpolation error that stopped PDF/A exports with a placed
  image. Plain exports keep it.
- make_pdfx4 writes pdf:Trapped False into the XMP, matching
  /Trapped/False in the document information.
- check_pdfx4 reports an image that asks for interpolation.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 02:22:52 -04:00
e161878e4c Hyphenation: Spanish text is hyphenated with Javier Bezos's patterns (#222)
Text set in Spanish was not hyphenated at all. It now uses Javier
Bezos's Liang patterns, hyph-es.tex 5.0 from hyph-utf8 under MIT,
through the hypher crate 0.1.8 with only its alloc and spanish
features. The full feature, which embeds 48 languages under several
licences, stays off.

- hyphen::Lang names the hyphenator for a run: English, or a hypher
  language. Lang::for_language maps a language name or locale code.
- word_breaks_in and hyphen_points_in take the language. word_breaks
  and hyphen_points stay as the English entry points.
- Composition hyphenates a non-English run whose language has
  patterns and skips the rest. The word cache key includes the
  language.
- NOTICE carries the Bezos copyright line and the MIT text.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 02:21:25 -04:00
e904bd6057 Add Ukrainian interface translation (#236)
* UK1: add Ukrainian interface translation

Translate existing interface catalogs and wire Ukrainian menu selection, contextual publishing labels and grammatical counters. Validate persisted selection, untouched document data, placeholder/key coverage and bundled Cyrillic glyphs.

Co-Authored-By: Codex <noreply@openai.com>

* i18n uk: translate the open and place alert titles

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 01:51:35 -04:00
skylar taylor-barrick 452134c13b FID2: preserve decoration color resets, geometry and paint order (#230) 2026-10-10 01:50:52 -04:00
skylar taylor-barrick cfac46a8d9 FID1: prevent mixed gradient color spaces from aborting PDF export (#229) 2026-10-10 01:50:47 -04:00
Vikram Bhaskaran 67d4539df7 Distribute selected table rows evenly and keep the edit undoable. (#224)
The rows share one fixed height that preserves their composed total. Merged rows and rows that are not laid out are refused, and the table stays unchanged.
2026-10-10 01:50:43 -04:00
aiongg 9b8aaadae0 UI: ruler units by right-click, Language in Properties (#145)
* UI: right-clicking a ruler picks that ruler's units, and the vertical ruler measures in the vertical units (#133)

* UI: the Properties panel's Character section sets the text's language, from the same list as the Character panel (#130)
2026-10-10 01:14:10 -04:00
Brandon Thomas f4a4c9709b Merge pull request #76 from CameronGilroy/pr/data-merge
Data merge: linked source, placeholders, preview, and tiled merge
2026-10-07 22:53:41 -04:00
Brandon ThomasandClaude Opus 5.5 8d2d180062 Merge origin/main into multi-language-fix
Resolve i18n conflicts: keep this branch's revised zh/ar wording plus
main's new rows (font-name, missing-glyph, Korean line-break labels).
Brazilian Portuguese (#103) now uses the same cached HashMap lookup as
Arabic; the cached-lookup test covers pt-br too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:32:13 -04:00
Brandon ThomasandClaude Opus 5.5 48dfbcfd19 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 <noreply@anthropic.com>
2026-10-07 22:28:15 -04:00
Brandon Thomas 9f2f61bfbf Merge pull request #82 from aiongg/fix/psd-channels
Images: PSD composites decode by colour mode
2026-10-07 22:13:10 -04:00
Brandon ThomasandClaude Opus 5.5 fdffbb4b23 M9: distinct zh label for Revert; translations are our own
Revert no longer shares Undo's Simplified Chinese label. Drop the
comments and ROADMAP wording that tied terminology to a vendor's help.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:01:57 -04:00
aiongg 99f1e653f2 Text: locale-coded language names (de_DE_2006) get their language
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).
2026-10-07 10:44:46 -04:00
aiongg c64e538de5 UI: the font menu groups fonts by script, CJK fonts by language
Font menus list Western (and other-script) families first, then
Japanese, Simplified Chinese, Traditional Chinese and Korean families,
each group alphabetical by the name shown, with a separator between
groups. The group comes from the font's own data, strongest signal
first: the meta table's dlng (else slng) languages, family names in a
CJK language (Windows and Macintosh language IDs), OS/2 code pages (JIS,
Chinese Simplified, Korean Wansung, Chinese Traditional, Korean Johab);
a font declaring none of these goes by what it covers (kana, Hangul,
ideographs). Ties go Japanese, Simplified, Traditional, Korean. Loaded faces are classified
once; installed fonts by the font scan, which also reads their OS/2 and
meta tables (and cmap only for fonts declaring nothing), capped.

CJK families show their family name in their language, the English
name on hover; Preferences > Type > Show Font Names in English
(prefs.set {showFontNamesInEnglish}, off by default) shows English
names. Search matches either name; documents store the English name.
The Character panel, Control bar, Properties, style dialogs, Find Font
and Glyphs panel lists all follow this order. The main font menu stays
open while searching or starring (it closed on any click inside).

Font previews don't draw DesignCraft's missing-glyph box for fonts
whose .notdef is empty.
2026-10-07 10:44:46 -04:00
aiongg 1ef187ad02 Text: missing glyphs show as boxes
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.
2026-10-07 10:44:46 -04:00
aiongg a08198e139 CJK 1.11: Chinese language names in their common spellings
language_tag ignores case and punctuation and recognises Chinese in its
common spellings and locale codes (Simplified Chinese, Chinese
(Simplified), zh_CN, zh-Hans; Traditional Chinese, zh_TW, zh_HK,
zh-Hant), and Japanese and Korean locale codes (ja_JP, ko_KR).
Typographer's quotes for Chinese and Japanese follow the tag, so every
spelling takes the same quotes. IDML import keeps the name as written.
2026-10-07 10:44:46 -04:00
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 1574dcdd45 CJK 1.6: upright text is shaped top to bottom
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.
2026-10-07 09:33:52 -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 2da99b70e2 CJK 1.2: font fallback follows the language
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.
2026-10-07 09:33:51 -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
Shao Wenbin 1c792b41e4 M9: remove stale localization terminology references
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.
2026-10-07 19:47:38 +08:00
Shao Wenbin f52c591b87 M9: cache final RTL label layouts
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.
2026-10-07 19:45:38 +08:00
Shao Wenbin 0f09d48a7c Adjust all UI translation according to InDesign UI 2026-10-07 19:45:38 +08:00
Shao Wenbin 02c5f61173 AR1.1: verify Kashida fallback without Tatweel fonts 2026-10-07 19:37:03 +08:00