Create FPDF_Thumbnail API and refactor raw stream retrieval

FPDF_Thumbnail API contains:
  - FPDFPage_GetRawThumbnailDataFromPage()
  - FPDFPage_GetDecodedThumbnailDataFromPage()
  - FPDFPage_GetThumbnailAsBitmapFromPage()

These allow getting the stream data for a thumbnail from a
page and also retrieving a thumbnail as a bitmap. Also adds
three new commands to pdfium_test:
  --save-thumbs
  --save-thumbs-dec
  --save-thumbs-raw

Bug: pdfium:1312
Change-Id: I8363ef52651986fb6bf959b596448f58c3c8b86c
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/55390
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/BUILD.gn b/fpdfsdk/BUILD.gn
index 3e2b605..0badb3c 100644
--- a/fpdfsdk/BUILD.gn
+++ b/fpdfsdk/BUILD.gn
@@ -63,6 +63,7 @@
     "fpdf_structtree.cpp",
     "fpdf_sysfontinfo.cpp",
     "fpdf_text.cpp",
+    "fpdf_thumbnail.cpp",
     "fpdf_transformpage.cpp",
     "fpdf_view.cpp",
     "ipdfsdk_annothandler.h",
@@ -155,6 +156,7 @@
     "fpdf_structtree_embeddertest.cpp",
     "fpdf_sysfontinfo_embeddertest.cpp",
     "fpdf_text_embeddertest.cpp",
+    "fpdf_thumbnail_embeddertest.cpp",
     "fpdf_transformpage_embeddertest.cpp",
     "fpdf_view_c_api_test.c",
     "fpdf_view_c_api_test.h",
