Move helper methods out of pdfium_test.

This CL splits some helper code out of pdfium_test into individual
files. This makes it a bit clearer what each chunk of code is doing and
makes pdfium_test easier to read.

Change-Id: I3dcff5a6a85ac4283336108eb3a24fa5f234284c
Reviewed-on: https://pdfium-review.googlesource.com/28990
Reviewed-by: Henrique Nakashima <hnakashima@chromium.org>
Commit-Queue: dsinclair <dsinclair@chromium.org>
diff --git a/samples/BUILD.gn b/samples/BUILD.gn
index 66b02f6..72f1302 100644
--- a/samples/BUILD.gn
+++ b/samples/BUILD.gn
@@ -51,6 +51,12 @@
   testonly = true
   sources = [
     "pdfium_test.cc",
+    "pdfium_test_dump_helper.cc",
+    "pdfium_test_dump_helper.h",
+    "pdfium_test_event_helper.cc",
+    "pdfium_test_event_helper.h",
+    "pdfium_test_write_helper.cc",
+    "pdfium_test_write_helper.h",
   ]
   deps = [
     "../:image_diff",
diff --git a/samples/pdfium_test.cc b/samples/pdfium_test.cc
index c026297..b0268d0 100644
--- a/samples/pdfium_test.cc
+++ b/samples/pdfium_test.cc
@@ -7,13 +7,11 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include <bitset>
 #include <iterator>
 #include <map>
 #include <memory>
 #include <sstream>
 #include <string>
-#include <utility>
 #include <vector>
 
 #if defined PDF_ENABLE_SKIA && !defined _SKIA_SUPPORT_
@@ -31,7 +29,9 @@
 #include "public/fpdf_structtree.h"
 #include "public/fpdf_text.h"
 #include "public/fpdfview.h"
-#include "testing/image_diff/image_diff_png.h"
+#include "samples/pdfium_test_dump_helper.h"
+#include "samples/pdfium_test_event_helper.h"
+#include "samples/pdfium_test_write_helper.h"
 #include "testing/test_support.h"
 #include "third_party/base/logging.h"
 
@@ -50,11 +50,6 @@
 #include "v8/include/v8.h"
 #endif  // PDF_ENABLE_V8
 
-#ifdef PDF_ENABLE_SKIA
-#include "third_party/skia/include/core/SkPictureRecorder.h"
-#include "third_party/skia/include/core/SkStream.h"
-#endif
-
 #ifdef _WIN32
 #define access _access
 #define snprintf _snprintf
@@ -133,14 +128,6 @@
   return static_cast<FPDF_FORMFILLINFO_PDFiumTest*>(form_fill_info);
 }
 
-bool CheckDimensions(int stride, int width, int height) {
-  if (stride < 0 || width < 0 || height < 0)
-    return false;
-  if (height > 0 && width > INT_MAX / height)
-    return false;
-  return true;
-}
-
 void OutputMD5Hash(const char* file_name, const char* buffer, int len) {
   // Get the MD5 hash and write it to stdout.
   std::string hash =
@@ -148,472 +135,6 @@
   printf("MD5:%s:%s\n", file_name, hash.c_str());
 }
 
