use crate::segments::Segment;
use super::anydoc_log::capture_anydoc;
use super::{ConvertError, markdown_to_plain, normalize_text};
pub struct PdfConversion {
pub text: String,
pub warnings: Vec<String>,
pub segments: Vec<Segment>,
}
fn error_code(error: &anydoc::ConvertError) -> &'static str {
if matches!(error, anydoc::ConvertError::Unsupported(_)) {
return "pdf_no_text_layer";
}
match error.code() {
"unsupported" => "unsupported",
"malformed" => "malformed",
"encrypted" => "encrypted",
"resourceLimit" => "resourceLimit",
"missingPart" => "missingPart",
_ => "io",
}
}
fn promote_warning(message: &str) -> String {
if message.contains("pages need OCR") {
let head = message.split(" and ").next().unwrap_or(message);
return format!("pdf_partial_text: {head}");
}
format!("anydoc_recovery: {message}")
}
pub fn convert_pdf(source: &[u8]) -> Result<PdfConversion, ConvertError> {
let (converted, captured) =
capture_anydoc(|| anydoc::to_markdown_bytes(source, anydoc::Format::Pdf));
let markdown = converted.map_err(|error| ConvertError {
code: error_code(&error),
message: error.to_string(),
})?;
let text = normalize_text(&markdown_to_plain(&markdown));
if !source.is_empty() && text.is_empty() {
return Err(ConvertError {
code: "empty_output",
message: format!(
"source is {} bytes but conversion produced no text",
source.len()
),
});
}
let warnings = captured
.iter()
.map(|message| promote_warning(message))
.collect();
let segments = vec![Segment::span(0, text.len(), "document")];
Ok(PdfConversion {
text,
warnings,
segments,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn partial_pdf_warnings_keep_their_prefix() {
assert_eq!(
promote_warning("1 of 2 pages need OCR and were not extracted"),
"pdf_partial_text: 1 of 2 pages need OCR"
);
assert_eq!(
promote_warning("recovered a broken xref"),
"anydoc_recovery: recovered a broken xref"
);
}
}