Home | History | Annotate | Download | only in tools
      1 /*
      2  * Copyright 2012 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 "SkCanvas.h"
      9 #include "SkCommandLineFlags.h"
     10 #include "SkDocument.h"
     11 #include "SkForceLinking.h"
     12 #include "SkGraphics.h"
     13 #include "SkImageEncoder.h"
     14 #include "SkOSFile.h"
     15 #include "SkPicture.h"
     16 #include "SkStream.h"
     17 #include "SkTArray.h"
     18 #include "SkTSort.h"
     19 #include "ProcStats.h"
     20 
     21 __SK_FORCE_IMAGE_DECODER_LINKING;
     22 
     23 #ifdef SK_USE_CDB
     24 #include "win_dbghelp.h"
     25 #endif
     26 
     27 /**
     28  * render_pdfs
     29  *
     30  * Given list of directories and files to use as input, expects to find .skp
     31  * files and it will convert them to .pdf files writing them in the output
     32  * directory.
     33  *
     34  * Returns zero exit code if all .skp files were converted successfully,
     35  * otherwise returns error code 1.
     36  */
     37 
     38 static const char PDF_FILE_EXTENSION[] = "pdf";
     39 static const char SKP_FILE_EXTENSION[] = "skp";
     40 
     41 
     42 DEFINE_string2(inputPaths, r, "",
     43               "A list of directories and files to use as input. "
     44               "Files are expected to have the .skp extension.");
     45 
     46 DEFINE_string2(outputDir, w, "",
     47                "Directory to write the rendered pdfs.");
     48 
     49 DEFINE_string2(match, m, "",
     50                "[~][^]substring[$] [...] of filenames to run.\n"
     51                "Multiple matches may be separated by spaces.\n"
     52                "~ causes a matching file to always be skipped\n"
     53                "^ requires the start of the file to match\n"
     54                "$ requires the end of the file to match\n"
     55                "^ and $ requires an exact match\n"
     56                "If a file does not match any list entry,\n"
     57                "it is skipped unless some list entry starts with ~");
     58 
     59 DEFINE_int32(jpegQuality, 100,
     60              "Encodes images in JPEG at quality level N, which can be in "
     61              "range 0-100).   N = -1 will disable JPEG compression. "
     62              "Default is N = 100, maximum quality.");
     63 
     64 /** 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  */
     71 static bool replace_filename_extension(SkString* path,
     72                                        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         }
     80         path->append(new_extension);
     81         return true;
     82     }
     83     return false;
     84 }
     85 
     86 // the size_t* parameter is deprecated, so we ignore it
     87 static SkData* encode_to_dct_data(size_t*, const SkBitmap& bitmap) {
     88     if (FLAGS_jpegQuality == -1) {
     89         return NULL;
     90     }
     91 
     92     SkBitmap bm = bitmap;
     93 #if defined(SK_BUILD_FOR_MAC)
     94     // Workaround bug #1043 where bitmaps with referenced pixels cause
     95     // CGImageDestinationFinalize to crash
     96     SkBitmap copy;
     97     bitmap.deepCopyTo(&copy);
     98     bm = copy;
     99 #endif
    100 
    101     return SkImageEncoder::EncodeData(
    102             bm, SkImageEncoder::kJPEG_Type, FLAGS_jpegQuality);
    103 }
    104 
    105 /** Builds the output filename. path = dir/name, and it replaces expected
    106  * .skp extension with .pdf extention.
    107  * @param path Output filename.
    108  * @param name The name of the file.
    109  * @returns false if the file did not has the expected extension.
    110  *  if false is returned, contents of path are undefined.
    111  */
    112 static bool make_output_filepath(SkString* path, const SkString& dir,
    113                                  const SkString& name) {
    114     *path = SkOSPath::Join(dir.c_str(), name.c_str());
    115     return replace_filename_extension(path,
    116                                       SKP_FILE_EXTENSION,
    117                                       PDF_FILE_EXTENSION);
    118 }
    119 
    120 namespace {
    121 // This is a write-only stream.
    122 class NullWStream : public SkWStream {
    123 public:
    124     NullWStream() : fBytesWritten(0) { }
    125     virtual bool write(const void*, size_t size) SK_OVERRIDE {
    126         fBytesWritten += size;
    127         return true;
    128     }
    129     virtual size_t bytesWritten() const SK_OVERRIDE { return fBytesWritten; }
    130     size_t fBytesWritten;
    131 };
    132 }  // namespace
    133 
    134 /** Write the output of pdf renderer to a file.
    135  * @param outputDir Output dir.
    136  * @param inputFilename The skp file that was read.
    137  * @param renderer The object responsible to write the pdf file.
    138  */
    139 static SkWStream* open_stream(const SkString& outputDir,
    140                               const SkString& inputFilename) {
    141     if (outputDir.isEmpty()) {
    142         return SkNEW(NullWStream);
    143     }
    144 
    145     SkString outputPath;
    146     if (!make_output_filepath(&outputPath, outputDir, inputFilename)) {
    147         return NULL;
    148     }
    149 
    150     SkAutoTDelete<SkFILEWStream> stream(
    151             SkNEW_ARGS(SkFILEWStream, (outputPath.c_str())));
    152     if (!stream.get() ||  !stream->isValid()) {
    153         SkDebugf("Could not write to file %s\n", outputPath.c_str());
    154         return NULL;
    155     }
    156 
    157     return stream.detach();
    158 }
    159 
    160 /**
    161  *  Given a SkPicture, write a one-page PDF document to the given
    162  *  output, using the provided encoder.
    163  */
    164 static bool pdf_to_stream(SkPicture* picture,
    165                           SkWStream* output,
    166                           SkPicture::EncodeBitmap encoder) {
    167     SkAutoTUnref<SkDocument> pdfDocument(
    168             SkDocument::CreatePDF(output, NULL, encoder));
    169     SkCanvas* canvas = pdfDocument->beginPage(picture->cullRect().width(),
    170                                               picture->cullRect().height());
    171     canvas->drawPicture(picture);
    172     canvas->flush();
    173     return pdfDocument->close();
    174 }
    175 
    176 static bool operator<(const SkString& a, const SkString& b) {
    177     return strcmp(a.c_str(), b.c_str()) < 0;
    178 }
    179 
    180 /**
    181  * @param A list of directories or a skp files.
    182  * @returns an alphabetical list of skp files.
    183  */
    184 static void process_input_files(
    185         const SkCommandLineFlags::StringArray& inputs,
    186         SkTArray<SkString>* files) {
    187     for (int i = 0; i < inputs.count(); i ++) {
    188         const char* input = inputs[i];
    189         if (sk_isdir(input)) {
    190             SkOSFile::Iter iter(input, SKP_FILE_EXTENSION);
    191             SkString inputFilename;
    192             while (iter.next(&inputFilename)) {
    193                 if (!SkCommandLineFlags::ShouldSkip(
    194                             FLAGS_match, inputFilename.c_str())) {
    195                     files->push_back(
    196                             SkOSPath::Join(input, inputFilename.c_str()));
    197                 }
    198             }
    199         } else {
    200             if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, input)) {
    201                 files->push_back(SkString(input));
    202             }
    203         }
    204     }
    205     if (files->count() > 0) {
    206         SkTQSort<SkString>(files->begin(), files->end() - 1);
    207     }
    208 }
    209 
    210 /** For each input skp file, read it, render it to pdf and write. the
    211  *  output to a pdf file
    212  */
    213 int tool_main_core(int argc, char** argv);
    214 int tool_main_core(int argc, char** argv) {
    215     SkCommandLineFlags::Parse(argc, argv);
    216 
    217     SkAutoGraphics ag;
    218 
    219     SkString outputDir;
    220     if (FLAGS_outputDir.count() > 0) {
    221         outputDir = FLAGS_outputDir[0];
    222         if (!sk_mkdir(outputDir.c_str())) {
    223             SkDebugf("Unable to mkdir '%s'\n", outputDir.c_str());
    224             return 1;
    225         }
    226     }
    227 
    228     SkTArray<SkString> files;
    229     process_input_files(FLAGS_inputPaths, &files);
    230 
    231     size_t maximumPathLength = 0;
    232     for (int i = 0; i < files.count(); i ++) {
    233         SkString basename = SkOSPath::Basename(files[i].c_str());
    234         maximumPathLength = SkTMax(maximumPathLength, basename.size());
    235     }
    236 
    237     int failures = 0;
    238     for (int i = 0; i < files.count(); i ++) {
    239         SkString basename = SkOSPath::Basename(files[i].c_str());
    240 
    241         SkFILEStream inputStream;
    242         inputStream.setPath(files[i].c_str());
    243         if (!inputStream.isValid()) {
    244             SkDebugf("Could not open file %s\n", files[i].c_str());
    245             ++failures;
    246             continue;
    247         }
    248 
    249         SkAutoTUnref<SkPicture> picture(
    250                 SkPicture::CreateFromStream(&inputStream));
    251         if (NULL == picture.get()) {
    252             SkDebugf("Could not read an SkPicture from %s\n",
    253                      files[i].c_str());
    254             ++failures;
    255             continue;
    256         }
    257         SkDebugf("[%f,%f,%f,%f] %-*s",
    258             picture->cullRect().fLeft, picture->cullRect().fTop,
    259             picture->cullRect().fRight, picture->cullRect().fBottom,
    260             maximumPathLength, basename.c_str());
    261 
    262         SkAutoTDelete<SkWStream> stream(open_stream(outputDir, files[i]));
    263         if (!stream.get()) {
    264             ++failures;
    265             continue;
    266         }
    267         if (!pdf_to_stream(picture, stream.get(), encode_to_dct_data)) {
    268             SkDebugf("Error in PDF Serialization.");
    269             ++failures;
    270         }
    271 
    272         int max_rss_mb = sk_tools::getMaxResidentSetSizeMB();
    273         if (max_rss_mb >= 0) {
    274             SkDebugf(" %4dM peak rss", max_rss_mb);
    275         }
    276 
    277         SkDebugf("\n");
    278     }
    279     if (failures != 0) {
    280         SkDebugf("Failed to render %i of %i PDFs.\n", failures, files.count());
    281         return 1;
    282     }
    283 
    284     return 0;
    285 }
    286 
    287 int tool_main(int argc, char** argv);
    288 int tool_main(int argc, char** argv) {
    289 #ifdef SK_USE_CDB
    290     setUpDebuggingFromArgs(argv[0]);
    291     __try {
    292 #endif
    293       return tool_main_core(argc, argv);
    294 #ifdef SK_USE_CDB
    295     }
    296     __except(GenerateDumpAndPrintCallstack(GetExceptionInformation()))
    297     {
    298         return -1;
    299     }
    300 #endif
    301     return 0;
    302 }
    303 #if !defined SK_BUILD_FOR_IOS
    304 int main(int argc, char * const argv[]) {
    305     return tool_main(argc, (char**) argv);
    306 }
    307 #endif
    308