diff --git a/ROADMAP.md b/ROADMAP.md index d60a6bf49..3db564851 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -410,6 +410,9 @@ Newest first. - **2026-10-13** · Text recomposes when the document baseline grid changes, when a layer holding a text-wrap object is shown or hidden, or when a section's start number, numbering style, prefix or marker changes; the composition cache keyed only on the section count and item identities and kept the old layout. No readiness percentage change. - **2026-10-13** · Reflowable EPUB and HTML export include tables (header and footer rows, merged cells), footnotes (a linked note after the paragraph that refers to it) and endnotes (a linked section at the end of the book). No readiness percentage change. - **2026-10-13** · Word import: table cells read text by the same rules as body text, so deleted text, moved-away text and field codes stay out (#721). Word's Normal comes in as its own paragraph style "Normal" over the document defaults, so text formatted only by Normal keeps its look; it conflicts only with a document's own Normal, and the style map can send it to [Basic Paragraph] (#722). No readiness percentage change. +- **2026-10-14** · Overset text is marked on every frame that ends an overset story, selected or not and inside groups too (the red mark at the out port, with frame edges shown). InDesign shows it only on the selected frame, which hides overset labels in grouped objects until Preflight. No readiness percentage change. +- **2026-10-14** · Three fixes from a real job: objects snap to the bleed guide's four sides while moving, resizing or drawing (`Spread::bleed_rect` is shared by the canvas and `snap.rs`); a selected group shows the in/out ports of the text frames inside it, so an overset frame in a group shows its red mark; the status bar's error count is a menu whose Preflight Panel… entry opens the panel. No readiness percentage change. +- **2026-10-14** · Direct Selection (A) moves a placed graphic inside its frame: `transform.move` honours the content selection (or `content: true`) and translates the graphic in the frame's inner space, also for rotated frames; the frame stays. Text frames, copies and moves to another spread move as a whole. Before, the frame moved with the graphic. No readiness percentage change. - **2026-10-12** · macOS Dock Quit uses the same unsaved-document prompts as window and menu Quit. Repeated requests keep the current answers; Cancel retains every tab and unsaved recovery checkpoint, and completed Save/Don't Save answers exit through the ordinary event loop. No readiness percentage change. - **2026-10-12** · Text strokes can sit behind the fill: the stroke lies wholly outside the letters at its full weight on screen, in PNG and in PDF, set from the Properties panel or Character Style Options. IDML `StrokeAlignment` on text imports and exports as this setting, so imported text with outside-aligned strokes keeps its letter shapes (#619). No readiness percentage change. - **2026-10-12** · Placed Word documents keep their bullets and numbering (#626): `numbering.xml` levels become bullet or numbered paragraphs with Word's number formats, label text, start numbers, restarts and list indents, also when a paragraph style carries the list. No readiness percentage change. diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index f690a6b65..61fd2001c 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -863,6 +863,21 @@ fn draw_frames(app: &DesignApp, painter: &egui::Painter, xf: &Xf, doc: &Document for it in &sp.items { draw_item_edges(painter, xf, doc, it, a); } + // Every overset frame, selected or not and inside groups too, shows its red mark at the + // out port (InDesign shows it only on the selected frame; a report in Preflight is easy + // to miss while laying out). + let mut overset = Vec::new(); + overset_frames(app, doc, &sp.items, a, 0, &mut overset); + for (it, m) in overset { + let r = it.inner_bounds(); + let c = xf.to_screen(m * Point::new(r.x1, r.y1)) - vec2(0.0, 12.0); + let s = 8.5; + painter.rect_filled(Rect::from_center_size(c, vec2(s, s)), 0.0, Color32::WHITE); + painter.rect_stroke(Rect::from_center_size(c, vec2(s, s)), 0.0, Stroke::new(hair(painter), layer_color(doc, it)), StrokeKind::Inside); + let red = Color32::from_rgb(230, 20, 20); + painter.line_segment([c - vec2(2.5, 0.0), c + vec2(2.5, 0.0)], Stroke::new(1.5, red)); + painter.line_segment([c - vec2(0.0, 2.5), c + vec2(0.0, 2.5)], Stroke::new(1.5, red)); + } // Tagged frames in their tag's colour. if app.ui.tagged_frames { for top in &sp.items { @@ -1030,6 +1045,36 @@ fn draw_selection(app: &DesignApp, painter: &egui::Painter, xf: &Xf, doc: &Docum /// How deep a selected group is walked for the text frames whose ports are drawn. const PORT_GROUP_DEPTH: usize = 32; +/// The visible text frames among `items` (groups walked) that end an overset story, each with +/// its full transform (`a` is the transform of the items' parent). +fn overset_frames<'a>( + app: &DesignApp, + doc: &Document, + items: &'a [std::sync::Arc], + a: Affine, + depth: usize, + out: &mut Vec<(&'a Item, Affine)>, +) { + if depth > PORT_GROUP_DEPTH { + return; + } + for it in items { + if it.hidden || doc.layer(it.layer).is_some_and(|l| !l.visible) { + continue; + } + match &it.content { + Content::Text(tf) => { + let Some(story) = doc.story(tf.story) else { continue }; + if story.frames.last() == Some(&it.id) && app.session.cache.get(doc, tf.story, None).is_overset() { + out.push((it, a * it.xf)); + } + } + Content::Group { items } => overset_frames(app, doc, items, a * it.xf, depth + 1, out), + _ => {} + } + } +} + /// `it` when it is a text frame, or every text frame nested in it when it is a group, each with /// its story and the transform of its parent (`a` is the transform of `it`'s parent). fn text_frames_in<'a>(it: &'a Item, a: Affine, depth: usize, out: &mut Vec<(&'a Item, designcraft_doc::StoryId, Affine)>) { @@ -2529,6 +2574,33 @@ mod tests { doc.item(designcraft_doc::ItemId(id)).unwrap() } + /// An overset frame inside a group is listed for the red mark even when nothing is + /// selected; a frame with room, or a hidden one, is not. + #[test] + fn overset_frames_are_found_inside_groups() { + let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); + app.run("file.new", json!({})).unwrap(); + let s = &mut app.session; + let frame_with = |s: &mut designcraft_engine::Session, rect: [f64; 4], text: &str| { + s.execute("frame.create", &json!({"rect": rect, "content": "text", "text": text})).unwrap()["id"].as_u64().unwrap() + }; + let tiny = frame_with(s, [10.0, 10.0, 40.0, 16.0], "far too much text for a frame this small, over and over again"); + let roomy = frame_with(s, [10.0, 100.0, 300.0, 300.0], "fits"); + s.execute("selection.set", &json!({"ids": [tiny, roomy]})).unwrap(); + s.execute("object.group", &json!({})).unwrap(); + s.execute("selection.set", &json!({"ids": []})).unwrap(); + let doc = app.session.doc().unwrap().doc.clone(); + let mut out = Vec::new(); + overset_frames(&app, &doc, &doc.spreads[0].items, Affine::IDENTITY, 0, &mut out); + assert_eq!(out.iter().map(|(it, _)| it.id.0).collect::>(), vec![tiny]); + // Hidden: no mark. + app.session.execute("object.hide", &json!({"ids": [tiny]})).unwrap(); + let doc = app.session.doc().unwrap().doc.clone(); + out.clear(); + overset_frames(&app, &doc, &doc.spreads[0].items, Affine::IDENTITY, 0, &mut out); + assert!(out.is_empty()); + } + #[test] fn right_clicking_a_ruler_sets_that_rulers_units() { let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 30512bdf9..26f80c22c 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -32,6 +32,7 @@ basics of editing fail for too many users on Windows and Linux. | Direct Selection, Pen family, Pencil, Scissors | ✓ | ✓ | ~ | moving reference point "0" fails (#337) | | Live corners (yellow handle) | ✓ | ✓ | ~ | not working for some users (#186) | | Content grabber, frame fitting by double-click | ✓ | ✓ | ✓ | | +| Overset mark (red + at the out port) | selected frame only | every overset frame, groups included, while frame edges show | ✓ | a deliberate step past InDesign: grouped labels that overflow were invisible until Preflight | | Smart guides, snapping to guides/grid/baseline | ✓ | ✓ (2,049-line `snap.rs`) | ~ | vertical smart spacing between two rectangles (#188) | | Nudging with arrows, Shift ×10 | ✓ | ✓ | ✓ | | | Document views: scrollbars (each split pane), Hand tool by Space, middle drag, or Alt while editing text | ✓ | ✓ | ✓ | `canvas.rs` `scrollbar_tests` |