1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
//! Info dictionary of a PDF document
use crate::OffsetDateTime;
use crate::PdfMetadata;
use lopdf;
/// "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)
#[derive(Default, Debug, Copy, Clone)]
pub struct DocumentInfo {
// DocumentInfo is older than XmpMetadata
// The following is a list of things available to the DocumentInfo dictionary.
// These keys don't have to be set:
/*
/Author ( Craig J. Hogan )
/Subject ( doi:10.1038/445037a )
/Keywords ( cosmology infrared protogalaxy starlight )
/Identifier ( doi:10.1038/445037a )
/Creator ( … )
/Producer ( … )
*/
// The more modern approach is to put them into the XmpMetadata struct:
// This struct is merely a wrapper around those types that HAVE to be in a PDF/X-conform
// document.
/*
<rdf:Description rdf:about=“” xmlns:dc=“http://purl.org/dc/elements/1.1/">
<dc:creator>Craig J. Hogan</dc:creator>
<dc:title>Cosmology: Ripples of early starlight</dc:title>
<dc:identifier>doi:10.1038/445037a</dc:identifier>
<dc:source>Nature 445, 37 (2007)</dc:source>
<dc:date>2007-01-04</dc:date>
<dc:format>application/pdf</dc:format>
<dc:publisher>Nature Publishing Group</dc:publisher>
<dc:language>en<dc:language>
<dc:rights>© 2007 Nature Publishing Group</dc:rights>
</rdf:Description>
<rdf:Description rdf:about=“” xmlns:prism=“http://prismstandard.org/namespaces/1.2/basic/">
<prism:publicationName>Nature</prism:publicationName>
<prism:issn>0028-0836</prism:issn>
<prism:eIssn>1476-4679</prism:eIssn>
<prism:publicationDate>2007-01-04</prism:publicationDate>
<prism:copyright>© 2007 Nature Publishing Group</prism:copyright>
<prism:rightsAgent>permissions@nature.com</prism:rightsAgent>
<prism:volume>445</prism:volume> <prism:number>7123</prism:number>
<prism:startingPage>37</prism:startingPage>
<prism:endingPage>37</prism:endingPage>
<prism:section>News and Views</prism:section>
</rdf:Description>
*/
}
impl DocumentInfo {
/// Create a new doucment info dictionary from a document
pub fn new() -> Self {
Self::default()
}
/// This functions is similar to the IntoPdfObject trait method,
/// but takes additional arguments in order to delay the setting
pub(crate) fn into_obj(self, m: &PdfMetadata) -> lopdf::Object {
use lopdf::Dictionary as LoDictionary;
use lopdf::Object::*;
use lopdf::StringFormat::Literal;
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(&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(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),
),
]))
}
}
// D:20170505150224+02'00'
fn to_pdf_time_stamp_metadata(date: &OffsetDateTime) -> String {
let offset = date.offset();
let offset_sign = if offset.is_negative() { '-' } else { '+' };
format!(
"D:{:04}{:02}{:02}{:02}{:02}{:02}{offset_sign}{:02}'{:02}'",
date.year(),
u8::from(date.month()),
date.day(),
date.hour(),
date.minute(),
date.second(),
offset.whole_hours().abs(),
offset.minutes_past_hour().abs(),
)
}
#[cfg(test)]
mod tests {
use time::{Date, Month, UtcOffset};
use super::to_pdf_time_stamp_metadata;
#[test]
fn pdf_timestamp_positive() {
let datetime = Date::from_calendar_date(2017, Month::May, 8)
.unwrap()
.with_hms(15, 2, 24)
.unwrap();
assert_eq!(
to_pdf_time_stamp_metadata(
&datetime.assume_offset(UtcOffset::from_hms(2, 28, 15).unwrap())
),
"D:20170508150224+02'28'"
);
assert_eq!(
to_pdf_time_stamp_metadata(&datetime.assume_utc()),
"D:20170508150224+00'00'"
);
}
#[test]
fn pdf_timestamp_negative() {
let datetime = Date::from_calendar_date(2017, Month::May, 8)
.unwrap()
.with_hms(15, 2, 24)
.unwrap()
.assume_offset(UtcOffset::from_hms(-2, -20, -30).unwrap());
assert_eq!(
to_pdf_time_stamp_metadata(&datetime),
"D:20170508150224-02'20'"
);
}
}