diff --git a/flake.nix b/flake.nix index 1ec6e50..543f8c3 100644 --- a/flake.nix +++ b/flake.nix @@ -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 diff --git a/src/lib.rs b/src/lib.rs index ab11c8e..9020693 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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::>() + .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 diff --git a/src/typst.rs b/src/typst.rs index 5104871..0e76015 100644 --- a/src/typst.rs +++ b/src/typst.rs @@ -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, + pub cstring_cache: HashMap, } #[allow(clippy::result_large_err)] @@ -146,6 +147,7 @@ fn compile(path: &Path) -> Result, Vec 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 TypstDocument { doc: output.expect("Should be valid syntax"), warnings: vec![], + cstring_cache: Default::default() } } diff --git a/src/zathura.rs b/src/zathura.rs index b499719..19979ec 100644 --- a/src/zathura.rs +++ b/src/zathura.rs @@ -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 for Rect { ) } } +impl From 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 { + list: *mut girara_list_t, + data: PhantomData>, +} + +impl GiraraList { + pub fn new() -> Self { + unsafe extern "C" fn free(t: *mut c_void) { + drop(unsafe { Box::from_raw(t as *mut T) }); + } + Self { + list: unsafe { girara_list_new_with_free(Some(free::)) }, + 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) { + for i in iter { + unsafe { self.append_allocated(i) } + } + } +} + +impl Extend for GiraraList { + fn extend>(&mut self, iter: I) { + for i in iter { + self.append(i); + } + } +} + +impl FromIterator for GiraraList { + fn from_iter>(iter: I) -> Self { + let mut list = Self::new(); + list.extend(iter); + list + } +} + +impl Drop for GiraraList { + fn drop(&mut self) { + unsafe { + girara_list_free(self.list); + } + } +}