Repository: fschutt/printpdf (language: rust)

Issue:
Is there a way to specify the author of the PDF file?
I'm wondering if there is a way to specify additional metadata, such as the author of the PDF file, prior to generation.


Discussion / hints:
You would have to set that in the `DocumentInfo` dictionary: https://github.com/fschutt/printpdf/blob/097defa25d5d5a14520498043a30979fe4af8bba/src/types/plugins/misc/document_info.rs#L17-L24

... but that's not currently exposed in printpdf. It's not that hard to implement, but I have a lot to do myself right now

Fix patch:
diff --git a/assets/catalog_xmp_metadata.txt b/assets/catalog_xmp_metadata.txt
index 29ba158..e8f5940 100644
--- a/assets/catalog_xmp_metadata.txt
+++ b/assets/catalog_xmp_metadata.txt
@@ -8,22 +8,41 @@
             xmlns:pdfxid="http://www.npes.org/pdfx/ns/id/"
             xmlns:pdfx="http://ns.adobe.com/pdfx/1.3/"
             xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
-         <xmp:CreateDate>{0}</xmp:CreateDate>
-         <xmp:ModifyDate>{1}</xmp:ModifyDate>
-         <xmp:MetadataDate>{2}</xmp:MetadataDate>
+         <xmp:CreateDate>{create}</xmp:CreateDate>
+         <xmp:ModifyDate>{modify}</xmp:ModifyDate>
+         <xmp:MetadataDate>{mdate}</xmp:MetadataDate>
+         <xmp:Identifier>{identifier}</xmp:Identifier>
          <dc:format>application/pdf</dc:format>
          <dc:title>
             <rdf:Alt>
-               <rdf:li xml:lang="x-default">{3}</rdf:li>
+               <rdf:li xml:lang="x-default">{title}</rdf:li>
             </rdf:Alt>
          </dc:title>
-         <xmpMM:DocumentID>uuid:{4}</xmpMM:DocumentID>
-         <xmpMM:InstanceID>uuid:{5}</xmpMM:InstanceID>
-         <xmpMM:RenditionClass>{6}</xmpMM:RenditionClass>
-         <xmpMM:VersionID>{7}</xmpMM:VersionID>
-         <pdfxid:GTS_PDFXVersion>{8}</pdfxid:GTS_PDFXVersion>
-         <pdfx:GTS_PDFXVersion>{8}</pdfx:GTS_PDFXVersion>
-         <pdf:Trapped>{9}</pdf:Trapped>
+         <dc:creator>
+            <rdf:Seq>
+               <rdf:li xml:lang="x-default">{creator}</rdf:li>
+            </rdf:Seq>
+         </dc:creator>
+         <dc:subject>
+            <rdf:Bag>
+               <rdf:li xml:lang="x-default">{subject}</rdf:li>
+            </rdf:Bag>
+         </dc:subject>
+         <dc:identifier>{identifier}</dc:identifier>
+         <dc:publisher>
+            <rdf:Bag>
+               <rdf:li xml:lang="x-default">{producer}</rdf:li>
+            </rdf:Bag>
+         </dc:publisher>
+         <pdf:Producer>{producer}</pdf:Producer>
+         <pdf:Keywords>{keywords}</pdf:Keywords>
+         <xmpMM:DocumentID>uuid:{id}</xmpMM:DocumentID>
+         <xmpMM:InstanceID>uuid:{instance}</xmpMM:InstanceID>
+         <xmpMM:RenditionClass>{class}</xmpMM:RenditionClass>
+         <xmpMM:VersionID>{version}</xmpMM:VersionID>
+         <pdfxid:GTS_PDFXVersion>{pdfx}</pdfxid:GTS_PDFXVersion>
+         <pdfx:GTS_PDFXVersion>{pdfx}</pdfx:GTS_PDFXVersion>
+         <pdf:Trapped>{trapping}</pdf:Trapped>
       </rdf:Description>
    </rdf:RDF>
 </x:xmpmeta>
diff --git a/src/document_info.rs b/src/document_info.rs
index b9638aa..92347fc 100644
--- a/src/document_info.rs
+++ b/src/document_info.rs
@@ -2,9 +2,7 @@
 
 use crate::OffsetDateTime;
 use lopdf;
-
-use PdfConformance;
-
+use PdfMetadata;
 /// "Info" dictionary of a PDF document.
 /// Actual data is contained in `DocumentMetadata`, to keep it in sync with the `XmpMetadata`
 /// (if the timestamps / settings are not in sync, Preflight will complain)
