Browse / github.com/storytold/designcraft / patch
Preferences: Units & Increments with no document open set the units of new documents, as in InDesign
Preferences: Units & Increments with no document open set the units of new documents, as in InDesign ## Problem In InDesign, preferences set with no document open become the defaults for every new document. A user who works in inches sets Units & Increments once, before opening anything. In DesignCraft, Preferences with no document open has no Units & Increments section. The units could only be set inside a document, so every new file had to be changed by hand (reported by a user who works in inches). ## Fix - **Preferences with no document open** now lists **Units & Increments**: ruler origin, horizontal and vertical ruler units, cursor-key and kerning/tracking increments. It is the same
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- moppeeer (imported from pull request #1554, open)
- Base
d4c1322d97bf0ea28cfbaf35a626f5dfc75428f0- Size
- +245 −12 in 7 files
- Review
- pending
- Shared
- 2026-10-28
- Id
f7c54388ecf0b875cf447a8a
How it went
No other operator has reported using it yet.
Diff
raw · ROADMAP.md crates/engine/src/cmd/file.rs crates/engine/src/cmd/prefs.rs crates/engine/src/lib.rs crates/ui-egui/src/dialogs.rs crates/ui-egui/src/menus.rs crates/ui-egui/src/new_document.rs
@@ -369,6 +369,7 @@ Newest first. - **2026-10-22** · Hyphenation Settings, as in InDesign: the Paragraph panel menu's Hyphenation… (`app.hyphenationDialog`) sets the selected paragraphs' Hyphenate, Words with at Least, After First, Before Last, Hyphen Limit, Hyphenation Zone, the Better Spacing ↔ Fewer Hyphens slider (InDesign's 0–10 steps), Hyphenate Capitalized Words, Last Word and Across Column, with Preview, as one undo step; Paragraph Style Options › Hyphenation has the same form. The panel menu lists its items in InDesign's order. Replicated from a hyphenation tutorial: the same text in Minion Pro breaks into the same lines as InDesign's with hyphenation on, off, and at Fewer Hyphens. No readiness percentage change. - **2026-10-22** · Hyphenate Across Column (Hyphenation Settings; IDML HyphenateAcrossColumns) is applied: with it off, a paragraph whose line ending in an automatic hyphen would end a column or frame is re-composed without that hyphenation point, so the word moves on whole, as in InDesign. Opened and imported files keep their own setting, so their layouts don't change. No readiness percentage change. - **2026-10-22** · The Paragraph panel is laid out as InDesign's: the nine alignment buttons in groups; indent, spacing and drop cap fields by their icons in two columns, with Space Between Paragraphs Using Same Style (Ignore or a space); Shading and Border with the Paragraph Borders and Shading button and colour; Hyphenate with Do Not Align / Align to Baseline Grid. Its panel menu follows InDesign's order: Paragraph and Single-line Composer, Only Align First Line to Grid, Balance Ragged Lines, Optical Margin Alignment, Justification…, Keep Options…, Span Columns…, Hyphenation…, Drop Caps and Nested Styles…, GREP Styles… (these three now open for the selected paragraphs), Paragraph Rules…, Paragraph Borders and Shading…, Bullets and Numbering…, Restart/Continue Numbering, Define Lists… and the paragraph direction. Convert Bullets and Numbering to Text is listed but not available yet. No readiness percentage change.+- **2026-10-22** · Preferences › Units & Increments with no document open, as in InDesign: the ruler units (horizontal and vertical), the ruler origin and the cursor-key and kerning increments set there are what new documents start with, and New Document opens in those units. They are kept with the application preferences (`prefs.set {defaultUnits}`); a document keeps its own once made. Reported by a reader who works in inches and had to change every file. No readiness percentage change. - **2026-10-22** · A ruler guide dragged with the Selection tools, out of a ruler, on its own or with other selected guides, snaps along its axis as objects do (#1221): to other ruler guides, margins, columns, bleed, page and object edges and centers (with the smart-guide mark) and the document grid, within the snap zone and following Snap to Guides and Smart Guides. Shift snaps to the ruler's ticks and Cmd suspends snapping. The preview line, the position readout and the drop use the same snapped position. No readiness percentage change. - **2026-10-22** · macOS: the app menu has a Language submenu after Settings… with each interface language in its own name and the current one checked, as in the suite's other apps; Preferences › General › Interface Language stays. No readiness percentage change. - **2026-10-22** · macOS menu bar: the menus keep working after the interface language or Show CJK Features changes (a rebuilt menu bar lost every click, Quit included), and its key equivalents are the shortcuts in effect, so a shortcut moved in the Keyboard Shortcuts editor leaves its old menu item. No readiness percentage change.@@ -221,10 +221,11 @@ fn find_preset(s: &Session, name: &str) -> Option<NewDocument> { } /// The settings `file.new` makes a document with for `p`: its preset, then the settings over it.+/// Without a preset or `units`, it is in the default units (Preferences with no document). fn new_document_settings(s: &Session, p: &Value) -> Result<NewDocument> { let mut nd = match str_param(p, "preset") { Some(name) => find_preset(s, name).ok_or_else(|| bad("file.new", format!("unknown preset `{name}`")))?,- None => NewDocument::default(),+ None => s.default_new_document(), }; apply_settings(&mut nd, p); check_size("file.new", &nd)?;@@ -248,12 +249,19 @@ fn file_new(s: &mut Session, p: &Value) -> Result<Value> { let mut nd = new_document_settings(s, p)?; s.untitled += 1; nd.title = str_param(p, "title").map(str::to_string).unwrap_or_else(|| format!("Untitled-{}", s.untitled));- let d = new_document(&nd);+ let mut d = new_document(&nd);+ s.prefs.default_units.apply(&mut d.settings, p.get("preset").is_some_and(|v| !v.is_null())); let i = s.add_document(DocState::new(d, None)); Ok(json!({"index": i})) } impl Session {+ /// What New Document starts with: the factory settings, in the default units (Preferences+ /// with no document open).+ pub fn default_new_document(&self) -> NewDocument {+ NewDocument { units: self.prefs.default_units.horizontal_units, ..NewDocument::default() }+ }+ /// New Document's Preview: a document made as `file.new` would make it with `p`, shown while /// the New Document window is open. It replaces the previous preview document in its tab and /// takes no Untitled number, no undo step and no recovery entry. Settings `file.new` refuses@@ -272,7 +280,9 @@ impl Session { self.untitled += 1; } nd.title = str_param(p, "title").map(str::to_string).unwrap_or_else(|| self.next_untitled_title());- let st = DocState::new(new_document(&nd), None);+ let mut d = new_document(&nd);+ self.prefs.default_units.apply(&mut d.settings, p.get("preset").is_some_and(|v| !v.is_null()));+ let st = DocState::new(d, None); let uid = st.uid; match replace.and_then(|i| self.docs.get_mut(i).map(|d| (i, d))) { Some((i, d)) => {@@ -50,12 +50,14 @@ pub fn specs() -> Vec<CommandSpec> { "Preferences", [], None,- "{showHiddenCharacters?, typographersQuotes?, polygonSides?, starInset?, scaleStrokes?, dimensionsIncludeStroke?, transformationsAreTotals?, absolutePageNumbers?, highlightHj?, highlightKeeps?, highlightCustomTracking?, highlightSubstitutedFonts?, richBlackOutput?, favoriteFonts?: [family], recentFonts?: [family], showFontNamesInEnglish?, smartTextReflow?, autocorrect?, autocorrectList?: [[word, correction]], showAddedText?, showNoteAnchors?, cjkFeatures?: bool|null (CJK-only menu items and dialogs shown; null follows the interface language), linkTextFiles? (Create Links When Placing Text Files), recoveryMinutes?, recentFilesMax?: 0–100} → all application preferences",+ "{showHiddenCharacters?, typographersQuotes?, polygonSides?, starInset?, scaleStrokes?, dimensionsIncludeStroke?, transformationsAreTotals?, absolutePageNumbers?, highlightHj?, highlightKeeps?, highlightCustomTracking?, highlightSubstitutedFonts?, richBlackOutput?, favoriteFonts?: [family], recentFonts?: [family], showFontNamesInEnglish?, smartTextReflow?, autocorrect?, autocorrectList?: [[word, correction]], showAddedText?, showNoteAnchors?, cjkFeatures?: bool|null (CJK-only menu items and dialogs shown; null follows the interface language), linkTextFiles? (Create Links When Placing Text Files), recoveryMinutes?, recentFilesMax?: 0–100, defaultUnits?: {horizontalUnits?, verticalUnits?, rulerOrigin?, keyboardIncrement? (pt), kerningIncrement? (1/1000 em)} (Units & Increments of new documents)} → all application preferences", super::always, |s, p| { let cur = serde_json::to_value(&s.prefs).map_err(|e| bad("prefs.set", e.to_string()))?; let new = merge("prefs.set", cur, p, None)?;- s.prefs = serde_json::from_value(new).map_err(|e| bad("prefs.set", e.to_string()))?;+ let prefs: crate::Prefs = serde_json::from_value(new).map_err(|e| bad("prefs.set", e.to_string()))?;+ prefs.default_units.check().map_err(|e| bad("prefs.set", e))?;+ s.prefs = prefs; s.prefs.trim_recent_files(); serde_json::to_value(&s.prefs).map_err(|e| bad("prefs.set", e.to_string())) }@@ -141,6 +143,53 @@ mod tests { s.execute("edit.undo", &json!({})).unwrap(); assert_eq!(s.doc().unwrap().doc.settings.horizontal_units, designcraft_geom::Unit::Picas); }++ /// Units & Increments set with no document open are what new documents start with, as in+ /// InDesign: a reader who works in inches sets them once, not in every file.+ #[test]+ fn default_units_are_what_new_documents_start_with() {+ use designcraft_geom::Unit;+ let mut s = Session::new();+ let r = s+ .execute(+ "prefs.set",+ &json!({"defaultUnits": {"horizontalUnits": "inches", "verticalUnits": "inches", "keyboardIncrement": 0.5, "rulerOrigin": "page"}}),+ )+ .unwrap();+ assert_eq!(r["defaultUnits"]["horizontalUnits"], "inches");+ assert_eq!(r["defaultUnits"]["kerningIncrement"], 20.0, "the rest kept");+ s.execute("file.new", &json!({})).unwrap();+ let st = s.doc().unwrap().doc.settings.clone();+ assert_eq!((st.horizontal_units, st.vertical_units), (Unit::Inches, Unit::Inches));+ assert_eq!((st.keyboard_increment, st.ruler_origin), (0.5, designcraft_doc::RulerOrigin::Page));+ assert!(!s.doc().unwrap().is_dirty(), "made so, not edited");+ // Inches across, millimetres down: New Document's units (the horizontal ones) keep the+ // vertical preference; other units picked there set both rulers.+ s.execute("prefs.set", &json!({"defaultUnits": {"verticalUnits": "millimeters"}})).unwrap();+ s.execute("file.new", &json!({"units": "inches"})).unwrap();+ let st = s.doc().unwrap().doc.settings.clone();+ assert_eq!((st.horizontal_units, st.vertical_units), (Unit::Inches, Unit::Millimeters));+ s.execute("file.new", &json!({"units": "points"})).unwrap();+ let st = s.doc().unwrap().doc.settings.clone();+ assert_eq!((st.horizontal_units, st.vertical_units), (Unit::Points, Unit::Points));+ // A preset keeps its own units, both of them, even when the default horizontal units are+ // the preset's.+ s.execute("prefs.set", &json!({"defaultUnits": {"horizontalUnits": "millimeters", "verticalUnits": "inches"}})).unwrap();+ s.execute("file.new", &json!({"preset": "A4"})).unwrap();+ let st = s.doc().unwrap().doc.settings.clone();+ assert_eq!((st.horizontal_units, st.vertical_units), (Unit::Millimeters, Unit::Millimeters));+ assert_eq!(st.keyboard_increment, 0.5, "the increments still apply");+ s.execute("prefs.set", &json!({"defaultUnits": {"horizontalUnits": "inches", "verticalUnits": "millimeters"}})).unwrap();+ // New Document opens with them; bad values are refused and change nothing.+ assert_eq!(s.default_new_document().units, Unit::Inches);+ for bad in [json!({"horizontalUnits": "furlongs"}), json!({"keyboardIncrement": 0}), json!({"kerningIncrement": -5})] {+ assert!(s.execute("prefs.set", &json!({"defaultUnits": bad})).is_err(), "{bad}");+ }+ assert_eq!(s.prefs.default_units.keyboard_increment, 0.5);+ // Kept with the application preferences.+ let saved: crate::Prefs = serde_json::from_slice(&serde_json::to_vec(&s.prefs).unwrap()).unwrap();+ assert_eq!(saved.default_units, s.prefs.default_units);+ } } #[cfg(test)]@@ -243,6 +243,61 @@ pub struct Prefs { pub recent_files: Vec<String>, /// Preferences › File Handling › Number of Recent Items to Display (0 keeps no list). pub recent_files_max: u32,+ /// Preferences › Units & Increments set with no document open: what new documents start+ /// with, as InDesign's application defaults. A document keeps its own once made.+ pub default_units: DefaultUnits,+}++/// Units & Increments for new documents (Preferences with no document open), in the keys+/// `document.preferences` uses.+#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]+#[serde(default, rename_all = "camelCase")]+pub struct DefaultUnits {+ pub horizontal_units: designcraft_geom::Unit,+ pub vertical_units: designcraft_geom::Unit,+ pub ruler_origin: designcraft_doc::RulerOrigin,+ /// Cursor Key, in points.+ pub keyboard_increment: f64,+ /// Kerning/Tracking, in 1/1000 em.+ pub kerning_increment: f64,+}++impl Default for DefaultUnits {+ /// A new document's own settings.+ fn default() -> Self {+ let d = designcraft_doc::DocSettings::default();+ DefaultUnits {+ horizontal_units: designcraft_geom::Unit::Picas,+ vertical_units: designcraft_geom::Unit::Picas,+ ruler_origin: d.ruler_origin,+ keyboard_increment: d.keyboard_increment,+ kerning_increment: d.kerning_increment,+ }+ }+}++impl DefaultUnits {+ /// Gives a new document these defaults. Unless it was made from a preset (whose units are its+ /// own), its vertical ruler follows the preference while it keeps the default horizontal+ /// units (units picked in New Document set both rulers).+ pub fn apply(&self, settings: &mut designcraft_doc::DocSettings, preset: bool) {+ if !preset && settings.horizontal_units == self.horizontal_units {+ settings.vertical_units = self.vertical_units;+ }+ settings.ruler_origin = self.ruler_origin;+ settings.keyboard_increment = self.keyboard_increment;+ settings.kerning_increment = self.kerning_increment;+ }++ /// Refuses increments that aren't finite and positive (at most 1000).+ pub fn check(&self) -> std::result::Result<(), String> {+ for (name, v) in [("keyboardIncrement", self.keyboard_increment), ("kerningIncrement", self.kerning_increment)] {+ if !(v.is_finite() && v > 0.0 && v <= 1000.0) {+ return Err(format!("`defaultUnits.{name}` must be more than 0 and at most 1000"));+ }+ }+ Ok(())+ } } /// The most recent files Preferences lets File › Open Recent keep.@@ -317,6 +372,7 @@ impl Default for Prefs { link_text_files: false, recent_files: Vec::new(), recent_files_max: 20,+ default_units: DefaultUnits::default(), } } }@@ -1569,10 +1569,11 @@ fn confirm_document_setup(app: &mut DesignApp, d: &Dialog) -> Result<Value, Stri app.run("layout.documentSetup", p) } -/// Preferences: a section list and the section's options. Application options always; units and-/// increments when a document is open (InDesign keeps those with the document).+/// Preferences: a section list and the section's options. Application options always; with a+/// document open, its own settings (InDesign keeps those with the document); with none, Units &+/// Increments for new documents. fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog, max_height: f32) {- let has_doc = d.fields.contains_key("horizontalUnits");+ let has_doc = d.fields.contains_key("horizontalUnits") && !d.b("defaults"); let sections: &[(&str, &str)] = if has_doc { &[ ("general", "General"),@@ -1599,6 +1600,7 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog, max_he ("interface", "Interface"), ("type", "Type"), ("composition", "Composition"),+ ("units", "Units & Increments"), ("spelling", "Spelling"), ("autocorrect", "Autocorrect"), ("notes", "Notes"),@@ -3417,7 +3419,23 @@ pub fn confirm(app: &mut DesignApp) -> Result<Value, String> { if d.b("cjkFeatures") != d.b("cjkFeatures.initial") { prefs["cjkFeatures"] = json!(d.b("cjkFeatures")); }- app.run("prefs.set", prefs)?;+ // No document open: the Units & Increments new documents start with, saved with the+ // rest in one `prefs.set` (which checks everything before it changes anything). A value+ // that doesn't read, or that it refuses, keeps the dialog open to be fixed, with no+ // preference changed.+ let saved = default_units_of(&d).and_then(|du| {+ if let Some(du) = du {+ prefs["defaultUnits"] = du;+ }+ app.run("prefs.set", prefs)+ });+ if let Err(e) = saved {+ let mut d = d.clone();+ d.fields.insert("status".into(), json!(e));+ app.status(e.clone());+ app.ui.dialog = Some(d);+ return Err(e);+ } app.ui.dynamic_spelling = d.b("dynamicSpelling"); if d.fields.contains_key("userWords") { let words: Vec<String> = d.s("userWords").lines().map(str::to_string).collect();@@ -3451,6 +3469,10 @@ pub fn confirm(app: &mut DesignApp) -> Result<Value, String> { } app.run("view.snapPreferences", snap)?; }+ if d.b("defaults") {+ // The default units went with the preferences above.+ return Ok(Value::Null);+ } let Some(horizontal) = d.fields.get("horizontalUnits") else { return Ok(Value::Null) }; let mut doc = json!({"horizontalUnits": horizontal}); // Fields can come from `ui.dialog.open`; rulers it doesn't name keep their units.@@ -5183,6 +5205,29 @@ fn section_list(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, cjk: bool, sectio }); } +/// Preferences with no document open: the Units & Increments new documents start with, as+/// `prefs.set`'s `defaultUnits` (None with a document open). An increment that doesn't read is an+/// error naming the field.+fn default_units_of(d: &Dialog) -> Result<Option<Value>, String> {+ if !d.b("defaults") {+ return Ok(None);+ }+ let mut du = json!({});+ for k in ["horizontalUnits", "verticalUnits", "rulerOrigin"] {+ if let Some(v) = d.fields.get(k).filter(|v| v.is_string()) {+ du[k] = v.clone();+ }+ }+ if let Some(t) = d.fields.get("keyboardIncrement").and_then(Value::as_str) {+ du["keyboardIncrement"] = json!(parse_measure(t, Unit::Points).map_err(|_| format!("Cursor Key: `{t}` is not a measurement"))?);+ }+ if let Some(v) = d.fields.get("kerningIncrement").filter(|v| !v.is_null()) {+ let n = d.n("kerningIncrement").filter(|v| v.is_finite());+ du["kerningIncrement"] = json!(n.ok_or_else(|| format!("Kerning/Tracking: `{}` is not a number", v.as_str().unwrap_or_default()))?);+ }+ Ok(Some(du))+}+ /// "Paragraph Style n" / "Character Style n": the first name no style has. fn next_style_name(styles: &designcraft_doc::Styles, para: bool) -> String { let (base, count) = if para { ("Paragraph Style", styles.paragraph.len()) } else { ("Character Style", styles.character.len()) };@@ -7433,6 +7478,60 @@ mod tests { } } + /// With no document open, Preferences has Units & Increments, as InDesign's: OK keeps them+ /// for new documents, and New Document opens in them (Lollo's inches, set once).+ #[test]+ fn preferences_with_no_document_set_the_units_of_new_documents() {+ let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());+ crate::menus::activate(&mut app, "app.preferences", &Value::Null);+ let mut d = app.ui.dialog.take().expect("Preferences");+ assert!(d.b("defaults"), "for new documents");+ let ctx = dialog_context("en");+ let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1178.0, 814.0));+ app.ui.dialog = Some(d.clone());+ let mut labels = vec![];+ for _ in 0..3 {+ labels = dialog_frame(&mut app, &ctx, screen, vec![]);+ }+ assert!(labels.iter().any(|(t, _, _)| t == "Units & Increments"), "listed with no document: {labels:?}");+ d.fields.insert("horizontalUnits".into(), json!("inches"));+ d.fields.insert("verticalUnits".into(), json!("inches"));+ d.fields.insert("keyboardIncrement".into(), json!("0.5 pt"));+ app.ui.dialog = Some(d);+ confirm(&mut app).unwrap();+ let du = app.session.prefs.default_units;+ assert_eq!((du.horizontal_units, du.vertical_units, du.keyboard_increment), (Unit::Inches, Unit::Inches, 0.5));+ assert!(app.session.active().is_none(), "no document made");+ // A Cursor Key of 0 or text that isn't a measure is refused, keeping the dialog open and+ // the saved values, the other preferences' too; a Kerning/Tracking of 0.5 is kept as it is.+ let scale = app.session.prefs.scale_strokes;+ for bad in [json!({"keyboardIncrement": "0 pt"}), json!({"keyboardIncrement": "wide"}), json!({"kerningIncrement": "lots"})] {+ let mut d = Dialog::new("preferences", json!({"defaults": true, "horizontalUnits": "inches", "scaleStrokes": !scale}));+ for (k, v) in bad.as_object().unwrap() {+ d.fields.insert(k.clone(), v.clone());+ }+ app.ui.dialog = Some(d);+ assert!(confirm(&mut app).is_err(), "{bad}");+ assert!(app.ui.dialog.as_ref().is_some_and(|d| d.id == "preferences"), "still open: {bad}");+ assert_eq!(app.session.prefs.default_units.keyboard_increment, 0.5, "{bad}");+ assert_eq!(app.session.prefs.scale_strokes, scale, "no other preference saved: {bad}");+ }+ app.ui.dialog = Some(Dialog::new("preferences", json!({"defaults": true, "horizontalUnits": "inches", "kerningIncrement": "0.5"})));+ confirm(&mut app).unwrap();+ assert_eq!(app.session.prefs.default_units.kerning_increment, 0.5, "not rounded up to 1");+ // New Document opens in inches, and the document it makes has inch rulers.+ crate::menus::activate(&mut app, "app.newDocumentDialog", &Value::Null);+ let nd = app.ui.dialog.clone().expect("New Document");+ assert_eq!(nd.fields["units"], json!("inches"));+ assert_eq!(nd.s("width"), designcraft_geom::format_measure(612.0, Unit::Inches));+ confirm(&mut app).unwrap();+ let st = &app.session.doc().unwrap().doc.settings;+ assert_eq!((st.horizontal_units, st.vertical_units, st.keyboard_increment), (Unit::Inches, Unit::Inches, 0.5));+ // With the document open, Preferences edits that document's units, not the defaults.+ crate::menus::activate(&mut app, "app.preferences", &Value::Null);+ assert!(!app.ui.dialog.as_ref().unwrap().b("defaults"));+ }+ #[test] fn preferences_ok_sets_only_the_ruler_units_it_is_given() { // `ui.dialog.open` takes any fields, so the vertical units can be missing.@@ -1047,7 +1047,9 @@ fn run_ui_inner(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value Ok(Value::Null) } "app.newDocumentDialog" => {- app.ui.dialog = Some(crate::dialogs::Dialog::new("newDocument", json!({})));+ // In the default units (Preferences with no document open).+ let fields = crate::new_document::defaults_for(&app.session.default_new_document());+ app.ui.dialog = Some(crate::dialogs::Dialog::new("newDocument", fields)); Ok(Value::Null) } "app.openDialog" => app.pick_and_open("open"),@@ -1437,6 +1439,15 @@ fn run_ui_inner(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value } f["pasteboard.h"] = m(&doc["pasteboard"][0]); f["pasteboard.v"] = m(&doc["pasteboard"][1]);+ } else {+ // No document: Units & Increments are what new documents start with, as InDesign's.+ let du = app.session.prefs.default_units;+ f["defaults"] = json!(true);+ f["horizontalUnits"] = json!(du.horizontal_units);+ f["verticalUnits"] = json!(du.vertical_units);+ f["rulerOrigin"] = json!(du.ruler_origin);+ f["keyboardIncrement"] = json!(designcraft_geom::format_measure(du.keyboard_increment, designcraft_geom::Unit::Points));+ f["kerningIncrement"] = json!(format!("{}", du.kerning_increment)); } f["snap.alignEdges"] = json!(app.ui.align_edges); f["snap.alignCenters"] = json!(app.ui.align_centers);@@ -59,7 +59,13 @@ fn fields_for(nd: &NewDocument) -> Map<String, Value> { /// The fields a new New Document dialog starts with: the factory settings on the Recent tab. pub(crate) fn defaults() -> Value {- let mut f = fields_for(&NewDocument::default());+ defaults_for(&NewDocument::default())+}++/// The fields New Document starts with for `nd` (the session's default new document, in the+/// default units), on the Recent tab.+pub(crate) fn defaults_for(nd: &NewDocument) -> Value {+ let mut f = fields_for(nd); f.insert("tab".into(), json!("recent")); f.insert("preset".into(), json!(DEFAULT_CARD)); f.insert("name".into(), json!(""));@@ -325,7 +331,8 @@ fn cards(app: &DesignApp, tab: &str) -> Vec<Card> { Card { key, label, nd, custom } }; // Recent and Saved both start with the factory settings, as in InDesign.- let default = Card { key: DEFAULT_CARD.into(), label: crate::i18n::tr(lang, DEFAULT_CARD).into(), nd: NewDocument::default(), custom: false };+ let default =+ Card { key: DEFAULT_CARD.into(), label: crate::i18n::tr(lang, DEFAULT_CARD).into(), nd: app.session.default_new_document(), custom: false }; match tab { "recent" => std::iter::once(default) .chain(app.ui.recent_new_documents.iter().enumerate().map(|(i, nd)| named(format!("recent:{i}"), nd.clone())))