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}