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