use super::{Handle, HandleStore};
use crate::pdf::form::{ChoiceOption, FieldFlags, Form, FormField, TextFormat, WidgetType};
use std::ffi::{CStr, c_char};
use std::sync::LazyLock;
pub static FORMS: LazyLock<HandleStore<Form>> = LazyLock::new(HandleStore::default);
pub static FORM_FIELDS: LazyLock<HandleStore<FormField>> = LazyLock::new(HandleStore::default);
#[unsafe(no_mangle)]
pub extern "C" fn pdf_form(_ctx: Handle, doc: Handle) -> Handle {
let mut form = Form::new();
if let Some(doc_arc) = crate::ffi::DOCUMENTS.get(doc) {
if let Ok(doc_guard) = doc_arc.lock() {
let data = doc_guard.data();
parse_acroform_fields(data, &mut form);
}
}
FORMS.insert(form)
}
#[cfg(test)]
pub(crate) fn test_parse_form_from_bytes(data: &[u8]) -> Form {
let mut form = Form::new();
parse_acroform_fields(data, &mut form);
form
}
fn parse_acroform_fields(data: &[u8], form: &mut Form) {
let text = String::from_utf8_lossy(data);
let acroform_start = match text.find("/AcroForm") {
Some(pos) => pos,
None => return,
};
let search_region = &text[acroform_start..];
let dict_start = match search_region.find("<<") {
Some(pos) => acroform_start + pos,
None => return,
};
let dict_text = match extract_balanced_dict(&text, dict_start) {
Some(t) => t,
None => return,
};
if let Some(fields_pos) = dict_text.find("/Fields") {
let after_fields = &dict_text[fields_pos + 7..];
let obj_refs = extract_pdf_array_refs(&dict_text, "/Fields");
for obj_num in obj_refs {
parse_and_add_field_with_kids(text.as_ref(), obj_num, form);
}
}
}
fn parse_and_add_field_with_kids(text: &str, obj_num: i32, form: &mut Form) {
if let Some(field) = parse_field_object(text, obj_num) {
form.add_field(field);
}
let dict_text = get_object_dict(text, obj_num);
let kid_refs = extract_pdf_array_refs(&dict_text, "/Kids");
for kid_num in kid_refs {
parse_and_add_field_with_kids(text, kid_num, form);
}
}
fn get_object_dict(text: &str, obj_num: i32) -> String {
let obj_header = format!("{} 0 obj", obj_num);
let obj_start = match text.find(&obj_header) {
Some(p) => p,
None => return String::new(),
};
let after_obj = &text[obj_start + obj_header.len()..];
let dict_offset = match after_obj.find("<<") {
Some(p) => p,
None => return String::new(),
};
extract_balanced_dict(&text[obj_start + obj_header.len()..], dict_offset).unwrap_or_default()
}
fn extract_balanced_dict(text: &str, start: usize) -> Option<String> {
let bytes = text.as_bytes();
if start + 1 >= bytes.len() || bytes[start] != b'<' || bytes[start + 1] != b'<' {
return None;
}
let mut depth = 0i32;
let mut i = start;
while i < bytes.len() - 1 {
if bytes[i] == b'<' && bytes[i + 1] == b'<' {
depth += 1;
i += 2;
} else if bytes[i] == b'>' && bytes[i + 1] == b'>' {
depth -= 1;
i += 2;
if depth == 0 {
return Some(text[start..i].to_string());
}
} else {
i += 1;
}
}
Some(text[start..].to_string())
}
fn parse_default_appearance(da: &str) -> (Option<String>, f32, [f32; 3]) {
let mut font_name = None;
let mut font_size = 12.0;
let parts: Vec<&str> = da.split_whitespace().collect();
for (i, &token) in parts.iter().enumerate() {
if token == "Tf" && i >= 2 {
if let Ok(size) = parts[i - 1].parse::<f32>() {
font_size = size;
}
if parts[i - 2].starts_with('/') {
font_name = Some(parts[i - 2][1..].to_string());
}
break;
}
}
let mut color = [0.0, 0.0, 0.0];
let mut i = 0;
while i < parts.len() {
if parts[i] == "g" && i >= 1 {
if let Ok(g) = parts[i - 1].parse::<f32>() {
color = [g, g, g];
}
break;
} else if parts[i] == "rg" && i >= 3 {
if let (Ok(r), Ok(g), Ok(b)) = (
parts[i - 3].parse::<f32>(),
parts[i - 2].parse::<f32>(),
parts[i - 1].parse::<f32>(),
) {
color = [r, g, b];
}
break;
}
i += 1;
}
(font_name, font_size, color)
}
fn parse_field_object(text: &str, obj_num: i32) -> Option<FormField> {
let obj_header = format!("{} 0 obj", obj_num);
let obj_start = text.find(&obj_header)?;
let after_obj = &text[obj_start + obj_header.len()..];
let dict_offset = after_obj.find("<<")?;
let dict_text = extract_balanced_dict(&text[obj_start + obj_header.len()..], dict_offset)?;
let name = extract_pdf_string(&dict_text, "/T")?;
let flags_val = extract_pdf_integer(&dict_text, "/Ff").unwrap_or(0) as u32;
let flags = FieldFlags::new(flags_val);
let ft = extract_pdf_name(&dict_text, "/FT").unwrap_or_default();
let widget_type = match ft.as_str() {
"Tx" => WidgetType::Text,
"Btn" => {
if flags.has(FieldFlags::RADIO) {
WidgetType::RadioButton
} else if flags.has(FieldFlags::PUSHBUTTON) {
WidgetType::Button
} else {
WidgetType::Checkbox
}
}
"Ch" => {
if flags.has(FieldFlags::COMBO) {
WidgetType::ComboBox
} else {
WidgetType::ListBox
}
}
"Sig" => WidgetType::Signature,
_ => WidgetType::Unknown,
};
let rect = extract_pdf_rect(&dict_text, "/Rect").unwrap_or(crate::fitz::geometry::Rect {
x0: 0.0,
y0: 0.0,
x1: 0.0,
y1: 0.0,
});
let mut field = FormField::new(name, widget_type, rect);
field.set_flags(flags);
let has_v = if let Some(value) = extract_pdf_string(&dict_text, "/V") {
let _ = field.set_value(value);
true
} else {
false
};
if !has_v && matches!(widget_type, WidgetType::Checkbox | WidgetType::RadioButton) {
if let Some(as_state) = extract_pdf_name(&dict_text, "/AS") {
if as_state != "Off" && !as_state.is_empty() {
let _ = field.set_value("Yes".to_string());
}
}
}
if let Some(default_value) = extract_pdf_string(&dict_text, "/DV") {
field.default_value = default_value;
}
if let Some(max_len) = extract_pdf_integer(&dict_text, "/MaxLen") {
if max_len > 0 {
field.set_max_len(Some(max_len as usize));
}
}
if let Some(tooltip) = extract_pdf_string(&dict_text, "/TU") {
field.set_tooltip(Some(tooltip));
}
if let Some(da) = extract_pdf_string(&dict_text, "/DA") {
let (font_name, font_size, color) = parse_default_appearance(&da);
field.font_name = font_name;
field.font_size = font_size;
field.border_color = color;
}
if matches!(widget_type, WidgetType::ComboBox | WidgetType::ListBox) {
let options = extract_pdf_opt_array(&dict_text);
if !options.is_empty() {
for opt in &options {
field.choices.push((opt.label.clone(), opt.value.clone()));
}
field.set_options(options);
field.is_combo = widget_type == WidgetType::ComboBox;
field.editable = flags.has(FieldFlags::EDIT);
field.multi_select = flags.has(FieldFlags::MULTI_SELECT);
}
}
Some(field)
}
fn extract_pdf_name(dict: &str, key: &str) -> Option<String> {
let pos = dict.find(key)?;
let after = dict[pos + key.len()..].trim_start();
if after.starts_with('/') {
let name_end = after[1..]
.find(|c: char| {
c.is_whitespace() || c == '/' || c == '>' || c == '<' || c == '[' || c == '('
})
.map(|i| i + 1)
.unwrap_or(after.len());
Some(after[1..name_end].to_string())
} else {
None
}
}
fn decode_hex_string(hex: &str) -> String {
let hex = hex.trim();
if hex.len() < 2 || !hex.len().is_multiple_of(2) {
return String::new();
}
let mut result = String::with_capacity(hex.len() / 2);
let mut chars = hex.chars();
while let (Some(h), Some(l)) = (chars.next(), chars.next()) {
if let (Some(high), Some(low)) = (h.to_digit(16), l.to_digit(16)) {
result.push(char::from_u32((high << 4) | low).unwrap_or('\u{fffd}'));
}
}
result
}
fn extract_pdf_string(dict: &str, key: &str) -> Option<String> {
let pos = dict.find(key)?;
let after = dict[pos + key.len()..].trim_start();
if after.starts_with('(') {
let mut depth = 0i32;
let mut end = 0;
for (i, ch) in after.chars().enumerate() {
match ch {
'(' => depth += 1,
')' => {
depth -= 1;
if depth == 0 {
end = i;
break;
}
}
_ => {}
}
}
if end > 1 {
Some(after[1..end].to_string())
} else {
None
}
} else if after.starts_with('<') {
let hex_end = after[1..].find('>').map(|i| i + 1).unwrap_or(after.len());
let hex_content = &after[1..hex_end];
let decoded = decode_hex_string(hex_content);
if decoded.is_empty() {
None
} else {
Some(decoded)
}
} else if after.starts_with('/') {
extract_pdf_name(dict, key)
} else {
None
}
}
fn extract_pdf_integer(dict: &str, key: &str) -> Option<i64> {
let pos = dict.find(key)?;
let after = dict[pos + key.len()..].trim_start();
let end = after
.find(|c: char| !c.is_ascii_digit() && c != '-')
.unwrap_or(after.len());
if end > 0 {
after[..end].parse::<i64>().ok()
} else {
None
}
}
fn extract_pdf_array_refs(dict: &str, key: &str) -> Vec<i32> {
let pos = match dict.find(key) {
Some(p) => p,
None => return vec![],
};
let after = dict[pos + key.len()..].trim_start();
let arr_start = match after.find('[') {
Some(p) => p,
None => return vec![],
};
let arr_end = match after[arr_start..].find(']') {
Some(p) => p,
None => return vec![],
};
let arr_text = &after[arr_start + 1..arr_start + arr_end];
let parts: Vec<&str> = arr_text.split_whitespace().collect();
let mut refs = Vec::new();
let mut i = 0;
while i + 2 < parts.len() {
if let (Ok(obj), Ok(_gen)) = (parts[i].parse::<i32>(), parts[i + 1].parse::<i32>()) {
if parts[i + 2] == "R" && obj > 0 {
refs.push(obj);
}
}
i += 3;
}
refs
}
fn extract_pdf_opt_array(dict: &str) -> Vec<ChoiceOption> {
let pos = match dict.find("/Opt") {
Some(p) => p,
None => return vec![],
};
let after = dict[pos + 4..].trim_start();
if !after.starts_with('[') {
return vec![];
}
let arr_content = match extract_balanced_brackets(after) {
Some(s) => s,
None => return vec![],
};
let mut options = Vec::new();
let mut i = 0;
while i < arr_content.len() {
let rest = arr_content[i..].trim_start();
let skip = arr_content[i..].len() - rest.len();
i += skip;
if rest.is_empty() {
break;
}
let consumed = if rest.starts_with('[') {
if let Some(inner) = extract_balanced_brackets(rest) {
let parts = parse_strings_from_array_like(&inner);
if parts.len() >= 2 {
options.push(ChoiceOption::new(parts[1].clone(), parts[0].clone()));
} else if let Some(first) = parts.first() {
options.push(ChoiceOption::simple(first.clone()));
}
inner.len() + 2
} else {
break;
}
} else if rest.starts_with('(') || rest.starts_with('<') {
if let Some((val, n)) = extract_next_pdf_string_with_len(rest) {
options.push(ChoiceOption::simple(val));
n
} else {
break;
}
} else {
break;
};
i += consumed;
}
options
}
fn extract_balanced_brackets(s: &str) -> Option<String> {
if !s.starts_with('[') {
return None;
}
let mut depth = 0i32;
for (i, ch) in s.char_indices() {
match ch {
'[' => depth += 1,
']' => {
depth -= 1;
if depth == 0 {
return Some(s[1..i].to_string());
}
}
_ => {}
}
}
None
}
fn parse_strings_from_array_like(s: &str) -> Vec<String> {
let mut result = Vec::new();
let mut i = 0;
while i < s.len() {
let rest = s[i..].trim_start();
i += s.len() - rest.len();
if rest.is_empty() {
break;
}
if let Some((val, consumed)) = extract_next_pdf_string_with_len(rest) {
result.push(val);
i += consumed;
} else {
break;
}
}
result
}
fn extract_next_pdf_string_with_len(s: &str) -> Option<(String, usize)> {
let s = s.trim_start();
if s.starts_with('(') {
let mut depth = 0i32;
for (i, ch) in s.char_indices() {
match ch {
'(' => depth += 1,
')' => {
depth -= 1;
if depth == 0 && i > 0 {
return Some((s[1..i].to_string(), i + 1));
}
}
_ => {}
}
}
None
} else if s.starts_with('<') {
if let Some(end) = s[1..].find('>') {
let hex = &s[1..=end];
let decoded = decode_hex_string(hex);
if !decoded.is_empty() {
return Some((decoded, end + 2));
}
}
None
} else {
None
}
}
fn extract_pdf_rect(dict: &str, key: &str) -> Option<crate::fitz::geometry::Rect> {
let pos = dict.find(key)?;
let after = dict[pos + key.len()..].trim_start();
let arr_start = after.find('[')?;
let arr_end = after[arr_start..].find(']')?;
let arr_text = &after[arr_start + 1..arr_start + arr_end];
let values: Vec<f32> = arr_text
.split_whitespace()
.filter_map(|s| s.parse::<f32>().ok())
.collect();
if values.len() == 4 {
Some(crate::fitz::geometry::Rect {
x0: values[0],
y0: values[1],
x1: values[2],
y1: values[3],
})
} else {
None
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_keep_form(_ctx: Handle, form: Handle) -> Handle {
if FORMS.get(form).is_some() {
return form;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_drop_form(_ctx: Handle, form: Handle) {
FORMS.remove(form);
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_first_widget(_ctx: Handle, page: Handle) -> Handle {
if let Some(p) = super::document::PAGES.get(page) {
if let Ok(guard) = p.lock() {
return guard.first_widget().unwrap_or(0);
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_next_widget(_ctx: Handle, widget: Handle) -> Handle {
if FORM_FIELDS.get(widget).is_some() {
for page_handle in 1..10000 {
if let Some(p) = super::document::PAGES.get(page_handle) {
if let Ok(guard) = p.lock() {
if guard.widgets.contains(&widget) {
return guard.next_widget(widget).unwrap_or(0);
}
}
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_create_text_field(
_ctx: Handle,
_form: Handle,
name: *const std::ffi::c_char,
x: f32,
y: f32,
width: f32,
height: f32,
max_len: i32,
) -> Handle {
if name.is_null() {
return 0;
}
if let Some(field_name) = super::safe_helpers::c_str_to_str(name) {
let rect = crate::fitz::geometry::Rect {
x0: x,
y0: y,
x1: x + width,
y1: y + height,
};
let max_length = if max_len > 0 {
Some(max_len as usize)
} else {
None
};
let field = FormField::text_field(field_name.to_string(), rect, max_length);
return FORM_FIELDS.insert(field);
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_create_checkbox(
_ctx: Handle,
_form: Handle,
name: *const std::ffi::c_char,
x: f32,
y: f32,
width: f32,
height: f32,
checked: i32,
) -> Handle {
if name.is_null() {
return 0;
}
if let Some(field_name) = super::safe_helpers::c_str_to_str(name) {
let rect = crate::fitz::geometry::Rect {
x0: x,
y0: y,
x1: x + width,
y1: y + height,
};
let field = FormField::checkbox(field_name.to_string(), rect, checked != 0);
return FORM_FIELDS.insert(field);
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_create_push_button(
_ctx: Handle,
_form: Handle,
name: *const std::ffi::c_char,
x: f32,
y: f32,
width: f32,
height: f32,
caption: *const std::ffi::c_char,
) -> Handle {
if name.is_null() {
return 0;
}
if let Some(field_name) = super::safe_helpers::c_str_to_str(name) {
let rect = crate::fitz::geometry::Rect {
x0: x,
y0: y,
x1: x + width,
y1: y + height,
};
let caption_str = if caption.is_null() {
""
} else {
super::safe_helpers::c_str_to_str(caption).unwrap_or("")
};
let field = FormField::push_button(field_name.to_string(), rect, caption_str);
return FORM_FIELDS.insert(field);
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_create_combo_box(
_ctx: Handle,
_form: Handle,
name: *const std::ffi::c_char,
x: f32,
y: f32,
width: f32,
height: f32,
) -> Handle {
if name.is_null() {
return 0;
}
if let Some(field_name) = super::safe_helpers::c_str_to_str(name) {
let rect = crate::fitz::geometry::Rect {
x0: x,
y0: y,
x1: x + width,
y1: y + height,
};
let field = FormField::combo_box(field_name.to_string(), rect, Vec::new());
return FORM_FIELDS.insert(field);
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_create_signature_field(
_ctx: Handle,
_form: Handle,
name: *const std::ffi::c_char,
x: f32,
y: f32,
width: f32,
height: f32,
) -> Handle {
if name.is_null() {
return 0;
}
if let Some(field_name) = super::safe_helpers::c_str_to_str(name) {
let rect = crate::fitz::geometry::Rect {
x0: x,
y0: y,
x1: x + width,
y1: y + height,
};
let field = FormField::signature(field_name.to_string(), rect);
return FORM_FIELDS.insert(field);
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_name(
_ctx: Handle,
field: Handle,
buf: *mut std::ffi::c_char,
size: i32,
) -> i32 {
if buf.is_null() || size <= 0 {
return 0;
}
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return super::safe_helpers::str_to_c_buffer(guard.name(), buf, size);
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_type(_ctx: Handle, field: Handle) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return match guard.field_type() {
WidgetType::Unknown => -1,
WidgetType::Button => 0,
WidgetType::Checkbox => 1,
WidgetType::ComboBox => 2,
WidgetType::ListBox => 3,
WidgetType::RadioButton => 4,
WidgetType::Signature => 5,
WidgetType::Text => 6,
};
}
}
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_value(
_ctx: Handle,
field: Handle,
buf: *mut std::ffi::c_char,
size: i32,
) -> i32 {
if buf.is_null() || size <= 0 {
return 0;
}
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return super::safe_helpers::str_to_c_buffer(guard.value(), buf, size);
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_value(
_ctx: Handle,
field: Handle,
value: *const std::ffi::c_char,
) -> i32 {
if value.is_null() {
return 0;
}
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(mut guard) = f.lock() {
if let Some(val_str) = super::safe_helpers::c_str_to_str(value) {
if guard.set_value(val_str.to_string()).is_ok() {
return 1;
}
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_rect(_ctx: Handle, field: Handle) -> super::geometry::fz_rect {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
let rect = guard.rect();
return super::geometry::fz_rect {
x0: rect.x0,
y0: rect.y0,
x1: rect.x1,
y1: 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 pdf_field_flags(_ctx: Handle, field: Handle) -> u32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.flags().value();
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_flags(_ctx: Handle, field: Handle, flags: u32) {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(mut guard) = f.lock() {
guard.set_flags(FieldFlags::new(flags));
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_read_only(_ctx: Handle, field: Handle) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.is_read_only() as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_required(_ctx: Handle, field: Handle) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.is_required() as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_checked(_ctx: Handle, field: Handle) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.is_checked() as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_checked(_ctx: Handle, field: Handle, checked: i32) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(mut guard) = f.lock() {
if guard.set_checked(checked != 0).is_ok() {
return 1;
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_multiline(_ctx: Handle, field: Handle) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.is_multiline() as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_password(_ctx: Handle, field: Handle) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.is_password() as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_signed(_ctx: Handle, field: Handle) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.is_signed() as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_max_len(_ctx: Handle, field: Handle) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.max_len().map(|l| l as i32).unwrap_or(0);
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_max_len(_ctx: Handle, field: Handle, max_len: i32) {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(mut guard) = f.lock() {
let max = if max_len > 0 {
Some(max_len as usize)
} else {
None
};
guard.set_max_len(max);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_text_format(_ctx: Handle, field: Handle) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return match guard.text_format() {
TextFormat::None => 0,
TextFormat::Number => 1,
TextFormat::Special => 2,
TextFormat::Date => 3,
TextFormat::Time => 4,
};
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_choice_count(_ctx: Handle, field: Handle) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.options().len() as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_choice_label(
_ctx: Handle,
field: Handle,
index: i32,
buf: *mut std::ffi::c_char,
size: i32,
) -> i32 {
if buf.is_null() || size <= 0 || index < 0 {
return 0;
}
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
let options = guard.options();
if let Some(option) = options.get(index as usize) {
return super::safe_helpers::str_to_c_buffer(&option.label, buf, size);
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_choice_value(
_ctx: Handle,
field: Handle,
index: i32,
buf: *mut std::ffi::c_char,
size: i32,
) -> i32 {
if buf.is_null() || size <= 0 || index < 0 {
return 0;
}
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
let options = guard.options();
if let Some(option) = options.get(index as usize) {
return super::safe_helpers::str_to_c_buffer(&option.value, buf, size);
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_add_field_choice(
_ctx: Handle,
field: Handle,
label: *const std::ffi::c_char,
value: *const std::ffi::c_char,
) -> i32 {
if label.is_null() || value.is_null() {
return 0;
}
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(mut guard) = f.lock() {
if let (Some(label_str), Some(value_str)) = (
super::safe_helpers::c_str_to_str(label),
super::safe_helpers::c_str_to_str(value),
) {
let mut options = guard.options().to_vec();
options.push(ChoiceOption::new(
label_str.to_string(),
value_str.to_string(),
));
guard.set_options(options);
return 1;
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_form_field_count(_ctx: Handle, form: Handle) -> i32 {
if let Some(f) = FORMS.get(form) {
if let Ok(guard) = f.lock() {
return guard.len() as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_lookup_field(
_ctx: Handle,
form: Handle,
name: *const std::ffi::c_char,
) -> Handle {
if name.is_null() {
return 0;
}
if let Some(f) = FORMS.get(form) {
if let Ok(guard) = f.lock() {
if let Some(field_name) = super::safe_helpers::c_str_to_str(name) {
if let Some(field) = guard.get_field(field_name) {
return FORM_FIELDS.insert(field.clone());
}
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_reset_form(_ctx: Handle, form: Handle) {
if let Some(f) = FORMS.get(form) {
if let Ok(mut guard) = f.lock() {
guard.reset();
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_validate_form(_ctx: Handle, form: Handle) -> i32 {
if let Some(f) = FORMS.get(form) {
if let Ok(guard) = f.lock() {
return guard.validate().is_ok() as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_delete_field(_ctx: Handle, _form: Handle, field: Handle) -> i32 {
FORM_FIELDS.remove(field);
1
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_default_value(
_ctx: Handle,
field: Handle,
buf: *mut c_char,
buf_size: i32,
) -> i32 {
if buf.is_null() || buf_size <= 0 {
return 0;
}
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
let default_val = &guard.default_value;
let bytes = default_val.as_bytes();
let len = (bytes.len() as i32).min(buf_size - 1);
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, len as usize);
*buf.offset(len as isize) = 0;
}
return len;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_default_value(
_ctx: Handle,
field: Handle,
value: *const c_char,
) -> i32 {
if value.is_null() {
return 0;
}
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(mut guard) = f.lock() {
if let Ok(val_str) = unsafe { CStr::from_ptr(value).to_str() } {
guard.default_value = val_str.to_string();
return 1;
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_border_width(_ctx: Handle, field: Handle) -> f32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.border_width;
}
}
1.0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_border_width(_ctx: Handle, field: Handle, width: f32) {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(mut guard) = f.lock() {
guard.border_width = width.max(0.0);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_border_color(_ctx: Handle, field: Handle, color: *mut f32) {
if color.is_null() {
return;
}
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
unsafe {
*color.offset(0) = guard.border_color[0];
*color.offset(1) = guard.border_color[1];
*color.offset(2) = guard.border_color[2];
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_border_color(_ctx: Handle, field: Handle, color: *const f32) {
if color.is_null() {
return;
}
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(mut guard) = f.lock() {
unsafe {
guard.border_color = [*color.offset(0), *color.offset(1), *color.offset(2)];
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_bg_color(_ctx: Handle, field: Handle, color: *mut f32) {
if color.is_null() {
return;
}
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
unsafe {
*color.offset(0) = guard.bg_color[0];
*color.offset(1) = guard.bg_color[1];
*color.offset(2) = guard.bg_color[2];
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_bg_color(_ctx: Handle, field: Handle, color: *const f32) {
if color.is_null() {
return;
}
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(mut guard) = f.lock() {
unsafe {
guard.bg_color = [*color.offset(0), *color.offset(1), *color.offset(2)];
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_font_size(_ctx: Handle, field: Handle) -> f32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.font_size;
}
}
12.0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_font_size(_ctx: Handle, field: Handle, size: f32) {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(mut guard) = f.lock() {
guard.font_size = size.max(1.0);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_alignment(_ctx: Handle, field: Handle) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.alignment;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_alignment(_ctx: Handle, field: Handle, align: i32) {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(mut guard) = f.lock() {
guard.alignment = align.clamp(0, 2);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_combo(_ctx: Handle, field: Handle) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.is_combo as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_edit(_ctx: Handle, field: Handle) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.editable as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_multiselect(_ctx: Handle, field: Handle) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.multi_select as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_selected_index(_ctx: Handle, field: Handle) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
return guard.selected_index;
}
}
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_set_field_selected_index(_ctx: Handle, field: Handle, idx: i32) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(mut guard) = f.lock() {
if idx >= 0 && (idx as usize) < guard.choices.len() {
guard.selected_index = idx;
return 1;
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_clear_selection(_ctx: Handle, field: Handle) {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(mut guard) = f.lock() {
guard.selected_index = -1;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_remove_field_choice(_ctx: Handle, field: Handle, idx: i32) -> i32 {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(mut guard) = f.lock() {
if idx >= 0 && (idx as usize) < guard.choices.len() {
guard.choices.remove(idx as usize);
return 1;
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_field_is_valid(_ctx: Handle, field: Handle) -> i32 {
if FORM_FIELDS.get(field).is_some() {
1
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pdf_clone_field(_ctx: Handle, field: Handle) -> Handle {
if let Some(f) = FORM_FIELDS.get(field) {
if let Ok(guard) = f.lock() {
let cloned = guard.clone();
return FORM_FIELDS.insert(cloned);
}
}
0
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pdf::form::WidgetType;
use std::ffi::CString;
#[test]
fn test_create_text_field() {
let name = CString::new("username").unwrap();
let field = pdf_create_text_field(0, 0, name.as_ptr(), 10.0, 10.0, 200.0, 30.0, 50);
assert_ne!(field, 0);
let field_type = pdf_field_type(0, field);
assert_eq!(field_type, 6);
FORM_FIELDS.remove(field);
}
#[test]
fn test_create_checkbox() {
let name = CString::new("agree").unwrap();
let field = pdf_create_checkbox(0, 0, name.as_ptr(), 10.0, 10.0, 20.0, 20.0, 1);
assert_ne!(field, 0);
let checked = pdf_field_is_checked(0, field);
assert_eq!(checked, 1);
FORM_FIELDS.remove(field);
}
#[test]
fn test_field_value() {
let name = CString::new("testfield").unwrap();
let field = pdf_create_text_field(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0, 0);
let value = CString::new("Test Value").unwrap();
let result = pdf_set_field_value(0, field, value.as_ptr());
assert_eq!(result, 1);
let mut buf = [0i8; 256];
let len = pdf_field_value(0, field, buf.as_mut_ptr(), 256);
assert!(len > 0);
FORM_FIELDS.remove(field);
}
#[test]
fn test_field_flags() {
let name = CString::new("readonly").unwrap();
let field = pdf_create_text_field(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0, 0);
pdf_set_field_flags(0, field, 1); let is_readonly = pdf_field_is_read_only(0, field);
assert_eq!(is_readonly, 1);
FORM_FIELDS.remove(field);
}
#[test]
fn test_choice_field() {
let name = CString::new("country").unwrap();
let field = pdf_create_combo_box(0, 0, name.as_ptr(), 0.0, 0.0, 150.0, 30.0);
let label1 = CString::new("United States").unwrap();
let value1 = CString::new("US").unwrap();
pdf_add_field_choice(0, field, label1.as_ptr(), value1.as_ptr());
let label2 = CString::new("Canada").unwrap();
let value2 = CString::new("CA").unwrap();
pdf_add_field_choice(0, field, label2.as_ptr(), value2.as_ptr());
let count = pdf_field_choice_count(0, field);
assert_eq!(count, 2);
FORM_FIELDS.remove(field);
}
#[test]
fn test_form_operations() {
let form = pdf_form(0, 0);
assert_ne!(form, 0);
let count = pdf_form_field_count(0, form);
assert_eq!(count, 0);
pdf_drop_form(0, form);
}
#[test]
fn test_parse_hex_string_field() {
let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (name) /FT /Tx /V <48656C6C6F> /Rect [ 0 0 100 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
let form = test_parse_form_from_bytes(pdf);
assert_eq!(form.len(), 1);
let field = form.get_field("name").unwrap();
assert_eq!(field.value(), "Hello");
}
#[test]
fn test_parse_choice_field_with_opt() {
let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (country) /FT /Ch /Ff 131072 /Opt [ (US) (CA) (UK) ] /Rect [ 0 0 100 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
let form = test_parse_form_from_bytes(pdf);
assert_eq!(form.len(), 1);
let field = form.get_field("country").unwrap();
assert_eq!(field.options().len(), 3);
assert_eq!(field.options()[0].value, "US");
assert_eq!(field.options()[1].value, "CA");
assert_eq!(field.options()[2].value, "UK");
}
#[test]
fn test_parse_radio_via_ff_flags() {
let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (choice) /FT /Btn /Ff 32768 /V (Yes) /Rect [ 0 0 20 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
let form = test_parse_form_from_bytes(pdf);
assert_eq!(form.len(), 1);
let field = form.get_field("choice").unwrap();
assert_eq!(field.field_type(), WidgetType::RadioButton);
assert!(field.is_checked());
}
#[test]
fn test_parse_checkbox_as_state() {
let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (agree) /FT /Btn /AS /Yes /Rect [ 0 0 20 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
let form = test_parse_form_from_bytes(pdf);
assert_eq!(form.len(), 1);
let field = form.get_field("agree").unwrap();
assert_eq!(field.field_type(), WidgetType::Checkbox);
assert!(field.is_checked());
}
#[test]
fn test_parse_tooltip() {
let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (email) /FT /Tx /TU (Enter your email) /Rect [ 0 0 200 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
let form = test_parse_form_from_bytes(pdf);
assert_eq!(form.len(), 1);
let field = form.get_field("email").unwrap();
assert_eq!(field.tooltip(), Some("Enter your email"));
}
#[test]
fn test_parse_default_appearance() {
let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (text) /FT /Tx /DA (/Helvetica 14 Tf 0.2 0.4 0.6 rg) /Rect [ 0 0 100 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
let form = test_parse_form_from_bytes(pdf);
assert_eq!(form.len(), 1);
let field = form.get_field("text").unwrap();
assert_eq!(field.font_size, 14.0);
assert_eq!(field.font_name.as_deref(), Some("Helvetica"));
assert!((field.border_color[0] - 0.2).abs() < 0.01);
assert!((field.border_color[1] - 0.4).abs() < 0.01);
assert!((field.border_color[2] - 0.6).abs() < 0.01);
}
#[test]
fn test_parse_kids_recursive() {
let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (parent) /FT /Tx /Kids [ 4 0 R ] /Rect [ 0 0 100 20 ] >> endobj
4 0 obj << /T (child) /FT /Tx /V (nested) /Rect [ 0 0 80 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
let form = test_parse_form_from_bytes(pdf);
assert!(form.len() >= 2, "expected parent and child fields");
assert!(form.get_field("parent").is_some());
assert!(form.get_field("child").is_some());
assert_eq!(form.get_field("child").unwrap().value(), "nested");
}
#[test]
fn test_parse_opt_array_export_display() {
let pdf = br#"%PDF-1.4
1 0 obj << /Type /Catalog /AcroForm 2 0 R >> endobj
2 0 obj << /Fields [ 3 0 R ] >> endobj
3 0 obj << /T (sel) /FT /Ch /Ff 131072 /Opt [ [(US)(United States)] [(CA)(Canada)] ] /Rect [ 0 0 100 20 ] >> endobj
trailer << /Root 1 0 R >>"#;
let form = test_parse_form_from_bytes(pdf);
assert_eq!(form.len(), 1);
let field = form.get_field("sel").unwrap();
assert_eq!(field.options().len(), 2);
assert_eq!(field.options()[0].value, "US");
assert_eq!(field.options()[0].label, "United States");
assert_eq!(field.options()[1].value, "CA");
assert_eq!(field.options()[1].label, "Canada");
}
#[test]
fn test_create_text_field_null_name() {
assert_eq!(
pdf_create_text_field(0, 0, std::ptr::null(), 0.0, 0.0, 100.0, 30.0, 0),
0
);
}
#[test]
fn test_create_checkbox_null_name() {
assert_eq!(
pdf_create_checkbox(0, 0, std::ptr::null(), 0.0, 0.0, 20.0, 20.0, 0),
0
);
}
#[test]
fn test_create_push_button_null_name() {
assert_eq!(
pdf_create_push_button(
0,
0,
std::ptr::null(),
0.0,
0.0,
100.0,
30.0,
c"OK".as_ptr()
),
0
);
}
#[test]
fn test_create_push_button_null_caption() {
let name = CString::new("btn").unwrap();
let field =
pdf_create_push_button(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0, std::ptr::null());
assert_ne!(field, 0);
FORM_FIELDS.remove(field);
}
#[test]
fn test_create_combo_box_null_name() {
assert_eq!(
pdf_create_combo_box(0, 0, std::ptr::null(), 0.0, 0.0, 100.0, 30.0),
0
);
}
#[test]
fn test_create_signature_field_null_name() {
assert_eq!(
pdf_create_signature_field(0, 0, std::ptr::null(), 0.0, 0.0, 100.0, 30.0),
0
);
}
#[test]
fn test_pdf_keep_form_invalid() {
assert_eq!(pdf_keep_form(0, 0), 0);
}
#[test]
fn test_pdf_first_widget_invalid_page() {
assert_eq!(pdf_first_widget(0, 0), 0);
}
#[test]
fn test_pdf_next_widget_invalid() {
assert_eq!(pdf_next_widget(0, 0), 0);
}
#[test]
fn test_pdf_field_name_null_buf() {
let name = CString::new("f").unwrap();
let field = pdf_create_text_field(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0, 0);
assert_eq!(pdf_field_name(0, field, std::ptr::null_mut(), 100), 0);
FORM_FIELDS.remove(field);
}
#[test]
fn test_pdf_field_type_invalid() {
assert_eq!(pdf_field_type(0, 0), -1);
}
#[test]
fn test_pdf_set_field_value_null() {
let name = CString::new("f").unwrap();
let field = pdf_create_text_field(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0, 0);
assert_eq!(pdf_set_field_value(0, field, std::ptr::null()), 0);
FORM_FIELDS.remove(field);
}
#[test]
fn test_pdf_field_rect_invalid() {
let rect = pdf_field_rect(0, 0);
assert_eq!(rect.x0, 0.0);
assert_eq!(rect.y0, 0.0);
assert_eq!(rect.x1, 0.0);
assert_eq!(rect.y1, 0.0);
}
#[test]
fn test_pdf_field_choice_label_invalid_index() {
let name = CString::new("c").unwrap();
let field = pdf_create_combo_box(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0);
let mut buf = [0i8; 64];
assert_eq!(
pdf_field_choice_label(0, field, -1, buf.as_mut_ptr(), 64),
0
);
assert_eq!(
pdf_field_choice_label(0, field, 99, buf.as_mut_ptr(), 64),
0
);
FORM_FIELDS.remove(field);
}
#[test]
fn test_pdf_add_field_choice_null_label() {
let name = CString::new("c").unwrap();
let field = pdf_create_combo_box(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0);
let value = CString::new("v").unwrap();
assert_eq!(
pdf_add_field_choice(0, field, std::ptr::null(), value.as_ptr()),
0
);
FORM_FIELDS.remove(field);
}
#[test]
fn test_pdf_lookup_field_null_name() {
let form = pdf_form(0, 0);
assert_eq!(pdf_lookup_field(0, form, std::ptr::null()), 0);
pdf_drop_form(0, form);
}
#[test]
fn test_pdf_form_field_count_invalid() {
assert_eq!(pdf_form_field_count(0, 0), 0);
}
#[test]
fn test_pdf_field_default_value_null_buf() {
let name = CString::new("f").unwrap();
let field = pdf_create_text_field(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0, 0);
assert_eq!(
pdf_field_default_value(0, field, std::ptr::null_mut(), 100),
0
);
FORM_FIELDS.remove(field);
}
#[test]
fn test_pdf_set_field_default_value_null() {
let name = CString::new("f").unwrap();
let field = pdf_create_text_field(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0, 0);
assert_eq!(pdf_set_field_default_value(0, field, std::ptr::null()), 0);
FORM_FIELDS.remove(field);
}
#[test]
fn test_pdf_field_is_valid_invalid() {
assert_eq!(pdf_field_is_valid(0, 0), 0);
}
#[test]
fn test_pdf_clone_field_invalid() {
assert_eq!(pdf_clone_field(0, 0), 0);
}
#[test]
fn test_pdf_field_border_color_null() {
pdf_field_border_color(0, 0, std::ptr::null_mut());
}
#[test]
fn test_pdf_set_field_border_color_null() {
pdf_set_field_border_color(0, 0, std::ptr::null());
}
#[test]
fn test_pdf_field_bg_color_null() {
pdf_field_bg_color(0, 0, std::ptr::null_mut());
}
#[test]
fn test_pdf_set_field_bg_color_null() {
pdf_set_field_bg_color(0, 0, std::ptr::null());
}
#[test]
fn test_pdf_create_signature_field() {
let name = CString::new("sig1").unwrap();
let field = pdf_create_signature_field(0, 0, name.as_ptr(), 10.0, 10.0, 150.0, 50.0);
assert_ne!(field, 0);
assert_eq!(pdf_field_type(0, field), 5);
assert_eq!(pdf_field_is_signed(0, field), 0);
FORM_FIELDS.remove(field);
}
#[test]
fn test_pdf_reset_form() {
let form = pdf_form(0, 0);
pdf_reset_form(0, form);
pdf_drop_form(0, form);
}
#[test]
fn test_pdf_validate_form_empty() {
let form = pdf_form(0, 0);
assert_eq!(pdf_validate_form(0, form), 1);
pdf_drop_form(0, form);
}
#[test]
fn test_pdf_delete_field() {
let name = CString::new("del").unwrap();
let field = pdf_create_text_field(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0, 0);
assert_eq!(pdf_delete_field(0, 0, field), 1);
}
#[test]
fn test_pdf_field_border_width() {
let name = CString::new("bw").unwrap();
let field = pdf_create_text_field(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0, 0);
assert!(pdf_field_border_width(0, field) >= 0.0);
pdf_set_field_border_width(0, field, 2.0);
assert!((pdf_field_border_width(0, field) - 2.0).abs() < 0.01);
FORM_FIELDS.remove(field);
}
#[test]
fn test_pdf_field_font_size() {
let name = CString::new("fs").unwrap();
let field = pdf_create_text_field(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0, 0);
pdf_set_field_font_size(0, field, 14.0);
assert!((pdf_field_font_size(0, field) - 14.0).abs() < 0.01);
FORM_FIELDS.remove(field);
}
#[test]
fn test_pdf_field_alignment() {
let name = CString::new("al").unwrap();
let field = pdf_create_text_field(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0, 0);
pdf_set_field_alignment(0, field, 1);
assert_eq!(pdf_field_alignment(0, field), 1);
FORM_FIELDS.remove(field);
}
#[test]
fn test_pdf_field_clear_selection() {
let name = CString::new("sel").unwrap();
let field = pdf_create_combo_box(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0);
let l = CString::new("A").unwrap();
let v = CString::new("a").unwrap();
pdf_add_field_choice(0, field, l.as_ptr(), v.as_ptr());
pdf_set_field_selected_index(0, field, 0);
pdf_field_clear_selection(0, field);
assert_eq!(pdf_field_selected_index(0, field), -1);
FORM_FIELDS.remove(field);
}
#[test]
fn test_pdf_field_choice_value() {
let name = CString::new("cv").unwrap();
let field = pdf_create_combo_box(0, 0, name.as_ptr(), 0.0, 0.0, 100.0, 30.0);
let l = CString::new("Opt").unwrap();
let v = CString::new("val").unwrap();
pdf_add_field_choice(0, field, l.as_ptr(), v.as_ptr());
let mut buf = [0i8; 64];
assert!(pdf_field_choice_value(0, field, 0, buf.as_mut_ptr(), 64) > 0);
FORM_FIELDS.remove(field);
}
}