use crate::ffi::buffer::Buffer;
use crate::ffi::{BUFFERS, Handle};
use std::ffi::{CStr, CString, c_char, c_void};
use std::path::Path;
use std::ptr;
#[unsafe(no_mangle)]
pub extern "C" fn fz_strnlen(s: *const c_char, maxlen: usize) -> usize {
if s.is_null() {
return 0;
}
let bytes = unsafe { std::slice::from_raw_parts(s as *const u8, maxlen) };
bytes.iter().position(|&b| b == 0).unwrap_or(maxlen)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_strlcpy(dst: *mut c_char, src: *const c_char, n: usize) -> usize {
if dst.is_null() || src.is_null() || n == 0 {
return 0;
}
let src_cstr = unsafe { CStr::from_ptr(src) };
let src_bytes = src_cstr.to_bytes();
let src_len = src_bytes.len();
let copy_len = std::cmp::min(src_len, n - 1);
unsafe {
ptr::copy_nonoverlapping(src as *const u8, dst as *mut u8, copy_len);
*dst.add(copy_len) = 0;
}
src_len
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_strlcat(dst: *mut c_char, src: *const c_char, n: usize) -> usize {
if dst.is_null() || src.is_null() || n == 0 {
return 0;
}
let dst_len = fz_strnlen(dst, n);
if dst_len >= n {
return dst_len + fz_strnlen(src, usize::MAX);
}
let remaining = n - dst_len;
let src_cstr = unsafe { CStr::from_ptr(src) };
let src_bytes = src_cstr.to_bytes();
let src_len = src_bytes.len();
let copy_len = std::cmp::min(src_len, remaining - 1);
unsafe {
ptr::copy_nonoverlapping(src as *const u8, dst.add(dst_len) as *mut u8, copy_len);
*dst.add(dst_len + copy_len) = 0;
}
dst_len + src_len
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_strsep(stringp: *mut *mut c_char, delim: *const c_char) -> *mut c_char {
if stringp.is_null() || delim.is_null() {
return ptr::null_mut();
}
let s = unsafe { *stringp };
if s.is_null() {
return ptr::null_mut();
}
let delim_cstr = unsafe { CStr::from_ptr(delim) };
let delim_bytes = delim_cstr.to_bytes();
let mut p = s;
loop {
let c = unsafe { *p };
if c == 0 {
unsafe { *stringp = ptr::null_mut() };
return s;
}
if delim_bytes.contains(&(c as u8)) {
unsafe {
*p = 0;
*stringp = p.add(1);
}
return s;
}
p = unsafe { p.add(1) };
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_strstr(haystack: *const c_char, needle: *const c_char) -> *const c_char {
if haystack.is_null() || needle.is_null() {
return ptr::null();
}
let haystack_str = unsafe {
match CStr::from_ptr(haystack).to_str() {
Ok(s) => s,
Err(_) => return ptr::null(),
}
};
let needle_str = unsafe {
match CStr::from_ptr(needle).to_str() {
Ok(s) => s,
Err(_) => return ptr::null(),
}
};
match haystack_str.find(needle_str) {
Some(pos) => unsafe { haystack.add(pos) },
None => ptr::null(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_strstrcase(haystack: *const c_char, needle: *const c_char) -> *const c_char {
if haystack.is_null() || needle.is_null() {
return ptr::null();
}
let haystack_str = unsafe {
match CStr::from_ptr(haystack).to_str() {
Ok(s) => s,
Err(_) => return ptr::null(),
}
};
let needle_str = unsafe {
match CStr::from_ptr(needle).to_str() {
Ok(s) => s,
Err(_) => return ptr::null(),
}
};
let haystack_lower = haystack_str.to_lowercase();
let needle_lower = needle_str.to_lowercase();
match haystack_lower.find(&needle_lower) {
Some(pos) => {
let byte_pos = haystack_str
.char_indices()
.zip(haystack_lower.char_indices())
.find(|((_, _), (lower_pos, _))| *lower_pos == pos)
.map(|((orig_pos, _), _)| orig_pos)
.unwrap_or(pos);
unsafe { haystack.add(byte_pos) }
}
None => ptr::null(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_memmem(
haystack: *const c_void,
haystacklen: usize,
needle: *const c_void,
needlelen: usize,
) -> *const c_void {
if haystack.is_null() || needle.is_null() || needlelen == 0 || haystacklen < needlelen {
return ptr::null();
}
let haystack_bytes = unsafe { std::slice::from_raw_parts(haystack as *const u8, haystacklen) };
let needle_bytes = unsafe { std::slice::from_raw_parts(needle as *const u8, needlelen) };
for i in 0..=(haystacklen - needlelen) {
if &haystack_bytes[i..i + needlelen] == needle_bytes {
return unsafe { (haystack as *const u8).add(i) as *const c_void };
}
}
ptr::null()
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_dirname(dir: *mut c_char, path: *const c_char, dirsize: usize) {
if dir.is_null() || path.is_null() || dirsize == 0 {
return;
}
let path_str = unsafe {
match CStr::from_ptr(path).to_str() {
Ok(s) => s,
Err(_) => {
unsafe { *dir = 0 };
return;
}
}
};
let dirname = Path::new(path_str)
.parent()
.map(|p| p.to_string_lossy())
.unwrap_or_default();
let dirname_bytes = dirname.as_bytes();
let copy_len = std::cmp::min(dirname_bytes.len(), dirsize - 1);
unsafe {
ptr::copy_nonoverlapping(dirname_bytes.as_ptr(), dir as *mut u8, copy_len);
*dir.add(copy_len) = 0;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_basename(path: *const c_char) -> *const c_char {
if path.is_null() {
return ptr::null();
}
let path_str = unsafe {
match CStr::from_ptr(path).to_str() {
Ok(s) => s,
Err(_) => return path,
}
};
let last_sep = path_str
.rfind(|c| c == '/' || c == '\\')
.map(|i| i + 1)
.unwrap_or(0);
unsafe { path.add(last_sep) }
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_cleanname(name: *mut c_char) -> *mut c_char {
if name.is_null() {
return name;
}
let name_str = unsafe {
match CStr::from_ptr(name).to_str() {
Ok(s) => s.to_string(),
Err(_) => return name,
}
};
let cleaned = clean_path(&name_str);
let cleaned_bytes = cleaned.as_bytes();
unsafe {
ptr::copy_nonoverlapping(cleaned_bytes.as_ptr(), name as *mut u8, cleaned_bytes.len());
*name.add(cleaned_bytes.len()) = 0;
}
name
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_strverscmp(s1: *const c_char, s2: *const c_char) -> i32 {
if s1.is_null() && s2.is_null() {
return 0;
}
if s1.is_null() {
return -1;
}
if s2.is_null() {
return 1;
}
let str1 = unsafe {
match CStr::from_ptr(s1).to_str() {
Ok(s) => s,
Err(_) => return 0,
}
};
let str2 = unsafe {
match CStr::from_ptr(s2).to_str() {
Ok(s) => s,
Err(_) => return 0,
}
};
version_compare(str1, str2)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_urldecode(url: *mut c_char) -> *mut c_char {
if url.is_null() {
return url;
}
let url_str = unsafe {
match CStr::from_ptr(url).to_str() {
Ok(s) => s.to_string(),
Err(_) => return url,
}
};
let decoded = url_decode(&url_str);
let decoded_bytes = decoded.as_bytes();
unsafe {
ptr::copy_nonoverlapping(decoded_bytes.as_ptr(), url as *mut u8, decoded_bytes.len());
*url.add(decoded_bytes.len()) = 0;
}
url
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_decode_uri(_ctx: Handle, s: *const c_char) -> *mut c_char {
if s.is_null() {
return ptr::null_mut();
}
let s_str = unsafe {
match CStr::from_ptr(s).to_str() {
Ok(s) => s,
Err(_) => return ptr::null_mut(),
}
};
let decoded = url_decode_preserve_reserved(s_str);
match CString::new(decoded) {
Ok(cstr) => cstr.into_raw(),
Err(_) => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_decode_uri_component(_ctx: Handle, s: *const c_char) -> *mut c_char {
if s.is_null() {
return ptr::null_mut();
}
let s_str = unsafe {
match CStr::from_ptr(s).to_str() {
Ok(s) => s,
Err(_) => return ptr::null_mut(),
}
};
let decoded = url_decode(s_str);
match CString::new(decoded) {
Ok(cstr) => cstr.into_raw(),
Err(_) => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_encode_uri(_ctx: Handle, s: *const c_char) -> *mut c_char {
if s.is_null() {
return ptr::null_mut();
}
let s_str = unsafe {
match CStr::from_ptr(s).to_str() {
Ok(s) => s,
Err(_) => return ptr::null_mut(),
}
};
let encoded = url_encode(s_str, false);
match CString::new(encoded) {
Ok(cstr) => cstr.into_raw(),
Err(_) => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_encode_uri_component(_ctx: Handle, s: *const c_char) -> *mut c_char {
if s.is_null() {
return ptr::null_mut();
}
let s_str = unsafe {
match CStr::from_ptr(s).to_str() {
Ok(s) => s,
Err(_) => return ptr::null_mut(),
}
};
let encoded = url_encode(s_str, true);
match CString::new(encoded) {
Ok(cstr) => cstr.into_raw(),
Err(_) => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_encode_uri_pathname(_ctx: Handle, s: *const c_char) -> *mut c_char {
if s.is_null() {
return ptr::null_mut();
}
let s_str = unsafe {
match CStr::from_ptr(s).to_str() {
Ok(s) => s,
Err(_) => return ptr::null_mut(),
}
};
let encoded = url_encode_pathname(s_str);
match CString::new(encoded) {
Ok(cstr) => cstr.into_raw(),
Err(_) => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_strcasecmp(a: *const c_char, b: *const c_char) -> i32 {
if a.is_null() && b.is_null() {
return 0;
}
if a.is_null() {
return -1;
}
if b.is_null() {
return 1;
}
let a_str = unsafe {
match CStr::from_ptr(a).to_str() {
Ok(s) => s.to_lowercase(),
Err(_) => return 0,
}
};
let b_str = unsafe {
match CStr::from_ptr(b).to_str() {
Ok(s) => s.to_lowercase(),
Err(_) => return 0,
}
};
a_str.cmp(&b_str) as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_strncasecmp(a: *const c_char, b: *const c_char, n: usize) -> i32 {
if a.is_null() && b.is_null() {
return 0;
}
if a.is_null() {
return -1;
}
if b.is_null() {
return 1;
}
let a_bytes = unsafe { std::slice::from_raw_parts(a as *const u8, fz_strnlen(a, n)) };
let b_bytes = unsafe { std::slice::from_raw_parts(b as *const u8, fz_strnlen(b, n)) };
let a_str = String::from_utf8_lossy(a_bytes).to_lowercase();
let b_str = String::from_utf8_lossy(b_bytes).to_lowercase();
a_str.cmp(&b_str) as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_tolower(c: i32) -> i32 {
if c < 0 {
return c;
}
if let Some(ch) = char::from_u32(c as u32) {
ch.to_lowercase().next().map(|c| c as i32).unwrap_or(c)
} else {
c
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_toupper(c: i32) -> i32 {
if c < 0 {
return c;
}
if let Some(ch) = char::from_u32(c as u32) {
ch.to_uppercase().next().map(|c| c as i32).unwrap_or(c)
} else {
c
}
}
pub const FZ_UTFMAX: i32 = 4;
pub const FZ_REPLACEMENT_CHARACTER: i32 = 0xFFFD;
#[unsafe(no_mangle)]
pub extern "C" fn fz_chartorune(rune: *mut i32, str: *const c_char) -> i32 {
if rune.is_null() || str.is_null() {
return 0;
}
let bytes = unsafe { std::slice::from_raw_parts(str as *const u8, 4) };
let first = bytes[0];
let (len, codepoint) = if first & 0x80 == 0 {
(1, first as u32)
} else if first & 0xE0 == 0xC0 && bytes.len() >= 2 {
let c = ((first as u32 & 0x1F) << 6) | (bytes[1] as u32 & 0x3F);
(2, c)
} else if first & 0xF0 == 0xE0 && bytes.len() >= 3 {
let c = ((first as u32 & 0x0F) << 12)
| ((bytes[1] as u32 & 0x3F) << 6)
| (bytes[2] as u32 & 0x3F);
(3, c)
} else if first & 0xF8 == 0xF0 && bytes.len() >= 4 {
let c = ((first as u32 & 0x07) << 18)
| ((bytes[1] as u32 & 0x3F) << 12)
| ((bytes[2] as u32 & 0x3F) << 6)
| (bytes[3] as u32 & 0x3F);
(4, c)
} else {
unsafe { *rune = FZ_REPLACEMENT_CHARACTER };
return 1;
};
unsafe { *rune = codepoint as i32 };
len
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_chartorunen(rune: *mut i32, str: *const c_char, n: usize) -> i32 {
if rune.is_null() || str.is_null() || n == 0 {
return 0;
}
let bytes = unsafe { std::slice::from_raw_parts(str as *const u8, n) };
let first = bytes[0];
let needed = if first & 0x80 == 0 {
1
} else if first & 0xE0 == 0xC0 {
2
} else if first & 0xF0 == 0xE0 {
3
} else if first & 0xF8 == 0xF0 {
4
} else {
unsafe { *rune = FZ_REPLACEMENT_CHARACTER };
return 1;
};
if n < needed {
unsafe { *rune = FZ_REPLACEMENT_CHARACTER };
return n as i32;
}
fz_chartorune(rune, str)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_runetochar(str: *mut c_char, rune: i32) -> i32 {
if str.is_null() {
return 0;
}
let codepoint = rune as u32;
let dst = str as *mut u8;
if codepoint < 0x80 {
unsafe { *dst = codepoint as u8 };
1
} else if codepoint < 0x800 {
unsafe {
*dst = (0xC0 | (codepoint >> 6)) as u8;
*dst.add(1) = (0x80 | (codepoint & 0x3F)) as u8;
}
2
} else if codepoint < 0x10000 {
unsafe {
*dst = (0xE0 | (codepoint >> 12)) as u8;
*dst.add(1) = (0x80 | ((codepoint >> 6) & 0x3F)) as u8;
*dst.add(2) = (0x80 | (codepoint & 0x3F)) as u8;
}
3
} else if codepoint < 0x110000 {
unsafe {
*dst = (0xF0 | (codepoint >> 18)) as u8;
*dst.add(1) = (0x80 | ((codepoint >> 12) & 0x3F)) as u8;
*dst.add(2) = (0x80 | ((codepoint >> 6) & 0x3F)) as u8;
*dst.add(3) = (0x80 | (codepoint & 0x3F)) as u8;
}
4
} else {
unsafe {
*dst = 0xEF;
*dst.add(1) = 0xBF;
*dst.add(2) = 0xBD;
}
3
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_runelen(rune: i32) -> i32 {
let codepoint = rune as u32;
if codepoint < 0x80 {
1
} else if codepoint < 0x800 {
2
} else if codepoint < 0x10000 {
3
} else if codepoint < 0x110000 {
4
} else {
3 }
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_runeidx(str: *const c_char, p: *const c_char) -> i32 {
if str.is_null() || p.is_null() {
return 0;
}
let str_ptr = str as usize;
let p_ptr = p as usize;
if p_ptr < str_ptr {
return 0;
}
let offset = p_ptr - str_ptr;
let str_str = unsafe {
match CStr::from_ptr(str).to_str() {
Ok(s) => s,
Err(_) => return 0,
}
};
str_str
.char_indices()
.take_while(|(i, _)| *i < offset)
.count() as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_runeptr(str: *const c_char, idx: i32) -> *const c_char {
if str.is_null() || idx < 0 {
return ptr::null();
}
let str_str = unsafe {
match CStr::from_ptr(str).to_str() {
Ok(s) => s,
Err(_) => return ptr::null(),
}
};
if let Some((byte_pos, _)) = str_str.char_indices().nth(idx as usize) {
unsafe { str.add(byte_pos) }
} else {
ptr::null()
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_utflen(s: *const c_char) -> i32 {
if s.is_null() {
return 0;
}
let s_str = unsafe {
match CStr::from_ptr(s).to_str() {
Ok(s) => s,
Err(_) => return 0,
}
};
s_str.chars().count() as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_atof(s: *const c_char) -> f32 {
if s.is_null() {
return 0.0;
}
let s_str = unsafe {
match CStr::from_ptr(s).to_str() {
Ok(s) => s.trim(),
Err(_) => return 0.0,
}
};
let lower = s_str.to_lowercase();
if lower == "nan" {
return f32::NAN;
}
if lower == "inf" || lower == "infinity" {
return f32::INFINITY;
}
if lower == "-inf" || lower == "-infinity" {
return f32::NEG_INFINITY;
}
s_str.parse::<f32>().unwrap_or(0.0)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_atoi(s: *const c_char) -> i32 {
if s.is_null() {
return 0;
}
let s_str = unsafe {
match CStr::from_ptr(s).to_str() {
Ok(s) => s.trim(),
Err(_) => return 0,
}
};
s_str.parse::<i32>().unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_atoi64(s: *const c_char) -> i64 {
if s.is_null() {
return 0;
}
let s_str = unsafe {
match CStr::from_ptr(s).to_str() {
Ok(s) => s.trim(),
Err(_) => return 0,
}
};
s_str.parse::<i64>().unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_strtof(s: *const c_char, es: *mut *mut c_char) -> f32 {
if s.is_null() {
if !es.is_null() {
unsafe { *es = s as *mut c_char };
}
return 0.0;
}
let s_str = unsafe {
match CStr::from_ptr(s).to_str() {
Ok(s) => s,
Err(_) => {
if !es.is_null() {
unsafe { *es = s as *mut c_char };
}
return 0.0;
}
}
};
let trimmed = s_str.trim_start();
let start_offset = s_str.len() - trimmed.len();
let mut end_idx = 0;
let chars: Vec<char> = trimmed.chars().collect();
if !chars.is_empty() && (chars[0] == '+' || chars[0] == '-') {
end_idx += 1;
}
while end_idx < chars.len() && chars[end_idx].is_ascii_digit() {
end_idx += 1;
}
if end_idx < chars.len() && chars[end_idx] == '.' {
end_idx += 1;
while end_idx < chars.len() && chars[end_idx].is_ascii_digit() {
end_idx += 1;
}
}
if end_idx < chars.len() && (chars[end_idx] == 'e' || chars[end_idx] == 'E') {
end_idx += 1;
if end_idx < chars.len() && (chars[end_idx] == '+' || chars[end_idx] == '-') {
end_idx += 1;
}
while end_idx < chars.len() && chars[end_idx].is_ascii_digit() {
end_idx += 1;
}
}
let num_str: String = chars[..end_idx].iter().collect();
let value = num_str.parse::<f32>().unwrap_or(0.0);
if !es.is_null() {
let byte_offset =
start_offset + chars[..end_idx].iter().map(|c| c.len_utf8()).sum::<usize>();
unsafe { *es = s.add(byte_offset) as *mut c_char };
}
value
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_strdup(_ctx: Handle, s: *const c_char) -> *mut c_char {
if s.is_null() {
return ptr::null_mut();
}
let cstr = unsafe { CStr::from_ptr(s) };
match CString::new(cstr.to_bytes()) {
Ok(dup) => dup.into_raw(),
Err(_) => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_free_string(_ctx: Handle, s: *mut c_char) {
if !s.is_null() {
unsafe {
let _ = CString::from_raw(s);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_is_page_range(_ctx: Handle, s: *const c_char) -> i32 {
if s.is_null() {
return 0;
}
let s_str = unsafe {
match CStr::from_ptr(s).to_str() {
Ok(s) => s,
Err(_) => return 0,
}
};
let parts: Vec<&str> = s_str.split(',').collect();
for part in parts {
let part = part.trim();
if part.is_empty() {
continue;
}
let range_parts: Vec<&str> = part.splitn(2, '-').collect();
for rp in &range_parts {
let rp = rp.trim();
if rp == "N" || rp.is_empty() {
continue;
}
if rp.starts_with('-') {
if rp[1..].parse::<i32>().is_err() {
return 0;
}
} else if rp.parse::<i32>().is_err() {
return 0;
}
}
}
1
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_format_output_path(
_ctx: Handle,
path: *mut c_char,
size: usize,
fmt: *const c_char,
page: i32,
) {
if path.is_null() || fmt.is_null() || size == 0 {
return;
}
let fmt_str = unsafe {
match CStr::from_ptr(fmt).to_str() {
Ok(s) => s,
Err(_) => return,
}
};
let result = if let Some(pos) = fmt_str.find('%') {
let remaining = &fmt_str[pos..];
if remaining.starts_with("%d") {
format!("{}{}{}", &fmt_str[..pos], page, &fmt_str[pos + 2..])
} else {
let mut width = 0;
let mut idx = 1;
if remaining.chars().nth(1) == Some('0') {
idx = 2;
while let Some(c) = remaining.chars().nth(idx) {
if c.is_ascii_digit() {
width = width * 10 + (c as i32 - '0' as i32);
idx += 1;
} else {
break;
}
}
}
if remaining.chars().nth(idx) == Some('d') {
format!(
"{}{:0width$}{}",
&fmt_str[..pos],
page,
&fmt_str[pos + idx + 1..],
width = width as usize
)
} else {
insert_page_number(fmt_str, page)
}
}
} else {
insert_page_number(fmt_str, page)
};
let result_bytes = result.as_bytes();
let copy_len = std::cmp::min(result_bytes.len(), size - 1);
unsafe {
ptr::copy_nonoverlapping(result_bytes.as_ptr(), path as *mut u8, copy_len);
*path.add(copy_len) = 0;
}
}
fn clean_path(path: &str) -> String {
let mut parts: Vec<&str> = Vec::new();
let is_absolute = path.starts_with('/');
for part in path.split('/') {
match part {
"" | "." => continue,
".." => {
if !parts.is_empty() && parts.last() != Some(&"..") {
parts.pop();
} else if !is_absolute {
parts.push("..");
}
}
_ => parts.push(part),
}
}
let result = parts.join("/");
if is_absolute {
format!("/{}", result)
} else if result.is_empty() {
".".to_string()
} else {
result
}
}
fn version_compare(s1: &str, s2: &str) -> i32 {
let mut it1 = s1.chars().peekable();
let mut it2 = s2.chars().peekable();
loop {
let c1 = it1.peek().copied();
let c2 = it2.peek().copied();
match (c1, c2) {
(None, None) => return 0,
(None, Some(_)) => return -1,
(Some(_), None) => return 1,
(Some(a), Some(b)) => {
if a.is_ascii_digit() && b.is_ascii_digit() {
let mut n1: u64 = 0;
let mut n2: u64 = 0;
while let Some(c) = it1.peek() {
if c.is_ascii_digit() {
n1 = n1 * 10 + (*c as u64 - '0' as u64);
it1.next();
} else {
break;
}
}
while let Some(c) = it2.peek() {
if c.is_ascii_digit() {
n2 = n2 * 10 + (*c as u64 - '0' as u64);
it2.next();
} else {
break;
}
}
if n1 != n2 {
return if n1 < n2 { -1 } else { 1 };
}
} else {
if a != b {
return if a < b { -1 } else { 1 };
}
it1.next();
it2.next();
}
}
}
}
}
fn url_decode(s: &str) -> String {
let mut result = String::new();
let mut chars = s.chars();
while let Some(c) = chars.next() {
if c == '%' {
let hex: String = chars.by_ref().take(2).collect();
if hex.len() == 2 {
if let Ok(byte) = u8::from_str_radix(&hex, 16) {
result.push(byte as char);
continue;
}
}
result.push('%');
result.push_str(&hex);
} else if c == '+' {
result.push(' ');
} else {
result.push(c);
}
}
result
}
fn url_decode_preserve_reserved(s: &str) -> String {
const RESERVED: &[char] = &[';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#'];
let mut result = String::new();
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == '%' {
let hex: String = chars.by_ref().take(2).collect();
if hex.len() == 2 {
if let Ok(byte) = u8::from_str_radix(&hex, 16) {
let decoded = byte as char;
if RESERVED.contains(&decoded) {
result.push('%');
result.push_str(&hex);
} else {
result.push(decoded);
}
continue;
}
}
result.push('%');
result.push_str(&hex);
} else {
result.push(c);
}
}
result
}
fn url_encode(s: &str, encode_reserved: bool) -> String {
const RESERVED: &[u8] = b";/?:@&=+$,#";
let mut result = String::new();
for byte in s.bytes() {
if byte.is_ascii_alphanumeric()
|| byte == b'-'
|| byte == b'_'
|| byte == b'.'
|| byte == b'~'
{
result.push(byte as char);
} else if !encode_reserved && RESERVED.contains(&byte) {
result.push(byte as char);
} else {
result.push_str(&format!("%{:02X}", byte));
}
}
result
}
fn url_encode_pathname(s: &str) -> String {
let mut result = String::new();
for byte in s.bytes() {
if byte.is_ascii_alphanumeric()
|| byte == b'-'
|| byte == b'_'
|| byte == b'.'
|| byte == b'~'
|| byte == b'/'
{
result.push(byte as char);
} else {
result.push_str(&format!("%{:02X}", byte));
}
}
result
}
fn insert_page_number(path: &str, page: i32) -> String {
if let Some(dot_pos) = path.rfind('.') {
format!("{}{}{}", &path[..dot_pos], page, &path[dot_pos..])
} else {
format!("{}{}", path, page)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
#[test]
fn test_strnlen() {
let s = CString::new("hello").unwrap();
assert_eq!(fz_strnlen(s.as_ptr(), 10), 5);
assert_eq!(fz_strnlen(s.as_ptr(), 3), 3);
assert_eq!(fz_strnlen(ptr::null(), 10), 0);
}
#[test]
fn test_strlcpy() {
let src = CString::new("hello world").unwrap();
let mut dst = [0i8; 10];
let len = fz_strlcpy(dst.as_mut_ptr(), src.as_ptr(), 10);
assert_eq!(len, 11); let result = unsafe { CStr::from_ptr(dst.as_ptr()) };
assert_eq!(result.to_str().unwrap(), "hello wor");
}
#[test]
fn test_strlcat() {
let mut buf = [0i8; 20];
let hello = CString::new("hello").unwrap();
let world = CString::new(" world").unwrap();
fz_strlcpy(buf.as_mut_ptr(), hello.as_ptr(), 20);
let len = fz_strlcat(buf.as_mut_ptr(), world.as_ptr(), 20);
assert_eq!(len, 11);
let result = unsafe { CStr::from_ptr(buf.as_ptr()) };
assert_eq!(result.to_str().unwrap(), "hello world");
}
#[test]
fn test_strstr() {
let haystack = CString::new("hello world").unwrap();
let needle = CString::new("world").unwrap();
let result = fz_strstr(haystack.as_ptr(), needle.as_ptr());
assert!(!result.is_null());
let found = unsafe { CStr::from_ptr(result) };
assert_eq!(found.to_str().unwrap(), "world");
}
#[test]
fn test_strstrcase() {
let haystack = CString::new("Hello World").unwrap();
let needle = CString::new("WORLD").unwrap();
let result = fz_strstrcase(haystack.as_ptr(), needle.as_ptr());
assert!(!result.is_null());
}
#[test]
fn test_dirname() {
let path = CString::new("/home/user/file.txt").unwrap();
let mut dir = [0i8; 100];
fz_dirname(dir.as_mut_ptr(), path.as_ptr(), 100);
let result = unsafe { CStr::from_ptr(dir.as_ptr()) };
assert_eq!(result.to_str().unwrap(), "/home/user");
}
#[test]
fn test_basename() {
let path = CString::new("/home/user/file.txt").unwrap();
let result = fz_basename(path.as_ptr());
assert!(!result.is_null());
let name = unsafe { CStr::from_ptr(result) };
assert_eq!(name.to_str().unwrap(), "file.txt");
}
#[test]
fn test_cleanname() {
let mut path = CString::new("/home/../home/./user//file.txt")
.unwrap()
.into_raw();
fz_cleanname(path);
let result = unsafe { CStr::from_ptr(path) };
assert_eq!(result.to_str().unwrap(), "/home/user/file.txt");
unsafe {
let _ = CString::from_raw(path);
}
}
#[test]
fn test_strverscmp() {
let v1 = CString::new("file1.txt").unwrap();
let v2 = CString::new("file2.txt").unwrap();
let v10 = CString::new("file10.txt").unwrap();
assert!(fz_strverscmp(v1.as_ptr(), v2.as_ptr()) < 0);
assert!(fz_strverscmp(v2.as_ptr(), v10.as_ptr()) < 0);
assert!(fz_strverscmp(v10.as_ptr(), v1.as_ptr()) > 0);
}
#[test]
fn test_url_encode_decode() {
let original = "hello world";
let encoded = url_encode(original, true);
assert_eq!(encoded, "hello%20world");
let decoded = url_decode(&encoded);
assert_eq!(decoded, original);
}
#[test]
fn test_utf8_functions() {
let s = CString::new("héllo").unwrap();
assert_eq!(fz_utflen(s.as_ptr()), 5);
let mut rune: i32 = 0;
let bytes = fz_chartorune(&mut rune, unsafe { s.as_ptr().offset(1) });
assert_eq!(bytes, 2); assert_eq!(rune, 'é' as i32);
}
#[test]
fn test_runetochar() {
let mut buf = [0i8; 5];
let len = fz_runetochar(buf.as_mut_ptr(), '€' as i32);
assert_eq!(len, 3); }
#[test]
fn test_tolower_toupper() {
assert_eq!(fz_tolower('A' as i32), 'a' as i32);
assert_eq!(fz_toupper('a' as i32), 'A' as i32);
assert_eq!(fz_tolower('É' as i32), 'é' as i32);
}
#[test]
fn test_atof() {
let s = CString::new("3.5").unwrap();
let v = fz_atof(s.as_ptr());
assert!((v - 3.5).abs() < 0.001);
let nan = CString::new("NaN").unwrap();
assert!(fz_atof(nan.as_ptr()).is_nan());
let inf = CString::new("Inf").unwrap();
assert!(fz_atof(inf.as_ptr()).is_infinite());
}
#[test]
fn test_atoi() {
let s = CString::new("42").unwrap();
assert_eq!(fz_atoi(s.as_ptr()), 42);
let neg = CString::new("-123").unwrap();
assert_eq!(fz_atoi(neg.as_ptr()), -123);
}
#[test]
fn test_strcasecmp() {
let a = CString::new("Hello").unwrap();
let b = CString::new("HELLO").unwrap();
assert_eq!(fz_strcasecmp(a.as_ptr(), b.as_ptr()), 0);
}
#[test]
fn test_memmem() {
let haystack = b"hello world";
let needle = b"world";
let result = fz_memmem(
haystack.as_ptr() as *const c_void,
haystack.len(),
needle.as_ptr() as *const c_void,
needle.len(),
);
assert!(!result.is_null());
}
#[test]
fn test_is_page_range() {
let ctx = 1;
let valid = CString::new("1-5,7,10-N").unwrap();
assert_eq!(fz_is_page_range(ctx, valid.as_ptr()), 1);
let invalid = CString::new("abc").unwrap();
assert_eq!(fz_is_page_range(ctx, invalid.as_ptr()), 0);
}
#[test]
fn test_format_output_path() {
let ctx = 1;
let mut path = [0i8; 100];
let fmt = CString::new("output%03d.png").unwrap();
fz_format_output_path(ctx, path.as_mut_ptr(), 100, fmt.as_ptr(), 5);
let result = unsafe { CStr::from_ptr(path.as_ptr()) };
assert_eq!(result.to_str().unwrap(), "output005.png");
}
#[test]
fn test_strdup() {
let ctx = 1;
let original = CString::new("test string").unwrap();
let dup = fz_strdup(ctx, original.as_ptr());
assert!(!dup.is_null());
let dup_str = unsafe { CStr::from_ptr(dup) };
assert_eq!(dup_str.to_str().unwrap(), "test string");
fz_free_string(ctx, dup);
}
#[test]
fn test_strlcpy_null() {
let s = CString::new("x").unwrap();
assert_eq!(fz_strlcpy(ptr::null_mut(), s.as_ptr(), 10), 0);
assert_eq!(fz_strlcpy([0i8; 1].as_ptr().cast_mut(), ptr::null(), 10), 0);
assert_eq!(fz_strlcpy([0i8; 1].as_ptr().cast_mut(), s.as_ptr(), 0), 0);
}
#[test]
fn test_strlcat_null() {
let mut buf = [0i8; 5];
let s = CString::new("x").unwrap();
assert_eq!(fz_strlcat(ptr::null_mut(), s.as_ptr(), 10), 0);
assert_eq!(fz_strlcat(buf.as_mut_ptr(), ptr::null(), 10), 0);
}
#[test]
fn test_strsep() {
assert!(fz_strsep(ptr::null_mut(), CString::new(",").unwrap().as_ptr()).is_null());
let s = CString::new("a,b,c").unwrap();
let mut ptr = s.into_raw();
let original = ptr;
let delim = CString::new(",").unwrap();
let first = fz_strsep(&mut ptr, delim.as_ptr());
assert!(!first.is_null());
unsafe {
assert_eq!(CStr::from_ptr(first).to_str().unwrap(), "a");
let _ = CString::from_raw(original);
}
}
#[test]
fn test_strstr_null() {
let s = CString::new("x").unwrap();
assert!(fz_strstr(ptr::null(), s.as_ptr()).is_null());
assert!(fz_strstr(s.as_ptr(), ptr::null()).is_null());
}
#[test]
fn test_strstr_not_found() {
let h = CString::new("hello").unwrap();
let n = CString::new("xyz").unwrap();
assert!(fz_strstr(h.as_ptr(), n.as_ptr()).is_null());
}
#[test]
fn test_memmem_null() {
assert!(fz_memmem(ptr::null(), 5, b"x".as_ptr().cast(), 1).is_null());
assert!(fz_memmem(b"xxx".as_ptr().cast(), 3, ptr::null(), 1).is_null());
assert!(fz_memmem(b"xx".as_ptr().cast(), 2, b"xxx".as_ptr().cast(), 3).is_null());
assert!(fz_memmem(b"xx".as_ptr().cast(), 2, b"x".as_ptr().cast(), 0).is_null());
}
#[test]
fn test_memmem_not_found() {
let h = b"hello";
let n = b"xyz";
assert!(fz_memmem(h.as_ptr().cast(), h.len(), n.as_ptr().cast(), n.len()).is_null());
}
#[test]
fn test_dirname_null() {
let mut buf = [0i8; 10];
let p = CString::new("/x/y").unwrap();
fz_dirname(ptr::null_mut(), p.as_ptr(), 10);
fz_dirname(buf.as_mut_ptr(), ptr::null(), 10);
}
#[test]
fn test_basename_null() {
assert!(fz_basename(ptr::null()).is_null());
}
#[test]
fn test_basename_no_sep() {
let p = CString::new("filename").unwrap();
let r = fz_basename(p.as_ptr());
assert!(!r.is_null());
unsafe { assert_eq!(CStr::from_ptr(r).to_str().unwrap(), "filename") };
}
#[test]
fn test_cleanname_null() {
assert!(fz_cleanname(ptr::null_mut()).is_null());
}
#[test]
fn test_strverscmp_null() {
let s = CString::new("1").unwrap();
assert_eq!(fz_strverscmp(ptr::null(), ptr::null()), 0);
assert!(fz_strverscmp(ptr::null(), s.as_ptr()) < 0);
assert!(fz_strverscmp(s.as_ptr(), ptr::null()) > 0);
}
#[test]
fn test_urldecode() {
let mut buf = CString::new("hello%20world").unwrap().into_raw();
let r = fz_urldecode(buf);
assert!(!r.is_null());
unsafe {
assert_eq!(CStr::from_ptr(r).to_str().unwrap(), "hello world");
let _ = CString::from_raw(buf);
}
}
#[test]
fn test_decode_encode_uri() {
let ctx = 0;
let s = CString::new("hello%20world").unwrap();
let decoded = fz_decode_uri(ctx, s.as_ptr());
assert!(!decoded.is_null());
unsafe {
assert_eq!(CStr::from_ptr(decoded).to_str().unwrap(), "hello world");
fz_free_string(ctx, decoded);
}
}
#[test]
fn test_decode_uri_component() {
let ctx = 0;
let s = CString::new("hello%2Bworld").unwrap();
let decoded = fz_decode_uri_component(ctx, s.as_ptr());
assert!(!decoded.is_null());
unsafe {
assert_eq!(CStr::from_ptr(decoded).to_str().unwrap(), "hello+world");
fz_free_string(ctx, decoded);
}
}
#[test]
fn test_encode_uri() {
let ctx = 0;
let s = CString::new("hello world").unwrap();
let encoded = fz_encode_uri(ctx, s.as_ptr());
assert!(!encoded.is_null());
unsafe {
assert!(CStr::from_ptr(encoded).to_str().unwrap().contains('%'));
fz_free_string(ctx, encoded);
}
}
#[test]
fn test_encode_uri_pathname() {
let ctx = 0;
let s = CString::new("path/with spaces").unwrap();
let encoded = fz_encode_uri_pathname(ctx, s.as_ptr());
assert!(!encoded.is_null());
unsafe {
let r = CStr::from_ptr(encoded).to_str().unwrap();
assert!(r.contains('/'));
assert!(r.contains('%'));
fz_free_string(ctx, encoded);
}
}
#[test]
fn test_uri_null() {
assert!(fz_decode_uri(0, ptr::null()).is_null());
assert!(fz_decode_uri_component(0, ptr::null()).is_null());
assert!(fz_encode_uri(0, ptr::null()).is_null());
assert!(fz_encode_uri_component(0, ptr::null()).is_null());
assert!(fz_encode_uri_pathname(0, ptr::null()).is_null());
}
#[test]
fn test_strcasecmp_null() {
let s = CString::new("x").unwrap();
assert_eq!(fz_strcasecmp(ptr::null(), ptr::null()), 0);
assert!(fz_strcasecmp(ptr::null(), s.as_ptr()) < 0);
assert!(fz_strcasecmp(s.as_ptr(), ptr::null()) > 0);
}
#[test]
fn test_strncasecmp() {
let a = CString::new("Hello").unwrap();
let b = CString::new("HELLO").unwrap();
assert_eq!(fz_strncasecmp(a.as_ptr(), b.as_ptr(), 5), 0);
}
#[test]
fn test_tolower_toupper_invalid() {
assert_eq!(fz_tolower(-1), -1);
assert_eq!(fz_toupper(0x110000), 0x110000);
}
#[test]
fn test_chartorune_null() {
let mut r: i32 = 0;
let s = CString::new("x").unwrap();
assert_eq!(fz_chartorune(ptr::null_mut(), s.as_ptr()), 0);
assert_eq!(fz_chartorune(&mut r, ptr::null()), 0);
}
#[test]
fn test_chartorune_invalid() {
let mut r: i32 = 0;
let s = CString::new(vec![0xff]).unwrap();
let n = fz_chartorune(&mut r, s.as_ptr());
assert_eq!(n, 1);
assert_eq!(r, FZ_REPLACEMENT_CHARACTER);
}
#[test]
fn test_chartorunen() {
let mut r: i32 = 0;
let s = CString::new(vec![0xc3, 0xa9]).unwrap();
assert_eq!(fz_chartorunen(&mut r, s.as_ptr(), 1), 1);
assert_eq!(r, FZ_REPLACEMENT_CHARACTER);
}
#[test]
fn test_runetochar_invalid() {
let mut buf = [0i8; 5];
let n = fz_runetochar(buf.as_mut_ptr(), 0x110000);
assert_eq!(n, 3);
}
#[test]
fn test_runelen() {
assert_eq!(fz_runelen('a' as i32), 1);
assert_eq!(fz_runelen(0x80), 2);
assert_eq!(fz_runelen(0x800), 3);
assert_eq!(fz_runelen(0x10000), 4);
assert_eq!(fz_runelen(0x110000), 3);
}
#[test]
fn test_runeidx_runeptr() {
let s = CString::new("héllo").unwrap();
assert_eq!(unsafe { fz_runeidx(s.as_ptr(), s.as_ptr().add(3)) }, 2);
let p = fz_runeptr(s.as_ptr(), 0);
assert!(!p.is_null());
assert_eq!(fz_runeptr(s.as_ptr(), -1), ptr::null());
}
#[test]
fn test_utflen_null() {
assert_eq!(fz_utflen(ptr::null()), 0);
}
#[test]
fn test_atof_neg_inf() {
let s = CString::new("-inf").unwrap();
assert!(fz_atof(s.as_ptr()).is_infinite());
assert!(fz_atof(s.as_ptr()) < 0.0);
}
#[test]
fn test_atof_null() {
assert_eq!(fz_atof(ptr::null()), 0.0);
}
#[test]
fn test_atoi_atoi64_null() {
assert_eq!(fz_atoi(ptr::null()), 0);
assert_eq!(fz_atoi64(ptr::null()), 0);
}
#[test]
fn test_strtof() {
let s = CString::new("3.14 rest").unwrap();
let mut end = ptr::null_mut();
let v = fz_strtof(s.as_ptr(), &mut end);
assert!((v - 3.14).abs() < 0.001);
assert!(!end.is_null());
}
#[test]
fn test_strtof_null() {
assert_eq!(fz_strtof(ptr::null(), ptr::null_mut()), 0.0);
}
#[test]
fn test_strdup_null() {
assert!(fz_strdup(0, ptr::null()).is_null());
}
#[test]
fn test_free_string_null() {
fz_free_string(0, ptr::null_mut());
}
#[test]
fn test_is_page_range_null() {
assert_eq!(fz_is_page_range(0, ptr::null()), 0);
}
#[test]
fn test_format_output_path_null() {
let mut buf = [0i8; 10];
let fmt = CString::new("%d").unwrap();
fz_format_output_path(0, ptr::null_mut(), 10, fmt.as_ptr(), 1);
fz_format_output_path(0, buf.as_mut_ptr(), 0, fmt.as_ptr(), 1);
fz_format_output_path(0, buf.as_mut_ptr(), 10, ptr::null(), 1);
}
#[test]
fn test_format_output_path_simple() {
let mut buf = [0i8; 100];
let fmt = CString::new("out%d").unwrap();
fz_format_output_path(0, buf.as_mut_ptr(), 100, fmt.as_ptr(), 42);
unsafe {
assert_eq!(CStr::from_ptr(buf.as_ptr()).to_str().unwrap(), "out42");
}
}
#[test]
fn test_cleanname_dotdot() {
let mut path = CString::new("a/../b").unwrap().into_raw();
fz_cleanname(path);
unsafe {
assert_eq!(CStr::from_ptr(path).to_str().unwrap(), "b");
let _ = CString::from_raw(path);
}
}
}