pdf_viewer_main.cpp revision 147adb10f7f80ae721879e08474fd575e719487c
1#include "SkCanvas.h"
2#include "SkCommandLineFlags.h"
3#include "SkDevice.h"
4#include "SkGraphics.h"
5#include "SkImageDecoder.h"
6#include "SkImageEncoder.h"
7#include "SkOSFile.h"
8#include "SkPicture.h"
9#include "SkStream.h"
10#include "SkTypeface.h"
11#include "SkTArray.h"
12#include "SkNulCanvas.h"
13
14#include "SkPdfRenderer.h"
15
16DEFINE_string2(readPath, r, "", "pdf files or directories of pdf files to process.");
17DEFINE_string2(writePath, w, "", "Directory to write the rendered pages.");
18DEFINE_bool2(noExtensionForOnePagePdf, n, false, "No page extension if only one page.");
19DEFINE_bool2(showMemoryUsage, m, false, "Show Memory usage.");
20DEFINE_string2(pages, p, "all", "What pages to render and how:\n"
21                                "\tall - all pages\n"
22                                "\treverse - all pages, in reverse order\n"
23                                "\tfirst - first page\n"
24                                "\tlast - last page\n"
25                                "\tnumber - a specific page number\n"
26               );
27DEFINE_double(DPI, 72, "DPI to be used for rendering (scale).");
28DEFINE_int32(benchLoad, 0, "Load the pdf file minimally N times, without any rendering and \n"
29             "\tminimal parsing to ensure correctness. Default 0 (disabled).");
30DEFINE_int32(benchRender, 0, "Render the pdf content N times. Default 0 (disabled)");
31DEFINE_string2(config, c, "8888", "Canvas to render:\n"
32                                  "\t8888 - all pages\n"
33                                  "\tnul - all pages, in reverse order\n"
34               );
35
36
37// TODO(edisonn): add config for device target(gpu, raster, pdf), + ability not to render at all
38
39/**
40 * Given list of directories and files to use as input, expects to find .pdf
41 * files and it will convert them to .png files writing them in the same directory
42 * one file for each page.
43 *
44 * Returns zero exit code if all .pdf files were converted successfully,
45 * otherwise returns error code 1.
46 */
47
48static const char PDF_FILE_EXTENSION[] = "pdf";
49static const char PNG_FILE_EXTENSION[] = "png";
50
51/** Replaces the extension of a file.
52 * @param path File name whose extension will be changed.
53 * @param old_extension The old extension.
54 * @param new_extension The new extension.
55 * @returns false if the file did not has the expected extension.
56 *  if false is returned, contents of path are undefined.
57 */
58static bool add_page_and_replace_filename_extension(SkString* path, int page,
59                                       const char old_extension[],
60                                       const char new_extension[]) {
61    if (path->endsWith(old_extension)) {
62        path->remove(path->size() - strlen(old_extension),
63                     strlen(old_extension));
64        if (!path->endsWith(".")) {
65            return false;
66        }
67        if (page >= 0) {
68            path->appendf("%i.", page);
69        }
70        path->append(new_extension);
71        return true;
72    }
73    return false;
74}
75
76void make_filepath(SkString* path, const SkString& dir, const SkString& name) {
77    size_t len = dir.size();
78    path->set(dir);
79    if (0 < len  && '/' != dir[len - 1]) {
80        path->append("/");
81    }
82    path->append(name);
83}
84
85bool is_path_seperator(const char chr) {
86#if defined(SK_BUILD_FOR_WIN)
87    return chr == '\\' || chr == '/';
88#else
89    return chr == '/';
90#endif
91}
92
93void get_basename(SkString* basename, const SkString& path) {
94    if (path.size() == 0) {
95        basename->reset();
96        return;
97    }
98
99    size_t end = path.size() - 1;
100
101    // Paths pointing to directories often have a trailing slash,
102    // we remove it so the name is not empty
103    if (is_path_seperator(path[end])) {
104        if (end == 0) {
105            basename->reset();
106            return;
107        }
108
109        end -= 1;
110    }
111
112    size_t i = end;
113    do {
114        --i;
115        if (is_path_seperator(path[i])) {
116              const char* basenameStart = path.c_str() + i + 1;
117              size_t basenameLength = end - i;
118              basename->set(basenameStart, basenameLength);
119              return;
120        }
121    } while (i > 0);
122
123    basename->set(path.c_str(), end + 1);
124}
125
126/** Builds the output filename. path = dir/name, and it replaces expected
127 * .skp extension with .pdf extention.
128 * @param path Output filename.
129 * @param name The name of the file.
130 * @returns false if the file did not has the expected extension.
131 *  if false is returned, contents of path are undefined.
132 */
133static bool make_output_filepath(SkString* path, const SkString& dir,
134                                 const SkString& name,
135                                 int page) {
136    make_filepath(path, dir, name);
137    return add_page_and_replace_filename_extension(path, page,
138                                                   PDF_FILE_EXTENSION,
139                                                   PNG_FILE_EXTENSION);
140}
141
142static void setup_bitmap(SkBitmap* bitmap, int width, int height, SkColor color = SK_ColorWHITE) {
143    bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
144
145    bitmap->allocPixels();
146    bitmap->eraseColor(color);
147}
148
149/** Write the output of pdf renderer to a file.
150 * @param outputDir Output dir.
151 * @param inputFilename The skp file that was read.
152 * @param renderer The object responsible to write the pdf file.
153 * @param page -1 means there is only one page (0), and render in a file without page extension
154 */
155
156extern "C" SkBitmap* gDumpBitmap;
157extern "C" SkCanvas* gDumpCanvas;
158
159static bool render_page(const SkString& outputDir,
160                        const SkString& inputFilename,
161                        const SkPdfRenderer& renderer,
162                        int page) {
163    SkRect rect = renderer.MediaBox(page < 0 ? 0 :page);
164
165    // Exercise all pdf codepaths as in normal rendering, but no actual bits are changed.
166    if (!FLAGS_config.isEmpty() && strcmp(FLAGS_config[0], "nul") == 0) {
167        SkBitmap bitmap;
168        SkAutoTUnref<SkDevice> device(SkNEW_ARGS(SkDevice, (bitmap)));
169        SkNulCanvas canvas(device);
170        renderer.renderPage(page < 0 ? 0 : page, &canvas, rect);
171    } else {
172        // 8888
173        SkRect rect = renderer.MediaBox(page < 0 ? 0 :page);
174
175        SkBitmap bitmap;
176        SkScalar width = SkScalarMul(rect.width(),  SkDoubleToScalar(sqrt(FLAGS_DPI / 72.0)));
177        SkScalar height = SkScalarMul(rect.height(),  SkDoubleToScalar(sqrt(FLAGS_DPI / 72.0)));
178
179        rect = SkRect::MakeWH(width, height);
180
181#ifdef PDF_DEBUG_3X
182        setup_bitmap(&bitmap, 3 * (int)SkScalarToDouble(width), 3 * (int)SkScalarToDouble(height));
183#else
184        setup_bitmap(&bitmap, (int)SkScalarToDouble(width), (int)SkScalarToDouble(height));
185#endif
186        SkAutoTUnref<SkDevice> device(SkNEW_ARGS(SkDevice, (bitmap)));
187        SkCanvas canvas(device);
188
189        gDumpBitmap = &bitmap;
190
191        gDumpCanvas = &canvas;
192        renderer.renderPage(page < 0 ? 0 : page, &canvas, rect);
193
194        SkString outputPath;
195        if (!make_output_filepath(&outputPath, outputDir, inputFilename, page)) {
196            return false;
197        }
198        SkImageEncoder::EncodeFile(outputPath.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100);
199
200        if (FLAGS_showMemoryUsage) {
201            SkDebugf("Memory usage after page %i rendered: %u\n", page < 0 ? 0 : page, (unsigned int)renderer.bytesUsed());
202        }
203    }
204    return true;
205}
206
207/** Reads an skp file, renders it to pdf and writes the output to a pdf file
208 * @param inputPath The skp file to be read.
209 * @param outputDir Output dir.
210 * @param renderer The object responsible to render the skp object into pdf.
211 */
212static bool process_pdf(const SkString& inputPath, const SkString& outputDir,
213                        SkPdfRenderer& renderer) {
214    SkDebugf("Loading PDF:  %s\n", inputPath.c_str());
215
216    SkString inputFilename;
217    get_basename(&inputFilename, inputPath);
218
219    SkFILEStream inputStream;
220    inputStream.setPath(inputPath.c_str());
221    if (!inputStream.isValid()) {
222        SkDebugf("Could not open file %s\n", inputPath.c_str());
223        return false;
224    }
225
226    bool success = false;
227
228    success = renderer.load(inputPath);
229    if (FLAGS_showMemoryUsage) {
230        SkDebugf("Memory usage after load: %u\n", (unsigned int)renderer.bytesUsed());
231    }
232
233    // TODO(edisonn): bench timers
234    if (FLAGS_benchLoad > 0) {
235        for (int i = 0 ; i < FLAGS_benchLoad; i++) {
236            success = renderer.load(inputPath);
237            if (FLAGS_showMemoryUsage) {
238                SkDebugf("Memory usage after load %i number : %u\n", i, (unsigned int)renderer.bytesUsed());
239            }
240        }
241    }
242
243    if (success) {
244        if (!renderer.pages())
245        {
246            SkDebugf("ERROR: Empty PDF Document %s\n", inputPath.c_str());
247            return false;
248        } else {
249            printf("renders = %i\n", FLAGS_benchRender);
250            for (int i = 0; i < FLAGS_benchRender + 1; i++) {
251                // TODO(edisonn) if (i == 1) start timer
252                if (strcmp(FLAGS_pages[0], "all") == 0) {
253                    for (int pn = 0; pn < renderer.pages(); ++pn) {
254                        success = render_page(outputDir, inputFilename, renderer, FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : pn) && success;
255                    }
256                } else if (strcmp(FLAGS_pages[0], "reverse") == 0) {
257                    for (int pn = renderer.pages() - 1; pn >= 0; --pn) {
258                        success = render_page(outputDir, inputFilename, renderer, FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : pn) && success;
259                    }
260                } else if (strcmp(FLAGS_pages[0], "first") == 0) {
261                    success = render_page(outputDir, inputFilename, renderer, FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : 0) && success;
262                } else if (strcmp(FLAGS_pages[0], "last") == 0) {
263                    success = render_page(outputDir, inputFilename, renderer, FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : renderer.pages() - 1) && success;
264                } else {
265                    int pn = atoi(FLAGS_pages[0]);
266                    success = render_page(outputDir, inputFilename, renderer, FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : renderer.pages() - 1) && pn;
267                }
268            }
269        }
270    }
271
272    return success;
273}
274
275/** For each file in the directory or for the file passed in input, call
276 * parse_pdf.
277 * @param input A directory or an pdf file.
278 * @param outputDir Output dir.
279 * @param renderer The object responsible to render the skp object into pdf.
280 */
281static int process_input(const char* input, const SkString& outputDir,
282                         SkPdfRenderer& renderer) {
283    int failures = 0;
284    if (sk_isdir(input)) {
285        SkOSFile::Iter iter(input, PDF_FILE_EXTENSION);
286        SkString inputFilename;
287        while (iter.next(&inputFilename)) {
288            SkString inputPath;
289            SkString _input;
290            _input.append(input);
291            make_filepath(&inputPath, _input, inputFilename);
292            if (!process_pdf(inputPath, outputDir, renderer)) {
293                ++failures;
294            }
295        }
296    } else {
297        SkString inputPath(input);
298        if (!process_pdf(inputPath, outputDir, renderer)) {
299            ++failures;
300        }
301    }
302    return failures;
303}
304
305int tool_main(int argc, char** argv);
306int tool_main(int argc, char** argv) {
307    SkCommandLineFlags::SetUsage("Parse and Render .pdf files (pdf viewer).");
308    SkCommandLineFlags::Parse(argc, argv);
309
310    if (FLAGS_readPath.isEmpty()) {
311        SkDebugf(".pdf files or directories are required.\n");
312        exit(-1);
313    }
314
315    SkPdfRenderer renderer;
316
317    SkString outputDir;
318    if (FLAGS_writePath.count() == 1) {
319        outputDir.set(FLAGS_writePath[0]);
320    }
321
322    int failures = 0;
323    for (int i = 0; i < FLAGS_readPath.count(); i ++) {
324        failures += process_input(FLAGS_readPath[i], outputDir, renderer);
325        renderer.unload();
326    }
327
328    reportPdfRenderStats();
329
330    if (failures != 0) {
331        SkDebugf("Failed to render %i PDFs.\n", failures);
332        return 1;
333    }
334
335    return 0;
336}
337
338#if !defined SK_BUILD_FOR_IOS
339int main(int argc, char * const argv[]) {
340    return tool_main(argc, (char**) argv);
341}
342#endif
343