pdfium_render/pdf/document/
metadata.rs

1//! Defines the [PdfMetadata] struct, a collection of all the metadata tags in a [PdfDocument].
2
3use crate::bindgen::FPDF_DOCUMENT;
4use crate::bindings::PdfiumLibraryBindings;
5use crate::utils::mem::create_byte_buffer;
6use crate::utils::utf16le::get_string_from_pdfium_utf16le_bytes;
7use std::os::raw::c_void;
8use std::slice::Iter;
9
10#[cfg(doc)]
11use crate::pdf::document::PdfDocument;
12
13/// Valid metadata tag types in a [PdfDocument].
14#[derive(Debug, Copy, Clone, PartialEq)]
15pub enum PdfDocumentMetadataTagType {
16    Title,
17    Author,
18    Subject,
19    Keywords,
20    Creator,
21    Producer,
22    CreationDate,
23    ModificationDate,
24}
25
26/// A single metadata tag in a [PdfDocument].
27#[derive(Debug, Clone, PartialEq)]
28pub struct PdfDocumentMetadataTag {
29    tag: PdfDocumentMetadataTagType,
30    value: String,
31}
32
33impl PdfDocumentMetadataTag {
34    #[inline]
35    pub(crate) fn new(tag: PdfDocumentMetadataTagType, value: String) -> Self {
36        PdfDocumentMetadataTag { tag, value }
37    }
38
39    /// Returns the type of this metadata tag.
40    #[inline]
41    pub fn tag_type(&self) -> PdfDocumentMetadataTagType {
42        self.tag
43    }
44
45    /// Returns the value of this metadata tag.
46    #[inline]
47    pub fn value(&self) -> &str {
48        self.value.as_str()
49    }
50}
51
52/// A collection of all the metadata tags in a [PdfDocument].
53pub struct PdfMetadata<'a> {
54    document_handle: FPDF_DOCUMENT,
55    bindings: &'a dyn PdfiumLibraryBindings,
56    tags: Vec<PdfDocumentMetadataTag>,
57}
58
59impl<'a> PdfMetadata<'a> {
60    pub(crate) fn from_pdfium(
61        document_handle: FPDF_DOCUMENT,
62        bindings: &'a dyn PdfiumLibraryBindings,
63    ) -> Self {
64        let mut result = PdfMetadata {
65            document_handle,
66            bindings,
67            tags: vec![],
68        };
69
70        if let Some(tag) = result.get(PdfDocumentMetadataTagType::Title) {
71            result.tags.push(tag);
72        }
73
74        if let Some(tag) = result.get(PdfDocumentMetadataTagType::Author) {
75            result.tags.push(tag);
76        }
77
78        if let Some(tag) = result.get(PdfDocumentMetadataTagType::Subject) {
79            result.tags.push(tag);
80        }
81
82        if let Some(tag) = result.get(PdfDocumentMetadataTagType::Keywords) {
83            result.tags.push(tag);
84        }
85
86        if let Some(tag) = result.get(PdfDocumentMetadataTagType::Creator) {
87            result.tags.push(tag);
88        }
89
90        if let Some(tag) = result.get(PdfDocumentMetadataTagType::Producer) {
91            result.tags.push(tag);
92        }
93
94        if let Some(tag) = result.get(PdfDocumentMetadataTagType::CreationDate) {
95            result.tags.push(tag);
96        }
97
98        if let Some(tag) = result.get(PdfDocumentMetadataTagType::ModificationDate) {
99            result.tags.push(tag);
100        }
101
102        result
103    }
104
105    /// Returns the [PdfiumLibraryBindings] used by this [PdfMetadata] collection.
106    #[inline]
107    pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
108        self.bindings
109    }
110
111    /// Returns the number of metadata tags in this [PdfMetadata] collection.
112    #[inline]
113    pub fn len(&self) -> usize {
114        self.tags.len()
115    }
116
117    /// Returns true if this [PdfMetadata] collection is empty.
118    #[inline]
119    pub fn is_empty(&self) -> bool {
120        self.len() == 0
121    }
122
123    /// Returns one metadata tag from this [PdfMetadata] collection, if it is defined.
124    pub fn get(&self, tag: PdfDocumentMetadataTagType) -> Option<PdfDocumentMetadataTag> {
125        let result = match tag {
126            PdfDocumentMetadataTagType::Title => self.get_raw_metadata_tag("Title"),
127            PdfDocumentMetadataTagType::Author => self.get_raw_metadata_tag("Author"),
128            PdfDocumentMetadataTagType::Subject => self.get_raw_metadata_tag("Subject"),
129            PdfDocumentMetadataTagType::Keywords => self.get_raw_metadata_tag("Keywords"),
130            PdfDocumentMetadataTagType::Creator => self.get_raw_metadata_tag("Creator"),
131            PdfDocumentMetadataTagType::Producer => self.get_raw_metadata_tag("Producer"),
132            PdfDocumentMetadataTagType::CreationDate => self.get_raw_metadata_tag("CreationDate"),
133            PdfDocumentMetadataTagType::ModificationDate => {
134                self.get_raw_metadata_tag("ModificationDate")
135            }
136        };
137
138        result.map(|value| PdfDocumentMetadataTag::new(tag, value))
139    }
140
141    #[inline]
142    fn get_raw_metadata_tag(&self, tag: &str) -> Option<String> {
143        // Retrieving the tag text from Pdfium is a two-step operation. First, we call
144        // FPDF_GetMetaText() with a null buffer; this will retrieve the length of
145        // the metadata text in bytes. If the length is zero, then there is no such tag.
146
147        // If the length is non-zero, then we reserve a byte buffer of the given
148        // length and call FPDF_GetMetaText() again with a pointer to the buffer;
149        // this will write the metadata text to the buffer in UTF16-LE format.
150
151        let buffer_length =
152            self.bindings
153                .FPDF_GetMetaText(self.document_handle, tag, std::ptr::null_mut(), 0);
154
155        if buffer_length == 0 {
156            // The tag is not present.
157
158            return None;
159        }
160
161        let mut buffer = create_byte_buffer(buffer_length as usize);
162
163        let result = self.bindings.FPDF_GetMetaText(
164            self.document_handle,
165            tag,
166            buffer.as_mut_ptr() as *mut c_void,
167            buffer_length,
168        );
169
170        assert_eq!(result, buffer_length);
171
172        get_string_from_pdfium_utf16le_bytes(buffer)
173    }
174
175    /// Returns an iterator over all the tags in this [PdfMetadata] collection.
176    #[inline]
177    pub fn iter(&self) -> Iter<'_, PdfDocumentMetadataTag> {
178        self.tags.iter()
179    }
180}