1use crate::error::Result;
7use crate::graphics::Color;
8use crate::objects::{Dictionary, Object, Stream};
9
10#[derive(Debug, Clone)]
12pub struct AppearanceCharacteristics {
13 pub rotation: i32,
15 pub border_color: Option<Color>,
17 pub background_color: Option<Color>,
19 pub normal_caption: Option<String>,
21 pub rollover_caption: Option<String>,
23 pub down_caption: Option<String>,
25 pub normal_icon: Option<Stream>,
27 pub rollover_icon: Option<Stream>,
29 pub down_icon: Option<Stream>,
31 pub icon_fit: Option<IconFit>,
33 pub text_position: TextPosition,
35}
36
37impl Default for AppearanceCharacteristics {
38 fn default() -> Self {
39 Self {
40 rotation: 0,
41 border_color: None,
42 background_color: None,
43 normal_caption: None,
44 rollover_caption: None,
45 down_caption: None,
46 normal_icon: None,
47 rollover_icon: None,
48 down_icon: None,
49 icon_fit: None,
50 text_position: TextPosition::CaptionOnly,
51 }
52 }
53}
54
55#[derive(Debug, Clone)]
57pub struct IconFit {
58 pub scale_type: IconScaleType,
60 pub scale_when: IconScaleWhen,
62 pub align_x: f64,
64 pub align_y: f64,
66 pub fit_bounds: bool,
68}
69
70#[derive(Debug, Clone, PartialEq)]
72pub enum IconScaleType {
73 Always,
75 Bigger,
77 Smaller,
79 Never,
81}
82
83#[derive(Debug, Clone, PartialEq)]
85pub enum IconScaleWhen {
86 Always,
88 IconBigger,
90 IconSmaller,
92 Never,
94}
95
96#[derive(Debug, Clone, PartialEq)]
98pub enum TextPosition {
99 CaptionOnly,
101 IconOnly,
103 CaptionBelowIcon,
105 CaptionAboveIcon,
107 CaptionRightIcon,
109 CaptionLeftIcon,
111 CaptionOverlayIcon,
113}
114
115impl AppearanceCharacteristics {
116 pub fn to_dict(&self) -> Dictionary {
118 let mut dict = Dictionary::new();
119
120 if self.rotation != 0 {
121 dict.set("R", Object::Integer(self.rotation as i64));
122 }
123
124 if let Some(color) = &self.border_color {
125 dict.set("BC", color.to_array());
126 }
127
128 if let Some(color) = &self.background_color {
129 dict.set("BG", color.to_array());
130 }
131
132 if let Some(caption) = &self.normal_caption {
133 dict.set("CA", Object::String(caption.clone()));
134 }
135
136 if let Some(caption) = &self.rollover_caption {
137 dict.set("RC", Object::String(caption.clone()));
138 }
139
140 if let Some(caption) = &self.down_caption {
141 dict.set("AC", Object::String(caption.clone()));
142 }
143
144 if let Some(fit) = &self.icon_fit {
145 dict.set("IF", fit.to_dict());
146 }
147
148 dict.set("TP", Object::Integer(self.text_position.to_int()));
149
150 dict
151 }
152}
153
154impl IconFit {
155 pub fn to_dict(&self) -> Object {
157 let mut dict = Dictionary::new();
158
159 dict.set(
160 "SW",
161 Object::Name(
162 match self.scale_when {
163 IconScaleWhen::Always => "A",
164 IconScaleWhen::IconBigger => "B",
165 IconScaleWhen::IconSmaller => "S",
166 IconScaleWhen::Never => "N",
167 }
168 .to_string(),
169 ),
170 );
171
172 dict.set(
173 "S",
174 Object::Name(
175 match self.scale_type {
176 IconScaleType::Always => "A",
177 IconScaleType::Bigger => "B",
178 IconScaleType::Smaller => "S",
179 IconScaleType::Never => "N",
180 }
181 .to_string(),
182 ),
183 );
184
185 dict.set(
186 "A",
187 Object::Array(vec![Object::Real(self.align_x), Object::Real(self.align_y)]),
188 );
189
190 if self.fit_bounds {
191 dict.set("FB", Object::Boolean(true));
192 }
193
194 Object::Dictionary(dict)
195 }
196}
197
198impl TextPosition {
199 pub fn to_int(&self) -> i64 {
200 match self {
201 TextPosition::CaptionOnly => 0,
202 TextPosition::IconOnly => 1,
203 TextPosition::CaptionBelowIcon => 2,
204 TextPosition::CaptionAboveIcon => 3,
205 TextPosition::CaptionRightIcon => 4,
206 TextPosition::CaptionLeftIcon => 5,
207 TextPosition::CaptionOverlayIcon => 6,
208 }
209 }
210}
211
212pub struct FieldAppearanceGenerator {
214 pub value: String,
216 pub font: String,
218 pub font_size: f64,
220 pub text_color: Color,
222 pub background_color: Option<Color>,
224 pub border_color: Option<Color>,
226 pub border_width: f64,
228 pub rect: [f64; 4],
230 pub alignment: TextAlignment,
232 pub multiline: bool,
234 pub max_length: Option<usize>,
236 pub comb: bool,
238}
239
240#[derive(Debug, Clone, Copy, PartialEq)]
242pub enum TextAlignment {
243 Left,
244 Center,
245 Right,
246}
247
248impl FieldAppearanceGenerator {
249 pub fn generate_text_field(&self) -> Result<Stream> {
251 let mut ops = Vec::new();
252 let width = self.rect[2] - self.rect[0];
253 let height = self.rect[3] - self.rect[1];
254
255 ops.push("q".to_string());
257
258 if let Some(bg_color) = &self.background_color {
260 ops.push(format!(
261 "{} {} {} rg",
262 bg_color.r(),
263 bg_color.g(),
264 bg_color.b()
265 ));
266 ops.push(format!("0 0 {} {} re", width, height));
267 ops.push("f".to_string());
268 }
269
270 if let Some(border_color) = &self.border_color {
272 if self.border_width > 0.0 {
273 ops.push(format!("{} w", self.border_width));
274 ops.push(format!(
275 "{} {} {} RG",
276 border_color.r(),
277 border_color.g(),
278 border_color.b()
279 ));
280 ops.push(format!(
281 "{} {} {} {} re",
282 self.border_width / 2.0,
283 self.border_width / 2.0,
284 width - self.border_width,
285 height - self.border_width
286 ));
287 ops.push("S".to_string());
288 }
289 }
290
291 ops.push("BT".to_string());
293
294 ops.push(format!("/{} {} Tf", self.font, self.font_size));
296
297 ops.push(format!(
299 "{} {} {} rg",
300 self.text_color.r(),
301 self.text_color.g(),
302 self.text_color.b()
303 ));
304
305 let padding = 2.0;
307 let text_y = height / 2.0 - self.font_size / 2.0;
308
309 if self.comb {
310 if let Some(max_len) = self.max_length {
311 let char_width = (width - 2.0 * padding) / max_len as f64;
313
314 for (i, ch) in self.value.chars().take(max_len).enumerate() {
315 let x = padding + (i as f64 + 0.5) * char_width;
316 ops.push(format!("{} {} Td", x, text_y));
317 ops.push(format!("({}) Tj", escape_string(&ch.to_string())));
318 if i < self.value.len() - 1 {
319 ops.push(format!("{} 0 Td", -x));
320 }
321 }
322 }
323 } else if self.multiline {
324 let lines = self.value.lines();
326 let line_height = self.font_size * 1.2;
327 let mut y = height - padding - self.font_size;
328
329 for line in lines {
330 let x = match self.alignment {
331 TextAlignment::Left => padding,
332 TextAlignment::Center => width / 2.0,
333 TextAlignment::Right => width - padding,
334 };
335
336 ops.push(format!("{} {} Td", x, y));
337 ops.push(format!("({}) Tj", escape_string(line)));
338
339 y -= line_height;
340 if y < padding {
341 break;
342 }
343 }
344 } else {
345 let x = match self.alignment {
347 TextAlignment::Left => padding,
348 TextAlignment::Center => width / 2.0,
349 TextAlignment::Right => width - padding,
350 };
351
352 ops.push(format!("{} {} Td", x, text_y));
353 ops.push(format!("({}) Tj", escape_string(&self.value)));
354 }
355
356 ops.push("ET".to_string());
358
359 ops.push("Q".to_string());
361
362 let content = ops.join("\n");
363
364 let mut stream = Stream::new(content.into_bytes());
365 stream
366 .dictionary_mut()
367 .set("Type", Object::Name("XObject".to_string()));
368 stream
369 .dictionary_mut()
370 .set("Subtype", Object::Name("Form".to_string()));
371 stream.dictionary_mut().set(
372 "BBox",
373 Object::Array(vec![
374 Object::Real(0.0),
375 Object::Real(0.0),
376 Object::Real(width),
377 Object::Real(height),
378 ]),
379 );
380
381 Ok(stream)
382 }
383}
384
385pub struct ButtonAppearanceGenerator {
387 pub style: ButtonStyle,
389 pub size: f64,
391 pub border_color: Color,
393 pub background_color: Color,
395 pub check_color: Color,
397 pub border_width: f64,
399}
400
401#[derive(Debug, Clone, Copy, PartialEq)]
403pub enum ButtonStyle {
404 Check,
406 Cross,
408 Diamond,
410 Circle,
412 Star,
414 Square,
416 Radio,
418}
419
420impl ButtonAppearanceGenerator {
421 pub fn generate_checked(&self) -> Result<Stream> {
423 let mut ops = Vec::new();
424
425 ops.push("q".to_string());
427
428 ops.push(format!(
430 "{} {} {} rg",
431 self.background_color.r(),
432 self.background_color.g(),
433 self.background_color.b()
434 ));
435
436 match self.style {
437 ButtonStyle::Radio => {
438 self.draw_circle(&mut ops, self.size / 2.0, self.size / 2.0, self.size / 2.0);
440 ops.push("f".to_string());
441
442 if self.border_width > 0.0 {
444 ops.push(format!("{} w", self.border_width));
445 ops.push(format!(
446 "{} {} {} RG",
447 self.border_color.r(),
448 self.border_color.g(),
449 self.border_color.b()
450 ));
451 ops.push("s".to_string());
452 }
453
454 let dot_size = self.size * 0.3;
456 ops.push(format!(
457 "{} {} {} rg",
458 self.check_color.r(),
459 self.check_color.g(),
460 self.check_color.b()
461 ));
462 self.draw_circle(&mut ops, self.size / 2.0, self.size / 2.0, dot_size);
463 ops.push("f".to_string());
464 }
465 _ => {
466 ops.push(format!("0 0 {} {} re", self.size, self.size));
468 ops.push("f".to_string());
469
470 if self.border_width > 0.0 {
472 ops.push(format!("{} w", self.border_width));
473 ops.push(format!(
474 "{} {} {} RG",
475 self.border_color.r(),
476 self.border_color.g(),
477 self.border_color.b()
478 ));
479 ops.push(format!(
480 "{} {} {} {} re",
481 self.border_width / 2.0,
482 self.border_width / 2.0,
483 self.size - self.border_width,
484 self.size - self.border_width
485 ));
486 ops.push("S".to_string());
487 }
488
489 ops.push(format!(
491 "{} {} {} rg",
492 self.check_color.r(),
493 self.check_color.g(),
494 self.check_color.b()
495 ));
496
497 self.draw_check_style(&mut ops);
498 }
499 }
500
501 ops.push("Q".to_string());
503
504 let content = ops.join("\n");
505
506 let mut stream = Stream::new(content.into_bytes());
507 stream
508 .dictionary_mut()
509 .set("Type", Object::Name("XObject".to_string()));
510 stream
511 .dictionary_mut()
512 .set("Subtype", Object::Name("Form".to_string()));
513 stream.dictionary_mut().set(
514 "BBox",
515 Object::Array(vec![
516 Object::Real(0.0),
517 Object::Real(0.0),
518 Object::Real(self.size),
519 Object::Real(self.size),
520 ]),
521 );
522
523 Ok(stream)
524 }
525
526 pub fn generate_unchecked(&self) -> Result<Stream> {
528 let mut ops = Vec::new();
529
530 ops.push("q".to_string());
532
533 ops.push(format!(
535 "{} {} {} rg",
536 self.background_color.r(),
537 self.background_color.g(),
538 self.background_color.b()
539 ));
540
541 if self.style == ButtonStyle::Radio {
542 self.draw_circle(&mut ops, self.size / 2.0, self.size / 2.0, self.size / 2.0);
544 ops.push("f".to_string());
545
546 if self.border_width > 0.0 {
548 ops.push(format!("{} w", self.border_width));
549 ops.push(format!(
550 "{} {} {} RG",
551 self.border_color.r(),
552 self.border_color.g(),
553 self.border_color.b()
554 ));
555 ops.push("s".to_string());
556 }
557 } else {
558 ops.push(format!("0 0 {} {} re", self.size, self.size));
560 ops.push("f".to_string());
561
562 if self.border_width > 0.0 {
564 ops.push(format!("{} w", self.border_width));
565 ops.push(format!(
566 "{} {} {} RG",
567 self.border_color.r(),
568 self.border_color.g(),
569 self.border_color.b()
570 ));
571 ops.push(format!(
572 "{} {} {} {} re",
573 self.border_width / 2.0,
574 self.border_width / 2.0,
575 self.size - self.border_width,
576 self.size - self.border_width
577 ));
578 ops.push("S".to_string());
579 }
580 }
581
582 ops.push("Q".to_string());
584
585 let content = ops.join("\n");
586
587 let mut stream = Stream::new(content.into_bytes());
588 stream
589 .dictionary_mut()
590 .set("Type", Object::Name("XObject".to_string()));
591 stream
592 .dictionary_mut()
593 .set("Subtype", Object::Name("Form".to_string()));
594 stream.dictionary_mut().set(
595 "BBox",
596 Object::Array(vec![
597 Object::Real(0.0),
598 Object::Real(0.0),
599 Object::Real(self.size),
600 Object::Real(self.size),
601 ]),
602 );
603
604 Ok(stream)
605 }
606
607 fn draw_circle(&self, ops: &mut Vec<String>, cx: f64, cy: f64, r: f64) {
608 let k = 0.552284749831; ops.push(format!("{} {} m", cx + r, cy));
612 ops.push(format!(
613 "{} {} {} {} {} {} c",
614 cx + r,
615 cy + r * k,
616 cx + r * k,
617 cy + r,
618 cx,
619 cy + r
620 ));
621 ops.push(format!(
622 "{} {} {} {} {} {} c",
623 cx - r * k,
624 cy + r,
625 cx - r,
626 cy + r * k,
627 cx - r,
628 cy
629 ));
630 ops.push(format!(
631 "{} {} {} {} {} {} c",
632 cx - r,
633 cy - r * k,
634 cx - r * k,
635 cy - r,
636 cx,
637 cy - r
638 ));
639 ops.push(format!(
640 "{} {} {} {} {} {} c",
641 cx + r * k,
642 cy - r,
643 cx + r,
644 cy - r * k,
645 cx + r,
646 cy
647 ));
648 }
649
650 fn draw_check_style(&self, ops: &mut Vec<String>) {
651 match self.style {
652 ButtonStyle::Check => {
653 ops.push(format!("{} w", self.size * 0.1));
655 ops.push(format!("{} {} m", self.size * 0.2, self.size * 0.5));
656 ops.push(format!("{} {} l", self.size * 0.4, self.size * 0.3));
657 ops.push(format!("{} {} l", self.size * 0.8, self.size * 0.7));
658 ops.push("S".to_string());
659 }
660 ButtonStyle::Cross => {
661 ops.push(format!("{} w", self.size * 0.1));
663 ops.push(format!("{} {} m", self.size * 0.2, self.size * 0.2));
664 ops.push(format!("{} {} l", self.size * 0.8, self.size * 0.8));
665 ops.push(format!("{} {} m", self.size * 0.2, self.size * 0.8));
666 ops.push(format!("{} {} l", self.size * 0.8, self.size * 0.2));
667 ops.push("S".to_string());
668 }
669 ButtonStyle::Diamond => {
670 ops.push(format!("{} {} m", self.size * 0.5, self.size * 0.8));
672 ops.push(format!("{} {} l", self.size * 0.8, self.size * 0.5));
673 ops.push(format!("{} {} l", self.size * 0.5, self.size * 0.2));
674 ops.push(format!("{} {} l", self.size * 0.2, self.size * 0.5));
675 ops.push("f".to_string());
676 }
677 ButtonStyle::Circle => {
678 self.draw_circle(ops, self.size / 2.0, self.size / 2.0, self.size * 0.3);
680 ops.push("f".to_string());
681 }
682 ButtonStyle::Star => {
683 let cx = self.size / 2.0;
685 let cy = self.size / 2.0;
686 let r = self.size * 0.4;
687
688 ops.push(format!("{} {} m", cx, cy + r));
689 for i in 1..10 {
690 let angle = i as f64 * 36.0 * std::f64::consts::PI / 180.0;
691 let radius = if i % 2 == 0 { r } else { r * 0.5 };
692 let x = cx + radius * angle.sin();
693 let y = cy + radius * angle.cos();
694 ops.push(format!("{} {} l", x, y));
695 }
696 ops.push("f".to_string());
697 }
698 ButtonStyle::Square => {
699 let inset = self.size * 0.25;
701 ops.push(format!(
702 "{} {} {} {} re",
703 inset,
704 inset,
705 self.size - 2.0 * inset,
706 self.size - 2.0 * inset
707 ));
708 ops.push("f".to_string());
709 }
710 _ => {}
711 }
712 }
713}
714
715fn escape_string(s: &str) -> String {
717 s.chars()
718 .map(|c| match c {
719 '(' => "\\(".to_string(),
720 ')' => "\\)".to_string(),
721 '\\' => "\\\\".to_string(),
722 '\n' => "\\n".to_string(),
723 '\r' => "\\r".to_string(),
724 '\t' => "\\t".to_string(),
725 c => c.to_string(),
726 })
727 .collect()
728}
729
730pub struct PushButtonAppearanceGenerator {
732 pub caption: String,
734 pub font: String,
736 pub font_size: f64,
738 pub text_color: Color,
740 pub background_color: Color,
742 pub border_color: Color,
744 pub border_width: f64,
746 pub size: [f64; 2],
748 pub border_style: ButtonBorderStyle,
750}
751
752#[derive(Debug, Clone, Copy, PartialEq)]
754pub enum ButtonBorderStyle {
755 Solid,
757 Dashed,
759 Beveled,
761 Inset,
763 Underline,
765}
766
767impl PushButtonAppearanceGenerator {
768 pub fn generate_normal(&self) -> Result<Stream> {
770 self.generate_appearance(false)
771 }
772
773 pub fn generate_rollover(&self) -> Result<Stream> {
775 let mut appearance = self.clone();
777 appearance.background_color = appearance.background_color.lighten(0.1);
778 appearance.generate_appearance(false)
779 }
780
781 pub fn generate_down(&self) -> Result<Stream> {
783 self.generate_appearance(true)
784 }
785
786 fn generate_appearance(&self, pressed: bool) -> Result<Stream> {
787 let mut ops = Vec::new();
788 let [width, height] = self.size;
789
790 ops.push("q".to_string());
792
793 let bg_color = if pressed {
795 self.background_color.darken(0.1)
796 } else {
797 self.background_color
798 };
799
800 ops.push(format!(
801 "{} {} {} rg",
802 bg_color.r(),
803 bg_color.g(),
804 bg_color.b()
805 ));
806 ops.push(format!("0 0 {} {} re", width, height));
807 ops.push("f".to_string());
808
809 self.draw_border(&mut ops, width, height, pressed);
811
812 if !self.caption.is_empty() {
814 ops.push("BT".to_string());
815 ops.push(format!("/{} {} Tf", self.font, self.font_size));
816 ops.push(format!(
817 "{} {} {} rg",
818 self.text_color.r(),
819 self.text_color.g(),
820 self.text_color.b()
821 ));
822
823 let text_x = width / 2.0;
825 let text_y = height / 2.0 - self.font_size / 2.0;
826
827 ops.push(format!("{} {} Td", text_x, text_y));
828 ops.push(format!("({}) Tj", escape_string(&self.caption)));
829 ops.push("ET".to_string());
830 }
831
832 ops.push("Q".to_string());
834
835 let content = ops.join("\n");
836
837 let mut stream = Stream::new(content.into_bytes());
838 stream
839 .dictionary_mut()
840 .set("Type", Object::Name("XObject".to_string()));
841 stream
842 .dictionary_mut()
843 .set("Subtype", Object::Name("Form".to_string()));
844 stream.dictionary_mut().set(
845 "BBox",
846 Object::Array(vec![
847 Object::Real(0.0),
848 Object::Real(0.0),
849 Object::Real(width),
850 Object::Real(height),
851 ]),
852 );
853
854 Ok(stream)
855 }
856
857 fn draw_border(&self, ops: &mut Vec<String>, width: f64, height: f64, pressed: bool) {
858 match self.border_style {
859 ButtonBorderStyle::Solid => {
860 if self.border_width > 0.0 {
861 ops.push(format!("{} w", self.border_width));
862 ops.push(format!(
863 "{} {} {} RG",
864 self.border_color.r(),
865 self.border_color.g(),
866 self.border_color.b()
867 ));
868 ops.push(format!(
869 "{} {} {} {} re",
870 self.border_width / 2.0,
871 self.border_width / 2.0,
872 width - self.border_width,
873 height - self.border_width
874 ));
875 ops.push("S".to_string());
876 }
877 }
878 ButtonBorderStyle::Dashed => {
879 if self.border_width > 0.0 {
880 ops.push(format!("{} w", self.border_width));
881 ops.push("[3 3] 0 d".to_string()); ops.push(format!(
883 "{} {} {} RG",
884 self.border_color.r(),
885 self.border_color.g(),
886 self.border_color.b()
887 ));
888 ops.push(format!(
889 "{} {} {} {} re",
890 self.border_width / 2.0,
891 self.border_width / 2.0,
892 width - self.border_width,
893 height - self.border_width
894 ));
895 ops.push("S".to_string());
896 }
897 }
898 ButtonBorderStyle::Beveled | ButtonBorderStyle::Inset => {
899 let is_inset = self.border_style == ButtonBorderStyle::Inset || pressed;
900 let light_color = if is_inset {
901 self.border_color.darken(0.3)
902 } else {
903 self.border_color.lighten(0.3)
904 };
905 let dark_color = if is_inset {
906 self.border_color.lighten(0.3)
907 } else {
908 self.border_color.darken(0.3)
909 };
910
911 ops.push(format!("{} w", self.border_width));
913 ops.push(format!(
914 "{} {} {} RG",
915 light_color.r(),
916 light_color.g(),
917 light_color.b()
918 ));
919 ops.push(format!("{} {} m", 0.0, 0.0));
920 ops.push(format!("{} {} l", 0.0, height));
921 ops.push(format!("{} {} l", width, height));
922 ops.push("S".to_string());
923
924 ops.push(format!(
926 "{} {} {} RG",
927 dark_color.r(),
928 dark_color.g(),
929 dark_color.b()
930 ));
931 ops.push(format!("{} {} m", width, height));
932 ops.push(format!("{} {} l", width, 0.0));
933 ops.push(format!("{} {} l", 0.0, 0.0));
934 ops.push("S".to_string());
935 }
936 ButtonBorderStyle::Underline => {
937 if self.border_width > 0.0 {
938 ops.push(format!("{} w", self.border_width));
939 ops.push(format!(
940 "{} {} {} RG",
941 self.border_color.r(),
942 self.border_color.g(),
943 self.border_color.b()
944 ));
945 ops.push(format!("{} {} m", 0.0, self.border_width / 2.0));
946 ops.push(format!("{} {} l", width, self.border_width / 2.0));
947 ops.push("S".to_string());
948 }
949 }
950 }
951 }
952}
953
954impl Clone for PushButtonAppearanceGenerator {
955 fn clone(&self) -> Self {
956 Self {
957 caption: self.caption.clone(),
958 font: self.font.clone(),
959 font_size: self.font_size,
960 text_color: self.text_color,
961 background_color: self.background_color,
962 border_color: self.border_color,
963 border_width: self.border_width,
964 size: self.size,
965 border_style: self.border_style,
966 }
967 }
968}
969
970impl Color {
971 pub fn lighten(&self, amount: f64) -> Color {
972 Color::rgb(
973 (self.r() + amount).min(1.0),
974 (self.g() + amount).min(1.0),
975 (self.b() + amount).min(1.0),
976 )
977 }
978
979 pub fn darken(&self, amount: f64) -> Color {
980 Color::rgb(
981 (self.r() - amount).max(0.0),
982 (self.g() - amount).max(0.0),
983 (self.b() - amount).max(0.0),
984 )
985 }
986
987 pub fn to_array(&self) -> Object {
988 Object::Array(vec![
989 Object::Real(self.r()),
990 Object::Real(self.g()),
991 Object::Real(self.b()),
992 ])
993 }
994}