// SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use mupdf_sys::{ fz_clone_context, fz_color_params, fz_colorspace, fz_context, fz_device, fz_document, fz_drop_context, fz_drop_device, fz_drop_document, fz_drop_page, fz_drop_path, fz_drop_text, fz_error_type_FZ_ERROR_GENERIC, fz_matrix, fz_page, fz_path, fz_path_walker, fz_point, fz_rect, fz_stroke_state, fz_text, fz_transform_point, fz_transform_point_xy, fz_walk_path, mupdf_document_page_count, mupdf_drop_error, mupdf_error_t, mupdf_load_page, mupdf_new_base_context, mupdf_new_derived_device, mupdf_open_document, mupdf_run_page, }; use std::{ cell::Cell, ffi::{CStr, CString, c_int, c_void}, fmt, marker::PhantomData, mem::ManuallyDrop, ptr::{self, NonNull}, sync::{Mutex, OnceLock}, }; #[derive(Debug)] struct MuPdfError { type_: c_int, message: CString, } impl MuPdfError { fn new_generic(message: impl ToString) -> Self { Self { type_: fz_error_type_FZ_ERROR_GENERIC as _, message: message.try_into().expect("nul byte in message"), } } } impl fmt::Display for MuPdfError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "MuPDF error: type: {}, message: {}", self.type_, self.message ) } } impl std::error::Error for MuPdfError {} struct OwnedMuPdfError(NonNull); impl Drop for OwnedMuPdfError { fn drop(&mut self) { unsafe { mupdf_drop_error(self.0.as_ptr()); } } } unsafe fn mupdf_try(f: impl FnOnce(&mut *mut mupdf_error_t) -> R) -> Result { let mut err = <*mut mupdf_error_t>::null_mut(); let retval = f(&mut err); let Some(err) = NonNull::new(err).map(OwnedMuPdfError) else { return Ok(retval); }; unsafe { Err(MuPdfError { type_: (*err.0).type_, message: CString::from(CStr::from_ptr((*err.0).message)), }) } } pub(crate) struct Context(NonNull); impl Context { fn new() -> Self { struct BaseContext(NonNull); unsafe impl Send for BaseContext {} static CTX: OnceLock> = OnceLock::new(); let base = CTX .get_or_init(|| { let ctx = unsafe { mupdf_new_base_context() }; let Some(ctx) = NonNull::new(ctx).map(BaseContext) else { panic!("failed to allocate a MuPDF context"); }; Mutex::new(ctx) }) .lock() .expect("not poisoned"); let ctx = unsafe { fz_clone_context(base.0.as_ptr()) }; let Some(ctx) = NonNull::new(ctx).map(Self) else { drop(base); panic!("failed to clone a MuPDF context"); }; ctx } pub(crate) fn with(f: impl FnOnce(&Self) -> R) -> R { thread_local! { static CTX: Context = Context::new(); } CTX.with(f) } pub(crate) fn as_ref(&self) -> ContextRef<'_> { ContextRef(self.0, PhantomData) } } impl Drop for Context { fn drop(&mut self) { unsafe { fz_drop_context(self.0.as_ptr()); } } } #[derive(Clone, Copy)] pub(crate) struct ContextRef<'ctx>(NonNull, PhantomData<&'ctx Context>); impl<'ctx> From<&'ctx Context> for ContextRef<'ctx> { fn from(value: &'ctx Context) -> Self { value.as_ref() } } pub(crate) struct Document<'ctx> { ptr: *mut fz_document, ctx: ContextRef<'ctx>, } impl<'ctx> Document<'ctx> { pub(crate) fn open( ctx: impl Into>, file_name: &CStr, ) -> Result, MuPdfError> { let ctx = ctx.into(); unsafe { mupdf_try(|errptr| mupdf_open_document(ctx.0.as_ptr(), file_name.as_ptr(), errptr)) .map(|ptr| Document { ptr, ctx }) } } pub(crate) fn page_count(&self) -> Result { unsafe { mupdf_try(|errptr| mupdf_document_page_count(self.ctx.0.as_ptr(), self.ptr, errptr))? .try_into() .map_err(MuPdfError::new_generic) } } pub(crate) fn load_page(&self, page: usize) -> Result, MuPdfError> { let page = page.try_into().map_err(MuPdfError::new_generic)?; unsafe { mupdf_try(|errptr| mupdf_load_page(self.ctx.0.as_ptr(), self.ptr, page, errptr)) .map(|ptr| Page { ptr, ctx: self.ctx }) } } } impl<'ctx> Drop for Document<'ctx> { fn drop(&mut self) { unsafe { fz_drop_document(self.ctx.0.as_ptr(), self.ptr); } } } pub(crate) struct Page<'ctx> { ptr: *mut fz_page, ctx: ContextRef<'ctx>, } impl<'ctx> Page<'ctx> { pub(crate) fn ctx(&self) -> ContextRef<'ctx> { self.ctx } pub(crate) fn run( &self, device: &Device<'ctx, T>, ctm: fz_matrix, ) -> Result<(), MuPdfError> { unsafe { mupdf_try(|errptr| { mupdf_run_page( self.ctx.0.as_ptr(), self.ptr, device.dev, ctm, ptr::null_mut(), errptr, ) }) } } } impl<'ctx> Drop for Page<'ctx> { fn drop(&mut self) { unsafe { fz_drop_page(self.ctx.0.as_ptr(), self.ptr); } } } pub(crate) struct Device<'ctx, T: 'ctx> { dev: *mut fz_device, ctx: ContextRef<'ctx>, _phantom: PhantomData>>, } pub(crate) trait DeviceCallbacks<'ctx> { fn fill_path(&self, ctx: ContextRef<'ctx>, path: &Path<'ctx>, even_odd: bool, cmt: fz_matrix); fn stroke_path(&self, ctx: ContextRef<'ctx>, path: &Path<'ctx>, cmt: fz_matrix); fn clip_path( &self, ctx: ContextRef<'ctx>, path: &Path<'ctx>, even_odd: bool, cmt: fz_matrix, scissor: fz_rect, ); fn clip_stroke_path( &self, ctx: ContextRef<'ctx>, path: &Path<'ctx>, cmt: fz_matrix, scissor: fz_rect, ); fn fill_text(&self, ctx: ContextRef<'ctx>, text: &Text<'ctx>, cmt: fz_matrix); fn stroke_text(&self, ctx: ContextRef<'ctx>, text: &Text<'ctx>, cmt: fz_matrix); fn clip_text(&self, ctx: ContextRef<'ctx>, text: &Text<'ctx>, cmt: fz_matrix, scissor: fz_rect); fn clip_stroke_text( &self, ctx: ContextRef<'ctx>, text: &Text<'ctx>, cmt: fz_matrix, scissor: fz_rect, ); fn ignore_text(&self, ctx: ContextRef<'ctx>, text: &Text<'ctx>, cmt: fz_matrix); } impl<'ctx, T: DeviceCallbacks<'ctx>> Device<'ctx, T> { pub(crate) fn new(ctx: impl Into>, value: Box) -> Result { let ctx = ctx.into(); unsafe { let dev_ptr = mupdf_try(|errptr| { mupdf_new_derived_device::>( ctx.0.as_ptr(), c"parse_powerisa_pdf::mupdf_ffi::Device", errptr, ) })?; let retval = Device { dev: dev_ptr.cast(), ctx, _phantom: PhantomData, }; (&raw mut (*dev_ptr).value).write(value); let fz_device { refs, hints, flags, close_device, drop_device, fill_path, stroke_path, clip_path, clip_stroke_path, fill_text, stroke_text, clip_text, clip_stroke_text, ignore_text, fill_shade, fill_image, fill_image_mask, clip_image_mask, pop_clip, begin_mask, end_mask, begin_group, end_group, begin_tile, end_tile, render_flags, set_default_colorspaces, begin_layer, end_layer, begin_structure, end_structure, begin_metatext, end_metatext, d1_rect, container_len, container_cap, container, } = &mut (*dev_ptr).base; *drop_device = Some(Self::drop_device_fn); *fill_path = Some(Self::fill_path_fn); *stroke_path = Some(Self::stroke_path_fn); *clip_path = Some(Self::clip_path_fn); *clip_stroke_path = Some(Self::clip_stroke_path_fn); *fill_text = Some(Self::fill_text_fn); *stroke_text = Some(Self::stroke_text_fn); *clip_text = Some(Self::clip_text_fn); *clip_stroke_text = Some(Self::clip_stroke_text_fn); *ignore_text = Some(Self::ignore_text_fn); Ok(retval) } } pub(crate) fn get(&self) -> &T { unsafe { &(*self.ptr.cast::>()).value } } unsafe extern "C" fn drop_device_fn(_ctx: *mut fz_context, dev: *mut fz_device) { unsafe { (&raw mut (*dev.cast::>()).value).drop_in_place(); } } unsafe extern "C" fn fill_path_fn( ctx: *mut fz_context, dev: *mut fz_device, path: *const fz_path, even_odd: c_int, cmt: fz_matrix, color_space: *mut fz_colorspace, color: *const f32, alpha: f32, color_params: fz_color_params, ) { let Some(ctx) = NonNull::new(ctx) else { return; }; let ctx = ContextRef(ctx, PhantomData); let this = unsafe { &mut (*dev.cast::>()).value }; this.fill_path( ctx, &ManuallyDrop::new(Path { ptr: path.cast_mut(), ctx, }), even_odd != 0, cmt, ); } unsafe extern "C" fn stroke_path_fn( ctx: *mut fz_context, dev: *mut fz_device, path: *const fz_path, stroke_state: *const fz_stroke_state, cmt: fz_matrix, color_space: *mut fz_colorspace, color: *const f32, alpha: f32, color_params: fz_color_params, ) { let Some(ctx) = NonNull::new(ctx) else { return; }; let ctx = ContextRef(ctx, PhantomData); let this = unsafe { &mut (*dev.cast::>()).value }; this.stroke_path( ctx, &ManuallyDrop::new(Path { ptr: path.cast_mut(), ctx, }), cmt, ); } unsafe extern "C" fn clip_path_fn( ctx: *mut fz_context, dev: *mut fz_device, path: *const fz_path, even_odd: ::std::os::raw::c_int, cmt: fz_matrix, scissor: fz_rect, ) { let Some(ctx) = NonNull::new(ctx) else { return; }; let ctx = ContextRef(ctx, PhantomData); let this = unsafe { &mut (*dev.cast::>()).value }; this.clip_path( ctx, &ManuallyDrop::new(Path { ptr: path.cast_mut(), ctx, }), even_odd != 0, cmt, scissor, ); } unsafe extern "C" fn clip_stroke_path_fn( ctx: *mut fz_context, dev: *mut fz_device, path: *const fz_path, stroke_state: *const fz_stroke_state, cmt: fz_matrix, scissor: fz_rect, ) { let Some(ctx) = NonNull::new(ctx) else { return; }; let ctx = ContextRef(ctx, PhantomData); let this = unsafe { &mut (*dev.cast::>()).value }; this.clip_stroke_path( ctx, &ManuallyDrop::new(Path { ptr: path.cast_mut(), ctx, }), cmt, scissor, ); } unsafe extern "C" fn fill_text_fn( ctx: *mut fz_context, dev: *mut fz_device, text: *const fz_text, cmt: fz_matrix, color_space: *mut fz_colorspace, color: *const f32, alpha: f32, color_params: fz_color_params, ) { let Some(ctx) = NonNull::new(ctx) else { return; }; let ctx = ContextRef(ctx, PhantomData); let this = unsafe { &mut (*dev.cast::>()).value }; this.fill_text( ctx, &ManuallyDrop::new(Text { ptr: text.cast_mut(), ctx, }), cmt, ); } unsafe extern "C" fn stroke_text_fn( ctx: *mut fz_context, dev: *mut fz_device, text: *const fz_text, stroke_state: *const fz_stroke_state, cmt: fz_matrix, color_space: *mut fz_colorspace, color: *const f32, alpha: f32, color_params: fz_color_params, ) { let Some(ctx) = NonNull::new(ctx) else { return; }; let ctx = ContextRef(ctx, PhantomData); let this = unsafe { &mut (*dev.cast::>()).value }; this.stroke_text( ctx, &ManuallyDrop::new(Text { ptr: text.cast_mut(), ctx, }), cmt, ); } unsafe extern "C" fn clip_text_fn( ctx: *mut fz_context, dev: *mut fz_device, text: *const fz_text, cmt: fz_matrix, scissor: fz_rect, ) { let Some(ctx) = NonNull::new(ctx) else { return; }; let ctx = ContextRef(ctx, PhantomData); let this = unsafe { &mut (*dev.cast::>()).value }; this.clip_text( ctx, &ManuallyDrop::new(Text { ptr: text.cast_mut(), ctx, }), cmt, scissor, ); } unsafe extern "C" fn clip_stroke_text_fn( ctx: *mut fz_context, dev: *mut fz_device, text: *const fz_text, stroke_state: *const fz_stroke_state, cmt: fz_matrix, scissor: fz_rect, ) { let Some(ctx) = NonNull::new(ctx) else { return; }; let ctx = ContextRef(ctx, PhantomData); let this = unsafe { &mut (*dev.cast::>()).value }; this.clip_stroke_text( ctx, &ManuallyDrop::new(Text { ptr: text.cast_mut(), ctx, }), cmt, scissor, ); } unsafe extern "C" fn ignore_text_fn( ctx: *mut fz_context, dev: *mut fz_device, text: *const fz_text, cmt: fz_matrix, ) { let Some(ctx) = NonNull::new(ctx) else { return; }; let ctx = ContextRef(ctx, PhantomData); let this = unsafe { &mut (*dev.cast::>()).value }; this.ignore_text( ctx, &ManuallyDrop::new(Text { ptr: text.cast_mut(), ctx, }), cmt, ); } } impl<'ctx, T> Drop for Device<'ctx, T> { fn drop(&mut self) { unsafe { // FIXME: fz_close_device may throw exceptions // fz_close_device(self.ctx.0.as_ptr(), self.dev); fz_drop_device(self.ctx.0.as_ptr(), self.dev); } } } #[repr(C)] struct DeviceStruct { base: fz_device, value: Box, } pub(crate) trait PathWalker<'ctx> { fn move_to(&mut self, ctx: ContextRef<'ctx>, x: f32, y: f32); fn line_to(&mut self, ctx: ContextRef<'ctx>, x: f32, y: f32); fn curve_to( &mut self, ctx: ContextRef<'ctx>, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, ); fn close_path(&mut self, ctx: ContextRef<'ctx>); fn rect_to(&mut self, ctx: ContextRef<'ctx>, x1: f32, y1: f32, x2: f32, y2: f32) { self.move_to(ctx, x1, y1); self.move_to(ctx, x2, y1); self.move_to(ctx, x2, y2); self.move_to(ctx, x1, y2); self.close_path(ctx); } } impl<'ctx, T: ?Sized + PathWalker<'ctx>> PathWalker<'ctx> for &'_ mut T { fn move_to(&mut self, ctx: ContextRef<'ctx>, x: f32, y: f32) { T::move_to(self, ctx, x, y); } fn line_to(&mut self, ctx: ContextRef<'ctx>, x: f32, y: f32) { T::line_to(self, ctx, x, y); } fn curve_to( &mut self, ctx: ContextRef<'ctx>, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, ) { T::curve_to(self, ctx, x1, y1, x2, y2, x3, y3); } fn close_path(&mut self, ctx: ContextRef<'ctx>) { T::close_path(self, ctx); } fn rect_to(&mut self, ctx: ContextRef<'ctx>, x1: f32, y1: f32, x2: f32, y2: f32) { T::rect_to(self, ctx, x1, y1, x2, y2); } } pub(crate) struct Path<'ctx> { ptr: *mut fz_path, ctx: ContextRef<'ctx>, } impl<'ctx> Path<'ctx> { pub(crate) fn walk>(&self, mut walker: W) { unsafe { fz_walk_path( self.ctx.0.as_ptr(), self.ptr, const { &fz_path_walker { moveto: Some(Self::move_to_fn::), lineto: Some(Self::line_to_fn::), curveto: Some(Self::curve_to_fn::), closepath: Some(Self::close_path_fn::), quadto: None, curvetov: None, curvetoy: None, rectto: Some(Self::rect_to_fn::), } }, (&raw mut walker).cast(), ); } } unsafe extern "C" fn move_to_fn>( ctx: *mut fz_context, arg: *mut c_void, x: f32, y: f32, ) { let Some(ctx) = NonNull::new(ctx) else { return; }; let ctx = ContextRef(ctx, PhantomData); let this = unsafe { &mut *arg.cast::() }; this.move_to(ctx, x, y); } unsafe extern "C" fn line_to_fn>( ctx: *mut fz_context, arg: *mut c_void, x: f32, y: f32, ) { let Some(ctx) = NonNull::new(ctx) else { return; }; let ctx = ContextRef(ctx, PhantomData); let this = unsafe { &mut *arg.cast::() }; this.line_to(ctx, x, y); } unsafe extern "C" fn curve_to_fn>( ctx: *mut fz_context, arg: *mut c_void, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, ) { let Some(ctx) = NonNull::new(ctx) else { return; }; let ctx = ContextRef(ctx, PhantomData); let this = unsafe { &mut *arg.cast::() }; this.curve_to(ctx, x1, y1, x2, y2, x3, y3); } unsafe extern "C" fn close_path_fn>( ctx: *mut fz_context, arg: *mut c_void, ) { let Some(ctx) = NonNull::new(ctx) else { return; }; let ctx = ContextRef(ctx, PhantomData); let this = unsafe { &mut *arg.cast::() }; this.close_path(ctx); } unsafe extern "C" fn rect_to_fn>( ctx: *mut fz_context, arg: *mut c_void, x1: f32, y1: f32, x2: f32, y2: f32, ) { let Some(ctx) = NonNull::new(ctx) else { return; }; let ctx = ContextRef(ctx, PhantomData); let this = unsafe { &mut *arg.cast::() }; this.rect_to(ctx, x1, y1, x2, y2); } } impl<'ctx> Drop for Path<'ctx> { fn drop(&mut self) { unsafe { fz_drop_path(self.ctx.0.as_ptr(), self.ptr); } } } pub(crate) struct Text<'ctx> { ptr: *mut fz_text, ctx: ContextRef<'ctx>, } impl<'ctx> Drop for Text<'ctx> { fn drop(&mut self) { unsafe { fz_drop_text(self.ctx.0.as_ptr(), self.ptr); } } } pub(crate) fn transform_point(point: fz_point, m: fz_matrix) -> fz_point { unsafe { fz_transform_point(point, m) } } pub(crate) fn transform_point_xy(x: f32, y: f32, m: fz_matrix) -> fz_point { unsafe { fz_transform_point_xy(x, y, m) } }