Add link handling

This commit is contained in:
bluepython508
2025-11-08 14:18:34 +00:00
parent 03353f9796
commit 81c62481c8
4 changed files with 193 additions and 35 deletions

View File

@@ -51,7 +51,7 @@
devShells = eachSystem ({ pkgs, ownPkgs, ... }: {
default = pkgs.mkShell {
inputsFrom = [ownPkgs.default];
packages = [pkgs.rust-analyzer pkgs.clippy pkgs.rustfmt];
packages = [pkgs.rust-analyzer pkgs.clippy pkgs.rustfmt pkgs.gdb];
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
shellHook = ''
# From: https://github.com/NixOS/nixpkgs/blob/1fab95f5190d087e66a3502481e34e15d62090aa/pkgs/applications/networking/browsers/firefox/common.nix#L247-L253

View File

@@ -1,15 +1,17 @@
use std::{
cmp::{max, min},
ffi::{CStr, OsStr, c_void},
ffi::{CStr, CString, OsStr, c_void},
fs,
os::unix::ffi::OsStrExt,
path::Path,
ptr::NonNull,
ptr::{self, NonNull},
slice,
};
use ::typst::{
Document,
layout::{Abs, FrameItem, Page, Point, Rect},
model::Destination,
text::{Glyph, TextItem},
};
use cairo::{Format, ImageSurface};
@@ -17,11 +19,7 @@ use cairo::{Format, ImageSurface};
use crate::{
typst::FrameItemIterator,
zathura::{
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,
cairo_t, 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_link_destination_type_e_ZATHURA_LINK_DESTINATION_UNKNOWN, zathura_link_destination_type_e_ZATHURA_LINK_DESTINATION_XYZ, zathura_link_free, zathura_link_new, zathura_link_target_s, zathura_link_type_e_ZATHURA_LINK_GOTO_DEST, zathura_link_type_e_ZATHURA_LINK_GOTO_REMOTE, zathura_link_type_e_ZATHURA_LINK_URI, 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, GiraraList, ZathuraResult
},
};
@@ -42,17 +40,17 @@ pub static zathura_plugin_6_7: zathura::zathura_plugin_definition_s =
functions: zathura::zathura_plugin_functions_s {
document_open: Some(document_open),
document_free: Some(document_free),
document_index_generate: None,
document_index_generate: None, // TODO?
document_save_as: Some(document_save_as),
document_attachments_get: None,
document_attachment_save: None,
document_get_information: None,
document_get_information: None, // TODO
page_init: Some(page_init),
page_clear: Some(page_clear),
page_search_text: None,
page_links_get: None,
page_search_text: None, // TODO?
page_links_get: Some(page_links_get),
page_form_fields_get: None,
page_images_get: None,
page_images_get: None, // TODO?
page_image_get_cairo: None,
page_get_text: Some(page_get_text),
page_get_selection: Some(page_get_selection),
@@ -232,7 +230,6 @@ fn selected_glyphs(
}))
})
.filter_map(move |(point, glyph)| {
dbg!(&item.text[glyph.range()]);
let bbox = item
.font
.ttf()
@@ -326,30 +323,108 @@ unsafe extern "C" fn page_get_selection(
}
});
unsafe extern "C" fn drop_rect(data: *mut c_void) {
drop(unsafe { Box::from_raw(data as *mut zathura_rectangle_s) });
*res = ZathuraResult::OK;
rects
.map(zathura_rectangle_s::from)
.collect::<GiraraList<zathura_rectangle_s>>()
.into_raw()
}
unsafe extern "C" fn page_links_get(
zpage: *mut zathura_page_t,
page: *mut c_void,
res: *mut ZathuraResult,
) -> *mut girara_list_t {
let res = if let Some(mut r) = NonNull::new(res) {
unsafe {
r.write(ZathuraResult::Unknown);
r.as_mut()
}
} else {
&mut ZathuraResult::OK
};
let doc = unsafe {
&mut (*(zathura_document_get_data(zathura_page_get_document(zpage)) as *mut TypstDocument))
};
let page: &Page = unsafe { &*(page as *const _) };
let links = FrameItemIterator::new(&page.frame)
.filter_map(|(point, item)| {
if let FrameItem::Link(dst, size) = item {
Some((Rect::from_pos_size(point, *size), dst))
} else {
None
}
})
.map(|(rect, dst)| {
let (ty, target) = match dst {
Destination::Url(url) => (
if url.starts_with("file://") {
zathura_link_type_e_ZATHURA_LINK_GOTO_REMOTE
} else {
zathura_link_type_e_ZATHURA_LINK_URI
},
zathura_link_target_s {
destination_type:
zathura_link_destination_type_e_ZATHURA_LINK_DESTINATION_UNKNOWN,
value: doc
.cstring_cache
.entry(url.to_string())
.or_insert_with(|| {
CString::new(url.as_bytes()).expect("URL shouldn't contain NUL")
})
.as_ptr() as *mut i8,
page_number: 0,
left: -1.,
right: -1.,
top: -1.,
bottom: -1.,
zoom: 0.,
},
),
Destination::Position(position) => (
zathura_link_type_e_ZATHURA_LINK_GOTO_DEST,
zathura_link_target_s {
destination_type:
zathura_link_destination_type_e_ZATHURA_LINK_DESTINATION_XYZ,
value: ptr::null_mut(),
page_number: position.page.get() as u32 - 1,
left: position.point.x.to_pt(),
right: -1.,
top: position.point.y.to_pt(),
bottom: -1.,
zoom: 0.,
},
),
Destination::Location(location) => {
let position = doc.doc.introspector().position(*location);
(
zathura_link_type_e_ZATHURA_LINK_GOTO_DEST,
zathura_link_target_s {
destination_type:
zathura_link_destination_type_e_ZATHURA_LINK_DESTINATION_XYZ,
value: ptr::null_mut(),
page_number: position.page.get() as u32 - 1,
left: position.point.x.to_pt(),
right: -1.,
top: position.point.y.to_pt(),
bottom: -1.,
zoom: 0.,
},
)
}
};
unsafe { zathura_link_new(ty, rect.into(), target) }
});
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 _,
);
}
let mut links_ = GiraraList::new_with_free(zathura_link_free);
links_.extend_allocated(links);
*res = ZathuraResult::OK;
list
links_.into_raw()
}
}
// TODO: render warnings
// TODO: link/... handling
// TODO: better caching