diff --git a/fpdfsdk/fpdf_thumbnail.cpp b/fpdfsdk/fpdf_thumbnail.cpp
new file mode 100644
index 0000000..e206841
--- /dev/null
+++ b/fpdfsdk/fpdf_thumbnail.cpp
@@ -0,0 +1,76 @@
+// Copyright 2019 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "public/fpdf_thumbnail.h"
+
+#include <vector>
+
+#include "core/fpdfapi/page/cpdf_page.h"
+#include "core/fpdfapi/parser/cpdf_dictionary.h"
+#include "core/fpdfapi/parser/cpdf_stream.h"
+#include "core/fpdfapi/parser/cpdf_stream_acc.h"
+#include "core/fpdfapi/render/cpdf_dibbase.h"
+#include "core/fxge/dib/cfx_dibitmap.h"
+#include "fpdfsdk/cpdfsdk_helpers.h"
+#include "public/fpdfview.h"
+
+namespace {
+
+const CPDF_Stream* CPDFStreamForThumbnailFromPage(FPDF_PAGE page) {
+  const CPDF_Page* p_page = CPDFPageFromFPDFPage(page);
+  if (!p_page)
+    return nullptr;
+
+  const CPDF_Dictionary* page_dict = p_page->GetDict();
+  if (!page_dict || !page_dict->KeyExist("Type"))
+    return nullptr;
+
+  return page_dict->GetStreamFor("Thumb");
+}
+
+}  // namespace
+
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFPage_GetDecodedThumbnailDataFromPage(FPDF_PAGE page,
+                                         void* buffer,
+                                         unsigned long buflen) {
+  const CPDF_Stream* thumb_stream = CPDFStreamForThumbnailFromPage(page);
+  if (!thumb_stream)
+    return 0u;
+
+  return DecodeStreamMaybeCopyAndReturnLength(thumb_stream, buffer, buflen);
+}
+
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFPage_GetRawThumbnailDataFromPage(FPDF_PAGE page,
+                                     void* buffer,
+                                     unsigned long buflen) {
+  const CPDF_Stream* thumb_stream = CPDFStreamForThumbnailFromPage(page);
+  if (!thumb_stream)
+    return 0u;
+
+  return GetRawStreamMaybeCopyAndReturnLength(thumb_stream, buffer, buflen);
+}
+
+FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV
+FPDFPage_GetThumbnailAsBitmapFromPage(FPDF_PAGE page) {
+  const CPDF_Stream* thumb_stream = CPDFStreamForThumbnailFromPage(page);
+  if (!thumb_stream)
+    return nullptr;
+
+  const CPDF_Page* p_page = CPDFPageFromFPDFPage(page);
+
+  auto p_source = pdfium::MakeRetain<CPDF_DIBBase>();
+  const CPDF_DIBBase::LoadState start_status = p_source->StartLoadDIBBase(
+      p_page->GetDocument(), thumb_stream, false, nullptr,
+      p_page->m_pPageResources.Get(), false, 0, false);
+  if (start_status == CPDF_DIBBase::LoadState::kFail)
+    return nullptr;
+
+  auto thumb_bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
+  if (!thumb_bitmap->Copy(p_source))
+    return nullptr;
+
+  return FPDFBitmapFromCFXDIBitmap(thumb_bitmap.Leak());
+}
diff --git a/fpdfsdk/fpdf_thumbnail_embeddertest.cpp b/fpdfsdk/fpdf_thumbnail_embeddertest.cpp
new file mode 100644
index 0000000..466f618
--- /dev/null
+++ b/fpdfsdk/fpdf_thumbnail_embeddertest.cpp
@@ -0,0 +1,299 @@
+// Copyright 2019 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <vector>
+
+#include "public/fpdf_thumbnail.h"
+#include "public/fpdfview.h"
+#include "testing/embedder_test.h"
+#include "testing/utils/hash.h"
+
+class FPDFThumbnailEmbedderTest : public EmbedderTest {};
+
+TEST_F(FPDFThumbnailEmbedderTest, GetDecodedThumbnailDataFromPageWithFilters) {
+  ASSERT_TRUE(OpenDocument("simple_thumbnail.pdf"));
+
+  {
+    const char kHashedDecodedData[] = "7902d0be831c9024960f4ebd5d7df1f7";
+    const unsigned long kExpectedSize = 1138u;
+
+    FPDF_PAGE page = LoadPage(0);
+    ASSERT_TRUE(page);
+
+    unsigned long length_bytes =
+        FPDFPage_GetDecodedThumbnailDataFromPage(page, nullptr, 0);
+    ASSERT_EQ(kExpectedSize, length_bytes);
+    std::vector<uint8_t> thumb_buf(length_bytes);
+
+    EXPECT_EQ(kExpectedSize, FPDFPage_GetDecodedThumbnailDataFromPage(
+                                 page, thumb_buf.data(), length_bytes));
+    EXPECT_EQ(kHashedDecodedData,
+              GenerateMD5Base16(thumb_buf.data(), kExpectedSize));
+
+    UnloadPage(page);
+  }
+
+  {
+    const char kHashedDecodedData[] = "e81123a573378ba1ea80461d25cc41f6";
+    const unsigned long kExpectedSize = 1110u;
+
+    FPDF_PAGE page = LoadPage(1);
+    ASSERT_TRUE(page);
+
+    unsigned long length_bytes =
+        FPDFPage_GetDecodedThumbnailDataFromPage(page, nullptr, 0);
+    ASSERT_EQ(kExpectedSize, length_bytes);
+    std::vector<uint8_t> thumb_buf(length_bytes);
+
+    EXPECT_EQ(kExpectedSize, FPDFPage_GetDecodedThumbnailDataFromPage(
+                                 page, thumb_buf.data(), length_bytes));
+    EXPECT_EQ(kHashedDecodedData,
+              GenerateMD5Base16(thumb_buf.data(), kExpectedSize));
+
+    UnloadPage(page);
+  }
+}
+
+TEST_F(FPDFThumbnailEmbedderTest,
+       GetDecodedThumbnailDataFromPageWithNoFilters) {
+  ASSERT_TRUE(OpenDocument("thumbnail_with_no_filters.pdf"));
+
+  const char kHashedDecodedData[] = "b5696e586382b3373741f8a1d651cab0";
+  const unsigned long kExpectedSize = 301u;
+
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  unsigned long length_bytes =
+      FPDFPage_GetDecodedThumbnailDataFromPage(page, nullptr, 0);
+  ASSERT_EQ(kExpectedSize, length_bytes);
+  std::vector<uint8_t> thumb_buf(length_bytes);
+
+  EXPECT_EQ(kExpectedSize, FPDFPage_GetDecodedThumbnailDataFromPage(
+                               page, thumb_buf.data(), length_bytes));
+  EXPECT_EQ(kHashedDecodedData,
+            GenerateMD5Base16(thumb_buf.data(), kExpectedSize));
+
+  UnloadPage(page);
+}
+
+TEST_F(FPDFThumbnailEmbedderTest,
+       GetDecodedThumbnailDataFromPageWithNoThumbnails) {
+  ASSERT_TRUE(OpenDocument("hello_world.pdf"));
+
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  EXPECT_EQ(0u, FPDFPage_GetDecodedThumbnailDataFromPage(page, nullptr, 0));
+
+  UnloadPage(page);
+}
+
+TEST_F(FPDFThumbnailEmbedderTest, GetDecodedThumbnailDataFromPageNullPage) {
+  EXPECT_EQ(0u, FPDFPage_GetDecodedThumbnailDataFromPage(nullptr, nullptr, 0));
+}
+
+TEST_F(FPDFThumbnailEmbedderTest, GetRawThumbnailDataFromPageWithFilters) {
+  ASSERT_TRUE(OpenDocument("simple_thumbnail.pdf"));
+
+  {
+    const char kHashedRawData[] = "f6a8e8db01cccd52abb91ea433a17373";
+    const unsigned long kExpectedSize = 1851u;
+
+    FPDF_PAGE page = LoadPage(0);
+    ASSERT_TRUE(page);
+
+    unsigned long length_bytes =
+        FPDFPage_GetRawThumbnailDataFromPage(page, nullptr, 0);
+    ASSERT_EQ(kExpectedSize, length_bytes);
+    std::vector<uint8_t> thumb_buf(length_bytes);
+
+    EXPECT_EQ(kExpectedSize, FPDFPage_GetRawThumbnailDataFromPage(
+                                 page, thumb_buf.data(), length_bytes));
+    EXPECT_EQ(kHashedRawData,
+              GenerateMD5Base16(thumb_buf.data(), kExpectedSize));
+
+    UnloadPage(page);
+  }
+
+  {
+    const char kHashedRawData[] = "c7558a461d5ecfb1d4757218b473afc0";
+    const unsigned long kExpectedSize = 1792u;
+
+    FPDF_PAGE page = LoadPage(1);
+    ASSERT_TRUE(page);
+
+    unsigned long length_bytes =
+        FPDFPage_GetRawThumbnailDataFromPage(page, nullptr, 0);
+    ASSERT_EQ(kExpectedSize, length_bytes);
+    std::vector<uint8_t> thumb_buf(length_bytes);
+
+    EXPECT_EQ(kExpectedSize, FPDFPage_GetRawThumbnailDataFromPage(
+                                 page, thumb_buf.data(), length_bytes));
+    EXPECT_EQ(kHashedRawData,
+              GenerateMD5Base16(thumb_buf.data(), kExpectedSize));
+
+    UnloadPage(page);
+  }
+}
+
+TEST_F(FPDFThumbnailEmbedderTest, GetRawThumbnailDataFromPageWithNoFilters) {
+  ASSERT_TRUE(OpenDocument("thumbnail_with_no_filters.pdf"));
+
+  const char kHashedRawData[] = "b5696e586382b3373741f8a1d651cab0";
+  const unsigned long kExpectedSize = 301u;
+
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  unsigned long length_bytes =
+      FPDFPage_GetRawThumbnailDataFromPage(page, nullptr, 0);
+  ASSERT_EQ(kExpectedSize, length_bytes);
+  std::vector<uint8_t> thumb_buf(length_bytes);
+
+  EXPECT_EQ(kExpectedSize, FPDFPage_GetRawThumbnailDataFromPage(
+                               page, thumb_buf.data(), length_bytes));
+  EXPECT_EQ(kHashedRawData, GenerateMD5Base16(thumb_buf.data(), kExpectedSize));
+
+  UnloadPage(page);
+}
+
+TEST_F(FPDFThumbnailEmbedderTest, GetRawThumbnailDataFromPageWithNoThumbnails) {
+  ASSERT_TRUE(OpenDocument("hello_world.pdf"));
+
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  EXPECT_EQ(0u, FPDFPage_GetRawThumbnailDataFromPage(page, nullptr, 0));
+
+  UnloadPage(page);
+}
+
+TEST_F(FPDFThumbnailEmbedderTest, GetRawThumbnailDataFromPageNullPage) {
+  EXPECT_EQ(0u, FPDFPage_GetRawThumbnailDataFromPage(nullptr, nullptr, 0));
+}
+
+TEST_F(FPDFThumbnailEmbedderTest, GetThumbnailAsBitmapFromPage) {
+  ASSERT_TRUE(OpenDocument("simple_thumbnail.pdf"));
+
+  {
+    FPDF_PAGE page = LoadPage(0);
+    ASSERT_TRUE(page);
+
+    ScopedFPDFBitmap thumb_bitmap(FPDFPage_GetThumbnailAsBitmapFromPage(page));
+
+    EXPECT_EQ(50, FPDFBitmap_GetWidth(thumb_bitmap.get()));
+    EXPECT_EQ(50, FPDFBitmap_GetHeight(thumb_bitmap.get()));
+    EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(thumb_bitmap.get()));
+    CompareBitmap(thumb_bitmap.get(), 50, 50,
+                  "312a156389470c3c69435f836e370a45");
+
+    UnloadPage(page);
+  }
+
+  {
+    FPDF_PAGE page = LoadPage(1);
+    ASSERT_TRUE(page);
+
+    ScopedFPDFBitmap thumb_bitmap(FPDFPage_GetThumbnailAsBitmapFromPage(page));
+
+    EXPECT_EQ(50, FPDFBitmap_GetWidth(thumb_bitmap.get()));
+    EXPECT_EQ(50, FPDFBitmap_GetHeight(thumb_bitmap.get()));
+    EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(thumb_bitmap.get()));
+    CompareBitmap(thumb_bitmap.get(), 50, 50,
+                  "d831dc43c6d5af82cb7a936dd317f82f");
+
+    UnloadPage(page);
+  }
+}
+
+TEST_F(FPDFThumbnailEmbedderTest,
+       GetThumbnailAsBitmapFromPageWithoutThumbnail) {
+  ASSERT_TRUE(OpenDocument("hello_world.pdf"));
+
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  ScopedFPDFBitmap thumb_bitmap(FPDFPage_GetThumbnailAsBitmapFromPage(page));
+  ASSERT_EQ(nullptr, thumb_bitmap.get());
+
+  UnloadPage(page);
+}
+
+TEST_F(FPDFThumbnailEmbedderTest,
+       GetThumbnailAsBitmapFromThumbnailWithEmptyStream) {
+  ASSERT_TRUE(OpenDocument("thumbnail_with_empty_stream.pdf"));
+
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  ScopedFPDFBitmap thumb_bitmap(FPDFPage_GetThumbnailAsBitmapFromPage(page));
+  ASSERT_EQ(nullptr, thumb_bitmap.get());
+
+  UnloadPage(page);
+}
+
+TEST_F(FPDFThumbnailEmbedderTest,
+       GetThumbnailAsBitmapFromThumbnailWithNoFilters) {
+  ASSERT_TRUE(OpenDocument("thumbnail_with_no_filters.pdf"));
+
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  ScopedFPDFBitmap thumb_bitmap(FPDFPage_GetThumbnailAsBitmapFromPage(page));
+
+  EXPECT_EQ(10, FPDFBitmap_GetWidth(thumb_bitmap.get()));
+  EXPECT_EQ(10, FPDFBitmap_GetHeight(thumb_bitmap.get()));
+  EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(thumb_bitmap.get()));
+  CompareBitmap(thumb_bitmap.get(), 10, 10, "20428776d40c398d44a5c09fcd57986d");
+
+  UnloadPage(page);
+}
+
+TEST_F(FPDFThumbnailEmbedderTest, GetThumbnailDoesNotAlterPage) {
+  ASSERT_TRUE(OpenDocument("simple_thumbnail.pdf"));
+
+  const char kHashedRawData[] = "f6a8e8db01cccd52abb91ea433a17373";
+  const unsigned long kExpectedRawSize = 1851u;
+
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  // Get the raw data
+  unsigned long raw_size =
+      FPDFPage_GetRawThumbnailDataFromPage(page, nullptr, 0);
+  ASSERT_EQ(kExpectedRawSize, raw_size);
+  std::vector<uint8_t> raw_thumb_buf(raw_size);
+
+  EXPECT_EQ(kExpectedRawSize, FPDFPage_GetRawThumbnailDataFromPage(
+                                  page, raw_thumb_buf.data(), raw_size));
+  EXPECT_EQ(kHashedRawData,
+            GenerateMD5Base16(raw_thumb_buf.data(), kExpectedRawSize));
+
+  // Get the thumbnail
+  ScopedFPDFBitmap thumb_bitmap(FPDFPage_GetThumbnailAsBitmapFromPage(page));
+
+  EXPECT_EQ(50, FPDFBitmap_GetWidth(thumb_bitmap.get()));
+  EXPECT_EQ(50, FPDFBitmap_GetHeight(thumb_bitmap.get()));
+  EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(thumb_bitmap.get()));
+  CompareBitmap(thumb_bitmap.get(), 50, 50, "312a156389470c3c69435f836e370a45");
+
+  // Get the raw data again
+  unsigned long new_raw_size =
+      FPDFPage_GetRawThumbnailDataFromPage(page, nullptr, 0);
+  ASSERT_EQ(kExpectedRawSize, new_raw_size);
+  std::vector<uint8_t> new_raw_thumb_buf(new_raw_size);
+
+  EXPECT_EQ(kExpectedRawSize,
+            FPDFPage_GetRawThumbnailDataFromPage(page, new_raw_thumb_buf.data(),
+                                                 new_raw_size));
+  EXPECT_EQ(kHashedRawData,
+            GenerateMD5Base16(new_raw_thumb_buf.data(), kExpectedRawSize));
+
+  UnloadPage(page);
+}
+
+TEST_F(FPDFThumbnailEmbedderTest, GetThumbnailAsBitmapFromPageNullPage) {
+  EXPECT_EQ(nullptr, FPDFPage_GetThumbnailAsBitmapFromPage(nullptr));
+}
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index b776c10..32bb18d 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -26,6 +26,7 @@
 #include "public/fpdf_structtree.h"
 #include "public/fpdf_sysfontinfo.h"
 #include "public/fpdf_text.h"
