1use crate::geometry::Rectangle;
4use crate::graphics::Color;
5use crate::objects::{Dictionary, Object};
6use std::collections::{HashMap, HashSet};
7
8#[derive(Debug, Clone, Copy, Default)]
10pub struct FieldFlags {
11 pub read_only: bool,
13 pub required: bool,
15 pub no_export: bool,
17}
18
19impl FieldFlags {
20 pub fn to_flags(&self) -> u32 {
22 let mut flags = 0u32;
23 if self.read_only {
24 flags |= 1 << 0;
25 }
26 if self.required {
27 flags |= 1 << 1;
28 }
29 if self.no_export {
30 flags |= 1 << 2;
31 }
32 flags
33 }
34}
35
36#[derive(Debug, Clone, Default)]
38pub struct FieldOptions {
39 pub flags: FieldFlags,
41 pub default_appearance: Option<String>,
43 pub quadding: Option<i32>,
45}
46
47#[derive(Debug, Clone)]
49pub struct WidgetAppearance {
50 pub border_color: Option<Color>,
52 pub background_color: Option<Color>,
54 pub border_width: f64,
56 pub border_style: BorderStyle,
58}
59
60#[derive(Debug, Clone, Copy)]
62pub enum BorderStyle {
63 Solid,
65 Dashed,
67 Beveled,
69 Inset,
71 Underline,
73}
74
75impl BorderStyle {
76 pub fn pdf_name(&self) -> &'static str {
78 match self {
79 BorderStyle::Solid => "S",
80 BorderStyle::Dashed => "D",
81 BorderStyle::Beveled => "B",
82 BorderStyle::Inset => "I",
83 BorderStyle::Underline => "U",
84 }
85 }
86}
87
88impl Default for WidgetAppearance {
89 fn default() -> Self {
90 Self {
91 border_color: Some(Color::black()),
92 background_color: None,
93 border_width: 1.0,
94 border_style: BorderStyle::Solid,
95 }
96 }
97}
98
99#[derive(Debug, Clone)]
101pub struct Widget {
102 pub rect: Rectangle,
104 pub appearance: WidgetAppearance,
106 pub parent: Option<String>,
108 pub appearance_streams: Option<crate::forms::AppearanceDictionary>,
110}
111
112impl Widget {
113 pub fn new(rect: Rectangle) -> Self {
115 Self {
116 rect,
117 appearance: WidgetAppearance::default(),
118 parent: None,
119 appearance_streams: None,
120 }
121 }
122
123 pub fn with_appearance(mut self, appearance: WidgetAppearance) -> Self {
125 self.appearance = appearance;
126 self
127 }
128
129 pub fn with_appearance_streams(mut self, streams: crate::forms::AppearanceDictionary) -> Self {
131 self.appearance_streams = Some(streams);
132 self
133 }
134
135 pub fn generate_appearance(
144 &mut self,
145 field_type: crate::forms::FieldType,
146 value: Option<&str>,
147 ) -> crate::error::Result<()> {
148 let _ = self.generate_appearance_with_font(field_type, value, None, None)?;
149 Ok(())
150 }
151
152 pub fn generate_appearance_with_font(
160 &mut self,
161 field_type: crate::forms::FieldType,
162 value: Option<&str>,
163 default_appearance: Option<&crate::forms::DefaultAppearance>,
164 custom_font: Option<&crate::fonts::Font>,
165 ) -> crate::error::Result<HashMap<String, HashSet<char>>> {
166 use crate::forms::{generate_field_appearance, AppearanceDictionary, AppearanceState};
167
168 let mut app_dict = AppearanceDictionary::new();
169 let mut merged: HashMap<String, HashSet<char>> = HashMap::new();
170
171 let normal =
173 generate_field_appearance(field_type, self, value, default_appearance, custom_font)?;
174 app_dict.set_appearance(AppearanceState::Normal, normal.stream);
175 for (font_name, chars) in normal.used_chars_by_font {
176 merged.entry(font_name).or_default().extend(chars);
177 }
178
179 if field_type == crate::forms::FieldType::Button {
183 let rollover = generate_field_appearance(
184 field_type,
185 self,
186 value,
187 default_appearance,
188 custom_font,
189 )?;
190 app_dict.set_appearance(AppearanceState::Rollover, rollover.stream);
191 for (font_name, chars) in rollover.used_chars_by_font {
192 merged.entry(font_name).or_default().extend(chars);
193 }
194
195 let down = generate_field_appearance(
196 field_type,
197 self,
198 value,
199 default_appearance,
200 custom_font,
201 )?;
202 app_dict.set_appearance(AppearanceState::Down, down.stream);
203 for (font_name, chars) in down.used_chars_by_font {
204 merged.entry(font_name).or_default().extend(chars);
205 }
206 }
207
208 self.appearance_streams = Some(app_dict);
209 Ok(merged)
210 }
211
212 pub fn to_annotation_dict(&self) -> Dictionary {
214 let mut dict = Dictionary::new();
215
216 dict.set("Type", Object::Name("Annot".to_string()));
218 dict.set("Subtype", Object::Name("Widget".to_string()));
219
220 let rect_array = vec![
222 Object::Real(self.rect.lower_left.x),
223 Object::Real(self.rect.lower_left.y),
224 Object::Real(self.rect.upper_right.x),
225 Object::Real(self.rect.upper_right.y),
226 ];
227 dict.set("Rect", Object::Array(rect_array));
228
229 let mut bs_dict = Dictionary::new();
231 bs_dict.set("W", Object::Real(self.appearance.border_width));
232 bs_dict.set(
233 "S",
234 Object::Name(self.appearance.border_style.pdf_name().to_string()),
235 );
236 dict.set("BS", Object::Dictionary(bs_dict));
237
238 let mut mk_dict = Dictionary::new();
240
241 if let Some(border_color) = &self.appearance.border_color {
242 let bc = match border_color {
243 Color::Rgb(r, g, b) => vec![Object::Real(*r), Object::Real(*g), Object::Real(*b)],
244 Color::Gray(g) => vec![Object::Real(*g)],
245 Color::Cmyk(c, m, y, k) => vec![
246 Object::Real(*c),
247 Object::Real(*m),
248 Object::Real(*y),
249 Object::Real(*k),
250 ],
251 };
252 mk_dict.set("BC", Object::Array(bc));
253 }
254
255 if let Some(bg_color) = &self.appearance.background_color {
256 let bg = match bg_color {
257 Color::Rgb(r, g, b) => vec![Object::Real(*r), Object::Real(*g), Object::Real(*b)],
258 Color::Gray(g) => vec![Object::Real(*g)],
259 Color::Cmyk(c, m, y, k) => vec![
260 Object::Real(*c),
261 Object::Real(*m),
262 Object::Real(*y),
263 Object::Real(*k),
264 ],
265 };
266 mk_dict.set("BG", Object::Array(bg));
267 }
268
269 dict.set("MK", Object::Dictionary(mk_dict));
270
271 dict.set("F", Object::Integer(4));
273
274 if let Some(ref app_streams) = self.appearance_streams {
276 dict.set("AP", Object::Dictionary(app_streams.to_dict()));
277 }
278
279 dict
280 }
281}
282
283pub trait Field {
285 fn name(&self) -> &str;
287
288 fn field_type(&self) -> &'static str;
290
291 fn to_dict(&self) -> Dictionary;
293}
294
295#[derive(Debug, Clone)]
297pub struct FormField {
298 pub field_dict: Dictionary,
300 pub widgets: Vec<Widget>,
302 pub default_appearance: Option<crate::forms::DefaultAppearance>,
307}
308
309impl FormField {
310 pub fn new(field_dict: Dictionary) -> Self {
312 Self {
313 field_dict,
314 widgets: Vec::new(),
315 default_appearance: None,
316 }
317 }
318
319 pub fn add_widget(&mut self, widget: Widget) {
321 self.widgets.push(widget);
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328 use crate::geometry::Point;
329
330 #[test]
331 fn test_field_flags() {
332 let flags = FieldFlags {
333 read_only: true,
334 required: true,
335 no_export: false,
336 };
337
338 assert_eq!(flags.to_flags(), 3); }
340
341 #[test]
342 fn test_border_style() {
343 assert_eq!(BorderStyle::Solid.pdf_name(), "S");
344 assert_eq!(BorderStyle::Dashed.pdf_name(), "D");
345 assert_eq!(BorderStyle::Beveled.pdf_name(), "B");
346 assert_eq!(BorderStyle::Inset.pdf_name(), "I");
347 assert_eq!(BorderStyle::Underline.pdf_name(), "U");
348 }
349
350 #[test]
351 fn test_widget_creation() {
352 let rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(200.0, 120.0));
353
354 let widget = Widget::new(rect);
355 assert_eq!(widget.rect.lower_left.x, 100.0);
356 assert_eq!(widget.appearance.border_width, 1.0);
357 }
358
359 #[test]
360 fn test_widget_annotation_dict() {
361 let rect = Rectangle::new(Point::new(50.0, 50.0), Point::new(150.0, 70.0));
362
363 let appearance = WidgetAppearance {
364 border_color: Some(Color::Rgb(0.0, 0.0, 1.0)),
365 background_color: Some(Color::Gray(0.9)),
366 border_width: 2.0,
367 border_style: BorderStyle::Solid,
368 };
369
370 let widget = Widget::new(rect).with_appearance(appearance);
371 let dict = widget.to_annotation_dict();
372
373 assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
374 assert_eq!(
375 dict.get("Subtype"),
376 Some(&Object::Name("Widget".to_string()))
377 );
378 assert!(dict.get("Rect").is_some());
379 assert!(dict.get("BS").is_some());
380 assert!(dict.get("MK").is_some());
381 }
382
383 #[test]
384 fn test_field_options_default() {
385 let options = FieldOptions::default();
386 assert!(!options.flags.read_only);
387 assert!(!options.flags.required);
388 assert!(!options.flags.no_export);
389 assert!(options.default_appearance.is_none());
390 assert!(options.quadding.is_none());
391 }
392
393 #[test]
394 fn test_field_flags_all_combinations() {
395 let flag_combos = [
397 (false, false, false, 0),
398 (true, false, false, 1),
399 (false, true, false, 2),
400 (false, false, true, 4),
401 (true, true, false, 3),
402 (true, false, true, 5),
403 (false, true, true, 6),
404 (true, true, true, 7),
405 ];
406
407 for (read_only, required, no_export, expected) in flag_combos {
408 let flags = FieldFlags {
409 read_only,
410 required,
411 no_export,
412 };
413 assert_eq!(
414 flags.to_flags(),
415 expected,
416 "Failed for read_only={read_only}, required={required}, no_export={no_export}"
417 );
418 }
419 }
420
421 #[test]
422 fn test_field_flags_debug_clone_default() {
423 let flags = FieldFlags::default();
424 let debug_str = format!("{flags:?}");
425 assert!(debug_str.contains("FieldFlags"));
426
427 let cloned = flags;
428 assert_eq!(flags.read_only, cloned.read_only);
429 assert_eq!(flags.required, cloned.required);
430 assert_eq!(flags.no_export, cloned.no_export);
431
432 let copied = flags;
434 assert_eq!(flags.read_only, copied.read_only);
435 }
436
437 #[test]
438 fn test_border_style_debug_clone_copy() {
439 let style = BorderStyle::Solid;
440 let debug_str = format!("{style:?}");
441 assert!(debug_str.contains("Solid"));
442
443 let cloned = style;
444 assert_eq!(style.pdf_name(), cloned.pdf_name());
445
446 let copied = style;
448 assert_eq!(style.pdf_name(), copied.pdf_name());
449 }
450
451 #[test]
452 fn test_all_border_styles() {
453 let styles = [
454 (BorderStyle::Solid, "S"),
455 (BorderStyle::Dashed, "D"),
456 (BorderStyle::Beveled, "B"),
457 (BorderStyle::Inset, "I"),
458 (BorderStyle::Underline, "U"),
459 ];
460
461 for (style, expected) in styles {
462 assert_eq!(style.pdf_name(), expected);
463 }
464 }
465
466 #[test]
467 fn test_widget_appearance_default() {
468 let appearance = WidgetAppearance::default();
469 assert_eq!(appearance.border_color, Some(Color::black()));
470 assert_eq!(appearance.background_color, None);
471 assert_eq!(appearance.border_width, 1.0);
472 match appearance.border_style {
473 BorderStyle::Solid => {}
474 _ => panic!("Expected solid border style"),
475 }
476 }
477
478 #[test]
479 fn test_widget_appearance_debug_clone() {
480 let appearance = WidgetAppearance {
481 border_color: Some(Color::red()),
482 background_color: Some(Color::gray(0.5)),
483 border_width: 2.5,
484 border_style: BorderStyle::Dashed,
485 };
486
487 let debug_str = format!("{appearance:?}");
488 assert!(debug_str.contains("WidgetAppearance"));
489
490 let cloned = appearance.clone();
491 assert_eq!(appearance.border_color, cloned.border_color);
492 assert_eq!(appearance.background_color, cloned.background_color);
493 assert_eq!(appearance.border_width, cloned.border_width);
494 }
495
496 #[test]
497 fn test_widget_debug_clone() {
498 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0));
499 let widget = Widget::new(rect);
500
501 let debug_str = format!("{widget:?}");
502 assert!(debug_str.contains("Widget"));
503
504 let cloned = widget.clone();
505 assert_eq!(widget.rect.lower_left.x, cloned.rect.lower_left.x);
506 assert_eq!(widget.parent, cloned.parent);
507 }
508
509 #[test]
510 fn test_widget_with_parent() {
511 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0));
512 let mut widget = Widget::new(rect);
513 widget.parent = Some("TextField1".to_string());
514
515 assert_eq!(widget.parent, Some("TextField1".to_string()));
516 }
517
518 #[test]
519 fn test_widget_annotation_dict_rgb_colors() {
520 let rect = Rectangle::new(Point::new(10.0, 20.0), Point::new(110.0, 40.0));
521 let appearance = WidgetAppearance {
522 border_color: Some(Color::rgb(1.0, 0.0, 0.0)),
523 background_color: Some(Color::rgb(0.0, 1.0, 0.0)),
524 border_width: 1.5,
525 border_style: BorderStyle::Dashed,
526 };
527
528 let widget = Widget::new(rect).with_appearance(appearance);
529 let dict = widget.to_annotation_dict();
530
531 if let Some(Object::Array(rect_array)) = dict.get("Rect") {
533 assert_eq!(rect_array.len(), 4);
534 assert_eq!(rect_array[0], Object::Real(10.0));
535 assert_eq!(rect_array[1], Object::Real(20.0));
536 assert_eq!(rect_array[2], Object::Real(110.0));
537 assert_eq!(rect_array[3], Object::Real(40.0));
538 } else {
539 panic!("Expected Rect array");
540 }
541
542 if let Some(Object::Dictionary(bs_dict)) = dict.get("BS") {
544 assert_eq!(bs_dict.get("W"), Some(&Object::Real(1.5)));
545 assert_eq!(bs_dict.get("S"), Some(&Object::Name("D".to_string())));
546 } else {
547 panic!("Expected BS dictionary");
548 }
549
550 if let Some(Object::Dictionary(mk_dict)) = dict.get("MK") {
552 if let Some(Object::Array(bc_array)) = mk_dict.get("BC") {
554 assert_eq!(bc_array.len(), 3);
555 assert_eq!(bc_array[0], Object::Real(1.0));
556 assert_eq!(bc_array[1], Object::Real(0.0));
557 assert_eq!(bc_array[2], Object::Real(0.0));
558 } else {
559 panic!("Expected BC array");
560 }
561
562 if let Some(Object::Array(bg_array)) = mk_dict.get("BG") {
564 assert_eq!(bg_array.len(), 3);
565 assert_eq!(bg_array[0], Object::Real(0.0));
566 assert_eq!(bg_array[1], Object::Real(1.0));
567 assert_eq!(bg_array[2], Object::Real(0.0));
568 } else {
569 panic!("Expected BG array");
570 }
571 } else {
572 panic!("Expected MK dictionary");
573 }
574
575 assert_eq!(dict.get("F"), Some(&Object::Integer(4)));
577 }
578
579 #[test]
580 fn test_widget_annotation_dict_gray_colors() {
581 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(50.0, 25.0));
582 let appearance = WidgetAppearance {
583 border_color: Some(Color::gray(0.3)),
584 background_color: Some(Color::gray(0.9)),
585 border_width: 0.5,
586 border_style: BorderStyle::Beveled,
587 };
588
589 let widget = Widget::new(rect).with_appearance(appearance);
590 let dict = widget.to_annotation_dict();
591
592 if let Some(Object::Dictionary(mk_dict)) = dict.get("MK") {
593 if let Some(Object::Array(bc_array)) = mk_dict.get("BC") {
595 assert_eq!(bc_array.len(), 1);
596 assert_eq!(bc_array[0], Object::Real(0.3));
597 } else {
598 panic!("Expected BC array");
599 }
600
601 if let Some(Object::Array(bg_array)) = mk_dict.get("BG") {
603 assert_eq!(bg_array.len(), 1);
604 assert_eq!(bg_array[0], Object::Real(0.9));
605 } else {
606 panic!("Expected BG array");
607 }
608 } else {
609 panic!("Expected MK dictionary");
610 }
611 }
612
613 #[test]
614 fn test_widget_annotation_dict_cmyk_colors() {
615 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(50.0, 25.0));
616 let appearance = WidgetAppearance {
617 border_color: Some(Color::cmyk(0.1, 0.2, 0.3, 0.4)),
618 background_color: Some(Color::cmyk(0.5, 0.6, 0.7, 0.8)),
619 border_width: 3.0,
620 border_style: BorderStyle::Inset,
621 };
622
623 let widget = Widget::new(rect).with_appearance(appearance);
624 let dict = widget.to_annotation_dict();
625
626 if let Some(Object::Dictionary(mk_dict)) = dict.get("MK") {
627 if let Some(Object::Array(bc_array)) = mk_dict.get("BC") {
629 assert_eq!(bc_array.len(), 4);
630 assert_eq!(bc_array[0], Object::Real(0.1));
631 assert_eq!(bc_array[1], Object::Real(0.2));
632 assert_eq!(bc_array[2], Object::Real(0.3));
633 assert_eq!(bc_array[3], Object::Real(0.4));
634 } else {
635 panic!("Expected BC array");
636 }
637
638 if let Some(Object::Array(bg_array)) = mk_dict.get("BG") {
640 assert_eq!(bg_array.len(), 4);
641 assert_eq!(bg_array[0], Object::Real(0.5));
642 assert_eq!(bg_array[1], Object::Real(0.6));
643 assert_eq!(bg_array[2], Object::Real(0.7));
644 assert_eq!(bg_array[3], Object::Real(0.8));
645 } else {
646 panic!("Expected BG array");
647 }
648 } else {
649 panic!("Expected MK dictionary");
650 }
651 }
652
653 #[test]
654 fn test_widget_annotation_dict_no_colors() {
655 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(50.0, 25.0));
656 let appearance = WidgetAppearance {
657 border_color: None,
658 background_color: None,
659 border_width: 2.0,
660 border_style: BorderStyle::Underline,
661 };
662
663 let widget = Widget::new(rect).with_appearance(appearance);
664 let dict = widget.to_annotation_dict();
665
666 if let Some(Object::Dictionary(mk_dict)) = dict.get("MK") {
667 assert!(mk_dict.get("BC").is_none());
668 assert!(mk_dict.get("BG").is_none());
669 } else {
670 panic!("Expected MK dictionary");
671 }
672 }
673
674 #[test]
675 fn test_field_options_with_values() {
676 let flags = FieldFlags {
677 read_only: true,
678 required: false,
679 no_export: true,
680 };
681
682 let options = FieldOptions {
683 flags,
684 default_appearance: Some("/Helv 12 Tf 0 g".to_string()),
685 quadding: Some(1), };
687
688 assert!(options.flags.read_only);
689 assert!(!options.flags.required);
690 assert!(options.flags.no_export);
691 assert_eq!(
692 options.default_appearance,
693 Some("/Helv 12 Tf 0 g".to_string())
694 );
695 assert_eq!(options.quadding, Some(1));
696 }
697
698 #[test]
699 fn test_field_options_debug_clone_default() {
700 let options = FieldOptions::default();
701 let debug_str = format!("{options:?}");
702 assert!(debug_str.contains("FieldOptions"));
703
704 let cloned = options.clone();
705 assert_eq!(options.flags.read_only, cloned.flags.read_only);
706 assert_eq!(options.default_appearance, cloned.default_appearance);
707 assert_eq!(options.quadding, cloned.quadding);
708 }
709
710 #[test]
711 fn test_form_field_creation() {
712 let mut field_dict = Dictionary::new();
713 field_dict.set("T", Object::String("TestField".to_string()));
714 field_dict.set("FT", Object::Name("Tx".to_string()));
715
716 let form_field = FormField::new(field_dict);
717 assert_eq!(form_field.widgets.len(), 0);
718 assert_eq!(
719 form_field.field_dict.get("T"),
720 Some(&Object::String("TestField".to_string()))
721 );
722 }
723
724 #[test]
725 fn test_form_field_add_widget() {
726 let mut field_dict = Dictionary::new();
727 field_dict.set("T", Object::String("TestField".to_string()));
728
729 let mut form_field = FormField::new(field_dict);
730
731 let rect1 = Rectangle::new(Point::new(10.0, 10.0), Point::new(110.0, 30.0));
732 let widget1 = Widget::new(rect1);
733
734 let rect2 = Rectangle::new(Point::new(10.0, 50.0), Point::new(110.0, 70.0));
735 let widget2 = Widget::new(rect2);
736
737 form_field.add_widget(widget1);
738 form_field.add_widget(widget2);
739
740 assert_eq!(form_field.widgets.len(), 2);
741 assert_eq!(form_field.widgets[0].rect.lower_left.x, 10.0);
742 assert_eq!(form_field.widgets[1].rect.lower_left.y, 50.0);
743 }
744
745 #[test]
746 fn test_form_field_debug_clone() {
747 let field_dict = Dictionary::new();
748 let form_field = FormField::new(field_dict);
749
750 let debug_str = format!("{form_field:?}");
751 assert!(debug_str.contains("FormField"));
752
753 let cloned = form_field.clone();
754 assert_eq!(form_field.widgets.len(), cloned.widgets.len());
755 }
756
757 #[test]
758 fn test_widget_rect_boundary_values() {
759 let test_rects = [
761 Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0)),
763 Rectangle::new(Point::new(5.0, 5.0), Point::new(6.0, 6.0)),
765 Rectangle::new(Point::new(0.0, 0.0), Point::new(1000.0, 800.0)),
767 Rectangle::new(Point::new(-50.0, -25.0), Point::new(50.0, 25.0)),
769 ];
770
771 for rect in test_rects {
772 let widget = Widget::new(rect);
773 let dict = widget.to_annotation_dict();
774
775 if let Some(Object::Array(rect_array)) = dict.get("Rect") {
776 assert_eq!(rect_array.len(), 4);
777 assert_eq!(rect_array[0], Object::Real(rect.lower_left.x));
778 assert_eq!(rect_array[1], Object::Real(rect.lower_left.y));
779 assert_eq!(rect_array[2], Object::Real(rect.upper_right.x));
780 assert_eq!(rect_array[3], Object::Real(rect.upper_right.y));
781 } else {
782 panic!("Expected Rect array");
783 }
784 }
785 }
786
787 #[test]
788 fn test_border_width_variations() {
789 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0));
790 let border_widths = [0.0, 0.5, 1.0, 2.5, 10.0];
791
792 for width in border_widths {
793 let appearance = WidgetAppearance {
794 border_color: Some(Color::black()),
795 background_color: None,
796 border_width: width,
797 border_style: BorderStyle::Solid,
798 };
799
800 let widget = Widget::new(rect).with_appearance(appearance);
801 let dict = widget.to_annotation_dict();
802
803 if let Some(Object::Dictionary(bs_dict)) = dict.get("BS") {
804 assert_eq!(bs_dict.get("W"), Some(&Object::Real(width)));
805 } else {
806 panic!("Expected BS dictionary");
807 }
808 }
809 }
810
811 #[test]
812 fn test_quadding_values() {
813 let test_quadding = [
814 (None, "no quadding"),
815 (Some(0), "left alignment"),
816 (Some(1), "center alignment"),
817 (Some(2), "right alignment"),
818 ];
819
820 for (quadding, description) in test_quadding {
821 let options = FieldOptions {
822 flags: FieldFlags::default(),
823 default_appearance: None,
824 quadding,
825 };
826
827 assert_eq!(options.quadding, quadding, "Failed for {description}");
828 }
829 }
830}