mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-25 18:56:50 -06:00
Conflicts resolved per file (both sides kept unless noted):
crates/engine/src/atomic.rs | 1 | main's: `write_file` removed (main routes command writes through `crate::io::write_file`); its one caller in file.rs now uses `crate::io::write_file`
crates/engine/src/cmd/datamerge/fill.rs | 1 | next's `Graphic` literal (has `clip` and `wrap`, which next's `Graphic` carries)
crates/engine/src/cmd/datamerge/mod.rs | 1 | main's `read_path_capped` (crate::io metadata/read_capped, roots checked); next's capped read is the same behaviour
crates/engine/src/cmd/datamerge/parse.rs | 1 | main's (`crate::io::metadata`, `crate::io::Metadata`)
crates/engine/src/cmd/edit.rs | 1 | next's `spread_param(p, "edit.paste")` (the second argument is the command id on both sides)
crates/engine/src/cmd/export.rs | 3 | main's: EPUB params doc, `toc_title` once (main's position; next's duplicate dropped)
crates/engine/src/cmd/file.rs | 7 | place doc: next's (lists page/rect/autoflow params #803 checks); next's recent-file functions + main's `foreign_document`; next's open (load_bytes, warnings, non-DesignCraft refusal) with main's checked `crate::io::read_file` for the bytes; main's `check_write` be
crates/engine/src/cmd/find.rs | 7 | main's Find scope (FindContext, scope/selection/story, continuation, UTF-8 repair) on next's `designcraft_compose::grep::Grep` (dialect GREP): find uses `find_all` capped at MAX_MATCHES, change uses `replacements`/`find_all` (literal change text not expanded), fin
crates/engine/src/cmd/fonts.rs | 1 | next's (`has_style`, resolved/replacement face); `has_style` already compares styles ignoring spacing and case, which is main's `same_style` fix; main's test kept
crates/engine/src/cmd/interchange.rs | 1 | next's `import_idml_report_with_previews` (previews for unreadable links)
crates/engine/src/cmd/layout.rs | 8 | pages.insert: main's overflow-safe `after` + next's checked `parent_ref`; main's duplicateSpread doc; layout.section: next's `SectionEdit` (already keeps unnamed settings, #717) with main's `d.repaginate()` moved into `SectionEdit::apply` (remove and set paths),
crates/engine/src/cmd/links.rs | 5 | next's links.list (no doc clone); main's `text_status` via crate::io; `use std::io::Read` dropped (unused after main's read_capped); test literals main's. `link_missing` (next) uses `crate::io::exists` for the file-guard scan
crates/engine/src/cmd/object.rs | 4 | object.fill/stroke tint-alone: main's (23e1680e7; out-of-range tint is an error, which main's test asserts); object.fit doc: next's (lists `axis`)
crates/engine/src/cmd/paths.rs | 1 | next's (shatai transform on outlines)
crates/engine/src/cmd/place_text.rs | 1 | next's `add_flow_frame` (used by autoflow)
crates/engine/src/cmd/preflight.rs | 2 | next's overset/unplacedStory issue (its `overset_location` already applies the parent transform main's #738 fix adds); next's two test modules kept with main's
crates/engine/src/cmd/scripts.rs | 1 | combined: next's `in_undo_step = false` (#406) + main's full rollback of a failed script (#709)
crates/engine/src/cmd/style.rs | 14 | param docs next's (gradient swatch.create, swatch stops, model colours); create_para next's; rename_style main's structure (rename entry, then `redirect_style_refs`), redirect has next's full reference set (cells, defaults, notes, TOC) + main's never-based-on-it
crates/engine/src/cmd/text.rs | 10 | main's selectSentence/Paragraph/Story + select_around; next's selectionAttrs (mixed) + main's openTypeFeatures/type.feature; main's comments; tracked typing: next's `reserve_command_text` + main's `insert_format_at`; empty-paragraph mark formatting main's (with `
crates/engine/src/cmd/thread.rs | 1 | next's `spread_param(p, "text.thread")`
crates/engine/src/dtd.rs | 1 | next's linear, depth-bounded `expand_parameter_entities` with main's budget (input + 1 MiB, `MAX_EXPANDED_BYTES`) and message ("expand the DTD beyond"); 16 MiB constant removed; both sides' tests pass on message
crates/engine/src/guard.rs | 2 | main's whole-transaction snapshot (docs, active, journal; #742) + next's `in_undo_step` restore; tests both
crates/engine/src/lib.rs | 5 | LoadedGraphic doc next's; next's `new_document_preview`; execute: main's `discard_stale_find_scope()` before next's nested-undo run; modules both (tests_autosize, tests_file_guard, tests_params, tests_roots)
crates/engine/src/recovery.rs | 1 | next's test (superset of main's assertion)
crates/engine/src/tests.rs | 4 | moved tests deduplicated (identical on both sides); next's preflight and direct-selection/kerning tests + main's style-delete, locked-frame and baseline-fit tests
crates/engine/src/tests_idml.rs | 3 | main's order (drop cap and margins tests identical on both sides, once each)
crates/ui-egui/src/canvas.rs | 3 | next's screen_options/DisplayKey block and tests kept; two tests that auto-merge had added twice (parent_guides_on_a_page_are_not_picked_there, baseline_grid_lines_are_the_ones_on_the_page) kept once; main's `typing`/`screen_at`/`click` helpers and its tests clicks
crates/ui-egui/src/control.rs | 3 | main's `count` check and pending budget (control queue + synthetic queue); exact-budget test runs both sides' cases (click 255 on 9,492 pending; drag 9492×255)
crates/ui-egui/src/cursors.rs | 1 | main's (same Pen cursor set)
crates/ui-egui/src/dialogs.rs | 14 | dialog body (newDocument … alert match) is next's text, with main's Find/Change scope reset (`set_field`, `reset_find_scope`, `find_change_params`) and main's Create Guides / Margins and Columns arms ported in; `Dialog::new` ruby section (next) + main's `set_fi
crates/ui-egui/src/i18n.rs | 7 | ka and pl modules, languages, statics and lookups both; `category_first` covers uk, ru, pl, ka and `count_label` keeps next's Czech plurals; shared TABLE union; next's duplicate rows for Auto, Number:, Gutter:, Top:, Bottom:, Inside:, Outside:, Left:, Right: dropped
crates/ui-egui/src/i18n/ar.rs | 2 | union of rows, no duplicate keys
crates/ui-egui/src/i18n/it.rs | 1 | union of rows, no duplicate keys
crates/ui-egui/src/i18n/ja.rs | 1 | union of rows, no duplicate keys
crates/ui-egui/src/i18n/pt_br.rs | 1 | union of rows, no duplicate keys
crates/ui-egui/src/i18n/uk.rs | 1 | union of rows, no duplicate keys
crates/ui-egui/src/icons.rs | 2 | both tests (main's typography icon, next's distribute icons)
crates/ui-egui/src/lib.rs | 11 | UiState: next's tabs_panel + main's last_color/last_gradient; `cursors` field once; next's SVG paste and paste-on-spread-in-view; next's extensionless document open; opens_as_document next's comment; tests both (next's control bar / key-name tests, main's rich black
crates/ui-egui/src/menus.rs | 15 | app.language doc next's text with pl and ka; UI_COMMANDS both (next's Tabs, Toggle Presentation, Apply None text; main's panel commands, Apply Color, Apply Gradient); Edit › Interface Language stays out (next moved it to Preferences); Type menu main's order with
crates/ui-egui/src/panels/glyphs.rs | 1 | next's (already has main's scroll-bar fix); the scroll-bar test module auto-merge had added twice kept once
crates/ui-egui/src/panels/layers.rs | 3 | next's drag-and-drop `object_rows`, with main's features ported into it: selection reveal (layer opens, row scrolls into view once), selected rows highlighted (also a group whose member is selected), double-click rename in place (`object.rename` queued like
crates/ui-egui/src/panels/mod.rs | 6 | tabs + typography modules and color_panel_tests; SelInfo has wrap_side/wrap_contour and wrap_offsets (offsets from the graphic's own wrap when it is selected); next's swatch_picker returning the chip response; next's content_selection_tests
crates/ui-egui/src/panels/pages.rs | 1 | next's (parents dragged by id)
crates/ui-egui/src/panels/properties.rs | 4 | next's swatch popup (Apply To, Tint); main's four wrap offsets then next's Wrap To / Contour Type; test fixtures main's (`..Default::default()`, no unused builder)
crates/ui-egui/src/panels/swatches.rs | 3 | main's Delete Swatch button + next's New Swatch comment; next's draggable rows; click: main's PICKED + fill/stroke in front, with next's no-apply on double-click
crates/ui-egui/src/toolbar.rs | 2 | main's Apply Color / Gradient / None wells and functions; next's apply_none doc line
crates/compose/src/lib.rs | 5 | next's line loop kept (mark_leading + last_line_mark, wrap segments/carry, grid gyoudori); main's mark-chars base_leading block (auto-merged) stays beside it. `fits`: next's #106 rule (baseline fit for horizontal frames, full descent for paths, vertical text, footnote
crates/compose/src/tests.rs | 5 | next's tests (tombstone set, sliver/one-sided/contour/clipping wrap tests, borders, lists, kenten, grid, anchored wrap, balance, shaped frames) plus main's an_empty_last_paragraph_isnt_overset; main's wrap_object_beside_one_narrow_column_leaves_the_other_alone re-ad
crates/doc/src/assets.rs | 1 | main's doc comment (same meaning)
crates/doc/src/build.rs | 3 | parent_page_for: next's parent_page_for_side (RTL-aware); single_sided_page_shows_first_parent_page test kept once, at main's position
crates/doc/src/item.rs | 1 | next's rotated_group_bounds_follow_the_children_outlines test kept
crates/doc/src/lib.rs | 1 | next's kerning_increment field kept; main's "Bulleted & Numbered" wording
crates/doc/src/page.rs | 1 | next's outer_page_indices + parent_page_for_side (integration of parent-margins with rtl-master-sides); main's parent_page_index dropped, its only caller (build.rs) uses parent_page_for_side
crates/doc/src/slice.rs | 5 | extract: next's `marked()` (get-based, fix_notes/fix_endnotes/fix_marks; main's version indexed slices and could panic); insert_story: main's take() limit on endnote/editorial payload copies with next's id comment, redundant `let ne`/`let nn` dropped; main's excess_note
crates/geom/src/corners.rs | 1 | next's box_outline_rounds_only_closed_corners test kept
crates/render/src/damage.rs | 1 | next's wrapping-anchored-object damage test kept
crates/render/src/fx.rs | 2 | feather: next's choke/corners sigma+join inside main's structure (body and clip at device resolution, with_filters(.., None)); inner glow centre: next's blend `mix` with main's clip argument (bp, xf)
crates/render/src/lib.rs | 3 | next's layer comment; next's front baseline grid draw after items; gradient match on g.kind (main's tuple scrutinee did not match the single-pattern arms)
crates/render/src/text.rs | 5 | paint_glyphs: main's paint_stroke closure structure with next's stroke overprint (overprinting helper, stroke `(TextPaint, bool)`); callers pass next's overprint_fill/overprint_stroke
crates/render/tests/placed_pdf.rs | 1 | main's `near` helper version
crates/tools/src/lib.rs | 1 | main's (PenConvert already declared below, avoids a duplicate variant)
crates/tools/src/pen.rs | 1 | main's module doc
crates/tools/src/select.rs | 1 | next's Drag::Corner variant (live corners) kept
crates/tools/src/tests.rs | 4 | next's tests (content drag, live corners, table border drag, eyedropper matchFrom, fitting handles, pen cursors once); eyedropper_ignores_inactive keeps next's samples_image check (main's exact-JSON compare lacks matchFrom) with main's `..Default::default()` Asset lit
crates/tools/src/text.rs | 2 | next's table border drag (BorderDrag, border_at) kept, then main's comment on locked text frames
crates/tools/src/xform.rs | 2 | main's explicit `Vec<&Item>` for shown children; next's eyedropper matchFrom (integration 796e9d713)
Cargo.toml | 2 | main's craft-pdf workspace dep added (craft-print stays once, in next's craft-libs block); [patch.crates-io] keeps next's winit (rust-windowing #4694 backport) and adds main's krilla patch; main's skylartaylor winit left out (see UNSURE 1)
Cargo.lock | 1 | union: next's craft-atomic and craft-cms entries plus main's craft-pdf entry; needs `cargo` normalisation
README.md | 1 | Open bullet combines INDD import via indd-utils (next) and editable PowerPoint export (main); main's paragraph break kept
contributors/people.toml | 1 | both people kept (switch87 from next, Nmarello from main)
apps/designcraft-cli/src/main.rs | 4 | next's `Export` enum dispatch kept; main's .pptx export added as `Export::Pptx` (run prints slides and warnings, script runs file.exportPptx); main's `--pdf-options` null-as-{} parse; usage lines list .pptx
apps/designcraft/src/main.rs | 3 | `startup.leave()` right after `app.ui` (main #380) and next's macOS `sync_movable` kept after the IME discard; next's Apple Events install kept; main's macOS Dock Quit `with_close_requested_on_terminate` block left out (needs the skylartaylor winit; UNSURE 1)
crates/fonts/src/fontdb.rs | 9 | FONT6 aliases (next) combined with #911 WWS names (main): ScannedFace and both CatalogEntry constructors carry `wws` and `aliases`; `canonical_family` falls back to WWS family names; `exact_style_traits` canonicalises then tries typographic, then WWS names (`named_st
crates/format/src/lib.rs | 1 | tests: both sides kept (next's r7a nullable-default tests; main's oversized ZIP entry/asset/entry-count tests)
crates/idml/src/import.rs | 11 | main's bounded `Package` reader under next's `import_idml_report_with_previews` (metadata read through `pkg.part`); next's `tab_list_repaired` kept; ruler origin: next's `settings.ruler_origin`, and main's guide import reads it (main's private `RulerOrigin` enum and
crates/idml/src/tests.rs | 6 | item-level union: all next tests plus main's new ones (ruler guides, package limits, CJK leading model, liquid page rules module, mojikumi); no test dropped
crates/mcp/src/backend.rs | 1 | test comment merged from both
crates/pdf/src/tests.rs | 1 | tests: both sides kept
crates/pdf/src/text.rs | 4 | next's (skew `tan`, cluster `start`, ruby overprint warning); main had no change here beyond base
crates/textimport/src/docx.rs | 5 | next's (ListCounter integration for list levels, page-break test position); main's side was the older form of the same code
crates/epub/src/lib.rs | 30 | non-test code is next's (StoryWriter holds img_src/images/no_alt/heading_ids; covers main's #787/#791/#782 fixes, plus #314 group transforms, anchored images in images_without_alt, endnote story skipped in the TOC); tests: next's set plus main's extra css_weight_style a
docs/control-protocol.md | 2 | next's rows (ui.paste, count 1–255, documentGrid) and wording
docs/file-format-parity.md | 1 | rows combined (next's ICC notes, main's TIFF indexed colour)
docs/gaps.md | 2 | status change notes unioned; Flex Layout removed from missing (main implemented it), next's Parent Options sentence kept
docs/localization-parity.md | 4 | Polish and Georgian rows; 5 languages beyond the twelve; file list includes cs and ka; both history rows
docs/roadmap.md | 1 | M1 next's (#186 fixed on next), M2 main's IME wording
docs/target-app-parity.md | 6 | next's status line; File formats row next's, UI/UX row main's (IME no longer failing); UI line next's plus main's Swatches detail; one Page tool line (main's fuller one); Load styles and main's fuller Pathfinder line both kept, main's duplicate short Pathfinder line r
docs/ui-parity.md | 1 | IME row main's, Font menu row next's
i18n/fr.rs: five rows dropped that main's shared table now has (Row, Open Path, Flex Layout, Current Page Number, Rulers).
381 lines
25 KiB
Markdown
381 lines
25 KiB
Markdown
<p align="center">
|
||
<a href="https://getartcraft.com/">
|
||
<picture>
|
||
<source media="(prefers-color-scheme: dark)" srcset="docs/brand/artcraft-logo-white.svg">
|
||
<img alt="ArtCraft" src="docs/brand/artcraft-logo.svg" width="200">
|
||
</picture>
|
||
</a>
|
||
</p>
|
||
|
||
|
||
<h1 align="center">DesignCraft</h1>
|
||
|
||
<p align="center">
|
||
<b>Page layout and publishing; an open-source, clean-room reimplementation of Adobe InDesign, rebuilt in pure Rust.</b>
|
||
</p>
|
||
|
||
<p align="center">
|
||
A fast, open-source, clean-room take on the Adobe InDesign workflow. It runs natively on macOS,
|
||
Windows and Linux, and in the browser via WebAssembly.<br>
|
||
<i>By the ArtCraft team.</i>
|
||
</p>
|
||
|
||
<p align="center">
|
||
<img alt="Written in Rust" src="https://img.shields.io/badge/written%20in-Rust-4d7a0a?style=flat-square&logo=rust&logoColor=white">
|
||
<img alt="Runs on macOS, Windows, Linux and the web" src="https://img.shields.io/badge/runs%20on-macOS%20%C2%B7%20Windows%20%C2%B7%20Linux%20%C2%B7%20Web-7bb51c?style=flat-square">
|
||
<img alt="License: MIT OR Apache-2.0" src="https://img.shields.io/badge/license-MIT%20%2F%20Apache--2.0-4d7a0a?style=flat-square">
|
||
<img alt="Agent-drivable over MCP" src="https://img.shields.io/badge/agents-MCP-7bb51c?style=flat-square">
|
||
</p>
|
||
|
||
<p align="center">
|
||
<a href="https://discord.gg/artcraft"><img alt="Join the ArtCraft community on Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
|
||
</p>
|
||
|
||
<p align="center">
|
||
<a href="https://getartcraft.com/apps/designcraft"><b>DesignCraft on getartcraft.com</b></a> ·
|
||
<a href="https://getartcraft.com/">ArtCraft</a> ·
|
||
<a href="https://getartcraft.com/apps">All Crafting Apps</a>
|
||
</p>
|
||
|
||
<br>
|
||
|
||
<p align="center">
|
||
<img src="docs/images/ui-spread.png" alt="DesignCraft showing a magazine spread: a threaded three-column story is selected with its in/out ports and thread line, the Control panel shows its position in picas and the Properties panel its text frame options" width="100%">
|
||
<br><sub><b>Quarterly, Spring Issue</b>: threaded three-column body text, a wrapped pull quote and parent-page folios, all set by DesignCraft's own paragraph composer.</sub>
|
||
</p>
|
||
|
||
> [!NOTE]
|
||
> **ArtCraft is a community of artists from all walks of life.** Digital, generative, music,
|
||
> games — if you make things, you're one of us. **[Come say hi on Discord](https://discord.gg/artcraft).**
|
||
|
||
<p align="center">
|
||
<a href="#the-sample-magazine">The sample magazine</a> ·
|
||
<a href="#why-designcraft">Why DesignCraft</a> ·
|
||
<a href="#quick-start">Quick start</a> ·
|
||
<a href="#web">Web</a> ·
|
||
<a href="#architecture">Architecture</a> ·
|
||
<a href="#downloads">Downloads</a> ·
|
||
<a href="#the-crafting-apps">The Crafting Apps</a> ·
|
||
<a href="#license-and-credits">License and credits</a>
|
||
</p>
|
||
|
||
## The sample magazine
|
||
|
||
Every page below was laid out by DesignCraft from code (`crates/engine/src/sample.rs`) and exported
|
||
by its own renderer. Try it yourself with `File → New → Sample Document`, or start the app with
|
||
`--sample`.
|
||
|
||
<table>
|
||
<tr>
|
||
<td width="50%" valign="top"><img src="docs/images/page-cover.png" alt="Magazine cover for The Spring Issue, No. 01: a dusk landscape of layered purple hills under a large pale sun, with the white serif headline The Quiet Art of Layout and an italic deck below" width="100%"><p align="center"><sub><b>Cover.</b> A full-bleed graphic frame, display type and an italic deck.</sub></p></td>
|
||
<td width="50%" valign="top"><img src="docs/images/page-2.png" alt="Feature opener titled Notes on the Grid, with an orange FEATURE · DESIGN kicker over a rule, an italic deck, a wide landscape picture with a small caption, and two columns of justified body text above a QUARTERLY folio" width="100%"><p align="center"><sub><b>Styles.</b> Kicker rule, headline, deck, caption and justified two-column body.</sub></p></td>
|
||
</tr>
|
||
<tr>
|
||
<td width="50%" valign="top"><img src="docs/images/page-3.png" alt="Three-column page of justified, hyphenated body text that wraps around a shaded pull quote reading The best layouts disappear. What remains is the story., with a gradient rule and SPRING ISSUE folio at the foot" width="100%"><p align="center"><sub><b>Threading, columns and text wrap.</b> One story flows through three columns and around the pull quote.</sub></p></td>
|
||
<td width="50%" valign="top"><img src="docs/images/page-4.png" alt="Coming Next page titled Color, Ink and Paper on a plum background, with four swatch circles labeled Ink Plum, Sunset, C=100 M=0 Y=0 K=0 and Paper Warm, above a short paragraph of body text" width="100%"><p align="center"><sub><b>Swatches.</b> Named colors and a CMYK process swatch, laid out as a palette.</sub></p></td>
|
||
</tr>
|
||
</table>
|
||
|
||
## Why DesignCraft
|
||
|
||
- **Familiar.** InDesign's layout, tools, menus, panels and shortcuts: spreads and parent pages,
|
||
frames and threaded stories, the Control panel, paragraph and character styles, swatches, text
|
||
wrap and more. If you know InDesign, you already know how to use it.
|
||
- **Beautiful type.** A Knuth–Plass paragraph composer (plus single-line), dictionary hyphenation
|
||
(the public-domain Moby word list plus our own trained patterns), word, letter and glyph-scaling
|
||
justification, keeps, optical margin alignment, columns, baseline grid, tabs, rules and shading.
|
||
Line breaks are identical on screen and in PDF.
|
||
- **Fast.** Multithreaded SIMD rendering (vello_cpu), copy-on-write documents with O(1) undo
|
||
snapshots, and cached composition.
|
||
- **Open.** A documented native format, IDML import and export, INDD import (through
|
||
[indd-utils](https://github.com/storytold/indd-utils)), editable PowerPoint export, and PNG, PDF
|
||
(PDF/X-4, PDF/A-2b) and EPUB export.
|
||
|
||
No subscription, no licence server, no telemetry.
|
||
- **Agent-native.** Every menu item, tool gesture, panel control and dialog can be driven over a
|
||
JSON control channel and an **MCP server**, so Claude and other agents can lay out and edit
|
||
documents like a designer. See [`docs/control-protocol.md`](docs/control-protocol.md) and
|
||
[`docs/mcp.md`](docs/mcp.md).
|
||
- **Everywhere.** One Rust codebase for desktop and the web.
|
||
|
||
## Quick start
|
||
|
||
The egui frontend requires Rust 1.95 or newer (including its shared `craft-ui` components).
|
||
|
||
```sh
|
||
cargo run --release -p designcraft # desktop app (start screen)
|
||
cargo run --release -p designcraft -- --sample # open the sample magazine
|
||
cargo run --release -p designcraft -- --sample --control 7979 # + JSON control channel
|
||
cargo run --release -p designcraft-cli -- run --sample --all-pages out/ # headless: render every page to PNG
|
||
cargo run --release -p designcraft-cli -- commands # list every command
|
||
cargo run --release -p designcraft-cli -- script --help # command script syntax (docs/agents.md)
|
||
cargo xtask ci # fmt, clippy, tests, assets, layering, wasm
|
||
```
|
||
|
||
Japanese text (UI and documents) uses fonts from
|
||
[storytold/craft-fonts](https://github.com/storytold/craft-fonts), an optional build input (font
|
||
files are never committed here; see the
|
||
[craft-fonts integration guide](https://github.com/storytold/craft-fonts/blob/main/docs/integration.md)):
|
||
|
||
```sh
|
||
git clone https://github.com/storytold/craft-fonts ../craft-fonts
|
||
CRAFT_FONTS_DIR="$PWD/../craft-fonts" cargo run --release -p designcraft # absolute path
|
||
```
|
||
|
||
Without it, Japanese falls back to the system's fonts (none on the web). Release builds always
|
||
include it.
|
||
|
||
To drive a running app, send JSON lines to `127.0.0.1:7979`. The protocol is described in
|
||
[`docs/control-protocol.md`](docs/control-protocol.md).
|
||
|
||
### Logs
|
||
|
||
The desktop app writes its `log` records to standard error and to `logs/designcraft.log` in the
|
||
settings directory, beside `ui.json` and `prefs.json` (Linux `~/.config/designcraft/logs/`, or
|
||
`$XDG_CONFIG_HOME/designcraft/logs/`; macOS `~/Library/Application Support/DesignCraft/logs/`;
|
||
Windows `%APPDATA%\DesignCraft\logs\`). A start launched from a desktop menu or the Dock has no
|
||
terminal, so this file is what to attach to a bug report: the graphics adapters found and the
|
||
one drawing, why the app stopped (also when its window never opened), the crash guard's panic
|
||
report and a failed crash-recovery save land there. Each launch moves the previous log to `designcraft.1.log`
|
||
(and that one to `designcraft.2.log`), so the log of a run that crashed survives the next start.
|
||
The file stops growing at 16 MiB. `--version` writes no file.
|
||
|
||
| Variable | Effect |
|
||
|---|---|
|
||
| `RUST_LOG` | Log levels for standard error and the log file. Default: `info` for DesignCraft's own crates, `warn` for everything else. env_logger-style directives replace that, e.g. `RUST_LOG=debug`, `RUST_LOG=warn,designcraft_render=trace` or `RUST_LOG=info,wgpu_core=warn`; a directive ending in `*` covers every target starting with it (`designcraft*=debug`). |
|
||
| `WGPU_BACKEND` | The graphics backend that composites the window: `dx12`, `vulkan`, `gl` or `metal` (a comma-separated list lets wgpu choose among them). Default: DirectX 12 on Windows, Vulkan on Linux, Metal on macOS, with OpenGL as the fallback. Setting it also turns off the start-up fallback described below. |
|
||
| `WGPU_POWER_PREF` | The kind of GPU that draws the window: `low` (integrated), `high` (discrete) or `none` (the system's order). Default: the GPU that drives the primary display, then `low` on Windows and macOS and `none` elsewhere. |
|
||
| `WGPU_ADAPTER_NAME` | The graphics adapter by (part of) its name, any case (`nvidia`, `radv`, `intel`); the log lists the names. |
|
||
| `WGPU_VALIDATION_INDIRECT_CALL` | `1` turns wgpu's check of indirect draw arguments back on. DesignCraft draws nothing indirectly and turns it off: the compute shader it needs failed to compile on an Intel Mac and lost the graphics device (#334). |
|
||
|
||
The logger is `apps/designcraft/src/logging.rs`; the web build logs to the browser console instead.
|
||
|
||
### Portable mode
|
||
|
||
A file named `portable.txt` next to the executable (on macOS, next to `DesignCraft.app`) makes the install portable: settings, logs and
|
||
crash recovery are kept in a `DesignCraftData` folder beside it instead of the per-user folders
|
||
above. The Windows portable zip ships with one; delete it to go back to `%APPDATA%\DesignCraft`.
|
||
If `DesignCraftData` cannot be written (a copy unzipped into Program Files), the per-user folders
|
||
are used instead and the log and the status bar say so.
|
||
`DESIGNCRAFT_CONFIG_DIR` still takes precedence. The code is `apps/designcraft/src/profile.rs`.
|
||
|
||
### Graphics backend and processor
|
||
|
||
The canvas is rendered on the CPU; the GPU only composites the interface, through wgpu, so any
|
||
adapter that can show the window will do. The code is `apps/designcraft/src/gpu.rs` (the adapter)
|
||
and `apps/designcraft/src/gpu/backend.rs` (the backend).
|
||
|
||
- **Backend.** A graphics driver that faults takes the process down before any Rust code can
|
||
catch it, so each start creates its wgpu instance with one backend, chosen before the window
|
||
exists: on Windows DirectX 12, then OpenGL, then Vulkan, each on its own (creating an OpenGL
|
||
instance crashed inside AMD's `atio6axx.dll`, #167, and creating a Vulkan instance loads every
|
||
installed Vulkan driver, of which a faulty one can crash the app); on Linux Vulkan with OpenGL
|
||
beside it, then OpenGL alone; on macOS Metal. Seen in the wild: on an AMD hybrid-graphics laptop
|
||
(a Radeon RX 6800M beside an integrated Radeon) the AMD Vulkan driver faulted at the first
|
||
present, which is why DirectX 12 is the Windows default.
|
||
- **A start that never showed a frame is remembered:** `gpu.json` in the settings directory
|
||
records the backend being tried until the first frame has been presented. A record left behind
|
||
means that backend crashed (or the app was killed) at start-up, so the next start tries the next
|
||
backend and says so in the status bar. The list starts over once every backend has failed, or
|
||
with a new DesignCraft version; delete the file or set `WGPU_BACKEND` to start afresh.
|
||
- **Adapter.** Among the adapters of those backends, the app picks the one that draws the window
|
||
(eframe's `native_adapter_selector`): adapters that report they can't present to the window are
|
||
never tried; then the one `WGPU_ADAPTER_NAME` names; hardware before software (llvmpipe, WARP);
|
||
the native backends before OpenGL; the GPU that drives a display, and among those the one that
|
||
drives the primary display; then the power preference; then a GPU's DirectX 12 adapter before its
|
||
Vulkan one. A GPU without a monitor is the wrong one even when it can present: Windows has to
|
||
copy every frame to the GPU that shows it, and an integrated GPU's driver reset under that,
|
||
losing the graphics device or blanking every monitor for minutes. Windows says which GPU drives
|
||
which display (`EnumDisplayDevices`), Linux through `/sys/class/drm` (a built-in `eDP`, `LVDS` or
|
||
`DSI` panel counts as the primary display); on macOS, or when nothing matches, the power
|
||
preference decides: power saving on Windows and macOS, the system's order elsewhere (Mesa puts
|
||
the GPU the desktop runs on first, and a Wayland compositor may refuse frames from another GPU).
|
||
`WGPU_POWER_PREF` replaces this with the user's choice of kind.
|
||
- **If the window fails on its adapter while starting up** (an error from wgpu, such as a lost
|
||
device, or a panic inside wgpu, egui-wgpu or naga during the first frames), the app starts
|
||
again without that adapter and tries the next one, telling which in the status bar; the run that
|
||
failed keeps its log as `designcraft.1.log`. When no adapter of the start's backends is left, it
|
||
starts again with the next backend. Software renderers (WARP, llvmpipe) come last: they are
|
||
passed over while another backend is left to try, and allowed once no graphics processor could
|
||
show the window. On Unix the new app replaces the process (an AppImage stays mounted); on
|
||
Windows it starts beside it. Adapters are told apart by backend and PCI ids, or by name where the
|
||
backend reports no ids (Metal lists every GPU of a dual-GPU Mac as `0000:0000`). What was left
|
||
out travels in the restarted app's `--gpu-skip=` argument (`Vulkan:1002:164e`, `Metal:Intel
|
||
Iris Pro Graphics`, or a whole backend such as `Dx12`), one more on each restart, so the
|
||
restarts end. When nothing is left, the app logs why and exits with an error. Documents
|
||
recovered after a crash stay in the recovery folder until they are saved or closed, so a restart
|
||
never loses them.
|
||
- **If the window's first frame is late** (20 seconds after the app was created and the files
|
||
named at the start opened), the log and the status bar say so. The adapter counts as hanging,
|
||
and the app starts again without it, only when the UI thread has been stuck in the graphics
|
||
stack, outside DesignCraft's own code, for 60 seconds while the window wasn't known to be
|
||
minimized, covered or in the background. A large document composing on a slow machine never
|
||
counts.
|
||
- **The log** lists the GPUs that drive a display (`display GPUs (PCI vendor:device): 10de:2204
|
||
(primary)`), every adapter with whether it drives a display, the one drawn on and why (`drawing
|
||
on NVIDIA GeForce RTX 3090 (Dx12, DiscreteGpu): it drives the primary display`), and the driver
|
||
version. That is the first thing to look for in a report of a window that stays black or never
|
||
opens.
|
||
- On Linux with Mesa, `MESA_VK_DEVICE_SELECT=10de:2f04!` (vendor:device as `vulkaninfo --summary`
|
||
or `lspci -nn` show it) leaves only that GPU to Vulkan, and `DRI_PRIME=1` picks the other GPU.
|
||
|
||
### Web
|
||
|
||
```sh
|
||
cd apps/designcraft-web && trunk build --release # → dist/web (serve it with any static server)
|
||
cd apps/designcraft-web && trunk serve --release # http://127.0.0.1:8767
|
||
```
|
||
|
||
You need [trunk](https://trunkrs.dev) and the `wasm32-unknown-unknown` target. The same app runs
|
||
through eframe's web runner on WebGPU, falling back to WebGL2 (`?webgl` forces it; `?sample` opens
|
||
the sample magazine). Open and Place use the browser's file picker, and dropping files works too.
|
||
Save and Export download the file. The web build has no control channel.
|
||
|
||
## Architecture
|
||
|
||
DesignCraft is an engine-first Cargo workspace with enforced layering (`cargo xtask layers`). The
|
||
egui frontend is a separate crate, so the UI can be swapped without touching the engine.
|
||
|
||
| Layer | Crates |
|
||
|---|---|
|
||
| L0 | `geom` (paths, units & measurement parsing, corner options) · `color` (CMYK/RGB/Lab, swatches, tints, gradients) |
|
||
| L1 | `doc` (spreads, pages, parents, layers, frames, stories, styles) · `fonts` (font DB, shaping, outlines) |
|
||
| L2 | `compose` (the text engine) |
|
||
| L3 | `render` (vello_cpu) |
|
||
| L4 | `tools` (pointer events → commands + overlays) |
|
||
| L5 | `engine` (session, history, command registry) |
|
||
| L6 | `ui-egui` (InDesign-style UI, control channel) |
|
||
| L7 | `apps/designcraft`, `apps/designcraft-cli`, `apps/designcraft-web` |
|
||
|
||
- **Status and milestones:** [ROADMAP.md](ROADMAP.md) (stage: pre-alpha); how close to InDesign: [`docs/target-app-parity.md`](docs/target-app-parity.md); known gaps: [`docs/gaps.md`](docs/gaps.md)
|
||
- **Contributor and agent rules** (clean-room, asset policy, quality gates): [`AGENTS.md`](AGENTS.md)
|
||
- **Bundled assets:** every one is listed with its licence in [`ASSETS.md`](ASSETS.md)
|
||
- **App icon and colour:** a calico cat in a polka-dot scarf on DesignCraft green `#7bb51c`; see [`assets/app-icon/`](assets/app-icon/README.md)
|
||
|
||
## Downloads
|
||
|
||
**New to DesignCraft?** Download it from the [DesignCraft page on getartcraft.com](https://getartcraft.com/apps/designcraft). That's the easiest way to install it.
|
||
|
||
**Want a specific build or format?** On GitHub, the [latest release](https://github.com/storytold/designcraft/releases/latest) has every build listed below, and [all releases](https://github.com/storytold/designcraft/releases) has earlier versions and their notes. `<ver>` in the file names is the version number, and `SHA256SUMS.txt` lists a checksum for every file.
|
||
|
||
### Windows
|
||
|
||
| Build | Installer | Portable |
|
||
|---|---|---|
|
||
| x64 (64-bit Intel/AMD) | `designcraft-<ver>-windows-x64.msi` | `designcraft-<ver>-windows-x64-portable.zip` |
|
||
| arm64 (Snapdragon and other ARM PCs) | `designcraft-<ver>-windows-arm64.msi` | `designcraft-<ver>-windows-arm64-portable.zip` |
|
||
| x86 (32-bit) | `designcraft-<ver>-windows-x86.msi` | `designcraft-<ver>-windows-x86-portable.zip` |
|
||
|
||
Installers and executables are code-signed.
|
||
|
||
### macOS
|
||
|
||
| Build | File | Notes |
|
||
|---|---|---|
|
||
| App, universal (Apple silicon + Intel) | `designcraft-<ver>-macos-universal.dmg` | Signed and notarized |
|
||
| Command-line tool, universal | `designcraft-cli-<ver>-macos-universal.zip` | Signed and notarized |
|
||
|
||
### Linux
|
||
|
||
| Format | x86_64 | aarch64 (ARM64) | Notes |
|
||
|---|---|---|---|
|
||
| AppImage | `designcraft-<ver>-linux-x86_64.AppImage` | `designcraft-<ver>-linux-aarch64.AppImage` | Runs anywhere; updates itself with [AppImageUpdate](https://github.com/AppImageCommunity/AppImageUpdate) (`.zsync` files) |
|
||
| Flatpak | `designcraft-<ver>-linux-x86_64.flatpak` | `designcraft-<ver>-linux-aarch64.flatpak` | Sandboxed; `flatpak install --user <file>` |
|
||
| Debian/Ubuntu | `designcraft-<ver>-linux-x86_64.deb` | `designcraft-<ver>-linux-aarch64.deb` | |
|
||
| Fedora/RHEL/openSUSE | `designcraft-<ver>-linux-x86_64.rpm` | `designcraft-<ver>-linux-aarch64.rpm` | |
|
||
| Arch Linux/Manjaro | `designcraft-<ver>-linux-x86_64.pkg.tar.zst` | `designcraft-<ver>-linux-aarch64.pkg.tar.zst` | `sudo pacman -U <file>`; installs as `ai.storyteller.designcraft`, replacing an AUR package |
|
||
| Tarball | `designcraft-<ver>-linux-x86_64.tar.gz` | `designcraft-<ver>-linux-aarch64.tar.gz` | Unpack anywhere |
|
||
|
||
RISC-V (riscv64): `designcraft-<ver>-linux-riscv64.tar.gz` only, cross-compiled; needs glibc 2.39+ (Ubuntu 24.04 or newer).
|
||
|
||
#### Community packages
|
||
|
||
These packages are maintained by community members, not by the DesignCraft team.
|
||
|
||
On Gentoo, the [::snakebyte overlay](https://github.com/switch87/snakebyte-overlay) packages the
|
||
Linux release as `media-gfx/designcraft-bin` (prebuilt) and `media-gfx/designcraft` (built from source):
|
||
|
||
```sh
|
||
eselect repository add snakebyte git https://github.com/switch87/snakebyte-overlay.git
|
||
emaint sync -r snakebyte
|
||
echo 'media-gfx/designcraft-bin ~amd64' >> /etc/portage/package.accept_keywords/designcraft
|
||
emerge --ask media-gfx/designcraft-bin
|
||
```
|
||
|
||
### FreeBSD
|
||
|
||
| Build | File |
|
||
|---|---|
|
||
| x86_64 | `designcraft-<ver>-freebsd-x86_64.tar.gz` |
|
||
|
||
### Web (WebAssembly)
|
||
|
||
| Build | File | Notes |
|
||
|---|---|---|
|
||
| Static site | `designcraft-web-<ver>.zip` | Runs in a modern browser; host it on any static server |
|
||
|
||
## The Crafting Apps
|
||
|
||
DesignCraft is one of the **Crafting Apps**: free, open-source creative tools from the
|
||
[ArtCraft](https://getartcraft.com/) team, each written from scratch in Rust and each able to
|
||
stand on its own.
|
||
|
||
| | App | What it's for | Code | Learn more |
|
||
|:-:|---|---|---|---|
|
||
| <img src="https://raw.githubusercontent.com/storytold/photocraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.photocraft.png" alt="" width="32" height="32"> | **PhotoCraft** | Image editing: layers, masks, type and real PSD files | [GitHub](https://github.com/storytold/photocraft) | [Website](https://getartcraft.com/apps/photocraft) |
|
||
| <img src="https://raw.githubusercontent.com/storytold/vectorcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.vectorcraft.png" alt="" width="32" height="32"> | **VectorCraft** | Vector illustration | [GitHub](https://github.com/storytold/vectorcraft) | [Website](https://getartcraft.com/apps/vectorcraft) |
|
||
| <img src="https://raw.githubusercontent.com/storytold/filmcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.filmcraft.png" alt="" width="32" height="32"> | **FilmCraft** | Video editing, color and sound | [GitHub](https://github.com/storytold/filmcraft) | [Website](https://getartcraft.com/apps/filmcraft) |
|
||
| <img src="https://raw.githubusercontent.com/storytold/lightcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.lightcraft.png" alt="" width="32" height="32"> | **LightCraft** | Photo library and raw development | [GitHub](https://github.com/storytold/lightcraft) | [Website](https://getartcraft.com/apps/lightcraft) |
|
||
| <img src="https://raw.githubusercontent.com/storytold/pdfcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.pdfcraft.png" alt="" width="32" height="32"> | **PdfCraft** | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/pdfcraft) | [Website](https://getartcraft.com/apps/pdfcraft) |
|
||
| <img src="https://raw.githubusercontent.com/storytold/effectcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png" alt="" width="32" height="32"> | **EffectCraft** | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) | [Website](https://getartcraft.com/apps/effectcraft) |
|
||
| <img src="https://raw.githubusercontent.com/storytold/designcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.designcraft.png" alt="" width="32" height="32"> | **DesignCraft** | **Page layout and publishing · you are here** | [GitHub](https://github.com/storytold/designcraft) | [Website](https://getartcraft.com/apps/designcraft) |
|
||
|
||
And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video studio for artists who want real control.
|
||
|
||
<br>
|
||
|
||
<p align="center">
|
||
<a href="https://discord.gg/artcraft"><img alt="Join the ArtCraft community on Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
|
||
</p>
|
||
|
||
<h3 align="center">Come make things with us</h3>
|
||
|
||
<p align="center">
|
||
Our Discord is where artists of every kind hang out: people who paint, shoot, draw, cut film,
|
||
set type, and people still figuring out what they like to make. Share what you're working on,
|
||
ask for help, tell us what's broken, or tell us what you wish these tools could do.
|
||
Whatever your medium and however long you've been at it, you're welcome here.
|
||
</p>
|
||
|
||
<p align="center">
|
||
<a href="https://discord.gg/artcraft"><b>discord.gg/artcraft</b></a> ·
|
||
<a href="https://getartcraft.com/">getartcraft.com</a> ·
|
||
<a href="https://getartcraft.com/apps">The Crafting Apps</a> ·
|
||
<a href="https://getartcraft.com/apps/designcraft">DesignCraft</a>
|
||
</p>
|
||
|
||
## License and credits
|
||
|
||
DesignCraft is dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your option.
|
||
Copyright (c) 2026 ArtCraft Team and the DesignCraft contributors. Required notices are in [NOTICE](NOTICE).
|
||
|
||
Bundled fonts, icons, images and other assets keep their own open licenses; each one is listed
|
||
with its author, source and license in [ASSETS.md](ASSETS.md).
|
||
|
||
The bundled fonts, and the craft-fonts fonts embedded by release builds, are under the SIL Open
|
||
Font License; all UI icons are drawn in code and are original.
|
||
|
||
The ArtCraft name, wordmark and logos in [`docs/brand/`](docs/brand/) are trademarks of the
|
||
ArtCraft Team and are not covered by this license. They may be used only unmodified, and only as
|
||
part of this repository and DesignCraft, under [`docs/brand/LICENSE-brand.txt`](docs/brand/LICENSE-brand.txt).
|
||
Forks and modified versions must remove them.
|
||
|
||
<sub>Adobe, Photoshop, Illustrator, Premiere Pro, Lightroom, Acrobat, After Effects and InDesign are trademarks or registered trademarks of Adobe Inc. in the United States and/or other countries. DesignCraft is an independent, open-source project and is not affiliated with, sponsored by or endorsed by Adobe Inc.; these names are used only to describe the workflows it is compatible with.</sub>
|
||
|
||
<br>
|
||
|
||
<p align="center">
|
||
<a href="https://getartcraft.com/"><img alt="ArtCraft" src="docs/brand/artcraft-mark.svg" width="28"></a><br>
|
||
<sub>Made by the <a href="https://getartcraft.com/">ArtCraft</a> team and community.</sub>
|
||
</p>
|