Index: src/doc/SkDocument_PDF.cpp |
diff --git a/src/doc/SkDocument_PDF.cpp b/src/doc/SkDocument_PDF.cpp |
index fb22f18ec5da5af7807935476b07f29783ef4651..ff7a038b6bf268c9a9b85ba2dbd1da38eb1b5035 100644 |
--- a/src/doc/SkDocument_PDF.cpp |
+++ b/src/doc/SkDocument_PDF.cpp |
@@ -13,6 +13,7 @@ |
#include "SkPDFTypes.h" |
#include "SkPDFUtils.h" |
#include "SkStream.h" |
+#include "SkPDFMetadata.h" |
class SkPDFDict; |
@@ -30,15 +31,18 @@ static void emit_pdf_footer(SkWStream* stream, |
SkPDFObject* docCatalog, |
int64_t objCount, |
int32_t xRefFileOffset, |
- SkPDFDict* info) { |
+ SkPDFObject* info /* take ownership */, |
+ SkPDFObject* id /* take ownership */) { |
SkPDFDict trailerDict; |
- // TODO(vandebo): Linearized format will take a Prev entry too. |
- // TODO(vandebo): PDF/A requires an ID entry. |
+ // TODO(http://crbug.com/80908): Linearized format will take a |
+ // Prev entry too. |
trailerDict.insertInt("Size", int(objCount)); |
trailerDict.insertObjRef("Root", SkRef(docCatalog)); |
SkASSERT(info); |
- trailerDict.insertObjRef("Info", SkRef(info)); |
- |
+ trailerDict.insertObjRef("Info", info); |
+ if (id) { |
+ trailerDict.insertObject("ID", id); |
+ } |
stream->writeText("trailer\n"); |
trailerDict.emitObject(stream, objNumMap, substitutes); |
stream->writeText("\nstartxref\n"); |
@@ -162,49 +166,8 @@ static void generate_page_tree(const SkTDArray<SkPDFDict*>& pages, |
} |
} |
-struct Metadata { |
- SkTArray<SkDocument::Attribute> fInfo; |
- SkAutoTDelete<const SkTime::DateTime> fCreation; |
- SkAutoTDelete<const SkTime::DateTime> fModified; |
-}; |
- |
-static SkString pdf_date(const SkTime::DateTime& dt) { |
- int timeZoneMinutes = SkToInt(dt.fTimeZoneMinutes); |
- char timezoneSign = timeZoneMinutes >= 0 ? '+' : '-'; |
- int timeZoneHours = SkTAbs(timeZoneMinutes) / 60; |
- timeZoneMinutes = SkTAbs(timeZoneMinutes) % 60; |
- return SkStringPrintf( |
- "D:%04u%02u%02u%02u%02u%02u%c%02d'%02d'", |
- static_cast<unsigned>(dt.fYear), static_cast<unsigned>(dt.fMonth), |
- static_cast<unsigned>(dt.fDay), static_cast<unsigned>(dt.fHour), |
- static_cast<unsigned>(dt.fMinute), |
- static_cast<unsigned>(dt.fSecond), timezoneSign, timeZoneHours, |
- timeZoneMinutes); |
-} |
- |
-SkPDFDict* create_document_information_dict(const Metadata& metadata) { |
- SkAutoTUnref<SkPDFDict> dict(new SkPDFDict); |
- static const char* keys[] = { |
- "Title", "Author", "Subject", "Keywords", "Creator" }; |
- for (const char* key : keys) { |
- for (const SkDocument::Attribute& keyValue : metadata.fInfo) { |
- if (keyValue.fKey.equals(key)) { |
- dict->insertString(key, keyValue.fValue); |
- } |
- } |
- } |
- dict->insertString("Producer", "Skia/PDF"); |
- if (metadata.fCreation) { |
- dict->insertString("CreationDate", pdf_date(*metadata.fCreation.get())); |
- } |
- if (metadata.fModified) { |
- dict->insertString("ModDate", pdf_date(*metadata.fModified.get())); |
- } |
- return dict.detach(); |
-} |
- |
static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices, |
- const Metadata& metadata, |
+ const SkPDFMetadata& metadata, |
SkWStream* stream) { |
if (pageDevices.isEmpty()) { |
return false; |
@@ -222,9 +185,37 @@ static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices, |
pages.push(page.detach()); |
} |
- SkTDArray<SkPDFDict*> pageTree; |
SkAutoTUnref<SkPDFDict> docCatalog(new SkPDFDict("Catalog")); |
+ SkAutoTUnref<SkPDFObject> infoDict( |
+ metadata.createDocumentInformationDict()); |
+ |
+ SkAutoTUnref<SkPDFObject> id, xmp; |
+#ifdef SK_PDF_GENERATE_PDFA |
+ SkPDFMetadata::UUID uuid = metadata.uuid(); |
+ // We use the same UUID for Document ID and Instance ID since this |
+ // is the first revision of this document (and Skia does not |
+ // support revising existing PDF documents). |
+ // If we are not in PDF/A mode, don't use a UUID since testing |
+ // works best with reproducible outputs. |
+ id.reset(SkPDFMetadata::CreatePdfId(uuid, uuid)); |
+ xmp.reset(metadata.createXMPObject(uuid, uuid)); |
+ docCatalog->insertObjRef("Metadata", xmp.detach()); |
+ |
+ // sRGB is specified by HTML, CSS, and SVG. |
+ SkAutoTUnref<SkPDFDict> outputIntent(new SkPDFDict("OutputIntent")); |
+ outputIntent->insertName("S", "GTS_PDFA1"); |
+ outputIntent->insertString("RegistryName", "http://www.color.org"); |
+ outputIntent->insertString("OutputConditionIdentifier", |
+ "sRGB IEC61966-2.1"); |
+ SkAutoTUnref<SkPDFArray> intentArray(new SkPDFArray); |
+ intentArray->appendObject(outputIntent.detach()); |
+ // Don't specify OutputIntents if we are not in PDF/A mode since |
+ // no one has ever asked for this feature. |
+ docCatalog->insertObject("OutputIntents", intentArray.detach()); |
+#endif |
+ |
+ SkTDArray<SkPDFDict*> pageTree; |
SkPDFDict* pageTreeRoot; |
generate_page_tree(pages, &pageTree, &pageTreeRoot); |
docCatalog->insertObjRef("Pages", SkRef(pageTreeRoot)); |
@@ -233,28 +224,13 @@ static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices, |
docCatalog->insertObjRef("Dests", dests.detach()); |
} |
- /* TODO(vandebo): output intent |
- SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent"); |
- outputIntent->insertName("S", "GTS_PDFA1"); |
- outputIntent->insertString("OutputConditionIdentifier", "sRGB"); |
- SkAutoTUnref<SkPDFArray> intentArray(new SkPDFArray); |
- intentArray->appendObject(SkRef(outputIntent.get())); |
- docCatalog->insertObject("OutputIntent", intentArray.detach()); |
- */ |
- |
// Build font subsetting info before proceeding. |
SkPDFSubstituteMap substitutes; |
perform_font_subsetting(pageDevices, &substitutes); |
- SkAutoTUnref<SkPDFDict> infoDict( |
- create_document_information_dict(metadata)); |
SkPDFObjNumMap objNumMap; |
- if (objNumMap.addObject(infoDict)) { |
- infoDict->addResources(&objNumMap, substitutes); |
- } |
- if (objNumMap.addObject(docCatalog.get())) { |
- docCatalog->addResources(&objNumMap, substitutes); |
- } |
+ objNumMap.addObjectRecursively(infoDict, substitutes); |
+ objNumMap.addObjectRecursively(docCatalog.get(), substitutes); |
size_t baseOffset = stream->bytesWritten(); |
emit_pdf_header(stream); |
SkTDArray<int32_t> offsets; |
@@ -286,7 +262,7 @@ static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices, |
stream->writeText(" 00000 n \n"); |
} |
emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount, |
- xRefFileOffset, infoDict); |
+ xRefFileOffset, infoDict.detach(), id.detach()); |
// The page tree has both child and parent pointers, so it creates a |
// reference cycle. We must clear that cycle to properly reclaim memory. |
@@ -404,7 +380,7 @@ private: |
SkTDArray<const SkPDFDevice*> fPageDevices; |
SkAutoTUnref<SkCanvas> fCanvas; |
SkScalar fRasterDpi; |
- Metadata fMetadata; |
+ SkPDFMetadata fMetadata; |
}; |
} // namespace |
/////////////////////////////////////////////////////////////////////////////// |