mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-25 10:57:14 -06:00
Merge PR #4090: Layout: a document of many pages paid a body-length scan per page
This commit is contained in:
@@ -700,6 +700,11 @@ enum Pass {
|
||||
/// Walk state while paginating.
|
||||
struct Ctx<'a> {
|
||||
doc: &'a Document,
|
||||
/// The body's sections ([`Document::sections`]) for one pass, with the block index each
|
||||
/// starts after: header and footer wrap work asks for them per page, and rebuilding the
|
||||
/// list walked the whole body every time, so a long document paid a body-length scan per
|
||||
/// page. The document does not change during a pass.
|
||||
sections: Vec<(usize, &'a SectionProps)>,
|
||||
cache: &'a mut LayoutCache,
|
||||
opts: &'a LayoutOptions,
|
||||
counters: Counters,
|
||||
@@ -2301,6 +2306,7 @@ fn layout_pass(
|
||||
});
|
||||
let mut ctx = Ctx {
|
||||
doc,
|
||||
sections: doc.sections(),
|
||||
cache,
|
||||
opts,
|
||||
hide_deleted: opts.hides_deleted(StoryRef::Body),
|
||||
@@ -3391,12 +3397,11 @@ fn footer_table_wraps(ctx: &mut Ctx, pb: &PageBuilder) -> Vec<WrapArea> {
|
||||
}
|
||||
ctx.footer_wraps.insert(key, Vec::new());
|
||||
let doc = ctx.doc;
|
||||
let sections = doc.sections();
|
||||
let Some((_, sect)) = sections.get(page.section) else { return Vec::new() };
|
||||
let Some((_, sect)) = ctx.sections.get(page.section).copied() else { return Vec::new() };
|
||||
if sect.text_direction.is_turned() {
|
||||
return Vec::new();
|
||||
}
|
||||
let set = linked(§ions, page.section, |s| s.footers);
|
||||
let set = linked(&ctx.sections, page.section, |s| s.footers);
|
||||
let id = if sect.title_page && first {
|
||||
set.first
|
||||
} else if doc.settings.even_odd_headers && page.number.is_multiple_of(2) {
|
||||
@@ -3408,7 +3413,7 @@ fn footer_table_wraps(ctx: &mut Ctx, pb: &PageBuilder) -> Vec<WrapArea> {
|
||||
let Some(part) = doc.parts.get(&id) else { return Vec::new() };
|
||||
let blocks = part.blocks.clone();
|
||||
let x = sect.margin_left + sect.gutter;
|
||||
let base = defining_width(§ions, page.section, |s| s.footers, Some(id));
|
||||
let base = defining_width(&ctx.sections, page.section, |s| s.footers, Some(id));
|
||||
let counters = std::mem::take(&mut ctx.counters);
|
||||
let boxes = std::mem::take(&mut ctx.boxes);
|
||||
let chains = std::mem::take(&mut ctx.chains);
|
||||
@@ -4526,12 +4531,11 @@ fn header_table_areas(ctx: &mut Ctx, pb: &PageBuilder) -> Vec<Rect> {
|
||||
if let Some(areas) = ctx.header_tables.get(&key) {
|
||||
return areas.clone();
|
||||
}
|
||||
let sections = ctx.doc.sections();
|
||||
let Some((_, sect)) = sections.get(page.section) else { return Vec::new() };
|
||||
let Some((_, sect)) = ctx.sections.get(page.section).copied() else { return Vec::new() };
|
||||
if sect.text_direction.is_turned() {
|
||||
return Vec::new();
|
||||
}
|
||||
let set = linked(§ions, page.section, |s| s.headers);
|
||||
let set = linked(&ctx.sections, page.section, |s| s.headers);
|
||||
let id = if sect.title_page && first {
|
||||
set.first
|
||||
} else if ctx.doc.settings.even_odd_headers && page.number.is_multiple_of(2) {
|
||||
@@ -4544,7 +4548,7 @@ fn header_table_areas(ctx: &mut Ctx, pb: &PageBuilder) -> Vec<Rect> {
|
||||
let blocks = part.blocks.clone();
|
||||
let x = sect.margin_left + sect.gutter;
|
||||
let frame = PageFrame { sect, origin: (x, sect.header), number: page.number, mirror: ctx.mirror };
|
||||
let base = defining_width(§ions, page.section, |s| s.headers, Some(id));
|
||||
let base = defining_width(&ctx.sections, page.section, |s| s.headers, Some(id));
|
||||
let counters = std::mem::take(&mut ctx.counters);
|
||||
let boxes = std::mem::take(&mut ctx.boxes);
|
||||
let chains = std::mem::take(&mut ctx.chains);
|
||||
|
||||
@@ -849,6 +849,26 @@ fn hidden_separators_with_automatic_hyphenation_lay_out_in_linear_time() {
|
||||
assert!(ratio < 24.0, "8x the text took {ratio:.1}x the time ({t1:.4} s → {tk:.4} s): not linear");
|
||||
}
|
||||
|
||||
/// n one-line paragraphs, each starting a page.
|
||||
fn page_break_paragraphs(n: usize) -> Document {
|
||||
let mut d = Document::from_text(&"hello\n".repeat(n));
|
||||
for i in 0..n {
|
||||
d.para_mut(StoryRef::Body, &Path::top(i)).unwrap().props.page_break_before = Some(true);
|
||||
}
|
||||
d
|
||||
}
|
||||
|
||||
/// A document of many paragraphs lays out in linear time. Every page asks the layout for the
|
||||
/// section properties (for the wrap areas of the headers and footers), and that used to rebuild
|
||||
/// the section list by walking the whole body, so a document of n paragraphs paid n body-length
|
||||
/// scans: a two-megabyte file took half a minute before the first page was drawn.
|
||||
#[test]
|
||||
fn a_document_of_many_paragraphs_lays_out_in_linear_time() {
|
||||
let (t1, tk, ratio) = layout_time_ratio(page_break_paragraphs, 2_500, &LayoutOptions::default());
|
||||
eprintln!("t(n) {t1:.4} s, t(8n) {tk:.4} s, ratio {ratio:.1}");
|
||||
assert!(ratio < 24.0, "8x the paragraphs took {ratio:.1}x the time ({t1:.4} s → {tk:.4} s): not linear");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_hidden_float_anchors_lay_out_in_linear_time() {
|
||||
// n square-wrapped shapes, each anchored in its own hidden run (bold alternating, so runs do not
|
||||
|
||||
Reference in New Issue
Block a user