aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/doc
diff options
context:
space:
mode:
authorGravatar halcanary <halcanary@google.com>2015-10-09 13:09:58 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2015-10-09 13:09:58 -0700
commit939c0fe51f157104758bcb268643c8b6d317a530 (patch)
tree1c2fb3639dd1ae608ae00fc99130d09a427bf5cd /src/doc
parente361781bf7ce8acd4af00c426b34596804d45d77 (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.cpp110
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
///////////////////////////////////////////////////////////////////////////////