mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-31 21:56:50 -06:00
Merge pull request #2057 from seanth/running-header-placeholder
Parent pages: running headers show their name on the parent itself only
This commit is contained in:
@@ -388,6 +388,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
|
||||
|
||||
Newest first.
|
||||
|
||||
- **2026-10-30** · Parent pages: a running-header variable shows its name in angle brackets on the parent itself (it was blank), as InDesign shows it; on the document pages, and in text with no page, it shows its text as before. No readiness percentage change.
|
||||
- **2026-10-30** · Without facing pages, a page of a multi-page spread shows the parent page in its place, as in InDesign: the second page of a two-page spread shows the parent's second page. Every single-sided page showed the parent's first page, so an InDesign book set up without facing pages but laid out in two-page spreads repeated the left parent page's items (a black heading bar) on its right pages and lost the right parent page's. No readiness percentage change.
|
||||
- **2026-10-30** · File > Package opens a dialog, as the reference app's: a Summary of fonts, links and images and inks, pages for each, a Printing Instructions step, and a PDF made with the options of a preset (#1514, #1515).
|
||||
- **2026-10-30** · IDML import reads TOC separators and entry styles written with the `$ID/` prefix or as `Properties` children, and the TOC separator notation (`^t`, `^y`, `^m`, `^>`) is shared by the dialog and IDML, with a special-character menu on the separator field (#1249, #1255).
|
||||
|
||||
@@ -581,8 +581,10 @@ pub struct FrameSpec {
|
||||
pub page_name: Option<String>,
|
||||
/// Marker text of the section the frame's page is in (None on parent pages).
|
||||
pub section_marker: Option<String>,
|
||||
/// Absolute document page the frame is on (None on parent pages).
|
||||
/// Absolute document page the frame is on (None on parent pages and the pasteboard).
|
||||
pub page: Option<usize>,
|
||||
/// The frame is on a parent spread.
|
||||
pub parent: bool,
|
||||
/// Baseline grid in inner space: (first grid line y, increment).
|
||||
pub grid: Option<(f64, f64)>,
|
||||
/// The frame is on a left page (for towards/away-from-spine alignment).
|
||||
@@ -817,6 +819,7 @@ pub(crate) fn frame_specs_placed(doc: &Document, sid: StoryId, placed: &[(ItemId
|
||||
page_name,
|
||||
section_marker,
|
||||
page,
|
||||
parent: matches!(spread_ref, Some(designcraft_doc::SpreadRef::Parent(_))),
|
||||
grid,
|
||||
left_page,
|
||||
right_page,
|
||||
@@ -862,7 +865,7 @@ struct SubstitutionInput<'a> {
|
||||
frame: Option<&'a FrameSpec>,
|
||||
opts: &'a ComposeOptions,
|
||||
var_story: bool,
|
||||
var_cache: &'a mut HashMap<Option<usize>, Arc<Vec<String>>>,
|
||||
var_cache: &'a mut HashMap<(Option<usize>, bool), Arc<Vec<String>>>,
|
||||
notes: &'a mut Notes,
|
||||
}
|
||||
|
||||
@@ -870,7 +873,9 @@ fn paragraph_substitution(input: SubstitutionInput<'_>) -> SubstCtx {
|
||||
let SubstitutionInput { doc, story, range: prange, props: pp, frame: cur_frame, opts, var_story, var_cache, notes } = input;
|
||||
let var_values = if var_story {
|
||||
let page = opts.page.or_else(|| cur_frame.and_then(|f| f.page));
|
||||
var_cache.entry(page).or_insert_with(|| std::sync::Arc::new(vars::values(doc, page, opts.running.as_deref()))).clone()
|
||||
// A parent page itself (not composed for one of the pages it applies to).
|
||||
let parent = page.is_none() && cur_frame.is_some_and(|f| f.parent);
|
||||
var_cache.entry((page, parent)).or_insert_with(|| std::sync::Arc::new(vars::values_shown(doc, page, parent, opts.running.as_deref()))).clone()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
@@ -2681,7 +2686,7 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C
|
||||
let var_story = vars::has_vars(story);
|
||||
let mut notes = Notes::new(doc, story);
|
||||
let mut note_snaps: Vec<(u32, Option<usize>, usize)> = Vec::with_capacity(np);
|
||||
let mut var_cache: std::collections::HashMap<Option<usize>, std::sync::Arc<Vec<String>>> = Default::default();
|
||||
let mut var_cache: std::collections::HashMap<(Option<usize>, bool), std::sync::Arc<Vec<String>>> = Default::default();
|
||||
// Column balancing above spanning paragraphs: settled and trial bottoms by band, the search
|
||||
// in progress, and a budget for the re-lays.
|
||||
let mut limits: Rc<Vec<(BandKey, f64)>> = Rc::default();
|
||||
@@ -5977,6 +5982,7 @@ mod column_allocation_safety_tests {
|
||||
page_name: None,
|
||||
section_marker: None,
|
||||
page: None,
|
||||
parent: false,
|
||||
grid: None,
|
||||
left_page: false,
|
||||
right_page: false,
|
||||
|
||||
@@ -176,6 +176,7 @@ impl Notes {
|
||||
page_name: f.page_name.clone(),
|
||||
section_marker: f.section_marker.clone(),
|
||||
page: f.page,
|
||||
parent: f.parent,
|
||||
grid: None,
|
||||
left_page: f.left_page,
|
||||
right_page: f.right_page,
|
||||
|
||||
@@ -132,6 +132,7 @@ fn compose_cell(doc: &Document, cell: &Cell, w: f64, opts: &ComposeOptions, (lef
|
||||
page_name: opts.page_name.clone(),
|
||||
section_marker: opts.section_marker.clone(),
|
||||
page: opts.page,
|
||||
parent: false,
|
||||
grid: None,
|
||||
left_page,
|
||||
right_page,
|
||||
|
||||
@@ -160,10 +160,18 @@ impl RunningIndex {
|
||||
|
||||
/// Values of every variable on `page`.
|
||||
pub fn values(doc: &Document, page: Option<usize>, running: Option<&RunningIndex>) -> Vec<String> {
|
||||
values_shown(doc, page, false, running)
|
||||
}
|
||||
|
||||
/// [`values`] as text shows them: on a parent page itself (`parent`, with no `page`), a running
|
||||
/// header has no entries of its own and shows which variable sits there, `<Name>`, as InDesign
|
||||
/// does. Elsewhere without a page (the pasteboard), it is its text before and after.
|
||||
pub fn values_shown(doc: &Document, page: Option<usize>, parent: bool, running: Option<&RunningIndex>) -> Vec<String> {
|
||||
doc.text_variables
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| match &v.kind {
|
||||
VarKind::RunningHeader { .. } if parent && page.is_none() => format!("<{}>", v.name),
|
||||
VarKind::RunningHeader { style, use_, character } => {
|
||||
let body = match (running, page) {
|
||||
(Some(r), Some(p)) => r.value(style, *character, *use_, p),
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
use super::*;
|
||||
|
||||
fn shown_on_parent(s: &Session, d: &designcraft_doc::Document, sid: designcraft_doc::StoryId) -> usize {
|
||||
let cs = s.cache.get(d, sid, None);
|
||||
cs.frames.iter().flat_map(|f| f.lines.iter()).map(|l| l.glyphs.iter().filter(|g| g.visible && g.adv > 0.0).count()).sum()
|
||||
}
|
||||
|
||||
/// A parent-page page-number marker follows Numbering & Section Options: changing a section's
|
||||
/// start number renames its pages without changing their index or the number of sections, and
|
||||
/// the marker composed for a page shows the new name.
|
||||
@@ -350,6 +355,14 @@ fn running_header_on_parent_page_follows_headings() {
|
||||
assert_eq!(idx.value("Heading", false, Default::default(), 2), "Epsilon");
|
||||
let list = s.execute("variables.list", &json!({})).unwrap();
|
||||
assert!(list.as_array().unwrap().iter().any(|v| v["name"] == "Last Page Number" && v["value"] == "3"));
|
||||
// On the parent page itself the header shows which variable it holds, not a blank. Text with
|
||||
// no page that isn't on a parent shows the variable's text before and after, as before.
|
||||
let head = d.variable("Chapter Head").unwrap();
|
||||
assert_eq!(crate::compose::vars::values_shown(&d, None, true, Some(&idx))[head], "<Chapter Head>");
|
||||
assert_eq!(crate::compose::vars::values_shown(&d, None, false, Some(&idx))[head], "");
|
||||
assert_eq!(crate::compose::vars::values(&d, None, Some(&idx))[head], "", "values() is the no-parent case");
|
||||
assert_eq!(crate::compose::vars::values_shown(&d, Some(2), true, Some(&idx))[head], "Epsilon", "composed for a page: its entry");
|
||||
assert!(shown_on_parent(&s, &d, sid) > p1, "the parent shows <Chapter Head>, longer than Alpha");
|
||||
// Deleting a variable flattens its instances to text.
|
||||
s.execute("variables.delete", &json!({"name": "Last Page Number"})).unwrap();
|
||||
assert!(s.doc().unwrap().doc.story(sid).unwrap().text.ends_with(" · 3"));
|
||||
|
||||
Reference in New Issue
Block a user