A: per-page sizes — Page tool and layout.pageSize
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ea20dc196c
commit
e79ec65323
|
|
@ -30,6 +30,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
|
|||
- Edit › Paste Into (copied objects become a frame's content, clipped by it; rendered, PDF and IDML both ways — verified in InDesign 2026); Polygon Settings (sides, star inset; double-click the Polygon tool).
|
||||
- Color panel (fill/stroke proxy, CMYK/RGB/Lab sliders with channel ramps, tint slider for swatches, spectrum ramp, Add to Swatches) and Color Picker (double-click the proxy); mixed colours are unnamed (not listed in Swatches until added); `object.color`, `swatch.addToSwatches`, `swatch.addUnnamed`.
|
||||
- Place text files: Word (.docx: styles by name with their attributes, bold/italic/underline/size/font, footnotes, tables, tabs, breaks), RTF and plain text, into the insertion point, the selected frame or a new frame; autoflow adds pages and threaded frames until the text fits; Remove Styles option (`designcraft-textimport`).
|
||||
- Page tool (⇧P) and `layout.pageSize`: pages of their own size (or a preset); the spine stays put and objects keep their place on their page.
|
||||
- File › Print (⌘P): printer, copies, page range, spreads, marks, bleed; printed as PDF through the system spooler (`file.print`, `file.printers`, `dryRun` for agents). Shortcuts now act like choosing the menu item (dialogs open), and UI commands win shortcut ties (⌘N opens New Document).
|
||||
- Nested styles (through/up to N sentences, words, characters, letters, digits, tabs, breaks, spaces or given characters) and GREP styles (regex → character style), edited in Paragraph Style Options and composed under local formatting; `ui.dialog.open` for agents. Nested line styles and their IDML mapping are still to come.
|
||||
- Table and cell styles: cell styles (fill, insets, vertical justification, strokes, paragraph style) and table styles (region cell styles, border, alternating rows, spacing); apply from the Table panel, edits re-apply to every user; `style.cell.*`, `style.table.*`. IDML mapping is still to come.
|
||||
|
|
|
|||
|
|
@ -219,6 +219,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
|||
})
|
||||
}
|
||||
),
|
||||
cmd!(
|
||||
"layout.pageSize",
|
||||
"Page Size",
|
||||
[],
|
||||
None,
|
||||
"{pages: [1-based page numbers], width?, height?, preset?: \"A4\"|\"Letter\"|…} — pages of their own size (Page tool); objects on the pages keep their place on them",
|
||||
has_doc,
|
||||
page_size
|
||||
),
|
||||
cmd!(
|
||||
"layout.section",
|
||||
"Numbering & Section Options…",
|
||||
|
|
@ -461,6 +470,63 @@ fn parent_ref(d: &designcraft_doc::Document, v: Option<&Value>) -> Option<Option
|
|||
#[allow(dead_code)]
|
||||
fn _r(_: Result<()>) {}
|
||||
|
||||
/// Layout › Page Size (the Page tool): resize pages one by one. Left pages grow to the left
|
||||
/// (the spine stays put); objects move with their page.
|
||||
fn page_size(s: &mut crate::Session, p: &Value) -> Result<Value> {
|
||||
let pages: Vec<usize> =
|
||||
p.get("pages").and_then(Value::as_array).map(|a| a.iter().filter_map(Value::as_u64).map(|n| n as usize).collect()).unwrap_or_default();
|
||||
let n = s.doc()?.doc.page_count();
|
||||
if pages.is_empty() || pages.iter().any(|x| *x == 0 || *x > n) {
|
||||
return Err(bad("layout.pageSize", "give `pages` (1-based)"));
|
||||
}
|
||||
let (mut w, mut h) = (p.get("width").and_then(Value::as_f64), p.get("height").and_then(Value::as_f64));
|
||||
if let Some(name) = str_param(p, "preset") {
|
||||
let pr = designcraft_doc::build::PRESETS
|
||||
.iter()
|
||||
.find(|x| x.name.eq_ignore_ascii_case(name))
|
||||
.ok_or_else(|| bad("layout.pageSize", format!("no preset `{name}`")))?;
|
||||
w = Some(pr.width);
|
||||
h = Some(pr.height);
|
||||
}
|
||||
s.edit(|d, _| {
|
||||
for page in &pages {
|
||||
let Some((si, pi)) = d.page_loc(page - 1) else { continue };
|
||||
let sp = std::sync::Arc::make_mut(&mut d.spreads[si]);
|
||||
let old: Vec<f64> = sp.pages.iter().map(|x| x.x).collect();
|
||||
let pg = &mut sp.pages[pi];
|
||||
let dw = w.map_or(0.0, |w| w.max(1.0) - pg.width);
|
||||
if let Some(w) = w {
|
||||
pg.width = w.max(1.0);
|
||||
}
|
||||
if let Some(h) = h {
|
||||
pg.height = h.max(1.0);
|
||||
}
|
||||
let left = pg.side == designcraft_doc::PageSide::Left;
|
||||
sp.relayout();
|
||||
// Keep the spine: shift the whole spread back when a left page grows.
|
||||
if left && dw != 0.0 {
|
||||
for q in &mut sp.pages {
|
||||
q.x -= dw;
|
||||
}
|
||||
}
|
||||
let new: Vec<f64> = sp.pages.iter().map(|x| x.x).collect();
|
||||
// Objects follow their page.
|
||||
let shifts: Vec<(f64, f64, f64)> =
|
||||
sp.pages.iter().enumerate().map(|(k, q)| (old[k], old[k] + q.width - if k == pi { dw } else { 0.0 }, new[k] - old[k])).collect();
|
||||
for it in &mut sp.items {
|
||||
let cx = it.bounds().center().x;
|
||||
if let Some((_, _, dx)) = shifts.iter().find(|(a, b, _)| cx >= *a && cx < *b)
|
||||
&& *dx != 0.0
|
||||
{
|
||||
let it = std::sync::Arc::make_mut(it);
|
||||
it.xf = designcraft_geom::Affine::translate((*dx, 0.0)) * it.xf;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(json!({"pages": pages.len()}))
|
||||
})
|
||||
}
|
||||
|
||||
/// File › Document Setup values.
|
||||
fn document_setup(d: &designcraft_doc::Document) -> Value {
|
||||
let st = &d.settings;
|
||||
|
|
@ -524,3 +590,31 @@ mod layer_tests {
|
|||
assert_eq!(d.item(designcraft_doc::ItemId(f)).unwrap().layer.0, l4);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod page_size_tests {
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn page_size_per_page_keeps_objects_on_their_page() {
|
||||
let mut s = crate::Session::new();
|
||||
s.execute("file.new", &json!({"pages": 3})).unwrap();
|
||||
// Page 3 is the right page of spread 2 (pages 2–3); an object on it.
|
||||
let d = s.doc().unwrap().doc.clone();
|
||||
let (si, pi) = d.page_loc(2).unwrap();
|
||||
let x0 = d.spreads[si].pages[pi].x;
|
||||
let id = s.execute("frame.create", &json!({"spread": si, "rect": [x0 + 100.0, 100, x0 + 200.0, 200]})).unwrap()["id"].as_u64().unwrap();
|
||||
s.execute("layout.pageSize", &json!({"pages": [2], "width": 300, "height": 500})).unwrap();
|
||||
let d = s.doc().unwrap().doc.clone();
|
||||
let left = &d.spreads[si].pages[0];
|
||||
assert_eq!((left.width, left.height), (300.0, 500.0));
|
||||
assert_eq!(d.spreads[si].pages[1].x, x0, "the spine stays");
|
||||
let it = d.item(designcraft_doc::ItemId(id)).unwrap();
|
||||
assert_eq!(it.bounds().x0, x0 + 100.0);
|
||||
assert_eq!(d.page_of_item(it.id), Some(2));
|
||||
s.execute("layout.pageSize", &json!({"pages": [3], "preset": "A4"})).unwrap();
|
||||
let d = s.doc().unwrap().doc.clone();
|
||||
assert!((d.spreads[si].pages[1].width - 595.276).abs() < 0.01);
|
||||
assert!(s.execute("layout.pageSize", &json!({"pages": [9], "width": 100})).is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -273,6 +273,7 @@ pub fn create(id: &str) -> Box<dyn Tool> {
|
|||
"hand" => Box::new(nav::HandTool::default()),
|
||||
"placeGun" => Box::new(nav::PlaceGun::default()),
|
||||
"zoom" => Box::new(nav::ZoomTool::default()),
|
||||
"page" => Box::new(nav::PageTool),
|
||||
other => Box::new(NoopTool(tool_info(other).map(|t| t.id).unwrap_or("selection"))),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,3 +148,27 @@ impl Tool for PlaceGun {
|
|||
Cursor::LoadedGraphic
|
||||
}
|
||||
}
|
||||
|
||||
/// Page tool (Shift+P): click a page to set its own size.
|
||||
#[derive(Default)]
|
||||
pub struct PageTool;
|
||||
|
||||
impl Tool for PageTool {
|
||||
fn id(&self) -> &'static str {
|
||||
"page"
|
||||
}
|
||||
fn pointer(&mut self, cx: &ToolContext, ev: &PointerEvent) -> Vec<Action> {
|
||||
if ev.kind != PointerKind::Up {
|
||||
return vec![];
|
||||
}
|
||||
let Some((designcraft_doc::SpreadRef::Doc(si), sp)) = cx.layout.spread_at(ev.pos) else { return vec![] };
|
||||
let Some(spread) = cx.doc.spreads.get(si) else { return vec![] };
|
||||
let Some(pi) = spread.pages.iter().position(|p| p.bounds().contains(sp)) else { return vec![] };
|
||||
let pg = &spread.pages[pi];
|
||||
let n = cx.doc.first_page_of_spread(si) + pi + 1;
|
||||
vec![Action::Dialog("cmd:layout.pageSize".into(), json!({"pages": [n], "width": pg.width, "height": pg.height}))]
|
||||
}
|
||||
fn cursor(&self, _cx: &ToolContext, _p: Point, _m: Mods) -> Cursor {
|
||||
Cursor::Arrow
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,3 +203,14 @@ fn zoom_tool_click_and_scrub() {
|
|||
}
|
||||
assert!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 250.0, 100.0)).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_tool_opens_page_size() {
|
||||
let d = Document::new(&NewDocument::default());
|
||||
let (s, c, l) = (Selection::default(), Cache::new(), CanvasLayout::new(&d, false));
|
||||
let cx = ctx(&d, &s, &c, &l);
|
||||
let off = l.offset(SpreadRef::Doc(0));
|
||||
let mut t = create("page");
|
||||
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 200.0 + off.x + d.spreads[0].pages[0].x, 200.0 + off.y));
|
||||
assert_eq!(a, vec![Action::Dialog("cmd:layout.pageSize".into(), serde_json::json!({"pages": [1], "width": 612.0, "height": 792.0}))]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
|||
| Priority | Features | Done | Partial | Missing | Parity |
|
||||
|---|---|---|---|---|---|
|
||||
| P0 | 102 | 99 | 3 | 0 | 99% |
|
||||
| P1 | 97 | 58 | 25 | 14 | 73% |
|
||||
| P1 | 97 | 59 | 25 | 13 | 74% |
|
||||
| P2 | 76 | 8 | 7 | 61 | 15% |
|
||||
|
||||
| Area | Parity |
|
||||
|---|---|
|
||||
| Application shell & workspace | 80% |
|
||||
| Documents, pages, spreads | 77% |
|
||||
| Documents, pages, spreads | 81% |
|
||||
| Layers | 95% |
|
||||
| Frames, shapes & paths | 87% |
|
||||
| Transform | 93% |
|
||||
|
|
@ -64,7 +64,7 @@ Open P0 items: Preferences, Vector/page formats (PDF/AI/SVG/EPS place), Export t
|
|||
| Documents, pages, spreads | Facing pages / spreads | P0 | D |
|
||||
| Documents, pages, spreads | Pages per spread up to 10 | P1 | M |
|
||||
| Documents, pages, spreads | Insert/move/duplicate/delete pages | P0 | D |
|
||||
| Documents, pages, spreads | Per-page size (Page tool) | P1 | M |
|
||||
| Documents, pages, spreads | Per-page size (Page tool) | P1 | D |
|
||||
| Documents, pages, spreads | Margins and Columns per page | P0 | D |
|
||||
| Documents, pages, spreads | Spread rotation (view) | P2 | M |
|
||||
| Documents, pages, spreads | Pasteboard | P0 | D |
|
||||
|
|
|
|||
Loading…
Reference in New Issue