diff --git a/crates/edit/src/tests.rs b/crates/edit/src/tests.rs index 555e608df..e72af1acc 100644 --- a/crates/edit/src/tests.rs +++ b/crates/edit/src/tests.rs @@ -3713,3 +3713,32 @@ fn a_subset_program_is_not_trusted_for_new_characters_and_the_error_names_the_ch assert!(msg.contains("ő (U+0151)") && msg.contains("Load font") && !msg.contains("Japanese"), "{msg}"); assert_eq!(page_content_bytes(&doc, 0), before); } + +#[test] +fn draft_layout_matches_the_paragraph_an_applied_edit_draws() { + let content = "BT /F1 12 Tf 14 TL 72 700 Td (A first paragraph of words) Tj T* (with a second row) Tj ET \ + BT /F1 12 Tf 72 600 Td (Another paragraph below) Tj ET BT /F1 12 Tf 320 700 Td (Side note) Tj ET"; + let doc = text_page(content); + let block = text::text_blocks(&doc, 0).unwrap().into_iter().position(|b| b.text.starts_with("A first")).unwrap(); + let source = text::text_blocks(&doc, 0).unwrap()[block].clone(); + for draft in [ + source.editor_text.replacen("words", "many more words than before", 1), + source.editor_text.replacen(" of words", "", 1), + format!("{}\nand a third row", source.editor_text), + ] { + let layout = text::text_edit_layout(&doc, 0, block, &draft, &text::BlockStyle::default()).unwrap(); + // Apply the same edit and measure every run that isn't one of the page's other runs. + let mut applied = doc.clone(); + text::rewrite_block(&mut applied, 0, block, Some(&draft), &text::BlockStyle::default()).unwrap(); + let others: Vec<_> = + text::text_lines(&doc, 0).unwrap().into_iter().enumerate().filter(|(i, _)| !source.lines.contains(i)).map(|(_, l)| l.rect).collect(); + let drawn = text::text_lines(&applied, 0) + .unwrap() + .into_iter() + .map(|l| l.rect) + .filter(|r| !others.contains(r)) + .reduce(|a, b| [a[0].min(b[0]), a[1].min(b[1]), a[2].max(b[2]), a[3].max(b[3])]); + assert_eq!(layout.draft_rect, drawn, "{draft:?}"); + assert_eq!(layout.source_rect, source.rect); + } +} diff --git a/crates/edit/src/text.rs b/crates/edit/src/text.rs index d434d01a9..07ce62ade 100644 --- a/crates/edit/src/text.rs +++ b/crates/edit/src/text.rs @@ -140,7 +140,10 @@ pub struct TextEditLayout { } pub fn text_edit_layout(doc: &Document, page: usize, block: usize, text: &str, style: &BlockStyle) -> Result { - let original = text_blocks(doc, page)?; + // This runs on every keystroke: read the page once for the paragraphs, the untouched runs + // and the rewrite, which starts from the same unchanged page. + let lines = text_lines(doc, page)?; + let original = group_blocks(&lines); let source = original.get(block).ok_or(EditError::Invalid("text block does not exist".into()))?; if text == source.editor_text && *style == BlockStyle::default() { return Ok(TextEditLayout { source_rect: source.rect, draft_rect: Some(source.rect), overflow: false }); @@ -150,13 +153,13 @@ pub fn text_edit_layout(doc: &Document, page: usize, block: usize, text: &str, s let key = |line: &TextLine| (line.text.clone(), line.font.clone(), line.rect.map(f64::to_bits), line.size.to_bits(), line.color.map(f64::to_bits)); let mut untouched = std::collections::BTreeMap::new(); - for (index, line) in text_lines(doc, page)?.iter().enumerate() { + for (index, line) in lines.iter().enumerate() { if !source.lines.contains(&index) { *untouched.entry(key(line)).or_insert(0_usize) += 1; } } let mut draft = doc.clone(); - rewrite_block(&mut draft, page, block, Some(text), style)?; + rewrite_block_from(&mut draft, page, lines, block, Some(text), style, MAX_TYPE3_GLYPHS)?; let mut rect: Option<[f64; 4]> = None; for line in text_lines(&draft, page)? { if let Some(count) = untouched.get_mut(&key(&line)) @@ -1734,6 +1737,19 @@ pub(crate) fn rewrite_block_with_limit( max_glyphs: usize, ) -> Result { let lines = text_lines(doc, page)?; + rewrite_block_from(doc, page, lines, block, text, style, max_glyphs) +} + +/// [`rewrite_block_with_limit`] with the page's [`text_lines`], read from `doc` as it is now. +fn rewrite_block_from( + doc: &mut Document, + page: usize, + lines: Vec, + block: usize, + text: Option<&str>, + style: &BlockStyle, + max_glyphs: usize, +) -> Result { let blocks = group_blocks(&lines); let b = blocks.get(block).cloned().ok_or_else(|| EditError::Invalid(format!("page {} has no paragraph {}", page + 1, block + 1)))?; let members: Vec<&TextLine> = b.lines.iter().map(|i| &lines[*i]).collect();