use std::ffi::CStr;
use std::os::raw::c_char;
use std::sync::LazyLock;
use super::{Handle, HandleStore};
use crate::fitz::geometry::Rect;
use crate::fitz::link::{Link, LinkList};
pub static LINKS: LazyLock<HandleStore<Link>> = LazyLock::new(HandleStore::new);
pub static LINK_LISTS: LazyLock<HandleStore<LinkList>> = LazyLock::new(HandleStore::new);
#[unsafe(no_mangle)]
pub extern "C" fn fz_create_link(
_ctx: Handle,
rect: super::geometry::fz_rect,
uri: *const c_char,
) -> Handle {
if uri.is_null() {
return 0;
}
unsafe {
if let Ok(uri_str) = CStr::from_ptr(uri).to_str() {
let r = Rect::new(rect.x0, rect.y0, rect.x1, rect.y1);
let link = Link::new(r, uri_str);
return LINKS.insert(link);
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_keep_link(_ctx: Handle, link: Handle) -> Handle {
link
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_drop_link(_ctx: Handle, link: Handle) {
LINKS.remove(link);
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_link_rect(_ctx: Handle, link: Handle) -> super::geometry::fz_rect {
if let Some(l) = LINKS.get(link) {
if let Ok(guard) = l.lock() {
return super::geometry::fz_rect {
x0: guard.rect.x0,
y0: guard.rect.y0,
x1: guard.rect.x1,
y1: guard.rect.y1,
};
}
}
super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 0.0,
y1: 0.0,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_link_uri(_ctx: Handle, link: Handle, buf: *mut c_char, bufsize: i32) -> i32 {
if buf.is_null() || bufsize <= 0 {
return 0;
}
if let Some(l) = LINKS.get(link) {
if let Ok(guard) = l.lock() {
let uri_bytes = guard.uri.as_bytes();
let copy_len = uri_bytes.len().min((bufsize - 1) as usize);
unsafe {
std::ptr::copy_nonoverlapping(uri_bytes.as_ptr(), buf as *mut u8, copy_len);
*buf.add(copy_len) = 0; }
return copy_len as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_is_external_link(_ctx: Handle, link: Handle) -> i32 {
if let Some(l) = LINKS.get(link) {
if let Ok(guard) = l.lock() {
return if guard.is_external() { 1 } else { 0 };
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_link_page_number(_ctx: Handle, link: Handle) -> i32 {
if let Some(l) = LINKS.get(link) {
if let Ok(guard) = l.lock() {
return guard.page_number().unwrap_or(-1);
}
}
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_is_page_link(_ctx: Handle, link: Handle) -> i32 {
if let Some(l) = LINKS.get(link) {
if let Ok(guard) = l.lock() {
return if guard.is_page_link() { 1 } else { 0 };
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_link_list(_ctx: Handle) -> Handle {
let list = LinkList::new();
LINK_LISTS.insert(list)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_drop_link_list(_ctx: Handle, list: Handle) {
LINK_LISTS.remove(list);
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_link_list_add(_ctx: Handle, list: Handle, link: Handle) {
if let Some(list_arc) = LINK_LISTS.get(list) {
if let Some(link_arc) = LINKS.get(link) {
if let (Ok(mut list_guard), Ok(link_guard)) = (list_arc.lock(), link_arc.lock()) {
list_guard.push(link_guard.clone());
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_link_list_count(_ctx: Handle, list: Handle) -> i32 {
if let Some(l) = LINK_LISTS.get(list) {
if let Ok(guard) = l.lock() {
return guard.len() as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_link_list_find_at_point(_ctx: Handle, list: Handle, x: f32, y: f32) -> Handle {
if let Some(l) = LINK_LISTS.get(list) {
if let Ok(guard) = l.lock() {
if let Some(link) = guard.link_at_point(x, y) {
return LINKS.insert(link.clone());
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_link_list_is_empty(_ctx: Handle, list: Handle) -> i32 {
if let Some(l) = LINK_LISTS.get(list) {
if let Ok(guard) = l.lock() {
return if guard.is_empty() { 1 } else { 0 };
}
}
1
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_link_list_first(_ctx: Handle, list: Handle) -> Handle {
if let Some(l) = LINK_LISTS.get(list) {
if let Ok(guard) = l.lock() {
if let Some(link) = guard.first() {
return LINKS.insert(link.clone());
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_link_list_get(_ctx: Handle, list: Handle, index: i32) -> Handle {
if index < 0 {
return 0;
}
if let Some(l) = LINK_LISTS.get(list) {
if let Ok(guard) = l.lock() {
if let Some(link) = guard.get(index as usize) {
return LINKS.insert(link.clone());
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_clone_link(_ctx: Handle, link: Handle) -> Handle {
if let Some(l) = LINKS.get(link) {
if let Ok(guard) = l.lock() {
return LINKS.insert(guard.clone());
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_link_is_valid(_ctx: Handle, link: Handle) -> i32 {
if LINKS.get(link).is_some() { 1 } else { 0 }
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_link_uri(_ctx: Handle, link: Handle, uri: *const c_char) -> i32 {
if uri.is_null() {
return 0;
}
if let Some(l) = LINKS.get(link) {
if let Ok(mut guard) = l.lock() {
if let Ok(uri_str) = unsafe { CStr::from_ptr(uri).to_str() } {
guard.uri = uri_str.to_string();
return 1;
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_link_rect(_ctx: Handle, link: Handle, rect: super::geometry::fz_rect) {
if let Some(l) = LINKS.get(link) {
if let Ok(mut guard) = l.lock() {
guard.rect = Rect::new(rect.x0, rect.y0, rect.x1, rect.y1);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_link_eq(_ctx: Handle, link1: Handle, link2: Handle) -> i32 {
if let (Some(l1), Some(l2)) = (LINKS.get(link1), LINKS.get(link2)) {
if let (Ok(g1), Ok(g2)) = (l1.lock(), l2.lock()) {
return if g1.uri == g2.uri { 1 } else { 0 };
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_link_list_clear(_ctx: Handle, list: Handle) {
if let Some(l) = LINK_LISTS.get(list) {
if let Ok(mut guard) = l.lock() {
guard.clear();
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_clone_link_list(_ctx: Handle, list: Handle) -> Handle {
if let Some(l) = LINK_LISTS.get(list) {
if let Ok(guard) = l.lock() {
let cloned = guard.clone();
return LINK_LISTS.insert(cloned);
}
}
0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_link() {
let rect = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 50.0,
};
let uri = c"https://example.com";
let link = fz_create_link(0, rect, uri.as_ptr());
assert_ne!(link, 0);
fz_drop_link(0, link);
}
#[test]
fn test_keep_link() {
let rect = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 50.0,
};
let uri = c"https://example.com";
let link = fz_create_link(0, rect, uri.as_ptr());
let kept = fz_keep_link(0, link);
assert_eq!(kept, link);
fz_drop_link(0, link);
}
#[test]
fn test_link_rect() {
let rect = super::super::geometry::fz_rect {
x0: 10.0,
y0: 20.0,
x1: 110.0,
y1: 70.0,
};
let uri = c"https://example.com";
let link = fz_create_link(0, rect, uri.as_ptr());
let retrieved_rect = fz_link_rect(0, link);
assert_eq!(retrieved_rect.x0, 10.0);
assert_eq!(retrieved_rect.y0, 20.0);
assert_eq!(retrieved_rect.x1, 110.0);
assert_eq!(retrieved_rect.y1, 70.0);
fz_drop_link(0, link);
}
#[test]
fn test_link_uri() {
let rect = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 50.0,
};
let uri = c"https://example.com";
let link = fz_create_link(0, rect, uri.as_ptr());
let mut buf = [0i8; 256];
let len = fz_link_uri(0, link, buf.as_mut_ptr(), 256);
assert!(len > 0);
let uri_str = unsafe { CStr::from_ptr(buf.as_ptr()) }.to_str().unwrap();
assert_eq!(uri_str, "https://example.com");
fz_drop_link(0, link);
}
#[test]
fn test_is_external_link() {
let rect = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 50.0,
};
let external = fz_create_link(0, rect, c"https://example.com".as_ptr());
assert_eq!(fz_is_external_link(0, external), 1);
fz_drop_link(0, external);
let internal = fz_create_link(0, rect, c"#page=5".as_ptr());
assert_eq!(fz_is_external_link(0, internal), 0);
fz_drop_link(0, internal);
}
#[test]
fn test_is_page_link() {
let rect = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 50.0,
};
let page_link = fz_create_link(0, rect, c"#page=5".as_ptr());
assert_eq!(fz_is_page_link(0, page_link), 1);
fz_drop_link(0, page_link);
let external = fz_create_link(0, rect, c"https://example.com".as_ptr());
assert_eq!(fz_is_page_link(0, external), 0);
fz_drop_link(0, external);
}
#[test]
fn test_link_page_number() {
let rect = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 50.0,
};
let link1 = fz_create_link(0, rect, c"#page=5".as_ptr());
assert_eq!(fz_link_page_number(0, link1), 5);
fz_drop_link(0, link1);
let link2 = fz_create_link(0, rect, c"#10".as_ptr());
assert_eq!(fz_link_page_number(0, link2), 10);
fz_drop_link(0, link2);
let external = fz_create_link(0, rect, c"https://example.com".as_ptr());
assert_eq!(fz_link_page_number(0, external), -1);
fz_drop_link(0, external);
}
#[test]
fn test_new_link_list() {
let list = fz_new_link_list(0);
assert_ne!(list, 0);
fz_drop_link_list(0, list);
}
#[test]
fn test_link_list_operations() {
let list = fz_new_link_list(0);
assert_eq!(fz_link_list_is_empty(0, list), 1);
assert_eq!(fz_link_list_count(0, list), 0);
let rect = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 50.0,
};
let link = fz_create_link(0, rect, c"#page=1".as_ptr());
fz_link_list_add(0, list, link);
assert_eq!(fz_link_list_is_empty(0, list), 0);
assert_eq!(fz_link_list_count(0, list), 1);
fz_drop_link(0, link);
fz_drop_link_list(0, list);
}
#[test]
fn test_link_list_find_at_point() {
let list = fz_new_link_list(0);
let rect1 = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 100.0,
};
let link1 = fz_create_link(0, rect1, c"#page=1".as_ptr());
fz_link_list_add(0, list, link1);
let rect2 = super::super::geometry::fz_rect {
x0: 200.0,
y0: 200.0,
x1: 300.0,
y1: 300.0,
};
let link2 = fz_create_link(0, rect2, c"#page=2".as_ptr());
fz_link_list_add(0, list, link2);
let found1 = fz_link_list_find_at_point(0, list, 50.0, 50.0);
assert_ne!(found1, 0);
fz_drop_link(0, found1);
let found2 = fz_link_list_find_at_point(0, list, 250.0, 250.0);
assert_ne!(found2, 0);
fz_drop_link(0, found2);
let not_found = fz_link_list_find_at_point(0, list, 500.0, 500.0);
assert_eq!(not_found, 0);
fz_drop_link(0, link1);
fz_drop_link(0, link2);
fz_drop_link_list(0, list);
}
#[test]
fn test_create_link_null_uri() {
let rect = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 50.0,
};
assert_eq!(fz_create_link(0, rect, std::ptr::null()), 0);
}
#[test]
fn test_link_uri_null_buf() {
let rect = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 50.0,
};
let link = fz_create_link(0, rect, c"https://x.com".as_ptr());
assert_eq!(fz_link_uri(0, link, std::ptr::null_mut(), 256), 0);
assert_eq!(fz_link_uri(0, link, [0i8].as_mut_ptr(), 0), 0);
fz_drop_link(0, link);
}
#[test]
fn test_link_invalid_handle() {
let r = fz_link_rect(0, 0);
assert_eq!(r.x0, 0.0);
assert_eq!(r.x1, 0.0);
let mut buf = [0i8; 256];
assert_eq!(fz_link_uri(0, 0, buf.as_mut_ptr(), 256), 0);
assert_eq!(fz_is_external_link(0, 0), 0);
assert_eq!(fz_link_page_number(0, 0), -1);
assert_eq!(fz_is_page_link(0, 0), 0);
assert_eq!(fz_link_is_valid(0, 0), 0);
}
#[test]
fn test_link_list_get() {
let list = fz_new_link_list(0);
let rect = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 50.0,
};
let link = fz_create_link(0, rect, c"#page=1".as_ptr());
fz_link_list_add(0, list, link);
let got = fz_link_list_get(0, list, 0);
assert_ne!(got, 0);
fz_drop_link(0, got);
assert_eq!(fz_link_list_get(0, list, 5), 0);
assert_eq!(fz_link_list_get(0, list, -1), 0);
fz_drop_link(0, link);
fz_drop_link_list(0, list);
}
#[test]
fn test_link_list_first() {
let list = fz_new_link_list(0);
assert_eq!(fz_link_list_first(0, list), 0);
fz_drop_link_list(0, list);
}
#[test]
fn test_clone_link() {
let rect = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 50.0,
};
let link = fz_create_link(0, rect, c"https://x.com".as_ptr());
let cloned = fz_clone_link(0, link);
assert_ne!(cloned, 0);
fz_drop_link(0, cloned);
fz_drop_link(0, link);
}
#[test]
fn test_set_link_uri() {
let rect = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 50.0,
};
let link = fz_create_link(0, rect, c"https://old.com".as_ptr());
assert_eq!(fz_set_link_uri(0, link, c"https://new.com".as_ptr()), 1);
let mut buf = [0i8; 256];
fz_link_uri(0, link, buf.as_mut_ptr(), 256);
let s = unsafe { CStr::from_ptr(buf.as_ptr()).to_str().unwrap() };
assert_eq!(s, "https://new.com");
assert_eq!(fz_set_link_uri(0, link, std::ptr::null()), 0);
fz_drop_link(0, link);
}
#[test]
fn test_set_link_rect() {
let rect = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 50.0,
};
let link = fz_create_link(0, rect, c"https://x.com".as_ptr());
let new_rect = super::super::geometry::fz_rect {
x0: 5.0,
y0: 10.0,
x1: 95.0,
y1: 40.0,
};
fz_set_link_rect(0, link, new_rect);
let got = fz_link_rect(0, link);
assert_eq!(got.x0, 5.0);
fz_drop_link(0, link);
}
#[test]
fn test_link_eq() {
let rect = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 50.0,
};
let link1 = fz_create_link(0, rect, c"https://same.com".as_ptr());
let link2 = fz_create_link(0, rect, c"https://same.com".as_ptr());
assert_eq!(fz_link_eq(0, link1, link2), 1);
let link3 = fz_create_link(0, rect, c"https://diff.com".as_ptr());
assert_eq!(fz_link_eq(0, link1, link3), 0);
assert_eq!(fz_link_eq(0, 0, link1), 0);
fz_drop_link(0, link1);
fz_drop_link(0, link2);
fz_drop_link(0, link3);
}
#[test]
fn test_link_list_clear() {
let list = fz_new_link_list(0);
let rect = super::super::geometry::fz_rect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 50.0,
};
let link = fz_create_link(0, rect, c"#1".as_ptr());
fz_link_list_add(0, list, link);
fz_link_list_clear(0, list);
assert_eq!(fz_link_list_count(0, list), 0);
fz_drop_link(0, link);
fz_drop_link_list(0, list);
}
#[test]
fn test_clone_link_list() {
let list = fz_new_link_list(0);
let cloned = fz_clone_link_list(0, list);
assert_ne!(cloned, 0);
fz_drop_link_list(0, cloned);
fz_drop_link_list(0, list);
}
}