diff --git a/crates/formats/src/model.rs b/crates/formats/src/model.rs index 46c9b88abf..78efba93f9 100644 --- a/crates/formats/src/model.rs +++ b/crates/formats/src/model.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use wordcraft_doc::numbering::{AbstractNum, Level, ListKind, Num, levels_for}; -use wordcraft_doc::para::{InlineObject, OBJ}; +use wordcraft_doc::para::{InlineObject, OBJ, PAGE_BREAK}; use wordcraft_doc::props::{ Border, BorderStyle, Borders, CellProps, CharProps, NumRef, ParaProps, RowProps, TextColor, Underline, VMerge, VertAlign, }; @@ -590,9 +590,11 @@ fn media_ext(key: &str, data: &[u8]) -> String { } } -/// A document paragraph as a flow paragraph. -pub fn flow_para(doc: &Document, p: &Paragraph) -> Para { +/// A document paragraph as flow paragraphs: a page break character inside the text ends the +/// paragraph, and the text after it starts a new one that carries `page_break`. +pub fn flow_paras(doc: &Document, p: &Paragraph) -> Vec { let (kind, list) = para_kind(doc, p); + let mut done: Vec = Vec::new(); let mut out = Para { kind, list, align: p.props.align, page_break: p.props.page_break_before.unwrap_or(false), inlines: Vec::new() }; let in_code = kind == Kind::Code; let mut k = 0usize; @@ -602,6 +604,20 @@ pub fn flow_para(doc: &Document, p: &Paragraph) -> Para { let f = fmt_of(doc, props, in_code); let mut buf = String::new(); for c in text.chars() { + if c == PAGE_BREAK { + if !hidden { + out.push_text(&std::mem::take(&mut buf), &f); + let next = Para { kind, list: None, align: p.props.align, page_break: true, inlines: Vec::new() }; + let prev = std::mem::replace(&mut out, next); + if prev.inlines.is_empty() { + out.page_break |= prev.page_break; + out.list = prev.list; + } else { + done.push(prev); + } + } + continue; + } if c != OBJ { buf.push(c); continue; @@ -638,14 +654,15 @@ pub fn flow_para(doc: &Document, p: &Paragraph) -> Para { out.push_text(&buf, &f); } } - out + done.push(out); + done } fn flow_blocks(doc: &Document, bl: &Blocks, depth: usize) -> Vec { let mut out = Vec::new(); for b in bl { match &**b { - Block::Para(p) => out.push(FBlock::Para(flow_para(doc, p))), + Block::Para(p) => out.extend(flow_paras(doc, p).into_iter().map(FBlock::Para)), Block::Table(t) if depth < MAX_DEPTH => out.push(FBlock::Table(flow_table(doc, t, depth))), Block::Table(t) => { for r in &t.rows { diff --git a/crates/formats/src/tests.rs b/crates/formats/src/tests.rs index f2d55ef3ed..3ef61c2f38 100644 --- a/crates/formats/src/tests.rs +++ b/crates/formats/src/tests.rs @@ -199,6 +199,19 @@ fn odt_round_trip() { assert_eq!(back.core.creator, "Ada Writer"); } +#[test] +fn page_break_char_splits_paragraph() { + let mut d = Document::new(); + d.body = vec![para_block(Paragraph::with_text("num two\u{c}Page two", CharProps::default()))]; + let b = flat(&d); + assert!(matches!(&b[..], [FBlock::Para(a), FBlock::Para(c)] if a.text() == "num two" && !a.page_break && c.text() == "Page two" && c.page_break)); + let txt = String::from_utf8(export("txt", &d).unwrap().unwrap()).unwrap(); + assert!(!txt.contains("twoPage"), "{txt:?}"); + let odt = import("odt", &export("odt", &d).unwrap().unwrap()).unwrap().unwrap(); + let b = flat(&odt); + assert!(b.iter().any(|x| matches!(x, FBlock::Para(p) if p.text().trim() == "Page two" && p.page_break))); +} + #[test] fn txt_round_trip() { check_round_trip("txt", false);