Compare commits

...
Author SHA1 Message Date
Brandon Thomas 5808531e53 Merge remote-tracking branch 'origin/main' into pr-5373
# Conflicts:
#	ROADMAP.md
#	docs/gaps.md
2026-11-03 09:02:40 -05:00
Dylan M. Taylor 472ee9b04a fix(docbin): attach sections to their paragraphs through an index of paragraph ends 2026-11-03 04:58:01 +00:00
4 changed files with 36 additions and 7 deletions
+2 -1
View File
File diff suppressed because one or more lines are too long
+7 -5
View File
@@ -364,6 +364,11 @@ pub fn read_with_password(bytes: &[u8], password: Option<&str>) -> Result<Docume
// Sections: each section's properties attach to the paragraph that ends it (its story of
// header/footer parts included); the section reaching the end of the text is the final one.
let mut paras = walk(&ctx, 0, fib.ccp.text, &mut pics);
// The first paragraph ending at each CP, found once rather than by a scan per section.
let mut ending: std::collections::HashMap<u32, usize> = std::collections::HashMap::new();
for (i, p) in paras.iter().enumerate() {
ending.entry(p.end_cp).or_insert(i);
}
let last = sects.last().map(|s| s.props.clone());
for (si, sec) in sects.iter().enumerate() {
let mut props = sec.props.clone();
@@ -374,11 +379,8 @@ pub fn read_with_password(bytes: &[u8], password: Option<&str>) -> Result<Docume
if is_final {
continue; // handled below through `last`
}
for p in paras.iter_mut() {
if p.end_cp == sec.end_cp {
p.para.section = Some(Box::new(props));
break;
}
if let Some(p) = ending.get(&sec.end_cp).and_then(|&i| paras.get_mut(i)) {
p.para.section = Some(Box::new(props));
}
}
if let Some(mut l) = last {
+26
View File
@@ -1018,6 +1018,32 @@ fn two_sections_box_the_first() {
assert!(!doc.last_section.title_page);
}
#[test]
fn sections_ending_off_a_paragraph_mark_attach_nowhere() {
// Hostile section ends that miss every paragraph mark are skipped; the one that ends a
// paragraph still boxes it. Paragraphs are found by their end, not scanned per section.
let mut sections: Vec<(u32, Vec<u8>)> = (1..6).map(|cp| (cp, Vec::new())).collect();
sections.push((6, grpprl(&[(0x300A, &[0x01])])));
sections.push((12, Vec::new()));
let spec = StructSpec {
text: "first\rsecond\r",
header_stories: vec![],
sections,
chpx_runs: vec![(0, Vec::new())],
papx_entries: vec![(0, 0, Vec::new()), (6, 0, Vec::new())],
plf_lst: Vec::new(),
plf_lfo: Vec::new(),
note_stories: vec![],
endnote_stories: vec![],
extra_pairs: Vec::new(),
data_stream: Vec::new(),
};
let doc = read(&struct_doc(&spec)).expect("opens");
let paras: Vec<_> = doc.body.iter().filter_map(|b| b.as_para()).collect();
assert!(paras.first().and_then(|p| p.section.as_ref()).expect("first paragraph boxed").title_page);
assert_eq!(paras.iter().filter(|p| p.section.is_some()).count(), 1, "the missing ends attach nowhere");
}
#[test]
fn header_story_becomes_part() {
let spec = StructSpec {
+1 -1
View File
File diff suppressed because one or more lines are too long