1/*
2 * Copyright 2016 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "SkMultiPictureDocument.h"
9#include "SkMultiPictureDocumentPriv.h"
10#include "SkNWayCanvas.h"
11#include "SkPicture.h"
12#include "SkPictureRecorder.h"
13#include "SkSerialProcs.h"
14#include "SkStream.h"
15#include "SkTArray.h"
16
17#include <limits.h>
18
19/*
20  File format:
21      BEGINNING_OF_FILE:
22        kMagic
23        uint32_t version_number (==2)
24        uint32_t page_count
25        {
26          float sizeX
27          float sizeY
28        } * page_count
29        skp file
30*/
31
32namespace {
33// The unique file signature for this file type.
34static constexpr char kMagic[] = "Skia Multi-Picture Doc\n\n";
35
36static constexpr char kEndPage[] = "SkMultiPictureEndPage";
37
38const uint32_t kVersion = 2;
39
40static SkSize join(const SkTArray<SkSize>& sizes) {
41    SkSize joined = {0, 0};
42    for (SkSize s : sizes) {
43        joined = SkSize{SkTMax(joined.width(), s.width()), SkTMax(joined.height(), s.height())};
44    }
45    return joined;
46}
47
48struct MultiPictureDocument final : public SkDocument {
49    const SkSerialProcs fProcs;
50    SkPictureRecorder fPictureRecorder;
51    SkSize fCurrentPageSize;
52    SkTArray<sk_sp<SkPicture>> fPages;
53    SkTArray<SkSize> fSizes;
54    MultiPictureDocument(SkWStream* s, const SkSerialProcs* procs)
55        : SkDocument(s)
56        , fProcs(procs ? *procs : SkSerialProcs())
57    {}
58    ~MultiPictureDocument() override { this->close(); }
59
60    SkCanvas* onBeginPage(SkScalar w, SkScalar h) override {
61        fCurrentPageSize.set(w, h);
62        return fPictureRecorder.beginRecording(w, h);
63    }
64    void onEndPage() override {
65        fSizes.push_back(fCurrentPageSize);
66        fPages.push_back(fPictureRecorder.finishRecordingAsPicture());
67    }
68    void onClose(SkWStream* wStream) override {
69        SkASSERT(wStream);
70        SkASSERT(wStream->bytesWritten() == 0);
71        wStream->writeText(kMagic);
72        wStream->write32(kVersion);
73        wStream->write32(SkToU32(fPages.count()));
74        for (SkSize s : fSizes) {
75            wStream->write(&s, sizeof(s));
76        }
77        SkSize bigsize = join(fSizes);
78        SkCanvas* c = fPictureRecorder.beginRecording(SkRect::MakeSize(bigsize));
79        for (const sk_sp<SkPicture>& page : fPages) {
80            c->drawPicture(page);
81            c->drawAnnotation(SkRect::MakeEmpty(), kEndPage, nullptr);
82        }
83        sk_sp<SkPicture> p = fPictureRecorder.finishRecordingAsPicture();
84        p->serialize(wStream, &fProcs);
85        fPages.reset();
86        fSizes.reset();
87        return;
88    }
89    void onAbort() override {
90        fPages.reset();
91        fSizes.reset();
92    }
93};
94}
95
96sk_sp<SkDocument> SkMakeMultiPictureDocument(SkWStream* wStream, const SkSerialProcs* procs) {
97    return sk_make_sp<MultiPictureDocument>(wStream, procs);
98}
99
100////////////////////////////////////////////////////////////////////////////////
101
102int SkMultiPictureDocumentReadPageCount(SkStreamSeekable* stream) {
103    if (!stream) {
104        return 0;
105    }
106    stream->seek(0);
107    const size_t size = sizeof(kMagic) - 1;
108    char buffer[size];
109    if (size != stream->read(buffer, size) || 0 != memcmp(kMagic, buffer, size)) {
110        stream = nullptr;
111        return 0;
112    }
113    uint32_t versionNumber = stream->readU32();
114    if (versionNumber != kVersion) {
115        return 0;
116    }
117    uint32_t pageCount = stream->readU32();
118    if (pageCount > INT_MAX) {
119        return 0;
120    }
121    // leave stream position right here.
122    return (int)pageCount;
123}
124
125bool SkMultiPictureDocumentReadPageSizes(SkStreamSeekable* stream,
126                                         SkDocumentPage* dstArray,
127                                         int dstArrayCount) {
128    if (!dstArray || dstArrayCount < 1) {
129        return false;
130    }
131    int pageCount = SkMultiPictureDocumentReadPageCount(stream);
132    if (pageCount < 1 || pageCount != dstArrayCount) {
133        return false;
134    }
135    for (int i = 0; i < pageCount; ++i) {
136        SkSize& s = dstArray[i].fSize;
137        if (sizeof(s) != stream->read(&s, sizeof(s))) {
138            return false;
139        }
140    }
141    // leave stream position right here.
142    return true;
143}
144
145namespace {
146struct PagerCanvas : public SkNWayCanvas {
147    SkPictureRecorder fRecorder;
148    SkDocumentPage* fDst;
149    int fCount;
150    int fIndex = 0;
151    PagerCanvas(SkISize wh, SkDocumentPage* dst, int count)
152            : SkNWayCanvas(wh.width(), wh.height()), fDst(dst), fCount(count) {
153        this->nextCanvas();
154    }
155    void nextCanvas() {
156        if (fIndex < fCount) {
157            SkRect bounds = SkRect::MakeSize(fDst[fIndex].fSize);
158            this->addCanvas(fRecorder.beginRecording(bounds));
159        }
160    }
161    void onDrawAnnotation(const SkRect& r, const char* key, SkData* d) override {
162        if (0 == strcmp(key, kEndPage)) {
163            this->removeAll();
164            if (fIndex < fCount) {
165                fDst[fIndex].fPicture = fRecorder.finishRecordingAsPicture();
166                ++fIndex;
167            }
168            this->nextCanvas();
169        } else {
170            this->SkNWayCanvas::onDrawAnnotation(r, key, d);
171        }
172    }
173};
174}  // namespace
175
176bool SkMultiPictureDocumentRead(SkStreamSeekable* stream,
177                                SkDocumentPage* dstArray,
178                                int dstArrayCount,
179                                const SkDeserialProcs* procs) {
180    if (!SkMultiPictureDocumentReadPageSizes(stream, dstArray, dstArrayCount)) {
181        return false;
182    }
183    SkSize joined = {0.0f, 0.0f};
184    for (int i = 0; i < dstArrayCount; ++i) {
185        joined = SkSize{SkTMax(joined.width(), dstArray[i].fSize.width()),
186                        SkTMax(joined.height(), dstArray[i].fSize.height())};
187    }
188
189    auto picture = SkPicture::MakeFromStream(stream, procs);
190
191    PagerCanvas canvas(joined.toCeil(), dstArray, dstArrayCount);
192    // Must call playback(), not drawPicture() to reach
193    // PagerCanvas::onDrawAnnotation().
194    picture->playback(&canvas);
195    if (canvas.fIndex != dstArrayCount) {
196        SkDEBUGF(("Malformed SkMultiPictureDocument\n"));
197    }
198    return true;
199}
200