@@ -65,31 +63,32 @@ impl DocumentInfo {
 
     /// This functions is similar to the IntoPdfObject trait method,
     /// but takes additional arguments in order to delay the setting
-    pub(in crate) fn into_obj<S>(self,
-                                 document_title: S,
-                                 trapping: bool,
-                                 conformance: PdfConformance,
-                                 creation_date: OffsetDateTime,
-                                 modification_date: OffsetDateTime)
-    -> lopdf::Object where S: Into<String>
+    pub(in crate) fn into_obj(self, m: &PdfMetadata)
+    -> lopdf::Object
     {
         use lopdf::Dictionary as LoDictionary;
         use lopdf::Object::*;
         use lopdf::StringFormat::Literal;
         use std::iter::FromIterator;
 
-        let trapping = if trapping { "True" } else { "False" };
-        let gts_pdfx_version = conformance.get_identifier_string();
+        let trapping = if m.trapping { "True" } else { "False" };
+        let gts_pdfx_version = m.conformance.get_identifier_string();
 
-        let info_mod_date = to_pdf_time_stamp_metadata(modification_date);
-        let info_create_date = to_pdf_time_stamp_metadata(creation_date);
+        let info_mod_date = to_pdf_time_stamp_metadata(m.modification_date);
+        let info_create_date = to_pdf_time_stamp_metadata(m.creation_date);
 
         Dictionary(LoDictionary::from_iter(vec![
             ("Trapped", trapping.into()),
             ("CreationDate", String(info_create_date.into_bytes(), Literal)),
             ("ModDate", String(info_mod_date.into_bytes(), Literal)),
             ("GTS_PDFXVersion", String(gts_pdfx_version.into(), Literal)),
-            ("Title", String(document_title.into().as_bytes().to_vec(), Literal))
+            ("Title", String(m.document_title.to_string().as_bytes().to_vec(), Literal)),
+            ("Author", String(m.author.as_bytes().to_vec(), Literal)),
+            ("Creator", String(m.creator.as_bytes().to_vec(), Literal)),
+            ("Producer", String(m.producer.as_bytes().to_vec(), Literal)),
+            ("Subject", String(m.subject.as_bytes().to_vec(), Literal)),
+            ("Identifier", String(m.identifier.as_bytes().to_vec(), Literal)),
+            ("Keywords", String(m.keywords.join(",").as_bytes().to_vec(), Literal))
         ]))
     }
 }
