Files
designcraft/README.md
T
aiongg 18ae8e4761 Merge upstream main a184db6f8 with our 15 PRs (batch-0) into next
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).
2026-10-16 03:09:22 +00:00

25 KiB
Raw Blame History

ArtCraft

DesignCraft

Page layout and publishing; an open-source, clean-room reimplementation of Adobe InDesign, rebuilt in pure Rust.

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.
By the ArtCraft team.

Written in Rust Runs on macOS, Windows, Linux and the web License: MIT OR Apache-2.0 Agent-drivable over MCP

Join the ArtCraft community on Discord

DesignCraft on getartcraft.com · ArtCraft · All Crafting Apps


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
Quarterly, Spring Issue: threaded three-column body text, a wrapped pull quote and parent-page folios, all set by DesignCraft's own paragraph composer.

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.

The sample magazine · Why DesignCraft · Quick start · Web · Architecture · Downloads · The Crafting Apps · License and credits

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.

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

Cover. A full-bleed graphic frame, display type and an italic deck.

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

Styles. Kicker rule, headline, deck, caption and justified two-column body.

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

Threading, columns and text wrap. One story flows through three columns and around the pull quote.

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

Swatches. Named colors and a CMYK process swatch, laid out as a palette.

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), 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 and 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).

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, an optional build input (font files are never committed here; see the craft-fonts integration guide):

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.

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

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 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

Downloads

New to DesignCraft? Download it from the DesignCraft page on getartcraft.com. That's the easiest way to install it.

Want a specific build or format? On GitHub, the latest release has every build listed below, and all 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 (.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 packages the Linux release as media-gfx/designcraft-bin (prebuilt) and media-gfx/designcraft (built from source):

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 team, each written from scratch in Rust and each able to stand on its own.

App What it's for Code Learn more
PhotoCraft Image editing: layers, masks, type and real PSD files GitHub Website
VectorCraft Vector illustration GitHub Website
FilmCraft Video editing, color and sound GitHub Website
LightCraft Photo library and raw development GitHub Website
PdfCraft Reading, organizing and protecting PDFs GitHub Website
EffectCraft Motion graphics and visual effects GitHub Website
DesignCraft Page layout and publishing · you are here GitHub Website

And ArtCraft itself, our AI image and video studio for artists who want real control.


Join the ArtCraft community on Discord

Come make things with us

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.

discord.gg/artcraft · getartcraft.com · The Crafting Apps · DesignCraft

License and credits

DesignCraft is dual-licensed under MIT or Apache-2.0, at your option. Copyright (c) 2026 ArtCraft Team and the DesignCraft contributors. Required notices are in 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.

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/ 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. Forks and modified versions must remove them.

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.


ArtCraft
Made by the ArtCraft team and community.