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 38static const char PDF_FILE_EXTENSION[] = "pdf"; 39static const char SKP_FILE_EXTENSION[] = "skp"; 40 41 42DEFINE_string2(inputPaths, r, "", 43 "A list of directories and files to use as input. " 44 "Files are expected to have the .skp extension."); 45 46DEFINE_string2(outputDir, w, "", 47 "Directory to write the rendered pdfs."); 48 49DEFINE_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 59DEFINE_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 */ 71static 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 87static 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 */ 112static 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 120namespace { 121// This is a write-only stream. 122class NullWStream : public SkWStream { 123public: 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 */ 139static 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 */ 164static 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 176static 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 */ 184static 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 */ 213int tool_main_core(int argc, char** argv); 214int 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 287int tool_main(int argc, char** argv); 288int 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 304int main(int argc, char * const argv[]) { 305 return tool_main(argc, (char**) argv); 306} 307#endif 308