blob: 5fe99ad180f9d3494c181f766567aa6383865e46 [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"
15#include "SkPicture.h"
16#include "SkStream.h"
17#include "SkTypeface.h"
18#include "SkTArray.h"
edisonn@google.comac03d912013-07-22 15:36:39 +000019#include "SkNulCanvas.h"
edisonn@google.com01cd4d52013-06-10 20:44:45 +000020
edisonn@google.com768bc6a2013-08-08 12:42:13 +000021#if SK_SUPPORT_GPU
22#include "GrContextFactory.h"
23#include "GrContext.h"
24#include "SkGpuDevice.h"
25#endif
26
edisonn@google.com222382b2013-07-10 22:33:10 +000027#include "SkPdfRenderer.h"
edisonn@google.comb857a0c2013-06-25 20:45:40 +000028
edisonn@google.coma5aaa792013-07-11 12:27:21 +000029DEFINE_string2(readPath, r, "", "pdf files or directories of pdf files to process.");
30DEFINE_string2(writePath, w, "", "Directory to write the rendered pages.");
31DEFINE_bool2(noExtensionForOnePagePdf, n, false, "No page extension if only one page.");
32DEFINE_bool2(showMemoryUsage, m, false, "Show Memory usage.");
edisonn@google.com7b328fd2013-07-11 12:53:06 +000033DEFINE_string2(pages, p, "all", "What pages to render and how:\n"
34 "\tall - all pages\n"
35 "\treverse - all pages, in reverse order\n"
36 "\tfirst - first page\n"
37 "\tlast - last page\n"
38 "\tnumber - a specific page number\n"
39 );
edisonn@google.com15b11182013-07-11 14:43:15 +000040DEFINE_double(DPI, 72, "DPI to be used for rendering (scale).");
edisonn@google.com6a9d4362013-07-11 16:25:51 +000041DEFINE_int32(benchLoad, 0, "Load the pdf file minimally N times, without any rendering and \n"
42 "\tminimal parsing to ensure correctness. Default 0 (disabled).");
43DEFINE_int32(benchRender, 0, "Render the pdf content N times. Default 0 (disabled)");
edisonn@google.comac03d912013-07-22 15:36:39 +000044DEFINE_string2(config, c, "8888", "Canvas to render:\n"
edisonn@google.com768bc6a2013-08-08 12:42:13 +000045 "\t8888 - argb\n"
46
47#if SK_SUPPORT_GPU
48 "\tgpu: use the gpu\n"
49#endif
50 "\tnul - render in null canvas, any draw will just return.\n"
edisonn@google.comac03d912013-07-22 15:36:39 +000051 );
edisonn@google.com6a9d4362013-07-11 16:25:51 +000052
53
edisonn@google.com15b11182013-07-11 14:43:15 +000054// TODO(edisonn): add config for device target(gpu, raster, pdf), + ability not to render at all
edisonn@google.com7b328fd2013-07-11 12:53:06 +000055
edisonn@google.com01cd4d52013-06-10 20:44:45 +000056/**
57 * Given list of directories and files to use as input, expects to find .pdf
58 * files and it will convert them to .png files writing them in the same directory
59 * one file for each page.
60 *
61 * Returns zero exit code if all .pdf files were converted successfully,
62 * otherwise returns error code 1.
63 */
64
65static const char PDF_FILE_EXTENSION[] = "pdf";
66static const char PNG_FILE_EXTENSION[] = "png";
67
edisonn@google.com01cd4d52013-06-10 20:44:45 +000068/** Replaces the extension of a file.
69 * @param path File name whose extension will be changed.
70 * @param old_extension The old extension.
71 * @param new_extension The new extension.
72 * @returns false if the file did not has the expected extension.
73 * if false is returned, contents of path are undefined.
74 */
edisonn@google.com222382b2013-07-10 22:33:10 +000075static bool add_page_and_replace_filename_extension(SkString* path, int page,
edisonn@google.com01cd4d52013-06-10 20:44:45 +000076 const char old_extension[],
77 const char new_extension[]) {
78 if (path->endsWith(old_extension)) {
79 path->remove(path->size() - strlen(old_extension),
80 strlen(old_extension));
81 if (!path->endsWith(".")) {
82 return false;
83 }
edisonn@google.com222382b2013-07-10 22:33:10 +000084 if (page >= 0) {
85 path->appendf("%i.", page);
86 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +000087 path->append(new_extension);
88 return true;
89 }
90 return false;
91}
edisonn@google.com222382b2013-07-10 22:33:10 +000092
edisonn@google.com33f11b62013-08-14 21:35:27 +000093static void make_filepath(SkString* path, const SkString& dir, const SkString& name) {
edisonn@google.com147adb12013-07-24 15:56:19 +000094 size_t len = dir.size();
95 path->set(dir);
96 if (0 < len && '/' != dir[len - 1]) {
97 path->append("/");
98 }
99 path->append(name);
100}
101
edisonn@google.com33f11b62013-08-14 21:35:27 +0000102static bool is_path_seperator(const char chr) {
edisonn@google.com147adb12013-07-24 15:56:19 +0000103#if defined(SK_BUILD_FOR_WIN)
104 return chr == '\\' || chr == '/';
105#else
106 return chr == '/';
107#endif
108}
109
edisonn@google.com33f11b62013-08-14 21:35:27 +0000110static void get_basename(SkString* basename, const SkString& path) {
edisonn@google.com147adb12013-07-24 15:56:19 +0000111 if (path.size() == 0) {
112 basename->reset();
113 return;
114 }
115
116 size_t end = path.size() - 1;
117
118 // Paths pointing to directories often have a trailing slash,
119 // we remove it so the name is not empty
120 if (is_path_seperator(path[end])) {
121 if (end == 0) {
122 basename->reset();
123 return;
124 }
125
126 end -= 1;
127 }
128
129 size_t i = end;
130 do {
131 --i;
132 if (is_path_seperator(path[i])) {
133 const char* basenameStart = path.c_str() + i + 1;
134 size_t basenameLength = end - i;
135 basename->set(basenameStart, basenameLength);
136 return;
137 }
138 } while (i > 0);
139
140 basename->set(path.c_str(), end + 1);
141}
142
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000143/** Builds the output filename. path = dir/name, and it replaces expected
144 * .skp extension with .pdf extention.
145 * @param path Output filename.
146 * @param name The name of the file.
147 * @returns false if the file did not has the expected extension.
148 * if false is returned, contents of path are undefined.
149 */
150static bool make_output_filepath(SkString* path, const SkString& dir,
edisonn@google.com222382b2013-07-10 22:33:10 +0000151 const SkString& name,
152 int page) {
edisonn@google.com147adb12013-07-24 15:56:19 +0000153 make_filepath(path, dir, name);
edisonn@google.com222382b2013-07-10 22:33:10 +0000154 return add_page_and_replace_filename_extension(path, page,
155 PDF_FILE_EXTENSION,
156 PNG_FILE_EXTENSION);
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000157}
edisonn@google.com222382b2013-07-10 22:33:10 +0000158
edisonn@google.com5149bd92013-08-05 17:26:11 +0000159static void setup_bitmap(SkBitmap* bitmap, int width, int height, SkColor color = SK_ColorTRANSPARENT) {
edisonn@google.com222382b2013-07-10 22:33:10 +0000160 bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
161
162 bitmap->allocPixels();
163 bitmap->eraseColor(color);
164}
165
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000166/** Write the output of pdf renderer to a file.
167 * @param outputDir Output dir.
168 * @param inputFilename The skp file that was read.
169 * @param renderer The object responsible to write the pdf file.
edisonn@google.comcdad30b2013-07-10 22:37:38 +0000170 * @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 +0000171 */
edisonn@google.com222382b2013-07-10 22:33:10 +0000172
edisonn@google.com13102382013-07-11 14:50:12 +0000173extern "C" SkBitmap* gDumpBitmap;
174extern "C" SkCanvas* gDumpCanvas;
175
edisonn@google.com768bc6a2013-08-08 12:42:13 +0000176#if SK_SUPPORT_GPU
177GrContextFactory gContextFactory;
178#endif
179
edisonn@google.com222382b2013-07-10 22:33:10 +0000180static bool render_page(const SkString& outputDir,
edisonn@google.com444e25a2013-07-11 15:20:50 +0000181 const SkString& inputFilename,
182 const SkPdfRenderer& renderer,
183 int page) {
edisonn@google.com222382b2013-07-10 22:33:10 +0000184 SkRect rect = renderer.MediaBox(page < 0 ? 0 :page);
185
edisonn@google.comac03d912013-07-22 15:36:39 +0000186 // Exercise all pdf codepaths as in normal rendering, but no actual bits are changed.
187 if (!FLAGS_config.isEmpty() && strcmp(FLAGS_config[0], "nul") == 0) {
188 SkBitmap bitmap;
robertphillips@google.com1f2f3382013-08-29 11:54:56 +0000189 SkAutoTUnref<SkBaseDevice> device(SkNEW_ARGS(SkBitmapDevice, (bitmap)));
edisonn@google.comac03d912013-07-22 15:36:39 +0000190 SkNulCanvas canvas(device);
191 renderer.renderPage(page < 0 ? 0 : page, &canvas, rect);
192 } else {
193 // 8888
194 SkRect rect = renderer.MediaBox(page < 0 ? 0 :page);
edisonn@google.com444e25a2013-07-11 15:20:50 +0000195
edisonn@google.comac03d912013-07-22 15:36:39 +0000196 SkBitmap bitmap;
197 SkScalar width = SkScalarMul(rect.width(), SkDoubleToScalar(sqrt(FLAGS_DPI / 72.0)));
198 SkScalar height = SkScalarMul(rect.height(), SkDoubleToScalar(sqrt(FLAGS_DPI / 72.0)));
199
200 rect = SkRect::MakeWH(width, height);
edisonn@google.com444e25a2013-07-11 15:20:50 +0000201
edisonn@google.com222382b2013-07-10 22:33:10 +0000202#ifdef PDF_DEBUG_3X
edisonn@google.comac03d912013-07-22 15:36:39 +0000203 setup_bitmap(&bitmap, 3 * (int)SkScalarToDouble(width), 3 * (int)SkScalarToDouble(height));
edisonn@google.com222382b2013-07-10 22:33:10 +0000204#else
edisonn@google.comac03d912013-07-22 15:36:39 +0000205 setup_bitmap(&bitmap, (int)SkScalarToDouble(width), (int)SkScalarToDouble(height));
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) {
251 SkDebugf("Memory usage after page %i rendered: %u\n", page < 0 ? 0 : page, (unsigned int)renderer.bytesUsed());
252 }
edisonn@google.com13102382013-07-11 14:50:12 +0000253 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000254 return true;
255}
edisonn@google.com222382b2013-07-10 22:33:10 +0000256
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000257/** Reads an skp file, renders it to pdf and writes the output to a pdf file
258 * @param inputPath The skp file to be read.
259 * @param outputDir Output dir.
260 * @param renderer The object responsible to render the skp object into pdf.
261 */
edisonn@google.com222382b2013-07-10 22:33:10 +0000262static bool process_pdf(const SkString& inputPath, const SkString& outputDir,
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000263 SkPdfRenderer& renderer) {
edisonn@google.com222382b2013-07-10 22:33:10 +0000264 SkDebugf("Loading PDF: %s\n", inputPath.c_str());
265
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000266 SkString inputFilename;
edisonn@google.com147adb12013-07-24 15:56:19 +0000267 get_basename(&inputFilename, inputPath);
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000268
edisonn@google.com2273f9b2013-08-06 21:48:44 +0000269 bool success = true;
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000270
edisonn@google.com222382b2013-07-10 22:33:10 +0000271 success = renderer.load(inputPath);
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000272 if (FLAGS_showMemoryUsage) {
273 SkDebugf("Memory usage after load: %u\n", (unsigned int)renderer.bytesUsed());
274 }
275
276 // TODO(edisonn): bench timers
277 if (FLAGS_benchLoad > 0) {
278 for (int i = 0 ; i < FLAGS_benchLoad; i++) {
edisonn@google.com2273f9b2013-08-06 21:48:44 +0000279 success = renderer.load(inputPath) && success;
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000280 if (FLAGS_showMemoryUsage) {
281 SkDebugf("Memory usage after load %i number : %u\n", i, (unsigned int)renderer.bytesUsed());
282 }
283 }
284 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000285
edisonn@google.com222382b2013-07-10 22:33:10 +0000286 if (success) {
287 if (!renderer.pages())
288 {
289 SkDebugf("ERROR: Empty PDF Document %s\n", inputPath.c_str());
290 return false;
291 } else {
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000292 for (int i = 0; i < FLAGS_benchRender + 1; i++) {
293 // TODO(edisonn) if (i == 1) start timer
294 if (strcmp(FLAGS_pages[0], "all") == 0) {
295 for (int pn = 0; pn < renderer.pages(); ++pn) {
296 success = render_page(outputDir, inputFilename, renderer, FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : pn) && success;
297 }
298 } else if (strcmp(FLAGS_pages[0], "reverse") == 0) {
299 for (int pn = renderer.pages() - 1; pn >= 0; --pn) {
300 success = render_page(outputDir, inputFilename, renderer, FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : pn) && success;
301 }
302 } else if (strcmp(FLAGS_pages[0], "first") == 0) {
303 success = render_page(outputDir, inputFilename, renderer, FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : 0) && success;
304 } else if (strcmp(FLAGS_pages[0], "last") == 0) {
305 success = render_page(outputDir, inputFilename, renderer, FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : renderer.pages() - 1) && success;
306 } else {
307 int pn = atoi(FLAGS_pages[0]);
edisonn@google.com2273f9b2013-08-06 21:48:44 +0000308 success = render_page(outputDir, inputFilename, renderer, FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : pn) && success;
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000309 }
edisonn@google.com222382b2013-07-10 22:33:10 +0000310 }
311 }
312 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000313
edisonn@google.com2273f9b2013-08-06 21:48:44 +0000314 if (!success) {
315 SkDebugf("Failures for file %s\n", inputPath.c_str());
316 }
317
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000318 return success;
319}
320
321/** For each file in the directory or for the file passed in input, call
322 * parse_pdf.
323 * @param input A directory or an pdf file.
324 * @param outputDir Output dir.
325 * @param renderer The object responsible to render the skp object into pdf.
326 */
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000327static int process_input(const char* input, const SkString& outputDir,
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000328 SkPdfRenderer& renderer) {
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000329 int failures = 0;
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000330 if (sk_isdir(input)) {
331 SkOSFile::Iter iter(input, PDF_FILE_EXTENSION);
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000332 SkString inputFilename;
333 while (iter.next(&inputFilename)) {
334 SkString inputPath;
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000335 SkString _input;
336 _input.append(input);
edisonn@google.com147adb12013-07-24 15:56:19 +0000337 make_filepath(&inputPath, _input, inputFilename);
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000338 if (!process_pdf(inputPath, outputDir, renderer)) {
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000339 ++failures;
340 }
341 }
342 } else {
343 SkString inputPath(input);
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000344 if (!process_pdf(inputPath, outputDir, renderer)) {
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000345 ++failures;
346 }
347 }
348 return failures;
349}
350
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000351int tool_main(int argc, char** argv);
352int tool_main(int argc, char** argv) {
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000353 SkCommandLineFlags::SetUsage("Parse and Render .pdf files (pdf viewer).");
354 SkCommandLineFlags::Parse(argc, argv);
355
356 if (FLAGS_readPath.isEmpty()) {
357 SkDebugf(".pdf files or directories are required.\n");
358 exit(-1);
359 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000360
edisonn@google.com222382b2013-07-10 22:33:10 +0000361 SkPdfRenderer renderer;
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000362
363 SkString outputDir;
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000364 if (FLAGS_writePath.count() == 1) {
365 outputDir.set(FLAGS_writePath[0]);
366 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000367
368 int failures = 0;
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000369 for (int i = 0; i < FLAGS_readPath.count(); i ++) {
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000370 failures += process_input(FLAGS_readPath[i], outputDir, renderer);
edisonn@google.com222382b2013-07-10 22:33:10 +0000371 renderer.unload();
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000372 }
373
374 reportPdfRenderStats();
375
376 if (failures != 0) {
377 SkDebugf("Failed to render %i PDFs.\n", failures);
378 return 1;
379 }
380
381 return 0;
382}
383
384#if !defined SK_BUILD_FOR_IOS
385int main(int argc, char * const argv[]) {
386 return tool_main(argc, (char**) argv);
387}
388#endif