mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-14 19:27:07 -04:00
M6: Adopt craft-print for CUPS and Windows PDF printing
This commit is contained in:
1 parent
16a7365701
commit
9f7da3f6ab
10 files changed
+92
-108
No files matched your search
Generated
+9
@@ -796,6 +796,14 @@ dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-print"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=c22bb1b0ce6316f84973cb69675fe7331c5886cc#c22bb1b0ce6316f84973cb69675fe7331c5886cc"
|
||||
dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-ui"
|
||||
version = "0.1.0"
|
||||
@@ -956,6 +964,7 @@ name = "designcraft-engine"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"caseless",
|
||||
"craft-print",
|
||||
"designcraft-color",
|
||||
"designcraft-compose",
|
||||
"designcraft-doc",
|
||||
|
||||
@@ -11,6 +11,7 @@ rust-version = "1.95"
|
||||
repository = "https://github.com/storytold/designcraft"
|
||||
|
||||
[workspace.dependencies]
|
||||
craft-print = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "c22bb1b0ce6316f84973cb69675fe7331c5886cc" }
|
||||
# internal
|
||||
designcraft-geom = { path = "crates/geom" }
|
||||
designcraft-color = { path = "crates/color" }
|
||||
|
||||
+3
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
**Stage: pre-alpha** · next: alpha, ~0% and ~15–25 h away; beta ~28 points and ~420–750 h beyond that (the core-workflow gate, not the percentage: ready for real work is already ~47%)
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** minor (stage re-normalized to pre-alpha by the core-workflow gate; earlier today: major, full re-measure against InDesign 2026 21.6; restructured to the craftrules progress-docs standard) · **Target:** Adobe InDesign 2026
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (shared print spooler; native Windows validation pending) · **Target:** Adobe InDesign 2026
|
||||
|
||||
DesignCraft aims at full Adobe InDesign parity, and to be better: faster, open (documented JSON
|
||||
format + IDML), scriptable by agents (MCP), and available on the web.
|
||||
@@ -129,6 +129,8 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
|
||||
|
||||
Newest first.
|
||||
|
||||
- **2026-10-11 (printing):** `file.printers` and `file.print` use craft-libs/craft-print for CUPS and Windows. DesignCraft retains PDF export, page selection, spreads, marks and bleed; dry runs create no temporary PDF or print job. Windows keeps sheet size/orientation and corrected duplex binding edges. Headless tests exercise exported bytes, job settings, dry runs and refusals; shared native Windows regression tests are present, with execution pending. PostScript, production print controls and physical output validation remain open. No readiness percentage change.
|
||||
|
||||
- **2026-10-10 (UI.4–UI.5):** Panel tabs can move into other groups, split the dock, float, resize and return to their saved locations through shared docking transactions. Layouts persist in custom workspaces; UI and control commands share the same validated action path. Shared pane geometry and widget-state theme application retain all five palettes and local fonts. Split panes send keyboard input only to the focused canvas and scope contextual toolbar IDs per pane. No readiness percentage change.
|
||||
- **2026-10-10** · Version 0.6.0; releases add a Linux riscv64 tar.gz (cross-compiled, CLI
|
||||
smoke-tested under QEMU).
|
||||
|
||||
@@ -9,6 +9,7 @@ description = "DesignCraft engine: session, documents, history, selection, comma
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
craft-print = { workspace = true }
|
||||
designcraft-images = { workspace = true }
|
||||
designcraft-geom = { workspace = true }
|
||||
designcraft-color = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
//! File › Print: the pages go to a printer through the system print spooler (`lpr`), as PDF.
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::io::Write;
|
||||
//! File › Print: the pages go to a printer through the system print spooler (craft-print), as PDF.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
@@ -20,91 +17,55 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Print…",
|
||||
["File"],
|
||||
Some("Cmd+P"),
|
||||
"{printer?, copies?: 1, pages?: \"1-3,5\", spreads?, marks?, bleed?, dryRun?: bool (only return the spool command)} → {printer, copies, pages, command}",
|
||||
"{printer?, copies?: 1, pages?: \"1-3,5\", spreads?, marks?, bleed?, dryRun?: bool (export and preview submission without printing)} → {printer, copies, pages, command, bytes}",
|
||||
has_doc,
|
||||
print
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn printers() -> Value {
|
||||
let run = |args: &[&str]| {
|
||||
std::process::Command::new("lpstat").args(args).output().ok().map(|o| String::from_utf8_lossy(&o.stdout).to_string()).unwrap_or_default()
|
||||
};
|
||||
let names: Vec<String> = run(&["-e"]).lines().map(str::trim).filter(|l| !l.is_empty()).map(str::to_string).collect();
|
||||
// "system default destination: NAME"
|
||||
let default = run(&["-d"]).rsplit(':').next().map(str::trim).filter(|s| !s.is_empty() && !s.contains("no system default")).map(str::to_string);
|
||||
let printers = craft_print::printers();
|
||||
let default = printers.iter().find(|p| p.default).map(|p| p.name.clone());
|
||||
let names: Vec<_> = printers.into_iter().map(|p| p.name).collect();
|
||||
json!({"printers": names, "default": default})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn printers() -> Value {
|
||||
json!({"printers": [], "default": null})
|
||||
}
|
||||
|
||||
fn print(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let d = &s.doc()?.doc;
|
||||
let opts = super::export::options(p, d.page_count())?;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
// Even a dry run cannot construct a native spool command in the browser.
|
||||
let _ = opts;
|
||||
super::export::options(p, s.doc()?.doc.page_count())?;
|
||||
Err(bad("file.print", "printing isn't available on the web: export a PDF and print it"))
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let copies = p.get("copies").and_then(Value::as_u64).unwrap_or(1).clamp(1, 999);
|
||||
let printer = str_param(p, "printer").map(str::to_string);
|
||||
let dry = p.get("dryRun").and_then(Value::as_bool).unwrap_or(false);
|
||||
let pdf = designcraft_pdf::export_pdf(d, &s.cache, &opts).map_err(|e| EngineError::Other(e.to_string()))?;
|
||||
let mut file = secure_print_file()?;
|
||||
let file_path = file.path().to_path_buf();
|
||||
let mut cmd: Vec<String> = vec!["lpr".into()];
|
||||
if let Some(pr) = &printer {
|
||||
cmd.extend(["-P".into(), pr.clone()]);
|
||||
}
|
||||
if copies > 1 {
|
||||
cmd.extend(["-#".into(), copies.to_string()]);
|
||||
}
|
||||
cmd.extend(["-T".into(), d.title.clone(), file_path.to_string_lossy().to_string()]);
|
||||
let pages = opts.pages.as_ref().map_or(d.page_count(), |v| v.len());
|
||||
let out = json!({"printer": printer, "copies": copies, "pages": pages, "command": cmd, "bytes": pdf.len()});
|
||||
if dry {
|
||||
return Ok(out);
|
||||
}
|
||||
file.as_file_mut().write_all(&pdf).map_err(|e| EngineError::Other(format!("{}: {e}", file.path().display())))?;
|
||||
file.as_file_mut().flush().map_err(|e| EngineError::Other(format!("{}: {e}", file.path().display())))?;
|
||||
let st = std::process::Command::new(&cmd[0]).args(&cmd[1..]).output().map_err(|e| bad("file.print", format!("lpr: {e}")))?;
|
||||
if !st.status.success() {
|
||||
return Err(bad("file.print", String::from_utf8_lossy(&st.stderr).trim().to_string()));
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
print_with(s, p, craft_print::submit)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn secure_print_file() -> Result<tempfile::NamedTempFile> {
|
||||
let file = tempfile::Builder::new()
|
||||
.prefix("designcraft-print-")
|
||||
.suffix(".pdf")
|
||||
.tempfile()
|
||||
.map_err(|e| EngineError::Other(format!("temporary print file: {e}")))?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
file.as_file()
|
||||
.set_permissions(std::fs::Permissions::from_mode(0o600))
|
||||
.map_err(|e| EngineError::Other(format!("{}: {e}", file.path().display())))?;
|
||||
fn print_with(
|
||||
s: &mut Session,
|
||||
p: &Value,
|
||||
submit: impl FnOnce(&[u8], &craft_print::Job) -> std::result::Result<String, craft_print::Error>,
|
||||
) -> Result<Value> {
|
||||
let d = &s.doc()?.doc;
|
||||
let opts = super::export::options(p, d.page_count())?;
|
||||
let copies = p.get("copies").and_then(Value::as_u64).unwrap_or(1).clamp(1, 999) as u32;
|
||||
let job =
|
||||
craft_print::Job { printer: str_param(p, "printer").map(str::to_string), copies, title: d.title.clone(), ..craft_print::Job::default() };
|
||||
let dry = p.get("dryRun").and_then(Value::as_bool).unwrap_or(false);
|
||||
let pdf = designcraft_pdf::export_pdf(d, &s.cache, &opts).map_err(|e| EngineError::Other(e.to_string()))?;
|
||||
let command = craft_print::command_preview(&job).map_err(|e| bad("file.print", e.to_string()))?;
|
||||
let pages = opts.pages.as_ref().map_or(d.page_count(), |v| v.len());
|
||||
let out = json!({"printer": job.printer, "copies": copies, "pages": pages, "command": command, "bytes": pdf.len()});
|
||||
if !dry {
|
||||
submit(&pdf, &job).map_err(|e| bad("file.print", e.to_string()))?;
|
||||
}
|
||||
Ok(file)
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(target_arch = "wasm32")))]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use crate::Session;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn print_builds_the_spool_command() {
|
||||
@@ -112,37 +73,42 @@ mod tests {
|
||||
s.execute("file.new", &json!({"pages": 4})).unwrap();
|
||||
let r = s.execute("file.print", &json!({"printer": "Office", "copies": 2, "pages": "2-3", "dryRun": true})).unwrap();
|
||||
assert_eq!(r["pages"], 2);
|
||||
let cmd: Vec<&str> = r["command"].as_array().unwrap().iter().filter_map(|v| v.as_str()).collect();
|
||||
assert_eq!(&cmd[..5], ["lpr", "-P", "Office", "-#", "2"]);
|
||||
assert!(cmd.last().unwrap().ends_with(".pdf"));
|
||||
assert!(!std::path::Path::new(cmd.last().unwrap()).exists());
|
||||
let command: Vec<&str> = r["command"].as_array().unwrap().iter().filter_map(Value::as_str).collect();
|
||||
if craft_print::backend() == Some(craft_print::Backend::Cups) {
|
||||
assert_eq!(&command[..5], ["lp", "-d", "Office", "-n", "2"]);
|
||||
assert!(command.contains(&"fit-to-page=false"));
|
||||
} else {
|
||||
assert_eq!(command[0], "powershell");
|
||||
}
|
||||
assert!(r["bytes"].as_u64().unwrap() > 500);
|
||||
assert!(s.execute("file.print", &json!({"pages": "9", "dryRun": true})).is_err());
|
||||
assert!(s.execute("file.printers", &json!({})).unwrap()["printers"].is_array());
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[test]
|
||||
fn print_uses_distinct_ephemeral_files() {
|
||||
let first = super::secure_print_file().unwrap();
|
||||
let second = super::secure_print_file().unwrap();
|
||||
let first_path = first.path().to_path_buf();
|
||||
let second_path = second.path().to_path_buf();
|
||||
assert_ne!(first_path, second_path);
|
||||
drop((first, second));
|
||||
assert!(!first_path.exists());
|
||||
assert!(!second_path.exists());
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), unix))]
|
||||
#[test]
|
||||
fn print_file_is_private_and_removed_on_drop() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let file = super::secure_print_file().unwrap();
|
||||
let path = file.path().to_path_buf();
|
||||
assert_eq!(file.as_file().metadata().unwrap().permissions().mode() & 0o777, 0o600);
|
||||
drop(file);
|
||||
assert!(!path.exists());
|
||||
fn print_submits_the_selected_export_and_reports_refusal() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"pages": 4})).unwrap();
|
||||
let params = json!({"printer": "Office (east)", "copies": 2, "pages": "2-3", "marks": true, "bleed": true});
|
||||
let expected = {
|
||||
let d = &s.doc().unwrap().doc;
|
||||
let options = super::super::export::options(¶ms, d.page_count()).unwrap();
|
||||
designcraft_pdf::export_pdf(d, &s.cache, &options).unwrap()
|
||||
};
|
||||
let r = print_with(&mut s, ¶ms, |pdf, job| {
|
||||
// The production export options and full bytes reach the shared spooler boundary.
|
||||
assert_eq!(pdf, expected);
|
||||
assert!(pdf.starts_with(b"%PDF-"));
|
||||
assert_eq!(job.printer.as_deref(), Some("Office (east)"));
|
||||
assert_eq!(job.copies, 2);
|
||||
assert!(!job.title.is_empty());
|
||||
Ok("request id is Office-1".into())
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(r["pages"], 2);
|
||||
let refusal = print_with(&mut s, ¶ms, |_, _| Err(craft_print::Error::Spool("printer offline".into()))).unwrap_err();
|
||||
assert!(refusal.to_string().contains("printer offline"));
|
||||
print_with(&mut s, &json!({"dryRun": true}), |_, _| panic!("dry runs must not spool")).unwrap();
|
||||
assert!(print_with(&mut s, &json!({"pages": "9"}), |_, _| panic!("invalid ranges must not spool")).is_err());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# File format parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (first version: every format InDesign 2026 declares, measured against the code) · **Target:** Adobe InDesign 2026 (21.6)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (shared print spooler; native Windows validation pending) · **Target:** Adobe InDesign 2026 (21.6)
|
||||
|
||||
Every format InDesign 2026 reads or writes, with DesignCraft's support, fidelity and tests. The
|
||||
InDesign list comes from its bundle's `Info.plist` (`CFBundleDocumentTypes`,
|
||||
@@ -46,7 +46,7 @@ score below).
|
||||
| EPS export | `.eps` | ✓ | ✗ | | <1% |
|
||||
| Text Only / RTF / Tagged Text | `.txt`, `.rtf` | ✓ | ✓ (`file.exportText` txt, rtf, tagged) | | 1% |
|
||||
| XML | `.xml` | ✓ | ✓ export and import, DTD validate | depth unmeasured | 1% |
|
||||
| Print / PostScript | | ✓ printers, PPDs, separations | ~ PDF to `lpr` (no Windows, no PostScript) | | 3% |
|
||||
| Print / PostScript | | ✓ printers, PPDs, separations | ~ PDF to CUPS/Windows (craft-print; no PostScript) | | 3% |
|
||||
| Package | folder | ✓ fonts, links, IDML, PDF, report | ✓ (#340: IDML inside links to absolute paths) | | 2% |
|
||||
|
||||
## Placed and imported content
|
||||
@@ -97,7 +97,7 @@ for real work** (estimated). INDD alone moves this dimension by up to 30 points.
|
||||
| INDD import via a clean-room converter (owner decision, #114) | 20–60 |
|
||||
| IDML real-file corpus, metric, fixes (#193, #338, #339, #340) | 60–100 |
|
||||
| PDF/X-1a and X-3 (transparency flattener), PDF presets and `.joboptions`, external validator | 40–70 |
|
||||
| Print path (Windows, PostScript level 3 output or system print APIs) | 25–45 |
|
||||
| Print fidelity, PostScript level 3 output and print presets | 25–45 |
|
||||
| ICML story import, IDMS snippets, INDT templates (once INDD reads) | 15–25 |
|
||||
| HTML / fixed-layout EPUB with live text | 20–35 |
|
||||
| JPEG export dialog, SVG export, EPS export | 10–20 |
|
||||
|
||||
+5
-3
@@ -1,6 +1,6 @@
|
||||
# Where DesignCraft falls short of InDesign
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** minor (stage re-normalized to pre-alpha by the core-workflow gate; earlier today: major, first version: gaps from the full re-measure and the issue tracker) · **Target:** Adobe InDesign 2026 (21.6)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (shared print spooler; native Windows validation pending) · **Target:** Adobe InDesign 2026 (21.6)
|
||||
|
||||
Every known shortfall against InDesign 2026, one entry each, ranked by how much it stops an
|
||||
InDesign professional from doing real work. This is the work list: agents pick from the top.
|
||||
@@ -117,8 +117,10 @@ Kind: **F** feature · **U** UI/UX · **T** typography · **FF** file format ·
|
||||
- **Estimate:** 40–70 h. **Doc:** [file-format-parity.md](file-format-parity.md)
|
||||
|
||||
### 10. Printing (H, F)
|
||||
- **Missing:** a real print path: `file.print` sends PDF to `lpr`, so Windows can't print; no
|
||||
PostScript, printer PPDs, separations to device, print presets, or trapping.
|
||||
- **Missing:** PostScript, a printer-driver options UI, separations to device, print presets, or trapping.
|
||||
- **Have:** `file.print` exports selected pages/spreads with marks and bleed, then submits through
|
||||
craft-print: CUPS on macOS/Linux and the Windows PDF raster spooler. Physical printer fidelity
|
||||
remains a hardware check.
|
||||
- **Estimate:** 25–45 h. **Doc:** [hardware-parity.md](hardware-parity.md)
|
||||
|
||||
### 11. Paragraph and character style editing reliability (F)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Hardware and platform parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (first version) · **Target:** Adobe InDesign 2026 (21.6)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (shared print spooler; native Windows validation pending) · **Target:** Adobe InDesign 2026 (21.6)
|
||||
|
||||
What InDesign does with hardware, per platform, against DesignCraft. Layout is not a
|
||||
GPU-hungry workload, so this dimension carries little weight, but a start that fails on a
|
||||
@@ -19,7 +19,7 @@ graphics driver loses the user entirely. Overall numbers: [target-app-parity.md]
|
||||
| HiDPI / Retina | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| UI scaling | ✓ | ✓ (menus and dialogs usable at larger scales, #281) | ✓ | ✓ | ✓ |
|
||||
| Multi-monitor, floating document windows | ✓ | ~ one window; New Window for the same document | ~ | ~ | ✗ |
|
||||
| Printing | system print with PPDs, PostScript, separations | PDF to `lpr` | ✗ (no `lpr`) | PDF to `lpr` (CUPS) | browser download |
|
||||
| Printing | system print with PPDs, PostScript, separations | PDF to CUPS (`craft-print`) | Windows PDF raster spooler (`craft-print`) | PDF to CUPS (`craft-print`) | browser download |
|
||||
| Colour-managed display (monitor profile) | ✓ | ✗ (working CMYK profile simulated; monitor profile not applied) | ✗ | ✗ | ✗ |
|
||||
| Touch / pen | Touch workspace (Windows), gestures | trackpad pinch/scroll | ~ | ~ | ~ |
|
||||
| Multi-core composition | single-threaded composer per story (documented by Adobe as mostly single-threaded) | parallel story composition (rayon), 6.7× on the sample | ✓ | ✓ | single-threaded |
|
||||
@@ -43,7 +43,7 @@ the developers don't use daily.
|
||||
| Work | Opus 5.5 h |
|
||||
|---|---|
|
||||
| Start-up robustness on every GPU: confirm #297 with reporters, Intel Metal fix, CPU-only present fallback | 15–30 |
|
||||
| Printing on Windows (system print API) and print presets | 15–30 (counted in file-format-parity.md) |
|
||||
| Printing fidelity, PostScript and print presets | 15–30 (counted in file-format-parity.md) |
|
||||
| Monitor profile in display colour management | 5–10 |
|
||||
| Windows and Linux runtime smoke tests in CI | 20–40 |
|
||||
| Multi-window document views | 15–30 |
|
||||
@@ -54,3 +54,6 @@ the developers don't use daily.
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-10 | major | Created from the code, InDesign 2026's GPU Performance features and the issue tracker |
|
||||
|
||||
Windows submission now uses shared craft-print. It preserves PDF sheet size/orientation and
|
||||
duplex binding edges; printer-driver options UI and physical output validation remain open.
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# DesignCraft roadmap (detail)
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** minor (stage re-normalized to pre-alpha by the core-workflow gate; earlier today: major, milestones re-assessed; beta plan from the full re-measure) · **Target:** Adobe InDesign 2026 (21.6)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (stage re-normalized to pre-alpha by the core-workflow gate; earlier today: major, milestones re-assessed; beta plan from the full re-measure) · **Target:** Adobe InDesign 2026 (21.6)
|
||||
|
||||
Forward-looking plan. The one-page summary is [ROADMAP.md](../ROADMAP.md); numbers are in
|
||||
[target-app-parity.md](target-app-parity.md); the work list is [gaps.md](gaps.md). Hours are
|
||||
@@ -88,7 +88,7 @@ that is ~28 points and **420–750 h** (~120–200 wall-clock hours with four to
|
||||
| Start-up robustness, fonts | 5, 6 | 25–50 |
|
||||
| CI, Windows/Linux runtime tests | 7, 14 | 35–70 |
|
||||
| IME | 8 | 10–20 |
|
||||
| PDF/X-1a/X-3, presets, print on Windows | 9, 10 | 65–110 |
|
||||
| PDF/X-1a/X-3, presets, physical print validation | 9, 10 | 65–110 |
|
||||
| Styles, tables, missing menu items (the most-used half) | 11, 12, 16 | 60–100 |
|
||||
| Long documents (Books) | 15 | 30–50 |
|
||||
| **Total** | | **420–750** |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# DesignCraft parity with Adobe InDesign
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** minor (stage re-normalized to pre-alpha by the core-workflow gate; earlier today: major, full re-measure against InDesign 2026 21.6; replaces the 2026-10-04 estimate in ROADMAP.md) · **Target:** Adobe InDesign 2026 (21.6.0.57, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (shared print spooler; native Windows validation pending) · **Target:** Adobe InDesign 2026 (21.6.0.57, macOS)
|
||||
|
||||
The authoritative assessment of how close DesignCraft is to Adobe InDesign. [ROADMAP.md](../ROADMAP.md)
|
||||
summarizes it; [gaps.md](gaps.md) lists every known shortfall one at a time; the feature-by-feature
|
||||
@@ -66,7 +66,7 @@ they receive, setting type exactly, and basic editing that never fails.
|
||||
| Performance | 5% | 65% | 40–80 | 300k characters compose in ~38 ms warm; typing repaints in < 1 ms (damage regions); no incremental composition; nothing benchmarked against InDesign; no large real documents | [gaps.md](gaps.md) |
|
||||
| Localization | 3% | 45% | 120–180 | 9 translations beyond English; Japanese 95%, most others 83–85%, pt-BR 61%; Hindi, Indonesian, Korean, Vietnamese none | [localization-parity.md](localization-parity.md) |
|
||||
| Platforms | 3% | 55% | 40–80 | macOS is where it's tested; Windows and Linux users hit basic failures; web and FreeBSD builds exist | [hardware-parity.md](hardware-parity.md) |
|
||||
| Hardware | 2% | 45% | 20–40 | Canvas rasterizes on the CPU (vello_cpu, multithreaded); GPU only presents; printing is `lpr` (no Windows printing, no PostScript) | [hardware-parity.md](hardware-parity.md) |
|
||||
| Hardware | 2% | 45% | 20–40 | Canvas rasterizes on the CPU (vello_cpu, multithreaded); GPU only presents; printing uses shared CUPS/Windows PDF spoolers (no PostScript; physical output needs validation) | [hardware-parity.md](hardware-parity.md) |
|
||||
| Ecosystem (scripting, plug-ins) | 2% | 15% | 150–250 | Own command/script/MCP API is strong; no ExtendScript/UXP DOM, so InDesign scripts don't run; no plug-ins | [gaps.md](gaps.md) |
|
||||
| AI features | 0% (not weighted) | 15% | 60–120 | Agent control through MCP goes beyond InDesign; no generative features (InDesign 2026: Generative Expand, Text to Image, Auto Style) | [gaps.md](gaps.md) |
|
||||
| **Weighted sum** | 100% | **47.2% → ~47%** (range 42–52%) | **1,150–1,950** | | |
|
||||
@@ -117,7 +117,7 @@ twice.
|
||||
| Tables | 5% | 55% | anchored objects in cells, Table Options submenus |
|
||||
| Layers | 3% | 75% | |
|
||||
| Effects and transparency | 3% | 50% | fidelity unmeasured |
|
||||
| Output: PDF export, print, package | 8% | 60% | PDF/X-4 strong; no X-1a, presets, Windows printing |
|
||||
| Output: PDF export, print, package | 8% | 60% | PDF/X-4 strong; no X-1a or presets; physical print validation pending |
|
||||
| View, navigation, undo, save | 3% | 70% | |
|
||||
| **Weighted depth** | 100% | **60.7%** | |
|
||||
|
||||
@@ -152,7 +152,7 @@ features), exchange edge cases, and everything excluded above.
|
||||
| Undo / redo | 5% | 85% | |
|
||||
| Save and reopen | 5% | 85% | `.designcraft` round trip, recovery |
|
||||
| Export PDF / PNG | 10% | 80% | |
|
||||
| Print | 5% | 40% | PDF to `lpr` only, nothing on Windows |
|
||||
| Print | 5% | 40% | PDF to CUPS/Windows via craft-print; physical output validation pending |
|
||||
| **Weighted depth** | 100% | **72.0%** | |
|
||||
|
||||
| Discount | Factor | Evidence |
|
||||
@@ -217,7 +217,7 @@ table is the one summed.
|
||||
| Tables | 100% | 55% | 25–40 | anchored objects in cells not drawn (#163); Table ▸ Table Options submenus, Diagonal Lines, Convert Rows, Edit Header/Footer |
|
||||
| Long documents | 100% | 45% | 30–50 | Books are shallow (sync, book pagination, book PDF/TOC/index); Table of Contents Styles |
|
||||
| Interactivity & digital | 100% | 40% | 30–50 | HTML export basic (#314); fixed-layout EPUB is page images; no animation, timing, media playback |
|
||||
| Output & production | 92% | 55% | 40–70 | PDF/X-4 only (no X-1a/X-3), no PDF/print presets or `.joboptions`, print via `lpr` (no Windows), no trapping; Output Preview panel |
|
||||
| Output & production | 92% | 55% | 40–70 | PDF/X-4 only (no X-1a/X-3), no PDF/print presets or `.joboptions`, print via craft-print (CUPS/Windows), no trapping; Output Preview panel |
|
||||
| XML & automation | 100% | 35% | (ecosystem dimension) | no ExtendScript/UXP; XML structure depth |
|
||||
| View & navigation | 100% | 65% | 10–20 | Navigator, display badges, Show/Hide extras (content grabber, live corners) |
|
||||
| Undo, history, saving | 100% | 70% | 5–10 | Undo/Redo name the action; File ▸ Open Recent |
|
||||
|
||||
Reference in new issue
Block a user