Add XPS support to pdfium_test Adds pdfium_test support for XPS output (--xps). This outputs an XPS file per page, and requires Skia and Windows. Fixed: pdfium:2059 Change-Id: Ifbb1befa7f74ae0dd03048eda9cdd8042279d79c Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/109232 Reviewed-by: Nigi <nigi@chromium.org> Commit-Queue: K. Moon <kmoon@chromium.org>
diff --git a/samples/BUILD.gn b/samples/BUILD.gn index 83b62dd..577d685 100644 --- a/samples/BUILD.gn +++ b/samples/BUILD.gn
@@ -57,6 +57,13 @@ ] configs += [ ":pdfium_samples_config" ] + if (is_win) { + sources += [ + "helpers/win32/com_factory.cc", + "helpers/win32/com_factory.h", + ] + } + if (pdf_enable_v8) { deps += [ "//v8:v8_headers",
diff --git a/samples/helpers/win32/com_factory.cc b/samples/helpers/win32/com_factory.cc new file mode 100644 index 0000000..7da80a7 --- /dev/null +++ b/samples/helpers/win32/com_factory.cc
@@ -0,0 +1,51 @@ +// Copyright 2023 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "samples/helpers/win32/com_factory.h" + +#include <combaseapi.h> +#include <objbase.h> +#include <winerror.h> +#include <xpsobjectmodel.h> + +#include "third_party/base/check_op.h" + +ComFactory::ComFactory() = default; + +ComFactory::~ComFactory() { + if (xps_om_object_factory_) { + xps_om_object_factory_->Release(); + } + + if (initialized_) { + CoUninitialize(); + } +} + +bool ComFactory::Initialize() { + if (!initialized_) { + HRESULT result = CoInitializeEx( + nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + DCHECK_NE(RPC_E_CHANGED_MODE, result); + initialized_ = SUCCEEDED(result); + } + + return initialized_; +} + +IXpsOMObjectFactory* ComFactory::GetXpsOMObjectFactory() { + if (!xps_om_object_factory_ && Initialize()) { + HRESULT result = + CoCreateInstance(__uuidof(XpsOMObjectFactory), /*pUnkOuter=*/nullptr, + CLSCTX_INPROC_SERVER, __uuidof(IXpsOMObjectFactory), + reinterpret_cast<LPVOID*>(&xps_om_object_factory_)); + if (SUCCEEDED(result)) { + DCHECK(xps_om_object_factory_); + } else { + DCHECK(!xps_om_object_factory_); + } + } + + return xps_om_object_factory_; +}
diff --git a/samples/helpers/win32/com_factory.h b/samples/helpers/win32/com_factory.h new file mode 100644 index 0000000..eabaffe --- /dev/null +++ b/samples/helpers/win32/com_factory.h
@@ -0,0 +1,25 @@ +// Copyright 2023 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SAMPLES_HELPERS_WIN32_COM_FACTORY_H_ +#define SAMPLES_HELPERS_WIN32_COM_FACTORY_H_ + +struct IXpsOMObjectFactory; + +// Factory for COM instances. +class ComFactory final { + public: + ComFactory(); + ~ComFactory(); + + IXpsOMObjectFactory* GetXpsOMObjectFactory(); + + private: + bool Initialize(); + + bool initialized_ = false; + IXpsOMObjectFactory* xps_om_object_factory_ = nullptr; +}; + +#endif // SAMPLES_HELPERS_WIN32_COM_FACTORY_H_
diff --git a/samples/helpers/write.cc b/samples/helpers/write.cc index aeeac25..13223d2 100644 --- a/samples/helpers/write.cc +++ b/samples/helpers/write.cc
@@ -595,16 +595,43 @@ #endif // _WIN32 #ifdef PDF_ENABLE_SKIA -std::string WriteSkp(const char* pdf_name, int num, const SkPicture& picture) { - std::string filename = GeneratePageOutputFilename(pdf_name, num, "skp"); +std::unique_ptr<SkWStream> WriteToSkWStream(const std::string& pdf_name, + int num, + const std::string& extension) { + std::string discarded_filename; + return WriteToSkWStream(pdf_name, num, extension, discarded_filename); +} + +std::unique_ptr<SkWStream> WriteToSkWStream(const std::string& pdf_name, + int num, + const std::string& extension, + std::string& filename) { + filename = + GeneratePageOutputFilename(pdf_name.c_str(), num, extension.c_str()); if (filename.empty()) { - return filename; + return nullptr; } - SkFILEWStream wStream(filename.c_str()); - picture.serialize(&wStream); + + auto stream = std::make_unique<SkFILEWStream>(filename.c_str()); + if (!stream->isValid()) { + return nullptr; + } + + return stream; +} + +std::string WriteSkp(const char* pdf_name, int num, const SkPicture& picture) { + std::string filename; + std::unique_ptr<SkWStream> stream = + WriteToSkWStream(pdf_name, num, "skp", filename); + if (!stream) { + return ""; + } + + picture.serialize(stream.get()); return filename; } -#endif +#endif // PDF_ENABLE_SKIA enum class ThumbnailDecodeType { kBitmap, kRawStream, kDecodedStream };
diff --git a/samples/helpers/write.h b/samples/helpers/write.h index 20212ad..b23760e 100644 --- a/samples/helpers/write.h +++ b/samples/helpers/write.h
@@ -5,13 +5,15 @@ #ifndef SAMPLES_HELPERS_WRITE_H_ #define SAMPLES_HELPERS_WRITE_H_ +#include <memory> #include <string> #include "public/fpdfview.h" #ifdef PDF_ENABLE_SKIA class SkPicture; -#endif +class SkWStream; +#endif // PDF_ENABLE_SKIA std::string WritePpm(const char* pdf_name, int num, @@ -40,6 +42,13 @@ #endif // _WIN32 #ifdef PDF_ENABLE_SKIA +std::unique_ptr<SkWStream> WriteToSkWStream(const std::string& pdf_name, + int num, + const std::string& extension); +std::unique_ptr<SkWStream> WriteToSkWStream(const std::string& pdf_name, + int num, + const std::string& extension, + std::string& filename); std::string WriteSkp(const char* pdf_name, int num, const SkPicture& picture); #endif // PDF_ENABLE_SKIA
diff --git a/samples/pdfium_test.cc b/samples/pdfium_test.cc index e724ba2..1dfc70c 100644 --- a/samples/pdfium_test.cc +++ b/samples/pdfium_test.cc
@@ -52,6 +52,7 @@ #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> @@ -64,12 +65,18 @@ #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 @@ -130,7 +137,10 @@ #endif #ifdef PDF_ENABLE_SKIA kSkp, -#endif +#ifdef _WIN32 + kXps, +#endif // _WIN32 +#endif // PDF_ENABLE_SKIA }; struct Options { @@ -578,6 +588,14 @@ 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()) { @@ -795,6 +813,10 @@ const Options& options() const { return *options_; } const std::function<void()>& idler() const { return *idler_; } +#ifdef _WIN32 + ComFactory& com_factory() { return com_factory_; } +#endif // _WIN32 + // Invokes `idler()`. void Idle() const { idler()(); } @@ -806,6 +828,10 @@ private: const Options* options_; const std::function<void()>* idler_; + +#ifdef _WIN32 + ComFactory com_factory_; +#endif // _WIN32 }; class PdfProcessor final { @@ -837,6 +863,10 @@ 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_; } @@ -1183,33 +1213,42 @@ #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_ = std::make_unique<SkPictureRecorder>(); recorder_->beginRecording(width(), height()); - - FPDF_RenderPageSkia( - reinterpret_cast<FPDF_SKIA_CANVAS>(recorder_->getRecordingCanvas()), - page(), /*size_x=*/width(), /*size_y=*/height()); - return true; + return SkCanvasPageRenderer::Start(); } void Finish(FPDF_FORMHANDLE form) override { - FPDF_FFLDrawSkia( - form, - reinterpret_cast<FPDF_SKIA_CANVAS>(recorder_->getRecordingCanvas()), - 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(); } @@ -1242,10 +1281,80 @@ 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 PdfProcessor::ProcessPage(const int page_index) { @@ -1332,6 +1441,31 @@ 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; + } + + std::unique_ptr<SkWStream> stream = + WriteToSkWStream(name(), page_index, "xps"); + if (!stream) { + break; + } + + 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: @@ -1674,7 +1808,10 @@ " --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" "";
diff --git a/skia/BUILD.gn b/skia/BUILD.gn index bcb7e07..afdd734 100644 --- a/skia/BUILD.gn +++ b/skia/BUILD.gn
@@ -174,6 +174,7 @@ sources += skia_sksl_gpu_sources sources += skia_sksl_sources sources += skia_utils_private + sources += skia_xps_sources sources += skia_codec_core sources += skia_codec_decode_bmp sources += skia_encode_srcs @@ -213,13 +214,15 @@ "//third_party/skia/src/utils/SkParsePath.cpp", ] + if (is_win) { + libs = [ "fontsub.lib" ] + } + # need separate win section to handle chromes auto gn filter # (build/config/BUILDCONFIG.gn) if (is_win) { sources -= [ #windows - "//third_party/skia/src/utils/win/SkAutoCoInitialize.cpp", - "//third_party/skia/src/utils/win/SkIStream.cpp", "//third_party/skia/src/utils/win/SkWGL_win.cpp", ] }