-std::string WritePpm(const char* pdf_name,
-                     int num,
-                     const void* buffer_void,
-                     int stride,
-                     int width,
-                     int height) {
-  const auto* buffer = reinterpret_cast<const char*>(buffer_void);
-
-  if (!CheckDimensions(stride, width, height))
-    return "";
-
-  int out_len = width * height;
-  if (out_len > INT_MAX / 3)
-    return "";
-  out_len *= 3;
-
-  char filename[256];
-  snprintf(filename, sizeof(filename), "%s.%d.ppm", pdf_name, num);
-  FILE* fp = fopen(filename, "wb");
-  if (!fp)
-    return "";
-  fprintf(fp, "P6\n# PDF test render\n%d %d\n255\n", width, height);
-  // Source data is B, G, R, unused.
-  // Dest data is R, G, B.
-  std::vector<char> result(out_len);
-  for (int h = 0; h < height; ++h) {
-    const char* src_line = buffer + (stride * h);
-    char* dest_line = result.data() + (width * h * 3);
-    for (int w = 0; w < width; ++w) {
-      // R
-      dest_line[w * 3] = src_line[(w * 4) + 2];
-      // G
-      dest_line[(w * 3) + 1] = src_line[(w * 4) + 1];
-      // B
-      dest_line[(w * 3) + 2] = src_line[w * 4];
-    }
-  }
-  if (fwrite(result.data(), out_len, 1, fp) != 1)
-    fprintf(stderr, "Failed to write to %s\n", filename);
-  fclose(fp);
-  return std::string(filename);
-}
-
-void WriteText(FPDF_PAGE page, const char* pdf_name, int num) {
-  char filename[256];
-  int chars_formatted =
-      snprintf(filename, sizeof(filename), "%s.%d.txt", pdf_name, num);
-  if (chars_formatted < 0 ||
-      static_cast<size_t>(chars_formatted) >= sizeof(filename)) {
-    fprintf(stderr, "Filename %s is too long\n", filename);
-    return;
-  }
-
-  FILE* fp = fopen(filename, "w");
-  if (!fp) {
-    fprintf(stderr, "Failed to open %s for output\n", filename);
-    return;
-  }
-
-  // Output in UTF32-LE.
-  uint32_t bom = 0x0000FEFF;
-  if (fwrite(&bom, sizeof(bom), 1, fp) != 1) {
-    fprintf(stderr, "Failed to write to %s\n", filename);
-    (void)fclose(fp);
-    return;
-  }
-
-  std::unique_ptr<void, FPDFTextPageDeleter> textpage(FPDFText_LoadPage(page));
-  for (int i = 0; i < FPDFText_CountChars(textpage.get()); i++) {
-    uint32_t c = FPDFText_GetUnicode(textpage.get(), i);
-    if (fwrite(&c, sizeof(c), 1, fp) != 1) {
-      fprintf(stderr, "Failed to write to %s\n", filename);
-      break;
-    }
-  }
-  (void)fclose(fp);
-}
-
-const char* AnnotSubtypeToCString(FPDF_ANNOTATION_SUBTYPE subtype) {
-  if (subtype == FPDF_ANNOT_TEXT)
-    return "Text";
-  if (subtype == FPDF_ANNOT_LINK)
-    return "Link";
-  if (subtype == FPDF_ANNOT_FREETEXT)
-    return "FreeText";
-  if (subtype == FPDF_ANNOT_LINE)
-    return "Line";
-  if (subtype == FPDF_ANNOT_SQUARE)
-    return "Square";
-  if (subtype == FPDF_ANNOT_CIRCLE)
-    return "Circle";
-  if (subtype == FPDF_ANNOT_POLYGON)
-    return "Polygon";
-  if (subtype == FPDF_ANNOT_POLYLINE)
-    return "PolyLine";
-  if (subtype == FPDF_ANNOT_HIGHLIGHT)
-    return "Highlight";
-  if (subtype == FPDF_ANNOT_UNDERLINE)
-    return "Underline";
-  if (subtype == FPDF_ANNOT_SQUIGGLY)
-    return "Squiggly";
-  if (subtype == FPDF_ANNOT_STRIKEOUT)
-    return "StrikeOut";
-  if (subtype == FPDF_ANNOT_STAMP)
-    return "Stamp";
-  if (subtype == FPDF_ANNOT_CARET)
-    return "Caret";
-  if (subtype == FPDF_ANNOT_INK)
-    return "Ink";
-  if (subtype == FPDF_ANNOT_POPUP)
-    return "Popup";
-  if (subtype == FPDF_ANNOT_FILEATTACHMENT)
-    return "FileAttachment";
-  if (subtype == FPDF_ANNOT_SOUND)
-    return "Sound";
-  if (subtype == FPDF_ANNOT_MOVIE)
-    return "Movie";
-  if (subtype == FPDF_ANNOT_WIDGET)
-    return "Widget";
-  if (subtype == FPDF_ANNOT_SCREEN)
-    return "Screen";
-  if (subtype == FPDF_ANNOT_PRINTERMARK)
-    return "PrinterMark";
-  if (subtype == FPDF_ANNOT_TRAPNET)
-    return "TrapNet";
-  if (subtype == FPDF_ANNOT_WATERMARK)
-    return "Watermark";
-  if (subtype == FPDF_ANNOT_THREED)
-    return "3D";
-  if (subtype == FPDF_ANNOT_RICHMEDIA)
-    return "RichMedia";
-  if (subtype == FPDF_ANNOT_XFAWIDGET)
-    return "XFAWidget";
-  NOTREACHED();
-  return "";
-}
-
-void AppendFlagString(const char* flag, std::string* output) {
-  if (!output->empty())
-    *output += ", ";
-  *output += flag;
-}
-
-std::string AnnotFlagsToString(int flags) {
-  std::string str;
-  if (flags & FPDF_ANNOT_FLAG_INVISIBLE)
-    AppendFlagString("Invisible", &str);
-  if (flags & FPDF_ANNOT_FLAG_HIDDEN)
-    AppendFlagString("Hidden", &str);
-  if (flags & FPDF_ANNOT_FLAG_PRINT)
-    AppendFlagString("Print", &str);
-  if (flags & FPDF_ANNOT_FLAG_NOZOOM)
-    AppendFlagString("NoZoom", &str);
-  if (flags & FPDF_ANNOT_FLAG_NOROTATE)
-    AppendFlagString("NoRotate", &str);
-  if (flags & FPDF_ANNOT_FLAG_NOVIEW)
-    AppendFlagString("NoView", &str);
-  if (flags & FPDF_ANNOT_FLAG_READONLY)
-    AppendFlagString("ReadOnly", &str);
-  if (flags & FPDF_ANNOT_FLAG_LOCKED)
-    AppendFlagString("Locked", &str);
-  if (flags & FPDF_ANNOT_FLAG_TOGGLENOVIEW)
-    AppendFlagString("ToggleNoView", &str);
-  return str;
-}
-
-const char* PageObjectTypeToCString(int type) {
-  if (type == FPDF_PAGEOBJ_TEXT)
-    return "Text";
-  if (type == FPDF_PAGEOBJ_PATH)
-    return "Path";
-  if (type == FPDF_PAGEOBJ_IMAGE)
-    return "Image";
-  if (type == FPDF_PAGEOBJ_SHADING)
-    return "Shading";
-  if (type == FPDF_PAGEOBJ_FORM)
-    return "Form";
-  NOTREACHED();
-  return "";
-}
-
-void WriteAnnot(FPDF_PAGE page, const char* pdf_name, int num) {
-  // Open the output text file.
-  char filename[256];
-  int chars_formatted =
-      snprintf(filename, sizeof(filename), "%s.%d.annot.txt", pdf_name, num);
-  if (chars_formatted < 0 ||
-      static_cast<size_t>(chars_formatted) >= sizeof(filename)) {
-    fprintf(stderr, "Filename %s is too long\n", filename);
-    return;
-  }
-  FILE* fp = fopen(filename, "w");
-  if (!fp) {
-    fprintf(stderr, "Failed to open %s for output\n", filename);
-    return;
-  }
-
-  int annot_count = FPDFPage_GetAnnotCount(page);
-  fprintf(fp, "Number of annotations: %d\n\n", annot_count);
-
-  // Iterate through all annotations on this page.
-  for (int i = 0; i < annot_count; ++i) {
-    // Retrieve the annotation object and its subtype.
-    fprintf(fp, "Annotation #%d:\n", i + 1);
-    std::unique_ptr<void, FPDFAnnotationDeleter> annot(
-        FPDFPage_GetAnnot(page, i));
-    if (!annot) {
-      fprintf(fp, "Failed to retrieve annotation!\n\n");
-      continue;
-    }
-    FPDF_ANNOTATION_SUBTYPE subtype = FPDFAnnot_GetSubtype(annot.get());
-    fprintf(fp, "Subtype: %s\n", AnnotSubtypeToCString(subtype));
-
-    // Retrieve the annotation flags.
-    fprintf(fp, "Flags set: %s\n",
-            AnnotFlagsToString(FPDFAnnot_GetFlags(annot.get())).c_str());
-
-    // Retrieve the annotation's object count and object types.
-    const int obj_count = FPDFAnnot_GetObjectCount(annot.get());
-    fprintf(fp, "Number of objects: %d\n", obj_count);
-    if (obj_count > 0) {
-      fprintf(fp, "Object types: ");
-      for (int j = 0; j < obj_count; ++j) {
-        const char* type = PageObjectTypeToCString(
-            FPDFPageObj_GetType(FPDFAnnot_GetObject(annot.get(), j)));
-        fprintf(fp, "%s  ", type);
-      }
-      fprintf(fp, "\n");
-    }
-
-    // Retrieve the annotation's color and interior color.
-    unsigned int R;
-    unsigned int G;
-    unsigned int B;
-    unsigned int A;
-    if (FPDFAnnot_GetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, &R, &G, &B,
-                           &A)) {
-      fprintf(fp, "Color in RGBA: %d %d %d %d\n", R, G, B, A);
-    } else {
-      fprintf(fp, "Failed to retrieve color.\n");
-    }
-    if (FPDFAnnot_GetColor(annot.get(), FPDFANNOT_COLORTYPE_InteriorColor, &R,
-                           &G, &B, &A)) {
-      fprintf(fp, "Interior color in RGBA: %d %d %d %d\n", R, G, B, A);
-    } else {
-      fprintf(fp, "Failed to retrieve interior color.\n");
-    }
-
-    // Retrieve the annotation's contents and author.
-    static constexpr char kContentsKey[] = "Contents";
-    static constexpr char kAuthorKey[] = "T";
-    unsigned long len =
-        FPDFAnnot_GetStringValue(annot.get(), kContentsKey, nullptr, 0);
-    std::vector<char> buf(len);
-    FPDFAnnot_GetStringValue(annot.get(), kContentsKey, buf.data(), len);
-    fprintf(fp, "Content: %ls\n",
-            GetPlatformWString(reinterpret_cast<unsigned short*>(buf.data()))
-                .c_str());
-    len = FPDFAnnot_GetStringValue(annot.get(), kAuthorKey, nullptr, 0);
-    buf.clear();
-    buf.resize(len);
-    FPDFAnnot_GetStringValue(annot.get(), kAuthorKey, buf.data(), len);
-    fprintf(fp, "Author: %ls\n",
-            GetPlatformWString(reinterpret_cast<unsigned short*>(buf.data()))
-                .c_str());
-
-    // Retrieve the annotation's quadpoints if it is a markup annotation.
-    if (FPDFAnnot_HasAttachmentPoints(annot.get())) {
-      FS_QUADPOINTSF quadpoints;
-      if (FPDFAnnot_GetAttachmentPoints(annot.get(), &quadpoints)) {
-        fprintf(fp,
-                "Quadpoints: (%.3f, %.3f), (%.3f, %.3f), (%.3f, %.3f), (%.3f, "
-                "%.3f)\n",
-                quadpoints.x1, quadpoints.y1, quadpoints.x2, quadpoints.y2,
-                quadpoints.x3, quadpoints.y3, quadpoints.x4, quadpoints.y4);
-      } else {
-        fprintf(fp, "Failed to retrieve quadpoints.\n");
-      }
-    }
-
-    // Retrieve the annotation's rectangle coordinates.
-    FS_RECTF rect;
-    if (FPDFAnnot_GetRect(annot.get(), &rect)) {
-      fprintf(fp, "Rectangle: l - %.3f, b - %.3f, r - %.3f, t - %.3f\n\n",
-              rect.left, rect.bottom, rect.right, rect.top);
-    } else {
-      fprintf(fp, "Failed to retrieve annotation rectangle.\n");
-    }
-  }
-
-  (void)fclose(fp);
-}
-
-std::string WritePng(const char* pdf_name,
-                     int num,
-                     const void* buffer_void,
-                     int stride,
-                     int width,
-                     int height) {
-  if (!CheckDimensions(stride, width, height))
-    return "";
-
-  std::vector<unsigned char> png_encoding;
-  const auto* buffer = static_cast<const unsigned char*>(buffer_void);
-  if (!image_diff_png::EncodeBGRAPNG(
-          buffer, width, height, stride, false, &png_encoding)) {
-    fprintf(stderr, "Failed to convert bitmap to PNG\n");
-    return "";
-  }
-
-  char filename[256];
-  int chars_formatted = snprintf(
-      filename, sizeof(filename), "%s.%d.png", pdf_name, num);
-  if (chars_formatted < 0 ||
-      static_cast<size_t>(chars_formatted) >= sizeof(filename)) {
-    fprintf(stderr, "Filename %s is too long\n", filename);
-    return "";
-  }
-
-  FILE* fp = fopen(filename, "wb");
-  if (!fp) {
-    fprintf(stderr, "Failed to open %s for output\n", filename);
-    return "";
-  }
-
-  size_t bytes_written = fwrite(
-      &png_encoding.front(), 1, png_encoding.size(), fp);
-  if (bytes_written != png_encoding.size())
-    fprintf(stderr, "Failed to write to %s\n", filename);
-
-  (void)fclose(fp);
-  return std::string(filename);
-}
-
-#ifdef _WIN32
-std::string WriteBmp(const char* pdf_name,
-                     int num,
-                     const void* buffer,
-                     int stride,
-                     int width,
-                     int height) {
-  if (!CheckDimensions(stride, width, height))
-    return "";
-
-  int out_len = stride * height;
-  if (out_len > INT_MAX / 3)
-    return "";
-
-  char filename[256];
-  snprintf(filename, sizeof(filename), "%s.%d.bmp", pdf_name, num);
-  FILE* fp = fopen(filename, "wb");
-  if (!fp)
-    return "";
-
-  BITMAPINFO bmi = {};
-  bmi.bmiHeader.biSize = sizeof(bmi) - sizeof(RGBQUAD);
-  bmi.bmiHeader.biWidth = width;
-  bmi.bmiHeader.biHeight = -height;  // top-down image
-  bmi.bmiHeader.biPlanes = 1;
-  bmi.bmiHeader.biBitCount = 32;
-  bmi.bmiHeader.biCompression = BI_RGB;
-  bmi.bmiHeader.biSizeImage = 0;
-
-  BITMAPFILEHEADER file_header = {};
-  file_header.bfType = 0x4d42;
-  file_header.bfSize = sizeof(file_header) + bmi.bmiHeader.biSize + out_len;
-  file_header.bfOffBits = file_header.bfSize - out_len;
-
-  if (fwrite(&file_header, sizeof(file_header), 1, fp) != 1 ||
-      fwrite(&bmi, bmi.bmiHeader.biSize, 1, fp) != 1 ||
-      fwrite(buffer, out_len, 1, fp) != 1) {
-    fprintf(stderr, "Failed to write to %s\n", filename);
-  }
-  fclose(fp);
-  return std::string(filename);
-}
-
-void WriteEmf(FPDF_PAGE page, const char* pdf_name, int num) {
-  char filename[256];
-  snprintf(filename, sizeof(filename), "%s.%d.emf", pdf_name, num);
-
-  HDC dc = CreateEnhMetaFileA(nullptr, filename, nullptr, nullptr);
-
-  int width = static_cast<int>(FPDF_GetPageWidth(page));
-  int height = static_cast<int>(FPDF_GetPageHeight(page));
-  HRGN rgn = CreateRectRgn(0, 0, width, height);
-  SelectClipRgn(dc, rgn);
-  DeleteObject(rgn);
-
-  SelectObject(dc, GetStockObject(NULL_PEN));
-  SelectObject(dc, GetStockObject(WHITE_BRUSH));
-  // If a PS_NULL pen is used, the dimensions of the rectangle are 1 pixel less.
-  Rectangle(dc, 0, 0, width + 1, height + 1);
-
-  FPDF_RenderPage(dc, page, 0, 0, width, height, 0,
-                  FPDF_ANNOT | FPDF_PRINTING | FPDF_NO_CATCH);
-
-  DeleteEnhMetaFile(CloseEnhMetaFile(dc));
-}
-
-int CALLBACK EnhMetaFileProc(HDC hdc,
-                             HANDLETABLE* handle_table,
-                             const ENHMETARECORD* record,
-                             int objects_count,
-                             LPARAM param) {
-  std::vector<const ENHMETARECORD*>& items =
-      *reinterpret_cast<std::vector<const ENHMETARECORD*>*>(param);
-  items.push_back(record);
-  return 1;
-}
-
-void WritePS(FPDF_PAGE page, const char* pdf_name, int num) {
-  char filename[256];
-  snprintf(filename, sizeof(filename), "%s.%d.ps", pdf_name, num);
-  FILE* fp = fopen(filename, "wb");
-  if (!fp)
-    return;
-
-  HDC dc = CreateEnhMetaFileA(nullptr, nullptr, nullptr, nullptr);
-
-  int width = static_cast<int>(FPDF_GetPageWidth(page));
-  int height = static_cast<int>(FPDF_GetPageHeight(page));
-  FPDF_RenderPage(dc, page, 0, 0, width, height, 0,
-                  FPDF_ANNOT | FPDF_PRINTING | FPDF_NO_CATCH);
-
-  HENHMETAFILE emf = CloseEnhMetaFile(dc);
-  std::vector<const ENHMETARECORD*> items;
-  EnumEnhMetaFile(nullptr, emf, &EnhMetaFileProc, &items, nullptr);
-  for (const ENHMETARECORD* record : items) {
-    if (record->iType != EMR_GDICOMMENT)
-      continue;
-
-    const auto* comment = reinterpret_cast<const EMRGDICOMMENT*>(record);
-    const char* data = reinterpret_cast<const char*>(comment->Data);
-    uint16_t size = *reinterpret_cast<const uint16_t*>(data);
-    if (fwrite(data + sizeof(uint16_t), size, 1, fp) != 1) {
-      fprintf(stderr, "Failed to write to %s\n", filename);
-      break;
-    }
-  }
-  fclose(fp);
-  DeleteEnhMetaFile(emf);
-}
-#endif  // _WIN32
-
-#ifdef PDF_ENABLE_SKIA
-std::string WriteSkp(const char* pdf_name,
-                     int num,
-                     SkPictureRecorder* recorder) {
-  char filename[256];
-  int chars_formatted =
-      snprintf(filename, sizeof(filename), "%s.%d.skp", pdf_name, num);
-
-  if (chars_formatted < 0 ||
-      static_cast<size_t>(chars_formatted) >= sizeof(filename)) {
-    fprintf(stderr, "Filename %s is too long\n", filename);
-    return "";
-  }
-
-  sk_sp<SkPicture> picture(recorder->finishRecordingAsPicture());
-  SkFILEWStream wStream(filename);
-  picture->serialize(&wStream);
-  return std::string(filename);
-}
-#endif
-
 // These example JS platform callback handlers are entirely optional,
 // and exist here to show the flow of information from a document back
 // to the embedder.
@@ -901,82 +422,6 @@
 
 void Add_Segment(FX_DOWNLOADHINTS* hints, size_t offset, size_t size) {}
 
-void SendPageEvents(FPDF_FORMHANDLE form,
-                    FPDF_PAGE page,
-                    const std::string& events) {
-  auto lines = StringSplit(events, '\n');
-  for (auto line : lines) {
-    auto command = StringSplit(line, '#');
-    if (command[0].empty())
-      continue;
-    auto tokens = StringSplit(command[0], ',');
-    if (tokens[0] == "charcode") {
-      if (tokens.size() == 2) {
-        int keycode = atoi(tokens[1].c_str());
-        FORM_OnChar(form, page, keycode, 0);
-      } else {
-        fprintf(stderr, "charcode: bad args\n");
-      }
-    } else if (tokens[0] == "keycode") {
-      if (tokens.size() == 2) {
-        int keycode = atoi(tokens[1].c_str());
-        FORM_OnKeyDown(form, page, keycode, 0);
-        FORM_OnKeyUp(form, page, keycode, 0);
-      } else {
-        fprintf(stderr, "keycode: bad args\n");
-      }
-    } else if (tokens[0] == "mousedown") {
-      if (tokens.size() == 4) {
-        int x = atoi(tokens[2].c_str());
-        int y = atoi(tokens[3].c_str());
-        if (tokens[1] == "left")
-          FORM_OnLButtonDown(form, page, 0, x, y);
-#ifdef PDF_ENABLE_XFA
-        else if (tokens[1] == "right")
-          FORM_OnRButtonDown(form, page, 0, x, y);
-#endif
-        else
-          fprintf(stderr, "mousedown: bad button name\n");
-      } else {
-        fprintf(stderr, "mousedown: bad args\n");
-      }
-    } else if (tokens[0] == "mouseup") {
-      if (tokens.size() == 4) {
-        int x = atoi(tokens[2].c_str());
-        int y = atoi(tokens[3].c_str());
-        if (tokens[1] == "left")
-          FORM_OnLButtonUp(form, page, 0, x, y);
-#ifdef PDF_ENABLE_XFA
-        else if (tokens[1] == "right")
-          FORM_OnRButtonUp(form, page, 0, x, y);
-#endif
-        else
-          fprintf(stderr, "mouseup: bad button name\n");
-      } else {
-        fprintf(stderr, "mouseup: bad args\n");
-      }
-    } else if (tokens[0] == "mousemove") {
-      if (tokens.size() == 3) {
-        int x = atoi(tokens[1].c_str());
-        int y = atoi(tokens[2].c_str());
-        FORM_OnMouseMove(form, page, 0, x, y);
-      } else {
-        fprintf(stderr, "mousemove: bad args\n");
-      }
-    } else if (tokens[0] == "focus") {
-      if (tokens.size() == 3) {
-        int x = atoi(tokens[1].c_str());
-        int y = atoi(tokens[2].c_str());
-        FORM_OnFocus(form, page, 0, x, y);
-      } else {
-        fprintf(stderr, "focus: bad args\n");
-      }
-    } else {
-      fprintf(stderr, "Unrecognized event: %s\n", tokens[0].c_str());
-    }
-  }
-}
-
 FPDF_PAGE GetPageForIndex(FPDF_FORMFILLINFO* param,
                           FPDF_DOCUMENT doc,
                           int index) {
@@ -1001,225 +446,6 @@
   return page_ptr;
 }
 
