mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-27 11:37:59 -06:00
fix(edit): wrap added text once per write, not once per line
Writing an added text item re-wrapped the whole text inside the loop over its lines to learn the line count, so multiline text cost time and temporary memory quadratic in its length. The wrapped lines are computed once.
This commit is contained in:
@@ -235,9 +235,17 @@ pub fn lines(t: &AddedText) -> Vec<String> {
|
||||
wrapped(t).into_iter().map(|(line, _)| line).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
thread_local! {
|
||||
/// How often [`wrapped`] laid a text out on this thread, for the test that keeps it out of per-line loops.
|
||||
pub(crate) static WRAP_CALLS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
|
||||
}
|
||||
|
||||
/// [`lines`], each with whether its paragraph runs right to left (its first strong character
|
||||
/// does).
|
||||
fn wrapped(t: &AddedText) -> Vec<(String, bool)> {
|
||||
#[cfg(test)]
|
||||
WRAP_CALLS.with(|c| c.set(c.get().saturating_add(1)));
|
||||
let width = (t.rect[2] - t.rect[0]).max(t.size);
|
||||
let mut out = Vec::new();
|
||||
for para in t.text.split('\n') {
|
||||
@@ -780,7 +788,8 @@ fn draw(doc: &mut Document, c: &Content, view: [f64; 6], taken: &Dict, existing:
|
||||
let r = text_rect(t);
|
||||
let [cr, cg, cb] = t.color.map(|v| v.clamp(0.0, 1.0));
|
||||
out.extend(format!("BT /{name} {} Tf {} {} {} rg\n", n(t.size), n(cr), n(cg), n(cb)).bytes());
|
||||
for (i, line) in lines(t).iter().enumerate() {
|
||||
let all = lines(t);
|
||||
for (i, line) in all.iter().enumerate() {
|
||||
let w = t.family.width(line, t.size, t.bold, t.italic);
|
||||
let x = match t.align {
|
||||
Align::Left | Align::Justify => r[0],
|
||||
@@ -790,7 +799,6 @@ fn draw(doc: &mut Document, c: &Content, view: [f64; 6], taken: &Dict, existing:
|
||||
// Baseline: 0.8 em below the line top.
|
||||
let y = r[3] - (i as f64 * 1.2 + 0.95) * t.size;
|
||||
// Justified: word spacing makes every line but the last fill the box.
|
||||
let all = lines(t);
|
||||
let spaces = line.matches(' ').count();
|
||||
let tw =
|
||||
if t.align == Align::Justify && i + 1 < all.len() && spaces > 0 { ((r[2] - r[0]) - w).max(0.0) / spaces as f64 } else { 0.0 };
|
||||
|
||||
@@ -6355,3 +6355,14 @@ fn reading_keeps_dotted_leader_entries_whole_and_in_order() {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn added_text_is_wrapped_once_per_write() {
|
||||
let mut doc = fixture();
|
||||
let lines: Vec<String> = (0..40).map(|i| format!("line {i}")).collect();
|
||||
let text = AddedText { rect: [72.0, 100.0, 300.0, 700.0], text: lines.join("\n"), size: 10.0, align: Align::Justify, ..AddedText::default() };
|
||||
added::WRAP_CALLS.with(|c| c.set(0));
|
||||
add_content(&mut doc, 0, &Content::Text(text)).unwrap();
|
||||
let calls = added::WRAP_CALLS.with(std::cell::Cell::get);
|
||||
assert!(calls <= 4, "the text was wrapped {calls} times for 40 lines");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user