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(©); 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