+#include "public/fpdf_thumbnail.h"
 #include "public/fpdf_transformpage.h"
 #include "public/fpdfview.h"
 
@@ -333,6 +334,11 @@
     CHK(FPDFText_GetUnicode);
     CHK(FPDFText_LoadPage);
 
+    // fpdf_thumbnail.h
+    CHK(FPDFPage_GetDecodedThumbnailDataFromPage);
+    CHK(FPDFPage_GetRawThumbnailDataFromPage);
+    CHK(FPDFPage_GetThumbnailAsBitmapFromPage);
+
     // fpdf_transformpage.h
     CHK(FPDFPageObj_TransformClipPath);
     CHK(FPDFPage_GetArtBox);
diff --git a/public/fpdf_thumbnail.h b/public/fpdf_thumbnail.h
new file mode 100644
index 0000000..dbad85a
--- /dev/null
+++ b/public/fpdf_thumbnail.h
@@ -0,0 +1,59 @@
+// Copyright 2019 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef PUBLIC_FPDF_THUMBNAIL_H_
+#define PUBLIC_FPDF_THUMBNAIL_H_
+
+#include <stdint.h>
+
+// NOLINTNEXTLINE(build/include)
+#include "fpdfview.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Experimental API.
+// Gets the decoded data from the thumbnail of |page| if it exists.
+// This only modifies |buffer| if |buflen| less than or equal to the
+// size of the decoded data. Returns the size of the decoded
+// data or 0 if thumbnail DNE. Optional, pass null to just retrieve
+// the size of the buffer needed.
+//
+//   page    - handle to a page.
+//   buffer  - buffer for holding the decoded image data.
+//   buflen  - length of the buffer in bytes.
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFPage_GetDecodedThumbnailDataFromPage(FPDF_PAGE page,
+                                         void* buffer,
+                                         unsigned long buflen);
+
+// Experimental API.
+// Gets the raw data from the thumbnail of |page| if it exists.
+// This only modifies |buffer| if |buflen| is less than or equal to
+// the size of the raw data. Returns the size of the raw data or 0
+// if thumbnail DNE. Optional, pass null to just retrieve the size
+// of the buffer needed.
+//
+//   page    - handle to a page.
+//   buffer  - buffer for holding the raw image data.
+//   buflen  - length of the buffer in bytes.
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFPage_GetRawThumbnailDataFromPage(FPDF_PAGE page,
+                                     void* buffer,
+                                     unsigned long buflen);
+
+// Experimental API.
+// Returns the thumbnail of |page| as a FPDF_BITMAP. Returns a nullptr
+// if unable to access the thumbnail's stream.
+//
+//   page - handle to a page.
+FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV
+FPDFPage_GetThumbnailAsBitmapFromPage(FPDF_PAGE page);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // PUBLIC_FPDF_THUMBNAIL_H_
diff --git a/samples/pdfium_test.cc b/samples/pdfium_test.cc
index b2e073d..0fd7327 100644
--- a/samples/pdfium_test.cc
+++ b/samples/pdfium_test.cc
@@ -102,6 +102,9 @@
   bool render_oneshot = false;
   bool save_attachments = false;
   bool save_images = false;
