Home | History | Annotate | Download | only in PdfViewer
      1 /*
      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 
      8 #include "SkBitmapDevice.h"
      9 #include "SkCanvas.h"
     10 #include "SkCommandLineFlags.h"
     11 #include "SkDevice.h"
     12 #include "SkGraphics.h"
     13 #include "SkImageDecoder.h"
     14 #include "SkImageEncoder.h"
     15 #include "SkOSFile.h"
     16 #include "SkPdfConfig.h"
     17 #include "SkPdfRenderer.h"
     18 #include "SkPicture.h"
     19 #include "SkStream.h"
     20 #include "SkTypeface.h"
     21 #include "SkTArray.h"
     22 #include "SkNulCanvas.h"
     23 
     24 DEFINE_string2(readPath, r, "", "pdf files or directories of pdf files to process.");
     25 DEFINE_string2(writePath, w, "", "Directory to write the rendered pages.");
     26 DEFINE_bool2(noExtensionForOnePagePdf, n, false, "No page extension if only one page.");
     27 DEFINE_bool2(showMemoryUsage, m, false, "Show Memory usage.");
     28 DEFINE_string2(pages, p, "all", "What pages to render and how:\n"
     29                                 "\tall - all pages\n"
     30                                 "\treverse - all pages, in reverse order\n"
     31                                 "\tfirst - first page\n"
     32                                 "\tlast - last page\n"
     33                                 "\tnumber - a specific page number\n"
     34                );
     35 DEFINE_double(DPI, 72, "DPI to be used for rendering (scale).");
     36 DEFINE_int32(benchLoad, 0, "Load the pdf file minimally N times, without any rendering and \n"
     37              "\tminimal parsing to ensure correctness. Default 0 (disabled).");
     38 DEFINE_int32(benchRender, 0, "Render the pdf content N times. Default 0 (disabled)");
     39 DEFINE_string2(config, c, "8888", "Canvas to render:\n"
     40                                   "\t8888 - argb\n"
     41                                   "\tnul - render in null canvas, any draw will just return.\n"
     42                );
     43 DEFINE_bool2(transparentBackground, t, false, "Make background transparent instead of white.");
     44 
     45 /**
     46  * Given list of directories and files to use as input, expects to find .pdf
     47  * files and it will convert them to .png files writing them in the same directory
     48  * one file for each page.
     49  *
     50  * Returns zero exit code if all .pdf files were converted successfully,
     51  * otherwise returns error code 1.
     52  */
     53 
     54 static const char PDF_FILE_EXTENSION[] = "pdf";
     55 static const char PNG_FILE_EXTENSION[] = "png";
     56 
     57 /** Replaces the extension of a file.
     58  * @param path File name whose extension will be changed.
     59  * @param old_extension The old extension.
     60  * @param new_extension The new extension.
     61  * @returns false if the file did not has the expected extension.
     62  *  if false is returned, contents of path are undefined.
     63  */
     64 static bool add_page_and_replace_filename_extension(SkString* path, int page,
     65                                        const char old_extension[],
     66                                        const char new_extension[]) {
     67     if (path->endsWith(old_extension)) {
     68         path->remove(path->size() - strlen(old_extension),
     69                      strlen(old_extension));
     70         if (!path->endsWith(".")) {
     71             return false;
     72         }
     73         if (page >= 0) {
     74             path->appendf("%i.", page);
     75         }
     76         path->append(new_extension);
     77         return true;
     78     }
     79     return false;
     80 }
     81 
     82 /** Builds the output filename. path = dir/name, and it replaces expected
     83  * .skp extension with .pdf extention.
     84  * @param path Output filename.
     85  * @param name The name of the file.
     86  * @returns false if the file did not has the expected extension.
     87  *  if false is returned, contents of path are undefined.
     88  */
     89 static bool make_output_filepath(SkString* path, const SkString& dir,
     90                                  const SkString& name,
     91                                  int page) {
     92     *path = SkOSPath::Join(dir.c_str(), name.c_str());
     93     return add_page_and_replace_filename_extension(path, page,
     94                                                    PDF_FILE_EXTENSION,
     95                                                    PNG_FILE_EXTENSION);
     96 }
     97 
     98 static void setup_bitmap(SkBitmap* bitmap, int width, int height, SkColor color) {
     99     bitmap->allocN32Pixels(width, height);
    100     bitmap->eraseColor(color);
    101 }
    102 
    103 /** Write the output of pdf renderer to a file.
    104  * @param outputDir Output dir.
    105  * @param inputFilename The skp file that was read.
    106  * @param renderer The object responsible to write the pdf file.
    107  * @param page -1 means there is only one page (0), and render in a file without page extension
    108  */
    109 
    110 #ifdef PDF_TRACE_DIFF_IN_PNG
    111 extern "C" SkBitmap* gDumpBitmap;
    112 extern "C" SkCanvas* gDumpCanvas;
    113 #endif
    114 
    115 static bool render_page(const SkString& outputDir,
    116                         const SkString& inputFilename,
    117                         const SkPdfRenderer& renderer,
    118                         int page) {
    119     SkRect rect = renderer.MediaBox(page < 0 ? 0 :page);
    120 
    121     // Exercise all pdf codepaths as in normal rendering, but no actual bits are changed.
    122     if (!FLAGS_config.isEmpty() && strcmp(FLAGS_config[0], "nul") == 0) {
    123         SkBitmap bitmap;
    124         SkAutoTUnref<SkBaseDevice> device(SkNEW_ARGS(SkBitmapDevice, (bitmap)));
    125         SkNulCanvas canvas(device);
    126         renderer.renderPage(page < 0 ? 0 : page, &canvas, rect);
    127     } else {
    128         // 8888
    129         SkRect rect = renderer.MediaBox(page < 0 ? 0 :page);
    130 
    131         SkBitmap bitmap;
    132         SkScalar width = SkScalarMul(rect.width(),  SkDoubleToScalar(FLAGS_DPI / 72.0));
    133         SkScalar height = SkScalarMul(rect.height(),  SkDoubleToScalar(FLAGS_DPI / 72.0));
    134 
    135         rect = SkRect::MakeWH(width, height);
    136 
    137         SkColor background = FLAGS_transparentBackground ? SK_ColorTRANSPARENT : SK_ColorWHITE;
    138 
    139 #ifdef PDF_DEBUG_3X
    140         setup_bitmap(&bitmap, 3 * (int)SkScalarToDouble(width), 3 * (int)SkScalarToDouble(height),
    141                      background);
    142 #else
    143         setup_bitmap(&bitmap, (int)SkScalarToDouble(width), (int)SkScalarToDouble(height),
    144                      background);
    145 #endif
    146         SkAutoTUnref<SkBaseDevice> device;
    147         if (strcmp(FLAGS_config[0], "8888") == 0) {
    148             device.reset(SkNEW_ARGS(SkBitmapDevice, (bitmap)));
    149         } else {
    150             SkDebugf("unknown --config: %s\n", FLAGS_config[0]);
    151             return false;
    152         }
    153         SkCanvas canvas(device);
    154 
    155 #ifdef PDF_TRACE_DIFF_IN_PNG
    156         gDumpBitmap = &bitmap;
    157         gDumpCanvas = &canvas;
    158 #endif
    159         renderer.renderPage(page < 0 ? 0 : page, &canvas, rect);
    160 
    161         SkString outputPath;
    162         if (!make_output_filepath(&outputPath, outputDir, inputFilename, page)) {
    163             return false;
    164         }
    165         SkImageEncoder::EncodeFile(outputPath.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100);
    166 
    167         if (FLAGS_showMemoryUsage) {
    168             SkDebugf("Memory usage after page %i rendered: %u\n",
    169                      page < 0 ? 0 : page, (unsigned int)renderer.bytesUsed());
    170         }
    171     }
    172     return true;
    173 }
    174 
    175 /** Reads an skp file, renders it to pdf and writes the output to a pdf file
    176  * @param inputPath The skp file to be read.
    177  * @param outputDir Output dir.
    178  */
    179 static bool process_pdf(const SkString& inputPath, const SkString& outputDir) {
    180     SkDebugf("Loading PDF:  %s\n", inputPath.c_str());
    181 
    182     SkString inputFilename = SkOSPath::Basename(inputPath.c_str());
    183 
    184     SkAutoTDelete<SkPdfRenderer> renderer(SkPdfRenderer::CreateFromFile(inputPath.c_str()));
    185     if (NULL == renderer.get()) {
    186         SkDebugf("Failure loading file %s\n", inputPath.c_str());
    187         return false;
    188     }
    189 
    190     if (FLAGS_showMemoryUsage) {
    191         SkDebugf("Memory usage after load: %u\n", (unsigned int) renderer->bytesUsed());
    192     }
    193 
    194     // TODO(edisonn): bench timers
    195     if (FLAGS_benchLoad > 0) {
    196         for (int i = 0 ; i < FLAGS_benchLoad; i++) {
    197             SkAutoTDelete<SkPdfRenderer> benchRenderer(
    198                     SkPdfRenderer::CreateFromFile(inputPath.c_str()));
    199             if (NULL == benchRenderer.get()) {
    200                 SkDebugf("Failed to load on %ith attempt\n", i);
    201             } else if (FLAGS_showMemoryUsage) {
    202                 SkDebugf("Memory usage after load %i number : %u\n", i,
    203                          (unsigned int) benchRenderer->bytesUsed());
    204             }
    205         }
    206     }
    207 
    208     if (!renderer->pages()) {
    209         // This should never happen, since CreateFromFile will return NULL if there are no pages.
    210         SkASSERT(false);
    211         SkDebugf("ERROR: Empty PDF Document %s\n", inputPath.c_str());
    212         return false;
    213     }
    214 
    215     bool success = true;
    216     for (int i = 0; i < FLAGS_benchRender + 1; i++) {
    217         // TODO(edisonn) if (i == 1) start timer
    218         if (strcmp(FLAGS_pages[0], "all") == 0) {
    219             for (int pn = 0; pn < renderer->pages(); ++pn) {
    220                 success &= render_page(outputDir, inputFilename, *renderer,
    221                         FLAGS_noExtensionForOnePagePdf && renderer->pages() == 1 ? -1 : pn);
    222             }
    223         } else if (strcmp(FLAGS_pages[0], "reverse") == 0) {
    224             for (int pn = renderer->pages() - 1; pn >= 0; --pn) {
    225                 success &= render_page(outputDir, inputFilename, *renderer,
    226                         FLAGS_noExtensionForOnePagePdf && renderer->pages() == 1 ? -1 : pn);
    227             }
    228         } else if (strcmp(FLAGS_pages[0], "first") == 0) {
    229             success &= render_page(outputDir, inputFilename, *renderer,
    230                     FLAGS_noExtensionForOnePagePdf && renderer->pages() == 1 ? -1 : 0);
    231         } else if (strcmp(FLAGS_pages[0], "last") == 0) {
    232             success &= render_page(outputDir, inputFilename, *renderer,
    233                     FLAGS_noExtensionForOnePagePdf && renderer->pages() == 1 ? -1
    234                     : renderer->pages() - 1);
    235         } else {
    236             int pn = atoi(FLAGS_pages[0]);
    237             success &= render_page(outputDir, inputFilename, *renderer,
    238                     FLAGS_noExtensionForOnePagePdf && renderer->pages() == 1 ? -1 : pn);
    239         }
    240     }
    241 
    242     if (!success) {
    243         SkDebugf("Failures for file %s\n", inputPath.c_str());
    244     }
    245 
    246     return success;
    247 }
    248 
    249 /** For each file in the directory or for the file passed in input, call
    250  * parse_pdf.
    251  * @param input A directory or an pdf file.
    252  * @param outputDir Output dir.
    253  */
    254 static int process_input(const char* input, const SkString& outputDir) {
    255     int failures = 0;
    256     if (sk_isdir(input)) {
    257         SkOSFile::Iter iter(input, PDF_FILE_EXTENSION);
    258         SkString inputFilename;
    259         while (iter.next(&inputFilename)) {
    260             SkString inputPath = SkOSPath::Join(input, inputFilename.c_str());
    261             if (!process_pdf(inputPath, outputDir)) {
    262                 ++failures;
    263             }
    264         }
    265     } else {
    266         SkString inputPath(input);
    267         if (!process_pdf(inputPath, outputDir)) {
    268             ++failures;
    269         }
    270     }
    271     return failures;
    272 }
    273 
    274 int tool_main(int argc, char** argv);
    275 int tool_main(int argc, char** argv) {
    276     SkCommandLineFlags::SetUsage("Parse and Render .pdf files (pdf viewer).");
    277     SkCommandLineFlags::Parse(argc, argv);
    278 
    279     if (FLAGS_readPath.isEmpty()) {
    280         SkDebugf(".pdf files or directories are required.\n");
    281         exit(-1);
    282     }
    283 
    284     SkString outputDir;
    285     if (FLAGS_writePath.count() == 1) {
    286         outputDir.set(FLAGS_writePath[0]);
    287     }
    288 
    289     int failures = 0;
    290     for (int i = 0; i < FLAGS_readPath.count(); i ++) {
    291         failures += process_input(FLAGS_readPath[i], outputDir);
    292     }
    293 
    294     reportPdfRenderStats();
    295 
    296     if (failures != 0) {
    297         SkDebugf("Failed to render %i PDFs.\n", failures);
    298         return 1;
    299     }
    300 
    301     return 0;
    302 }
    303 
    304 #if !defined SK_BUILD_FOR_IOS
    305 int main(int argc, char * const argv[]) {
    306     return tool_main(argc, (char**) argv);
    307 }
    308 #endif
    309