1/*
2 * Copyright 2017 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 "SampleCode.h"
9#include "SkCanvas.h"
10#include "SkCommandLineFlags.h"
11#include "SkOSPath.h"
12#include "SkPath.h"
13#include "SkPicture.h"
14#include "SkStream.h"
15#include <stack>
16
17DEFINE_string(pathfinderTrail, "", "List of keystrokes to execute upon loading a pathfinder.");
18
19/**
20 * This is a simple utility designed to extract the paths from an SKP file and then isolate a single
21 * one of them. Use the 'x' and 'X' keys to guide a binary search:
22 *
23 *   'x': Throw out half the paths.
24 *   'X': Toggle which half gets tossed and which half is kept.
25 *   'Z': Back up one level.
26 *   'D': Dump the path.
27 */
28class PathFinderView : public SampleView, public SkCanvas {
29public:
30    PathFinderView(const char name[] = nullptr)
31        : SkCanvas(4096, 4096, nullptr)
32        , fFilename(name) {
33        SkFILEStream stream(fFilename.c_str());
34        if (!stream.isValid()) {
35            SkDebugf("couldn't load picture at \"%s\"\n", fFilename.c_str());
36            return;
37        }
38        sk_sp<SkPicture> pic = SkPicture::MakeFromStream(&stream);
39        if (!pic) {
40            SkDebugf("couldn't load picture at \"%s\"\n", fFilename.c_str());
41            return;
42        }
43        pic->playback(this);
44        for (int i = 0; i < FLAGS_pathfinderTrail.count(); ++i) {
45            const char* key = FLAGS_pathfinderTrail[i];
46            while (*key) {
47                this->handleKeystroke(*key++);
48            }
49        }
50    }
51
52    ~PathFinderView() override {}
53
54private:
55    // Called through SkPicture::playback during construction.
56    void onDrawPath(const SkPath& path, const SkPaint& paint) override {
57        fPaths.push_back() = {path, paint, this->getTotalMatrix()};
58    }
59
60    // overrides from SkEventSink
61    bool onQuery(SkEvent* evt) override {
62        if (SampleCode::TitleQ(*evt)) {
63            SkString name("PATHFINDER:");
64            const char* basename = strrchr(fFilename.c_str(), SkOSPath::SEPARATOR);
65            name.append(basename ? basename+1: fFilename.c_str());
66            SampleCode::TitleR(evt, name.c_str());
67            return true;
68        }
69        SkUnichar key;
70        if (SampleCode::CharQ(*evt, &key)) {
71            if (this->handleKeystroke(key)) {
72                return true;
73            }
74        }
75        return this->INHERITED::onQuery(evt);
76    }
77
78    bool handleKeystroke(SkUnichar key) {
79        switch (key) {
80            case 'X':
81                if (!fTossedPaths.empty()) {
82                    SkTSwap(fPaths, fTossedPaths);
83                    if ('X' == fTrail.back()) {
84                        fTrail.pop_back();
85                    } else {
86                        fTrail.push_back('X');
87                    }
88                    this->inval(nullptr);
89                }
90                return true;
91            case 'x':
92                if (fPaths.count() > 1) {
93                    int midpt = (fPaths.count() + 1) / 2;
94                    fPathHistory.emplace(fPaths, fTossedPaths);
95                    fTossedPaths.reset(fPaths.begin() + midpt, fPaths.count() - midpt);
96                    fPaths.resize_back(midpt);
97                    fTrail.push_back('x');
98                    this->inval(nullptr);
99                }
100                return true;
101            case 'Z': {
102                if (!fPathHistory.empty()) {
103                    fPaths = fPathHistory.top().first;
104                    fTossedPaths = fPathHistory.top().second;
105                    fPathHistory.pop();
106                    char ch;
107                    do {
108                        ch = fTrail.back();
109                        fTrail.pop_back();
110                    } while (ch != 'x');
111                    this->inval(nullptr);
112                }
113                return true;
114            }
115            case 'D':
116                SkDebugf("SampleApp --pathfinder %s", fFilename.c_str());
117                if (!fTrail.empty()) {
118                    SkDebugf(" --pathfinderTrail ", fFilename.c_str());
119                    for (char ch : fTrail) {
120                        SkDebugf("%c", ch);
121                    }
122                }
123                SkDebugf("\n");
124                for (const FoundPath& foundPath : fPaths) {
125                    foundPath.fPath.dump();
126                }
127                return true;
128        }
129        return false;
130    }
131
132    void onDrawContent(SkCanvas* canvas) override {
133        for (const FoundPath& path : fPaths) {
134            SkAutoCanvasRestore acr(canvas, true);
135            canvas->concat(path.fViewMatrix);
136            canvas->drawPath(path.fPath, path.fPaint);
137        }
138    }
139
140    struct FoundPath {
141        SkPath     fPath;
142        SkPaint    fPaint;
143        SkMatrix   fViewMatrix;
144    };
145
146    SkString              fFilename;
147    SkTArray<FoundPath>   fPaths;
148    SkTArray<FoundPath>   fTossedPaths;
149    SkTArray<char>        fTrail;
150
151    std::stack<std::pair<SkTArray<FoundPath>, SkTArray<FoundPath>>> fPathHistory;
152
153    typedef SampleView INHERITED;
154};
155
156SampleView* CreateSamplePathFinderView(const char filename[]) {
157    return new PathFinderView(filename);
158}
159