1use crate::geometry::Rectangle;
4use crate::graphics::Color;
5use crate::objects::{Dictionary, Object};
6
7#[derive(Debug, Clone, Copy, Default)]
9pub struct FieldFlags {
10 pub read_only: bool,
12 pub required: bool,
14 pub no_export: bool,
16}
17
18impl FieldFlags {
19 pub fn to_flags(&self) -> u32 {
21 let mut flags = 0u32;
22 if self.read_only {
23 flags |= 1 << 0;
24 }
25 if self.required {
26 flags |= 1 << 1;
27 }
28 if self.no_export {
29 flags |= 1 << 2;
30 }
31 flags
32 }
33}
34
35#[derive(Debug, Clone, Default)]
37pub struct FieldOptions {
38 pub flags: FieldFlags,
40 pub default_appearance: Option<String>,
42 pub quadding: Option<i32>,
44}
45
46#[derive(Debug, Clone)]
48pub struct WidgetAppearance {
49 pub border_color: Option<Color>,
51 pub background_color: Option<Color>,
53 pub border_width: f64,
55 pub border_style: BorderStyle,
57}
58
59#[derive(Debug, Clone, Copy)]
61pub enum BorderStyle {
62 Solid,
64 Dashed,
66 Beveled,
68 Inset,
70 Underline,
72}
73
74impl BorderStyle {
75 pub fn pdf_name(&self) -> &'static str {
77 match self {
78 BorderStyle::Solid => "S",
79 BorderStyle::Dashed => "D",
80 BorderStyle::Beveled => "B",
81 BorderStyle::Inset => "I",
82 BorderStyle::Underline => "U",
83 }
84 }
85}
86
87impl Default for WidgetAppearance {
88 fn default() -> Self {
89 Self {
90 border_color: Some(Color::black()),
91 background_color: None,
92 border_width: 1.0,
93 border_style: BorderStyle::Solid,
94 }
95 }
96}
97
98#[derive(Debug, Clone)]
100pub struct Widget {
101 pub rect: Rectangle,
103 pub appearance: WidgetAppearance,
105 pub parent: Option<String>,
107}
108
109impl Widget {
110 pub fn new(rect: Rectangle) -> Self {
112 Self {
113 rect,
114 appearance: WidgetAppearance::default(),
115 parent: None,
116 }
117 }
118
119 pub fn with_appearance(mut self, appearance: WidgetAppearance) -> Self {
121 self.appearance = appearance;
122 self
123 }
124
125 pub fn to_annotation_dict(&self) -> Dictionary {
127 let mut dict = Dictionary::new();
128
129 dict.set("Type", Object::Name("Annot".to_string()));
131 dict.set("Subtype", Object::Name("Widget".to_string()));
132
133 let rect_array = vec![
135 Object::Real(self.rect.lower_left.x),
136 Object::Real(self.rect.lower_left.y),
137 Object::Real(self.rect.upper_right.x),
138 Object::Real(self.rect.upper_right.y),
139 ];
140 dict.set("Rect", Object::Array(rect_array));
141
142 let mut bs_dict = Dictionary::new();
144 bs_dict.set("W", Object::Real(self.appearance.border_width));
145 bs_dict.set(
146 "S",
147 Object::Name(self.appearance.border_style.pdf_name().to_string()),
148 );
149 dict.set("BS", Object::Dictionary(bs_dict));
150
151 let mut mk_dict = Dictionary::new();
153
154 if let Some(border_color) = &self.appearance.border_color {
155 let bc = match border_color {
156 Color::Rgb(r, g, b) => vec![Object::Real(*r), Object::Real(*g), Object::Real(*b)],
157 Color::Gray(g) => vec![Object::Real(*g)],
158 Color::Cmyk(c, m, y, k) => vec![
159 Object::Real(*c),
160 Object::Real(*m),
161 Object::Real(*y),
162 Object::Real(*k),
163 ],
164 };
165 mk_dict.set("BC", Object::Array(bc));
166 }
167
168 if let Some(bg_color) = &self.appearance.background_color {
169 let bg = match bg_color {
170 Color::Rgb(r, g, b) => vec![Object::Real(*r), Object::Real(*g), Object::Real(*b)],
171 Color::Gray(g) => vec![Object::Real(*g)],
172 Color::Cmyk(c, m, y, k) => vec![
173 Object::Real(*c),
174 Object::Real(*m),
175 Object::Real(*y),
176 Object::Real(*k),
177 ],
178 };
179 mk_dict.set("BG", Object::Array(bg));
180 }
181
182 dict.set("MK", Object::Dictionary(mk_dict));
183
184 dict.set("F", Object::Integer(4));
186
187 dict
188 }
189}
190
191pub trait Field {
193 fn name(&self) -> &str;
195
196 fn field_type(&self) -> &'static str;
198
199 fn to_dict(&self) -> Dictionary;
201}
202
203#[derive(Debug, Clone)]
205pub struct FormField {
206 pub field_dict: Dictionary,
208 pub widgets: Vec<Widget>,
210}
211
212impl FormField {
213 pub fn new(field_dict: Dictionary) -> Self {
215 Self {
216 field_dict,
217 widgets: Vec::new(),
218 }
219 }
220
221 pub fn add_widget(&mut self, widget: Widget) {
223 self.widgets.push(widget);
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use crate::geometry::Point;
231
232 #[test]
233 fn test_field_flags() {
234 let flags = FieldFlags {
235 read_only: true,
236 required: true,
237 no_export: false,
238 };
239
240 assert_eq!(flags.to_flags(), 3); }
242
243 #[test]
244 fn test_border_style() {
245 assert_eq!(BorderStyle::Solid.pdf_name(), "S");
246 assert_eq!(BorderStyle::Dashed.pdf_name(), "D");
247 assert_eq!(BorderStyle::Beveled.pdf_name(), "B");
248 assert_eq!(BorderStyle::Inset.pdf_name(), "I");
249 assert_eq!(BorderStyle::Underline.pdf_name(), "U");
250 }
251
252 #[test]
253 fn test_widget_creation() {
254 let rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(200.0, 120.0));
255
256 let widget = Widget::new(rect);
257 assert_eq!(widget.rect.lower_left.x, 100.0);
258 assert_eq!(widget.appearance.border_width, 1.0);
259 }
260
261 #[test]
262 fn test_widget_annotation_dict() {
263 let rect = Rectangle::new(Point::new(50.0, 50.0), Point::new(150.0, 70.0));
264
265 let appearance = WidgetAppearance {
266 border_color: Some(Color::Rgb(0.0, 0.0, 1.0)),
267 background_color: Some(Color::Gray(0.9)),
268 border_width: 2.0,
269 border_style: BorderStyle::Solid,
270 };
271
272 let widget = Widget::new(rect).with_appearance(appearance);
273 let dict = widget.to_annotation_dict();
274
275 assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
276 assert_eq!(
277 dict.get("Subtype"),
278 Some(&Object::Name("Widget".to_string()))
279 );
280 assert!(dict.get("Rect").is_some());
281 assert!(dict.get("BS").is_some());
282 assert!(dict.get("MK").is_some());
283 }
284
285 #[test]
286 fn test_field_options_default() {
287 let options = FieldOptions::default();
288 assert!(!options.flags.read_only);
289 assert!(!options.flags.required);
290 assert!(!options.flags.no_export);
291 assert!(options.default_appearance.is_none());
292 assert!(options.quadding.is_none());
293 }
294
295 #[test]
296 fn test_field_flags_all_combinations() {
297 let flag_combos = [
299 (false, false, false, 0),
300 (true, false, false, 1),
301 (false, true, false, 2),
302 (false, false, true, 4),
303 (true, true, false, 3),
304 (true, false, true, 5),
305 (false, true, true, 6),
306 (true, true, true, 7),
307 ];
308
309 for (read_only, required, no_export, expected) in flag_combos {
310 let flags = FieldFlags {
311 read_only,
312 required,
313 no_export,
314 };
315 assert_eq!(
316 flags.to_flags(),
317 expected,
318 "Failed for read_only={}, required={}, no_export={}",
319 read_only,
320 required,
321 no_export
322 );
323 }
324 }
325
326 #[test]
327 fn test_field_flags_debug_clone_default() {
328 let flags = FieldFlags::default();
329 let debug_str = format!("{:?}", flags);
330 assert!(debug_str.contains("FieldFlags"));
331
332 let cloned = flags.clone();
333 assert_eq!(flags.read_only, cloned.read_only);
334 assert_eq!(flags.required, cloned.required);
335 assert_eq!(flags.no_export, cloned.no_export);
336
337 let copied = flags;
339 assert_eq!(flags.read_only, copied.read_only);
340 }
341
342 #[test]
343 fn test_border_style_debug_clone_copy() {
344 let style = BorderStyle::Solid;
345 let debug_str = format!("{:?}", style);
346 assert!(debug_str.contains("Solid"));
347
348 let cloned = style.clone();
349 assert_eq!(style.pdf_name(), cloned.pdf_name());
350
351 let copied = style;
353 assert_eq!(style.pdf_name(), copied.pdf_name());
354 }
355
356 #[test]
357 fn test_all_border_styles() {
358 let styles = [
359 (BorderStyle::Solid, "S"),
360 (BorderStyle::Dashed, "D"),
361 (BorderStyle::Beveled, "B"),
362 (BorderStyle::Inset, "I"),
363 (BorderStyle::Underline, "U"),
364 ];
365
366 for (style, expected) in styles {
367 assert_eq!(style.pdf_name(), expected);
368 }
369 }
370
371 #[test]
372 fn test_widget_appearance_default() {
373 let appearance = WidgetAppearance::default();
374 assert_eq!(appearance.border_color, Some(Color::black()));
375 assert_eq!(appearance.background_color, None);
376 assert_eq!(appearance.border_width, 1.0);
377 match appearance.border_style {
378 BorderStyle::Solid => {}
379 _ => panic!("Expected solid border style"),
380 }
381 }
382
383 #[test]
384 fn test_widget_appearance_debug_clone() {
385 let appearance = WidgetAppearance {
386 border_color: Some(Color::red()),
387 background_color: Some(Color::gray(0.5)),
388 border_width: 2.5,
389 border_style: BorderStyle::Dashed,
390 };
391
392 let debug_str = format!("{:?}", appearance);
393 assert!(debug_str.contains("WidgetAppearance"));
394
395 let cloned = appearance.clone();
396 assert_eq!(appearance.border_color, cloned.border_color);
397 assert_eq!(appearance.background_color, cloned.background_color);
398 assert_eq!(appearance.border_width, cloned.border_width);
399 }
400
401 #[test]
402 fn test_widget_debug_clone() {
403 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0));
404 let widget = Widget::new(rect);
405
406 let debug_str = format!("{:?}", widget);
407 assert!(debug_str.contains("Widget"));
408
409 let cloned = widget.clone();
410 assert_eq!(widget.rect.lower_left.x, cloned.rect.lower_left.x);
411 assert_eq!(widget.parent, cloned.parent);
412 }
413
414 #[test]
415 fn test_widget_with_parent() {
416 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0));
417 let mut widget = Widget::new(rect);
418 widget.parent = Some("TextField1".to_string());
419
420 assert_eq!(widget.parent, Some("TextField1".to_string()));
421 }
422
423 #[test]
424 fn test_widget_annotation_dict_rgb_colors() {
425 let rect = Rectangle::new(Point::new(10.0, 20.0), Point::new(110.0, 40.0));
426 let appearance = WidgetAppearance {
427 border_color: Some(Color::rgb(1.0, 0.0, 0.0)),
428 background_color: Some(Color::rgb(0.0, 1.0, 0.0)),
429 border_width: 1.5,
430 border_style: BorderStyle::Dashed,
431 };
432
433 let widget = Widget::new(rect).with_appearance(appearance);
434 let dict = widget.to_annotation_dict();
435
436 if let Some(Object::Array(rect_array)) = dict.get("Rect") {
438 assert_eq!(rect_array.len(), 4);
439 assert_eq!(rect_array[0], Object::Real(10.0));
440 assert_eq!(rect_array[1], Object::Real(20.0));
441 assert_eq!(rect_array[2], Object::Real(110.0));
442 assert_eq!(rect_array[3], Object::Real(40.0));
443 } else {
444 panic!("Expected Rect array");
445 }
446
447 if let Some(Object::Dictionary(bs_dict)) = dict.get("BS") {
449 assert_eq!(bs_dict.get("W"), Some(&Object::Real(1.5)));
450 assert_eq!(bs_dict.get("S"), Some(&Object::Name("D".to_string())));
451 } else {
452 panic!("Expected BS dictionary");
453 }
454
455 if let Some(Object::Dictionary(mk_dict)) = dict.get("MK") {
457 if let Some(Object::Array(bc_array)) = mk_dict.get("BC") {
459 assert_eq!(bc_array.len(), 3);
460 assert_eq!(bc_array[0], Object::Real(1.0));
461 assert_eq!(bc_array[1], Object::Real(0.0));
462 assert_eq!(bc_array[2], Object::Real(0.0));
463 } else {
464 panic!("Expected BC array");
465 }
466
467 if let Some(Object::Array(bg_array)) = mk_dict.get("BG") {
469 assert_eq!(bg_array.len(), 3);
470 assert_eq!(bg_array[0], Object::Real(0.0));
471 assert_eq!(bg_array[1], Object::Real(1.0));
472 assert_eq!(bg_array[2], Object::Real(0.0));
473 } else {
474 panic!("Expected BG array");
475 }
476 } else {
477 panic!("Expected MK dictionary");
478 }
479
480 assert_eq!(dict.get("F"), Some(&Object::Integer(4)));
482 }
483
484 #[test]
485 fn test_widget_annotation_dict_gray_colors() {
486 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(50.0, 25.0));
487 let appearance = WidgetAppearance {
488 border_color: Some(Color::gray(0.3)),
489 background_color: Some(Color::gray(0.9)),
490 border_width: 0.5,
491 border_style: BorderStyle::Beveled,
492 };
493
494 let widget = Widget::new(rect).with_appearance(appearance);
495 let dict = widget.to_annotation_dict();
496
497 if let Some(Object::Dictionary(mk_dict)) = dict.get("MK") {
498 if let Some(Object::Array(bc_array)) = mk_dict.get("BC") {
500 assert_eq!(bc_array.len(), 1);
501 assert_eq!(bc_array[0], Object::Real(0.3));
502 } else {
503 panic!("Expected BC array");
504 }
505
506 if let Some(Object::Array(bg_array)) = mk_dict.get("BG") {
508 assert_eq!(bg_array.len(), 1);
509 assert_eq!(bg_array[0], Object::Real(0.9));
510 } else {
511 panic!("Expected BG array");
512 }
513 } else {
514 panic!("Expected MK dictionary");
515 }
516 }
517
518 #[test]
519 fn test_widget_annotation_dict_cmyk_colors() {
520 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(50.0, 25.0));
521 let appearance = WidgetAppearance {
522 border_color: Some(Color::cmyk(0.1, 0.2, 0.3, 0.4)),
523 background_color: Some(Color::cmyk(0.5, 0.6, 0.7, 0.8)),
524 border_width: 3.0,
525 border_style: BorderStyle::Inset,
526 };
527
528 let widget = Widget::new(rect).with_appearance(appearance);
529 let dict = widget.to_annotation_dict();
530
531 if let Some(Object::Dictionary(mk_dict)) = dict.get("MK") {
532 if let Some(Object::Array(bc_array)) = mk_dict.get("BC") {
534 assert_eq!(bc_array.len(), 4);
535 assert_eq!(bc_array[0], Object::Real(0.1));
536 assert_eq!(bc_array[1], Object::Real(0.2));
537 assert_eq!(bc_array[2], Object::Real(0.3));
538 assert_eq!(bc_array[3], Object::Real(0.4));
539 } else {
540 panic!("Expected BC array");
541 }
542
543 if let Some(Object::Array(bg_array)) = mk_dict.get("BG") {
545 assert_eq!(bg_array.len(), 4);
546 assert_eq!(bg_array[0], Object::Real(0.5));
547 assert_eq!(bg_array[1], Object::Real(0.6));
548 assert_eq!(bg_array[2], Object::Real(0.7));
549 assert_eq!(bg_array[3], Object::Real(0.8));
550 } else {
551 panic!("Expected BG array");
552 }
553 } else {
554 panic!("Expected MK dictionary");
555 }
556 }
557
558 #[test]
559 fn test_widget_annotation_dict_no_colors() {
560 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(50.0, 25.0));
561 let appearance = WidgetAppearance {
562 border_color: None,
563 background_color: None,
564 border_width: 2.0,
565 border_style: BorderStyle::Underline,
566 };
567
568 let widget = Widget::new(rect).with_appearance(appearance);
569 let dict = widget.to_annotation_dict();
570
571 if let Some(Object::Dictionary(mk_dict)) = dict.get("MK") {
572 assert!(mk_dict.get("BC").is_none());
573 assert!(mk_dict.get("BG").is_none());
574 } else {
575 panic!("Expected MK dictionary");
576 }
577 }
578
579 #[test]
580 fn test_field_options_with_values() {
581 let flags = FieldFlags {
582 read_only: true,
583 required: false,
584 no_export: true,
585 };
586
587 let options = FieldOptions {
588 flags,
589 default_appearance: Some("/Helv 12 Tf 0 g".to_string()),
590 quadding: Some(1), };
592
593 assert!(options.flags.read_only);
594 assert!(!options.flags.required);
595 assert!(options.flags.no_export);
596 assert_eq!(
597 options.default_appearance,
598 Some("/Helv 12 Tf 0 g".to_string())
599 );
600 assert_eq!(options.quadding, Some(1));
601 }
602
603 #[test]
604 fn test_field_options_debug_clone_default() {
605 let options = FieldOptions::default();
606 let debug_str = format!("{:?}", options);
607 assert!(debug_str.contains("FieldOptions"));
608
609 let cloned = options.clone();
610 assert_eq!(options.flags.read_only, cloned.flags.read_only);
611 assert_eq!(options.default_appearance, cloned.default_appearance);
612 assert_eq!(options.quadding, cloned.quadding);
613 }
614
615 #[test]
616 fn test_form_field_creation() {
617 let mut field_dict = Dictionary::new();
618 field_dict.set("T", Object::String("TestField".to_string()));
619 field_dict.set("FT", Object::Name("Tx".to_string()));
620
621 let form_field = FormField::new(field_dict);
622 assert_eq!(form_field.widgets.len(), 0);
623 assert_eq!(
624 form_field.field_dict.get("T"),
625 Some(&Object::String("TestField".to_string()))
626 );
627 }
628
629 #[test]
630 fn test_form_field_add_widget() {
631 let mut field_dict = Dictionary::new();
632 field_dict.set("T", Object::String("TestField".to_string()));
633
634 let mut form_field = FormField::new(field_dict);
635
636 let rect1 = Rectangle::new(Point::new(10.0, 10.0), Point::new(110.0, 30.0));
637 let widget1 = Widget::new(rect1);
638
639 let rect2 = Rectangle::new(Point::new(10.0, 50.0), Point::new(110.0, 70.0));
640 let widget2 = Widget::new(rect2);
641
642 form_field.add_widget(widget1);
643 form_field.add_widget(widget2);
644
645 assert_eq!(form_field.widgets.len(), 2);
646 assert_eq!(form_field.widgets[0].rect.lower_left.x, 10.0);
647 assert_eq!(form_field.widgets[1].rect.lower_left.y, 50.0);
648 }
649
650 #[test]
651 fn test_form_field_debug_clone() {
652 let field_dict = Dictionary::new();
653 let form_field = FormField::new(field_dict);
654
655 let debug_str = format!("{:?}", form_field);
656 assert!(debug_str.contains("FormField"));
657
658 let cloned = form_field.clone();
659 assert_eq!(form_field.widgets.len(), cloned.widgets.len());
660 }
661
662 #[test]
663 fn test_widget_rect_boundary_values() {
664 let test_rects = [
666 Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0)),
668 Rectangle::new(Point::new(5.0, 5.0), Point::new(6.0, 6.0)),
670 Rectangle::new(Point::new(0.0, 0.0), Point::new(1000.0, 800.0)),
672 Rectangle::new(Point::new(-50.0, -25.0), Point::new(50.0, 25.0)),
674 ];
675
676 for rect in test_rects {
677 let widget = Widget::new(rect);
678 let dict = widget.to_annotation_dict();
679
680 if let Some(Object::Array(rect_array)) = dict.get("Rect") {
681 assert_eq!(rect_array.len(), 4);
682 assert_eq!(rect_array[0], Object::Real(rect.lower_left.x));
683 assert_eq!(rect_array[1], Object::Real(rect.lower_left.y));
684 assert_eq!(rect_array[2], Object::Real(rect.upper_right.x));
685 assert_eq!(rect_array[3], Object::Real(rect.upper_right.y));
686 } else {
687 panic!("Expected Rect array");
688 }
689 }
690 }
691
692 #[test]
693 fn test_border_width_variations() {
694 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0));
695 let border_widths = [0.0, 0.5, 1.0, 2.5, 10.0];
696
697 for width in border_widths {
698 let appearance = WidgetAppearance {
699 border_color: Some(Color::black()),
700 background_color: None,
701 border_width: width,
702 border_style: BorderStyle::Solid,
703 };
704
705 let widget = Widget::new(rect).with_appearance(appearance);
706 let dict = widget.to_annotation_dict();
707
708 if let Some(Object::Dictionary(bs_dict)) = dict.get("BS") {
709 assert_eq!(bs_dict.get("W"), Some(&Object::Real(width)));
710 } else {
711 panic!("Expected BS dictionary");
712 }
713 }
714 }
715
716 #[test]
717 fn test_quadding_values() {
718 let test_quadding = [
719 (None, "no quadding"),
720 (Some(0), "left alignment"),
721 (Some(1), "center alignment"),
722 (Some(2), "right alignment"),
723 ];
724
725 for (quadding, description) in test_quadding {
726 let options = FieldOptions {
727 flags: FieldFlags::default(),
728 default_appearance: None,
729 quadding,
730 };
731
732 assert_eq!(options.quadding, quadding, "Failed for {}", description);
733 }
734 }
735}