1/*
2 * Copyright 2012 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 "SkCanvas.h"
9#include "SkCommandLineFlags.h"
10#include "SkDocument.h"
11#include "SkForceLinking.h"
12#include "SkGraphics.h"
13#include "SkImageEncoder.h"
14#include "SkOSFile.h"
15#include "SkPicture.h"
16#include "SkStream.h"
17#include "SkTArray.h"
18#include "SkTSort.h"
19#include "ProcStats.h"
20
21__SK_FORCE_IMAGE_DECODER_LINKING;
22
23#ifdef SK_USE_CDB
24#include "win_dbghelp.h"
25#endif
26
27/**
28 * render_pdfs
29 *
30 * Given list of directories and files to use as input, expects to find .skp
31 * files and it will convert them to .pdf files writing them in the output
32 * directory.
33 *
34 * Returns zero exit code if all .skp files were converted successfully,
35 * otherwise returns error code 1.
36 */
37
38static const char PDF_FILE_EXTENSION[] = "pdf";
39static const char SKP_FILE_EXTENSION[] = "skp";
40
41
42DEFINE_string2(inputPaths, r, "",
43              "A list of directories and files to use as input. "
44              "Files are expected to have the .skp extension.");
45
46DEFINE_string2(outputDir, w, "",
47               "Directory to write the rendered pdfs.");
48
49DEFINE_string2(match, m, "",
50               "[~][^]substring[$] [...] of filenames to run.\n"
51               "Multiple matches may be separated by spaces.\n"
52               "~ causes a matching file to always be skipped\n"
53               "^ requires the start of the file to match\n"
54               "$ requires the end of the file to match\n"
55               "^ and $ requires an exact match\n"
56               "If a file does not match any list entry,\n"
57               "it is skipped unless some list entry starts with ~");
58
59DEFINE_int32(jpegQuality, 100,
60             "Encodes images in JPEG at quality level N, which can be in "
61             "range 0-100).   N = -1 will disable JPEG compression. "
62             "Default is N = 100, maximum quality.");
63
64/** Replaces the extension of a file.
65 * @param path File name whose extension will be changed.
66 * @param old_extension The old extension.
67 * @param new_extension The new extension.
68 * @returns false if the file did not has the expected extension.
69 *  if false is returned, contents of path are undefined.
70 */
71static bool replace_filename_extension(SkString* path,
72                                       const char old_extension[],
73                                       const char new_extension[]) {
74    if (path->endsWith(old_extension)) {
75        path->remove(path->size() - strlen(old_extension),
76                     strlen(old_extension));
77        if (!path->endsWith(".")) {
78            return false;
79        }
80        path->append(new_extension);
81        return true;
82    }
83    return false;
84}
85
86// the size_t* parameter is deprecated, so we ignore it
87static SkData* encode_to_dct_data(size_t*, const SkBitmap& bitmap) {
88    if (FLAGS_jpegQuality == -1) {
89        return NULL;
90    }
91
92    SkBitmap bm = bitmap;
93#if defined(SK_BUILD_FOR_MAC)
94    // Workaround bug #1043 where bitmaps with referenced pixels cause
95    // CGImageDestinationFinalize to crash
96    SkBitmap copy;
97    bitmap.deepCopyTo(&copy);
98    bm = copy;
99#endif
100
101    return SkImageEncoder::EncodeData(
102            bm, SkImageEncoder::kJPEG_Type, FLAGS_jpegQuality);
103}
104
105/** Builds the output filename. path = dir/name, and it replaces expected
106 * .skp extension with .pdf extention.
107 * @param path Output filename.
108 * @param name The name of the file.
109 * @returns false if the file did not has the expected extension.
110 *  if false is returned, contents of path are undefined.
111 */
112static bool make_output_filepath(SkString* path, const SkString& dir,
113                                 const SkString& name) {
114    *path = SkOSPath::Join(dir.c_str(), name.c_str());
115    return replace_filename_extension(path,
116                                      SKP_FILE_EXTENSION,
117                                      PDF_FILE_EXTENSION);
118}
119
120namespace {
121// This is a write-only stream.
122class NullWStream : public SkWStream {
123public:
124    NullWStream() : fBytesWritten(0) { }
125    virtual bool write(const void*, size_t size) SK_OVERRIDE {
126        fBytesWritten += size;
127        return true;
128    }
129    virtual size_t bytesWritten() const SK_OVERRIDE { return fBytesWritten; }
130    size_t fBytesWritten;
131};
132}  // namespace
133
134/** Write the output of pdf renderer to a file.
135 * @param outputDir Output dir.
136 * @param inputFilename The skp file that was read.
137 * @param renderer The object responsible to write the pdf file.
138 */
139static SkWStream* open_stream(const SkString& outputDir,
140                              const SkString& inputFilename) {
141    if (outputDir.isEmpty()) {
142        return SkNEW(NullWStream);
143    }
144
145    SkString outputPath;
146    if (!make_output_filepath(&outputPath, outputDir, inputFilename)) {
147        return NULL;
148    }
149
150    SkAutoTDelete<SkFILEWStream> stream(
151            SkNEW_ARGS(SkFILEWStream, (outputPath.c_str())));
152    if (!stream.get() ||  !stream->isValid()) {
153        SkDebugf("Could not write to file %s\n", outputPath.c_str());
154        return NULL;
155    }
156
157    return stream.detach();
158}
159
160/**
161 *  Given a SkPicture, write a one-page PDF document to the given
162 *  output, using the provided encoder.
163 */
164static bool pdf_to_stream(SkPicture* picture,
165                          SkWStream* output,
166                          SkPicture::EncodeBitmap encoder) {
167    SkAutoTUnref<SkDocument> pdfDocument(
168            SkDocument::CreatePDF(output, NULL, encoder));
169    SkCanvas* canvas = pdfDocument->beginPage(picture->cullRect().width(),
170                                              picture->cullRect().height());
171    canvas->drawPicture(picture);
172    canvas->flush();
173    return pdfDocument->close();
174}
175
176static bool operator<(const SkString& a, const SkString& b) {
177    return strcmp(a.c_str(), b.c_str()) < 0;
178}
179
180/**
181 * @param A list of directories or a skp files.
182 * @returns an alphabetical list of skp files.
183 */
184static void process_input_files(
185        const SkCommandLineFlags::StringArray& inputs,
186        SkTArray<SkString>* files) {
187    for (int i = 0; i < inputs.count(); i ++) {
188        const char* input = inputs[i];
189        if (sk_isdir(input)) {
190            SkOSFile::Iter iter(input, SKP_FILE_EXTENSION);
191            SkString inputFilename;
192            while (iter.next(&inputFilename)) {
193                if (!SkCommandLineFlags::ShouldSkip(
194                            FLAGS_match, inputFilename.c_str())) {
195                    files->push_back(
196                            SkOSPath::Join(input, inputFilename.c_str()));
197                }
198            }
199        } else {
200            if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, input)) {
201                files->push_back(SkString(input));
202            }
203        }
204    }
205    if (files->count() > 0) {
206        SkTQSort<SkString>(files->begin(), files->end() - 1);
207    }
208}
209
210/** For each input skp file, read it, render it to pdf and write. the
211 *  output to a pdf file
212 */
213int tool_main_core(int argc, char** argv);
214int tool_main_core(int argc, char** argv) {
215    SkCommandLineFlags::Parse(argc, argv);
216
217    SkAutoGraphics ag;
218
219    SkString outputDir;
220    if (FLAGS_outputDir.count() > 0) {
221        outputDir = FLAGS_outputDir[0];
222        if (!sk_mkdir(outputDir.c_str())) {
223            SkDebugf("Unable to mkdir '%s'\n", outputDir.c_str());
224            return 1;
225        }
226    }
227
228    SkTArray<SkString> files;
229    process_input_files(FLAGS_inputPaths, &files);
230
231    size_t maximumPathLength = 0;
232    for (int i = 0; i < files.count(); i ++) {
233        SkString basename = SkOSPath::Basename(files[i].c_str());
234        maximumPathLength = SkTMax(maximumPathLength, basename.size());
235    }
236
237    int failures = 0;
238    for (int i = 0; i < files.count(); i ++) {
239        SkString basename = SkOSPath::Basename(files[i].c_str());
240
241        SkFILEStream inputStream;
242        inputStream.setPath(files[i].c_str());
243        if (!inputStream.isValid()) {
244            SkDebugf("Could not open file %s\n", files[i].c_str());
245            ++failures;
246            continue;
247        }
248
249        SkAutoTUnref<SkPicture> picture(
250                SkPicture::CreateFromStream(&inputStream));
251        if (NULL == picture.get()) {
252            SkDebugf("Could not read an SkPicture from %s\n",
253                     files[i].c_str());
254            ++failures;
255            continue;
256        }
257        SkDebugf("[%f,%f,%f,%f] %-*s",
258            picture->cullRect().fLeft, picture->cullRect().fTop,
259            picture->cullRect().fRight, picture->cullRect().fBottom,
260            maximumPathLength, basename.c_str());
261
262        SkAutoTDelete<SkWStream> stream(open_stream(outputDir, files[i]));
263        if (!stream.get()) {
264            ++failures;
265            continue;
266        }
267        if (!pdf_to_stream(picture, stream.get(), encode_to_dct_data)) {
268            SkDebugf("Error in PDF Serialization.");
269            ++failures;
270        }
271
272        int max_rss_mb = sk_tools::getMaxResidentSetSizeMB();
273        if (max_rss_mb >= 0) {
274            SkDebugf(" %4dM peak rss", max_rss_mb);
275        }
276
277        SkDebugf("\n");
278    }
279    if (failures != 0) {
280        SkDebugf("Failed to render %i of %i PDFs.\n", failures, files.count());
281        return 1;
282    }
283
284    return 0;
285}
286
287int tool_main(int argc, char** argv);
288int tool_main(int argc, char** argv) {
289#ifdef SK_USE_CDB
290    setUpDebuggingFromArgs(argv[0]);
291    __try {
292#endif
293      return tool_main_core(argc, argv);
294#ifdef SK_USE_CDB
295    }
296    __except(GenerateDumpAndPrintCallstack(GetExceptionInformation()))
297    {
298        return -1;
299    }
300#endif
301    return 0;
302}
303#if !defined SK_BUILD_FOR_IOS
304int main(int argc, char * const argv[]) {
305    return tool_main(argc, (char**) argv);
306}
307#endif
308