pdf_writer/
annotations.rs

1use super::*;
2
3/// Writer for an _annotation dictionary_.
4///
5/// An array of this struct is created by [`Chunk::annotation`].
6pub struct Annotation<'a> {
7    pub(crate) dict: Dict<'a>,
8}
9
10writer!(Annotation: |obj| {
11    let mut dict = obj.dict();
12    dict.pair(Name(b"Type"), Name(b"Annot"));
13    Self { dict }
14});
15
16impl Annotation<'_> {
17    /// Write the `/Subtype` attribute to tell the viewer the type of this
18    /// particular annotation.
19    pub fn subtype(&mut self, kind: AnnotationType) -> &mut Self {
20        self.pair(Name(b"Subtype"), kind.to_name());
21        self
22    }
23
24    /// Write the `/Rect` attribute. This is the location and dimensions of the
25    /// annotation on the page.
26    pub fn rect(&mut self, rect: Rect) -> &mut Self {
27        self.pair(Name(b"Rect"), rect);
28        self
29    }
30
31    /// Write the `/Contents` attribute. This is the content or alt-text,
32    /// depending on the [`AnnotationType`].
33    pub fn contents(&mut self, text: TextStr) -> &mut Self {
34        self.pair(Name(b"Contents"), text);
35        self
36    }
37
38    /// Write the `/P` attribute. This provides an indirect reference to the
39    /// page object with which this annotation is associated. Required for the
40    /// subtype `Screen` associated with rendition actions. PDF 1.3+.
41    pub fn page(&mut self, id: Ref) -> &mut Self {
42        self.pair(Name(b"P"), id);
43        self
44    }
45
46    /// Write the `/NM` attribute. This uniquely identifies the annotation on the
47    /// page. PDF 1.3+.
48    pub fn name(&mut self, text: TextStr) -> &mut Self {
49        self.pair(Name(b"NM"), text);
50        self
51    }
52
53    /// Write the `/M` attribute, specifying the date the annotation was last
54    /// modified. PDF 1.1+.
55    pub fn modified(&mut self, date: Date) -> &mut Self {
56        self.pair(Name(b"M"), date);
57        self
58    }
59
60    /// Write the `/F` attribute.
61    ///
62    /// Required for all annotations in PDF/A except for `Popup`.
63    pub fn flags(&mut self, flags: AnnotationFlags) -> &mut Self {
64        self.pair(Name(b"F"), flags.bits() as i32);
65        self
66    }
67
68    /// Start writing the `/AP` dictionary to set how the annotation shall
69    /// be presented visually. If this dictionary contains sub dictionaries,
70    /// [`Self::appearance_state`] must be set. PDF 1.2+.
71    ///
72    /// Required for many annotations in PDF/A.
73    pub fn appearance(&mut self) -> Appearance<'_> {
74        self.insert(Name(b"AP")).start()
75    }
76
77    /// Write the `/AS` attribute to set the annotation's current appearance
78    /// state from the `/AP` subdictionary. Must be set if [`Self::appearance`]
79    /// has one or more subdictionaries. PDF 1.2+.
80    pub fn appearance_state(&mut self, name: Name) -> &mut Self {
81        self.pair(Name(b"AS"), name);
82        self
83    }
84
85    /// Write the `/Border` attribute. This describes the look of the border
86    /// around the annotation, including width and horizontal and vertical
87    /// border radii. The function may also receive a dash pattern which
88    /// specifies the lengths and gaps of the border segments on a dashed
89    /// border. Although all PDF versions accept `/Border`, this feature
90    /// specifically is only available in PDF 1.1 or later.
91    pub fn border(
92        &mut self,
93        h_radius: f32,
94        v_radius: f32,
95        width: f32,
96        dash_pattern: Option<&[f32]>,
97    ) -> &mut Self {
98        let mut array = self.insert(Name(b"Border")).array();
99        array.item(h_radius);
100        array.item(v_radius);
101        array.item(width);
102
103        if let Some(pattern) = dash_pattern {
104            array.push().array().items(pattern);
105        }
106
107        array.finish();
108        self
109    }
110
111    /// Start writing the `/BS` attribute. These are some more elaborate border
112    /// settings taking precedence over `/B` for some annotation types. PDF 1.2+.
113    pub fn border_style(&mut self) -> BorderStyle<'_> {
114        self.insert(Name(b"BS")).start()
115    }
116
117    /// Write the `/C` attribute forcing a transparent color. This sets the
118    /// annotations background color and its popup title bar color. PDF 1.1+.
119    pub fn color_transparent(&mut self) -> &mut Self {
120        self.insert(Name(b"C")).array();
121        self
122    }
123
124    /// Write the `/C` attribute using a grayscale color. This sets the
125    /// annotations background color and its popup title bar color. PDF 1.1+.
126    pub fn color_gray(&mut self, gray: f32) -> &mut Self {
127        self.insert(Name(b"C")).array().item(gray);
128        self
129    }
130
131    /// Write the `/C` attribute using an RGB color. This sets the annotations
132    /// background color and its popup title bar color. PDF 1.1+.
133    pub fn color_rgb(&mut self, r: f32, g: f32, b: f32) -> &mut Self {
134        self.insert(Name(b"C")).array().items([r, g, b]);
135        self
136    }
137
138    /// Write the `/C` attribute using a CMYK color. This sets the annotations
139    /// background color and its popup title bar color. PDF 1.1+.
140    pub fn color_cmyk(&mut self, c: f32, m: f32, y: f32, k: f32) -> &mut Self {
141        self.insert(Name(b"C")).array().items([c, m, y, k]);
142        self
143    }
144
145    /// Write the `/StructParent` attribute to indicate the [structure tree
146    /// element][StructElement] this annotation belongs to. PDF 1.3+.
147    pub fn struct_parent(&mut self, key: i32) -> &mut Self {
148        self.pair(Name(b"StructParent"), key);
149        self
150    }
151
152    /// Start writing the `/A` dictionary. Only permissible for the subtypes
153    /// `Link` and `Widget`.
154    ///
155    /// Note that this attribute is forbidden in PDF/A.
156    pub fn action(&mut self) -> Action<'_> {
157        self.insert(Name(b"A")).start()
158    }
159
160    /// Start writing the `/AA` dictionary. Only permissible for the subtype
161    /// `Widget`. PDF 1.3+.
162    ///
163    /// Note that this attribute is forbidden in PDF/A.
164    pub fn additional_actions(&mut self) -> AdditionalActions<'_> {
165        self.insert(Name(b"AA")).start()
166    }
167
168    /// Write the `/H` attribute to set what effect is used to convey that the
169    /// user is pressing a link or widget annotation. Only permissible for the
170    /// subtypes `Link` and `Widget`. PDF 1.2+.
171    pub fn highlight(&mut self, effect: HighlightEffect) -> &mut Self {
172        self.pair(Name(b"H"), effect.to_name());
173        self
174    }
175
176    /// Write the `/T` attribute. This is in the title bar of markup annotations
177    /// and should be the name of the annotation author. PDF 1.1+.
178    pub fn author(&mut self, text: TextStr) -> &mut Self {
179        self.pair(Name(b"T"), text);
180        self
181    }
182
183    /// Write the `/Subj` attribute. This is the subject of the annotation.
184    /// PDF 1.5+.
185    pub fn subject(&mut self, text: TextStr) -> &mut Self {
186        self.pair(Name(b"Subj"), text);
187        self
188    }
189
190    /// Write the `/QuadPoints` attribute, specifying the region in which the
191    /// link should be activated. PDF 1.6+.
192    pub fn quad_points(
193        &mut self,
194        coordinates: impl IntoIterator<Item = f32>,
195    ) -> &mut Self {
196        self.insert(Name(b"QuadPoints")).array().items(coordinates);
197        self
198    }
199
200    /// Write the `/L` attribute. This defines the start and end point of a
201    /// line annotation
202    pub fn line_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) -> &mut Self {
203        self.insert(Name(b"L")).array().items([x1, y1, x2, y2]);
204        self
205    }
206
207    /// Start writing the `/FS` attribute, setting which file to reference.
208    pub fn file_spec(&mut self) -> FileSpec<'_> {
209        self.insert(Name(b"FS")).start()
210    }
211
212    /// Write the `/Name` attribute. Refer to the specification to see which
213    /// names are allowed for which annotation types.
214    pub fn icon(&mut self, icon: AnnotationIcon) -> &mut Self {
215        self.pair(Name(b"Name"), icon.to_name());
216        self
217    }
218
219    /// Start writing the `/MK` dictionary. Only permissible for the subtype
220    /// `Widget`.
221    pub fn appearance_characteristics(&mut self) -> AppearanceCharacteristics<'_> {
222        self.insert(Name(b"MK")).start()
223    }
224
225    /// Write the `/Parent` attribute. Only permissible for the subtype
226    /// `Widget`.
227    pub fn parent(&mut self, id: Ref) -> &mut Self {
228        self.pair(Name(b"Parent"), id);
229        self
230    }
231
232    /// Start writing the `/AF` array to specify the associated files of the
233    /// annotation. PDF 2.0+ or PDF/A-3.
234    pub fn associated_files(&mut self) -> TypedArray<'_, FileSpec> {
235        self.insert(Name(b"AF")).array().typed()
236    }
237}
238
239deref!('a, Annotation<'a> => Dict<'a>, dict);
240
241/// Kind of the annotation to produce.
242#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
243pub enum AnnotationType {
244    /// Inline text.
245    Text,
246    /// A link.
247    Link,
248    /// A line. PDF 1.3+.
249    Line,
250    /// A square. PDF 1.3+.
251    Square,
252    /// A circle. PDF 1.3+.
253    Circle,
254    /// Highlighting the text on the page. PDF 1.3+.
255    Highlight,
256    /// Underline the text on the page. PDF 1.3+.
257    Underline,
258    /// Squiggly underline of the text on the page. PDF 1.4+.
259    Squiggly,
260    /// Strike out the text on the page. PDF 1.3+.
261    StrikeOut,
262    /// A reference to another file. PDF 1.3+.
263    ///
264    /// Note that this annotation type is forbidden in PDF/A-1 and restricted in
265    /// other PDF/A parts.
266    FileAttachment,
267    /// A widget annotation. PDF 1.2+.
268    Widget,
269    /// A screen annotation. PDF 1.5+.
270    ///
271    /// Note that this annotation type is forbidden in PDF/A.
272    Screen,
273}
274
275impl AnnotationType {
276    pub(crate) fn to_name(self) -> Name<'static> {
277        match self {
278            Self::Text => Name(b"Text"),
279            Self::Link => Name(b"Link"),
280            Self::Line => Name(b"Line"),
281            Self::Square => Name(b"Square"),
282            Self::Circle => Name(b"Circle"),
283            Self::Highlight => Name(b"Highlight"),
284            Self::Underline => Name(b"Underline"),
285            Self::Squiggly => Name(b"Squiggly"),
286            Self::StrikeOut => Name(b"StrikeOut"),
287            Self::FileAttachment => Name(b"FileAttachment"),
288            Self::Widget => Name(b"Widget"),
289            Self::Screen => Name(b"Screen"),
290        }
291    }
292}
293
294/// Possible icons for an annotation.
295#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
296pub enum AnnotationIcon<'a> {
297    /// Speech bubble. For use with text annotations.
298    Comment,
299    /// For use with text annotations.
300    Key,
301    /// Sticky note. For use with text annotations.
302    Note,
303    /// Question mark or manual. For use with text annotations.
304    Help,
305    /// For use with text annotations.
306    NewParagraph,
307    /// For use with text annotations.
308    Paragraph,
309    /// A plus or similar. For use with text annotations.
310    Insert,
311    /// Chart. For use with file attachment annotations.
312    Graph,
313    /// For use with file attachment annotations.
314    PushPin,
315    /// For use with file attachment annotations.
316    Paperclip,
317    /// For use with file attachment annotations.
318    Tag,
319    /// A custom icon name.
320    Custom(Name<'a>),
321}
322
323impl<'a> AnnotationIcon<'a> {
324    pub(crate) fn to_name(self) -> Name<'a> {
325        match self {
326            Self::Comment => Name(b"Comment"),
327            Self::Key => Name(b"Key"),
328            Self::Note => Name(b"Note"),
329            Self::Help => Name(b"Help"),
330            Self::NewParagraph => Name(b"NewParagraph"),
331            Self::Paragraph => Name(b"Paragraph"),
332            Self::Insert => Name(b"Insert"),
333            Self::Graph => Name(b"Graph"),
334            Self::PushPin => Name(b"PushPin"),
335            Self::Paperclip => Name(b"Paperclip"),
336            Self::Tag => Name(b"Tag"),
337            Self::Custom(name) => name,
338        }
339    }
340}
341
342bitflags::bitflags! {
343    /// Bitflags describing various characteristics of annotations.
344    pub struct AnnotationFlags: u32 {
345        /// This will hide the annotation if the viewer does not recognize its
346        /// subtype. Otherwise, it will be rendered as specified in its appearance
347        /// stream.
348        ///
349        /// Must not be set for PDF/A.
350        const INVISIBLE = 1 << 0;
351        /// This hides the annotation from view and disallows interaction. PDF 1.2+.
352        ///
353        /// Must not be set for PDF/A.
354        const HIDDEN = 1 << 1;
355        /// Print the annotation. If not set, it will be always hidden on print.
356        /// PDF 1.2+.
357        ///
358        /// Must be set for PDF/A.
359        const PRINT = 1 << 2;
360        /// Do not zoom the annotation appearance if the document is zoomed in.
361        /// PDF 1.3+.
362        ///
363        /// Must be set for text annotations in PDF/A.
364        const NO_ZOOM = 1 << 3;
365        /// Do not rotate the annotation appearance if the document is zoomed in.
366        /// PDF 1.3+.
367        ///
368        /// Must be set for text annotations in PDF/A.
369        const NO_ROTATE = 1 << 4;
370        /// Do not view the annotation on screen. It may still show on print.
371        /// PDF 1.3+.
372        ///
373        /// Must not be set for PDF/A.
374        const NO_VIEW = 1 << 5;
375        /// Do not allow interactions. PDF 1.3+.
376        const READ_ONLY = 1 << 6;
377        /// Do not allow the user to delete or reposition the annotation. Contents
378        /// may still be changed. PDF 1.4+.
379        const LOCKED = 1 << 7;
380        /// Invert the interpretation of the `no_view` flag for certain events.
381        /// PDF 1.5+.
382        ///
383        /// Must not be set for PDF/A.
384        const TOGGLE_NO_VIEW = 1 << 8;
385        /// Do not allow content changes. PDF 1.7+.
386        const LOCKED_CONTENTS = 1 << 9;
387    }
388}
389
390/// Writer for an _appearance characteristics dictionary_.
391///
392/// This struct is created by [`Annotation::appearance_characteristics`].
393pub struct AppearanceCharacteristics<'a> {
394    dict: Dict<'a>,
395}
396
397writer!(AppearanceCharacteristics: |obj| Self { dict: obj.dict() });
398
399impl AppearanceCharacteristics<'_> {
400    /// Write the `/R` attribute. This is the number of degrees the widget
401    /// annotation should be rotated by counterclockwise relative to its page
402    /// when displayed. This should be a multiple of 90.
403    pub fn rotate(&mut self, degrees: i32) -> &mut Self {
404        self.pair(Name(b"R"), degrees);
405        self
406    }
407
408    /// Write the `/BC` attribute forcing a transparent color. This sets the
409    /// widget annotation's border color.
410    pub fn border_color_transparent(&mut self) -> &mut Self {
411        self.insert(Name(b"BC")).array();
412        self
413    }
414
415    /// Write the `/BC` attribute using a grayscale color. This sets the
416    /// widget annotation's border color.
417    pub fn border_color_gray(&mut self, gray: f32) -> &mut Self {
418        self.insert(Name(b"BC")).array().item(gray);
419        self
420    }
421
422    /// Write the `/BC` attribute using an RGB color. This sets the widget
423    /// annotation's border color.
424    pub fn border_color_rgb(&mut self, r: f32, g: f32, b: f32) -> &mut Self {
425        self.insert(Name(b"BC")).array().items([r, g, b]);
426        self
427    }
428
429    /// Write the `/BC` attribute using an RGB color. This sets the widget
430    /// annotation's border color.
431    pub fn border_color_cymk(&mut self, c: f32, y: f32, m: f32, k: f32) -> &mut Self {
432        self.insert(Name(b"BC")).array().items([c, y, m, k]);
433        self
434    }
435
436    /// Write the `/BG` attribute forcing a transparent color. This sets the
437    /// widget annotation's background color.
438    pub fn background_color_transparent(&mut self) -> &mut Self {
439        self.insert(Name(b"BG")).array();
440        self
441    }
442
443    /// Write the `/BG` attribute using a grayscale color. This sets the
444    /// widget annotation's backround color.
445    pub fn background_color_gray(&mut self, gray: f32) -> &mut Self {
446        self.insert(Name(b"BG")).array().item(gray);
447        self
448    }
449
450    /// Write the `/BG` attribute using an RGB color. This sets the widget
451    /// annotation's backround color.
452    pub fn background_color_rgb(&mut self, r: f32, g: f32, b: f32) -> &mut Self {
453        self.insert(Name(b"BG")).array().items([r, g, b]);
454        self
455    }
456
457    /// Write the `/BG` attribute using an RGB color. This sets the widget
458    /// annotation's backround color.
459    pub fn background_color_cymk(&mut self, c: f32, y: f32, m: f32, k: f32) -> &mut Self {
460        self.insert(Name(b"BG")).array().items([c, y, m, k]);
461        self
462    }
463
464    /// Write the `/CA` attribute. This sets the widget annotation's normal
465    /// caption. Only permissible for button fields.
466    pub fn normal_caption(&mut self, caption: TextStr) -> &mut Self {
467        self.pair(Name(b"CA"), caption);
468        self
469    }
470
471    /// Write the `/RC` attribute. This sets the widget annotation's rollover
472    /// (hover) caption. Only permissible for push button fields.
473    pub fn rollover_caption(&mut self, caption: TextStr) -> &mut Self {
474        self.pair(Name(b"RC"), caption);
475        self
476    }
477
478    /// Write the `/AC` attribute. This sets the widget annotation's alternate
479    /// (down) caption. Only permissible for push button fields.
480    pub fn alterante_caption(&mut self, caption: TextStr) -> &mut Self {
481        self.pair(Name(b"AC"), caption);
482        self
483    }
484
485    /// Write the `/I` attribute. This sets the widget annotation's normal icon
486    /// as a reference to a [`FormXObject`]. Only permissible for push button
487    /// fields.
488    pub fn normal_icon(&mut self, id: Ref) -> &mut Self {
489        self.pair(Name(b"I"), id);
490        self
491    }
492
493    /// Write the `/RI` attribute. This sets the widget annotation's rollover
494    /// (hover) icon as a reference to a [`FormXObject`]. Only permissible for
495    /// push button fields.
496    pub fn rollover_icon(&mut self, id: Ref) -> &mut Self {
497        self.pair(Name(b"RI"), id);
498        self
499    }
500
501    /// Write the `/IX` attribute. This sets the widget annotation's alternate
502    /// (down) icon as a reference to a [`FormXObject`]. Only permissible for
503    /// push button fields.
504    pub fn alternate_icon(&mut self, id: Ref) -> &mut Self {
505        self.pair(Name(b"IX"), id);
506        self
507    }
508
509    /// Start writing the `/IF` dictonary. This sets the widget annotation's
510    /// icon display characteristics. Only permissible for push button fields.
511    pub fn icon_fit(&mut self) -> IconFit<'_> {
512        self.insert(Name(b"IF")).start()
513    }
514
515    /// Write the `/TP` attribute. This sets the widget annotation's caption
516    /// position relative to the annotation's icon. Only permissible for push
517    /// button fields.
518    pub fn text_position(&mut self, position: TextPosition) -> &mut Self {
519        self.pair(Name(b"TP"), position as i32);
520        self
521    }
522}
523
524deref!('a, AppearanceCharacteristics<'a> => Dict<'a>, dict);
525
526/// The position the text of the widget annotation's caption relative to its
527/// icon.
528#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
529pub enum TextPosition {
530    /// Hide icon, show only caption.
531    CaptionOnly = 0,
532    /// Hide caption, show only icon.
533    IconOnly = 1,
534    /// The caption should be placed below the icon.
535    Below = 2,
536    /// The caption should be placed above the icon.
537    Above = 3,
538    /// The caption should be placed to the right of the icon.
539    Right = 4,
540    /// The caption should be placed to the left of the icon.
541    Left = 5,
542    /// The caption should be placed overlaid directly on the icon.
543    Overlaid = 6,
544}
545
546/// Writer for an _icon fit dictionary_.
547///
548/// This struct is created by [`AppearanceCharacteristics::icon_fit`].
549pub struct IconFit<'a> {
550    dict: Dict<'a>,
551}
552
553writer!(IconFit: |obj| Self { dict: obj.dict() });
554
555impl IconFit<'_> {
556    /// Write the `/SW` attribute. This sets under which circumstances the icon
557    /// of the widget annotation should be scaled.
558    pub fn scale(&mut self, value: IconScale) -> &mut Self {
559        self.pair(Name(b"SW"), value.to_name());
560        self
561    }
562
563    /// Write the `/S` attribute. This sets the scaling type of this annoation.
564    pub fn scale_type(&mut self, value: IconScaleType) -> &mut Self {
565        self.pair(Name(b"S"), value.to_name());
566        self
567    }
568
569    /// Write the `/A` attribute. This sets the widget annotation's leftover
570    /// space if proportional scaling is applied given as fractions between
571    /// `0.0` and `1.0`.
572    pub fn leftover_space(&mut self, x: f32, y: f32) -> &mut Self {
573        self.insert(Name(b"A")).array().items([x, y]);
574        self
575    }
576
577    /// Wrtite the `/FB` attribute. This sets whether the border line width
578    /// should be ignored when scaling the icon to fit the annotation bounds.
579    /// PDF 1.5+.
580    pub fn fit_bounds(&mut self, fit: bool) -> &mut Self {
581        self.pair(Name(b"FB"), fit);
582        self
583    }
584}
585
586deref!('a, IconFit<'a> => Dict<'a>, dict);
587
588/// How the icon in a push button field should be scaled.
589#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
590pub enum IconScale {
591    /// Always scale the icon.
592    Always,
593    /// Scale the icon only when the icon is bigger than the annotation
594    /// rectangle.
595    Bigger,
596    /// Scale the icon only when the icon is smaller than the annotation
597    /// rectangle.
598    Smaller,
599    /// Never scale the icon.
600    Never,
601}
602
603impl IconScale {
604    pub(crate) fn to_name(self) -> Name<'static> {
605        match self {
606            Self::Always => Name(b"A"),
607            Self::Bigger => Name(b"B"),
608            Self::Smaller => Name(b"S"),
609            Self::Never => Name(b"N"),
610        }
611    }
612}
613
614/// How the icon in a push button field should be scaled.
615#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
616pub enum IconScaleType {
617    /// Scale the icon to fill the annotation rectangle exactly, without regard
618    /// to its original aspect ratio (ratio of width to height).
619    Anamorphic,
620    /// Scale the icon to fit the width or height of the annotation rectangle
621    /// while maintaining the icon’s original aspect ratio. If the required
622    /// horizontal and vertical scaling factors are different, use the smaller
623    /// of the two, centering the icon within the annotation rectangle in the
624    /// other dimension.
625    Proportional,
626}
627
628impl IconScaleType {
629    pub(crate) fn to_name(self) -> Name<'static> {
630        match self {
631            Self::Anamorphic => Name(b"A"),
632            Self::Proportional => Name(b"P"),
633        }
634    }
635}
636
637/// Highlighting effect applied when a user holds the mouse button over an
638/// annotation.
639#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
640pub enum HighlightEffect {
641    /// No effect.
642    None,
643    /// Invert the colors inside of the annotation rect.
644    Invert,
645    /// Invert the colors on the annotation border.
646    Outline,
647    /// Make the annotation rect's area appear depressed.
648    Push,
649}
650
651impl HighlightEffect {
652    pub(crate) fn to_name(self) -> Name<'static> {
653        match self {
654            Self::None => Name(b"N"),
655            Self::Invert => Name(b"I"),
656            Self::Outline => Name(b"O"),
657            Self::Push => Name(b"P"),
658        }
659    }
660}
661
662/// Writer for an _appearance dictionary_.
663///
664/// This struct is created by [`Annotation::appearance`].
665pub struct Appearance<'a> {
666    dict: Dict<'a>,
667}
668
669writer!(Appearance: |obj| Self { dict: obj.dict() });
670
671impl Appearance<'_> {
672    /// Start writing the `/N` stream or dictionary to set the annotation's
673    /// normal appearance.
674    pub fn normal(&mut self) -> AppearanceEntry<'_> {
675        self.insert(Name(b"N")).start()
676    }
677
678    /// Start writing the `/R` stream or dictionary to set the annotation's
679    /// rollover (hover) appearance.
680    pub fn rollover(&mut self) -> AppearanceEntry<'_> {
681        self.insert(Name(b"R")).start()
682    }
683
684    /// Start writing the `/D` stream or dictionary to set the annotation's
685    /// alternate (down) appearance.
686    pub fn alternate(&mut self) -> AppearanceEntry<'_> {
687        self.insert(Name(b"D")).start()
688    }
689}
690
691deref!('a, Appearance<'a> => Dict<'a>, dict);
692
693/// Writer for an _appearance stream_ or an _appearance subdictionary_.
694///
695/// This struct is created by [`Appearance::normal`], [`Appearance::rollover`]
696/// and [`Appearance::alternate`].
697pub struct AppearanceEntry<'a> {
698    obj: Obj<'a>,
699}
700
701writer!(AppearanceEntry: |obj| Self { obj });
702
703impl<'a> AppearanceEntry<'a> {
704    /// Write an indirect reference to a [`FormXObject`] containing the
705    /// appearance stream.
706    pub fn stream(self, id: Ref) {
707        self.obj.primitive(id);
708    }
709
710    /// Start writing an appearance subdictionary containing indirect references
711    /// to [`FormXObject`]s for each appearance state.
712    pub fn streams(self) -> TypedDict<'a, Ref> {
713        self.obj.dict().typed()
714    }
715}
716
717deref!('a, AppearanceEntry<'a> => Obj<'a>, obj);
718
719/// Writer for an _border style dictionary_.
720///
721/// This struct is created by [`Annotation::border_style`].
722pub struct BorderStyle<'a> {
723    dict: Dict<'a>,
724}
725
726writer!(BorderStyle: |obj| {
727    let mut dict = obj.dict();
728    dict.pair(Name(b"Type"), Name(b"Border"));
729    Self { dict }
730});
731
732impl BorderStyle<'_> {
733    /// Write the `/W` attribute. This is the width of the border in points.
734    pub fn width(&mut self, points: f32) -> &mut Self {
735        self.pair(Name(b"W"), points);
736        self
737    }
738
739    /// Write the `/S` attribute.
740    pub fn style(&mut self, style: BorderType) -> &mut Self {
741        self.pair(Name(b"S"), style.to_name());
742        self
743    }
744
745    /// Write the `/D` attribute to set the repeating lengths of dashes and gaps
746    /// in between.
747    pub fn dashes(&mut self, dash_pattern: impl IntoIterator<Item = f32>) -> &mut Self {
748        self.insert(Name(b"D")).array().items(dash_pattern);
749        self
750    }
751}
752
753deref!('a, BorderStyle<'a> => Dict<'a>, dict);
754
755/// The kind of line to draw on the border.
756#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
757pub enum BorderType {
758    /// A solid line.
759    Solid,
760    /// A dashed line, dash pattern may be specified further elsewhere.
761    Dashed,
762    /// A line with a 3D effect.
763    Beveled,
764    /// A line that makes the rectangle appear depressed.
765    Inset,
766    /// A single line at the bottom of the border rectangle.
767    Underline,
768}
769
770impl BorderType {
771    pub(crate) fn to_name(self) -> Name<'static> {
772        match self {
773            Self::Solid => Name(b"S"),
774            Self::Dashed => Name(b"D"),
775            Self::Beveled => Name(b"B"),
776            Self::Inset => Name(b"I"),
777            Self::Underline => Name(b"U"),
778        }
779    }
780}
781
782#[cfg(test)]
783mod tests {
784    use super::*;
785
786    #[test]
787    fn test_annotations() {
788        test!(
789            crate::tests::slice(|w| {
790                w.annotation(Ref::new(1)).rect(Rect::new(0.0, 0.0, 1.0, 1.0));
791                w.annotation(Ref::new(2)).rect(Rect::new(1.0, 1.0, 0.0, 0.0));
792            }),
793            b"1 0 obj",
794            b"<<",
795            b"  /Type /Annot",
796            b"  /Rect [0 0 1 1]",
797            b">>",
798            b"endobj\n",
799            b"2 0 obj",
800            b"<<",
801            b"  /Type /Annot",
802            b"  /Rect [1 1 0 0]",
803            b">>",
804            b"endobj\n\n",
805        );
806    }
807}