1/* 2 * Copyright 2015 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#include "SkCanvas.h" 8#include "SkCommandLineFlags.h" 9#include "SkDocument.h" 10#include "SkForceLinking.h" 11#include "SkGraphics.h" 12#include "SkMD5.h" 13#include "SkOSFile.h" 14#include "SkPicture.h" 15#include "SkStream.h" 16#include "SkTArray.h" 17#include "SkTSort.h" 18 19#include "SkDmuxWStream.h" 20 21static const char kUsage[] = 22 "This program takes a list of Skia Picture (SKP) files and renders\n" 23 "each as a multipage PDF, then prints out the MD5 checksum of the\n" 24 "PDF file. This can be used to verify that changes to the PDF\n" 25 "backend will not change PDF output.\n"; 26 27__SK_FORCE_IMAGE_DECODER_LINKING; 28 29DEFINE_string2(inputPaths, 30 r, 31 "", 32 "A list of directories and files to use as input.\n" 33 "Files are expected to have the .skp extension."); 34 35DEFINE_string2(outputDirectoryPath, w, "", "TODO: document this"); 36 37static const char SKP_FILE_EXTENSION[] = ".skp"; 38static const char PDF_FILE_EXTENSION[] = ".pdf"; 39 40// Used by SkTQSort<SkString>() 41static bool operator<(const SkString& a, const SkString& b) { 42 return strcmp(a.c_str(), b.c_str()) < 0; 43} 44 45// Process --inputPaths defined on the command line. Return false if 46// no files found. 47static bool process_input_files(SkTArray<SkString>* files) { 48 for (int i = 0; i < FLAGS_inputPaths.count(); i++) { 49 const char* input = FLAGS_inputPaths[i]; 50 if (sk_isdir(input)) { 51 SkOSFile::Iter iter(input, SKP_FILE_EXTENSION); 52 SkString inputFilename; 53 while (iter.next(&inputFilename)) { 54 files->push_back(SkOSPath::Join(input, inputFilename.c_str())); 55 } 56 } else { 57 if (SkStrEndsWith(input, SKP_FILE_EXTENSION)) { 58 if (sk_exists(input)) { 59 files->push_back(SkString(input)); 60 } else { 61 SkDebugf("file_does_not_exist %s\n", input); 62 } 63 } else { 64 SkDebugf("skipping_file %s\n", input); 65 } 66 } 67 } 68 if (files->count() > 0) { 69 SkTQSort<SkString>(files->begin(), files->end() - 1); 70 return true; 71 } 72 return false; 73} 74 75// Print the given SkPicture to a PDF, breaking on 8.5x11 pages. 76static void picture_to_pdf(const SkPicture& picture, SkWStream* out) { 77 SkAutoTUnref<SkDocument> pdfDocument(SkDocument::CreatePDF(out)); 78 79 int width = picture.cullRect().width(); 80 int height = picture.cullRect().height(); 81 82 const int kLetterWidth = 612; // 8.5 * 72 83 const int kLetterHeight = 792; // 11 * 72 84 SkRect letterRect = SkRect::MakeWH(SkIntToScalar(kLetterWidth), 85 SkIntToScalar(kLetterHeight)); 86 87 int xPages = ((width - 1) / kLetterWidth) + 1; 88 int yPages = ((height - 1) / kLetterHeight) + 1; 89 90 for (int y = 0; y < yPages; ++y) { 91 for (int x = 0; x < xPages; ++x) { 92 int w = SkTMin(kLetterWidth, width - (x * kLetterWidth)); 93 int h = SkTMin(kLetterHeight, height - (y * kLetterHeight)); 94 SkCanvas* canvas = pdfDocument->beginPage(w, h); 95 canvas->clipRect(letterRect); 96 canvas->translate(SkIntToScalar(-kLetterWidth * x), 97 SkIntToScalar(-kLetterHeight * y)); 98 canvas->drawPicture(&picture); 99 canvas->flush(); 100 pdfDocument->endPage(); 101 } 102 } 103 pdfDocument->close(); 104 out->flush(); 105} 106 107static bool skp_to_pdf_md5(SkStream* input, SkMD5::Digest* digest) { 108 SkAutoTUnref<SkPicture> picture(SkPicture::CreateFromStream(input)); 109 if (NULL == picture.get()) { 110 return false; 111 } 112 113 SkMD5 checksumWStream; 114 picture_to_pdf(*picture, &checksumWStream); 115 checksumWStream.finish(*digest); 116 return true; 117} 118 119static bool skp_to_pdf_and_md5(SkStream* input, 120 const char* path, 121 SkMD5::Digest* digest) { 122 SkAutoTUnref<SkPicture> picture(SkPicture::CreateFromStream(input)); 123 if (NULL == picture.get()) { 124 return false; 125 } 126 127 SkMD5 checksumWStream; 128 SkFILEWStream fileWStream(path); 129 SkWStream* wStreamArray[] = {&checksumWStream, &fileWStream}; 130 SkDmuxWStream dmuxWStream(wStreamArray, SK_ARRAY_COUNT(wStreamArray)); 131 132 picture_to_pdf(*picture, &dmuxWStream); 133 checksumWStream.finish(*digest); 134 return true; 135} 136 137SkString digest_to_hex(const SkMD5::Digest& digest) { 138 static const char kHex[] = "0123456789ABCDEF"; 139 SkString string(2 * sizeof(digest.data)); 140 char* p = string.writable_str(); 141 for (size_t i = 0; i < sizeof(digest.data); ++i) { 142 uint8_t c = digest.data[i]; 143 *(p++) = kHex[c >> 4]; 144 *(p++) = kHex[c & 0xF]; 145 } 146 return string; 147} 148 149static void str_replace_ending(SkString* str, 150 const char* oldExt, 151 const char* newExt) { 152 SkASSERT(str->endsWith(oldExt)); 153 SkASSERT(str->size() >= strlen(oldExt)); 154 str->remove(str->size() - strlen(oldExt), strlen(oldExt)); 155 str->append(newExt); 156} 157 158int main(int argc, char** argv) { 159 SkCommandLineFlags::SetUsage(kUsage); 160 SkCommandLineFlags::Parse(argc, argv); 161 const char* outputDir = FLAGS_outputDirectoryPath.count() > 0 162 ? FLAGS_outputDirectoryPath[0] 163 : NULL; 164 if (outputDir) { 165 sk_mkdir(outputDir); 166 } 167 168 SkAutoGraphics ag; 169 int successCount = 0; 170 SkTArray<SkString> files; 171 if (!process_input_files(&files)) { 172 SkDebugf("You need to specify a --inputPaths option.\n"); 173 return 1; 174 } 175 for (int i = 0; i < files.count(); ++i) { 176 SkString basename = SkOSPath::Basename(files[i].c_str()); 177 SkFILEStream inputStream(files[i].c_str()); 178 if (!inputStream.isValid()) { 179 SkDebugf("could_not_open %s\n", basename.c_str()); 180 continue; 181 } 182 SkMD5::Digest digest; 183 184 if (outputDir) { 185 SkString path = SkOSPath::Join(outputDir, basename.c_str()); 186 str_replace_ending(&path, SKP_FILE_EXTENSION, PDF_FILE_EXTENSION); 187 if (!skp_to_pdf_and_md5(&inputStream, path.c_str(), &digest)) { 188 SkDebugf("invalid_skp %s\n", basename.c_str()); 189 continue; 190 } 191 } else { 192 if (!skp_to_pdf_md5(&inputStream, &digest)) { 193 SkDebugf("invalid_skp %s\n", basename.c_str()); 194 continue; 195 } 196 } 197 SkString hexDigest = digest_to_hex(digest); 198 printf("%s %s\n", hexDigest.c_str(), basename.c_str()); 199 ++successCount; 200 } 201 return successCount == files.count() ? 0 : 1; 202} 203 204