SkMultiPictureDocument & SkMultiPictureDocumentReader

also, a new DM::Src.

motivation: To be used to test the printing pipeline in Chromium.

BUG=skia:5370

GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2023593002

Review-Url: https://codereview.chromium.org/2023593002
diff --git a/src/utils/SkMultiPictureDocument.cpp b/src/utils/SkMultiPictureDocument.cpp
new file mode 100644
index 0000000..1cbf0ae
--- /dev/null
+++ b/src/utils/SkMultiPictureDocument.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkMultiPictureDocument.h"
+#include "SkMultiPictureDocumentPriv.h"
+#include "SkPicture.h"
+#include "SkPictureRecorder.h"
+#include "SkStream.h"
+
+/*
+  File format:
+      BEGINNING_OF_FILE:
+        kMagic
+        uint32_t version_number
+        uint32_t page_count
+        {
+          uint64_t offset
+          float sizeX
+          float sizeY
+        } * page_count
+      FIRST_OFFSET:
+        skp file
+      SECOND_OFFSET:
+        skp file
+      ...
+      LAST_OFFSET:
+        skp file
+        "\nEndOfMultiPicture\n"
+*/
+
+namespace {
+static SkCanvas* trim(SkCanvas* canvas,
+                      SkScalar w, SkScalar h,
+                      const SkRect& trimBox) {
+    // Only trim if necessary.
+    if (trimBox != SkRect::MakeWH(w, h)) {
+        // All SkDocument implementations implement trimBox using a
+        // clip+translate.
+        canvas->clipRect(trimBox);
+        canvas->translate(trimBox.x(), trimBox.y());
+    }
+    return canvas;
+}
+
+struct NullWStream : public SkWStream {
+    NullWStream() : fN(0) {}
+    bool write(const void*, size_t n) override {
+        fN += n;
+        return true;
+    }
+    size_t bytesWritten() const override { return fN; }
+    size_t fN;
+};
+
+struct MultiPictureDocument final : public SkDocument {
+    SkPictureRecorder fPictureRecorder;
+    SkTArray<sk_sp<SkPicture>> fPages;
+    MultiPictureDocument(SkWStream* s, void (*d)(SkWStream*, bool))
+        : SkDocument(s, d) {}
+    ~MultiPictureDocument() { this->close(); }
+
+    SkCanvas* onBeginPage(SkScalar w, SkScalar h, const SkRect& c) override {
+        return trim(fPictureRecorder.beginRecording(w, h), w, h, c);
+    }
+    void onEndPage() override {
+        fPages.emplace_back(fPictureRecorder.finishRecordingAsPicture());
+    }
+    bool onClose(SkWStream* wStream) override {
+        SkASSERT(wStream);
+        SkASSERT(wStream->bytesWritten() == 0);
+        bool good = true;
+        good &= wStream->writeText(SkMultiPictureDocumentProtocol::kMagic);
+        good &= wStream->write32(SkToU32(1));  // version
+        good &= wStream->write32(SkToU32(fPages.count()));
+        uint64_t offset = wStream->bytesWritten();
+        offset += fPages.count() * sizeof(SkMultiPictureDocumentProtocol::Entry);
+        for (const auto& page : fPages) {
+            SkRect cullRect = page->cullRect();
+            // We recorded a picture at the origin.
+            SkASSERT(cullRect.x() == 0 && cullRect.y() == 0);
+            SkMultiPictureDocumentProtocol::Entry entry{
+                    offset, (float)cullRect.right(), (float)cullRect.bottom()};
+            good &= wStream->write(&entry, sizeof(entry));
+            NullWStream buffer;
+            page->serialize(&buffer);
+            offset += buffer.bytesWritten();
+        }
+        for (const auto& page : fPages) {
+            page->serialize(wStream);
+        }
+        SkASSERT(wStream->bytesWritten() == offset);
+        good &= wStream->writeText("\nEndOfMultiPicture\n");
+        fPages.reset();
+        return good;
+    }
+    void onAbort() override { fPages.reset(); }
+};
+}
+
+sk_sp<SkDocument> SkMakeMultiPictureDocument(SkWStream* wStream) {
+    return sk_make_sp<MultiPictureDocument>(wStream, nullptr);
+}
diff --git a/src/utils/SkMultiPictureDocument.h b/src/utils/SkMultiPictureDocument.h
new file mode 100644
index 0000000..1da105e
--- /dev/null
+++ b/src/utils/SkMultiPictureDocument.h
@@ -0,0 +1,16 @@
+/*
+ * 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 SkMultiPictureDocument_DEFINED
+#define SkMultiPictureDocument_DEFINED
+
+#include "SkDocument.h"
+
+/** Writes into an experimental, undocumented file format that is
+    useful for debugging documents printed via Skia. */
+SK_API sk_sp<SkDocument> SkMakeMultiPictureDocument(SkWStream* dst);
+
+#endif  // SkMultiPictureDocument_DEFINED
diff --git a/src/utils/SkMultiPictureDocumentPriv.h b/src/utils/SkMultiPictureDocumentPriv.h
new file mode 100644
index 0000000..124dad7
--- /dev/null
+++ b/src/utils/SkMultiPictureDocumentPriv.h
@@ -0,0 +1,23 @@
+/*
+ * 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 SkMultiPictureDocumentPriv_DEFINED
+#define SkMultiPictureDocumentPriv_DEFINED
+
+#include "stdint.h"
+
+namespace SkMultiPictureDocumentProtocol {
+static constexpr char kMagic[] = "Skia Multi-Picture Doc\n\n";
+
+struct Entry {
+    uint64_t offset;
+    float sizeX;
+    float sizeY;
+};
+}
+
+#endif  // SkMultiPictureDocumentPriv_DEFINED
diff --git a/src/utils/SkMultiPictureDocumentReader.cpp b/src/utils/SkMultiPictureDocumentReader.cpp
new file mode 100644
index 0000000..6bc77bf
--- /dev/null
+++ b/src/utils/SkMultiPictureDocumentReader.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkMultiPictureDocumentPriv.h"
+#include "SkMultiPictureDocumentReader.h"
+#include "SkPicture.h"
+#include "SkStream.h"
+
+bool SkMultiPictureDocumentReader::init(SkStreamSeekable* stream) {
+    if (!stream) {
+        return false;
+    }
+    stream->seek(0);
+    const size_t size = sizeof(SkMultiPictureDocumentProtocol::kMagic) - 1;
+    char buffer[size];
+    if (size != stream->read(buffer, size) ||
+        0 != memcmp(SkMultiPictureDocumentProtocol::kMagic, buffer, size)) {
+        stream = nullptr;
+        return false;
+    }
+    bool good = true;
+    uint32_t versionNumber = stream->readU32();
+    if (versionNumber != 1) {
+        return false;
+    }
+    uint32_t pageCount = stream->readU32();
+    fSizes.reset(pageCount);
+    fOffsets.reset(pageCount);
+    for (uint32_t i = 0; i < pageCount; ++i) {
+        SkMultiPictureDocumentProtocol::Entry entry;
+        good &= sizeof(entry) == stream->read(&entry, sizeof(entry));
+        fSizes[i] = SkSize::Make(entry.sizeX, entry.sizeY);
+        good &= SkTFitsIn<size_t>(entry.offset);
+        fOffsets[i] = static_cast<size_t>(entry.offset);
+    }
+    return good;
+}
+
+sk_sp<SkPicture> SkMultiPictureDocumentReader::readPage(SkStreamSeekable* stream,
+                                                        int pageNumber) const {
+    SkASSERT(pageNumber >= 0);
+    SkASSERT(pageNumber < fOffsets.count());
+    SkAssertResult(stream->seek(fOffsets[pageNumber]));
+    return SkPicture::MakeFromStream(stream);
+}
diff --git a/src/utils/SkMultiPictureDocumentReader.h b/src/utils/SkMultiPictureDocumentReader.h
new file mode 100644
index 0000000..8e0a630
--- /dev/null
+++ b/src/utils/SkMultiPictureDocumentReader.h
@@ -0,0 +1,45 @@
+/*
+ * 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 SkMultiPictureDocumentReader_DEFINED
+#define SkMultiPictureDocumentReader_DEFINED
+
+#include "../private/SkTArray.h"
+#include "SkPicture.h"
+#include "SkSize.h"
+#include "SkStream.h"
+
+/** A lightweight helper class for reading a Skia MultiPictureDocument. */
+class SkMultiPictureDocumentReader {
+public:
+    /** Initialize the MultiPictureDocument.  Does not take ownership
+        of the SkStreamSeekable. */
+    bool init(SkStreamSeekable*);
+
+    /** Return to factory settings. */
+    void reset() {
+        fSizes.reset();
+        fOffsets.reset();
+    }
+
+    /** Call this after calling init() */
+    int pageCount() const { return fSizes.count(); }
+
+    /** Deserialize a page from the stream.  Call init() first.  The
+        SkStreamSeekable doesn't need to be the same object, but
+        should point to the same information as before. */
+    sk_sp<SkPicture> readPage(SkStreamSeekable*, int) const;
+
+    /** Fetch the size of the given page, without deserializing the
+        entire page. */
+    SkSize pageSize(int i) const { return fSizes[i]; }
+
+private:
+    SkTArray<SkSize> fSizes;
+    SkTArray<size_t> fOffsets;
+};
+
+#endif  // SkMultiPictureDocumentReader_DEFINED