mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
Comments: line breaks saved as CR show in comment text (#1113)
Acrobat saves the line breaks in a comment's /Contents as CR, e.g.
"first line\r\rsecond paragraph". egui draws CR as nothing and only breaks
lines at LF, so the comments panel, the hover popup and the edit box ran
the paragraphs together ("first linesecond paragraph").
Both readers of comment text (pdfcraft_render::inspect on open and
pdfcraft_annot::summaries after an edit) now turn CRLF and CR into LF.
Regression tests: carriage_returns_in_comment_text_become_line_breaks in
annot and render (both fail before this change).
This commit is contained in:
1 parent
0e8c978c6a
commit
92c9de7aad
3 files changed
+48
-2
No files matched your search
@@ -1931,7 +1931,9 @@ pub fn summaries(doc: &Document) -> Vec<Summary> {
|
||||
index,
|
||||
subtype,
|
||||
author: text_value(doc, d, b"T"),
|
||||
contents: text_value(doc, d, b"Contents"),
|
||||
// Acrobat ends lines with CR, which the interface would draw as nothing (keep in
|
||||
// step with `pdfcraft_render::inspect`).
|
||||
contents: text_value(doc, d, b"Contents").map(|s| s.replace("\r\n", "\n").replace('\r', "\n")),
|
||||
modified: text_value(doc, d, b"M"),
|
||||
name: text_value(doc, d, b"NM"),
|
||||
in_reply_to,
|
||||
|
||||
@@ -432,6 +432,19 @@ fn real_annotation_flags_do_not_lock_comments() {
|
||||
assert!(set_info(&mut doc, 0, 0, Some("Ada"), None, None, &meta("")).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn carriage_returns_in_comment_text_become_line_breaks() {
|
||||
let mut doc = fixture();
|
||||
let page = page_refs(&doc).unwrap()[0];
|
||||
let mut entries = annots(&doc, page);
|
||||
let Object::Dict(annotation) = &mut entries[0] else { panic!("fixture annotation is inline") };
|
||||
// Acrobat ends lines in /Contents with CR; the interface only breaks lines at LF.
|
||||
annotation.set(b"Contents".to_vec(), PdfString::text("one\r\rtwo\r\nthree"));
|
||||
set_annots(&mut doc, page, entries).unwrap();
|
||||
|
||||
assert_eq!(summaries(&doc)[0].contents.as_deref(), Some("one\n\ntwo\nthree"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_locked_text_box_still_refits_its_text() {
|
||||
let mut doc = fixture();
|
||||
|
||||
@@ -921,7 +921,9 @@ impl<'a> Inspector<'a> {
|
||||
page,
|
||||
subtype,
|
||||
author: self.text(d, b"T"),
|
||||
contents: self.text(d, b"Contents"),
|
||||
// Acrobat ends lines with CR, which the interface would draw as nothing (keep
|
||||
// in step with `pdfcraft_annot::summaries`).
|
||||
contents: self.text(d, b"Contents").map(|s| s.replace("\r\n", "\n").replace('\r', "\n")),
|
||||
modified: self.text(d, b"M").map(|m| pretty_date(&m)),
|
||||
name: self.text(d, b"NM"),
|
||||
in_reply_to,
|
||||
@@ -2233,6 +2235,35 @@ trailer << /Root 1 0 R >>
|
||||
assert!(info.warnings.is_empty(), "{:?}", info.warnings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn carriage_returns_in_comment_text_become_line_breaks() {
|
||||
use lopdf::dictionary;
|
||||
|
||||
let mut doc = Document::with_version("1.7");
|
||||
let pages_id = doc.new_object_id();
|
||||
let annot_id = doc.add_object(dictionary! {
|
||||
"Type" => "Annot",
|
||||
"Subtype" => "Text",
|
||||
"Rect" => vec![10.into(), 10.into(), 30.into(), 30.into()],
|
||||
// Acrobat ends lines in /Contents with CR; the interface only breaks lines at LF.
|
||||
"Contents" => Object::string_literal("one\r\rtwo\r\nthree"),
|
||||
});
|
||||
let page_id = doc.add_object(dictionary! {
|
||||
"Type" => "Page",
|
||||
"Parent" => pages_id,
|
||||
"MediaBox" => vec![0.into(), 0.into(), 200.into(), 300.into()],
|
||||
"Annots" => vec![annot_id.into()],
|
||||
});
|
||||
doc.objects.insert(pages_id, dictionary! { "Type" => "Pages", "Kids" => vec![page_id.into()], "Count" => 1 }.into());
|
||||
let catalog_id = doc.add_object(dictionary! { "Type" => "Catalog", "Pages" => pages_id });
|
||||
doc.trailer.set("Root", catalog_id);
|
||||
let mut bytes = Vec::new();
|
||||
doc.save_to(&mut bytes).expect("write synthetic fixture");
|
||||
|
||||
let info = inspect(Arc::new(bytes), None).expect("opens");
|
||||
assert_eq!(info.annotations[0].contents.as_deref(), Some("one\n\ntwo\nthree"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dates_are_prettified() {
|
||||
assert_eq!(pretty_date("D:20260930104512-04'00'"), "2026-09-30 10:45");
|
||||
|
||||
Reference in new issue
Block a user