Skip to main content

pdf_writer/
renditions.rs

1use super::*;
2use crate::object::TextStrLike;
3
4/// Writer for an _rendition dictionary_.
5///
6/// This struct is created by [`Action::rendition`].
7pub struct Rendition<'a> {
8    dict: Dict<'a>,
9}
10
11writer!(Rendition: |obj| {
12    let mut dict = obj.dict();
13    dict.pair(Name(b"Type"), Name(b"Rendition"));
14    Self { dict }
15});
16
17impl Rendition<'_> {
18    /// Write the `/S` attribute to set the rendition type.
19    pub fn subtype(&mut self, kind: RenditionType) -> &mut Self {
20        self.pair(Name(b"S"), kind.to_name());
21        self
22    }
23
24    /// Write the `/N` attribute. Specify the name of the rendition for use in a
25    /// user interface and for name tree lookup by JavaScript actions.
26    pub fn name(&mut self, text: impl TextStrLike) -> &mut Self {
27        self.pair(Name(b"N"), text);
28        self
29    }
30
31    /// Start writing the `/C`, i.e. media clip, dictionary which specifies what
32    /// media should be played. Only permissible for Media Renditions.
33    pub fn media_clip(&mut self) -> MediaClip<'_> {
34        self.insert(Name(b"C")).start()
35    }
36
37    /// Start writing the `/P`, i.e. media play parameters, dictionary which
38    /// specifies how the media should be played. Only permissible for Media
39    /// Renditions.
40    pub fn media_play_params(&mut self) -> MediaPlayParams<'_> {
41        self.insert(Name(b"P")).start()
42    }
43}
44
45deref!('a, Rendition<'a> => Dict<'a>, dict);
46
47/// Writer for an _media clip dictionary_.
48///
49/// This struct is created by [`Rendition::media_clip`].
50///
51/// ## Note on reader compatibility
52///
53/// Different PDF readers may have support for different media codecs and
54/// container formats.
55///
56/// For example, [Adobe's documentation][1] states that Adobe Acrobat can play
57/// videos in MP4, MOV, M4V, 3GP, and 3G2 containers using the H.264 codec.
58///
59/// Other readers may depend on the media libraries installed on the system. KDE
60/// Okular, for example, uses the Phonon library to support a range of media
61/// formats.
62///
63/// Yet other viewers do not support media clips at all. At the time of writing,
64/// this includes the popular Pdfium library used by Google Chrome and Microsoft
65/// Edge, `pdf.js` used by Firefox, mupdf, and Quartz, the PDF viewer on Apple
66/// platforms.
67///
68/// [1]: https://helpx.adobe.com/acrobat/using/playing-video-audio-multimedia-formats.html#supported_video_audio_and_interactive_formats
69pub struct MediaClip<'a> {
70    dict: Dict<'a>,
71}
72
73writer!(MediaClip: |obj| {
74    let mut dict = obj.dict();
75    dict.pair(Name(b"Type"), Name(b"MediaClip"));
76    Self { dict }
77});
78
79impl MediaClip<'_> {
80    /// Write the `/S` attribute to set the media clip type.
81    pub fn subtype(&mut self, kind: MediaClipType) -> &mut Self {
82        self.pair(Name(b"S"), kind.to_name());
83        self
84    }
85
86    /// Write the `/N` attribute. Specifies the name of the media clip, for use
87    /// in the user interface.
88    pub fn name(&mut self, text: impl TextStrLike) -> &mut Self {
89        self.pair(Name(b"N"), text);
90        self
91    }
92
93    /// Start writing the `/D` dictionary specifying the media data.
94    pub fn data(&mut self) -> FileSpec<'_> {
95        self.insert(Name(b"D")).start()
96    }
97
98    /// Write the `/CT` attribute identifying the type of data in `/D`, i.e. the
99    /// MIME type.
100    pub fn data_type(&mut self, tf: Str) -> &mut Self {
101        self.pair(Name(b"CT"), tf);
102        self
103    }
104
105    /// Start writing the `/P`, i.e. media permissions, dictionary.
106    pub fn permissions(&mut self) -> MediaPermissions<'_> {
107        self.insert(Name(b"P")).start()
108    }
109
110    /// Write the `/Alt` attribute, listing alternate text descriptions which
111    /// are specified as a multi-language text array. A multi-language text
112    /// array shall contain pairs of strings.
113    pub fn alt_texts<'b>(
114        &mut self,
115        texts: impl IntoIterator<Item = TextStr<'b>>,
116    ) -> &mut Self {
117        self.insert(Name(b"Alt")).array().items(texts);
118        self
119    }
120}
121
122deref!('a, MediaClip<'a> => Dict<'a>, dict);
123
124/// Writer for an _media play parameters dictionary_.
125///
126/// This struct is created by [`Rendition::media_play_params`].
127pub struct MediaPlayParams<'a> {
128    dict: Dict<'a>,
129}
130
131writer!(MediaPlayParams: |obj| {
132    let mut dict = obj.dict();
133    dict.pair(Name(b"Type"), Name(b"MediaPlayParams"));
134    Self { dict }
135});
136
137impl MediaPlayParams<'_> {
138    /// Write the `/C` attribute inside a `/BE` dictionary specifying whether to
139    /// display a player-specific controls.
140    ///
141    /// This avoids implementing the "must honour" (MH) or "best effort" (BE)
142    /// dictionaries for MediaPlayParams, as the required boiler-plate code
143    /// would be high, and its usefulness low.
144    pub fn controls(&mut self, c: bool) -> &mut Self {
145        self.insert(Name(b"BE")).dict().pair(Name(b"C"), c);
146        self
147    }
148}
149
150deref!('a, MediaPlayParams<'a> => Dict<'a>, dict);
151
152/// Writer for an _media permissions dictionary_.
153///
154/// This struct is created by [`MediaClip::permissions`].
155pub struct MediaPermissions<'a> {
156    dict: Dict<'a>,
157}
158
159writer!(MediaPermissions: |obj| {
160    let mut dict = obj.dict();
161    dict.pair(Name(b"Type"), Name(b"MediaPermissions"));
162    Self { dict }
163});
164
165impl MediaPermissions<'_> {
166    /// Write the `/TF` attribute to control permissions to write a temporary file.
167    pub fn temp_file(&mut self, tf: TempFileType) -> &mut Self {
168        self.pair(Name(b"TF"), tf.to_str());
169        self
170    }
171}
172
173deref!('a, MediaPermissions<'a> => Dict<'a>, dict);
174
175/// The circumstances under which it is acceptable to write a temporary file in
176/// order to play a media clip.
177#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)]
178pub enum TempFileType {
179    /// Never allowed.
180    #[default]
181    Never,
182    /// Allowed only if the document permissions allow content extraction.
183    Extract,
184    /// Allowed only if the document permissions allow content extraction,
185    /// including for accessibility purposes.
186    Access,
187    /// Always allowed.
188    Always,
189}
190
191impl TempFileType {
192    pub(crate) fn to_str(self) -> Str<'static> {
193        match self {
194            Self::Never => Str(b"TEMPNEVER"),
195            Self::Extract => Str(b"TEMPEXTRACT"),
196            Self::Access => Str(b"TEMPACCESS"),
197            Self::Always => Str(b"TEMPALWAYS"),
198        }
199    }
200}
201
202/// Type of rendition objects.
203#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
204pub enum RenditionType {
205    /// Media Rendition.
206    Media,
207    /// Selector Rendition.
208    Selector,
209}
210
211impl RenditionType {
212    pub(crate) fn to_name(self) -> Name<'static> {
213        match self {
214            Self::Media => Name(b"MR"),
215            Self::Selector => Name(b"SR"),
216        }
217    }
218}
219
220/// Type of media clip objects.
221#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
222pub enum MediaClipType {
223    /// Media Clip Data.
224    Data,
225    /// Media Clip Section.
226    Section,
227}
228
229impl MediaClipType {
230    pub(crate) fn to_name(self) -> Name<'static> {
231        match self {
232            Self::Data => Name(b"MCD"),
233            Self::Section => Name(b"MCS"),
234        }
235    }
236}