View File

@@ -1,5 +1,5 @@
use std::{
collections::{hash_map::Entry, HashMap}, hash::Hash, io::{self, ErrorKind}, iter, path::{Path, PathBuf}, slice, sync::{LazyLock, Mutex}
collections::{hash_map::Entry, HashMap}, ffi::CString, hash::Hash, io::{self, ErrorKind}, iter, path::{Path, PathBuf}, slice, sync::{LazyLock, Mutex}
};
use codespan_reporting::{
@@ -8,7 +8,7 @@ use codespan_reporting::{
term::{Config, DisplayStyle, emit_to_write_style, termcolor::Ansi},
};
use typst::{
diag::{FileError, FileResult, SourceDiagnostic, Warned}, foundations::{Bytes, Datetime}, layout::{Frame, FrameItem, GroupItem, Point, Transform}, syntax::{FileId, Lines, Source, Span, VirtualPath}, text::{Font, FontBook}, utils::LazyHash, Library, LibraryExt, WorldExt
diag::{FileError, FileResult, SourceDiagnostic, Warned}, foundations::{Bytes, Datetime}, layout::{Frame, FrameItem, GroupItem, Point, Transform}, model::Url, syntax::{FileId, Lines, Source, Span, VirtualPath}, text::{Font, FontBook}, utils::LazyHash, Library, LibraryExt, WorldExt
};
use typst_kit::{
download::{Downloader, ProgressSink},
@@ -131,6 +131,7 @@ impl World {
pub struct TypstDocument {
pub doc: typst::layout::PagedDocument,
pub warnings: Vec<SourceDiagnostic>,
pub cstring_cache: HashMap<String, CString>,
}
#[allow(clippy::result_large_err)]
@@ -146,6 +147,7 @@ fn compile(path: &Path) -> Result<TypstDocument, (Option<World>, Vec<SourceDiagn
Ok(doc) => Ok(TypstDocument {
doc,
warnings: warnings.to_vec(),
cstring_cache: Default::default(),
}),
Err(mut errors) => {
errors.extend(warnings);
@@ -277,6 +279,7 @@ fn diagnostics(world: &World, errors: impl IntoIterator<Item = SourceDiagnostic>
TypstDocument {
doc: output.expect("Should be valid syntax"),
warnings: vec![],
cstring_cache: Default::default()
}
}

View File

@@ -1,4 +1,6 @@
#![allow(warnings)]
use std::{ffi::c_void, marker::PhantomData, mem::ManuallyDrop};
pub use cairo::ffi::*;
use typst::layout::{Abs, Point, Rect};
@@ -8,6 +10,7 @@ unsafe impl Sync for zathura_plugin_definition_s {}
pub type zathura_plugin_error_e = ZathuraResult;
#[repr(u32)]
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub enum ZathuraResult {
/// No error occurred
OK = 0,
@@ -45,3 +48,80 @@ impl From<zathura_rectangle_s> for Rect {
)
}
}
impl From<Rect> for zathura_rectangle_s {
fn from(rect: Rect) -> Self {
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(),
}
}
}
pub struct GiraraList<T> {
list: *mut girara_list_t,
data: PhantomData<Box<T>>,
}
impl<T> GiraraList<T> {
pub fn new() -> Self {
unsafe extern "C" fn free<T>(t: *mut c_void) {
drop(unsafe { Box::from_raw(t as *mut T) });
}
Self {
list: unsafe { girara_list_new_with_free(Some(free::<T>)) },
data: PhantomData,
}
}
pub unsafe fn new_with_free(free: unsafe extern "C" fn(*mut T)) -> Self {
Self {
list: unsafe { girara_list_new_with_free(Some(std::mem::transmute(free))) },
data: PhantomData,
}
}
pub fn into_raw(self) -> *mut girara_list_t {
let s = ManuallyDrop::new(self);
s.list
}
pub fn append(&mut self, t: T) {
unsafe { self.append_allocated(Box::into_raw(Box::new(t)) as _) };
}
pub unsafe fn append_allocated(&mut self, t: *mut T) {
unsafe { girara_list_append(self.list, t as _) };
}
pub unsafe fn extend_allocated(&mut self, iter: impl IntoIterator<Item = *mut T>) {
for i in iter {
unsafe { self.append_allocated(i) }
}
}
}
impl<T> Extend<T> for GiraraList<T> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
for i in iter {
self.append(i);
}
}
}
impl<T> FromIterator<T> for GiraraList<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut list = Self::new();
list.extend(iter);
list
}
}
impl<T> Drop for GiraraList<T> {
fn drop(&mut self) {
unsafe {
girara_list_free(self.list);
}
}
}