diff options
Diffstat (limited to 'samples/pdfium_test.cc')
-rw-r--r-- | samples/pdfium_test.cc | 850 |
1 files changed, 631 insertions, 219 deletions
diff --git a/samples/pdfium_test.cc b/samples/pdfium_test.cc index 2693a5627..282c539b2 100644 --- a/samples/pdfium_test.cc +++ b/samples/pdfium_test.cc @@ -3,6 +3,8 @@ // found in the LICENSE file. #include <locale.h> +#include <stddef.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -30,9 +32,10 @@ #include "public/fpdf_structtree.h" #include "public/fpdf_text.h" #include "public/fpdfview.h" -#include "samples/pdfium_test_dump_helper.h" -#include "samples/pdfium_test_event_helper.h" -#include "samples/pdfium_test_write_helper.h" +#include "samples/helpers/dump.h" +#include "samples/helpers/event.h" +#include "samples/helpers/page_renderer.h" +#include "samples/helpers/write.h" #include "testing/command_line_helpers.h" #include "testing/font_renamer.h" #include "testing/fx_string_testhelpers.h" @@ -41,11 +44,16 @@ #include "testing/utils/hash.h" #include "testing/utils/path_service.h" #include "third_party/abseil-cpp/absl/types/optional.h" +#include "third_party/base/check_op.h" #ifdef _WIN32 #include <crtdbg.h> #include <errhandlingapi.h> #include <io.h> +#include <wingdi.h> + +#include "samples/helpers/win32/com_factory.h" +#include "third_party/base/win/scoped_select_object.h" #else #include <unistd.h> #endif // _WIN32 @@ -54,15 +62,25 @@ #include <valgrind/callgrind.h> #endif // ENABLE_CALLGRIND +#if defined(PDF_USE_PARTITION_ALLOC) +#include "testing/allocator_shim_config.h" +#endif + #ifdef PDF_ENABLE_SKIA #include "third_party/skia/include/core/SkCanvas.h" // nogncheck #include "third_party/skia/include/core/SkColor.h" // nogncheck +#include "third_party/skia/include/core/SkDocument.h" // nogncheck #include "third_party/skia/include/core/SkPicture.h" // nogncheck #include "third_party/skia/include/core/SkPictureRecorder.h" // nogncheck #include "third_party/skia/include/core/SkPixmap.h" // nogncheck #include "third_party/skia/include/core/SkRefCnt.h" // nogncheck +#include "third_party/skia/include/core/SkStream.h" // nogncheck #include "third_party/skia/include/core/SkSurface.h" // nogncheck +#ifdef _WIN32 +#include "third_party/skia/include/docs/SkXPSDocument.h" // nogncheck +#endif + #ifdef BUILD_WITH_CHROMIUM #include "samples/chromium_support/discardable_memory_allocator.h" // nogncheck #endif @@ -95,6 +113,17 @@ namespace { +enum class RendererType { + kDefault, + kAgg, +#ifdef _WIN32 + kGdi, +#endif // _WIN32 +#if defined(PDF_ENABLE_SKIA) + kSkia, +#endif // defined(PDF_ENABLE_SKIA) +}; + enum class OutputFormat { kNone, kPageInfo, @@ -112,7 +141,10 @@ enum class OutputFormat { #endif #ifdef PDF_ENABLE_SKIA kSkp, -#endif +#ifdef _WIN32 + kXps, +#endif // _WIN32 +#endif // PDF_ENABLE_SKIA }; struct Options { @@ -141,7 +173,7 @@ struct Options { bool save_thumbnails = false; bool save_thumbnails_decoded = false; bool save_thumbnails_raw = false; - absl::optional<FPDF_RENDERER_TYPE> use_renderer_type; + RendererType use_renderer_type = RendererType::kDefault; #ifdef PDF_ENABLE_V8 bool disable_javascript = false; std::string js_flags; // Extra flags to pass to v8 init. @@ -486,23 +518,25 @@ bool ParseCommandLine(const std::vector<std::string>& args, options->save_thumbnails_decoded = true; } else if (cur_arg == "--save-thumbs-raw") { options->save_thumbnails_raw = true; -#if defined(_SKIA_SUPPORT_) } else if (ParseSwitchKeyValue(cur_arg, "--use-renderer=", &value)) { - if (options->use_renderer_type.has_value()) { + if (options->use_renderer_type != RendererType::kDefault) { fprintf(stderr, "Duplicate --use-renderer argument\n"); return false; } if (value == "agg") { - options->use_renderer_type = FPDF_RENDERERTYPE_AGG; + options->use_renderer_type = RendererType::kAgg; +#ifdef _WIN32 + } else if (value == "gdi") { + options->use_renderer_type = RendererType::kGdi; +#endif // _WIN32 +#if defined(PDF_ENABLE_SKIA) } else if (value == "skia") { - options->use_renderer_type = FPDF_RENDERERTYPE_SKIA; + options->use_renderer_type = RendererType::kSkia; +#endif // defined(PDF_ENABLE_SKIA) } else { - fprintf(stderr, - "Invalid --use-renderer argument, value must be one of agg or " - "skia\n"); + fprintf(stderr, "Invalid --use-renderer argument\n"); return false; } -#endif // defined(_SKIA_SUPPORT_) #ifdef PDF_ENABLE_V8 } else if (cur_arg == "--disable-javascript") { options->disable_javascript = true; @@ -558,6 +592,14 @@ bool ParseCommandLine(const std::vector<std::string>& args, return false; } options->output_format = OutputFormat::kSkp; +#ifdef _WIN32 + } else if (cur_arg == "--xps") { + if (options->output_format != OutputFormat::kNone) { + fprintf(stderr, "Duplicate or conflicting --xps argument\n"); + return false; + } + options->output_format = OutputFormat::kXps; +#endif // _WIN32 #endif // PDF_ENABLE_SKIA } else if (ParseSwitchKeyValue(cur_arg, "--font-dir=", &value)) { if (!options->font_directory.empty()) { @@ -764,71 +806,165 @@ FPDF_BOOL NeedToPauseNow(IFSDK_PAUSE* p) { return true; } -// Renderer for a single page. -class PageRenderer { +class Processor final { public: - virtual ~PageRenderer() = default; + Processor(const Options* options, const std::function<void()>* idler) + : options_(options), idler_(idler) { + DCHECK(options_); + DCHECK(idler_); + } - // Returns `true` if the rendered output exists. Must call `Finish()` first. - virtual bool HasOutput() const = 0; + const Options& options() const { return *options_; } + const std::function<void()>& idler() const { return *idler_; } - // Starts rendering the page, returning `false` on failure. - virtual bool Start() = 0; +#ifdef _WIN32 + ComFactory& com_factory() { return com_factory_; } +#endif // _WIN32 - // Continues rendering the page, returning `false` when complete. - virtual bool Continue() { return false; } + // Invokes `idler()`. + void Idle() const { idler()(); } - // Finishes rendering the page. - virtual void Finish(FPDF_FORMHANDLE form) = 0; + void ProcessPdf(const std::string& name, + const char* buf, + size_t len, + const std::string& events); - // Writes rendered output to a file, returning `false` on failure. - virtual bool Write(const std::string& name, int page_index, bool md5) = 0; + private: + const Options* options_; + const std::function<void()>* idler_; - protected: - PageRenderer(FPDF_PAGE page, int width, int height, int flags) - : page_(page), width_(width), height_(height), flags_(flags) {} +#ifdef _WIN32 + ComFactory com_factory_; +#endif // _WIN32 +}; + +class PdfProcessor final { + public: + PdfProcessor(Processor* processor, + const std::string* name, + const std::string* events, + FPDF_DOCUMENT doc, + FPDF_FORMHANDLE form, + FPDF_FORMFILLINFO_PDFiumTest* form_fill_info) + : processor_(processor), + name_(name), + events_(events), + doc_(doc), + form_(form), + form_fill_info_(form_fill_info) { + DCHECK(processor_); + DCHECK(name_); + DCHECK(events_); + DCHECK(doc_); + DCHECK(form_); + DCHECK(form_fill_info_); + } - FPDF_PAGE page() { return page_; } - int width() const { return width_; } - int height() const { return height_; } - int flags() const { return flags_; } + bool ProcessPage(int page_index); private: - FPDF_PAGE page_; - int width_; - int height_; - int flags_; + // Per processor state. + const Options& options() const { return processor_->options(); } + const std::function<void()>& idler() const { return processor_->idler(); } + +#ifdef _WIN32 + ComFactory& com_factory() { return processor_->com_factory(); } +#endif // _WIN32 + + // Per PDF state. + const std::string& name() const { return *name_; } + const std::string& events() const { return *events_; } + FPDF_DOCUMENT doc() const { return doc_; } + FPDF_FORMHANDLE form() const { return form_; } + + // Invokes `idler()`. + void Idle() const { idler()(); } + + FPDF_PAGE GetPage(int page_index) const { + return GetPageForIndex(form_fill_info_, doc_, page_index); + } + + Processor* processor_; + const std::string* name_; + const std::string* events_; + FPDF_DOCUMENT doc_; + FPDF_FORMHANDLE form_; + FPDF_FORMFILLINFO_PDFiumTest* form_fill_info_; }; // Page renderer with bitmap output. class BitmapPageRenderer : public PageRenderer { public: - // Function type that writes a bitmap to an image file. The function returns - // the name of the image file on success, or an empty name on failure. + // Function type that writes rendered output to a file, returning `false` on + // failure. // - // Intended for use with some of the `pdfium_test_write_helper.h` functions. - using BitmapWriter = std::string (*)(const char* pdf_name, - int num, - void* buffer, - int stride, - int width, - int height); + // Intended to wrap functions from `pdfium_test_write_helper.h`. + using PageWriter = std::function<bool(BitmapPageRenderer& renderer, + const std::string& name, + int page_index, + bool md5)>; + + // Wraps a `PageWriter` around a function pointer that writes the text page. + static PageWriter WrapPageWriter( + void (*text_page_writer)(FPDF_TEXTPAGE text_page, + const char* pdf_name, + int num)) { + return [text_page_writer](BitmapPageRenderer& renderer, + const std::string& name, int page_index, + bool /*md5*/) { + ScopedFPDFTextPage text_page(FPDFText_LoadPage(renderer.page())); + if (!text_page) { + return false; + } - bool HasOutput() const override { return !!bitmap_; } + text_page_writer(text_page.get(), name.c_str(), page_index); + return true; + }; + } - bool Start() override { - bool alpha = FPDFPage_HasTransparency(page()); - bitmap_.reset(FPDFBitmap_Create(/*width=*/width(), /*height=*/height(), - /*alpha=*/alpha)); - if (!bitmap_) - return false; + // Wraps a `PageWriter` around a function pointer that writes the page. + static PageWriter WrapPageWriter(void (*page_writer)(FPDF_PAGE page, + const char* pdf_name, + int num)) { + return [page_writer](BitmapPageRenderer& renderer, const std::string& name, + int page_index, bool /*md5*/) { + page_writer(renderer.page(), name.c_str(), page_index); + return true; + }; + } - FPDF_DWORD fill_color = alpha ? 0x00000000 : 0xFFFFFFFF; - FPDFBitmap_FillRect(bitmap(), /*left=*/0, /*top=*/0, /*width=*/width(), - /*height=*/height(), /*color=*/fill_color); - return true; + // Wraps a `PageWriter` around a function pointer that writes the rasterized + // bitmap to an image file. + static PageWriter WrapPageWriter( + std::string (*bitmap_writer)(const char* pdf_name, + int num, + void* buffer, + int stride, + int width, + int height)) { + return [bitmap_writer](BitmapPageRenderer& renderer, + const std::string& name, int page_index, bool md5) { + int stride = FPDFBitmap_GetStride(renderer.bitmap()); + void* buffer = FPDFBitmap_GetBuffer(renderer.bitmap()); + std::string image_file_name = bitmap_writer( + name.c_str(), page_index, buffer, /*stride=*/stride, + /*width=*/renderer.width(), /*height=*/renderer.height()); + if (image_file_name.empty()) { + return false; + } + + if (md5) { + // Write the filename and the MD5 of the buffer to stdout. + OutputMD5Hash(image_file_name.c_str(), + {static_cast<const uint8_t*>(buffer), + static_cast<size_t>(stride) * renderer.height()}); + } + return true; + }; } + bool HasOutput() const override { return !!bitmap_; } + void Finish(FPDF_FORMHANDLE form) override { FPDF_FFLDraw(form, bitmap(), page(), /*start_x=*/0, /*start_y=*/0, /*size_x=*/width(), /*size_y=*/height(), /*rotate=*/0, @@ -837,24 +973,7 @@ class BitmapPageRenderer : public PageRenderer { } bool Write(const std::string& name, int page_index, bool md5) override { - if (!writer_) - return false; - - int stride = FPDFBitmap_GetStride(bitmap()); - void* buffer = FPDFBitmap_GetBuffer(bitmap()); - std::string image_file_name = - writer_(name.c_str(), /*num=*/page_index, buffer, /*stride=*/stride, - /*width=*/width(), /*height=*/height()); - if (image_file_name.empty()) - return false; - - if (md5) { - // Write the filename and the MD5 of the buffer to stdout. - OutputMD5Hash(image_file_name.c_str(), - {static_cast<const uint8_t*>(buffer), - static_cast<size_t>(stride) * height()}); - } - return true; + return writer_ && writer_(*this, name, page_index, md5); } protected: @@ -863,17 +982,35 @@ class BitmapPageRenderer : public PageRenderer { int height, int flags, const std::function<void()>& idler, - BitmapWriter writer) + PageWriter writer) : PageRenderer(page, /*width=*/width, /*height=*/height, /*flags=*/flags), idler_(idler), - writer_(writer) {} + writer_(std::move(writer)) {} + + bool InitializeBitmap(void* first_scan) { + bool alpha = FPDFPage_HasTransparency(page()); + bitmap_.reset(FPDFBitmap_CreateEx( + /*width=*/width(), /*height=*/height(), + /*format=*/alpha ? FPDFBitmap_BGRA : FPDFBitmap_BGRx, first_scan, + /*stride=*/width() * sizeof(uint32_t))); + if (!bitmap()) { + return false; + } + + FPDF_DWORD fill_color = alpha ? 0x00000000 : 0xFFFFFFFF; + FPDFBitmap_FillRect(bitmap(), /*left=*/0, /*top=*/0, /*width=*/width(), + /*height=*/height(), /*color=*/fill_color); + return true; + } + + void ResetBitmap() { bitmap_.reset(); } void Idle() const { idler_(); } FPDF_BITMAP bitmap() { return bitmap_.get(); } private: const std::function<void()>& idler_; - BitmapWriter writer_; + PageWriter writer_; ScopedFPDFBitmap bitmap_; }; @@ -885,17 +1022,18 @@ class OneShotBitmapPageRenderer : public BitmapPageRenderer { int height, int flags, const std::function<void()>& idler, - BitmapWriter writer) + PageWriter writer) : BitmapPageRenderer(page, /*width=*/width, /*height=*/height, /*flags=*/flags, idler, - writer) {} + std::move(writer)) {} bool Start() override { - if (!BitmapPageRenderer::Start()) + if (!InitializeBitmap(/*first_scan=*/nullptr)) { return false; + } // Note, client programs probably want to use this method instead of the // progressive calls. The progressive calls are if you need to pause the @@ -915,22 +1053,23 @@ class ProgressiveBitmapPageRenderer : public BitmapPageRenderer { int height, int flags, const std::function<void()>& idler, - BitmapWriter writer, + PageWriter writer, const FPDF_COLORSCHEME* color_scheme) : BitmapPageRenderer(page, /*width=*/width, /*height=*/height, /*flags=*/flags, idler, - writer), + std::move(writer)), color_scheme_(color_scheme) { pause_.version = 1; pause_.NeedToPauseNow = &NeedToPauseNow; } bool Start() override { - if (!BitmapPageRenderer::Start()) + if (!InitializeBitmap(/*first_scan=*/nullptr)) { return false; + } if (FPDF_RenderPageBitmapWithColorScheme_Start( bitmap(), page(), /*start_x=*/0, /*start_y=*/0, /*size_x=*/width(), @@ -961,28 +1100,159 @@ class ProgressiveBitmapPageRenderer : public BitmapPageRenderer { bool to_be_continued_ = false; }; +#ifdef _WIN32 +class ScopedGdiDc final { + public: + ~ScopedGdiDc() { Reset(nullptr); } + + void Reset(HDC dc) { + if (dc_) { + [[maybe_unused]] BOOL success = DeleteDC(dc_); + DCHECK(success); + } + dc_ = dc; + } + + HDC Get() const { return dc_; } + + private: + HDC dc_ = nullptr; +}; + +class ScopedGdiObject final { + public: + ~ScopedGdiObject() { Reset(nullptr); } + + void Reset(HGDIOBJ object) { + if (object_) { + [[maybe_unused]] BOOL success = DeleteObject(object_); + DCHECK(success); + } + object_ = object; + } + + HGDIOBJ Get() const { return object_; } + + private: + HGDIOBJ object_ = nullptr; +}; + +class GdiDisplayPageRenderer : public BitmapPageRenderer { + public: + GdiDisplayPageRenderer(FPDF_PAGE page, + int width, + int height, + int flags, + const std::function<void()>& idler, + PageWriter writer) + : BitmapPageRenderer(page, + /*width=*/width, + /*height=*/height, + /*flags=*/flags, + idler, + std::move(writer)) {} + + ~GdiDisplayPageRenderer() override { + // Need to free `bitmap()` first, in case it points at `dib_` memory. + ResetBitmap(); + } + + bool Start() override { + // Create an in-memory DC compatible with the display. + dc_.Reset(CreateCompatibleDC(/*hdc=*/nullptr)); + if (!dc_.Get()) { + return false; + } + + // Create a BGRA DIB and select it into the in-memory DC. + BITMAPINFO dib_info; + memset(&dib_info, 0, sizeof(BITMAPINFO)); + dib_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + dib_info.bmiHeader.biWidth = width(); + dib_info.bmiHeader.biHeight = -height(); // top-down + dib_info.bmiHeader.biPlanes = 1; + dib_info.bmiHeader.biBitCount = 32; + dib_info.bmiHeader.biCompression = BI_RGB; + + VOID* dib_pixels; + dib_.Reset(CreateDIBSection(dc_.Get(), &dib_info, DIB_RGB_COLORS, + &dib_pixels, /*hSection=*/nullptr, + /*offset=*/0)); + if (!dib_.Get() || !InitializeBitmap(dib_pixels)) { + return false; + } + pdfium::base::win::ScopedSelectObject select_dib(dc_.Get(), dib_.Get()); + + // Render into the in-memory DC. + FPDF_RenderPage(dc_.Get(), page(), /*start_x=*/0, /*start_y=*/0, + /*size_x=*/width(), /*size_y=*/height(), /*rotate=*/0, + /*flags=*/flags()); + + return !!GdiFlush(); + } + + void Finish(FPDF_FORMHANDLE /*form*/) override { + // Note that `fpdf_formfill.h` does not support GDI. + + // The GDI backend doesn't support alpha and clears the alpha component to + // transparent, so clear the alpha component back to opaque. + const int stride = FPDFBitmap_GetStride(bitmap()); + DCHECK_EQ(width() * sizeof(uint32_t), static_cast<size_t>(stride)); + const int pixel_stride = stride / sizeof(uint32_t); + + uint32_t* scanline = + reinterpret_cast<uint32_t*>(FPDFBitmap_GetBuffer(bitmap())); + for (int row = 0; row < height(); ++row) { + for (int column = 0; column < width(); ++column) { + scanline[column] |= 0xFF000000; + } + scanline += pixel_stride; + } + } + + private: + ScopedGdiDc dc_; + ScopedGdiObject dib_; +}; +#endif // _WIN32 + #ifdef PDF_ENABLE_SKIA -class SkPicturePageRenderer : public PageRenderer { +class SkCanvasPageRenderer : public PageRenderer { + public: + bool Start() override { + FPDF_RenderPageSkia(reinterpret_cast<FPDF_SKIA_CANVAS>(canvas()), page(), + width(), height()); + return true; + } + + void Finish(FPDF_FORMHANDLE form) override { + FPDF_FFLDrawSkia(form, reinterpret_cast<FPDF_SKIA_CANVAS>(canvas()), page(), + /*start_x=*/0, /*start_y=*/0, width(), height(), + /*rotate=*/0, flags()); + } + + protected: + SkCanvasPageRenderer(FPDF_PAGE page, int width, int height, int flags) + : PageRenderer(page, width, height, flags) {} + + virtual SkCanvas* canvas() = 0; +}; + +class SkPicturePageRenderer final : public SkCanvasPageRenderer { public: SkPicturePageRenderer(FPDF_PAGE page, int width, int height, int flags) - : PageRenderer(page, - /*width=*/width, - /*height=*/height, - /*flags=*/flags) {} + : SkCanvasPageRenderer(page, width, height, flags) {} bool HasOutput() const override { return !!picture_; } bool Start() override { - recorder_.reset(reinterpret_cast<SkPictureRecorder*>( - FPDF_RenderPageSkp(page(), /*size_x=*/width(), /*size_y=*/height()))); - return !!recorder_; + recorder_ = std::make_unique<SkPictureRecorder>(); + recorder_->beginRecording(width(), height()); + return SkCanvasPageRenderer::Start(); } void Finish(FPDF_FORMHANDLE form) override { - FPDF_FFLRecord(form, reinterpret_cast<FPDF_RECORDER>(recorder_.get()), - page(), /*start_x=*/0, /*start_y=*/0, /*size_x=*/width(), - /*size_y=*/height(), /*rotate=*/0, /*flags=*/0); - + SkCanvasPageRenderer::Finish(form); picture_ = recorder_->finishRecordingAsPicture(); recorder_.reset(); } @@ -994,8 +1264,8 @@ class SkPicturePageRenderer : public PageRenderer { if (md5) { // Play back the `SkPicture` so we can take a hash of the result. - sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul( - /*width=*/width(), /*height=*/height()); + sk_sp<SkSurface> surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul( + /*width=*/width(), /*height=*/height())); if (!surface) return false; @@ -1015,92 +1285,216 @@ class SkPicturePageRenderer : public PageRenderer { return true; } + protected: + SkCanvas* canvas() override { return recorder_->getRecordingCanvas(); } + private: std::unique_ptr<SkPictureRecorder> recorder_; sk_sp<SkPicture> picture_; }; + +class SkDocumentPageRenderer final : public SkCanvasPageRenderer { + public: + SkDocumentPageRenderer(std::unique_ptr<SkWStream> stream, + sk_sp<SkDocument> document, + FPDF_PAGE page, + int width, + int height, + int flags) + : SkCanvasPageRenderer(page, width, height, flags), + stream_(std::move(stream)), + document_(std::move(document)) { + DCHECK(stream_); + DCHECK(document_); + } + + bool HasOutput() const override { return has_output_; } + + bool Start() override { + if (!document_) { + return false; + } + + DCHECK(!canvas_); + canvas_ = document_->beginPage(width(), height()); + if (!canvas_) { + return false; + } + + return SkCanvasPageRenderer::Start(); + } + + void Finish(FPDF_FORMHANDLE form) override { + SkCanvasPageRenderer::Finish(form); + + DCHECK(canvas_); + canvas_ = nullptr; + document_->endPage(); + + has_output_ = true; + } + + bool Write(const std::string& /*name*/, + int /*page_index*/, + bool /*md5*/) override { + bool success = HasOutput(); + if (success) { + document_->close(); + } else { + document_->abort(); + } + + document_.reset(); + stream_.reset(); + return success; + } + + protected: + SkCanvas* canvas() override { return canvas_; } + + private: + std::unique_ptr<SkWStream> stream_; + sk_sp<SkDocument> document_; + + SkCanvas* canvas_ = nullptr; + bool has_output_ = false; +}; #endif // PDF_ENABLE_SKIA -bool ProcessPage(const std::string& name, - FPDF_DOCUMENT doc, - FPDF_FORMHANDLE form, - FPDF_FORMFILLINFO_PDFiumTest* form_fill_info, - const int page_index, - const Options& options, - const std::string& events, - const std::function<void()>& idler) { - FPDF_PAGE page = GetPageForIndex(form_fill_info, doc, page_index); - if (!page) +bool PdfProcessor::ProcessPage(const int page_index) { + FPDF_PAGE page = GetPage(page_index); + if (!page) { return false; - if (options.send_events) - SendPageEvents(form, page, events, idler); - if (options.save_images) - WriteImages(page, name.c_str(), page_index); - if (options.save_rendered_images) - WriteRenderedImages(doc, page, name.c_str(), page_index); - if (options.save_thumbnails) - WriteThumbnail(page, name.c_str(), page_index); - if (options.save_thumbnails_decoded) - WriteDecodedThumbnailStream(page, name.c_str(), page_index); - if (options.save_thumbnails_raw) - WriteRawThumbnailStream(page, name.c_str(), page_index); - if (options.output_format == OutputFormat::kPageInfo) { + } + + if (options().send_events) { + SendPageEvents(form(), page, events(), idler()); + } + if (options().save_images) { + WriteImages(page, name().c_str(), page_index); + } + if (options().save_rendered_images) { + WriteRenderedImages(doc(), page, name().c_str(), page_index); + } + if (options().save_thumbnails) { + WriteThumbnail(page, name().c_str(), page_index); + } + if (options().save_thumbnails_decoded) { + WriteDecodedThumbnailStream(page, name().c_str(), page_index); + } + if (options().save_thumbnails_raw) { + WriteRawThumbnailStream(page, name().c_str(), page_index); + } + if (options().output_format == OutputFormat::kPageInfo) { DumpPageInfo(page, page_index); return true; } - if (options.output_format == OutputFormat::kStructure) { + if (options().output_format == OutputFormat::kStructure) { DumpPageStructure(page, page_index); return true; } ScopedFPDFTextPage text_page(FPDFText_LoadPage(page)); double scale = 1.0; - if (!options.scale_factor_as_string.empty()) - std::stringstream(options.scale_factor_as_string) >> scale; + if (!options().scale_factor_as_string.empty()) { + std::stringstream(options().scale_factor_as_string) >> scale; + } int width = static_cast<int>(FPDF_GetPageWidthF(page) * scale); int height = static_cast<int>(FPDF_GetPageHeightF(page) * scale); - int flags = PageRenderFlagsFromOptions(options); + int flags = PageRenderFlagsFromOptions(options()); std::unique_ptr<PageRenderer> renderer; -#ifdef PDF_ENABLE_SKIA - if (options.output_format == OutputFormat::kSkp) { - renderer = std::make_unique<SkPicturePageRenderer>( - page, /*width=*/width, /*height=*/height, /*flags=*/flags); - } else { -#else - { -#endif // PDF_ENABLE_SKIA - BitmapPageRenderer::BitmapWriter writer; - switch (options.output_format) { + BitmapPageRenderer::PageWriter writer; + switch (options().output_format) { + case OutputFormat::kText: + writer = BitmapPageRenderer::WrapPageWriter(WriteText); + break; + + case OutputFormat::kAnnot: + writer = BitmapPageRenderer::WrapPageWriter(WriteAnnot); + break; + + case OutputFormat::kPpm: + writer = BitmapPageRenderer::WrapPageWriter(WritePpm); + break; + + case OutputFormat::kPng: + writer = BitmapPageRenderer::WrapPageWriter(WritePng); + break; + #ifdef _WIN32 - case OutputFormat::kBmp: - writer = WriteBmp; - break; + case OutputFormat::kBmp: + writer = BitmapPageRenderer::WrapPageWriter(WriteBmp); + break; + + case OutputFormat::kEmf: + // TODO(crbug.com/pdfium/2054): Render directly to DC. + writer = BitmapPageRenderer::WrapPageWriter(WriteEmf); + break; + + case OutputFormat::kPs2: + case OutputFormat::kPs3: + // TODO(crbug.com/pdfium/2054): Render directly to DC. + writer = BitmapPageRenderer::WrapPageWriter(WritePS); + break; #endif // _WIN32 - case OutputFormat::kPng: - writer = WritePng; +#ifdef PDF_ENABLE_SKIA + case OutputFormat::kSkp: + renderer = std::make_unique<SkPicturePageRenderer>( + page, /*width=*/width, /*height=*/height, /*flags=*/flags); + break; + +#ifdef _WIN32 + case OutputFormat::kXps: { + IXpsOMObjectFactory* xps_factory = com_factory().GetXpsOMObjectFactory(); + if (!xps_factory) { break; + } - case OutputFormat::kPpm: - writer = WritePpm; + std::unique_ptr<SkWStream> stream = + WriteToSkWStream(name(), page_index, "xps"); + if (!stream) { break; + } - default: - // Other formats won't write the output to a file, but still rasterize. - writer = nullptr; + sk_sp<SkDocument> document = + SkXPS::MakeDocument(stream.get(), xps_factory); + if (!document) { break; + } + + renderer = std::make_unique<SkDocumentPageRenderer>( + std::move(stream), std::move(document), page, width, height, flags); + break; } +#endif // _WIN32 +#endif // PDF_ENABLE_SKIA + + default: + // Other formats won't write the output to a file, but still rasterize. + break; + } + +#ifdef _WIN32 + if (!renderer && options().use_renderer_type == RendererType::kGdi) { + renderer = std::make_unique<GdiDisplayPageRenderer>( + page, /*width=*/width, /*height=*/height, /*flags=*/flags, idler(), + std::move(writer)); + } +#endif // _WIN32 - if (options.render_oneshot) { + if (!renderer) { + // Use a rasterizing page renderer by default. + if (options().render_oneshot) { renderer = std::make_unique<OneShotBitmapPageRenderer>( - page, /*width=*/width, /*height=*/height, /*flags=*/flags, idler, - writer); + page, /*width=*/width, /*height=*/height, /*flags=*/flags, idler(), + std::move(writer)); } else { // Client programs will be setting these values when rendering. // This is a sample color scheme with distinct colors. - // Used only when `options.forced_color` is true. + // Used only when `options().forced_color` is true. FPDF_COLORSCHEME color_scheme; color_scheme.path_fill_color = 0xFFFF0000; color_scheme.path_stroke_color = 0xFF00FF00; @@ -1108,59 +1502,33 @@ bool ProcessPage(const std::string& name, color_scheme.text_stroke_color = 0xFF00FFFF; renderer = std::make_unique<ProgressiveBitmapPageRenderer>( - page, /*width=*/width, /*height=*/height, /*flags=*/flags, idler, - writer, options.forced_color ? &color_scheme : nullptr); + page, /*width=*/width, /*height=*/height, /*flags=*/flags, idler(), + std::move(writer), options().forced_color ? &color_scheme : nullptr); } } if (renderer->Start()) { while (renderer->Continue()) continue; - renderer->Finish(form); - - switch (options.output_format) { -#ifdef _WIN32 - case OutputFormat::kEmf: - WriteEmf(page, name.c_str(), page_index); - break; - - case OutputFormat::kPs2: - case OutputFormat::kPs3: - WritePS(page, name.c_str(), page_index); - break; -#endif // _WIN32 - - case OutputFormat::kText: - WriteText(text_page.get(), name.c_str(), page_index); - break; - - case OutputFormat::kAnnot: - WriteAnnot(page, name.c_str(), page_index); - break; - - default: - renderer->Write(name, page_index, /*md5=*/options.md5); - break; - } + renderer->Finish(form()); + renderer->Write(name(), page_index, /*md5=*/options().md5); } else { fprintf(stderr, "Page was too large to be rendered.\n"); } - FORM_DoPageAAction(page, form, FPDFPAGE_AACTION_CLOSE); - idler(); + FORM_DoPageAAction(page, form(), FPDFPAGE_AACTION_CLOSE); + Idle(); - FORM_OnBeforeClosePage(page, form); - idler(); + FORM_OnBeforeClosePage(page, form()); + Idle(); return renderer->HasOutput(); } -void ProcessPdf(const std::string& name, - const char* buf, - size_t len, - const Options& options, - const std::string& events, - const std::function<void()>& idler) { +void Processor::ProcessPdf(const std::string& name, + const char* buf, + size_t len, + const std::string& events) { TestLoader loader({buf, len}); FPDF_FILEACCESS file_access = {}; @@ -1183,9 +1551,9 @@ void ProcessPdf(const std::string& name, ScopedFPDFDocument doc; const char* password = - options.password.empty() ? nullptr : options.password.c_str(); + options().password.empty() ? nullptr : options().password.c_str(); bool is_linearized = false; - if (options.use_load_mem_document) { + if (options().use_load_mem_document) { doc.reset(FPDF_LoadMemDocument(buf, len, password)); } else { if (FPDFAvail_IsLinearized(pdf_avail.get()) == PDF_LINEARIZED) { @@ -1224,11 +1592,13 @@ void ProcessPdf(const std::string& name, (void)FPDF_GetDocPermissions(doc.get()); - if (options.show_metadata) + if (options().show_metadata) { DumpMetaData(doc.get()); + } - if (options.save_attachments) + if (options().save_attachments) { WriteAttachments(doc.get(), name); + } #ifdef PDF_ENABLE_V8 IPDF_JSPLATFORM platform_callbacks = {}; @@ -1248,7 +1618,7 @@ void ProcessPdf(const std::string& name, #ifdef PDF_ENABLE_XFA form_callbacks.version = 2; form_callbacks.xfa_disabled = - options.disable_xfa || options.disable_javascript; + options().disable_xfa || options().disable_javascript; form_callbacks.FFI_PopupMenu = ExamplePopupMenu; #else // PDF_ENABLE_XFA form_callbacks.version = 1; @@ -1257,8 +1627,9 @@ void ProcessPdf(const std::string& name, form_callbacks.FFI_GetPage = GetPageForIndex; #ifdef PDF_ENABLE_V8 - if (!options.disable_javascript) + if (!options().disable_javascript) { form_callbacks.m_pJsPlatform = &platform_callbacks; + } #endif // PDF_ENABLE_V8 ScopedFPDFFormHandle form( @@ -1266,7 +1637,7 @@ void ProcessPdf(const std::string& name, form_callbacks.form_handle = form.get(); #ifdef PDF_ENABLE_XFA - if (!options.disable_xfa && !options.disable_javascript) { + if (!options().disable_xfa && !options().disable_javascript) { int doc_type = FPDF_GetFormType(doc.get()); if (doc_type == FORMTYPE_XFA_FULL || doc_type == FORMTYPE_XFA_FOREGROUND) { if (!FPDF_LoadXFA(doc.get())) @@ -1281,19 +1652,22 @@ void ProcessPdf(const std::string& name, FORM_DoDocumentOpenAction(form.get()); #if _WIN32 - if (options.output_format == OutputFormat::kPs2) + if (options().output_format == OutputFormat::kPs2) { FPDF_SetPrintMode(FPDF_PRINTMODE_POSTSCRIPT2); - else if (options.output_format == OutputFormat::kPs3) + } else if (options().output_format == OutputFormat::kPs3) { FPDF_SetPrintMode(FPDF_PRINTMODE_POSTSCRIPT3); - else if (options.output_format == OutputFormat::kPs3Type42) + } else if (options().output_format == OutputFormat::kPs3Type42) { FPDF_SetPrintMode(FPDF_PRINTMODE_POSTSCRIPT3_TYPE42); + } #endif int page_count = FPDF_GetPageCount(doc.get()); int processed_pages = 0; int bad_pages = 0; - int first_page = options.pages ? options.first_page : 0; - int last_page = options.pages ? options.last_page + 1 : page_count; + int first_page = options().pages ? options().first_page : 0; + int last_page = options().pages ? options().last_page + 1 : page_count; + PdfProcessor pdf_processor(this, &name, &events, doc.get(), form.get(), + &form_callbacks); for (int i = first_page; i < last_page; ++i) { if (is_linearized) { int avail_status = PDF_DATA_NOTAVAIL; @@ -1306,17 +1680,16 @@ void ProcessPdf(const std::string& name, return; } } - if (ProcessPage(name, doc.get(), form.get(), &form_callbacks, i, options, - events, idler)) { + if (pdf_processor.ProcessPage(i)) { ++processed_pages; } else { ++bad_pages; } - idler(); + Idle(); } FORM_DoDocumentAAction(form.get(), FPDFDOC_AACTION_WC); - idler(); + Idle(); fprintf(stderr, "Processed %d pages.\n", processed_pages); if (bad_pages) @@ -1346,6 +1719,9 @@ void ShowConfig() { #ifdef PDF_ENABLE_SKIA append_config("SKIA"); #endif +#ifdef _WIN32 + append_config("GDI"); +#endif printf("%s\n", config.c_str()); } @@ -1385,9 +1761,21 @@ constexpr char kUsageString[] = "<pdf-name>.thumbnail.decoded.<page-number>.png\n" " --save-thumbs-raw - write page thumbnails' raw stream data" "<pdf-name>.thumbnail.raw.<page-number>.png\n" -#if defined(_SKIA_SUPPORT_) + +#if defined(PDF_ENABLE_SKIA) +#ifdef _WIN32 + " --use-renderer - renderer to use, one of [agg | gdi | skia]\n" +#else " --use-renderer - renderer to use, one of [agg | skia]\n" -#endif +#endif // _WIN32 +#else +#ifdef _WIN32 + " --use-renderer - renderer to use, one of [agg | gdi]\n" +#else + " --use-renderer - renderer to use, one of [agg]\n" +#endif // _WIN32 +#endif // defined(PDF_ENABLE_SKIA) + #ifdef PDF_ENABLE_V8 " --disable-javascript - do not execute JS in PDF files\n" " --js-flags=<flags> - additional flags to pass to V8\n" @@ -1424,7 +1812,10 @@ constexpr char kUsageString[] = " --annot - write annotation info <pdf-name>.<page-number>.annot.txt\n" #ifdef PDF_ENABLE_SKIA " --skp - write page images <pdf-name>.<page-number>.skp\n" -#endif +#ifdef _WIN32 + " --xps - write page images <pdf-name>.<page-number>.xps\n" +#endif // _WIN32 +#endif // PDF_ENABLE_SKIA " --md5 - write output image paths and their md5 hashes to stdout.\n" " --time=<number> - Seconds since the epoch to set system time.\n" ""; @@ -1445,6 +1836,10 @@ void SetUpErrorHandling() { } // namespace int main(int argc, const char* argv[]) { +#if defined(PDF_USE_PARTITION_ALLOC) + pdfium::ConfigurePartitionAllocShimPartitionForTest(); +#endif + SetUpErrorHandling(); setlocale(LC_CTYPE, "en_US.UTF-8"); // For printf() of high-characters. @@ -1466,23 +1861,40 @@ int main(int argc, const char* argv[]) { return 1; } - const FPDF_RENDERER_TYPE renderer_type = - options.use_renderer_type.value_or(GetDefaultRendererType()); -#if defined(PDF_ENABLE_SKIA) && defined(BUILD_WITH_CHROMIUM) - if (renderer_type == FPDF_RENDERERTYPE_SKIA) { - // Needed to support Chromium's copy of Skia, which uses a - // DiscardableMemoryAllocator. - chromium_support::InitializeDiscardableMemoryAllocator(); - } -#endif - FPDF_LIBRARY_CONFIG config; config.version = 4; config.m_pUserFontPaths = nullptr; config.m_pIsolate = nullptr; config.m_v8EmbedderSlot = 0; config.m_pPlatform = nullptr; - config.m_RendererType = renderer_type; + + switch (options.use_renderer_type) { + case RendererType::kDefault: + config.m_RendererType = GetDefaultRendererType(); + break; + + case RendererType::kAgg: + config.m_RendererType = FPDF_RENDERERTYPE_AGG; + break; + +#ifdef _WIN32 + case RendererType::kGdi: + // GDI renderer uses `FPDF_RenderPage()`, rather than a renderer type. + config.m_RendererType = GetDefaultRendererType(); + break; +#endif // _WIN32 + +#if defined(PDF_ENABLE_SKIA) + case RendererType::kSkia: +#if defined(BUILD_WITH_CHROMIUM) + // Needed to support Chromium's copy of Skia, which uses a + // `DiscardableMemoryAllocator`. + chromium_support::InitializeDiscardableMemoryAllocator(); +#endif // defined(BUILD_WITH_CHROMIUM) + config.m_RendererType = FPDF_RENDERERTYPE_SKIA; + break; +#endif // defined(PDF_ENABLE_SKIA) + } std::function<void()> idler = []() {}; #ifdef PDF_ENABLE_V8 @@ -1547,6 +1959,7 @@ int main(int argc, const char* argv[]) { FSDK_SetLocaltimeFunction([](const time_t* tp) { return gmtime(tp); }); } + Processor processor(&options, &idler); for (const std::string& filename : files) { size_t file_length = 0; std::unique_ptr<char, pdfium::FreeDeleter> file_contents = @@ -1580,8 +1993,7 @@ int main(int argc, const char* argv[]) { } } - ProcessPdf(filename, file_contents.get(), file_length, options, events, - idler); + processor.ProcessPdf(filename, file_contents.get(), file_length, events); #ifdef ENABLE_CALLGRIND if (options.callgrind_delimiters) |