use super::{ButtonFieldFlags, FormFieldEntry, FormFieldWidget};
use crate::geometry::Rect;
use crate::object::{Object, ObjectRef};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct CheckboxWidget {
name: String,
rect: Rect,
checked: bool,
export_value: String,
flags: ButtonFieldFlags,
border_color: Option<(f32, f32, f32)>,
background_color: Option<(f32, f32, f32)>,
check_color: (f32, f32, f32),
border_width: f32,
tooltip: Option<String>,
}
impl CheckboxWidget {
pub fn new(name: impl Into<String>, rect: Rect) -> Self {
Self {
name: name.into(),
rect,
checked: false,
export_value: "Yes".to_string(),
flags: ButtonFieldFlags::empty(),
border_color: Some((0.0, 0.0, 0.0)), background_color: Some((1.0, 1.0, 1.0)), check_color: (0.0, 0.0, 0.0), border_width: 1.0,
tooltip: None,
}
}
pub fn checked(mut self) -> Self {
self.checked = true;
self
}
pub fn unchecked(mut self) -> Self {
self.checked = false;
self
}
pub fn with_checked(mut self, checked: bool) -> Self {
self.checked = checked;
self
}
pub fn with_export_value(mut self, value: impl Into<String>) -> Self {
self.export_value = value.into();
self
}
pub fn read_only(mut self) -> Self {
self.flags |= ButtonFieldFlags::READ_ONLY;
self
}
pub fn required(mut self) -> Self {
self.flags |= ButtonFieldFlags::REQUIRED;
self
}
pub fn with_border_color(mut self, r: f32, g: f32, b: f32) -> Self {
self.border_color = Some((r, g, b));
self
}
pub fn no_border(mut self) -> Self {
self.border_color = None;
self.border_width = 0.0;
self
}
pub fn with_background_color(mut self, r: f32, g: f32, b: f32) -> Self {
self.background_color = Some((r, g, b));
self
}
pub fn with_check_color(mut self, r: f32, g: f32, b: f32) -> Self {
self.check_color = (r, g, b);
self
}
pub fn with_border_width(mut self, width: f32) -> Self {
self.border_width = width;
self
}
pub fn with_tooltip(mut self, tooltip: impl Into<String>) -> Self {
self.tooltip = Some(tooltip.into());
self
}
pub fn is_checked(&self) -> bool {
self.checked
}
pub fn export_value(&self) -> &str {
&self.export_value
}
pub fn build_entry(&self, page_ref: ObjectRef) -> FormFieldEntry {
FormFieldEntry {
widget_dict: self.build_widget_dict(page_ref),
field_dict: self.build_field_dict(),
name: self.name.clone(),
rect: self.rect,
field_type: "Btn".to_string(),
}
}
}
impl FormFieldWidget for CheckboxWidget {
fn field_name(&self) -> &str {
&self.name
}
fn rect(&self) -> Rect {
self.rect
}
fn field_type(&self) -> &'static str {
"Btn"
}
fn field_flags(&self) -> u32 {
self.flags.bits()
}
fn build_field_dict(&self) -> HashMap<String, Object> {
let mut dict = HashMap::new();
dict.insert("FT".to_string(), Object::Name("Btn".to_string()));
dict.insert("T".to_string(), Object::String(self.name.as_bytes().to_vec()));
let value = if self.checked {
self.export_value.clone()
} else {
"Off".to_string()
};
dict.insert("V".to_string(), Object::Name(value.clone()));
dict.insert("DV".to_string(), Object::Name(value));
if self.flags.bits() != 0 {
dict.insert("Ff".to_string(), Object::Integer(self.flags.bits() as i64));
}
dict
}
fn build_widget_dict(&self, page_ref: ObjectRef) -> HashMap<String, Object> {
let mut dict = HashMap::new();
dict.insert("Type".to_string(), Object::Name("Annot".to_string()));
dict.insert("Subtype".to_string(), Object::Name("Widget".to_string()));
dict.insert(
"Rect".to_string(),
Object::Array(vec![
Object::Real(self.rect.x as f64),
Object::Real(self.rect.y as f64),
Object::Real((self.rect.x + self.rect.width) as f64),
Object::Real((self.rect.y + self.rect.height) as f64),
]),
);
dict.insert("P".to_string(), Object::Reference(page_ref));
dict.insert("F".to_string(), Object::Integer(4));
let as_name = if self.checked {
self.export_value.clone()
} else {
"Off".to_string()
};
dict.insert("AS".to_string(), Object::Name(as_name));
if let Some(ref tip) = self.tooltip {
dict.insert("TU".to_string(), Object::String(tip.as_bytes().to_vec()));
}
if self.border_width > 0.0 {
let mut bs = HashMap::new();
bs.insert("W".to_string(), Object::Real(self.border_width as f64));
bs.insert("S".to_string(), Object::Name("S".to_string())); dict.insert("BS".to_string(), Object::Dictionary(bs));
}
let mut mk = HashMap::new();
if let Some((r, g, b)) = self.border_color {
mk.insert(
"BC".to_string(),
Object::Array(vec![
Object::Real(r as f64),
Object::Real(g as f64),
Object::Real(b as f64),
]),
);
}
if let Some((r, g, b)) = self.background_color {
mk.insert(
"BG".to_string(),
Object::Array(vec![
Object::Real(r as f64),
Object::Real(g as f64),
Object::Real(b as f64),
]),
);
}
mk.insert("CA".to_string(), Object::String("4".as_bytes().to_vec()));
if !mk.is_empty() {
dict.insert("MK".to_string(), Object::Dictionary(mk));
}
dict
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_checkbox_new() {
let checkbox = CheckboxWidget::new("agree", Rect::new(72.0, 700.0, 15.0, 15.0));
assert_eq!(checkbox.name, "agree");
assert!(!checkbox.checked);
assert_eq!(checkbox.export_value, "Yes");
}
#[test]
fn test_checkbox_checked() {
let checkbox = CheckboxWidget::new("agree", Rect::new(72.0, 700.0, 15.0, 15.0)).checked();
assert!(checkbox.is_checked());
}
#[test]
fn test_checkbox_export_value() {
let checkbox = CheckboxWidget::new("opt_in", Rect::new(72.0, 700.0, 15.0, 15.0))
.with_export_value("Accepted")
.checked();
assert_eq!(checkbox.export_value(), "Accepted");
}
#[test]
fn test_checkbox_required() {
let checkbox = CheckboxWidget::new("terms", Rect::new(72.0, 700.0, 15.0, 15.0)).required();
assert!(checkbox.flags.contains(ButtonFieldFlags::REQUIRED));
}
#[test]
fn test_checkbox_build_field_dict_checked() {
let checkbox = CheckboxWidget::new("agree", Rect::new(72.0, 700.0, 15.0, 15.0))
.with_export_value("Yes")
.checked();
let dict = checkbox.build_field_dict();
assert_eq!(dict.get("FT"), Some(&Object::Name("Btn".to_string())));
assert_eq!(dict.get("V"), Some(&Object::Name("Yes".to_string())));
}
#[test]
fn test_checkbox_build_field_dict_unchecked() {
let checkbox = CheckboxWidget::new("agree", Rect::new(72.0, 700.0, 15.0, 15.0));
let dict = checkbox.build_field_dict();
assert_eq!(dict.get("V"), Some(&Object::Name("Off".to_string())));
}
#[test]
fn test_checkbox_build_widget_dict() {
let checkbox = CheckboxWidget::new("agree", Rect::new(72.0, 700.0, 15.0, 15.0))
.checked()
.with_tooltip("Check to agree");
let page_ref = ObjectRef::new(10, 0);
let dict = checkbox.build_widget_dict(page_ref);
assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
assert_eq!(dict.get("Subtype"), Some(&Object::Name("Widget".to_string())));
assert_eq!(dict.get("AS"), Some(&Object::Name("Yes".to_string())));
assert!(dict.contains_key("TU")); assert!(dict.contains_key("MK")); }
#[test]
fn test_checkbox_trait_impl() {
let checkbox = CheckboxWidget::new("test", Rect::new(72.0, 700.0, 15.0, 15.0));
assert_eq!(checkbox.field_name(), "test");
assert_eq!(checkbox.field_type(), "Btn");
assert!(checkbox.needs_appearance());
}
#[test]
fn test_checkbox_colors() {
let checkbox = CheckboxWidget::new("test", Rect::new(72.0, 700.0, 15.0, 15.0))
.with_border_color(0.0, 0.0, 1.0)
.with_background_color(0.9, 0.9, 1.0)
.with_check_color(0.0, 0.5, 0.0);
assert_eq!(checkbox.border_color, Some((0.0, 0.0, 1.0)));
assert_eq!(checkbox.background_color, Some((0.9, 0.9, 1.0)));
assert_eq!(checkbox.check_color, (0.0, 0.5, 0.0));
}
}