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",
]
}