+  bool save_thumbnails = false;
+  bool save_thumbnails_decoded = false;
+  bool save_thumbnails_raw = false;
 #ifdef PDF_ENABLE_V8
   bool disable_javascript = false;
 #ifdef PDF_ENABLE_XFA
@@ -346,6 +349,12 @@
       options->save_attachments = true;
     } else if (cur_arg == "--save-images") {
       options->save_images = true;
+    } else if (cur_arg == "--save-thumbs") {
+      options->save_thumbnails = true;
+    } else if (cur_arg == "--save-thumbs-dec") {
+      options->save_thumbnails_decoded = true;
+    } else if (cur_arg == "--save-thumbs-raw") {
+      options->save_thumbnails_raw = true;
 #ifdef PDF_ENABLE_V8
     } else if (cur_arg == "--disable-javascript") {
       options->disable_javascript = true;
@@ -603,7 +612,12 @@
     SendPageEvents(form, page, events);
   if (options.save_images)
     WriteImages(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 == OUTPUT_PAGEINFO) {
     DumpPageInfo(page, page_index);
     return true;
@@ -903,6 +917,12 @@
     "<pdf-name>.attachment.<attachment-name>\n"
     "  --save-images        - write embedded images "
     "<pdf-name>.<page-number>.<object-number>.png\n"
+    "  --save-thumbs        - write page thumbnails "
+    "<pdf-name>.thumbnail.<page-number>.png\n"
+    "  --save-thumbs-dec    - write page thumbnails' decoded stream data"
+    "<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"
 #ifdef PDF_ENABLE_V8
     "  --disable-javascript - do not execute JS in PDF files\n"
 #ifdef PDF_ENABLE_XFA
diff --git a/samples/pdfium_test_write_helper.cc b/samples/pdfium_test_write_helper.cc
index c6f630a..7dda548 100644
--- a/samples/pdfium_test_write_helper.cc
+++ b/samples/pdfium_test_write_helper.cc
@@ -7,12 +7,15 @@
 #include <limits.h>
 
 #include <memory>
+#include <string>
+#include <utility>
 #include <vector>
 
 #include "public/cpp/fpdf_scopers.h"
 #include "public/fpdf_annot.h"
 #include "public/fpdf_attachment.h"
 #include "public/fpdf_edit.h"
+#include "public/fpdf_thumbnail.h"
 #include "testing/fx_string_testhelpers.h"
 #include "testing/image_diff/image_diff_png.h"
 #include "third_party/base/logging.h"
@@ -535,6 +538,37 @@
 }
 #endif
 
+enum class ThumbnailDecodeType { kBitmap, kRawStream, kDecodedStream };
+
+bool GetThumbnailFilename(char* name_buf,
+                          size_t name_buf_size,
+                          const char* pdf_name,
+                          int page_num,
+                          ThumbnailDecodeType decode_type) {
+  const char* format;
+  switch (decode_type) {
+    case ThumbnailDecodeType::kBitmap:
+      format = "%s.thumbnail.%d.png";
+      break;
+    case ThumbnailDecodeType::kDecodedStream:
+      format = "%s.thumbnail.decoded.%d.bin";
+      break;
+    case ThumbnailDecodeType::kRawStream:
+      format = "%s.thumbnail.raw.%d.bin";
+      break;
+  }
+
+  int chars_formatted =
+      snprintf(name_buf, name_buf_size, format, pdf_name, page_num);
+  if (chars_formatted < 0 ||
+      static_cast<size_t>(chars_formatted) >= name_buf_size) {
+    fprintf(stderr, "Filename %s for saving is too long.\n", name_buf);
+    return false;
+  }
+
+  return true;
+}
+
 void WriteBufferToFile(const void* buf,
                        size_t buflen,
                        const char* filename,
@@ -553,6 +587,23 @@
   fclose(fp);
 }
 
+std::vector<unsigned char> EncodeBitmapToPng(ScopedFPDFBitmap bitmap) {
+  std::vector<unsigned char> png_encoding;
+  int format = FPDFBitmap_GetFormat(bitmap.get());
+  if (format == FPDFBitmap_Unknown)
+    return png_encoding;
+
+  const unsigned char* buffer =
+      static_cast<const unsigned char*>(FPDFBitmap_GetBuffer(bitmap.get()));
+
+  int width = FPDFBitmap_GetWidth(bitmap.get());
+  int height = FPDFBitmap_GetHeight(bitmap.get());
+  int stride = FPDFBitmap_GetStride(bitmap.get());
+
+  png_encoding = EncodePng(buffer, width, height, stride, format);
+  return png_encoding;
+}
+
 void WriteAttachments(FPDF_DOCUMENT doc, const std::string& name) {
   for (int i = 0; i < FPDFDoc_GetAttachmentCount(doc); ++i) {
     FPDF_ATTACHMENT attachment = FPDFDoc_GetAttachment(doc, i);
@@ -615,29 +666,6 @@
       continue;
     }
 
-    int format = FPDFBitmap_GetFormat(bitmap.get());
-    if (format == FPDFBitmap_Unknown) {
-      fprintf(stderr,
-              "Image object #%d on page #%d has a bitmap of unknown format.\n",
-              i + 1, page_num + 1);
-      continue;
-    }
-
-    const unsigned char* buffer =
-        static_cast<const unsigned char*>(FPDFBitmap_GetBuffer(bitmap.get()));
-    int width = FPDFBitmap_GetWidth(bitmap.get());
-    int height = FPDFBitmap_GetHeight(bitmap.get());
-    int stride = FPDFBitmap_GetStride(bitmap.get());
-
-    std::vector<unsigned char> png_encoding =
-        EncodePng(buffer, width, height, stride, format);
-    if (png_encoding.empty()) {
-      fprintf(stderr,
-              "Failed to convert image object #%d on page #%d to png.\n", i + 1,
-              page_num + 1);
-      continue;
-    }
-
     char filename[256];
     int chars_formatted = snprintf(filename, sizeof(filename), "%s.%d.%d.png",
                                    pdf_name, page_num, i);
@@ -647,7 +675,101 @@
       continue;
     }
 
+    std::vector<unsigned char> png_encoding =
+        EncodeBitmapToPng(std::move(bitmap));
+    if (png_encoding.empty()) {
+      fprintf(stderr,
+              "Failed to convert image object #%d, on page #%d to png.\n",
+              i + 1, page_num + 1);
+      continue;
+    }
+
     WriteBufferToFile(&png_encoding.front(), png_encoding.size(), filename,
                       "image");
   }
 }