diff --git a/src/pdf_document.rs b/src/pdf_document.rs
index 9921568..9b34c50 100644
--- a/src/pdf_document.rs
+++ b/src/pdf_document.rs
@@ -131,9 +131,64 @@ impl PdfDocumentReference {
     /// Changes the title on both the document info dictionary as well as the metadata
     #[inline]
     pub fn with_title<S>(self, new_title: S)
-    -> () where S: Into<String>
+    -> Self where S: Into<String>
     {
         self.document.borrow_mut().metadata.document_title = new_title.into();
+        self
+    }
+    
+    /// Changes the author metadata property on both the document info dictionary as well as the metadata
+    #[inline]
+    pub fn with_author<S>(self, author: S)
+    -> Self where S: Into<String>
+    {
+        self.document.borrow_mut().metadata.author = author.into();
+        self
+    }
+    
+    /// Changes the creator metadata property on both the document info dictionary as well as the metadata
+    #[inline]
+    pub fn with_creator<S>(self, creator: S)
+    -> Self where S: Into<String>
+    {
+        self.document.borrow_mut().metadata.creator = creator.into();
+        self
+    }
+     
+    /// Changes the producer/publisher metadata property on both the document info dictionary as well as the metadata
+    #[inline]
+    pub fn with_producer<S>(self, producer: S)
+    -> Self where S: Into<String>
+    {
+        self.document.borrow_mut().metadata.producer = producer.into();
+        self
+    }
+    
+    /// Changes the keywords metadata property on both the document info dictionary as well as the metadata
+    #[inline]
+    pub fn with_keywords<S>(self, keywords: Vec<S>)
+    -> Self where S: Into<String>
+    {
+        self.document.borrow_mut().metadata.keywords = keywords.into_iter().map(|s| s.into()).collect();
+        self
+    }
+
+    /// Changes the subject metadata property on both the document info dictionary as well as the metadata
+    #[inline]
+    pub fn with_subject<S>(self, subject: S)
+    -> Self where S: Into<String>
+    {
+        self.document.borrow_mut().metadata.subject = subject.into();
+        self
+    }
+    
+    /// Changes the subject metadata property on both the document info dictionary as well as the metadata
+    #[inline]
+    pub fn with_identifier<S>(self, identifier: S)
+    -> Self where S: Into<String>
+    {
+        self.document.borrow_mut().metadata.identifier = identifier.into();
+        self
     }
 
     /// Set the trapping of the document
diff --git a/src/pdf_metadata.rs b/src/pdf_metadata.rs
index d778e14..3d9fdc8 100644
--- a/src/pdf_metadata.rs
+++ b/src/pdf_metadata.rs
@@ -20,6 +20,18 @@ pub struct PdfMetadata {
 	pub metadata_date: OffsetDateTime,
 	/// PDF document title
 	pub document_title: String,
+	/// PDF document author
+	pub author: String,
+	/// The creator of the document
+	pub creator: String,
+	/// The producer of the document
+	pub producer: String,
+	/// Keywords associated with the document
+	pub keywords: Vec<String>,
+	/// The subject of the document
+	pub subject: String,
+	/// Identifier associated with the document
+	pub identifier: String,
 	/// Is the document trapped?
 	pub trapping: bool,
 	/// PDF document version
@@ -36,7 +48,7 @@ pub struct PdfMetadata {
 
 impl PdfMetadata {
 
-	/// Creates a new metadat object
+	/// Creates a new metadata object
 	pub fn new<S>(title: S, document_version: u32, trapping: bool, conformance: PdfConformance)
 	-> Self where S: Into<String>
 	{
@@ -47,6 +59,12 @@ impl PdfMetadata {
 			modification_date: current_time.clone(),
 			metadata_date: current_time.clone(),
 			document_title: title.into(),
+			author: String::new(),
+			creator: String::new(),
+			producer: String::new(),	
+			keywords: Vec::new(),
+			subject: String::new(),
+			identifier: String::new(),
 			trapping: trapping,
 			document_version: document_version,
 			conformance: conformance,
@@ -60,25 +78,16 @@ impl PdfMetadata {
 	pub fn into_obj(self)
 	-> (Option<lopdf::Object>, lopdf::Object, Option<IccProfile>)
 	{
+		let metadata = self.clone();
 		let xmp_obj = {
 			if self.conformance.must_have_xmp_metadata() {
-				Some(self.xmp_metadata.into_obj(
-					 	self.conformance.clone(),
-						self.trapping,
-						self.creation_date.clone(),
-						self.modification_date.clone(),
-						self.metadata_date,
-						self.document_title.clone()))
+				Some(self.xmp_metadata.into_obj(&metadata))
 			} else {
 				None
 			}
 		};
 
-		let doc_info_obj = self.document_info.into_obj(self.document_title,
-													   self.trapping,
-													   self.conformance.clone(),
-													   self.creation_date.clone(),
-													   self.modification_date.clone());
+		let doc_info_obj = self.document_info.into_obj(&metadata);
 		// add icc profile if necessary
 		let icc_profile = {
 		    if self.conformance.must_have_icc_profile() {
diff --git a/src/xmp_metadata.rs b/src/xmp_metadata.rs
index 471a3af..9482e08 100644
--- a/src/xmp_metadata.rs
+++ b/src/xmp_metadata.rs
@@ -3,7 +3,7 @@
 use crate::OffsetDateTime;
 use lopdf;
 
-use PdfConformance;
+use PdfMetadata;
 use utils::random_character_string_32;
 
 /// Initial struct for Xmp metatdata. This should be expanded later for XML handling, etc.
@@ -34,29 +34,23 @@ impl XmpMetadata {
 
     /// Consumes the XmpMetadata and turns it into a PDF Object.
     /// This is similar to the
-    pub(in crate) fn into_obj<S>(self,
-                           conformance: PdfConformance,
-                           trapping: bool,
-                           creation_date: OffsetDateTime,
-                           modification_date: OffsetDateTime,
-                           metadata_date: OffsetDateTime,
-                           document_title: S)
-    -> lopdf::Object where S: Into<String> + ::std::fmt::Display
+    pub(in crate) fn into_obj(self, m: &PdfMetadata)
+    -> lopdf::Object
     {
         use lopdf::{Stream as LoStream, Dictionary as LoDictionary};
         use lopdf::Object::*;
         use std::iter::FromIterator;
 
         // Shared between XmpMetadata and DocumentInfo
-        let trapping = if trapping { "True" } else { "False" };
+        let trapping = if m.trapping { "True" } else { "False" };
 
         // let xmp_instance_id = "2898d852-f86f-4479-955b-804d81046b19";
         let instance_id = random_character_string_32();
-        let create_date = to_pdf_xmp_date(creation_date);
-        let modification_date = to_pdf_xmp_date(modification_date);
-        let metadata_date = to_pdf_xmp_date(metadata_date);
+        let create_date = to_pdf_xmp_date(m.creation_date);
+        let modification_date = to_pdf_xmp_date(m.modification_date);
+        let metadata_date = to_pdf_xmp_date(m.metadata_date);
 
-        let pdf_x_version = conformance.get_identifier_string();
+        let pdf_x_version = m.conformance.get_identifier_string();
         let document_version = self.document_version.to_string();
         let document_id = self.document_id.to_string();
 
@@ -66,8 +60,24 @@ impl XmpMetadata {
         };
 
         let xmp_metadata = format!(include_str!("../assets/catalog_xmp_metadata.txt"),
-                           create_date, modification_date, metadata_date, document_title, document_id,
-                           instance_id, rendition_class, document_version, pdf_x_version, trapping);
+                           create = create_date, 
+                           modify = modification_date, 
+                           mdate = metadata_date, 
+                           title = m.document_title, 
+                           id = document_id,
+                           instance = instance_id, 
+                           class = rendition_class, 
+                           version = document_version, 
+                           pdfx = pdf_x_version, 
+                           trapping = trapping, 
+                           creator = m.creator,
+                           subject = m.subject,
+                           keywords = m.keywords.join(","),
+                           identifier = m.identifier,
+                           producer = m.producer);
+
+
+
 
         Stream(LoStream::new(LoDictionary::from_iter(vec![
             ("Type", "Metadata".into()),


Test patch: