diff --git a/build.rs b/build.rs index 06baf10..1b83305 100644 --- a/build.rs +++ b/build.rs @@ -11,6 +11,7 @@ fn main() { .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .formatter(bindgen::Formatter::Prettyplease) .allowlist_item("zathura_.*") + .allowlist_item("girara_list_.*") .blocklist_item("zathura_plugin_error_e") .blocklist_item("^cairo_.*") .generate() diff --git a/src/lib.rs b/src/lib.rs index 0f80d4e..386a787 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,20 @@ use std::{ - ffi::{CStr, OsStr, c_void}, + ffi::{CStr, CString, OsStr, c_void}, + mem::MaybeUninit, os::unix::ffi::OsStrExt, path::Path, + slice, }; -use ::typst::layout::Page; +use ::typst::layout::{Frame, FrameItem, GroupItem, Page, Point, Transform}; use cairo::{Format, ImageSurface}; use crate::zathura::{ - cairo_t, zathura_document_get_data, zathura_document_get_path, zathura_document_s, zathura_document_set_data, zathura_document_set_number_of_pages, zathura_page_get_data, zathura_page_get_document, zathura_page_get_index, zathura_page_set_data, zathura_page_set_height, zathura_page_set_width, zathura_page_t, ZathuraResult + ZathuraResult, cairo_t, girara_list_append, girara_list_new_with_free, girara_list_t, + zathura_document_get_data, zathura_document_get_path, zathura_document_s, + zathura_document_set_data, zathura_document_set_number_of_pages, zathura_page_get_document, + zathura_page_get_index, zathura_page_set_data, zathura_page_set_height, zathura_page_set_width, + zathura_page_t, zathura_rectangle_s, }; mod typst; @@ -40,8 +46,8 @@ pub static zathura_plugin_6_7: zathura::zathura_plugin_definition_s = page_form_fields_get: None, page_images_get: None, page_image_get_cairo: None, - page_get_text: None, - page_get_selection: None, + page_get_text: Some(page_get_text), + page_get_selection: Some(page_get_selection), page_render_cairo: Some(page_render_cairo), page_get_label: None, page_get_signatures: None, @@ -97,12 +103,12 @@ unsafe extern "C" fn page_clear(_: *mut zathura_page_t, _: *mut c_void) -> Zathu } unsafe extern "C" fn page_render_cairo( - page: *mut zathura_page_t, - _: *mut c_void, + _page: *mut zathura_page_t, + typst_page: *mut c_void, cairo: *mut cairo_t, _printing: bool, ) -> ZathuraResult { - let typst_page: &Page = unsafe { &*(zathura_page_get_data(page) as *mut _)}; + let typst_page: &Page = unsafe { &*(typst_page as *mut _) }; let context = unsafe { cairo::Context::from_raw_none(cairo) }; let surface = context.target(); @@ -147,8 +153,152 @@ unsafe extern "C" fn page_render_cairo( ZathuraResult::OK } + +unsafe extern "C" fn page_get_text( + _page: *mut zathura_page_t, + data: *mut c_void, + rect: zathura_rectangle_s, + res: *mut ZathuraResult, +) -> *mut i8 { + let mut res_ = ZathuraResult::Unknown; + let res = unsafe { + &mut *(if res.is_null() { &raw mut res_ } else { res } as *mut MaybeUninit) + } + .write(ZathuraResult::Unknown); + let typst_page: &Page = unsafe { &*(data as *mut _) }; + + let text = FrameItemIterator::new(&typst_page.frame) + .filter(|(Point { x, y }, _)| { + (rect.x1..=rect.x2).contains(&x.to_pt()) && (rect.y1..=rect.y2).contains(&y.to_pt()) + }) + .filter_map(|(_, item)| { + if let FrameItem::Text(item) = item { + Some(item.text.to_string()) + } else { + None + } + }) + .collect::(); + + *res = ZathuraResult::OK; + + unsafe { + let str = slice::from_raw_parts_mut( + cairo::glib::ffi::g_malloc0_n(text.len() + 1, 1) as *mut u8, + text.len() + 1, + ); + str[..text.len()].copy_from_slice(text.as_bytes()); + + str.as_mut_ptr() as _ + } +} + +unsafe extern "C" fn page_get_selection( + _page: *mut zathura_page_t, + data: *mut c_void, + rect: zathura_rectangle_s, + res: *mut ZathuraResult, +) -> *mut girara_list_t { + let mut res_ = ZathuraResult::Unknown; + let res = unsafe { + &mut *(if res.is_null() { &raw mut res_ } else { res } as *mut MaybeUninit) + } + .write(ZathuraResult::Unknown); + let typst_page: &Page = unsafe { &*(data as *mut _) }; + let min = |x, y| if x < y { x } else { y }; + let max = |x, y| if x > y { x } else { y }; + + let rect = zathura_rectangle_s { + x1: min(rect.x1, rect.x2), + y1: min(rect.y1, rect.y2), + x2: max(rect.x1, rect.x2), + y2: max(rect.y1, rect.y2), + }; + + let rects = FrameItemIterator::new(&typst_page.frame) + .filter(|(Point { x, y }, _)| { + (rect.x1..=rect.x2).contains(&x.to_pt()) && (rect.y1..=rect.y2).contains(&y.to_pt()) + }) + .filter_map(|(_, item)| { + if let FrameItem::Text(item) = item { + Some(item.bbox()) + } else { + None + } + }); + + unsafe extern "C" fn drop_rect(data: *mut c_void) { + drop(unsafe { Box::from_raw(data as *mut zathura_rectangle_s) }); + } + + unsafe { + let list = girara_list_new_with_free(Some(drop_rect)); + + for rect in rects { + girara_list_append( + list, + Box::into_raw(Box::new(zathura_rectangle_s { + x1: rect.min.x.to_pt(), + y1: rect.min.y.to_pt(), + x2: rect.max.x.to_pt(), + y2: rect.max.y.to_pt(), + })) as *mut _, + ); + } + + *res = ZathuraResult::OK; + list + } +} + +struct FrameItemIterator<'a> { + transform: Transform, + recur: Option>>, + current: slice::Iter<'a, (Point, FrameItem)>, +} + +impl<'a> FrameItemIterator<'a> { + fn new(root: &'a Frame) -> Self { + Self::new_at(Default::default(), root) + } + + fn new_at(transform: Transform, root: &'a Frame) -> Self { + Self { + transform, + recur: None, + current: root.items(), + } + } +} + +impl<'a> Iterator for FrameItemIterator<'a> { + type Item = (Point, &'a FrameItem); + + fn next(&mut self) -> Option { + if let Some((position, item)) = self.recur.as_mut().and_then(|r| r.next()) { + return Some((position.transform(self.transform), item)); + } + self.recur = None; + let (position, item) = self.current.next()?; + if let FrameItem::Group(GroupItem { + frame, transform, .. + }) = item + { + self.recur = Some(Box::new(Self::new_at( + transform + .invert() + .unwrap() + .post_concat(Transform::translate(position.x, position.y)), + frame, + ))); + } + Some((position.transform(self.transform), item)) + } +} + // TODO: render warnings -// TODO: text/link/... handling +// TODO: PDF as attachment +// TODO: link/... handling // TODO: better caching // TODO: nixify compilation of magicdb (file -Cm ~/.magic:/nix/store/vi7ya34k19nid2m0dmkljqip5572g0bi-file-5.45/share/misc/magic.mgc) /*