+
+void WriteDecodedThumbnailStream(FPDF_PAGE page,
+                                 const char* pdf_name,
+                                 int page_num) {
+  char filename[256];
+  if (!GetThumbnailFilename(filename, sizeof(filename), pdf_name, page_num,
+                            ThumbnailDecodeType::kDecodedStream)) {
+    return;
+  }
+
+  unsigned long decoded_data_size =
+      FPDFPage_GetDecodedThumbnailDataFromPage(page, nullptr, 0u);
+
+  // Only continue if there actually is a thumbnail for this page
+  if (decoded_data_size == 0) {
+    fprintf(stderr, "Failed to get decoded thumbnail for page #%d.\n",
+            page_num + 1);
+    return;
+  }
+
+  std::vector<uint8_t> thumb_buf(decoded_data_size);
+  if (FPDFPage_GetDecodedThumbnailDataFromPage(
+          page, thumb_buf.data(), decoded_data_size) != decoded_data_size) {
+    fprintf(stderr, "Failed to get decoded thumbnail data for %s.\n", filename);
+    return;
+  }
+
+  WriteBufferToFile(thumb_buf.data(), decoded_data_size, filename,
+                    "decoded thumbnail");
+}
+
+void WriteRawThumbnailStream(FPDF_PAGE page,
+                             const char* pdf_name,
+                             int page_num) {
+  char filename[256];
+  if (!GetThumbnailFilename(filename, sizeof(filename), pdf_name, page_num,
+                            ThumbnailDecodeType::kRawStream)) {
+    return;
+  }
+
+  unsigned long raw_data_size =
+      FPDFPage_GetRawThumbnailDataFromPage(page, nullptr, 0u);
+
+  // Only continue if there actually is a thumbnail for this page
+  if (raw_data_size == 0) {
+    fprintf(stderr, "Failed to get raw thumbnail data for page #%d.\n",
+            page_num + 1);
+    return;
+  }
+
+  std::vector<uint8_t> thumb_buf(raw_data_size);
+  if (FPDFPage_GetRawThumbnailDataFromPage(page, thumb_buf.data(),
+                                           raw_data_size) != raw_data_size) {
+    fprintf(stderr, "Failed to get raw thumbnail data for %s.\n", filename);
+    return;
+  }
+
+  WriteBufferToFile(thumb_buf.data(), raw_data_size, filename, "raw thumbnail");
+}
+
+void WriteThumbnail(FPDF_PAGE page, const char* pdf_name, int page_num) {
+  char filename[256];
+  if (!GetThumbnailFilename(filename, sizeof(filename), pdf_name, page_num,
+                            ThumbnailDecodeType::kBitmap)) {
+    return;
+  }
+
+  ScopedFPDFBitmap thumb_bitmap(FPDFPage_GetThumbnailAsBitmapFromPage(page));
+  if (!thumb_bitmap) {
+    fprintf(stderr, "Thumbnail of page #%d has an empty bitmap.\n",
+            page_num + 1);
+    return;
+  }
+
+  std::vector<unsigned char> png_encoding =
+      EncodeBitmapToPng(std::move(thumb_bitmap));
+  if (png_encoding.empty()) {
+    fprintf(stderr, "Failed to convert thumbnail of page #%d to png.\n",
+            page_num + 1);
+    return;
+  }
+
+  WriteBufferToFile(&png_encoding.front(), png_encoding.size(), filename,
+                    "thumbnail");
+}
diff --git a/samples/pdfium_test_write_helper.h b/samples/pdfium_test_write_helper.h
index eb2a6c6..f44c2e7 100644
--- a/samples/pdfium_test_write_helper.h
+++ b/samples/pdfium_test_write_helper.h
@@ -48,5 +48,12 @@
 
 void WriteAttachments(FPDF_DOCUMENT doc, const std::string& name);
 void WriteImages(FPDF_PAGE page, const char* pdf_name, int page_num);
