blob: 0adc7e3cf5889ba4397efd906c5c6fc09c8809ac [file] [log] [blame]
edisonn@google.comcf2cfa12013-08-21 16:31:37 +00001/*
2 * Copyright 2013 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
edisonn@google.com01cd4d52013-06-10 20:44:45 +00008#include "SkCanvas.h"
edisonn@google.coma5aaa792013-07-11 12:27:21 +00009#include "SkCommandLineFlags.h"
edisonn@google.com01cd4d52013-06-10 20:44:45 +000010#include "SkDevice.h"
11#include "SkGraphics.h"
12#include "SkImageDecoder.h"
13#include "SkImageEncoder.h"
14#include "SkOSFile.h"
edisonn@google.come50d9a12013-10-10 20:58:22 +000015#include "SkPdfRenderer.h"
edisonn@google.com01cd4d52013-06-10 20:44:45 +000016#include "SkPicture.h"
17#include "SkStream.h"
18#include "SkTypeface.h"
19#include "SkTArray.h"
edisonn@google.comac03d912013-07-22 15:36:39 +000020#include "SkNulCanvas.h"
edisonn@google.com01cd4d52013-06-10 20:44:45 +000021
edisonn@google.com768bc6a2013-08-08 12:42:13 +000022#if SK_SUPPORT_GPU
23#include "GrContextFactory.h"
24#include "GrContext.h"
25#include "SkGpuDevice.h"
26#endif
27
edisonn@google.coma5aaa792013-07-11 12:27:21 +000028DEFINE_string2(readPath, r, "", "pdf files or directories of pdf files to process.");
29DEFINE_string2(writePath, w, "", "Directory to write the rendered pages.");
30DEFINE_bool2(noExtensionForOnePagePdf, n, false, "No page extension if only one page.");
31DEFINE_bool2(showMemoryUsage, m, false, "Show Memory usage.");
edisonn@google.com7b328fd2013-07-11 12:53:06 +000032DEFINE_string2(pages, p, "all", "What pages to render and how:\n"
33 "\tall - all pages\n"
34 "\treverse - all pages, in reverse order\n"
35 "\tfirst - first page\n"
36 "\tlast - last page\n"
37 "\tnumber - a specific page number\n"
38 );
edisonn@google.com15b11182013-07-11 14:43:15 +000039DEFINE_double(DPI, 72, "DPI to be used for rendering (scale).");
edisonn@google.com6a9d4362013-07-11 16:25:51 +000040DEFINE_int32(benchLoad, 0, "Load the pdf file minimally N times, without any rendering and \n"
41 "\tminimal parsing to ensure correctness. Default 0 (disabled).");
42DEFINE_int32(benchRender, 0, "Render the pdf content N times. Default 0 (disabled)");
edisonn@google.comac03d912013-07-22 15:36:39 +000043DEFINE_string2(config, c, "8888", "Canvas to render:\n"
edisonn@google.com768bc6a2013-08-08 12:42:13 +000044 "\t8888 - argb\n"
edisonn@google.com768bc6a2013-08-08 12:42:13 +000045#if SK_SUPPORT_GPU
46 "\tgpu: use the gpu\n"
47#endif
48 "\tnul - render in null canvas, any draw will just return.\n"
edisonn@google.comac03d912013-07-22 15:36:39 +000049 );
edisonn@google.com598cf5d2013-10-09 15:13:19 +000050DEFINE_bool2(transparentBackground, t, false, "Make background transparent instead of white.");
edisonn@google.com6a9d4362013-07-11 16:25:51 +000051
edisonn@google.com01cd4d52013-06-10 20:44:45 +000052/**
53 * Given list of directories and files to use as input, expects to find .pdf
54 * files and it will convert them to .png files writing them in the same directory
55 * one file for each page.
56 *
57 * Returns zero exit code if all .pdf files were converted successfully,
58 * otherwise returns error code 1.
59 */
60
61static const char PDF_FILE_EXTENSION[] = "pdf";
62static const char PNG_FILE_EXTENSION[] = "png";
63
edisonn@google.com01cd4d52013-06-10 20:44:45 +000064/** 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 */
edisonn@google.com222382b2013-07-10 22:33:10 +000071static bool add_page_and_replace_filename_extension(SkString* path, int page,
edisonn@google.com01cd4d52013-06-10 20:44:45 +000072 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 }
edisonn@google.com222382b2013-07-10 22:33:10 +000080 if (page >= 0) {
81 path->appendf("%i.", page);
82 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +000083 path->append(new_extension);
84 return true;
85 }
86 return false;
87}
edisonn@google.com222382b2013-07-10 22:33:10 +000088
edisonn@google.com33f11b62013-08-14 21:35:27 +000089static void make_filepath(SkString* path, const SkString& dir, const SkString& name) {
edisonn@google.com147adb12013-07-24 15:56:19 +000090 size_t len = dir.size();
91 path->set(dir);
92 if (0 < len && '/' != dir[len - 1]) {
93 path->append("/");
94 }
95 path->append(name);
96}
97
edisonn@google.com33f11b62013-08-14 21:35:27 +000098static bool is_path_seperator(const char chr) {
edisonn@google.com147adb12013-07-24 15:56:19 +000099#if defined(SK_BUILD_FOR_WIN)
100 return chr == '\\' || chr == '/';
101#else
102 return chr == '/';
103#endif
104}
105
edisonn@google.com33f11b62013-08-14 21:35:27 +0000106static void get_basename(SkString* basename, const SkString& path) {
edisonn@google.com147adb12013-07-24 15:56:19 +0000107 if (path.size() == 0) {
108 basename->reset();
109 return;
110 }
111
112 size_t end = path.size() - 1;
113
114 // Paths pointing to directories often have a trailing slash,
115 // we remove it so the name is not empty
116 if (is_path_seperator(path[end])) {
117 if (end == 0) {
118 basename->reset();
119 return;
120 }
121
122 end -= 1;
123 }
124
125 size_t i = end;
126 do {
127 --i;
128 if (is_path_seperator(path[i])) {
129 const char* basenameStart = path.c_str() + i + 1;
130 size_t basenameLength = end - i;
131 basename->set(basenameStart, basenameLength);
132 return;
133 }
134 } while (i > 0);
135
136 basename->set(path.c_str(), end + 1);
137}
138
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000139/** Builds the output filename. path = dir/name, and it replaces expected
140 * .skp extension with .pdf extention.
141 * @param path Output filename.
142 * @param name The name of the file.
143 * @returns false if the file did not has the expected extension.
144 * if false is returned, contents of path are undefined.
145 */
146static bool make_output_filepath(SkString* path, const SkString& dir,
edisonn@google.com222382b2013-07-10 22:33:10 +0000147 const SkString& name,
148 int page) {
edisonn@google.com147adb12013-07-24 15:56:19 +0000149 make_filepath(path, dir, name);
edisonn@google.com222382b2013-07-10 22:33:10 +0000150 return add_page_and_replace_filename_extension(path, page,
151 PDF_FILE_EXTENSION,
152 PNG_FILE_EXTENSION);
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000153}
edisonn@google.com222382b2013-07-10 22:33:10 +0000154
edisonn@google.com598cf5d2013-10-09 15:13:19 +0000155static void setup_bitmap(SkBitmap* bitmap, int width, int height, SkColor color) {
edisonn@google.com222382b2013-07-10 22:33:10 +0000156 bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
157
158 bitmap->allocPixels();
159 bitmap->eraseColor(color);
160}
161
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000162/** Write the output of pdf renderer to a file.
163 * @param outputDir Output dir.
164 * @param inputFilename The skp file that was read.
165 * @param renderer The object responsible to write the pdf file.
edisonn@google.comcdad30b2013-07-10 22:37:38 +0000166 * @param page -1 means there is only one page (0), and render in a file without page extension
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000167 */
edisonn@google.com222382b2013-07-10 22:33:10 +0000168
edisonn@google.com13102382013-07-11 14:50:12 +0000169extern "C" SkBitmap* gDumpBitmap;
170extern "C" SkCanvas* gDumpCanvas;
171
edisonn@google.com768bc6a2013-08-08 12:42:13 +0000172#if SK_SUPPORT_GPU
173GrContextFactory gContextFactory;
174#endif
175
edisonn@google.com222382b2013-07-10 22:33:10 +0000176static bool render_page(const SkString& outputDir,
edisonn@google.com444e25a2013-07-11 15:20:50 +0000177 const SkString& inputFilename,
178 const SkPdfRenderer& renderer,
179 int page) {
edisonn@google.com222382b2013-07-10 22:33:10 +0000180 SkRect rect = renderer.MediaBox(page < 0 ? 0 :page);
181
edisonn@google.comac03d912013-07-22 15:36:39 +0000182 // Exercise all pdf codepaths as in normal rendering, but no actual bits are changed.
183 if (!FLAGS_config.isEmpty() && strcmp(FLAGS_config[0], "nul") == 0) {
184 SkBitmap bitmap;
robertphillips@google.com1f2f3382013-08-29 11:54:56 +0000185 SkAutoTUnref<SkBaseDevice> device(SkNEW_ARGS(SkBitmapDevice, (bitmap)));
edisonn@google.comac03d912013-07-22 15:36:39 +0000186 SkNulCanvas canvas(device);
187 renderer.renderPage(page < 0 ? 0 : page, &canvas, rect);
188 } else {
189 // 8888
190 SkRect rect = renderer.MediaBox(page < 0 ? 0 :page);
edisonn@google.com444e25a2013-07-11 15:20:50 +0000191
edisonn@google.comac03d912013-07-22 15:36:39 +0000192 SkBitmap bitmap;
193 SkScalar width = SkScalarMul(rect.width(), SkDoubleToScalar(sqrt(FLAGS_DPI / 72.0)));
194 SkScalar height = SkScalarMul(rect.height(), SkDoubleToScalar(sqrt(FLAGS_DPI / 72.0)));
195
196 rect = SkRect::MakeWH(width, height);
edisonn@google.com444e25a2013-07-11 15:20:50 +0000197
edisonn@google.com598cf5d2013-10-09 15:13:19 +0000198 SkColor background = FLAGS_transparentBackground ? SK_ColorTRANSPARENT : SK_ColorWHITE;
199
edisonn@google.com222382b2013-07-10 22:33:10 +0000200#ifdef PDF_DEBUG_3X
edisonn@google.come50d9a12013-10-10 20:58:22 +0000201 setup_bitmap(&bitmap, 3 * (int)SkScalarToDouble(width), 3 * (int)SkScalarToDouble(height),
202 background);
edisonn@google.com222382b2013-07-10 22:33:10 +0000203#else
edisonn@google.come50d9a12013-10-10 20:58:22 +0000204 setup_bitmap(&bitmap, (int)SkScalarToDouble(width), (int)SkScalarToDouble(height),
205 background);
edisonn@google.com222382b2013-07-10 22:33:10 +0000206#endif
robertphillips@google.com1f2f3382013-08-29 11:54:56 +0000207 SkAutoTUnref<SkBaseDevice> device;
edisonn@google.com768bc6a2013-08-08 12:42:13 +0000208 if (strcmp(FLAGS_config[0], "8888") == 0) {
robertphillips@google.com1f2f3382013-08-29 11:54:56 +0000209 device.reset(SkNEW_ARGS(SkBitmapDevice, (bitmap)));
edisonn@google.com768bc6a2013-08-08 12:42:13 +0000210 }
211#if SK_SUPPORT_GPU
212 else if (strcmp(FLAGS_config[0], "gpu") == 0) {
213 SkAutoTUnref<GrSurface> target;
214 GrContext* gr = gContextFactory.get(GrContextFactory::kNative_GLContextType);
215 if (gr) {
216 // create a render target to back the device
217 GrTextureDesc desc;
218 desc.fConfig = kSkia8888_GrPixelConfig;
219 desc.fFlags = kRenderTarget_GrTextureFlagBit;
220 desc.fWidth = width;
221 desc.fHeight = height;
222 desc.fSampleCnt = 0;
223 target.reset(gr->createUncachedTexture(desc, NULL, 0));
224 }
225 if (NULL == target.get()) {
226 SkASSERT(0);
227 return false;
228 }
229
230 device.reset(SkGpuDevice::Create(target));
231 }
232#endif
233 else {
234 SkDebugf("unknown --config: %s\n", FLAGS_config[0]);
235 return false;
236 }
edisonn@google.comac03d912013-07-22 15:36:39 +0000237 SkCanvas canvas(device);
edisonn@google.com222382b2013-07-10 22:33:10 +0000238
edisonn@google.comac03d912013-07-22 15:36:39 +0000239 gDumpBitmap = &bitmap;
edisonn@google.com222382b2013-07-10 22:33:10 +0000240
edisonn@google.comac03d912013-07-22 15:36:39 +0000241 gDumpCanvas = &canvas;
242 renderer.renderPage(page < 0 ? 0 : page, &canvas, rect);
edisonn@google.com222382b2013-07-10 22:33:10 +0000243
edisonn@google.comac03d912013-07-22 15:36:39 +0000244 SkString outputPath;
245 if (!make_output_filepath(&outputPath, outputDir, inputFilename, page)) {
246 return false;
247 }
248 SkImageEncoder::EncodeFile(outputPath.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100);
249
250 if (FLAGS_showMemoryUsage) {
edisonn@google.come50d9a12013-10-10 20:58:22 +0000251 SkDebugf("Memory usage after page %i rendered: %u\n",
252 page < 0 ? 0 : page, (unsigned int)renderer.bytesUsed());
edisonn@google.comac03d912013-07-22 15:36:39 +0000253 }
edisonn@google.com13102382013-07-11 14:50:12 +0000254 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000255 return true;
256}
edisonn@google.com222382b2013-07-10 22:33:10 +0000257
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000258/** Reads an skp file, renders it to pdf and writes the output to a pdf file
259 * @param inputPath The skp file to be read.
260 * @param outputDir Output dir.
261 * @param renderer The object responsible to render the skp object into pdf.
262 */
edisonn@google.com222382b2013-07-10 22:33:10 +0000263static bool process_pdf(const SkString& inputPath, const SkString& outputDir,
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000264 SkPdfRenderer& renderer) {
edisonn@google.com222382b2013-07-10 22:33:10 +0000265 SkDebugf("Loading PDF: %s\n", inputPath.c_str());
266
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000267 SkString inputFilename;
edisonn@google.com147adb12013-07-24 15:56:19 +0000268 get_basename(&inputFilename, inputPath);
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000269
edisonn@google.com2273f9b2013-08-06 21:48:44 +0000270 bool success = true;
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000271
edisonn@google.com222382b2013-07-10 22:33:10 +0000272 success = renderer.load(inputPath);
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000273 if (FLAGS_showMemoryUsage) {
274 SkDebugf("Memory usage after load: %u\n", (unsigned int)renderer.bytesUsed());
275 }
276
277 // TODO(edisonn): bench timers
278 if (FLAGS_benchLoad > 0) {
279 for (int i = 0 ; i < FLAGS_benchLoad; i++) {
edisonn@google.com2273f9b2013-08-06 21:48:44 +0000280 success = renderer.load(inputPath) && success;
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000281 if (FLAGS_showMemoryUsage) {
edisonn@google.come50d9a12013-10-10 20:58:22 +0000282 SkDebugf("Memory usage after load %i number : %u\n", i,
283 (unsigned int)renderer.bytesUsed());
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000284 }
285 }
286 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000287
edisonn@google.com222382b2013-07-10 22:33:10 +0000288 if (success) {
289 if (!renderer.pages())
290 {
291 SkDebugf("ERROR: Empty PDF Document %s\n", inputPath.c_str());
292 return false;
293 } else {
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000294 for (int i = 0; i < FLAGS_benchRender + 1; i++) {
295 // TODO(edisonn) if (i == 1) start timer
296 if (strcmp(FLAGS_pages[0], "all") == 0) {
297 for (int pn = 0; pn < renderer.pages(); ++pn) {
edisonn@google.come50d9a12013-10-10 20:58:22 +0000298 success = render_page(
299 outputDir,
300 inputFilename,
301 renderer,
302 FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 :
303 pn) &&
304 success;
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000305 }
306 } else if (strcmp(FLAGS_pages[0], "reverse") == 0) {
307 for (int pn = renderer.pages() - 1; pn >= 0; --pn) {
edisonn@google.come50d9a12013-10-10 20:58:22 +0000308 success = render_page(
309 outputDir,
310 inputFilename,
311 renderer,
312 FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 :
313 pn) &&
314 success;
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000315 }
316 } else if (strcmp(FLAGS_pages[0], "first") == 0) {
edisonn@google.come50d9a12013-10-10 20:58:22 +0000317 success = render_page(
318 outputDir,
319 inputFilename,
320 renderer,
321 FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : 0) &&
322 success;
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000323 } else if (strcmp(FLAGS_pages[0], "last") == 0) {
edisonn@google.come50d9a12013-10-10 20:58:22 +0000324 success = render_page(
325 outputDir,
326 inputFilename,
327 renderer,
328 FLAGS_noExtensionForOnePagePdf &&
329 renderer.pages() == 1 ? -1 : renderer.pages() - 1) && success;
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000330 } else {
331 int pn = atoi(FLAGS_pages[0]);
edisonn@google.come50d9a12013-10-10 20:58:22 +0000332 success = render_page(outputDir, inputFilename, renderer,
333 FLAGS_noExtensionForOnePagePdf &&
334 renderer.pages() == 1 ? -1 : pn) && success;
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000335 }
edisonn@google.com222382b2013-07-10 22:33:10 +0000336 }
337 }
338 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000339
edisonn@google.com2273f9b2013-08-06 21:48:44 +0000340 if (!success) {
341 SkDebugf("Failures for file %s\n", inputPath.c_str());
342 }
343
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000344 return success;
345}
346
347/** For each file in the directory or for the file passed in input, call
348 * parse_pdf.
349 * @param input A directory or an pdf file.
350 * @param outputDir Output dir.
351 * @param renderer The object responsible to render the skp object into pdf.
352 */
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000353static int process_input(const char* input, const SkString& outputDir,
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000354 SkPdfRenderer& renderer) {
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000355 int failures = 0;
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000356 if (sk_isdir(input)) {
357 SkOSFile::Iter iter(input, PDF_FILE_EXTENSION);
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000358 SkString inputFilename;
359 while (iter.next(&inputFilename)) {
360 SkString inputPath;
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000361 SkString _input;
362 _input.append(input);
edisonn@google.com147adb12013-07-24 15:56:19 +0000363 make_filepath(&inputPath, _input, inputFilename);
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000364 if (!process_pdf(inputPath, outputDir, renderer)) {
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000365 ++failures;
366 }
367 }
368 } else {
369 SkString inputPath(input);
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000370 if (!process_pdf(inputPath, outputDir, renderer)) {
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000371 ++failures;
372 }
373 }
374 return failures;
375}
376
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000377int tool_main(int argc, char** argv);
378int tool_main(int argc, char** argv) {
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000379 SkCommandLineFlags::SetUsage("Parse and Render .pdf files (pdf viewer).");
380 SkCommandLineFlags::Parse(argc, argv);
381
382 if (FLAGS_readPath.isEmpty()) {
383 SkDebugf(".pdf files or directories are required.\n");
384 exit(-1);
385 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000386
edisonn@google.com222382b2013-07-10 22:33:10 +0000387 SkPdfRenderer renderer;
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000388
389 SkString outputDir;
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000390 if (FLAGS_writePath.count() == 1) {
391 outputDir.set(FLAGS_writePath[0]);
392 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000393
394 int failures = 0;
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000395 for (int i = 0; i < FLAGS_readPath.count(); i ++) {
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000396 failures += process_input(FLAGS_readPath[i], outputDir, renderer);
edisonn@google.com222382b2013-07-10 22:33:10 +0000397 renderer.unload();
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000398 }
399
400 reportPdfRenderStats();
401
402 if (failures != 0) {
403 SkDebugf("Failed to render %i PDFs.\n", failures);
404 return 1;
405 }
406
407 return 0;
408}
409
410#if !defined SK_BUILD_FOR_IOS
411int main(int argc, char * const argv[]) {
412 return tool_main(argc, (char**) argv);
413}
414#endif