Refactor pdfium_test's rendering paths into classes
Splits pdfium_test's rendering paths for one-shot, progressive, and SKP
rendering into separate "PageRenderer" classes. This avoids mixing logic
specific to one rendering path with an unrelated rendering path.
The main motivation is to avoid calling FPDF_RenderPageBitmap() when
using the --skp output format.
Fixed: pdfium:1935
Change-Id: I472f5ce16bf0b92de9266f72fad5933a7cb176b7
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/101472
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: K. Moon <kmoon@chromium.org>
diff --git a/samples/pdfium_test.cc b/samples/pdfium_test.cc
index 900e4be..1e63000 100644
--- a/samples/pdfium_test.cc
+++ b/samples/pdfium_test.cc
@@ -81,6 +81,8 @@
#include <wordexp.h>
#endif // WORDEXP_AVAILABLE
+namespace {
+
enum class OutputFormat {
kNone,
kPageInfo,
@@ -101,8 +103,6 @@
#endif
};
-namespace {
-
struct Options {
Options() = default;
@@ -752,6 +752,246 @@
return true;
}
+// Renderer for a single page.
+class PageRenderer {
+ public:
+ virtual ~PageRenderer() = default;
+
+ // Returns `true` if the rendered output exists. Must call `Start()` first.
+ virtual bool HasOutput() const = 0;
+
+ // Starts rendering the page, returning `false` on failure.
+ virtual bool Start() = 0;
+
+ // Continues rendering the page, returning `false` when complete.
+ virtual bool Continue() { return false; }
+
+ // Finishes rendering the page.
+ virtual void Finish(FPDF_FORMHANDLE form) = 0;
+
+ // Writes rendered output to a file, returning `false` on failure.
+ virtual bool Write(const std::string& name, int page_index, bool md5) = 0;
+
+ protected:
+ PageRenderer(FPDF_PAGE page, int width, int height, int flags)
+ : page_(page), width_(width), height_(height), flags_(flags) {}
+
+ FPDF_PAGE page() { return page_; }
+ int width() const { return width_; }
+ int height() const { return height_; }
+ int flags() const { return flags_; }
+
+ private:
+ FPDF_PAGE page_;
+ int width_;
+ int height_;
+ int flags_;
+};
+
+// 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.
+ //
+ // 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);
+
+ bool HasOutput() const override { return !!bitmap_; }
+
+ bool Start() override {
+ bool alpha = FPDFPage_HasTransparency(page());
+ bitmap_.reset(FPDFBitmap_Create(/*width=*/width(), /*height=*/height(),
+ /*alpha=*/alpha));
+ if (!HasOutput())
+ 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 Finish(FPDF_FORMHANDLE form) override {
+ FPDF_FFLDraw(form, bitmap(), page(), /*start_x=*/0, /*start_y=*/0,
+ /*size_x=*/width(), /*size_y=*/height(), /*rotate=*/0,
+ /*flags=*/flags());
+ Idle();
+ }
+
+ 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;
+ }
+
+ protected:
+ BitmapPageRenderer(FPDF_PAGE page,
+ int width,
+ int height,
+ int flags,
+ const std::function<void()>& idler,
+ BitmapWriter writer)
+ : PageRenderer(page, /*width=*/width, /*height=*/height, /*flags=*/flags),
+ idler_(idler),
+ writer_(writer) {}
+
+ void Idle() const { idler_(); }
+ FPDF_BITMAP bitmap() { return bitmap_.get(); }
+
+ private:
+ const std::function<void()>& idler_;
+ BitmapWriter writer_;
+ ScopedFPDFBitmap bitmap_;
+};
+
+// Bitmap page renderer completing in a single operation.
+class OneShotBitmapPageRenderer : public BitmapPageRenderer {
+ public:
+ OneShotBitmapPageRenderer(FPDF_PAGE page,
+ int width,
+ int height,
+ int flags,
+ const std::function<void()>& idler,
+ BitmapWriter writer)
+ : BitmapPageRenderer(page,
+ /*width=*/width,
+ /*height=*/height,
+ /*flags=*/flags,
+ idler,
+ writer) {}
+
+ bool Start() override {
+ if (!BitmapPageRenderer::Start())
+ 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
+ // rendering to update the UI, the PDF renderer will break when possible.
+ FPDF_RenderPageBitmap(bitmap(), page(), /*start_x=*/0, /*start_y=*/0,
+ /*size_x=*/width(), /*size_y=*/height(), /*rotate=*/0,
+ /*flags=*/flags());
+ return true;
+ }
+};
+
+// Bitmap page renderer completing over multiple operations.
+class ProgressiveBitmapPageRenderer : public BitmapPageRenderer {
+ public:
+ ProgressiveBitmapPageRenderer(FPDF_PAGE page,
+ int width,
+ int height,
+ int flags,
+ const std::function<void()>& idler,
+ BitmapWriter writer,
+ const FPDF_COLORSCHEME* color_scheme)
+ : BitmapPageRenderer(page,
+ /*width=*/width,
+ /*height=*/height,
+ /*flags=*/flags,
+ idler,
+ writer),
+ color_scheme_(color_scheme) {
+ pause_.version = 1;
+ pause_.NeedToPauseNow = &NeedToPauseNow;
+ }
+
+ bool Start() override {
+ if (!BitmapPageRenderer::Start())
+ return false;
+
+ if (FPDF_RenderPageBitmapWithColorScheme_Start(
+ bitmap(), page(), /*start_x=*/0, /*start_y=*/0, /*size_x=*/width(),
+ /*size_y=*/height(), /*rotate=*/0, /*flags=*/flags(), color_scheme_,
+ &pause_) == FPDF_RENDER_TOBECONTINUED) {
+ to_be_continued_ = true;
+ }
+ return true;
+ }
+
+ bool Continue() override {
+ if (to_be_continued_) {
+ to_be_continued_ = (FPDF_RenderPage_Continue(page(), &pause_) ==
+ FPDF_RENDER_TOBECONTINUED);
+ }
+ return to_be_continued_;
+ }
+
+ void Finish(FPDF_FORMHANDLE form) override {
+ BitmapPageRenderer::Finish(form);
+ FPDF_RenderPage_Close(page());
+ Idle();
+ }
+
+ private:
+ const FPDF_COLORSCHEME* color_scheme_;
+ IFSDK_PAUSE pause_;
+ bool to_be_continued_ = false;
+};
+
+#ifdef PDF_ENABLE_SKIA
+class SkPicturePageRenderer : public PageRenderer {
+ public:
+ SkPicturePageRenderer(FPDF_PAGE page, int width, int height, int flags)
+ : PageRenderer(page,
+ /*width=*/width,
+ /*height=*/height,
+ /*flags=*/flags) {}
+
+ bool HasOutput() const override { return !!recorder_; }
+
+ bool Start() override {
+ recorder_.reset(reinterpret_cast<SkPictureRecorder*>(
+ FPDF_RenderPageSkp(page(), /*size_x=*/width(), /*size_y=*/height())));
+ return HasOutput();
+ }
+
+ 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);
+ }
+
+ bool Write(const std::string& name, int page_index, bool md5) override {
+ std::string image_file_name =
+ WriteSkp(name.c_str(), page_index, recorder_.get());
+ if (image_file_name.empty())
+ return false;
+
+ if (md5) {
+ // Supporting --md5 would require rasterization.
+ // TODO(crbug.com/pdfium/1929): It may be useful to compute the MD5 of the
+ // replayed SKP, in order to compare with direct rasterization.
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ std::unique_ptr<SkPictureRecorder> recorder_;
+};
+#endif // PDF_ENABLE_SKIA
+
bool ProcessPage(const std::string& name,
FPDF_DOCUMENT doc,
FPDF_FORMHANDLE form,
@@ -789,59 +1029,68 @@
if (!options.scale_factor_as_string.empty())
std::stringstream(options.scale_factor_as_string) >> scale;
- auto width = static_cast<int>(FPDF_GetPageWidthF(page) * scale);
- auto height = static_cast<int>(FPDF_GetPageHeightF(page) * scale);
- int alpha = FPDFPage_HasTransparency(page) ? 1 : 0;
- ScopedFPDFBitmap bitmap(FPDFBitmap_Create(width, height, alpha));
+ int width = static_cast<int>(FPDF_GetPageWidthF(page) * scale);
+ int height = static_cast<int>(FPDF_GetPageHeightF(page) * scale);
+ int flags = PageRenderFlagsFromOptions(options);
- if (bitmap) {
- FPDF_DWORD fill_color = alpha ? 0x00000000 : 0xFFFFFFFF;
- FPDFBitmap_FillRect(bitmap.get(), 0, 0, width, height, fill_color);
-
- int flags = PageRenderFlagsFromOptions(options);
- if (options.render_oneshot) {
- // Note, client programs probably want to use this method instead of the
- // progressive calls. The progressive calls are if you need to pause the
- // rendering to update the UI, the PDF renderer will break when possible.
- FPDF_RenderPageBitmap(bitmap.get(), page, 0, 0, width, height, 0, flags);
- } else {
- IFSDK_PAUSE pause;
- pause.version = 1;
- pause.NeedToPauseNow = &NeedToPauseNow;
-
- // 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.
- const FPDF_COLORSCHEME color_scheme{
- /*path_fill_color=*/0xFFFF0000, /*path_stroke_color=*/0xFF00FF00,
- /*text_fill_color=*/0xFF0000FF, /*text_stroke_color=*/0xFF00FFFF};
-
- int rv = FPDF_RenderPageBitmapWithColorScheme_Start(
- bitmap.get(), page, 0, 0, width, height, 0, flags,
- options.forced_color ? &color_scheme : nullptr, &pause);
- while (rv == FPDF_RENDER_TOBECONTINUED)
- rv = FPDF_RenderPage_Continue(page, &pause);
- }
-
- FPDF_FFLDraw(form, bitmap.get(), page, 0, 0, width, height, 0, flags);
- idler();
-
- if (!options.render_oneshot) {
- FPDF_RenderPage_Close(page);
- idler();
- }
-
- int stride = FPDFBitmap_GetStride(bitmap.get());
- void* buffer = FPDFBitmap_GetBuffer(bitmap.get());
-
- std::string image_file_name;
+ 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) {
#ifdef _WIN32
case OutputFormat::kBmp:
- image_file_name =
- WriteBmp(name.c_str(), page_index, buffer, stride, width, height);
+ writer = WriteBmp;
+ break;
+#endif // _WIN32
+
+ case OutputFormat::kPng:
+ writer = WritePng;
break;
+ case OutputFormat::kPpm:
+ writer = WritePpm;
+ break;
+
+ default:
+ // Other formats won't write the output to a file, but still rasterize.
+ writer = nullptr;
+ break;
+ }
+
+ if (options.render_oneshot) {
+ renderer = std::make_unique<OneShotBitmapPageRenderer>(
+ page, /*width=*/width, /*height=*/height, /*flags=*/flags, idler,
+ 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.
+ FPDF_COLORSCHEME color_scheme;
+ color_scheme.path_fill_color = 0xFFFF0000;
+ color_scheme.path_stroke_color = 0xFF00FF00;
+ color_scheme.text_fill_color = 0xFF0000FF;
+ 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);
+ }
+ }
+
+ 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;
@@ -850,7 +1099,8 @@
case OutputFormat::kPs3:
WritePS(page, name.c_str(), page_index);
break;
-#endif
+#endif // _WIN32
+
case OutputFormat::kText:
WriteText(text_page.get(), name.c_str(), page_index);
break;
@@ -859,36 +1109,10 @@
WriteAnnot(page, name.c_str(), page_index);
break;
- case OutputFormat::kPng:
- image_file_name =
- WritePng(name.c_str(), page_index, buffer, stride, width, height);
- break;
-
- case OutputFormat::kPpm:
- image_file_name =
- WritePpm(name.c_str(), page_index, buffer, stride, width, height);
- break;
-
-#ifdef PDF_ENABLE_SKIA
- case OutputFormat::kSkp: {
- std::unique_ptr<SkPictureRecorder> recorder(
- reinterpret_cast<SkPictureRecorder*>(
- FPDF_RenderPageSkp(page, width, height)));
- FPDF_FFLRecord(form, recorder.get(), page, 0, 0, width, height, 0, 0);
- image_file_name = WriteSkp(name.c_str(), page_index, recorder.get());
- } break;
-#endif
default:
+ renderer->Write(name, page_index, /*md5=*/options.md5);
break;
}
-
- // Write the filename and the MD5 of the buffer to stdout if we wrote a
- // file.
- if (options.md5 && !image_file_name.empty()) {
- OutputMD5Hash(image_file_name.c_str(),
- {static_cast<const uint8_t*>(buffer),
- static_cast<size_t>(stride) * height});
- }
} else {
fprintf(stderr, "Page was too large to be rendered.\n");
}
@@ -899,7 +1123,7 @@
FORM_OnBeforeClosePage(page, form);
idler();
- return !!bitmap;
+ return renderer->HasOutput();
}
void ProcessPdf(const std::string& name,