+void WriteDecodedThumbnailStream(FPDF_PAGE page,
+                                 const char* pdf_name,
+                                 int page_num);
+void WriteRawThumbnailStream(FPDF_PAGE page,
+                             const char* pdf_name,
+                             int page_num);
+void WriteThumbnail(FPDF_PAGE page, const char* pdf_name, int page_num);
 
 #endif  // SAMPLES_PDFIUM_TEST_WRITE_HELPER_H_
diff --git a/testing/resources/simple_thumbnail.in b/testing/resources/simple_thumbnail.in
new file mode 100644
index 0000000..a00b096
--- /dev/null
+++ b/testing/resources/simple_thumbnail.in
@@ -0,0 +1,115 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [ 0 0 200 200 ]
+  /Count 2
+  /Kids [
+    3 0 R
+    4 0 R
+  ]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Thumb 5 0 R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Thumb 6 0 R
+>>
+endobj
+{{object 5 0}} <<
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode /DCTDecode]
+  {{streamlen}}
+>>
+stream
+789cfb7fe3ff0306012f374f37064620d061d46160f8ff8f41d8b92835b1
+243545a13cb32443c1ddd337e0ff6d066706662626100202162062e56065
+656161e5626767e3e0e1e2e1e1e6e2e6e6e51312e0e513e4e3e616101310
+1416111515e5e11797101391101211150119c2c80cd4c3c2cac9caca29c2
+cbcd2b4232f87f88419083c188c188995190814990915990f1ff11062906
+46062666460624c0cac6ccc2ce0194136144110781ffb7187880aa990498
+05181818bf7a3030707c0e9965f9f30283b65b90c35af100c67853ef0090
+ca56e5d320da1e688e9c0003131030238c616105f20c18d9048415802672
+00ed616465ba70e0862ea3e366139bb7fafa9ce5f30e879c8a8d30bff4e1
+e744c6996bbe68ffc83bf6f5c691cfedd51b97ab5d0df14a2afdb51fe444
+41742706800d646664b46774c7a38009a6c010e83846261666563676a802
+462666164121614543061147560125834005152313a744e7c2a2c6a6890b
+a1ee65b367faa495f58427b9c96c6ad3fb04fd03e7b9b6160a8bbcd8cdc9
+f6edb7ecd17f93ac57bba7ffb9e299acc75321bb8f8579f546ede5de77be
+f0de3fb5c864ce994e9e2eb68f6292d36d6d24befedbba5de8cae2e3a7fc
+39845cf3c5b2e6ca5f0e35b35f5861b34a67f2a4558639dbcd74bf6fb876
+37374fc8c265f289ab532e9d54bc7ae7b97b67ff1b7b057607db593ab334
+b58d5f05b19ffea36a7da2e446e14d910bc1cf02afa47d364e50d7e7ba5b
+7a23e6d7cf9d5fb4369f94b07bf1b6c5f7cfbec9fff2eb03b9968acc9dcb
+b4f759557bcf6e7e17ae88fcec7bb772d7dd0b07c7162328ba90d204a3a0
+a2a1238381804260222cb6ec155fe89dd7d8aa306587c51d4eada0059b42
+e7acafd3b3624833f0333360dbef997758c4c1ca504cd2d1bb316eb68383
+f63417bf2b876a1e5ab5b24c4853c9503aafb467495746dad60605065bbb
+5745e7176c88395869f963eae7a8bc805fd7733acc9ab6b24404cd59c26d
+39af498e63e929c61d6aa7aafec3531f032829094cf25469f25498e0a922
+e4a932c98305c89d4f204508104a116005f202201fb220a5576000302818
+180a38061636c24340e081f0eb1bd50e4b199d4ecd7de9b4b04d2be9756b
+c17cbe3b9fece5ae39dcd99493c064a32122a863d0f176bea5a5a05151cb
+0cce159dc63a6db97a1107dcfca2e3b39537bce5395fed33a3aee73fc387
+e8777f825f3d0cf93047c566a1c7aa8d5c6794ae7465079e08cca88e59ce
+2b53feff2600fe084b25
+endstream
+endobj
+{{object 6 0}} <<
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode /DCTDecode]
+  {{streamlen}}
+>>
+stream
+789cfb7fe3ff0306012f374f37064620d061d46160f8ff8f41d8b92835b1
+243545a13cb32443c1ddd337e0ff6d066706662626100202162062e56065
+656161e5626767e3e0e1e2e1e1e6e2e6e6e51312e0e513e4e3e616101310
+1416111515e5e11797101391101211150119c2c80cd4c3c2cac9caca29c2
+cbcd2b4232f87f88419083c188c188995190814990915990f1ff110671b0
+fb1990011b2b075042044d1408fedf62e0616664601260166060607c3581
+8141f069e3bc549105c2ae9af392a63a00954cd56a50eb0ae00ce00d0e61
+6060071a232fc0c0c4c0cec08c308585958189994d40cc80514405682207
+d01e4656a607d71e7cdaf2c3e6973633d3e3b85339dbb65ddb3d8b9fcb8d
+39d8d57efbc9e9c72eeb645f5b76dd6ad7cbe42821d3850aa24eba8f416e
+1444776300d8446646467b46773c0a98600af481ce63646262e566852960
+646266111452646055324c1410165130300e0c4a9a7a49d9d129b811ea5c
+367ba6374a2d415b3e30c8ad2b2db03232dcb493bd6247f64fa7c9277543
+1d539b143b6f066c6def6d9ba8636c90d9a6e172d124e6c5d677331a8e4e
+6f0f304dd53aa920517955559eb743bb3327774bd1ddf98aada7f5b20e4e
+afda34c7abbe8579a9c9e7d756a7f6f53d73e90b14887e98fa2740f64cb5
+956fb25ac846671da3fdaf8f242bf62f4f36feb6bff5c4d3ab9a3c67c43c
+657c39181ef5cfce5d3cf5e86cb6ba75ca2bbf244deaac99127cd2afbd4f
+4421be6feebb0ddfa5fae741a9fe8b2b1fb72e792fc3bbd362737ef2916f
+f6c060501000c5023075c2c389515051c0d09141c120b0b071212c9aec15
+3f04ff7a905c637578425266fa4bf6b35c874b67683d78752a48ff8a50ca
+7696dd733b363c9bb5429375d79a0cf1d63d7379858e28192b05af3d7f42
+72ebefa78f1f5def3f6698be20c65a9187e7839a0b5b2297db3769bba203
+df620f7533ec307d7ded5eeaeed69f8e19abd69e6f87a53570ca1198e4a9
+328953610290f45069f26010f254994f20fa0508453f58811cc8df4cc0ac
+89e46f01450343864005c744b8af051e28fba49a2dd0c8befde6e38619f7
+823a0f363232297e0a5d7226ce51ed0cc793fa555b96647db16d3ab03af9
+947487d14429bf736177af07797906ba15febb71e2f2f5e5ddebe773fede
+a5c799a3105320f9e6f3feeddedb959c23591236e6ee9e92ee57baf7d499
+297be4ffdf040073884a15
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/simple_thumbnail.pdf b/testing/resources/simple_thumbnail.pdf
new file mode 100644
index 0000000..cac9d12
--- /dev/null
+++ b/testing/resources/simple_thumbnail.pdf
@@ -0,0 +1,128 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [ 0 0 200 200 ]
+  /Count 2
+  /Kids [
+    3 0 R
+    4 0 R
+  ]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Thumb 5 0 R
+>>
+endobj
+4 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Thumb 6 0 R
+>>
+endobj
+5 0 obj <<
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode /DCTDecode]
+  /Length 1851
+>>
+stream
+789cfb7fe3ff0306012f374f37064620d061d46160f8ff8f41d8b92835b1
+243545a13cb32443c1ddd337e0ff6d066706662626100202162062e56065
+656161e5626767e3e0e1e2e1e1e6e2e6e6e51312e0e513e4e3e616101310
+1416111515e5e11797101391101211150119c2c80cd4c3c2cac9caca29c2
+cbcd2b4232f87f88419083c188c188995190814990915990f1ff11062906
+46062666460624c0cac6ccc2ce0194136144110781ffb7187880aa990498
+05181818bf7a3030707c0e9965f9f30283b65b90c35af100c67853ef0090
+ca56e5d320da1e688e9c0003131030238c616105f20c18d9048415802672
+00ed616465ba70e0862ea3e366139bb7fafa9ce5f30e879c8a8d30bff4e1
+e744c6996bbe68ffc83bf6f5c691cfedd51b97ab5d0df14a2afdb51fe444
+41742706800d646664b46774c7a38009a6c010e83846261666563676a802
+462666164121614543061147560125834005152313a744e7c2a2c6a6890b
+a1ee65b367faa495f58427b9c96c6ad3fb04fd03e7b9b6160a8bbcd8cdc9
+f6edb7ecd17f93ac57bba7ffb9e299acc75321bb8f8579f546ede5de77be
+f0de3fb5c864ce994e9e2eb68f6292d36d6d24befedbba5de8cae2e3a7fc
+39845cf3c5b2e6ca5f0e35b35f5861b34a67f2a4558639dbcd74bf6fb876
+37374fc8c265f289ab532e9d54bc7ae7b97b67ff1b7b057607db593ab334
+b58d5f05b19ffea36a7da2e446e14d910bc1cf02afa47d364e50d7e7ba5b
+7a23e6d7cf9d5fb4369f94b07bf1b6c5f7cfbec9fff2eb03b9968acc9dcb
+b4f759557bcf6e7e17ae88fcec7bb772d7dd0b07c7162328ba90d204a3a0
+a2a1238381804260222cb6ec155fe89dd7d8aa306587c51d4eada0059b42
+e7acafd3b3624833f0333360dbef997758c4c1ca504cd2d1bb316eb68383
+f63417bf2b876a1e5ab5b24c4853c9503aafb467495746dad60605065bbb
+5745e7176c88395869f963eae7a8bc805fd7733acc9ab6b24404cd59c26d
+39af498e63e929c61d6aa7aafec3531f032829094cf25469f25498e0a922
+e4a932c98305c89d4f204508104a116005f202201fb220a5576000302818
+180a38061636c24340e081f0eb1bd50e4b199d4ecd7de9b4b04d2be9756b
+c17cbe3b9fece5ae39dcd99493c064a32122a863d0f176bea5a5a05151cb
+0cce159dc63a6db97a1107dcfca2e3b39537bce5395fed33a3aee73fc387
+e8777f825f3d0cf93047c566a1c7aa8d5c6794ae7465079e08cca88e59ce
+2b53feff2600fe084b25
+endstream
+endobj
+6 0 obj <<
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode /DCTDecode]
+  /Length 1792
+>>
+stream
+789cfb7fe3ff0306012f374f37064620d061d46160f8ff8f41d8b92835b1
+243545a13cb32443c1ddd337e0ff6d066706662626100202162062e56065
+656161e5626767e3e0e1e2e1e1e6e2e6e6e51312e0e513e4e3e616101310
+1416111515e5e11797101391101211150119c2c80cd4c3c2cac9caca29c2
+cbcd2b4232f87f88419083c188c188995190814990915990f1ff110671b0
+fb1990011b2b075042044d1408fedf62e0616664601260166060607c3581
+8141f069e3bc549105c2ae9af392a63a00954cd56a50eb0ae00ce00d0e61
+6060071a232fc0c0c4c0cec08c308585958189994d40cc80514405682207
+d01e4656a607d71e7cdaf2c3e6973633d3e3b85339dbb65ddb3d8b9fcb8d
+39d8d57efbc9e9c72eeb645f5b76dd6ad7cbe42821d3850aa24eba8f416e
+1444776300d8446646467b46773c0a98600af481ce63646262e566852960
+646266111452646055324c1410165130300e0c4a9a7a49d9d129b811ea5c
+367ba6374a2d415b3e30c8ad2b2db03232dcb493bd6247f64fa7c9277543
+1d539b143b6f066c6def6d9ba8636c90d9a6e172d124e6c5d677331a8e4e
+6f0f304dd53aa920517955559eb743bb3327774bd1ddf98aada7f5b20e4e
+afda34c7abbe8579a9c9e7d756a7f6f53d73e90b14887e98fa2740f64cb5
+956fb25ac846671da3fdaf8f242bf62f4f36feb6bff5c4d3ab9a3c67c43c
+657c39181ef5cfce5d3cf5e86cb6ba75ca2bbf244deaac99127cd2afbd4f
+4421be6feebb0ddfa5fae741a9fe8b2b1fb72e792fc3bbd362737ef2916f
+f6c060501000c5023075c2c389515051c0d09141c120b0b071212c9aec15
+3f04ff7a905c637578425266fa4bf6b35c874b67683d78752a48ff8a50ca
+7696dd733b363c9bb5429375d79a0cf1d63d7379858e28192b05af3d7f42
+72ebefa78f1f5def3f6698be20c65a9187e7839a0b5b2297db3769bba203
+df620f7533ec307d7ded5eeaeed69f8e19abd69e6f87a53570ca1198e4a9
+328953610290f45069f26010f254994f20fa0508453f58811cc8df4cc0ac
+89e46f01450343864005c744b8af051e28fba49a2dd0c8befde6e38619f7
+823a0f363232297e0a5d7226ce51ed0cc793fa555b96647db16d3ab03af9
+947487d14429bf736177af07797906ba15febb71e2f2f5e5ddebe773fede
+a5c799a3105320f9e6f3feeddedb959c23591236e6ee9e92ee57baf7d499
+297be4ffdf040073884a15
+endstream
+endobj
+xref
+0 7
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000177 00000 n 
+0000000243 00000 n 
+0000000309 00000 n 
+0000002337 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 7
+>>
+startxref
+4306
+%%EOF
diff --git a/testing/resources/thumbnail_with_empty_stream.in b/testing/resources/thumbnail_with_empty_stream.in
new file mode 100644
index 0000000..d540171
--- /dev/null
+++ b/testing/resources/thumbnail_with_empty_stream.in
@@ -0,0 +1,33 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [ 0 0 200 200 ]
+  /Count 1
+  /Kids [ 3 0 R ]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Thumb 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+>>
+stream
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/thumbnail_with_empty_stream.pdf b/testing/resources/thumbnail_with_empty_stream.pdf
new file mode 100644
index 0000000..2a1e9ef
--- /dev/null
+++ b/testing/resources/thumbnail_with_empty_stream.pdf
@@ -0,0 +1,44 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [ 0 0 200 200 ]
+  /Count 1
+  /Kids [ 3 0 R ]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Thumb 4 0 R
+>>
+endobj
+4 0 obj <<
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+>>
+stream
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000161 00000 n 
+0000000227 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 5
+>>
+startxref
+378
+%%EOF