use crate::djvu_document::{DjVuDocument, DjVuPage, DocError};
use crate::djvu_render::{self, RenderError, RenderOptions};
use crate::pixmap::Pixmap;
#[derive(Debug)]
pub(crate) enum ForeignError {
Parse(String),
Decode(String),
OutOfRange(String),
}
impl ForeignError {
pub(crate) fn code(&self) -> i32 {
match self {
ForeignError::Parse(_) => 1,
ForeignError::Decode(_) => 2,
ForeignError::OutOfRange(_) => 3,
}
}
fn from_page_lookup(e: DocError) -> Self {
match e {
DocError::PageOutOfRange { .. } => ForeignError::OutOfRange(e.to_string()),
other => ForeignError::Decode(other.to_string()),
}
}
}
impl core::fmt::Display for ForeignError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ForeignError::Parse(m) | ForeignError::Decode(m) | ForeignError::OutOfRange(m) => {
f.write_str(m)
}
}
}
}
pub(crate) fn open(data: &[u8]) -> Result<DjVuDocument, ForeignError> {
DjVuDocument::parse(data).map_err(|e| ForeignError::Parse(e.to_string()))
}
pub(crate) fn page(doc: &DjVuDocument, index: usize) -> Result<&DjVuPage, ForeignError> {
doc.page(index).map_err(ForeignError::from_page_lookup)
}
pub(crate) fn page_width(doc: &DjVuDocument, index: usize) -> Result<u32, ForeignError> {
Ok(page(doc, index)?.width() as u32)
}
pub(crate) fn page_height(doc: &DjVuDocument, index: usize) -> Result<u32, ForeignError> {
Ok(page(doc, index)?.height() as u32)
}
pub(crate) fn page_dpi(doc: &DjVuDocument, index: usize) -> Result<u32, ForeignError> {
Ok(page(doc, index)?.dpi() as u32)
}
pub(crate) fn text(doc: &DjVuDocument, index: usize) -> Result<Option<String>, ForeignError> {
page(doc, index)?
.text()
.map_err(|e| ForeignError::Decode(e.to_string()))
}
pub(crate) fn render_opts_for_dpi(page: &DjVuPage, target_dpi: f32) -> RenderOptions {
let (width, height) = crate::export_common::size_at_dpi(page, target_dpi);
RenderOptions {
width,
height,
permissive: true,
..RenderOptions::default()
}
}
pub(crate) fn render_at_dpi(
doc: &DjVuDocument,
index: usize,
target_dpi: f32,
) -> Result<Pixmap, ForeignError> {
let page = page(doc, index)?;
let opts = render_opts_for_dpi(page, target_dpi);
djvu_render::render_pixmap(page, &opts).map_err(map_render_err)
}
pub(crate) fn map_render_err(e: RenderError) -> ForeignError {
ForeignError::Decode(e.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
fn chicken_bytes() -> Vec<u8> {
let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("references/djvujs/library/assets/chicken.djvu");
std::fs::read(&path).expect("chicken.djvu must exist")
}
#[test]
fn foreign_error_codes() {
assert_eq!(ForeignError::Parse("p".into()).code(), 1);
assert_eq!(ForeignError::Decode("d".into()).code(), 2);
assert_eq!(ForeignError::OutOfRange("o".into()).code(), 3);
}
#[test]
fn foreign_error_display() {
assert_eq!(ForeignError::Parse("msg".into()).to_string(), "msg");
assert_eq!(ForeignError::Decode("msg".into()).to_string(), "msg");
assert_eq!(ForeignError::OutOfRange("msg".into()).to_string(), "msg");
}
#[test]
fn open_parses_valid_doc() {
let data = chicken_bytes();
let doc = open(&data).expect("chicken.djvu must parse");
assert_eq!(doc.page_count(), 1);
}
#[test]
fn open_invalid_bytes_returns_parse_error() {
let err = open(b"not a djvu file").unwrap_err();
assert!(matches!(err, ForeignError::Parse(_)));
}
#[test]
fn page_width_height_dpi_are_nonzero() {
let data = chicken_bytes();
let doc = open(&data).unwrap();
assert!(page_width(&doc, 0).unwrap() > 0);
assert!(page_height(&doc, 0).unwrap() > 0);
assert!(page_dpi(&doc, 0).unwrap() > 0);
}
#[test]
fn page_out_of_range_returns_out_of_range_error() {
let data = chicken_bytes();
let doc = open(&data).unwrap();
let err = page(&doc, 999).unwrap_err();
assert!(matches!(err, ForeignError::OutOfRange(_)), "{err:?}");
}
#[test]
fn text_returns_option() {
let data = chicken_bytes();
let doc = open(&data).unwrap();
let _ = text(&doc, 0).expect("text() must not error on valid page");
}
#[test]
fn map_render_err_returns_decode_error() {
let render_err = RenderError::BufTooSmall { need: 100, got: 10 };
let err = map_render_err(render_err);
assert!(matches!(err, ForeignError::Decode(_)), "{err:?}");
}
#[test]
fn render_at_dpi_returns_pixmap() {
let data = chicken_bytes();
let doc = open(&data).unwrap();
let pm = render_at_dpi(&doc, 0, 72.0).expect("render must succeed");
assert!(pm.width > 0 && pm.height > 0);
}
}