diff options
author | halcanary <halcanary@google.com> | 2015-10-09 13:09:58 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-10-09 13:09:58 -0700 |
commit | 939c0fe51f157104758bcb268643c8b6d317a530 (patch) | |
tree | 1c2fb3639dd1ae608ae00fc99130d09a427bf5cd /src/doc | |
parent | e361781bf7ce8acd4af00c426b34596804d45d77 (diff) |
SkPDF: Optionally output PDF/A-2b archive format.
Note: this format does not yet pass validation tests.
Add skia_pdf_generate_pdfa GYP flag. Default to off for now.
PDF/A files are not reproducable, so they make correctness
testing harder.
Turn the Metadata struct into te SkPDFMetadata struct. This
splits out a lot of functionality around both kinds of metadata.
When PDF/A is used, add an ID entry to the trailer.
Add SkPDFObjNumMap::addObjectRecursively.
Test with
GYP_DEFINES=skia_pdf_generate_pdfa=1 bin/sync-and-gyp
ninja -C out/Release dm
out/Release/dm --config pdf --src skp gm -w /tmp/dm
With skia_pdf_generate_pdfa=0, all PDFs generated from GMs and
SKPs are identical. With skia_pdf_generate_pdfa=1, all PDFs
generated from GMs and SKPs render identically in Pdfium.
BUG=skia:3110
Review URL: https://codereview.chromium.org/1394263003
Diffstat (limited to 'src/doc')
-rw-r--r-- | src/doc/SkDocument_PDF.cpp | 110 |
1 files changed, 43 insertions, 67 deletions
diff --git a/src/doc/SkDocument_PDF.cpp b/src/doc/SkDocument_PDF.cpp index fb22f18ec5..ff7a038b6b 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 /////////////////////////////////////////////////////////////////////////////// |