skia2/experimental/PdfViewer/pdf_viewer_main.cpp
scroggo@google.com 5092adc546 Separate and update PDF_DIFF_TRACE_IN_PNG
Move its functionality out of readToken() and into its own class.
Callers of the previous readToken() now call
SkPdfNativeTokenizer::readToken(), which in turn calls a function
for writing the diff to a file, if the caller requests it and
PDF_TRACE_DIFF_IN_PNG is defined.

Do not attempt to draw a diff for compatibility sections, which we
do not draw.

Use SkString to handle string manipulation.

Hide globals only used by PDF_TRACE_DIFF_IN_PNG behind that flag.

Remove hasVisualEffects, which always returns true.

Rename gLastOpKeyword to gOpCounter for clarity.

In SkPdfNativeTokenizer, set fEmpty to true when the entire stream
has been read.

Use SkBitmap::copyTo instead of manually copying an SkBitmap.

Builds on https://codereview.chromium.org/79933003/

R=mtklein@google.com

Review URL: https://codereview.chromium.org/80463005

git-svn-id: http://skia.googlecode.com/svn/trunk@12436 2bbb7eff-a529-9590-31e7-b0007b416f81
2013-12-02 20:22:31 +00:00

352 lines
13 KiB
C++

/*
* Copyright 2013 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkBitmapDevice.h"
#include "SkCanvas.h"
#include "SkCommandLineFlags.h"
#include "SkDevice.h"
#include "SkGraphics.h"
#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
#include "SkOSFile.h"
#include "SkPdfConfig.h"
#include "SkPdfRenderer.h"
#include "SkPicture.h"
#include "SkStream.h"
#include "SkTypeface.h"
#include "SkTArray.h"
#include "SkNulCanvas.h"
#if SK_SUPPORT_GPU
#include "GrContextFactory.h"
#include "GrContext.h"
#include "SkGpuDevice.h"
#endif
DEFINE_string2(readPath, r, "", "pdf files or directories of pdf files to process.");
DEFINE_string2(writePath, w, "", "Directory to write the rendered pages.");
DEFINE_bool2(noExtensionForOnePagePdf, n, false, "No page extension if only one page.");
DEFINE_bool2(showMemoryUsage, m, false, "Show Memory usage.");
DEFINE_string2(pages, p, "all", "What pages to render and how:\n"
"\tall - all pages\n"
"\treverse - all pages, in reverse order\n"
"\tfirst - first page\n"
"\tlast - last page\n"
"\tnumber - a specific page number\n"
);
DEFINE_double(DPI, 72, "DPI to be used for rendering (scale).");
DEFINE_int32(benchLoad, 0, "Load the pdf file minimally N times, without any rendering and \n"
"\tminimal parsing to ensure correctness. Default 0 (disabled).");
DEFINE_int32(benchRender, 0, "Render the pdf content N times. Default 0 (disabled)");
#if SK_SUPPORT_GPU
DEFINE_string2(config, c, "8888", "Canvas to render:\n"
"\t8888 - argb\n"
"\tgpu: use the gpu\n"
"\tnul - render in null canvas, any draw will just return.\n"
);
#else
DEFINE_string2(config, c, "8888", "Canvas to render:\n"
"\t8888 - argb\n"
"\tnul - render in null canvas, any draw will just return.\n"
);
#endif
DEFINE_bool2(transparentBackground, t, false, "Make background transparent instead of white.");
/**
* Given list of directories and files to use as input, expects to find .pdf
* files and it will convert them to .png files writing them in the same directory
* one file for each page.
*
* Returns zero exit code if all .pdf files were converted successfully,
* otherwise returns error code 1.
*/
static const char PDF_FILE_EXTENSION[] = "pdf";
static const char PNG_FILE_EXTENSION[] = "png";
/** Replaces the extension of a file.
* @param path File name whose extension will be changed.
* @param old_extension The old extension.
* @param new_extension The new extension.
* @returns false if the file did not has the expected extension.
* if false is returned, contents of path are undefined.
*/
static bool add_page_and_replace_filename_extension(SkString* path, int page,
const char old_extension[],
const char new_extension[]) {
if (path->endsWith(old_extension)) {
path->remove(path->size() - strlen(old_extension),
strlen(old_extension));
if (!path->endsWith(".")) {
return false;
}
if (page >= 0) {
path->appendf("%i.", page);
}
path->append(new_extension);
return true;
}
return false;
}
/** Builds the output filename. path = dir/name, and it replaces expected
* .skp extension with .pdf extention.
* @param path Output filename.
* @param name The name of the file.
* @returns false if the file did not has the expected extension.
* if false is returned, contents of path are undefined.
*/
static bool make_output_filepath(SkString* path, const SkString& dir,
const SkString& name,
int page) {
*path = SkOSPath::SkPathJoin(dir.c_str(), name.c_str());
return add_page_and_replace_filename_extension(path, page,
PDF_FILE_EXTENSION,
PNG_FILE_EXTENSION);
}
static void setup_bitmap(SkBitmap* bitmap, int width, int height, SkColor color) {
bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
bitmap->allocPixels();
bitmap->eraseColor(color);
}
/** Write the output of pdf renderer to a file.
* @param outputDir Output dir.
* @param inputFilename The skp file that was read.
* @param renderer The object responsible to write the pdf file.
* @param page -1 means there is only one page (0), and render in a file without page extension
*/
#ifdef PDF_TRACE_DIFF_IN_PNG
extern "C" SkBitmap* gDumpBitmap;
extern "C" SkCanvas* gDumpCanvas;
#endif
#if SK_SUPPORT_GPU
GrContextFactory gContextFactory;
#endif
static bool render_page(const SkString& outputDir,
const SkString& inputFilename,
const SkPdfRenderer& renderer,
int page) {
SkRect rect = renderer.MediaBox(page < 0 ? 0 :page);
// Exercise all pdf codepaths as in normal rendering, but no actual bits are changed.
if (!FLAGS_config.isEmpty() && strcmp(FLAGS_config[0], "nul") == 0) {
SkBitmap bitmap;
SkAutoTUnref<SkBaseDevice> device(SkNEW_ARGS(SkBitmapDevice, (bitmap)));
SkNulCanvas canvas(device);
renderer.renderPage(page < 0 ? 0 : page, &canvas, rect);
} else {
// 8888
SkRect rect = renderer.MediaBox(page < 0 ? 0 :page);
SkBitmap bitmap;
SkScalar width = SkScalarMul(rect.width(), SkDoubleToScalar(FLAGS_DPI / 72.0));
SkScalar height = SkScalarMul(rect.height(), SkDoubleToScalar(FLAGS_DPI / 72.0));
rect = SkRect::MakeWH(width, height);
SkColor background = FLAGS_transparentBackground ? SK_ColorTRANSPARENT : SK_ColorWHITE;
#ifdef PDF_DEBUG_3X
setup_bitmap(&bitmap, 3 * (int)SkScalarToDouble(width), 3 * (int)SkScalarToDouble(height),
background);
#else
setup_bitmap(&bitmap, (int)SkScalarToDouble(width), (int)SkScalarToDouble(height),
background);
#endif
SkAutoTUnref<SkBaseDevice> device;
if (strcmp(FLAGS_config[0], "8888") == 0) {
device.reset(SkNEW_ARGS(SkBitmapDevice, (bitmap)));
}
#if SK_SUPPORT_GPU
else if (strcmp(FLAGS_config[0], "gpu") == 0) {
SkAutoTUnref<GrSurface> target;
GrContext* gr = gContextFactory.get(GrContextFactory::kNative_GLContextType);
if (gr) {
// create a render target to back the device
GrTextureDesc desc;
desc.fConfig = kSkia8888_GrPixelConfig;
desc.fFlags = kRenderTarget_GrTextureFlagBit;
desc.fWidth = SkScalarCeilToInt(width);
desc.fHeight = SkScalarCeilToInt(height);
desc.fSampleCnt = 0;
target.reset(gr->createUncachedTexture(desc, NULL, 0));
}
if (NULL == target.get()) {
SkASSERT(0);
return false;
}
device.reset(SkGpuDevice::Create(target));
}
#endif
else {
SkDebugf("unknown --config: %s\n", FLAGS_config[0]);
return false;
}
SkCanvas canvas(device);
#ifdef PDF_TRACE_DIFF_IN_PNG
gDumpBitmap = &bitmap;
gDumpCanvas = &canvas;
#endif
renderer.renderPage(page < 0 ? 0 : page, &canvas, rect);
SkString outputPath;
if (!make_output_filepath(&outputPath, outputDir, inputFilename, page)) {
return false;
}
SkImageEncoder::EncodeFile(outputPath.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100);
if (FLAGS_showMemoryUsage) {
SkDebugf("Memory usage after page %i rendered: %u\n",
page < 0 ? 0 : page, (unsigned int)renderer.bytesUsed());
}
}
return true;
}
/** Reads an skp file, renders it to pdf and writes the output to a pdf file
* @param inputPath The skp file to be read.
* @param outputDir Output dir.
*/
static bool process_pdf(const SkString& inputPath, const SkString& outputDir) {
SkDebugf("Loading PDF: %s\n", inputPath.c_str());
SkString inputFilename = SkOSPath::SkBasename(inputPath.c_str());
SkAutoTDelete<SkPdfRenderer> renderer(SkPdfRenderer::CreateFromFile(inputPath.c_str()));
if (NULL == renderer.get()) {
SkDebugf("Failure loading file %s\n", inputPath.c_str());
return false;
}
if (FLAGS_showMemoryUsage) {
SkDebugf("Memory usage after load: %u\n", (unsigned int) renderer->bytesUsed());
}
// TODO(edisonn): bench timers
if (FLAGS_benchLoad > 0) {
for (int i = 0 ; i < FLAGS_benchLoad; i++) {
SkAutoTDelete<SkPdfRenderer> benchRenderer(
SkPdfRenderer::CreateFromFile(inputPath.c_str()));
if (NULL == benchRenderer.get()) {
SkDebugf("Failed to load on %ith attempt\n", i);
} else if (FLAGS_showMemoryUsage) {
SkDebugf("Memory usage after load %i number : %u\n", i,
(unsigned int) benchRenderer->bytesUsed());
}
}
}
if (!renderer->pages()) {
// This should never happen, since CreateFromFile will return NULL if there are no pages.
SkASSERT(false);
SkDebugf("ERROR: Empty PDF Document %s\n", inputPath.c_str());
return false;
}
bool success = true;
for (int i = 0; i < FLAGS_benchRender + 1; i++) {
// TODO(edisonn) if (i == 1) start timer
if (strcmp(FLAGS_pages[0], "all") == 0) {
for (int pn = 0; pn < renderer->pages(); ++pn) {
success &= render_page(outputDir, inputFilename, *renderer,
FLAGS_noExtensionForOnePagePdf && renderer->pages() == 1 ? -1 : pn);
}
} else if (strcmp(FLAGS_pages[0], "reverse") == 0) {
for (int pn = renderer->pages() - 1; pn >= 0; --pn) {
success &= render_page(outputDir, inputFilename, *renderer,
FLAGS_noExtensionForOnePagePdf && renderer->pages() == 1 ? -1 : pn);
}
} else if (strcmp(FLAGS_pages[0], "first") == 0) {
success &= render_page(outputDir, inputFilename, *renderer,
FLAGS_noExtensionForOnePagePdf && renderer->pages() == 1 ? -1 : 0);
} else if (strcmp(FLAGS_pages[0], "last") == 0) {
success &= render_page(outputDir, inputFilename, *renderer,
FLAGS_noExtensionForOnePagePdf && renderer->pages() == 1 ? -1
: renderer->pages() - 1);
} else {
int pn = atoi(FLAGS_pages[0]);
success &= render_page(outputDir, inputFilename, *renderer,
FLAGS_noExtensionForOnePagePdf && renderer->pages() == 1 ? -1 : pn);
}
}
if (!success) {
SkDebugf("Failures for file %s\n", inputPath.c_str());
}
return success;
}
/** For each file in the directory or for the file passed in input, call
* parse_pdf.
* @param input A directory or an pdf file.
* @param outputDir Output dir.
*/
static int process_input(const char* input, const SkString& outputDir) {
int failures = 0;
if (sk_isdir(input)) {
SkOSFile::Iter iter(input, PDF_FILE_EXTENSION);
SkString inputFilename;
while (iter.next(&inputFilename)) {
SkString inputPath = SkOSPath::SkPathJoin(input, inputFilename.c_str());
if (!process_pdf(inputPath, outputDir)) {
++failures;
}
}
} else {
SkString inputPath(input);
if (!process_pdf(inputPath, outputDir)) {
++failures;
}
}
return failures;
}
int tool_main(int argc, char** argv);
int tool_main(int argc, char** argv) {
SkCommandLineFlags::SetUsage("Parse and Render .pdf files (pdf viewer).");
SkCommandLineFlags::Parse(argc, argv);
if (FLAGS_readPath.isEmpty()) {
SkDebugf(".pdf files or directories are required.\n");
exit(-1);
}
SkString outputDir;
if (FLAGS_writePath.count() == 1) {
outputDir.set(FLAGS_writePath[0]);
}
int failures = 0;
for (int i = 0; i < FLAGS_readPath.count(); i ++) {
failures += process_input(FLAGS_readPath[i], outputDir);
}
reportPdfRenderStats();
if (failures != 0) {
SkDebugf("Failed to render %i PDFs.\n", failures);
return 1;
}
return 0;
}
#if !defined SK_BUILD_FOR_IOS
int main(int argc, char * const argv[]) {
return tool_main(argc, (char**) argv);
}
#endif