blob: 99506e914fb867964dd40806de9fda644c5751a1 [file] [log] [blame]
edisonn@google.com01cd4d52013-06-10 20:44:45 +00001#include "SkCanvas.h"
edisonn@google.coma5aaa792013-07-11 12:27:21 +00002#include "SkCommandLineFlags.h"
edisonn@google.com01cd4d52013-06-10 20:44:45 +00003#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"
edisonn@google.comac03d912013-07-22 15:36:39 +000012#include "SkNulCanvas.h"
edisonn@google.com01cd4d52013-06-10 20:44:45 +000013
edisonn@google.com222382b2013-07-10 22:33:10 +000014#include "SkPdfRenderer.h"
edisonn@google.comb857a0c2013-06-25 20:45:40 +000015
edisonn@google.coma5aaa792013-07-11 12:27:21 +000016DEFINE_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.");
edisonn@google.com7b328fd2013-07-11 12:53:06 +000020DEFINE_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 );
edisonn@google.com15b11182013-07-11 14:43:15 +000027DEFINE_double(DPI, 72, "DPI to be used for rendering (scale).");
edisonn@google.com6a9d4362013-07-11 16:25:51 +000028DEFINE_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)");
edisonn@google.comac03d912013-07-22 15:36:39 +000031DEFINE_string2(config, c, "8888", "Canvas to render:\n"
32 "\t8888 - all pages\n"
33 "\tnul - all pages, in reverse order\n"
34 );
edisonn@google.com6a9d4362013-07-11 16:25:51 +000035
36
edisonn@google.com15b11182013-07-11 14:43:15 +000037// TODO(edisonn): add config for device target(gpu, raster, pdf), + ability not to render at all
edisonn@google.com7b328fd2013-07-11 12:53:06 +000038
edisonn@google.com01cd4d52013-06-10 20:44:45 +000039/**
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
edisonn@google.com01cd4d52013-06-10 20:44:45 +000051/** 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 */
edisonn@google.com222382b2013-07-10 22:33:10 +000058static bool add_page_and_replace_filename_extension(SkString* path, int page,
edisonn@google.com01cd4d52013-06-10 20:44:45 +000059 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 }
edisonn@google.com222382b2013-07-10 22:33:10 +000067 if (page >= 0) {
68 path->appendf("%i.", page);
69 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +000070 path->append(new_extension);
71 return true;
72 }
73 return false;
74}
edisonn@google.com222382b2013-07-10 22:33:10 +000075
edisonn@google.com147adb12013-07-24 15:56:19 +000076void 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
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000126/** 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,
edisonn@google.com222382b2013-07-10 22:33:10 +0000134 const SkString& name,
135 int page) {
edisonn@google.com147adb12013-07-24 15:56:19 +0000136 make_filepath(path, dir, name);
edisonn@google.com222382b2013-07-10 22:33:10 +0000137 return add_page_and_replace_filename_extension(path, page,
138 PDF_FILE_EXTENSION,
139 PNG_FILE_EXTENSION);
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000140}
edisonn@google.com222382b2013-07-10 22:33:10 +0000141
edisonn@google.com5149bd92013-08-05 17:26:11 +0000142static void setup_bitmap(SkBitmap* bitmap, int width, int height, SkColor color = SK_ColorTRANSPARENT) {
edisonn@google.com222382b2013-07-10 22:33:10 +0000143 bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
144
145 bitmap->allocPixels();
146 bitmap->eraseColor(color);
147}
148
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000149/** 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.
edisonn@google.comcdad30b2013-07-10 22:37:38 +0000153 * @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 +0000154 */
edisonn@google.com222382b2013-07-10 22:33:10 +0000155
edisonn@google.com13102382013-07-11 14:50:12 +0000156extern "C" SkBitmap* gDumpBitmap;
157extern "C" SkCanvas* gDumpCanvas;
158
edisonn@google.com222382b2013-07-10 22:33:10 +0000159static bool render_page(const SkString& outputDir,
edisonn@google.com444e25a2013-07-11 15:20:50 +0000160 const SkString& inputFilename,
161 const SkPdfRenderer& renderer,
162 int page) {
edisonn@google.com222382b2013-07-10 22:33:10 +0000163 SkRect rect = renderer.MediaBox(page < 0 ? 0 :page);
164
edisonn@google.comac03d912013-07-22 15:36:39 +0000165 // 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);
edisonn@google.com444e25a2013-07-11 15:20:50 +0000174
edisonn@google.comac03d912013-07-22 15:36:39 +0000175 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);
edisonn@google.com444e25a2013-07-11 15:20:50 +0000180
edisonn@google.com222382b2013-07-10 22:33:10 +0000181#ifdef PDF_DEBUG_3X
edisonn@google.comac03d912013-07-22 15:36:39 +0000182 setup_bitmap(&bitmap, 3 * (int)SkScalarToDouble(width), 3 * (int)SkScalarToDouble(height));
edisonn@google.com222382b2013-07-10 22:33:10 +0000183#else
edisonn@google.comac03d912013-07-22 15:36:39 +0000184 setup_bitmap(&bitmap, (int)SkScalarToDouble(width), (int)SkScalarToDouble(height));
edisonn@google.com222382b2013-07-10 22:33:10 +0000185#endif
edisonn@google.comac03d912013-07-22 15:36:39 +0000186 SkAutoTUnref<SkDevice> device(SkNEW_ARGS(SkDevice, (bitmap)));
187 SkCanvas canvas(device);
edisonn@google.com222382b2013-07-10 22:33:10 +0000188
edisonn@google.comac03d912013-07-22 15:36:39 +0000189 gDumpBitmap = &bitmap;
edisonn@google.com222382b2013-07-10 22:33:10 +0000190
edisonn@google.comac03d912013-07-22 15:36:39 +0000191 gDumpCanvas = &canvas;
192 renderer.renderPage(page < 0 ? 0 : page, &canvas, rect);
edisonn@google.com222382b2013-07-10 22:33:10 +0000193
edisonn@google.comac03d912013-07-22 15:36:39 +0000194 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 }
edisonn@google.com13102382013-07-11 14:50:12 +0000203 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000204 return true;
205}
edisonn@google.com222382b2013-07-10 22:33:10 +0000206
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000207/** 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 */
edisonn@google.com222382b2013-07-10 22:33:10 +0000212static bool process_pdf(const SkString& inputPath, const SkString& outputDir,
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000213 SkPdfRenderer& renderer) {
edisonn@google.com222382b2013-07-10 22:33:10 +0000214 SkDebugf("Loading PDF: %s\n", inputPath.c_str());
215
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000216 SkString inputFilename;
edisonn@google.com147adb12013-07-24 15:56:19 +0000217 get_basename(&inputFilename, inputPath);
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000218
edisonn@google.com2273f9b2013-08-06 21:48:44 +0000219 bool success = true;
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000220
edisonn@google.com222382b2013-07-10 22:33:10 +0000221 success = renderer.load(inputPath);
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000222 if (FLAGS_showMemoryUsage) {
223 SkDebugf("Memory usage after load: %u\n", (unsigned int)renderer.bytesUsed());
224 }
225
226 // TODO(edisonn): bench timers
227 if (FLAGS_benchLoad > 0) {
228 for (int i = 0 ; i < FLAGS_benchLoad; i++) {
edisonn@google.com2273f9b2013-08-06 21:48:44 +0000229 success = renderer.load(inputPath) && success;
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000230 if (FLAGS_showMemoryUsage) {
231 SkDebugf("Memory usage after load %i number : %u\n", i, (unsigned int)renderer.bytesUsed());
232 }
233 }
234 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000235
edisonn@google.com222382b2013-07-10 22:33:10 +0000236 if (success) {
237 if (!renderer.pages())
238 {
239 SkDebugf("ERROR: Empty PDF Document %s\n", inputPath.c_str());
240 return false;
241 } else {
edisonn@google.com6a9d4362013-07-11 16:25:51 +0000242 for (int i = 0; i < FLAGS_benchRender + 1; i++) {
243 // TODO(edisonn) if (i == 1) start timer
244 if (strcmp(FLAGS_pages[0], "all") == 0) {
245 for (int pn = 0; pn < renderer.pages(); ++pn) {
246 success = render_page(outputDir, inputFilename, renderer, FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : pn) && success;
247 }
248 } else if (strcmp(FLAGS_pages[0], "reverse") == 0) {
249 for (int pn = renderer.pages() - 1; pn >= 0; --pn) {
250 success = render_page(outputDir, inputFilename, renderer, FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : pn) && success;
251 }
252 } else if (strcmp(FLAGS_pages[0], "first") == 0) {
253 success = render_page(outputDir, inputFilename, renderer, FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : 0) && success;
254 } else if (strcmp(FLAGS_pages[0], "last") == 0) {
255 success = render_page(outputDir, inputFilename, renderer, FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : renderer.pages() - 1) && success;
256 } else {
257 int pn = atoi(FLAGS_pages[0]);
edisonn@google.com2273f9b2013-08-06 21:48:44 +0000258 success = render_page(outputDir, inputFilename, renderer, FLAGS_noExtensionForOnePagePdf && renderer.pages() == 1 ? -1 : pn) && success;
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000259 }
edisonn@google.com222382b2013-07-10 22:33:10 +0000260 }
261 }
262 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000263
edisonn@google.com2273f9b2013-08-06 21:48:44 +0000264 if (!success) {
265 SkDebugf("Failures for file %s\n", inputPath.c_str());
266 }
267
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000268 return success;
269}
270
271/** For each file in the directory or for the file passed in input, call
272 * parse_pdf.
273 * @param input A directory or an pdf file.
274 * @param outputDir Output dir.
275 * @param renderer The object responsible to render the skp object into pdf.
276 */
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000277static int process_input(const char* input, const SkString& outputDir,
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000278 SkPdfRenderer& renderer) {
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000279 int failures = 0;
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000280 if (sk_isdir(input)) {
281 SkOSFile::Iter iter(input, PDF_FILE_EXTENSION);
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000282 SkString inputFilename;
283 while (iter.next(&inputFilename)) {
284 SkString inputPath;
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000285 SkString _input;
286 _input.append(input);
edisonn@google.com147adb12013-07-24 15:56:19 +0000287 make_filepath(&inputPath, _input, inputFilename);
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000288 if (!process_pdf(inputPath, outputDir, renderer)) {
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000289 ++failures;
290 }
291 }
292 } else {
293 SkString inputPath(input);
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000294 if (!process_pdf(inputPath, outputDir, renderer)) {
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000295 ++failures;
296 }
297 }
298 return failures;
299}
300
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000301int tool_main(int argc, char** argv);
302int tool_main(int argc, char** argv) {
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000303 SkCommandLineFlags::SetUsage("Parse and Render .pdf files (pdf viewer).");
304 SkCommandLineFlags::Parse(argc, argv);
305
306 if (FLAGS_readPath.isEmpty()) {
307 SkDebugf(".pdf files or directories are required.\n");
308 exit(-1);
309 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000310
edisonn@google.com222382b2013-07-10 22:33:10 +0000311 SkPdfRenderer renderer;
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000312
313 SkString outputDir;
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000314 if (FLAGS_writePath.count() == 1) {
315 outputDir.set(FLAGS_writePath[0]);
316 }
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000317
318 int failures = 0;
edisonn@google.coma5aaa792013-07-11 12:27:21 +0000319 for (int i = 0; i < FLAGS_readPath.count(); i ++) {
edisonn@google.com7b328fd2013-07-11 12:53:06 +0000320 failures += process_input(FLAGS_readPath[i], outputDir, renderer);
edisonn@google.com222382b2013-07-10 22:33:10 +0000321 renderer.unload();
edisonn@google.com01cd4d52013-06-10 20:44:45 +0000322 }
323
324 reportPdfRenderStats();
325
326 if (failures != 0) {
327 SkDebugf("Failed to render %i PDFs.\n", failures);
328 return 1;
329 }
330
331 return 0;
332}
333
334#if !defined SK_BUILD_FOR_IOS
335int main(int argc, char * const argv[]) {
336 return tool_main(argc, (char**) argv);
337}
338#endif