1/*
2 * Copyright 2015 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#include "SkCanvas.h"
8#include "SkCommandLineFlags.h"
9#include "SkDocument.h"
10#include "SkForceLinking.h"
11#include "SkGraphics.h"
12#include "SkMD5.h"
13#include "SkOSFile.h"
14#include "SkPicture.h"
15#include "SkStream.h"
16#include "SkTArray.h"
17#include "SkTSort.h"
18
19#include "SkDmuxWStream.h"
20
21static const char kUsage[] =
22    "This program takes a list of Skia Picture (SKP) files and renders\n"
23    "each as a multipage PDF, then prints out the MD5 checksum of the\n"
24    "PDF file.  This can be used to verify that changes to the PDF\n"
25    "backend will not change PDF output.\n";
26
27__SK_FORCE_IMAGE_DECODER_LINKING;
28
29DEFINE_string2(inputPaths,
30               r,
31               "",
32               "A list of directories and files to use as input.\n"
33               "Files are expected to have the .skp extension.");
34
35DEFINE_string2(outputDirectoryPath, w, "", "TODO: document this");
36
37static const char SKP_FILE_EXTENSION[] = ".skp";
38static const char PDF_FILE_EXTENSION[] = ".pdf";
39
40// Used by SkTQSort<SkString>()
41static bool operator<(const SkString& a, const SkString& b) {
42    return strcmp(a.c_str(), b.c_str()) < 0;
43}
44
45// Process --inputPaths defined on the command line.  Return false if
46// no files found.
47static bool process_input_files(SkTArray<SkString>* files) {
48    for (int i = 0; i < FLAGS_inputPaths.count(); i++) {
49        const char* input = FLAGS_inputPaths[i];
50        if (sk_isdir(input)) {
51            SkOSFile::Iter iter(input, SKP_FILE_EXTENSION);
52            SkString inputFilename;
53            while (iter.next(&inputFilename)) {
54                files->push_back(SkOSPath::Join(input, inputFilename.c_str()));
55            }
56        } else {
57            if (SkStrEndsWith(input, SKP_FILE_EXTENSION)) {
58                if (sk_exists(input)) {
59                    files->push_back(SkString(input));
60                } else {
61                    SkDebugf("file_does_not_exist %s\n", input);
62                }
63            } else {
64                SkDebugf("skipping_file %s\n", input);
65            }
66        }
67    }
68    if (files->count() > 0) {
69        SkTQSort<SkString>(files->begin(), files->end() - 1);
70        return true;
71    }
72    return false;
73}
74
75// Print the given SkPicture to a PDF, breaking on 8.5x11 pages.
76static void picture_to_pdf(const SkPicture& picture, SkWStream* out) {
77    SkAutoTUnref<SkDocument> pdfDocument(SkDocument::CreatePDF(out));
78
79    int width = picture.cullRect().width();
80    int height = picture.cullRect().height();
81
82    const int kLetterWidth = 612;   // 8.5 * 72
83    const int kLetterHeight = 792;  // 11 * 72
84    SkRect letterRect = SkRect::MakeWH(SkIntToScalar(kLetterWidth),
85                                       SkIntToScalar(kLetterHeight));
86
87    int xPages = ((width - 1) / kLetterWidth) + 1;
88    int yPages = ((height - 1) / kLetterHeight) + 1;
89
90    for (int y = 0; y < yPages; ++y) {
91        for (int x = 0; x < xPages; ++x) {
92            int w = SkTMin(kLetterWidth, width - (x * kLetterWidth));
93            int h = SkTMin(kLetterHeight, height - (y * kLetterHeight));
94            SkCanvas* canvas = pdfDocument->beginPage(w, h);
95            canvas->clipRect(letterRect);
96            canvas->translate(SkIntToScalar(-kLetterWidth * x),
97                              SkIntToScalar(-kLetterHeight * y));
98            canvas->drawPicture(&picture);
99            canvas->flush();
100            pdfDocument->endPage();
101        }
102    }
103    pdfDocument->close();
104    out->flush();
105}
106
107static bool skp_to_pdf_md5(SkStream* input, SkMD5::Digest* digest) {
108    SkAutoTUnref<SkPicture> picture(SkPicture::CreateFromStream(input));
109    if (NULL == picture.get()) {
110        return false;
111    }
112
113    SkMD5 checksumWStream;
114    picture_to_pdf(*picture, &checksumWStream);
115    checksumWStream.finish(*digest);
116    return true;
117}
118
119static bool skp_to_pdf_and_md5(SkStream* input,
120                               const char* path,
121                               SkMD5::Digest* digest) {
122    SkAutoTUnref<SkPicture> picture(SkPicture::CreateFromStream(input));
123    if (NULL == picture.get()) {
124        return false;
125    }
126
127    SkMD5 checksumWStream;
128    SkFILEWStream fileWStream(path);
129    SkWStream* wStreamArray[] = {&checksumWStream, &fileWStream};
130    SkDmuxWStream dmuxWStream(wStreamArray, SK_ARRAY_COUNT(wStreamArray));
131
132    picture_to_pdf(*picture, &dmuxWStream);
133    checksumWStream.finish(*digest);
134    return true;
135}
136
137SkString digest_to_hex(const SkMD5::Digest& digest) {
138    static const char kHex[] = "0123456789ABCDEF";
139    SkString string(2 * sizeof(digest.data));
140    char* p = string.writable_str();
141    for (size_t i = 0; i < sizeof(digest.data); ++i) {
142        uint8_t c = digest.data[i];
143        *(p++) = kHex[c >> 4];
144        *(p++) = kHex[c & 0xF];
145    }
146    return string;
147}
148
149static void str_replace_ending(SkString* str,
150                               const char* oldExt,
151                               const char* newExt) {
152    SkASSERT(str->endsWith(oldExt));
153    SkASSERT(str->size() >= strlen(oldExt));
154    str->remove(str->size() - strlen(oldExt), strlen(oldExt));
155    str->append(newExt);
156}
157
158int main(int argc, char** argv) {
159    SkCommandLineFlags::SetUsage(kUsage);
160    SkCommandLineFlags::Parse(argc, argv);
161    const char* outputDir = FLAGS_outputDirectoryPath.count() > 0
162                                    ? FLAGS_outputDirectoryPath[0]
163                                    : NULL;
164    if (outputDir) {
165        sk_mkdir(outputDir);
166    }
167
168    SkAutoGraphics ag;
169    int successCount = 0;
170    SkTArray<SkString> files;
171    if (!process_input_files(&files)) {
172        SkDebugf("You need to specify a --inputPaths option.\n");
173        return 1;
174    }
175    for (int i = 0; i < files.count(); ++i) {
176        SkString basename = SkOSPath::Basename(files[i].c_str());
177        SkFILEStream inputStream(files[i].c_str());
178        if (!inputStream.isValid()) {
179            SkDebugf("could_not_open %s\n", basename.c_str());
180            continue;
181        }
182        SkMD5::Digest digest;
183
184        if (outputDir) {
185            SkString path = SkOSPath::Join(outputDir, basename.c_str());
186            str_replace_ending(&path, SKP_FILE_EXTENSION, PDF_FILE_EXTENSION);
187            if (!skp_to_pdf_and_md5(&inputStream, path.c_str(), &digest)) {
188                SkDebugf("invalid_skp %s\n", basename.c_str());
189                continue;
190            }
191        } else {
192            if (!skp_to_pdf_md5(&inputStream, &digest)) {
193                SkDebugf("invalid_skp %s\n", basename.c_str());
194                continue;
195            }
196        }
197        SkString hexDigest = digest_to_hex(digest);
198        printf("%s %s\n", hexDigest.c_str(), basename.c_str());
199        ++successCount;
200    }
201    return successCount == files.count() ? 0 : 1;
202}
203
204