Files
FoFreeXit/crates/ff-engine/src/render.rs
T
admin 274012f854 Initial commit: FoFreeXit Phase 1 (Viewer) + Phase 2 (Annotate)
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.
2026-06-17 22:34:06 +07:00

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)
}