// 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_font, fz_matrix, fz_page, fz_path, fz_path_walker, fz_point, fz_rect, fz_stroke_state, fz_text, fz_text_item, fz_text_span, 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, c_int, c_void}, fmt, marker::PhantomData, mem::ManuallyDrop, ptr::{self, NonNull}, sync::{Mutex, OnceLock}, }; #[derive(Debug)] pub(crate) struct MuPdfError { type_: c_int, message: String, } impl MuPdfError { fn new_generic(message: impl ToString) -> Self { Self { type_: fz_error_type_FZ_ERROR_GENERIC as _, message: message.to_string(), } } } 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 = ptr::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.as_ptr()).type_, message: CStr::from_ptr((*err.0.as_ptr()).message) .to_string_lossy() .into_owned(), }) } } 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 { drop_device, fill_path, stroke_path, clip_path, clip_stroke_path, fill_text, stroke_text, clip_text, clip_stroke_text, ignore_text, .. } = &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.dev.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); } } } impl<'ctx> Text<'ctx> { pub(crate) fn spans<'a>(&'a self) -> TextSpanIter<'a, 'ctx> { TextSpanIter { ptr: unsafe { NonNull::new((*self.ptr).head) }, ctx: self.ctx, _phantom: PhantomData, } } } #[derive(Clone)] pub(crate) struct TextSpanIter<'a, 'ctx> { ptr: Option>, ctx: ContextRef<'ctx>, _phantom: PhantomData<&'a Text<'ctx>>, } impl<'a, 'ctx> Iterator for TextSpanIter<'a, 'ctx> { type Item = TextSpanRef<'a, 'ctx>; fn next(&mut self) -> Option { let ptr = self.ptr?; self.ptr = NonNull::new(unsafe { ptr.as_ref().next }); Some(TextSpanRef { ptr: unsafe { &*ptr.as_ptr() }, ctx: self.ctx, _phantom: PhantomData, }) } } #[derive(Copy, Clone)] pub(crate) struct TextSpanRef<'a, 'ctx> { ptr: &'a fz_text_span, ctx: ContextRef<'ctx>, _phantom: PhantomData<&'a Text<'ctx>>, } impl<'a, 'ctx> TextSpanRef<'a, 'ctx> { pub(crate) fn get(self) -> &'a fz_text_span { self.ptr } pub(crate) fn font(self) -> FontRef<'a, 'ctx> { FontRef { ptr: unsafe { &*self.ptr.font }, ctx: self.ctx, _phantom: PhantomData, } } pub(crate) fn items(self) -> &'a [fz_text_item] { let len = self.ptr.len as usize; if len == 0 { return &[]; } unsafe { std::slice::from_raw_parts(self.ptr.items, len) } } } #[derive(Clone, Copy)] pub(crate) struct FontRef<'a, 'ctx> { ptr: &'a fz_font, ctx: ContextRef<'ctx>, _phantom: PhantomData<&'a Text<'ctx>>, } impl<'a, 'ctx> FontRef<'a, 'ctx> { pub(crate) fn get(self) -> &'a fz_font { 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) } }