-std::wstring ConvertToWString(const unsigned short* buf,
-                              unsigned long buf_size) {
-  std::wstring result;
-  result.reserve(buf_size);
-  std::copy(buf, buf + buf_size, std::back_inserter(result));
-  return result;
-}
-
-void DumpChildStructure(FPDF_STRUCTELEMENT child, int indent) {
-  static const size_t kBufSize = 1024;
-  unsigned short buf[kBufSize];
-  unsigned long len = FPDF_StructElement_GetType(child, buf, kBufSize);
-  printf("%*s%ls", indent * 2, "", ConvertToWString(buf, len).c_str());
-
-  memset(buf, 0, sizeof(buf));
-  len = FPDF_StructElement_GetTitle(child, buf, kBufSize);
-  if (len > 0)
-    printf(": '%ls'", ConvertToWString(buf, len).c_str());
-
-  memset(buf, 0, sizeof(buf));
-  len = FPDF_StructElement_GetAltText(child, buf, kBufSize);
-  if (len > 0)
-    printf(" (%ls)", ConvertToWString(buf, len).c_str());
-  printf("\n");
-
-  for (int i = 0; i < FPDF_StructElement_CountChildren(child); ++i) {
-    FPDF_STRUCTELEMENT sub_child = FPDF_StructElement_GetChildAtIndex(child, i);
-    // If the child is not an Element then this will return null. This can
-    // happen if the element is things like an object reference or a stream.
-    if (!sub_child)
-      continue;
-
-    DumpChildStructure(sub_child, indent + 1);
-  }
-}
-
-void DumpPageStructure(FPDF_PAGE page, const int page_idx) {
-  std::unique_ptr<void, FPDFStructTreeDeleter> tree(
-      FPDF_StructTree_GetForPage(page));
-  if (!tree) {
-    fprintf(stderr, "Failed to load struct tree for page %d\n", page_idx);
-    return;
-  }
-
-  printf("Structure Tree for Page %d\n", page_idx);
-  for (int i = 0; i < FPDF_StructTree_CountChildren(tree.get()); ++i) {
-    FPDF_STRUCTELEMENT child = FPDF_StructTree_GetChildAtIndex(tree.get(), i);
-    if (!child) {
-      fprintf(stderr, "Failed to load child %d for page %d\n", i, page_idx);
-      continue;
-    }
-    DumpChildStructure(child, 0);
-  }
-  printf("\n\n");
-}
-
-void DumpMetaData(FPDF_DOCUMENT doc) {
-  constexpr const char* meta_tags[] = {"Title",        "Author",  "Subject",
-                                       "Keywords",     "Creator", "Producer",
-                                       "CreationDate", "ModDate"};
-  for (const char* meta_tag : meta_tags) {
-    char meta_buffer[4096];
-    unsigned long len =
-        FPDF_GetMetaText(doc, meta_tag, meta_buffer, sizeof(meta_buffer));
-    if (!len)
-      continue;
-
-    auto* meta_string = reinterpret_cast<unsigned short*>(meta_buffer);
-    printf("%-12s = %ls (%lu bytes)\n", meta_tag,
-           GetPlatformWString(meta_string).c_str(), len);
-  }
-}
-
-void SaveAttachments(FPDF_DOCUMENT doc, const std::string& name) {
-  for (int i = 0; i < FPDFDoc_GetAttachmentCount(doc); ++i) {
-    FPDF_ATTACHMENT attachment = FPDFDoc_GetAttachment(doc, i);
-
-    // Retrieve the attachment file name.
-    std::string attachment_name;
-    unsigned long len = FPDFAttachment_GetName(attachment, nullptr, 0);
-    if (len) {
-      std::vector<char> buf(len);
-      unsigned long actual_len =
-          FPDFAttachment_GetName(attachment, buf.data(), len);
-      if (actual_len == len) {
-        attachment_name =
-            GetPlatformString(reinterpret_cast<unsigned short*>(buf.data()));
-      }
-    }
-    if (attachment_name.empty()) {
-      fprintf(stderr, "Attachment #%d has an empty file name.\n", i + 1);
-      continue;
-    }
-
-    // Calculate the full attachment file name.
-    char save_name[256];
-    int chars_formatted =
-        snprintf(save_name, sizeof(save_name), "%s.attachment.%s", name.c_str(),
-                 attachment_name.c_str());
-    if (chars_formatted < 0 ||
-        static_cast<size_t>(chars_formatted) >= sizeof(save_name)) {
-      fprintf(stderr, "Filename %s is too long\n", save_name);
-      continue;
-    }
-
-    // Retrieve the attachment.
-    len = FPDFAttachment_GetFile(attachment, nullptr, 0);
-    std::vector<char> data_buf(len);
-    if (len) {
-      unsigned long actual_len =
-          FPDFAttachment_GetFile(attachment, data_buf.data(), len);
-      if (actual_len != len)
-        data_buf.clear();
-    }
-    if (data_buf.empty()) {
-      fprintf(stderr, "Attachment \"%s\" is empty.\n", attachment_name.c_str());
-      continue;
-    }
-
-    // Write the attachment file.
-    FILE* fp = fopen(save_name, "wb");
-    if (!fp) {
-      fprintf(stderr, "Failed to open %s for saving attachment.\n", save_name);
-      continue;
-    }
-
-    size_t written_len = fwrite(data_buf.data(), 1, len, fp);
-    if (written_len == len) {
-      fprintf(stderr, "Saved attachment \"%s\" as: %s.\n",
-              attachment_name.c_str(), save_name);
-    } else {
-      fprintf(stderr, "Failed to write to %s\n", save_name);
-    }
-    fclose(fp);
-  }
-}
-
-void SaveImages(FPDF_PAGE page, const char* pdf_name, int page_num) {
-  for (int i = 0; i < FPDFPage_CountObjects(page); ++i) {
-    FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, i);
-    if (FPDFPageObj_GetType(obj) != FPDF_PAGEOBJ_IMAGE)
-      continue;
-
-    std::unique_ptr<void, FPDFBitmapDeleter> bitmap(
-        FPDFImageObj_GetBitmap(obj));
-    if (!bitmap) {
-      fprintf(stderr, "Image object #%d on page #%d has an empty bitmap.\n",
-              i + 1, page_num + 1);
-      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;
-    }
-
-    std::vector<unsigned char> 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());
-    bool ret = false;
-    switch (format) {
-      case FPDFBitmap_Gray:
-        ret = image_diff_png::EncodeGrayPNG(buffer, width, height, stride,
-                                            &png_encoding);
-        break;
-      case FPDFBitmap_BGR:
-        ret = image_diff_png::EncodeBGRPNG(buffer, width, height, stride,
-                                           &png_encoding);
-        break;
-      case FPDFBitmap_BGRx:
-        ret = image_diff_png::EncodeBGRAPNG(buffer, width, height, stride, true,
-                                            &png_encoding);
-        break;
-      case FPDFBitmap_BGRA:
-        ret = image_diff_png::EncodeBGRAPNG(buffer, width, height, stride,
-                                            false, &png_encoding);
-        break;
-      default:
-        NOTREACHED();
-    }
-    if (!ret) {
-      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);
-    if (chars_formatted < 0 ||
-        static_cast<size_t>(chars_formatted) >= sizeof(filename)) {
-      fprintf(stderr, "Filename %s for saving image is too long\n", filename);
-      continue;
-    }
-
-    FILE* fp = fopen(filename, "wb");
-    if (!fp) {
-      fprintf(stderr, "Failed to open %s for saving image.\n", filename);
-      continue;
-    }
-
-    size_t bytes_written =
-        fwrite(&png_encoding.front(), 1, png_encoding.size(), fp);
-    if (bytes_written != png_encoding.size())
-      fprintf(stderr, "Failed to write to %s.\n", filename);
-    else
-      fprintf(stderr, "Successfully wrote embedded image %s.\n", filename);
-
-    (void)fclose(fp);
-  }
-}
-
 // Note, for a client using progressive rendering you'd want to determine if you
 // need the rendering to pause instead of always saying |true|. This is for
 // testing to force the renderer to break whenever possible.
@@ -1240,7 +466,7 @@
   if (options.send_events)
     SendPageEvents(form, page, events);
   if (options.save_images)
