mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-26 02:58:32 -06:00
Merge PR #2754: Keep leading spaces and tabs when wrapping plain text into a PDF
This commit is contained in:
@@ -124,6 +124,18 @@ fn text_is_wrapped_and_paginated() {
|
||||
assert!(content(p.last().unwrap()).contains("(After a form feed) Tj"), "a form feed starts a page");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_keeps_leading_spaces_and_tabs() {
|
||||
let doc = reopen(&from_text("t", " indented\n\ttabbed\nplain", LETTER, 11.0).unwrap());
|
||||
let p = pages(&doc);
|
||||
let c = doc.resolve(p[0].get(b"Contents").unwrap());
|
||||
let Object::Stream(s) = &*c else { panic!() };
|
||||
let content = String::from_utf8_lossy(&s.decoded().unwrap()).into_owned();
|
||||
assert!(content.contains("( indented) Tj"), "{content}");
|
||||
assert!(content.contains("( tabbed) Tj"), "{content}");
|
||||
assert!(content.contains("(plain) Tj"), "{content}");
|
||||
}
|
||||
|
||||
/// Text Helvetica can't draw is refused with its line and character, never set as `?`; text
|
||||
/// PdfCraft generates writes such characters as [U+XXXX] and says so.
|
||||
#[test]
|
||||
|
||||
+24
-3
@@ -80,13 +80,19 @@ pub fn wrap_with(text: &str, size: f64, width: f64, measure: impl Fn(&str, f64)
|
||||
let mut lines = Vec::new();
|
||||
for para in text.split(['\n', '\r']) {
|
||||
let mut line = String::new();
|
||||
// No word is on the line yet; an empty word (a leading space) still starts it.
|
||||
let mut fresh = true;
|
||||
for word in para.split(' ') {
|
||||
let candidate = if line.is_empty() { word.to_string() } else { format!("{line} {word}") };
|
||||
if measure(&candidate, size) <= width || line.is_empty() && measure(word, size) <= width {
|
||||
let candidate = if fresh { word.to_string() } else { format!("{line} {word}") };
|
||||
if measure(&candidate, size) <= width || fresh && measure(word, size) <= width {
|
||||
line = candidate;
|
||||
fresh = false;
|
||||
continue;
|
||||
}
|
||||
if !line.is_empty() {
|
||||
// Indentation alone is not a line: drop it before an over-long word.
|
||||
if line.trim().is_empty() {
|
||||
line.clear();
|
||||
} else {
|
||||
lines.push(std::mem::take(&mut line));
|
||||
}
|
||||
for ch in word.chars() {
|
||||
@@ -95,6 +101,7 @@ pub fn wrap_with(text: &str, size: f64, width: f64, measure: impl Fn(&str, f64)
|
||||
}
|
||||
line.push(ch);
|
||||
}
|
||||
fresh = line.is_empty();
|
||||
}
|
||||
lines.push(line);
|
||||
}
|
||||
@@ -256,6 +263,20 @@ pub fn literal(bytes: &[u8]) -> Vec<u8> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn wrap_keeps_leading_spaces() {
|
||||
assert_eq!(wrap(" indented", 11.0, 500.0), [" indented"]);
|
||||
assert_eq!(wrap(" a b\n c", 11.0, 500.0), [" a b", " c"]);
|
||||
assert_eq!(wrap("", 11.0, 500.0), [""]);
|
||||
// A single leading space before a word that must be broken leaves no blank first line.
|
||||
let broken = wrap(" abcdefghijklmnopqrstuvwxyz", 11.0, 40.0);
|
||||
assert!(broken.len() > 1 && broken.iter().all(|l| !l.is_empty()), "{broken:?}");
|
||||
// So does indentation of two or more spaces: no whitespace-only line, same as one space.
|
||||
let broken = wrap(" abcdefghijklmnopqrstuvwxyz", 11.0, 40.0);
|
||||
assert!(broken.len() > 1 && broken.iter().all(|l| !l.trim().is_empty()), "{broken:?}");
|
||||
assert_eq!(broken, wrap(" abcdefghijklmnopqrstuvwxyz", 11.0, 40.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn win_ansi_encodability_matches_the_encoder() {
|
||||
for c in ['a', 'é', 'ÿ', '€', '—', '™', '\t', '?'] {
|
||||
|
||||
Reference in New Issue
Block a user