Add basic text handling

This commit is contained in:
bluepython508
2025-11-01 19:37:25 +00:00
parent 481369ea7c
commit 5fd32e20c5
2 changed files with 160 additions and 9 deletions

View File

@@ -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()

View File

@@ -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<ZathuraResult>)
}
.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::<String>();
*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<ZathuraResult>)
}
.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<Box<FrameItemIterator<'a>>>,
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<Self::Item> {
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)
/*