-    SaveImages(page, name.c_str(), page_index);
+    WriteImages(page, name.c_str(), page_index);
   if (options.output_format == OUTPUT_STRUCTURE) {
     DumpPageStructure(page, page_index);
     return true;
@@ -1412,7 +638,7 @@
     DumpMetaData(doc.get());
 
   if (options.save_attachments)
-    SaveAttachments(doc.get(), name);
+    WriteAttachments(doc.get(), name);
 
   IPDF_JSPLATFORM platform_callbacks = {};
   platform_callbacks.version = 3;
diff --git a/samples/pdfium_test_dump_helper.cc b/samples/pdfium_test_dump_helper.cc
new file mode 100644
index 0000000..5d7af1c
--- /dev/null
+++ b/samples/pdfium_test_dump_helper.cc
@@ -0,0 +1,88 @@
+// Copyright 2018 The 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 "samples/pdfium_test_dump_helper.h"
+
+#include <string>
+#include <utility>
+
+#include "public/cpp/fpdf_deleters.h"
+#include "testing/test_support.h"
+
+namespace {
+
+std::wstring ConvertToWString(const unsigned short* buf,
+                              unsigned long buf_size) {
+  std::wstring result;
+  result.reserve(buf_size);
+  std::copy(buf, buf + buf_size, std::back_inserter(result));
+  return result;
+}
+
+}  // namespace
+
+void DumpChildStructure(FPDF_STRUCTELEMENT child, int indent) {
+  static const size_t kBufSize = 1024;
+  unsigned short buf[kBufSize];
+  unsigned long len = FPDF_StructElement_GetType(child, buf, kBufSize);
+  printf("%*s%ls", indent * 2, "", ConvertToWString(buf, len).c_str());
+
+  memset(buf, 0, sizeof(buf));
+  len = FPDF_StructElement_GetTitle(child, buf, kBufSize);
+  if (len > 0)
+    printf(": '%ls'", ConvertToWString(buf, len).c_str());
+
+  memset(buf, 0, sizeof(buf));
+  len = FPDF_StructElement_GetAltText(child, buf, kBufSize);
+  if (len > 0)
+    printf(" (%ls)", ConvertToWString(buf, len).c_str());
+  printf("\n");
+
+  for (int i = 0; i < FPDF_StructElement_CountChildren(child); ++i) {
+    FPDF_STRUCTELEMENT sub_child = FPDF_StructElement_GetChildAtIndex(child, i);
+    // If the child is not an Element then this will return null. This can
+    // happen if the element is things like an object reference or a stream.
+    if (!sub_child)
+      continue;
+
+    DumpChildStructure(sub_child, indent + 1);
+  }
+}
+
+void DumpPageStructure(FPDF_PAGE page, const int page_idx) {
+  std::unique_ptr<void, FPDFStructTreeDeleter> tree(
+      FPDF_StructTree_GetForPage(page));
+  if (!tree) {
+    fprintf(stderr, "Failed to load struct tree for page %d\n", page_idx);
+    return;
+  }
+
+  printf("Structure Tree for Page %d\n", page_idx);
+  for (int i = 0; i < FPDF_StructTree_CountChildren(tree.get()); ++i) {
+    FPDF_STRUCTELEMENT child = FPDF_StructTree_GetChildAtIndex(tree.get(), i);
+    if (!child) {
+      fprintf(stderr, "Failed to load child %d for page %d\n", i, page_idx);
+      continue;
+    }
+    DumpChildStructure(child, 0);
+  }
+  printf("\n\n");
+}
+
+void DumpMetaData(FPDF_DOCUMENT doc) {
+  constexpr const char* meta_tags[] = {"Title",        "Author",  "Subject",
+                                       "Keywords",     "Creator", "Producer",
+                                       "CreationDate", "ModDate"};
+  for (const char* meta_tag : meta_tags) {
+    char meta_buffer[4096];
+    unsigned long len =
+        FPDF_GetMetaText(doc, meta_tag, meta_buffer, sizeof(meta_buffer));
+    if (!len)
+      continue;
+
+    auto* meta_string = reinterpret_cast<unsigned short*>(meta_buffer);
+    printf("%-12s = %ls (%lu bytes)\n", meta_tag,
+           GetPlatformWString(meta_string).c_str(), len);
+  }
+}
diff --git a/samples/pdfium_test_dump_helper.h b/samples/pdfium_test_dump_helper.h
new file mode 100644
index 0000000..e2276f7
--- /dev/null
+++ b/samples/pdfium_test_dump_helper.h
@@ -0,0 +1,14 @@
+// Copyright 2018 The 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 SAMPLES_PDFIUM_TEST_DUMP_HELPER_H_
+#define SAMPLES_PDFIUM_TEST_DUMP_HELPER_H_
+
+#include "public/fpdfview.h"
+
+void DumpChildStructure(FPDF_STRUCTELEMENT child, int indent);
+void DumpPageStructure(FPDF_PAGE page, const int page_idx);
+void DumpMetaData(FPDF_DOCUMENT doc);
+
+#endif  // SAMPLES_PDFIUM_TEST_DUMP_HELPER_H_
diff --git a/samples/pdfium_test_event_helper.cc b/samples/pdfium_test_event_helper.cc
new file mode 100644
index 0000000..9643da1
--- /dev/null
+++ b/samples/pdfium_test_event_helper.cc
@@ -0,0 +1,88 @@
+// Copyright 2018 The 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 "samples/pdfium_test_event_helper.h"
+
+#include <stdio.h>
+
+#include <string>
+
+#include "public/fpdfview.h"
+#include "testing/test_support.h"
+
+void SendPageEvents(FPDF_FORMHANDLE form,
+                    FPDF_PAGE page,
+                    const std::string& events) {
+  auto lines = StringSplit(events, '\n');
+  for (auto line : lines) {
+    auto command = StringSplit(line, '#');
+    if (command[0].empty())
+      continue;
+    auto tokens = StringSplit(command[0], ',');
+    if (tokens[0] == "charcode") {
+      if (tokens.size() == 2) {
+        int keycode = atoi(tokens[1].c_str());
+        FORM_OnChar(form, page, keycode, 0);
+      } else {
+        fprintf(stderr, "charcode: bad args\n");
+      }
+    } else if (tokens[0] == "keycode") {
+      if (tokens.size() == 2) {
+        int keycode = atoi(tokens[1].c_str());
+        FORM_OnKeyDown(form, page, keycode, 0);
+        FORM_OnKeyUp(form, page, keycode, 0);
+      } else {
+        fprintf(stderr, "keycode: bad args\n");
+      }
+    } else if (tokens[0] == "mousedown") {
+      if (tokens.size() == 4) {
+        int x = atoi(tokens[2].c_str());
+        int y = atoi(tokens[3].c_str());
+        if (tokens[1] == "left")
+          FORM_OnLButtonDown(form, page, 0, x, y);
+#ifdef PDF_ENABLE_XFA
+        else if (tokens[1] == "right")
+          FORM_OnRButtonDown(form, page, 0, x, y);
+#endif
+        else
+          fprintf(stderr, "mousedown: bad button name\n");
+      } else {
+        fprintf(stderr, "mousedown: bad args\n");
+      }
+    } else if (tokens[0] == "mouseup") {
+      if (tokens.size() == 4) {
+        int x = atoi(tokens[2].c_str());
+        int y = atoi(tokens[3].c_str());
+        if (tokens[1] == "left")
+          FORM_OnLButtonUp(form, page, 0, x, y);
+#ifdef PDF_ENABLE_XFA
+        else if (tokens[1] == "right")
+          FORM_OnRButtonUp(form, page, 0, x, y);
+#endif
+        else
+          fprintf(stderr, "mouseup: bad button name\n");
+      } else {
+        fprintf(stderr, "mouseup: bad args\n");
+      }
+    } else if (tokens[0] == "mousemove") {
+      if (tokens.size() == 3) {
+        int x = atoi(tokens[1].c_str());
+        int y = atoi(tokens[2].c_str());
+        FORM_OnMouseMove(form, page, 0, x, y);
+      } else {
+        fprintf(stderr, "mousemove: bad args\n");
+      }
+    } else if (tokens[0] == "focus") {
+      if (tokens.size() == 3) {
+        int x = atoi(tokens[1].c_str());
+        int y = atoi(tokens[2].c_str());
+        FORM_OnFocus(form, page, 0, x, y);
+      } else {
+        fprintf(stderr, "focus: bad args\n");
+      }
+    } else {
+      fprintf(stderr, "Unrecognized event: %s\n", tokens[0].c_str());
+    }
+  }
+}
diff --git a/samples/pdfium_test_event_helper.h b/samples/pdfium_test_event_helper.h
new file mode 100644
index 0000000..e823369
--- /dev/null
+++ b/samples/pdfium_test_event_helper.h
@@ -0,0 +1,17 @@
+// Copyright 2018 The 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 SAMPLES_PDFIUM_TEST_EVENT_HELPER_H_
+#define SAMPLES_PDFIUM_TEST_EVENT_HELPER_H_
+
+#include <string>
+
+#include "public/fpdf_formfill.h"
+#include "public/fpdfview.h"
+
+void SendPageEvents(FPDF_FORMHANDLE form,
+                    FPDF_PAGE page,
+                    const std::string& events);
+
+#endif  // SAMPLES_PDFIUM_TEST_EVENT_HELPER_H_
diff --git a/samples/pdfium_test_write_helper.cc b/samples/pdfium_test_write_helper.cc
new file mode 100644
index 0000000..27f43a6
--- /dev/null
+++ b/samples/pdfium_test_write_helper.cc
@@ -0,0 +1,644 @@
+// Copyright 2018 The 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 "samples/pdfium_test_write_helper.h"
+
+#include "public/cpp/fpdf_deleters.h"
+#include "public/fpdf_annot.h"
+#include "public/fpdf_attachment.h"
+#include "public/fpdf_edit.h"
+#include "testing/image_diff/image_diff_png.h"
+#include "testing/test_support.h"
+#include "third_party/base/logging.h"
+
+namespace {
+
+bool CheckDimensions(int stride, int width, int height) {
+  if (stride < 0 || width < 0 || height < 0)
+    return false;
+  if (height > 0 && width > INT_MAX / height)
+    return false;
+  return true;
+}
+
+const char* AnnotSubtypeToCString(FPDF_ANNOTATION_SUBTYPE subtype) {
+  if (subtype == FPDF_ANNOT_TEXT)
+    return "Text";
+  if (subtype == FPDF_ANNOT_LINK)
+    return "Link";
+  if (subtype == FPDF_ANNOT_FREETEXT)
+    return "FreeText";
+  if (subtype == FPDF_ANNOT_LINE)
+    return "Line";
+  if (subtype == FPDF_ANNOT_SQUARE)
+    return "Square";
+  if (subtype == FPDF_ANNOT_CIRCLE)
+    return "Circle";
+  if (subtype == FPDF_ANNOT_POLYGON)
+    return "Polygon";
+  if (subtype == FPDF_ANNOT_POLYLINE)
+    return "PolyLine";
+  if (subtype == FPDF_ANNOT_HIGHLIGHT)
+    return "Highlight";
+  if (subtype == FPDF_ANNOT_UNDERLINE)
+    return "Underline";
+  if (subtype == FPDF_ANNOT_SQUIGGLY)
+    return "Squiggly";
+  if (subtype == FPDF_ANNOT_STRIKEOUT)
+    return "StrikeOut";
+  if (subtype == FPDF_ANNOT_STAMP)
+    return "Stamp";
+  if (subtype == FPDF_ANNOT_CARET)
+    return "Caret";
+  if (subtype == FPDF_ANNOT_INK)
+    return "Ink";
+  if (subtype == FPDF_ANNOT_POPUP)
+    return "Popup";
+  if (subtype == FPDF_ANNOT_FILEATTACHMENT)
+    return "FileAttachment";
+  if (subtype == FPDF_ANNOT_SOUND)
+    return "Sound";
+  if (subtype == FPDF_ANNOT_MOVIE)
+    return "Movie";
+  if (subtype == FPDF_ANNOT_WIDGET)
+    return "Widget";
+  if (subtype == FPDF_ANNOT_SCREEN)
+    return "Screen";
+  if (subtype == FPDF_ANNOT_PRINTERMARK)
+    return "PrinterMark";
+  if (subtype == FPDF_ANNOT_TRAPNET)
+    return "TrapNet";
+  if (subtype == FPDF_ANNOT_WATERMARK)
+    return "Watermark";
+  if (subtype == FPDF_ANNOT_THREED)
+    return "3D";
+  if (subtype == FPDF_ANNOT_RICHMEDIA)
+    return "RichMedia";
+  if (subtype == FPDF_ANNOT_XFAWIDGET)
+    return "XFAWidget";
+  NOTREACHED();
+  return "";
+}
+
+void AppendFlagString(const char* flag, std::string* output) {
+  if (!output->empty())
+    *output += ", ";
+  *output += flag;
+}
+
+std::string AnnotFlagsToString(int flags) {
+  std::string str;
+  if (flags & FPDF_ANNOT_FLAG_INVISIBLE)
+    AppendFlagString("Invisible", &str);
+  if (flags & FPDF_ANNOT_FLAG_HIDDEN)
+    AppendFlagString("Hidden", &str);
+  if (flags & FPDF_ANNOT_FLAG_PRINT)
+    AppendFlagString("Print", &str);
+  if (flags & FPDF_ANNOT_FLAG_NOZOOM)
+    AppendFlagString("NoZoom", &str);
+  if (flags & FPDF_ANNOT_FLAG_NOROTATE)
+    AppendFlagString("NoRotate", &str);
+  if (flags & FPDF_ANNOT_FLAG_NOVIEW)
+    AppendFlagString("NoView", &str);
+  if (flags & FPDF_ANNOT_FLAG_READONLY)
+    AppendFlagString("ReadOnly", &str);
+  if (flags & FPDF_ANNOT_FLAG_LOCKED)
+    AppendFlagString("Locked", &str);
+  if (flags & FPDF_ANNOT_FLAG_TOGGLENOVIEW)
+    AppendFlagString("ToggleNoView", &str);
+  return str;
+}
+
+const char* PageObjectTypeToCString(int type) {
+  if (type == FPDF_PAGEOBJ_TEXT)
+    return "Text";
+  if (type == FPDF_PAGEOBJ_PATH)
+    return "Path";
+  if (type == FPDF_PAGEOBJ_IMAGE)
+    return "Image";
+  if (type == FPDF_PAGEOBJ_SHADING)
+    return "Shading";
+  if (type == FPDF_PAGEOBJ_FORM)
+    return "Form";
+  NOTREACHED();
+  return "";
+}
+
+#ifdef _WIN32
+int CALLBACK EnhMetaFileProc(HDC hdc,
+                             HANDLETABLE* handle_table,
+                             const ENHMETARECORD* record,
+                             int objects_count,
+                             LPARAM param) {
+  std::vector<const ENHMETARECORD*>& items =
+      *reinterpret_cast<std::vector<const ENHMETARECORD*>*>(param);
+  items.push_back(record);
+  return 1;
+}
+#endif  // _WIN32
+
+}  // namespace
+
+std::string WritePpm(const char* pdf_name,
+                     int num,
+                     const void* buffer_void,
+                     int stride,
+                     int width,
+                     int height) {
+  const auto* buffer = reinterpret_cast<const char*>(buffer_void);
+
+  if (!CheckDimensions(stride, width, height))
+    return "";
+
+  int out_len = width * height;
+  if (out_len > INT_MAX / 3)
+    return "";
+
+  out_len *= 3;
+
+  char filename[256];
+  snprintf(filename, sizeof(filename), "%s.%d.ppm", pdf_name, num);
+  FILE* fp = fopen(filename, "wb");
+  if (!fp)
+    return "";
+
+  fprintf(fp, "P6\n# PDF test render\n%d %d\n255\n", width, height);
+  // Source data is B, G, R, unused.
+  // Dest data is R, G, B.
+  std::vector<char> result(out_len);
+  for (int h = 0; h < height; ++h) {
+    const char* src_line = buffer + (stride * h);
+    char* dest_line = result.data() + (width * h * 3);
+    for (int w = 0; w < width; ++w) {
+      // R
+      dest_line[w * 3] = src_line[(w * 4) + 2];
+      // G
+      dest_line[(w * 3) + 1] = src_line[(w * 4) + 1];
+      // B
+      dest_line[(w * 3) + 2] = src_line[w * 4];
+    }
+  }
+  if (fwrite(result.data(), out_len, 1, fp) != 1)
+    fprintf(stderr, "Failed to write to %s\n", filename);
+
+  fclose(fp);
+  return std::string(filename);
+}
+
+void WriteText(FPDF_PAGE page, const char* pdf_name, int num) {
+  char filename[256];
+  int chars_formatted =
+      snprintf(filename, sizeof(filename), "%s.%d.txt", pdf_name, num);
+  if (chars_formatted < 0 ||
+      static_cast<size_t>(chars_formatted) >= sizeof(filename)) {
+    fprintf(stderr, "Filename %s is too long\n", filename);
+    return;
+  }
+
+  FILE* fp = fopen(filename, "w");
+  if (!fp) {
+    fprintf(stderr, "Failed to open %s for output\n", filename);
+    return;
+  }
+
+  // Output in UTF32-LE.
+  uint32_t bom = 0x0000FEFF;
+  if (fwrite(&bom, sizeof(bom), 1, fp) != 1) {
+    fprintf(stderr, "Failed to write to %s\n", filename);
+    (void)fclose(fp);
+    return;
+  }
+
+  std::unique_ptr<void, FPDFTextPageDeleter> textpage(FPDFText_LoadPage(page));
+  for (int i = 0; i < FPDFText_CountChars(textpage.get()); i++) {
+    uint32_t c = FPDFText_GetUnicode(textpage.get(), i);
+    if (fwrite(&c, sizeof(c), 1, fp) != 1) {
+      fprintf(stderr, "Failed to write to %s\n", filename);
+      break;
+    }
+  }
+  (void)fclose(fp);
+}
+
+void WriteAnnot(FPDF_PAGE page, const char* pdf_name, int num) {
+  // Open the output text file.
+  char filename[256];
+  int chars_formatted =
+      snprintf(filename, sizeof(filename), "%s.%d.annot.txt", pdf_name, num);
+  if (chars_formatted < 0 ||
+      static_cast<size_t>(chars_formatted) >= sizeof(filename)) {
+    fprintf(stderr, "Filename %s is too long\n", filename);
+    return;
+  }
+
+  FILE* fp = fopen(filename, "w");
+  if (!fp) {
+    fprintf(stderr, "Failed to open %s for output\n", filename);
+    return;
+  }
+
+  int annot_count = FPDFPage_GetAnnotCount(page);
+  fprintf(fp, "Number of annotations: %d\n\n", annot_count);
+
+  // Iterate through all annotations on this page.
+  for (int i = 0; i < annot_count; ++i) {
+    // Retrieve the annotation object and its subtype.
+    fprintf(fp, "Annotation #%d:\n", i + 1);
+    std::unique_ptr<void, FPDFAnnotationDeleter> annot(
+        FPDFPage_GetAnnot(page, i));
+    if (!annot) {
+      fprintf(fp, "Failed to retrieve annotation!\n\n");
+      continue;
+    }
+
+    FPDF_ANNOTATION_SUBTYPE subtype = FPDFAnnot_GetSubtype(annot.get());
+    fprintf(fp, "Subtype: %s\n", AnnotSubtypeToCString(subtype));
+
+    // Retrieve the annotation flags.
+    fprintf(fp, "Flags set: %s\n",
+            AnnotFlagsToString(FPDFAnnot_GetFlags(annot.get())).c_str());
+
+    // Retrieve the annotation's object count and object types.
+    const int obj_count = FPDFAnnot_GetObjectCount(annot.get());
+    fprintf(fp, "Number of objects: %d\n", obj_count);
+    if (obj_count > 0) {
+      fprintf(fp, "Object types: ");
+      for (int j = 0; j < obj_count; ++j) {
+        const char* type = PageObjectTypeToCString(
+            FPDFPageObj_GetType(FPDFAnnot_GetObject(annot.get(), j)));
+        fprintf(fp, "%s  ", type);
+      }
+      fprintf(fp, "\n");
+    }
+
+    // Retrieve the annotation's color and interior color.
+    unsigned int R;
+    unsigned int G;
+    unsigned int B;
+    unsigned int A;
+    if (FPDFAnnot_GetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, &R, &G, &B,
+                           &A)) {
+      fprintf(fp, "Color in RGBA: %d %d %d %d\n", R, G, B, A);
+    } else {
+      fprintf(fp, "Failed to retrieve color.\n");
+    }
+    if (FPDFAnnot_GetColor(annot.get(), FPDFANNOT_COLORTYPE_InteriorColor, &R,
+                           &G, &B, &A)) {
+      fprintf(fp, "Interior color in RGBA: %d %d %d %d\n", R, G, B, A);
+    } else {
+      fprintf(fp, "Failed to retrieve interior color.\n");
+    }
+
+    // Retrieve the annotation's contents and author.
+    static constexpr char kContentsKey[] = "Contents";
+    static constexpr char kAuthorKey[] = "T";
+    unsigned long len =
+        FPDFAnnot_GetStringValue(annot.get(), kContentsKey, nullptr, 0);
+    std::vector<char> buf(len);
+    FPDFAnnot_GetStringValue(annot.get(), kContentsKey, buf.data(), len);
+    fprintf(fp, "Content: %ls\n",
+            GetPlatformWString(reinterpret_cast<unsigned short*>(buf.data()))
+                .c_str());
+    len = FPDFAnnot_GetStringValue(annot.get(), kAuthorKey, nullptr, 0);
+    buf.clear();
+    buf.resize(len);
+    FPDFAnnot_GetStringValue(annot.get(), kAuthorKey, buf.data(), len);
+    fprintf(fp, "Author: %ls\n",
+            GetPlatformWString(reinterpret_cast<unsigned short*>(buf.data()))
+                .c_str());
+
+    // Retrieve the annotation's quadpoints if it is a markup annotation.
+    if (FPDFAnnot_HasAttachmentPoints(annot.get())) {
+      FS_QUADPOINTSF quadpoints;
+      if (FPDFAnnot_GetAttachmentPoints(annot.get(), &quadpoints)) {
+        fprintf(fp,
+                "Quadpoints: (%.3f, %.3f), (%.3f, %.3f), (%.3f, %.3f), (%.3f, "
+                "%.3f)\n",
+                quadpoints.x1, quadpoints.y1, quadpoints.x2, quadpoints.y2,
+                quadpoints.x3, quadpoints.y3, quadpoints.x4, quadpoints.y4);
+      } else {
+        fprintf(fp, "Failed to retrieve quadpoints.\n");
+      }
+    }
+
+    // Retrieve the annotation's rectangle coordinates.
+    FS_RECTF rect;
+    if (FPDFAnnot_GetRect(annot.get(), &rect)) {
+      fprintf(fp, "Rectangle: l - %.3f, b - %.3f, r - %.3f, t - %.3f\n\n",
+              rect.left, rect.bottom, rect.right, rect.top);
+    } else {
+      fprintf(fp, "Failed to retrieve annotation rectangle.\n");
+    }
+  }
+
+  (void)fclose(fp);
+}
+
+std::string WritePng(const char* pdf_name,
+                     int num,
+                     const void* buffer_void,
+                     int stride,
+                     int width,
+                     int height) {
+  if (!CheckDimensions(stride, width, height))
+    return "";
+
+  std::vector<unsigned char> png_encoding;
+  const auto* buffer = static_cast<const unsigned char*>(buffer_void);
+  if (!image_diff_png::EncodeBGRAPNG(buffer, width, height, stride, false,
+                                     &png_encoding)) {
+    fprintf(stderr, "Failed to convert bitmap to PNG\n");
+    return "";
+  }
+
+  char filename[256];
+  int chars_formatted =
+      snprintf(filename, sizeof(filename), "%s.%d.png", pdf_name, num);
+  if (chars_formatted < 0 ||
+      static_cast<size_t>(chars_formatted) >= sizeof(filename)) {
+    fprintf(stderr, "Filename %s is too long\n", filename);
+    return "";
+  }
+
+  FILE* fp = fopen(filename, "wb");
+  if (!fp) {
+    fprintf(stderr, "Failed to open %s for output\n", filename);
+    return "";
+  }
+
+  size_t bytes_written =
+      fwrite(&png_encoding.front(), 1, png_encoding.size(), fp);
+  if (bytes_written != png_encoding.size())
+    fprintf(stderr, "Failed to write to %s\n", filename);
+
+  (void)fclose(fp);
+  return std::string(filename);
+}
+
+#ifdef _WIN32
+std::string WriteBmp(const char* pdf_name,
+                     int num,
+                     const void* buffer,
+                     int stride,
+                     int width,
+                     int height) {
+  if (!CheckDimensions(stride, width, height))
+    return "";
+
+  int out_len = stride * height;
+  if (out_len > INT_MAX / 3)
+    return "";
+
+  char filename[256];
+  snprintf(filename, sizeof(filename), "%s.%d.bmp", pdf_name, num);
+  FILE* fp = fopen(filename, "wb");
+  if (!fp)
+    return "";
+
+  BITMAPINFO bmi = {};
+  bmi.bmiHeader.biSize = sizeof(bmi) - sizeof(RGBQUAD);
+  bmi.bmiHeader.biWidth = width;
+  bmi.bmiHeader.biHeight = -height;  // top-down image
+  bmi.bmiHeader.biPlanes = 1;
+  bmi.bmiHeader.biBitCount = 32;
+  bmi.bmiHeader.biCompression = BI_RGB;
+  bmi.bmiHeader.biSizeImage = 0;
+
+  BITMAPFILEHEADER file_header = {};
+  file_header.bfType = 0x4d42;
+  file_header.bfSize = sizeof(file_header) + bmi.bmiHeader.biSize + out_len;
+  file_header.bfOffBits = file_header.bfSize - out_len;
+
+  if (fwrite(&file_header, sizeof(file_header), 1, fp) != 1 ||
+      fwrite(&bmi, bmi.bmiHeader.biSize, 1, fp) != 1 ||
+      fwrite(buffer, out_len, 1, fp) != 1) {
+    fprintf(stderr, "Failed to write to %s\n", filename);
+  }
+  fclose(fp);
+  return std::string(filename);
+}
+
+void WriteEmf(FPDF_PAGE page, const char* pdf_name, int num) {
+  char filename[256];
+  snprintf(filename, sizeof(filename), "%s.%d.emf", pdf_name, num);
+
+  HDC dc = CreateEnhMetaFileA(nullptr, filename, nullptr, nullptr);
+
+  int width = static_cast<int>(FPDF_GetPageWidth(page));
+  int height = static_cast<int>(FPDF_GetPageHeight(page));
+  HRGN rgn = CreateRectRgn(0, 0, width, height);
+  SelectClipRgn(dc, rgn);
+  DeleteObject(rgn);
+
+  SelectObject(dc, GetStockObject(NULL_PEN));
+  SelectObject(dc, GetStockObject(WHITE_BRUSH));
+  // If a PS_NULL pen is used, the dimensions of the rectangle are 1 pixel less.
+  Rectangle(dc, 0, 0, width + 1, height + 1);
+
+  FPDF_RenderPage(dc, page, 0, 0, width, height, 0,
+                  FPDF_ANNOT | FPDF_PRINTING | FPDF_NO_CATCH);
+
+  DeleteEnhMetaFile(CloseEnhMetaFile(dc));
+}
+
+void WritePS(FPDF_PAGE page, const char* pdf_name, int num) {
+  char filename[256];
+  snprintf(filename, sizeof(filename), "%s.%d.ps", pdf_name, num);
+  FILE* fp = fopen(filename, "wb");
+  if (!fp)
+    return;
+
+  HDC dc = CreateEnhMetaFileA(nullptr, nullptr, nullptr, nullptr);
+
+  int width = static_cast<int>(FPDF_GetPageWidth(page));
+  int height = static_cast<int>(FPDF_GetPageHeight(page));
+  FPDF_RenderPage(dc, page, 0, 0, width, height, 0,
+                  FPDF_ANNOT | FPDF_PRINTING | FPDF_NO_CATCH);
+
+  HENHMETAFILE emf = CloseEnhMetaFile(dc);
+  std::vector<const ENHMETARECORD*> items;
+  EnumEnhMetaFile(nullptr, emf, &EnhMetaFileProc, &items, nullptr);
+  for (const ENHMETARECORD* record : items) {
+    if (record->iType != EMR_GDICOMMENT)
+      continue;
+
+    const auto* comment = reinterpret_cast<const EMRGDICOMMENT*>(record);
+    const char* data = reinterpret_cast<const char*>(comment->Data);
+    uint16_t size = *reinterpret_cast<const uint16_t*>(data);
+    if (fwrite(data + sizeof(uint16_t), size, 1, fp) != 1) {
+      fprintf(stderr, "Failed to write to %s\n", filename);
+      break;
+    }
+  }
+  fclose(fp);
+  DeleteEnhMetaFile(emf);
+}
+#endif  // _WIN32
+
+#ifdef PDF_ENABLE_SKIA
+std::string WriteSkp(const char* pdf_name,
+                     int num,
+                     SkPictureRecorder* recorder) {
+  char filename[256];
+  int chars_formatted =
+      snprintf(filename, sizeof(filename), "%s.%d.skp", pdf_name, num);
+
+  if (chars_formatted < 0 ||
+      static_cast<size_t>(chars_formatted) >= sizeof(filename)) {
+    fprintf(stderr, "Filename %s is too long\n", filename);
+    return "";
+  }
+
+  sk_sp<SkPicture> picture(recorder->finishRecordingAsPicture());
+  SkFILEWStream wStream(filename);
+  picture->serialize(&wStream);
+  return std::string(filename);
+}
+#endif
+
+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);
+
+    // Retrieve the attachment file name.
+    std::string attachment_name;
+    unsigned long len = FPDFAttachment_GetName(attachment, nullptr, 0);
+    if (len) {
+      std::vector<char> buf(len);
+      unsigned long actual_len =
+          FPDFAttachment_GetName(attachment, buf.data(), len);
+      if (actual_len == len) {
+        attachment_name =
+            GetPlatformString(reinterpret_cast<unsigned short*>(buf.data()));
+      }
+    }
+    if (attachment_name.empty()) {
+      fprintf(stderr, "Attachment #%d has an empty file name.\n", i + 1);
+      continue;
+    }
+
+    // Calculate the full attachment file name.
+    char save_name[256];
+    int chars_formatted =
+        snprintf(save_name, sizeof(save_name), "%s.attachment.%s", name.c_str(),
+                 attachment_name.c_str());
+    if (chars_formatted < 0 ||
+        static_cast<size_t>(chars_formatted) >= sizeof(save_name)) {
+      fprintf(stderr, "Filename %s is too long\n", save_name);
+      continue;
+    }
+
+    // Retrieve the attachment.
+    len = FPDFAttachment_GetFile(attachment, nullptr, 0);
+    std::vector<char> data_buf(len);
+    if (len) {
+      unsigned long actual_len =
+          FPDFAttachment_GetFile(attachment, data_buf.data(), len);
+      if (actual_len != len)
+        data_buf.clear();
+    }
+    if (data_buf.empty()) {
+      fprintf(stderr, "Attachment \"%s\" is empty.\n", attachment_name.c_str());
+      continue;
+    }
+
+    // Write the attachment file.
+    FILE* fp = fopen(save_name, "wb");
+    if (!fp) {
+      fprintf(stderr, "Failed to open %s for saving attachment.\n", save_name);
+      continue;
+    }
+
+    size_t written_len = fwrite(data_buf.data(), 1, len, fp);
+    if (written_len == len) {
+      fprintf(stderr, "Saved attachment \"%s\" as: %s.\n",
+              attachment_name.c_str(), save_name);
+    } else {
+      fprintf(stderr, "Failed to write to %s\n", save_name);
+    }
+    fclose(fp);
+  }
+}
+
+void WriteImages(FPDF_PAGE page, const char* pdf_name, int page_num) {
+  for (int i = 0; i < FPDFPage_CountObjects(page); ++i) {
+    FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, i);
+    if (FPDFPageObj_GetType(obj) != FPDF_PAGEOBJ_IMAGE)
+      continue;
+
+    std::unique_ptr<void, FPDFBitmapDeleter> bitmap(
+        FPDFImageObj_GetBitmap(obj));
+    if (!bitmap) {
+      fprintf(stderr, "Image object #%d on page #%d has an empty bitmap.\n",
+              i + 1, page_num + 1);
+      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;
+    }
+
+    std::vector<unsigned char> 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());
+    bool ret = false;
+    switch (format) {
+      case FPDFBitmap_Gray:
+        ret = image_diff_png::EncodeGrayPNG(buffer, width, height, stride,
+                                            &png_encoding);
+        break;
+      case FPDFBitmap_BGR:
+        ret = image_diff_png::EncodeBGRPNG(buffer, width, height, stride,
+                                           &png_encoding);
+        break;
+      case FPDFBitmap_BGRx:
+        ret = image_diff_png::EncodeBGRAPNG(buffer, width, height, stride, true,
+                                            &png_encoding);
+        break;
+      case FPDFBitmap_BGRA:
+        ret = image_diff_png::EncodeBGRAPNG(buffer, width, height, stride,
+                                            false, &png_encoding);
+        break;
+      default:
+        NOTREACHED();
+    }
+    if (!ret) {
+      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);
+    if (chars_formatted < 0 ||
+        static_cast<size_t>(chars_formatted) >= sizeof(filename)) {
+      fprintf(stderr, "Filename %s for saving image is too long\n", filename);
+      continue;
+    }
+
+    FILE* fp = fopen(filename, "wb");
+    if (!fp) {
+      fprintf(stderr, "Failed to open %s for saving image.\n", filename);
+      continue;
+    }
+
+    size_t bytes_written =
+        fwrite(&png_encoding.front(), 1, png_encoding.size(), fp);
+    if (bytes_written != png_encoding.size())
+      fprintf(stderr, "Failed to write to %s.\n", filename);
+    else
+      fprintf(stderr, "Successfully wrote embedded image %s.\n", filename);
+
+    (void)fclose(fp);
+  }
+}
diff --git a/samples/pdfium_test_write_helper.h b/samples/pdfium_test_write_helper.h
new file mode 100644
index 0000000..eb2a6c6
--- /dev/null
+++ b/samples/pdfium_test_write_helper.h
@@ -0,0 +1,52 @@
+// Copyright 2018 The 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 SAMPLES_PDFIUM_TEST_WRITE_HELPER_H_
+#define SAMPLES_PDFIUM_TEST_WRITE_HELPER_H_
+
+#include <string>
+
+#include "public/fpdfview.h"
+
+#ifdef PDF_ENABLE_SKIA
+#include "third_party/skia/include/core/SkPictureRecorder.h"
+#include "third_party/skia/include/core/SkStream.h"
+#endif
+
+std::string WritePpm(const char* pdf_name,
+                     int num,
+                     const void* buffer_void,
+                     int stride,
+                     int width,
+                     int height);
+void WriteText(FPDF_PAGE page, const char* pdf_name, int num);
+void WriteAnnot(FPDF_PAGE page, const char* pdf_name, int num);
+std::string WritePng(const char* pdf_name,
+                     int num,
+                     const void* buffer_void,
+                     int stride,
+                     int width,
+                     int height);
+
+#ifdef _WIN32
+std::string WriteBmp(const char* pdf_name,
+                     int num,
+                     const void* buffer,
+                     int stride,
+                     int width,
+                     int height);
+void WriteEmf(FPDF_PAGE page, const char* pdf_name, int num);
+void WritePS(FPDF_PAGE page, const char* pdf_name, int num);
+#endif  // _WIN32
+
+#ifdef PDF_ENABLE_SKIA
+std::string WriteSkp(const char* pdf_name,
+                     int num,
+                     SkPictureRecorder* recorder);
+#endif  // PDF_ENABLE_SKIA
+
+void WriteAttachments(FPDF_DOCUMENT doc, const std::string& name);
+void WriteImages(FPDF_PAGE page, const char* pdf_name, int page_num);
+
+#endif  // SAMPLES_PDFIUM_TEST_WRITE_HELPER_H_