diff --git a/crates/pdf/src/import.rs b/crates/pdf/src/import.rs index 471e56cfe..6e665c1a4 100644 --- a/crates/pdf/src/import.rs +++ b/crates/pdf/src/import.rs @@ -149,6 +149,10 @@ pub const PAST_PAGE_NOTE: &str = "art reaching past the page was clipped to it, /// Warning: glyphs were kept as outlines rather than read as type. pub const OUTLINED_TEXT_NOTE: &str = "text was converted to outlines"; +/// Warning: text drawn invisibly (text rendering mode 3, as a scanned page's OCR layer is) was left +/// out: VectorCraft has no type that is kept but not drawn, so a PDF saved from it loses that +/// searchable text (#2675). +pub const INVISIBLE_TEXT_NOTE: &str = "invisible text (such as a scanned page's searchable OCR layer) was left out"; /// Warning: glyphs that differ from the installed font of their name were kept as outlines. pub const UNMATCHED_GLYPHS_NOTE: &str = "text whose glyphs differ from the installed font of the same name was kept as outlines"; /// The end of the warning that glyphs whose characters the file doesn't name were kept as @@ -1963,7 +1967,10 @@ impl<'a> Device<'a> for Builder<'_> { ) { self.pending = false; let stroke = match draw_mode { - GlyphDrawMode::Invisible => return, + GlyphDrawMode::Invisible => { + self.warn(INVISIBLE_TEXT_NOTE); + return; + } GlyphDrawMode::Fill => None, GlyphDrawMode::Stroke(p) => Some(p.clone()), }; diff --git a/crates/pdf/src/lib.rs b/crates/pdf/src/lib.rs index ddec82ba4..b855fa242 100644 --- a/crates/pdf/src/lib.rs +++ b/crates/pdf/src/lib.rs @@ -74,8 +74,8 @@ pub use encrypt::Encryption; pub use export::{export, export_with_report, page_areas}; pub use file_info::{FileInfo, file_info}; pub use import::{ - OFF_ARTBOARD_NOTE, OUTLINED_TEXT_NOTE, PAST_PAGE_NOTE, UNMATCHED_GLYPHS_NOTE, UNNAMED_CHARACTERS_NOTE, holds_outlines, import, - import_with_report, is_glyph_outlines, is_outlines_note, + INVISIBLE_TEXT_NOTE, OFF_ARTBOARD_NOTE, OUTLINED_TEXT_NOTE, PAST_PAGE_NOTE, UNMATCHED_GLYPHS_NOTE, UNNAMED_CHARACTERS_NOTE, holds_outlines, + import, import_with_report, is_glyph_outlines, is_outlines_note, }; pub use pages::{PageInfo, PdfInfo, has_illustrator_data, illustrator_data, info, is_postscript}; pub use post::{THUMBNAIL_SIZE, Thumbnail}; diff --git a/crates/pdf/src/tests_reader_compat.rs b/crates/pdf/src/tests_reader_compat.rs index a98674ea3..9c93e44cf 100644 --- a/crates/pdf/src/tests_reader_compat.rs +++ b/crates/pdf/src/tests_reader_compat.rs @@ -177,3 +177,21 @@ pub(crate) fn transfer_contract() { fn soft_mask_inversion_uses_quantized_transfer_endpoints() { transfer_contract(); } + +/// #2675: text in rendering mode 3 (invisible, as a scanned page's OCR layer) isn't kept, and the +/// import now says so, for type and outlines alike. Visible text beside it still comes in, and a +/// page without invisible text gets no such warning. +#[test] +fn invisible_text_is_reported_when_left_out() { + let open = |content: &str, text_as: TextAs| import_with_report(&text_pdf(content), &ImportOptions { text_as, ..Default::default() }).unwrap(); + let invisible = "BT /F1 20 Tf 3 Tr 1 0 0 1 10 40 Tm (AB) Tj 0 Tr 1 0 0 1 10 10 Tm (CA) Tj ET"; + for text_as in [TextAs::Text, TextAs::Outlines] { + let r = open(invisible, text_as); + assert!(r.warnings.iter().any(|w| w == crate::INVISIBLE_TEXT_NOTE), "{text_as:?}: {:?}", r.warnings); + let mut kept = 0; + r.document.walk(|n| kept += usize::from(matches!(n.kind, NodeKind::Text(_) | NodeKind::Path { .. } | NodeKind::Compound { .. }))); + assert!(kept >= 1, "{text_as:?}: the visible line still comes in"); + let visible = open("BT /F1 20 Tf 1 0 0 1 10 10 Tm (CA) Tj ET", text_as); + assert!(!visible.warnings.iter().any(|w| w == crate::INVISIBLE_TEXT_NOTE), "{text_as:?}: {:?}", visible.warnings); + } +}