pdf_writer/
annotations.rs

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