Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ tui-textarea = "0.7"
# ansi-to-tui = "7.0.0"
vt100 = "0.15"
codepage-437 = "0.1.0"
oxidize-pdf = "1.6"
html-escape = "0.2"
# Vendored ratatui-image dependencies
icy_sixel = "0.1.1"
base64 = "0.21.2"
Expand Down
92 changes: 90 additions & 2 deletions src/book_manager.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::pdf_handler::PdfDocument;
use epub::doc::EpubDoc;
use log::{error, info};
use log::{error, info, warn};
use std::io::BufReader;
use std::path::Path;

Expand Down Expand Up @@ -45,7 +46,11 @@ impl BookManager {
let entry = entry.ok()?;
let path = entry.path();
let extension = path.extension()?.to_str()?;
if extension == "epub" || extension == "html" || extension == "htm" {
if extension == "epub"
|| extension == "html"
|| extension == "htm"
|| extension == "pdf"
{
let path_str = path.to_str()?.to_string();
let display_name = Self::extract_display_name(&path_str);
Some(BookInfo {
Expand Down Expand Up @@ -94,6 +99,8 @@ impl BookManager {
if self.is_html_file(path) {
// For HTML files, create a fake EPUB
self.create_fake_epub_from_html(path)
} else if self.is_pdf_file(path) {
self.create_fake_epub_from_pdf(path)
} else {
info!("Attempting to load EPUB file: {path}");
match EpubDoc::new(path) {
Expand Down Expand Up @@ -356,4 +363,85 @@ impl BookManager {
None => false,
}
}

pub fn is_pdf_file(&self, path: &str) -> bool {
let path = Path::new(path);
match path.extension().and_then(|ext| ext.to_str()) {
Some(ext) => ext == "pdf",
None => false,
}
}

fn create_fake_epub_from_pdf(
&self,
path: &str,
) -> Result<EpubDoc<BufReader<std::fs::File>>, String> {
info!("Creating fake EPUB from PDF: {path}");

match PdfDocument::load(path) {
Ok(pdf_doc) => {
let page_count = pdf_doc.page_count();
info!("PDF loaded with {page_count} pages");

let filename = Path::new(path)
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("PDF Document");

let title = filename.replace(".pdf", "").replace(".PDF", "");

let text_content = match pdf_doc.extract_text() {
Ok(text) => text,
Err(e) => {
warn!("Failed to extract text from PDF: {e}");
format!(
"PDF Document\n\nFile: {}\nPages: {}\n\nCould not extract text from this PDF.",
title, page_count
)
}
};

self.create_fake_epub_from_pdf_parts(path, page_count, text_content)
}
Err(e) => {
error!("Failed to load PDF: {e}");
Err(format!("Failed to load PDF: {e}"))
}
}
}

pub fn create_fake_epub_from_pdf_parts(
&self,
path: &str,
page_count: usize,
text_content: String,
) -> Result<EpubDoc<BufReader<std::fs::File>>, String> {
let filename = Path::new(path)
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("PDF Document");

let title = filename.replace(".pdf", "").replace(".PDF", "");

let html_content = if text_content.trim().is_empty() {
format!(
r#"<h1>{}</h1>
<p><em>PDF with {} pages</em></p>
<p>This PDF appears to have no extractable text content.</p>"#,
title, page_count
)
} else {
format!(
r#"<h1>{}</h1>
<p><em>PDF with {} pages</em></p>
<hr/>
<pre>{}</pre>"#,
title,
page_count,
html_escape::encode_text(&text_content)
)
};

self.create_minimal_epub_from_html(&html_content, path)
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod comments;
pub use inputs::event_source;
pub mod components;
pub mod images;
pub mod pdf_handler;
// Vendored ratatui-image
pub mod vendored;
pub use vendored::ratatui_image;
Expand All @@ -24,6 +25,7 @@ pub use widget::reading_history;
pub use widget::text_reader as markdown_text_reader;
pub mod panic_handler;
pub mod parsing;
pub mod preferences;
pub mod search;
pub mod search_engine;
pub mod system_command;
Expand Down
Loading