PDF viewer (render/search/text-select/thumbnails/outline) and annotation engine (Highlight/Underline/Strikeout/Square/FreeText/Note) over PDFium + lopdf, with Unicode font embedding for Vietnamese text and undo/redo.
112 lines
3.4 KiB
Rust
112 lines
3.4 KiB
Rust
//! Render PDF qua PDFium (tải động pdfium.dll qua pdfium-render).
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use pdfium_render::prelude::*;
|
|
|
|
use crate::EngineError;
|
|
|
|
/// Ảnh một trang đã render (RGBA8).
|
|
pub struct PageImage {
|
|
pub width: u32,
|
|
pub height: u32,
|
|
pub image: image::DynamicImage,
|
|
}
|
|
|
|
/// Khởi tạo PDFium: tìm và nạp thư viện động.
|
|
///
|
|
/// Thứ tự tìm: biến môi trường `FOFREEXIT_PDFIUM_PATH` (thư mục chứa lib) →
|
|
/// thư mục làm việc hiện tại → thư mục chứa file thực thi → thư viện hệ thống.
|
|
pub fn bind_pdfium() -> Result<Pdfium, EngineError> {
|
|
let mut tried: Vec<String> = Vec::new();
|
|
|
|
let mut candidates: Vec<PathBuf> = Vec::new();
|
|
if let Ok(dir) = std::env::var("FOFREEXIT_PDFIUM_PATH") {
|
|
candidates.push(PathBuf::from(dir));
|
|
}
|
|
if let Ok(cwd) = std::env::current_dir() {
|
|
candidates.push(cwd.clone());
|
|
candidates.push(cwd.join("pdfium"));
|
|
}
|
|
if let Ok(exe) = std::env::current_exe() {
|
|
if let Some(dir) = exe.parent() {
|
|
candidates.push(dir.to_path_buf());
|
|
}
|
|
}
|
|
|
|
for dir in candidates {
|
|
let lib = Pdfium::pdfium_platform_library_name_at_path(&dir);
|
|
match Pdfium::bind_to_library(&lib) {
|
|
Ok(bindings) => return Ok(Pdfium::new(bindings)),
|
|
Err(e) => tried.push(format!("{} ({e})", lib.display())),
|
|
}
|
|
}
|
|
|
|
// Cuối cùng thử thư viện hệ thống.
|
|
match Pdfium::bind_to_system_library() {
|
|
Ok(bindings) => Ok(Pdfium::new(bindings)),
|
|
Err(e) => {
|
|
tried.push(format!("system library ({e})"));
|
|
Err(EngineError::PdfiumNotFound(tried.join("; ")))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Đếm số trang của tài liệu.
|
|
pub fn page_count(pdfium: &Pdfium, input: &Path, password: Option<&str>) -> Result<u16, EngineError> {
|
|
let document = pdfium
|
|
.load_pdf_from_file(input, password)
|
|
.map_err(|e| EngineError::Pdfium(e.to_string()))?;
|
|
Ok(document.pages().len())
|
|
}
|
|
|
|
/// Render một trang (0-based) ra ảnh với chiều rộng mục tiêu `target_width` px.
|
|
pub fn render_page(
|
|
pdfium: &Pdfium,
|
|
input: &Path,
|
|
page_index: u16,
|
|
target_width: u32,
|
|
password: Option<&str>,
|
|
) -> Result<PageImage, EngineError> {
|
|
let document = pdfium
|
|
.load_pdf_from_file(input, password)
|
|
.map_err(|e| EngineError::Pdfium(e.to_string()))?;
|
|
|
|
let page = document
|
|
.pages()
|
|
.get(page_index)
|
|
.map_err(|e| EngineError::Pdfium(format!("không lấy được trang {page_index}: {e}")))?;
|
|
|
|
let config = PdfRenderConfig::new()
|
|
.set_target_width(target_width as i32)
|
|
.set_maximum_height((target_width as i32) * 4);
|
|
|
|
let bitmap = page
|
|
.render_with_config(&config)
|
|
.map_err(|e| EngineError::Pdfium(format!("render trang {page_index} thất bại: {e}")))?;
|
|
|
|
let image = bitmap.as_image();
|
|
Ok(PageImage {
|
|
width: image.width(),
|
|
height: image.height(),
|
|
image,
|
|
})
|
|
}
|
|
|
|
/// Render một trang và ghi ra file PNG.
|
|
pub fn render_page_png(
|
|
pdfium: &Pdfium,
|
|
input: &Path,
|
|
page_index: u16,
|
|
output: &Path,
|
|
target_width: u32,
|
|
password: Option<&str>,
|
|
) -> Result<PageImage, EngineError> {
|
|
let rendered = render_page(pdfium, input, page_index, target_width, password)?;
|
|
rendered
|
|
.image
|
|
.save(output)
|
|
.map_err(|e| EngineError::Pdfium(format!("ghi PNG thất bại: {e}")))?;
|
|
Ok(rendered)
|
|
}
|