use std::path::Path;
use crate::error::{Result, SearchError};
use crate::indexer::schema::StructuralMeta;
use crate::parser::ParsedDoc;
pub fn parse_pdf(path: &Path) -> Result<ParsedDoc> {
let text = extract_pdf_text(path).map_err(|e| SearchError::Parse {
path: path.to_path_buf(),
reason: e,
})?;
let title = extract_pdf_title(path);
let snippet = text.chars().take(256).collect();
Ok(ParsedDoc {
text,
title,
snippet,
metadata: StructuralMeta::default(),
})
}
fn extract_pdf_text(path: &Path) -> std::result::Result<String, String> {
match pdf_extract::extract_text(path) {
Ok(text) if !text.trim().is_empty() => return Ok(normalize_pdf_text(text)),
Ok(_) => {} Err(_) => {} }
extract_via_lopdf(path)
}
fn extract_via_lopdf(path: &Path) -> std::result::Result<String, String> {
let doc = lopdf::Document::load(path).map_err(|e| format!("lopdf: {}", e))?;
let mut text = String::new();
let pages = doc.get_pages();
for (page_num, _) in pages {
match doc.extract_text(&[page_num]) {
Ok(page_text) => {
text.push_str(&page_text);
text.push('\n');
}
Err(_) => continue,
}
}
if text.trim().is_empty() {
Err("PDF appears to be image-based or contains no extractable text".to_string())
} else {
Ok(normalize_pdf_text(text))
}
}
fn normalize_pdf_text(text: String) -> String {
let mut out = String::with_capacity(text.len());
let mut prev_was_space = false;
for c in text.chars() {
if c == '\r' {
continue;
}
if c == '\n' {
if !prev_was_space {
out.push(' ');
prev_was_space = true;
}
} else if c.is_whitespace() {
if !prev_was_space {
out.push(' ');
prev_was_space = true;
}
} else {
out.push(c);
prev_was_space = false;
}
}
out.trim().to_string()
}
fn extract_pdf_title(path: &Path) -> Option<String> {
if let Ok(doc) = lopdf::Document::load(path) {
if let Ok(info) = doc.get_object(doc.trailer.get(b"Info").ok()?.as_reference().ok()?) {
if let Ok(dict) = info.as_dict() {
if let Ok(title_obj) = dict.get(b"Title") {
if let Ok(title) = title_obj.as_str() {
let s = String::from_utf8_lossy(title).trim().to_string();
if !s.is_empty() {
return Some(s);
}
}
}
}
}
}
path.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_string())
}