SkMultiSKP: version 2

Measurable size improvement.

BUG=skia:5370

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

Review-Url: https://codereview.chromium.org/2255333003
diff --git a/src/utils/SkMultiPictureDocument.cpp b/src/utils/SkMultiPictureDocument.cpp
index 214e6ad..c40f1c9 100644
--- a/src/utils/SkMultiPictureDocument.cpp
+++ b/src/utils/SkMultiPictureDocument.cpp
@@ -5,33 +5,24 @@
  * found in the LICENSE file.
  */
 
-#include <vector>
-
 #include "SkMultiPictureDocument.h"
 #include "SkMultiPictureDocumentPriv.h"
 #include "SkPicture.h"
 #include "SkPictureRecorder.h"
 #include "SkStream.h"
+#include "SkTArray.h"
 
 /*
   File format:
       BEGINNING_OF_FILE:
         kMagic
-        uint32_t version_number
+        uint32_t version_number (==2)
         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 {
@@ -48,30 +39,11 @@
     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 Page {
-  Page(SkSize s, sk_sp<SkPicture> c) : fSize(s), fContent(std::move(c)) {}
-  Page(Page&&) = default;
-  Page(const Page&) = default;
-  Page& operator=(const Page&) = default;
-  Page& operator=(Page&&) = default;
-  SkSize fSize;
-  sk_sp<SkPicture> fContent;
-};
-
 struct MultiPictureDocument final : public SkDocument {
     SkPictureRecorder fPictureRecorder;
     SkSize fCurrentPageSize;
-    std::vector<Page> fPages;
+    SkTArray<sk_sp<SkPicture>> fPages;
+    SkTArray<SkSize> fSizes;
     MultiPictureDocument(SkWStream* s, void (*d)(SkWStream*, bool))
         : SkDocument(s, d) {}
     ~MultiPictureDocument() { this->close(); }
@@ -81,35 +53,37 @@
         return trim(fPictureRecorder.beginRecording(w, h), w, h, c);
     }
     void onEndPage() override {
-        fPages.emplace_back(fCurrentPageSize,
-                            fPictureRecorder.finishRecordingAsPicture());
+        fSizes.push_back(fCurrentPageSize);
+        fPages.push_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.size()));
-        uint64_t offset = wStream->bytesWritten();
-        offset += fPages.size() * sizeof(SkMultiPictureDocumentProtocol::Entry);
-        for (const auto& page : fPages) {
-            SkMultiPictureDocumentProtocol::Entry entry{
-                    offset, page.fSize.width(), page.fSize.height()};
-            good &= wStream->write(&entry, sizeof(entry));
-            NullWStream buffer;
-            page.fContent->serialize(&buffer);
-            offset += buffer.bytesWritten();
+        good &= wStream->write32(SkMultiPictureDocumentProtocol::kVersion);
+        good &= wStream->write32(SkToU32(fPages.count()));
+        for (SkSize s : fSizes) {
+            good &= wStream->write(&s, sizeof(s));
         }
-        for (const auto& page : fPages) {
-            page.fContent->serialize(wStream);
+        SkSize bigsize = SkMultiPictureDocumentProtocol::Join(fSizes);
+        SkCanvas* c = fPictureRecorder.beginRecording(SkRect::MakeSize(bigsize));
+        for (const sk_sp<SkPicture>& page : fPages) {
+            c->drawPicture(page);
+            c->drawAnnotation(SkRect::MakeEmpty(),
+                              SkMultiPictureDocumentProtocol::kEndPage,
+                              nullptr);
         }
-        SkASSERT(wStream->bytesWritten() == offset);
-        good &= wStream->writeText("\nEndOfMultiPicture\n");
-        fPages.clear();
+        sk_sp<SkPicture> p = fPictureRecorder.finishRecordingAsPicture();
+        p->serialize(wStream);
+        fPages.reset();
+        fSizes.reset();
         return good;
     }
-    void onAbort() override { fPages.clear(); }
+    void onAbort() override {
+        fPages.reset();
+        fSizes.reset();
+    }
 };
 }
 
diff --git a/src/utils/SkMultiPictureDocument.h b/src/utils/SkMultiPictureDocument.h
index 1da105e..ac78260 100644
--- a/src/utils/SkMultiPictureDocument.h
+++ b/src/utils/SkMultiPictureDocument.h
@@ -7,6 +7,38 @@
 #ifndef SkMultiPictureDocument_DEFINED
 #define SkMultiPictureDocument_DEFINED
 
+/*
+  This format is not intended to be used in production.
+
+  For clients looking for a way to represent a document in memory,
+
+    struct Doc {
+        std::vector<sk_sp<SkPicture>> fPages;
+        std::vector<SkSize> fPageSizes;
+    };
+
+  or
+
+    struct Page {
+        sk_sp<SkPicture> fPage;
+        SkSize fPageSize;
+    };
+    std::vector<Page> pages;
+
+  would work much better.
+
+  Multi-SkPicture (MSKP) files are still useful for debugging and
+  testing.
+
+  The downsides of this format are currently:
+  - no way to extract a single page; must read the entire file at once.
+  - must use `dm` to convert to another format before passing into
+    standard skp tools.
+  - `dm` can extract the first page to skp, but no others.
+
+  TODO(halcanary): replace with somthing that addresses these issues.
+ */
+
 #include "SkDocument.h"
 
 /** Writes into an experimental, undocumented file format that is
diff --git a/src/utils/SkMultiPictureDocumentPriv.h b/src/utils/SkMultiPictureDocumentPriv.h
index 124dad7..6d5ab47 100644
--- a/src/utils/SkMultiPictureDocumentPriv.h
+++ b/src/utils/SkMultiPictureDocumentPriv.h
@@ -8,16 +8,25 @@
 #ifndef SkMultiPictureDocumentPriv_DEFINED
 #define SkMultiPictureDocumentPriv_DEFINED
 
-#include "stdint.h"
+#include "SkTArray.h"
+#include "SkSize.h"
 
 namespace SkMultiPictureDocumentProtocol {
 static constexpr char kMagic[] = "Skia Multi-Picture Doc\n\n";
 
-struct Entry {
-    uint64_t offset;
-    float sizeX;
-    float sizeY;
-};
+static constexpr char kEndPage[] = "SkMultiPictureEndPage";
+
+const uint32_t kVersion = 2;
+
+inline SkSize Join(const SkTArray<SkSize>& sizes) {
+    SkSize joined = SkSize::Make(0, 0);
+    for (SkSize s : sizes) {
+        joined = SkSize::Make(SkTMax(joined.width(), s.width()),
+                              SkTMax(joined.height(), s.height()));
+    }
+    return joined;
+}
+
 }
 
 #endif  // SkMultiPictureDocumentPriv_DEFINED
diff --git a/src/utils/SkMultiPictureDocumentReader.cpp b/src/utils/SkMultiPictureDocumentReader.cpp
index 6bc77bf..3924f3e 100644
--- a/src/utils/SkMultiPictureDocumentReader.cpp
+++ b/src/utils/SkMultiPictureDocumentReader.cpp
@@ -9,6 +9,8 @@
 #include "SkMultiPictureDocumentReader.h"
 #include "SkPicture.h"
 #include "SkStream.h"
+#include "SkPictureRecorder.h"
+#include "SkNWayCanvas.h"
 
 bool SkMultiPictureDocumentReader::init(SkStreamSeekable* stream) {
     if (!stream) {
@@ -24,26 +26,68 @@
     }
     bool good = true;
     uint32_t versionNumber = stream->readU32();
-    if (versionNumber != 1) {
+    if (versionNumber != SkMultiPictureDocumentProtocol::kVersion) {
         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);
+        SkSize size;
+        good &= sizeof(size) == stream->read(&size, sizeof(size));
+        fSizes[i] = size;
     }
+    fOffset = stream->getPosition();
     return good;
 }
 
+namespace {
+struct PagerCanvas : public SkNWayCanvas {
+    SkPictureRecorder fRecorder;
+    const SkTArray<SkSize>* fSizes;
+    SkTArray<sk_sp<SkPicture>>* fDest;
+    PagerCanvas(SkISize  wh,
+                const SkTArray<SkSize>* s,
+                SkTArray<sk_sp<SkPicture>>* d)
+        : SkNWayCanvas(wh.width(), wh.height()), fSizes(s), fDest(d) {
+        this->nextCanvas();
+    }
+    void nextCanvas() {
+        int i = fDest->count();
+        if (i < fSizes->count()) {
+            SkRect bounds = SkRect::MakeSize((*fSizes)[i]);
+            this->addCanvas(fRecorder.beginRecording(bounds));
+        }
+    }
+    void onDrawAnnotation(const SkRect& r, const char* key, SkData* d) override {
+        if (0 == strcmp(key, SkMultiPictureDocumentProtocol::kEndPage)) {
+            this->removeAll();
+            if (fRecorder.getRecordingCanvas()) {
+                fDest->emplace_back(fRecorder.finishRecordingAsPicture());
+            }
+            this->nextCanvas();
+        } else {
+            this->SkNWayCanvas::onDrawAnnotation(r, key, d);
+        }
+    }
+};
+}  // namespace
+
 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);
+    SkASSERT(pageNumber < fSizes.count());
+    if (0 == fPages.count()) {
+        stream->seek(fOffset); // jump to beginning of skp
+        auto picture = SkPicture::MakeFromStream(stream);
+        SkISize size = SkMultiPictureDocumentProtocol::Join(fSizes).toCeil();
+        PagerCanvas canvas(size, &fSizes, &this->fPages);
+        // Must call playback(), not drawPicture() to reach
+        // PagerCanvas::onDrawAnnotation().
+        picture->playback(&canvas);
+        if (fPages.count() != fSizes.count()) {
+            SkDEBUGF(("Malformed SkMultiPictureDocument\n"));
+        }
+    }
+    // Allow for malformed document.
+    return pageNumber < fPages.count() ? fPages[pageNumber] : nullptr;
 }
diff --git a/src/utils/SkMultiPictureDocumentReader.h b/src/utils/SkMultiPictureDocumentReader.h
index 8e0a630..e0473a6 100644
--- a/src/utils/SkMultiPictureDocumentReader.h
+++ b/src/utils/SkMultiPictureDocumentReader.h
@@ -22,10 +22,10 @@
     /** Return to factory settings. */
     void reset() {
         fSizes.reset();
-        fOffsets.reset();
+        fPages.reset();
     }
 
-    /** Call this after calling init() */
+    /** Call this after calling init() (otherwise you'll always get zero). */
     int pageCount() const { return fSizes.count(); }
 
     /** Deserialize a page from the stream.  Call init() first.  The
@@ -39,7 +39,8 @@
 
 private:
     SkTArray<SkSize> fSizes;
-    SkTArray<size_t> fOffsets;
+    size_t fOffset;
+    mutable SkTArray<sk_sp<SkPicture>> fPages;
 };
 
 #endif  // SkMultiPictureDocumentReader_DEFINED