SkPDF: move all pdf sources into src/pdf
also, consolidate XPS backend into src/xps
remove from include/ almost always a good thing.
TBR=reed@google.com
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1781773002
CQ_EXTRA_TRYBOTS=client.skia.compile:Build-Ubuntu-GCC-x86_64-Release-CMake-Trybot,Build-Mac-Clang-x86_64-Release-CMake-Trybot
Review URL: https://codereview.chromium.org/1781773002
diff --git a/src/pdf/SkDocument_PDF_None.cpp b/src/pdf/SkDocument_PDF_None.cpp
new file mode 100644
index 0000000..f146cba
--- /dev/null
+++ b/src/pdf/SkDocument_PDF_None.cpp
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SkDocument.h"
+SkDocument* SkDocument::CreatePDF(SkWStream*, SkScalar) { return nullptr; }
+SkDocument* SkDocument::CreatePDF(const char path[], SkScalar) { return nullptr; }
diff --git a/src/pdf/SkPDFDocument.cpp b/src/pdf/SkPDFDocument.cpp
new file mode 100644
index 0000000..bacd9ec
--- /dev/null
+++ b/src/pdf/SkPDFDocument.cpp
@@ -0,0 +1,404 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPDFCanon.h"
+#include "SkPDFDevice.h"
+#include "SkPDFDocument.h"
+#include "SkPDFFont.h"
+#include "SkPDFMetadata.h"
+#include "SkPDFStream.h"
+#include "SkPDFTypes.h"
+#include "SkPDFUtils.h"
+#include "SkStream.h"
+
+static void emit_pdf_header(SkWStream* stream) {
+ stream->writeText("%PDF-1.4\n%");
+ // The PDF spec recommends including a comment with four bytes, all
+ // with their high bits set. This is "Skia" with the high bits set.
+ stream->write32(0xD3EBE9E1);
+ stream->writeText("\n");
+}
+
+static void emit_pdf_footer(SkWStream* stream,
+ const SkPDFObjNumMap& objNumMap,
+ const SkPDFSubstituteMap& substitutes,
+ SkPDFObject* docCatalog,
+ int64_t objCount,
+ int32_t xRefFileOffset,
+ sk_sp<SkPDFObject> info,
+ sk_sp<SkPDFObject> id) {
+ SkPDFDict trailerDict;
+ // TODO(http://crbug.com/80908): Linearized format will take a
+ // Prev entry too.
+ trailerDict.insertInt("Size", int(objCount));
+ trailerDict.insertObjRef("Root", sk_ref_sp(docCatalog));
+ SkASSERT(info);
+ trailerDict.insertObjRef("Info", std::move(info));
+ if (id) {
+ trailerDict.insertObject("ID", std::move(id));
+ }
+ stream->writeText("trailer\n");
+ trailerDict.emitObject(stream, objNumMap, substitutes);
+ stream->writeText("\nstartxref\n");
+ stream->writeBigDecAsText(xRefFileOffset);
+ stream->writeText("\n%%EOF");
+}
+
+static void perform_font_subsetting(
+ const SkTArray<sk_sp<const SkPDFDevice>>& pageDevices,
+ SkPDFSubstituteMap* substituteMap) {
+ SkASSERT(substituteMap);
+
+ SkPDFGlyphSetMap usage;
+ for (const sk_sp<const SkPDFDevice>& pageDevice : pageDevices) {
+ usage.merge(pageDevice->getFontGlyphUsage());
+ }
+ SkPDFGlyphSetMap::F2BIter iterator(usage);
+ const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next();
+ while (entry) {
+ sk_sp<SkPDFFont> subsetFont(
+ entry->fFont->getFontSubset(entry->fGlyphSet));
+ if (subsetFont) {
+ substituteMap->setSubstitute(entry->fFont, subsetFont.get());
+ }
+ entry = iterator.next();
+ }
+}
+
+static sk_sp<SkPDFDict> create_pdf_page(const SkPDFDevice* pageDevice) {
+ auto page = sk_make_sp<SkPDFDict>("Page");
+ page->insertObject("Resources", pageDevice->makeResourceDict());
+ page->insertObject("MediaBox", pageDevice->copyMediaBox());
+ auto annotations = sk_make_sp<SkPDFArray>();
+ pageDevice->appendAnnotations(annotations.get());
+ if (annotations->size() > 0) {
+ page->insertObject("Annots", std::move(annotations));
+ }
+ auto content = pageDevice->content();
+ page->insertObjRef("Contents", sk_make_sp<SkPDFStream>(content.get()));
+ return page;
+}
+
+// return root node.
+static sk_sp<SkPDFDict> generate_page_tree(
+ const SkTDArray<SkPDFDict*>& pages,
+ SkTDArray<SkPDFDict*>* pageTree) {
+ // PDF wants a tree describing all the pages in the document. We arbitrary
+ // choose 8 (kNodeSize) as the number of allowed children. The internal
+ // nodes have type "Pages" with an array of children, a parent pointer, and
+ // the number of leaves below the node as "Count." The leaves are passed
+ // into the method, have type "Page" and need a parent pointer. This method
+ // builds the tree bottom up, skipping internal nodes that would have only
+ // one child.
+ static const int kNodeSize = 8;
+
+ // curNodes takes a reference to its items, which it passes to pageTree.
+ SkTDArray<SkPDFDict*> curNodes;
+ curNodes.setReserve(pages.count());
+ for (int i = 0; i < pages.count(); i++) {
+ SkSafeRef(pages[i]);
+ curNodes.push(pages[i]);
+ }
+
+ // nextRoundNodes passes its references to nodes on to curNodes.
+ SkTDArray<SkPDFDict*> nextRoundNodes;
+ nextRoundNodes.setReserve((pages.count() + kNodeSize - 1)/kNodeSize);
+
+ int treeCapacity = kNodeSize;
+ do {
+ for (int i = 0; i < curNodes.count(); ) {
+ if (i > 0 && i + 1 == curNodes.count()) {
+ nextRoundNodes.push(curNodes[i]);
+ break;
+ }
+
+ auto newNode = sk_make_sp<SkPDFDict>("Pages");
+ auto kids = sk_make_sp<SkPDFArray>();
+ kids->reserve(kNodeSize);
+
+ int count = 0;
+ for (; i < curNodes.count() && count < kNodeSize; i++, count++) {
+ curNodes[i]->insertObjRef("Parent", newNode);
+ kids->appendObjRef(sk_ref_sp(curNodes[i]));
+
+ // TODO(vandebo): put the objects in strict access order.
+ // Probably doesn't matter because they are so small.
+ if (curNodes[i] != pages[0]) {
+ pageTree->push(curNodes[i]); // Transfer reference.
+ } else {
+ SkSafeUnref(curNodes[i]);
+ }
+ }
+
+ // treeCapacity is the number of leaf nodes possible for the
+ // current set of subtrees being generated. (i.e. 8, 64, 512, ...).
+ // It is hard to count the number of leaf nodes in the current
+ // subtree. However, by construction, we know that unless it's the
+ // last subtree for the current depth, the leaf count will be
+ // treeCapacity, otherwise it's what ever is left over after
+ // consuming treeCapacity chunks.
+ int pageCount = treeCapacity;
+ if (i == curNodes.count()) {
+ pageCount = ((pages.count() - 1) % treeCapacity) + 1;
+ }
+ newNode->insertInt("Count", pageCount);
+ newNode->insertObject("Kids", std::move(kids));
+ nextRoundNodes.push(newNode.release()); // Transfer reference.
+ }
+
+ curNodes = nextRoundNodes;
+ nextRoundNodes.rewind();
+ treeCapacity *= kNodeSize;
+ } while (curNodes.count() > 1);
+
+ pageTree->push(curNodes[0]); // Transfer reference.
+ return sk_ref_sp(curNodes[0]);
+}
+
+static bool emit_pdf_document(const SkTArray<sk_sp<const SkPDFDevice>>& pageDevices,
+ const SkPDFMetadata& metadata,
+ SkWStream* stream) {
+ if (pageDevices.empty()) {
+ return false;
+ }
+
+ SkTDArray<SkPDFDict*> pages; // TODO: SkTArray<sk_sp<SkPDFDict>>
+ auto dests = sk_make_sp<SkPDFDict>();
+
+ for (const sk_sp<const SkPDFDevice>& pageDevice : pageDevices) {
+ SkASSERT(pageDevice);
+ SkASSERT(pageDevices[0]->getCanon() == pageDevice->getCanon());
+ sk_sp<SkPDFDict> page(create_pdf_page(pageDevice.get()));
+ pageDevice->appendDestinations(dests.get(), page.get());
+ pages.push(page.release());
+ }
+
+ auto docCatalog = sk_make_sp<SkPDFDict>("Catalog");
+
+ sk_sp<SkPDFObject> infoDict(metadata.createDocumentInformationDict());
+
+ sk_sp<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", std::move(xmp));
+
+ // sRGB is specified by HTML, CSS, and SVG.
+ auto outputIntent = sk_make_sp<SkPDFDict>("OutputIntent");
+ outputIntent->insertName("S", "GTS_PDFA1");
+ outputIntent->insertString("RegistryName", "http://www.color.org");
+ outputIntent->insertString("OutputConditionIdentifier",
+ "sRGB IEC61966-2.1");
+ auto intentArray = sk_make_sp<SkPDFArray>();
+ intentArray->appendObject(std::move(outputIntent));
+ // Don't specify OutputIntents if we are not in PDF/A mode since
+ // no one has ever asked for this feature.
+ docCatalog->insertObject("OutputIntents", std::move(intentArray));
+#endif
+
+ SkTDArray<SkPDFDict*> pageTree;
+ docCatalog->insertObjRef("Pages", generate_page_tree(pages, &pageTree));
+
+ if (dests->size() > 0) {
+ docCatalog->insertObjRef("Dests", std::move(dests));
+ }
+
+ // Build font subsetting info before proceeding.
+ SkPDFSubstituteMap substitutes;
+ perform_font_subsetting(pageDevices, &substitutes);
+
+ SkPDFObjNumMap objNumMap;
+ objNumMap.addObjectRecursively(infoDict.get(), substitutes);
+ objNumMap.addObjectRecursively(docCatalog.get(), substitutes);
+ size_t baseOffset = stream->bytesWritten();
+ emit_pdf_header(stream);
+ SkTDArray<int32_t> offsets;
+ for (int i = 0; i < objNumMap.objects().count(); ++i) {
+ SkPDFObject* object = objNumMap.objects()[i];
+ size_t offset = stream->bytesWritten();
+ // This assert checks that size(pdf_header) > 0 and that
+ // the output stream correctly reports bytesWritten().
+ SkASSERT(offset > baseOffset);
+ offsets.push(SkToS32(offset - baseOffset));
+ SkASSERT(object == substitutes.getSubstitute(object));
+ SkASSERT(objNumMap.getObjectNumber(object) == i + 1);
+ stream->writeDecAsText(i + 1);
+ stream->writeText(" 0 obj\n"); // Generation number is always 0.
+ object->emitObject(stream, objNumMap, substitutes);
+ stream->writeText("\nendobj\n");
+ }
+ int32_t xRefFileOffset = SkToS32(stream->bytesWritten() - baseOffset);
+
+ // Include the zeroth object in the count.
+ int32_t objCount = SkToS32(offsets.count() + 1);
+
+ stream->writeText("xref\n0 ");
+ stream->writeDecAsText(objCount);
+ stream->writeText("\n0000000000 65535 f \n");
+ for (int i = 0; i < offsets.count(); i++) {
+ SkASSERT(offsets[i] > 0);
+ stream->writeBigDecAsText(offsets[i], 10);
+ stream->writeText(" 00000 n \n");
+ }
+ emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount,
+ xRefFileOffset, std::move(infoDict), std::move(id));
+
+ // The page tree has both child and parent pointers, so it creates a
+ // reference cycle. We must clear that cycle to properly reclaim memory.
+ for (int i = 0; i < pageTree.count(); i++) {
+ pageTree[i]->clear();
+ }
+ pageTree.safeUnrefAll();
+ pages.unrefAll();
+ return true;
+}
+
+#if 0
+// TODO(halcanary): expose notEmbeddableCount in SkDocument
+void GetCountOfFontTypes(
+ const SkTDArray<SkPDFDevice*>& pageDevices,
+ int counts[SkAdvancedTypefaceMetrics::kOther_Font + 1],
+ int* notSubsettableCount,
+ int* notEmbeddableCount) {
+ sk_bzero(counts, sizeof(int) *
+ (SkAdvancedTypefaceMetrics::kOther_Font + 1));
+ SkTDArray<SkFontID> seenFonts;
+ int notSubsettable = 0;
+ int notEmbeddable = 0;
+
+ for (int pageNumber = 0; pageNumber < pageDevices.count(); pageNumber++) {
+ const SkTDArray<SkPDFFont*>& fontResources =
+ pageDevices[pageNumber]->getFontResources();
+ for (int font = 0; font < fontResources.count(); font++) {
+ SkFontID fontID = fontResources[font]->typeface()->uniqueID();
+ if (seenFonts.find(fontID) == -1) {
+ counts[fontResources[font]->getType()]++;
+ seenFonts.push(fontID);
+ if (!fontResources[font]->canSubset()) {
+ notSubsettable++;
+ }
+ if (!fontResources[font]->canEmbed()) {
+ notEmbeddable++;
+ }
+ }
+ }
+ }
+ if (notSubsettableCount) {
+ *notSubsettableCount = notSubsettable;
+
+ }
+ if (notEmbeddableCount) {
+ *notEmbeddableCount = notEmbeddable;
+ }
+}
+#endif
+
+template <typename T> static T* clone(const T* o) { return o ? new T(*o) : nullptr; }
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+class SkPDFDocument : public SkDocument {
+public:
+ SkPDFDocument(SkWStream* stream,
+ void (*doneProc)(SkWStream*, bool),
+ SkScalar rasterDpi,
+ SkPixelSerializer* jpegEncoder)
+ : SkDocument(stream, doneProc)
+ , fRasterDpi(rasterDpi) {
+ fCanon.setPixelSerializer(SkSafeRef(jpegEncoder));
+ }
+
+ virtual ~SkPDFDocument() {
+ // subclasses must call close() in their destructors
+ this->close();
+ }
+
+protected:
+ SkCanvas* onBeginPage(SkScalar width, SkScalar height,
+ const SkRect& trimBox) override {
+ SkASSERT(!fCanvas.get());
+
+ SkISize pageSize = SkISize::Make(
+ SkScalarRoundToInt(width), SkScalarRoundToInt(height));
+ sk_sp<SkPDFDevice> device(
+ SkPDFDevice::Create(pageSize, fRasterDpi, &fCanon));
+ fCanvas.reset(new SkCanvas(device.get()));
+ fPageDevices.push_back(std::move(device));
+ fCanvas->clipRect(trimBox);
+ fCanvas->translate(trimBox.x(), trimBox.y());
+ return fCanvas.get();
+ }
+
+ void onEndPage() override {
+ SkASSERT(fCanvas.get());
+ fCanvas->flush();
+ fCanvas.reset(nullptr);
+ }
+
+ bool onClose(SkWStream* stream) override {
+ SkASSERT(!fCanvas.get());
+
+ bool success = emit_pdf_document(fPageDevices, fMetadata, stream);
+ fPageDevices.reset();
+ fCanon.reset();
+ return success;
+ }
+
+ void onAbort() override {
+ fPageDevices.reset();
+ fCanon.reset();
+ }
+
+ void setMetadata(const SkDocument::Attribute info[],
+ int infoCount,
+ const SkTime::DateTime* creationDate,
+ const SkTime::DateTime* modifiedDate) override {
+ fMetadata.fInfo.reset(info, infoCount);
+ fMetadata.fCreation.reset(clone(creationDate));
+ fMetadata.fModified.reset(clone(modifiedDate));
+ }
+
+private:
+ SkPDFCanon fCanon;
+ SkTArray<sk_sp<const SkPDFDevice>> fPageDevices;
+ sk_sp<SkCanvas> fCanvas;
+ SkScalar fRasterDpi;
+ SkPDFMetadata fMetadata;
+};
+} // namespace
+///////////////////////////////////////////////////////////////////////////////
+
+sk_sp<SkDocument> SkPDFMakeDocument(SkWStream* stream,
+ void (*proc)(SkWStream*, bool),
+ SkScalar dpi,
+ SkPixelSerializer* jpeg) {
+ return stream ? sk_make_sp<SkPDFDocument>(stream, proc, dpi, jpeg) : nullptr;
+}
+
+SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) {
+ return SkPDFMakeDocument(stream, nullptr, dpi, nullptr).release();
+}
+
+SkDocument* SkDocument::CreatePDF(SkWStream* stream,
+ SkScalar dpi,
+ SkPixelSerializer* jpegEncoder) {
+ return SkPDFMakeDocument(stream, nullptr, dpi, jpegEncoder).release();
+}
+
+SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) {
+ auto delete_wstream = [](SkWStream* stream, bool) { delete stream; };
+ SkAutoTDelete<SkFILEWStream> stream(new SkFILEWStream(path));
+ return stream->isValid()
+ ? SkPDFMakeDocument(stream.detach(), delete_wstream, dpi, nullptr).release()
+ : nullptr;
+}
diff --git a/src/pdf/SkPDFDocument.h b/src/pdf/SkPDFDocument.h
new file mode 100644
index 0000000..8ed7677
--- /dev/null
+++ b/src/pdf/SkPDFDocument.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkPDFDocument_DEFINED
+#define SkPDFDocument_DEFINED
+
+#include "SkDocument.h"
+
+sk_sp<SkDocument> SkPDFMakeDocument(
+ SkWStream* stream,
+ void (*doneProc)(SkWStream*, bool),
+ SkScalar rasterDpi,
+ SkPixelSerializer* jpegEncoder);
+
+#endif // SkPDFDocument_DEFINED