mupdf 0.8.0

Safe Rust wrapper to MuPDF
Documentation
use std::convert::TryFrom;
use std::slice;

use mupdf_sys::*;

use crate::{context, Error, Pixmap};

/// Bitmaps have 1 bit per component.
/// Only used for creating halftoned versions of contone buffers, and saving out.
/// Samples are stored msb first, akin to pbms.
#[derive(Debug)]
pub struct Bitmap {
    pub(crate) inner: *mut fz_bitmap,
}

impl Bitmap {
    pub fn from_pixmap(pixmap: &Pixmap) -> Result<Self, Error> {
        unsafe { ffi_try!(mupdf_new_bitmap_from_pixmap(context(), pixmap.inner)) }
            .map(|inner| Self { inner })
    }

    /// Width of the region in pixels.
    pub fn width(&self) -> u32 {
        unsafe { (*self.inner).w as u32 }
    }

    /// Height of the region in pixels.
    pub fn height(&self) -> u32 {
        unsafe { (*self.inner).h as u32 }
    }

    pub fn stride(&self) -> i32 {
        unsafe { (*self.inner).stride }
    }

    pub fn n(&self) -> i32 {
        unsafe { (*self.inner).n }
    }

    /// Horizontal and vertical resolution in dpi (dots per inch).
    pub fn resolution(&self) -> (i32, i32) {
        unsafe {
            let x_res = (*self.inner).xres;
            let y_res = (*self.inner).yres;
            (x_res, y_res)
        }
    }

    pub fn samples(&self) -> &[u8] {
        let len = match crate::samples_len(self.stride(), self.height()) {
            Some(len) => len,
            None => return &[],
        };
        let ptr = unsafe { (*self.inner).samples };
        if ptr.is_null() {
            &[]
        } else {
            unsafe { slice::from_raw_parts(ptr, len) }
        }
    }

    pub fn samples_mut(&mut self) -> &mut [u8] {
        let len = match crate::samples_len(self.stride(), self.height()) {
            Some(len) => len,
            None => return &mut [],
        };
        let ptr = unsafe { (*self.inner).samples };
        if ptr.is_null() {
            &mut []
        } else {
            unsafe { slice::from_raw_parts_mut(ptr, len) }
        }
    }
}

impl Drop for Bitmap {
    fn drop(&mut self) {
        if !self.inner.is_null() {
            unsafe {
                fz_drop_bitmap(context(), self.inner);
            }
        }
    }
}

impl TryFrom<Pixmap> for Bitmap {
    type Error = Error;

    fn try_from(pixmap: Pixmap) -> Result<Self, Self::Error> {
        Self::from_pixmap(&pixmap)
    }
}

#[cfg(test)]
mod test {
    use crate::{Bitmap, Colorspace, Pixmap};

    #[test]
    fn test_new_bitmap() {
        let cs = Colorspace::device_gray();
        let mut pixmap = Pixmap::new_with_w_h(&cs, 100, 100, false).expect("Pixmap::new_with_w_h");
        pixmap.clear().unwrap();
        let bitmap = Bitmap::from_pixmap(&pixmap).unwrap();
        assert_eq!(bitmap.width(), 100);
        assert_eq!(bitmap.n(), 1);
    }

    #[test]
    fn test_new_bitmap_error() {
        let cs = Colorspace::device_rgb();
        let mut pixmap = Pixmap::new_with_w_h(&cs, 100, 100, false).expect("Pixmap::new_with_w_h");
        pixmap.clear().unwrap();
        assert!(Bitmap::from_pixmap(&pixmap).is_err());
    }

    #[test]
    fn test_bitmap_samples_len_is_stride_times_height() {
        // Bitmaps pack 1 bit per pixel, row-aligned to 32 bits, so the sample
        // buffer is `stride * height` bytes -- NOT `width * height`, which would
        // read ~8x past the allocation (see fz_new_bitmap in MuPDF's bitmap.c).
        let cs = Colorspace::device_gray();
        let mut pixmap = Pixmap::new_with_w_h(&cs, 100, 100, false).expect("Pixmap::new_with_w_h");
        pixmap.clear().unwrap();
        let mut bitmap = Bitmap::from_pixmap(&pixmap).expect("Bitmap::from_pixmap");
        assert_eq!(bitmap.n(), 1);
        let expected = (bitmap.stride() as usize) * (bitmap.height() as usize);
        assert_eq!(bitmap.samples().len(), expected);
        assert_eq!(bitmap.samples_mut().len(), expected);
    }
}