blob: 94735ac49a796599e15b838260bec03b0ab559e9 [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"
scroggo@google.com5092adc2013-12-02 20:22:31 +000015#include "SkPdfConfig.h"
edisonn@google.come50d9a12013-10-10 20:58:22 +000016#include "SkPdfRenderer.h"
edisonn@google.com01cd4d52013-06-10 20:44:45 +000017#include "SkPicture.h"
18#include "SkStream.h"
19#include "SkTypeface.h"
20#include "SkTArray.h"
edisonn@google.comac03d912013-07-22 15:36:39 +000021#include "SkNulCanvas.h"
edisonn@google.com01cd4d52013-06-10 20:44:45 +000022
edisonn@google.coma5aaa792013-07-11 12:27:21 +000023DEFINE_string2(readPath, r, "", "pdf files or directories of pdf files to process.");
24DEFINE_string2(writePath, w, "", "Directory to write the rendered pages.");
25DEFINE_bool2(noExtensionForOnePagePdf, n, false, "No page extension if only one page.");
26DEFINE_bool2(showMemoryUsage, m, false, "Show Memory usage.");
edisonn@google.com7b328fd2013-07-11 12:53:06 +000027DEFINE_string2(pages, p, "all", "What pages to render and how:\n"
28 "\tall - all pages\n"
29 "\treverse - all pages, in reverse order\n"
30 "\tfirst - first page\n"
31 "\tlast - last page\n"
32 "\tnumber - a specific page number\n"
33 );
edisonn@google.com15b11182013-07-11 14:43:15 +000034DEFINE_double(DPI, 72, "DPI to be used for rendering (scale).");
edisonn@google.com6a9d4362013-07-11 16:25:51 +000035DEFINE_int32(benchLoad, 0, "Load the pdf file minimally N times, without any rendering and \n"
36 "\tminimal parsing to ensure correctness. Default 0 (disabled).");
37DEFINE_int32(benchRender, 0, "Render the pdf content N times. Default 0 (disabled)");
scroggo@google.comc6e1e9a2013-11-14 17:05:05 +000038DEFINE_string2(config, c, "8888", "Canvas to render:\n"
39 "\t8888 - argb\n"
40 "\tnul - render in null canvas, any draw will just return.\n"
41 );
edisonn@google.com598cf5d2013-10-09 15:13:19 +000042DEFINE_bool2(transparentBackground, t, false, "Make background transparent instead of white.");
edisonn@google.com6a9d4362013-07-11 16:25:51 +000043
edisonn@google.com01cd4d52013-06-10 20:44:45 +000044/**
45 * Given list of directories and files to use as input, expects to find .pdf
46 * files and it will convert them to .png files writing them in the same directory
47 * one file for each page.
48 *
49 * Returns zero exit code if all .pdf files were converted successfully,
50 * otherwise returns error code 1.
51 */
52
53static const char PDF_FILE_EXTENSION[] = "pdf";
54static const char PNG_FILE_EXTENSION[] = "png";
55
edisonn@google.com01cd4d52013-06-10 20:44:45 +000056/** Replaces the extension of a file.
57 * @param path File name whose extension will be changed.
58 * @param old_extension The old extension.
59 * @param new_extension The new extension.
60 * @returns false if the file did not has the expected extension.
61 * if false is returned, contents of path are undefined.
62 */
edisonn@google.com222382b2013-07-10 22:33:10 +000063static bool add_page_and_replace_filename_extension(SkString* path, int page,
edisonn@google.com01cd4d52013-06-10 20:44:45 +000064 const char old_extension[],
65 const char new_extension[]) {
66 if (path->endsWith(old_extension)) {
67 path->remove(path->size() - strlen(old_extension),
68 strlen(old_extension));
69 if (!path->endsWith(".")) {
70 return false;
71 }
edisonn@google.com222382b2013-07-10 22:33:10 +000072 if (page >= 0) {
73 path->appendf("%i.", page);
74 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +000075 path->append(new_extension);
76 return true;
77 }
78 return false;
79}
edisonn@google.com222382b2013-07-10 22:33:10 +000080
edisonn@google.com01cd4d52013-06-10 20:44:45 +000081/** Builds the output filename. path = dir/name, and it replaces expected
82 * .skp extension with .pdf extention.
83 * @param path Output filename.
84 * @param name The name of the file.
85 * @returns false if the file did not has the expected extension.
86 * if false is returned, contents of path are undefined.
87 */
88static bool make_output_filepath(SkString* path, const SkString& dir,
edisonn@google.com222382b2013-07-10 22:33:10 +000089 const SkString& name,
90 int page) {
tfarinaa8e2e152014-07-28 19:26:58 -070091 *path = SkOSPath::Join(dir.c_str(), name.c_str());
edisonn@google.com222382b2013-07-10 22:33:10 +000092 return add_page_and_replace_filename_extension(path, page,
93 PDF_FILE_EXTENSION,
94 PNG_FILE_EXTENSION);
edisonn@google.com01cd4d52013-06-10 20:44:45 +000095}
edisonn@google.com222382b2013-07-10 22:33:10 +000096
edisonn@google.com598cf5d2013-10-09 15:13:19 +000097static void setup_bitmap(SkBitmap* bitmap, int width, int height, SkColor color) {
reed6c225732014-06-09 19:52:07 -070098 bitmap->allocN32Pixels(width, height);
edisonn@google.com222382b2013-07-10 22:33:10 +000099 bitmap->eraseColor(color);
100}
101
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000102/** Write the output of pdf renderer to a file.
103 * @param outputDir Output dir.
104 * @param inputFilename The skp file that was read.
105 * @param renderer The object responsible to write the pdf file.
edisonn@google.comcdad30b2013-07-10 22:37:38 +0000106 * @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 +0000107 */
edisonn@google.com222382b2013-07-10 22:33:10 +0000108
scroggo@google.com5092adc2013-12-02 20:22:31 +0000109#ifdef PDF_TRACE_DIFF_IN_PNG
edisonn@google.com13102382013-07-11 14:50:12 +0000110extern "C" SkBitmap* gDumpBitmap;
111extern "C" SkCanvas* gDumpCanvas;
scroggo@google.com5092adc2013-12-02 20:22:31 +0000112#endif
edisonn@google.com13102382013-07-11 14:50:12 +0000113
edisonn@google.com222382b2013-07-10 22:33:10 +0000114static bool render_page(const SkString& outputDir,
edisonn@google.com444e25a2013-07-11 15:20:50 +0000115 const SkString& inputFilename,
116 const SkPdfRenderer& renderer,
117 int page) {
edisonn@google.com222382b2013-07-10 22:33:10 +0000118 SkRect rect = renderer.MediaBox(page < 0 ? 0 :page);
119
edisonn@google.comac03d912013-07-22 15:36:39 +0000120 // Exercise all pdf codepaths as in normal rendering, but no actual bits are changed.
121 if (!FLAGS_config.isEmpty() && strcmp(FLAGS_config[0], "nul") == 0) {
122 SkBitmap bitmap;
robertphillips9a53fd72015-06-22 09:46:59 -0700123 SkNulCanvas canvas(bitmap);
edisonn@google.comac03d912013-07-22 15:36:39 +0000124 renderer.renderPage(page < 0 ? 0 : page, &canvas, rect);
125 } else {
126 // 8888
127 SkRect rect = renderer.MediaBox(page < 0 ? 0 :page);
edisonn@google.com444e25a2013-07-11 15:20:50 +0000128
edisonn@google.comac03d912013-07-22 15:36:39 +0000129 SkBitmap bitmap;
edisonn@google.com04068b12013-11-12 21:56:39 +0000130 SkScalar width = SkScalarMul(rect.width(), SkDoubleToScalar(FLAGS_DPI / 72.0));
131 SkScalar height = SkScalarMul(rect.height(), SkDoubleToScalar(FLAGS_DPI / 72.0));
edisonn@google.comac03d912013-07-22 15:36:39 +0000132
133 rect = SkRect::MakeWH(width, height);
edisonn@google.com444e25a2013-07-11 15:20:50 +0000134
edisonn@google.com598cf5d2013-10-09 15:13:19 +0000135 SkColor background = FLAGS_transparentBackground ? SK_ColorTRANSPARENT : SK_ColorWHITE;
136
edisonn@google.com222382b2013-07-10 22:33:10 +0000137#ifdef PDF_DEBUG_3X
edisonn@google.come50d9a12013-10-10 20:58:22 +0000138 setup_bitmap(&bitmap, 3 * (int)SkScalarToDouble(width), 3 * (int)SkScalarToDouble(height),
139 background);
edisonn@google.com222382b2013-07-10 22:33:10 +0000140#else
edisonn@google.come50d9a12013-10-10 20:58:22 +0000141 setup_bitmap(&bitmap, (int)SkScalarToDouble(width), (int)SkScalarToDouble(height),
142 background);
edisonn@google.com222382b2013-07-10 22:33:10 +0000143#endif
robertphillips9a53fd72015-06-22 09:46:59 -0700144 if (strcmp(FLAGS_config[0], "8888") != 0) {
edisonn@google.com768bc6a2013-08-08 12:42:13 +0000145 SkDebugf("unknown --config: %s\n", FLAGS_config[0]);
146 return false;
147 }
robertphillips9a53fd72015-06-22 09:46:59 -0700148 SkCanvas canvas(bitmap);
edisonn@google.com222382b2013-07-10 22:33:10 +0000149
scroggo@google.com5092adc2013-12-02 20:22:31 +0000150#ifdef PDF_TRACE_DIFF_IN_PNG
edisonn@google.comac03d912013-07-22 15:36:39 +0000151 gDumpBitmap = &bitmap;
edisonn@google.comac03d912013-07-22 15:36:39 +0000152 gDumpCanvas = &canvas;
scroggo@google.com5092adc2013-12-02 20:22:31 +0000153#endif
edisonn@google.comac03d912013-07-22 15:36:39 +0000154 renderer.renderPage(page < 0 ? 0 : page, &canvas, rect);
edisonn@google.com222382b2013-07-10 22:33:10 +0000155
edisonn@google.comac03d912013-07-22 15:36:39 +0000156 SkString outputPath;
157 if (!make_output_filepath(&outputPath, outputDir, inputFilename, page)) {
158 return false;
159 }
160 SkImageEncoder::EncodeFile(outputPath.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100);
161
162 if (FLAGS_showMemoryUsage) {
edisonn@google.come50d9a12013-10-10 20:58:22 +0000163 SkDebugf("Memory usage after page %i rendered: %u\n",
164 page < 0 ? 0 : page, (unsigned int)renderer.bytesUsed());
edisonn@google.comac03d912013-07-22 15:36:39 +0000165 }
edisonn@google.com13102382013-07-11 14:50:12 +0000166 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000167 return true;
168}
edisonn@google.com222382b2013-07-10 22:33:10 +0000169
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000170/** Reads an skp file, renders it to pdf and writes the output to a pdf file
171 * @param inputPath The skp file to be read.
172 * @param outputDir Output dir.
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000173 */
scroggo@google.com90922892013-11-14 19:09:27 +0000174static bool process_pdf(const SkString& inputPath, const SkString& outputDir) {
edisonn@google.com222382b2013-07-10 22:33:10 +0000175 SkDebugf("Loading PDF: %s\n", inputPath.c_str());
176
tfarinaa8e2e152014-07-28 19:26:58 -0700177 SkString inputFilename = SkOSPath::Basename(inputPath.c_str());
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000178
scroggo@google.com90922892013-11-14 19:09:27 +0000179 SkAutoTDelete<SkPdfRenderer> renderer(SkPdfRenderer::CreateFromFile(inputPath.c_str()));
180 if (NULL == renderer.get()) {
181 SkDebugf("Failure loading file %s\n", inputPath.c_str());
182 return false;
183 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000184
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000185 if (FLAGS_showMemoryUsage) {
scroggo@google.com90922892013-11-14 19:09:27 +0000186 SkDebugf("Memory usage after load: %u\n", (unsigned int) renderer->bytesUsed());
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000187 }
188
189 // TODO(edisonn): bench timers
190 if (FLAGS_benchLoad > 0) {
191 for (int i = 0 ; i < FLAGS_benchLoad; i++) {
scroggo@google.com90922892013-11-14 19:09:27 +0000192 SkAutoTDelete<SkPdfRenderer> benchRenderer(
193 SkPdfRenderer::CreateFromFile(inputPath.c_str()));
194 if (NULL == benchRenderer.get()) {
195 SkDebugf("Failed to load on %ith attempt\n", i);
196 } else if (FLAGS_showMemoryUsage) {
edisonn@google.come50d9a12013-10-10 20:58:22 +0000197 SkDebugf("Memory usage after load %i number : %u\n", i,
scroggo@google.com90922892013-11-14 19:09:27 +0000198 (unsigned int) benchRenderer->bytesUsed());
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000199 }
200 }
201 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000202
scroggo@google.com90922892013-11-14 19:09:27 +0000203 if (!renderer->pages()) {
204 // This should never happen, since CreateFromFile will return NULL if there are no pages.
205 SkASSERT(false);
206 SkDebugf("ERROR: Empty PDF Document %s\n", inputPath.c_str());
207 return false;
208 }
209
210 bool success = true;
211 for (int i = 0; i < FLAGS_benchRender + 1; i++) {
212 // TODO(edisonn) if (i == 1) start timer
213 if (strcmp(FLAGS_pages[0], "all") == 0) {
214 for (int pn = 0; pn < renderer->pages(); ++pn) {
215 success &= render_page(outputDir, inputFilename, *renderer,
216 FLAGS_noExtensionForOnePagePdf && renderer->pages() == 1 ? -1 : pn);
edisonn@google.com222382b2013-07-10 22:33:10 +0000217 }
scroggo@google.com90922892013-11-14 19:09:27 +0000218 } else if (strcmp(FLAGS_pages[0], "reverse") == 0) {
219 for (int pn = renderer->pages() - 1; pn >= 0; --pn) {
220 success &= render_page(outputDir, inputFilename, *renderer,
221 FLAGS_noExtensionForOnePagePdf && renderer->pages() == 1 ? -1 : pn);
222 }
223 } else if (strcmp(FLAGS_pages[0], "first") == 0) {
224 success &= render_page(outputDir, inputFilename, *renderer,
225 FLAGS_noExtensionForOnePagePdf && renderer->pages() == 1 ? -1 : 0);
226 } else if (strcmp(FLAGS_pages[0], "last") == 0) {
227 success &= render_page(outputDir, inputFilename, *renderer,
228 FLAGS_noExtensionForOnePagePdf && renderer->pages() == 1 ? -1
229 : renderer->pages() - 1);
230 } else {
231 int pn = atoi(FLAGS_pages[0]);
232 success &= render_page(outputDir, inputFilename, *renderer,
233 FLAGS_noExtensionForOnePagePdf && renderer->pages() == 1 ? -1 : pn);
edisonn@google.com222382b2013-07-10 22:33:10 +0000234 }
235 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000236
edisonn@google.com2273f9b2013-08-06 21:48:44 +0000237 if (!success) {
238 SkDebugf("Failures for file %s\n", inputPath.c_str());
239 }
240
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000241 return success;
242}
243
244/** For each file in the directory or for the file passed in input, call
245 * parse_pdf.
246 * @param input A directory or an pdf file.
247 * @param outputDir Output dir.
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000248 */
scroggo@google.com90922892013-11-14 19:09:27 +0000249static int process_input(const char* input, const SkString& outputDir) {
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000250 int failures = 0;
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000251 if (sk_isdir(input)) {
252 SkOSFile::Iter iter(input, PDF_FILE_EXTENSION);
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000253 SkString inputFilename;
254 while (iter.next(&inputFilename)) {
tfarinaa8e2e152014-07-28 19:26:58 -0700255 SkString inputPath = SkOSPath::Join(input, inputFilename.c_str());
scroggo@google.com90922892013-11-14 19:09:27 +0000256 if (!process_pdf(inputPath, outputDir)) {
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000257 ++failures;
258 }
259 }
260 } else {
261 SkString inputPath(input);
scroggo@google.com90922892013-11-14 19:09:27 +0000262 if (!process_pdf(inputPath, outputDir)) {
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000263 ++failures;
264 }
265 }
266 return failures;
267}
268
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000269int tool_main(int argc, char** argv);
270int tool_main(int argc, char** argv) {
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000271 SkCommandLineFlags::SetUsage("Parse and Render .pdf files (pdf viewer).");
272 SkCommandLineFlags::Parse(argc, argv);
273
274 if (FLAGS_readPath.isEmpty()) {
275 SkDebugf(".pdf files or directories are required.\n");
276 exit(-1);
277 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000278
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000279 SkString outputDir;
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000280 if (FLAGS_writePath.count() == 1) {
281 outputDir.set(FLAGS_writePath[0]);
282 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000283
284 int failures = 0;
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000285 for (int i = 0; i < FLAGS_readPath.count(); i ++) {
scroggo@google.com90922892013-11-14 19:09:27 +0000286 failures += process_input(FLAGS_readPath[i], outputDir);
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000287 }
288
289 reportPdfRenderStats();
290
291 if (failures != 0) {
292 SkDebugf("Failed to render %i PDFs.\n", failures);
293 return 1;
294 }
295
296 return 0;
297}
298
299#if !defined SK_BUILD_FOR_IOS
300int main(int argc, char * const argv[]) {
301 return tool_main(argc, (char**) argv);
302}
303#endif