Update pdfium to Chrome 114.0.5735.130 pdfium

pdfium last commit id: 9505810f6

Bug: 279055389
Test: Build the code and flash the device and check Print functionality
Test: atest FrameworksCoreTests
Test: atest CtsPrintTestCases
Test: atest CtsPdfTestCases
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3e2fb7d98efb4ba7b51fd84e9a0ae04f8c0f7805)
Merged-In: I2efabeec0d0fa3925bcbeebf36031cee6f7f9fc4
Change-Id: I2efabeec0d0fa3925bcbeebf36031cee6f7f9fc4
diff --git a/testing/BUILD.gn b/testing/BUILD.gn
index 787aa6d..034de3c 100644
--- a/testing/BUILD.gn
+++ b/testing/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2018 The PDFium Authors. All rights reserved.
+# Copyright 2018 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -7,16 +7,23 @@
 source_set("test_support") {
   testonly = true
   sources = [
+    "command_line_helpers.cpp",
+    "command_line_helpers.h",
+    "font_renamer.cpp",
+    "font_renamer.h",
     "fx_string_testhelpers.cpp",
     "fx_string_testhelpers.h",
     "invalid_seekable_read_stream.cpp",
     "invalid_seekable_read_stream.h",
     "pseudo_retainable.h",
+    "scoped_set_tz.cpp",
+    "scoped_set_tz.h",
     "string_write_stream.cpp",
     "string_write_stream.h",
+    "test_fonts.cpp",
+    "test_fonts.h",
     "test_loader.cpp",
     "test_loader.h",
-    "test_support.cpp",
     "test_support.h",
     "utils/bitmap_saver.cpp",
     "utils/bitmap_saver.h",
@@ -24,10 +31,12 @@
     "utils/file_util.h",
     "utils/hash.cpp",
     "utils/hash.h",
-    "utils/path_service.cpp",
-    "utils/path_service.h",
   ]
   data = [ "resources/" ]
+  public_deps = [
+    ":path_service",
+    "//third_party/test_fonts",
+  ]
   deps = [
     "../:pdfium_public_headers",
     "../core/fdrm",
@@ -35,9 +44,11 @@
     "../core/fxge",
     "image_diff",
   ]
-  configs += [ "../:pdfium_core_config" ]
+  configs += [
+    "../:pdfium_strict_config",
+    "../:pdfium_noshorten_config",
+  ]
   visibility = [ "../*" ]
-
   if (pdf_enable_v8) {
     sources += [
       "v8_initializer.cpp",
@@ -51,25 +62,92 @@
   }
 }
 
+source_set("path_service") {
+  testonly = true
+  sources = [
+    "utils/path_service.cpp",
+    "utils/path_service.h",
+  ]
+  deps = [ "../core/fxcrt" ]
+  configs += [
+    "../:pdfium_strict_config",
+    "../:pdfium_noshorten_config",
+  ]
+  visibility = [ "../*" ]
+}
+
+source_set("test_environments") {
+  testonly = true
+  sources = [
+    "pdf_test_environment.cpp",
+    "pdf_test_environment.h",
+  ]
+  deps = [
+    ":test_support",
+    "../core/fxcrt",
+    "../core/fxge",
+    "//testing/gtest",
+  ]
+  configs += [
+    "../:pdfium_strict_config",
+    "../:pdfium_noshorten_config",
+  ]
+  if (pdf_enable_v8) {
+    sources += [
+      "v8_test_environment.cpp",
+      "v8_test_environment.h",
+    ]
+    deps += [
+      "../fxjs",
+      "//v8",
+      "//v8:v8_libplatform",
+    ]
+    configs += [ "//v8:external_startup_data" ]
+  }
+  if (pdf_enable_xfa) {
+    sources += [
+      "xfa_test_environment.cpp",
+      "xfa_test_environment.h",
+    ]
+    deps += [
+      "../fxjs:gc",
+      "../xfa/fgas/font",
+    ]
+  }
+}
+
 source_set("unit_test_support") {
   testonly = true
   sources = []
   deps = []
-
-  configs += [ "../:pdfium_core_config" ]
-  visibility = [ "../*" ]
-
-  if (pdf_enable_xfa) {
+  configs += [
+    "../:pdfium_strict_config",
+    "../:pdfium_noshorten_config",
+  ]
+  public_deps = [
+    ":test_environments",
+    ":test_support",
+  ]
+  if (pdf_enable_v8) {
     sources += [
-      "xfa_unit_test_support.cpp",
-      "xfa_unit_test_support.h",
+      "fxv8_unittest.cpp",
+      "fxv8_unittest.h",
     ]
     deps += [
-      "../:pdfium",
-      "../core/fxge",
-      "../xfa/fgas",
+      "../fxjs",
       "//testing/gtest",
     ]
+    configs += [ "//v8:external_startup_data" ]
+    if (pdf_enable_xfa) {
+      sources += [
+        "fxgc_unittest.cpp",
+        "fxgc_unittest.h",
+      ]
+      deps += [
+        "../fxjs:gc",
+        "//testing/gtest",
+      ]
+    }
   }
 }
 
@@ -78,6 +156,10 @@
   sources = [
     "embedder_test.cpp",
     "embedder_test.h",
+    "embedder_test_constants.cpp",
+    "embedder_test_constants.h",
+    "embedder_test_environment.cpp",
+    "embedder_test_environment.h",
     "embedder_test_mock_delegate.h",
     "embedder_test_timer_handling_delegate.h",
     "fake_file_access.cpp",
@@ -85,26 +167,37 @@
     "range_set.cpp",
     "range_set.h",
   ]
-
   deps = [
-    ":test_support",
     "../:pdfium_public_headers",
     "../core/fdrm",
     "../core/fxcrt",
+    "../core/fxge",
     "../third_party:pdfium_base",
     "//testing/gmock",
     "//testing/gtest",
   ]
-  configs += [ "../:pdfium_core_config" ]
+  public_deps = [
+    ":test_environments",
+    ":test_support",
+  ]
+  configs += [
+    "../:pdfium_strict_config",
+    "../:pdfium_noshorten_config",
+  ]
   visibility = [ "../*" ]
-
   if (pdf_enable_v8) {
     sources += [
+      "external_engine_embedder_test.cpp",
+      "external_engine_embedder_test.h",
       "js_embedder_test.cpp",
       "js_embedder_test.h",
     ]
-    deps += [ "../fxjs" ]
-
+    deps += [
+      "../fxjs",
+      "//v8",
+      "//v8:v8_libplatform",
+    ]
+    configs += [ "//v8:external_startup_data" ]
     if (pdf_enable_xfa) {
       sources += [
         "xfa_js_embedder_test.cpp",
@@ -119,3 +212,7 @@
     }
   }
 }
+
+# Dummy group to keep satisfy references from //build.
+group("test_scripts_shared") {
+}
diff --git a/testing/SUPPRESSIONS b/testing/SUPPRESSIONS
index b3059e9..b50d3ce 100644
--- a/testing/SUPPRESSIONS
+++ b/testing/SUPPRESSIONS
@@ -1,4 +1,4 @@
-# Copyright 2016 The PDFium Authors. All rights reserved.
+# Copyright 2016 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 #
@@ -10,6 +10,7 @@
 # Column 1: platform: *, win, mac, linux
 # Column 2: v8 support: *, nov8, v8
 # Column 3: xfa support: *, noxfa, xfa
+# Column 4: rendering support: *, agg, skia
 #
 # All columns on a line on a line must match, but filenames may be repeated
 # on subsequent lines to suppress more cases.  Within each column, any one of
@@ -21,337 +22,379 @@
 #
 # Corpus tests
 #
-050_extra_m.pdf mac,win * *
-12.pdf mac * *
-1_10_watermark.pdf * * *
-1_1_textbox.pdf * * *
-1_2_typewriter.pdf * * *
-1_3_callout.pdf * * *
-1_matrix.pdf mac * *
-1m_diff_lsjdf.pdf mac * *
-1m_same_xxxx.pdf mac * *
-2_11_stamp3.pdf mac * *
-2_6_textbox.pdf * * *
-2_color_calrgb.pdf mac * *
-2_color_indexed.pdf mac * *
-3_4_textbox.pdf * * *
-3_interpolate_image.pdf mac * *
-3bigpreview.pdf mac,win * *
-4_35.pdf mac * *
-4_39.pdf mac * *
-5.1.pdf mac * *
-5.2.pdf * * *
-5.5_simple_font.pdf mac,win * *
-8.2_name_dest_f_dest.pdf mac * *
-8.2_outline.pdf mac * *
-8.3_presentation.pdf mac * *
-FRC_10_8.2.2__T8.3_original_file.pdf * * *
-FRC_11_8.2.2__T8.3_first_last_exchange.pdf * * *
-FRC_12_8.2.2__T8.3_first_outline_obj_ID.pdf * * *
-FRC_13_8.2.2__T8.3_Count_edit300.pdf * * *
-FRC_14_8.2.2__T8.3_Count_edit0.pdf * * *
-FRC_15_8.2.2__T8.3_Count_edit1.pdf * * *
-FRC_16_8.2.2__T8.3_Count_edit_1.pdf * * *
-FRC_1_8.2.2__T8.3_First_empty.pdf * * *
-FRC_2_8.2.2__T8.3_Last_empty.pdf * * *
-FRC_3.5_P__3616_Password_1.pdf * * *
-FRC_3_8.2.2_Type_empty.pdf * * *
-FRC_4_8.2.2__T8.3_Count_empty.pdf * * *
-FRC_5_8.2.2__T8.3_First_remove.pdf * * *
-FRC_6_8.2.2__T8.3_Last_remove.pdf * * *
-FRC_7_8.2.2_Type_remove.pdf * * *
-FRC_8.4.1_Annotations_M.pdf * * *
-FRC_8.4.1_Annotations_NM.pdf * * *
-FRC_8.5_Page_C_SubmitForm.pdf * * *
-FRC_8.5_Page_PI_ResetForm_Phantom.pdf * * *
-FRC_8.5_Widget_F.pdf * nov8 *
-FRC_8_8.2.2__T8.3_Count_remove.pdf * * *
-FRC_9_8.2.2__T8.3_remove_first_item.pdf * * *
-action.pdf * * *
-action_execute_a_menu_item.pdf mac * *
-action_hide_show_form.pdf mac * *
-action_on_focus.pdf mac * *
-action_open_a_file.pdf mac * *
-action_pdf_save_close.pdf mac * *
-action_reset.pdf mac * *
-action_run_javascript.pdf mac * *
-action_submit_a_form.pdf mac * *
-all_trigger_alert.pdf * * *
-all_trigger_browsefordoc.pdf * * *
-all_trigger_mailmsg.pdf * * *
-all_trigger_newdoc.pdf * * *
-all_trigger_print.pdf * * *
-all_trigger_run_js_lunchurl.pdf mac * *
-all_trigger_run_js_maildoc.pdf mac * *
-annotation_highlight_author_content.pdf mac * *
-annotation_highlight_long_content.pdf mac * *
-annotation_highlight_no_author.pdf mac * *
-app_launchurl.pdf mac * *
-appstoredescription3.1_en_updated.pdf mac * *
-bookmark.pdf * * *
-bookmarkgetcolor.pdf mac * *
-bug_0_length_line.pdf mac * *
-bug_0_width_line.pdf mac * *
-bug_440132.pdf mac * *
-bug_white_space.pdf mac * *
-calcorderindex_test.pdf * * *
-calculate_average.pdf mac * *
-calculate_order.pdf * * *
-calculate_sum_a_b_c.pdf mac * *
-calculate_validate.pdf mac * *
-calculate_validate.pdf * nov8 *
-ch_1.pdf * * *
-check_box.pdf * * *
-color.pdf mac * *
-colorspace.pdf mac * *
-colorspace_test1.pdf mac * *
-combo_box.pdf * * *
-combo_box_format.pdf mac * *
-date.pdf mac * *
-edit_transform.pdf mac * *
-en_contact.pdf mac * *
-en_diy.pdf mac * *
-en_foxit.pdf mac * *
-en_fqa2.pdf mac * *
-en_introduce.pdf mac * *
-en_tem.pdf mac * *
-en_uicase_.pdf mac * *
-event.change.pdf mac * *
-event.changeex.pdf mac * *
-event.keydown.pdf mac * *
-event.keydown_1_.pdf mac * *
-event.type_name.pdf mac * *
-event.value.pdf mac * *
-event_change.pdf mac * *
-example_001.pdf mac * *
-example_002.pdf mac,win * *
-example_003.pdf mac,win * *
-example_004.pdf mac * *
-example_005.pdf mac * *
-example_006.pdf mac,win * *
-example_007.pdf mac * *
-example_008.pdf mac * *
-example_009.pdf mac * *
-example_010.pdf mac * *
-example_011.pdf mac * *
-example_012.pdf mac * *
-example_013.pdf mac * *
-example_014.pdf mac * *
-example_015.pdf mac * *
-example_016.pdf mac * *
-example_017.pdf mac * *
-example_018.pdf mac * *
-example_019.pdf mac * *
-example_020.pdf mac * *
-example_021.pdf mac * *
-example_022.pdf mac * *
-example_023.pdf mac * *
-example_024.pdf mac * *
-example_025.pdf mac * *
-example_026.pdf mac * *
-example_027.pdf mac * *
-example_028.pdf mac * *
-example_029.pdf mac * *
-example_030.pdf mac * *
-example_031.pdf mac * *
-example_032.pdf mac * *
-example_033.pdf mac,win * *
-example_034.pdf mac * *
-example_035.pdf mac,win * *
-example_036.pdf mac,win * *
-example_037.pdf mac * *
-example_038.pdf mac,win * *
-example_039.pdf mac * *
-example_040.pdf mac * *
-example_041.pdf mac,win * *
-example_042.pdf mac * *
-example_043.pdf mac * *
-example_044.pdf mac * *
-example_045.pdf mac,win * *
-example_046.pdf mac,win * *
-example_047.pdf mac * *
-example_048.pdf mac * *
-example_049.pdf mac * *
-example_050.pdf mac * *
-example_051.pdf mac * *
-example_052.pdf mac * *
-example_053.pdf mac * *
-example_054.pdf mac * *
-example_055.pdf mac,win * *
-example_056.pdf mac * *
-example_057.pdf mac * *
-example_058.pdf mac * *
-example_059.pdf mac,win * *
-example_060.pdf mac * *
-example_061.pdf mac,win * *
-example_062.pdf mac * *
-example_063.pdf mac * *
-example_064.pdf mac * *
-example_065.pdf mac * *
-fillform.pdf * * *
-form_action_trigger.pdf * * *
-form_button_sign_url.pdf * * *
-form_combo_sign_url.pdf * * *
-form_combobox0.pdf * * *
-form_combobox_actioin_goto.pdf * * *
-form_combobox_date.pdf mac * *
-form_combobox_date.pdf * nov8 *
-form_combobox_date1.pdf * * *
-form_combobox_date2.pdf mac * *
-form_combobox_date2.pdf * nov8 *
-form_combobox_importform.pdf * * *
-form_combobox_num.pdf mac * *
-form_combobox_num.pdf * nov8 *
-form_combobox_per.pdf mac * *
-form_combobox_per.pdf * nov8 *
-form_combobox_plus.pdf mac * *
-form_combobox_plus.pdf * nov8 *
-form_combobox_product.pdf mac * *
-form_combobox_product.pdf * nov8 *
-form_combobox_resetform.pdf * * *
-form_combobox_time.pdf mac * *
-form_combobox_time.pdf * nov8 *
-form_list.pdf * * *
-form_list1.pdf * * *
-form_same.pdf mac * *
-form_text_sign_url.pdf * * *
-format_combo_box.pdf mac * *
-format_combo_box.pdf * nov8 *
-format_custom_format.pdf linux,mac * *
-format_custom_format.pdf * nov8 *
-format_custom_keystroke.pdf * * *
-format_date.pdf * nov8 *
-format_number.pdf mac * *
-format_percentage.pdf mac * *
-format_special.pdf * nov8 *
-format_text_color.pdf mac * *
-formfeild.pdf * * *
-getarray.pdf mac * *
-javascriptaction.pdf * * *
-jetman_std.pdf mac * *
-jetman_std_fixed.pdf mac * *
-js_calculate.pdf * * *
-list_box.pdf * * *
-negative.pdf mac * *
-new_certify1.pdf mac * *
-new_signature1.pdf mac * *
-new_signature2.pdf mac * *
-new_stamp4.pdf mac * *
-new_stamp5.pdf mac * *
-new_textmarkup1.pdf mac * *
-new_textmarkup1_hidden.pdf mac * *
-new_textmarkup2.pdf mac * *
-new_textmarkup2_hidden.pdf mac * *
-new_textmarkup4.pdf mac * *
-new_textmarkup4_hidden.pdf mac * *
-new_textmarkup5.pdf mac * *
-new_textmarkup5_hidden.pdf mac * *
-new_textmarkup6.pdf mac * *
-new_textmarkup7.pdf mac * *
-new_textmarkup7_hidden.pdf mac * *
-new_textmarkup8.pdf mac * *
-new_textmarkup8_hidden.pdf mac * *
-number.pdf * * *
-octest.pdf mac * *
-open_a_weblink.pdf mac * *
-path_10_jd.pdf mac * *
-path_5_pattern.pdf mac * *
-path_6_graphics4.5.5.pdf mac * *
-path_7.pdf mac * *
-path_9.pdf mac * *
-percentage.pdf mac * *
-push_button.pdf * * *
-quick_start_guide.pdf mac * *
-radio_button.pdf * * *
-run_custom_validate_script.pdf * * *
-show_1.pdf mac * *
-signature.pdf * * *
-signature_4.pdf * * *
-simplified_field_notation.pdf mac * *
-special.pdf mac * *
-submit_form.pdf mac * *
-test_app_beep.pdf * * *
-test_control.pdf * * *
-test_m.pdf mac,win * *
-text_field.pdf * * *
-text_field_font_input_decimal_point.pdf mac * *
-text_field_multiline_line_spacing.pdf mac * *
-thread_action.pdf mac * *
-time.pdf mac * *
-transformation.pdf mac * *
-transparent.pdf mac * *
-whats_new_in_v3.0.pdf mac * *
-widget_javascript.pdf mac * *
-zh_file1.pdf mac * *
-zh_function_list.pdf mac * *
-zh_shared_document.pdf mac * *
+12.pdf mac * * agg
+1_1_textbox.pdf * * * *
+1_2_typewriter.pdf * * * *
+1_3_callout.pdf * * * *
+1_matrix.pdf mac * * agg
+1m_diff_lsjdf.pdf mac * * agg
+1m_same_xxxx.pdf mac * * agg
+2_11_stamp3.pdf mac * * agg
+2_6_textbox.pdf * * * *
+2_color_calrgb.pdf mac * * agg
+2_color_indexed.pdf mac * * agg
+3_4_textbox.pdf * * * *
+3_interpolate_image.pdf mac * * agg
+3bigpreview.pdf mac * * agg
+4_35.pdf mac * * agg
+4_39.pdf mac * * agg
+5.1.pdf mac * * agg
+5.2.pdf * * * *
+5.5_simple_font.pdf mac * * agg
+8.2_name_dest_f_dest.pdf mac * * agg
+8.2_outline.pdf mac * * agg
+8.3_presentation.pdf mac * * agg
+FRC_10_8.2.2__T8.3_original_file.pdf * * * *
+FRC_11_8.2.2__T8.3_first_last_exchange.pdf * * * *
+FRC_12_8.2.2__T8.3_first_outline_obj_ID.pdf * * * *
+FRC_13_8.2.2__T8.3_Count_edit300.pdf * * * *
+FRC_14_8.2.2__T8.3_Count_edit0.pdf * * * *
+FRC_15_8.2.2__T8.3_Count_edit1.pdf * * * *
+FRC_16_8.2.2__T8.3_Count_edit_1.pdf * * * *
+FRC_1_8.2.2__T8.3_First_empty.pdf * * * *
+FRC_2_8.2.2__T8.3_Last_empty.pdf * * * *
+FRC_3.5_P__3616_Password_1.pdf * * * *
+FRC_3_8.2.2_Type_empty.pdf * * * *
+FRC_4_8.2.2__T8.3_Count_empty.pdf * * * *
+FRC_5_8.2.2__T8.3_First_remove.pdf * * * *
+FRC_6_8.2.2__T8.3_Last_remove.pdf * * * *
+FRC_7_8.2.2_Type_remove.pdf * * * *
+FRC_8.4.1_Annotations_M.pdf * * * *
+FRC_8.4.1_Annotations_NM.pdf * * * *
+FRC_8.5_Page_C_SubmitForm.pdf * * * *
+FRC_8.5_Page_PI_ResetForm_Phantom.pdf * * * *
+FRC_8.5_Widget_F.pdf * nov8 * *
+FRC_8_8.2.2__T8.3_Count_remove.pdf * * * *
+FRC_9_8.2.2__T8.3_remove_first_item.pdf * * * *
+action.pdf * * * *
+action_execute_a_menu_item.pdf mac * * agg
+action_hide_show_form.pdf mac * * agg
+action_on_focus.pdf mac * * agg
+action_open_a_file.pdf mac * * agg
+action_pdf_save_close.pdf mac * * agg
+action_reset.pdf mac * * agg
+action_run_javascript.pdf mac * * agg
+action_submit_a_form.pdf mac * * agg
+all_trigger_alert.pdf * * * *
+all_trigger_mailmsg.pdf * * * *
+all_trigger_print.pdf * * * *
+all_trigger_run_js_lunchurl.pdf mac * * agg
+all_trigger_run_js_maildoc.pdf mac * * agg
+annotation_highlight_author_content.pdf mac * * agg
+annotation_highlight_long_content.pdf mac * * agg
+annotation_highlight_no_author.pdf mac * * agg
+app_launchurl.pdf mac * * agg
+appstoredescription3.1_en_updated.pdf mac * * agg
+bookmark.pdf * * * *
+bookmarkgetcolor.pdf mac * * agg
+bug_0_length_line.pdf mac * * agg
+
+# TODO(pdfium:1812): Remove after associated bug is fixed
+bug_0_length_line.pdf * * * skia
+
+bug_0_width_line.pdf mac * * agg
+bug_440132.pdf mac * * agg
+bug_white_space.pdf mac * * agg
+calcorderindex_test.pdf * * * *
+calculate_average.pdf mac * * agg
+calculate_order.pdf * * * *
+calculate_sum_a_b_c.pdf mac * * agg
+calculate_validate.pdf mac * * agg
+calculate_validate.pdf * nov8 * *
+ch_1.pdf * * * *
+check_box.pdf * * * *
+color.pdf mac * * agg
+colorspace.pdf mac * * agg
+colorspace_test1.pdf mac * * agg
+combo_box.pdf * * * *
+combo_box_format.pdf mac * * agg
+date.pdf mac * * agg
+edit_transform.pdf mac * * agg
+en_contact.pdf mac * * agg
+en_diy.pdf mac * * agg
+en_foxit.pdf mac * * agg
+en_fqa2.pdf mac * * agg
+en_introduce.pdf mac * * agg
+en_tem.pdf mac * * agg
+en_uicase_.pdf mac * * agg
+event.change.pdf mac * * agg
+event.changeex.pdf mac * * agg
+event.keydown.pdf mac * * agg
+event.keydown_1_.pdf mac * * agg
+event.type_name.pdf mac * * agg
+event.value.pdf mac * * agg
+event_change.pdf mac * * agg
+example_001.pdf mac * * agg
+example_002.pdf mac * * agg
+example_003.pdf mac * * agg
+example_004.pdf mac * * agg
+example_005.pdf mac * * agg
+example_006.pdf mac * * agg
+example_007.pdf mac * * agg
+example_008.pdf mac * * agg
+example_009.pdf mac * * agg
+example_010.pdf mac * * agg
+example_011.pdf mac * * agg
+example_012.pdf mac * * agg
+example_013.pdf mac * * agg
+example_014.pdf mac * * agg
+example_015.pdf mac * * agg
+example_016.pdf mac * * agg
+example_017.pdf mac * * agg
+example_018.pdf mac * * agg
+example_019.pdf mac * * agg
+example_020.pdf mac * * agg
+example_021.pdf mac * * agg
+example_022.pdf mac * * agg
+example_023.pdf mac * * agg
+example_024.pdf mac * * agg
+example_025.pdf mac * * agg
+example_026.pdf mac * * agg
+example_027.pdf mac * * agg
+example_028.pdf mac * * agg
+example_029.pdf mac * * agg
+example_030.pdf mac * * agg
+example_031.pdf mac * * agg
+example_032.pdf mac * * agg
+example_033.pdf mac * * agg
+example_034.pdf mac * * agg
+example_035.pdf mac * * agg
+example_036.pdf mac * * agg
+example_037.pdf mac * * agg
+example_038.pdf mac * * agg
+example_039.pdf mac * * agg
+example_040.pdf mac * * agg
+example_041.pdf mac * * agg
+example_042.pdf mac * * agg
+example_043.pdf mac * * agg
+example_044.pdf mac * * agg
+example_045.pdf mac * * agg
+example_046.pdf mac * * agg
+example_047.pdf mac * * agg
+example_048.pdf mac * * agg
+example_049.pdf mac * * agg
+example_050.pdf mac * * agg
+example_051.pdf mac * * agg
+example_052.pdf mac * * agg
+example_053.pdf mac * * agg
+example_054.pdf mac * * agg
+example_055.pdf mac * * agg
+example_056.pdf mac * * agg
+example_057.pdf mac * * agg
+example_058.pdf mac * * agg
+example_059.pdf mac * * agg
+example_060.pdf mac * * agg
+example_061.pdf mac * * agg
+example_062.pdf mac * * agg
+example_063.pdf mac * * agg
+example_064.pdf mac * * agg
+example_065.pdf mac * * agg
+fillform.pdf * * * *
+form_action_trigger.pdf * * * *
+form_button_sign_url.pdf * * * *
+form_combo_sign_url.pdf * * * *
+form_combobox0.pdf * * * *
+form_combobox_actioin_goto.pdf * * * *
+form_combobox_date.pdf mac * * agg
+form_combobox_date.pdf * nov8 * *
+form_combobox_date1.pdf * * * *
+form_combobox_date2.pdf mac * * agg
+form_combobox_date2.pdf * nov8 * *
+form_combobox_importform.pdf * * * *
+form_combobox_num.pdf mac * * agg
+form_combobox_num.pdf * nov8 * *
+form_combobox_per.pdf mac * * agg
+form_combobox_per.pdf * nov8 * *
+form_combobox_plus.pdf mac * * agg
+form_combobox_plus.pdf * nov8 * *
+form_combobox_product.pdf mac * * agg
+form_combobox_product.pdf * nov8 * *
+form_combobox_resetform.pdf * * * *
+form_combobox_time.pdf mac * * agg
+form_combobox_time.pdf * nov8 * *
+form_list.pdf * * * *
+form_list1.pdf * * * *
+form_same.pdf mac * * agg
+form_text_sign_url.pdf * * * *
+format_combo_box.pdf mac * * agg
+format_combo_box.pdf * nov8 * *
+format_custom_format.pdf * nov8 * *
+format_custom_keystroke.pdf * * * *
+format_date.pdf * nov8 * *
+format_number.pdf mac * * agg
+format_percentage.pdf mac * * agg
+format_special.pdf * nov8 * *
+format_text_color.pdf mac * * agg
+formfield.pdf * * * *
+getarray.pdf mac * * agg
+
+# TODO(pdfium:489): Remove after associated bug is fixed.
+gradient_many_stops.pdf * * * agg
+
+javascriptaction.pdf * * * *
+jetman_std.pdf mac * * agg
+jetman_std_fixed.pdf mac * * agg
+js_calculate.pdf * * * *
+list_box.pdf * * * *
+negative.pdf mac * * agg
+new_certify1.pdf mac * * agg
+new_signature1.pdf mac * * agg
+new_signature2.pdf mac * * agg
+new_stamp4.pdf mac * * agg
+new_stamp5.pdf mac * * agg
+new_textmarkup1.pdf mac * * agg
+new_textmarkup1_hidden.pdf mac * * agg
+new_textmarkup2.pdf mac * * agg
+new_textmarkup2_hidden.pdf mac * * agg
+new_textmarkup4.pdf mac * * agg
+new_textmarkup4_hidden.pdf mac * * agg
+new_textmarkup5.pdf mac * * agg
+new_textmarkup5_hidden.pdf mac * * agg
+new_textmarkup6.pdf mac * * agg
+new_textmarkup7.pdf mac * * agg
+new_textmarkup7_hidden.pdf mac * * agg
+new_textmarkup8.pdf mac * * agg
+new_textmarkup8_hidden.pdf mac * * agg
+number.pdf * * * *
+octest.pdf mac * * agg
+open_a_weblink.pdf mac * * agg
+path_10_jd.pdf mac * * agg
+path_5_pattern.pdf mac * * agg
+path_6_graphics4.5.5.pdf mac * * agg
+path_7.pdf mac * * agg
+path_9.pdf mac * * agg
+percentage.pdf mac * * agg
+push_button.pdf * * * *
+quick_start_guide.pdf mac * * agg
+radio_button.pdf * * * *
+run_custom_validate_script.pdf * * * *
+show_1.pdf mac * * agg
+signature.pdf * * * *
+signature_4.pdf * * * *
+simplified_field_notation.pdf mac * * agg
+special.pdf mac * * agg
+submit_form.pdf mac * * agg
+test_app_beep.pdf * * * *
+test_control.pdf * * * *
+text_field.pdf * * * *
+text_field_font_input_decimal_point.pdf mac * * agg
+text_field_multiline_line_spacing.pdf mac * * agg
+thread_action.pdf mac * * agg
+time.pdf mac * * agg
+transformation.pdf mac * * agg
+transparent.pdf mac * * agg
+whats_new_in_v3.0.pdf mac * * agg
+widget_javascript.pdf mac * * agg
+
+# TODO(pdfium:1990): Remove after associated bug is fixed
+xfermodes2.pdf * * * agg
+
+zh_file1.pdf mac * * agg
+zh_function_list.pdf mac * * agg
+zh_shared_document.pdf mac * * agg
 
 # TODO(hnakashima): These might never have been run. Go over them and fix.
 
-FRC_8.5_E&X.pdf * * *
-FRC_8.5_O&PO_GoToE.pdf * * *
-FRC_8.5_OpenAction&O_URI.pdf * * *
-FRC_8.5_PC&C_GoToE_T_T.pdf * * *
-FRC_8.5_PC_GoToE_T_R&P&A.pdf * * *
-FRC_8.5_PO_GoToE_T_R&N.pdf * * *
+FRC_8.5_E&X.pdf * * * *
+FRC_8.5_O&PO_GoToE.pdf * * * *
+FRC_8.5_OpenAction&O_URI.pdf * * * *
+FRC_8.5_PC&C_GoToE_T_T.pdf * * * *
+FRC_8.5_PC_GoToE_T_R&P&A.pdf * * * *
+FRC_8.5_PO_GoToE_T_R&N.pdf * * * *
 
 # xfa_specific
 
-Choose.pdf * * *
-data_binding.pdf * * *
+Choose.pdf * * * *
+data_binding.pdf * * * *
 # TODO(npm): Add proper evt for MouseEvents.
-MouseEvents_enter.pdf * * *
-MouseEvents_exit.pdf * * *
-Oneof3.pdf * * *
-Sum.pdf * * *
-TimeField.pdf win,linux * *
-Test_CheckBox.pdf * * *
-Test_DateField_locale_zh_HK.pdf mac,win * *
+MouseEvents_enter.pdf * * * *
+MouseEvents_exit.pdf * * * *
+Oneof3.pdf * * * *
+Sum.pdf * * * *
+Test_CheckBox.pdf * * * *
 
 #
 # JavaScript tests
 #
-bug_679642.in * * noxfa
-bug_679643.in * * noxfa
-bug_735912.in * * noxfa
+bug_679642.in * * noxfa *
+bug_679643.in * * noxfa *
+bug_735912.in * * noxfa *
+
+# JS tests in nov8 mode expect empty results. This one will
+# not be empty as the callback is not js-based.
+named_action.in * nov8 * *
 
 # xfa_specific
 
 # TODO(pdfium:1106): Remove after associated bug is fixed
-resolve_nodes_1.pdf * * *
+resolve_nodes_1.pdf * * * *
 
 #
 # Pixel tests
 #
-bug_492.in * nov8 *
 
-# TODO(pdfium:304): Remove after associated bug is fixed
-bug_304.pdf * * *
+# TODO(pdfium:1747): Remove after associated bug is fixed
+bug_1258634.in * * * *
 
 # TODO(pdfium:1331): Remove after associated bug is fixed
-bug_1331.in * * *
+bug_1331.in * * * *
 
-# TODO(pdfium:1461): Remove after associated bug is fixed
-bug_1402.in win * *
+# TODO(chromium:1356149): Remove after associated bug is fixed
+bug_1356149.in mac * * agg
 
 # TODO(pdfium:1457): Remove after associated bug is fixed
-bug_1457.in * * *
+bug_1457.in * * * *
+
+# TODO(pdfium:1519): Remove after associated bug is fixed
+bug_1519.in * * * *
+
+# TODO(pdfium:1571): Remove after associated bug is fixed
+bug_1571.in * * * *
+
+# TODO(pdfium:1723): Remove after associated bug is fixed
+bug_1723.in * * * *
+
+# TODO(pdfium:1972): Remove after associated bug is fixed
+bug_1972_1.in * * * agg
+bug_1972_2.in * * * agg
+bug_1972_3.in * * * agg
+
+# TODO(pdfium:1973): Remove after associated bug is fixed
+bug_1973.in * * * *
+
+# TODO(pdfium:2001): Remove after associated bug is fixed
+bug_2001.pdf * * * *
+
+# TODO(chromium:237527): Remove after associated bug is fixed
+bug_237527_1.in * * * *
 
 # TODO(chromium:451366): Remove after associated bug is fixed
-bug_451366.in * * *
+bug_451366.in * * * agg
 
-# TODO(chromium:1012369): Remove after associated bug is fixed
-bug_1012369.in * * *
+# TODO(pdfium:492): Remove after associated bug is fixed
+bug_492.in * nov8 * *
+
+# TODO(chromium:725555, skia:9265): Remove after associated bug is fixed
+bug_725555.in * * * skia
+
+# TODO(chromium:983289): Remove after associated bug is fixed
+bug_983289.in * * * agg
+
+# TODO(pdfium:1747): Remove after associated bug is fixed
+jpxdecode.in * * * *
+jpxdecode_without_bitspercomponent.in * * * *
+jpxdecode_without_colorspace.in * * * *
+
+# TODO(chromium:1028991): Remove after associated bug is fixed
+reset_button.in * * * *
 
 # xfa_specific
 
+# TODO(pdfium:1095): Remove after associated bug is fixed
+bug_997412.in win * * *
 # TODO(pdfium:1107): Remove after associated bug is fixed
-standard_symbols.pdf * * *
-# TODO(pdfium:1168): Remove after associated bug is fixed
-xfa_bmp_image.in * * *
+standard_symbols.pdf * * * *
 # TODO(pdfium:1095): Remove after associated bug is fixed
-xfa_example.in win * *
-# TODO(pdfium:1167): Remove after associated bug is fixed
-xfa_gif_image.in * * *
+xfa_example.in win * * *
 # TODO(pdfium:1095): Remove after associated bug is fixed
-xfa_textfield.in win * *
+xfa_textfield.in win * * *
diff --git a/testing/SUPPRESSIONS_EXACT_MATCHING b/testing/SUPPRESSIONS_EXACT_MATCHING
new file mode 100644
index 0000000..7c260d1
--- /dev/null
+++ b/testing/SUPPRESSIONS_EXACT_MATCHING
@@ -0,0 +1,28 @@
+# Copyright 2023 The PDFium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# List of tests to use fuzzy instead of exact matching, one per line.
+# There are four space-separated columns per line
+# Each column (except column 0) can contain a comma-separated list of values.
+#
+# Column 0: test file name
+# Column 1: platform: *, win, mac, linux
+# Column 2: v8 support: *, nov8, v8
+# Column 3: xfa support: *, noxfa, xfa
+# Column 4: rendering support: *, agg, skia
+#
+# All columns on a line on a line must match, but filenames may be repeated
+# on subsequent lines to suppress more cases.  Within each column, any one of
+# the comma-separated values must match in order for the colum to "match".
+# The filenames and keywords are case-sensitive.
+#
+# Try to keep the file alphabetized within each category of test.
+
+#
+# Corpus tests
+#
+
+# Device-specific ColorBurn differences (see pdfium:1959).
+xfermodes2.pdf * * * skia
+xfermodes3.pdf * * * skia
diff --git a/testing/SUPPRESSIONS_IMAGE_DIFF b/testing/SUPPRESSIONS_IMAGE_DIFF
index cdb2d66..e501756 100644
--- a/testing/SUPPRESSIONS_IMAGE_DIFF
+++ b/testing/SUPPRESSIONS_IMAGE_DIFF
@@ -1,4 +1,4 @@
-# Copyright 2017 The PDFium Authors. All rights reserved.
+# Copyright 2017 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 #
@@ -10,6 +10,7 @@
 # Column 1: platform: *, win, mac, linux
 # Column 2: v8 support: *, nov8, v8
 # Column 3: xfa support: *, noxfa, xfa
+# Column 4: rendering support: *, agg, skia
 #
 # All columns on a line on a line must match, but filenames may be repeated
 # on subsequent lines to suppress more cases.  Within each column, any one of
@@ -21,12 +22,12 @@
 #
 # Corpus tests
 #
-FRC_3.5_CF_Strf_stmf_DefaultCryptFilter.pdf * * *
-FRC_3.5_EncryptMetadata_T.pdf * * *
-FRC_3.5_Encrypt_is_damage.pdf * * *
-FRC_3.5_Filter_PubSec_SubFilter_s5.pdf * * *
-FRC_3.5_Filter_PubSec_Sub_SubFilter_s4.pdf * * *
-MouseEvents.pdf * * *
-Oneof.pdf * * *
-bug_651304.pdf * * *
-outline.pdf * * *
+FRC_3.5_CF_Strf_stmf_DefaultCryptFilter.pdf * * * *
+FRC_3.5_EncryptMetadata_T.pdf * * * *
+FRC_3.5_Encrypt_is_damage.pdf * * * *
+FRC_3.5_Filter_PubSec_SubFilter_s5.pdf * * * *
+FRC_3.5_Filter_PubSec_Sub_SubFilter_s4.pdf * * * *
+MouseEvents.pdf * * * *
+Oneof.pdf * * * *
+bug_651304.pdf * * * *
+outline.pdf * * * *
diff --git a/testing/command_line_helpers.cpp b/testing/command_line_helpers.cpp
new file mode 100644
index 0000000..758f278
--- /dev/null
+++ b/testing/command_line_helpers.cpp
@@ -0,0 +1,23 @@
+// Copyright 2022 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/command_line_helpers.h"
+
+bool ParseSwitchKeyValue(const std::string& arg,
+                         const std::string& key,
+                         std::string* value) {
+  if (arg.size() <= key.size() || arg.compare(0, key.size(), key) != 0)
+    return false;
+
+  *value = arg.substr(key.size());
+  return true;
+}
+
+FPDF_RENDERER_TYPE GetDefaultRendererType() {
+#if defined(_SKIA_SUPPORT_)
+  return FPDF_RENDERERTYPE_SKIA;
+#else
+  return FPDF_RENDERERTYPE_AGG;
+#endif
+}
diff --git a/testing/command_line_helpers.h b/testing/command_line_helpers.h
new file mode 100644
index 0000000..cc134cb
--- /dev/null
+++ b/testing/command_line_helpers.h
@@ -0,0 +1,23 @@
+// Copyright 2022 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_COMMAND_LINE_HELPERS_H_
+#define TESTING_COMMAND_LINE_HELPERS_H_
+
+#include <string>
+
+#include "public/fpdfview.h"
+
+// Extract the value from a keyed command line argument.
+// `arg` is expected to be "--key=value", and `key` is "--key=".
+bool ParseSwitchKeyValue(const std::string& arg,
+                         const std::string& key,
+                         std::string* value);
+
+// Identifies the compile-time default 2D graphics library to use for rendering
+// to FPDF_BITMAPs. Used as part of support to override the renderer at runtime
+// based upon command line options.
+FPDF_RENDERER_TYPE GetDefaultRendererType();
+
+#endif  // TESTING_COMMAND_LINE_HELPERS_H_
diff --git a/testing/embedder_test.cpp b/testing/embedder_test.cpp
index 9bf4a18..366088d 100644
--- a/testing/embedder_test.cpp
+++ b/testing/embedder_test.cpp
@@ -1,13 +1,10 @@
-// Copyright 2015 PDFium Authors. All rights reserved.
+// Copyright 2015 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "testing/embedder_test.h"
 
-#include <limits.h>
-
-#include <list>
-#include <map>
+#include <algorithm>
 #include <memory>
 #include <string>
 #include <utility>
@@ -19,20 +16,17 @@
 #include "public/fpdf_edit.h"
 #include "public/fpdf_text.h"
 #include "public/fpdfview.h"
+#include "testing/embedder_test_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/test_loader.h"
 #include "testing/utils/bitmap_saver.h"
 #include "testing/utils/file_util.h"
 #include "testing/utils/hash.h"
 #include "testing/utils/path_service.h"
-#include "third_party/base/logging.h"
-#include "third_party/base/ptr_util.h"
-#include "third_party/base/stl_util.h"
-
-#ifdef PDF_ENABLE_V8
-#include "v8/include/v8-platform.h"
-#include "v8/include/v8.h"
-#endif  // PDF_ENABLE_V8
+#include "third_party/base/check.h"
+#include "third_party/base/containers/contains.h"
+#include "third_party/base/notreached.h"
+#include "third_party/base/numerics/safe_conversions.h"
 
 namespace {
 
@@ -40,7 +34,7 @@
   return EmbedderTest::BytesPerPixelForFormat(FPDFBitmap_GetFormat(bitmap));
 }
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
 int CALLBACK GetRecordProc(HDC hdc,
                            HANDLETABLE* handle_table,
                            const ENHMETARECORD* record,
@@ -50,12 +44,224 @@
   records.push_back(record);
   return 1;
 }
-#endif  // defined(OS_WIN)
+#endif  // BUILDFLAG(IS_WIN)
+
+// These "jump" into the delegate to do actual testing.
+void UnsupportedHandlerTrampoline(UNSUPPORT_INFO* info, int type) {
+  auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
+  delegate->UnsupportedHandler(type);
+}
+
+int AlertTrampoline(IPDF_JSPLATFORM* platform,
+                    FPDF_WIDESTRING message,
+                    FPDF_WIDESTRING title,
+                    int type,
+                    int icon) {
+  auto* delegate = static_cast<EmbedderTest*>(platform)->GetDelegate();
+  return delegate->Alert(message, title, type, icon);
+}
+
+int SetTimerTrampoline(FPDF_FORMFILLINFO* info, int msecs, TimerCallback fn) {
+  auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
+  return delegate->SetTimer(msecs, fn);
+}
+
+void KillTimerTrampoline(FPDF_FORMFILLINFO* info, int id) {
+  auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
+  return delegate->KillTimer(id);
+}
+
+FPDF_PAGE GetPageTrampoline(FPDF_FORMFILLINFO* info,
+                            FPDF_DOCUMENT document,
+                            int page_index) {
+  auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
+  return delegate->GetPage(info, document, page_index);
+}
+
+void DoURIActionTrampoline(FPDF_FORMFILLINFO* info, FPDF_BYTESTRING uri) {
+  auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
+  return delegate->DoURIAction(uri);
+}
+
+void DoGoToActionTrampoline(FPDF_FORMFILLINFO* info,
+                            int page_index,
+                            int zoom_mode,
+                            float* pos_array,
+                            int array_size) {
+  auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
+  return delegate->DoGoToAction(info, page_index, zoom_mode, pos_array,
+                                array_size);
+}
+
+void OnFocusChangeTrampoline(FPDF_FORMFILLINFO* info,
+                             FPDF_ANNOTATION annot,
+                             int page_index) {
+  auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
+  return delegate->OnFocusChange(info, annot, page_index);
+}
+
+void DoURIActionWithKeyboardModifierTrampoline(FPDF_FORMFILLINFO* info,
+                                               FPDF_BYTESTRING uri,
+                                               int modifiers) {
+  auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
+  return delegate->DoURIActionWithKeyboardModifier(info, uri, modifiers);
+}
+
+// These do nothing (but must return a reasonable default value).
+void InvalidateStub(FPDF_FORMFILLINFO* pThis,
+                    FPDF_PAGE page,
+                    double left,
+                    double top,
+                    double right,
+                    double bottom) {}
+
+void OutputSelectedRectStub(FPDF_FORMFILLINFO* pThis,
+                            FPDF_PAGE page,
+                            double left,
+                            double top,
+                            double right,
+                            double bottom) {}
+
+void SetCursorStub(FPDF_FORMFILLINFO* pThis, int nCursorType) {}
+
+FPDF_SYSTEMTIME GetLocalTimeStub(FPDF_FORMFILLINFO* pThis) {
+  return {122, 11, 6, 28, 12, 59, 59, 500};
+}
+
+void OnChangeStub(FPDF_FORMFILLINFO* pThis) {}
+
+FPDF_PAGE GetCurrentPageStub(FPDF_FORMFILLINFO* pThis, FPDF_DOCUMENT document) {
+  return GetPageTrampoline(pThis, document, 0);
+}
+
+int GetRotationStub(FPDF_FORMFILLINFO* pThis, FPDF_PAGE page) {
+  return 0;
+}
+
+void ExecuteNamedActionStub(FPDF_FORMFILLINFO* pThis, FPDF_BYTESTRING name) {}
+
+void SetTextFieldFocusStub(FPDF_FORMFILLINFO* pThis,
+                           FPDF_WIDESTRING value,
+                           FPDF_DWORD valueLen,
+                           FPDF_BOOL is_focus) {}
+
+#ifdef PDF_ENABLE_XFA
+void DisplayCaretStub(FPDF_FORMFILLINFO* pThis,
+                      FPDF_PAGE page,
+                      FPDF_BOOL bVisible,
+                      double left,
+                      double top,
+                      double right,
+                      double bottom) {}
+
+int GetCurrentPageIndexStub(FPDF_FORMFILLINFO* pThis, FPDF_DOCUMENT document) {
+  return 0;
+}
+
+void SetCurrentPageStub(FPDF_FORMFILLINFO* pThis,
+                        FPDF_DOCUMENT document,
+                        int iCurPage) {}
+
+void GotoURLStub(FPDF_FORMFILLINFO* pThis,
+                 FPDF_DOCUMENT document,
+                 FPDF_WIDESTRING wsURL) {}
+
+void GetPageViewRectStub(FPDF_FORMFILLINFO* pThis,
+                         FPDF_PAGE page,
+                         double* left,
+                         double* top,
+                         double* right,
+                         double* bottom) {
+  *left = 0.0;
+  *top = 0.0;
+  *right = 512.0;
+  *bottom = 512.0;
+}
+
+void PageEventStub(FPDF_FORMFILLINFO* pThis,
+                   int page_count,
+                   FPDF_DWORD event_type) {}
+
+FPDF_BOOL PopupMenuStub(FPDF_FORMFILLINFO* pThis,
+                        FPDF_PAGE page,
+                        FPDF_WIDGET hWidget,
+                        int menuFlag,
+                        float x,
+                        float y) {
+  return true;
+}
+
+FPDF_FILEHANDLER* OpenFileStub(FPDF_FORMFILLINFO* pThis,
+                               int fileFlag,
+                               FPDF_WIDESTRING wsURL,
+                               const char* mode) {
+  return nullptr;
+}
+
+void EmailToStub(FPDF_FORMFILLINFO* pThis,
+                 FPDF_FILEHANDLER* fileHandler,
+                 FPDF_WIDESTRING pTo,
+                 FPDF_WIDESTRING pSubject,
+                 FPDF_WIDESTRING pCC,
+                 FPDF_WIDESTRING pBcc,
+                 FPDF_WIDESTRING pMsg) {}
+
+void UploadToStub(FPDF_FORMFILLINFO* pThis,
+                  FPDF_FILEHANDLER* fileHandler,
+                  int fileFlag,
+                  FPDF_WIDESTRING uploadTo) {}
+
+int GetPlatformStub(FPDF_FORMFILLINFO* pThis, void* platform, int length) {
+  return 0;
+}
+
+int GetLanguageStub(FPDF_FORMFILLINFO* pThis, void* language, int length) {
+  return 0;
+}
+
+FPDF_FILEHANDLER* DownloadFromURLStub(FPDF_FORMFILLINFO* pThis,
+                                      FPDF_WIDESTRING URL) {
+  static const char kString[] = "<body>secrets</body>";
+  static FPDF_FILEHANDLER kFakeFileHandler = {
+      nullptr,
+      [](void*) -> void {},
+      [](void*) -> FPDF_DWORD { return sizeof(kString); },
+      [](void*, FPDF_DWORD off, void* buffer, FPDF_DWORD size) -> FPDF_RESULT {
+        memcpy(buffer, kString, std::min<size_t>(size, sizeof(kString)));
+        return 0;
+      },
+      [](void*, FPDF_DWORD, const void*, FPDF_DWORD) -> FPDF_RESULT {
+        return -1;
+      },
+      [](void*) -> FPDF_RESULT { return 0; },
+      [](void*, FPDF_DWORD) -> FPDF_RESULT { return 0; }};
+  return &kFakeFileHandler;
+}
+
+FPDF_BOOL PostRequestURLStub(FPDF_FORMFILLINFO* pThis,
+                             FPDF_WIDESTRING wsURL,
+                             FPDF_WIDESTRING wsData,
+                             FPDF_WIDESTRING wsContentType,
+                             FPDF_WIDESTRING wsEncode,
+                             FPDF_WIDESTRING wsHeader,
+                             FPDF_BSTR* response) {
+  const char kString[] = "p\0o\0s\0t\0e\0d\0";
+  FPDF_BStr_Set(response, kString, sizeof(kString) - 1);
+  return true;
+}
+
+FPDF_BOOL PutRequestURLStub(FPDF_FORMFILLINFO* pThis,
+                            FPDF_WIDESTRING wsURL,
+                            FPDF_WIDESTRING wsData,
+                            FPDF_WIDESTRING wsEncode) {
+  return true;
+}
+#endif  // PDF_ENABLE_XFA
 
 }  // namespace
 
 EmbedderTest::EmbedderTest()
-    : default_delegate_(pdfium::MakeUnique<EmbedderTest::Delegate>()),
+    : default_delegate_(std::make_unique<EmbedderTest::Delegate>()),
       delegate_(default_delegate_.get()) {
   FPDF_FILEWRITE::version = 1;
   FPDF_FILEWRITE::WriteBlock = WriteBlockCallback;
@@ -64,52 +270,31 @@
 EmbedderTest::~EmbedderTest() = default;
 
 void EmbedderTest::SetUp() {
-  FPDF_LIBRARY_CONFIG config;
-  config.version = 2;
-  config.m_pUserFontPaths = nullptr;
-  config.m_v8EmbedderSlot = 0;
-  config.m_pIsolate = external_isolate_;
-  FPDF_InitLibraryWithConfig(&config);
-
   UNSUPPORT_INFO* info = static_cast<UNSUPPORT_INFO*>(this);
   memset(info, 0, sizeof(UNSUPPORT_INFO));
   info->version = 1;
   info->FSDK_UnSupport_Handler = UnsupportedHandlerTrampoline;
   FSDK_SetUnSpObjProcessHandler(info);
-
-  saved_document_ = nullptr;
 }
 
 void EmbedderTest::TearDown() {
   // Use an EXPECT_EQ() here and continue to let TearDown() finish as cleanly as
-  // possible. This can fail when an ASSERT test fails in a test case.
+  // possible. This can fail when an DCHECK test fails in a test case.
   EXPECT_EQ(0U, page_map_.size());
   EXPECT_EQ(0U, saved_page_map_.size());
-
-  if (document_) {
-    FORM_DoDocumentAAction(form_handle_, FPDFDOC_AACTION_WC);
+  if (document())
     CloseDocument();
-  }
-
-  FPDFAvail_Destroy(avail_);
-  FPDF_DestroyLibrary();
-  loader_.reset();
 }
 
-#ifdef PDF_ENABLE_V8
-void EmbedderTest::SetExternalIsolate(void* isolate) {
-  external_isolate_ = static_cast<v8::Isolate*>(isolate);
+void EmbedderTest::CreateEmptyDocument() {
+  CreateEmptyDocumentWithoutFormFillEnvironment();
+  form_handle_.reset(SetupFormFillEnvironment(
+      document(), JavaScriptOption::kEnableJavaScript));
 }
-#endif  // PDF_ENABLE_V8
 
-bool EmbedderTest::CreateEmptyDocument() {
-  document_ = FPDF_CreateNewDocument();
-  if (!document_)
-    return false;
-
-  form_handle_ =
-      SetupFormFillEnvironment(document_, JavaScriptOption::kEnableJavaScript);
-  return true;
+void EmbedderTest::CreateEmptyDocumentWithoutFormFillEnvironment() {
+  document_.reset(FPDF_CreateNewDocument());
+  DCHECK(document_);
 }
 
 bool EmbedderTest::OpenDocument(const std::string& filename) {
@@ -150,7 +335,7 @@
     return false;
 
   EXPECT_TRUE(!loader_);
-  loader_ = pdfium::MakeUnique<TestLoader>(
+  loader_ = std::make_unique<TestLoader>(
       pdfium::make_span(file_contents_.get(), file_length_));
 
   memset(&file_access_, 0, sizeof(file_access_));
@@ -158,7 +343,7 @@
   file_access_.m_GetBlock = TestLoader::GetBlock;
   file_access_.m_Param = loader_.get();
 
-  fake_file_access_ = pdfium::MakeUnique<FakeFileAccess>(&file_access_);
+  fake_file_access_ = std::make_unique<FakeFileAccess>(&file_access_);
   return OpenDocumentHelper(password, linearize_option, javascript_option,
                             fake_file_access_.get(), &document_, &avail_,
                             &form_handle_);
@@ -168,42 +353,45 @@
                                       LinearizeOption linearize_option,
                                       JavaScriptOption javascript_option,
                                       FakeFileAccess* network_simulator,
-                                      FPDF_DOCUMENT* document,
-                                      FPDF_AVAIL* avail,
-                                      FPDF_FORMHANDLE* form_handle) {
+                                      ScopedFPDFDocument* document,
+                                      ScopedFPDFAvail* avail,
+                                      ScopedFPDFFormHandle* form_handle) {
   network_simulator->AddSegment(0, 1024);
   network_simulator->SetRequestedDataAvailable();
-  *avail = FPDFAvail_Create(network_simulator->GetFileAvail(),
-                            network_simulator->GetFileAccess());
-  if (FPDFAvail_IsLinearized(*avail) == PDF_LINEARIZED) {
+  avail->reset(FPDFAvail_Create(network_simulator->GetFileAvail(),
+                                network_simulator->GetFileAccess()));
+  FPDF_AVAIL avail_ptr = avail->get();
+  FPDF_DOCUMENT document_ptr = nullptr;
+  if (FPDFAvail_IsLinearized(avail_ptr) == PDF_LINEARIZED) {
     int32_t nRet = PDF_DATA_NOTAVAIL;
     while (nRet == PDF_DATA_NOTAVAIL) {
       network_simulator->SetRequestedDataAvailable();
-      nRet =
-          FPDFAvail_IsDocAvail(*avail, network_simulator->GetDownloadHints());
+      nRet = FPDFAvail_IsDocAvail(avail_ptr,
+                                  network_simulator->GetDownloadHints());
     }
     if (nRet == PDF_DATA_ERROR)
       return false;
 
-    *document = FPDFAvail_GetDocument(*avail, password);
-    if (!*document)
+    document->reset(FPDFAvail_GetDocument(avail_ptr, password));
+    document_ptr = document->get();
+    if (!document_ptr)
       return false;
 
     nRet = PDF_DATA_NOTAVAIL;
     while (nRet == PDF_DATA_NOTAVAIL) {
       network_simulator->SetRequestedDataAvailable();
-      nRet =
-          FPDFAvail_IsFormAvail(*avail, network_simulator->GetDownloadHints());
+      nRet = FPDFAvail_IsFormAvail(avail_ptr,
+                                   network_simulator->GetDownloadHints());
     }
     if (nRet == PDF_FORM_ERROR)
       return false;
 
-    int page_count = FPDF_GetPageCount(*document);
+    int page_count = FPDF_GetPageCount(document_ptr);
     for (int i = 0; i < page_count; ++i) {
       nRet = PDF_DATA_NOTAVAIL;
       while (nRet == PDF_DATA_NOTAVAIL) {
         network_simulator->SetRequestedDataAvailable();
-        nRet = FPDFAvail_IsPageAvail(*avail, i,
+        nRet = FPDFAvail_IsPageAvail(avail_ptr, i,
                                      network_simulator->GetDownloadHints());
       }
       if (nRet == PDF_DATA_ERROR)
@@ -213,27 +401,31 @@
     if (linearize_option == LinearizeOption::kMustLinearize)
       return false;
     network_simulator->SetWholeFileAvailable();
-    *document =
-        FPDF_LoadCustomDocument(network_simulator->GetFileAccess(), password);
-    if (!*document)
+    document->reset(
+        FPDF_LoadCustomDocument(network_simulator->GetFileAccess(), password));
+    document_ptr = document->get();
+    if (!document_ptr)
       return false;
   }
-  *form_handle = SetupFormFillEnvironment(*document, javascript_option);
+  form_handle->reset(SetupFormFillEnvironment(document_ptr, javascript_option));
 
-  int doc_type = FPDF_GetFormType(*document);
+  int doc_type = FPDF_GetFormType(document_ptr);
   if (doc_type == FORMTYPE_XFA_FULL || doc_type == FORMTYPE_XFA_FOREGROUND)
-    FPDF_LoadXFA(*document);
+    FPDF_LoadXFA(document_ptr);
 
-  (void)FPDF_GetDocPermissions(*document);
+  (void)FPDF_GetDocPermissions(document_ptr);
   return true;
 }
 
 void EmbedderTest::CloseDocument() {
-  FPDFDOC_ExitFormFillEnvironment(form_handle_);
-  form_handle_ = nullptr;
-
-  FPDF_CloseDocument(document_);
-  document_ = nullptr;
+  FORM_DoDocumentAAction(form_handle(), FPDFDOC_AACTION_WC);
+  form_handle_.reset();
+  document_.reset();
+  avail_.reset();
+  fake_file_access_.reset();
+  memset(&file_access_, 0, sizeof(file_access_));
+  loader_.reset();
+  file_contents_.reset();
 }
 
 FPDF_FORMHANDLE EmbedderTest::SetupFormFillEnvironment(
@@ -241,21 +433,46 @@
     JavaScriptOption javascript_option) {
   IPDF_JSPLATFORM* platform = static_cast<IPDF_JSPLATFORM*>(this);
   memset(platform, '\0', sizeof(IPDF_JSPLATFORM));
-  platform->version = 2;
+  platform->version = 3;
   platform->app_alert = AlertTrampoline;
-  platform->m_isolate = external_isolate_;
 
   FPDF_FORMFILLINFO* formfillinfo = static_cast<FPDF_FORMFILLINFO*>(this);
   memset(formfillinfo, 0, sizeof(FPDF_FORMFILLINFO));
-#ifdef PDF_ENABLE_XFA
-  formfillinfo->version = 2;
-#else   // PDF_ENABLE_XFA
-  formfillinfo->version = 1;
-#endif  // PDF_ENABLE_XFA
+  formfillinfo->version = form_fill_info_version_;
+  formfillinfo->FFI_Invalidate = InvalidateStub;
+  formfillinfo->FFI_OutputSelectedRect = OutputSelectedRectStub;
+  formfillinfo->FFI_SetCursor = SetCursorStub;
   formfillinfo->FFI_SetTimer = SetTimerTrampoline;
   formfillinfo->FFI_KillTimer = KillTimerTrampoline;
+  formfillinfo->FFI_GetLocalTime = GetLocalTimeStub;
+  formfillinfo->FFI_OnChange = OnChangeStub;
   formfillinfo->FFI_GetPage = GetPageTrampoline;
+  formfillinfo->FFI_GetCurrentPage = GetCurrentPageStub;
+  formfillinfo->FFI_GetRotation = GetRotationStub;
+  formfillinfo->FFI_ExecuteNamedAction = ExecuteNamedActionStub;
+  formfillinfo->FFI_SetTextFieldFocus = SetTextFieldFocusStub;
   formfillinfo->FFI_DoURIAction = DoURIActionTrampoline;
+  formfillinfo->FFI_DoGoToAction = DoGoToActionTrampoline;
+#ifdef PDF_ENABLE_XFA
+  formfillinfo->FFI_DisplayCaret = DisplayCaretStub;
+  formfillinfo->FFI_GetCurrentPageIndex = GetCurrentPageIndexStub;
+  formfillinfo->FFI_SetCurrentPage = SetCurrentPageStub;
+  formfillinfo->FFI_GotoURL = GotoURLStub;
+  formfillinfo->FFI_GetPageViewRect = GetPageViewRectStub;
+  formfillinfo->FFI_PageEvent = PageEventStub;
+  formfillinfo->FFI_PopupMenu = PopupMenuStub;
+  formfillinfo->FFI_OpenFile = OpenFileStub;
+  formfillinfo->FFI_EmailTo = EmailToStub;
+  formfillinfo->FFI_UploadTo = UploadToStub;
+  formfillinfo->FFI_GetPlatform = GetPlatformStub;
+  formfillinfo->FFI_GetLanguage = GetLanguageStub;
+  formfillinfo->FFI_DownloadFromURL = DownloadFromURLStub;
+  formfillinfo->FFI_PostRequestURL = PostRequestURLStub;
+  formfillinfo->FFI_PutRequestURL = PutRequestURLStub;
+#endif  // PDF_ENABLE_XFA
+  formfillinfo->FFI_OnFocusChange = OnFocusChangeTrampoline;
+  formfillinfo->FFI_DoURIActionWithKeyboardModifier =
+      DoURIActionWithKeyboardModifierTrampoline;
 
   if (javascript_option == JavaScriptOption::kEnableJavaScript)
     formfillinfo->m_pJsPlatform = platform;
@@ -267,22 +484,22 @@
 }
 
 void EmbedderTest::DoOpenActions() {
-  ASSERT(form_handle_);
-  FORM_DoDocumentJSAction(form_handle_);
-  FORM_DoDocumentOpenAction(form_handle_);
+  DCHECK(form_handle());
+  FORM_DoDocumentJSAction(form_handle());
+  FORM_DoDocumentOpenAction(form_handle());
 }
 
 int EmbedderTest::GetFirstPageNum() {
-  int first_page = FPDFAvail_GetFirstPageNum(document_);
-  (void)FPDFAvail_IsPageAvail(avail_, first_page,
+  int first_page = FPDFAvail_GetFirstPageNum(document());
+  (void)FPDFAvail_IsPageAvail(avail(), first_page,
                               fake_file_access_->GetDownloadHints());
   return first_page;
 }
 
 int EmbedderTest::GetPageCount() {
-  int page_count = FPDF_GetPageCount(document_);
+  int page_count = FPDF_GetPageCount(document());
   for (int i = 0; i < page_count; ++i)
-    (void)FPDFAvail_IsPageAvail(avail_, i,
+    (void)FPDFAvail_IsPageAvail(avail(), i,
                                 fake_file_access_->GetDownloadHints());
   return page_count;
 }
@@ -296,17 +513,17 @@
 }
 
 FPDF_PAGE EmbedderTest::LoadPageCommon(int page_number, bool do_events) {
-  ASSERT(form_handle_);
-  ASSERT(page_number >= 0);
-  ASSERT(!pdfium::ContainsKey(page_map_, page_number));
+  DCHECK(form_handle());
+  DCHECK(page_number >= 0);
+  DCHECK(!pdfium::Contains(page_map_, page_number));
 
-  FPDF_PAGE page = FPDF_LoadPage(document_, page_number);
+  FPDF_PAGE page = FPDF_LoadPage(document(), page_number);
   if (!page)
     return nullptr;
 
   if (do_events) {
-    FORM_OnAfterLoadPage(page, form_handle_);
-    FORM_DoPageAAction(page, form_handle_, FPDFPAGE_AACTION_OPEN);
+    FORM_OnAfterLoadPage(page, form_handle());
+    FORM_DoPageAAction(page, form_handle(), FPDFPAGE_AACTION_OPEN);
   }
   page_map_[page_number] = page;
   return page;
@@ -321,15 +538,15 @@
 }
 
 void EmbedderTest::UnloadPageCommon(FPDF_PAGE page, bool do_events) {
-  ASSERT(form_handle_);
+  DCHECK(form_handle());
   int page_number = GetPageNumberForLoadedPage(page);
   if (page_number < 0) {
     NOTREACHED();
     return;
   }
   if (do_events) {
-    FORM_DoPageAAction(page, form_handle_, FPDFPAGE_AACTION_CLOSE);
-    FORM_OnBeforeClosePage(page, form_handle_);
+    FORM_DoPageAAction(page, form_handle(), FPDFPAGE_AACTION_CLOSE);
+    FORM_OnBeforeClosePage(page, form_handle());
   }
   FPDF_ClosePage(page);
   page_map_.erase(page_number);
@@ -350,7 +567,7 @@
     NOTREACHED();
     return nullptr;
   }
-  return RenderPageWithFlags(page, form_handle_, flags);
+  return RenderPageWithFlags(page, form_handle(), flags);
 }
 
 ScopedFPDFBitmap EmbedderTest::RenderSavedPage(FPDF_PAGE page) {
@@ -363,7 +580,7 @@
     NOTREACHED();
     return nullptr;
   }
-  return RenderPageWithFlags(page, saved_form_handle_, flags);
+  return RenderPageWithFlags(page, saved_form_handle(), flags);
 }
 
 // static
@@ -386,7 +603,7 @@
   return RenderPageWithFlags(page, nullptr, 0);
 }
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
 // static
 std::vector<uint8_t> EmbedderTest::RenderPageWithFlagsToEmf(FPDF_PAGE page,
                                                             int flags) {
@@ -406,7 +623,7 @@
   FPDF_RenderPage(dc, page, 0, 0, width, height, 0, flags);
 
   HENHMETAFILE emf = CloseEnhMetaFile(dc);
-  size_t size_in_bytes = GetEnhMetaFileBits(emf, 0, nullptr);
+  UINT size_in_bytes = GetEnhMetaFileBits(emf, 0, nullptr);
   std::vector<uint8_t> buffer(size_in_bytes);
   GetEnhMetaFileBits(emf, size_in_bytes, buffer.data());
   DeleteEnhMetaFile(emf);
@@ -417,7 +634,8 @@
 std::string EmbedderTest::GetPostScriptFromEmf(
     pdfium::span<const uint8_t> emf_data) {
   // This comes from Emf::InitFromData() in Chromium.
-  HENHMETAFILE emf = SetEnhMetaFileBits(emf_data.size(), emf_data.data());
+  HENHMETAFILE emf = SetEnhMetaFileBits(
+      pdfium::base::checked_cast<UINT>(emf_data.size()), emf_data.data());
   if (!emf)
     return std::string();
 
@@ -446,7 +664,7 @@
   DeleteEnhMetaFile(emf);
   return ps_data;
 }
-#endif  // defined(OS_WIN)
+#endif  // BUILDFLAG(IS_WIN)
 
 FPDF_DOCUMENT EmbedderTest::OpenSavedDocument() {
   return OpenSavedDocumentWithPassword(nullptr);
@@ -471,51 +689,48 @@
 FPDF_DOCUMENT EmbedderTest::OpenSavedDocumentWithPassword(
     const char* password) {
   memset(&saved_file_access_, 0, sizeof(saved_file_access_));
-  saved_file_access_.m_FileLen = data_string_.size();
+  saved_file_access_.m_FileLen =
+      pdfium::base::checked_cast<unsigned long>(data_string_.size());
   saved_file_access_.m_GetBlock = GetBlockFromString;
   // Copy data to prevent clearing it before saved document close.
   saved_document_file_data_ = data_string_;
   saved_file_access_.m_Param = &saved_document_file_data_;
 
   saved_fake_file_access_ =
-      pdfium::MakeUnique<FakeFileAccess>(&saved_file_access_);
+      std::make_unique<FakeFileAccess>(&saved_file_access_);
 
   EXPECT_TRUE(OpenDocumentHelper(
       password, LinearizeOption::kDefaultLinearize,
       JavaScriptOption::kEnableJavaScript, saved_fake_file_access_.get(),
       &saved_document_, &saved_avail_, &saved_form_handle_));
-  return saved_document_;
+  return saved_document();
 }
 
 void EmbedderTest::CloseSavedDocument() {
-  ASSERT(saved_document_);
+  DCHECK(saved_document());
 
-  FPDFDOC_ExitFormFillEnvironment(saved_form_handle_);
-  FPDF_CloseDocument(saved_document_);
-  FPDFAvail_Destroy(saved_avail_);
-
-  saved_form_handle_ = nullptr;
-  saved_document_ = nullptr;
-  saved_avail_ = nullptr;
+  saved_form_handle_.reset();
+  saved_document_.reset();
+  saved_avail_.reset();
 }
 
 FPDF_PAGE EmbedderTest::LoadSavedPage(int page_number) {
-  ASSERT(saved_form_handle_);
-  ASSERT(page_number >= 0);
-  ASSERT(!pdfium::ContainsKey(saved_page_map_, page_number));
+  DCHECK(saved_form_handle());
+  DCHECK(page_number >= 0);
+  DCHECK(!pdfium::Contains(saved_page_map_, page_number));
 
-  FPDF_PAGE page = FPDF_LoadPage(saved_document_, page_number);
+  FPDF_PAGE page = FPDF_LoadPage(saved_document(), page_number);
   if (!page)
     return nullptr;
 
-  FORM_OnAfterLoadPage(page, saved_form_handle_);
-  FORM_DoPageAAction(page, saved_form_handle_, FPDFPAGE_AACTION_OPEN);
+  FORM_OnAfterLoadPage(page, saved_form_handle());
+  FORM_DoPageAAction(page, saved_form_handle(), FPDFPAGE_AACTION_OPEN);
   saved_page_map_[page_number] = page;
   return page;
 }
 
 void EmbedderTest::CloseSavedPage(FPDF_PAGE page) {
-  ASSERT(saved_form_handle_);
+  DCHECK(saved_form_handle());
 
   int page_number = GetPageNumberForSavedPage(page);
   if (page_number < 0) {
@@ -523,8 +738,8 @@
     return;
   }
 
-  FORM_DoPageAAction(page, saved_form_handle_, FPDFPAGE_AACTION_CLOSE);
-  FORM_OnBeforeClosePage(page, saved_form_handle_);
+  FORM_DoPageAAction(page, saved_form_handle(), FPDFPAGE_AACTION_CLOSE);
+  FORM_OnBeforeClosePage(page, saved_form_handle());
   FPDF_ClosePage(page);
 
   saved_page_map_.erase(page_number);
@@ -534,8 +749,8 @@
                                         int width,
                                         int height,
                                         const char* md5) {
-  ASSERT(saved_document_);
-  ASSERT(page);
+  DCHECK(saved_document());
+  DCHECK(page);
 
   ScopedFPDFBitmap bitmap = RenderSavedPageWithFlags(page, FPDF_ANNOT);
   CompareBitmap(bitmap.get(), width, height, md5);
@@ -550,10 +765,19 @@
 }
 
 void EmbedderTest::SetWholeFileAvailable() {
-  ASSERT(fake_file_access_);
+  DCHECK(fake_file_access_);
   fake_file_access_->SetWholeFileAvailable();
 }
 
+void EmbedderTest::SetDocumentFromAvail() {
+  document_.reset(FPDFAvail_GetDocument(avail(), nullptr));
+}
+
+void EmbedderTest::CreateAvail(FX_FILEAVAIL* file_avail,
+                               FPDF_FILEACCESS* file) {
+  avail_.reset(FPDFAvail_Create(file_avail, file));
+}
+
 FPDF_PAGE EmbedderTest::Delegate::GetPage(FPDF_FORMFILLINFO* info,
                                           FPDF_DOCUMENT document,
                                           int page_index) {
@@ -563,52 +787,6 @@
 }
 
 // static
-void EmbedderTest::UnsupportedHandlerTrampoline(UNSUPPORT_INFO* info,
-                                                int type) {
-  EmbedderTest* test = static_cast<EmbedderTest*>(info);
-  test->delegate_->UnsupportedHandler(type);
-}
-
-// static
-int EmbedderTest::AlertTrampoline(IPDF_JSPLATFORM* platform,
-                                  FPDF_WIDESTRING message,
-                                  FPDF_WIDESTRING title,
-                                  int type,
-                                  int icon) {
-  EmbedderTest* test = static_cast<EmbedderTest*>(platform);
-  return test->delegate_->Alert(message, title, type, icon);
-}
-
-// static
-int EmbedderTest::SetTimerTrampoline(FPDF_FORMFILLINFO* info,
-                                     int msecs,
-                                     TimerCallback fn) {
-  EmbedderTest* test = static_cast<EmbedderTest*>(info);
-  return test->delegate_->SetTimer(msecs, fn);
-}
-
-// static
-void EmbedderTest::KillTimerTrampoline(FPDF_FORMFILLINFO* info, int id) {
-  EmbedderTest* test = static_cast<EmbedderTest*>(info);
-  return test->delegate_->KillTimer(id);
-}
-
-// static
-FPDF_PAGE EmbedderTest::GetPageTrampoline(FPDF_FORMFILLINFO* info,
-                                          FPDF_DOCUMENT document,
-                                          int page_index) {
-  return static_cast<EmbedderTest*>(info)->delegate_->GetPage(info, document,
-                                                              page_index);
-}
-
-// static
-void EmbedderTest::DoURIActionTrampoline(FPDF_FORMFILLINFO* info,
-                                         FPDF_BYTESTRING uri) {
-  EmbedderTest* test = static_cast<EmbedderTest*>(info);
-  return test->delegate_->DoURIAction(uri);
-}
-
-// static
 std::string EmbedderTest::HashBitmap(FPDF_BITMAP bitmap) {
   int stride = FPDFBitmap_GetStride(bitmap);
   int usable_bytes_per_row =
@@ -625,13 +803,11 @@
   return CryptToBase16(digest);
 }
 
-#ifndef NDEBUG
 // static
 void EmbedderTest::WriteBitmapToPng(FPDF_BITMAP bitmap,
                                     const std::string& filename) {
   BitmapSaver::WriteBitmapToPng(bitmap, filename);
 }
-#endif
 
 // static
 void EmbedderTest::CompareBitmap(FPDF_BITMAP bitmap,
@@ -650,7 +826,11 @@
   if (!expected_md5sum)
     return;
 
-  EXPECT_EQ(expected_md5sum, HashBitmap(bitmap));
+  std::string actual_md5sum = HashBitmap(bitmap);
+  EXPECT_EQ(expected_md5sum, actual_md5sum);
+  if (EmbedderTestEnvironment::GetInstance()->write_pngs()) {
+    WriteBitmapToPng(bitmap, actual_md5sum + ".png");
+  }
 }
 
 // static
@@ -693,7 +873,7 @@
   for (const auto& it : page_map) {
     if (it.second == page) {
       int page_number = it.first;
-      ASSERT(page_number >= 0);
+      DCHECK(page_number >= 0);
       return page_number;
     }
   }
diff --git a/testing/embedder_test.h b/testing/embedder_test.h
index 662333c..c73dd61 100644
--- a/testing/embedder_test.h
+++ b/testing/embedder_test.h
@@ -1,4 +1,4 @@
-// Copyright 2015 PDFium Authors. All rights reserved.
+// Copyright 2015 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -25,6 +25,10 @@
 
 class TestLoader;
 
+// The loading time of the CFGAS_FontMgr is linear in the number of times it is
+// loaded. So, if a test suite has a lot of tests that need a font manager they
+// can end up executing very, very slowly.
+
 // This class is used to load a PDF document, and then run programatic
 // API tests against it.
 class EmbedderTest : public ::testing::Test,
@@ -64,6 +68,23 @@
 
     // Equivalent to FPDF_FORMFILLINFO::FFI_DoURIAction().
     virtual void DoURIAction(FPDF_BYTESTRING uri) {}
+
+    // Equivalent to FPDF_FORMFILLINFO::FFI_DoGoToAction().
+    virtual void DoGoToAction(FPDF_FORMFILLINFO* info,
+                              int page_index,
+                              int zoom_mode,
+                              float* pos_arry,
+                              int array_size) {}
+
+    // Equivalent to FPDF_FORMFILLINFO::FFI_OnFocusChange().
+    virtual void OnFocusChange(FPDF_FORMFILLINFO* info,
+                               FPDF_ANNOTATION annot,
+                               int page_index) {}
+
+    // Equivalent to FPDF_FORMFILLINFO::FFI_DoURIActionWithKeyboardModifier().
+    virtual void DoURIActionWithKeyboardModifier(FPDF_FORMFILLINFO* info,
+                                                 FPDF_BYTESTRING uri,
+                                                 int modifiers) {}
   };
 
   EmbedderTest();
@@ -72,21 +93,30 @@
   void SetUp() override;
   void TearDown() override;
 
-#ifdef PDF_ENABLE_V8
-  // Call before SetUp to pass shared isolate, otherwise PDFium creates one.
-  void SetExternalIsolate(void* isolate);
-#endif  // PDF_ENABLE_V8
-
+  Delegate* GetDelegate() { return delegate_; }
   void SetDelegate(Delegate* delegate) {
     delegate_ = delegate ? delegate : default_delegate_.get();
   }
 
-  FPDF_DOCUMENT document() const { return document_; }
-  FPDF_FORMHANDLE form_handle() const { return form_handle_; }
+  void SetFormFillInfoVersion(int form_fill_info_version) {
+    form_fill_info_version_ = form_fill_info_version;
+  }
 
-  // Create an empty document, and its form fill environment. Returns true
-  // on success or false on failure.
-  bool CreateEmptyDocument();
+  void SetDocumentFromAvail();
+  FPDF_DOCUMENT document() const { return document_.get(); }
+  FPDF_DOCUMENT saved_document() const { return saved_document_.get(); }
+  FPDF_FORMHANDLE form_handle() const { return form_handle_.get(); }
+  FPDF_FORMHANDLE saved_form_handle() const { return saved_form_handle_.get(); }
+
+  // Wrapper for FPDFAvail_Create() to set `avail_`.
+  void CreateAvail(FX_FILEAVAIL* file_avail, FPDF_FILEACCESS* file);
+  FPDF_AVAIL avail() { return avail_.get(); }
+
+  // Create an empty document, and its form fill environment.
+  void CreateEmptyDocument();
+
+  // Create an empty document without a form fill environment.
+  void CreateEmptyDocumentWithoutFormFillEnvironment();
 
   // Open the document specified by |filename|, and create its form fill
   // environment, or return false on failure. The |filename| is relative to
@@ -171,14 +201,14 @@
   // Simplified form of RenderPageWithFlags() with no handle and no flags.
   static ScopedFPDFBitmap RenderPage(FPDF_PAGE page);
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   // Convert |page| into EMF with the specified page rendering |flags|.
   static std::vector<uint8_t> RenderPageWithFlagsToEmf(FPDF_PAGE page,
                                                        int flags);
 
   // Get the PostScript data from |emf_data|.
   static std::string GetPostScriptFromEmf(pdfium::span<const uint8_t> emf_data);
-#endif  // defined(OS_WIN)
+#endif  // BUILDFLAG(IS_WIN)
 
   // Return bytes for each of the FPDFBitmap_* format types.
   static int BytesPerPixelForFormat(int format);
@@ -190,9 +220,9 @@
                           LinearizeOption linearize_option,
                           JavaScriptOption javascript_option,
                           FakeFileAccess* network_simulator,
-                          FPDF_DOCUMENT* document,
-                          FPDF_AVAIL* avail,
-                          FPDF_FORMHANDLE* form_handle);
+                          ScopedFPDFDocument* document,
+                          ScopedFPDFAvail* avail,
+                          ScopedFPDFFormHandle* form_handle);
 
   FPDF_FORMHANDLE SetupFormFillEnvironment(FPDF_DOCUMENT doc,
                                            JavaScriptOption javascript_option);
@@ -201,11 +231,9 @@
   // any, at the end of a row where the stride is larger than width * bpp.
   static std::string HashBitmap(FPDF_BITMAP bitmap);
 
-#ifndef NDEBUG
   // For debugging purposes.
   // Write |bitmap| as a PNG to |filename|.
   static void WriteBitmapToPng(FPDF_BITMAP bitmap, const std::string& filename);
-#endif
 
   // Check |bitmap| to make sure it has the right dimensions and content.
   static void CompareBitmap(FPDF_BITMAP bitmap,
@@ -245,45 +273,7 @@
   void ClosePDFFileForWrite();
 #endif
 
-  std::unique_ptr<Delegate> default_delegate_;
-  Delegate* delegate_;
-
-  FPDF_DOCUMENT document_ = nullptr;
-  FPDF_FORMHANDLE form_handle_ = nullptr;
-  FPDF_AVAIL avail_ = nullptr;
-  FPDF_FILEACCESS file_access_;                       // must outlive |avail_|.
-  std::unique_ptr<FakeFileAccess> fake_file_access_;  // must outlive |avail_|.
-
-  void* external_isolate_ = nullptr;
-  std::unique_ptr<TestLoader> loader_;
-  size_t file_length_ = 0;
-  std::unique_ptr<char, pdfium::FreeDeleter> file_contents_;
-  PageNumberToHandleMap page_map_;
-
-  FPDF_DOCUMENT saved_document_ = nullptr;
-  FPDF_FORMHANDLE saved_form_handle_ = nullptr;
-  FPDF_AVAIL saved_avail_ = nullptr;
-  FPDF_FILEACCESS saved_file_access_;  // must outlive |saved_avail_|.
-  // must outlive |saved_avail_|.
-  std::unique_ptr<FakeFileAccess> saved_fake_file_access_;
-  PageNumberToHandleMap saved_page_map_;
-
  private:
-  static void UnsupportedHandlerTrampoline(UNSUPPORT_INFO*, int type);
-  static int AlertTrampoline(IPDF_JSPLATFORM* plaform,
-                             FPDF_WIDESTRING message,
-                             FPDF_WIDESTRING title,
-                             int type,
-                             int icon);
-  static int SetTimerTrampoline(FPDF_FORMFILLINFO* info,
-                                int msecs,
-                                TimerCallback fn);
-  static void KillTimerTrampoline(FPDF_FORMFILLINFO* info, int id);
-  static FPDF_PAGE GetPageTrampoline(FPDF_FORMFILLINFO* info,
-                                     FPDF_DOCUMENT document,
-                                     int page_index);
-  static void DoURIActionTrampoline(FPDF_FORMFILLINFO* info,
-                                    FPDF_BYTESTRING uri);
   static int WriteBlockCallback(FPDF_FILEWRITE* pFileWrite,
                                 const void* data,
                                 unsigned long size);
@@ -301,6 +291,34 @@
   void UnloadPageCommon(FPDF_PAGE page, bool do_events);
   FPDF_PAGE LoadPageCommon(int page_number, bool do_events);
 
+  std::unique_ptr<Delegate> default_delegate_;
+  Delegate* delegate_;
+
+#ifdef PDF_ENABLE_XFA
+  int form_fill_info_version_ = 2;
+#else   // PDF_ENABLE_XFA
+  int form_fill_info_version_ = 1;
+#endif  // PDF_ENABLE_XFA
+
+  size_t file_length_ = 0;
+  // must outlive `loader_`.
+  std::unique_ptr<char, pdfium::FreeDeleter> file_contents_;
+  std::unique_ptr<TestLoader> loader_;
+  FPDF_FILEACCESS file_access_;                       // must outlive `avail_`.
+  std::unique_ptr<FakeFileAccess> fake_file_access_;  // must outlive `avail_`.
+  ScopedFPDFAvail avail_;
+  ScopedFPDFDocument document_;
+  ScopedFPDFFormHandle form_handle_;
+  PageNumberToHandleMap page_map_;
+
+  FPDF_FILEACCESS saved_file_access_;  // must outlive `saved_avail_`.
+  // must outlive `saved_avail_`.
+  std::unique_ptr<FakeFileAccess> saved_fake_file_access_;
+  ScopedFPDFAvail saved_avail_;
+  ScopedFPDFDocument saved_document_;
+  ScopedFPDFFormHandle saved_form_handle_;
+  PageNumberToHandleMap saved_page_map_;
+
   std::string data_string_;
   std::string saved_document_file_data_;
   std::ofstream filestream_;
diff --git a/testing/embedder_test_constants.cpp b/testing/embedder_test_constants.cpp
new file mode 100644
index 0000000..9e8caee
--- /dev/null
+++ b/testing/embedder_test_constants.cpp
@@ -0,0 +1,68 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/embedder_test_constants.h"
+
+#include "build/build_config.h"
+#include "core/fxge/cfx_defaultrenderdevice.h"
+
+namespace pdfium {
+
+const char* AnnotationStampWithApChecksum() {
+  if (CFX_DefaultRenderDevice::SkiaIsDefaultRenderer())
+    return "a31381406d0b95049e418720750b78dd";
+#if BUILDFLAG(IS_APPLE)
+  return "0521eaa52fe2aa43aafd3e4495f63f0b";
+#else
+  return "5f19ddad9d48f5b7b87ee7d92f577db6";
+#endif
+}
+
+const char kBlankPage612By792Checksum[] = "1940568c9ba33bac5d0b1ee9558c76b3";
+
+const char* Bug890322Checksum() {
+  if (CFX_DefaultRenderDevice::SkiaIsDefaultRenderer())
+    return "793689536cf64fe792c2f241888c0cf3";
+  return "6c674642154408e877d88c6c082d67e9";
+}
+
+const char* HelloWorldChecksum() {
+#if BUILDFLAG(IS_APPLE)
+  if (!CFX_DefaultRenderDevice::SkiaIsDefaultRenderer())
+    return "6eef7237f7591f07616e238422086737";
+#endif
+  return "c1c548442e0e0f949c5550d89bf8ae3b";
+}
+
+const char* HelloWorldRemovedChecksum() {
+#if BUILDFLAG(IS_APPLE)
+  if (!CFX_DefaultRenderDevice::SkiaIsDefaultRenderer())
+    return "6e1cae48a2e35c521dee4ca502f48af6";
+#endif
+  return "4a9b80f675f7f3bf2da1b02f12449e4b";
+}
+
+const char* ManyRectanglesChecksum() {
+  if (CFX_DefaultRenderDevice::SkiaIsDefaultRenderer())
+    return "4e7e280c1597222afcb0ee3bb90ec119";
+  return "b0170c575b65ecb93ebafada0ff0f038";
+}
+
+const char* RectanglesChecksum() {
+  if (CFX_DefaultRenderDevice::SkiaIsDefaultRenderer())
+    return "b4e411a6b5ffa59a50efede2efece597";
+  return "0a90de37f52127619c3dfb642b5fa2fe";
+}
+
+const char* TextFormChecksum() {
+  if (CFX_DefaultRenderDevice::SkiaIsDefaultRenderer())
+    return "e6d2eb75f18d773f0dad938b1bb22e23";
+#if BUILDFLAG(IS_APPLE)
+  return "fa2bf756942a950101fc147fc4ef3f82";
+#else
+  return "6f86fe1dbed5965d91aec6e0b829e29f";
+#endif
+}
+
+}  // namespace pdfium
diff --git a/testing/embedder_test_constants.h b/testing/embedder_test_constants.h
new file mode 100644
index 0000000..306880b
--- /dev/null
+++ b/testing/embedder_test_constants.h
@@ -0,0 +1,36 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_EMBEDDER_TEST_CONSTANTS_H_
+#define TESTING_EMBEDDER_TEST_CONSTANTS_H_
+
+namespace pdfium {
+
+// MD5 hash for rendering annotation_stamp_with_ap.pdf with annotations.
+const char* AnnotationStampWithApChecksum();
+
+// MD5 hash for rendering a 612x792 blank page.
+extern const char kBlankPage612By792Checksum[];
+
+// MD5 hash for rendering bug_890322.pdf.
+const char* Bug890322Checksum();
+
+// MD5 hash for rendering hello_world.pdf or bug_455199.pdf.
+const char* HelloWorldChecksum();
+
+// MD5 hash for rendering hello_world.pdf after removing "Goodbye, world!".
+const char* HelloWorldRemovedChecksum();
+
+// MD5 hash for rendering many_rectangles.pdf.
+const char* ManyRectanglesChecksum();
+
+// MD5 hash for rendering rectangles.pdf.
+const char* RectanglesChecksum();
+
+// MD5 hash for rendering text_form.pdf.
+const char* TextFormChecksum();
+
+}  // namespace pdfium
+
+#endif  // TESTING_EMBEDDER_TEST_CONSTANTS_H_
diff --git a/testing/embedder_test_environment.cpp b/testing/embedder_test_environment.cpp
new file mode 100644
index 0000000..07e459a
--- /dev/null
+++ b/testing/embedder_test_environment.cpp
@@ -0,0 +1,93 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/embedder_test_environment.h"
+
+#include <ostream>
+
+#include "core/fxcrt/fx_system.h"
+#include "public/fpdfview.h"
+#include "testing/command_line_helpers.h"
+#include "third_party/base/check.h"
+
+#ifdef PDF_ENABLE_V8
+#include "testing/v8_test_environment.h"
+#endif  // PDF_ENABLE_V8
+
+namespace {
+
+EmbedderTestEnvironment* g_environment = nullptr;
+
+}  // namespace
+
+EmbedderTestEnvironment::EmbedderTestEnvironment()
+    : renderer_type_(GetDefaultRendererType()) {
+  DCHECK(!g_environment);
+  g_environment = this;
+}
+
+EmbedderTestEnvironment::~EmbedderTestEnvironment() {
+  DCHECK(g_environment);
+  g_environment = nullptr;
+}
+
+// static
+EmbedderTestEnvironment* EmbedderTestEnvironment::GetInstance() {
+  return g_environment;
+}
+
+void EmbedderTestEnvironment::SetUp() {
+  FPDF_LIBRARY_CONFIG config;
+  config.version = 4;
+  config.m_pUserFontPaths = nullptr;
+  config.m_v8EmbedderSlot = 0;
+  config.m_pPlatform = nullptr;
+
+  config.m_pUserFontPaths = test_fonts_.font_paths();
+
+#ifdef PDF_ENABLE_V8
+  config.m_pIsolate = V8TestEnvironment::GetInstance()->isolate();
+  config.m_pPlatform = V8TestEnvironment::GetInstance()->platform();
+#else   // PDF_ENABLE_V8
+  config.m_pIsolate = nullptr;
+  config.m_pPlatform = nullptr;
+#endif  // PDF_ENABLE_V8
+  config.m_RendererType = renderer_type_;
+
+  FPDF_InitLibraryWithConfig(&config);
+
+  test_fonts_.InstallFontMapper();
+}
+
+void EmbedderTestEnvironment::TearDown() {
+  FPDF_DestroyLibrary();
+}
+
+void EmbedderTestEnvironment::AddFlags(int argc, char** argv) {
+  for (int i = 1; i < argc; ++i)
+    AddFlag(argv[i]);
+}
+
+void EmbedderTestEnvironment::AddFlag(const std::string& flag) {
+  if (flag == "--write-pngs") {
+    write_pngs_ = true;
+    return;
+  }
+#if defined(_SKIA_SUPPORT_)
+  std::string value;
+  if (ParseSwitchKeyValue(flag, "--use-renderer=", &value)) {
+    if (value == "agg") {
+      renderer_type_ = FPDF_RENDERERTYPE_AGG;
+    } else if (value == "skia") {
+      renderer_type_ = FPDF_RENDERERTYPE_SKIA;
+    } else {
+      std::cerr << "Invalid --use-renderer argument, value must be one of agg "
+                   "or skia\n";
+    }
+    return;
+  }
+#endif  // defined(_SKIA_SUPPORT_)
+
+  std::cerr << "Unknown flag: " << flag << "\n";
+}
diff --git a/testing/embedder_test_environment.h b/testing/embedder_test_environment.h
new file mode 100644
index 0000000..e04c5ae
--- /dev/null
+++ b/testing/embedder_test_environment.h
@@ -0,0 +1,39 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_EMBEDDER_TEST_ENVIRONMENT_H_
+#define TESTING_EMBEDDER_TEST_ENVIRONMENT_H_
+
+#include <string>
+
+#include "public/fpdfview.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/test_fonts.h"
+
+class EmbedderTestEnvironment : public testing::Environment {
+ public:
+  EmbedderTestEnvironment();
+  ~EmbedderTestEnvironment() override;
+
+  // Note: GetInstance() does not create one if it does not exist,
+  // so the main program must explicitly add this enviroment.
+  static EmbedderTestEnvironment* GetInstance();
+
+  // testing::Environment:
+  void SetUp() override;
+  void TearDown() override;
+
+  void AddFlags(int argc, char** argv);
+
+  bool write_pngs() const { return write_pngs_; }
+
+ private:
+  void AddFlag(const std::string& flag);
+
+  FPDF_RENDERER_TYPE renderer_type_;
+  bool write_pngs_ = false;
+  TestFonts test_fonts_;
+};
+
+#endif  // TESTING_EMBEDDER_TEST_ENVIRONMENT_H_
diff --git a/testing/embedder_test_main.cpp b/testing/embedder_test_main.cpp
index 34dfb06..53049b6 100644
--- a/testing/embedder_test_main.cpp
+++ b/testing/embedder_test_main.cpp
@@ -1,90 +1,36 @@
-// Copyright 2018 PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <memory>
-#include <string>
-
+#include "build/build_config.h"
 #include "core/fxcrt/fx_memory.h"
+#include "testing/embedder_test_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 #ifdef PDF_ENABLE_V8
-#include "testing/v8_initializer.h"
-#include "v8/include/v8-platform.h"
-#include "v8/include/v8.h"
-#endif  // PDF_ENABLE_v8
-
-namespace {
-
-const char* g_exe_path = nullptr;
-
-#ifdef PDF_ENABLE_V8
-#ifdef V8_USE_EXTERNAL_STARTUP_DATA
-v8::StartupData* g_v8_snapshot = nullptr;
-#endif  // V8_USE_EXTERNAL_STARTUP_DATA
+#include "testing/v8_test_environment.h"
 #endif  // PDF_ENABLE_V8
 
-// The loading time of the CFGAS_FontMgr is linear in the number of times it is
-// loaded. So, if a test suite has a lot of tests that need a font manager they
-// can end up executing very, very slowly.
-class Environment final : public testing::Environment {
- public:
-  void SetUp() override {
-#ifdef PDF_ENABLE_V8
-#ifdef V8_USE_EXTERNAL_STARTUP_DATA
-    if (g_v8_snapshot) {
-      platform_ = InitializeV8ForPDFiumWithStartupData(g_exe_path,
-                                                       std::string(), nullptr);
-    } else {
-      g_v8_snapshot = new v8::StartupData;
-      platform_ = InitializeV8ForPDFiumWithStartupData(
-          g_exe_path, std::string(), g_v8_snapshot);
-    }
-#else
-    platform_ = InitializeV8ForPDFium(g_exe_path);
-#endif  // V8_USE_EXTERNAL_STARTUP_DATA
-#endif  // FPDF_ENABLE_V8
-  }
-
-  void TearDown() override {
-#ifdef PDF_ENABLE_V8
-    v8::V8::ShutdownPlatform();
-#endif  // PDF_ENABLE_V8
-  }
-
- private:
-#ifdef PDF_ENABLE_V8
-  std::unique_ptr<v8::Platform> platform_;
-#endif  // PDF_ENABLE_V8
-};
-
-Environment* env_ = nullptr;
-
-}  // namespace
-
-// Can't use gtest-provided main since we need to stash the path to the
-// executable in order to find the external V8 binary data files.
+// Can't use gtest-provided main since we need to create our own
+// testing environment which needs the executable path in order to
+// find the external V8 binary data files.
 int main(int argc, char** argv) {
-  g_exe_path = argv[0];
+  FX_InitializeMemoryAllocators();
 
-  FXMEM_InitializePartitionAlloc();
-
-  env_ = new Environment();
+#ifdef PDF_ENABLE_V8
   // The env will be deleted by gtest.
-  AddGlobalTestEnvironment(env_);
+  AddGlobalTestEnvironment(new V8TestEnvironment(argv[0]));
+#endif  // PDF_ENABLE_V8
+
+  // The env will be deleted by gtest.
+  AddGlobalTestEnvironment(new EmbedderTestEnvironment);
 
   testing::InitGoogleTest(&argc, argv);
   testing::InitGoogleMock(&argc, argv);
 
-  int ret_val = RUN_ALL_TESTS();
+  // Anything remaining in argc/argv is an embedder_tests flag.
+  EmbedderTestEnvironment::GetInstance()->AddFlags(argc, argv);
 
-#ifdef PDF_ENABLE_V8
-#ifdef V8_USE_EXTERNAL_STARTUP_DATA
-  if (g_v8_snapshot)
-    free(const_cast<char*>(g_v8_snapshot->data));
-#endif  // V8_USE_EXTERNAL_STARTUP_DATA
-#endif  // PDF_ENABLE_V8
-
-  return ret_val;
+  return RUN_ALL_TESTS();
 }
diff --git a/testing/embedder_test_mock_delegate.h b/testing/embedder_test_mock_delegate.h
index c3f2820..7309541 100644
--- a/testing/embedder_test_mock_delegate.h
+++ b/testing/embedder_test_mock_delegate.h
@@ -1,4 +1,4 @@
-// Copyright 2015 PDFium Authors. All rights reserved.
+// Copyright 2015 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -8,7 +8,7 @@
 #include "testing/embedder_test.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
-class EmbedderTestMockDelegate final : public EmbedderTest::Delegate {
+class EmbedderTestMockDelegate : public EmbedderTest::Delegate {
  public:
   MOCK_METHOD1(UnsupportedHandler, void(int type));
   MOCK_METHOD4(
@@ -16,6 +16,21 @@
       int(FPDF_WIDESTRING message, FPDF_WIDESTRING title, int type, int icon));
   MOCK_METHOD2(SetTimer, int(int msecs, TimerCallback fn));
   MOCK_METHOD1(KillTimer, void(int msecs));
+  MOCK_METHOD1(DoURIAction, void(FPDF_BYTESTRING uri));
+  MOCK_METHOD5(DoGoToAction,
+               void(FPDF_FORMFILLINFO* info,
+                    int page_index,
+                    int zoom_mode,
+                    float* pos_array,
+                    int array_size));
+  MOCK_METHOD3(OnFocusChange,
+               void(FPDF_FORMFILLINFO* info,
+                    FPDF_ANNOTATION annot,
+                    int page_index));
+  MOCK_METHOD3(DoURIActionWithKeyboardModifier,
+               void(FPDF_FORMFILLINFO* info,
+                    FPDF_BYTESTRING uri,
+                    int modifiers));
 };
 
 #endif  // TESTING_EMBEDDER_TEST_MOCK_DELEGATE_H_
diff --git a/testing/embedder_test_timer_handling_delegate.h b/testing/embedder_test_timer_handling_delegate.h
index a32ad20..e511254 100644
--- a/testing/embedder_test_timer_handling_delegate.h
+++ b/testing/embedder_test_timer_handling_delegate.h
@@ -1,4 +1,4 @@
-// Copyright 2015 PDFium Authors. All rights reserved.
+// Copyright 2015 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -57,7 +57,7 @@
 
   void AdvanceTime(int increment_msecs) {
     fake_elapsed_msecs_ += increment_msecs;
-    while (1) {
+    while (true) {
       auto iter = expiry_to_timer_map_.begin();
       if (iter == expiry_to_timer_map_.end()) {
         break;
diff --git a/testing/external_engine_embedder_test.cpp b/testing/external_engine_embedder_test.cpp
new file mode 100644
index 0000000..031981d
--- /dev/null
+++ b/testing/external_engine_embedder_test.cpp
@@ -0,0 +1,37 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/external_engine_embedder_test.h"
+
+#include <memory>
+
+#include "fxjs/cfxjs_engine.h"
+#include "testing/v8_test_environment.h"
+#include "v8/include/v8-context.h"
+#include "v8/include/v8-isolate.h"
+#include "v8/include/v8-local-handle.h"
+
+ExternalEngineEmbedderTest::ExternalEngineEmbedderTest() = default;
+
+ExternalEngineEmbedderTest::~ExternalEngineEmbedderTest() = default;
+
+void ExternalEngineEmbedderTest::SetUp() {
+  EmbedderTest::SetUp();
+
+  v8::Isolate::Scope isolate_scope(isolate());
+  v8::HandleScope handle_scope(isolate());
+  FXJS_PerIsolateData::SetUp(isolate());
+  m_Engine = std::make_unique<CFXJS_Engine>(isolate());
+  m_Engine->InitializeEngine();
+}
+
+void ExternalEngineEmbedderTest::TearDown() {
+  m_Engine->ReleaseEngine();
+  m_Engine.reset();
+  JSEmbedderTest::TearDown();
+}
+
+v8::Local<v8::Context> ExternalEngineEmbedderTest::GetV8Context() {
+  return m_Engine->GetV8Context();
+}
diff --git a/testing/external_engine_embedder_test.h b/testing/external_engine_embedder_test.h
new file mode 100644
index 0000000..258e124
--- /dev/null
+++ b/testing/external_engine_embedder_test.h
@@ -0,0 +1,35 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_EXTERNAL_ENGINE_EMBEDDER_TEST_H_
+#define TESTING_EXTERNAL_ENGINE_EMBEDDER_TEST_H_
+
+#include <memory>
+
+#include "testing/js_embedder_test.h"
+#include "v8/include/v8-context.h"
+#include "v8/include/v8-local-handle.h"
+
+class CFXJS_Engine;
+
+// Test class that allows creating a FXJS javascript engine without
+// first having to load a document and instantiate a form filler
+// against it. Generally, most tests will want to do the latter.
+class ExternalEngineEmbedderTest : public JSEmbedderTest {
+ public:
+  ExternalEngineEmbedderTest();
+  ~ExternalEngineEmbedderTest() override;
+
+  // EmbedderTest:
+  void SetUp() override;
+  void TearDown() override;
+
+  CFXJS_Engine* engine() const { return m_Engine.get(); }
+  v8::Local<v8::Context> GetV8Context();
+
+ private:
+  std::unique_ptr<CFXJS_Engine> m_Engine;
+};
+
+#endif  // TESTING_EXTERNAL_ENGINE_EMBEDDER_TEST_H_
diff --git a/testing/fake_file_access.cpp b/testing/fake_file_access.cpp
index 723f772..4bd8fa1 100644
--- a/testing/fake_file_access.cpp
+++ b/testing/fake_file_access.cpp
@@ -1,16 +1,13 @@
-// Copyright 2017 PDFium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "testing/fake_file_access.h"
 
-#include <algorithm>
-#include <set>
 #include <utility>
-#include <vector>
 
 #include "core/fxcrt/fx_system.h"
-#include "third_party/base/ptr_util.h"
+#include "third_party/base/check.h"
 
 namespace {
 
@@ -76,13 +73,13 @@
 
 FakeFileAccess::FakeFileAccess(FPDF_FILEACCESS* file_access)
     : file_access_(file_access),
-      file_access_wrapper_(pdfium::MakeUnique<FileAccessWrapper>(this)),
-      file_avail_(pdfium::MakeUnique<FileAvailImpl>(this)),
-      download_hints_(pdfium::MakeUnique<DownloadHintsImpl>(this)) {
-  ASSERT(file_access_);
+      file_access_wrapper_(std::make_unique<FileAccessWrapper>(this)),
+      file_avail_(std::make_unique<FileAvailImpl>(this)),
+      download_hints_(std::make_unique<DownloadHintsImpl>(this)) {
+  DCHECK(file_access_);
 }
 
-FakeFileAccess::~FakeFileAccess() {}
+FakeFileAccess::~FakeFileAccess() = default;
 
 FPDF_BOOL FakeFileAccess::IsDataAvail(size_t offset, size_t size) const {
   return available_data_.Contains(RangeSet::Range(offset, offset + size));
diff --git a/testing/fake_file_access.h b/testing/fake_file_access.h
index c8c08e3..6a0f3cf 100644
--- a/testing/fake_file_access.h
+++ b/testing/fake_file_access.h
@@ -1,4 +1,4 @@
-// Copyright 2017 PDFium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/font_renamer.cpp b/testing/font_renamer.cpp
new file mode 100644
index 0000000..6a3a81c
--- /dev/null
+++ b/testing/font_renamer.cpp
@@ -0,0 +1,91 @@
+// Copyright 2022 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/font_renamer.h"
+
+#include <string>
+
+#include "testing/test_fonts.h"
+
+namespace {
+
+FPDF_SYSFONTINFO* GetImpl(FPDF_SYSFONTINFO* info) {
+  return static_cast<FontRenamer*>(info)->impl();
+}
+
+void ReleaseImpl(FPDF_SYSFONTINFO* info) {
+  FPDF_SYSFONTINFO* impl = GetImpl(info);
+  impl->Release(impl);
+}
+
+void EnumFontsImpl(FPDF_SYSFONTINFO* info, void* mapper) {
+  FPDF_SYSFONTINFO* impl = GetImpl(info);
+  impl->EnumFonts(impl, mapper);
+}
+
+void* MapFontImpl(FPDF_SYSFONTINFO* info,
+                  int weight,
+                  FPDF_BOOL italic,
+                  int charset,
+                  int pitch_family,
+                  const char* face,
+                  FPDF_BOOL* exact) {
+  std::string renamed_face = TestFonts::RenameFont(face);
+  FPDF_SYSFONTINFO* impl = GetImpl(info);
+  return impl->MapFont(impl, weight, italic, charset, pitch_family,
+                       renamed_face.c_str(), exact);
+}
+
+void* GetFontImpl(FPDF_SYSFONTINFO* info, const char* face) {
+  // Any non-null return will do.
+  FPDF_SYSFONTINFO* impl = GetImpl(info);
+  std::string renamed_face = TestFonts::RenameFont(face);
+  return impl->GetFont(impl, renamed_face.c_str());
+}
+
+unsigned long GetFontDataImpl(FPDF_SYSFONTINFO* info,
+                              void* font,
+                              unsigned int table,
+                              unsigned char* buffer,
+                              unsigned long buf_size) {
+  FPDF_SYSFONTINFO* impl = GetImpl(info);
+  return impl->GetFontData(impl, font, table, buffer, buf_size);
+}
+
+unsigned long GetFaceNameImpl(FPDF_SYSFONTINFO* info,
+                              void* font,
+                              char* buffer,
+                              unsigned long buf_size) {
+  FPDF_SYSFONTINFO* impl = GetImpl(info);
+  return impl->GetFaceName(impl, font, buffer, buf_size);
+}
+
+int GetFontCharsetImpl(FPDF_SYSFONTINFO* info, void* font) {
+  FPDF_SYSFONTINFO* impl = GetImpl(info);
+  return impl->GetFontCharset(impl, font);
+}
+
+void DeleteFontImpl(FPDF_SYSFONTINFO* info, void* font) {
+  FPDF_SYSFONTINFO* impl = GetImpl(info);
+  impl->DeleteFont(impl, font);
+}
+
+}  // namespace
+
+FontRenamer::FontRenamer() : impl_(FPDF_GetDefaultSystemFontInfo()) {
+  version = 1;
+  Release = ReleaseImpl;
+  EnumFonts = EnumFontsImpl;
+  MapFont = MapFontImpl;
+  GetFont = GetFontImpl;
+  GetFontData = GetFontDataImpl;
+  GetFaceName = GetFaceNameImpl;
+  GetFontCharset = GetFontCharsetImpl;
+  DeleteFont = DeleteFontImpl;
+  FPDF_SetSystemFontInfo(this);
+}
+
+FontRenamer::~FontRenamer() {
+  FPDF_FreeDefaultSystemFontInfo(impl_.ExtractAsDangling());
+}
diff --git a/testing/font_renamer.h b/testing/font_renamer.h
new file mode 100644
index 0000000..2c32c35
--- /dev/null
+++ b/testing/font_renamer.h
@@ -0,0 +1,22 @@
+// Copyright 2022 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_FONT_RENAMER_H_
+#define TESTING_FONT_RENAMER_H_
+
+#include "core/fxcrt/unowned_ptr.h"
+#include "public/fpdf_sysfontinfo.h"
+
+class FontRenamer final : public FPDF_SYSFONTINFO {
+ public:
+  FontRenamer();
+  ~FontRenamer();
+
+  FPDF_SYSFONTINFO* impl() { return impl_; }
+
+ private:
+  UnownedPtr<FPDF_SYSFONTINFO> impl_;
+};
+
+#endif  // TESTING_FONT_RENAMER_H_
diff --git a/testing/free_deleter.h b/testing/free_deleter.h
index 58f40da..d183050 100644
--- a/testing/free_deleter.h
+++ b/testing/free_deleter.h
@@ -1,4 +1,4 @@
-// Copyright 2019 PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/fuzzers/BUILD.gn b/testing/fuzzers/BUILD.gn
index 03e0610..35624cb 100644
--- a/testing/fuzzers/BUILD.gn
+++ b/testing/fuzzers/BUILD.gn
@@ -1,16 +1,16 @@
-# Copyright 2016 The PDFium Authors. All rights reserved.
+# Copyright 2016 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build_overrides/build.gni")
 import("../../pdfium.gni")
 
 config("fuzzer_config") {
-  configs = [ "../..:pdfium_core_config" ]
-
-  defines = [
-    "PNG_PREFIX",
-    "PNG_USE_READ_MACROS",
+  configs = [
+    "../..:pdfium_strict_config",
+    "../..:pdfium_noshorten_config",
   ]
+  defines = []
   include_dirs = [ "../.." ]
 }
 
@@ -43,9 +43,9 @@
       "pdf_cfx_barcode_fuzzer",
       "pdf_codec_jpeg_fuzzer",
       "pdf_css_fuzzer",
-      "pdf_fm2js_fuzzer",
       "pdf_formcalc_context_fuzzer",
       "pdf_formcalc_fuzzer",
+      "pdf_formcalc_translate_fuzzer",
       "pdfium_xfa_fuzzer",
       "pdfium_xfa_lpm_fuzzer",
     ]
@@ -68,45 +68,96 @@
 }
 if (is_clang) {
   # Fuzzers that use FuzzedDataProvider can only be built with Clang.
-  fuzzer_list += [ "pdf_nametree_fuzzer" ]
+  fuzzer_list += [
+    "pdf_cpdf_tounicodemap_fuzzer",
+    "pdf_nametree_fuzzer",
+  ]
+  if (pdf_enable_xfa) {
+    fuzzer_list += [
+      "pdf_xfa_fdp_fuzzer",
+      "pdf_xfa_raw_fuzzer",
+      "pdf_xfa_xdp_fdp_fuzzer",
+    ]
+  }
 }
 
+# Note that this only compiles all the fuzzers, to prevent compile breakages.
+# It does not link and create fuzzer executables. That is done in Chromium.
 group("fuzzers") {
   testonly = true
   deps = []
   foreach(fuzzer, fuzzer_list) {
     deps += [ ":${fuzzer}_src" ]
   }
+
+  if (is_component_build) {
+    deps += [ ":fuzzer_impls" ]
+  }
+}
+
+source_set("fuzzer_pdf_templates") {
+  sources = [ "pdf_fuzzer_templates.h" ]
 }
 
 source_set("fuzzer_init") {
   testonly = true
   sources = [ "pdf_fuzzer_init.cc" ]
   include_dirs = [ "../.." ]
-  deps = [ "../../:pdfium_public_headers" ]
+  deps = [
+    "../../:pdfium_public_headers",
+    "../../fpdfsdk",
+  ]
+}
+
+if (pdf_enable_xfa) {
+  assert(pdf_enable_v8)
+  source_set("fuzzer_xfa_process_state") {
+    testonly = !is_component_build
+    sources = [
+      "xfa_process_state.cc",
+      "xfa_process_state.h",
+    ]
+    configs += [ ":fuzzer_config" ]
+    deps = [
+      "../../fxjs:gc",
+      "//v8",
+    ]
+  }
 }
 
 source_set("fuzzer_init_public") {
   testonly = true
   sources = [ "pdf_fuzzer_init_public.cc" ]
   include_dirs = [ "../.." ]
-  deps = [ "../../:pdfium_public_headers" ]
+  deps = [
+    ":fuzzer_utils",
+    "../../:pdfium_public_headers",
+    "../../fpdfsdk",
+  ]
   if (pdf_enable_v8) {
     configs += [ "//v8:external_startup_data" ]
     deps += [
       "../:test_support",
+      "../../fxjs",
       "//v8",
       "//v8:v8_libplatform",
     ]
+    if (pdf_enable_xfa) {
+      deps += [ ":fuzzer_xfa_process_state" ]
+    }
   }
 }
 
 if (is_component_build) {
   group("fuzzer_impls") {
+    testonly = true
     deps = []
     foreach(fuzzer, fuzzer_list) {
       deps += [ ":${fuzzer}_impl" ]
     }
+    if (pdf_enable_xfa) {
+      deps += [ ":fuzzer_xfa_process_state" ]
+    }
   }
 }
 
@@ -119,6 +170,7 @@
   configs += [ ":fuzzer_config" ]
   deps = [
     "../../:pdfium_public_headers",
+    "../../fpdfsdk",
     "../../third_party:pdfium_base",
   ]
 }
@@ -137,18 +189,20 @@
 }
 
 template("pdfium_fuzzer") {
-  if (defined(invoker.public_fuzzer) && invoker.public_fuzzer) {
+  is_public = defined(invoker.public_fuzzer) && invoker.public_fuzzer
+  if (is_public) {
     init_dep = ":fuzzer_init_public"
   } else {
     init_dep = ":fuzzer_init"
   }
   if (is_component_build) {
     # In component builds, fuzzers are split into "_impl" and "_src" targets.
-    # The "_impl" target exports the fuzzer implementation and gets statically
-    # linked into the PDFium shared library.  The "_src" target is a thin
-    # wrapper that imports the fuzzer from PDFium; this gets linked into the
-    # real fuzzer executable.  In static builds, there's only a single "_src"
-    # target that contains the implementation and statically links in PDFium.
+    # The "_impl" target exports the fuzzer implementation. The "_src" target
+    # is a thin wrapper that imports the fuzzer from PDFium; this gets linked
+    # into the real fuzzer executable. The real fuzzer target has to depend on
+    # both the "_impl" and "_src" targets.
+    # In static builds, there's only a single "_src" target that contains the
+    # implementation and statically links in PDFium.
 
     impl_name = target_name + "_impl"
     template_target_name = target_name
@@ -156,7 +210,7 @@
       testonly = true
       sources = [ "component_fuzzer_template.cc" ]
       deps = [
-        "../../:pdfium",
+        "../../:pdfium_public_headers",
         init_dep,
       ]
       configs += [ ":fuzzer_config" ]
@@ -166,16 +220,14 @@
     impl_name = target_name + "_src"
   }
   source_set(impl_name) {
+    testonly = true
     sources = invoker.sources
+    defines = []
     deps = []
     if (defined(invoker.deps)) {
       deps += invoker.deps
     }
-    configs -= [ "//build/config/compiler:chromium_code" ]
-    configs += [
-      "//build/config/compiler:no_chromium_code",
-      ":fuzzer_config",
-    ]
+    configs += [ ":fuzzer_config" ]
     if (is_component_build) {
       # |export| should be consistent with FPDF_EXPORT In public/fpdfview.h.
       if (is_win) {
@@ -183,7 +235,7 @@
       } else {
         export = "__attribute__((visibility(\"default\")))"
       }
-      defines = [ "LLVMFuzzerTestOneInput=${export} ${template_target_name}" ]
+      defines += [ "LLVMFuzzerTestOneInput=${export} ${template_target_name}" ]
       deps += [ "../../:pdfium_public_headers" ]
     } else {
       testonly = true
@@ -192,6 +244,12 @@
         init_dep,
       ]
     }
+    if (is_public && pdf_enable_xfa) {
+      deps += [ ":fuzzer_xfa_process_state" ]
+    }
+    if (build_with_chromium) {
+      defines += [ "BUILD_WITH_CHROMIUM" ]
+    }
   }
 }
 
@@ -200,6 +258,7 @@
     sources = [ "pdf_cjs_util_fuzzer.cc" ]
     deps = [
       "../../core/fxcrt",
+      "../../fpdfsdk",
       "../../fxjs",
     ]
   }
@@ -207,6 +266,7 @@
     sources = [ "pdf_fx_date_helpers_fuzzer.cc" ]
     deps = [
       "../../core/fxcrt",
+      "../../fpdfsdk",
       "../../fxjs",
     ]
   }
@@ -218,7 +278,7 @@
         "../../:freetype_common",
         "../../core/fxcrt",
         "../../core/fxge",
-        "../../xfa/fgas",
+        "../../xfa/fgas/font",
         "../../xfa/fgas/layout",
         "//third_party/icu:icuuc",
       ]
@@ -227,10 +287,15 @@
     pdfium_fuzzer("pdf_cfgas_stringformatter_fuzzer") {
       sources = [ "pdf_cfgas_stringformatter_fuzzer.cc" ]
       deps = [
+        ":fuzzer_utils",
         "../../core/fxcrt",
-        "../../xfa/fgas",
+        "../../fpdfsdk",
+        "../../fxjs:gc",
+        "../../xfa/fgas/crt",
+        "../../xfa/fxfa",
         "../../xfa/fxfa/parser",
       ]
+      public_fuzzer = true
     }
 
     pdfium_fuzzer("pdf_cfx_barcode_fuzzer") {
@@ -331,12 +396,15 @@
       ]
     }
 
-    pdfium_fuzzer("pdf_fm2js_fuzzer") {
-      sources = [ "pdf_fm2js_fuzzer.cc" ]
+    pdfium_fuzzer("pdf_formcalc_translate_fuzzer") {
+      sources = [ "pdf_formcalc_translate_fuzzer.cc" ]
       deps = [
+        ":fuzzer_utils",
         "../../core/fxcrt",
+        "../../fpdfsdk",
         "../../fxjs",
       ]
+      public_fuzzer = true
     }
 
     pdfium_fuzzer("pdf_formcalc_context_fuzzer") {
@@ -356,9 +424,12 @@
     pdfium_fuzzer("pdf_formcalc_fuzzer") {
       sources = [ "pdf_formcalc_fuzzer.cc" ]
       deps = [
+        ":fuzzer_utils",
         "../../core/fxcrt",
-        "../../xfa/fxfa/fm2js",
+        "../../fxjs:gc",
+        "../../xfa/fxfa/formcalc",
       ]
+      public_fuzzer = true
     }
 
     pdfium_fuzzer("pdfium_xfa_fuzzer") {
@@ -385,6 +456,15 @@
 }
 
 if (is_clang) {
+  pdfium_fuzzer("pdf_cpdf_tounicodemap_fuzzer") {
+    sources = [ "pdf_cpdf_tounicodemap_fuzzer.cc" ]
+    deps = [
+      "../../core/fpdfapi/font",
+      "../../core/fpdfapi/parser",
+      "../../core/fxcrt",
+    ]
+  }
+
   pdfium_fuzzer("pdf_nametree_fuzzer") {
     sources = [ "pdf_nametree_fuzzer.cc" ]
     deps = [
@@ -394,6 +474,34 @@
       "../../third_party:pdfium_base",
     ]
   }
+  if (pdf_enable_xfa) {
+    pdfium_fuzzer("pdf_xfa_fdp_fuzzer") {
+      sources = [ "pdf_xfa_fdp_fuzzer.cc" ]
+      deps = [
+        ":fuzzer_helper",
+        ":fuzzer_pdf_templates",
+        "../../third_party:pdfium_base",
+      ]
+      public_fuzzer = true
+    }
+    pdfium_fuzzer("pdf_xfa_raw_fuzzer") {
+      sources = [ "pdf_xfa_raw_fuzzer.cc" ]
+      deps = [
+        ":fuzzer_helper",
+        ":fuzzer_pdf_templates",
+        "../../third_party:pdfium_base",
+      ]
+      public_fuzzer = true
+    }
+    pdfium_fuzzer("pdf_xfa_xdp_fdp_fuzzer") {
+      sources = [ "pdf_xfa_xdp_fdp_fuzzer.cc" ]
+      deps = [
+        ":fuzzer_helper",
+        ":fuzzer_pdf_templates",
+      ]
+      public_fuzzer = true
+    }
+  }
 }
 
 pdfium_fuzzer("pdf_cmap_fuzzer") {
@@ -401,6 +509,7 @@
   deps = [
     "../../:freetype_common",
     "../../core/fpdfapi/font",
+    "../../core/fxcrt",
     "../../third_party:pdfium_base",
   ]
 }
@@ -485,6 +594,7 @@
   sources = [ "pdf_scanlinecompositor_fuzzer.cc" ]
   deps = [
     ":fuzzer_utils",
+    "../../core/fxcrt",
     "../../core/fxge",
     "../../third_party:pdfium_base",
   ]
@@ -510,5 +620,11 @@
 pdfium_fuzzer("pdfium_fuzzer") {
   sources = [ "pdfium_fuzzer.cc" ]
   deps = [ ":fuzzer_helper" ]
+  if (build_with_chromium) {
+    deps += [
+      "//base",
+      "//base/test:test_support",
+    ]
+  }
   public_fuzzer = true
 }
diff --git a/testing/fuzzers/DEPS b/testing/fuzzers/DEPS
index fe9eaf6..d577044 100644
--- a/testing/fuzzers/DEPS
+++ b/testing/fuzzers/DEPS
@@ -1,4 +1,7 @@
 include_rules = [
   '+fxbarcode',
   '+xfa',
+
+  # Only used when the fuzzer is embedded in Chromium.
+  '+base',
 ]
diff --git a/testing/fuzzers/component_fuzzer_template.cc b/testing/fuzzers/component_fuzzer_template.cc
index 89883f5..6f65bc1 100644
--- a/testing/fuzzers/component_fuzzer_template.cc
+++ b/testing/fuzzers/component_fuzzer_template.cc
@@ -1,9 +1,9 @@
-// Copyright 2019 The PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <cstddef>
-#include <cstdint>
+#include <stddef.h>
+#include <stdint.h>
 
 #include "public/fpdfview.h"
 
diff --git a/testing/fuzzers/pdf_bidi_fuzzer.cc b/testing/fuzzers/pdf_bidi_fuzzer.cc
index 614df52..c0ca776 100644
--- a/testing/fuzzers/pdf_bidi_fuzzer.cc
+++ b/testing/fuzzers/pdf_bidi_fuzzer.cc
@@ -1,28 +1,30 @@
-// Copyright 2018 The PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include <cstdint>
+#include <memory>
 
+#include "core/fxcrt/fx_codepage.h"
 #include "core/fxcrt/widestring.h"
 #include "core/fxge/cfx_font.h"
 #include "core/fxge/fx_font.h"
-#include "third_party/base/ptr_util.h"
-#include "xfa/fgas/font/cfgas_fontmgr.h"
 #include "xfa/fgas/font/cfgas_gefont.h"
-#include "xfa/fgas/layout/cfx_char.h"
-#include "xfa/fgas/layout/cfx_rtfbreak.h"
+#include "xfa/fgas/layout/cfgas_char.h"
+#include "xfa/fgas/layout/cfgas_rtfbreak.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  auto fontmgr = pdfium::MakeUnique<CFGAS_FontMgr>();
+  if (size > 8192)
+    return 0;
 
-  auto font = pdfium::MakeUnique<CFX_Font>();
-  font->LoadSubst("Arial", true, 0, FXFONT_FW_NORMAL, 0, 0, 0);
+  auto font = std::make_unique<CFX_Font>();
+  font->LoadSubst("Arial", true, 0, FXFONT_FW_NORMAL, 0, FX_CodePage::kDefANSI,
+                  0);
   assert(font);
 
-  CFX_RTFBreak rtf_break(FX_LAYOUTSTYLE_ExpandTab);
+  CFGAS_RTFBreak rtf_break(CFGAS_Break::LayoutStyle::kExpandTab);
   rtf_break.SetLineBreakTolerance(1);
-  rtf_break.SetFont(CFGAS_GEFont::LoadFont(std::move(font), fontmgr.get()));
+  rtf_break.SetFont(CFGAS_GEFont::LoadFont(std::move(font)));
   rtf_break.SetFontSize(12);
 
   WideString input =
@@ -31,8 +33,8 @@
   for (wchar_t ch : input)
     rtf_break.AppendChar(ch);
 
-  std::vector<CFX_Char> chars =
+  std::vector<CFGAS_Char> chars =
       rtf_break.GetCurrentLineForTesting()->m_LineChars;
-  CFX_Char::BidiLine(&chars, chars.size());
+  CFGAS_Char::BidiLine(&chars, chars.size());
   return 0;
 }
diff --git a/testing/fuzzers/pdf_cfgas_stringformatter_fuzzer.cc b/testing/fuzzers/pdf_cfgas_stringformatter_fuzzer.cc
index b6a6663..12f6206 100644
--- a/testing/fuzzers/pdf_cfgas_stringformatter_fuzzer.cc
+++ b/testing/fuzzers/pdf_cfgas_stringformatter_fuzzer.cc
@@ -1,4 +1,4 @@
-// Copyright 2019 PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -6,17 +6,25 @@
 
 #include <stdint.h>
 
-#include "core/fxcrt/fx_memory.h"
+#include <iterator>
+
+#include "core/fxcrt/cfx_datetime.h"
 #include "core/fxcrt/fx_string.h"
-#include "third_party/base/ptr_util.h"
+#include "public/fpdfview.h"
+#include "testing/fuzzers/pdfium_fuzzer_util.h"
+#include "testing/fuzzers/xfa_process_state.h"
+#include "v8/include/cppgc/heap.h"
+#include "v8/include/cppgc/persistent.h"
 #include "xfa/fxfa/parser/cxfa_localemgr.h"
 
 namespace {
 
 const wchar_t* const kLocales[] = {L"en", L"fr", L"jp", L"zh"};
-const FX_DATETIMETYPE kTypes[] = {FX_DATETIMETYPE_Date, FX_DATETIMETYPE_Time,
-                                  FX_DATETIMETYPE_DateTime,
-                                  FX_DATETIMETYPE_TimeDate};
+const CFGAS_StringFormatter::DateTimeType kTypes[] = {
+    CFGAS_StringFormatter::DateTimeType::kDate,
+    CFGAS_StringFormatter::DateTimeType::kTime,
+    CFGAS_StringFormatter::DateTimeType::kDateTime,
+    CFGAS_StringFormatter::DateTimeType::kTimeDate};
 
 }  // namespace
 
@@ -24,16 +32,12 @@
   if (size < 5 || size > 128)  // Big strings are unlikely to help.
     return 0;
 
-  // Static for speed.
-  static std::vector<std::unique_ptr<CXFA_LocaleMgr>> mgrs;
-  if (mgrs.empty()) {
-    for (const auto* locale : kLocales)
-      mgrs.push_back(pdfium::MakeUnique<CXFA_LocaleMgr>(nullptr, locale));
-  }
+  auto* state = static_cast<XFAProcessState*>(FPDF_GetFuzzerPerProcessState());
+  cppgc::Heap* heap = state->GetHeap();
 
   uint8_t test_selector = data[0] % 10;
-  uint8_t locale_selector = data[1] % FX_ArraySize(kLocales);
-  uint8_t type_selector = data[2] % FX_ArraySize(kTypes);
+  uint8_t locale_selector = data[1] % std::size(kLocales);
+  uint8_t type_selector = data[2] % std::size(kTypes);
   data += 3;
   size -= 3;
 
@@ -44,8 +48,7 @@
   WideString value =
       WideString::FromLatin1(ByteStringView(data + pattern_len, value_len));
 
-  auto fmt = pdfium::MakeUnique<CFGAS_StringFormatter>(
-      mgrs[locale_selector].get(), pattern);
+  auto fmt = std::make_unique<CFGAS_StringFormatter>(pattern);
 
   WideString result;
   CFX_DateTime dt;
@@ -53,33 +56,55 @@
     case 0:
       fmt->FormatText(value, &result);
       break;
-    case 1:
-      fmt->FormatNum(value, &result);
+    case 1: {
+      auto* mgr = cppgc::MakeGarbageCollected<CXFA_LocaleMgr>(
+          heap->GetAllocationHandle(), heap, nullptr,
+          kLocales[locale_selector]);
+      fmt->FormatNum(mgr, value, &result);
       break;
-    case 2:
-      fmt->FormatDateTime(value, kTypes[type_selector], &result);
+    }
+    case 2: {
+      auto* mgr = cppgc::MakeGarbageCollected<CXFA_LocaleMgr>(
+          heap->GetAllocationHandle(), heap, nullptr,
+          kLocales[locale_selector]);
+      fmt->FormatDateTime(mgr, value, kTypes[type_selector], &result);
       break;
-    case 3:
+    }
+    case 3: {
       fmt->FormatNull(&result);
       break;
-    case 4:
+    }
+    case 4: {
       fmt->FormatZero(&result);
       break;
-    case 5:
+    }
+    case 5: {
       fmt->ParseText(value, &result);
       break;
-    case 6:
-      fmt->ParseNum(value, &result);
+    }
+    case 6: {
+      auto* mgr = cppgc::MakeGarbageCollected<CXFA_LocaleMgr>(
+          heap->GetAllocationHandle(), heap, nullptr,
+          kLocales[locale_selector]);
+      fmt->ParseNum(mgr, value, &result);
       break;
-    case 7:
-      fmt->ParseDateTime(value, kTypes[type_selector], &dt);
+    }
+    case 7: {
+      auto* mgr = cppgc::MakeGarbageCollected<CXFA_LocaleMgr>(
+          heap->GetAllocationHandle(), heap, nullptr,
+          kLocales[locale_selector]);
+      fmt->ParseDateTime(mgr, value, kTypes[type_selector], &dt);
       break;
-    case 8:
+    }
+    case 8: {
       fmt->ParseNull(value);
       break;
-    case 9:
+    }
+    case 9: {
       fmt->ParseZero(value);
       break;
+    }
   }
+  state->ForceGCAndPump();
   return 0;
 }
diff --git a/testing/fuzzers/pdf_cfx_barcode_fuzzer.cc b/testing/fuzzers/pdf_cfx_barcode_fuzzer.cc
index 7b31b2b..35afec9 100644
--- a/testing/fuzzers/pdf_cfx_barcode_fuzzer.cc
+++ b/testing/fuzzers/pdf_cfx_barcode_fuzzer.cc
@@ -1,9 +1,7 @@
-// Copyright 2017 The PDFium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <memory>
-
 #include "core/fxcrt/fx_string.h"
 #include "fxbarcode/cfx_barcode.h"
 
@@ -11,7 +9,8 @@
   if (size < 2 * sizeof(uint16_t))
     return 0;
 
-  BC_TYPE type = static_cast<BC_TYPE>(data[0] % (BC_LAST + 1));
+  BC_TYPE type =
+      static_cast<BC_TYPE>(data[0] % (static_cast<int>(BC_TYPE::kLast) + 1));
 
   // Only used one byte, but align with uint16_t for string below.
   data += sizeof(uint16_t);
diff --git a/testing/fuzzers/pdf_cjs_util_fuzzer.cc b/testing/fuzzers/pdf_cjs_util_fuzzer.cc
index 5ccb65b..3885c7b 100644
--- a/testing/fuzzers/pdf_cjs_util_fuzzer.cc
+++ b/testing/fuzzers/pdf_cjs_util_fuzzer.cc
@@ -1,4 +1,4 @@
-// Copyright 2018 The PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/fuzzers/pdf_cmap_fuzzer.cc b/testing/fuzzers/pdf_cmap_fuzzer.cc
index 180a6a7..d4f9c70 100644
--- a/testing/fuzzers/pdf_cmap_fuzzer.cc
+++ b/testing/fuzzers/pdf_cmap_fuzzer.cc
@@ -1,10 +1,11 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <cstdint>
+#include <stdint.h>
 
 #include "core/fpdfapi/font/cpdf_cmap.h"
+#include "core/fxcrt/retain_ptr.h"
 #include "third_party/base/span.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
diff --git a/testing/fuzzers/pdf_codec_a85_fuzzer.cc b/testing/fuzzers/pdf_codec_a85_fuzzer.cc
index f9ae1fe..2b1614d 100644
--- a/testing/fuzzers/pdf_codec_a85_fuzzer.cc
+++ b/testing/fuzzers/pdf_codec_a85_fuzzer.cc
@@ -1,16 +1,12 @@
-// Copyright 2017 The PDFium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include <cstdint>
-#include <memory>
 
 #include "core/fxcodec/basic/basicmodule.h"
-#include "core/fxcrt/fx_memory_wrappers.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  std::unique_ptr<uint8_t, FxFreeDeleter> dest_buf;
-  uint32_t dest_size = 0;
-  BasicModule::A85Encode({data, size}, &dest_buf, &dest_size);
+  BasicModule::A85Encode({data, size});
   return 0;
 }
diff --git a/testing/fuzzers/pdf_codec_bmp_fuzzer.cc b/testing/fuzzers/pdf_codec_bmp_fuzzer.cc
index 71f9150..1bc023a 100644
--- a/testing/fuzzers/pdf_codec_bmp_fuzzer.cc
+++ b/testing/fuzzers/pdf_codec_bmp_fuzzer.cc
@@ -1,4 +1,4 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/fuzzers/pdf_codec_fax_fuzzer.cc b/testing/fuzzers/pdf_codec_fax_fuzzer.cc
index d0c2984..793d18e 100644
--- a/testing/fuzzers/pdf_codec_fax_fuzzer.cc
+++ b/testing/fuzzers/pdf_codec_fax_fuzzer.cc
@@ -1,4 +1,4 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -38,7 +38,7 @@
 
   if (decoder) {
     int line = 0;
-    while (decoder->GetScanline(line))
+    while (!decoder->GetScanline(line).empty())
       line++;
   }
 
diff --git a/testing/fuzzers/pdf_codec_gif_fuzzer.cc b/testing/fuzzers/pdf_codec_gif_fuzzer.cc
index 69129e7..4adbad7 100644
--- a/testing/fuzzers/pdf_codec_gif_fuzzer.cc
+++ b/testing/fuzzers/pdf_codec_gif_fuzzer.cc
@@ -1,4 +1,4 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/fuzzers/pdf_codec_icc_fuzzer.cc b/testing/fuzzers/pdf_codec_icc_fuzzer.cc
index ca027331..4db79d2 100644
--- a/testing/fuzzers/pdf_codec_icc_fuzzer.cc
+++ b/testing/fuzzers/pdf_codec_icc_fuzzer.cc
@@ -1,23 +1,21 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include <cstdint>
 
-#include "core/fxcodec/icc/iccmodule.h"
+#include "core/fxcodec/icc/icc_transform.h"
 #include "third_party/base/span.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  std::unique_ptr<CLcmsCmm> transform =
-      IccModule::CreateTransformSRGB(pdfium::make_span(data, size));
+  std::unique_ptr<fxcodec::IccTransform> transform =
+      fxcodec::IccTransform::CreateTransformSRGB(pdfium::make_span(data, size));
+  if (!transform)
+    return 0;
 
-  if (transform) {
-    float src[4];
-    float dst[4];
-    for (int i = 0; i < 4; i++)
-      src[i] = 0.5f;
-    IccModule::Translate(transform.get(), transform->components(), src, dst);
-  }
-
+  const float src[4] = {0.5f, 0.5f, 0.5f, 0.5f};
+  float dst[4];
+  transform->Translate(pdfium::make_span(src).first(transform->components()),
+                       pdfium::make_span(dst));
   return 0;
 }
diff --git a/testing/fuzzers/pdf_codec_jbig2_fuzzer.cc b/testing/fuzzers/pdf_codec_jbig2_fuzzer.cc
index 2878d7c..59eda76 100644
--- a/testing/fuzzers/pdf_codec_jbig2_fuzzer.cc
+++ b/testing/fuzzers/pdf_codec_jbig2_fuzzer.cc
@@ -1,17 +1,16 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <cstdint>
+#include <stdint.h>
 
 #include "core/fxcodec/jbig2/JBig2_Context.h"
 #include "core/fxcodec/jbig2/JBig2_DocumentContext.h"
-#include "core/fxcodec/jbig2/jbig2module.h"
+#include "core/fxcodec/jbig2/jbig2_decoder.h"
 #include "core/fxcrt/fx_safe_types.h"
 #include "core/fxge/dib/cfx_dibitmap.h"
-#include "core/fxge/fx_dib.h"
+#include "core/fxge/dib/fx_dib.h"
 #include "testing/fuzzers/pdfium_fuzzer_util.h"
-#include "third_party/base/ptr_util.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   const size_t kParameterSize = 8;
@@ -32,17 +31,16 @@
     return 0;
 
   auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
-  if (!bitmap->Create(width, height, FXDIB_1bppRgb))
+  if (!bitmap->Create(width, height, FXDIB_Format::k1bppRgb))
     return 0;
 
-  Jbig2Module module;
+  JBig2_DocumentContext document_context;
   Jbig2Context jbig2_context;
-  std::unique_ptr<JBig2_DocumentContext> document_context;
-  FXCODEC_STATUS status = module.StartDecode(
+  FXCODEC_STATUS status = Jbig2Decoder::StartDecode(
       &jbig2_context, &document_context, width, height, {data, size}, 1, {}, 0,
       bitmap->GetBuffer(), bitmap->GetPitch(), nullptr);
 
-  while (status == FXCODEC_STATUS_DECODE_TOBECONTINUE)
-    status = module.ContinueDecode(&jbig2_context, nullptr);
+  while (status == FXCODEC_STATUS::kDecodeToBeContinued)
+    status = Jbig2Decoder::ContinueDecode(&jbig2_context, nullptr);
   return 0;
 }
diff --git a/testing/fuzzers/pdf_codec_jpeg_fuzzer.cc b/testing/fuzzers/pdf_codec_jpeg_fuzzer.cc
index eaa0889..a9f7216 100644
--- a/testing/fuzzers/pdf_codec_jpeg_fuzzer.cc
+++ b/testing/fuzzers/pdf_codec_jpeg_fuzzer.cc
@@ -1,4 +1,4 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/fuzzers/pdf_codec_png_fuzzer.cc b/testing/fuzzers/pdf_codec_png_fuzzer.cc
index 61a6574..14d9bd3 100644
--- a/testing/fuzzers/pdf_codec_png_fuzzer.cc
+++ b/testing/fuzzers/pdf_codec_png_fuzzer.cc
@@ -1,4 +1,4 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/fuzzers/pdf_codec_rle_fuzzer.cc b/testing/fuzzers/pdf_codec_rle_fuzzer.cc
index 7b40b01..70ce5d4 100644
--- a/testing/fuzzers/pdf_codec_rle_fuzzer.cc
+++ b/testing/fuzzers/pdf_codec_rle_fuzzer.cc
@@ -1,16 +1,12 @@
-// Copyright 2017 The PDFium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include <cstdint>
-#include <memory>
 
 #include "core/fxcodec/basic/basicmodule.h"
-#include "core/fxcrt/fx_memory_wrappers.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  std::unique_ptr<uint8_t, FxFreeDeleter> dest_buf;
-  uint32_t dest_size = 0;
-  BasicModule::RunLengthEncode({data, size}, &dest_buf, &dest_size);
+  BasicModule::RunLengthEncode({data, size});
   return 0;
 }
diff --git a/testing/fuzzers/pdf_codec_tiff_fuzzer.cc b/testing/fuzzers/pdf_codec_tiff_fuzzer.cc
index 187c311..6566b6b 100644
--- a/testing/fuzzers/pdf_codec_tiff_fuzzer.cc
+++ b/testing/fuzzers/pdf_codec_tiff_fuzzer.cc
@@ -1,4 +1,4 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/fuzzers/pdf_cpdf_tounicodemap_fuzzer.cc b/testing/fuzzers/pdf_cpdf_tounicodemap_fuzzer.cc
new file mode 100644
index 0000000..5f18564
--- /dev/null
+++ b/testing/fuzzers/pdf_cpdf_tounicodemap_fuzzer.cc
@@ -0,0 +1,38 @@
+// Copyright 2021 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "core/fpdfapi/font/cpdf_tounicodemap.h"
+#include "core/fpdfapi/parser/cpdf_stream.h"
+#include "core/fxcrt/retain_ptr.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  static constexpr size_t kParameterSize = sizeof(uint32_t) + sizeof(wchar_t);
+  if (size <= kParameterSize)
+    return 0;
+
+  // Limit data size to prevent fuzzer timeout.
+  static constexpr size_t kMaxDataSize = 256 * 1024;
+  if (size > kParameterSize + kMaxDataSize)
+    return 0;
+
+  FuzzedDataProvider data_provider(data, size);
+  uint32_t charcode_to_lookup = data_provider.ConsumeIntegral<uint32_t>();
+  wchar_t char_for_reverse_lookup = data_provider.ConsumeIntegral<wchar_t>();
+
+  std::vector<uint8_t> remaining =
+      data_provider.ConsumeRemainingBytes<uint8_t>();
+  auto stream = pdfium::MakeRetain<CPDF_Stream>();
+  stream->SetData(remaining);
+
+  auto to_unicode_map = std::make_unique<CPDF_ToUnicodeMap>(std::move(stream));
+  to_unicode_map->Lookup(charcode_to_lookup);
+  to_unicode_map->ReverseLookup(char_for_reverse_lookup);
+  return 0;
+}
diff --git a/testing/fuzzers/pdf_css_fuzzer.cc b/testing/fuzzers/pdf_css_fuzzer.cc
index 5f1471d..f11705c 100644
--- a/testing/fuzzers/pdf_css_fuzzer.cc
+++ b/testing/fuzzers/pdf_css_fuzzer.cc
@@ -1,9 +1,7 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <memory>
-
 #include "core/fxcrt/css/cfx_css.h"
 #include "core/fxcrt/css/cfx_csssyntaxparser.h"
 #include "core/fxcrt/fx_string.h"
@@ -16,11 +14,11 @@
   if (input.IsEmpty())
     return 0;
 
-  CFX_CSSSyntaxParser parser(input.c_str(), input.GetLength());
-  CFX_CSSSyntaxStatus status;
+  CFX_CSSSyntaxParser parser(input.AsStringView());
+  CFX_CSSSyntaxParser::Status status;
   do {
     status = parser.DoSyntaxParse();
-  } while (status != CFX_CSSSyntaxStatus::Error &&
-           status != CFX_CSSSyntaxStatus::EOS);
+  } while (status != CFX_CSSSyntaxParser::Status::kError &&
+           status != CFX_CSSSyntaxParser::Status::kEOS);
   return 0;
 }
diff --git a/testing/fuzzers/pdf_fm2js_fuzzer.cc b/testing/fuzzers/pdf_fm2js_fuzzer.cc
deleted file mode 100644
index 67daa46..0000000
--- a/testing/fuzzers/pdf_fm2js_fuzzer.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2016 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 <cstddef>
-#include <cstdint>
-
-#include "core/fxcrt/cfx_widetextbuf.h"
-#include "core/fxcrt/fx_safe_types.h"
-#include "core/fxcrt/fx_string.h"
-#include "fxjs/xfa/cfxjse_formcalc_context.h"
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  FX_SAFE_SIZE_T safe_size = size;
-  if (!safe_size.IsValid())
-    return 0;
-
-  CFX_WideTextBuf js;
-  WideString input =
-      WideString::FromUTF8(ByteStringView(data, safe_size.ValueOrDie()));
-  CFXJSE_FormCalcContext::Translate(input.AsStringView(), &js);
-  return 0;
-}
diff --git a/testing/fuzzers/pdf_font_fuzzer.cc b/testing/fuzzers/pdf_font_fuzzer.cc
index 7c59630..a02052a 100644
--- a/testing/fuzzers/pdf_font_fuzzer.cc
+++ b/testing/fuzzers/pdf_font_fuzzer.cc
@@ -1,16 +1,15 @@
-// Copyright 2017 The PDFium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <cstring>
-#include <memory>
-
 #include "public/cpp/fpdf_scopers.h"
 #include "public/fpdf_edit.h"
 #include "public/fpdfview.h"
 
+static constexpr size_t kMaxFuzzBytes = 1024 * 1024 * 1024;  // 1 GB.
+
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  if (size < 2)
+  if (size < 2 || size > kMaxFuzzBytes)
     return 0;
 
   ScopedFPDFDocument doc(FPDF_CreateNewDocument());
@@ -19,7 +18,8 @@
   FPDF_BOOL cid = data[1];
   data += 2;
   size -= 2;
-  ScopedFPDFFont font(FPDFText_LoadFont(doc.get(), data, size, font_type, cid));
+  ScopedFPDFFont font(FPDFText_LoadFont(
+      doc.get(), data, static_cast<uint32_t>(size), font_type, cid));
   if (!font)
     return 0;
 
diff --git a/testing/fuzzers/pdf_formcalc_context_fuzzer.cc b/testing/fuzzers/pdf_formcalc_context_fuzzer.cc
index e2d73a8..638d086 100644
--- a/testing/fuzzers/pdf_formcalc_context_fuzzer.cc
+++ b/testing/fuzzers/pdf_formcalc_context_fuzzer.cc
@@ -1,9 +1,11 @@
-// Copyright 2019 The PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include <stdint.h>
 
+#include <memory>
+
 #include "core/fxcrt/fx_string.h"
 #include "fpdfsdk/cpdfsdk_helpers.h"
 #include "fpdfsdk/fpdfxfa/cpdfxfa_context.h"
@@ -11,7 +13,6 @@
 #include "fxjs/xfa/cfxjse_value.h"
 #include "public/fpdf_formfill.h"
 #include "testing/fuzzers/pdfium_fuzzer_helper.h"
-#include "v8/include/v8.h"
 #include "xfa/fxfa/cxfa_eventparam.h"
 
 namespace {
@@ -522,15 +523,14 @@
 
     CXFA_EventParam params;
     params.m_bCancelAction = false;
-    script_context->SetEventParam(&params);
+    CFXJSE_Engine::EventParamScope param_scope(script_context, nullptr,
+                                               &params);
     ByteStringView data_view(data_, size_);
 
-    auto value = pdfium::MakeUnique<CFXJSE_Value>(script_context->GetIsolate());
+    auto value = std::make_unique<CFXJSE_Value>();
     script_context->RunScript(CXFA_Script::Type::Formcalc,
                               WideString::FromUTF8(data_view).AsStringView(),
                               value.get(), xfa_document->GetRoot());
-
-    script_context->SetEventParam(nullptr);
   }
 
  private:
diff --git a/testing/fuzzers/pdf_formcalc_fuzzer.cc b/testing/fuzzers/pdf_formcalc_fuzzer.cc
index 08e22bb..d8a2a89 100644
--- a/testing/fuzzers/pdf_formcalc_fuzzer.cc
+++ b/testing/fuzzers/pdf_formcalc_fuzzer.cc
@@ -1,16 +1,18 @@
-// Copyright 2017 The PDFium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "core/fxcrt/cfx_widetextbuf.h"
 #include "core/fxcrt/fx_string.h"
-#include "xfa/fxfa/fm2js/cxfa_fmparser.h"
+#include "testing/fuzzers/pdfium_fuzzer_util.h"
+#include "testing/fuzzers/xfa_process_state.h"
+#include "xfa/fxfa/formcalc/cxfa_fmparser.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  auto* state = static_cast<XFAProcessState*>(FPDF_GetFuzzerPerProcessState());
   WideString input = WideString::FromUTF8(ByteStringView(data, size));
-
-  CXFA_FMParser parser(input.AsStringView());
+  CXFA_FMLexer lexer(input.AsStringView());
+  CXFA_FMParser parser(state->GetHeap(), &lexer);
   parser.Parse();
-
+  state->ForceGCAndPump();
   return 0;
 }
diff --git a/testing/fuzzers/pdf_formcalc_translate_fuzzer.cc b/testing/fuzzers/pdf_formcalc_translate_fuzzer.cc
new file mode 100644
index 0000000..82b8188
--- /dev/null
+++ b/testing/fuzzers/pdf_formcalc_translate_fuzzer.cc
@@ -0,0 +1,20 @@
+// Copyright 2016 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "core/fxcrt/fx_safe_types.h"
+#include "core/fxcrt/fx_string.h"
+#include "fxjs/xfa/cfxjse_formcalc_context.h"
+#include "testing/fuzzers/pdfium_fuzzer_util.h"
+#include "testing/fuzzers/xfa_process_state.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  auto* state = static_cast<XFAProcessState*>(FPDF_GetFuzzerPerProcessState());
+  WideString input = WideString::FromUTF8(ByteStringView(data, size));
+  CFXJSE_FormCalcContext::Translate(state->GetHeap(), input.AsStringView());
+  state->ForceGCAndPump();
+  return 0;
+}
diff --git a/testing/fuzzers/pdf_fuzzer_init.cc b/testing/fuzzers/pdf_fuzzer_init.cc
index 954eed0..9a75dd2 100644
--- a/testing/fuzzers/pdf_fuzzer_init.cc
+++ b/testing/fuzzers/pdf_fuzzer_init.cc
@@ -1,4 +1,4 @@
-// Copyright 2017 The PDFium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/fuzzers/pdf_fuzzer_init_public.cc b/testing/fuzzers/pdf_fuzzer_init_public.cc
index 5ece0bc..3227d47 100644
--- a/testing/fuzzers/pdf_fuzzer_init_public.cc
+++ b/testing/fuzzers/pdf_fuzzer_init_public.cc
@@ -1,24 +1,33 @@
-// Copyright 2019 The PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <string.h>
+#include "testing/fuzzers/pdf_fuzzer_init_public.h"
 
-#include <memory>
+#include <string.h>  // For memset()
 
-#include "public/fpdf_ext.h"
+#include <string>
+
+#include "testing/fuzzers/pdfium_fuzzer_util.h"
 
 #ifdef PDF_ENABLE_V8
 #include "testing/free_deleter.h"
 #include "testing/v8_initializer.h"
 #include "v8/include/v8-platform.h"
-#include "v8/include/v8.h"
-#endif
+#ifdef PDF_ENABLE_XFA
+#include "testing/fuzzers/xfa_process_state.h"
+#include "v8/include/v8-array-buffer.h"
+#include "v8/include/v8-isolate.h"
+#endif  // PDF_ENABLE_XFA
+#endif  // PDF_ENABLE_V8
 
 #ifdef _WIN32
 #include <windows.h>
 #elif defined(__APPLE__)
 #include <mach-o/dyld.h>
+#elif defined(__Fuchsia__)
+#include <limits.h>
+#include <unistd.h>
 #else  // Linux
 #include <linux/limits.h>
 #include <unistd.h>
@@ -26,10 +35,13 @@
 
 namespace {
 
+// pdf_fuzzer_init.cc and pdf_fuzzer_init_public.cc are mutually exclusive
+// and should not be built together.
+PDFFuzzerInitPublic* g_instance = new PDFFuzzerInitPublic();
+
 #ifdef PDF_ENABLE_V8
 std::string ProgramPath() {
   std::string result;
-
 #ifdef _WIN32
   char path[MAX_PATH];
   DWORD len = GetModuleFileNameA(nullptr, path, MAX_PATH);
@@ -56,41 +68,45 @@
 
 }  // namespace
 
-// Initialize the library once for all runs of the fuzzer.
-struct TestCase {
-  TestCase() {
+PDFFuzzerInitPublic::PDFFuzzerInitPublic() {
 #ifdef PDF_ENABLE_V8
 #ifdef V8_USE_EXTERNAL_STARTUP_DATA
-    platform = InitializeV8ForPDFiumWithStartupData(
-        ProgramPath(), std::string(), &snapshot_blob);
-#else
-    platform = InitializeV8ForPDFium(ProgramPath());
+  platform_ = InitializeV8ForPDFiumWithStartupData(
+      ProgramPath(), std::string(), std::string(), &snapshot_blob_);
+#else   // V8_USE_EXTERNAL_STARTUP_DATA
+  platform_ = InitializeV8ForPDFium(ProgramPath(), std::string());
 #endif  // V8_USE_EXTERNAL_STARTUP_DATA
+#ifdef PDF_ENABLE_XFA
+  allocator_.reset(v8::ArrayBuffer::Allocator::NewDefaultAllocator());
+  v8::Isolate::CreateParams create_params;
+  create_params.array_buffer_allocator = allocator_.get();
+  isolate_.reset(v8::Isolate::New(create_params));
+#endif  // PDF_ENABLE_XFA
 #endif  // PDF_ENABLE_V8
-
-    memset(&config, '\0', sizeof(config));
-    config.version = 2;
-    config.m_pUserFontPaths = nullptr;
-    config.m_pIsolate = nullptr;
-    config.m_v8EmbedderSlot = 0;
-    FPDF_InitLibraryWithConfig(&config);
-
-    memset(&unsupport_info, '\0', sizeof(unsupport_info));
-    unsupport_info.version = 1;
-    unsupport_info.FSDK_UnSupport_Handler = [](UNSUPPORT_INFO*, int) {};
-    FSDK_SetUnSpObjProcessHandler(&unsupport_info);
-  }
-
+  memset(&config_, '\0', sizeof(config_));
+  config_.version = 3;
+  config_.m_pUserFontPaths = nullptr;
+  config_.m_pPlatform = nullptr;
+  config_.m_pIsolate = nullptr;
+  config_.m_v8EmbedderSlot = 0;
 #ifdef PDF_ENABLE_V8
-  std::unique_ptr<v8::Platform> platform;
-  v8::StartupData snapshot_blob;
+  config_.m_pPlatform = platform_.get();
+  config_.m_pIsolate = isolate_.get();
+#endif  // PDF_ENABLE_V8
+  FPDF_InitLibraryWithConfig(&config_);
+
+  memset(&unsupport_info_, '\0', sizeof(unsupport_info_));
+  unsupport_info_.version = 1;
+  unsupport_info_.FSDK_UnSupport_Handler = [](UNSUPPORT_INFO*, int) {};
+  FSDK_SetUnSpObjProcessHandler(&unsupport_info_);
+
+#ifdef PDF_ENABLE_XFA
+  xfa_process_state_ =
+      std::make_unique<XFAProcessState>(platform_.get(), isolate_.get());
+  FPDF_SetFuzzerPerProcessState(xfa_process_state_.get());
 #endif
+}
 
-  FPDF_LIBRARY_CONFIG config;
-  UNSUPPORT_INFO unsupport_info;
-};
-
-// pdf_fuzzer_init.cc and pdfium_fuzzer_init_public.cc are mutually exclusive
-// and should not be built together. They deliberately have the same global
-// variable.
-static TestCase* g_test_case = new TestCase();
+PDFFuzzerInitPublic::~PDFFuzzerInitPublic() {
+  FPDF_SetFuzzerPerProcessState(nullptr);
+}
diff --git a/testing/fuzzers/pdf_fuzzer_init_public.h b/testing/fuzzers/pdf_fuzzer_init_public.h
new file mode 100644
index 0000000..4b7f46f
--- /dev/null
+++ b/testing/fuzzers/pdf_fuzzer_init_public.h
@@ -0,0 +1,50 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_FUZZERS_PDF_FUZZER_INIT_PUBLIC_H_
+#define TESTING_FUZZERS_PDF_FUZZER_INIT_PUBLIC_H_
+
+#include <memory>
+
+#include "public/fpdf_ext.h"
+#include "public/fpdfview.h"
+
+#ifdef PDF_ENABLE_V8
+#include "fxjs/cfx_v8.h"
+#include "v8/include/v8-array-buffer.h"
+#include "v8/include/v8-snapshot.h"
+#endif  // PDF_ENABLE_V8
+
+class XFAProcessState;
+
+#ifdef PDF_ENABLE_V8
+namespace v8 {
+class Isolate;
+class Platform;
+}  // namespace v8
+#endif  // PDF_ENABLE_V8
+
+// Initializes the library once for all runs of the fuzzer.
+class PDFFuzzerInitPublic {
+ public:
+  PDFFuzzerInitPublic();
+  ~PDFFuzzerInitPublic();
+
+ private:
+  FPDF_LIBRARY_CONFIG config_;
+  UNSUPPORT_INFO unsupport_info_;
+#ifdef PDF_ENABLE_V8
+#ifdef V8_USE_EXTERNAL_STARTUP_DATA
+  v8::StartupData snapshot_blob_;
+#endif  // V8_USE_EXTERNAL_STARTUP_DATA
+  std::unique_ptr<v8::Platform> platform_;
+  std::unique_ptr<v8::ArrayBuffer::Allocator> allocator_;
+  std::unique_ptr<v8::Isolate, CFX_V8IsolateDeleter> isolate_;
+#ifdef PDF_ENABLE_XFA
+  std::unique_ptr<XFAProcessState> xfa_process_state_;
+#endif  // PDF_ENABLE_XFA
+#endif  // PDF_ENABLE_V8
+};
+
+#endif  // TESTING_FUZZERS_PDF_FUZZER_INIT_PUBLIC_H_
diff --git a/testing/fuzzers/pdf_fuzzer_templates.h b/testing/fuzzers/pdf_fuzzer_templates.h
new file mode 100644
index 0000000..3fe4ea8
--- /dev/null
+++ b/testing/fuzzers/pdf_fuzzer_templates.h
@@ -0,0 +1,52 @@
+// Copyright 2021 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// File for holding strings representing PDF templates that are used by fuzzers.
+
+#ifndef TESTING_FUZZERS_PDF_FUZZER_TEMPLATES_H_
+#define TESTING_FUZZERS_PDF_FUZZER_TEMPLATES_H_
+
+constexpr char kSimplePdfTemplate[] = R"(%PDF-1.7
+1 0 obj
+<</Type /Catalog /Pages 2 0 R /AcroForm <</XFA 30 0 R>> /NeedsRendering true>>
+endobj
+2 0 obj
+<</Type /Pages /Kids [3 0 R] /Count 1>>
+endobj
+3 0 obj
+<</Type /Page /Parent 2 0 R /MediaBox [0 0 3 3]>>
+endobj
+30 0 obj
+<</Length $1>>
+stream
+$2
+endstream
+endobj
+trailer
+<</Root 1 0 R /Size 31>>
+%%EOF)";
+
+// We define the bytes of the header explicitly to make the values more readable
+constexpr uint8_t kSimplePdfHeader[] = {0x25, 0x50, 0x44, 0x46, 0x2d,
+                                        0x31, 0x2e, 0x37, 0x0a, 0x25,
+                                        0xa0, 0xf2, 0xa4, 0xf4, 0x0a};
+
+constexpr char kCatalog[] = R""(<</AcroForm 2 0 R /Extensions
+  <</ADBE <</BaseVersion /1.7 /ExtensionLevel 8>>>> /NeedsRendering true
+  /Pages 3 0 R /Type /Catalog>>)"";
+
+constexpr char kSimpleXfaObjWrapper[] = R""(<</XFA
+  [(preamble) 5 0 R ($1) 6 0 R ($2) 7 0 R ($3) 8 0 R
+  (postamble) 9 0 R]>>)"";
+
+constexpr char kSimplePagesObj[] = "<</Count 1 /Kids [4 0 R] /Type /Pages>>";
+constexpr char kSimplePageObj[] =
+    "<</MediaBox [0 0 612 792] /Parent 3 0 R /Type /Page>>";
+constexpr char kSimplePreamble[] =
+    R""(<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/" 
+timeStamp="2021-12-14T14:14:14Z" 
+uuid="11111111-1ab1-11b1-aa1a-1aaaaaaa11a1">)"";
+constexpr char kSimplePostamble[] = "</xdp:xdp>";
+
+#endif  // TESTING_FUZZERS_PDF_FUZZER_TEMPLATES_H_
diff --git a/testing/fuzzers/pdf_fx_date_helpers_fuzzer.cc b/testing/fuzzers/pdf_fx_date_helpers_fuzzer.cc
index e31decc..d98fffd 100644
--- a/testing/fuzzers/pdf_fx_date_helpers_fuzzer.cc
+++ b/testing/fuzzers/pdf_fx_date_helpers_fuzzer.cc
@@ -1,9 +1,7 @@
-// Copyright 2018 The PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <string>
-
 #include "core/fxcrt/widestring.h"
 #include "fxjs/fx_date_helpers.h"
 
diff --git a/testing/fuzzers/pdf_hint_table_fuzzer.cc b/testing/fuzzers/pdf_hint_table_fuzzer.cc
index 1540074..3743b1a 100644
--- a/testing/fuzzers/pdf_hint_table_fuzzer.cc
+++ b/testing/fuzzers/pdf_hint_table_fuzzer.cc
@@ -1,4 +1,4 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -11,7 +11,6 @@
 #include "core/fpdfapi/parser/cpdf_linearized_header.h"
 #include "core/fpdfapi/parser/cpdf_number.h"
 #include "core/fxcrt/cfx_bitstream.h"
-#include "third_party/base/ptr_util.h"
 #include "third_party/base/span.h"
 
 int32_t GetData(const int32_t** data32, const uint8_t** data, size_t* size) {
@@ -28,7 +27,7 @@
                       int shared_hint_table_offset)
       : CPDF_HintTables(nullptr, pLinearized),
         shared_hint_table_offset_(shared_hint_table_offset) {}
-  ~HintTableForFuzzing() {}
+  ~HintTableForFuzzing() = default;
 
   void Fuzz(const uint8_t* data, size_t size) {
     if (shared_hint_table_offset_ <= 0)
@@ -76,9 +75,9 @@
 
   auto hint_info = pdfium::MakeRetain<CPDF_Array>();
   // Add primary hint stream offset
-  hint_info->AddNew<CPDF_Number>(GetData(&data32, &data, &size));
+  hint_info->AppendNew<CPDF_Number>(GetData(&data32, &data, &size));
   // Add primary hint stream size
-  hint_info->AddNew<CPDF_Number>(GetData(&data32, &data, &size));
+  hint_info->AppendNew<CPDF_Number>(GetData(&data32, &data, &size));
   // Set hint stream info.
   linearized_dict->SetFor("H", std::move(hint_info));
 
diff --git a/testing/fuzzers/pdf_jpx_fuzzer.cc b/testing/fuzzers/pdf_jpx_fuzzer.cc
index 3986ae2..3021d76 100644
--- a/testing/fuzzers/pdf_jpx_fuzzer.cc
+++ b/testing/fuzzers/pdf_jpx_fuzzer.cc
@@ -1,17 +1,15 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include <cstdint>
 #include <memory>
-#include <vector>
 
 #include "core/fpdfapi/page/cpdf_colorspace.h"
 #include "core/fxcodec/jpx/cjpx_decoder.h"
-#include "core/fxcodec/jpx/jpxmodule.h"
 #include "core/fxcrt/fx_safe_types.h"
 #include "core/fxge/dib/cfx_dibitmap.h"
-#include "core/fxge/fx_dib.h"
+#include "core/fxge/dib/fx_dib.h"
 
 namespace {
 
@@ -21,19 +19,19 @@
   static constexpr uint32_t kMemLimitBytes = 1024 * 1024 * 1024;  // 1 GB.
   FX_SAFE_UINT32 mem = image_info.width;
   mem *= image_info.height;
-  mem *= image_info.components;
+  mem *= image_info.channels;
   return mem.IsValid() && mem.ValueOrDie() <= kMemLimitBytes;
 }
 
 }  // namespace
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  if (size < 1)
+  if (size < 2)
     return 0;
 
-  std::unique_ptr<CJPX_Decoder> decoder = JpxModule::CreateDecoder(
-      {data + 1, size - 1},
-      static_cast<CJPX_Decoder::ColorSpaceOption>(data[0] % 3));
+  std::unique_ptr<CJPX_Decoder> decoder = CJPX_Decoder::Create(
+      {data + 2, size - 2},
+      static_cast<CJPX_Decoder::ColorSpaceOption>(data[0] % 3), data[1]);
   if (!decoder)
     return 0;
 
@@ -52,15 +50,15 @@
     return 0;
 
   FXDIB_Format format;
-  if (image_info.components == 1) {
-    format = FXDIB_8bppRgb;
-  } else if (image_info.components <= 3) {
-    format = FXDIB_Rgb;
-  } else if (image_info.components == 4) {
-    format = FXDIB_Rgb32;
+  if (image_info.channels == 1) {
+    format = FXDIB_Format::k8bppRgb;
+  } else if (image_info.channels <= 3) {
+    format = FXDIB_Format::kRgb;
+  } else if (image_info.channels == 4) {
+    format = FXDIB_Format::kRgb32;
   } else {
-    image_info.width = (image_info.width * image_info.components + 2) / 3;
-    format = FXDIB_Rgb;
+    image_info.width = (image_info.width * image_info.channels + 2) / 3;
+    format = FXDIB_Format::kRgb;
   }
   auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
   if (!bitmap->Create(image_info.width, image_info.height, format))
@@ -71,8 +69,8 @@
           static_cast<uint32_t>(bitmap->GetHeight()))
     return 0;
 
-  decoder->Decode(bitmap->GetBuffer(), bitmap->GetPitch(),
-                  /*swap_rgb=*/false);
+  decoder->Decode(bitmap->GetBuffer(), bitmap->GetPitch(), /*swap_rgb=*/false,
+                  GetCompsFromFormat(format));
 
   return 0;
 }
diff --git a/testing/fuzzers/pdf_lzw_fuzzer.cc b/testing/fuzzers/pdf_lzw_fuzzer.cc
index e4d993e..38d7929 100644
--- a/testing/fuzzers/pdf_lzw_fuzzer.cc
+++ b/testing/fuzzers/pdf_lzw_fuzzer.cc
@@ -1,10 +1,13 @@
-// Copyright 2017 The PDFium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <stddef.h>
+#include <stdint.h>
+
 #include <vector>
 
-#include "core/fxcodec/gif/cfx_lzwdecompressor.h"
+#include "core/fxcodec/gif/lzw_decompressor.h"
 #include "third_party/base/numerics/safe_conversions.h"
 
 // Between 2x and 5x is a standard range for LZW according to a quick
@@ -12,12 +15,14 @@
 constexpr uint32_t kMinCompressionRatio = 2;
 constexpr uint32_t kMaxCompressionRatio = 10;
 
+static constexpr size_t kMaxFuzzBytes = 1024 * 1024 * 1024;  // 1 GB.
+
 void LZWFuzz(const uint8_t* src_buf,
-             size_t src_size,
+             uint32_t src_size,
              uint8_t color_exp,
              uint8_t code_exp) {
-  std::unique_ptr<CFX_LZWDecompressor> decompressor =
-      CFX_LZWDecompressor::Create(color_exp, code_exp);
+  std::unique_ptr<LZWDecompressor> decompressor =
+      LZWDecompressor::Create(color_exp, code_exp);
   if (!decompressor)
     return;
 
@@ -27,8 +32,9 @@
     // This cast should be safe since the caller is checking for overflow on
     // the initial data.
     uint32_t dest_size = static_cast<uint32_t>(dest_buf.size());
-    if (CFX_GifDecodeStatus::InsufficientDestSize !=
-        decompressor->Decode(src_buf, src_size, dest_buf.data(), &dest_size)) {
+    decompressor->SetSource(src_buf, src_size);
+    if (LZWDecompressor::Status::kInsufficientDestSize !=
+        decompressor->Decode(dest_buf.data(), &dest_size)) {
       return;
     }
   }
@@ -36,7 +42,7 @@
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   // Need at least 3 bytes to do anything.
-  if (size < 3)
+  if (size < 3 || size > kMaxFuzzBytes)
     return 0;
 
   // Normally the GIF would provide the code and color sizes, instead, going
diff --git a/testing/fuzzers/pdf_nametree_fuzzer.cc b/testing/fuzzers/pdf_nametree_fuzzer.cc
index 9a37024..bc141ed 100644
--- a/testing/fuzzers/pdf_nametree_fuzzer.cc
+++ b/testing/fuzzers/pdf_nametree_fuzzer.cc
@@ -1,4 +1,4 @@
-// Copyright 2019 The PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -54,22 +54,23 @@
 
   CPDF_StreamParser parser(remaining);
   auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
-  CPDF_NameTree name_tree(dict.Get());
+  std::unique_ptr<CPDF_NameTree> name_tree =
+      CPDF_NameTree::CreateForTesting(dict.Get());
   for (const auto& name : params.names) {
     RetainPtr<CPDF_Object> obj = parser.ReadNextObject(
         /*bAllowNestedArray*/ true, /*bInArray=*/false, /*dwRecursionLevel=*/0);
     if (!obj)
       break;
 
-    name_tree.AddValueAndName(std::move(obj), name);
+    name_tree->AddValueAndName(std::move(obj), name);
   }
 
   if (params.delete_backwards) {
     for (size_t i = params.count; i > 0; --i)
-      name_tree.DeleteValueAndName(i);
+      name_tree->DeleteValueAndName(i);
   } else {
     for (size_t i = 0; i < params.count; ++i)
-      name_tree.DeleteValueAndName(0);
+      name_tree->DeleteValueAndName(0);
   }
   return 0;
 }
diff --git a/testing/fuzzers/pdf_psengine_fuzzer.cc b/testing/fuzzers/pdf_psengine_fuzzer.cc
index d72088d..5cc2a76 100644
--- a/testing/fuzzers/pdf_psengine_fuzzer.cc
+++ b/testing/fuzzers/pdf_psengine_fuzzer.cc
@@ -1,8 +1,8 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <cstdint>
+#include <stdint.h>
 
 #include "core/fpdfapi/page/cpdf_psengine.h"
 #include "third_party/base/span.h"
diff --git a/testing/fuzzers/pdf_scanlinecompositor_fuzzer.cc b/testing/fuzzers/pdf_scanlinecompositor_fuzzer.cc
index 4b20068..9c6e434 100644
--- a/testing/fuzzers/pdf_scanlinecompositor_fuzzer.cc
+++ b/testing/fuzzers/pdf_scanlinecompositor_fuzzer.cc
@@ -1,20 +1,36 @@
-// Copyright 2019 The PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <iterator>
+#include <memory>
+
+#include "core/fxcrt/fx_safe_types.h"
 #include "core/fxge/cfx_cliprgn.h"
 #include "core/fxge/dib/cfx_dibitmap.h"
-#include "core/fxge/fx_dib.h"
+#include "core/fxge/dib/fx_dib.h"
 #include "testing/fuzzers/pdfium_fuzzer_util.h"
-#include "third_party/base/ptr_util.h"
 
 namespace {
 
+// Some unused formats were removed, and their slots have been filled in
+// `FXDIB_Format::kInvalid` to keep the fuzzer input stable.
 constexpr FXDIB_Format kFormat[] = {
-    FXDIB_Invalid, FXDIB_1bppRgb,   FXDIB_8bppRgb,  FXDIB_Rgb,
-    FXDIB_Rgb32,   FXDIB_1bppMask,  FXDIB_8bppMask, FXDIB_8bppRgba,
-    FXDIB_Rgba,    FXDIB_Argb,      FXDIB_1bppCmyk, FXDIB_8bppCmyk,
-    FXDIB_Cmyk,    FXDIB_8bppCmyka, FXDIB_Cmyka};
+    FXDIB_Format::kInvalid,
+    FXDIB_Format::k1bppRgb,
+    FXDIB_Format::k8bppRgb,
+    FXDIB_Format::kRgb,
+    FXDIB_Format::kRgb32,
+    FXDIB_Format::k1bppMask,
+    FXDIB_Format::k8bppMask,
+    FXDIB_Format::kInvalid /* Was FXDIB_Format::k8bppRgba */,
+    FXDIB_Format::kInvalid /* Was FXDIB_Format::kRgba */,
+    FXDIB_Format::kArgb,
+    FXDIB_Format::kInvalid /* Was FXDIB_Format::k1bppCmyk */,
+    FXDIB_Format::kInvalid /* Was FXDIB_Format::k8bppCmyk */,
+    FXDIB_Format::kInvalid /* Was FXDIB_Format::kCmyk */,
+    FXDIB_Format::kInvalid /* Was FXDIB_Format::k8bppCmyka */,
+    FXDIB_Format::kInvalid /* Was FXDIB_Format::kCmyka */};
 
 }  // namespace
 
@@ -33,27 +49,35 @@
 
   BlendMode blend_mode = static_cast<BlendMode>(
       data[28] % (static_cast<int>(BlendMode::kLast) + 1));
-  FXDIB_Format dest_format = kFormat[data[29] % FX_ArraySize(kFormat)];
-  FXDIB_Format src_format = kFormat[data[30] % FX_ArraySize(kFormat)];
+  FXDIB_Format dest_format = kFormat[data[29] % std::size(kFormat)];
+  FXDIB_Format src_format = kFormat[data[30] % std::size(kFormat)];
   bool is_clip = !(data[31] % 2);
   bool is_rgb_byte_order = !(data[32] % 2);
   size -= kParameterSize;
   data += kParameterSize;
 
+  static constexpr uint32_t kMemLimit = 512000000;  // 512 MB
+  static constexpr uint32_t kComponents = 4;
+  FX_SAFE_UINT32 mem = width;
+  mem *= height;
+  mem *= kComponents;
+  if (!mem.IsValid() || mem.ValueOrDie() > kMemLimit)
+    return 0;
+
   auto src_bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
   auto dest_bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
   if (!src_bitmap->Create(width, height, src_format) ||
       !dest_bitmap->Create(width, height, dest_format)) {
     return 0;
   }
-  if (!src_bitmap->GetBuffer() || !dest_bitmap->GetBuffer()) {
+  if (src_bitmap->GetBuffer().empty() || dest_bitmap->GetBuffer().empty()) {
     return 0;
   }
 
   std::unique_ptr<CFX_ClipRgn> clip_rgn;
   if (is_clip)
-    clip_rgn = pdfium::MakeUnique<CFX_ClipRgn>(width, height);
-  if (src_bitmap->IsAlphaMask()) {
+    clip_rgn = std::make_unique<CFX_ClipRgn>(width, height);
+  if (src_bitmap->IsMaskFormat()) {
     dest_bitmap->CompositeMask(dest_left, dest_top, width, height, src_bitmap,
                                argb, src_left, src_top, blend_mode,
                                clip_rgn.get(), is_rgb_byte_order);
diff --git a/testing/fuzzers/pdf_streamparser_fuzzer.cc b/testing/fuzzers/pdf_streamparser_fuzzer.cc
index 2bbda5e..e3bd787 100644
--- a/testing/fuzzers/pdf_streamparser_fuzzer.cc
+++ b/testing/fuzzers/pdf_streamparser_fuzzer.cc
@@ -1,9 +1,8 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <cstdint>
-#include <memory>
+#include <stdint.h>
 
 #include "core/fpdfapi/page/cpdf_streamparser.h"
 #include "core/fpdfapi/parser/cpdf_object.h"
diff --git a/testing/fuzzers/pdf_xfa_fdp_fuzzer.cc b/testing/fuzzers/pdf_xfa_fdp_fuzzer.cc
new file mode 100644
index 0000000..c438c89
--- /dev/null
+++ b/testing/fuzzers/pdf_xfa_fdp_fuzzer.cc
@@ -0,0 +1,914 @@
+// Copyright 2021 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include <string>
+#include <vector>
+
+#include "public/fpdf_formfill.h"
+#include "testing/fuzzers/pdf_fuzzer_templates.h"
+#include "testing/fuzzers/pdfium_fuzzer_helper.h"
+#include "third_party/base/containers/adapters.h"
+
+class PDFiumXFAFuzzer : public PDFiumFuzzerHelper {
+ public:
+  PDFiumXFAFuzzer() = default;
+  ~PDFiumXFAFuzzer() override = default;
+
+  int GetFormCallbackVersion() const override { return 2; }
+
+  void SetFdp(FuzzedDataProvider* fdp) { fdp_ = fdp; }
+
+  // Return false if XFA doesn't load as otherwise we're duplicating the work
+  // done by the non-xfa fuzzer.
+  bool OnFormFillEnvLoaded(FPDF_DOCUMENT doc) override {
+    int form_type = FPDF_GetFormType(doc);
+    if (form_type != FORMTYPE_XFA_FULL && form_type != FORMTYPE_XFA_FOREGROUND)
+      return false;
+    return FPDF_LoadXFA(doc);
+  }
+
+  void FormActionHandler(FPDF_FORMHANDLE form,
+                         FPDF_DOCUMENT doc,
+                         FPDF_PAGE page) override {
+    if (!fdp_) {
+      return;
+    }
+    char local_buf[50];
+    int number_of_calls = fdp_->ConsumeIntegralInRange<int>(0, 250);
+    for (int i = 0; i < number_of_calls; i++) {
+      UserInteraction selector = fdp_->ConsumeEnum<UserInteraction>();
+      switch (selector) {
+        case kOnLButtonUp: {
+          FORM_OnLButtonUp(form, page, fdp_->ConsumeIntegral<int>(),
+                           fdp_->ConsumeIntegralInRange<int>(-100, 1000),
+                           fdp_->ConsumeIntegralInRange<int>(-100, 1000));
+          break;
+        }
+        case kOnRButtonUp: {
+          FORM_OnRButtonUp(form, page, fdp_->ConsumeIntegral<int>(),
+                           fdp_->ConsumeIntegralInRange<int>(-100, 1000),
+                           fdp_->ConsumeIntegralInRange<int>(-100, 1000));
+          break;
+        }
+        case kOnLButtonDown: {
+          FORM_OnLButtonDown(form, page, fdp_->ConsumeIntegral<int>(),
+                             fdp_->ConsumeIntegralInRange<int>(-100, 1000),
+                             fdp_->ConsumeIntegralInRange<int>(-100, 1000));
+          break;
+        }
+        case kOnRButtonDown: {
+          FORM_OnRButtonDown(form, page, fdp_->ConsumeIntegral<int>(),
+                             fdp_->ConsumeIntegralInRange<int>(-100, 1000),
+                             fdp_->ConsumeIntegralInRange<int>(-100, 1000));
+          break;
+        }
+        case kOnChar: {
+          FORM_OnChar(form, page, fdp_->ConsumeIntegral<int>(),
+                      fdp_->ConsumeIntegral<int>());
+          break;
+        }
+        case kOnKeyDown: {
+          FORM_OnKeyDown(form, page, fdp_->ConsumeIntegral<int>(),
+                         fdp_->ConsumeIntegral<int>());
+          break;
+        }
+        case kOnKeyUp: {
+          FORM_OnKeyUp(form, page, fdp_->ConsumeIntegral<int>(),
+                       fdp_->ConsumeIntegral<int>());
+          break;
+        }
+        case kOnLButtonDoubleClick: {
+          FORM_OnLButtonDoubleClick(form, page, fdp_->ConsumeIntegral<int>(),
+                                    fdp_->ConsumeIntegral<int>(),
+                                    fdp_->ConsumeIntegral<int>());
+          break;
+        }
+        case kOnMouseMove: {
+          FORM_OnMouseMove(form, page, fdp_->ConsumeIntegral<int>(),
+                           fdp_->ConsumeIntegral<int>(),
+                           fdp_->ConsumeIntegral<int>());
+          break;
+        }
+        case kOnMouseWheel: {
+          const FS_POINTF point = {fdp_->ConsumeFloatingPoint<float>(),
+                                   fdp_->ConsumeFloatingPoint<float>()};
+          FORM_OnMouseWheel(form, page, fdp_->ConsumeIntegral<int>(), &point,
+                            fdp_->ConsumeIntegral<int>(),
+                            fdp_->ConsumeIntegral<int>());
+          break;
+        }
+        case kOnFocus: {
+          FORM_OnFocus(form, page, fdp_->ConsumeIntegral<int>(),
+                       fdp_->ConsumeIntegral<int>(),
+                       fdp_->ConsumeIntegral<int>());
+          break;
+        }
+        case kUndo: {
+          if (FORM_CanUndo(form, page)) {
+            FORM_Undo(form, page);
+          }
+          break;
+        }
+        case kSelectAllText: {
+          FORM_SelectAllText(form, page);
+          break;
+        }
+        case kRedo: {
+          if (FORM_CanRedo(form, page)) {
+            FORM_Redo(form, page);
+          }
+          break;
+        }
+        case kAnnot: {
+          FPDF_ANNOTATION annot = nullptr;
+          int page_index = -2;
+          FORM_GetFocusedAnnot(form, &page_index, &annot);
+          if (annot) {
+            FORM_SetFocusedAnnot(form, annot);
+          }
+          break;
+        }
+        case kSetIndexSelected: {
+          FORM_SetIndexSelected(form, page, fdp_->ConsumeIntegral<int>(),
+                                fdp_->ConsumeBool());
+          break;
+        }
+        case kIsIndexSelected: {
+          FORM_IsIndexSelected(form, page, fdp_->ConsumeIntegral<int>());
+          break;
+        }
+        case kHasFormFieldAtPoint: {
+          FPDFPage_HasFormFieldAtPoint(form, page, fdp_->ConsumeIntegral<int>(),
+                                       fdp_->ConsumeIntegral<int>());
+          break;
+        }
+        case kFormFieldZOrderAtPoint: {
+          FPDFPage_FormFieldZOrderAtPoint(form, page,
+                                          fdp_->ConsumeIntegral<int>(),
+                                          fdp_->ConsumeIntegral<int>());
+          break;
+        }
+        case kGetSelectedText: {
+          FORM_GetSelectedText(form, page, local_buf, sizeof(local_buf));
+          break;
+        }
+        case kGetFocusedText: {
+          FORM_GetFocusedText(form, page, local_buf, sizeof(local_buf));
+          break;
+        }
+        default: {
+          break;
+        }
+      }
+    }
+  }
+
+ private:
+  enum UserInteraction {
+    kOnLButtonUp = 0,
+    kOnRButtonUp,
+    kOnLButtonDown,
+    kOnRButtonDown,
+    kOnChar,
+    kOnKeyDown,
+    kOnKeyUp,
+    kOnLButtonDoubleClick,
+    kOnMouseMove,
+    kOnMouseWheel,
+    kOnFocus,
+    kUndo,
+    kSelectAllText,
+    kRedo,
+    kAnnot,
+    kSetIndexSelected,
+    kIsIndexSelected,
+    kHasFormFieldAtPoint,
+    kFormFieldZOrderAtPoint,
+    kGetSelectedText,
+    kGetFocusedText,
+    kMaxValue = kGetFocusedText
+  };
+  FuzzedDataProvider* fdp_ = nullptr;
+};
+
+// Possible names of an XFA FormCalc script function
+std::string GenXfaFormCalcScriptFuncName(FuzzedDataProvider* data_provider) {
+  static const char* const kXfaScriptFuncs[] = {
+      "Abs",       "Apr",        "At",           "Avg",          "Ceil",
+      "Choose",    "Concat",     "Count",        "Cterm",        "Date",
+      "Date2Num",  "DateFmt",    "Decode",       "Encode",       "Eval",
+      "Exists",    "Floor",      "Format",       "FV",           "Get",
+      "HasValue",  "If",         "Ipmt",         "IsoDate2Num",  "IsoTime2Num",
+      "Left",      "Len",        "LocalDateFmt", "LocalTimeFmt", "Lower",
+      "Ltrim",     "Max",        "Min",          "Mod",          "NPV",
+      "Num2Date",  "Num2GMTime", "Num2Time",     "Oneof",        "Parse",
+      "Pmt",       "Post",       "PPmt",         "Put",          "PV",
+      "Rate",      "Ref",        "Replace",      "Right",        "Round",
+      "Rtrim",     "Space",      "Str",          "Stuff",        "Substr",
+      "Sum",       "Term",       "Time",         "Time2Num",     "TimeFmt",
+      "Translate", "UnitType",   "UnitValue",    "Upper",        "Uuid",
+      "Within",    "WordNum",
+  };
+
+  size_t elem_selector = data_provider->ConsumeIntegralInRange<size_t>(
+      0, std::size(kXfaScriptFuncs) - 1);
+  return kXfaScriptFuncs[elem_selector];
+}
+
+std::string MaybeQuote(FuzzedDataProvider* data_provider, std::string body) {
+  if (data_provider->ConsumeIntegralInRange<uint32_t>(0, 100) < 20) {
+    return "\"" + body + "\"";
+  }
+  return body;
+}
+
+// Possible arguments to a XFA script function
+std::string GenXfaScriptParam(FuzzedDataProvider* data_provider) {
+  static const char* const kXfaFuncParams[] = {
+      "$",
+      "-0",
+      "04/13/2019",
+      ".05",
+      "-1",
+      "1",
+      " 1 | 0",
+      "10 * 10 * 10 * 9 * 123",
+      "1024",
+      "10 * a + 9",
+      "1.2131",
+      "[1,2,3]",
+      "%123",
+      "[1,2,3][0]",
+      "123124",
+      "123342123",
+      "13:13:13",
+      "13:13:13 GMT",
+      "19960315T20:20:20",
+      "1 and 1",
+      "1 and 2",
+      "2",
+      "20000201",
+      "2009-06-01T13:45:30",
+      "2009-06-15T01:45:30",
+      "2009-06-15T13:45:30-07:00",
+      "2009-06-15T13:45:30.5275000",
+      " 2 < 3 + 1",
+      "2 + 3 + 9",
+      "3",
+      "3 * 1",
+      "3 -9",
+      "5 < 5",
+      "-99",
+      "99",
+      "9999999",
+      "99999999999",
+      "A",
+      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+      "\xc3\x81\xc3\x82\xc3\x83\xc3\x84\xc3\x85\xc3\x86",
+      "<a><b></b></a>",
+      "&Acirc;",
+      "&AElig;&Aacute;&Acirc;&Aacute;",
+      "Amount[*]",
+      "~!@#$%^&amp;*()_+",
+      "&amp;|",
+      "&apos",
+      "apr",
+      "april",
+      "B",
+      "<br>",
+      "C",
+      "de_DE",
+      "es_ES",
+      "feb",
+      "febuary",
+      "HH:MM:SS",
+      "<html>",
+      "html",
+      "HTML",
+      "jan",
+      "january",
+      "json",
+      "lkdjfglsdkfgj",
+      "mar",
+      "march",
+      "name[0]",
+      "name1",
+      "name2",
+      "name3",
+      "name4",
+      "name[*].numAmount",
+      "&quot;",
+      "Space",
+      "Str",
+      "url",
+      "xhtml",
+      "xml",
+      "XML&quot;",
+  };
+
+  size_t elem_selector = data_provider->ConsumeIntegralInRange<size_t>(
+      0, std::size(kXfaFuncParams) - 1);
+  return MaybeQuote(data_provider, kXfaFuncParams[elem_selector]);
+}
+
+// Possible XFA tags
+std::string GenXfaTag(FuzzedDataProvider* data_provider) {
+  static const char* const kXfaElemTags[] = {
+      "accessibleContent",
+      "acrobat",
+      "acrobat",
+      "acrobat7",
+      "ADBE_JSConsole",
+      "ADBE_JSDebugger",
+      "addSilentPrint",
+      "addViewerPreferences",
+      "adjustData",
+      "adobeExtensionLevel",
+      "agent",
+      "alwaysEmbed",
+      "amd",
+      "appearanceFilter",
+      "arc",
+      "area",
+      "assist",
+      "attributes",
+      "autoSave",
+      "barcode",
+      "base",
+      "batchOutput",
+      "behaviorOverride",
+      "bind",
+      "bindItems",
+      "bookend",
+      "boolean",
+      "border",
+      "break",
+      "breakAfter",
+      "breakBefore",
+      "button",
+      "cache",
+      "calculate",
+      "calendarSymbols",
+      "caption",
+      "certificate",
+      "certificates",
+      "change",
+      "checkButton",
+      "choiceList",
+      "color",
+      "comb",
+      "command",
+      "common",
+      "compress",
+      "compression",
+      "compressLogicalStructure",
+      "compressObjectStream",
+      "config",
+      "config",
+      "conformance",
+      "connect",
+      "connectionSet",
+      "connectString",
+      "contentArea",
+      "contentCopy",
+      "copies",
+      "corner",
+      "creator",
+      "currencySymbol",
+      "currencySymbols",
+      "currentPage",
+      "data",
+      "dataGroup",
+      "dataModel",
+      "dataValue",
+      "dataWindow",
+      "date",
+      "datePattern",
+      "datePatterns",
+      "dateTime",
+      "dateTimeEdit",
+      "dateTimeSymbols",
+      "day",
+      "dayNames",
+      "debug",
+      "decimal",
+      "defaultTypeface",
+      "defaultUi",
+      "delete",
+      "delta",
+      "deltas",
+      "desc",
+      "destination",
+      "digestMethod",
+      "digestMethods",
+      "documentAssembly",
+      "draw",
+      "driver",
+      "dSigData",
+      "duplexOption",
+      "dynamicRender",
+      "edge",
+      "effectiveInputPolicy",
+      "effectiveOutputPolicy",
+      "embed",
+      "encoding",
+      "encodings",
+      "encrypt",
+      "encryption",
+      "encryptionLevel",
+      "encryptionMethod",
+      "encryptionMethods",
+      "enforce",
+      "equate",
+      "equateRange",
+      "era",
+      "eraNames",
+      "event",
+      "eventPseudoModel",
+      "exclGroup",
+      "exclude",
+      "excludeNS",
+      "exData",
+      "execute",
+      "exObject",
+      "extras",
+      "field",
+      "fill",
+      "filter",
+      "flipLabel",
+      "float",
+      "font",
+      "fontInfo",
+      "form",
+      "format",
+      "formFieldFilling",
+      "groupParent",
+      "handler",
+      "hostPseudoModel",
+      "hyphenation",
+      "ifEmpty",
+      "image",
+      "imageEdit",
+      "includeXDPContent",
+      "incrementalLoad",
+      "incrementalMerge",
+      "insert",
+      "instanceManager",
+      "integer",
+      "interactive",
+      "issuers",
+      "items",
+      "jog",
+      "keep",
+      "keyUsage",
+      "labelPrinter",
+      "layout",
+      "layoutPseudoModel",
+      "level",
+      "line",
+      "linear",
+      "linearized",
+      "list",
+      "locale",
+      "localeSet",
+      "lockDocument",
+      "log",
+      "logPseudoModel",
+      "manifest",
+      "map",
+      "margin",
+      "mdp",
+      "medium",
+      "mediumInfo",
+      "meridiem",
+      "meridiemNames",
+      "message",
+      "messaging",
+      "mode",
+      "modifyAnnots",
+      "month",
+      "monthNames",
+      "msgId",
+      "nameAttr",
+      "neverEmbed",
+      "numberOfCopies",
+      "numberPattern",
+      "numberPatterns",
+      "numberSymbol",
+      "numberSymbols",
+      "numericEdit",
+      "object",
+      "occur",
+      "oid",
+      "oids",
+      "openAction",
+      "operation",
+      "output",
+      "outputBin",
+      "outputXSL",
+      "overflow",
+      "overprint",
+      "packet",
+      "packets",
+      "pageArea",
+      "pageOffset",
+      "pageRange",
+      "pageSet",
+      "pagination",
+      "paginationOverride",
+      "para",
+      "part",
+      "password",
+      "passwordEdit",
+      "pattern",
+      "pcl",
+      "pdf",
+      "pdfa",
+      "permissions",
+      "pickTrayByPDFSize",
+      "picture",
+      "plaintextMetadata",
+      "presence",
+      "present",
+      "present",
+      "print",
+      "printerName",
+      "printHighQuality",
+      "printScaling",
+      "producer",
+      "proto",
+      "ps",
+      "psMap",
+      "query",
+      "radial",
+      "range",
+      "reason",
+      "reasons",
+      "record",
+      "recordSet",
+      "rectangle",
+      "ref",
+      "relevant",
+      "rename",
+      "renderPolicy",
+      "rootElement",
+      "runScripts",
+      "script",
+      "scriptModel",
+      "select",
+      "setProperty",
+      "severity",
+      "signature",
+      "signatureProperties",
+      "signaturePseudoModel",
+      "signData",
+      "signing",
+      "silentPrint",
+      "soapAction",
+      "soapAddress",
+      "solid",
+      "source",
+      "sourceSet",
+      "speak",
+      "staple",
+      "startNode",
+      "startPage",
+      "stipple",
+      "subform",
+      "subform",
+      "subformSet",
+      "subjectDN",
+      "subjectDNs",
+      "submit",
+      "submitFormat",
+      "submitUrl",
+      "subsetBelow",
+      "suppressBanner",
+      "tagged",
+      "template",
+      "template",
+      "templateCache",
+      "#text",
+      "text",
+      "textedit",
+      "textEdit",
+      "threshold",
+      "time",
+      "timePattern",
+      "timePatterns",
+      "timeStamp",
+      "to",
+      "toolTip",
+      "trace",
+      "transform",
+      "traversal",
+      "traverse",
+      "treeList",
+      "type",
+      "typeface",
+      "typefaces",
+      "ui",
+      "update",
+      "uri",
+      "user",
+      "validate",
+      "validate",
+      "validateApprovalSignatures",
+      "validationMessaging",
+      "value",
+      "variables",
+      "version",
+      "versionControl",
+      "viewerPreferences",
+      "webClient",
+      "whitespace",
+      "window",
+      "wsdlAddress",
+      "wsdlConnection",
+      "xdc",
+      "xdp",
+      "xfa",
+      "#xHTML",
+      "#xml",
+      "xmlConnection",
+      "xsdConnection",
+      "xsl",
+      "zpl",
+  };
+
+  size_t elem_selector = data_provider->ConsumeIntegralInRange<size_t>(
+      0, std::size(kXfaElemTags) - 1);
+  return kXfaElemTags[elem_selector];
+}
+
+// Possible XFA attributes values
+std::string GenXfaTagValue(FuzzedDataProvider* data_provider) {
+  static const char* const kXfaTagVals[] = {
+      "0",         "0pt",         "-1",
+      "123",       "1pt",         "203.2mm",
+      "22.1404mm", "255",         "256",
+      "321",       "5431.21mm",   "6.35mm",
+      "8in",       "8pt",         "application/x-javascript",
+      "bold",      "bold",        "change",
+      "click",     "consumeData", "docReady",
+      "en_US",     "form1",       "initialize",
+      "italic",    "middle",      "name2",
+      "name3",     "name4",       "name5",
+      "onEnter",   "Page1",       "RadioList[0]",
+      "subform_1", "tb",          "Verdana",
+  };
+
+  size_t elem_selector = data_provider->ConsumeIntegralInRange<size_t>(
+      0, std::size(kXfaTagVals) - 1);
+  return MaybeQuote(data_provider, kXfaTagVals[elem_selector]);
+}
+
+// possible XFA attributes
+std::string GenXfaTagName(FuzzedDataProvider* data_provider) {
+  static const char* const kXfaTagNames[] = {
+      "activity",    "baselineShift",
+      "contentType", "h",
+      "id",          "layout",
+      "layout",      "leftInset",
+      "locale",      "long",
+      "marginLeft",  "marginRight",
+      "marginRight", "mergeMode",
+      "name",        "ref",
+      "scriptTest",  "short",
+      "size",        "spaceAbove",
+      "spaceBelow",  "startNew",
+      "stock",       "textIndent",
+      "timeStamp",   "typeface",
+      "uuid",        "vAlign",
+      "value",       "w",
+      "weight",      "x",
+      "y",
+  };
+  size_t elem_selector = data_provider->ConsumeIntegralInRange<size_t>(
+      0, std::size(kXfaTagNames) - 1);
+  return kXfaTagNames[elem_selector];
+}
+
+// Will create a simple XFA FormCalc script that calls a single function.
+std::string GenXfaFormCalcScript(FuzzedDataProvider* data_provider) {
+  std::string xfa_string = GenXfaFormCalcScriptFuncName(data_provider);
+  xfa_string += "(";
+
+  // Generate parameters
+  size_t num_params = data_provider->ConsumeIntegralInRange<size_t>(0, 3);
+  for (size_t i = 0; i < num_params; i++) {
+    if (i != 0) {
+      xfa_string += ",";
+    }
+    xfa_string += GenXfaScriptParam(data_provider);
+  }
+  xfa_string += ")";
+  return xfa_string;
+}
+
+// XFA Javascript logic
+std::string GenXfaName(FuzzedDataProvider* data_provider) {
+  return "name" + std::to_string(data_provider->ConsumeIntegralInRange(0, 25));
+}
+
+std::string GetXfaJSPrimitiveType(FuzzedDataProvider* data_provider) {
+  return GenXfaScriptParam(data_provider);
+}
+
+std::string GenXfaJSRValue(FuzzedDataProvider* data_provider) {
+  if (data_provider->ConsumeBool()) {
+    return GenXfaScriptParam(data_provider);
+  }
+
+  std::string xfa_string;
+  if (data_provider->ConsumeBool()) {
+    xfa_string += "xfa.form.";
+  }
+
+  // Handle the possibility of nested names
+  size_t num_nests = data_provider->ConsumeIntegralInRange<size_t>(1, 3);
+  for (size_t i = 0; i < num_nests; i++) {
+    if (i != 0) {
+      xfa_string += ".";
+    }
+    xfa_string += GenXfaName(data_provider);
+  }
+  return MaybeQuote(data_provider, xfa_string);
+}
+
+std::string GenXfaJSAssignment(FuzzedDataProvider* data_provider) {
+  return GenXfaName(data_provider) + " = " + GenXfaJSRValue(data_provider);
+}
+
+std::string GenXfaJSMethodCall(FuzzedDataProvider* data_provider) {
+  static const char* const kXfaJSFuncs[] = {
+      "addItem",
+      "boundItem",
+      "clearItems",
+      "deleteItem",
+      "execCalculate",
+      "execEvent",
+      "execInitialize",
+      "execValidate",
+      "getDisplayItem",
+      "getItemState",
+      "getSaveItem",
+      "exec.form.formNodes",
+      "exec.form.recalculate",
+      "setItemState",
+      "xfa.container.getDelta",
+      "xfa.container.getDeltas",
+      "xfa.event.emit",
+      "xfa.event.reset",
+      "xfa.form.execCalculat",
+      "xfa.form.execInitialize",
+      "xfa.form.execValidate",
+      "xfa.form.remerge",
+      "xfa.host.beep",
+      "xfa.host.documentCountInBatch",
+      "xfa.host.documentInBatch",
+      "xfa.host.exportData",
+      "xfa.host.getFocus",
+      "xfa.host.gotoURL",
+      "xfa.host.importData",
+      "xfa.host.messageBox",
+      "xfa.host.openList",
+      "xfa.host.pageDown",
+      "xfa.host.pageUp",
+      "xfa.host.print",
+      "xfa.host.resetData",
+      "xfa.host.setFocus",
+      "xfa.host.response",
+      "xfa.resolveNode",
+  };
+
+  std::string xfa_string = data_provider->PickValueInArray(kXfaJSFuncs);
+  xfa_string += "(";
+
+  // Get the params
+  size_t param_count = data_provider->ConsumeIntegralInRange<size_t>(0, 3);
+  for (size_t i = 0; i < param_count; i++) {
+    if (i != 0) {
+      xfa_string += ",";
+    }
+    xfa_string += GenXfaJSRValue(data_provider);
+  }
+  xfa_string += ")";
+  return xfa_string;
+}
+
+// This is a simple generator of xfa-based javascript. The function creates
+// simple javascript statements that are related to XFA logic and the goal is
+// not to create fully-fleged javascript programs but rather use simple
+// statements to ensure XFA code is covered.
+enum XFAJSStatement {
+  kAssignment = 0,
+  kJSMethodCall,
+  kJSObjectCall,
+  kMaxValue = kJSObjectCall
+};
+
+std::string GenXfaJSScript(FuzzedDataProvider* data_provider) {
+  std::string xfa_string;
+
+  size_t num_stmts = data_provider->ConsumeIntegralInRange<size_t>(1, 10);
+  for (size_t i = 0; i < num_stmts; i++) {
+    XFAJSStatement stmt = data_provider->ConsumeEnum<XFAJSStatement>();
+    switch (stmt) {
+      case kAssignment:
+        xfa_string += GenXfaJSAssignment(data_provider);
+        break;
+      case kJSMethodCall:
+        xfa_string += GenXfaJSMethodCall(data_provider);
+        break;
+      case kJSObjectCall:
+        xfa_string += GenXfaName(data_provider);
+        xfa_string += ".";
+        xfa_string += GenXfaJSMethodCall(data_provider);
+        break;
+    }
+    xfa_string += ";\n";
+  }
+  return xfa_string;
+}
+
+std::string GenXfacript(FuzzedDataProvider* data_provider) {
+  // Determine if this should be a FormCalc script or Javascript, 50/50 chance
+  // for each.
+  if (data_provider->ConsumeBool()) {
+    return GenXfaFormCalcScript(data_provider);
+  }
+  return GenXfaJSScript(data_provider);
+}
+
+// Will create a single XFA attributes, with both lhs and rhs.
+std::string getXfaElemAttributes(FuzzedDataProvider* data_provider) {
+  // Generate a set of tags, and a set of values for the tags.
+  return GenXfaTagName(data_provider) + "=" + GenXfaTagValue(data_provider);
+}
+
+// Creates an XFA structure wrapped in <xdp tags.
+std::string GenXfaTree(FuzzedDataProvider* data_provider) {
+  std::string xfa_string = "<xdp xmlns=\"http://ns.adobe.com/xdp/\">";
+
+  // One stack iteration
+  int stack_iterations = data_provider->ConsumeIntegralInRange(1, 3);
+  for (int si = 0; si < stack_iterations; si++) {
+    int elem_count = data_provider->ConsumeIntegralInRange(1, 6);
+    std::vector<std::string> xml_stack;
+    xml_stack.reserve(elem_count);
+    for (int i = 0; i < elem_count; i++) {
+      std::string tag = GenXfaTag(data_provider);
+      xfa_string += "<" + tag;
+
+      // in 30% of cases, add attributes
+      if (data_provider->ConsumeIntegralInRange(1, 100) > 70) {
+        size_t attribute_count = data_provider->ConsumeIntegralInRange(1, 5);
+        for (; 0 < attribute_count; attribute_count--) {
+          xfa_string += " " + getXfaElemAttributes(data_provider);
+        }
+      }
+      xfa_string += ">";
+
+      // If needed, add a body to the tag
+      if (tag == "script") {
+        xfa_string += GenXfacript(data_provider);
+      }
+
+      // Push the tag to the stack so we can close it when done
+      xml_stack.push_back(tag);
+    }
+    for (const std::string& tag : pdfium::base::Reversed(xml_stack)) {
+      xfa_string += "</" + tag + ">";
+    }
+  }
+  xfa_string += "</xdp>";
+  return xfa_string;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  FuzzedDataProvider data_provider(data, size);
+  std::string xfa_string = GenXfaTree(&data_provider);
+
+  // Add 1 for newline before endstream.
+  std::string xfa_stream_len = std::to_string(xfa_string.size() + 1);
+
+  // Compose the fuzzer
+  std::string xfa_final_str = std::string(kSimplePdfTemplate);
+  xfa_final_str.replace(xfa_final_str.find("$1"), 2, xfa_stream_len);
+  xfa_final_str.replace(xfa_final_str.find("$2"), 2, xfa_string);
+
+#ifdef PDFIUM_FUZZER_DUMP
+  for (size_t i = 0; i < xfa_final_str.size(); i++) {
+    putc(xfa_final_str[i], stdout);
+  }
+#endif
+
+  PDFiumXFAFuzzer fuzzer;
+  fuzzer.SetFdp(&data_provider);
+  fuzzer.RenderPdf(xfa_final_str.c_str(), xfa_final_str.size());
+  return 0;
+}
diff --git a/testing/fuzzers/pdf_xfa_raw_fuzzer.cc b/testing/fuzzers/pdf_xfa_raw_fuzzer.cc
new file mode 100644
index 0000000..8dce02f
--- /dev/null
+++ b/testing/fuzzers/pdf_xfa_raw_fuzzer.cc
@@ -0,0 +1,101 @@
+// Copyright 2021 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include <cctype>
+#include <string>
+
+#include "public/fpdf_formfill.h"
+#include "testing/fuzzers/pdf_fuzzer_templates.h"
+#include "testing/fuzzers/pdfium_fuzzer_helper.h"
+
+class PDFiumXFAFuzzer : public PDFiumFuzzerHelper {
+ public:
+  PDFiumXFAFuzzer() = default;
+  ~PDFiumXFAFuzzer() override = default;
+
+  int GetFormCallbackVersion() const override { return 2; }
+
+  // Return false if XFA doesn't load as otherwise we're duplicating the work
+  // done by the non-xfa fuzzer.
+  bool OnFormFillEnvLoaded(FPDF_DOCUMENT doc) override {
+    int form_type = FPDF_GetFormType(doc);
+    if (form_type != FORMTYPE_XFA_FULL && form_type != FORMTYPE_XFA_FOREGROUND)
+      return false;
+    return FPDF_LoadXFA(doc);
+  }
+};
+
+bool IsValidForFuzzing(const uint8_t* data, size_t size) {
+  if (size > 2048) {
+    return false;
+  }
+
+  const char* ptr = reinterpret_cast<const char*>(data);
+  bool is_open = false;
+  size_t tag_size = 0;
+  for (size_t i = 0; i < size; i++) {
+    if (!std::isspace(ptr[i]) && !std::isprint(ptr[i])) {
+      return false;
+    }
+
+    // We do not want any script tags. The reason is this fuzzer
+    // should avoid exploring v8 code. Avoiding anything with "script"
+    // is an over-approximation, in that some inputs may contain "script"
+    // and still be a valid fuzz-case. However, this over-approximation is
+    // used to enforce strict constraints and avoid cases where whitespace
+    // may play a role, or other tags, e.g. "Javascript" will end up triggering
+    // large explorations of v8 code. The alternative we considered were
+    // "<script"
+    if (i + 6 < size && memcmp(ptr + i, "script", 6) == 0) {
+      return false;
+    }
+
+    if (ptr[i] == '<') {
+      if (is_open) {
+        return false;
+      }
+      is_open = true;
+      tag_size = 0;
+    } else if (ptr[i] == '>') {
+      if (!is_open || tag_size == 0) {
+        return false;
+      }
+      is_open = false;
+    } else if (is_open) {
+      tag_size++;
+    }
+  }
+  // we must close the last bracket.
+  return !is_open;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  // Filter the string to reduce the state space exploration.
+  if (!IsValidForFuzzing(data, size)) {
+    return 0;
+  }
+  std::string xfa_string = "<xdp xmlns=\"http://ns.adobe.com/xdp/\">";
+  xfa_string += std::string(reinterpret_cast<const char*>(data), size);
+  xfa_string += "</xdp>";
+
+  // Add 1 for newline before endstream.
+  std::string xfa_stream_len = std::to_string(xfa_string.size() + 1);
+
+  // Compose the fuzzer
+  std::string xfa_final_str = std::string(kSimplePdfTemplate);
+  xfa_final_str.replace(xfa_final_str.find("$1"), 2, xfa_stream_len);
+  xfa_final_str.replace(xfa_final_str.find("$2"), 2, xfa_string);
+
+#ifdef PDFIUM_FUZZER_DUMP
+  for (size_t i = 0; i < xfa_final_str.size(); i++) {
+    putc(xfa_final_str[i], stdout);
+  }
+#endif
+
+  PDFiumXFAFuzzer fuzzer;
+  fuzzer.RenderPdf(xfa_final_str.c_str(), xfa_final_str.size());
+  return 0;
+}
diff --git a/testing/fuzzers/pdf_xfa_xdp_fdp_fuzzer.cc b/testing/fuzzers/pdf_xfa_xdp_fdp_fuzzer.cc
new file mode 100644
index 0000000..8917ce7
--- /dev/null
+++ b/testing/fuzzers/pdf_xfa_xdp_fdp_fuzzer.cc
@@ -0,0 +1,206 @@
+// Copyright 2021 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include <string>
+
+#include "public/fpdf_formfill.h"
+#include "testing/fuzzers/pdf_fuzzer_templates.h"
+#include "testing/fuzzers/pdfium_fuzzer_helper.h"
+
+class PDFiumXDPFuzzer : public PDFiumFuzzerHelper {
+ public:
+  PDFiumXDPFuzzer() = default;
+  ~PDFiumXDPFuzzer() override = default;
+
+  int GetFormCallbackVersion() const override { return 2; }
+
+  bool OnFormFillEnvLoaded(FPDF_DOCUMENT doc) override {
+    int form_type = FPDF_GetFormType(doc);
+    if (form_type != FORMTYPE_XFA_FULL && form_type != FORMTYPE_XFA_FOREGROUND)
+      return false;
+    return FPDF_LoadXFA(doc);
+  }
+};
+
+struct Tag {
+  const char* tag_name;
+  const char* tag_start;
+  const char* tag_end;
+};
+
+const Tag kTagData[]{
+    {.tag_name = "config",
+     .tag_start =
+         R""(<xfa:config xmlns:xfa="http://www.xfa.org/schema/xci/3.1/">)"",
+     .tag_end = "</xfa:config>"},
+    {.tag_name = "template",
+     .tag_start =
+         R""(<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">)"",
+     .tag_end = "</template>"},
+    {.tag_name = "sourceSet",
+     .tag_start =
+         R""(<sourceSet xmlns="http://www.xfa.org/schema/xfa-source-set/2.7/">)"",
+     .tag_end = "</sourceSet>"},
+    {.tag_name = "localeSet",
+     .tag_start =
+         R""(<localeSet xmlns="http://www.xfa.org/schema/xfa-locale-set/2.7/">)"",
+     .tag_end = "</localeSet>"},
+    {.tag_name = "dataSet",
+     .tag_start =
+         R""(<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">)"",
+     .tag_end = "</xfa:datasets>"},
+    {.tag_name = "connectionSet",
+     .tag_start =
+         R""(<connectionSet xmlns="http://www.xfa.org/schema/xfa-connection-set/2.8/">)"",
+     .tag_end = "</connectionSet>"},
+    {.tag_name = "xdc",
+     .tag_start =
+         R""(<xsl:xdc xmlns:xdc="http://www.xfa.org/schema/xdc/1.0/">)"",
+     .tag_end = "</xsl:xdc>"},
+    {.tag_name = "signature",
+     .tag_start = R""(<signature xmlns="http://www.w3.org/2000/09/xmldsig#">)"",
+     .tag_end = "</signature>"},
+    {.tag_name = "stylesheet",
+     .tag_start =
+         R""(<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" id="identifier">)"",
+     .tag_end = "</stylesheet>"},
+    {.tag_name = "xfdf",
+     .tag_start =
+         R""(<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">)"",
+     .tag_end = "</xfdf>"},
+    {.tag_name = "xmpmeta",
+     .tag_start =
+         R""(<xmpmeta xmlns="http://ns.adobe.com/xmpmeta/" xml:space="preserve">)"",
+     .tag_end = "</xmpmeta>"}};
+
+std::string CreateObject(int obj_num, const std::string& body) {
+  std::string obj_template = R""($1 0 obj
+$2
+endobj
+)"";
+
+  obj_template.replace(obj_template.find("$1"), 2, std::to_string(obj_num));
+  obj_template.replace(obj_template.find("$2"), 2, body);
+  return obj_template;
+}
+
+std::string CreateStreamObject(int obj_num, const std::string& body) {
+  std::string obj_template = R""($1 0 obj
+<</Length $2>>
+stream
+$3
+endstream
+endobj
+)"";
+
+  obj_template.replace(obj_template.find("$1"), 2, std::to_string(obj_num));
+  obj_template.replace(obj_template.find("$2"), 2,
+                       std::to_string(body.size() + 1));
+  obj_template.replace(obj_template.find("$3"), 2, body);
+
+  return obj_template;
+}
+
+std::string GenXrefEntry(size_t offset) {
+  return std::string(10 - std::to_string(offset).size(), '0') +
+         std::to_string(offset) + " 00000 n\n";
+}
+
+std::string GenTagBody(const Tag& tag, FuzzedDataProvider* data_provider) {
+  std::string tag_content = data_provider->ConsumeRandomLengthString();
+  return tag.tag_start + tag_content + tag.tag_end;
+}
+
+std::string GenXDPPdfFile(FuzzedDataProvider* data_provider) {
+  std::vector<std::string> pdf_objects;
+  std::string pdf_header =
+      std::string(reinterpret_cast<const char*>(kSimplePdfHeader),
+                  sizeof(kSimplePdfHeader));
+
+  pdf_objects.push_back(CreateObject(1, kCatalog));
+
+  std::string xfa_obj = kSimpleXfaObjWrapper;
+  Tag tag1 = data_provider->PickValueInArray(kTagData);
+  Tag tag2 = data_provider->PickValueInArray(kTagData);
+  Tag tag3 = data_provider->PickValueInArray(kTagData);
+  xfa_obj.replace(xfa_obj.find("$1"), 2, tag1.tag_name);
+  xfa_obj.replace(xfa_obj.find("$2"), 2, tag2.tag_name);
+  xfa_obj.replace(xfa_obj.find("$3"), 2, tag3.tag_name);
+  pdf_objects.push_back(CreateObject(2, xfa_obj));
+  pdf_objects.push_back(CreateObject(3, kSimplePagesObj));
+  pdf_objects.push_back(CreateObject(4, kSimplePageObj));
+
+  // preamble
+  pdf_objects.push_back(CreateStreamObject(5, kSimplePreamble));
+
+  // The three XFA tags
+  pdf_objects.push_back(CreateStreamObject(6, GenTagBody(tag1, data_provider)));
+  pdf_objects.push_back(CreateStreamObject(7, GenTagBody(tag2, data_provider)));
+  pdf_objects.push_back(CreateStreamObject(8, GenTagBody(tag3, data_provider)));
+
+  // postamble
+  pdf_objects.push_back(CreateStreamObject(9, kSimplePostamble));
+
+  // Create the xref table
+  std::string xref = R""(xref
+0 10
+0000000000 65535 f
+)"";
+
+  // Add xref entries
+  size_t curr_offset = pdf_header.size();
+  for (const auto& ostr : pdf_objects) {
+    xref += GenXrefEntry(curr_offset);
+    curr_offset += ostr.size();
+  }
+
+  std::string footer = R""(trailer
+<</Root 1 0 R /Size 10>>
+startxref
+$1
+%%EOF)"";
+  footer.replace(footer.find("$1"), 2, std::to_string(curr_offset));
+
+  std::string pdf_core;
+  for (const auto& ostr : pdf_objects) {
+    pdf_core += ostr;
+  }
+
+  // Return the full PDF
+  return pdf_header + pdf_core + xref + footer;
+}
+
+bool IsValidForFuzzing(const uint8_t* data, size_t size) {
+  if (size > 2048) {
+    return false;
+  }
+  const char* ptr = reinterpret_cast<const char*>(data);
+  for (size_t i = 0; i < size; i++) {
+    if (!std::isspace(ptr[i]) && !std::isprint(ptr[i])) {
+      return false;
+    }
+  }
+  return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if (!IsValidForFuzzing(data, size)) {
+    return 0;
+  }
+
+  FuzzedDataProvider data_provider(data, size);
+  std::string xfa_final_str = GenXDPPdfFile(&data_provider);
+
+#ifdef PDFIUM_FUZZER_DUMP
+  for (size_t i = 0; i < xfa_final_str.size(); i++) {
+    putc(xfa_final_str[i], stdout);
+  }
+#endif
+
+  PDFiumXDPFuzzer fuzzer;
+  fuzzer.RenderPdf(xfa_final_str.c_str(), xfa_final_str.size());
+  return 0;
+}
diff --git a/testing/fuzzers/pdf_xml_fuzzer.cc b/testing/fuzzers/pdf_xml_fuzzer.cc
index e858f5b..31ab6d7 100644
--- a/testing/fuzzers/pdf_xml_fuzzer.cc
+++ b/testing/fuzzers/pdf_xml_fuzzer.cc
@@ -1,4 +1,4 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -6,7 +6,7 @@
 #include <cstdint>
 #include <memory>
 
-#include "core/fxcrt/cfx_readonlymemorystream.h"
+#include "core/fxcrt/cfx_read_only_span_stream.h"
 #include "core/fxcrt/fx_safe_types.h"
 #include "core/fxcrt/fx_system.h"
 #include "core/fxcrt/xml/cfx_xmldocument.h"
@@ -19,8 +19,8 @@
   if (!safe_size.IsValid())
     return 0;
 
-  auto stream = pdfium::MakeRetain<CFX_ReadOnlyMemoryStream>(
-      pdfium::make_span(data, size));
+  auto stream =
+      pdfium::MakeRetain<CFX_ReadOnlySpanStream>(pdfium::make_span(data, size));
   CFX_XMLParser parser(stream);
   std::unique_ptr<CFX_XMLDocument> doc = parser.Parse();
   if (!doc || !doc->GetRoot())
diff --git a/testing/fuzzers/pdfium_fuzzer.cc b/testing/fuzzers/pdfium_fuzzer.cc
index dc15378..7e25fed 100644
--- a/testing/fuzzers/pdfium_fuzzer.cc
+++ b/testing/fuzzers/pdfium_fuzzer.cc
@@ -1,4 +1,4 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -6,6 +6,12 @@
 
 #include "testing/fuzzers/pdfium_fuzzer_helper.h"
 
+#if defined(BUILD_WITH_CHROMIUM)
+#include "base/memory/discardable_memory_allocator.h"
+#include "base/no_destructor.h"
+#include "base/test/test_discardable_memory_allocator.h"
+#endif
+
 class PDFiumFuzzer : public PDFiumFuzzerHelper {
  public:
   PDFiumFuzzer() = default;
@@ -15,6 +21,12 @@
 };
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+#if defined(BUILD_WITH_CHROMIUM)
+  static base::NoDestructor<base::TestDiscardableMemoryAllocator>
+      test_memory_allocator;
+  base::DiscardableMemoryAllocator::SetInstance(test_memory_allocator.get());
+#endif
+
   PDFiumFuzzer fuzzer;
   fuzzer.RenderPdf(reinterpret_cast<const char*>(data), size);
   return 0;
diff --git a/testing/fuzzers/pdfium_fuzzer_helper.cc b/testing/fuzzers/pdfium_fuzzer_helper.cc
index 266666d..f011b18 100644
--- a/testing/fuzzers/pdfium_fuzzer_helper.cc
+++ b/testing/fuzzers/pdfium_fuzzer_helper.cc
@@ -1,19 +1,16 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "testing/fuzzers/pdfium_fuzzer_helper.h"
 
 #include <assert.h>
-#include <limits.h>
-
 #include <stddef.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include <memory>
 #include <sstream>
 #include <string>
 #include <tuple>
@@ -23,6 +20,7 @@
 #include "public/fpdf_dataavail.h"
 #include "public/fpdf_ext.h"
 #include "public/fpdf_text.h"
+#include "third_party/base/notreached.h"
 #include "third_party/base/span.h"
 
 namespace {
@@ -95,7 +93,7 @@
 std::pair<int, int> GetRenderingAndFormFlagFromData(const char* data,
                                                     size_t len) {
   std::string data_str = std::string(data, len);
-  std::size_t data_hash = std::hash<std::string>()(data_str);
+  size_t data_hash = std::hash<std::string>()(data_str);
 
   // The largest flag value is 0x4FFF, so just take 16 bits from |data_hash| at
   // a time.
@@ -218,6 +216,8 @@
   FORM_OnAfterLoadPage(page.get(), form);
   FORM_DoPageAAction(page.get(), form, FPDFPAGE_AACTION_OPEN);
 
+  FormActionHandler(form, doc, page.get());
+
   const double scale = 1.0;
   int width = static_cast<int>(FPDF_GetPageWidthF(page.get()) * scale);
   int height = static_cast<int>(FPDF_GetPageHeightF(page.get()) * scale);
diff --git a/testing/fuzzers/pdfium_fuzzer_helper.h b/testing/fuzzers/pdfium_fuzzer_helper.h
index af14941..900f55d 100644
--- a/testing/fuzzers/pdfium_fuzzer_helper.h
+++ b/testing/fuzzers/pdfium_fuzzer_helper.h
@@ -1,4 +1,4 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -16,6 +16,9 @@
   virtual int GetFormCallbackVersion() const = 0;
   virtual bool OnFormFillEnvLoaded(FPDF_DOCUMENT doc);
   virtual void OnRenderFinished(FPDF_DOCUMENT doc) {}
+  virtual void FormActionHandler(FPDF_FORMHANDLE form,
+                                 FPDF_DOCUMENT doc,
+                                 FPDF_PAGE page) {}
 
  protected:
   PDFiumFuzzerHelper();
diff --git a/testing/fuzzers/pdfium_fuzzer_util.cc b/testing/fuzzers/pdfium_fuzzer_util.cc
index 9238f0e..f8f8286 100644
--- a/testing/fuzzers/pdfium_fuzzer_util.cc
+++ b/testing/fuzzers/pdfium_fuzzer_util.cc
@@ -1,9 +1,21 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "testing/fuzzers/pdfium_fuzzer_util.h"
 
+namespace {
+void* g_fuzzer_init_per_process_state = nullptr;
+}  // namespace
+
 int GetInteger(const uint8_t* data) {
   return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
 }
+
+FPDF_EXPORT void FPDF_CALLCONV FPDF_SetFuzzerPerProcessState(void* state) {
+  g_fuzzer_init_per_process_state = state;
+}
+
+FPDF_EXPORT void* FPDF_CALLCONV FPDF_GetFuzzerPerProcessState() {
+  return g_fuzzer_init_per_process_state;
+}
diff --git a/testing/fuzzers/pdfium_fuzzer_util.h b/testing/fuzzers/pdfium_fuzzer_util.h
index d82f44b..a37544d 100644
--- a/testing/fuzzers/pdfium_fuzzer_util.h
+++ b/testing/fuzzers/pdfium_fuzzer_util.h
@@ -1,4 +1,4 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -7,7 +7,19 @@
 
 #include <stdint.h>
 
+#include "public/fpdfview.h"
+
 // Returns an integer from the first 4 bytes of |data|.
 int GetInteger(const uint8_t* data);
 
+// Plumb access to any context created by fuzzer initialization into
+// the LLVMFuzzerTestOneInput() function, as that function does not
+// allow for additional parameters, nor can it reach back up to the
+// top-level fuzzer shim during a component build (see the comment
+// in BUILD.gn about splitting fuzzers into _impl and _src targets).
+extern "C" {
+FPDF_EXPORT void FPDF_CALLCONV FPDF_SetFuzzerPerProcessState(void* state);
+FPDF_EXPORT void* FPDF_CALLCONV FPDF_GetFuzzerPerProcessState();
+}  // extern "C"
+
 #endif  // TESTING_FUZZERS_PDFIUM_FUZZER_UTIL_H_
diff --git a/testing/fuzzers/pdfium_xfa_fuzzer.cc b/testing/fuzzers/pdfium_xfa_fuzzer.cc
index f9a69d4..1ba78cc 100644
--- a/testing/fuzzers/pdfium_xfa_fuzzer.cc
+++ b/testing/fuzzers/pdfium_xfa_fuzzer.cc
@@ -1,4 +1,4 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/fuzzers/pdfium_xfa_lpm_fuzz_stub.cc b/testing/fuzzers/pdfium_xfa_lpm_fuzz_stub.cc
index c4b55b6..9f6c78a 100644
--- a/testing/fuzzers/pdfium_xfa_lpm_fuzz_stub.cc
+++ b/testing/fuzzers/pdfium_xfa_lpm_fuzz_stub.cc
@@ -1,4 +1,4 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/fuzzers/pdfium_xfa_lpm_fuzz_stub.h b/testing/fuzzers/pdfium_xfa_lpm_fuzz_stub.h
index bb6bf1d..a7c2ec0 100644
--- a/testing/fuzzers/pdfium_xfa_lpm_fuzz_stub.h
+++ b/testing/fuzzers/pdfium_xfa_lpm_fuzz_stub.h
@@ -1,4 +1,4 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/fuzzers/xfa_codec_fuzzer.h b/testing/fuzzers/xfa_codec_fuzzer.h
index 4f5668d..b14b7d7 100644
--- a/testing/fuzzers/xfa_codec_fuzzer.h
+++ b/testing/fuzzers/xfa_codec_fuzzer.h
@@ -1,4 +1,4 @@
-// Copyright 2016 The PDFium Authors. All rights reserved.
+// Copyright 2016 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -6,12 +6,14 @@
 #define TESTING_FUZZERS_XFA_CODEC_FUZZER_H_
 
 #include <memory>
+#include <utility>
 
 #include "core/fxcodec/fx_codec.h"
-#include "core/fxcodec/progressivedecoder.h"
-#include "core/fxcrt/cfx_readonlymemorystream.h"
+#include "core/fxcodec/progressive_decoder.h"
+#include "core/fxcrt/cfx_read_only_span_stream.h"
+#include "core/fxcrt/fx_safe_types.h"
+#include "core/fxcrt/retain_ptr.h"
 #include "core/fxge/dib/cfx_dibitmap.h"
-#include "third_party/base/ptr_util.h"
 #include "third_party/base/span.h"
 
 // Support up to 64 MB. This prevents trivial OOM when MSAN is on and
@@ -21,14 +23,13 @@
 class XFACodecFuzzer {
  public:
   static int Fuzz(const uint8_t* data, size_t size, FXCODEC_IMAGE_TYPE type) {
-    auto* mgr = fxcodec::ModuleMgr::GetInstance();
-    std::unique_ptr<ProgressiveDecoder> decoder =
-        mgr->CreateProgressiveDecoder();
-    auto source = pdfium::MakeRetain<CFX_ReadOnlyMemoryStream>(
+    auto decoder = std::make_unique<ProgressiveDecoder>();
+    auto source = pdfium::MakeRetain<CFX_ReadOnlySpanStream>(
         pdfium::make_span(data, size));
     CFX_DIBAttribute attr;
-    FXCODEC_STATUS status = decoder->LoadImageInfo(source, type, &attr, true);
-    if (status != FXCODEC_STATUS_FRAME_READY)
+    FXCODEC_STATUS status =
+        decoder->LoadImageInfo(std::move(source), type, &attr, true);
+    if (status != FXCODEC_STATUS::kFrameReady)
       return 0;
 
     // Skipping very large images, since they will take a long time and may lead
@@ -42,16 +43,17 @@
     }
 
     auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
-    bitmap->Create(decoder->GetWidth(), decoder->GetHeight(), FXDIB_Argb);
+    bitmap->Create(decoder->GetWidth(), decoder->GetHeight(),
+                   FXDIB_Format::kArgb);
 
     size_t frames;
     std::tie(status, frames) = decoder->GetFrames();
-    if (status != FXCODEC_STATUS_DECODE_READY || frames == 0)
+    if (status != FXCODEC_STATUS::kDecodeReady || frames == 0)
       return 0;
 
     status = decoder->StartDecode(bitmap, 0, 0, bitmap->GetWidth(),
                                   bitmap->GetHeight());
-    while (status == FXCODEC_STATUS_DECODE_TOBECONTINUE)
+    while (status == FXCODEC_STATUS::kDecodeToBeContinued)
       status = decoder->ContinueDecode();
 
     return 0;
diff --git a/testing/fuzzers/xfa_process_state.cc b/testing/fuzzers/xfa_process_state.cc
new file mode 100644
index 0000000..f768fea
--- /dev/null
+++ b/testing/fuzzers/xfa_process_state.cc
@@ -0,0 +1,25 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/fuzzers/xfa_process_state.h"
+
+#include "fxjs/gc/heap.h"
+#include "v8/include/libplatform/libplatform.h"
+
+XFAProcessState::XFAProcessState(v8::Platform* platform, v8::Isolate* isolate)
+    : platform_(platform), isolate_(isolate), heap_(FXGC_CreateHeap()) {}
+
+XFAProcessState::~XFAProcessState() {
+  FXGC_ForceGarbageCollection(heap_.get());
+}
+
+cppgc::Heap* XFAProcessState::GetHeap() const {
+  return heap_.get();
+}
+
+void XFAProcessState::ForceGCAndPump() {
+  FXGC_ForceGarbageCollection(heap_.get());
+  while (v8::platform::PumpMessageLoop(platform_, isolate_))
+    continue;
+}
diff --git a/testing/fuzzers/xfa_process_state.h b/testing/fuzzers/xfa_process_state.h
new file mode 100644
index 0000000..12d8a7d
--- /dev/null
+++ b/testing/fuzzers/xfa_process_state.h
@@ -0,0 +1,33 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_FUZZERS_XFA_PROCESS_STATE_H_
+#define TESTING_FUZZERS_XFA_PROCESS_STATE_H_
+
+#if !defined(PDF_ENABLE_XFA)
+#error "XFA only"
+#endif
+
+#include "fxjs/gc/heap.h"
+
+namespace v8 {
+class Isolate;
+class Platform;
+}  // namespace v8
+
+class XFAProcessState {
+ public:
+  XFAProcessState(v8::Platform* platform, v8::Isolate* isolate);
+  ~XFAProcessState();
+
+  cppgc::Heap* GetHeap() const;
+  void ForceGCAndPump();
+
+ private:
+  v8::Platform* const platform_;
+  v8::Isolate* const isolate_;
+  FXGCScopedHeap heap_;
+};
+
+#endif  // TESTING_FUZZERS_XFA_PROCESS_STATE_H_
diff --git a/testing/fx_string_testhelpers.cpp b/testing/fx_string_testhelpers.cpp
index 4a7bda7..8e76a76 100644
--- a/testing/fx_string_testhelpers.cpp
+++ b/testing/fx_string_testhelpers.cpp
@@ -1,4 +1,4 @@
-// Copyright 2014 PDFium Authors. All rights reserved.
+// Copyright 2014 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -6,8 +6,11 @@
 
 #include <iomanip>
 #include <ios>
+#include <ostream>
 
+#include "core/fxcrt/cfx_datetime.h"
 #include "core/fxcrt/fx_string.h"
+#include "third_party/base/check_op.h"
 #include "third_party/base/span.h"
 
 std::ostream& operator<<(std::ostream& os, const CFX_DateTime& dt) {
@@ -22,7 +25,7 @@
 std::vector<std::string> StringSplit(const std::string& str, char delimiter) {
   std::vector<std::string> result;
   size_t pos = 0;
-  while (1) {
+  while (true) {
     size_t found = str.find(delimiter, pos);
     if (found == std::string::npos)
       break;
@@ -48,16 +51,17 @@
   while (wstr[characters])
     ++characters;
 
-  std::wstring platform_string(characters, L'\0');
-  for (size_t i = 0; i < characters + 1; ++i) {
+  std::wstring platform_string;
+  platform_string.reserve(characters);
+  for (size_t i = 0; i < characters; ++i) {
     const unsigned char* ptr = reinterpret_cast<const unsigned char*>(&wstr[i]);
-    platform_string[i] = ptr[0] + 256 * ptr[1];
+    platform_string.push_back(ptr[0] + 256 * ptr[1]);
   }
   return platform_string;
 }
 
 ScopedFPDFWideString GetFPDFWideString(const std::wstring& wstr) {
-  size_t length = sizeof(uint16_t) * (wstr.length() + 1);
+  size_t length = sizeof(uint16_t) * (wstr.size() + 1);
   ScopedFPDFWideString result(static_cast<FPDF_WCHAR*>(malloc(length)));
   pdfium::span<uint8_t> result_span(reinterpret_cast<uint8_t*>(result.get()),
                                     length);
@@ -72,6 +76,6 @@
 }
 
 std::vector<FPDF_WCHAR> GetFPDFWideStringBuffer(size_t length_bytes) {
-  ASSERT(length_bytes % sizeof(FPDF_WCHAR) == 0);
+  DCHECK_EQ(length_bytes % sizeof(FPDF_WCHAR), 0);
   return std::vector<FPDF_WCHAR>(length_bytes / sizeof(FPDF_WCHAR));
 }
diff --git a/testing/fx_string_testhelpers.h b/testing/fx_string_testhelpers.h
index b90838a..207947c 100644
--- a/testing/fx_string_testhelpers.h
+++ b/testing/fx_string_testhelpers.h
@@ -1,19 +1,20 @@
-// Copyright 2014 PDFium Authors. All rights reserved.
+// Copyright 2014 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #ifndef TESTING_FX_STRING_TESTHELPERS_H_
 #define TESTING_FX_STRING_TESTHELPERS_H_
 
+#include <iosfwd>
 #include <memory>
-#include <ostream>
 #include <string>
 #include <vector>
 
-#include "core/fxcrt/cfx_datetime.h"
 #include "public/fpdfview.h"
 #include "testing/free_deleter.h"
 
+class CFX_DateTime;
+
 // Output stream operator so GTEST macros work with CFX_DateTime objects.
 std::ostream& operator<<(std::ostream& os, const CFX_DateTime& dt);
 
diff --git a/testing/fxgc_unittest.cpp b/testing/fxgc_unittest.cpp
new file mode 100644
index 0000000..cc01ab4
--- /dev/null
+++ b/testing/fxgc_unittest.cpp
@@ -0,0 +1,39 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/fxgc_unittest.h"
+
+#include "fxjs/gc/heap.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/v8_test_environment.h"
+#include "v8/include/libplatform/libplatform.h"
+
+FXGCUnitTest::FXGCUnitTest() = default;
+
+FXGCUnitTest::~FXGCUnitTest() = default;
+
+void FXGCUnitTest::SetUp() {
+  ::testing::Test::SetUp();
+  auto* env = V8TestEnvironment::GetInstance();
+  FXGC_Initialize(env->platform(), env->isolate());
+  heap_ = FXGC_CreateHeap();
+  ASSERT_TRUE(heap_);
+}
+
+void FXGCUnitTest::TearDown() {
+  ForceGCAndPump();
+  heap_.reset();
+  FXGC_Release();
+  ::testing::Test::TearDown();
+}
+
+void FXGCUnitTest::ForceGCAndPump() {
+  FXGC_ForceGarbageCollection(heap_.get());
+  Pump();
+}
+
+void FXGCUnitTest::Pump() {
+  V8TestEnvironment::PumpPlatformMessageLoop(
+      V8TestEnvironment::GetInstance()->isolate());
+}
diff --git a/testing/fxgc_unittest.h b/testing/fxgc_unittest.h
new file mode 100644
index 0000000..0625190
--- /dev/null
+++ b/testing/fxgc_unittest.h
@@ -0,0 +1,28 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_FXGC_UNITTEST_H_
+#define TESTING_FXGC_UNITTEST_H_
+
+#include "fxjs/gc/heap.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class FXGCUnitTest : public ::testing::Test {
+ public:
+  FXGCUnitTest();
+  ~FXGCUnitTest() override;
+
+  // testing::Test:
+  void SetUp() override;
+  void TearDown() override;
+
+  cppgc::Heap* heap() const { return heap_.get(); }
+  void ForceGCAndPump();
+  void Pump();
+
+ private:
+  FXGCScopedHeap heap_;
+};
+
+#endif  // TESTING_FXGC_UNITTEST_H_
diff --git a/testing/fxv8_unittest.cpp b/testing/fxv8_unittest.cpp
new file mode 100644
index 0000000..fdd4c08
--- /dev/null
+++ b/testing/fxv8_unittest.cpp
@@ -0,0 +1,26 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/fxv8_unittest.h"
+
+#include <memory>
+
+#include "fxjs/cfx_v8_array_buffer_allocator.h"
+#include "v8/include/v8-isolate.h"
+
+void FXV8UnitTest::V8IsolateDeleter::operator()(v8::Isolate* ptr) const {
+  ptr->Dispose();
+}
+
+FXV8UnitTest::FXV8UnitTest() = default;
+
+FXV8UnitTest::~FXV8UnitTest() = default;
+
+void FXV8UnitTest::SetUp() {
+  array_buffer_allocator_ = std::make_unique<CFX_V8ArrayBufferAllocator>();
+
+  v8::Isolate::CreateParams params;
+  params.array_buffer_allocator = array_buffer_allocator_.get();
+  isolate_.reset(v8::Isolate::New(params));
+}
diff --git a/testing/fxv8_unittest.h b/testing/fxv8_unittest.h
new file mode 100644
index 0000000..0757c73
--- /dev/null
+++ b/testing/fxv8_unittest.h
@@ -0,0 +1,37 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_FXV8_UNITTEST_H_
+#define TESTING_FXV8_UNITTEST_H_
+
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+class CFX_V8;
+class CFX_V8ArrayBufferAllocator;
+
+namespace v8 {
+class Isolate;
+}  // namespace v8
+
+class FXV8UnitTest : public ::testing::Test {
+ public:
+  struct V8IsolateDeleter {
+    void operator()(v8::Isolate* ptr) const;
+  };
+
+  FXV8UnitTest();
+  ~FXV8UnitTest() override;
+
+  void SetUp() override;
+
+  v8::Isolate* isolate() const { return isolate_.get(); }
+
+ protected:
+  std::unique_ptr<CFX_V8ArrayBufferAllocator> array_buffer_allocator_;
+  std::unique_ptr<v8::Isolate, V8IsolateDeleter> isolate_;
+};
+
+#endif  // TESTING_FXV8_UNITTEST_H_
diff --git a/testing/gmock/BUILD.gn b/testing/gmock/BUILD.gn
index d7de2e9..feb8240 100644
--- a/testing/gmock/BUILD.gn
+++ b/testing/gmock/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2018 The PDFium Authors. All rights reserved.
+# Copyright 2018 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -6,31 +6,14 @@
 # it stabilizes, Chromium code MUST use this target instead of reaching directly
 # into //third_party/googletest.
 
-import("//build_overrides/build.gni")
-
 source_set("gmock") {
   testonly = true
   sources = [
     "include/gmock/gmock-actions.h",
-    "include/gmock/gmock-generated-function-mockers.h",
     "include/gmock/gmock-matchers.h",
     "include/gmock/gmock.h",
   ]
-  deps = [ "//third_party/googletest:gmock" ]
-
-  # TODO(crbug.com/806952): Depending on gmock_mutant only if build_with_chromium,
-  # because gmock_mutant depends on //base which uses C++14. Since gmock is a
-  # third_party library used by other projects it should not include C++14 only code.
-  if (build_with_chromium) {
-    # Allow Chromium targets depending on gmock to #include testing/gmock_mutant.h
-    # without triggering a `gn check` error.
-    public_deps = [ "//testing:gmock_mutant" ]
-  }
-
-  public_configs = [
-    "//third_party/googletest:gmock_config",
-    "//third_party/googletest:gtest_config",
-  ]
+  public_deps = [ "//third_party/googletest:gmock" ]
 }
 
 # The file/directory layout of Google Test is not yet considered stable. Until
diff --git a/testing/gmock/include/gmock/gmock-actions.h b/testing/gmock/include/gmock/gmock-actions.h
index fb193e5..21fc6b0 100644
--- a/testing/gmock/include/gmock/gmock-actions.h
+++ b/testing/gmock/include/gmock/gmock-actions.h
@@ -1,4 +1,4 @@
-// Copyright 2018 The PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/gmock/include/gmock/gmock-generated-function-mockers.h b/testing/gmock/include/gmock/gmock-generated-function-mockers.h
index 57cbc0a..8ce51a0 100644
--- a/testing/gmock/include/gmock/gmock-generated-function-mockers.h
+++ b/testing/gmock/include/gmock/gmock-generated-function-mockers.h
@@ -1,4 +1,4 @@
-// Copyright 2018 The PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/gmock/include/gmock/gmock-matchers.h b/testing/gmock/include/gmock/gmock-matchers.h
index 25d25e9..ca7cf0e 100644
--- a/testing/gmock/include/gmock/gmock-matchers.h
+++ b/testing/gmock/include/gmock/gmock-matchers.h
@@ -1,4 +1,4 @@
-// Copyright 2018 The PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/gmock/include/gmock/gmock.h b/testing/gmock/include/gmock/gmock.h
index a344bcb..dfcd2d4 100644
--- a/testing/gmock/include/gmock/gmock.h
+++ b/testing/gmock/include/gmock/gmock.h
@@ -1,4 +1,4 @@
-// Copyright 2018 The PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/gtest/BUILD.gn b/testing/gtest/BUILD.gn
index ad0b269..f6b98df 100644
--- a/testing/gtest/BUILD.gn
+++ b/testing/gtest/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2018 The PDFium Authors. All rights reserved.
+# Copyright 2018 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -55,10 +55,7 @@
     sources += [ "../platform_test.h" ]
   }
 
-  if ((is_mac || is_ios) && gtest_include_objc_support) {
-    if (is_ios) {
-      set_sources_assignment_filter([])
-    }
+  if (is_apple && gtest_include_objc_support) {
     sources += [
       "../gtest_mac.h",
       "../gtest_mac.mm",
@@ -66,15 +63,10 @@
     if (gtest_include_platform_test) {
       sources += [ "../platform_test_mac.mm" ]
     }
-    set_sources_assignment_filter(sources_assignment_filter)
   }
 
   if (is_ios && gtest_include_ios_coverage) {
-    sources += [
-      "../coverage_util_ios.h",
-      "../coverage_util_ios.mm",
-    ]
-    deps = [ ":ios_enable_coverage" ]
+    public_deps += [ ":ios_coverage_utils" ]
   }
 }
 
@@ -87,6 +79,16 @@
 }
 
 if (is_ios) {
+  # These headers are needed in some non test targets for iOS code coverage. So
+  # can not be testonly.
+  source_set("ios_coverage_utils") {
+    sources = [
+      "../coverage_util_ios.h",
+      "../coverage_util_ios.mm",
+    ]
+    deps = [ ":ios_enable_coverage" ]
+  }
+
   buildflag_header("ios_enable_coverage") {
     header = "ios_enable_coverage.h"
     flags = [ "IOS_ENABLE_COVERAGE=$use_clang_coverage" ]
diff --git a/testing/gtest/empty.cc b/testing/gtest/empty.cc
index 5186b59..7cfe79f 100644
--- a/testing/gtest/empty.cc
+++ b/testing/gtest/empty.cc
@@ -1,3 +1,3 @@
-// Copyright 2018 The PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
diff --git a/testing/gtest/include/gtest/gtest-death-test.h b/testing/gtest/include/gtest/gtest-death-test.h
new file mode 100644
index 0000000..6e79c82
--- /dev/null
+++ b/testing/gtest/include/gtest/gtest-death-test.h
@@ -0,0 +1,10 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The file/directory layout of Google Test is not yet considered stable. Until
+// it stabilizes, PDFium code will use forwarding headers in testing/gtest
+// and testing/gmock, instead of directly including files in
+// third_party/googletest.
+
+#include "third_party/googletest/src/googletest/include/gtest/gtest-death-test.h"
diff --git a/testing/gtest/include/gtest/gtest-message.h b/testing/gtest/include/gtest/gtest-message.h
new file mode 100644
index 0000000..7859730
--- /dev/null
+++ b/testing/gtest/include/gtest/gtest-message.h
@@ -0,0 +1,10 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The file/directory layout of Google Test is not yet considered stable. Until
+// it stabilizes, PDFium code will use forwarding headers in testing/gtest
+// and testing/gmock, instead of directly including files in
+// third_party/googletest.
+
+#include "third_party/googletest/src/googletest/include/gtest/gtest-message.h"
diff --git a/testing/gtest/include/gtest/gtest-param-test.h b/testing/gtest/include/gtest/gtest-param-test.h
new file mode 100644
index 0000000..3d681e4
--- /dev/null
+++ b/testing/gtest/include/gtest/gtest-param-test.h
@@ -0,0 +1,10 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The file/directory layout of Google Test is not yet considered stable. Until
+// it stabilizes, PDFium code will use forwarding headers in testing/gtest
+// and testing/gmock, instead of directly including files in
+// third_party/googletest.
+
+#include "third_party/googletest/src/googletest/include/gtest/gtest-param-test.h"
diff --git a/testing/gtest/include/gtest/gtest-spi.h b/testing/gtest/include/gtest/gtest-spi.h
new file mode 100644
index 0000000..24ba5ee
--- /dev/null
+++ b/testing/gtest/include/gtest/gtest-spi.h
@@ -0,0 +1,10 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The file/directory layout of Google Test is not yet considered stable. Until
+// it stabilizes, PDFium code will use forwarding headers in testing/gtest
+// and testing/gmock, instead of directly including files in
+// third_party/googletest.
+
+#include "third_party/googletest/src/googletest/include/gtest/gtest-spi.h"
diff --git a/testing/gtest/include/gtest/gtest.h b/testing/gtest/include/gtest/gtest.h
index 9425b25..4706280 100644
--- a/testing/gtest/include/gtest/gtest.h
+++ b/testing/gtest/include/gtest/gtest.h
@@ -1,9 +1,9 @@
-// Copyright 2018 The PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 // The file/directory layout of Google Test is not yet considered stable. Until
-// it stabilizes, Chromium code will use forwarding headers in testing/gtest
+// it stabilizes, PDFium code will use forwarding headers in testing/gtest
 // and testing/gmock, instead of directly including files in
 // third_party/googletest.
 
diff --git a/testing/gtest/include/gtest/gtest_prod.h b/testing/gtest/include/gtest/gtest_prod.h
index 2d67b42..703a176 100644
--- a/testing/gtest/include/gtest/gtest_prod.h
+++ b/testing/gtest/include/gtest/gtest_prod.h
@@ -1,9 +1,9 @@
-// Copyright 2018 The PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 // The file/directory layout of Google Test is not yet considered stable. Until
-// it stabilizes, Chromium code will use forwarding headers in testing/gtest
+// it stabilizes, PDFium code will use forwarding headers in testing/gtest
 // and testing/gmock, instead of directly including files in
 // third_party/googletest.
 
diff --git a/testing/gtest_mac.h b/testing/gtest_mac.h
index 0c0b655..95d7d32 100644
--- a/testing/gtest_mac.h
+++ b/testing/gtest_mac.h
@@ -1,6 +1,7 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright 2010 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+
 #ifndef TESTING_GTEST_MAC_H_
 #define TESTING_GTEST_MAC_H_
 #include <gtest/internal/gtest-port.h>
diff --git a/testing/gtest_mac.mm b/testing/gtest_mac.mm
index b490f55..47912c7 100644
--- a/testing/gtest_mac.mm
+++ b/testing/gtest_mac.mm
@@ -1,6 +1,7 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright 2010 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+
 #import "gtest_mac.h"
 #include <string>
 #include <gtest/gtest.h>
diff --git a/testing/image_diff/BUILD.gn b/testing/image_diff/BUILD.gn
index 141769b..0f9b71b 100644
--- a/testing/image_diff/BUILD.gn
+++ b/testing/image_diff/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2018 The PDFium Authors. All rights reserved.
+# Copyright 2018 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -10,7 +10,10 @@
     "image_diff_png.cpp",
     "image_diff_png.h",
   ]
-  configs += [ "../../:pdfium_core_config" ]
+  configs += [
+    "../../:pdfium_strict_config",
+    "../../:pdfium_noshorten_config",
+  ]
   deps = [
     "../../third_party:pdfium_base",
     "../../third_party:png",
diff --git a/testing/image_diff/DEPS b/testing/image_diff/DEPS
index 4bd2335..fcac201 100644
--- a/testing/image_diff/DEPS
+++ b/testing/image_diff/DEPS
@@ -1,5 +1,5 @@
 include_rules = [
-  '+third_party/libpng16',
+  '+third_party/libpng',
   '+third_party/zlib',
 ]
 
diff --git a/testing/image_diff/image_diff.cpp b/testing/image_diff/image_diff.cpp
index c8f9caf..a701921 100644
--- a/testing/image_diff/image_diff.cpp
+++ b/testing/image_diff/image_diff.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright 2011 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -8,12 +8,12 @@
 // The exact format of this tool's output to stdout is important, to match
 // what the run-webkit-tests script expects.
 
-#include <assert.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <string.h>
 
 #include <algorithm>
+#include <cmath>
 #include <map>
 #include <string>
 #include <vector>
@@ -21,10 +21,10 @@
 #include "core/fxcrt/fx_memory.h"
 #include "testing/image_diff/image_diff_png.h"
 #include "testing/utils/path_service.h"
-#include "third_party/base/logging.h"
+#include "third_party/base/cxx17_backports.h"
 #include "third_party/base/numerics/safe_conversions.h"
 
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
 #include <windows.h>
 #endif
 
@@ -39,8 +39,9 @@
 
 class Image {
  public:
-  Image() : w_(0), h_(0) {}
-  Image(const Image& image) : w_(image.w_), h_(image.h_), data_(image.data_) {}
+  Image() = default;
+  Image(const Image& image) = default;
+  Image& operator=(const Image& other) = default;
 
   bool has_image() const { return w_ > 0 && h_ > 0; }
   int w() const { return w_; }
@@ -110,8 +111,8 @@
   size_t pixel_address(int x, int y) const { return (y * w_ + x) * 4; }
 
   // Pixel dimensions of the image.
-  int w_;
-  int h_;
+  int w_ = 0;
+  int h_ = 0;
 
   std::vector<uint8_t> data_;
 };
@@ -143,7 +144,36 @@
   *pixels_different += (max_h - h) * max_w;
 }
 
-float PercentageDifferent(const Image& baseline, const Image& actual) {
+struct UnpackedPixel {
+  explicit UnpackedPixel(uint32_t packed)
+      : red(packed & 0xff),
+        green((packed >> 8) & 0xff),
+        blue((packed >> 16) & 0xff),
+        alpha((packed >> 24) & 0xff) {}
+
+  uint8_t red;
+  uint8_t green;
+  uint8_t blue;
+  uint8_t alpha;
+};
+
+uint8_t ChannelDelta(uint8_t baseline_channel, uint8_t actual_channel) {
+  // No casts are necessary because arithmetic operators implicitly convert
+  // `uint8_t` to `int` first. The final delta is always in the range 0 to 255.
+  return std::abs(baseline_channel - actual_channel);
+}
+
+uint8_t MaxPixelPerChannelDelta(const UnpackedPixel& baseline_pixel,
+                                const UnpackedPixel& actual_pixel) {
+  return std::max({ChannelDelta(baseline_pixel.red, actual_pixel.red),
+                   ChannelDelta(baseline_pixel.green, actual_pixel.green),
+                   ChannelDelta(baseline_pixel.blue, actual_pixel.blue),
+                   ChannelDelta(baseline_pixel.alpha, actual_pixel.alpha)});
+}
+
+float PercentageDifferent(const Image& baseline,
+                          const Image& actual,
+                          uint8_t max_pixel_per_channel_delta) {
   int w = std::min(baseline.w(), actual.w());
   int h = std::min(baseline.h(), actual.h());
 
@@ -151,8 +181,17 @@
   int pixels_different = 0;
   for (int y = 0; y < h; ++y) {
     for (int x = 0; x < w; ++x) {
-      if (baseline.pixel_at(x, y) != actual.pixel_at(x, y))
+      const uint32_t baseline_pixel = baseline.pixel_at(x, y);
+      const uint32_t actual_pixel = actual.pixel_at(x, y);
+      if (baseline_pixel == actual_pixel) {
+        continue;
+      }
+
+      if (MaxPixelPerChannelDelta(UnpackedPixel(baseline_pixel),
+                                  UnpackedPixel(actual_pixel)) >
+          max_pixel_per_channel_delta) {
         ++pixels_different;
+      }
     }
   }
 
@@ -198,23 +237,31 @@
   fprintf(
       stderr,
       "Usage:\n"
-      "  %s OPTIONS <compare file> <reference file>\n"
-      "    Compares two files on disk, returning 0 when they are the same;\n"
+      "  %s OPTIONS <compare_file> <reference_file>\n"
+      "    Compares two files on disk, returning 0 when they are the same.\n"
       "    Passing \"--histogram\" additionally calculates a diff of the\n"
-      "    RGBA value histograms. (which is resistant to shifts in layout)\n"
-      "    Passing \"--reverse-byte-order\" additionally assumes the compare\n"
-      "    file has BGRA byte ordering.\n"
-      "  %s --diff <compare file> <reference file> <output file>\n"
-      "    Compares two files on disk, outputs an image that visualizes the\n"
-      "    difference to <output file>\n",
-      binary_name.c_str(), binary_name.c_str());
+      "    RGBA value histograms (which is resistant to shifts in layout).\n"
+      "    Passing \"--reverse-byte-order\" additionally assumes the\n"
+      "    compare file has BGRA byte ordering.\n"
+      "    Passing \"--fuzzy\" additionally allows individual pixels to\n"
+      "    differ by at most 1 on each channel.\n\n"
+      "  %s --diff <compare_file> <reference_file> <output_file>\n"
+      "    Compares two files on disk, and if they differ, outputs an image\n"
+      "    to <output_file> that visualizes the differing pixels as red\n"
+      "    dots.\n\n"
+      "  %s --subtract <compare_file> <reference_file> <output_file>\n"
+      "    Compares two files on disk, and if they differ, outputs an image\n"
+      "    to <output_file> that visualizes the difference as a scaled\n"
+      "    subtraction of pixel values.\n",
+      binary_name.c_str(), binary_name.c_str(), binary_name.c_str());
 }
 
 int CompareImages(const std::string& binary_name,
                   const std::string& file1,
                   const std::string& file2,
                   bool compare_histograms,
-                  bool reverse_byte_order) {
+                  bool reverse_byte_order,
+                  uint8_t max_pixel_per_channel_delta) {
   Image actual_image;
   Image baseline_image;
 
@@ -240,7 +287,8 @@
   }
 
   const char* const diff_name = compare_histograms ? "exact diff" : "diff";
-  float percent = PercentageDifferent(actual_image, baseline_image);
+  float percent = PercentageDifferent(actual_image, baseline_image,
+                                      max_pixel_per_channel_delta);
   const char* const passed = percent > 0.0 ? "failed" : "passed";
   printf("%s: %01.2f%% %s\n", diff_name, percent, passed);
 
@@ -280,10 +328,48 @@
   return same;
 }
 
+bool SubtractImages(const Image& image1, const Image& image2, Image* out) {
+  int w = std::min(image1.w(), image2.w());
+  int h = std::min(image1.h(), image2.h());
+  *out = Image(image1);
+  bool same = (image1.w() == image2.w()) && (image1.h() == image2.h());
+
+  for (int y = 0; y < h; ++y) {
+    for (int x = 0; x < w; ++x) {
+      uint32_t pixel1 = image1.pixel_at(x, y);
+      int32_t r1 = pixel1 & 0xff;
+      int32_t g1 = (pixel1 >> 8) & 0xff;
+      int32_t b1 = (pixel1 >> 16) & 0xff;
+
+      uint32_t pixel2 = image2.pixel_at(x, y);
+      int32_t r2 = pixel2 & 0xff;
+      int32_t g2 = (pixel2 >> 8) & 0xff;
+      int32_t b2 = (pixel2 >> 16) & 0xff;
+
+      int32_t delta_r = r1 - r2;
+      int32_t delta_g = g1 - g2;
+      int32_t delta_b = b1 - b2;
+      same &= (delta_r == 0 && delta_g == 0 && delta_b == 0);
+
+      delta_r = pdfium::clamp(128 + delta_r * 8, 0, 255);
+      delta_g = pdfium::clamp(128 + delta_g * 8, 0, 255);
+      delta_b = pdfium::clamp(128 + delta_b * 8, 0, 255);
+
+      uint32_t new_pixel = RGBA_ALPHA;
+      new_pixel |= delta_r;
+      new_pixel |= (delta_g << 8);
+      new_pixel |= (delta_b << 16);
+      out->set_pixel_at(x, y, new_pixel);
+    }
+  }
+  return same;
+}
+
 int DiffImages(const std::string& binary_name,
                const std::string& file1,
                const std::string& file2,
-               const std::string& out_file) {
+               const std::string& out_file,
+               bool do_subtraction) {
   Image actual_image;
   Image baseline_image;
 
@@ -299,7 +385,9 @@
   }
 
   Image diff_image;
-  bool same = CreateImageDiff(baseline_image, actual_image, &diff_image);
+  bool same = do_subtraction
+                  ? SubtractImages(baseline_image, actual_image, &diff_image)
+                  : CreateImageDiff(baseline_image, actual_image, &diff_image);
   if (same)
     return kStatusSame;
 
@@ -321,11 +409,13 @@
 }
 
 int main(int argc, const char* argv[]) {
-  FXMEM_InitializePartitionAlloc();
+  FX_InitializeMemoryAllocators();
 
   bool histograms = false;
   bool produce_diff_image = false;
+  bool produce_image_subtraction = false;
   bool reverse_byte_order = false;
+  uint8_t max_pixel_per_channel_delta = 0;
   std::string filename1;
   std::string filename2;
   std::string diff_filename;
@@ -343,8 +433,12 @@
       histograms = true;
     } else if (strcmp(arg, "--diff") == 0) {
       produce_diff_image = true;
+    } else if (strcmp(arg, "--subtract") == 0) {
+      produce_image_subtraction = true;
     } else if (strcmp(arg, "--reverse-byte-order") == 0) {
       reverse_byte_order = true;
+    } else if (strcmp(arg, "--fuzzy") == 0) {
+      max_pixel_per_channel_delta = 1;
     }
   }
   if (i < argc)
@@ -354,13 +448,14 @@
   if (i < argc)
     diff_filename = argv[i++];
 
-  if (produce_diff_image) {
+  if (produce_diff_image || produce_image_subtraction) {
     if (!diff_filename.empty()) {
-      return DiffImages(binary_name, filename1, filename2, diff_filename);
+      return DiffImages(binary_name, filename1, filename2, diff_filename,
+                        produce_image_subtraction);
     }
   } else if (!filename2.empty()) {
     return CompareImages(binary_name, filename1, filename2, histograms,
-                         reverse_byte_order);
+                         reverse_byte_order, max_pixel_per_channel_delta);
   }
 
   PrintHelp(binary_name);
diff --git a/testing/image_diff/image_diff_png.cpp b/testing/image_diff/image_diff_png.cpp
index dcd9d69..c2e8b03 100644
--- a/testing/image_diff/image_diff_png.cpp
+++ b/testing/image_diff/image_diff_png.cpp
@@ -1,4 +1,4 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
+// Copyright 2013 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -16,8 +16,7 @@
 
 #include <string>
 
-#include "third_party/base/compiler_specific.h"
-#include "third_party/base/logging.h"
+#include "third_party/base/notreached.h"
 
 #ifdef USE_SYSTEM_ZLIB
 #include <zlib.h>
@@ -28,7 +27,7 @@
 #ifdef USE_SYSTEM_LIBPNG
 #include <png.h>
 #else
-#include "third_party/libpng16/png.h"
+#include "third_party/libpng/png.h"
 #endif
 
 namespace image_diff_png {
@@ -92,12 +91,12 @@
                       int pixel_width,
                       uint8_t* rgb,
                       bool* is_opaque) {
+  const uint8_t* pixel_in = rgba;
+  uint8_t* pixel_out = rgb;
   for (int x = 0; x < pixel_width; x++) {
-    const uint8_t* pixel_in = &rgba[x * 4];
-    uint8_t* pixel_out = &rgb[x * 3];
-    pixel_out[0] = pixel_in[0];
-    pixel_out[1] = pixel_in[1];
-    pixel_out[2] = pixel_in[2];
+    memcpy(pixel_out, pixel_in, 3);
+    pixel_in += 4;
+    pixel_out += 3;
   }
 }
 
@@ -148,13 +147,13 @@
                       int pixel_width,
                       uint8_t* rgba,
                       bool* is_opaque) {
+  const uint8_t* pixel_in = rgb;
+  uint8_t* pixel_out = rgba;
   for (int x = 0; x < pixel_width; x++) {
-    const uint8_t* pixel_in = &rgb[x * 3];
-    uint8_t* pixel_out = &rgba[x * 4];
-    pixel_out[0] = pixel_in[0];
-    pixel_out[1] = pixel_in[1];
-    pixel_out[2] = pixel_in[2];
+    memcpy(pixel_out, pixel_in, 3);
     pixel_out[3] = 0xff;
+    pixel_in += 3;
+    pixel_out += 4;
   }
 }
 
@@ -428,7 +427,7 @@
 #ifdef PNG_TEXT_SUPPORTED
 
 inline char* strdup(const char* str) {
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
   return _strdup(str);
 #else
   return ::strdup(str);
@@ -461,11 +460,11 @@
   void AddComment(size_t pos, const Comment& comment) {
     png_text_[pos].compression = PNG_TEXT_COMPRESSION_NONE;
     // A PNG comment's key can only be 79 characters long.
-    if (comment.key.length() > 79)
+    if (comment.key.size() > 79)
       return;
     png_text_[pos].key = strdup(comment.key.substr(0, 78).c_str());
     png_text_[pos].text = strdup(comment.text.c_str());
-    png_text_[pos].text_length = comment.text.length();
+    png_text_[pos].text_length = comment.text.size();
 #ifdef PNG_iTXt_SUPPORTED
     png_text_[pos].itxt_length = 0;
     png_text_[pos].lang = 0;
@@ -571,7 +570,7 @@
   switch (format) {
     case FORMAT_BGR:
       converter = ConvertBGRtoRGB;
-      FALLTHROUGH;
+      [[fallthrough]];
 
     case FORMAT_RGB:
       input_color_components = 3;
diff --git a/testing/image_diff/image_diff_png.h b/testing/image_diff/image_diff_png.h
index 2c39bf0..370ce32 100644
--- a/testing/image_diff/image_diff_png.h
+++ b/testing/image_diff/image_diff_png.h
@@ -1,4 +1,4 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
+// Copyright 2013 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/invalid_seekable_read_stream.cpp b/testing/invalid_seekable_read_stream.cpp
index a4116b0..778783c 100644
--- a/testing/invalid_seekable_read_stream.cpp
+++ b/testing/invalid_seekable_read_stream.cpp
@@ -1,4 +1,4 @@
-// Copyright 2019 PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,9 +9,8 @@
 
 InvalidSeekableReadStream::~InvalidSeekableReadStream() = default;
 
-bool InvalidSeekableReadStream::ReadBlockAtOffset(void* buffer,
-                                                  FX_FILESIZE offset,
-                                                  size_t size) {
+bool InvalidSeekableReadStream::ReadBlockAtOffset(pdfium::span<uint8_t> buffer,
+                                                  FX_FILESIZE offset) {
   return false;
 }
 
diff --git a/testing/invalid_seekable_read_stream.h b/testing/invalid_seekable_read_stream.h
index 9322bc6..ae24b1a 100644
--- a/testing/invalid_seekable_read_stream.h
+++ b/testing/invalid_seekable_read_stream.h
@@ -1,4 +1,4 @@
-// Copyright 2019 PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -6,17 +6,16 @@
 #define TESTING_INVALID_SEEKABLE_READ_STREAM_H_
 
 #include "core/fxcrt/fx_stream.h"
+#include "core/fxcrt/retain_ptr.h"
 
 // A stream used for testing where reads always fail.
 class InvalidSeekableReadStream final : public IFX_SeekableReadStream {
  public:
-  template <typename T, typename... Args>
-  friend RetainPtr<T> pdfium::MakeRetain(Args&&... args);
+  CONSTRUCT_VIA_MAKE_RETAIN;
 
   // IFX_SeekableReadStream overrides:
-  bool ReadBlockAtOffset(void* buffer,
-                         FX_FILESIZE offset,
-                         size_t size) override;
+  bool ReadBlockAtOffset(pdfium::span<uint8_t> buffer,
+                         FX_FILESIZE offset) override;
   FX_FILESIZE GetSize() override;
 
  private:
diff --git a/testing/js_embedder_test.cpp b/testing/js_embedder_test.cpp
index 57e6c4d..d6f800c 100644
--- a/testing/js_embedder_test.cpp
+++ b/testing/js_embedder_test.cpp
@@ -1,40 +1,15 @@
-// Copyright 2015 PDFium Authors. All rights reserved.
+// Copyright 2015 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "testing/js_embedder_test.h"
 
-#include "fxjs/cfxjs_engine.h"
-#include "third_party/base/ptr_util.h"
+#include "testing/v8_test_environment.h"
 
-JSEmbedderTest::JSEmbedderTest()
-    : m_pArrayBufferAllocator(
-          pdfium::MakeUnique<CFX_V8ArrayBufferAllocator>()) {}
+JSEmbedderTest::JSEmbedderTest() = default;
 
-JSEmbedderTest::~JSEmbedderTest() {}
+JSEmbedderTest::~JSEmbedderTest() = default;
 
-void JSEmbedderTest::SetUp() {
-  v8::Isolate::CreateParams params;
-  params.array_buffer_allocator = m_pArrayBufferAllocator.get();
-  m_pIsolate.reset(v8::Isolate::New(params));
-
-  EmbedderTest::SetExternalIsolate(isolate());
-  EmbedderTest::SetUp();
-
-  v8::Isolate::Scope isolate_scope(isolate());
-  v8::HandleScope handle_scope(isolate());
-  FXJS_PerIsolateData::SetUp(isolate());
-  m_Engine = pdfium::MakeUnique<CFXJS_Engine>(isolate());
-  m_Engine->InitializeEngine();
-}
-
-void JSEmbedderTest::TearDown() {
-  m_Engine->ReleaseEngine();
-  m_Engine.reset();
-  EmbedderTest::TearDown();
-  m_pIsolate.reset();
-}
-
-v8::Local<v8::Context> JSEmbedderTest::GetV8Context() {
-  return m_Engine->GetV8Context();
+v8::Isolate* JSEmbedderTest::isolate() const {
+  return V8TestEnvironment::GetInstance()->isolate();
 }
diff --git a/testing/js_embedder_test.h b/testing/js_embedder_test.h
index dca0bd7..dda285a 100644
--- a/testing/js_embedder_test.h
+++ b/testing/js_embedder_test.h
@@ -1,35 +1,22 @@
-// Copyright 2015 PDFium Authors. All rights reserved.
+// Copyright 2015 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #ifndef TESTING_JS_EMBEDDER_TEST_H_
 #define TESTING_JS_EMBEDDER_TEST_H_
 
-#include <memory>
-
-#include "fxjs/cfx_v8.h"
 #include "testing/embedder_test.h"
-#include "v8/include/v8.h"
 
-class CFXJS_Engine;
-class CFX_V8ArrayBufferAllocator;
+namespace v8 {
+class Isolate;
+}  // namespace v8
 
 class JSEmbedderTest : public EmbedderTest {
  public:
   JSEmbedderTest();
   ~JSEmbedderTest() override;
 
-  void SetUp() override;
-  void TearDown() override;
-
-  v8::Isolate* isolate() const { return m_pIsolate.get(); }
-  CFXJS_Engine* engine() const { return m_Engine.get(); }
-  v8::Local<v8::Context> GetV8Context();
-
- private:
-  std::unique_ptr<CFX_V8ArrayBufferAllocator> m_pArrayBufferAllocator;
-  std::unique_ptr<v8::Isolate, CFX_V8IsolateDeleter> m_pIsolate;
-  std::unique_ptr<CFXJS_Engine> m_Engine;
+  v8::Isolate* isolate() const;
 };
 
 #endif  // TESTING_JS_EMBEDDER_TEST_H_
diff --git a/testing/pdf_test_environment.cpp b/testing/pdf_test_environment.cpp
new file mode 100644
index 0000000..2865027
--- /dev/null
+++ b/testing/pdf_test_environment.cpp
@@ -0,0 +1,20 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/pdf_test_environment.h"
+
+#include "core/fxge/cfx_gemodule.h"
+
+PDFTestEnvironment::PDFTestEnvironment() = default;
+
+PDFTestEnvironment::~PDFTestEnvironment() = default;
+
+// testing::Environment:
+void PDFTestEnvironment::SetUp() {
+  CFX_GEModule::Create(test_fonts_.font_paths());
+}
+
+void PDFTestEnvironment::TearDown() {
+  CFX_GEModule::Destroy();
+}
diff --git a/testing/pdf_test_environment.h b/testing/pdf_test_environment.h
new file mode 100644
index 0000000..818d8e4
--- /dev/null
+++ b/testing/pdf_test_environment.h
@@ -0,0 +1,24 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_PDF_TEST_ENVIRONMENT_H_
+#define TESTING_PDF_TEST_ENVIRONMENT_H_
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/test_fonts.h"
+
+class PDFTestEnvironment : public testing::Environment {
+ public:
+  PDFTestEnvironment();
+  ~PDFTestEnvironment() override;
+
+  // testing::Environment:
+  void SetUp() override;
+  void TearDown() override;
+
+ private:
+  TestFonts test_fonts_;
+};
+
+#endif  // TESTING_PDF_TEST_ENVIRONMENT_H_
diff --git a/testing/pseudo_retainable.h b/testing/pseudo_retainable.h
index c4390d6..6dbfffc 100644
--- a/testing/pseudo_retainable.h
+++ b/testing/pseudo_retainable.h
@@ -1,4 +1,4 @@
-// Copyright 2018 PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -8,8 +8,8 @@
 class PseudoRetainable {
  public:
   PseudoRetainable() = default;
-  void Retain() { ++retain_count_; }
-  void Release() {
+  void Retain() const { ++retain_count_; }
+  void Release() const {
     if (++release_count_ == retain_count_)
       alive_ = false;
   }
@@ -18,9 +18,9 @@
   int release_count() const { return release_count_; }
 
  private:
-  bool alive_ = true;
-  int retain_count_ = 0;
-  int release_count_ = 0;
+  mutable bool alive_ = true;
+  mutable int retain_count_ = 0;
+  mutable int release_count_ = 0;
 };
 
 #endif  // TESTING_PSEUDO_RETAINABLE_H_
diff --git a/testing/range_set.cpp b/testing/range_set.cpp
index 2fc540f..725fd7d 100644
--- a/testing/range_set.cpp
+++ b/testing/range_set.cpp
@@ -1,4 +1,4 @@
-// Copyright 2017 PDFium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -7,9 +7,11 @@
 #include <algorithm>
 
 #include "core/fxcrt/fx_system.h"
+#include "third_party/base/check.h"
 
-RangeSet::RangeSet() {}
-RangeSet::~RangeSet() {}
+RangeSet::RangeSet() = default;
+
+RangeSet::~RangeSet() = default;
 
 bool RangeSet::Contains(const Range& range) const {
   if (IsEmptyRange(range))
@@ -51,15 +53,14 @@
 
   --end;
 
-  const int new_start = std::min<size_t>(start->first, fixed_range.first);
-  const int new_end = std::max(end->second, fixed_range.second);
-
+  const size_t new_start = std::min(start->first, fixed_range.first);
+  const size_t new_end = std::max(end->second, fixed_range.second);
   ranges_.erase(start, ++end);
   ranges_.insert(Range(new_start, new_end));
 }
 
 void RangeSet::Union(const RangeSet& range_set) {
-  ASSERT(&range_set != this);
+  DCHECK(&range_set != this);
   for (const auto& it : range_set.ranges())
     Union(it);
 }
diff --git a/testing/range_set.h b/testing/range_set.h
index 6ed24bd..7ba10df 100644
--- a/testing/range_set.h
+++ b/testing/range_set.h
@@ -1,4 +1,4 @@
-// Copyright 2017 PDFium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/resources/CMYK-alpha.jpf b/testing/resources/CMYK-alpha.jpf
new file mode 100644
index 0000000..538e6d6
--- /dev/null
+++ b/testing/resources/CMYK-alpha.jpf
Binary files differ
diff --git a/testing/resources/CMYK.jpf b/testing/resources/CMYK.jpf
new file mode 100644
index 0000000..00f2ef9
--- /dev/null
+++ b/testing/resources/CMYK.jpf
Binary files differ
diff --git a/testing/resources/RGB-alpha.jp2 b/testing/resources/RGB-alpha.jp2
new file mode 100644
index 0000000..4ab41da
--- /dev/null
+++ b/testing/resources/RGB-alpha.jp2
Binary files differ
diff --git a/testing/resources/RGB.jp2 b/testing/resources/RGB.jp2
new file mode 100644
index 0000000..7b7d428
--- /dev/null
+++ b/testing/resources/RGB.jp2
Binary files differ
diff --git a/testing/resources/annot_javascript.in b/testing/resources/annot_javascript.in
new file mode 100644
index 0000000..fe9ac46
--- /dev/null
+++ b/testing/resources/annot_javascript.in
@@ -0,0 +1,42 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [4 0 R]
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 612 792]
+  /Kids [3 0 R]
+  /Count 1
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [4 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /Rect [85 721 153 735]
+  /FT /Tx
+  /P 3 0 R
+  /T (Widget)
+  /AA <<
+    /F <<
+      /JS (AFDate_FormatEx\("yyyy-mm-dd"\);)
+      /S /JavaScript
+    >>
+  >>
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/annot_javascript.pdf b/testing/resources/annot_javascript.pdf
new file mode 100644
index 0000000..fbeb5df
--- /dev/null
+++ b/testing/resources/annot_javascript.pdf
@@ -0,0 +1,53 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [4 0 R]
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [0 0 612 792]
+  /Kids [3 0 R]
+  /Count 1
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [4 0 R]
+>>
+endobj
+4 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /Rect [85 721 153 735]
+  /FT /Tx
+  /P 3 0 R
+  /T (Widget)
+  /AA <<
+    /F <<
+      /JS (AFDate_FormatEx\("yyyy-mm-dd"\);)
+      /S /JavaScript
+    >>
+  >>
+>>
+endobj
+xref
+0 5
+0000000000 65535 f 
+0000000015 00000 n 
+0000000108 00000 n 
+0000000197 00000 n 
+0000000292 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 5
+>>
+startxref
+504
+%%EOF
diff --git a/testing/resources/annots.in b/testing/resources/annots.in
new file mode 100644
index 0000000..cf575f5
--- /dev/null
+++ b/testing/resources/annots.in
@@ -0,0 +1,362 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [23 0 R]
+    /DR <<
+      /Font <<
+        /F1 7 0 R
+      >>
+    >>
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 2
+  /Kids [3 0 R 4 0 R]
+  /MediaBox [0 0 612 792]
+  /CropBox [0 0 612 792]
+  /Resources <<
+    /Font <<
+      /F1 7 0 R
+      /F2 8 0 R
+    >>
+    /ProcSet [/PDF /Text /ImageC]
+    /ExtGState <<
+      /GS0 24 0 R
+    >>
+  >>
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 5 0 R
+  /Annots [15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R 23 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 6 0 R
+  /Annots [15 0 R 16 0 R 26 0 R]
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+70 700 Td
+/F1 18 Tf
+(Link Annotations - Page 1) Tj
+0 -65 Td
+/F2 14 Tf
+(1. Link with destination to first page) Tj
+10 -20 Td
+/F2 14 Tf
+(2. Link with destination to second page) Tj
+-12 -84 Td
+/F2 10 Tf
+(PDF Reference, Version 1.7, Section 8.4.5 defines Annotations) Tj
+2 -53 Td
+(3.  An example of Highlight with text notes) Tj
+0 -18 Td
+(https://pdfium.googlesource.com/pdfium is link in plain text, not link annotation. These are referred to) Tj
+0 -17 Td
+(as WebLinks in PDFium.)Tj
+ET
+endstream
+endobj
+{{object 6 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+70 700 Td
+/F1 18 Tf
+(Link Annotations - Page 2) Tj
+0 -65 Td
+/F2 14 Tf
+(1. Link with destination to first page) Tj
+10 -20 Td
+/F2 14 Tf
+(2. Link with destination to second page) Tj
+ET
+endstream
+endobj
+{{object 7 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+{{object 8 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{object 9 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  {{streamlen}}
+  /BBox [293 530 349 542]
+  /Resources <<
+    /XObject <<
+      /Form0 10 0 R
+    >>
+    /ExtGState <<
+      /GS0 25 0 R
+    >>
+  >>
+>>
+stream
+/GS0 gs
+/Form0 Do
+endstream
+endobj
+{{object 10 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /Group <<
+    /S /Transparency
+  >>
+  {{streamlen}}
+  /BBox [293 530 349 542]
+>>
+stream
+1.0 1.0 0.0 rg
+293 530 m
+349 530 l
+349 542 l
+293 542 l
+h f
+endstream
+endobj
+{{object 11 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  {{streamlen}}
+  /BBox [83 440 178 453]
+  /Resources <<
+    /XObject <<
+      /Form0 12 0 R
+    >>
+    /ExtGState <<
+      /GS0 25 0 R
+    >>
+  >>
+>>
+stream
+/GS0 gs
+/Form0 Do
+endstream
+endobj
+{{object 12 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /Group <<
+    /S /Transparency
+  >>
+  {{streamlen}}
+  /BBox [83 440 178 453]
+>>
+stream
+0.0 1.0 1.0 rg
+83 440 m
+178 440 l
+178 453 l
+83 453 l
+h f
+endstream
+endobj
+{{object 13 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  {{streamlen}}
+  /BBox [149 476 191 487]
+  /Resources <<
+    /XObject <<
+      /Form0 14 0 R
+    >>
+    /ExtGState <<
+      /GS0 25 0 R
+    >>
+  >>
+>>
+stream
+/GS0 gs
+/Form0 Do
+endstream
+endobj
+{{object 14 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /Group <<
+    /S /Transparency
+  >>
+  {{streamlen}}
+  /BBox [149 476 191 487]
+>>
+stream
+0.0 1.0 0.0 rg
+149 476 m
+191 476 l
+191 487 l
+149 487 l
+h f
+endstream
+endobj
+{{object 15 0}} <<
+  /Type /Annot
+  /Subtype /Link
+  /BS <<
+    /W 0
+  >>
+  /Rect [69 633 542 653]
+  /Dest [3 0 R /XYZ 200 725 0]
+  /F 4
+>>
+endobj
+{{object 16 0}} <<
+  /Type /Annot
+  /Subtype /Link
+  /BS <<
+    /W 0
+  >>
+  /Rect [80 613 542 633]
+  /Dest [4 0 R /XYZ 200 725 0]
+  /F 4
+>>
+endobj
+{{object 17 0}} <<
+  /Type /Annot
+  /Subtype /Link
+  /BS <<
+    /W 0
+  >>
+  /Rect [66 529 196 544]
+  /A <<
+    /Type /Action
+    /URI (https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf)
+    /S /URI
+  >>
+  /F 4
+>>
+endobj
+{{object 18 0}} <<
+  /Type /Annot
+  /Subtype /Link
+  /BS <<
+    /W 0
+  >>
+  /Rect [83 440 178 453]
+  /QuadPoints [83 453 178 453 83 440 178 440]
+  /A <<
+    /Type /Action
+    /URI (https://cs.chromium.org/chromium/src/third_party/pdfium/public/fpdf_text.h)
+    /S /URI
+  >>
+  /F 4
+>>
+endobj
+{{object 19 0}} <<
+  /Type /Annot
+  /Subtype /Highlight
+  /AP <<
+    /N 9 0 R
+  >>
+  /NM (Highlight-1)
+  /F 4
+  /QuadPoints [293 542 349 542 293 530 349 530]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+{{object 20 0}} <<
+  /Type /Annot
+  /Subtype /Highlight
+  /AP <<
+    /N 11 0 R
+  >>
+  /NM (Highlight-2)
+  /F 4
+  /QuadPoints [83 453 178 453 83 440 178 440]
+  /P 3 0 R
+  /C [0.26667 0.78431 0.96078]
+  /Rect [83 440 178 453]
+>>
+endobj
+{{object 21 0}} <<
+  /Type /Annot
+  /Subtype /Popup
+  /Parent 22 0 R
+  /Rect [191 377 443 488]
+>>
+endobj
+{{object 22 0}} <<
+  /Type /Annot
+  /Subtype /Highlight
+  /Popup 21 0 R
+  /AP <<
+    /N 13 0 R
+  >>
+  /NM (Highlight-With-Popup-1)
+  /Contents (Text Note)
+  /QuadPoints [149 487 191 487 149 476 191 476]
+  /P 3 0 R
+  /C [0.14902 0.90196 0]
+  /Rect [149 476 191 487]
+  /F 4
+>>
+endobj
+{{object 23 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /Ff 131072
+  /T (Combo1)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [70 350 170 380]
+  /Opt [(Highlight) (Link) (Popup) (Widget)]
+>>
+endobj
+{{object 24 0}} <<
+  /ca 1
+  /Type /ExtGState
+  /CA 1
+  /BM /Normal
+>>
+endobj
+{{object 25 0}} <<
+  /ca 1
+  /Type /ExtGState
+  /CA 1
+  /AIS false
+  /BM /Multiply
+>>
+endobj
+{{object 26 0}} <<
+  /Type /Annot
+  /Subtype /Square
+  /Border [0 0 2]
+  /C [1 0 0]
+  /F 4
+  /P 3 0 R
+  /Rect [50 100 60 120]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/annots.pdf b/testing/resources/annots.pdf
new file mode 100644
index 0000000..745346c
--- /dev/null
+++ b/testing/resources/annots.pdf
@@ -0,0 +1,395 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [23 0 R]
+    /DR <<
+      /Font <<
+        /F1 7 0 R
+      >>
+    >>
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 2
+  /Kids [3 0 R 4 0 R]
+  /MediaBox [0 0 612 792]
+  /CropBox [0 0 612 792]
+  /Resources <<
+    /Font <<
+      /F1 7 0 R
+      /F2 8 0 R
+    >>
+    /ProcSet [/PDF /Text /ImageC]
+    /ExtGState <<
+      /GS0 24 0 R
+    >>
+  >>
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 5 0 R
+  /Annots [15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R 23 0 R]
+>>
+endobj
+4 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 6 0 R
+  /Annots [15 0 R 16 0 R 26 0 R]
+>>
+endobj
+5 0 obj <<
+  /Length 486
+>>
+stream
+BT
+70 700 Td
+/F1 18 Tf
+(Link Annotations - Page 1) Tj
+0 -65 Td
+/F2 14 Tf
+(1. Link with destination to first page) Tj
+10 -20 Td
+/F2 14 Tf
+(2. Link with destination to second page) Tj
+-12 -84 Td
+/F2 10 Tf
+(PDF Reference, Version 1.7, Section 8.4.5 defines Annotations) Tj
+2 -53 Td
+(3.  An example of Highlight with text notes) Tj
+0 -18 Td
+(https://pdfium.googlesource.com/pdfium is link in plain text, not link annotation. These are referred to) Tj
+0 -17 Td
+(as WebLinks in PDFium.)Tj
+ET
+endstream
+endobj
+6 0 obj <<
+  /Length 185
+>>
+stream
+BT
+70 700 Td
+/F1 18 Tf
+(Link Annotations - Page 2) Tj
+0 -65 Td
+/F2 14 Tf
+(1. Link with destination to first page) Tj
+10 -20 Td
+/F2 14 Tf
+(2. Link with destination to second page) Tj
+ET
+endstream
+endobj
+7 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+8 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+9 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /Length 18
+  /BBox [293 530 349 542]
+  /Resources <<
+    /XObject <<
+      /Form0 10 0 R
+    >>
+    /ExtGState <<
+      /GS0 25 0 R
+    >>
+  >>
+>>
+stream
+/GS0 gs
+/Form0 Do
+endstream
+endobj
+10 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /Group <<
+    /S /Transparency
+  >>
+  /Length 59
+  /BBox [293 530 349 542]
+>>
+stream
+1.0 1.0 0.0 rg
+293 530 m
+349 530 l
+349 542 l
+293 542 l
+h f
+endstream
+endobj
+11 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /Length 18
+  /BBox [83 440 178 453]
+  /Resources <<
+    /XObject <<
+      /Form0 12 0 R
+    >>
+    /ExtGState <<
+      /GS0 25 0 R
+    >>
+  >>
+>>
+stream
+/GS0 gs
+/Form0 Do
+endstream
+endobj
+12 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /Group <<
+    /S /Transparency
+  >>
+  /Length 57
+  /BBox [83 440 178 453]
+>>
+stream
+0.0 1.0 1.0 rg
+83 440 m
+178 440 l
+178 453 l
+83 453 l
+h f
+endstream
+endobj
+13 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /Length 18
+  /BBox [149 476 191 487]
+  /Resources <<
+    /XObject <<
+      /Form0 14 0 R
+    >>
+    /ExtGState <<
+      /GS0 25 0 R
+    >>
+  >>
+>>
+stream
+/GS0 gs
+/Form0 Do
+endstream
+endobj
+14 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /Group <<
+    /S /Transparency
+  >>
+  /Length 59
+  /BBox [149 476 191 487]
+>>
+stream
+0.0 1.0 0.0 rg
+149 476 m
+191 476 l
+191 487 l
+149 487 l
+h f
+endstream
+endobj
+15 0 obj <<
+  /Type /Annot
+  /Subtype /Link
+  /BS <<
+    /W 0
+  >>
+  /Rect [69 633 542 653]
+  /Dest [3 0 R /XYZ 200 725 0]
+  /F 4
+>>
+endobj
+16 0 obj <<
+  /Type /Annot
+  /Subtype /Link
+  /BS <<
+    /W 0
+  >>
+  /Rect [80 613 542 633]
+  /Dest [4 0 R /XYZ 200 725 0]
+  /F 4
+>>
+endobj
+17 0 obj <<
+  /Type /Annot
+  /Subtype /Link
+  /BS <<
+    /W 0
+  >>
+  /Rect [66 529 196 544]
+  /A <<
+    /Type /Action
+    /URI (https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf)
+    /S /URI
+  >>
+  /F 4
+>>
+endobj
+18 0 obj <<
+  /Type /Annot
+  /Subtype /Link
+  /BS <<
+    /W 0
+  >>
+  /Rect [83 440 178 453]
+  /QuadPoints [83 453 178 453 83 440 178 440]
+  /A <<
+    /Type /Action
+    /URI (https://cs.chromium.org/chromium/src/third_party/pdfium/public/fpdf_text.h)
+    /S /URI
+  >>
+  /F 4
+>>
+endobj
+19 0 obj <<
+  /Type /Annot
+  /Subtype /Highlight
+  /AP <<
+    /N 9 0 R
+  >>
+  /NM (Highlight-1)
+  /F 4
+  /QuadPoints [293 542 349 542 293 530 349 530]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+20 0 obj <<
+  /Type /Annot
+  /Subtype /Highlight
+  /AP <<
+    /N 11 0 R
+  >>
+  /NM (Highlight-2)
+  /F 4
+  /QuadPoints [83 453 178 453 83 440 178 440]
+  /P 3 0 R
+  /C [0.26667 0.78431 0.96078]
+  /Rect [83 440 178 453]
+>>
+endobj
+21 0 obj <<
+  /Type /Annot
+  /Subtype /Popup
+  /Parent 22 0 R
+  /Rect [191 377 443 488]
+>>
+endobj
+22 0 obj <<
+  /Type /Annot
+  /Subtype /Highlight
+  /Popup 21 0 R
+  /AP <<
+    /N 13 0 R
+  >>
+  /NM (Highlight-With-Popup-1)
+  /Contents (Text Note)
+  /QuadPoints [149 487 191 487 149 476 191 476]
+  /P 3 0 R
+  /C [0.14902 0.90196 0]
+  /Rect [149 476 191 487]
+  /F 4
+>>
+endobj
+23 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /Ff 131072
+  /T (Combo1)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [70 350 170 380]
+  /Opt [(Highlight) (Link) (Popup) (Widget)]
+>>
+endobj
+24 0 obj <<
+  /ca 1
+  /Type /ExtGState
+  /CA 1
+  /BM /Normal
+>>
+endobj
+25 0 obj <<
+  /ca 1
+  /Type /ExtGState
+  /CA 1
+  /AIS false
+  /BM /Multiply
+>>
+endobj
+26 0 obj <<
+  /Type /Annot
+  /Subtype /Square
+  /Border [0 0 2]
+  /C [1 0 0]
+  /F 4
+  /P 3 0 R
+  /Rect [50 100 60 120]
+>>
+endobj
+xref
+0 27
+0000000000 65535 f 
+0000000015 00000 n 
+0000000169 00000 n 
+0000000439 00000 n 
+0000000583 00000 n 
+0000000685 00000 n 
+0000001223 00000 n 
+0000001460 00000 n 
+0000001538 00000 n 
+0000001614 00000 n 
+0000001864 00000 n 
+0000002087 00000 n 
+0000002337 00000 n 
+0000002557 00000 n 
+0000002808 00000 n 
+0000003031 00000 n 
+0000003171 00000 n 
+0000003311 00000 n 
+0000003558 00000 n 
+0000003842 00000 n 
+0000004059 00000 n 
+0000004286 00000 n 
+0000004384 00000 n 
+0000004659 00000 n 
+0000004849 00000 n 
+0000004920 00000 n 
+0000005006 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 27
+>>
+startxref
+5135
+%%EOF
diff --git a/testing/resources/annots_action_handling.in b/testing/resources/annots_action_handling.in
new file mode 100644
index 0000000..e5c728b
--- /dev/null
+++ b/testing/resources/annots_action_handling.in
@@ -0,0 +1,132 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /Names 12 0 R
+  /AcroForm [6 0 R 7 0 R]
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 2
+  /Kids [3 0 R 4 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R]
+  /Contents 5 0 R
+  /Tabs /R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Tabs /R
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+70 340 Td
+14 Tf
+(External Link ) Tj
+0 -35 Td
+14 Tf
+(Internal Link ) Tj
+0 -35 Td
+14 Tf
+(Link1 to top ) Tj
+0 -35 Td
+14 Tf
+(Link2 to top ) Tj
+ET
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /Parent 3 0 R
+  /T (TextField)
+  /Rect [69 670 220 690]
+>>
+endobj
+{{object 7 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /Rect [69 360 220 380]
+  /A <<
+    /URI (https://www.google.com)
+    /S /URI
+  >>
+  /F 4
+  /T (button)
+  /Ff 65536
+>>
+endobj
+{{object 8 0}} <<
+  /Type /Annot
+  /Subtype /Link
+  /Rect [69 338 180 358]
+  /A <<
+    /Type /Action
+    /S /URI
+    /URI (https://cs.chromium.org/)
+  >>
+  /F 4
+>>
+endobj
+{{object 9 0}} <<
+  /Type /Annot
+  /Subtype /Link
+  /Rect [69 305 180 325]
+  /BS <<
+    /W 0
+  >>
+  /Dest [4 0 R /XYZ 200 725 0]
+  /F 4
+>>
+{{object 10 0}} <<
+  /Type /Annot
+  /Subtype /Link
+  /Rect [69 270 180 290]
+  /BS <<
+    /W 0
+  >>
+  /Dest /top
+  /F 4
+>>
+{{object 11 0}} <<
+  /Type /Annot
+  /Subtype /Link
+  /Rect [69 235 180 255]
+  /BS <<
+    /W 0
+  >>
+  /Dest (target10)
+  /F 4
+>>
+{{object 12 0}} <<
+  /Dests 13 0 R
+>>
+endobj
+{{object 13 0}} <<
+  /Names [
+    (target10) 14 0 R
+    /top 14 0 R
+  ]
+>>
+endobj
+{{object 14 0}} <<
+  /D [3 0 R /XYZ 100 200 0]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
\ No newline at end of file
diff --git a/testing/resources/annots_action_handling.pdf b/testing/resources/annots_action_handling.pdf
new file mode 100644
index 0000000..b8b6c2a
--- /dev/null
+++ b/testing/resources/annots_action_handling.pdf
@@ -0,0 +1,153 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /Names 12 0 R
+  /AcroForm [6 0 R 7 0 R]
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 2
+  /Kids [3 0 R 4 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R]
+  /Contents 5 0 R
+  /Tabs /R
+>>
+endobj
+4 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Tabs /R
+>>
+endobj
+5 0 obj <<
+  /Length 145
+>>
+stream
+BT
+70 340 Td
+14 Tf
+(External Link ) Tj
+0 -35 Td
+14 Tf
+(Internal Link ) Tj
+0 -35 Td
+14 Tf
+(Link1 to top ) Tj
+0 -35 Td
+14 Tf
+(Link2 to top ) Tj
+ET
+endstream
+endobj
+6 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /Parent 3 0 R
+  /T (TextField)
+  /Rect [69 670 220 690]
+>>
+endobj
+7 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /Rect [69 360 220 380]
+  /A <<
+    /URI (https://www.google.com)
+    /S /URI
+  >>
+  /F 4
+  /T (button)
+  /Ff 65536
+>>
+endobj
+8 0 obj <<
+  /Type /Annot
+  /Subtype /Link
+  /Rect [69 338 180 358]
+  /A <<
+    /Type /Action
+    /S /URI
+    /URI (https://cs.chromium.org/)
+  >>
+  /F 4
+>>
+endobj
+9 0 obj <<
+  /Type /Annot
+  /Subtype /Link
+  /Rect [69 305 180 325]
+  /BS <<
+    /W 0
+  >>
+  /Dest [4 0 R /XYZ 200 725 0]
+  /F 4
+>>
+10 0 obj <<
+  /Type /Annot
+  /Subtype /Link
+  /Rect [69 270 180 290]
+  /BS <<
+    /W 0
+  >>
+  /Dest /top
+  /F 4
+>>
+11 0 obj <<
+  /Type /Annot
+  /Subtype /Link
+  /Rect [69 235 180 255]
+  /BS <<
+    /W 0
+  >>
+  /Dest (target10)
+  /F 4
+>>
+12 0 obj <<
+  /Dests 13 0 R
+>>
+endobj
+13 0 obj <<
+  /Names [
+    (target10) 14 0 R
+    /top 14 0 R
+  ]
+>>
+endobj
+14 0 obj <<
+  /D [3 0 R /XYZ 100 200 0]
+>>
+endobj
+xref
+0 15
+0000000000 65535 f 
+0000000015 00000 n 
+0000000110 00000 n 
+0000000179 00000 n 
+0000000309 00000 n 
+0000000371 00000 n 
+0000000568 00000 n 
+0000000691 00000 n 
+0000000874 00000 n 
+0000001038 00000 n 
+0000001170 00000 n 
+0000001285 00000 n 
+0000001406 00000 n 
+0000001444 00000 n 
+0000001519 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 15
+>>
+startxref
+1569
+%%EOF
\ No newline at end of file
diff --git a/testing/resources/bad_dict_keys.in b/testing/resources/bad_dict_keys.in
new file mode 100644
index 0000000..3f53c28
--- /dev/null
+++ b/testing/resources/bad_dict_keys.in
@@ -0,0 +1,23 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 300 300]
+  /Count 1
+  /Kids [3 0 R]
+  / [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bad_dict_keys.pdf b/testing/resources/bad_dict_keys.pdf
new file mode 100644
index 0000000..b55c434
--- /dev/null
+++ b/testing/resources/bad_dict_keys.pdf
@@ -0,0 +1,33 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [0 0 300 300]
+  /Count 1
+  /Kids [3 0 R]
+  / [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+>>
+endobj
+xref
+0 4
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000169 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 4
+>>
+startxref
+220
+%%EOF
diff --git a/testing/resources/bigtable_mini.in b/testing/resources/bigtable_mini.in
new file mode 100644
index 0000000..7e80992
--- /dev/null
+++ b/testing/resources/bigtable_mini.in
@@ -0,0 +1,113 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Resources <<
+    /ProcSet [/PDF /ImageB /Text]
+    /Font <<
+      /F1 5 0 R
+      /F2 6 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q BT
+1 0 0 1 250 667 Tm
+/F2 8.96638 Tf
+-243.635 -15.84 Td
+(f)Tj
+/F1 8.96638 Tf
+4.55491 0 Td
+(f)Tj
+2.87476 0 Td
+(ay)Tj
+7.79253 0 Td
+(,jef)Tj
+11.506 0 Td
+(f,sanjay)Tj
+27.4558 0 Td
+(,wilsonh,k)Tj
+37.1801 0 Td
+(err)Tj
+9.58372 0 Td
+(,m3b,tushar)Tj
+41.8537 0 Td
+(,k)Tj
+11.6349 0 Td
+(es,gruber)Tj
+/F2 8.96638 Tf
+32.9694 0 Td
+(g)Tj
+/F1 8.96638 Tf
+4.55491 0 Td
+(@google.com)Tj
+ET Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /FirstChar 102
+  /BaseFont /RFSQHQ+CMSY9
+  /FontDescriptor 7 0 R
+  /LastChar 103
+  /Widths [508 508]
+>>
+endobj
+{{object 7 0}} <<
+  /Type /FontDescriptor
+  /Ascent 750
+  /CapHeight 750
+  /CharSet (/braceleft/braceright)
+  /Descent -250
+  /Flags 4
+  /FontBBox [0 -250 440 750]
+  /FontFile3 8 0 R
+  /FontName /RFSQHQ+CMSY9
+  /ItalicAngle 0
+  /StemV 65
+>>
+endobj
+{{object 8 0}} <<
+  /Subtype /Type1C
+  /Filter [/ASCII85Decode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+GhQY<?t!MPA7Xa1EXNBL#h4%k%?@du#q5_5B)EgioL"p)pKs!j9@_A!X@n"A#^s/nr,0aY?!i,5R?>nA
+43BRuTX#t%4ekD2_c]pSd*g?I_(dmV-o3k<:Veup,U50*Ylr.i;$bHCc:fghe5L>1a\`<FkpTR<8hEdi
+.SEJ:>O%`N>>SLd>,:)GT9<BBa2#L+Pa9V1&Ao("^^P</!u&Ql7PhdY9T5#6IePGiOgOaNdLsRg)OHi+
+n+a.f[/4_7lnp:OXP)%6XER"WK`:C2*&F%pl[L]/LH0S#rJk:S[coEjaA0p=l`BI4BP[$LCn09862jKs
+Y)F7W^!5B(h.[kDUY*1W\e@l8mE%N?^8RN2qNUCce!bWPjLtF8=4Zg,R,1!:HR6^V/b\TIh0Z`11`hZZ
+*BO'Za1C[ph)dSm>aE#>k-A'_h4)!bqJD$jjj@/_bu*BN?.Ud@HV0Y&l1Vg$1F)e^]:6ER3IV(Tbj1;S
+BposQ0Df/MDiTcoYO4BSY\&*%<aJs1;J/:.>?>$UG7-pseF)R.T'a;@,PN5BX]sWQf<*o6H:RGZ*f^Pj
+qkd,*mVA#1lAJeH\%d!)GAm3[%_gAF5OB8mr>(pE^5_Fj[i?3Bq0X7/D)6E](`9a_gaUoK~>
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bigtable_mini.pdf b/testing/resources/bigtable_mini.pdf
new file mode 100644
index 0000000..adb3e02
--- /dev/null
+++ b/testing/resources/bigtable_mini.pdf
@@ -0,0 +1,128 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Resources <<
+    /ProcSet [/PDF /ImageB /Text]
+    /Font <<
+      /F1 5 0 R
+      /F2 6 0 R
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Length 374
+>>
+stream
+q BT
+1 0 0 1 250 667 Tm
+/F2 8.96638 Tf
+-243.635 -15.84 Td
+(f)Tj
+/F1 8.96638 Tf
+4.55491 0 Td
+(f)Tj
+2.87476 0 Td
+(ay)Tj
+7.79253 0 Td
+(,jef)Tj
+11.506 0 Td
+(f,sanjay)Tj
+27.4558 0 Td
+(,wilsonh,k)Tj
+37.1801 0 Td
+(err)Tj
+9.58372 0 Td
+(,m3b,tushar)Tj
+41.8537 0 Td
+(,k)Tj
+11.6349 0 Td
+(es,gruber)Tj
+/F2 8.96638 Tf
+32.9694 0 Td
+(g)Tj
+/F1 8.96638 Tf
+4.55491 0 Td
+(@google.com)Tj
+ET Q
+endstream
+endobj
+5 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+6 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /FirstChar 102
+  /BaseFont /RFSQHQ+CMSY9
+  /FontDescriptor 7 0 R
+  /LastChar 103
+  /Widths [508 508]
+>>
+endobj
+7 0 obj <<
+  /Type /FontDescriptor
+  /Ascent 750
+  /CapHeight 750
+  /CharSet (/braceleft/braceright)
+  /Descent -250
+  /Flags 4
+  /FontBBox [0 -250 440 750]
+  /FontFile3 8 0 R
+  /FontName /RFSQHQ+CMSY9
+  /ItalicAngle 0
+  /StemV 65
+>>
+endobj
+8 0 obj <<
+  /Subtype /Type1C
+  /Filter [/ASCII85Decode /FlateDecode]
+  /Length 640
+>>
+stream
+GhQY<?t!MPA7Xa1EXNBL#h4%k%?@du#q5_5B)EgioL"p)pKs!j9@_A!X@n"A#^s/nr,0aY?!i,5R?>nA
+43BRuTX#t%4ekD2_c]pSd*g?I_(dmV-o3k<:Veup,U50*Ylr.i;$bHCc:fghe5L>1a\`<FkpTR<8hEdi
+.SEJ:>O%`N>>SLd>,:)GT9<BBa2#L+Pa9V1&Ao("^^P</!u&Ql7PhdY9T5#6IePGiOgOaNdLsRg)OHi+
+n+a.f[/4_7lnp:OXP)%6XER"WK`:C2*&F%pl[L]/LH0S#rJk:S[coEjaA0p=l`BI4BP[$LCn09862jKs
+Y)F7W^!5B(h.[kDUY*1W\e@l8mE%N?^8RN2qNUCce!bWPjLtF8=4Zg,R,1!:HR6^V/b\TIh0Z`11`hZZ
+*BO'Za1C[ph)dSm>aE#>k-A'_h4)!bqJD$jjj@/_bu*BN?.Ud@HV0Y&l1Vg$1F)e^]:6ER3IV(Tbj1;S
+BposQ0Df/MDiTcoYO4BSY\&*%<aJs1;J/:.>?>$UG7-pseF)R.T'a;@,PN5BX]sWQf<*o6H:RGZ*f^Pj
+qkd,*mVA#1lAJeH\%d!)GAm3[%_gAF5OB8mr>(pE^5_Fj[i?3Bq0X7/D)6E](`9a_gaUoK~>
+endstream
+endobj
+xref
+0 9
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000333 00000 n 
+0000000759 00000 n 
+0000000837 00000 n 
+0000000993 00000 n 
+0000001234 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 9
+>>
+startxref
+1985
+%%EOF
diff --git a/testing/resources/bookmarks.in b/testing/resources/bookmarks.in
index 793f6ae..b0489a5 100644
--- a/testing/resources/bookmarks.in
+++ b/testing/resources/bookmarks.in
@@ -2,7 +2,7 @@
 {{object 1 0}} <<
   /Type /Catalog
   /Pages 2 0 R
-  /Outlines 14 0 R
+  /Outlines 8 0 R
 >>
 endobj
 {{object 2 0}} <<
@@ -19,9 +19,11 @@
   /Type /Page
   /Parent 2 0 R
   /Resources <<
-    /Font <</F1 15 0 R>>
+    /Font <<
+      /F1 5 0 R
+    >>
   >>
-  /Contents [21 0 R]
+  /Contents [6 0 R]
   /MediaBox [0 0 612 792]
 >>
 endobj
@@ -30,46 +32,24 @@
   /Type /Page
   /Parent 2 0 R
   /Resources  <<
-    /Font <</F1 15 0 R>>
+    /Font <<
+      /F1 5 0 R
+    >>
   >>
-  /Contents [22 0 R]
+  /Contents [7 0 R]
   /MediaBox [0 0 612 792]
 >>
 endobj
-% First bookmark
-{{object 10 0}} <<
-  /Title (A Good Beginning)
-  /Parent 14 0 R
-  /Next 11 0 R
-  /Dest (foo)
->>
-endobj
-% Last bookmark
-{{object 11 0}} <<
-  /Title (A Good Ending)
-  /Parent 14 0 R
-  /Prev 10 0 R
-  /Dest (bar)
->>
-endobj
-% Root bookmark
-{{object 14 0}} <<
-  /Type /Outlines
-  /First 10 0 R
-  /Last  11 0 R
-  /Count 2
->>
-endobj
 % Font resource.
-{{object 15 0}} <<
+{{object 5 0}} <<
   /Type /Font
   /Subtype /Type1
   /BaseFont /Arial
 >>
 endobj
 % Content for page 0.
-{{object 21 0}} <<
-  /Length 0
+{{object 6 0}} <<
+  {{streamlen}}
 >>
 stream
 BT
@@ -79,8 +59,8 @@
 endstream
 endobj
 % Content for page 1.
-{{object 22 0}} <<
-  /Length 0
+{{object 7 0}} <<
+  {{streamlen}}
 >>
 stream
 BT
@@ -89,6 +69,72 @@
 ET
 endstream
 endobj
+% Root bookmark
+{{object 8 0}} <<
+  /Type /Outlines
+  /Count 3
+  /First 9 0 R
+  /Last 12 0 R
+>>
+endobj
+% First child bookmark (leaf node)
+{{object 9 0}} <<
+  /Title (A Good Beginning)
+  /Parent 8 0 R
+  /Next 10 0 R
+  /Dest (foo)
+>>
+endobj
+% Second child bookmark (open)
+{{object 10 0}} <<
+  /Title (Open Middle)
+  /Parent 8 0 R
+  /First 11 0 R
+  /Last 11 0 R
+  /Prev 9 0 R
+  /Next 12 0 R
+  /Count 1
+  /A <<
+    /Type /Action
+    /S /URI
+    /URI (https://theplay.test)
+  >>
+>>
+endobj
+% First grandchild bookmark
+{{object 11 0}} <<
+  /Title (Open Middle Descendant)
+  /Parent 10 0 R
+  /Dest [3 0 R /XYZ 100 200 0]
+>>
+endobj
+% Third child bookmark (closed)
+{{object 12 0}} <<
+  /Title (A Good Closed Ending)
+  /Parent 8 0 R
+  /First 13 0 R
+  /Last 14 0 R
+  /Prev 10 0 R
+  /Count -2
+  /Dest (bar)
+>>
+endobj
+% Second grandchild bookmark
+{{object 13 0}} <<
+  /Title (A Good Closed Ending Descendant)
+  /Parent 12 0 R
+  /Next 14 0 R
+  /Dest (bar)
+>>
+endobj
+% Third grandchild bookmark
+{{object 14 0}} <<
+  /Title (A Good Closed Ending Descendant 2)
+  /Parent 12 0 R
+  /Prev 13 0 R
+  /Dest (bar)
+>>
+endobj
 {{xref}}
 {{trailer}}
 {{startxref}}
diff --git a/testing/resources/bookmarks.pdf b/testing/resources/bookmarks.pdf
index 8c2eb5a..757f859 100644
--- a/testing/resources/bookmarks.pdf
+++ b/testing/resources/bookmarks.pdf
@@ -3,7 +3,7 @@
 1 0 obj <<
   /Type /Catalog
   /Pages 2 0 R
-  /Outlines 14 0 R
+  /Outlines 8 0 R
 >>
 endobj
 2 0 obj <<
@@ -20,9 +20,11 @@
   /Type /Page
   /Parent 2 0 R
   /Resources <<
-    /Font <</F1 15 0 R>>
+    /Font <<
+      /F1 5 0 R
+    >>
   >>
-  /Contents [21 0 R]
+  /Contents [6 0 R]
   /MediaBox [0 0 612 792]
 >>
 endobj
@@ -31,46 +33,24 @@
   /Type /Page
   /Parent 2 0 R
   /Resources  <<
-    /Font <</F1 15 0 R>>
+    /Font <<
+      /F1 5 0 R
+    >>
   >>
-  /Contents [22 0 R]
+  /Contents [7 0 R]
   /MediaBox [0 0 612 792]
 >>
 endobj
-% First bookmark
-10 0 obj <<
-  /Title (A Good Beginning)
-  /Parent 14 0 R
-  /Next 11 0 R
-  /Dest (foo)
->>
-endobj
-% Last bookmark
-11 0 obj <<
-  /Title (A Good Ending)
-  /Parent 14 0 R
-  /Prev 10 0 R
-  /Dest (bar)
->>
-endobj
-% Root bookmark
-14 0 obj <<
-  /Type /Outlines
-  /First 10 0 R
-  /Last  11 0 R
-  /Count 2
->>
-endobj
 % Font resource.
-15 0 obj <<
+5 0 obj <<
   /Type /Font
   /Subtype /Type1
   /BaseFont /Arial
 >>
 endobj
 % Content for page 0.
-21 0 obj <<
-  /Length 0
+6 0 obj <<
+  /Length 37
 >>
 stream
 BT
@@ -80,8 +60,8 @@
 endstream
 endobj
 % Content for page 1.
-22 0 obj <<
-  /Length 0
+7 0 obj <<
+  /Length 37
 >>
 stream
 BT
@@ -90,32 +70,93 @@
 ET
 endstream
 endobj
+% Root bookmark
+8 0 obj <<
+  /Type /Outlines
+  /Count 3
+  /First 9 0 R
+  /Last 12 0 R
+>>
+endobj
+% First child bookmark (leaf node)
+9 0 obj <<
+  /Title (A Good Beginning)
+  /Parent 8 0 R
+  /Next 10 0 R
+  /Dest (foo)
+>>
+endobj
+% Second child bookmark (open)
+10 0 obj <<
+  /Title (Open Middle)
+  /Parent 8 0 R
+  /First 11 0 R
+  /Last 11 0 R
+  /Prev 9 0 R
+  /Next 12 0 R
+  /Count 1
+  /A <<
+    /Type /Action
+    /S /URI
+    /URI (https://theplay.test)
+  >>
+>>
+endobj
+% First grandchild bookmark
+11 0 obj <<
+  /Title (Open Middle Descendant)
+  /Parent 10 0 R
+  /Dest [3 0 R /XYZ 100 200 0]
+>>
+endobj
+% Third child bookmark (closed)
+12 0 obj <<
+  /Title (A Good Closed Ending)
+  /Parent 8 0 R
+  /First 13 0 R
+  /Last 14 0 R
+  /Prev 10 0 R
+  /Count -2
+  /Dest (bar)
+>>
+endobj
+% Second grandchild bookmark
+13 0 obj <<
+  /Title (A Good Closed Ending Descendant)
+  /Parent 12 0 R
+  /Next 14 0 R
+  /Dest (bar)
+>>
+endobj
+% Third grandchild bookmark
+14 0 obj <<
+  /Title (A Good Closed Ending Descendant 2)
+  /Parent 12 0 R
+  /Prev 13 0 R
+  /Dest (bar)
+>>
+endobj
 xref
-0 23
+0 15
 0000000000 65535 f 
 0000000015 00000 n 
-0000000087 00000 n 
-0000000185 00000 n 
-0000000346 00000 n 
-0000000000 65535 f 
-0000000000 65535 f 
-0000000000 65535 f 
-0000000000 65535 f 
-0000000000 65535 f 
-0000000508 00000 n 
-0000000620 00000 n 
-0000000000 65535 f 
-0000000000 65535 f 
-0000000729 00000 n 
-0000000829 00000 n 
-0000000000 65535 f 
-0000000000 65535 f 
-0000000000 65535 f 
-0000000000 65535 f 
-0000000000 65535 f 
-0000000924 00000 n 
-0000001034 00000 n 
-trailer<< /Root 1 0 R /Size 23 >>
+0000000086 00000 n 
+0000000184 00000 n 
+0000000355 00000 n 
+0000000527 00000 n 
+0000000621 00000 n 
+0000000731 00000 n 
+0000000835 00000 n 
+0000000950 00000 n 
+0000001075 00000 n 
+0000001310 00000 n 
+0000001446 00000 n 
+0000001617 00000 n 
+0000001756 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 15
+>>
 startxref
-1122
+1869
 %%EOF
diff --git a/testing/resources/bug_1055869.in b/testing/resources/bug_1055869.in
new file mode 100644
index 0000000..26cbf5f
--- /dev/null
+++ b/testing/resources/bug_1055869.in
@@ -0,0 +1,62 @@
+{{header}}
+{{include xfa_catalog_1_0.fragment}}
+{{include xfa_object_2_0.fragment}}
+{{include xfa_preamble_3_0.fragment}}
+{{include xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform layout="tb" locale="en_US" name="form1" restoreState="auto">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <subform h="10.5in" w="8in" name="subform2">
+      <field h="10mm" name="choiceList0" w="50mm" x="5mm" y="50mm">
+        <ui>
+          <choiceList/>
+        </ui>
+        <items>
+          <text>Single</text>
+        </items>
+      </field>
+      <field name="choiceList1" h="200mm" w="200mm" x="1mm" y="1mm">
+        <ui>
+          <textEdit/>
+        </ui>
+        <value>
+          <text>pdfium</text>
+        </value>
+        <event activity="change">
+          <script contentType="application/x-javascript">
+            change_count += 1;
+            if (change_count == 2) {
+                f1 = xfa.resolveNode("xfa.form..choiceList0");
+                xfa.host.setFocus(f1);
+                xfa.template.remerge();
+                xfa.host.openList(f1);
+            }
+          </script>
+        </event>
+      </field>
+    </subform>
+    <event activity="docReady">
+      <script contentType="application/x-javascript">
+        change_count = 0;
+      </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{include xfa_locale_6_0.fragment}}
+{{include xfa_postamble_7_0.fragment}}
+{{include xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1055869.pdf b/testing/resources/bug_1055869.pdf
new file mode 100644
index 0000000..832d029
--- /dev/null
+++ b/testing/resources/bug_1055869.pdf
@@ -0,0 +1,271 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /AcroForm 2 0 R
+  /Extensions <<
+    /ADBE <<
+      /BaseVersion /1.7
+      /ExtensionLevel 8
+    >>
+  >>
+  /NeedsRendering true
+  /Pages 8 0 R
+  /Type /Catalog
+>>
+endobj
+2 0 obj <<
+  /XFA [
+    (preamble)
+    3 0 R
+    (config)
+    4 0 R
+    (template)
+    5 0 R
+    (localeSet)
+    6 0 R
+    (postamble)
+    7 0 R
+  ]
+>>
+endobj
+3 0 obj <<
+  /Length 124
+>>
+stream
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/" timeStamp="2018-02-23T21:37:11Z" uuid="21482798-7bf0-40a4-bc5d-3cefdccf32b5">
+endstream
+endobj
+4 0 obj <<
+  /Length 642
+>>
+stream
+<config xmlns="http://www.xfa.org/schema/xci/3.0/">
+<agent name="designer">
+  <destination>pdf</destination>
+  <pdf>
+    <fontInfo/>
+  </pdf>
+</agent>
+<present>
+  <pdf>
+    <version>1.7</version>
+    <adobeExtensionLevel>8</adobeExtensionLevel>
+    <renderPolicy>client</renderPolicy>
+    <scriptModel>XFA</scriptModel>
+    <interactive>1</interactive>
+  </pdf>
+  <xdp>
+    <packets>*</packets>
+  </xdp>
+  <destination>pdf</destination>
+  <script>
+    <runScripts>server</runScripts>
+  </script>
+</present>
+<acrobat>
+  <acrobat7>
+    <dynamicRender>required</dynamicRender>
+  </acrobat7>
+  <validate>preSubmit</validate>
+</acrobat>
+</config>
+endstream
+endobj
+5 0 obj <<
+  /Length 1361
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform layout="tb" locale="en_US" name="form1" restoreState="auto">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <subform h="10.5in" w="8in" name="subform2">
+      <field h="10mm" name="choiceList0" w="50mm" x="5mm" y="50mm">
+        <ui>
+          <choiceList/>
+        </ui>
+        <items>
+          <text>Single</text>
+        </items>
+      </field>
+      <field name="choiceList1" h="200mm" w="200mm" x="1mm" y="1mm">
+        <ui>
+          <textEdit/>
+        </ui>
+        <value>
+          <text>pdfium</text>
+        </value>
+        <event activity="change">
+          <script contentType="application/x-javascript">
+            change_count += 1;
+            if (change_count == 2) {
+                f1 = xfa.resolveNode("xfa.form..choiceList0");
+                xfa.host.setFocus(f1);
+                xfa.template.remerge();
+                xfa.host.openList(f1);
+            }
+          </script>
+        </event>
+      </field>
+    </subform>
+    <event activity="docReady">
+      <script contentType="application/x-javascript">
+        change_count = 0;
+      </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+6 0 obj <<
+  /Length 3455
+>>
+stream
+<localeSet xmlns="http://www.xfa.org/schema/xfa-locale-set/2.7/">
+  <locale name="en_US" desc="English (United States)">
+    <calendarSymbols name="gregorian">
+      <monthNames>
+        <month>January</month>
+        <month>February</month>
+        <month>March</month>
+        <month>April</month>
+        <month>May</month>
+        <month>June</month>
+        <month>July</month>
+        <month>August</month>
+        <month>September</month>
+        <month>October</month>
+        <month>November</month>
+        <month>December</month>
+      </monthNames>
+      <monthNames abbr="1">
+        <month>Jan</month>
+        <month>Feb</month>
+        <month>Mar</month>
+        <month>Apr</month>
+        <month>May</month>
+        <month>Jun</month>
+        <month>Jul</month>
+        <month>Aug</month>
+        <month>Sep</month>
+        <month>Oct</month>
+        <month>Nov</month>
+        <month>Dec</month>
+      </monthNames>
+      <dayNames>
+        <day>Sunday</day>
+        <day>Monday</day>
+        <day>Tuesday</day>
+        <day>Wednesday</day>
+        <day>Thursday</day>
+        <day>Friday</day>
+        <day>Saturday</day>
+      </dayNames>
+      <dayNames abbr="1">
+        <day>Sun</day>
+        <day>Mon</day>
+        <day>Tue</day>
+        <day>Wed</day>
+        <day>Thu</day>
+        <day>Fri</day>
+        <day>Sat</day>
+      </dayNames>
+      <meridiemNames>
+        <meridiem>AM</meridiem>
+        <meridiem>PM</meridiem>
+      </meridiemNames>
+      <eraNames>
+        <era>BC</era>
+        <era>AD</era>
+      </eraNames>
+    </calendarSymbols>
+    <datePatterns>
+      <datePattern name="full">EEEE, MMMM D, YYYY</datePattern>
+      <datePattern name="long">MMMM D, YYYY</datePattern>
+      <datePattern name="med">MMM D, YYYY</datePattern>
+      <datePattern name="short">M/D/YY</datePattern>
+    </datePatterns>
+    <timePatterns>
+      <timePattern name="full">h:MM:SS A Z</timePattern>
+      <timePattern name="long">h:MM:SS A Z</timePattern>
+      <timePattern name="med">h:MM:SS A</timePattern>
+      <timePattern name="short">h:MM A</timePattern>
+    </timePatterns>
+    <dateTimeSymbols>GyMdkHmsSEDFwWahKzZ</dateTimeSymbols>
+    <numberPatterns>
+      <numberPattern name="numeric">z,zz9.zzz</numberPattern>
+      <numberPattern name="currency">$z,zz9.99|($z,zz9.99)</numberPattern>
+      <numberPattern name="percent">z,zz9%</numberPattern>
+    </numberPatterns>
+    <numberSymbols>
+      <numberSymbol name="decimal">.</numberSymbol>
+      <numberSymbol name="grouping">,</numberSymbol>
+      <numberSymbol name="percent">%</numberSymbol>
+      <numberSymbol name="minus">-</numberSymbol>
+      <numberSymbol name="zero">0</numberSymbol>
+    </numberSymbols>
+    <currencySymbols>
+      <currencySymbol name="symbol">$</currencySymbol>
+      <currencySymbol name="isoname">USD</currencySymbol>
+      <currencySymbol name="decimal">.</currencySymbol>
+    </currencySymbols>
+    <typefaces>
+      <typeface name="Myriad Pro"/>
+      <typeface name="Minion Pro"/>
+      <typeface name="Courier Std"/>
+      <typeface name="Adobe Pi Std"/>
+      <typeface name="Adobe Hebrew"/>
+      <typeface name="Adobe Arabic"/>
+      <typeface name="Adobe Thai"/>
+      <typeface name="Kozuka Gothic Pro-VI M"/>
+      <typeface name="Kozuka Mincho Pro-VI R"/>
+      <typeface name="Adobe Ming Std L"/>
+      <typeface name="Adobe Song Std L"/>
+      <typeface name="Adobe Myungjo Std M"/>
+    </typefaces>
+  </locale>
+</localeSet>
+endstream
+endobj
+7 0 obj <<
+  /Length 11
+>>
+stream
+</xdp:xdp>
+endstream
+endobj
+8 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [9 0 R]
+>>
+endobj
+9 0 obj <<
+  /Type /Page
+  /Parent 8 0 R
+  /MediaBox [0 0 612 792]
+>>
+endobj
+xref
+0 10
+0000000000 65535 f 
+0000000015 00000 n 
+0000000199 00000 n 
+0000000358 00000 n 
+0000000534 00000 n 
+0000001228 00000 n 
+0000002642 00000 n 
+0000006150 00000 n 
+0000006212 00000 n 
+0000006275 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 10
+>>
+startxref
+6352
+%%EOF
diff --git a/testing/resources/bug_1058653.in b/testing/resources/bug_1058653.in
new file mode 100644
index 0000000..5973c54
--- /dev/null
+++ b/testing/resources/bug_1058653.in
@@ -0,0 +1,73 @@
+{{header}}
+{{include xfa_catalog_1_0.fragment}}
+{{include xfa_object_2_0.fragment}}
+{{include xfa_preamble_3_0.fragment}}
+{{include xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform layout="tb" locale="en_US" name="form1" restoreState="auto">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <subform h="10.5in" w="8in" name="subform2">
+      <field name="f1" h="10mm" w="10mm" x="20mm" y="20mm">
+        <ui>
+          <choiceList/>
+        </ui>
+        <items save="1">
+          <text>aaaaaaaaaa</text>
+        </items>
+      </field>
+      <subform name="f4" x="1mm" y="1mm">
+        <occur max="-1"/>
+        <field name="f2" h="350mm" w="200mm">
+          <ui>
+            <choiceList textEntry="1">
+            </choiceList>
+          </ui>
+          <items>
+                <text>Albania</text>
+                <text>Andorra</text>
+                <text>Armenia</text>
+          </items>
+          <event activity="change">
+            <script contentType="application/x-javascript">
+              a += 1;
+              if (a == 2) {
+                  c = xfa.resolveNode("xfa.form..f1");
+                  xfa.host.setFocus(c);
+                  d = xfa.resolveNode("xfa.form..f4");
+                  d.instanceManager.addInstance(1);
+                  d.instanceManager.removeInstance(0);
+                  xfa.host.openList(c);
+              }
+            </script>
+          </event>
+        </field>
+      </subform>
+    </subform>
+    <event activity="docReady">
+      <script contentType="application/x-javascript">
+        a = 0;
+        f2 = xfa.resolveNode("xfa.form..f2");
+        f2.rawValue = "minhtttttt";
+        xfa.host.setFocus(f2);
+      </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{include xfa_locale_6_0.fragment}}
+{{include xfa_postamble_7_0.fragment}}
+{{include xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1058653.pdf b/testing/resources/bug_1058653.pdf
new file mode 100644
index 0000000..f278dfb
--- /dev/null
+++ b/testing/resources/bug_1058653.pdf
@@ -0,0 +1,282 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /AcroForm 2 0 R
+  /Extensions <<
+    /ADBE <<
+      /BaseVersion /1.7
+      /ExtensionLevel 8
+    >>
+  >>
+  /NeedsRendering true
+  /Pages 8 0 R
+  /Type /Catalog
+>>
+endobj
+2 0 obj <<
+  /XFA [
+    (preamble)
+    3 0 R
+    (config)
+    4 0 R
+    (template)
+    5 0 R
+    (localeSet)
+    6 0 R
+    (postamble)
+    7 0 R
+  ]
+>>
+endobj
+3 0 obj <<
+  /Length 124
+>>
+stream
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/" timeStamp="2018-02-23T21:37:11Z" uuid="21482798-7bf0-40a4-bc5d-3cefdccf32b5">
+endstream
+endobj
+4 0 obj <<
+  /Length 642
+>>
+stream
+<config xmlns="http://www.xfa.org/schema/xci/3.0/">
+<agent name="designer">
+  <destination>pdf</destination>
+  <pdf>
+    <fontInfo/>
+  </pdf>
+</agent>
+<present>
+  <pdf>
+    <version>1.7</version>
+    <adobeExtensionLevel>8</adobeExtensionLevel>
+    <renderPolicy>client</renderPolicy>
+    <scriptModel>XFA</scriptModel>
+    <interactive>1</interactive>
+  </pdf>
+  <xdp>
+    <packets>*</packets>
+  </xdp>
+  <destination>pdf</destination>
+  <script>
+    <runScripts>server</runScripts>
+  </script>
+</present>
+<acrobat>
+  <acrobat7>
+    <dynamicRender>required</dynamicRender>
+  </acrobat7>
+  <validate>preSubmit</validate>
+</acrobat>
+</config>
+endstream
+endobj
+5 0 obj <<
+  /Length 1772
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform layout="tb" locale="en_US" name="form1" restoreState="auto">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <subform h="10.5in" w="8in" name="subform2">
+      <field name="f1" h="10mm" w="10mm" x="20mm" y="20mm">
+        <ui>
+          <choiceList/>
+        </ui>
+        <items save="1">
+          <text>aaaaaaaaaa</text>
+        </items>
+      </field>
+      <subform name="f4" x="1mm" y="1mm">
+        <occur max="-1"/>
+        <field name="f2" h="350mm" w="200mm">
+          <ui>
+            <choiceList textEntry="1">
+            </choiceList>
+          </ui>
+          <items>
+                <text>Albania</text>
+                <text>Andorra</text>
+                <text>Armenia</text>
+          </items>
+          <event activity="change">
+            <script contentType="application/x-javascript">
+              a += 1;
+              if (a == 2) {
+                  c = xfa.resolveNode("xfa.form..f1");
+                  xfa.host.setFocus(c);
+                  d = xfa.resolveNode("xfa.form..f4");
+                  d.instanceManager.addInstance(1);
+                  d.instanceManager.removeInstance(0);
+                  xfa.host.openList(c);
+              }
+            </script>
+          </event>
+        </field>
+      </subform>
+    </subform>
+    <event activity="docReady">
+      <script contentType="application/x-javascript">
+        a = 0;
+        f2 = xfa.resolveNode("xfa.form..f2");
+        f2.rawValue = "minhtttttt";
+        xfa.host.setFocus(f2);
+      </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+6 0 obj <<
+  /Length 3455
+>>
+stream
+<localeSet xmlns="http://www.xfa.org/schema/xfa-locale-set/2.7/">
+  <locale name="en_US" desc="English (United States)">
+    <calendarSymbols name="gregorian">
+      <monthNames>
+        <month>January</month>
+        <month>February</month>
+        <month>March</month>
+        <month>April</month>
+        <month>May</month>
+        <month>June</month>
+        <month>July</month>
+        <month>August</month>
+        <month>September</month>
+        <month>October</month>
+        <month>November</month>
+        <month>December</month>
+      </monthNames>
+      <monthNames abbr="1">
+        <month>Jan</month>
+        <month>Feb</month>
+        <month>Mar</month>
+        <month>Apr</month>
+        <month>May</month>
+        <month>Jun</month>
+        <month>Jul</month>
+        <month>Aug</month>
+        <month>Sep</month>
+        <month>Oct</month>
+        <month>Nov</month>
+        <month>Dec</month>
+      </monthNames>
+      <dayNames>
+        <day>Sunday</day>
+        <day>Monday</day>
+        <day>Tuesday</day>
+        <day>Wednesday</day>
+        <day>Thursday</day>
+        <day>Friday</day>
+        <day>Saturday</day>
+      </dayNames>
+      <dayNames abbr="1">
+        <day>Sun</day>
+        <day>Mon</day>
+        <day>Tue</day>
+        <day>Wed</day>
+        <day>Thu</day>
+        <day>Fri</day>
+        <day>Sat</day>
+      </dayNames>
+      <meridiemNames>
+        <meridiem>AM</meridiem>
+        <meridiem>PM</meridiem>
+      </meridiemNames>
+      <eraNames>
+        <era>BC</era>
+        <era>AD</era>
+      </eraNames>
+    </calendarSymbols>
+    <datePatterns>
+      <datePattern name="full">EEEE, MMMM D, YYYY</datePattern>
+      <datePattern name="long">MMMM D, YYYY</datePattern>
+      <datePattern name="med">MMM D, YYYY</datePattern>
+      <datePattern name="short">M/D/YY</datePattern>
+    </datePatterns>
+    <timePatterns>
+      <timePattern name="full">h:MM:SS A Z</timePattern>
+      <timePattern name="long">h:MM:SS A Z</timePattern>
+      <timePattern name="med">h:MM:SS A</timePattern>
+      <timePattern name="short">h:MM A</timePattern>
+    </timePatterns>
+    <dateTimeSymbols>GyMdkHmsSEDFwWahKzZ</dateTimeSymbols>
+    <numberPatterns>
+      <numberPattern name="numeric">z,zz9.zzz</numberPattern>
+      <numberPattern name="currency">$z,zz9.99|($z,zz9.99)</numberPattern>
+      <numberPattern name="percent">z,zz9%</numberPattern>
+    </numberPatterns>
+    <numberSymbols>
+      <numberSymbol name="decimal">.</numberSymbol>
+      <numberSymbol name="grouping">,</numberSymbol>
+      <numberSymbol name="percent">%</numberSymbol>
+      <numberSymbol name="minus">-</numberSymbol>
+      <numberSymbol name="zero">0</numberSymbol>
+    </numberSymbols>
+    <currencySymbols>
+      <currencySymbol name="symbol">$</currencySymbol>
+      <currencySymbol name="isoname">USD</currencySymbol>
+      <currencySymbol name="decimal">.</currencySymbol>
+    </currencySymbols>
+    <typefaces>
+      <typeface name="Myriad Pro"/>
+      <typeface name="Minion Pro"/>
+      <typeface name="Courier Std"/>
+      <typeface name="Adobe Pi Std"/>
+      <typeface name="Adobe Hebrew"/>
+      <typeface name="Adobe Arabic"/>
+      <typeface name="Adobe Thai"/>
+      <typeface name="Kozuka Gothic Pro-VI M"/>
+      <typeface name="Kozuka Mincho Pro-VI R"/>
+      <typeface name="Adobe Ming Std L"/>
+      <typeface name="Adobe Song Std L"/>
+      <typeface name="Adobe Myungjo Std M"/>
+    </typefaces>
+  </locale>
+</localeSet>
+endstream
+endobj
+7 0 obj <<
+  /Length 11
+>>
+stream
+</xdp:xdp>
+endstream
+endobj
+8 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [9 0 R]
+>>
+endobj
+9 0 obj <<
+  /Type /Page
+  /Parent 8 0 R
+  /MediaBox [0 0 612 792]
+>>
+endobj
+xref
+0 10
+0000000000 65535 f 
+0000000015 00000 n 
+0000000199 00000 n 
+0000000358 00000 n 
+0000000534 00000 n 
+0000001228 00000 n 
+0000003053 00000 n 
+0000006561 00000 n 
+0000006623 00000 n 
+0000006686 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 10
+>>
+startxref
+6763
+%%EOF
diff --git a/testing/resources/bug_1124998.pdf b/testing/resources/bug_1124998.pdf
new file mode 100644
index 0000000..4368222
--- /dev/null
+++ b/testing/resources/bug_1124998.pdf
Binary files differ
diff --git a/testing/resources/bug_1229106.in b/testing/resources/bug_1229106.in
new file mode 100644
index 0000000..7ffe9bf
--- /dev/null
+++ b/testing/resources/bug_1229106.in
@@ -0,0 +1,68 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 4
+  /Kids [3 0 R 3 0 R 5 0 R 5 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Rotate 90
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+0 1 -1 0 792 0 cm
+0 0 0 rg
+100 400 150 50 re f
+1 0 0 rg
+0 180 100 50 re f
+0 1 0 rg
+0 742 100 50 re f
+0 0 1 rg
+692 742 100 50 re f
+1 0 1 rg
+692 180 100 50 re f
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 792 612]
+  /Contents 6 0 R
+>>
+endobj
+{{object 6 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+0 0 0 rg
+100 220 150 50 re f
+1 0 0 rg
+0 0 100 50 re f
+0 1 0 rg
+0 562 100 50 re f
+0 0 1 rg
+692 562 100 50 re f
+1 0 1 rg
+692 0 100 50 re f
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1229106.pdf b/testing/resources/bug_1229106.pdf
new file mode 100644
index 0000000..e0d625c
--- /dev/null
+++ b/testing/resources/bug_1229106.pdf
@@ -0,0 +1,81 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 4
+  /Kids [3 0 R 3 0 R 5 0 R 5 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Rotate 90
+  /Contents 4 0 R
+>>
+endobj
+4 0 obj <<
+  /Length 163
+>>
+stream
+q
+0 1 -1 0 792 0 cm
+0 0 0 rg
+100 400 150 50 re f
+1 0 0 rg
+0 180 100 50 re f
+0 1 0 rg
+0 742 100 50 re f
+0 0 1 rg
+692 742 100 50 re f
+1 0 1 rg
+692 180 100 50 re f
+Q
+endstream
+endobj
+5 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 792 612]
+  /Contents 6 0 R
+>>
+endobj
+6 0 obj <<
+  /Length 141
+>>
+stream
+q
+0 0 0 rg
+100 220 150 50 re f
+1 0 0 rg
+0 0 100 50 re f
+0 1 0 rg
+0 562 100 50 re f
+0 0 1 rg
+692 562 100 50 re f
+1 0 1 rg
+692 0 100 50 re f
+Q
+endstream
+endobj
+xref
+0 7
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000149 00000 n 
+0000000257 00000 n 
+0000000472 00000 n 
+0000000567 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 7
+>>
+startxref
+760
+%%EOF
diff --git a/testing/resources/bug_1296920.in b/testing/resources/bug_1296920.in
new file mode 100644
index 0000000..5c9da58
--- /dev/null
+++ b/testing/resources/bug_1296920.in
@@ -0,0 +1,115 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /StructTreeRoot 6 0 R
+  /MarkInfo <<
+    /Marked true
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Contents 4 0 R
+  /MediaBox [0 0 100 100]
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+  /StructParents 0
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+/P <</MCID 1>> BDC
+BT
+/F1 12 Tf
+1 0 0 1 20 50 Tm
+(Hello) Tj
+ET
+EMC
+/P <</MCID 2>> BDC
+BT
+/F1 12 Tf
+1 0 0 1 50 50 Tm
+(World) Tj
+ET
+EMC
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{object 6 0}} <<
+  /Type /StructTreeRoot
+  /K [9 0 R]
+  /ParentTree 7 0 R
+>>
+endobj
+{{object 7 0}} <<
+  /Nums [0 8 0 R 1 10 0 R]
+>>
+endobj
+{{object 8 0}}
+[12 0 R 13 0 R]
+endobj
+{{object 9 0}} <<
+  /Type /StructElem
+  /S /Document
+  /P 6 0 R
+  /K [10 0 R]
+>>
+endobj
+{{object 10 0}} <<
+  /Type /StructElem
+  /S /Part
+  /P 9 0 R
+  /K [11 0 R]
+>>
+endobj
+{{object 11 0}} <<
+  /Type /StructElem
+  /S /Div
+  /P 10 0 R
+  /K [12 0 R 13 0 R 14 0 R]
+>>
+endobj
+{{object 12 0}} <<
+  /Type /StructElem
+  /S /P
+  /P 11 0 R
+  /K 1
+  /Pg 3 0 R
+>>
+endobj
+{{object 13 0}} <<
+  /Type /StructElem
+  /S /P
+  /P 11 0 R
+  /K 2
+  /Pg 3 0 R
+>>
+endobj
+{{object 14 0}} <<
+  /Type /StructElem
+  /S /Div
+  /P 11 0 R
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1296920.pdf b/testing/resources/bug_1296920.pdf
new file mode 100644
index 0000000..92d778c
--- /dev/null
+++ b/testing/resources/bug_1296920.pdf
@@ -0,0 +1,136 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /StructTreeRoot 6 0 R
+  /MarkInfo <<
+    /Marked true
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Contents 4 0 R
+  /MediaBox [0 0 100 100]
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+  /StructParents 0
+>>
+endobj
+4 0 obj <<
+  /Length 134
+>>
+stream
+/P <</MCID 1>> BDC
+BT
+/F1 12 Tf
+1 0 0 1 20 50 Tm
+(Hello) Tj
+ET
+EMC
+/P <</MCID 2>> BDC
+BT
+/F1 12 Tf
+1 0 0 1 50 50 Tm
+(World) Tj
+ET
+EMC
+endstream
+endobj
+5 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+6 0 obj <<
+  /Type /StructTreeRoot
+  /K [9 0 R]
+  /ParentTree 7 0 R
+>>
+endobj
+7 0 obj <<
+  /Nums [0 8 0 R 1 10 0 R]
+>>
+endobj
+8 0 obj
+[12 0 R 13 0 R]
+endobj
+9 0 obj <<
+  /Type /StructElem
+  /S /Document
+  /P 6 0 R
+  /K [10 0 R]
+>>
+endobj
+10 0 obj <<
+  /Type /StructElem
+  /S /Part
+  /P 9 0 R
+  /K [11 0 R]
+>>
+endobj
+11 0 obj <<
+  /Type /StructElem
+  /S /Div
+  /P 10 0 R
+  /K [12 0 R 13 0 R 14 0 R]
+>>
+endobj
+12 0 obj <<
+  /Type /StructElem
+  /S /P
+  /P 11 0 R
+  /K 1
+  /Pg 3 0 R
+>>
+endobj
+13 0 obj <<
+  /Type /StructElem
+  /S /P
+  /P 11 0 R
+  /K 2
+  /Pg 3 0 R
+>>
+endobj
+14 0 obj <<
+  /Type /StructElem
+  /S /Div
+  /P 11 0 R
+>>
+endobj
+xref
+0 15
+0000000000 65535 f 
+0000000015 00000 n 
+0000000129 00000 n 
+0000000192 00000 n 
+0000000363 00000 n 
+0000000549 00000 n 
+0000000625 00000 n 
+0000000703 00000 n 
+0000000751 00000 n 
+0000000782 00000 n 
+0000000863 00000 n 
+0000000941 00000 n 
+0000001033 00000 n 
+0000001114 00000 n 
+0000001195 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 15
+>>
+startxref
+1259
+%%EOF
diff --git a/testing/resources/bug_1301.pdf b/testing/resources/bug_1301.pdf
index 285c7f7..8c6e45f 100644
--- a/testing/resources/bug_1301.pdf
+++ b/testing/resources/bug_1301.pdf
@@ -217,7 +217,7 @@
 endobj
 9 0 obj <<
   /Type /Page
-  /Parent 2 0 R
+  /Parent 8 0 R
   /MediaBox [0 0 612 792]
 >>
 endobj
diff --git a/testing/resources/bug_1302455.in b/testing/resources/bug_1302455.in
new file mode 100644
index 0000000..1e18f12
--- /dev/null
+++ b/testing/resources/bug_1302455.in
@@ -0,0 +1,75 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /DR 4 0 R
+    /Fields [6 0 R 7 0 R]
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Kids [3 0 R]
+  /Count 1
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [6 0 R 7 0 R]
+  /MediaBox [0 0 300 300]
+  /Resources 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  /Font <<
+    /F1 5 0 R
+  >>
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /AP <<
+    /N 8 0 R
+  >>
+  /F 4
+  /Rect [100 100 200 130]
+  /T (Text Box 1)
+>>
+endobj
+{{object 7 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /AP <<
+    /N 8 0 R
+  >>
+  /F 4
+  /Rect [100 160 200 190]
+  /T (Text Box 2)
+>>
+endobj
+{{object 8 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 100 30]
+  {{streamlen}}
+>>
+stream
+1 0 0 rg
+0 0 100 30 re f
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1302455.pdf b/testing/resources/bug_1302455.pdf
new file mode 100644
index 0000000..a9782c4
--- /dev/null
+++ b/testing/resources/bug_1302455.pdf
@@ -0,0 +1,90 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /DR 4 0 R
+    /Fields [6 0 R 7 0 R]
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Kids [3 0 R]
+  /Count 1
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [6 0 R 7 0 R]
+  /MediaBox [0 0 300 300]
+  /Resources 4 0 R
+>>
+endobj
+4 0 obj <<
+  /Font <<
+    /F1 5 0 R
+  >>
+>>
+endobj
+5 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+6 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /AP <<
+    /N 8 0 R
+  >>
+  /F 4
+  /Rect [100 100 200 130]
+  /T (Text Box 1)
+>>
+endobj
+7 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /AP <<
+    /N 8 0 R
+  >>
+  /F 4
+  /Rect [100 160 200 190]
+  /T (Text Box 2)
+>>
+endobj
+8 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 100 30]
+  /Length 25
+>>
+stream
+1 0 0 rg
+0 0 100 30 re f
+endstream
+endobj
+xref
+0 9
+0000000000 65535 f 
+0000000015 00000 n 
+0000000128 00000 n 
+0000000191 00000 n 
+0000000311 00000 n 
+0000000362 00000 n 
+0000000438 00000 n 
+0000000581 00000 n 
+0000000724 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 9
+>>
+startxref
+855
+%%EOF
diff --git a/testing/resources/bug_1324189.in b/testing/resources/bug_1324189.in
new file mode 100644
index 0000000..45fceba
--- /dev/null
+++ b/testing/resources/bug_1324189.in
@@ -0,0 +1,16 @@
+{{header}}
+{{object 1 0}} <
+  /Type /Catalog
+>>
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
+{{xref}}
+trailer <<
+  /Root 1 0 R
+  /Prev -200
+  {{trailersize}}
+>>
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1324189.pdf b/testing/resources/bug_1324189.pdf
new file mode 100644
index 0000000..9652398
--- /dev/null
+++ b/testing/resources/bug_1324189.pdf
@@ -0,0 +1,28 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <
+  /Type /Catalog
+>>
+xref
+0 2
+0000000000 65535 f 
+0000000015 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 2
+>>
+startxref
+45
+%%EOF
+xref
+0 2
+0000000000 65535 f 
+0000000015 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Prev -200
+  /Size 2
+>>
+startxref
+151
+%%EOF
diff --git a/testing/resources/bug_1324503.in b/testing/resources/bug_1324503.in
new file mode 100644
index 0000000..a46d142
--- /dev/null
+++ b/testing/resources/bug_1324503.in
@@ -0,0 +1,16 @@
+{{header}}
+{{object 1 0}} <
+  /Type /Catalog
+>>
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
+{{xref}}
+trailer <<
+  /Root 1 0 R
+  /XRefStm -1
+  {{trailersize}}
+>>
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1324503.pdf b/testing/resources/bug_1324503.pdf
new file mode 100644
index 0000000..2ce7425
--- /dev/null
+++ b/testing/resources/bug_1324503.pdf
@@ -0,0 +1,28 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <
+  /Type /Catalog
+>>
+xref
+0 2
+0000000000 65535 f 
+0000000015 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 2
+>>
+startxref
+45
+%%EOF
+xref
+0 2
+0000000000 65535 f 
+0000000015 00000 n 
+trailer <<
+  /Root 1 0 R
+  /XRefStm -1
+  /Size 2
+>>
+startxref
+151
+%%EOF
diff --git a/testing/resources/bug_1327884.pdf b/testing/resources/bug_1327884.pdf
new file mode 100644
index 0000000..0e28c06
--- /dev/null
+++ b/testing/resources/bug_1327884.pdf
@@ -0,0 +1,6 @@
+%PDF
+1 0 obj<</Pages 2 0 R/AcroForm<</XFA 30 0 R>>>>2 0 obj<</>
+30 0 obj<<>>stream
+<xdp xmlns="http://ns.adobe.com/xdp/"><con0ig><acrobat><acrobat0></acrobat0></acrobat></con0ig><template><desc e=""use=" .[use=&quot;*&quot;]"
+endobj
+trailer<</Root 1 0 R>>
\ No newline at end of file
diff --git a/testing/resources/bug_1328389.in b/testing/resources/bug_1328389.in
new file mode 100644
index 0000000..3370ce7
--- /dev/null
+++ b/testing/resources/bug_1328389.in
@@ -0,0 +1,24 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Kids [3 0 R]
+  /Count 1
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 100 100]
+  % https://crbug.com/1328389
+  /Foo /
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1328389.pdf b/testing/resources/bug_1328389.pdf
new file mode 100644
index 0000000..e45a7a8
--- /dev/null
+++ b/testing/resources/bug_1328389.pdf
@@ -0,0 +1,34 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Kids [3 0 R]
+  /Count 1
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 100 100]
+  % https://crbug.com/1328389
+  /Foo /
+>>
+endobj
+xref
+0 4
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 4
+>>
+startxref
+247
+%%EOF
diff --git a/testing/resources/bug_1333298.in b/testing/resources/bug_1333298.in
new file mode 100644
index 0000000..5b45ed9
--- /dev/null
+++ b/testing/resources/bug_1333298.in
@@ -0,0 +1,28 @@
+{{header}}
+{{include xfa_catalog_1_0.fragment}}
+{{include xfa_object_2_0.fragment}}
+{{include xfa_preamble_3_0.fragment}}
+{{include xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template>
+  <subform>
+    <desc name="N01" use=" .[N01.use=&quot; .[N01.#use]&quot;]"/>
+    <proto>
+      <proto>
+        <bindItems/>
+      </proto>
+    </proto>
+  </subform>
+</template>
+endstream
+endobj
+{{include xfa_locale_6_0.fragment}}
+{{include xfa_postamble_7_0.fragment}}
+{{include xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1333298.pdf b/testing/resources/bug_1333298.pdf
new file mode 100644
index 0000000..c003ca9
--- /dev/null
+++ b/testing/resources/bug_1333298.pdf
@@ -0,0 +1,237 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /AcroForm 2 0 R
+  /Extensions <<
+    /ADBE <<
+      /BaseVersion /1.7
+      /ExtensionLevel 8
+    >>
+  >>
+  /NeedsRendering true
+  /Pages 8 0 R
+  /Type /Catalog
+>>
+endobj
+2 0 obj <<
+  /XFA [
+    (preamble)
+    3 0 R
+    (config)
+    4 0 R
+    (template)
+    5 0 R
+    (localeSet)
+    6 0 R
+    (postamble)
+    7 0 R
+  ]
+>>
+endobj
+3 0 obj <<
+  /Length 124
+>>
+stream
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/" timeStamp="2018-02-23T21:37:11Z" uuid="21482798-7bf0-40a4-bc5d-3cefdccf32b5">
+endstream
+endobj
+4 0 obj <<
+  /Length 642
+>>
+stream
+<config xmlns="http://www.xfa.org/schema/xci/3.0/">
+<agent name="designer">
+  <destination>pdf</destination>
+  <pdf>
+    <fontInfo/>
+  </pdf>
+</agent>
+<present>
+  <pdf>
+    <version>1.7</version>
+    <adobeExtensionLevel>8</adobeExtensionLevel>
+    <renderPolicy>client</renderPolicy>
+    <scriptModel>XFA</scriptModel>
+    <interactive>1</interactive>
+  </pdf>
+  <xdp>
+    <packets>*</packets>
+  </xdp>
+  <destination>pdf</destination>
+  <script>
+    <runScripts>server</runScripts>
+  </script>
+</present>
+<acrobat>
+  <acrobat7>
+    <dynamicRender>required</dynamicRender>
+  </acrobat7>
+  <validate>preSubmit</validate>
+</acrobat>
+</config>
+endstream
+endobj
+5 0 obj <<
+  /Length 189
+>>
+stream
+<template>
+  <subform>
+    <desc name="N01" use=" .[N01.use=&quot; .[N01.#use]&quot;]"/>
+    <proto>
+      <proto>
+        <bindItems/>
+      </proto>
+    </proto>
+  </subform>
+</template>
+endstream
+endobj
+6 0 obj <<
+  /Length 3455
+>>
+stream
+<localeSet xmlns="http://www.xfa.org/schema/xfa-locale-set/2.7/">
+  <locale name="en_US" desc="English (United States)">
+    <calendarSymbols name="gregorian">
+      <monthNames>
+        <month>January</month>
+        <month>February</month>
+        <month>March</month>
+        <month>April</month>
+        <month>May</month>
+        <month>June</month>
+        <month>July</month>
+        <month>August</month>
+        <month>September</month>
+        <month>October</month>
+        <month>November</month>
+        <month>December</month>
+      </monthNames>
+      <monthNames abbr="1">
+        <month>Jan</month>
+        <month>Feb</month>
+        <month>Mar</month>
+        <month>Apr</month>
+        <month>May</month>
+        <month>Jun</month>
+        <month>Jul</month>
+        <month>Aug</month>
+        <month>Sep</month>
+        <month>Oct</month>
+        <month>Nov</month>
+        <month>Dec</month>
+      </monthNames>
+      <dayNames>
+        <day>Sunday</day>
+        <day>Monday</day>
+        <day>Tuesday</day>
+        <day>Wednesday</day>
+        <day>Thursday</day>
+        <day>Friday</day>
+        <day>Saturday</day>
+      </dayNames>
+      <dayNames abbr="1">
+        <day>Sun</day>
+        <day>Mon</day>
+        <day>Tue</day>
+        <day>Wed</day>
+        <day>Thu</day>
+        <day>Fri</day>
+        <day>Sat</day>
+      </dayNames>
+      <meridiemNames>
+        <meridiem>AM</meridiem>
+        <meridiem>PM</meridiem>
+      </meridiemNames>
+      <eraNames>
+        <era>BC</era>
+        <era>AD</era>
+      </eraNames>
+    </calendarSymbols>
+    <datePatterns>
+      <datePattern name="full">EEEE, MMMM D, YYYY</datePattern>
+      <datePattern name="long">MMMM D, YYYY</datePattern>
+      <datePattern name="med">MMM D, YYYY</datePattern>
+      <datePattern name="short">M/D/YY</datePattern>
+    </datePatterns>
+    <timePatterns>
+      <timePattern name="full">h:MM:SS A Z</timePattern>
+      <timePattern name="long">h:MM:SS A Z</timePattern>
+      <timePattern name="med">h:MM:SS A</timePattern>
+      <timePattern name="short">h:MM A</timePattern>
+    </timePatterns>
+    <dateTimeSymbols>GyMdkHmsSEDFwWahKzZ</dateTimeSymbols>
+    <numberPatterns>
+      <numberPattern name="numeric">z,zz9.zzz</numberPattern>
+      <numberPattern name="currency">$z,zz9.99|($z,zz9.99)</numberPattern>
+      <numberPattern name="percent">z,zz9%</numberPattern>
+    </numberPatterns>
+    <numberSymbols>
+      <numberSymbol name="decimal">.</numberSymbol>
+      <numberSymbol name="grouping">,</numberSymbol>
+      <numberSymbol name="percent">%</numberSymbol>
+      <numberSymbol name="minus">-</numberSymbol>
+      <numberSymbol name="zero">0</numberSymbol>
+    </numberSymbols>
+    <currencySymbols>
+      <currencySymbol name="symbol">$</currencySymbol>
+      <currencySymbol name="isoname">USD</currencySymbol>
+      <currencySymbol name="decimal">.</currencySymbol>
+    </currencySymbols>
+    <typefaces>
+      <typeface name="Myriad Pro"/>
+      <typeface name="Minion Pro"/>
+      <typeface name="Courier Std"/>
+      <typeface name="Adobe Pi Std"/>
+      <typeface name="Adobe Hebrew"/>
+      <typeface name="Adobe Arabic"/>
+      <typeface name="Adobe Thai"/>
+      <typeface name="Kozuka Gothic Pro-VI M"/>
+      <typeface name="Kozuka Mincho Pro-VI R"/>
+      <typeface name="Adobe Ming Std L"/>
+      <typeface name="Adobe Song Std L"/>
+      <typeface name="Adobe Myungjo Std M"/>
+    </typefaces>
+  </locale>
+</localeSet>
+endstream
+endobj
+7 0 obj <<
+  /Length 11
+>>
+stream
+</xdp:xdp>
+endstream
+endobj
+8 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [9 0 R]
+>>
+endobj
+9 0 obj <<
+  /Type /Page
+  /Parent 8 0 R
+  /MediaBox [0 0 612 792]
+>>
+endobj
+xref
+0 10
+0000000000 65535 f 
+0000000015 00000 n 
+0000000199 00000 n 
+0000000358 00000 n 
+0000000534 00000 n 
+0000001228 00000 n 
+0000001469 00000 n 
+0000004977 00000 n 
+0000005039 00000 n 
+0000005102 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 10
+>>
+startxref
+5179
+%%EOF
diff --git a/testing/resources/bug_1388_2.pdf b/testing/resources/bug_1388_2.pdf
new file mode 100644
index 0000000..1fee088
--- /dev/null
+++ b/testing/resources/bug_1388_2.pdf
@@ -0,0 +1,80 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 200 200]
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /Font <<
+      /TT2 5 0 R
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Length 40
+>>
+stream
+BT
+/TT2 12 Tf
+40 100 Td
+[(X  X)] TJ
+ET
+endstream
+endobj
+5 0 obj <<
+  /Type /Font
+  /Subtype /TrueType
+  /BaseFont /TimesNewRomanPSMT
+  /Encoding /WinAnsiEncoding
+  /FirstChar 31
+  /FontDescriptor 6 0 R
+  /LastChar 252
+>>
+endobj
+6 0 obj <<
+  /Type /FontDescriptor
+  /Ascent 891
+  /CapHeight 656
+  /Descent -216
+  /Flags 34
+  /FontBBox [-568 -307 2000 1007]
+  /FontFamily (Times New Roman)
+  /FontName /TimesNewRomanPSMT
+  /FontStretch /Normal
+  /FontWeight 400
+  /ItalicAngle 0
+  /MissingWidth 778
+  /StemV 82
+  /XHeight -546
+>>
+endobj
+xref
+0 7
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000310 00000 n 
+0000000401 00000 n 
+0000000573 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 7
+>>
+startxref
+880
+%%EOF
diff --git a/testing/resources/bug_1396264.in b/testing/resources/bug_1396264.in
new file mode 100644
index 0000000..db1fc5a
--- /dev/null
+++ b/testing/resources/bug_1396264.in
@@ -0,0 +1,86 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 100 200]
+  /Resources <<
+    /XObject <<
+      % Both images are BGRA.
+      /ImBlue 5 0 R
+      /ImRed 6 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+100 0 0 100 0 0 cm
+/ImBlue Do
+Q
+q
+100 0 0 100 0 100 cm
+/ImRed Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /JPXDecode]
+  /Height 64
+  /Width 64
+  {{streamlen}}
+>>
+stream
+0000000c6a5020200d0a870a00000014667479706a703220000000006a7032200000004f6a7032
+68000000166968647200000040000000400004070700000000000f636f6c720100000000001000
+000022636465660004000000000001000100000002000200000003000300010000000000c06a70
+3263ff4fff51003200000000004000000040000000000000000000000040000000400000000000
+0000000004070101070101070101070101ff52000c00000001010504040001ff5c001340404848
+50484850484850484850484850ff640025000143726561746564206279204f70656e4a50454720
+76657273696f6e20322e332e30ff90000a0000000000360001ff93cfb41008908a6fdf801801ca
+bf00cfb40c01cabf0000000000000000000000000000000000000000ffd9
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /JPXDecode]
+  /Height 64
+  /Width 64
+  {{streamlen}}
+>>
+stream
+0000000c6a5020200d0a870a00000014667479706a703220000000006a7032200000004f6a7032
+68000000166968647200000040000000400004070700000000000f636f6c720100000000001000
+000022636465660004000000000001000100000002000200000003000300010000000000c06a70
+3263ff4fff51003200000000004000000040000000000000000000000040000000400000000000
+0000000004070101070101070101070101ff52000c00000001010504040001ff5c001340404848
+50484850484850484850484850ff640025000143726561746564206279204f70656e4a50454720
+76657273696f6e20322e332e30ff90000a0000000000360001ff93cfb41008908a6f00df801801
+cabfcfb40c01cabf0000000000000000000000000000000000000000ffd9
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1396264.pdf b/testing/resources/bug_1396264.pdf
new file mode 100644
index 0000000..9c2bddc
--- /dev/null
+++ b/testing/resources/bug_1396264.pdf
@@ -0,0 +1,99 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 100 200]
+  /Resources <<
+    /XObject <<
+      % Both images are BGRA.
+      /ImBlue 5 0 R
+      /ImRed 6 0 R
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Length 69
+>>
+stream
+q
+100 0 0 100 0 0 cm
+/ImBlue Do
+Q
+q
+100 0 0 100 0 100 cm
+/ImRed Do
+Q
+endstream
+endobj
+5 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /JPXDecode]
+  /Height 64
+  /Width 64
+  /Length 614
+>>
+stream
+0000000c6a5020200d0a870a00000014667479706a703220000000006a7032200000004f6a7032
+68000000166968647200000040000000400004070700000000000f636f6c720100000000001000
+000022636465660004000000000001000100000002000200000003000300010000000000c06a70
+3263ff4fff51003200000000004000000040000000000000000000000040000000400000000000
+0000000004070101070101070101070101ff52000c00000001010504040001ff5c001340404848
+50484850484850484850484850ff640025000143726561746564206279204f70656e4a50454720
+76657273696f6e20322e332e30ff90000a0000000000360001ff93cfb41008908a6fdf801801ca
+bf00cfb40c01cabf0000000000000000000000000000000000000000ffd9
+endstream
+endobj
+6 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /JPXDecode]
+  /Height 64
+  /Width 64
+  /Length 614
+>>
+stream
+0000000c6a5020200d0a870a00000014667479706a703220000000006a7032200000004f6a7032
+68000000166968647200000040000000400004070700000000000f636f6c720100000000001000
+000022636465660004000000000001000100000002000200000003000300010000000000c06a70
+3263ff4fff51003200000000004000000040000000000000000000000040000000400000000000
+0000000004070101070101070101070101ff52000c00000001010504040001ff5c001340404848
+50484850484850484850484850ff640025000143726561746564206279204f70656e4a50454720
+76657273696f6e20322e332e30ff90000a0000000000360001ff93cfb41008908a6f00df801801
+cabfcfb40c01cabf0000000000000000000000000000000000000000ffd9
+endstream
+endobj
+xref
+0 7
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000339 00000 n 
+0000000459 00000 n 
+0000001271 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 7
+>>
+startxref
+2083
+%%EOF
diff --git a/testing/resources/bug_1469.jp2 b/testing/resources/bug_1469.jp2
new file mode 100644
index 0000000..b84a029
--- /dev/null
+++ b/testing/resources/bug_1469.jp2
Binary files differ
diff --git a/testing/resources/bug_1506.in b/testing/resources/bug_1506.in
new file mode 100644
index 0000000..63c4c4d
--- /dev/null
+++ b/testing/resources/bug_1506.in
@@ -0,0 +1,60 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /Names <<
+    /Dests 7 0 R
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 4
+  /Kids [
+    3 0 R
+    4 0 R
+  ]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Pages
+  /Parent 2 0 R
+  /Count 2
+  /Kids [
+    5 0 R
+    5 0 R
+  ]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Pages
+  /Parent 2 0 R
+  /Count 2
+  /Kids [
+    5 0 R
+    6 0 R
+  ]
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 100 200]
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 100 300]
+>>
+endobj
+{{object 7 0}} <<
+  /Names [
+    (First) [6 0 R]
+  ]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1506.pdf b/testing/resources/bug_1506.pdf
new file mode 100644
index 0000000..7395fe1
--- /dev/null
+++ b/testing/resources/bug_1506.pdf
@@ -0,0 +1,74 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /Names <<
+    /Dests 7 0 R
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 4
+  /Kids [
+    3 0 R
+    4 0 R
+  ]
+>>
+endobj
+3 0 obj <<
+  /Type /Pages
+  /Parent 2 0 R
+  /Count 2
+  /Kids [
+    5 0 R
+    5 0 R
+  ]
+>>
+endobj
+4 0 obj <<
+  /Type /Pages
+  /Parent 2 0 R
+  /Count 2
+  /Kids [
+    5 0 R
+    6 0 R
+  ]
+>>
+endobj
+5 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 100 200]
+>>
+endobj
+6 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 100 300]
+>>
+endobj
+7 0 obj <<
+  /Names [
+    (First) [6 0 R]
+  ]
+>>
+endobj
+xref
+0 8
+0000000000 65535 f 
+0000000015 00000 n 
+0000000102 00000 n 
+0000000183 00000 n 
+0000000280 00000 n 
+0000000377 00000 n 
+0000000454 00000 n 
+0000000531 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 8
+>>
+startxref
+587
+%%EOF
diff --git a/testing/resources/bug_1549.in b/testing/resources/bug_1549.in
new file mode 100644
index 0000000..18285ca
--- /dev/null
+++ b/testing/resources/bug_1549.in
@@ -0,0 +1,63 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 100 150]
+  /Contents 4 0 R
+  /Resources 5 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+20 0 0 20 0 100 cm
+/Im1 Do
+Q
+BT
+/Cs1 cs
+1 0 0 0 scn
+30 20 50 40 re f
+ET
+endstream
+endobj
+{{object 5 0}} <<
+  /ColorSpace <<
+    /Cs1 /DeviceCMYK
+  >>
+  /ProcSet [/PDF /ImageB]
+  /XObject <<
+    /Im1 6 0 R
+  >>
+>>
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 1
+  /Height 1
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter /ASCIIHexDecode
+  {{streamlen}}
+>>
+stream
+33
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1549.pdf b/testing/resources/bug_1549.pdf
new file mode 100644
index 0000000..946ba43
--- /dev/null
+++ b/testing/resources/bug_1549.pdf
@@ -0,0 +1,76 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 100 150]
+  /Contents 4 0 R
+  /Resources 5 0 R
+>>
+endobj
+4 0 obj <<
+  /Length 74
+>>
+stream
+q
+20 0 0 20 0 100 cm
+/Im1 Do
+Q
+BT
+/Cs1 cs
+1 0 0 0 scn
+30 20 50 40 re f
+ET
+endstream
+endobj
+5 0 obj <<
+  /ColorSpace <<
+    /Cs1 /DeviceCMYK
+  >>
+  /ProcSet [/PDF /ImageB]
+  /XObject <<
+    /Im1 6 0 R
+  >>
+>>
+endobj
+6 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 1
+  /Height 1
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter /ASCIIHexDecode
+  /Length 3
+>>
+stream
+33
+endstream
+endobj
+xref
+0 7
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000245 00000 n 
+0000000370 00000 n 
+0000000494 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 7
+>>
+startxref
+679
+%%EOF
diff --git a/testing/resources/bug_1558.in b/testing/resources/bug_1558.in
new file mode 100644
index 0000000..439038c
--- /dev/null
+++ b/testing/resources/bug_1558.in
@@ -0,0 +1,54 @@
+{{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
+  /Resources <<
+    /Font <<
+      /F1 4 0 R
+    >>
+  >>
+  /Contents 5 0 R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+12 40 100 30 re
+W
+n
+0 g
+BT
+20 100 Td
+/F1 12 Tf
+(Clipped Text) Tj
+0 -50 Td
+/F1 12 Tf
+(Unclipped Text) Tj
+ET
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1558.pdf b/testing/resources/bug_1558.pdf
new file mode 100644
index 0000000..2feec46
--- /dev/null
+++ b/testing/resources/bug_1558.pdf
@@ -0,0 +1,66 @@
+%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
+  /Resources <<
+    /Font <<
+      /F1 4 0 R
+    >>
+  >>
+  /Contents 5 0 R
+>>
+endobj
+4 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+5 0 obj <<
+  /Length 111
+>>
+stream
+q
+12 40 100 30 re
+W
+n
+0 g
+BT
+20 100 Td
+/F1 12 Tf
+(Clipped Text) Tj
+0 -50 Td
+/F1 12 Tf
+(Unclipped Text) Tj
+ET
+Q
+endstream
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000283 00000 n 
+0000000361 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+524
+%%EOF
diff --git a/testing/resources/bug_1574.in b/testing/resources/bug_1574.in
new file mode 100644
index 0000000..5d7a08d
--- /dev/null
+++ b/testing/resources/bug_1574.in
@@ -0,0 +1,60 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 200 300]
+  /Resources <<
+    /Font <<
+      /F1 4 0 R
+    >>
+  >>
+  /Contents 5 0 R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+0 0 50 75 re
+W* n
+1 1 1 rg
+0 150 m
+100 150 l
+100 0 l
+0 0 l
+0 150 l
+h
+f*
+q
+0 0 0 rg
+BT
+20 50 Td
+/F1 12 Tf
+(Text) Tj
+ET
+Q
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1574.pdf b/testing/resources/bug_1574.pdf
new file mode 100644
index 0000000..df27418
--- /dev/null
+++ b/testing/resources/bug_1574.pdf
@@ -0,0 +1,72 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 200 300]
+  /Resources <<
+    /Font <<
+      /F1 4 0 R
+    >>
+  >>
+  /Contents 5 0 R
+>>
+endobj
+4 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+5 0 obj <<
+  /Length 124
+>>
+stream
+q
+0 0 50 75 re
+W* n
+1 1 1 rg
+0 150 m
+100 150 l
+100 0 l
+0 0 l
+0 150 l
+h
+f*
+q
+0 0 0 rg
+BT
+20 50 Td
+/F1 12 Tf
+(Text) Tj
+ET
+Q
+Q
+endstream
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000283 00000 n 
+0000000361 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+537
+%%EOF
diff --git a/testing/resources/bug_1591.in b/testing/resources/bug_1591.in
new file mode 100644
index 0000000..7632e13
--- /dev/null
+++ b/testing/resources/bug_1591.in
@@ -0,0 +1,106 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 160 400]
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+0 0 0 rg
+BT
+/F1 1 Tf
+240 0 0 240 50 50 Tm
+0.2 Tw
+0.05 0 Td
+(1) Tj
+0.1 0 Td
+(2) Tj
+0.05 0 Td
+(1) Tj
+ET
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type3
+  /CharProcs 6 0 R
+  /Encoding 7 0 R
+  /FirstChar 49
+  /FontBBox [-1 -8 28 27]
+  /FontMatrix [0.001 0 0 0.001 0 0]
+  /LastChar 50
+  /Name /F1
+  /Resources <<
+    /ProcSet [/PDF /ImageB]
+  >>
+  /Widths [8 10]
+>>
+endobj
+{{object 6 0}} <<
+  /uniE022 8 0 R
+  /uniE023 9 0 R
+>>
+endobj
+{{object 7 0}} <<
+  /Type /Encoding
+  /Differences [49 /uniE022 /uniE023]
+>>
+endobj
+{{object 8 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+6 0 0 48 6 0 cm
+BI
+/W 6
+/H 48
+/BPC 1
+/IM true
+ID
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+EI
+Q
+endstream
+endobj
+{{object 9 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+BI
+/W 4
+/H 4
+/BPC 1
+/IM true
+ID
+xx
+EI
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1591.pdf b/testing/resources/bug_1591.pdf
new file mode 100644
index 0000000..3ec0a13
--- /dev/null
+++ b/testing/resources/bug_1591.pdf
@@ -0,0 +1,122 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 160 400]
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Length 102
+>>
+stream
+0 0 0 rg
+BT
+/F1 1 Tf
+240 0 0 240 50 50 Tm
+0.2 Tw
+0.05 0 Td
+(1) Tj
+0.1 0 Td
+(2) Tj
+0.05 0 Td
+(1) Tj
+ET
+endstream
+endobj
+5 0 obj <<
+  /Type /Font
+  /Subtype /Type3
+  /CharProcs 6 0 R
+  /Encoding 7 0 R
+  /FirstChar 49
+  /FontBBox [-1 -8 28 27]
+  /FontMatrix [0.001 0 0 0.001 0 0]
+  /LastChar 50
+  /Name /F1
+  /Resources <<
+    /ProcSet [/PDF /ImageB]
+  >>
+  /Widths [8 10]
+>>
+endobj
+6 0 obj <<
+  /uniE022 8 0 R
+  /uniE023 9 0 R
+>>
+endobj
+7 0 obj <<
+  /Type /Encoding
+  /Differences [49 /uniE022 /uniE023]
+>>
+endobj
+8 0 obj <<
+  /Length 105
+>>
+stream
+q
+6 0 0 48 6 0 cm
+BI
+/W 6
+/H 48
+/BPC 1
+/IM true
+ID
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+EI
+Q
+endstream
+endobj
+9 0 obj <<
+  /Length 42
+>>
+stream
+q
+BI
+/W 4
+/H 4
+/BPC 1
+/IM true
+ID
+xx
+EI
+Q
+endstream
+endobj
+xref
+0 10
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000309 00000 n 
+0000000463 00000 n 
+0000000724 00000 n 
+0000000779 00000 n 
+0000000856 00000 n 
+0000001013 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 10
+>>
+startxref
+1106
+%%EOF
diff --git a/testing/resources/bug_1646.pdf b/testing/resources/bug_1646.pdf
new file mode 100644
index 0000000..8728c1b
--- /dev/null
+++ b/testing/resources/bug_1646.pdf
@@ -0,0 +1,77 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids[ 3 0 R ]
+>>
+endobj
+2 0 obj <<
+  /Type /Catalog
+  /Pages 1 0 R
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 1 0 R
+  /Resources 6 0 R
+  /MediaBox[ 0 0 64 64]
+  /Contents[ 5 0 R ]
+>>
+endobj
+4 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /Name /I1
+  /Width 5000
+  /Height 5000
+  /BitsPerComponent 8
+  /ColorSpace /DeviceCMYK
+  /Filter [/ASCIIHexDecode /FlateDecode /DCTDecode]
+  /Decode [1.0 0.0 1.0 0.0 1.0 0.0 1.0 0.0]
+  /Length 734
+>>
+stream
+789cedcd3d4ec2001806e00f680529d4165a643671f112c6901095b09838b01983899bd7e02c9ec
+2c143f8b37802af50cba81770799eef9ddebcc9d7bc355f515c2faf96d1e974e2b2bd68be23bfd8
+3edd3fc436f69acf5844afdbdda795b44907699a24e9b0df3f188c86a35136ccb2713e29c679996
+759312bca6955d7f5e8e8783eabe693aaae9a97a806d3dd74972c4e625dc6a68c5519cd6bd4ede3
+f8edb0adabe26f1bcd47e4c922d6b18955c4793cdf9ddedc9e3dfe9d01000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000fc835ef
+3fe03ce2e1eba
+endstream
+endobj
+5 0 obj <<
+/Length 28
+>>
+stream
+q
+64 0 0 64 0 0 cm
+/I1 Do
+Q
+endstream
+endobj
+6 0 obj <<
+  /ProcSet [/PDF /ImageC]
+  /XObject<</I1 4 0 R >>
+>>
+endobj
+xref
+0 7
+0000000000 65535 f 
+0000000015 00000 n 
+0000000079 00000 n 
+0000000132 00000 n 
+0000000247 00000 n 
+0000001253 00000 n 
+0000001330 00000 n 
+trailer <<
+  /Size 7
+  /Root 2 0 R
+>>
+startxref
+1402
+%%EOF
diff --git a/testing/resources/bug_1752_truetype_font.fragment b/testing/resources/bug_1752_truetype_font.fragment
new file mode 100644
index 0000000..5dc8ea7
--- /dev/null
+++ b/testing/resources/bug_1752_truetype_font.fragment
@@ -0,0 +1,184 @@
+789ced5b0b7054d779fecf7decae9ebb1292102c4877b968179090848431
+60d95aa45d21211e42d2925df1b056da95b4a0d7689757c60fe23c4cd638
+76d2c44d1c9a382e75e28431579452d9a169ea3ab1f31aa74e26f5b46eea
+f1d86de201c7751c9a3ab0dbfffcf7ac9040c4c4339d691b16eefdbff39f
+73feff3fff77cebde7de0bc000a0104f0a682d2ddd5b3efc93879e00b865
+2d6a176df4f95b6039d4022cf82996dd1b3bb675bd19cc6e07a83f07c0ee
+dad81568ea3fb7e202d65fc2e35bdbba6aea0e157ce1d758f7356cdfdb3f
+121ecf5d9c857512da604dfd07125ad9b79d5900793e2c4b03e383236b72
+4a7201e40480f5cc60383e0e25a0a3ade7b0bf6370f8f0c06b254f3e0890
+df8b012e1b8a8623d26b3ff80bec5b85f56b865091f74b3989e50896970e
+8d240e2d5fabf4a03f1b96970d8ff587d5f50ab6cd6fc572c948f8d0b8d4
+0c7f86ed8f62591b0d8f446b7bbe588709c0b2551a1f8b27c00eff82fe8f
+71fbe313d1f181334ffe3b40ce32b4b91078ae58f6ed2b7f7bfcc777da1b
+7e03e5dc0dc08b2bbe1912722740fabcdaa3fe008b3690c0fc613fdb8397
+3127ea3ff2a2da439666feb249930d4b40a6b2040e980fbbb0ebf7a08f34
+b23a9f3d0c2ad8d447d57ad4979b52fe071890c0264939aaa4a88a2429af
+c27d692f1c4a631f17efb865dbd66de0850db0d18cc1f6a014d4807df95f
+311665937a96670272a4f3e023c75fc7f816e3510fcfabf9703746fcbc12
+c7c383e59fc0dd3c12712c12a368c012a332ce21e03349a1f2c6348f6143
+3a9d7e36335e6681e9a13389c639fb77755ee6fac98a6ab1dab26ea0e5ff
+d29f77e3ce9e5030d0ddd5b9bd63dbd62d9bdb37b5b56e6cf1fb9a9b3678
+1befb8bde1b6f5ebd6debae69655b535d52bab9679dc154bf525aef2d2a2
+02873d3f2f273bcb66b5a88a2c31a8d20cd6eb37e40aada025acfbf570eb
+ca2acd5f3ae45b59e5d75b7a0d2dac192814b7deda4a2a3d6c68bd9ae146
+119ea1ee35bcd872e0aa965eb3a577ba2573680dd0c05de89af1239fae4d
+b19eed41c40ffaf490665c20bc85b0e2a6421e165c2eec4151f16835bfd1
+726028e9efc518d9644e76b3de1ccd5e590593d93908731019cbf4f149b6
+ec0e46405ae65f3f89133c8fbbc591fac311a3637bd0ef73ba5ca195556d
+46beeea32a68269386a5d9b092492dc6438707b4c9aa6f278f4d39a0afb7
+3237a247c2bb82861cc6be49d99f4cde6f14541acb759fb1fcc3af97e2c8
+a34695eef31b95dc6a7be7b49ff62b2e99a15638742df91bc0e1e817cecf
+d68485c652e1f80d706848cd06eb0cbaf8cfd982b94e265b74ad25d99b0c
+4fa58ff4e99a434f4ee6e626c7fd986ee808a289a9f4330f388d966321c3
+d13bc4d687c4d05b3adb8d79db77060da9a2451b0aa306ff36eaaeb54e57
+c1749b8eeb5503a605938319d6781a3b8249cd9f7cc0e772bafc3eac7619
+47b607797a1e98d2a0cf791abc35952143eae535dfced4140778cd914c8d
+a9f4626275e4b8bd2b9834948ab688eec7cc3f10368ef4e12cdbcb09d21d
+46fe45a74b4f161668eb6a42d49647d116896986eac66461af991d70fef0
+2e490715f22f9ae282131db80b0ab5753a9ae176fcbabf57fc3d30548a06
+344c786ba53921ba8386d787c01b16ccf9276b6bb047b817898bf98854a3
+461f378af4a669962939b1ae207511dd8ca26603ef66a29751e3a7f585e9
+ebf59921705bfaf6e0d3509f7e7572b5e6fccb7a580d211f6f5cd28cb3cd
+ed4f0623034679af3382eb6f400b3a5d8637844c87f46034c4a71f6668f9
+ab4e9a24219a33ddc1f62ebd7d7b4f70ad08c4ace0e6940aff5566f4a0d3
+348313d1b055d8b4a0e49443d8d0810aad0581ded48067c35a61c3c38109
+272d9fc04d0d5a903921d31ac330966bfea84fb4e3e55946553ead9a5b33
+d62cbc88769a5b9dae90cbfcadac92b05a138eb1878d27b5355385972bac
+b0e13c6d6e2515cf65299ffc5a508fea217d4833bc1d413e369e1ecab248
+06e55c70d53dab3423599826706175a6c09369b4543a6726d7d84865be6c
+0ca95bc7c982ebea085675188c37d8490bd6897329e49cad479569e9f7f7
+751a6dd7766d339df29e499bdede95e431eb224ec08414e0d1b61753d185
+73b777ae0a9cb2deb0a8d0927a5b24a977051b9c9483cee0ddce0ff31116
+423b6bef6e5a593529b1a6499d1ddd3ee96547bb7a8278256d7ada8137fc
+a3ddc1d312939a7b9b42934bb13ef8b486f725d24a5ccb95bca0f102b7d6
+89051bb5773eed053842b50a29a8dc3fc58074b68c8e41ff9464ea1ca623
+3739f2e24dbf7f4a316bbc99d60aea6ca6ee08e9e837099c2c3e0abe0e4b
+879064bcadf8b5089f2077858692bd21bec8a004d3837f99c1f43b305dfa
+1d934cb2e41ad97ab4c9c8d19bb8be91eb1b4dbd85ebad38355909c3eeb3
+b9c3a5a82dfc9e33e9b8c05319c28b49d2f1c64a6f3acb55af054063bfd2
+d8c31abb57631a2b0b94d7cb81f232662f6b2c3b5526efece80934f66ceb
+917a583010ea08066a83de205e9183ca873a7604b41deced1d6c07eb0e04
+3aba03e3ddacbc9bd5767bbb8f74ffb8fbd56eb5760bdb226d0d6c8cb404
+1a5b4eb5482dac3db0a9a32dd086725bc7d640fd56b615e1e68ef640793b
+b3b737b69f6a97bb3a3a03b775b24ed611d8ded111b87529bb271c290fde
+cd0ec6cbca0fc43794ef8fd79527f0f8d3389b8ab397e36c417d69c08a51
+5be474f9486c41f970cc5dbe2f767bf9de586bf95763acc7cd6cee56b7d4
+ea3eea7ec12ddfab3fa4bfa8cbf3ea0bbd230195c901850f586676b9513e
+25cb91ddabcb0ff5b3bedd7a7978777d79efee15e577e2b107f1d4ee1776
+4b2feffec56e6951c41928a92f0e38eaed01bb7d9b5d2ab7bf6897ecf6b4
+5db2e02626809bf7c018dc0ba7e057a038801d29612a9b620f4f76775556
+b64f59d378dfcbead869b0a34645173f7bb7f71896a306047a76062719fb
+54e8e30f3e084d8bdb8d3a5c36bd8b43ed4684af1f0e8e20702c9e2c81a6
+503c5e59b9279ed85f5959194f54f25fdc04f17822612a78cd7e5193a98e
+9b656a453dafd4a0aab2740f80a50872a00377d2f3613f9d67fd945628e2
+327d7ef6395594be88fbecb330efd267d31752f7a6f6a6cea537a53ef0ae
+d27c1661f5c077c617211f2de7a5ce5c4aa5df4df7a6df9525c865f7a7df
+91bd902fdd92fe35eef3f3b0dde7e021b81f77f5c7e104bc010f4c1b7b1c
+9e8047b1eea3b0775af705f812b6fa347cfc8a4f560c5f049f5a40852afa
+13811fb25cd6c292ec59898f5be2cf1552440de0538215aa2719d4349cb6
+2af75ea89bb4a8af349c962584302973b5cad5a7ad9623971a4e33aeaf2f
+701554b80a5c3e494b2d659f4f0da981f7bee1537e04e2c94175aae7d06a
+1e54794b14ab2d4fceb7672b6a9e140be5e529aa0c4a2134d63716aeaba9
+2c28847505856cfebaba55b52ed925ebac3e8bc91e8b5556761997cf3df5
+8c74db13d2fa54cf899632d713ec3ba9dbd473eff9a421f6cd7f3ed49ff2
+037fe2791eb9fc0e66d50ea5f864d5e7bdc56ab31539162eb42b45396565
+457679896e5de0702cd8177ad9c1d63b986c7730bbea70d8014af78540b6
+2fdc17b263409550da48f194d6dcb967f79dbb775f09aea0feca9f55b5f3
+5c9a525c64b11697b1e2224577cd73d5adb965b5bb922d6026d29f675f61
+6ea67eeae8935f4ebdfbf6e5d414cb3f79fc1b4fbfc33cc6d7cf9c51cf9e
+fae67d4f2ec85e6c7cfab957e4d6d18f1cda7bf97397df3a9afce8ddfc49
+ebeef479e5457c529d0f6e6f916c2fceb2cba50be6412c344fc9b5c442b9
+f3a643a5c856d5b22550e080faba5b8bf399ae41c1eac2a5f575f3ad6e79
+ddf9d43bccf2cef1bfdf7d723cf537a93fff2a6b7cf91727379d5297a79e
+4bbd957a23f5a3f58f2d59c03ec9865e63dd533b8e77107fcff32761cca7
+0d9f7597798bf215c852b20a0ad5bc7d215556f2f785942bc962ebd69939
+610e58ce0a5c1ac80e70e905f59ada93da9f7a281563cfb200bbebd9d485
+d49b6f3f2f754abf4c3d92fa887a36f5c9d409369f95ff8ecf5ac67dcabf
+459f3910f0aeb264673305170e9373f32c59b1d0cb16b6dec2b22ccc0e96
+c72c52a16cb130495263214966d9b110bb86bb3dbb89bc69e256d5f2d88a
+5de621fff6d28bf2aacbf74bdd974f4a87d5b38fa7d63e76f922cc8a230b
+5abc95fca158c128b27378146de8dfd268b9d3326679c8f2658b2507c390
+58a1198719014e68f43ec335ce693de398dd3b455e3b2f3f853e2fbff715
+c1b57a1b72ed84dbbd5abe546493162c70dae4458bc1190b210fb9b905c8
+b8a2ce8f85d479b386397b7ccc55309b7b8e9189ba5b71bc4decbf126f7f
+29753af599b3ec43bf7cf3fb2d7f3795fa5dea9fd84296f727c7527f2b2b
+97eb2bdcec18eb7f83edf8ab1d8f75d3e4f879ea259d9de6592963a3d3cf
+ff35d02b30c31c9d1058c2b5fe2d816558c432ef0b70e6b05502ab789d0b
+086c416e13025ba153d205b641b1f4b8c0595024bd26703668f23a8173a0
+4cfebcc0b9e05336089c07d5caeb02e7c30a35c6df7e28fc9abb87a2e298
+41213c2cb084ad9e125886b5f05d811528648b0556a194dd26b0051cac4f
+602b9c60770b6c8315d20181b360993425703678a5ff1438071ae41e8173
+e1a8fcd702e7c12ee51302e7433732dc0c63300e8761026230084390000d
+eaa01656e159838d583b86fa618862a90d46a11faa116d40cd30cacee95e
+712a455146d1d6013c47b025348f8d1f9e880d0e25b4bada5575dac6b1b1
+c1e1a8d636da5fad6d181ed63a79555ceb8cc6a31307a211ecb015fd25f0
+d0a00bc2e82f8eaab1c498d6151e45c83d0cc27ef41d462fd0191ddc3f1c
+46b0057b8c52cfc3381a33d6116c3388f18de259839578cc617bcbd8e858
+e2f0388634121e8c8d0e6a2bb519ee7e7f303b68a87174c19d6b98b26a4c
+dc6aac884ec46363a3daaaeadad5578ccc3271b5fd1825308c47026d8631
+79511ac004ec43dd180cfc1e32b8d9084adefe30ca3eaa9fa05471bb098a
+334a6d6214693f6912d8de2cefc5944e50db089efbb1c61c539c5348238e
+c5b5b096980847a223e1897ddad8c02c2ac3a3116d247c58eb8b6a13d1c1
+583c119d8846b4d8a8d61f9d488451eedd3f118b4762fd094c4bbcfa46f8
+ca0cef5a86b8c7b92cf868f6f1f9384a698ca2bd9119fd7dd1786c70544b
+44c3a89dd9d8cc4c1fe5ee5ab391b9cd9ad670947d87b5691791992ef8aa
+48a091f5780dabc1b18c91a96a347485c66ad28f60fd381ea3623ad460e7
+44627c7d4d4dff58245a3d4899aeee1f1ba919af414f6335571b3f487faa
+d1d0ece863b3927ac55907b2cdc3d94f5cc7716a9a8b364633238e2dba85
+85d9799ac09643d8f320b60bd352cf047af0e0c1ea11918698491405dc31
+3116d9df9f8877e1fa8ef547e335ddd840e46ea26b68ec607f388e56360b
+d7a36494d3b19f263577c9933e44546cc090c2d8ce2ccdee53859aab1764
+1d2d48d88c8e47e348d5fed14874424b0c45b50de3e17e14a2a64acbacd8
+baeadaebe5363cc379353a98c09cd420893383e099db8c53b719fcb8b6bb
+f0bc3213c48c3c85c979f5d8c460cdb01940bc66735bb37f6b977f250530
+fd363c3d800b7fae5f36dd0f25580a15e0060f2c83e5b0827432de1fb2f1
+da6e4da7717fcae89e05500265d89ad7f27b92223c3080e97b2bb779045e
+07b5fff0045ed923d103755085e730b40c4e44f7c19ee17062142f140ac5
+95265bd9c0b66ce8d47834a851454c0aa2d935a63f05ef68337bcfc4d670
+df813854f50d1f1c80f5788e83b7bf7f641c368fc71371e89cd8373100c1
+03e1c47ebab7aad45bc511570a9ffcce5f85c9aee66b677a4cb57466a2cd
+2a9c4fc348ec5df03138069fa5279d937006cec173f043f829bc82a33f0f
+efc225a6e0734b115bc496b22ab69a35301fdbccbad92e1661c32cc1ee62
+1fc3adcb67d97176829d6467d839f61cfb21fb297b85bd8e23445feca899
+0bf688287f5e94bf2fe4bf9952924c69f98c29ad6b85fcb9296d1f13f267
+a6cc8a08f98e29b38f9832275fc8e3a6cc5d21e4b890c25fde2e215f3265
+7eab90df32a57db5905f37a54313f21121df356541a7908690174d59d820
+e47d42be60ca7922be79dd423e2ae4aba62c720b392ca4a82f12fd8b579b
+792c16f5c58f09993265499d908f9bed4a4ed0572986ab80efbfaecc7366
+d683f955ad0816e1fae173a76a468bb90e45cc23b3673edae4f617ced1ce
+fc92f5fe6d78ab20ecc2750b109973857fd0df4c6fadf0199ce54fc069dc
+2b7f1f7e06afc15bf01eb3b04256c656b035ac896d653d6c804db07bf029
+fd11f6387b8a3dc3becb5e623f676fb28b9224e54b0b25b75427dd216d92
+82d2809490ee931e96be249d949e915e907e26bd2ebd23839c2f2f9257c8
+6be516b95bee93c7e57be463f2a3f2d7e4b3f273f24bf2abf25bf225255b
+2955dc740de2e35f2eae461cc994158e2d8455c256c216c236c256c25984
+6d84b3096711ce219c4d3897700ee13cc2b984f309e711b613ce27ec206c
+275c40d841b8907001e179844d568b08cf235c4cb8887009e162c2f30997
+102e253c9ff002c2a5627670bc80b093f042f13d956327617a32200d3e17
+115e4cb89c7019618d70396117618df012c22ec23a61f3abf252c23ae10a
+c24b09bb095710f61076135e46df7115d2015dc5394b1ec193423c790453
+0a31e5115c29c49547b0a5105b1ec197427c7904630a31e6119c29c49947
+b0a6106b1ec19b42bc7904730a31e711dc29c49d47b0a7107b1ec19f42fc
+7904830a31e8111c2ac4a147b0a8108b1ec1a3423c7a04930a31e9115c2a
+c4a547b0a9109b1ec1a7427c7a04a30a31ea119c2ac4a947b0aa10ab1ec1
+ab42bc7a04b30a31eb11dc2ac4ad47b0ab10bb1ec1af42fc7a04c30a31ec
+111c2bc4b147b0ac10cb1ec1b3423c7b04d312c645f76dba365dffda928b
+635b8277e03adc2635c126dc30eec1add104de5db91d09cfa654845485b4
+086915d226760759624720d1acc920751a59a691751ad96e46fb3f1aed42
+9c87593463ae1fab8ad19a77d2aa1b8eeec62c67d3aed58d7bba357007de
+cb3af17e3970c33e149101791a29d3489d4696697433aa9b51fd7145558a
+ab967f2a5a7add98148caa90ae45ef17c78d58b3095bfcbad6002dd0f1be
+5615c85ccd32489946ea34bae9fda6f79bdeffb8bc97a0652bf03df0dcbe
+2df884605e4f6b710f753dbf0a64aea2ca740419a4fe3ff352848cf1b71f
+da9c3e3257faa5d7d857a63952a63dddb476d3daff656b85b89ef81bc3b2
+396c49b4de3276326faee50fdcab0cd7750e9657c0b5bf9916b2708c0b71
+7c6e7c8eaa83b5b807f441e66d22902500f3dda14a670b9dad7436ff7f41
+169db3df375b32fd3b8bd2eb5e49fed077a33c42f3dd28ccba2facc15d6c
+cf7572957bd548de5f9379476825cd152e9661b6e67ec7aacc8966eec66b
+e9d97a748e1833ed2d94ad7cccc7b539ff43da70f441de3dcb74175d046c
+3ac38cb26d2259a0cc13c400dc37c7f79bb5f4fdc647df6f3ae0ca571736
+03c375309b85a5482c9e80fc7dd18951cc3f40e66deecc5925d1584b690e
+306812f23e60f44e8af35984f9e707ff22a5a6cf43161e38163a67beec58
+684edf789ef87c71ccc8d2ec3979e376f89584c7b662ce8c9b6b3ba35567
+651f5eb9a7f43fb89cf97f80fe1b5550eca6
diff --git a/testing/resources/bug_1768.in b/testing/resources/bug_1768.in
new file mode 100644
index 0000000..3f06f7f
--- /dev/null
+++ b/testing/resources/bug_1768.in
@@ -0,0 +1,317 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /MarkInfo <<
+    /Marked true
+  >>
+  /StructTreeRoot 6 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+  /MediaBox [0 0 612 792]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q /P << /MCID 0 >> BDC Q
+q
+0 0 612 792 re W n
+BT 8 0 0 8 72 712.75 Tm /F1 1 Tf (AAA) Tj ET EMC
+/P << /MCID 1 >> BDC BT 8 0 0 8 532.8 712.75 Tm /F1 1 Tf (111) Tj ET EMC
+
+0.2835 w 2 J
+72 659.03 332.64 24.56 re S
+72 622.98 332.64 36.06 re S
+404.64 622.98 85.68 36.06 re S
+404.64 659.03 85.68 24.56 re S
+490.32 622.98 85.68 36.06 re S
+490.32 659.03 85.68 24.56 re S
+
+/P << /MCID 2 >> BDC BT 10 0 0 10 257.37 667.84 Tm /F1 1 Tf (2) Tj ET EMC
+/P << /MCID 3 >> BDC BT 10 0 0 10 428.73 667.84 Tm /F1 1 Tf (3) Tj ET EMC
+/P << /MCID 4 >> BDC BT 10 0 0 10 514.41 667.84 Tm /F1 1 Tf (4) Tj ET EMC
+/P << /MCID 5 >> BDC BT 10 0 0 10 259.87 637.54 Tm /F1 1 Tf (BB) Tj ET EMC
+/P << /MCID 6 >> BDC BT 10 0 0 10 431.23 637.54 Tm /F1 1 Tf (CC) Tj ET EMC
+/P << /MCID 7 >> BDC BT 10 0 0 10 528.16 637.54 Tm /F1 1 Tf (DD) Tj ET EMC
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+{{object 6 0}} <<
+  /Type /StructTreeRoot
+  /K 7 0 R
+>>
+endobj
+{{object 7 0}} <<
+  /Type /StructElem
+  /S /Document
+  /P 6 0 R
+  /K [8 0 R]
+>>
+endobj
+{{object 8 0}} <<
+  /Type /StructElem
+  /S /Document
+  /P 7 0 R
+  /K [9 0 R 10 0 R]
+>>
+endobj
+{{object 9 0}} <<
+  /Type /StructElem
+  /S /Table
+  /P 8 0 R
+  /K [11 0 R]
+>>
+endobj
+{{object 10 0}} <<
+  /Type /StructElem
+  /S /Table
+  /P 8 0 R
+  /K [21 0 R 22 0 R]
+>>
+endobj
+{{object 11 0}} <<
+  /Type /StructElem
+  /S /TR
+  /P 9 0 R
+  /K [12 0 R]
+>>
+endobj
+{{object 12 0}} <<
+  /Type /StructElem
+  /S /TD
+  /P 11 0 R
+  /K [13 0 R]
+>>
+endobj
+{{object 13 0}} <<
+  /Type /StructElem
+  /S /Table
+  /P 12 0 R
+  /K [14 0 R]
+>>
+endobj
+{{object 14 0}} <<
+  /Type /StructElem
+  /S /TR
+  /P 13 0 R
+  /K [15 0 R 16 0 R]
+>>
+endobj
+{{object 15 0}} <<
+  /Type /StructElem
+  /S /TD
+  /P 14 0 R
+  /K [17 0 R]
+>>
+endobj
+{{object 16 0}} <<
+  /Type /StructElem
+  /S /TD
+  /P 14 0 R
+  /K [19 0 R]
+>>
+endobj
+{{object 17 0}} <<
+  /Type /StructElem
+  /S /P
+  /P 15 0 R
+  /K [18 0 R]
+>>
+endobj
+{{object 18 0}} <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 17 0 R
+  /K 0
+>>
+endobj
+{{object 19 0}} <<
+  /Type /StructElem
+  /S /P
+  /P 16 0 R
+  /K [20 0 R]
+>>
+endobj
+{{object 20 0}} <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 19 0 R
+  /K 1
+>>
+endobj
+{{object 21 0}} <<
+  /Type /StructElem
+  /S /TR
+  /P 10 0 R
+  /K [23 0 R 24 0 R 25 0 R]
+>>
+endobj
+{{object 22 0}} <<
+  /Type /StructElem
+  /S /TR
+  /P 10 0 R
+  /K [32 0 R 33 0 R 34 0 R]
+>>
+endobj
+{{object 23 0}} <<
+  /Type /StructElem
+  /S /TD
+  /P 21 0 R
+  /K [26 0 R]
+>>
+endobj
+{{object 24 0}} <<
+  /Type /StructElem
+  /S /TD
+  /P 21 0 R
+  /K [28 0 R]
+>>
+endobj
+{{object 25 0}} <<
+  /Type /StructElem
+  /S /TD
+  /P 21 0 R
+  /K [30 0 R]
+>>
+endobj
+{{object 26 0}} <<
+  /Type /StructElem
+  /S /P
+  /P 23 0 R
+  /K [27 0 R]
+>>
+endobj
+{{object 27 0}} <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 26 0 R
+  /K 2
+>>
+endobj
+{{object 28 0}} <<
+  /Type /StructElem
+  /S /P
+  /P 24 0 R
+  /K [29 0 R]
+>>
+endobj
+{{object 29 0}} <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 28 0 R
+  /K 3
+>>
+endobj
+{{object 30 0}} <<
+  /Type /StructElem
+  /S /P
+  /P 25 0 R
+  /K [31 0 R]
+>>
+endobj
+{{object 31 0}} <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 30 0 R
+  /K 4
+>>
+endobj
+{{object 32 0}} <<
+  /Type /StructElem
+  /S /TD
+  /P 22 0 R
+  /K [35 0 R]
+>>
+endobj
+{{object 33 0}} <<
+  /Type /StructElem
+  /S /TD
+  /P 22 0 R
+  /K [37 0 R]
+>>
+endobj
+{{object 34 0}} <<
+  /Type /StructElem
+  /S /TD
+  /P 22 0 R
+  /K [39 0 R]
+>>
+endobj
+{{object 35 0}} <<
+  /Type /StructElem
+  /S /P
+  /P 32 0 R
+  /K [36 0 R]
+>>
+endobj
+{{object 36 0}} <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 35 0 R
+  /K 5
+>>
+endobj
+{{object 37 0}} <<
+  /Type /StructElem
+  /S /P
+  /P 33 0 R
+  /K [38 0 R]
+>>
+endobj
+{{object 38 0}} <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 37 0 R
+  /K 6
+>>
+endobj
+{{object 39 0}} <<
+  /Type /StructElem
+  /S /P
+  /P 34 0 R
+  /K [40 0 R]
+>>
+endobj
+{{object 40 0}} <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 39 0 R
+  /K 7
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1768.pdf b/testing/resources/bug_1768.pdf
new file mode 100644
index 0000000..32b1644
--- /dev/null
+++ b/testing/resources/bug_1768.pdf
@@ -0,0 +1,364 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /MarkInfo <<
+    /Marked true
+  >>
+  /StructTreeRoot 6 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+  /MediaBox [0 0 612 792]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Length 812
+>>
+stream
+q /P << /MCID 0 >> BDC Q
+q
+0 0 612 792 re W n
+BT 8 0 0 8 72 712.75 Tm /F1 1 Tf (AAA) Tj ET EMC
+/P << /MCID 1 >> BDC BT 8 0 0 8 532.8 712.75 Tm /F1 1 Tf (111) Tj ET EMC
+
+0.2835 w 2 J
+72 659.03 332.64 24.56 re S
+72 622.98 332.64 36.06 re S
+404.64 622.98 85.68 36.06 re S
+404.64 659.03 85.68 24.56 re S
+490.32 622.98 85.68 36.06 re S
+490.32 659.03 85.68 24.56 re S
+
+/P << /MCID 2 >> BDC BT 10 0 0 10 257.37 667.84 Tm /F1 1 Tf (2) Tj ET EMC
+/P << /MCID 3 >> BDC BT 10 0 0 10 428.73 667.84 Tm /F1 1 Tf (3) Tj ET EMC
+/P << /MCID 4 >> BDC BT 10 0 0 10 514.41 667.84 Tm /F1 1 Tf (4) Tj ET EMC
+/P << /MCID 5 >> BDC BT 10 0 0 10 259.87 637.54 Tm /F1 1 Tf (BB) Tj ET EMC
+/P << /MCID 6 >> BDC BT 10 0 0 10 431.23 637.54 Tm /F1 1 Tf (CC) Tj ET EMC
+/P << /MCID 7 >> BDC BT 10 0 0 10 528.16 637.54 Tm /F1 1 Tf (DD) Tj ET EMC
+Q
+endstream
+endobj
+5 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+6 0 obj <<
+  /Type /StructTreeRoot
+  /K 7 0 R
+>>
+endobj
+7 0 obj <<
+  /Type /StructElem
+  /S /Document
+  /P 6 0 R
+  /K [8 0 R]
+>>
+endobj
+8 0 obj <<
+  /Type /StructElem
+  /S /Document
+  /P 7 0 R
+  /K [9 0 R 10 0 R]
+>>
+endobj
+9 0 obj <<
+  /Type /StructElem
+  /S /Table
+  /P 8 0 R
+  /K [11 0 R]
+>>
+endobj
+10 0 obj <<
+  /Type /StructElem
+  /S /Table
+  /P 8 0 R
+  /K [21 0 R 22 0 R]
+>>
+endobj
+11 0 obj <<
+  /Type /StructElem
+  /S /TR
+  /P 9 0 R
+  /K [12 0 R]
+>>
+endobj
+12 0 obj <<
+  /Type /StructElem
+  /S /TD
+  /P 11 0 R
+  /K [13 0 R]
+>>
+endobj
+13 0 obj <<
+  /Type /StructElem
+  /S /Table
+  /P 12 0 R
+  /K [14 0 R]
+>>
+endobj
+14 0 obj <<
+  /Type /StructElem
+  /S /TR
+  /P 13 0 R
+  /K [15 0 R 16 0 R]
+>>
+endobj
+15 0 obj <<
+  /Type /StructElem
+  /S /TD
+  /P 14 0 R
+  /K [17 0 R]
+>>
+endobj
+16 0 obj <<
+  /Type /StructElem
+  /S /TD
+  /P 14 0 R
+  /K [19 0 R]
+>>
+endobj
+17 0 obj <<
+  /Type /StructElem
+  /S /P
+  /P 15 0 R
+  /K [18 0 R]
+>>
+endobj
+18 0 obj <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 17 0 R
+  /K 0
+>>
+endobj
+19 0 obj <<
+  /Type /StructElem
+  /S /P
+  /P 16 0 R
+  /K [20 0 R]
+>>
+endobj
+20 0 obj <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 19 0 R
+  /K 1
+>>
+endobj
+21 0 obj <<
+  /Type /StructElem
+  /S /TR
+  /P 10 0 R
+  /K [23 0 R 24 0 R 25 0 R]
+>>
+endobj
+22 0 obj <<
+  /Type /StructElem
+  /S /TR
+  /P 10 0 R
+  /K [32 0 R 33 0 R 34 0 R]
+>>
+endobj
+23 0 obj <<
+  /Type /StructElem
+  /S /TD
+  /P 21 0 R
+  /K [26 0 R]
+>>
+endobj
+24 0 obj <<
+  /Type /StructElem
+  /S /TD
+  /P 21 0 R
+  /K [28 0 R]
+>>
+endobj
+25 0 obj <<
+  /Type /StructElem
+  /S /TD
+  /P 21 0 R
+  /K [30 0 R]
+>>
+endobj
+26 0 obj <<
+  /Type /StructElem
+  /S /P
+  /P 23 0 R
+  /K [27 0 R]
+>>
+endobj
+27 0 obj <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 26 0 R
+  /K 2
+>>
+endobj
+28 0 obj <<
+  /Type /StructElem
+  /S /P
+  /P 24 0 R
+  /K [29 0 R]
+>>
+endobj
+29 0 obj <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 28 0 R
+  /K 3
+>>
+endobj
+30 0 obj <<
+  /Type /StructElem
+  /S /P
+  /P 25 0 R
+  /K [31 0 R]
+>>
+endobj
+31 0 obj <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 30 0 R
+  /K 4
+>>
+endobj
+32 0 obj <<
+  /Type /StructElem
+  /S /TD
+  /P 22 0 R
+  /K [35 0 R]
+>>
+endobj
+33 0 obj <<
+  /Type /StructElem
+  /S /TD
+  /P 22 0 R
+  /K [37 0 R]
+>>
+endobj
+34 0 obj <<
+  /Type /StructElem
+  /S /TD
+  /P 22 0 R
+  /K [39 0 R]
+>>
+endobj
+35 0 obj <<
+  /Type /StructElem
+  /S /P
+  /P 32 0 R
+  /K [36 0 R]
+>>
+endobj
+36 0 obj <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 35 0 R
+  /K 5
+>>
+endobj
+37 0 obj <<
+  /Type /StructElem
+  /S /P
+  /P 33 0 R
+  /K [38 0 R]
+>>
+endobj
+38 0 obj <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 37 0 R
+  /K 6
+>>
+endobj
+39 0 obj <<
+  /Type /StructElem
+  /S /P
+  /P 34 0 R
+  /K [40 0 R]
+>>
+endobj
+40 0 obj <<
+  /Type /StructElem
+  /S /Span
+  /Pg 3 0 R
+  /P 39 0 R
+  /K 7
+>>
+endobj
+xref
+0 41
+0000000000 65535 f 
+0000000015 00000 n 
+0000000129 00000 n 
+0000000218 00000 n 
+0000000370 00000 n 
+0000001234 00000 n 
+0000001312 00000 n 
+0000001368 00000 n 
+0000001448 00000 n 
+0000001535 00000 n 
+0000001613 00000 n 
+0000001699 00000 n 
+0000001775 00000 n 
+0000001852 00000 n 
+0000001932 00000 n 
+0000002016 00000 n 
+0000002093 00000 n 
+0000002170 00000 n 
+0000002246 00000 n 
+0000002330 00000 n 
+0000002406 00000 n 
+0000002490 00000 n 
+0000002581 00000 n 
+0000002672 00000 n 
+0000002749 00000 n 
+0000002826 00000 n 
+0000002903 00000 n 
+0000002979 00000 n 
+0000003063 00000 n 
+0000003139 00000 n 
+0000003223 00000 n 
+0000003299 00000 n 
+0000003383 00000 n 
+0000003460 00000 n 
+0000003537 00000 n 
+0000003614 00000 n 
+0000003690 00000 n 
+0000003774 00000 n 
+0000003850 00000 n 
+0000003934 00000 n 
+0000004010 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 41
+>>
+startxref
+4094
+%%EOF
diff --git a/testing/resources/bug_1769.in b/testing/resources/bug_1769.in
new file mode 100644
index 0000000..a6f6682
--- /dev/null
+++ b/testing/resources/bug_1769.in
@@ -0,0 +1,72 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Resources <<
+    /XObject <<
+      /Im1 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+1000 0 0 1000 50 100 cm
+/Im1 Do
+Q
+q
+1 0 0 1 200 100 cm
+/Im1 Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 100 100]
+  /Resources <<
+    /Font <<
+      /F1 6 0 R
+    >>
+  >>
+{{streamlen}}
+>>
+stream
+0.001 0 0 0.001 0 0 cm
+BT
+/F1 24 Tf
+(w) Tj
+(o) Tj
+(r) Tj
+(l) Tj
+(d) Tj
+ET
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1769.pdf b/testing/resources/bug_1769.pdf
new file mode 100644
index 0000000..3a9fba1
--- /dev/null
+++ b/testing/resources/bug_1769.pdf
@@ -0,0 +1,85 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Resources <<
+    /XObject <<
+      /Im1 5 0 R
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Length 67
+>>
+stream
+q
+1000 0 0 1000 50 100 cm
+/Im1 Do
+Q
+q
+1 0 0 1 200 100 cm
+/Im1 Do
+Q
+endstream
+endobj
+5 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 100 100]
+  /Resources <<
+    /Font <<
+      /F1 6 0 R
+    >>
+  >>
+/Length 74
+>>
+stream
+0.001 0 0 0.001 0 0 cm
+BT
+/F1 24 Tf
+(w) Tj
+(o) Tj
+(r) Tj
+(l) Tj
+(d) Tj
+ET
+endstream
+endobj
+6 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+xref
+0 7
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000287 00000 n 
+0000000405 00000 n 
+0000000655 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 7
+>>
+startxref
+731
+%%EOF
diff --git a/testing/resources/bug_1919.pdf b/testing/resources/bug_1919.pdf
new file mode 100644
index 0000000..8331e0d
--- /dev/null
+++ b/testing/resources/bug_1919.pdf
Binary files differ
diff --git a/testing/resources/bug_306123.pdf b/testing/resources/bug_306123.pdf
index 5bd1900..8b542cc 100644
--- a/testing/resources/bug_306123.pdf
+++ b/testing/resources/bug_306123.pdf
@@ -1243,7 +1243,7 @@
 endobj
 9 0 obj <<
   /Type /Page
-  /Parent 2 0 R
+  /Parent 8 0 R
   /MediaBox [0 0 612 792]
 >>
 endobj
diff --git a/testing/resources/bug_481363.in b/testing/resources/bug_481363.in
index 32a724d..1e05346 100644
--- a/testing/resources/bug_481363.in
+++ b/testing/resources/bug_481363.in
@@ -29,7 +29,7 @@
 100 500 100 100 re b
 endstream
 endobj
-{{object 5 0)) <<
+{{object 5 0}} <<
   /Type /Font
   /Subtype /Type1
   /BaseFont /He
diff --git a/testing/resources/bug_481363.pdf b/testing/resources/bug_481363.pdf
index 53468a0..8263b3f 100644
--- a/testing/resources/bug_481363.pdf
+++ b/testing/resources/bug_481363.pdf
@@ -30,7 +30,7 @@
 100 500 100 100 re b
 endstream
 endobj
-{{object 5 0)) <<
+5 0 obj <<
   /Type /Font
   /Subtype /Type1
   /BaseFont /He
@@ -51,12 +51,12 @@
 0000000078 00000 n 
 0000000253 00000 n 
 0000000306 00000 n 
-0000000000 65535 f 
-0000000517 00000 n 
+0000000400 00000 n 
+0000000510 00000 n 
 trailer <<
   /Size 0
   /Root 3 0 R
 >>
 startxref
-621
+614
 %%EOF
diff --git a/testing/resources/bug_674771.in b/testing/resources/bug_674771.in
new file mode 100644
index 0000000..3352f30
--- /dev/null
+++ b/testing/resources/bug_674771.in
@@ -0,0 +1,56 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents [4 0 R]
+  /MediaBox [0 0 792 612]
+  /Resources <<
+    /ProcSet [/PDF /ImageB]
+    /XObject <<
+      /Im0 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+400 0 0 300 60 100 cm
+/Im0 Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 1
+  /Decode [0.0 1.0]
+  /Filter [/ASCIIHexDecode /JBIG2Decode]
+  /Height 400
+  /ImageMask true
+  /Width 400
+  {{streamlen}}
+>>
+stream
+000000003000010000001300000dea00000353000017110000171151000000000001260001000000
+3500000dea000003530000000000000000020003fffdff02fefefeff7f86530fb6c922cfff7fff7f
+ff7fff7fff7fff7fff7fff7fffac
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_674771.pdf b/testing/resources/bug_674771.pdf
new file mode 100644
index 0000000..6b2dab8
--- /dev/null
+++ b/testing/resources/bug_674771.pdf
@@ -0,0 +1,68 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents [4 0 R]
+  /MediaBox [0 0 792 612]
+  /Resources <<
+    /ProcSet [/PDF /ImageB]
+    /XObject <<
+      /Im0 5 0 R
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Length 34
+>>
+stream
+q
+400 0 0 300 60 100 cm
+/Im0 Do
+Q
+endstream
+endobj
+5 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 1
+  /Decode [0.0 1.0]
+  /Filter [/ASCIIHexDecode /JBIG2Decode]
+  /Height 400
+  /ImageMask true
+  /Width 400
+  /Length 191
+>>
+stream
+000000003000010000001300000dea00000353000017110000171151000000000001260001000000
+3500000dea000003530000000000000000020003fffdff02fefefeff7f86530fb6c922cfff7fff7f
+ff7fff7fff7fff7fff7fff7fffac
+endstream
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000317 00000 n 
+0000000402 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+808
+%%EOF
diff --git a/testing/resources/bug_680376.in b/testing/resources/bug_680376.in
index c21df24..32daee1 100644
--- a/testing/resources/bug_680376.in
+++ b/testing/resources/bug_680376.in
@@ -85,8 +85,8 @@
 >>
 endobj
 % Old-style top-level Dests dictionary. Note that FirstAlternate
-% intentionally references non-exisstant page 11 and LastAlternate
-% intentionally references non-existant object 999.
+% intentionally references non-existent page 11 and LastAlternate
+% intentionally references non-existent object 999.
 {{object 14 0}} <<
   /FirstAlternate [11 /XYZ 200 400 800]
   /LastAlternate  <</D [999 0 R /XYZ 0 0 -200]>>
diff --git a/testing/resources/bug_680376.pdf b/testing/resources/bug_680376.pdf
index fb01c57..8d7a884 100644
--- a/testing/resources/bug_680376.pdf
+++ b/testing/resources/bug_680376.pdf
@@ -1,5 +1,5 @@
 %PDF-1.7
-% ò¤ô
+% ò¤ô
 1 0 obj <<
   /Type /Catalog
   /Pages 2 0 R
@@ -86,8 +86,8 @@
 >>
 endobj
 % Old-style top-level Dests dictionary. Note that FirstAlternate
-% intentionally references non-exisstant page 11 and LastAlternate
-% intentionally references non-existant object 999.
+% intentionally references non-existent page 11 and LastAlternate
+% intentionally references non-existent object 999.
 14 0 obj <<
   /FirstAlternate [11 /XYZ 200 400 800]
   /LastAlternate  <</D [999 0 R /XYZ 0 0 -200]>>
@@ -138,19 +138,19 @@
 0000000903 00000 n 
 0000000993 00000 n 
 0000000000 65535 f 
-0000001287 00000 n 
-0000001415 00000 n 
+0000001286 00000 n 
+0000001414 00000 n 
 0000000000 65535 f 
 0000000000 65535 f 
 0000000000 65535 f 
 0000000000 65535 f 
 0000000000 65535 f 
-0000001510 00000 n 
-0000001620 00000 n 
+0000001509 00000 n 
+0000001619 00000 n 
 trailer <<
   /Size 6
   /Root 1 0 R
 >>
 startxref
-1708
+1707
 %%EOF
diff --git a/testing/resources/bug_765384.in b/testing/resources/bug_765384.in
index e7e46c7..69256bc 100644
--- a/testing/resources/bug_765384.in
+++ b/testing/resources/bug_765384.in
@@ -66,7 +66,7 @@
   /JS 21 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 21 0}} <<
 >>
 stream
diff --git a/testing/resources/bug_765384.pdf b/testing/resources/bug_765384.pdf
index e820fcc..41e042d 100644
--- a/testing/resources/bug_765384.pdf
+++ b/testing/resources/bug_765384.pdf
@@ -67,7 +67,7 @@
   /JS 21 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 21 0 obj <<
 >>
 stream
diff --git a/testing/resources/bug_821454.in b/testing/resources/bug_821454.in
index 168c983..ee7f20c 100644
--- a/testing/resources/bug_821454.in
+++ b/testing/resources/bug_821454.in
@@ -1,71 +1,63 @@
 {{header}}
-{{object 1 0}}
-<<
+{{object 1 0}} <<
   /Type /Catalog
   /Pages 2 0 R
-  /Names 14 0 R
-  /AcroForm << /Fields [ 8 0 R 9 0 R ] >>
+  /Names 7 0 R
+  /AcroForm <<
+    /Fields [ 5 0 R 6 0 R ]
+  >>
 >>
 endobj
-{{object 2 0}}
-<<
+{{object 2 0}} <<
   /Type /Pages
   /Count 1
   /Kids [ 3 0 R ]
 >>
 endobj
-{{object 3 0}}
-<<
+{{object 3 0}} <<
   /Type /Page
   /Parent 2 0 R
-  /Contents 6 0 R
+  /Contents 4 0 R
   /MediaBox [ 0 0 300 600 ]
-  /Annots [ 8 0 R 9 0 R ]
+  /Annots [ 5 0 R 6 0 R ]
 >>
 endobj
-{{object 6 0}}
-<<
-{{streamlen}}
+{{object 4 0}} <<
+  {{streamlen}}
 >>
 stream
 endstream
 endobj
-{{object 8 0}}
-<<
+{{object 5 0}} <<
   /Type /Annot
   /Subtype /Link
   /Rect [ 100 350 200 380 ]
   /Dest (target10)
 >>
 endobj
-{{object 9 0}}
-<<
+{{object 6 0}} <<
   /Type /Annot
   /Subtype /Link
   /Rect [ 100 400 200 430 ]
   /Dest (target100)
 >>
 endobj
-{{object 14 0}}
-<<
-  /Dests 15 0 R
+{{object 7 0}} <<
+  /Dests 8 0 R
 >>
 endobj
-{{object 15 0}}
-<<
+{{object 8 0}} <<
   /Names [
-    (target10) 16 0 R
-    (target100) 17 0 R
+    (target10) 9 0 R
+    (target100) 10 0 R
   ]
 >>
 endobj
-{{object 16 0}}
-<<
+{{object 9 0}} <<
   /D [3 0 R /XYZ 100 200 0]
 >>
 endobj
-{{object 17 0}}
-<<
+{{object 10 0}} <<
   /D [3 0 R /XYZ 150 250 0]
 >>
 endobj
diff --git a/testing/resources/bug_821454.pdf b/testing/resources/bug_821454.pdf
index d6229a5..1e11c13 100644
--- a/testing/resources/bug_821454.pdf
+++ b/testing/resources/bug_821454.pdf
@@ -1,96 +1,84 @@
 %PDF-1.7
 % ò¤ô
-1 0 obj
-<<
+1 0 obj <<
   /Type /Catalog
   /Pages 2 0 R
-  /Names 14 0 R
-  /AcroForm << /Fields [ 8 0 R 9 0 R ] >>
+  /Names 7 0 R
+  /AcroForm <<
+    /Fields [ 5 0 R 6 0 R ]
+  >>
 >>
 endobj
-2 0 obj
-<<
+2 0 obj <<
   /Type /Pages
   /Count 1
   /Kids [ 3 0 R ]
 >>
 endobj
-3 0 obj
-<<
+3 0 obj <<
   /Type /Page
   /Parent 2 0 R
-  /Contents 6 0 R
+  /Contents 4 0 R
   /MediaBox [ 0 0 300 600 ]
-  /Annots [ 8 0 R 9 0 R ]
+  /Annots [ 5 0 R 6 0 R ]
 >>
 endobj
-6 0 obj
-<<
-/Length 0
+4 0 obj <<
+  /Length 0
 >>
 stream
 endstream
 endobj
-8 0 obj
-<<
+5 0 obj <<
   /Type /Annot
   /Subtype /Link
   /Rect [ 100 350 200 380 ]
   /Dest (target10)
 >>
 endobj
-9 0 obj
-<<
+6 0 obj <<
   /Type /Annot
   /Subtype /Link
   /Rect [ 100 400 200 430 ]
   /Dest (target100)
 >>
 endobj
-14 0 obj
-<<
-  /Dests 15 0 R
+7 0 obj <<
+  /Dests 8 0 R
 >>
 endobj
-15 0 obj
-<<
+8 0 obj <<
   /Names [
-    (target10) 16 0 R
-    (target100) 17 0 R
+    (target10) 9 0 R
+    (target100) 10 0 R
   ]
 >>
 endobj
-16 0 obj
-<<
+9 0 obj <<
   /D [3 0 R /XYZ 100 200 0]
 >>
 endobj
-17 0 obj
-<<
+10 0 obj <<
   /D [3 0 R /XYZ 150 250 0]
 >>
 endobj
 xref
-0 18
+0 11
 0000000000 65535 f 
 0000000015 00000 n 
-0000000126 00000 n 
-0000000191 00000 n 
-0000000000 65535 f 
-0000000000 65535 f 
-0000000314 00000 n 
-0000000000 65535 f 
-0000000362 00000 n 
-0000000462 00000 n 
-0000000000 65535 f 
-0000000000 65535 f 
-0000000000 65535 f 
-0000000000 65535 f 
-0000000563 00000 n 
-0000000601 00000 n 
-0000000683 00000 n 
-0000000733 00000 n 
-trailer<< /Root 1 0 R /Size 18 >>
+0000000131 00000 n 
+0000000196 00000 n 
+0000000319 00000 n 
+0000000369 00000 n 
+0000000469 00000 n 
+0000000570 00000 n 
+0000000606 00000 n 
+0000000686 00000 n 
+0000000735 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 11
+>>
 startxref
-783
+785
 %%EOF
diff --git a/testing/resources/bug_889099.in b/testing/resources/bug_889099.in
new file mode 100644
index 0000000..77ae5f4
--- /dev/null
+++ b/testing/resources/bug_889099.in
@@ -0,0 +1,64 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [4 0 R]
+    /DR 5 0 R
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources 5 0 R
+  /MediaBox [0 0 300 -400]
+  /Contents 7 0 R
+  /Annots [4 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /DA (0 0 0 rg /F1 12 Tf)
+  /F 4
+  /Rect [100 100 200 -130]
+  /T (Text Box)
+  /V (Good :])
+>>
+endobj
+{{object 5 0}} <<
+  /Font <<
+    /F1 6 0 R
+  >>
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{object 7 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+0 0 0 rg
+/F1 12 Tf
+100 150 Td
+(Bad :[) Tj
+ET
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_889099.pdf b/testing/resources/bug_889099.pdf
new file mode 100644
index 0000000..a406cd1
--- /dev/null
+++ b/testing/resources/bug_889099.pdf
@@ -0,0 +1,78 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [4 0 R]
+    /DR 5 0 R
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources 5 0 R
+  /MediaBox [0 0 300 -400]
+  /Contents 7 0 R
+  /Annots [4 0 R]
+>>
+endobj
+4 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /DA (0 0 0 rg /F1 12 Tf)
+  /F 4
+  /Rect [100 100 200 -130]
+  /T (Text Box)
+  /V (Good :])
+>>
+endobj
+5 0 obj <<
+  /Font <<
+    /F1 6 0 R
+  >>
+>>
+endobj
+6 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+7 0 obj <<
+  /Length 48
+>>
+stream
+BT
+0 0 0 rg
+/F1 12 Tf
+100 150 Td
+(Bad :[) Tj
+ET
+endstream
+endobj
+xref
+0 8
+0000000000 65535 f 
+0000000015 00000 n 
+0000000122 00000 n 
+0000000185 00000 n 
+0000000318 00000 n 
+0000000475 00000 n 
+0000000526 00000 n 
+0000000602 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 8
+>>
+startxref
+701
+%%EOF
diff --git a/testing/resources/bug_921.in b/testing/resources/bug_921.in
index a0c2e73..ccd44fb 100644
--- a/testing/resources/bug_921.in
+++ b/testing/resources/bug_921.in
@@ -43,7 +43,7 @@
 >>
 endobj
 {{object 6 0}} <<
-{{streamlen}}
+  {{streamlen}}
 >>
 stream
 BT
diff --git a/testing/resources/bug_921.pdf b/testing/resources/bug_921.pdf
index fb3950e..84e0755 100644
--- a/testing/resources/bug_921.pdf
+++ b/testing/resources/bug_921.pdf
@@ -44,7 +44,7 @@
 >>
 endobj
 6 0 obj <<
-/Length 2784
+  /Length 2784
 >>
 stream
 BT
@@ -68,7 +68,10 @@
 0000000287 00000 n 
 0000000412 00000 n 
 0000000593 00000 n 
-trailer<< /Root 1 0 R /Size 7 >>
+trailer <<
+  /Root 1 0 R
+  /Size 7
+>>
 startxref
-3428
+3430
 %%EOF
diff --git a/testing/resources/bug_925981.in b/testing/resources/bug_925981.in
index 3d6e37a..f5f8ebe 100644
--- a/testing/resources/bug_925981.in
+++ b/testing/resources/bug_925981.in
@@ -36,7 +36,7 @@
 >>
 endobj
 {{object 6 0}} <<
-{{streamlen}}
+  {{streamlen}}
 >>
 stream
 BT
diff --git a/testing/resources/bug_925981.pdf b/testing/resources/bug_925981.pdf
index 8d856e1..5c35e38 100644
--- a/testing/resources/bug_925981.pdf
+++ b/testing/resources/bug_925981.pdf
@@ -37,7 +37,7 @@
 >>
 endobj
 6 0 obj <<
-/Length 83
+  /Length 83
 >>
 stream
 BT
@@ -64,11 +64,11 @@
 0000000309 00000 n 
 0000000387 00000 n 
 0000000463 00000 n 
-0000000595 00000 n 
+0000000597 00000 n 
 trailer <<
   /Root 1 0 R
   /Size 8
 >>
 startxref
-646
+648
 %%EOF
diff --git a/testing/resources/bug_972518.pdf b/testing/resources/bug_972518.pdf
index 51185f5..fde54f8 100644
--- a/testing/resources/bug_972518.pdf
+++ b/testing/resources/bug_972518.pdf
@@ -60,7 +60,7 @@
 endobj
 9 0 obj <<
   /Type /Page
-  /Parent 2 0 R
+  /Parent 8 0 R
   /MediaBox [0 0 612 792]
 >>
 endobj
diff --git a/testing/resources/dashed_lines.in b/testing/resources/dashed_lines.in
new file mode 100644
index 0000000..b4f74bb
--- /dev/null
+++ b/testing/resources/dashed_lines.in
@@ -0,0 +1,37 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 200 100]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+0 0 0 rg
+10 25 m 190 25 l S
+[6 5 4 3 2 1] 5 d
+10 50 m 190 50 l S
+[] 0 d
+10 75 m 190 75 l S
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/dashed_lines.pdf b/testing/resources/dashed_lines.pdf
new file mode 100644
index 0000000..f40a591
--- /dev/null
+++ b/testing/resources/dashed_lines.pdf
@@ -0,0 +1,48 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [0 0 200 100]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+4 0 obj <<
+  /Length 95
+>>
+stream
+q
+0 0 0 rg
+10 25 m 190 25 l S
+[6 5 4 3 2 1] 5 d
+10 50 m 190 50 l S
+[] 0 d
+10 75 m 190 75 l S
+Q
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000226 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 5
+>>
+startxref
+372
+%%EOF
diff --git a/testing/resources/docmdp.in b/testing/resources/docmdp.in
new file mode 100644
index 0000000..a8fe283
--- /dev/null
+++ b/testing/resources/docmdp.in
@@ -0,0 +1,88 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [7 0 R]
+    /SigFlags 3
+  >>
+>>
+endobj
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 200 300]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Annots [7 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Sig
+  /Reference [
+    <<
+      /Type /SigRef
+      /TransformMethod /DocMDP
+      /TransformParams <<
+        /Type /TransformParams
+        /P 1
+        /V /1.2
+      >>
+    >>
+  ]
+>>
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 0 0]
+  /Length 0
+>>
+stream
+endstream
+endobj
+{{object 7 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Sig
+  /F 132
+  /Rect [0 0 0 0]
+  /P 3 0 R
+  /T (Signature1)
+  /V 5 0 R
+  /DV 5 0 R
+  /AP <<
+    /N 6 0 R
+  >>
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/docmdp.pdf b/testing/resources/docmdp.pdf
new file mode 100644
index 0000000..0191a00
--- /dev/null
+++ b/testing/resources/docmdp.pdf
@@ -0,0 +1,102 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [7 0 R]
+    /SigFlags 3
+  >>
+>>
+endobj
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [0 0 200 300]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Annots [7 0 R]
+>>
+endobj
+4 0 obj <<
+  /Length 188
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+5 0 obj <<
+  /Type /Sig
+  /Reference [
+    <<
+      /Type /SigRef
+      /TransformMethod /DocMDP
+      /TransformParams <<
+        /Type /TransformParams
+        /P 1
+        /V /1.2
+      >>
+    >>
+  ]
+>>
+endobj
+6 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 0 0]
+  /Length 0
+>>
+stream
+endstream
+endobj
+7 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Sig
+  /F 132
+  /Rect [0 0 0 0]
+  /P 3 0 R
+  /T (Signature1)
+  /V 5 0 R
+  /DV 5 0 R
+  /AP <<
+    /N 6 0 R
+  >>
+>>
+endobj
+xref
+0 8
+0000000000 65535 f 
+0000000015 00000 n 
+0000000131 00000 n 
+0000000220 00000 n 
+0000000307 00000 n 
+0000000547 00000 n 
+0000000760 00000 n 
+0000000862 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 8
+>>
+startxref
+1034
+%%EOF
diff --git a/testing/resources/embedded_attachments_invalid_data.in b/testing/resources/embedded_attachments_invalid_data.in
new file mode 100644
index 0000000..26545a4
--- /dev/null
+++ b/testing/resources/embedded_attachments_invalid_data.in
@@ -0,0 +1,41 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Names 4 0 R
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Resources <<
+    /ProcSet [/PDF]
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  /EmbeddedFiles 5 0 R
+>>
+endobj
+{{object 5 0}} <<
+  /Names [(1.txt) 6 0 R]
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Filespec
+  /Desc ()
+  /F (1.txt)
+  /UF (1.txt)
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/embedded_attachments_invalid_data.pdf b/testing/resources/embedded_attachments_invalid_data.pdf
new file mode 100644
index 0000000..8a830e2
--- /dev/null
+++ b/testing/resources/embedded_attachments_invalid_data.pdf
@@ -0,0 +1,54 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Names 4 0 R
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Resources <<
+    /ProcSet [/PDF]
+  >>
+>>
+endobj
+4 0 obj <<
+  /EmbeddedFiles 5 0 R
+>>
+endobj
+5 0 obj <<
+  /Names [(1.txt) 6 0 R]
+>>
+endobj
+6 0 obj <<
+  /Type /Filespec
+  /Desc ()
+  /F (1.txt)
+  /UF (1.txt)
+>>
+endobj
+xref
+0 7
+0000000000 65535 f 
+0000000015 00000 n 
+0000000083 00000 n 
+0000000146 00000 n 
+0000000264 00000 n 
+0000000308 00000 n 
+0000000354 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 7
+>>
+startxref
+431
+%%EOF
diff --git a/testing/resources/find_text_consecutive.in b/testing/resources/find_text_consecutive.in
index 9e35e1d..d484584 100644
--- a/testing/resources/find_text_consecutive.in
+++ b/testing/resources/find_text_consecutive.in
@@ -29,7 +29,7 @@
 >>
 endobj
 {{object 5 0}} <<
-{{streamlen}}
+  {{streamlen}}
 >>
 stream
 BT
diff --git a/testing/resources/find_text_consecutive.pdf b/testing/resources/find_text_consecutive.pdf
index 5083d75..bd78380 100644
--- a/testing/resources/find_text_consecutive.pdf
+++ b/testing/resources/find_text_consecutive.pdf
@@ -30,7 +30,7 @@
 >>
 endobj
 5 0 obj <<
-/Length 51
+  /Length 51
 >>
 stream
 BT
@@ -53,5 +53,5 @@
   /Size 6
 >>
 startxref
-465
+467
 %%EOF
diff --git a/testing/resources/fonts/SymbolNeu.ttf b/testing/resources/fonts/SymbolNeu.ttf
new file mode 100644
index 0000000..08932c9
--- /dev/null
+++ b/testing/resources/fonts/SymbolNeu.ttf
Binary files differ
diff --git a/testing/resources/get_page_aaction.in b/testing/resources/get_page_aaction.in
new file mode 100644
index 0000000..83f0c01
--- /dev/null
+++ b/testing/resources/get_page_aaction.in
@@ -0,0 +1,50 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Kids [
+    3 0 R
+    4 0 R
+  ]
+  /Count 2
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /AA <<
+    /O <<
+      /F (\\\\127.0.0.1\\c$\\Program Files\\test.exe)
+      /D [1 /Fit]
+      /S /GoToE
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /AA <<
+    /O <<
+      /F (\\\\127.0.0.1\\c$\\Program Files\\test_page_two_open.exe)
+      /D [1 /Fit]
+      /S /GoToE
+    >>
+    /C <<
+      /F (\\\\127.0.0.1\\c$\\Program Files\\test_page_two_close.exe)
+      /D [1 /Fit]
+      /S /GoToE
+    >>
+  >>
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/get_page_aaction.pdf b/testing/resources/get_page_aaction.pdf
new file mode 100644
index 0000000..7d46651
--- /dev/null
+++ b/testing/resources/get_page_aaction.pdf
@@ -0,0 +1,61 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Kids [
+    3 0 R
+    4 0 R
+  ]
+  /Count 2
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /AA <<
+    /O <<
+      /F (\\\\127.0.0.1\\c$\\Program Files\\test.exe)
+      /D [1 /Fit]
+      /S /GoToE
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /AA <<
+    /O <<
+      /F (\\\\127.0.0.1\\c$\\Program Files\\test_page_two_open.exe)
+      /D [1 /Fit]
+      /S /GoToE
+    >>
+    /C <<
+      /F (\\\\127.0.0.1\\c$\\Program Files\\test_page_two_close.exe)
+      /D [1 /Fit]
+      /S /GoToE
+    >>
+  >>
+>>
+endobj
+xref
+0 5
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000149 00000 n 
+0000000345 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 5
+>>
+startxref
+675
+%%EOF
diff --git a/testing/resources/gotoe_action.in b/testing/resources/gotoe_action.in
new file mode 100644
index 0000000..04b7cf0
--- /dev/null
+++ b/testing/resources/gotoe_action.in
@@ -0,0 +1,48 @@
+{{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
+  /Annots [4 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Link
+  /FT /Tx
+  /Ff 29360128
+  /A 5 0 R
+  /T (txtName)
+  /F 4
+  /M (D:20150514070426+05'30')
+  /Rect [1 1 199 199]
+  /BS  <<
+    /W 1
+    /S /S
+  >>
+  /DA (/Helv 0 Tf 0 0 0 rg)
+  /V ()
+>>
+endobj
+{{object 5 0}} <<
+  /S /GoToE
+  /D [1 /Fit]
+  /F (ExampleFile.pdf)
+>>
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/gotoe_action.pdf b/testing/resources/gotoe_action.pdf
new file mode 100644
index 0000000..8d588e0
--- /dev/null
+++ b/testing/resources/gotoe_action.pdf
@@ -0,0 +1,60 @@
+%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
+  /Annots [4 0 R]
+>>
+endobj
+4 0 obj <<
+  /Type /Annot
+  /Subtype /Link
+  /FT /Tx
+  /Ff 29360128
+  /A 5 0 R
+  /T (txtName)
+  /F 4
+  /M (D:20150514070426+05'30')
+  /Rect [1 1 199 199]
+  /BS  <<
+    /W 1
+    /S /S
+  >>
+  /DA (/Helv 0 Tf 0 0 0 rg)
+  /V ()
+>>
+endobj
+5 0 obj <<
+  /S /GoToE
+  /D [1 /Fit]
+  /F (ExampleFile.pdf)
+>>
+>>
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000226 00000 n 
+0000000460 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+533
+%%EOF
diff --git a/testing/resources/gray-alpha.jp2 b/testing/resources/gray-alpha.jp2
new file mode 100644
index 0000000..f5e3f34
--- /dev/null
+++ b/testing/resources/gray-alpha.jp2
Binary files differ
diff --git a/testing/resources/gray.jp2 b/testing/resources/gray.jp2
new file mode 100644
index 0000000..53293bd
--- /dev/null
+++ b/testing/resources/gray.jp2
Binary files differ
diff --git a/testing/resources/hebrew_mirrored.in b/testing/resources/hebrew_mirrored.in
new file mode 100644
index 0000000..09ede5d
--- /dev/null
+++ b/testing/resources/hebrew_mirrored.in
@@ -0,0 +1,160 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 200 200]
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+/F1 12 Tf
+150 160 Td
+[<01>2<02>2<03>-4<02>2<04>5<05>]TJ
+ET
+
+BT
+0 100 Td
+-1 0 0 1 50 40 Tm
+[<01>5<05>]TJ
+ET
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /TrueType
+  /BaseFont /BAAAAA+NotoSansHebrew-Regular
+  /FirstChar 0
+  /FontDescriptor 6 0 R
+  /LastChar 5
+  /ToUnicode 8 0 R
+  /Widths [600 294 235 645 397 542]
+>>
+endobj
+{{object 6 0}} <<
+  /Type /FontDescriptor
+  /Ascent 1069
+  /CapHeight 869
+  /Descent -293
+  /Flags 4
+  /FontBBox [-210 -252 716 869]
+  /FontFile2 7 0 R
+  /FontName /BAAAAA+NotoSansHebrew-Regular
+  /ItalicAngle 0
+  /StemV 80
+>>
+endobj
+{{object 7 0}} <<
+  /Filter [/ASCII85Decode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+GhVOeCMXsCFZt#(%p<i9AU.uO6]Ye/ifKHXbLV!I1<gsgHY$Eg-tbPt@rk=Ap?l+FQ/u;^8JASXVQS>V
+nuG<j"nF$,Rii%*89oFrq6<34,1m)cJ0IF5(22*&"c<?^q>^jZEkc[.-qdl^dk1F&,fKT9F2)eaH/1QX
+<gN4)W0n]g:798;]<j42dYR+b7&$II`er0Do2PVi55OH.DU&3oF7^D>=p%W;pT6OOkC.gL0;aQZ%EQ]$
+CMpO/ouiOe*=oqMFW5p^ge<JWIt[XVg[cqr8aR%^g[TKTpYK&S\F4)WJbd&hhE(,./q'ef/\k-FOKm`h
+f=q)o6_0Sd-Ap"u/`'bn6!o+8Zu;5]Q^2D[J,6jbf.d4Ck't7.im"Z=^^/7D4VXdnJ*hX.ia&@S<Zl_D
+Xq`#Xlf3.[Q6GL-.IS+nj<JEQ1+=HefoeH8Qi0[)c<.\^P$4LTD?DamV./;?-PDR^8[b$k8'^LB2C4&'
+NUg=HC6k8A'.Knb7Wh)elDCX74)L`tc?`Z\Sb;i$>AElUWZdj+!8FWu:gnKPdkmGSM;%q39@mC[7<ZEH
+BP,jX746Y[8VL'e=mlrkg+[&/4Xn#1'(72pCc`cXNN+/Z0?DM&g%BgA@co7e)9J3X;X\\(5@"=N^/d""
+T`@poCcT@G`,Q43B>^KnLk;$<#C1`ZRI:qa#-tMr)R?VE.RR\7=sKe@N)#YuA]JfMaHgYN/!?c&OsZTe
+^cXe0%l-1*krrj[$8W`Wl)*K?>t9dJirfrR7MYH\=XPN5Q2qL_I4eudg4b]KRT\<OAl/T-PZ'?3dZ$p;
+<A/YSC[`O')u&1O:[moi)'2=Vd5KR72=0E6LAR(+/#@80mS5hidn)NRbS3]PMfW2+=&LegaZXffSM\+*
+%@ESe'CB7@?)cYMPG3X<A98q?pK.WHgPW4%FOOV*1/i@`;U-_0)m/crX^pV$",X/"BsMn?;R::1,iO?7
+d*"n-c#gLp.bPO!Jk.*r-gP*5[&0>a[qOG`>on_9`Lhgl?)c@P!H#2#PFffIEAQP^TS_DS:Xem?lK2!M
+h#c+\A=NN1)ZW-WA38,70l'k`MSW-$8rA0><3W]ffk"ZqCrdIAQBE9>j@mZ'<HHGofhsV-;mOl@]Mb1L
+qSIIYnkLM"Z97EVYElh]%[>>bb#jgf**OlY[IHaK5,@C./bHbDalE":hWu.1N,@;S&gl,n3:<uV$;N?L
+*(2O_s!JrF=fbn$ki"!LOY9qefX_V\<NFjuO0ngY8TpJ(,;39KIP'3^N)o:sU^S>oeCuK%7\,dgAiUN1
+E!0r_=$ol73>k&7]_*E"mUSrX[62980+uUk50ME#I*V:L4N8s+bi=/qMstu,ntJ5f[(HSa5@JhaGYD:Q
+]auqs7`nFM/]DAtfVG=ND6[_hnTB=9[;GHTf&F!MBNQP7P.LnAZ2VP"Ploh`?NTa8nEUYFj3^8B4<?t>
+4SBp^*fhF-*fhL/*r?NkO.Sgf4:WJt*n-E0W]KmAKl)1[L<V@'WjXmHBl;cn9--aK@lTjrE@*#k?-6on
+,:;=;+3B=[oI&q3p0&\dDEW8m^^H(gHjR8,7ldq.4hG]/>/mf;Hl1F^:B&.Xiqu!h(RA"GpXOmI2eJ(p
+-Ufu7H4'I5!71?\VoB`KajX::mq>=Rr&Q;g]E7s$-kZ.E)/3T4mfoT]_V3^s3J#ALKI7nF?TacHK]qhj
+L<ncjl]HN[$fPFZ;`(\GZEhSIf%/9H@>qA*jpO7b7bY=!,<-,5gQ6!\>V]AQ)^-GB`&C"Q_Tt5F^2b04
+H^dXF*&#KkrUdWPo<maGSjIV2C<A6%Y#l1eDB-11=5MCe/mQ;H^`5k(:$aRZO01rVm/hX\fYmD8f*Dj@
+M@i=qKg0)_(>4O.G!a5^E2>bO$]QiDc\2C6pdgJSEti>7EGJ-(WqC@QJiDd@NbtVSKCL`LBr7+Q7e(#%
+mfT9"IKcR0OZpuAD#Y..(\(Ya*`U>DQ9W"+'>%SR14Rbu3okUD1UoZtSAfKYf.IftGdnZe:>-X$>r]=V
+G-"HZ$ThCqcd_nA%GR%YOrq)?cB9h+fAIVB(kUJ,MLq_e+>iX=AF@bgIcap`;p5$2F+]o@5&$NLhu:.I
+2<(<.=8A484n4$P`>fQKA"tBRWbq]tDur6ODulS&f!BB<ZPSc21WW3#buP0(+9gU#Z,bH/SFFh24G?`5
+dOt)^QkNbrnhfi)90SEq&n`q[F;s7b!nXc$g#@aPQN3qbFeWl+!e=Zt`EF'M1(Bm%7HR8DbV4gg<TZ"1
+'QY,(Ci/)_["1h766XD'&ADrraHq=DH%OF$jsfp?jsbDCGm`Bc4nAYbhNNN6HSW+QHSVNRk<)"p3=KZH
+@5,_A#.Kh(3M>-L01N,n^DOYar*o;enr(m9jVO\;jVOZqjVO[\jEPdIJ4jW(1Ou\+n/U@A-XG!&R,,`f
+2W1\3O?*%L7g,>@T`!f8eMl#PBrq/m2=C@'l+[6\!TC@KAA[MWaFrFRGAPIZkLE8R^YhJV5O]iFIg_\G
+iO=6VK\u;C'>=b?fe%PhWL9K1abNRi>otj#+Z';<n+i%OCHc:GQ`]p-obCOtip.a^cf<UjY!FG)PRTLL
++"jYG+4_li`+jB7`VE<s"4AVj".>pGko/fq8/"=:R5@Xn#Q]c2U1bel!_n`0,C>VAYsbZH&PH@FJ$f#/
+0td=Kg'>AR25F7&YOo!<mI]$*rRjT2aR!Q]%Fi)aOtJSn?+UM2'/_)[:p`0l2g4bJBatZEkin+Um_FDG
+q;M6)[I9OhMIS=<lFaD[8\^p:_H)J0l0'R9&ZuGO_>dFFCb.uJQlb(Q4<5Au742_H^C[$nr;0XJ4b3Tg
+[;48b%t<.Gdkk?8S<Vi`;Poe&S_S0PK8iOs+@Vd0N*gN+=Plp50G^#i&Kg"CSmrqAknTi"D:*>YSUrma
+g=r&8<Pj&ND`$13'HCOjG_kBC'T)*ekL$!:6XuRpIX?)+^V5+S2cXNJB2ec80tlUL?G#q-m,hbS%$"V@
+p"JQ?igh1:m'G\$*s_?+,4a,JI@3GnX*+U"_Zr.(+*>1jkFAnU9>ZXAU\g3ArglE9&,uBU"0[/n3+!B'
+W[)MC"?*YACpqa=bi'p-F[R+Y&Jh':>DS9I;-PEL7\\5FC1<[_U$dQmm+CP5IQgiD>SQGm;oRG4k5.U/
+0D?:mI/C=6)f;AM:/Ut^\F':\li2"qaZh<t\"cP;!(<3T?G(>oo5D,:=$o+C?]KqCU?geEFjWG&V"PRp
+&9$#`m3'G9"&tosqAZg/@%\C2I=Z6p1:0Yl"+@)T)so1U@.,fWm/arHR8d:MJXMQVC8K5]=Jm_KIXjdB
+83[K$Qla[fJ0ST]0*O=-3O'X`"r\'Q&`>h%Y8I@YUo\`'^eHY_XTc5&7^\ik\4-8s4VZgTU@:6TdipBk
+I9NLLkSE!O'mP*-*Z_.eTF%\P6@O])QsDsH.[1,u#K\!Fdp$""eZ=M1#$Z$(ZO5i+BWp@?TH^qnB%D7/
+h#sWFD^p`NF:Yfb85;E[:q^5Oc;Ss`!7br9/Ss$74_<U(m:SQ#s-3*J/tuGNgXsJtKtqF1bXL_J,7?@i
+'BEj1Ilon*aW+o61dn83AfE7FcXE_u9)ggC=\p"(7u!%6Y7^]'TMPP?@sg_akMgC`EYfUsdCeJO#2(2D
+hQ;Br<4m&15-"(TYi[P!iN8/Ok#@VVm[UO"bZs@LQZDI&pjXBX+4dX`csI+Oe#J=,^c=d&n:"<LdpP?/
+57NYh_**Nu%GYGtPrZ:2pA@k$5I#/DZ?u(p.mKdY_mpagr"[aHSA,e@EE+%EftN*$X1t;gS`@?f>ZH3j
+dG.dX,4]pij-88?@>Es&4"OKq4Pb_H)9:8fq`(0Ie[V>KnkIYS^43jk=+).,aXsOX(4cqM6KaT:7HMS5
+5VqW;.CtD!kNC3=e+gS7q$"nkh'kl'g`'1Rk*3jhL&JE!FZWhT#NI(1R)?@JnB0)&ZC7m>1Wud3mOB<F
+<?1UlEeaPIK4D$_#X<cE%HM2B7nrD$SI/J?MJ9$\[%L0[X#c.:>Z)WgX'F4+E];'H25pPfMT*QlPBH<B
+2jEaIeeu%@g_SZi^/+`r>koI@22thoJ$T#-Ig:8=C[r)9A@gh.nJ+P$rtk!a7oB995m%NRR_tlI7kn;C
+L?s85#0T`VHa<+&O5K'$Eo(6pn-XUq4Y6-9Re-B$rhtBq!Tfi]GEbPb2*A\G,P/,p;d\J77]S.F#9J`B
+DopA\Sjp8ME&Btm*&[^S4k/0Z~>
+endstream
+endobj
+{{object 8 0}} <<
+  {{streamlen}}
+>>
+stream
+/CIDInit/ProcSet findresource begin
+12 dict begin
+begincmap
+/CIDSystemInfo<<
+/Registry (Adobe)
+/Ordering (UCS)
+/Supplement 0
+>> def
+/CMapName/Adobe-Identity-UCS def
+/CMapType 2 def
+1 begincodespacerange
+<00> <FF>
+endcodespacerange
+5 beginbfchar
+<01> <05DF>
+<02> <05D9>
+<03> <05DE>
+<04> <05E0>
+<05> <05D1>
+endbfchar
+endcmap
+CMapName currentdict /CMap defineresource pop
+end
+end
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/hebrew_mirrored.pdf b/testing/resources/hebrew_mirrored.pdf
new file mode 100644
index 0000000..1134350
--- /dev/null
+++ b/testing/resources/hebrew_mirrored.pdf
@@ -0,0 +1,175 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 200 200]
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Length 110
+>>
+stream
+BT
+/F1 12 Tf
+150 160 Td
+[<01>2<02>2<03>-4<02>2<04>5<05>]TJ
+ET
+
+BT
+0 100 Td
+-1 0 0 1 50 40 Tm
+[<01>5<05>]TJ
+ET
+endstream
+endobj
+5 0 obj <<
+  /Type /Font
+  /Subtype /TrueType
+  /BaseFont /BAAAAA+NotoSansHebrew-Regular
+  /FirstChar 0
+  /FontDescriptor 6 0 R
+  /LastChar 5
+  /ToUnicode 8 0 R
+  /Widths [600 294 235 645 397 542]
+>>
+endobj
+6 0 obj <<
+  /Type /FontDescriptor
+  /Ascent 1069
+  /CapHeight 869
+  /Descent -293
+  /Flags 4
+  /FontBBox [-210 -252 716 869]
+  /FontFile2 7 0 R
+  /FontName /BAAAAA+NotoSansHebrew-Regular
+  /ItalicAngle 0
+  /StemV 80
+>>
+endobj
+7 0 obj <<
+  /Filter [/ASCII85Decode /FlateDecode]
+  /Length 4240
+>>
+stream
+GhVOeCMXsCFZt#(%p<i9AU.uO6]Ye/ifKHXbLV!I1<gsgHY$Eg-tbPt@rk=Ap?l+FQ/u;^8JASXVQS>V
+nuG<j"nF$,Rii%*89oFrq6<34,1m)cJ0IF5(22*&"c<?^q>^jZEkc[.-qdl^dk1F&,fKT9F2)eaH/1QX
+<gN4)W0n]g:798;]<j42dYR+b7&$II`er0Do2PVi55OH.DU&3oF7^D>=p%W;pT6OOkC.gL0;aQZ%EQ]$
+CMpO/ouiOe*=oqMFW5p^ge<JWIt[XVg[cqr8aR%^g[TKTpYK&S\F4)WJbd&hhE(,./q'ef/\k-FOKm`h
+f=q)o6_0Sd-Ap"u/`'bn6!o+8Zu;5]Q^2D[J,6jbf.d4Ck't7.im"Z=^^/7D4VXdnJ*hX.ia&@S<Zl_D
+Xq`#Xlf3.[Q6GL-.IS+nj<JEQ1+=HefoeH8Qi0[)c<.\^P$4LTD?DamV./;?-PDR^8[b$k8'^LB2C4&'
+NUg=HC6k8A'.Knb7Wh)elDCX74)L`tc?`Z\Sb;i$>AElUWZdj+!8FWu:gnKPdkmGSM;%q39@mC[7<ZEH
+BP,jX746Y[8VL'e=mlrkg+[&/4Xn#1'(72pCc`cXNN+/Z0?DM&g%BgA@co7e)9J3X;X\\(5@"=N^/d""
+T`@poCcT@G`,Q43B>^KnLk;$<#C1`ZRI:qa#-tMr)R?VE.RR\7=sKe@N)#YuA]JfMaHgYN/!?c&OsZTe
+^cXe0%l-1*krrj[$8W`Wl)*K?>t9dJirfrR7MYH\=XPN5Q2qL_I4eudg4b]KRT\<OAl/T-PZ'?3dZ$p;
+<A/YSC[`O')u&1O:[moi)'2=Vd5KR72=0E6LAR(+/#@80mS5hidn)NRbS3]PMfW2+=&LegaZXffSM\+*
+%@ESe'CB7@?)cYMPG3X<A98q?pK.WHgPW4%FOOV*1/i@`;U-_0)m/crX^pV$",X/"BsMn?;R::1,iO?7
+d*"n-c#gLp.bPO!Jk.*r-gP*5[&0>a[qOG`>on_9`Lhgl?)c@P!H#2#PFffIEAQP^TS_DS:Xem?lK2!M
+h#c+\A=NN1)ZW-WA38,70l'k`MSW-$8rA0><3W]ffk"ZqCrdIAQBE9>j@mZ'<HHGofhsV-;mOl@]Mb1L
+qSIIYnkLM"Z97EVYElh]%[>>bb#jgf**OlY[IHaK5,@C./bHbDalE":hWu.1N,@;S&gl,n3:<uV$;N?L
+*(2O_s!JrF=fbn$ki"!LOY9qefX_V\<NFjuO0ngY8TpJ(,;39KIP'3^N)o:sU^S>oeCuK%7\,dgAiUN1
+E!0r_=$ol73>k&7]_*E"mUSrX[62980+uUk50ME#I*V:L4N8s+bi=/qMstu,ntJ5f[(HSa5@JhaGYD:Q
+]auqs7`nFM/]DAtfVG=ND6[_hnTB=9[;GHTf&F!MBNQP7P.LnAZ2VP"Ploh`?NTa8nEUYFj3^8B4<?t>
+4SBp^*fhF-*fhL/*r?NkO.Sgf4:WJt*n-E0W]KmAKl)1[L<V@'WjXmHBl;cn9--aK@lTjrE@*#k?-6on
+,:;=;+3B=[oI&q3p0&\dDEW8m^^H(gHjR8,7ldq.4hG]/>/mf;Hl1F^:B&.Xiqu!h(RA"GpXOmI2eJ(p
+-Ufu7H4'I5!71?\VoB`KajX::mq>=Rr&Q;g]E7s$-kZ.E)/3T4mfoT]_V3^s3J#ALKI7nF?TacHK]qhj
+L<ncjl]HN[$fPFZ;`(\GZEhSIf%/9H@>qA*jpO7b7bY=!,<-,5gQ6!\>V]AQ)^-GB`&C"Q_Tt5F^2b04
+H^dXF*&#KkrUdWPo<maGSjIV2C<A6%Y#l1eDB-11=5MCe/mQ;H^`5k(:$aRZO01rVm/hX\fYmD8f*Dj@
+M@i=qKg0)_(>4O.G!a5^E2>bO$]QiDc\2C6pdgJSEti>7EGJ-(WqC@QJiDd@NbtVSKCL`LBr7+Q7e(#%
+mfT9"IKcR0OZpuAD#Y..(\(Ya*`U>DQ9W"+'>%SR14Rbu3okUD1UoZtSAfKYf.IftGdnZe:>-X$>r]=V
+G-"HZ$ThCqcd_nA%GR%YOrq)?cB9h+fAIVB(kUJ,MLq_e+>iX=AF@bgIcap`;p5$2F+]o@5&$NLhu:.I
+2<(<.=8A484n4$P`>fQKA"tBRWbq]tDur6ODulS&f!BB<ZPSc21WW3#buP0(+9gU#Z,bH/SFFh24G?`5
+dOt)^QkNbrnhfi)90SEq&n`q[F;s7b!nXc$g#@aPQN3qbFeWl+!e=Zt`EF'M1(Bm%7HR8DbV4gg<TZ"1
+'QY,(Ci/)_["1h766XD'&ADrraHq=DH%OF$jsfp?jsbDCGm`Bc4nAYbhNNN6HSW+QHSVNRk<)"p3=KZH
+@5,_A#.Kh(3M>-L01N,n^DOYar*o;enr(m9jVO\;jVOZqjVO[\jEPdIJ4jW(1Ou\+n/U@A-XG!&R,,`f
+2W1\3O?*%L7g,>@T`!f8eMl#PBrq/m2=C@'l+[6\!TC@KAA[MWaFrFRGAPIZkLE8R^YhJV5O]iFIg_\G
+iO=6VK\u;C'>=b?fe%PhWL9K1abNRi>otj#+Z';<n+i%OCHc:GQ`]p-obCOtip.a^cf<UjY!FG)PRTLL
++"jYG+4_li`+jB7`VE<s"4AVj".>pGko/fq8/"=:R5@Xn#Q]c2U1bel!_n`0,C>VAYsbZH&PH@FJ$f#/
+0td=Kg'>AR25F7&YOo!<mI]$*rRjT2aR!Q]%Fi)aOtJSn?+UM2'/_)[:p`0l2g4bJBatZEkin+Um_FDG
+q;M6)[I9OhMIS=<lFaD[8\^p:_H)J0l0'R9&ZuGO_>dFFCb.uJQlb(Q4<5Au742_H^C[$nr;0XJ4b3Tg
+[;48b%t<.Gdkk?8S<Vi`;Poe&S_S0PK8iOs+@Vd0N*gN+=Plp50G^#i&Kg"CSmrqAknTi"D:*>YSUrma
+g=r&8<Pj&ND`$13'HCOjG_kBC'T)*ekL$!:6XuRpIX?)+^V5+S2cXNJB2ec80tlUL?G#q-m,hbS%$"V@
+p"JQ?igh1:m'G\$*s_?+,4a,JI@3GnX*+U"_Zr.(+*>1jkFAnU9>ZXAU\g3ArglE9&,uBU"0[/n3+!B'
+W[)MC"?*YACpqa=bi'p-F[R+Y&Jh':>DS9I;-PEL7\\5FC1<[_U$dQmm+CP5IQgiD>SQGm;oRG4k5.U/
+0D?:mI/C=6)f;AM:/Ut^\F':\li2"qaZh<t\"cP;!(<3T?G(>oo5D,:=$o+C?]KqCU?geEFjWG&V"PRp
+&9$#`m3'G9"&tosqAZg/@%\C2I=Z6p1:0Yl"+@)T)so1U@.,fWm/arHR8d:MJXMQVC8K5]=Jm_KIXjdB
+83[K$Qla[fJ0ST]0*O=-3O'X`"r\'Q&`>h%Y8I@YUo\`'^eHY_XTc5&7^\ik\4-8s4VZgTU@:6TdipBk
+I9NLLkSE!O'mP*-*Z_.eTF%\P6@O])QsDsH.[1,u#K\!Fdp$""eZ=M1#$Z$(ZO5i+BWp@?TH^qnB%D7/
+h#sWFD^p`NF:Yfb85;E[:q^5Oc;Ss`!7br9/Ss$74_<U(m:SQ#s-3*J/tuGNgXsJtKtqF1bXL_J,7?@i
+'BEj1Ilon*aW+o61dn83AfE7FcXE_u9)ggC=\p"(7u!%6Y7^]'TMPP?@sg_akMgC`EYfUsdCeJO#2(2D
+hQ;Br<4m&15-"(TYi[P!iN8/Ok#@VVm[UO"bZs@LQZDI&pjXBX+4dX`csI+Oe#J=,^c=d&n:"<LdpP?/
+57NYh_**Nu%GYGtPrZ:2pA@k$5I#/DZ?u(p.mKdY_mpagr"[aHSA,e@EE+%EftN*$X1t;gS`@?f>ZH3j
+dG.dX,4]pij-88?@>Es&4"OKq4Pb_H)9:8fq`(0Ie[V>KnkIYS^43jk=+).,aXsOX(4cqM6KaT:7HMS5
+5VqW;.CtD!kNC3=e+gS7q$"nkh'kl'g`'1Rk*3jhL&JE!FZWhT#NI(1R)?@JnB0)&ZC7m>1Wud3mOB<F
+<?1UlEeaPIK4D$_#X<cE%HM2B7nrD$SI/J?MJ9$\[%L0[X#c.:>Z)WgX'F4+E];'H25pPfMT*QlPBH<B
+2jEaIeeu%@g_SZi^/+`r>koI@22thoJ$T#-Ig:8=C[r)9A@gh.nJ+P$rtk!a7oB995m%NRR_tlI7kn;C
+L?s85#0T`VHa<+&O5K'$Eo(6pn-XUq4Y6-9Re-B$rhtBq!Tfi]GEbPb2*A\G,P/,p;d\J77]S.F#9J`B
+DopA\Sjp8ME&Btm*&[^S4k/0Z~>
+endstream
+endobj
+8 0 obj <<
+  /Length 377
+>>
+stream
+/CIDInit/ProcSet findresource begin
+12 dict begin
+begincmap
+/CIDSystemInfo<<
+/Registry (Adobe)
+/Ordering (UCS)
+/Supplement 0
+>> def
+/CMapName/Adobe-Identity-UCS def
+/CMapType 2 def
+1 begincodespacerange
+<00> <FF>
+endcodespacerange
+5 beginbfchar
+<01> <05DF>
+<02> <05D9>
+<03> <05DE>
+<04> <05E0>
+<05> <05D1>
+endbfchar
+endcmap
+CMapName currentdict /CMap defineresource pop
+end
+end
+endstream
+endobj
+xref
+0 9
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000309 00000 n 
+0000000471 00000 n 
+0000000678 00000 n 
+0000000905 00000 n 
+0000005238 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 9
+>>
+startxref
+5667
+%%EOF
diff --git a/testing/resources/hello_world.in b/testing/resources/hello_world.in
index d3cf265..754820a 100644
--- a/testing/resources/hello_world.in
+++ b/testing/resources/hello_world.in
@@ -6,9 +6,9 @@
 endobj
 {{object 2 0}} <<
   /Type /Pages
-  /MediaBox [ 0 0 200 200 ]
+  /MediaBox [0 0 200 200]
   /Count 1
-  /Kids [ 3 0 R ]
+  /Kids [3 0 R]
 >>
 endobj
 {{object 3 0}} <<
@@ -36,6 +36,7 @@
 >>
 endobj
 {{object 6 0}} <<
+  % Note this object deliberately does not use {{streamlen}}.
 >>
 stream
 BT
diff --git a/testing/resources/hello_world.pdf b/testing/resources/hello_world.pdf
index dcde8f7..48c714b 100644
--- a/testing/resources/hello_world.pdf
+++ b/testing/resources/hello_world.pdf
@@ -7,9 +7,9 @@
 endobj
 2 0 obj <<
   /Type /Pages
-  /MediaBox [ 0 0 200 200 ]
+  /MediaBox [0 0 200 200]
   /Count 1
-  /Kids [ 3 0 R ]
+  /Kids [3 0 R]
 >>
 endobj
 3 0 obj <<
@@ -37,6 +37,7 @@
 >>
 endobj
 6 0 obj <<
+  % Note this object deliberately does not use /Length 83.
 >>
 stream
 BT
@@ -54,11 +55,14 @@
 0000000000 65535 f 
 0000000015 00000 n 
 0000000068 00000 n 
-0000000161 00000 n 
-0000000303 00000 n 
-0000000381 00000 n 
-0000000457 00000 n 
-trailer<< /Root 1 0 R /Size 7 >>
+0000000157 00000 n 
+0000000299 00000 n 
+0000000377 00000 n 
+0000000453 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 7
+>>
 startxref
-578
+633
 %%EOF
diff --git a/testing/resources/hello_world_2_pages.in b/testing/resources/hello_world_2_pages.in
new file mode 100644
index 0000000..ec33354
--- /dev/null
+++ b/testing/resources/hello_world_2_pages.in
@@ -0,0 +1,67 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 2
+  /Kids [3 0 R 4 0 R]
+  /MediaBox [0 0 200 200]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+      /F2 6 0 R
+    >>
+  >>
+  /Contents 7 0 R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+      /F2 6 0 R
+    >>
+  >>
+  /Contents 7 0 R
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{object 7 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+20 50 Td
+/F1 12 Tf
+(Hello, world!) Tj
+0 50 Td
+/F2 16 Tf
+(Goodbye, world!) Tj
+ET
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/hello_world_2_pages.pdf b/testing/resources/hello_world_2_pages.pdf
new file mode 100644
index 0000000..4942e3a
--- /dev/null
+++ b/testing/resources/hello_world_2_pages.pdf
@@ -0,0 +1,81 @@
+%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
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+      /F2 6 0 R
+    >>
+  >>
+  /Contents 7 0 R
+>>
+endobj
+4 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+      /F2 6 0 R
+    >>
+  >>
+  /Contents 7 0 R
+>>
+endobj
+5 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+6 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+7 0 obj <<
+  /Length 83
+>>
+stream
+BT
+20 50 Td
+/F1 12 Tf
+(Hello, world!) Tj
+0 50 Td
+/F2 16 Tf
+(Goodbye, world!) Tj
+ET
+endstream
+endobj
+xref
+0 8
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000163 00000 n 
+0000000305 00000 n 
+0000000447 00000 n 
+0000000525 00000 n 
+0000000601 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 8
+>>
+startxref
+735
+%%EOF
diff --git a/testing/resources/hello_world_2_pages_shared_resources_dict.in b/testing/resources/hello_world_2_pages_shared_resources_dict.in
new file mode 100644
index 0000000..9c30780
--- /dev/null
+++ b/testing/resources/hello_world_2_pages_shared_resources_dict.in
@@ -0,0 +1,64 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 2
+  /Kids [3 0 R 4 0 R]
+  /MediaBox [0 0 200 200]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources 5 0 R
+  /Contents 8 0 R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources 5 0 R
+  /Contents 8 0 R
+>>
+endobj
+{{object 5 0}} <<
+  /Font <<
+    /F1 6 0 R
+    /F2 7 0 R
+  >>
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+{{object 7 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{object 8 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+20 50 Td
+/F1 12 Tf
+(Hello, world!) Tj
+0 50 Td
+/F2 16 Tf
+(Goodbye, world!) Tj
+ET
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/hello_world_2_pages_shared_resources_dict.pdf b/testing/resources/hello_world_2_pages_shared_resources_dict.pdf
new file mode 100644
index 0000000..06b2531
--- /dev/null
+++ b/testing/resources/hello_world_2_pages_shared_resources_dict.pdf
@@ -0,0 +1,79 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 2
+  /Kids [3 0 R 4 0 R]
+  /MediaBox [0 0 200 200]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources 5 0 R
+  /Contents 8 0 R
+>>
+endobj
+4 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources 5 0 R
+  /Contents 8 0 R
+>>
+endobj
+5 0 obj <<
+  /Font <<
+    /F1 6 0 R
+    /F2 7 0 R
+  >>
+>>
+endobj
+6 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+7 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+8 0 obj <<
+  /Length 83
+>>
+stream
+BT
+20 50 Td
+/F1 12 Tf
+(Hello, world!) Tj
+0 50 Td
+/F2 16 Tf
+(Goodbye, world!) Tj
+ET
+endstream
+endobj
+xref
+0 9
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000163 00000 n 
+0000000251 00000 n 
+0000000339 00000 n 
+0000000404 00000 n 
+0000000482 00000 n 
+0000000558 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 9
+>>
+startxref
+692
+%%EOF
diff --git a/testing/resources/hello_world_2_pages_split_streams.in b/testing/resources/hello_world_2_pages_split_streams.in
new file mode 100644
index 0000000..ef68f5c
--- /dev/null
+++ b/testing/resources/hello_world_2_pages_split_streams.in
@@ -0,0 +1,75 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 2
+  /Kids [3 0 R 4 0 R]
+  /MediaBox [0 0 200 200]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+      /F2 6 0 R
+    >>
+  >>
+  /Contents 7 0 R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+      /F2 6 0 R
+    >>
+  >>
+  /Contents 7 0 R
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{object 7 0}} [8 0 R 9 0 R]
+{{object 8 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+20 50 Td
+/F1 12 Tf
+(Hello, world!) Tj
+endstream
+endobj
+{{object 9 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+20 100 Td
+/F2 16 Tf
+(Goodbye, world!) Tj
+ET
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/hello_world_2_pages_split_streams.pdf b/testing/resources/hello_world_2_pages_split_streams.pdf
new file mode 100644
index 0000000..3ac829f
--- /dev/null
+++ b/testing/resources/hello_world_2_pages_split_streams.pdf
@@ -0,0 +1,91 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 2
+  /Kids [3 0 R 4 0 R]
+  /MediaBox [0 0 200 200]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+      /F2 6 0 R
+    >>
+  >>
+  /Contents 7 0 R
+>>
+endobj
+4 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+      /F2 6 0 R
+    >>
+  >>
+  /Contents 7 0 R
+>>
+endobj
+5 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+6 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+7 0 obj [8 0 R 9 0 R]
+8 0 obj <<
+  /Length 41
+>>
+stream
+BT
+20 50 Td
+/F1 12 Tf
+(Hello, world!) Tj
+endstream
+endobj
+9 0 obj <<
+  /Length 47
+>>
+stream
+BT
+20 100 Td
+/F2 16 Tf
+(Goodbye, world!) Tj
+ET
+endstream
+endobj
+xref
+0 10
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000163 00000 n 
+0000000305 00000 n 
+0000000447 00000 n 
+0000000525 00000 n 
+0000000601 00000 n 
+0000000623 00000 n 
+0000000715 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 10
+>>
+startxref
+813
+%%EOF
diff --git a/testing/resources/ink_annot.in b/testing/resources/ink_annot.in
new file mode 100644
index 0000000..da90b29
--- /dev/null
+++ b/testing/resources/ink_annot.in
@@ -0,0 +1,48 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [
+    4 0 R 5 0 R
+  ]
+  /Tabs /R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Ink
+  /NM (Ink-1)
+  /F 4
+  /InkList [[159 296 350 411 472 243.42]]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Annot
+  /Subtype /Ink
+  /NM (Ink-2)
+  /F 4
+  /InkList [[259 396 450 511 572 343 42]]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/ink_annot.pdf b/testing/resources/ink_annot.pdf
new file mode 100644
index 0000000..306f28d
--- /dev/null
+++ b/testing/resources/ink_annot.pdf
@@ -0,0 +1,60 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [
+    4 0 R 5 0 R
+  ]
+  /Tabs /R
+>>
+endobj
+4 0 obj <<
+  /Type /Annot
+  /Subtype /Ink
+  /NM (Ink-1)
+  /F 4
+  /InkList [[159 296 350 411 472 243.42]]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+5 0 obj <<
+  /Type /Annot
+  /Subtype /Ink
+  /NM (Ink-2)
+  /F 4
+  /InkList [[259 396 450 511 572 343 42]]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000251 00000 n 
+0000000422 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+593
+%%EOF
diff --git a/testing/resources/javascript/annot_properties.in b/testing/resources/javascript/annot_properties.in
index 939365c..e143ac8 100644
--- a/testing/resources/javascript/annot_properties.in
+++ b/testing/resources/javascript/annot_properties.in
@@ -74,6 +74,15 @@
 } catch (e) {
   app.alert("ERROR: " + e);
 }
+app.alert("Test setting empty name under changed name");
+try {
+  var annot = this.getAnnot(0, "nonesuch");
+  annot.name = "";
+  app.alert("name after empty name change: " + annot.name);
+} catch (e) {
+  app.alert("ERROR: " + e);
+}
+
 endstream
 endobj
 {{object 22 0}} <<
diff --git a/testing/resources/javascript/annot_properties_expected.txt b/testing/resources/javascript/annot_properties_expected.txt
index 54548e6..252fd2b 100644
--- a/testing/resources/javascript/annot_properties_expected.txt
+++ b/testing/resources/javascript/annot_properties_expected.txt
@@ -14,3 +14,5 @@
 Alert: SUCCESS: Document.getAnnot: Object no longer exists.
 Alert: Test lookup under changed name
 Alert: nonesuch after name change: object
+Alert: Test setting empty name under changed name
+Alert: name after empty name change: 
diff --git a/testing/resources/javascript/app_methods.in b/testing/resources/javascript/app_methods.in
index 81fa660..1c01e84 100644
--- a/testing/resources/javascript/app_methods.in
+++ b/testing/resources/javascript/app_methods.in
@@ -19,6 +19,7 @@
   /Parent 2 0 R
   /MediaBox [0 0 612 792]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
@@ -26,7 +27,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
@@ -34,7 +35,8 @@
 {{include expect.js}}
 
 try {
-  expect("app.alert('message', 1, 2, 'title')", 0);
+  // Test unicode support, no particular reason for these CJK characters.
+  expect("app.alert('message \u4023', 1, 2, 'title \u4024')", 0);
   expect("app.alert({'cMsg': 'message', 'cTitle': 'title'})", 0);
   expect("app.alert({'cMsg': 'message', 'cTitle': 'title', 'nIcon': 3, 'nType': 4})", 0);
   expect("app.alert(undefined)", 0);
diff --git a/testing/resources/javascript/app_methods_expected.txt b/testing/resources/javascript/app_methods_expected.txt
index 015ff28..86fb3a9 100644
--- a/testing/resources/javascript/app_methods_expected.txt
+++ b/testing/resources/javascript/app_methods_expected.txt
@@ -1,5 +1,5 @@
-title[icon=1,type=2]: message
-Alert: PASS: app.alert('message', 1, 2, 'title') = 0
+title 䀤[icon=1,type=2]: message 䀣
+Alert: PASS: app.alert('message 䀣', 1, 2, 'title 䀤') = 0
 title: message
 Alert: PASS: app.alert({'cMsg': 'message', 'cTitle': 'title'}) = 0
 title[icon=3,type=4]: message
diff --git a/testing/resources/javascript/app_properties.in b/testing/resources/javascript/app_properties.in
index 1355e64..290fc24 100644
--- a/testing/resources/javascript/app_properties.in
+++ b/testing/resources/javascript/app_properties.in
@@ -25,6 +25,7 @@
   >>
   /MediaBox [0 0 612 792]
 >>
+endobj
 % Page number 1.
 {{object 4 0}} <<
   /Type /Page
@@ -34,6 +35,7 @@
   >>
   /MediaBox [0 0 612 792]
 >>
+endobj
 % Page number 2.
 {{object 5 0}} <<
   /Type /Page
@@ -43,6 +45,7 @@
   >>
   /MediaBox [0 0 612 792]
 >>
+endobj
 % Page number 3.
 {{object 6 0}} <<
   /Type /Page
@@ -52,6 +55,7 @@
   >>
   /MediaBox [0 0 612 792]
 >>
+endobj
 
 % Info
 {{object 9 0}} <<
@@ -66,7 +70,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/apply.in b/testing/resources/javascript/apply.in
index d944e83..e8ec5e2 100644
--- a/testing/resources/javascript/apply.in
+++ b/testing/resources/javascript/apply.in
@@ -23,6 +23,7 @@
   /Contents [21 0 R]
   /MediaBox [0 0 612 792]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
@@ -30,7 +31,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/array_buffer.in b/testing/resources/javascript/array_buffer.in
index 06371ba..3790c6c 100644
--- a/testing/resources/javascript/array_buffer.in
+++ b/testing/resources/javascript/array_buffer.in
@@ -23,6 +23,7 @@
   /Contents [21 0 R]
   /MediaBox [0 0 612 792]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
@@ -30,7 +31,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/bug_1098213.in b/testing/resources/javascript/bug_1098213.in
new file mode 100644
index 0000000..0c99311
--- /dev/null
+++ b/testing/resources/javascript/bug_1098213.in
@@ -0,0 +1,65 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /OpenAction 8 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 2
+  /Kids [
+    3 0 R
+    4 0 R
+  ]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /CropBox [179 173 200 75]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /CropBox [127 214 186 29]
+  /Annots [
+    5 0 R
+    6 0 R
+    7 0 R
+  ]
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Annot
+>
+endobj
+{{object 6 0}} <<
+  /Type /Annot
+>>
+endobj
+{{object 7 0}} <<
+  /Type /Annot
+>>
+endobj
+{{object 8 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS (
+    Object.defineProperty(Array.prototype, 1, {
+      set: (v) => {
+        Object.defineProperty(Array.prototype, 1, {
+          get: () => {}
+        });
+      }
+    });
+    try { this.getAnnots(); } catch (e) { app.alert('Caught: ' + e); }
+    app.alert('Done');
+  )
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/bug_1098213_expected.txt b/testing/resources/javascript/bug_1098213_expected.txt
new file mode 100644
index 0000000..9de1818
--- /dev/null
+++ b/testing/resources/javascript/bug_1098213_expected.txt
@@ -0,0 +1 @@
+Alert: Done
diff --git a/testing/resources/javascript/bug_1142688.in b/testing/resources/javascript/bug_1142688.in
new file mode 100644
index 0000000..6d3825d
--- /dev/null
+++ b/testing/resources/javascript/bug_1142688.in
@@ -0,0 +1,78 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 5 0 R
+  /AcroForm <<
+    /Fields [
+      3 0 R
+      2 0 R
+    ]
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /T (tf1)
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /T (tf0)
+  /AA <<
+    /F 10 0 R
+  >>
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Pages
+  /Count 2
+  /Kids [
+    8 0 R
+    9 0 R
+  ]
+>>
+endobj
+{{object 8 0}} <<
+  /Type /Page
+  /Parent 5 0 R
+  /Annots [3 0 R]
+>>
+endobj
+{{object 9 0}} <<
+  /Type /Page
+  /Parent 5 0 R
+  /Annots [2 0 R]
+>>
+endobj
+{{object 10 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS 13 0 R
+>>
+{{object 13 0}} <<
+  {{streamlen}}
+>>
+stream
+function f3() {
+  // Setup dubious values in event recorder.
+  try { AFSpecial_Format(2); } catch(e) {}
+  try { AFNumber_Format(-302907477,0,1,-6,"",true); } catch(e) {}
+
+  // Exhaust call stack, then do work upon exiting each frame. The
+  // objective is to get any call() made under the covers to throw
+  // with a stack size exception.
+  try { f3(); } catch(e) {}
+  try { AFDate_Keystroke("yymm-dd"); } catch(e) {}
+}
+f3();
+app.alert('Done.');
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/bug_1142688_expected.txt b/testing/resources/javascript/bug_1142688_expected.txt
new file mode 100644
index 0000000..7cb32ca
--- /dev/null
+++ b/testing/resources/javascript/bug_1142688_expected.txt
@@ -0,0 +1 @@
+Alert: Done.
diff --git a/testing/resources/javascript/bug_1314658.in b/testing/resources/javascript/bug_1314658.in
new file mode 100644
index 0000000..f05b3ed
--- /dev/null
+++ b/testing/resources/javascript/bug_1314658.in
@@ -0,0 +1,29 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /]
+  /Subtype /Widget
+  /T (0)
+  /FT /Ch
+  /AA <<
+    /F 5 0 R
+  >>
+  /Annots 4 0 R
+4 0 obj [3 0 R /Ff 393216]
+  /AP<</N<<>>
+stream
+{{object 5 0}} <<
+  /JS (app.alert('done'))
+>>
+endobj
+{{trailer}}
+%%EOF
diff --git a/testing/resources/javascript/bug_1314658_expected.txt b/testing/resources/javascript/bug_1314658_expected.txt
new file mode 100644
index 0000000..daa1eca
--- /dev/null
+++ b/testing/resources/javascript/bug_1314658_expected.txt
@@ -0,0 +1 @@
+Alert: done
diff --git a/testing/resources/javascript/bug_1335681.in b/testing/resources/javascript/bug_1335681.in
new file mode 100644
index 0000000..254e596
--- /dev/null
+++ b/testing/resources/javascript/bug_1335681.in
@@ -0,0 +1,38 @@
+{{header}}
+{{object 1 0}} <<
+  /Pages 1 0 R
+  /OpenAction 2 0 R
+  /Names <<
+      /Dests 3 0 R
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS (
+    app.alert\("Starting"\);
+    this.gotoNamedDest\(""\);
+  )
+>>
+endobj
+{{object 3 0}} <<
+  /Kids 4 0 R
+>>
+endobj
+{{object 4 0}} [
+  << >>
+  << >>
+  <<
+    /Kids [
+      <<
+        /Limits 4 0 R
+      >>
+    ]
+  >>
+]
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/bug_1335681_expected.txt b/testing/resources/javascript/bug_1335681_expected.txt
new file mode 100644
index 0000000..80a6951
--- /dev/null
+++ b/testing/resources/javascript/bug_1335681_expected.txt
@@ -0,0 +1 @@
+Alert: Starting
diff --git a/testing/resources/javascript/bug_1358075.in b/testing/resources/javascript/bug_1358075.in
new file mode 100644
index 0000000..b503bf2
--- /dev/null
+++ b/testing/resources/javascript/bug_1358075.in
@@ -0,0 +1,39 @@
+{{header}}
+{{object 1 0}} <<
+  /Pages 1 0 R
+  /OpenAction 2 0 R
+  /Names <<
+    /Dests 3 0 R
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS (
+        this.gotoNamedDest\("2"\);
+        app.alert\("completed"\);
+  )
+>>
+endobj
+{{object 3 0}} <<
+  /Kids 4 0 R
+>>
+endobj
+{{object 4 0}} [
+  (1)
+  (3)
+  <<
+    /Kids [
+      <<
+        /Limits 4 0 R
+        /Names [(2) []]
+      >>
+    ]
+  >>
+]
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/bug_1358075_expected.txt b/testing/resources/javascript/bug_1358075_expected.txt
new file mode 100644
index 0000000..13d460b
--- /dev/null
+++ b/testing/resources/javascript/bug_1358075_expected.txt
@@ -0,0 +1 @@
+Alert: completed
diff --git a/testing/resources/javascript/bug_1445426.evt b/testing/resources/javascript/bug_1445426.evt
new file mode 100644
index 0000000..265e85b
--- /dev/null
+++ b/testing/resources/javascript/bug_1445426.evt
@@ -0,0 +1,3 @@
+mousedown,left,202,697
+mouseup,left,202,697
+keycode,40
diff --git a/testing/resources/javascript/bug_1445426.in b/testing/resources/javascript/bug_1445426.in
new file mode 100644
index 0000000..1483da7
--- /dev/null
+++ b/testing/resources/javascript/bug_1445426.in
@@ -0,0 +1,114 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm 4 0 R
+  /OpenAction 40 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 2
+  /Kids [
+    32 0 R
+    34 0 R
+  ]
+>>
+endobj
+% Forms
+{{object 4 0}} <<
+  /Fields [
+    10 0 R
+    11 0 R
+  ]
+>>
+endobj
+% Fields
+{{object 10 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /T (Field_TextEdit)
+  /Rect [0 0 612 792]
+>>
+{{object 11 0}} <<
+  /T (Field_ComboBox)
+  /Parent 4 0 R
+  /Kids [12 0 R]
+  /Opt [(a) (b) (c) (d)]
+  /V [(a)]
+>>
+endobj
+{{object 12 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /Ff 131072
+  /Parent 11 0 R
+  /Kids [13 0 R]
+>>
+endobj
+{{object 13 0}} <<
+  /Parent 12 0 R
+  /Type /Annot
+  /Subtype /Widget
+  /Rect [0 0 612 792]
+  /AA << /K 20 0 R >>
+>>
+endobj
+% Pages
+{{object 32 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [13 0 R]
+
+>>
+endobj
+{{object 34 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [10 0 R]
+>>
+endobj
+% Document JS Action
+{{object 40 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS 41 0 R
+>>
+endobj
+% JS program to exexute
+{{object 41 0}} <<
+  {{streamlen}}
+>>
+stream
+var field_text = this.getField("Field_TextEdit");
+var field_combobox = this.getField("Field_ComboBox");
+field_combobox.setFocus();
+this.__defineGetter__("filesize", function new_getter(){
+                                    field_text.setFocus();
+                                    field_combobox.borderStyle="dashed";
+                                    field_combobox.setFocus();
+                                  });
+endstream
+endobj
+% OpenAction action
+{{object 20 0}} <<
+  /S /JavaScript
+  /JS 21 0 R
+>>
+endobj
+% JS program to exexute
+{{object 21 0}} <<
+  {{streamlen}}
+>>
+stream
+var t = this.filesize;
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/bug_361.in b/testing/resources/javascript/bug_361.in
index 4af86a6..386142a 100644
--- a/testing/resources/javascript/bug_361.in
+++ b/testing/resources/javascript/bug_361.in
@@ -24,10 +24,12 @@
   /Contents [21 0 R]
   /MediaBox [0 0 612 792]
 >>
+endobj
 % Forms
 {{object 4 0}} <<
   /Fields [5 0 R]
 >>
+endobj
 % Field
 {{object 5 0}} <<
  /FT /Tx
@@ -36,6 +38,7 @@
  /Subtype /Widget
  /Rect [100 200 150 250]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
@@ -43,7 +46,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/bug_492_1.in b/testing/resources/javascript/bug_492_1.in
index 9d48ad5..4b8026f 100644
--- a/testing/resources/javascript/bug_492_1.in
+++ b/testing/resources/javascript/bug_492_1.in
@@ -50,7 +50,7 @@
   /JS 21 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 21 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/bug_494057.in b/testing/resources/javascript/bug_494057.in
index bbfa13e..3615d03 100644
--- a/testing/resources/javascript/bug_494057.in
+++ b/testing/resources/javascript/bug_494057.in
@@ -87,7 +87,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/bug_679642.in b/testing/resources/javascript/bug_679642.in
index 644a259..b63cb70 100644
--- a/testing/resources/javascript/bug_679642.in
+++ b/testing/resources/javascript/bug_679642.in
@@ -86,7 +86,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/bug_679643.in b/testing/resources/javascript/bug_679643.in
index c60fc0f..5ebe312 100644
--- a/testing/resources/javascript/bug_679643.in
+++ b/testing/resources/javascript/bug_679643.in
@@ -86,7 +86,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/bug_695826.in b/testing/resources/javascript/bug_695826.in
index e17eaf1..9d32be7 100644
--- a/testing/resources/javascript/bug_695826.in
+++ b/testing/resources/javascript/bug_695826.in
@@ -23,6 +23,7 @@
   /Contents [21 0 R]
   /MediaBox [0 0 612 792]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
@@ -30,7 +31,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/bug_735912.in b/testing/resources/javascript/bug_735912.in
index 06bd9a3..f3db5a2 100644
--- a/testing/resources/javascript/bug_735912.in
+++ b/testing/resources/javascript/bug_735912.in
@@ -53,7 +53,7 @@
   /JS 31 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 31 0}} <<
   {{streamlen}}
 >>
@@ -69,7 +69,7 @@
   /JS 35 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 35 0}} <<
   {{streamlen}}
 >>
@@ -85,7 +85,7 @@
   /JS 37 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 37 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/bug_740166.in b/testing/resources/javascript/bug_740166.in
index 425374d..6f0c829 100644
--- a/testing/resources/javascript/bug_740166.in
+++ b/testing/resources/javascript/bug_740166.in
@@ -24,10 +24,12 @@
   /Contents [21 0 R]
   /MediaBox [0 0 612 792]
 >>
+endobj
 % Forms
 {{object 4 0}} <<
   /Fields [5 0 R]
 >>
+endobj
 % Field
 {{object 5 0}} <<
  /FT /Tx
@@ -36,6 +38,7 @@
  /Subtype /Widget
  /Rect [100 200 150 250]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
@@ -43,7 +46,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/bug_959274_1.in b/testing/resources/javascript/bug_959274_1.in
index 74909c0..9efb9dc 100644
--- a/testing/resources/javascript/bug_959274_1.in
+++ b/testing/resources/javascript/bug_959274_1.in
@@ -23,6 +23,7 @@
   /Contents [21 0 R]
   /MediaBox [0 0 612 792]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
diff --git a/testing/resources/javascript/color_methods.in b/testing/resources/javascript/color_methods.in
index bf03bb4..44afced 100644
--- a/testing/resources/javascript/color_methods.in
+++ b/testing/resources/javascript/color_methods.in
@@ -27,7 +27,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/color_properties.in b/testing/resources/javascript/color_properties.in
index 7d09656..0fea81e 100644
--- a/testing/resources/javascript/color_properties.in
+++ b/testing/resources/javascript/color_properties.in
@@ -20,6 +20,7 @@
   /Contents [21 0 R]
   /MediaBox [0 0 612 792]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
@@ -27,7 +28,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/console_methods.in b/testing/resources/javascript/console_methods.in
index 0fbba3f..0dc74e3 100644
--- a/testing/resources/javascript/console_methods.in
+++ b/testing/resources/javascript/console_methods.in
@@ -27,7 +27,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/constructor.in b/testing/resources/javascript/constructor.in
index 1211f89..90c41c0 100644
--- a/testing/resources/javascript/constructor.in
+++ b/testing/resources/javascript/constructor.in
@@ -19,6 +19,7 @@
   /Parent 2 0 R
   /MediaBox [0 0 612 792]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
@@ -26,34 +27,18 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
 stream
-function testIllegalConstructor(name, allowed) {
-  const constructorString = name + ".constructor";
-  let constructor;
-  try {
-    constructor = eval(constructorString);
-  } catch (e) {
-    app.alert("FAIL: No such " + constructorString);
-    return;
-  }
-  try {
-    constructor();
-    app.alert("FAIL: " + constructorString + "(): returned");
-  } catch (e) {
-    app.alert("PASS: " + constructorString + "(): " + e);
-  }
-  try {
-    new constructor;
-    app.alert("FAIL: new " + constructorString + ": returned");
-  } catch (e) {
-    app.alert("PASS: new " + constructorString + ": " + e);
-  }
-}
+{{include constructor.js}}
+
+// Global objects
 testIllegalConstructor("this");
+testIllegalConstructor("globalThis");
+
+// Static objects
 testIllegalConstructor("app");
 testIllegalConstructor("event");
 testIllegalConstructor("font");
@@ -69,6 +54,11 @@
 testIllegalConstructor("zoomtype");
 testIllegalConstructor("scaleHow");
 testIllegalConstructor("scaleWhen");
+
+// Dynamic objects
+timer1 = app.setTimeOut("var marked = true", 1000);
+testLegalConstructor("timer1");
+
 endstream
 endobj
 {{xref}}
diff --git a/testing/resources/javascript/constructor.js b/testing/resources/javascript/constructor.js
new file mode 100644
index 0000000..f8c98aa
--- /dev/null
+++ b/testing/resources/javascript/constructor.js
@@ -0,0 +1,49 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+function testLegalConstructor(name, allowed) {
+  const constructorString = name + ".constructor";
+  var constructor;
+  try {
+    constructor = eval(constructorString);
+  } catch (e) {
+    app.alert("FAIL: No such " + constructorString);
+    return;
+  }
+  try {
+    constructor();
+    app.alert("FAIL: " + constructorString + "(): returned");
+  } catch (e) {
+    app.alert("PASS: " + constructorString + "(): " + e);
+  }
+  try {
+    var thing = new constructor;
+    app.alert("PASS: new " + constructorString + ": " + thing);
+  } catch (e) {
+    app.alert("FAIL: new " + constructorString + ": " + e);
+  }
+}
+
+function testIllegalConstructor(name, allowed) {
+  const constructorString = name + ".constructor";
+  var constructor;
+  try {
+    constructor = eval(constructorString);
+  } catch (e) {
+    app.alert("FAIL: No such " + constructorString);
+    return;
+  }
+  try {
+    constructor();
+    app.alert("FAIL: " + constructorString + "(): returned");
+  } catch (e) {
+    app.alert("PASS: " + constructorString + "(): " + e);
+  }
+  try {
+    new constructor;
+    app.alert("FAIL: new " + constructorString + ": returned");
+  } catch (e) {
+    app.alert("PASS: new " + constructorString + ": " + e);
+  }
+}
diff --git a/testing/resources/javascript/constructor_expected.txt b/testing/resources/javascript/constructor_expected.txt
index af03337..8c58aad 100644
--- a/testing/resources/javascript/constructor_expected.txt
+++ b/testing/resources/javascript/constructor_expected.txt
@@ -1,5 +1,7 @@
 Alert: PASS: this.constructor(): illegal constructor
 Alert: PASS: new this.constructor: not a dynamic object
+Alert: PASS: globalThis.constructor(): illegal constructor
+Alert: PASS: new globalThis.constructor: not a dynamic object
 Alert: PASS: app.constructor(): illegal constructor
 Alert: PASS: new app.constructor: not a dynamic object
 Alert: PASS: event.constructor(): illegal constructor
@@ -30,3 +32,5 @@
 Alert: PASS: new scaleHow.constructor: not a dynamic object
 Alert: PASS: scaleWhen.constructor(): illegal constructor
 Alert: PASS: new scaleWhen.constructor: not a dynamic object
+Alert: PASS: timer1.constructor(): illegal constructor
+Alert: PASS: new timer1.constructor: [object Object]
diff --git a/testing/resources/javascript/consts.in b/testing/resources/javascript/consts.in
index dbfd579..b4180ff 100644
--- a/testing/resources/javascript/consts.in
+++ b/testing/resources/javascript/consts.in
@@ -23,6 +23,7 @@
   /Contents [21 0 R]
   /MediaBox [0 0 612 792]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
@@ -30,7 +31,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/document_methods.in b/testing/resources/javascript/document_methods.in
index 9571e55..5db9775 100644
--- a/testing/resources/javascript/document_methods.in
+++ b/testing/resources/javascript/document_methods.in
@@ -26,6 +26,7 @@
   /MediaBox [0 0 612 792]
   /Contents 8 0 R
 >>
+endobj
 % Page number 1.
 {{object 4 0}} <<
   /Type /Page
@@ -35,6 +36,7 @@
   >>
   /MediaBox [0 0 612 792]
 >>
+endobj
 % Page number 2.
 {{object 5 0}} <<
   /Type /Page
@@ -44,6 +46,7 @@
   >>
   /MediaBox [0 0 612 792]
 >>
+endobj
 % Page number 3.
 {{object 6 0}} <<
   /Type /Page
@@ -53,6 +56,7 @@
   >>
   /MediaBox [0 0 612 792]
 >>
+endobj
 % Contents of the page.
 {{object 8 0}} <<
   {{streamlen}}
@@ -78,7 +82,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/document_properties.in b/testing/resources/javascript/document_properties.in
index fa7d92a..44a108b 100644
--- a/testing/resources/javascript/document_properties.in
+++ b/testing/resources/javascript/document_properties.in
@@ -25,6 +25,7 @@
   >>
   /MediaBox [0 0 612 792]
 >>
+endobj
 % Page number 1.
 {{object 4 0}} <<
   /Type /Page
@@ -34,6 +35,7 @@
   >>
   /MediaBox [0 0 612 792]
 >>
+endobj
 % Page number 2.
 {{object 5 0}} <<
   /Type /Page
@@ -43,6 +45,7 @@
   >>
   /MediaBox [0 0 612 792]
 >>
+endobj
 % Page number 3.
 {{object 6 0}} <<
   /Type /Page
@@ -52,6 +55,7 @@
   >>
   /MediaBox [0 0 612 792]
 >>
+endobj
 
 % Info
 {{object 9 0}} <<
@@ -66,7 +70,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/document_properties_expected.txt b/testing/resources/javascript/document_properties_expected.txt
index 5bab82f..ba89da0 100644
--- a/testing/resources/javascript/document_properties_expected.txt
+++ b/testing/resources/javascript/document_properties_expected.txt
@@ -237,13 +237,13 @@
 Alert: this.zoomType = 3; yields 3
 Alert: *** Getting properties ***
 Alert: this.ADBE is undefined undefined
-Alert: this.author is string 3
+Alert: this.author is string Joe Random Author
 Alert: this.baseURL is string 3
 Alert: this.bookmarkRoot is undefined undefined
 Alert: this.calculate is boolean true
 Alert: this.Collab is undefined undefined
-Alert: this.creationDate is string 3
-Alert: this.creator is string 3
+Alert: this.creationDate is string 
+Alert: this.creator is string Joe Random Creator
 Alert: this.delay is boolean true
 Alert: this.dirty is boolean true
 Alert: this.documentFileName is string 
@@ -251,10 +251,10 @@
 Alert: this.filesize is number 0
 Alert: this.icons is undefined undefined
 Alert: this.info is object [object Object]
-Alert: this.keywords is string 3
+Alert: this.keywords is string 
 Alert: this.layout is undefined undefined
 Alert: this.media is undefined undefined
-Alert: this.modDate is string 3
+Alert: this.modDate is string 
 Alert: this.mouseX is undefined undefined
 Alert: this.mouseY is undefined undefined
 Alert: this.numFields is number 0
@@ -262,9 +262,9 @@
 Alert: this.pageNum is undefined undefined
 Alert: this.pageWindowRect is undefined undefined
 Alert: this.path is string /myfile.pdf
-Alert: this.producer is string 3
-Alert: this.subject is string 3
-Alert: this.title is string 3
+Alert: this.producer is string 
+Alert: this.subject is string 
+Alert: this.title is string 
 Alert: this.URL is string myfile.pdf
 Alert: this.zoom is undefined undefined
 Alert: this.zoomType is undefined undefined
diff --git a/testing/resources/javascript/expect.js b/testing/resources/javascript/expect.js
index 832f1d6..a7a5643 100644
--- a/testing/resources/javascript/expect.js
+++ b/testing/resources/javascript/expect.js
@@ -1,3 +1,7 @@
+// Copyright 2022 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
 function expect(expression, expected) {
   try {
     var actual = eval(expression);
diff --git a/testing/resources/javascript/field.fragment b/testing/resources/javascript/field.fragment
index 335e1ae..ff8f7f4 100644
--- a/testing/resources/javascript/field.fragment
+++ b/testing/resources/javascript/field.fragment
@@ -46,6 +46,7 @@
     10 0 R
     11 0 R
     12 0 R
+    13 0 R
   ]
 >>
 endobj
@@ -68,6 +69,9 @@
   /Parent 5 0 R
   /T (MyPushButton)
   /Rect [220 221 240 241]
+  /MK <<
+    /TP 4
+  >>
 >>
 endobj
 {{object 8 0}} <<
@@ -132,6 +136,20 @@
   /V ("/path/to/file.pdf")
 >>
 endobj
+% Questionable TP value.
+{{object 13 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /Ff 65536
+  /Parent 5 0 R
+  /T (MyBadPushButton)
+  /Rect [400 421 440 441]
+  /MK <<
+    /TP 7
+  >>
+>>
+endobj
 % OpenAction action
 {{object 15 0}} <<
   /Type /Action
diff --git a/testing/resources/javascript/field_methods.in b/testing/resources/javascript/field_methods.in
index 81b9eed..4684552 100644
--- a/testing/resources/javascript/field_methods.in
+++ b/testing/resources/javascript/field_methods.in
@@ -1,7 +1,7 @@
 {{header}}
 {{include field.fragment}}
 
-% JS program to exexute
+% JS program to execute
 {{object 16 0}} <<
   {{streamlen}}
 >>
@@ -51,7 +51,18 @@
 testGetArray();
 
 expect("this.getField('MyField.MyPushButton').buttonGetCaption()", "");
+expect("this.getField('MyField.MyPushButton').buttonGetCaption(0)", "");
+expect("this.getField('MyField.MyPushButton').buttonGetCaption(1)", "");
+expect("this.getField('MyField.MyPushButton').buttonGetCaption(2)", "");
+expectError("this.getField('MyField.MyPushButton').buttonGetCaption(3)")
+expectError("this.getField('MyField.MyMultiSelect').buttonGetCaption()")
+
 expect("this.getField('MyField.MyPushButton').buttonGetIcon()", "[object Object]");
+expect("this.getField('MyField.MyPushButton').buttonGetIcon(0)", "[object Object]");
+expect("this.getField('MyField.MyPushButton').buttonGetIcon(1)", "[object Object]");
+expectError("this.getField('MyField.MyPushButton').buttonGetIcon(3)");
+expectError("this.getField('MyField.MyMultiSelect').buttonGetIcon()");
+
 expect("this.getField('MyField.MyPushButton').buttonImportIcon()", undefined);
 
 expect("this.getField('MyField.MyFile').browseForFileToSubmit()", undefined);
diff --git a/testing/resources/javascript/field_methods_expected.txt b/testing/resources/javascript/field_methods_expected.txt
index 8d7ec84..99dbf09 100644
--- a/testing/resources/javascript/field_methods_expected.txt
+++ b/testing/resources/javascript/field_methods_expected.txt
@@ -8,7 +8,8 @@
 Alert: dotdot1 is 
 Alert: dotdot2 is MyField.MyPushButton
 Alert: dotdot3 is MyField
-Alert: found 7 sub-fields:
+Alert: found 8 sub-fields:
+Alert: MyField.MyBadPushButton
 Alert: MyField.MyCheckBox
 Alert: MyField.MyFile
 Alert: MyField.MyMultiSelect
@@ -17,7 +18,16 @@
 Alert: MyField.MySingleSelect
 Alert: MyField.MyText
 Alert: PASS: this.getField('MyField.MyPushButton').buttonGetCaption() = 
+Alert: PASS: this.getField('MyField.MyPushButton').buttonGetCaption(0) = 
+Alert: PASS: this.getField('MyField.MyPushButton').buttonGetCaption(1) = 
+Alert: PASS: this.getField('MyField.MyPushButton').buttonGetCaption(2) = 
+Alert: PASS: this.getField('MyField.MyPushButton').buttonGetCaption(3) threw Field.buttonGetCaption: Incorrect parameter value.
+Alert: PASS: this.getField('MyField.MyMultiSelect').buttonGetCaption() threw Field.buttonGetCaption: Object is of the wrong type.
 Alert: PASS: this.getField('MyField.MyPushButton').buttonGetIcon() = [object Object]
+Alert: PASS: this.getField('MyField.MyPushButton').buttonGetIcon(0) = [object Object]
+Alert: PASS: this.getField('MyField.MyPushButton').buttonGetIcon(1) = [object Object]
+Alert: PASS: this.getField('MyField.MyPushButton').buttonGetIcon(3) threw Field.buttonGetIcon: Incorrect parameter value.
+Alert: PASS: this.getField('MyField.MyMultiSelect').buttonGetIcon() threw Field.buttonGetIcon: Object is of the wrong type.
 Alert: PASS: this.getField('MyField.MyPushButton').buttonImportIcon() = undefined
 Alert: PASS: this.getField('MyField.MyFile').browseForFileToSubmit() = undefined
 Alert: PASS: this.getField('MyField.MyMultiSelect').getItemAt(0) = foo
diff --git a/testing/resources/javascript/field_properties.in b/testing/resources/javascript/field_properties.in
index 2126384..3887787 100644
--- a/testing/resources/javascript/field_properties.in
+++ b/testing/resources/javascript/field_properties.in
@@ -1,6 +1,6 @@
 {{header}}
 {{include field.fragment}}
-% JS program to exexute
+% JS program to execute
 {{object 16 0}} <<
   {{streamlen}}
 >>
@@ -11,6 +11,7 @@
     var field = this.getField("MyField");
     var text = this.getField("MyField.MyText");
     var button = this.getField("MyField.MyPushButton");
+    var badbutton = this.getField("MyField.MyBadPushButton");
     var radio = this.getField("MyField.MyRadio");
     var list = this.getField("MyField.MyMultiSelect");
     var check = this.getField("MyField.MyCheckBox");
@@ -23,6 +24,7 @@
     testFieldPropertiesCase(field);
     testTextPropertiesCase(text);
     testPushButtonPropertiesCase(button);
+    testBadPushButtonPropertiesCase(badbutton);
     testRadioButtonPropertiesCase(radio);
     testCheckBoxPropertiesCase(check);
     testListBoxPropertiesCase(list);
@@ -68,7 +70,7 @@
     testROProperty(field, "page", 0);
     testRIProperty(field, "password", false, 42);
     testRWProperty(field, "print", true, false);
-    testRIProperty(field, "readonly", false, true);
+    testRWProperty(field, "readonly", false, true);
     testROProperty(field, "rect", [200,221,220,201]);
     // testROProperty(field, "required", "clams");
     testRIProperty(field, "richText", false, true);
@@ -94,7 +96,7 @@
     testRIProperty(field, "buttonAlignX", 0, 50);
     testRIProperty(field, "buttonAlignY", 0, 50);
     testRIProperty(field, "buttonFitBounds", false);
-    testRIProperty(field, "buttonPosition", 0);
+    testRIProperty(field, "buttonPosition", 4);
     testRIProperty(field, "buttonScaleHow", 0);
     testRIProperty(field, "buttonScaleWhen", 0);
     testRIProperty(field, "highlight", "invert");
@@ -104,11 +106,19 @@
   }
 }
 
+function testBadPushButtonPropertiesCase(field) {
+  try {
+    testRIProperty(field, "buttonPosition", 7); // not checked.
+  } catch (e) {
+    app.alert("Unexpected error: " + e);
+  }
+}
+
 function testRadioButtonPropertiesCase(field) {
   try {
-    testROProperty(field, "exportValues", "N");
+    testROProperty(field, "exportValues", "Yes");
     testRIProperty(field, "radiosInUnison", false);
-    testRIProperty(field, "style", "check");
+    testRIProperty(field, "style", "circle");
     testROProperty(field, "type", "radiobutton");
     testRIProperty(field, "value", "Off");
     testROProperty(field, "valueAsString", "Off");
@@ -119,7 +129,7 @@
 
 function testCheckBoxPropertiesCase(field) {
   try {
-    testROProperty(field, "exportValues", "N");
+    testROProperty(field, "exportValues", "Yes");
     testRIProperty(field, "style", "check");
     testROProperty(field, "type", "checkbox");
     testRIProperty(field, "value", "Off");
diff --git a/testing/resources/javascript/field_properties_expected.txt b/testing/resources/javascript/field_properties_expected.txt
index 3b2e6f6..2c57daf 100644
--- a/testing/resources/javascript/field_properties_expected.txt
+++ b/testing/resources/javascript/field_properties_expected.txt
@@ -51,7 +51,7 @@
 Alert: PASS: print = true
 Alert: PASS: print = false
 Alert: PASS: readonly = false
-Alert: PASS: readonly = false
+Alert: PASS: readonly = true
 Alert: PASS: rect = 200,221,220,201
 Alert: PASS: rect threw Field.rect: Incorrect parameter value.
 Alert: PASS: richText = false
@@ -84,8 +84,8 @@
 Alert: PASS: buttonAlignY = 0
 Alert: PASS: buttonFitBounds = false
 Alert: PASS: buttonFitBounds = false
-Alert: PASS: buttonPosition = 0
-Alert: PASS: buttonPosition = 0
+Alert: PASS: buttonPosition = 4
+Alert: PASS: buttonPosition = 4
 Alert: PASS: buttonScaleHow = false
 Alert: PASS: buttonScaleHow = false
 Alert: PASS: buttonScaleWhen = 0
@@ -94,19 +94,21 @@
 Alert: PASS: highlight = invert
 Alert: PASS: type = button
 Alert: PASS: type threw Field.type: Operation not supported.
-Alert: PASS: exportValues = N
+Alert: PASS: buttonPosition = 7
+Alert: PASS: buttonPosition = 7
+Alert: PASS: exportValues = Yes
 Alert: PASS: exportValues threw Field.exportValues: Object no longer exists.
 Alert: PASS: radiosInUnison = false
 Alert: PASS: radiosInUnison = false
-Alert: PASS: style = check
-Alert: PASS: style = check
+Alert: PASS: style = circle
+Alert: PASS: style = circle
 Alert: PASS: type = radiobutton
 Alert: PASS: type threw Field.type: Operation not supported.
 Alert: PASS: value = Off
 Alert: PASS: value = Off
 Alert: PASS: valueAsString = Off
 Alert: PASS: valueAsString threw Field.valueAsString: Operation not supported.
-Alert: PASS: exportValues = N
+Alert: PASS: exportValues = Yes
 Alert: PASS: exportValues threw Field.exportValues: Object no longer exists.
 Alert: PASS: style = check
 Alert: PASS: style = check
diff --git a/testing/resources/javascript/foreground_task.in b/testing/resources/javascript/foreground_task.in
new file mode 100644
index 0000000..dd5444a
--- /dev/null
+++ b/testing/resources/javascript/foreground_task.in
@@ -0,0 +1,43 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /OpenAction 10 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [
+    3 0 R
+  ]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+>>
+endobj
+{{object 10 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS 11 0 R
+>>
+endobj
+{{object 11 0}} <<
+  {{streamlen}}
+>>
+stream
+try {
+  gc({type: 'major', execution: 'async'}).then(() => { app.alert('Resolved') });
+  app.alert('Posted');
+} catch (e) {
+  app.alert('Error: ' + e);
+}
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/foreground_task_expected.txt b/testing/resources/javascript/foreground_task_expected.txt
new file mode 100644
index 0000000..d40e100
--- /dev/null
+++ b/testing/resources/javascript/foreground_task_expected.txt
@@ -0,0 +1,2 @@
+Alert: Posted
+Alert: Resolved
diff --git a/testing/resources/javascript/globals.in b/testing/resources/javascript/globals.in
index de67392..bbca813 100644
--- a/testing/resources/javascript/globals.in
+++ b/testing/resources/javascript/globals.in
@@ -23,6 +23,7 @@
   /Contents [21 0 R]
   /MediaBox [0 0 612 792]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
@@ -30,7 +31,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
@@ -58,7 +59,11 @@
 
   // Test null and undefined.
   { "name": "null_var", "value": null },
-  { "name": "undefined_var", "value": undefined }
+  { "name": "undefined_var", "value": undefined },
+
+  // Test to show unicode currently handled.
+  { "name": "unicode_var", "value": "\u4025\u4026_string" },
+  { "name": "\u4025\u4026_var", "value": "string" },
 ];
 
 function setup_global() {
diff --git a/testing/resources/javascript/globals_expected.txt b/testing/resources/javascript/globals_expected.txt
index fcebd70..9e80a62 100644
--- a/testing/resources/javascript/globals_expected.txt
+++ b/testing/resources/javascript/globals_expected.txt
@@ -10,17 +10,20 @@
 Alert:   object_var = undefined
 Alert:   null_var = undefined
 Alert:   undefined_var = undefined
+Alert:   unicode_var = undefined
+Alert:   䀥䀦_var = undefined
 Alert: ************ After Setup ************
 Alert: Enumerable Globals:
 Alert:   setPersistent = function setPersistent() { [native code] }, own property = true
-Alert:   true_var = true, own property = true
 Alert:   false_var = false, own property = true
-Alert:   zero_var = 0, own property = true
-Alert:   number_var = -3.918, own property = true
-Alert:   string_var = This is a string, own property = true
-Alert:   object_var = [object Object], own property = true
 Alert:   null_var = null, own property = true
-Alert:   undefined_var = undefined, own property = true
+Alert:   number_var = -3.918, own property = true
+Alert:   object_var = [object Object], own property = true
+Alert:   string_var = This is a string, own property = true
+Alert:   true_var = true, own property = true
+Alert:   unicode_var = 䀥䀦_string, own property = true
+Alert:   zero_var = 0, own property = true
+Alert:   䀥䀦_var = string, own property = true
 Alert: Expected Globals:
 Alert:   true_var = true
 Alert:   false_var = false
@@ -33,6 +36,8 @@
 Alert:     blue
 Alert:   null_var = null
 Alert:   undefined_var = undefined
+Alert:   unicode_var = 䀥䀦_string
+Alert:   䀥䀦_var = string
 Alert: ************ After Deletion ************
 Alert: Enumerable Globals:
 Alert:   setPersistent = function setPersistent() { [native code] }, own property = true
@@ -45,18 +50,21 @@
 Alert:   object_var = undefined
 Alert:   null_var = undefined
 Alert:   undefined_var = undefined
+Alert:   unicode_var = undefined
+Alert:   䀥䀦_var = undefined
 Alert: For undefined_var: Set Persistent: ERROR: global.setPersistent: Global value not found.
 Alert: ************ After Setup and Persist false ************
 Alert: Enumerable Globals:
 Alert:   setPersistent = function setPersistent() { [native code] }, own property = true
-Alert:   true_var = true, own property = true
 Alert:   false_var = false, own property = true
-Alert:   zero_var = 0, own property = true
-Alert:   number_var = -3.918, own property = true
-Alert:   string_var = This is a string, own property = true
-Alert:   object_var = [object Object], own property = true
 Alert:   null_var = null, own property = true
-Alert:   undefined_var = undefined, own property = true
+Alert:   number_var = -3.918, own property = true
+Alert:   object_var = [object Object], own property = true
+Alert:   string_var = This is a string, own property = true
+Alert:   true_var = true, own property = true
+Alert:   unicode_var = 䀥䀦_string, own property = true
+Alert:   zero_var = 0, own property = true
+Alert:   䀥䀦_var = string, own property = true
 Alert: Expected Globals:
 Alert:   true_var = true
 Alert:   false_var = false
@@ -69,6 +77,8 @@
 Alert:     blue
 Alert:   null_var = null
 Alert:   undefined_var = undefined
+Alert:   unicode_var = 䀥䀦_string
+Alert:   䀥䀦_var = string
 Alert: For true_var: Set Persistent: ERROR: global.setPersistent: Global value not found.
 Alert: For false_var: Set Persistent: ERROR: global.setPersistent: Global value not found.
 Alert: For zero_var: Set Persistent: ERROR: global.setPersistent: Global value not found.
@@ -77,6 +87,8 @@
 Alert: For object_var: Set Persistent: ERROR: global.setPersistent: Global value not found.
 Alert: For null_var: Set Persistent: ERROR: global.setPersistent: Global value not found.
 Alert: For undefined_var: Set Persistent: ERROR: global.setPersistent: Global value not found.
+Alert: For unicode_var: Set Persistent: ERROR: global.setPersistent: Global value not found.
+Alert: For 䀥䀦_var: Set Persistent: ERROR: global.setPersistent: Global value not found.
 Alert: ************ After Delete and Persist ************
 Alert: Enumerable Globals:
 Alert:   setPersistent = function setPersistent() { [native code] }, own property = true
@@ -89,18 +101,21 @@
 Alert:   object_var = undefined
 Alert:   null_var = undefined
 Alert:   undefined_var = undefined
+Alert:   unicode_var = undefined
+Alert:   䀥䀦_var = undefined
 Alert: For undefined_var: Set Persistent: ERROR: global.setPersistent: Global value not found.
 Alert: ************ After Setup and Persist true ************
 Alert: Enumerable Globals:
 Alert:   setPersistent = function setPersistent() { [native code] }, own property = true
-Alert:   true_var = true, own property = true
 Alert:   false_var = false, own property = true
-Alert:   zero_var = 0, own property = true
-Alert:   number_var = -3.918, own property = true
-Alert:   string_var = This is a string, own property = true
-Alert:   object_var = [object Object], own property = true
 Alert:   null_var = null, own property = true
-Alert:   undefined_var = undefined, own property = true
+Alert:   number_var = -3.918, own property = true
+Alert:   object_var = [object Object], own property = true
+Alert:   string_var = This is a string, own property = true
+Alert:   true_var = true, own property = true
+Alert:   unicode_var = 䀥䀦_string, own property = true
+Alert:   zero_var = 0, own property = true
+Alert:   䀥䀦_var = string, own property = true
 Alert: Expected Globals:
 Alert:   true_var = true
 Alert:   false_var = false
@@ -113,3 +128,5 @@
 Alert:     blue
 Alert:   null_var = null
 Alert:   undefined_var = undefined
+Alert:   unicode_var = 䀥䀦_string
+Alert:   䀥䀦_var = string
diff --git a/testing/resources/javascript/immutable_proto.in b/testing/resources/javascript/immutable_proto.in
new file mode 100644
index 0000000..61885c5
--- /dev/null
+++ b/testing/resources/javascript/immutable_proto.in
@@ -0,0 +1,43 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /OpenAction 10 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [
+    3 0 R
+  ]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+>>
+endobj
+% OpenAction action
+{{object 10 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS 11 0 R
+>>
+endobj
+{{object 11 0}} <<
+  {{streamlen}}
+>>
+stream
+{{include expect.js}}
+expect("this.__proto__", "[object Object]");
+expect("app.__proto__", "[object Object]");
+expectError("this.__proto__ = {}");
+expectError("app.__proto__ = this");
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/immutable_proto_expected.txt b/testing/resources/javascript/immutable_proto_expected.txt
new file mode 100644
index 0000000..7e7c670
--- /dev/null
+++ b/testing/resources/javascript/immutable_proto_expected.txt
@@ -0,0 +1,4 @@
+Alert: PASS: this.__proto__ = [object Object]
+Alert: PASS: app.__proto__ = [object Object]
+Alert: PASS: this.__proto__ = {} threw TypeError: Immutable prototype object '[object global]' cannot have their prototype set
+Alert: PASS: app.__proto__ = this threw TypeError: Immutable prototype object '[object Object]' cannot have their prototype set
diff --git a/testing/resources/javascript/mouse_events.in b/testing/resources/javascript/mouse_events.in
index 4c6935e..b7b5a6d 100644
--- a/testing/resources/javascript/mouse_events.in
+++ b/testing/resources/javascript/mouse_events.in
@@ -24,6 +24,7 @@
   /Contents [21 0 R]
   /MediaBox [0 0 612 792]
 >>
+endobj
 % Forms
 {{object 4 0}} <<
   /Fields [
@@ -32,6 +33,7 @@
     7 0 R
   ]
 >>
+endobj
 % Field with actions:
 % Cursor enter: E
 % Cursor exit: X
@@ -54,6 +56,7 @@
    /Bl 15 0 R
  >>
 >>
+endobj
 {{object 6 0}} <<
  /Type /Annot
  /Subtype /Widget
diff --git a/testing/resources/javascript/named_action.in b/testing/resources/javascript/named_action.in
new file mode 100644
index 0000000..ba8d28b
--- /dev/null
+++ b/testing/resources/javascript/named_action.in
@@ -0,0 +1,33 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /OpenAction 10 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [
+    3 0 R
+  ]
+>>
+endobj
+% Page number 0.
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+>>
+endobj
+% OpenAction action - not really JS, but generates text under test env.
+{{object 10 0}} <<
+  /Type /Action
+  /S /Named
+  /N (Print)
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/named_action_expected.txt b/testing/resources/javascript/named_action_expected.txt
new file mode 100644
index 0000000..1099a7f
--- /dev/null
+++ b/testing/resources/javascript/named_action_expected.txt
@@ -0,0 +1 @@
+Execute named action: Print
diff --git a/testing/resources/javascript/property_test_helpers.js b/testing/resources/javascript/property_test_helpers.js
index 47a25ef..0e405fc 100644
--- a/testing/resources/javascript/property_test_helpers.js
+++ b/testing/resources/javascript/property_test_helpers.js
@@ -1,4 +1,4 @@
-// Copyright 2019 PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/resources/javascript/public_methods_expected.txt b/testing/resources/javascript/public_methods_expected.txt
index 17abeff..70cd8ee 100644
--- a/testing/resources/javascript/public_methods_expected.txt
+++ b/testing/resources/javascript/public_methods_expected.txt
@@ -21,11 +21,11 @@
 Alert: **********************
 Alert: PASS: AFDate_KeystrokeEx() threw AFDate_KeystrokeEx: AFDate_KeystrokeEx's parameter size not correct
 Alert: PASS: AFDate_KeystrokeEx(1, 2) threw AFDate_KeystrokeEx: AFDate_KeystrokeEx's parameter size not correct
-[icon=3,type=0]: The input value can't be parsed as a valid date/time (2).
+AFDate_KeystrokeEx[icon=3,type=0]: The input value can't be parsed as a valid date/time (2).
 Alert: PASS: AFDate_KeystrokeEx(2) = x
-[icon=3,type=0]: The input value can't be parsed as a valid date/time (blooey).
+AFDate_KeystrokeEx[icon=3,type=0]: The input value can't be parsed as a valid date/time (blooey).
 Alert: PASS: AFDate_KeystrokeEx('blooey') = x
-[icon=3,type=0]: The input value can't be parsed as a valid date/time (m/d).
+AFDate_KeystrokeEx[icon=3,type=0]: The input value can't be parsed as a valid date/time (m/d).
 Alert: PASS: AFDate_KeystrokeEx('m/d') = x
 Alert: **********************
 Alert: PASS: AFExtractNums() threw AFExtractNums: Incorrect number of parameters passed to function.
@@ -50,7 +50,7 @@
 Alert: **********************
 Alert: PASS: AFNumber_Keystroke() threw AFNumber_Keystroke: Incorrect number of parameters passed to function.
 Alert: PASS: AFNumber_Keystroke(1) threw AFNumber_Keystroke: Incorrect number of parameters passed to function.
-[icon=3,type=0]: The input value is invalid.
+AFNumber_Keystroke[icon=3,type=0]: The input value is invalid.
 Alert: PASS: AFNumber_Keystroke(1, 2) threw AFNumber_Keystroke: The input value is invalid.
 Alert: PASS: AFNumber_Keystroke(1, 2) = 123
 Alert: PASS: AFNumber_Keystroke(1, 2, 3) = 123
@@ -277,19 +277,19 @@
 Alert: **********************
 Alert: PASS: AFPercent_Keystroke() threw AFPercent_Keystroke: Incorrect number of parameters passed to function.
 Alert: PASS: AFPercent_Keystroke(1) threw AFPercent_Keystroke: Incorrect number of parameters passed to function.
-[icon=3,type=0]: The input value is invalid.
+AFNumber_Keystroke[icon=3,type=0]: The input value is invalid.
 Alert: PASS: AFPercent_Keystroke(1, 0) threw AFPercent_Keystroke: The input value is invalid.
 Alert: PASS: AFPercent_Keystroke(1, 0) = .123
 Alert: **********************
 Alert: PASS: AFRange_Validate() threw AFRange_Validate: Incorrect number of parameters passed to function.
 Alert: PASS: AFRange_Validate(1, 2, 3, 4, 5) threw AFRange_Validate: Incorrect number of parameters passed to function.
-[icon=3,type=0]: The input value must be greater than or equal to 2 and less than or equal to 4.
+AFRange_Validate[icon=3,type=0]: The input value must be greater than or equal to 2 and less than or equal to 4.
 Alert: PASS: AFRange_Validate(true, 2, true, 4) = 1
-[icon=3,type=0]: The input value must be greater than or equal to 2 and less than or equal to 4.
+AFRange_Validate[icon=3,type=0]: The input value must be greater than or equal to 2 and less than or equal to 4.
 Alert: PASS: AFRange_Validate(true, 2, true, 4) = 5
-[icon=3,type=0]: The input value must be greater than or equal to 2.
+AFRange_Validate[icon=3,type=0]: The input value must be greater than or equal to 2.
 Alert: PASS: AFRange_Validate(true, 2, false, 4) = 1
-[icon=3,type=0]: The input value must be less than or equal to 4.
+AFRange_Validate[icon=3,type=0]: The input value must be less than or equal to 4.
 Alert: PASS: AFRange_Validate(false, 2, true, 4) = 5
 Alert: PASS: AFRange_Validate(true, 2, true, 4) = 3
 Alert: PASS: AFRange_Validate(false, 2, true, 4) = 1
@@ -345,11 +345,11 @@
 Alert: **********************
 Alert: PASS: AFSpecial_KeystrokeEx() threw AFSpecial_KeystrokeEx: Incorrect number of parameters passed to function.
 Alert: PASS: AFSpecial_KeystrokeEx('') = 12345
-[icon=3,type=0]: The input value is invalid.
+AFSpecial_KeystrokeEx[icon=3,type=0]: The input value is invalid.
 Alert: PASS: AFSpecial_KeystrokeEx('9999') = 123
-[icon=3,type=0]: The input value is too long.
+AFSpecial_KeystrokeEx[icon=3,type=0]: The input value is too long.
 Alert: PASS: AFSpecial_KeystrokeEx('9999') = 12345
-[icon=3,type=0]: The input value is invalid.
+AFSpecial_KeystrokeEx[icon=3,type=0]: The input value is invalid.
 Alert: PASS: AFSpecial_KeystrokeEx('9999') = abcd
 Alert: PASS: AFSpecial_KeystrokeEx('9999') = 1234
 Alert: PASS: AFSpecial_KeystrokeEx('XXXX') = abcd
@@ -385,13 +385,13 @@
 Alert: PASS: AFSpecial_KeystrokeEx() threw AFSpecial_KeystrokeEx: Incorrect number of parameters passed to function.
 Alert: PASS: AFSpecial_KeystrokeEx('') = 12345
 Alert: PASS: AFSpecial_KeystrokeEx('9999') = 123
-[icon=3,type=0]: The input value is too long.
+AFSpecial_KeystrokeEx[icon=3,type=0]: The input value is too long.
 Alert: PASS: AFSpecial_KeystrokeEx('9999') = 12345
-[icon=3,type=0]: The input value is too long.
+AFSpecial_KeystrokeEx[icon=3,type=0]: The input value is too long.
 Alert: PASS: AFSpecial_KeystrokeEx('9999') = abcd
-[icon=3,type=0]: The input value is too long.
+AFSpecial_KeystrokeEx[icon=3,type=0]: The input value is too long.
 Alert: PASS: AFSpecial_KeystrokeEx('9999') = 1234
-[icon=3,type=0]: The input value is too long.
+AFSpecial_KeystrokeEx[icon=3,type=0]: The input value is too long.
 Alert: PASS: AFSpecial_KeystrokeEx('XXXX') = abcd
 Alert: **********************
 Alert: PASS: AFMergeChange() threw AFMergeChange: Incorrect number of parameters passed to function.
diff --git a/testing/resources/javascript/unsupported.in b/testing/resources/javascript/unsupported.in
index 4d1ad5e..eccfbab 100644
--- a/testing/resources/javascript/unsupported.in
+++ b/testing/resources/javascript/unsupported.in
@@ -19,6 +19,7 @@
   /Parent 2 0 R
   /MediaBox [0 0 612 792]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
@@ -26,7 +27,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/util_bytetochar.in b/testing/resources/javascript/util_bytetochar.in
index 531e679..23dc423 100644
--- a/testing/resources/javascript/util_bytetochar.in
+++ b/testing/resources/javascript/util_bytetochar.in
@@ -23,6 +23,7 @@
   /Contents [21 0 R]
   /MediaBox [0 0 612 792]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
@@ -30,7 +31,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/util_printd.in b/testing/resources/javascript/util_printd.in
index e4c8afc..d0d17b4 100644
--- a/testing/resources/javascript/util_printd.in
+++ b/testing/resources/javascript/util_printd.in
@@ -23,6 +23,7 @@
   /Contents [21 0 R]
   /MediaBox [0 0 612 792]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
@@ -30,7 +31,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/util_printx.in b/testing/resources/javascript/util_printx.in
index 3002051..84d4846 100644
--- a/testing/resources/javascript/util_printx.in
+++ b/testing/resources/javascript/util_printx.in
@@ -23,6 +23,7 @@
   /Contents [21 0 R]
   /MediaBox [0 0 612 792]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
@@ -30,7 +31,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/util_scand.in b/testing/resources/javascript/util_scand.in
index 25462d0..344aacd 100644
--- a/testing/resources/javascript/util_scand.in
+++ b/testing/resources/javascript/util_scand.in
@@ -27,7 +27,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/v8_features.in b/testing/resources/javascript/v8_features.in
index 3d90473..8a5b6af 100644
--- a/testing/resources/javascript/v8_features.in
+++ b/testing/resources/javascript/v8_features.in
@@ -19,6 +19,7 @@
   /Parent 2 0 R
   /MediaBox [0 0 612 792]
 >>
+endobj
 % OpenAction action
 {{object 10 0}} <<
   /Type /Action
@@ -26,7 +27,7 @@
   /JS 11 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 11 0}} <<
   {{streamlen}}
 >>
diff --git a/testing/resources/javascript/xfa_specific/bug_1004106.in b/testing/resources/javascript/xfa_specific/bug_1004106.in
new file mode 100644
index 0000000..926c39b
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1004106.in
@@ -0,0 +1,65 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template>
+  <subform layout="tb" name="subform1">
+    <pageSet id="page" relation="orderedOccurrence">
+      <occur initial="1" max="1" min="1"/>
+      <pageArea id="Page1" name="Page1">
+        <occur max="1" min="1"/>
+        <contentArea h="100mm" w="200mm" x="0.25in" y="0.25in"/>
+        <medium long="297mm" short="210mm" stock="a4"/>
+      </pageArea>
+    </pageSet>
+    <subform layout="tb" name="subform2">
+      <variables name="vara">
+      </variables>
+      <occur initial="1" max="10" min="0" name="occur1">
+      </occur>
+      <field h="10mm" name="field1" w="40mm" x="10mm" y="10mm">
+        <event activity="ready" ref="$form">
+          <script contentType="application/x-javascript">
+            var ref = [];
+
+            app.test = function () {
+              var arr = []
+              var v1 = 1;
+              st = {};
+              var v2 = [0, st, 2, 3];
+              var v3 = [0, "poc", {}];
+              st.toString = function(){
+                for (var i = 0; i != 8; i++) v3.push({});
+                return "poc";
+              }
+
+              // Trigger
+              pfm_rt.Oneof(1, v1, v2, v3);
+            }
+          </script>
+        </event>
+      </field>
+      <field h="10mm" name="field2" w="40mm" x="10mm" y="10mm">
+        <event activity="ready" ref="$form">
+          <script contentType="application/x-formcalc">
+            app.test();
+          </script>
+        </event>
+      </field>
+    </subform>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1026991.evt b/testing/resources/javascript/xfa_specific/bug_1026991.evt
new file mode 100644
index 0000000..37d9022
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1026991.evt
@@ -0,0 +1,5 @@
+mousemove,0,0
+mousedown,left,0,0
+mouseup,left,0,0
+charcode,80
+mousemove,0,200
\ No newline at end of file
diff --git a/testing/resources/javascript/xfa_specific/bug_1026991.in b/testing/resources/javascript/xfa_specific/bug_1026991.in
new file mode 100644
index 0000000..4fb7657
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1026991.in
@@ -0,0 +1,65 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /XFA 4 0 R
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+<xdp xmlns="http://ns.adobe.com/xdp/">
+  <config>
+    <acrobat>
+      <acrobat7>
+        <dynamicRender>required</dynamicRender>
+      </acrobat7>
+    </acrobat>
+    <present>
+      <pdf>
+        <interactive>1</interactive>
+      </pdf>
+    </present>
+  </config>
+  <template>
+    <subform>
+      <bookend leader="$"/>
+      <keep intact="none" previous="contentArea"/>
+      <field name="N01" minH="32in">
+        <ui>
+          <choiceList>
+            <margin rightInset="8in"/>
+          </choiceList>
+        </ui>
+      </field>
+      <field minH="32in">
+        <event activity="change">
+          <script>
+            $host.setFocus("N01")
+          </script>
+        </event>
+      </field>
+    </subform>
+  </template>
+</xdp>
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1039629.evt b/testing/resources/javascript/xfa_specific/bug_1039629.evt
new file mode 100644
index 0000000..96d37be
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1039629.evt
@@ -0,0 +1,3 @@
+mousedown,left,295,187
+mouseup,left,295,187
+keycode,40
\ No newline at end of file
diff --git a/testing/resources/javascript/xfa_specific/bug_1039629.in b/testing/resources/javascript/xfa_specific/bug_1039629.in
new file mode 100644
index 0000000..a911db6
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1039629.in
@@ -0,0 +1,188 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /AcroForm 3 0 R
+  /Pages 5 0 R
+  /NeedsRendering true
+>>
+endobj
+{{object 3 0}} <<
+  /XFA [
+    (preamble) 7 0 R
+    (config) 8 0 R
+    (template) 9 0 R
+    (form) 10 0 R
+    (postamble) 11 0 R
+  ]
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [ 12 0 R ]
+>>
+endobj
+{{object 7 0}} <<
+  {{streamlen}}
+>>
+stream
+<?xml version="1.0" encoding="UTF-8"?>
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/" timeStamp="2007-08-07T18:35:24Z" uuid="3b6c6ea3-3edf-40f5-90ff-51ee202e15b4">
+endstream
+endobj
+{{object 8 0}} <<
+  {{streamlen}}
+>>
+stream
+<config xmlns="http://www.xfa.org/schema/xci/2.6/">
+  <agent name="designer">
+    <destination>pdf</destination>
+    <pdf>
+      <!--  [0..n]  -->
+      <fontInfo/>
+    </pdf>
+  </agent>
+  <present>
+    <!--  [0..n]  -->
+    <pdf>
+      <!--  [0..n]  -->
+      <version>1.7</version>
+      <renderPolicy>client</renderPolicy>
+      <creator>Adobe LiveCycle Designer ES 8.1</creator>
+      <producer>Adobe LiveCycle Designer ES 8.1</producer>
+      <scriptModel>XFA</scriptModel>
+      <interactive>1</interactive>
+      <tagged>1</tagged>
+      <fontInfo>
+        <embed>1</embed>
+      </fontInfo>
+      <compression>
+        <level>6</level>
+        <compressLogicalStructure>1</compressLogicalStructure>
+      </compression>
+      <linearized>1</linearized>
+      <viewerPreferences>
+        <addViewerPreferences>0</addViewerPreferences>
+        <duplexOption>simplex</duplexOption>
+        <numberOfCopies>0</numberOfCopies>
+      </viewerPreferences>
+    </pdf>
+    <cache>
+      <macroCache/>
+      <renderCache/>
+    </cache>
+    <incrementalMerge/>
+    <script>
+      <runScripts/>
+      <exclude/>
+      <currentPage/>
+    </script>
+    <copies/>
+    <layout/>
+    <xdp>
+      <packets>*</packets>
+    </xdp>
+    <destination>pdf</destination>
+  </present>
+  <psMap/>
+  <acrobat>
+    <acrobat7>
+      <dynamicRender>required</dynamicRender>
+    </acrobat7>
+  </acrobat>
+</config>
+endstream
+endobj
+{{object 9 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform name="form1">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <subform h="10.5in" w="8in">
+      <field h="9.0001mm" name="ChoiceList" w="47.625mm" x="6.35mm" y="92.075mm">
+        <ui>
+          <choiceList/>
+        </ui>
+        <items>
+          <text>Foo</text>
+        </items>
+      </field>
+      <field h="100mm" name="ChoiceList1" w="100mm" x="0mm" y="0mm">
+      <ui>
+        <choiceList open="onEntry"/>
+      </ui>
+      <items>
+        <text>1111111111111</text>
+        <text>222222222222222</text>
+      </items>
+      <event activity="change">
+        <script contentType="application/x-javascript">
+        a += 1;
+        if (a == 2) {
+            list1 = xfa.resolveNode("ChoiceList");
+            xfa.host.setFocus(list1);
+            xfa.template.remerge();
+            xfa.host.openList(list1);
+        }
+        </script>
+      </event>
+    </field>
+  </subform>
+    <event activity="docReady">
+      <script contentType="application/x-javascript">
+        a = 0;
+      </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{object 10 0}} <<
+  {{streamlen}}
+>>
+stream
+<form xmlns="http://www.xfa.org/schema/xfa-form/2.6/">
+</form>
+endstream
+endobj
+{{object 11 0}} <<
+  {{streamlen}}
+>>
+stream
+</xdp:xdp>
+endstream
+endobj
+{{object 12 0}} <<
+  /Type /Page
+  /Contents 13 0 R
+  /CropBox [ 0.0 0.0 612.0 792.0 ]
+  /MediaBox [ 0.0 0.0 612.0 792.0 ]
+  /Parent 5 0 R
+  /Resources <<
+    /Font <<
+      /T1_0 14 0 R
+    >>
+    /ProcSet [ /PDF /Text ]
+  >>
+  /Rotate 0
+  /StructParents 0
+>>
+endobj
+{{object 14 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+  /Encoding /WinAnsiEncoding
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1040329.in b/testing/resources/javascript/xfa_specific/bug_1040329.in
new file mode 100644
index 0000000..7117084
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1040329.in
@@ -0,0 +1,99 @@
+{{header}}
+{{object 1 0}} <<
+  /Pages 2 0 R
+  /NeedsRendering true
+  /AcroForm <</XFA 5 0 R>>
+  /OpenAction 3 0 R
+  /Type /Catalog
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [ 4 0 R ]
+>>
+endobj
+{{object 3 0}} <<
+  /S /JavaScript
+  /JS (
+  )
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [ 0 0 795 842 ]
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+<template >
+  <subform allowMacro="0" layout="lr" mergeMode="consumeData" name="subform_0" >
+    <pageSet >
+      <occur initial="1" max="10" min="0" ></occur>
+      <pageArea >
+        <contentArea h="0.75in" w="0.25in" x="0.25in" y="0.25in" ></contentArea>
+        <subform allowMacro="0" layout="row" mergeMode="consumeData" name="subform_1" >
+          <event activity="initialize" >
+            <script contentType="application/x-javascript">
+            </script>
+          </event>
+          <field name="field_1" >
+            <bind match="dataRef" ref="a" ></bind>
+            <calculate >
+              <script contentType="application/x-javascript">
+                var f=xfa.resolveNode("xfa.form.subform_0");
+                xfa.host.setFocus(f);
+                f.mark ="square";
+              </script>
+            </calculate>
+            <event activity="docReady" >
+              <script contentType="application/x-javascript">
+                xfa.host.resetData(this);
+              </script>
+            </event>
+          </field>
+        </subform>
+      </pageArea>
+    </pageSet>
+    <field name="field_2" >
+      <bind match="dataRef" ref="a" ></bind>
+      <calculate >
+        <script contentType="application/x-javascript">
+          xfa.host.setFocus(this);
+        </script>
+      </calculate>
+      <items presence="hidden" save="0" >
+        <text >a</text>
+        <text >b</text>
+        <text >c</text>
+      </items>
+      <ui >
+        <choiceList commitOn="exit" open="onEntry" textEntry="1" ></choiceList>
+      </ui>
+      <value override="0" >
+        <text >a</text>
+      </value>
+      <event activity="initialize" >
+        <script contentType="application/x-javascript">
+        </script>
+      </event>
+      <event activity="change" >
+        <script contentType="application/x-javascript">
+          xfa.form.remerge();
+          xfa.host.openList(this);
+        </script>
+      </event>
+    </field>
+  </subform>
+</template>
+</xdp:xdp>
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1042915.evt b/testing/resources/javascript/xfa_specific/bug_1042915.evt
new file mode 100644
index 0000000..c0cea89
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1042915.evt
@@ -0,0 +1,8 @@
+mousedown,left,0,0
+mouseup,left,0,0
+keycode,9
+keycode,9
+mousedown,left,0,0
+mouseup,left,0,0
+mousedown,left,0,0
+mouseup,left,0,0
\ No newline at end of file
diff --git a/testing/resources/javascript/xfa_specific/bug_1042915.pdf b/testing/resources/javascript/xfa_specific/bug_1042915.pdf
new file mode 100644
index 0000000..36a2c8b
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1042915.pdf
Binary files differ
diff --git a/testing/resources/javascript/xfa_specific/bug_1042915_expected.txt b/testing/resources/javascript/xfa_specific/bug_1042915_expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1042915_expected.txt
diff --git a/testing/resources/javascript/xfa_specific/bug_1043508.in b/testing/resources/javascript/xfa_specific/bug_1043508.in
new file mode 100644
index 0000000..2afe129
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1043508.in
@@ -0,0 +1,62 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /XFA 4 0 R
+  >>
+  /NeedsRendering true
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 3 3]
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+<xdp xmlns="http://ns.adobe.com/xdp/">
+<config>
+  <acrobat>
+    <acrobat7>
+      <dynamicRender>required</dynamicRender>
+    </acrobat7>
+  </acrobat>
+</config>
+<template>
+  <subform>
+    <breakBefore>
+      <script>
+        $.#field[1]=2
+      </script>
+    </breakBefore>
+    <field>
+      <ui>
+        <choiceList open="multiSelect"></choiceList>
+      </ui>
+    </field>
+    <field>
+      <event activity="change">
+        <script>
+          $host.openList(&quot;$form.#field[0]&quot;)
+        </script>
+      </event>
+    </field>
+  </subform>
+</template>
+</xdp>
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
\ No newline at end of file
diff --git a/testing/resources/javascript/xfa_specific/bug_1043510.evt b/testing/resources/javascript/xfa_specific/bug_1043510.evt
index 891a49d..08e98cf 100644
--- a/testing/resources/javascript/xfa_specific/bug_1043510.evt
+++ b/testing/resources/javascript/xfa_specific/bug_1043510.evt
@@ -1 +1 @@
-mousedoubleclick,left,0,0
\ No newline at end of file
+mousedoubleclick,left,0,0
diff --git a/testing/resources/javascript/xfa_specific/bug_1047914.evt b/testing/resources/javascript/xfa_specific/bug_1047914.evt
new file mode 100644
index 0000000..45b948d
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1047914.evt
@@ -0,0 +1,4 @@
+mousedown,left,0,2
+mouseup,left,0,2
+mousedown,left,0,2
+mouseup,left,0,2
\ No newline at end of file
diff --git a/testing/resources/javascript/xfa_specific/bug_1047914.in b/testing/resources/javascript/xfa_specific/bug_1047914.in
new file mode 100644
index 0000000..969d7a0
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1047914.in
@@ -0,0 +1,47 @@
+{{header}}
+{{object 1 0}} <<
+  /AcroForm <<
+    /XFA 2 0 R
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  {{streamlen}}
+>>
+stream
+<xdp xmlns="http://ns.adobe.com/xdp/">
+  <config>
+    <acrobat>
+      <acrobat7>
+        <dynamicRender>required</dynamicRender>
+      </acrobat7>
+    </acrobat>
+    <present>
+      <pdf>
+        <interactive>1</interactive>
+      </pdf>
+    </present>
+  </config>
+  <template>
+    <subform>
+      <bookend leader="$"></bookend>
+      <keep previous="pageArea"></keep>
+      <field anchorType="middleCenter">
+        <ui>
+          <choiceList open="multiSelect">
+            <border><edge stroke="raised"></edge></border>
+            <margin></margin>
+          </choiceList>
+        </ui>
+        <items save="1"><float>41.0</float></items>
+        <items><float>43.0</float><integer>44</integer></items>
+      </field>
+    </subform>
+  </template>
+</xdp>
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
\ No newline at end of file
diff --git a/testing/resources/javascript/xfa_specific/bug_1052651.evt b/testing/resources/javascript/xfa_specific/bug_1052651.evt
new file mode 100644
index 0000000..e88ef06
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1052651.evt
@@ -0,0 +1,4 @@
+mousemove,100,100
+mousedown,left,100,100
+mouseup,left,100,100
+charcode,96
diff --git a/testing/resources/javascript/xfa_specific/bug_1052651.in b/testing/resources/javascript/xfa_specific/bug_1052651.in
new file mode 100644
index 0000000..a26312d
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1052651.in
@@ -0,0 +1,168 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /AcroForm 3 0 R
+  /Pages 5 0 R
+  /NeedsRendering true
+>>
+endobj
+{{object 3 0}} <<
+  /DA (/Helv 0 Tf 0 g )
+  /DR <<
+    /Font <<
+      /Helv 9 0 R
+    >>
+  >>
+  /XFA [
+    (preamble) 10 0 R
+    (config) 11 0 R
+    (template) 12 0 R
+    (form) 13 0 R
+    (postamble) 14 0 R
+  ]
+>>
+endobj
+{{object 5 0}}  <<
+  /Type /Pages
+  /Count 1
+  /Kids [6 0 R]
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Page
+  /CropBox [0.0 0.0 612.0 792.0]
+  /MediaBox [0.0 0.0 612.0 792.0]
+  /Parent 5 0 R
+  /Resources <<
+    /ProcSet [/PDF /Text]
+  >>
+  /Rotate 0
+  /StructParents 0
+>>
+endobj
+{{object 8 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+  /Encoding /WinAnsiEncoding
+>>
+endobj
+{{object 9 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+  /Encoding /WinAnsiEncoding
+>>
+endobj
+{{object 10 0}} <<
+  {{streamlen}}
+>>
+stream
+<?xml version="1.0" encoding="UTF-8"?>
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+endstream
+endobj
+{{object 11 0}} <<
+  {{streamlen}}
+>>
+stream
+<config xmlns="http://www.xfa.org/schema/xci/2.6/">
+  <agent name="designer">
+    <destination>pdf</destination>
+    <pdf>
+      <fontInfo/>
+    </pdf>
+  </agent>
+  <present>
+    <pdf>
+      <version>1.7</version>
+      <renderPolicy>client</renderPolicy>
+      <scriptModel>XFA</scriptModel>
+      <interactive>1</interactive>
+    </pdf>
+    <xdp>
+      <packets>*</packets>
+    </xdp>
+    <destination>pdf</destination>
+  </present>
+  <acrobat>
+    <acrobat7>
+      <dynamicRender>required</dynamicRender>
+    </acrobat7>
+  </acrobat>
+</config>
+endstream
+endobj
+{{object 12 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform layout="tb" locale="en_US" name="form1" restoreState="auto">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <subform h="10.5in" w="8in" name="subform2">
+      <field h="9.0001mm" name="field1" w="47.625mm" x="6.35mm" y="92.075mm">
+        <ui>
+          <choiceList/>
+        </ui>
+        <items save="1">
+          <text>Single</text>
+          <text>Married</text>
+          <text>Other</text>
+        </items>
+      </field>
+      <field name="field2" h="200mm" w="200mm" x="1mm" y="1mm">
+        <ui>
+          <textEdit/>
+        </ui>
+        <value>
+          <text>pdfium</text>
+        </value>
+        <event activity="change">
+          <script contentType="application/x-javascript">
+            f2_change = f2_change + 1;
+            if (f2_change == 2) {
+                xfa.event.cancelAction = true;
+                f1 = xfa.resolveNode("xfa.form..field1");
+                xfa.host.setFocus(f1);
+                xfa.template.remerge();
+                xfa.host.openList(f1);
+            }
+          </script>
+        </event>
+      </field>
+    </subform>
+    <event activity="docReady">
+      <script contentType="application/x-javascript">
+        f2_change = 0;
+        f2 = xfa.resolveNode("xfa.form..field2");
+        xfa.host.setFocus(f2);
+     </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{object 13 0}} <<
+  {{streamlen}}
+>>
+stream
+<form xmlns="http://www.xfa.org/schema/xfa-form/2.6/"></form>
+endstream
+endobj
+{{object 14 0}} <<
+ {{streamlen}}
+>>
+stream
+</xdp:xdp>
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1052786.in b/testing/resources/javascript/xfa_specific/bug_1052786.in
new file mode 100644
index 0000000..b9dd5e9
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1052786.in
@@ -0,0 +1,168 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /AcroForm 3 0 R
+  /Pages 5 0 R
+  /NeedsRendering true
+>>
+endobj
+{{object 3 0}} <<
+  /DA (/Helv 0 Tf 0 g )
+  /DR <<
+    /Font <<
+      /Helv 9 0 R
+    >>
+  >>
+  /XFA [
+    (preamble) 10 0 R
+    (config) 11 0 R
+    (template) 12 0 R
+    (form) 13 0 R
+    (postamble) 14 0 R
+  ]
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [6 0 R]
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Page
+  /CropBox [0.0 0.0 612.0 792.0]
+  /MediaBox [0.0 0.0 612.0 792.0]
+  /Parent 5 0 R
+  /Resources <<
+    /ProcSet [/PDF /Text]
+  >>
+  /Rotate 0
+>>
+endobj
+{{object 8 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+  /Encoding /WinAnsiEncoding
+>>
+endobj
+{{object 9 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+  /Encoding /WinAnsiEncoding
+>>
+endobj
+{{object 10 0}} <<
+  {{streamlen}}
+>>
+stream
+<?xml version="1.0" encoding="UTF-8"?>
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+endstream
+endobj
+{{object 11 0}} <<
+  {{streamlen}}
+>>
+stream
+<config xmlns="http://www.xfa.org/schema/xci/2.6/">
+  <agent name="designer">
+    <destination>pdf</destination>
+    <pdf>
+      <fontInfo/>
+    </pdf>
+  </agent>
+  <present>
+    <pdf>
+      <version>1.7</version>
+      <renderPolicy>client</renderPolicy>
+      <scriptModel>XFA</scriptModel>
+      <interactive>1</interactive>
+    </pdf>
+    <xdp>
+      <packets>*</packets>
+    </xdp>
+    <destination>pdf</destination>
+  </present>
+  <psMap/>
+  <acrobat>
+    <acrobat7>
+      <dynamicRender>required</dynamicRender>
+    </acrobat7>
+  </acrobat>
+</config>
+endstream
+endobj
+{{object 12 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform layout="tb" locale="en_US" name="form1" restoreState="auto">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <subform h="10.5in" w="8in" name="subform2">
+      <field h="9.0001mm" name="choiceList0" w="47.625mm" x="6.35mm" y="92.075mm">
+        <ui>
+          <choiceList/>
+        </ui>
+        <items save="1">
+          <text>Single</text>
+          <text>Married</text>
+          <text>Other</text>
+        </items>
+      </field>
+      <field name="choiceList1" h="10mm" w="1mm" x="5mm" y="50mm">
+        <ui>
+          <textEdit hScrollPolicy="off" vScrollPolicy="off" multiLine="0"/>
+        </ui>
+      <value>
+        <text maxChars="6">pdfium</text>
+      </value>
+      <event activity="full">
+        <script contentType="application/x-javascript">
+          full_count += 1;
+          if (full_count == 2) {
+            f1 = xfa.resolveNode("xfa.form..choiceList0");
+            xfa.host.setFocus(f1);
+            xfa.template.remerge();
+            xfa.host.openList(f1);
+          }
+        </script>
+      </event>
+    </field>
+  </subform>
+    <event activity="docReady">
+      <script contentType="application/x-javascript">
+        full_count = 0;
+        choiceList1 = xfa.resolveNode("xfa.form..choiceList1");
+        choiceList1.rawValue = "12345888888888888888";
+        xfa.host.setFocus(choiceList1);
+      </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{object 13 0}} <<
+  {{streamlen}}
+>>
+stream
+<form xmlns="http://www.xfa.org/schema/xfa-form/2.6/"></form>
+endstream
+endobj
+{{object 14 0}} <<
+  {{streamlen}}
+>>
+stream
+</xdp:xdp>
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1053617.in b/testing/resources/javascript/xfa_specific/bug_1053617.in
new file mode 100644
index 0000000..1e98bdc
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1053617.in
@@ -0,0 +1,155 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /AcroForm 2 0 R
+  /Pages 3 0 R
+  /Extensions <<
+    /ADBE <<
+      /BaseVersion /1.7
+      /ExtensionLevel 8
+    >>
+  >>
+  /NeedsRendering true
+>>
+endobj
+{{object 2 0}} <<
+  /XFA [
+    (preamble) 5 0 R
+    (config) 6 0 R
+    (template) 7 0 R
+    (postamble) 9 0 R
+  ]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [4 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+endstream
+endobj
+{{object 6 0}} <<
+  {{streamlen}}
+>>
+stream
+<config xmlns="http://www.xfa.org/schema/xci/3.0/">
+<agent name="designer">
+  <destination>pdf</destination>
+  <pdf>
+    <fontInfo/>
+  </pdf>
+</agent>
+<present>
+  <pdf>
+    <version>1.7</version>
+    <adobeExtensionLevel>8</adobeExtensionLevel>
+    <renderPolicy>client</renderPolicy>
+    <scriptModel>XFA</scriptModel>
+    <interactive>1</interactive>
+  </pdf>
+  <xdp>
+    <packets>*</packets>
+  </xdp>
+  <destination>pdf</destination>
+  <script>
+    <runScripts>server</runScripts>
+  </script>
+</present>
+<acrobat>
+  <acrobat7>
+    <dynamicRender>required</dynamicRender>
+  </acrobat7>
+  <validate>preSubmit</validate>
+</acrobat>
+</config>
+endstream
+endobj
+{{object 7 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform name="form1">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <subform h="10.5in" w="8in" name="subform2">
+      <field name="f1" h="10mm" w="10mm" x="20mm" y="20mm">
+        <ui>
+          <choiceList/>
+        </ui>
+        <items save="1">
+          <text>Single</text>
+        </items>
+      </field>
+      <subform name="f4" x="5mm" y="5mm">
+          <occur max="-1"/>
+          <field name="f2" h="10mm" w="10mm" x="1mm" y="1mm">
+            <ui>
+              <dateTimeEdit/>
+            </ui>
+            <value>
+              <date/>
+            </value>
+            <format>
+              <picture>date{MM/DD/YY}</picture>
+            </format>
+            <event activity="initialize">
+              <script contentType="application/x-javascript">
+                this.rawValue = "2020" + "-" + "12" + "-" + "10";
+              </script>
+            </event>
+            <event activity="change">
+              <script contentType="application/x-javascript">
+                a = a + 1;
+                if (a == 2) {
+                  xfa.event.cancelAction = true;
+                  c = xfa.resolveNode("xfa.form..f1");
+                  xfa.host.setFocus(c);
+                  d = xfa.resolveNode("xfa.form..f4");
+                  d.instanceManager.addInstance(1);
+                  d.instanceManager.removeInstance(0);
+                  xfa.host.openList(c);
+                }
+              </script>
+            </event>
+          </field>
+      </subform>
+    </subform>
+    <event activity="docReady">
+      <script contentType="application/x-javascript">
+        a = 0;
+        b = xfa.resolveNode("xfa.form..f2");
+        xfa.host.setFocus(b);
+      </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{object 9 0}} <<
+  {{streamlen}}
+>>
+stream
+</xdp:xdp>
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1054429.evt b/testing/resources/javascript/xfa_specific/bug_1054429.evt
new file mode 100644
index 0000000..86e0905
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1054429.evt
@@ -0,0 +1,2 @@
+mousedown,left,61,51
+keycode,46
\ No newline at end of file
diff --git a/testing/resources/javascript/xfa_specific/bug_1054429.in b/testing/resources/javascript/xfa_specific/bug_1054429.in
new file mode 100644
index 0000000..7295e5f
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1054429.in
@@ -0,0 +1,147 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /AcroForm 2 0 R
+  /Pages 3 0 R
+  /Extensions <<
+    /ADBE <<
+      /BaseVersion /1.7
+      /ExtensionLevel 8
+    >>
+  >>
+  /NeedsRendering true
+>>
+endobj
+{{object 2 0}} <<
+  /XFA [
+    (preamble) 5 0 R
+    (config) 6 0 R
+    (template) 7 0 R
+    (postamble) 9 0 R
+  ]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [4 0 R]
+>>
+endobj
+{{object 4 0}} <<
+ /Type /Page
+ /Parent 2 0 R
+ /MediaBox [0 0 612 792]
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+endstream
+endobj
+{{object 6 0}} <<
+  {{streamlen}}
+>>
+stream
+<config xmlns="http://www.xfa.org/schema/xci/3.0/">
+<agent name="designer">
+  <destination>pdf</destination>
+  <pdf>
+    <fontInfo/>
+  </pdf>
+</agent>
+<present>
+  <pdf>
+    <version>1.7</version>
+    <adobeExtensionLevel>8</adobeExtensionLevel>
+    <renderPolicy>client</renderPolicy>
+    <scriptModel>XFA</scriptModel>
+    <interactive>1</interactive>
+  </pdf>
+  <xdp>
+    <packets>*</packets>
+  </xdp>
+  <destination>pdf</destination>
+  <script>
+    <runScripts>server</runScripts>
+  </script>
+</present>
+<acrobat>
+  <acrobat7>
+    <dynamicRender>required</dynamicRender>
+  </acrobat7>
+  <validate>preSubmit</validate>
+</acrobat>
+</config>
+endstream
+endobj
+{{object 7 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform layout="tb" locale="en_US" name="form1" restoreState="auto">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <subform h="10.5in" w="8in" name="subform2">
+      <field h="10mm" name="f1" w="40mm" x="5mm" y="90mm">
+        <ui>
+          <choiceList/>
+        </ui>
+        <items save="1">
+          <text>Single</text>
+        </items>
+      </field>
+      <subform name="f4" x="5mm" y="5mm">
+          <occur max="-1"/>
+          <field name="f2" h="300mm" w="300mm" x="1mm" y="1mm">
+            <ui>
+              <textEdit/>
+            </ui>
+            <value>
+              <text>pdfiummmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm</text>
+            </value>
+            <event activity="change">
+              <script contentType="application/x-javascript">
+                a = a + 1;
+                if (a == 2)  {
+                    xfa.event.cancelAction = true;
+                    b = xfa.resolveNode("xfa.form..f1");
+                    xfa.host.setFocus(b);
+                    d = xfa.resolveNode("xfa.form..f4");
+                    d.instanceManager.addInstance(1);
+                    d.instanceManager.removeInstance(0);
+                    xfa.host.openList(b);
+                }
+              </script>
+            </event>
+          </field>
+      </subform>
+    </subform>
+    <event activity="docReady">
+      <script contentType="application/x-javascript">
+        a = 0;
+        c = xfa.resolveNode("xfa.form..f2");
+        xfa.host.setFocus(c);
+      </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{object 9 0}} <<
+  {{streamlen}}
+>>
+stream
+</xdp:xdp>
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1060549.evt b/testing/resources/javascript/xfa_specific/bug_1060549.evt
new file mode 100644
index 0000000..cee75e2
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1060549.evt
@@ -0,0 +1,3 @@
+mousedown,left,82,130
+mousedown,left,82,130
+keycode,09
\ No newline at end of file
diff --git a/testing/resources/javascript/xfa_specific/bug_1060549.in b/testing/resources/javascript/xfa_specific/bug_1060549.in
new file mode 100644
index 0000000..2652548
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1060549.in
@@ -0,0 +1,59 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform layout="tb" locale="en_US" name="form1" restoreState="auto">
+    <subform h="10.5in" w="8in" name="subform2">
+      <field h="10mm" name="field1" w="10mm" x="0mm" y="20mm">
+        <ui>
+          <choiceList/>
+        </ui>
+        <items>
+          <text>aaaaaaaaaaaaaaaaaaaaa</text>
+        </items>
+      </field>
+      <field name="field2" h="20mm" w="50mm" x="0mm" y="30mm">
+        <ui>
+            <textEdit>
+            </textEdit>
+        </ui>
+      </field>
+      <subform name="subform3" x="0mm" y="5mm">
+        <occur max="-1"/>
+          <traversal>
+              <traverse operation="next" ref="$xfa.(eval('if (aa == 1) {aa=2;xfa.host.setFocus(cc);bb();xfa.host.openList(cc);}') == 0)"/>
+          </traversal>
+      </subform>
+    </subform>
+    <event activity="initialize">
+      <script contentType="application/x-javascript">
+        aa = 1;
+      </script>
+    </event>
+    <event activity="docReady">
+      <script contentType="application/x-javascript">
+        bb = function() {
+            dd = xfa.resolveNode("xfa.form..subform3");
+            dd.instanceManager.addInstance(1);
+            dd.instanceManager.removeInstance(0);
+        }
+        cc = xfa.resolveNode("xfa.form..field1");
+      </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1069700.evt b/testing/resources/javascript/xfa_specific/bug_1069700.evt
new file mode 100644
index 0000000..069f712
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1069700.evt
@@ -0,0 +1,3 @@
+mousedown,left,200,200
+mouseup,left,200,200
+keycode,9,shift
diff --git a/testing/resources/javascript/xfa_specific/bug_1069700.in b/testing/resources/javascript/xfa_specific/bug_1069700.in
new file mode 100644
index 0000000..1dd6ba8
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1069700.in
@@ -0,0 +1,73 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform name="form1">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <subform h="10.5in" w="8in" name="subform2">
+      <field h="10mm" name="DropDownList1" w="10mm" x="0mm" y="20mm">
+        <ui>
+          <choiceList/>
+        </ui>
+        <items save="1">
+          <text>Single</text>
+          <text>Married</text>
+          <text>Other</text>
+        </items>
+      </field>
+      <field h="200mm" name="DropDownList2" w="2mm" x="0mm" y="30mm">
+        <ui>
+          <textEdit/>
+       </ui>
+      </field>
+      <subform name="subform3" x="0mm" y="5mm">
+        <occur max="-1"/>
+        <traversal>
+          <traverse operation="next" ref="$xfa.(eval('try { if (aaaa == 1) {bb();} } catch(e){}') == 0)"/>
+        </traversal>
+      </subform>
+      <bookend leader="$"/>
+      <keep intact="none" previous="contentArea"/>
+    </subform>
+    <subform name="subform4" x="0mm">
+      <occur max="-1"/>
+      <field name="choiceList3" minH="32in">
+        <ui>
+          <choiceList/>
+        </ui>
+      </field>
+    </subform>
+    <event activity="docReady">
+      <script contentType="application/x-javascript">
+        aaaa = 1;
+        bb = function() {
+          xfa.host.setFocus(f1);
+          d = xfa.resolveNode("xfa.form..subform4");
+          d.instanceManager.addInstance(1);
+          xfa.host.openList(f1);
+        }
+        f1 = xfa.resolveNode("xfa.form..DropDownList1");
+      </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1069789.evt b/testing/resources/javascript/xfa_specific/bug_1069789.evt
new file mode 100644
index 0000000..768f6a9
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1069789.evt
@@ -0,0 +1 @@
+mousedown,right,200,200
\ No newline at end of file
diff --git a/testing/resources/javascript/xfa_specific/bug_1069789.in b/testing/resources/javascript/xfa_specific/bug_1069789.in
new file mode 100644
index 0000000..2cb09db
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1069789.in
@@ -0,0 +1,51 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform name="form1">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <subform h="10.5in" w="8in" name="subform2">
+      <field h="10mm" name="DropDownList1" w="10mm" x="0mm" y="20mm">
+        <ui>
+          <choiceList/>
+        </ui>
+        <items>
+          <text>Single</text>
+        </items>
+      </field>
+      <field h="200mm" name="DropDownList2" w="200mm" x="0mm" y="30mm">
+        <ui>
+          <textEdit/>
+        </ui>
+        <event activity="enter">
+          <script contentType="application/x-javascript">
+            f1 = xfa.resolveNode("xfa.form..DropDownList1");
+            xfa.host.setFocus(f1);
+            xfa.template.remerge();
+            xfa.host.openList(f1);
+          </script>
+        </event>
+      </field>
+    </subform>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1082597.in b/testing/resources/javascript/xfa_specific/bug_1082597.in
new file mode 100644
index 0000000..91a0ef0
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1082597.in
@@ -0,0 +1,150 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streanlen}}
+>>
+stream
+<template>
+ <subform layout="tb" name="my_doc">
+  <pageSet id="page" relation="orderedOccurrence">
+   <occur initial="1" max="4" min="1"/>
+   <pageArea id="Page1" name="Page1">
+    <occur max="1" min="1"/>
+    <contentArea h="100mm" w="200mm" x="0.25in" y="0.25in"/>
+    <medium long="297mm" short="210mm" stock="a4"/>
+   </pageArea>
+   <pageArea id="Page2" name="Page2">
+    <occur max="1" min="1"/>
+    <contentArea h="100mm" w="200mm" x="0.25in" y="0.25in"/>
+    <medium long="297mm" short="210mm" stock="a4"/>
+   </pageArea>
+  </pageSet>
+  <event activity="docReady" ref="$host">
+   <script contentType="application/x-javascript">
+    xfa.event.emit();
+    var f=xfa.resolveNode("xfa.form.my_doc.page1_form.combox");
+    xfa.record.nodes.append(this);
+    xfa.form.remerge();
+    xfa.host.setFocus(f);
+    app.alert('Done');
+   </script>
+  </event>
+  <subform layout="tb" name="subform_push_button_1">
+   <occur initial="1" max="10" min="0" name="occur_subform_push_button_1"/>
+   <field h="10mm" name="push_button" w="40mm" x="30mm" y="500mm">
+    <ui>
+     <button highlight="push"/>
+    </ui>
+    <caption>
+     <value>
+      <text>Button</text>
+     </value>
+    </caption>
+    <items>
+     <text name="down">Down Text</text>
+     <text name="rollover">Rollover Text</text>
+    </items>
+   </field>
+  </subform>
+  <subform layout="tb" name="subform_checkbutton_group_0">
+   <occur initial="1" max="10" min="0" name="occur_subform_checkbutton_group_0"/>
+   <exclGroup layout="tb" name="checkbutton_group">
+    <field h="10mm" name="checkbutton_check1" w="40mm" x="30mm" y="600mm">
+     <ui>
+      <checkButton shape="round">
+       <border>
+        <edge/>
+       </border>
+      </checkButton>
+     </ui>
+     <items>
+      <integer>1</integer>
+     </items>
+     <value>
+      <text>Select 1</text>
+     </value>
+     <caption placement="left">
+      <value>
+       <text>Option 1</text>
+      </value>
+     </caption>
+     <event activity="ready" ref="$layout">
+      <script contentType="application/x-javascript">
+      </script>
+     </event>
+    </field>
+    <field h="10mm" name="checkbutton_check2" w="40mm" x="30mm" y="600mm">
+     <ui>
+      <checkButton shape="round">
+       <border>
+        <edge/>
+       </border>
+      </checkButton>
+     </ui>
+     <items>
+      <integer>2</integer>
+     </items>
+     <value>
+      <text>Select 2</text>
+     </value>
+     <caption placement="left">
+      <value>
+       <text>Option 2</text>
+      </value>
+     </caption>
+    </field>
+   </exclGroup>
+  </subform>
+  <subform layout="tb" name="page2_form">
+   <occur initial="1" max="3" min="0"/>
+   <field h="10mm" name="textedit" w="40mm" x="20mm" y="20mm">
+    <ui>
+     <textEdit/>
+    </ui>
+    <value>
+     <text>CLGT.</text>
+    </value>
+   </field>
+  </subform>
+  <subform layout="tb" name="page1_form">
+   <occur initial="1" max="3" min="0"/>
+   <field h="10mm" name="combox" w="40mm" x="10mm" y="10mm">
+    <ui>
+     <choiceList open="onEntry">
+      <border>
+       <edge/>
+      </border>
+     </choiceList>
+    </ui>
+    <items save="1">
+     <text>apples</text>
+     <text>bananas</text>
+     <text>pears</text>
+    </items>
+    <value>
+     <text>
+      apples
+     </text>
+    </value>
+    <event activity="enter" name="event__enter">
+     <script contentType="application/x-javascript">
+      xfa.host.setFocus(0);
+      xfa.host.openList(this);
+     </script>
+    </event>
+   </field>
+  </subform>
+ </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1082597_expected.txt b/testing/resources/javascript/xfa_specific/bug_1082597_expected.txt
new file mode 100644
index 0000000..3cb772d
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1082597_expected.txt
@@ -0,0 +1,3 @@
+Alert: Done
+Alert: Done
+Alert: Done
diff --git a/testing/resources/javascript/xfa_specific/bug_1109108.evt b/testing/resources/javascript/xfa_specific/bug_1109108.evt
new file mode 100644
index 0000000..7d58927
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1109108.evt
@@ -0,0 +1 @@
+keycode,9
diff --git a/testing/resources/javascript/xfa_specific/bug_1109108.in b/testing/resources/javascript/xfa_specific/bug_1109108.in
new file mode 100644
index 0000000..550112e
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1109108.in
@@ -0,0 +1,64 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template>
+  <subform name="s1">
+    <subform name="s5">
+      <field name="field_28">
+        <calculate>
+          <script contentType="application/x-javascript">
+            var f=xfa.resolveNode("xfa.form.s1.s5.s6.field_35");
+            f.relevant ="print";
+          </script>
+        </calculate>
+        <ui>
+          <textEdit/>
+        </ui>
+        <value>
+          <text>CLGT.</text>
+        </value>
+      </field>
+      <subform name="s6">
+        <field name="field_35">
+          <ui>
+            <choiceList open="onEntry">
+              <border>
+                <edge/>
+              </border>
+            </choiceList>
+          </ui>
+          <items save="1">
+            <text>apples</text>
+            <text>bananas</text>
+            <text>pears</text>
+          </items>
+          <value>
+            <text>apples</text>
+          </value>
+          <event activity="change">
+            <script contentType="application/x-javascript">
+              xfa.template.remerge();
+              xfa.host.openList(this);
+              app.alert('remerged');
+            </script>
+          </event>
+        </field>
+      </subform>
+    </subform>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1109108_expected.txt b/testing/resources/javascript/xfa_specific/bug_1109108_expected.txt
new file mode 100644
index 0000000..86c4845
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1109108_expected.txt
@@ -0,0 +1,2 @@
+Alert: remerged
+Alert: remerged
diff --git a/testing/resources/javascript/xfa_specific/bug_1137668.evt b/testing/resources/javascript/xfa_specific/bug_1137668.evt
new file mode 100644
index 0000000..d4b67d8
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1137668.evt
@@ -0,0 +1,9 @@
+keycode,9,shift
+mousedown,left,50,50
+keycode,9
+mousemove,50,50
+mousedown,left,50,50
+keycode,9
+mousemove,20,20
+mousedown,left,20,20
+mousedown,right,20,20
diff --git a/testing/resources/javascript/xfa_specific/bug_1137668.in b/testing/resources/javascript/xfa_specific/bug_1137668.in
new file mode 100644
index 0000000..11014e7
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1137668.in
@@ -0,0 +1,338 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template>
+  <subform name="s1">
+    <subform name="s5">
+      <subform name="s6">
+        <event activity="enter">
+          <script contentType="application/x-javascript">
+            xfa.template.remerge();
+            xfa.form.remerge();
+          </script>
+        </event>
+        <field name="field_31">
+          <ui>
+            <button highlight="push"/>
+          </ui>
+          <caption>
+            <value>
+              <text>
+                Button
+              </text>
+            </value>
+          </caption>
+          <items>
+            <text name="down">
+              Down Text
+            </text>
+            <text name="rollover">
+              Rollover Text
+            </text>
+          </items>
+        </field>
+        <field name="field_32">
+          <ui>
+            <barcode/>
+          </ui>
+          <value>
+            <text>
+              test
+            </text>
+          </value>
+        </field>
+        <field name="field_33">
+          <ui>
+            <checkButton shape="round">
+              <border>
+                <edge/>
+              </border>
+            </checkButton>
+          </ui>
+          <items>
+            <integer>
+              2
+            </integer>
+          </items>
+          <value>
+            <text>
+              Select 2
+            </text>
+          </value>
+          <caption placement="left">
+            <value>
+              <text>
+                Option 2
+              </text>
+            </value>
+          </caption>
+        </field>
+        <field name="field_34">
+          <ui>
+            <textEdit/>
+          </ui>
+          <value>
+            <text>
+              CLGT.
+            </text>
+          </value>
+          <event activity="ready">
+            <script contentType="application/x-javascript">
+            </script>
+          </event>
+          <event activity="enter">
+            <script contentType="application/x-javascript">
+            </script>
+          </event>
+          <event activity="initialize">
+            <script contentType="application/x-javascript">
+            </script>
+          </event>
+        </field>
+        <field name="field_35">
+          <validate>
+            <script contentType="application/x-javascript">
+            </script>
+          </validate>
+          <calculate>
+            <script contentType="application/x-javascript">
+            </script>
+          </calculate>
+          <ui>
+            <choiceList open="onEntry">
+              <border><edge/></border>
+            </choiceList>
+          </ui>
+          <items save="1">
+            <text>apples</text>
+            <text>bananas</text>
+            <text>pears</text>
+          </items>
+          <value>
+            <text>apples</text>
+          </value>
+          <event activity="docClose">
+            <script contentType="application/x-javascript">
+            </script>
+          </event>
+          <event activity="initialize">
+            <script contentType="application/x-javascript">
+            </script>
+          </event>
+          <event activity="change">
+            <script contentType="application/x-javascript">
+            </script>
+          </event>
+        </field>
+        <subform name="s7">
+          <field name="field_36">
+            <ui>
+              <dateTimeEdit>
+                <comb numberOfCells="8"/>
+                <border hand="right">
+                  <edge/>
+                </border>
+              </dateTimeEdit>
+            </ui>
+          </field>
+          <field name="field_37">
+            <validate>
+              <script contentType="application/x-javascript">
+              </script>
+            </validate>
+            <calculate>
+              <script contentType="application/x-javascript">
+              </script>
+            </calculate>
+            <ui>
+              <button highlight="push"/>
+            </ui>
+            <caption>
+              <value>
+                <text>
+                  Button
+                </text>
+              </value>
+            </caption>
+            <items>
+              <text name="down">
+                Down Text
+              </text>
+              <text name="rollover">
+                Rollover Text
+              </text>
+            </items>
+            <event activity="docClose">
+              <script contentType="application/x-javascript">
+              </script>
+            </event>
+            <event activity="change">
+              <script contentType="application/x-javascript">
+              </script>
+            </event>
+            <event activity="exit">
+              <script contentType="application/x-javascript">
+              </script>
+            </event>
+          </field>
+          <field name="field_38">
+            <validate>
+              <script contentType="application/x-javascript">
+              </script>
+            </validate>
+            <calculate>
+              <script contentType="application/x-javascript">
+              </script>
+            </calculate>
+            <ui>
+              <barcode/>
+            </ui>
+            <value>
+              <text>
+                test
+              </text>
+            </value>
+            <event activity="initialize">
+              <script contentType="application/x-javascript">
+              </script>
+            </event>
+            <event activity="change">
+              <script contentType="application/x-javascript">
+              </script>
+            </event>
+            <event activity="ready">
+              <script contentType="application/x-javascript">
+              </script>
+            </event>
+          </field>
+          <field name="field_39">
+            <validate>
+              <script contentType="application/x-javascript">
+              </script>
+            </validate>
+            <calculate>
+              <script contentType="application/x-javascript">
+              </script>
+            </calculate>
+            <ui>
+              <checkButton shape="round">
+                <border>
+                  <edge/>
+                </border>
+              </checkButton>
+            </ui>
+            <items>
+              <integer>
+                2
+              </integer>
+            </items>
+            <value>
+              <text>
+                Select 2
+              </text>
+            </value>
+            <caption placement="left">
+              <value>
+                <text>
+                  Option 2
+                </text>
+              </value>
+            </caption>
+            <event activity="enter">
+              <script contentType="application/x-javascript">
+              </script>
+            </event>
+            <event activity="ready">
+              <script contentType="application/x-javascript">
+              </script>
+            </event>
+            <event activity="change">
+              <script contentType="application/x-javascript">
+              </script>
+            </event>
+          </field>
+          <field name="field_40">
+            <validate>
+              <script contentType="application/x-javascript">
+              </script>
+            </validate>
+            <calculate>
+              <script contentType="application/x-javascript">
+              </script>
+            </calculate>
+            <ui>
+              <textEdit/>
+            </ui>
+            <value>
+              <text>
+                CLGT.
+              </text>
+            </value>
+            <event activity="ready">
+              <script contentType="application/x-javascript">
+              </script>
+            </event>
+            <event activity="docClose">
+              <script contentType="application/x-javascript">
+              </script>
+            </event>
+            <event activity="exit">
+              <script contentType="application/x-javascript">
+              </script>
+            </event>
+          </field>
+          <field name="field_41">
+            <validate>
+              <script contentType="application/x-javascript">
+              </script>
+            </validate>
+            <calculate>
+              <script contentType="application/x-javascript">
+              </script>
+            </calculate>
+            <ui>
+              <choiceList open="onEntry">
+                <border><edge/></border>
+              </choiceList>
+            </ui>
+            <items save="1">
+              <text>apples</text>
+              <text>bananas</text>
+              <text>pears</text>
+            </items>
+            <value>
+              <text>apples</text>
+            </value>
+            <event activity="docClose">
+              <script contentType="application/x-javascript">
+              </script>
+            </event>
+            <event activity="initialize">
+              <script contentType="application/x-javascript">
+              </script>
+            </event>
+            <event activity="initialize">
+              <script contentType="application/x-javascript">
+              </script>
+            </event>
+          </field>
+        </subform>
+      </subform>
+    </subform>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1164158.in b/testing/resources/javascript/xfa_specific/bug_1164158.in
new file mode 100644
index 0000000..28b7eff
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1164158.in
@@ -0,0 +1,37 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
+  <subform>
+    <pageSet relation="simplexPaginated">
+      <pageArea pagePosition="last">
+        <subform>
+          <subform layout="table">
+            <subform layout="row">
+              <field />
+              <field colSpan="4294967295" presence="inactive" />
+            </subform>
+          </subform>
+        </subform>
+      </pageArea>
+      <pageArea>
+        <contentArea />
+      </pageArea>
+    </pageSet>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1248901.in b/testing/resources/javascript/xfa_specific/bug_1248901.in
new file mode 100644
index 0000000..8e2638a
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1248901.in
@@ -0,0 +1,47 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform layout="tb" locale="en_US" name="form1" restoreState="auto">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <subform h="10.5in" w="8in" name="subform2">
+      <field h="9.0001mm" name="field1" w="47.625mm" x="6.35mm" y="92.075mm">
+        <ui>
+          <choiceList/>
+        </ui>
+      </field>
+    </subform>
+    <event activity="docReady" ref="$host">
+      <script contentType="application/x-javascript">
+        try {
+          var form1 = xfa.resolveNode("xfa.form.form1");
+          var tmp = xfa.resolveNode("xfa.form..field1");
+          tmp.nodes.append(this);
+        } catch (e) {
+          app.alert("PASS: Caught: " + e);
+        }
+     </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1248901_expected.txt b/testing/resources/javascript/xfa_specific/bug_1248901_expected.txt
new file mode 100644
index 0000000..abf828c
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1248901_expected.txt
@@ -0,0 +1 @@
+Alert: PASS: Caught: XFAObject.append: Operation would create a cycle.
diff --git a/testing/resources/javascript/xfa_specific/bug_1312736.in b/testing/resources/javascript/xfa_specific/bug_1312736.in
new file mode 100644
index 0000000..f388810
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1312736.in
@@ -0,0 +1,40 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{object 3 0}} <<
+  {{streamlen}}
+>>
+stream
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+<template x="">
+  <subform>
+    <pageSet>
+      <pageArea>
+        <contentArea/>
+        <exclGroup name="0">
+          <field>
+            <ui><checkButton/></ui>
+            <items><textEdit/></items>
+          </field>
+        </exclGroup>
+        <subform name="Sho0">
+          <event activity="initialize">
+            <script contentType="application/x-javascript">
+              Sho0.presence=0;
+              app.alert("done");
+            </script>
+          </event>
+        </subform>
+      </pageArea>
+    </pageSet>
+  </subform>
+</template>
+endstream
+endobj
+{{object 8 0} <<
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_1312736_expected.txt b/testing/resources/javascript/xfa_specific/bug_1312736_expected.txt
new file mode 100644
index 0000000..daa1eca
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1312736_expected.txt
@@ -0,0 +1 @@
+Alert: done
diff --git a/testing/resources/javascript/xfa_specific/bug_1444238.evt b/testing/resources/javascript/xfa_specific/bug_1444238.evt
new file mode 100644
index 0000000..adca35a
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1444238.evt
@@ -0,0 +1,3 @@
+mousedown,left,91,539
+mouseup,left,91,539
+charcode,32
diff --git a/testing/resources/javascript/xfa_specific/bug_1444238.in b/testing/resources/javascript/xfa_specific/bug_1444238.in
new file mode 100644
index 0000000..675178c
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_1444238.in
@@ -0,0 +1,149 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm 4 0 R
+  /OpenAction 40 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 2
+  /Kids [
+    32 0 R
+    34 0 R
+  ]
+>>
+endobj
+% Forms
+{{object 4 0}} <<
+  /XFA 43 0 R
+  /Fields [
+    10 0 R
+    11 0 R
+  ]
+>>
+endobj
+% Fields
+{{object 10 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /T (MyField5)
+  /V (myfield_5)
+  /Rect [0 500 600 600]
+>>
+% Fields
+{{object 11 0}} <<
+  /T (MyField3)
+  /Parent 4 0 R
+  /Kids [12 0 R]
+  /Opt [(a) (b) (c) (d)]
+  /V [(a) (b) (c)]
+>>
+endobj
+% Fields
+{{object 12 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /Ff 131072
+  /Parent 11 0 R
+  /Kids [13 0 R]
+>>
+endobj
+% Fields
+{{object 13 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /Parent 12 0 R
+  /Rect [0 400 600 600]
+>>
+endobj
+% Fields
+{{object 14 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /Parent 12 0 R
+  /Rect [100 400 500 500]
+>>
+endobj
+% Page number 2.
+{{object 32 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [13 0 R]
+
+>>
+endobj
+{{object 34 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [10 0 R]
+>>
+endobj
+% Document JS Action
+{{object 40 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS 41 0 R
+>>
+endobj
+% JS program to exexute
+{{object 41 0}} <<
+>>
+stream
+var f5 = this.getField("MyField5");
+var f3 = this.getField("MyField3");
+f3.setFocus();
+this.__defineGetter__("pageNum",function o(){f5.setFocus(); f3.borderStyle="dashed"; f3.setFocus();});
+endstream
+endobj
+{{object 43 0}} <<
+  {{streamlen}}
+>>
+stream
+<?xml version="1.0" encoding="UTF-8"?>
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+<config></config>
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.8/">
+  <subform layout="tb" locale="en_US">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="268.939mm" w="203.2mm" x="6.35mm" y="6.35mm"/>
+        <medium long="792pt" short="612pt" stock="default"/>
+      </pageArea>
+    </pageSet>
+    <field h="9.0001mm" name="MyField3" w="47.625mm" x="120mm" y="120mm">
+      <ui>
+        <choiceList open="onEntry">
+          <border>
+            <edge/>
+          </border>
+        </choiceList>
+      </ui>
+      <items save="1">
+        <text>apples</text>
+        <text>bananas</text>
+        <text>pears</text>
+      </items>
+      <value>
+        <text>apples</text>
+      </value>
+      <event activity="preOpen">
+        <script contentType="application/x-javascript">
+            var aa = this.pageNum;
+        </script>
+      </event>
+    </field>
+  </subform>
+</template>
+</xdp:xdp>
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_980116.evt b/testing/resources/javascript/xfa_specific/bug_980116.evt
new file mode 100644
index 0000000..a5025fa
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_980116.evt
@@ -0,0 +1,4 @@
+mousemove,50,50
+mousedown,left,50,50
+mouseup,left,50,50
+keycode,9
diff --git a/testing/resources/javascript/xfa_specific/bug_980116.in b/testing/resources/javascript/xfa_specific/bug_980116.in
new file mode 100644
index 0000000..11bcbf2
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_980116.in
@@ -0,0 +1,58 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform layout="tb" locale="en_US" name="form1" restoreState="auto">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <subform h="10.5in" w="8in">
+      <field h="3mm" name="DropDownList1" w="3mm" x="0mm" y="1mm">
+        <ui>
+          <choiceList/>
+        </ui>
+        <items save="1">
+          <text>Single</text>
+          <text>Married</text>
+          <text>Other</text>
+        </items>
+      </field>
+      <field h="500.0001mm" name="DropDownList2" w="500.625mm" x="0mm" y="0mm">
+        <ui>
+          <textEdit></textEdit>
+        </ui>
+        <value>
+          <text>Employee</text>
+        </value>
+      </field>
+    </subform>
+    <traversal>
+        <traverse operation="first" ref="$xfa.(eval('xfa.host.setFocus(field_DropDownList1); xfa.template.remerge(); xfa.host.openList(field_DropDownList1);') == 0)"/>
+    </traversal>
+    <event activity="docReady">
+      <script contentType="application/x-javascript">
+        field_DropDownList1 = xfa.resolveNode("xfa.form..DropDownList1");
+        field_DropDownList2 = xfa.resolveNode("xfa.form..DropDownList2");
+        xfa.host.setFocus(field_DropDownList2);
+      </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/bug_980161.evt b/testing/resources/javascript/xfa_specific/bug_980161.evt
new file mode 100644
index 0000000..a5025fa
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_980161.evt
@@ -0,0 +1,4 @@
+mousemove,50,50
+mousedown,left,50,50
+mouseup,left,50,50
+keycode,9
diff --git a/testing/resources/javascript/xfa_specific/bug_980161.in b/testing/resources/javascript/xfa_specific/bug_980161.in
new file mode 100644
index 0000000..058444d
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/bug_980161.in
@@ -0,0 +1,55 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform layout="tb" locale="en_US" name="form1" restoreState="auto">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <subform h="10.5in" w="8in">
+      <field h="3mm" name="DropDownList1" w="3mm" x="0mm" y="1mm">
+        <ui>
+          <choiceList/>
+        </ui>
+        <items save="1">
+          <text>Single</text>
+          <text>Married</text>
+          <text>Other</text>
+        </items>
+      </field>
+      <field h="500.0001mm" name="DropDownList2" w="500.625mm" x="0mm" y="0mm">
+        <ui>
+          <textEdit></textEdit>
+        </ui>
+        <traversal>
+          <traverse operation="next" ref="$xfa.(eval('xfa.host.setFocus(field_DropDownList1); xfa.template.remerge(); xfa.host.openList(field_DropDownList1);') == 0)"/>
+        </traversal>
+      </field>
+    </subform>
+    <event activity="docReady">
+      <script contentType="application/x-javascript">
+        field_DropDownList1 = xfa.resolveNode("xfa.form..DropDownList1");
+        field_DropDownList2 = xfa.resolveNode("xfa.form..DropDownList2");
+        xfa.host.setFocus(field_DropDownList2);
+      </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/cross_engine_apply.in b/testing/resources/javascript/xfa_specific/cross_engine_apply.in
index cdaad2a..3a41708 100644
--- a/testing/resources/javascript/xfa_specific/cross_engine_apply.in
+++ b/testing/resources/javascript/xfa_specific/cross_engine_apply.in
@@ -46,6 +46,7 @@
 endobj
 {{include ../../xfa_locale_6_0.fragment}}
 {{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
 {{xref}}
 {{trailer}}
 {{startxref}}
diff --git a/testing/resources/javascript/xfa_specific/dump_tree.js b/testing/resources/javascript/xfa_specific/dump_tree.js
new file mode 100644
index 0000000..171c72a
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/dump_tree.js
@@ -0,0 +1,22 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Note: Be sure that this file is only included inside a CDATA block,
+// otherwise the less-than comparision below will break the XML parse.
+
+function dumpTree(node, level) {
+  level = level || 0;
+  var indentation = "| ".repeat(level);
+  try {
+    app.alert(indentation + node.className);
+    var children = node.nodes;
+    if (children) {
+      for (var i = 0; i < children.length; ++i) {
+        dumpTree(children.item(i), level + 1);
+      }
+    }
+  } catch (e) {
+    app.alert(indentation + "Error: " + e);
+  }
+}
diff --git a/testing/resources/javascript/xfa_specific/instance_manager.in b/testing/resources/javascript/xfa_specific/instance_manager.in
new file mode 100644
index 0000000..fe3d776
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/instance_manager.in
@@ -0,0 +1,99 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform layout="tb" locale="en_US" name="form1" restoreState="auto">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <subform h="10.5in" w="8in" name="subform2">
+      <field h="9.0001mm" name="field1" w="47.625mm" x="6.35mm" y="92.075mm">
+        <ui>
+          <choiceList/>
+        </ui>
+        <items save="1">
+          <text>Single</text>
+          <text>Married</text>
+          <text>Other</text>
+        </items>
+      </field>
+      <field name="field3" h="10.625mm" w="30.625mm" x="5mm" y="50mm">
+      </field>
+      <subform name="field4" x="5mm" y="5mm">
+        <occur max="-1"/>
+        <field name="field5" w="64.77mm" h="6.35mm">
+        </field>
+      </subform>
+    </subform>
+    <event activity="docReady">
+      <script contentType="application/x-javascript"><![CDATA[
+        {{include ../expect.js}}
+        {{include ../property_test_helpers.js}}
+        {{include dump_tree.js}}
+
+        var mgr = xfa.resolveNode("xfa.form..field4").instanceManager;
+        dumpTree(mgr);
+
+        testRWProperty(mgr, "count", 1, 12);
+        testROProperty(mgr, "min", 1);
+        testROProperty(mgr, "max", -1);
+
+        expectError("mgr.setInstances()");
+        expectError("mgr.setInstances(-10)");
+        expectError("mgr.setInstances('clams')");
+        expectError("mgr.setInstances([1, 2, 3])");
+        // setInstances(10000000) will hang or hit OOM.
+        expect("mgr.setInstances(4)", undefined);
+        expect("mgr.count", 4);
+        expect("mgr.setInstances(2)", undefined);
+        expect("mgr.count", 2);
+
+        expectError("mgr.moveInstance()");
+        expectError("mgr.moveInstance(0)");
+        expectError("mgr.moveInstance('clams')");
+        expectError("mgr.moveInstance([1, 2, 3])");
+        expect("mgr.moveInstance(0, 1)", undefined);
+        expect("mgr.count", 2);
+
+        expectError("mgr.addInstance(1, 2, 3)");
+        expect("mgr.addInstance().className", "subform");
+        expect("mgr.addInstance(true).className", "subform");
+        expect("mgr.count", 4);
+
+        expectError("mgr.insertInstance()");
+        expectError("mgr.insertInstance(1, 2, 3)");
+        expect("mgr.insertInstance(1, true).className", "subform");
+        expect("mgr.count", 5);
+
+        expectError("mgr.removeInstance()");
+        expectError("mgr.removeInstance(1, 2)");
+        expect("mgr.removeInstance(0)", undefined);
+        expect("mgr.removeInstance(0)", undefined);
+        expect("mgr.removeInstance(0)", undefined);
+        expect("mgr.removeInstance(0)", undefined);
+        expect("mgr.count", 1);
+
+        expectError("mgr.removeInstance(0)");
+        expect("mgr.count", 1);
+      ]]></script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/instance_manager_expected.txt b/testing/resources/javascript/xfa_specific/instance_manager_expected.txt
new file mode 100644
index 0000000..d39657f
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/instance_manager_expected.txt
@@ -0,0 +1,39 @@
+Alert: instanceManager
+Alert: | occur
+Alert: PASS: count = 1
+Alert: PASS: count = 12
+Alert: PASS: min = 1
+Alert: PASS: min threw Error: Invalid property set operation.
+Alert: PASS: max = -1
+Alert: PASS: max threw Error: Invalid property set operation.
+Alert: PASS: mgr.setInstances() threw XFAObject.setInstances: Incorrect number of parameters passed to function.
+Alert: PASS: mgr.setInstances(-10) threw Error: The element [min] has violated its allowable number of occurrences.
+Alert: PASS: mgr.setInstances('clams') threw Error: The element [min] has violated its allowable number of occurrences.
+Alert: PASS: mgr.setInstances([1, 2, 3]) threw Error: The element [min] has violated its allowable number of occurrences.
+Alert: PASS: mgr.setInstances(4) = undefined
+Alert: PASS: mgr.count = 4
+Alert: PASS: mgr.setInstances(2) = undefined
+Alert: PASS: mgr.count = 2
+Alert: PASS: mgr.moveInstance() threw XFAObject.moveInstance: Incorrect number of parameters passed to function.
+Alert: PASS: mgr.moveInstance(0) threw XFAObject.moveInstance: Incorrect number of parameters passed to function.
+Alert: PASS: mgr.moveInstance('clams') threw XFAObject.moveInstance: Incorrect number of parameters passed to function.
+Alert: PASS: mgr.moveInstance([1, 2, 3]) threw XFAObject.moveInstance: Incorrect number of parameters passed to function.
+Alert: PASS: mgr.moveInstance(0, 1) = undefined
+Alert: PASS: mgr.count = 2
+Alert: PASS: mgr.addInstance(1, 2, 3) threw XFAObject.addInstance: Incorrect number of parameters passed to function.
+Alert: PASS: mgr.addInstance().className = subform
+Alert: PASS: mgr.addInstance(true).className = subform
+Alert: PASS: mgr.count = 4
+Alert: PASS: mgr.insertInstance() threw XFAObject.insertInstance: Incorrect number of parameters passed to function.
+Alert: PASS: mgr.insertInstance(1, 2, 3) threw XFAObject.insertInstance: Incorrect number of parameters passed to function.
+Alert: PASS: mgr.insertInstance(1, true).className = subform
+Alert: PASS: mgr.count = 5
+Alert: PASS: mgr.removeInstance() threw XFAObject.removeInstance: Incorrect number of parameters passed to function.
+Alert: PASS: mgr.removeInstance(1, 2) threw XFAObject.removeInstance: Incorrect number of parameters passed to function.
+Alert: PASS: mgr.removeInstance(0) = undefined
+Alert: PASS: mgr.removeInstance(0) = undefined
+Alert: PASS: mgr.removeInstance(0) = undefined
+Alert: PASS: mgr.removeInstance(0) = undefined
+Alert: PASS: mgr.count = 1
+Alert: PASS: mgr.removeInstance(0) threw XFAObject.removeInstance: Too many occurrences.
+Alert: PASS: mgr.count = 1
diff --git a/testing/resources/javascript/xfa_specific/mixed_widgets.evt b/testing/resources/javascript/xfa_specific/mixed_widgets.evt
new file mode 100644
index 0000000..f4ee07d
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/mixed_widgets.evt
@@ -0,0 +1,5 @@
+mousemove,302,302
+mousedown,left,302,302
+mouseup,left,302,302
+charcode,65
+charcode,66
diff --git a/testing/resources/javascript/xfa_specific/mixed_widgets.in b/testing/resources/javascript/xfa_specific/mixed_widgets.in
new file mode 100644
index 0000000..2974112
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/mixed_widgets.in
@@ -0,0 +1,48 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /NeedsRendering true
+  /AcroForm <<
+    /Fields [6 0 R]
+    /XFA 5 0 R
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [4 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 512 512]
+  /Annots [6 0 R]
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<xdp xmlns="http://ns.adobe.com/xdp/">
+  <template>
+    <subform x="0pt" y="0pt" w="198pt" h="198pt">
+      <field name="MyBox"/>
+    </subform>
+  </template>
+</xdp>
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /Rect [200 200 512 512]
+  /T (MyBox)
+>>
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/popup_menu.evt b/testing/resources/javascript/xfa_specific/popup_menu.evt
new file mode 100644
index 0000000..7820426
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/popup_menu.evt
@@ -0,0 +1,5 @@
+mousemove,20,20
+mousedown,left,20,20
+mouseup,left,20,20
+mousedown,right,20,20
+mouseup,right,20,20
diff --git a/testing/resources/javascript/xfa_specific/popup_menu.in b/testing/resources/javascript/xfa_specific/popup_menu.in
new file mode 100644
index 0000000..52f7a45
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/popup_menu.in
@@ -0,0 +1,40 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
+  <subform name="form1" layout="tb" locale="en_US" restoreState="auto">
+    <pageSet>
+      <pageArea name="Page1" id="Page1">
+        <contentArea x="18pt" y="18pt" w="612pt" h="792pt"/>
+        <medium stock="default" short="612pt" long="792pt"/>
+      </pageArea>
+    </pageSet>
+    <subform w="576pt" h="756pt" name="Page1">
+      <field name="ImageField1" y="0pt" x="0pt" w="425pt" h="80pt">
+        <ui>
+          <choiceList open="userControl"/>
+        </ui>
+        <items save="1">
+          <text>clams</text>
+          <text>oysters</text>
+          <text>crabs</text>
+        </items>
+      </field>
+    </subform>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/popup_menu_expected.txt b/testing/resources/javascript/xfa_specific/popup_menu_expected.txt
new file mode 100644
index 0000000..dc856e9
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/popup_menu_expected.txt
@@ -0,0 +1 @@
+Popup: x=20.0, y=20.0, flags=0x0
diff --git a/testing/resources/javascript/xfa_specific/xfa_exclgroup.in b/testing/resources/javascript/xfa_specific/xfa_exclgroup.in
new file mode 100644
index 0000000..1a29352
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/xfa_exclgroup.in
@@ -0,0 +1,86 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template>
+  <subform layout="tb">
+    <exclGroup name="RadioButtonList" layout="lr-tb">
+      <field w="30mm" h="6mm" name="yes">
+        <ui>
+          <checkButton shape="round">
+          </checkButton>
+        </ui>
+        <items>
+          <text>Agree</text>
+        </items>
+      </field>
+      <field w="30mm" h="6mm" name="no">
+        <ui>
+          <checkButton shape="round">
+          </checkButton>
+        </ui>
+        <items>
+          <text>Disagree</text>
+        </items>
+      </field>
+      <validate nullTest="error"/>
+    </exclGroup>
+    <event activity="initialize">
+      <script contentType="application/x-javascript"><![CDATA[
+        {{include ../expect.js}}
+        expect("RadioButtonList.className", "exclGroup");
+
+        expectError("RadioButtonList.execEvent()");
+        expectError("RadioButtonList.execEvent(1, 2)");
+        expect("RadioButtonList.execEvent('nonesuch')", null);
+        expect("RadioButtonList.execEvent('calculate')", null);
+        expect("RadioButtonList.execEvent('change')", null);
+        expect("RadioButtonList.execEvent('click')", null);
+        expect("RadioButtonList.execEvent('enter')", null);
+        expect("RadioButtonList.execEvent('exit')", null);
+        expect("RadioButtonList.execEvent('full')", null);
+        expect("RadioButtonList.execEvent('indexChange')", null);
+        expect("RadioButtonList.execEvent('initialize')", null);
+        expect("RadioButtonList.execEvent('mouseDown')", null);
+        expect("RadioButtonList.execEvent('mouseEnter')", null);
+        expect("RadioButtonList.execEvent('mouseExit')", null);
+        expect("RadioButtonList.execEvent('mouseUp')", null);
+        expect("RadioButtonList.execEvent('postOpen')", null);
+        expect("RadioButtonList.execEvent('preOpen')", null);
+        expect("RadioButtonList.execEvent('preSign')", null);
+        expect("RadioButtonList.execEvent('validate')", null);
+
+        expectError("RadioButtonList.execInitialize('badarg')");
+        expect("RadioButtonList.execInitialize()", null);
+
+        expectError("RadioButtonList.execCalculate('badarg')");
+        expect("RadioButtonList.execCalculate()", null);
+
+        expectError("RadioButtonList.execValidate('badarg')");
+        expect("RadioButtonList.execValidate()", true);
+
+        expectError("RadioButtonList.selectedMember('badarg')");
+        expect("RadioButtonList.selectedMember()", null);
+
+        expect("RadioButtonList.defaultValue",  undefined);
+        expect("RadioButtonList.rawValue", null);
+        expect("RadioButtonList.transient", undefined);
+        expect("RadioButtonList.errorText", undefined);
+      ]]></script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/xfa_exclgroup_expected.txt b/testing/resources/javascript/xfa_specific/xfa_exclgroup_expected.txt
new file mode 100644
index 0000000..7ca5a78
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/xfa_exclgroup_expected.txt
@@ -0,0 +1,32 @@
+Alert: PASS: RadioButtonList.className = exclGroup
+Alert: PASS: RadioButtonList.execEvent() threw XFAObject.execEvent: Incorrect number of parameters passed to function.
+Alert: PASS: RadioButtonList.execEvent(1, 2) threw XFAObject.execEvent: Incorrect number of parameters passed to function.
+Alert: PASS: RadioButtonList.execEvent('nonesuch') = undefined
+Alert: PASS: RadioButtonList.execEvent('calculate') = undefined
+Alert: PASS: RadioButtonList.execEvent('change') = undefined
+Alert: PASS: RadioButtonList.execEvent('click') = undefined
+Alert: PASS: RadioButtonList.execEvent('enter') = undefined
+Alert: PASS: RadioButtonList.execEvent('exit') = undefined
+Alert: PASS: RadioButtonList.execEvent('full') = undefined
+Alert: PASS: RadioButtonList.execEvent('indexChange') = undefined
+Alert: PASS: RadioButtonList.execEvent('initialize') = undefined
+Alert: PASS: RadioButtonList.execEvent('mouseDown') = undefined
+Alert: PASS: RadioButtonList.execEvent('mouseEnter') = undefined
+Alert: PASS: RadioButtonList.execEvent('mouseExit') = undefined
+Alert: PASS: RadioButtonList.execEvent('mouseUp') = undefined
+Alert: PASS: RadioButtonList.execEvent('postOpen') = undefined
+Alert: PASS: RadioButtonList.execEvent('preOpen') = undefined
+Alert: PASS: RadioButtonList.execEvent('preSign') = undefined
+Alert: PASS: RadioButtonList.execEvent('validate') = undefined
+Alert: PASS: RadioButtonList.execInitialize('badarg') threw XFAObject.execInitialize: Incorrect number of parameters passed to function.
+Alert: PASS: RadioButtonList.execInitialize() = undefined
+Alert: PASS: RadioButtonList.execCalculate('badarg') threw XFAObject.execCalculate: Incorrect number of parameters passed to function.
+Alert: PASS: RadioButtonList.execCalculate() = undefined
+Alert: PASS: RadioButtonList.execValidate('badarg') threw XFAObject.execValidate: Incorrect number of parameters passed to function.
+Alert: PASS: RadioButtonList.execValidate() = true
+Alert: PASS: RadioButtonList.selectedMember('badarg') threw XFAObject.selectedMember: Incorrect number of parameters passed to function.
+Alert: PASS: RadioButtonList.selectedMember() = null
+Alert: PASS: RadioButtonList.defaultValue = undefined
+Alert: PASS: RadioButtonList.rawValue = null
+Alert: PASS: RadioButtonList.transient = undefined
+Alert: PASS: RadioButtonList.errorText = undefined
diff --git a/testing/resources/javascript/xfa_specific/xfa_field.in b/testing/resources/javascript/xfa_specific/xfa_field.in
new file mode 100644
index 0000000..a358921
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/xfa_field.in
@@ -0,0 +1,92 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template>
+  <subform layout="tb" name="subform1">
+    <pageSet id="page" relation="orderedOccurrence">
+      <occur initial="1" max="1" min="1"/>
+      <pageArea id="Page1" name="Page1">
+        <occur max="1" min="1"/>
+        <contentArea h="100mm" w="200mm" x="0.25in" y="0.25in"/>
+        <medium long="297mm" short="210mm" stock="a4"/>
+      </pageArea>
+    </pageSet>
+    <subform layout="tb" name="subform2">
+      <occur initial="1" max="10" min="0" name="occur1">
+      </occur>
+      <field name="field1" h="10mm"  w="40mm" x="10mm" y="12mm" border="solid">
+        <items>
+          <text>and a one</text>
+          <text>and a two</text>
+        </items>
+        <event activity="ready" ref="$form">
+          <script contentType="application/x-javascript"><![CDATA[
+            {{include ../expect.js}}
+            {{include ../property_test_helpers.js}}
+            var field = xfa.resolveNode("field1");
+            testRWProperty(field, "x", "10mm", "11mm");
+            testRWProperty(field, "y", "12mm", "13mm");
+            testRWProperty(field, "h", "10mm", "2in");
+            testRWProperty(field, "w", "40mm", "3in");
+            testRWProperty(field, "fontColor", "0,0,0", "42,62,4");
+            testRWProperty(field, "fillColor", "255,255,255", "41,61,11");
+            testRWProperty(field, "borderColor", "0,0,0", "241,161,11");
+            // TODO(tsepez): find a way to make this be defined.
+            // testRWProperty(field, "borderWidth", "1", "4");
+            testRWProperty(field, "mandatory", "disabled", "solid");
+            testRWProperty(field, "mandatoryMessage", "", "keep out");
+            testROProperty(field, "dataNode", "[object XFAObject]");
+            testROProperty(field, "length", 2);
+
+            expectError("field.execInitialize('phooey')");
+            expect("field.execInitialize()", undefined);
+
+            expectError("field.execEvent()");
+            expectError("field.execEvent(1, 2)");
+            expect("field.execEvent('validate')", true);
+
+            expectError("field.deleteItem()");
+            expectError("field.deleteItem(1, 2)");
+            expect("field.deleteItem(1)", true);
+            expect("field.deleteItem(137)", true);  // silently ignored?
+
+            expectError("field.getSaveItem()");
+            expectError("field.getSaveItem(1, 2)");
+            expect("field.getSaveItem(0)", "and a one");
+            expect("field.getSaveItem(137)", null);
+
+            expectError("field.getItemState()");
+            expectError("field.getItemState(1, 2)");
+            expect("field.getItemState(0)", false);
+            expect("field.getItemState(1)", false);
+            expect("field.getItemState(137)", false);
+            expect("field.getItemState(-137)", false);
+
+            expectError("field.setItemState()");
+            expectError("field.setItemState(1, 2, 3)");
+            expect("field.setItemState(0, 1)", undefined);
+            expect("field.getItemState(0)", true);
+            expect("field.setItemState(0, 0)", undefined);
+            expect("field.getItemState(0)", false);
+
+          ]]></script>
+        </event>
+      </field>
+    </subform>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/xfa_field_expected.txt b/testing/resources/javascript/xfa_specific/xfa_field_expected.txt
new file mode 100644
index 0000000..645518a
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/xfa_field_expected.txt
@@ -0,0 +1,47 @@
+Alert: PASS: x = 10mm
+Alert: PASS: x = 11mm
+Alert: PASS: y = 12mm
+Alert: PASS: y = 13mm
+Alert: PASS: h = 10mm
+Alert: PASS: h = 2in
+Alert: PASS: w = 40mm
+Alert: PASS: w = 3in
+Alert: PASS: fontColor = 0,0,0
+Alert: PASS: fontColor = 42,62,4
+Alert: PASS: fillColor = 255,255,255
+Alert: PASS: fillColor = 41,61,11
+Alert: PASS: borderColor = 0,0,0
+Alert: PASS: borderColor = 241,161,11
+Alert: PASS: mandatory = disabled
+Alert: PASS: mandatory = solid
+Alert: PASS: mandatoryMessage = 
+Alert: PASS: mandatoryMessage = keep out
+Alert: PASS: dataNode = [object XFAObject]
+Alert: PASS: dataNode threw Error: Invalid property set operation.
+Alert: PASS: length = 2
+Alert: PASS: length threw Error: Invalid property set operation.
+Alert: PASS: field.execInitialize('phooey') threw XFAObject.execInitialize: Incorrect number of parameters passed to function.
+Alert: PASS: field.execInitialize() = undefined
+Alert: PASS: field.execEvent() threw XFAObject.execEvent: Incorrect number of parameters passed to function.
+Alert: PASS: field.execEvent(1, 2) threw XFAObject.execEvent: Incorrect number of parameters passed to function.
+Alert: PASS: field.execEvent('validate') = true
+Alert: PASS: field.deleteItem() threw XFAObject.deleteItem: Incorrect number of parameters passed to function.
+Alert: PASS: field.deleteItem(1, 2) threw XFAObject.deleteItem: Incorrect number of parameters passed to function.
+Alert: PASS: field.deleteItem(1) = true
+Alert: PASS: field.deleteItem(137) = true
+Alert: PASS: field.getSaveItem() threw XFAObject.getSaveItem: Incorrect number of parameters passed to function.
+Alert: PASS: field.getSaveItem(1, 2) threw XFAObject.getSaveItem: Incorrect number of parameters passed to function.
+Alert: PASS: field.getSaveItem(0) = and a one
+Alert: PASS: field.getSaveItem(137) = null
+Alert: PASS: field.getItemState() threw XFAObject.getItemState: Incorrect number of parameters passed to function.
+Alert: PASS: field.getItemState(1, 2) threw XFAObject.getItemState: Incorrect number of parameters passed to function.
+Alert: PASS: field.getItemState(0) = false
+Alert: PASS: field.getItemState(1) = false
+Alert: PASS: field.getItemState(137) = false
+Alert: PASS: field.getItemState(-137) = false
+Alert: PASS: field.setItemState() threw XFAObject.setItemState: Incorrect number of parameters passed to function.
+Alert: PASS: field.setItemState(1, 2, 3) threw XFAObject.setItemState: Incorrect number of parameters passed to function.
+Alert: PASS: field.setItemState(0, 1) = undefined
+Alert: PASS: field.getItemState(0) = true
+Alert: PASS: field.setItemState(0, 0) = undefined
+Alert: PASS: field.getItemState(0) = false
diff --git a/testing/resources/javascript/xfa_specific/xfa_form.in b/testing/resources/javascript/xfa_specific/xfa_form.in
new file mode 100644
index 0000000..e21688f
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/xfa_form.in
@@ -0,0 +1,61 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<form>
+</form>
+<template>
+  <subform layout="tb" name="my_subform">
+    <pageSet id="page" relation="orderedOccurrence">
+      <occur initial="1" max="1" min="1"/>
+      <pageArea id="Page1" name="Page1">
+        <occur max="1" min="1"/>
+        <contentArea h="100mm" w="200mm" x="0.25in" y="0.25in"/>
+      </pageArea>
+      <pageArea id="Page2" name="Page2">
+        <occur max="1" min="1"/>
+        <contentArea h="100mm" w="200mm" x="0.25in" y="0.25in"/>
+      </pageArea>
+    </pageSet>
+    <event activity="docReady" ref="$host">
+      <script contentType="application/x-javascript">
+        {{include ../expect.js}}
+        {{include ../property_test_helpers.js}}
+        var my_form = xfa.resolveNode("#form");
+        testRWProperty(my_form, "checksum", "", "11");
+        expect("typeof my_form.execInitialize", "function");
+        expect("typeof my_form.execCalculate", "function");
+        expect("typeof my_form.execValidate", "function");
+        expectError("my_form.execInitialize('nonesuch')");
+        expect("my_form.execInitialize()", undefined);
+        expectError("my_form.execCalculate('nonesuch')");
+        expect("my_form.execCalculate()", undefined);
+        expectError("my_form.execValidate('nonesuch')");
+        expect("my_form.execValidate()", true);
+        expectError("my_form.formNodes()");
+        expectError("my_form.formNodes(1, 2)");
+        expectError("my_form.formNodes('nonesuch')");
+        expect("my_form.formNodes(my_subform)", "[object XFAObject]");
+        expectError("my_form.remerge(1)");
+        expect("my_form.remerge()", true);
+        expectError("my_form.recalculate()");
+        expectError("my_form.recalculate(1, 2)");
+        expect("my_form.recalculate(0)", undefined);
+      </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/xfa_form_expected.txt b/testing/resources/javascript/xfa_specific/xfa_form_expected.txt
new file mode 100644
index 0000000..191d702
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/xfa_form_expected.txt
@@ -0,0 +1,20 @@
+Alert: PASS: checksum = 
+Alert: PASS: checksum = 11
+Alert: PASS: typeof my_form.execInitialize = function
+Alert: PASS: typeof my_form.execCalculate = function
+Alert: PASS: typeof my_form.execValidate = function
+Alert: PASS: my_form.execInitialize('nonesuch') threw XFAObject.execInitialize: Incorrect number of parameters passed to function.
+Alert: PASS: my_form.execInitialize() = undefined
+Alert: PASS: my_form.execCalculate('nonesuch') threw XFAObject.execCalculate: Incorrect number of parameters passed to function.
+Alert: PASS: my_form.execCalculate() = undefined
+Alert: PASS: my_form.execValidate('nonesuch') threw XFAObject.execValidate: Incorrect number of parameters passed to function.
+Alert: PASS: my_form.execValidate() = true
+Alert: PASS: my_form.formNodes() threw XFAObject.formNodes: Incorrect number of parameters passed to function.
+Alert: PASS: my_form.formNodes(1, 2) threw XFAObject.formNodes: Incorrect number of parameters passed to function.
+Alert: PASS: my_form.formNodes('nonesuch') threw XFAObject.formNodes: Incorrect parameter value.
+Alert: PASS: my_form.formNodes(my_subform) = [object XFAObject]
+Alert: PASS: my_form.remerge(1) threw XFAObject.remerge: Incorrect number of parameters passed to function.
+Alert: FAIL: my_form.remerge() = undefined, expected true 
+Alert: PASS: my_form.recalculate() threw XFAObject.recalculate: Incorrect number of parameters passed to function.
+Alert: PASS: my_form.recalculate(1, 2) threw XFAObject.recalculate: Incorrect number of parameters passed to function.
+Alert: PASS: my_form.recalculate(0) = undefined
diff --git a/testing/resources/javascript/xfa_specific/xfa_globalobject.in b/testing/resources/javascript/xfa_specific/xfa_globalobject.in
new file mode 100644
index 0000000..be1f3be
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/xfa_globalobject.in
@@ -0,0 +1,41 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template>
+  <subform layout="tb" name="my_doc">
+    <pageSet id="page" relation="orderedOccurrence">
+      <occur initial="1" max="1" min="1"/>
+      <pageArea id="Page1" name="Page1">
+        <occur max="1" min="1"/>
+        <contentArea h="100mm" w="200mm" x="0.25in" y="0.25in"/>
+      </pageArea>
+      <pageArea id="Page2" name="Page2">
+        <occur max="1" min="1"/>
+        <contentArea h="100mm" w="200mm" x="0.25in" y="0.25in"/>
+      </pageArea>
+    </pageSet>
+    <event activity="docReady" ref="$host">
+      <script contentType="application/x-javascript">
+        app.alert('We search non-xfa global space on missing prop: ' + (app == xfa.app));
+        app.alert('Global toString says ' + toString());
+        app.alert('toString.apply to bad object is ' + toString.apply(xfa));
+        app.alert('toString.apply to bad non-xfa object is ' + toString.apply(app));
+      </script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/xfa_globalobject_expected.txt b/testing/resources/javascript/xfa_specific/xfa_globalobject_expected.txt
new file mode 100644
index 0000000..6384afe
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/xfa_globalobject_expected.txt
@@ -0,0 +1,4 @@
+Alert: We search non-xfa global space on missing prop: true
+Alert: Global toString says [object Root]
+Alert: toString.apply to bad object is [object Root]
+Alert: toString.apply to bad non-xfa object is [object Root]
diff --git a/testing/resources/javascript/xfa_specific/xfa_host_pseudomodel_expected.txt b/testing/resources/javascript/xfa_specific/xfa_host_pseudomodel_expected.txt
index 3835e4a..6c1359d 100644
--- a/testing/resources/javascript/xfa_specific/xfa_host_pseudomodel_expected.txt
+++ b/testing/resources/javascript/xfa_specific/xfa_host_pseudomodel_expected.txt
@@ -52,7 +52,7 @@
 Alert: PASS: xfa.host.pageDown('ignored arg') = undefined
 Alert: PASS: xfa.host.print(1, 2, 3, 4, 5, 6, 7) threw XFAObject.print: Incorrect number of parameters passed to function.
 Alert: PASS: xfa.host.print(1, 2, 3, 4, 5, 6, 7, 8, 9) threw XFAObject.print: Incorrect number of parameters passed to function.
-Doc Print: 1, 1, 1, 2, 4, 8, 16, 32
+Doc Print: 1, 1, 1, 1, 1, 1, 1, 1
 Alert: PASS: xfa.host.print(true, 1, 1, true, true, true, true, true) = undefined
 Alert: PASS: xfa.host.response() threw XFAObject.response: Incorrect number of parameters passed to function.
 Alert: PASS: xfa.host.response(1, 2, 3, 4, 5) threw XFAObject.response: Incorrect number of parameters passed to function.
diff --git a/testing/resources/javascript/xfa_specific/xfa_items.in b/testing/resources/javascript/xfa_specific/xfa_items.in
new file mode 100644
index 0000000..f1ee317
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/xfa_items.in
@@ -0,0 +1,196 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
+  <subform name="form1">
+    <pageSet>
+      <pageArea id="Page1" name="Page1">
+        <contentArea h="10.5in" w="8in" x="0.25in" y="0.25in"/>
+        <medium long="11in" short="8.5in" stock="letter"/>
+      </pageArea>
+    </pageSet>
+    <field h="9.0001mm" name="field1" w="47.625mm" x="6.35mm" y="92.075mm">
+      <ui>
+        <choiceList/>
+      </ui>
+      <items nonesuch="3">
+        <arc name="arc1"></arc>
+        <boolean name="bool0">0</boolean>
+        <boolean name="bool1">1</boolean>
+        <boolean name="boolbad">bad</boolean>
+        <boolean name="booltruenottrue">true</boolean>
+        <date name="date0"></date>
+        <date name="date1">2020-02-02</date>
+        <date name="date2">2039-12-01</date>
+        <date name="datebad">bad</date>
+        <dateTime name="datetime0"></dateTime>
+        <dateTime name="datetime1">2020-02-02T12:34:56</dateTime>
+        <dateTime name="datetime2">2039-12-01T12:34:56</dateTime>
+        <dateTime name="datetimebad">bad</dateTime>
+        <decimal name="decimal0"></decimal>
+        <decimal name="decimal1">42.0000000000000000001</decimal>
+        <decimal name="decimalbad">bad</decimal>
+        <exData name="ex0"></exData>
+        <exData name="ex1"><![CDATA[YZYZYZYZYZYZYZYZYZYZYZYZYZYZ]]></exData>
+        <float name="float0">-12.34</float>
+        <float name="float1">-12.34</float>
+        <float name="floatbad">bad</float>
+        <image name="image0">ABABABABABABABABA</image>
+        <image name="image1"><![CDATA[ABABABABABABABABA]]></image>
+        <integer name="int0"></integer>
+        <integer name="int1">1234</integer>
+        <integer name="intbad">bad</integer>
+        <line name="line0"></line>
+        <rectangle name="rect0"></rectangle>
+        <text name="text0"></text>
+        <text name="text1">Ahoy !!!</text>
+        <time name="time0"></time>
+        <time name="time1">12:34:56</time>
+        <goop name="goop0">Nonsense nodes not allowed here</goop>
+      </items>
+    </field>
+    <event activity="docReady">
+      <script contentType="application/x-javascript"><![CDATA[
+        {{include ../expect.js}}
+        {{include dump_tree.js}}
+
+        itemlist = xfa.resolveNode("form1.field1.#items");
+        dumpTree(itemlist);
+
+        arc1 = itemlist.resolveNode("arc1");
+
+        bool0 = itemlist.resolveNode("bool0");
+        expect("bool0.value", false);
+        bool0.value = 1;
+        expect("bool0.value", true);
+
+        bool1 = itemlist.resolveNode("bool1");
+        expect("bool1.value", true);
+        bool1.value = 0;
+        expect("bool1.value", false);
+
+        boolbad = itemlist.resolveNode("boolbad");
+        expect("boolbad.value", false);
+
+        booltruenottrue = itemlist.resolveNode("booltruenottrue");
+        expect("booltruenottrue.value", false);
+
+        // TODO(tsepez): confirm if this is correct.
+        booltruenottrue.value = true;
+        expect("booltruenottrue.value", false);
+
+        booltruenottrue.value = "zerp";
+        expect("booltruenottrue.value", false);
+        booltruenottrue.value = "1";
+        expect("booltruenottrue.value", true);
+        booltruenottrue.value = "10";
+        expect("booltruenottrue.value", true);
+        booltruenottrue.value = "1zerp";
+        expect("booltruenottrue.value", true);
+
+        // Date is just a node, and allows any text within.
+        date0 = itemlist.resolveNode("date0");
+        expect("date0.value", null);
+
+        date1 = itemlist.resolveNode("date1");
+        expect("date1.value", "2020-02-02");
+
+        date2 = itemlist.resolveNode("date2");
+        expect("date2.value", "2039-12-01");
+
+        datebad = itemlist.resolveNode("datebad");
+        expect("datebad.value", "bad");
+
+        // These are pretty much just nodes, and allow any text within.
+        // Just check that they parsed and that we can retrieve them.
+        datetime0 = itemlist.resolveNode("datetime0");
+        expect("datetime0", "[object XFAObject]");
+
+        datetime1 = itemlist.resolveNode("datetime1");
+        expect("datetime1", "[object XFAObject]");
+
+        datetime2 = itemlist.resolveNode("datetime2");
+        expect("datetime2", "[object XFAObject]");
+
+        datetimebad = itemlist.resolveNode("datetimebad");
+        expect("datetimebad", "[object XFAObject]");
+
+        decimal0 = itemlist.resolveNode("decimal0");
+        expect("decimal0", "[object XFAObject]");
+
+        decimal1 = itemlist.resolveNode("decimal1");
+        expect("decimal1", "[object XFAObject]");
+
+        decimalbad = itemlist.resolveNode("decimalbad");
+        expect("decimalbad", "[object XFAObject]");
+
+        ex0 = itemlist.resolveNode("ex0");
+        expect("ex0", "[object XFAObject]");
+
+        ex1 = itemlist.resolveNode("ex1");
+        expect("ex1", "[object XFAObject]");
+
+        float0 = itemlist.resolveNode("float0");
+        expect("float0", "[object XFAObject]");
+
+        float1 = itemlist.resolveNode("float1");
+        expect("float1", "[object XFAObject]");
+
+        floatbad = itemlist.resolveNode("floatbad");
+        expect("floatbad", "[object XFAObject]");
+
+        image0 = itemlist.resolveNode("image0");
+        expect("image0", "[object XFAObject]");
+
+        image1 = itemlist.resolveNode("image1");
+        expect("image1", "[object XFAObject]");
+
+        int0 = itemlist.resolveNode("int0");
+        expect("int0", "[object XFAObject]");
+
+        int1 = itemlist.resolveNode("int1");
+        expect("int1", "[object XFAObject]");
+
+        intbad = itemlist.resolveNode("intbad");
+        expect("intbad", "[object XFAObject]");
+
+        line0 = itemlist.resolveNode("line0");
+        expect("line0", "[object XFAObject]");
+
+        rect0 = itemlist.resolveNode("rect0");
+        expect("rect0", "[object XFAObject]");
+
+        text0 = itemlist.resolveNode("text0");
+        expect("text0", "[object XFAObject]");
+
+        text1 = itemlist.resolveNode("text1");
+        expect("text1", "[object XFAObject]");
+
+        time0 = itemlist.resolveNode("time0");
+        expect("time0", "[object XFAObject]");
+
+        time1 = itemlist.resolveNode("time1");
+        expect("time1", "[object XFAObject]");
+
+        // The parser is picky and won't let fake nodes in here.
+        goop0 = itemlist.resolveNode("goop0");
+        expect("goop0", null);
+      ]]></script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/xfa_items_expected.txt b/testing/resources/javascript/xfa_specific/xfa_items_expected.txt
new file mode 100644
index 0000000..cc0c7c5
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/xfa_items_expected.txt
@@ -0,0 +1,80 @@
+Alert: items
+Alert: | arc
+Alert: | boolean
+Alert: | boolean
+Alert: | boolean
+Alert: | boolean
+Alert: | date
+Alert: | date
+Alert: | | #text
+Alert: | date
+Alert: | | #text
+Alert: | date
+Alert: | | #text
+Alert: | dateTime
+Alert: | dateTime
+Alert: | | #text
+Alert: | dateTime
+Alert: | | #text
+Alert: | dateTime
+Alert: | | #text
+Alert: | decimal
+Alert: | decimal
+Alert: | decimal
+Alert: | exData
+Alert: | exData
+Alert: | float
+Alert: | float
+Alert: | float
+Alert: | image
+Alert: | | #text
+Alert: | image
+Alert: | | #text
+Alert: | integer
+Alert: | integer
+Alert: | integer
+Alert: | line
+Alert: | rectangle
+Alert: | text
+Alert: | text
+Alert: | time
+Alert: | time
+Alert: PASS: bool0.value = false
+Alert: PASS: bool0.value = true
+Alert: PASS: bool1.value = true
+Alert: PASS: bool1.value = false
+Alert: PASS: boolbad.value = false
+Alert: PASS: booltruenottrue.value = false
+Alert: PASS: booltruenottrue.value = false
+Alert: PASS: booltruenottrue.value = false
+Alert: PASS: booltruenottrue.value = true
+Alert: PASS: booltruenottrue.value = true
+Alert: PASS: booltruenottrue.value = true
+Alert: PASS: date0.value = null
+Alert: PASS: date1.value = 2020-02-02
+Alert: PASS: date2.value = 2039-12-01
+Alert: PASS: datebad.value = bad
+Alert: PASS: datetime0 = [object XFAObject]
+Alert: PASS: datetime1 = [object XFAObject]
+Alert: PASS: datetime2 = [object XFAObject]
+Alert: PASS: datetimebad = [object XFAObject]
+Alert: PASS: decimal0 = [object XFAObject]
+Alert: PASS: decimal1 = [object XFAObject]
+Alert: PASS: decimalbad = [object XFAObject]
+Alert: PASS: ex0 = [object XFAObject]
+Alert: PASS: ex1 = [object XFAObject]
+Alert: PASS: float0 = [object XFAObject]
+Alert: PASS: float1 = [object XFAObject]
+Alert: PASS: floatbad = [object XFAObject]
+Alert: PASS: image0 = [object XFAObject]
+Alert: PASS: image1 = [object XFAObject]
+Alert: PASS: int0 = [object XFAObject]
+Alert: PASS: int1 = [object XFAObject]
+Alert: PASS: intbad = [object XFAObject]
+Alert: PASS: line0 = [object XFAObject]
+Alert: PASS: rect0 = [object XFAObject]
+Alert: PASS: text0 = [object XFAObject]
+Alert: PASS: text1 = [object XFAObject]
+Alert: PASS: time0 = [object XFAObject]
+Alert: PASS: time1 = [object XFAObject]
+Alert: PASS: goop0 = null
diff --git a/testing/resources/javascript/xfa_specific/xfa_log_pseudomodel.in b/testing/resources/javascript/xfa_specific/xfa_log_pseudomodel.in
index 2de417e..a3f28fc 100644
--- a/testing/resources/javascript/xfa_specific/xfa_log_pseudomodel.in
+++ b/testing/resources/javascript/xfa_specific/xfa_log_pseudomodel.in
@@ -40,6 +40,7 @@
 endobj
 {{include ../../xfa_locale_6_0.fragment}}
 {{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
 {{xref}}
 {{trailer}}
 {{startxref}}
diff --git a/testing/resources/javascript/xfa_specific/xfa_node.in b/testing/resources/javascript/xfa_specific/xfa_node.in
index 7157993..a0e9b25 100644
--- a/testing/resources/javascript/xfa_specific/xfa_node.in
+++ b/testing/resources/javascript/xfa_specific/xfa_node.in
@@ -7,6 +7,7 @@
   {{streamlen}}
 >>
 stream
+<goop viscosity="sludge">Spurious Nonsense</goop>
 <template>
   <subform layout="tb" name="my_doc">
     <pageSet id="page" relation="orderedOccurrence">
@@ -21,9 +22,13 @@
       </pageArea>
     </pageSet>
     <event activity="docReady" ref="$host">
-      <script contentType="application/x-javascript">
+      <script contentType="application/x-javascript"><![CDATA[
         {{include ../expect.js}}
         {{include ../property_test_helpers.js}}
+        {{include dump_tree.js}}
+
+        dumpTree(xfa);
+
         testROProperty(my_doc, "isContainer", true);
         testROProperty(my_doc, "isNull", false);
         testROProperty(my_doc, "model", "[object XFAObject]");
@@ -74,7 +79,35 @@
         // Test free-form attributes outside of the XFA schema.
         expect("my_doc.setAttribute('fake_value', 'fake_attr')", undefined);
         expect("my_doc.getAttribute('fake_attr')", 'fake_value');
-      </script>
+
+        // Test magic "xfa" property bound to all nodes.
+        expect("my_doc.xfa == xfa", true);
+
+        // Test "packet" nodes which are unrecognized tags at XDP level.
+        expect("goop", undefined);
+        var goop = this.resolveNode('#packet');
+        expect("goop.className", "packet");
+        expect("goop.viscosity", undefined);
+        expectError("goop.getAttribute()");
+        expectError("goop.getAttribute(1, 2)");
+        expect("goop.getAttribute('nonesuch')", "");
+        expect("goop.getAttribute('viscosity')", "sludge");
+        expectError("goop.setAttribute()");
+        expectError("goop.setAttribute(1)");
+        expectError("goop.setAttribute(1, 2, 3)");
+
+        // Remember, args to setAttribute() are backwards from what one might
+        // typically expect ...
+        expect("goop.setAttribute(7, 'ph')", undefined);
+        expect("goop.getAttribute('ph')", 7);
+
+        expect("goop.content", "Spurious Nonsense");
+        expect("goop.content = ' for All'", " for All");
+
+        // TODO(tsepez): assigning new content seems to append although it
+        // wasn't included in the return value of the assignment above.
+        expect("goop.content", "Spurious Nonsense for All");
+      ]]></script>
     </event>
   </subform>
 </template>
diff --git a/testing/resources/javascript/xfa_specific/xfa_node_expected.txt b/testing/resources/javascript/xfa_specific/xfa_node_expected.txt
index 68b73a9..8455004 100644
--- a/testing/resources/javascript/xfa_specific/xfa_node_expected.txt
+++ b/testing/resources/javascript/xfa_specific/xfa_node_expected.txt
@@ -1,3 +1,200 @@
+Alert: xfa
+Alert: | config
+Alert: | | agent
+Alert: | | | destination
+Alert: | | | | #text
+Alert: | | | pdf
+Alert: | | | | fontInfo
+Alert: | | present
+Alert: | | | pdf
+Alert: | | | | version
+Alert: | | | | | #text
+Alert: | | | | adobeExtensionLevel
+Alert: | | | | | #text
+Alert: | | | | renderPolicy
+Alert: | | | | | #text
+Alert: | | | | scriptModel
+Alert: | | | | | #text
+Alert: | | | | interactive
+Alert: | | | | | #text
+Alert: | | | xdp
+Alert: | | | | packets
+Alert: | | | | | #text
+Alert: | | | destination
+Alert: | | | | #text
+Alert: | | | script
+Alert: | | | | #text
+Alert: | | acrobat
+Alert: | | | acrobat7
+Alert: | | | | dynamicRender
+Alert: | | | | | #text
+Alert: | | | validate
+Alert: | | | | #text
+Alert: | packet
+Alert: | template
+Alert: | | subform
+Alert: | | | event
+Alert: | | | | script
+Alert: | | | | | #text
+Alert: | localeSet
+Alert: | | locale
+Alert: | | | calendarSymbols
+Alert: | | | | monthNames
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | monthNames
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | | month
+Alert: | | | | | | #text
+Alert: | | | | dayNames
+Alert: | | | | | day
+Alert: | | | | | | #text
+Alert: | | | | | day
+Alert: | | | | | | #text
+Alert: | | | | | day
+Alert: | | | | | | #text
+Alert: | | | | | day
+Alert: | | | | | | #text
+Alert: | | | | | day
+Alert: | | | | | | #text
+Alert: | | | | | day
+Alert: | | | | | | #text
+Alert: | | | | | day
+Alert: | | | | | | #text
+Alert: | | | | dayNames
+Alert: | | | | | day
+Alert: | | | | | | #text
+Alert: | | | | | day
+Alert: | | | | | | #text
+Alert: | | | | | day
+Alert: | | | | | | #text
+Alert: | | | | | day
+Alert: | | | | | | #text
+Alert: | | | | | day
+Alert: | | | | | | #text
+Alert: | | | | | day
+Alert: | | | | | | #text
+Alert: | | | | | day
+Alert: | | | | | | #text
+Alert: | | | | meridiemNames
+Alert: | | | | | meridiem
+Alert: | | | | | | #text
+Alert: | | | | | meridiem
+Alert: | | | | | | #text
+Alert: | | | | eraNames
+Alert: | | | | | era
+Alert: | | | | | | #text
+Alert: | | | | | era
+Alert: | | | | | | #text
+Alert: | | | datePatterns
+Alert: | | | | datePattern
+Alert: | | | | | #text
+Alert: | | | | datePattern
+Alert: | | | | | #text
+Alert: | | | | datePattern
+Alert: | | | | | #text
+Alert: | | | | datePattern
+Alert: | | | | | #text
+Alert: | | | timePatterns
+Alert: | | | | timePattern
+Alert: | | | | | #text
+Alert: | | | | timePattern
+Alert: | | | | | #text
+Alert: | | | | timePattern
+Alert: | | | | | #text
+Alert: | | | | timePattern
+Alert: | | | | | #text
+Alert: | | | dateTimeSymbols
+Alert: | | | | #text
+Alert: | | | numberPatterns
+Alert: | | | | numberPattern
+Alert: | | | | | #text
+Alert: | | | | numberPattern
+Alert: | | | | | #text
+Alert: | | | | numberPattern
+Alert: | | | | | #text
+Alert: | | | numberSymbols
+Alert: | | | | numberSymbol
+Alert: | | | | | #text
+Alert: | | | | numberSymbol
+Alert: | | | | | #text
+Alert: | | | | numberSymbol
+Alert: | | | | | #text
+Alert: | | | | numberSymbol
+Alert: | | | | | #text
+Alert: | | | | numberSymbol
+Alert: | | | | | #text
+Alert: | | | currencySymbols
+Alert: | | | | currencySymbol
+Alert: | | | | | #text
+Alert: | | | | currencySymbol
+Alert: | | | | | #text
+Alert: | | | | currencySymbol
+Alert: | | | | | #text
+Alert: | | | typefaces
+Alert: | | | | typeface
+Alert: | | | | typeface
+Alert: | | | | typeface
+Alert: | | | | typeface
+Alert: | | | | typeface
+Alert: | | | | typeface
+Alert: | | | | typeface
+Alert: | | | | typeface
+Alert: | | | | typeface
+Alert: | | | | typeface
+Alert: | | | | typeface
+Alert: | | | | typeface
+Alert: | dataModel
+Alert: | | dataGroup
+Alert: | | | dataGroup
+Alert: | form
+Alert: | | subform
+Alert: | | | event
+Alert: | | | | script
+Alert: | | | | | #text
 Alert: PASS: isContainer = true
 Alert: PASS: isContainer threw Error: Invalid property set operation.
 Alert: PASS: isNull = false
@@ -49,3 +246,19 @@
 Alert: PASS: my_doc.getAttribute('ns') = something
 Alert: PASS: my_doc.setAttribute('fake_value', 'fake_attr') = undefined
 Alert: PASS: my_doc.getAttribute('fake_attr') = fake_value
+Alert: PASS: my_doc.xfa == xfa = true
+Alert: PASS: goop = undefined
+Alert: PASS: goop.className = packet
+Alert: PASS: goop.viscosity = undefined
+Alert: PASS: goop.getAttribute() threw XFAObject.getAttribute: Incorrect number of parameters passed to function.
+Alert: PASS: goop.getAttribute(1, 2) threw XFAObject.getAttribute: Incorrect number of parameters passed to function.
+Alert: PASS: goop.getAttribute('nonesuch') = 
+Alert: PASS: goop.getAttribute('viscosity') = sludge
+Alert: PASS: goop.setAttribute() threw XFAObject.setAttribute: Incorrect number of parameters passed to function.
+Alert: PASS: goop.setAttribute(1) threw XFAObject.setAttribute: Incorrect number of parameters passed to function.
+Alert: PASS: goop.setAttribute(1, 2, 3) threw XFAObject.setAttribute: Incorrect number of parameters passed to function.
+Alert: PASS: goop.setAttribute(7, 'ph') = null
+Alert: PASS: goop.getAttribute('ph') = 7
+Alert: PASS: goop.content = Spurious Nonsense
+Alert: PASS: goop.content = ' for All' =  for All
+Alert: PASS: goop.content = Spurious Nonsense for All
diff --git a/testing/resources/javascript/xfa_specific/xfa_template.in b/testing/resources/javascript/xfa_specific/xfa_template.in
new file mode 100644
index 0000000..60ad46d
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/xfa_template.in
@@ -0,0 +1,60 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template>
+  <subform layout="tb" name="my_doc">
+    <pageSet id="page" relation="orderedOccurrence">
+      <occur initial="1" max="1" min="1"/>
+      <pageArea id="Page1" name="Page1">
+        <occur max="1" min="1"/>
+        <contentArea h="100mm" w="200mm" x="0.25in" y="0.25in"/>
+      </pageArea>
+      <pageArea id="Page2" name="Page2">
+        <occur max="1" min="1"/>
+        <contentArea h="100mm" w="200mm" x="0.25in" y="0.25in"/>
+      </pageArea>
+    </pageSet>
+    <event activity="docReady" ref="$host">
+      <script contentType="application/x-javascript"><![CDATA[
+        {{include ../expect.js}}
+        template0 = xfa.resolveNode("#template");
+        expect("template0.className", "template");
+
+        expectError("template0.execCalculate(1)");
+        expect("template0.execCalculate()", false);
+
+        expectError("template0.execInitialize(1)", false);
+        expect("template0.execInitialize()", false);
+
+        expectError("template0.execValidate(1)", false);
+        expect("template0.execValidate()", false);
+
+        expectError("template0.formNodes()", undefined);
+        expectError("template0.formNodes(1, 2)", undefined);
+        expect("template0.formNodes(true)", true);
+
+        expectError("template0.recalculate()", undefined);
+        expectError("template0.recalculate(1, 2)", undefined);
+        expect("template0.recalculate(true)", true);
+
+        expectError("template0.remerge(1)", undefined);
+        expect("template0.remerge()", undefined);
+      ]]></script>
+    </event>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/xfa_template_expected.txt b/testing/resources/javascript/xfa_specific/xfa_template_expected.txt
new file mode 100644
index 0000000..9bb1ffd
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/xfa_template_expected.txt
@@ -0,0 +1,15 @@
+Alert: PASS: template0.className = template
+Alert: PASS: template0.execCalculate(1) threw XFAObject.execCalculate: Incorrect number of parameters passed to function.
+Alert: PASS: template0.execCalculate() = false
+Alert: PASS: template0.execInitialize(1) threw XFAObject.execInitialize: Incorrect number of parameters passed to function.
+Alert: PASS: template0.execInitialize() = false
+Alert: PASS: template0.execValidate(1) threw XFAObject.execValidate: Incorrect number of parameters passed to function.
+Alert: PASS: template0.execValidate() = false
+Alert: PASS: template0.formNodes() threw XFAObject.formNodes: Incorrect number of parameters passed to function.
+Alert: PASS: template0.formNodes(1, 2) threw XFAObject.formNodes: Incorrect number of parameters passed to function.
+Alert: PASS: template0.formNodes(true) = true
+Alert: PASS: template0.recalculate() threw XFAObject.recalculate: Incorrect number of parameters passed to function.
+Alert: PASS: template0.recalculate(1, 2) threw XFAObject.recalculate: Incorrect number of parameters passed to function.
+Alert: PASS: template0.recalculate(true) = true
+Alert: PASS: template0.remerge(1) threw XFAObject.remerge: Incorrect number of parameters passed to function.
+Alert: PASS: template0.remerge() = undefined
diff --git a/testing/resources/javascript/xfa_specific/xfa_variables.in b/testing/resources/javascript/xfa_specific/xfa_variables.in
new file mode 100644
index 0000000..6bfdb97
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/xfa_variables.in
@@ -0,0 +1,76 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template>
+  <subform layout="tb" name="my_doc">
+    <variables>
+      <text name="xx01">123</text>
+      <text name="xx02">456</text>
+      <integer name="xx03">123</integer>
+      <integer name="xx04">456</integer>
+    </variables>
+    <pageSet id="page" relation="orderedOccurrence">
+      <occur initial="1" max="1" min="1"/>
+      <pageArea id="Page1" name="Page1">
+        <occur max="1" min="1"/>
+        <contentArea h="100mm" w="200mm" x="0.25in" y="0.25in"/>
+      </pageArea>
+      <pageArea id="Page2" name="Page2">
+        <occur max="1" min="1"/>
+        <contentArea h="100mm" w="200mm" x="0.25in" y="0.25in"/>
+      </pageArea>
+    </pageSet>
+    <event activity="docReady" ref="$host">
+      <script name="my_script" contentType="application/x-javascript"><![CDATA[
+        {{include ../expect.js}}
+        try {
+          var script1 = xfa.resolveNode('template..my_script');
+          var script2 = xfa.resolveNode('template..their_script');
+          var script3 = xfa.resolveNode('template..other_script');
+          app.alert('First, poke at a script node itsef');
+          expect('script1.stateless', '0');
+          expectError('script1.stateless = 42');
+          app.alert('We search variables context ' + (xx01.value + xx02.value));
+          app.alert('We search variables context ' + (xx03.value + xx04.value));
+          app.alert('We resolve off of script1 ' + (script1.xx01.value + script1.xx02.value));
+          app.alert('We resolve off of script2 ' + (script2.xx01.value + script2.xx02.value));
+          app.alert('We resolve off of script3 ' + (script3.xx01.value + script3.xx02.value));
+          app.alert('We resolve off of script1 ' + script1.nonesuch);
+          app.alert('We resolve off of script2 ' + script2.nonesuch);
+          app.alert('We resolve off of script3 ' + script3.nonesuch);
+        } catch (e) {
+          app.alert('Error: ' + e);
+        }
+      ]]></script>
+    </event>
+  </subform>
+  <subform layout="tb" name="their_doc">
+    <variables>
+      <text name="xx01">78</text>
+      <text name="xx02">90</text>
+      <integer name="xx03">78</integer>
+      <integer name="xx04">90</integer>
+      <script name="other_script">
+        var xx01 = "chips";
+      </script>
+    </variables>
+    <script name="their_script">
+      var xx01 = "clams";
+    </script>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/xfa_specific/xfa_variables_expected.txt b/testing/resources/javascript/xfa_specific/xfa_variables_expected.txt
new file mode 100644
index 0000000..75a457f
--- /dev/null
+++ b/testing/resources/javascript/xfa_specific/xfa_variables_expected.txt
@@ -0,0 +1,11 @@
+Alert: First, poke at a script node itsef
+Alert: PASS: script1.stateless = 0
+Alert: PASS: script1.stateless = 42 threw Error: Invalid property set operation.
+Alert: We search variables context 123456
+Alert: We search variables context 579
+Alert: We resolve off of script1 123456
+Alert: We resolve off of script2 7890
+Alert: We resolve off of script3 7890
+Alert: We resolve off of script1 undefined
+Alert: We resolve off of script2 undefined
+Alert: We resolve off of script3 undefined
diff --git a/testing/resources/jpx_lzw.in b/testing/resources/jpx_lzw.in
new file mode 100644
index 0000000..46914c1
--- /dev/null
+++ b/testing/resources/jpx_lzw.in
@@ -0,0 +1,55 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Resources <<
+    /XObject <<
+      /Im0 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+612 0 0 792 0 0 cm
+/Im0 Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Filter [/ASCIIHexDecode /LZWDecode /JPXDecode]
+  /Width 612
+  /Height 792
+  {{streamlen}}
+>>
+stream
+80002040c351404020068290e0a8100028663a1e4e06a380c8410d004522d0d16c68d10d0b1a4d06
+439408061881008c8000181c0f180003cc66f361c80330084358d1a31bfc9eff288005f189549a51
+30a24ae5947a44b00e0100d3ea3507f948000c86d400a040200002ff2e0002640201209050b35a2c
+f69b4bfe5625af10ce465309d0ca6410188f220279c0ca6e251408a47101d8ca72399a4de6e100c8
+5c33170c1fe9000432303faf3fd26cf5a14022502a2bdfe7f369a0b480980c12097d1801be801c05
+0005c57bf818daa6ee5fed9801
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/jpx_lzw.pdf b/testing/resources/jpx_lzw.pdf
new file mode 100644
index 0000000..8735018
--- /dev/null
+++ b/testing/resources/jpx_lzw.pdf
@@ -0,0 +1,67 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Resources <<
+    /XObject <<
+      /Im0 5 0 R
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Length 31
+>>
+stream
+q
+612 0 0 792 0 0 cm
+/Im0 Do
+Q
+endstream
+endobj
+5 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /Filter [/ASCIIHexDecode /LZWDecode /JPXDecode]
+  /Width 612
+  /Height 792
+  /Length 432
+>>
+stream
+80002040c351404020068290e0a8100028663a1e4e06a380c8410d004522d0d16c68d10d0b1a4d06
+439408061881008c8000181c0f180003cc66f361c80330084358d1a31bfc9eff288005f189549a51
+30a24ae5947a44b00e0100d3ea3507f948000c86d400a040200002ff2e0002640201209050b35a2c
+f69b4bfe5625af10ce465309d0ca6410188f220279c0ca6e251408a47101d8ca72399a4de6e100c8
+5c33170c1fe9000432303faf3fd26cf5a14022502a2bdfe7f369a0b480980c12097d1801be801c05
+0005c57bf818daa6ee5fed9801
+endstream
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000287 00000 n 
+0000000369 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+965
+%%EOF
diff --git a/testing/resources/js.pdf b/testing/resources/js.pdf
index 07e9b75..8d64866 100644
--- a/testing/resources/js.pdf
+++ b/testing/resources/js.pdf
@@ -22,7 +22,6 @@
 4 0 obj <<
   /Names [
     (normal) 5 0 R
-    (encoded_subtype) 6 0 R
     (no_type) 7 0 R
     (wrongtype) 8 0 R
     (wrongsubtype) 9 0 R
@@ -36,12 +35,6 @@
   /JS 11 0 R
 >>
 endobj
-6 0 obj <<
-  /Type /Action
-  /S /J#61v#61Script
-  /JS 11 0 R
->>
-endobj
 7 0 obj <<
   /S /JavaScript
   /JS 12 0 R
diff --git a/testing/resources/line_annot.in b/testing/resources/line_annot.in
new file mode 100644
index 0000000..0778169
--- /dev/null
+++ b/testing/resources/line_annot.in
@@ -0,0 +1,50 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [
+    4 0 R 5 0 R
+  ]
+  /Tabs /R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Line
+  /NM (Line-1)
+  /F 4
+  /L [159 296 472 243.42]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+  /Border [0.25 0.5 2]
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Annot
+  /Subtype /Line
+  /NM (Line-2)
+  /F 4
+  /L [159 296 472]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+  /Border [0.25 0.5]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/line_annot.pdf b/testing/resources/line_annot.pdf
new file mode 100644
index 0000000..7a7fee3
--- /dev/null
+++ b/testing/resources/line_annot.pdf
@@ -0,0 +1,62 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [
+    4 0 R 5 0 R
+  ]
+  /Tabs /R
+>>
+endobj
+4 0 obj <<
+  /Type /Annot
+  /Subtype /Line
+  /NM (Line-1)
+  /F 4
+  /L [159 296 472 243.42]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+  /Border [0.25 0.5 2]
+>>
+endobj
+5 0 obj <<
+  /Type /Annot
+  /Subtype /Line
+  /NM (Line-2)
+  /F 4
+  /L [159 296 472]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+  /Border [0.25 0.5]
+>>
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000251 00000 n 
+0000000431 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+602
+%%EOF
diff --git a/testing/resources/link_annots.in b/testing/resources/link_annots.in
deleted file mode 100644
index 076069c..0000000
--- a/testing/resources/link_annots.in
+++ /dev/null
@@ -1,333 +0,0 @@
-{{header}}
-{{object 1 0}} <<
-  /Type /Catalog
-  /Pages 2 0 R
->>
-endobj
-{{object 2 0}} <<
-  /Type /Pages
-  /Count 2
-  /Kids [3 0 R 4 0 R]
-  /MediaBox [0 0 612 792]
-  /CropBox [0 0 612 792]
-  /Resources <<
-    /Font <<
-      /F1 7 0 R
-      /F2 8 0 R
-    >>
-    /ProcSet [/PDF /Text /ImageC]
-    /ExtGState <<
-      /GS0 23 0 R
-    >>
-  >>
->>
-endobj
-{{object 3 0}} <<
-  /Type /Page
-  /Parent 2 0 R
-  /Contents 5 0 R
-  /Annots [15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R]
->>
-endobj
-{{object 4 0}} <<
-  /Type /Page
-  /Parent 2 0 R
-  /Contents 6 0 R
-  /Annots [15 0 R 16 0 R]
->>
-endobj
-{{object 5 0}} <<
-  {{streamlen}}
->>
-stream
-BT
-70 700 Td
-/F1 18 Tf
-(Link Annotations - Page 1) Tj
-0 -65 Td
-/F2 14 Tf
-(1. Link with destination to first page) Tj
-10 -20 Td
-/F2 14 Tf
-(2. Link with destination to second page) Tj
--12 -84 Td
-/F2 10 Tf
-(PDF Reference, Version 1.7, Section 8.4.5 defines Annotations) Tj
-2 -53 Td
-(3.  An example of Highlight with text notes) Tj
-0 -18 Td
-(https://pdfium.googlesource.com/pdfium is link in plain text, not link annotation. These are referred to) Tj
-0 -17 Td
-(as WebLinks in PDFium.)Tj
-ET
-endstream
-endobj
-{{object 6 0}} <<
-  {{streamlen}}
->>
-stream
-BT
-70 700 Td
-/F1 18 Tf
-(Link Annotations - Page 2) Tj
-0 -65 Td
-/F2 14 Tf
-(1. Link with destination to first page) Tj
-10 -20 Td
-/F2 14 Tf
-(2. Link with destination to second page) Tj
-ET
-endstream
-endobj
-{{object 7 0}} <<
-  /Type /Font
-  /Subtype /Type1
-  /BaseFont /Times-Roman
->>
-endobj
-{{object 8 0}} <<
-  /Type /Font
-  /Subtype /Type1
-  /BaseFont /Helvetica
->>
-endobj
-{{object 9 0}} <<
-  /Type /XObject
-  /Subtype /Form
-  /FormType 1
-  {{streamlen}}
-  /BBox [293 530 349 542]
-  /Resources <<
-    /XObject <<
-      /Form0 10 0 R
-    >>
-    /ExtGState <<
-      /GS0 24 0 R
-    >>
-  >>
->>
-stream
-/GS0 gs
-/Form0 Do
-endstream
-endobj
-{{object 10 0}} <<
-  /Type /XObject
-  /Subtype /Form
-  /FormType 1
-  /Group <<
-    /S /Transparency
-  >>
-  {{streamlen}}
-  /BBox [293 530 349 542]
->>
-stream
-1.0 1.0 0.0 rg
-293 530 m
-349 530 l
-349 542 l
-293 542 l
-h f
-endstream
-endobj
-{{object 11 0}} <<
-  /Type /XObject
-  /Subtype /Form
-  /FormType 1
-  {{streamlen}}
-  /BBox [83 440 178 453]
-  /Resources <<
-    /XObject <<
-      /Form0 12 0 R
-    >>
-    /ExtGState <<
-      /GS0 24 0 R
-    >>
-  >>
->>
-stream
-/GS0 gs
-/Form0 Do
-endstream
-endobj
-{{object 12 0}} <<
-  /Type /XObject
-  /Subtype /Form
-  /FormType 1
-  /Group <<
-    /S /Transparency
-  >>
-  {{streamlen}}
-  /BBox [83 440 178 453]
->>
-stream
-0.0 1.0 1.0 rg
-83 440 m
-178 440 l
-178 453 l
-83 453 l
-h f
-endstream
-endobj
-{{object 13 0}} <<
-  /Type /XObject
-  /Subtype /Form
-  /FormType 1
-  {{streamlen}}
-  /BBox [149 476 191 487]
-  /Resources <<
-    /XObject <<
-      /Form0 14 0 R
-    >>
-    /ExtGState <<
-      /GS0 24 0 R
-    >>
-  >>
->>
-stream
-/GS0 gs
-/Form0 Do
-endstream
-endobj
-{{object 14 0}} <<
-  /Type /XObject
-  /Subtype /Form
-  /FormType 1
-  /Group <<
-    /S /Transparency
-  >>
-  {{streamlen}}
-  /BBox [149 476 191 487]
->>
-stream
-0.0 1.0 0.0 rg
-149 476 m
-191 476 l
-191 487 l
-149 487 l
-h f
-endstream
-endobj
-{{object 15 0}} <<
-  /Type /Annot
-  /Subtype /Link
-  /BS <<
-    /W 0
-  >>
-  /Rect [69 633 542 653]
-  /Dest [3 0 R /XYZ 200 725 0]
-  /F 4
->>
-endobj
-{{object 16 0}} <<
-  /Type /Annot
-  /Subtype /Link
-  /BS <<
-    /W 0
-  >>
-  /Rect [80 613 542 633]
-  /Dest [4 0 R /XYZ 200 725 0]
-  /F 4
->>
-endobj
-{{object 17 0}} <<
-  /Type /Annot
-  /Subtype /Link
-  /BS <<
-    /W 0
-  >>
-  /Rect [66 529 196 544]
-  /A <<
-    /Type /Action
-    /URI (https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf)
-    /S /URI
-  >>
-  /F 4
->>
-endobj
-{{object 18 0}} <<
-  /Type /Annot
-  /Subtype /Link
-  /BS <<
-    /W 0
-  >>
-  /Rect [83 440 178 453]
-  /QuadPoints [83 453 178 453 83 440 178 440]
-  /A <<
-    /Type /Action
-    /URI (https://cs.chromium.org/chromium/src/third_party/pdfium/public/fpdf_text.h)
-    /S /URI
-  >>
-  /F 4
->>
-endobj
-{{object 19 0}} <<
-  /Type /Annot
-  /Subtype /Highlight
-  /AP <<
-    /N 9 0 R
-  >>
-  /NM (Highlight-1)
-  /F 4
-  /QuadPoints [293 542 349 542 293 530 349 530]
-  /P 3 0 R
-  /C [1 0.90196 0]
-  /Rect [293 530 349 542]
->>
-endobj
-{{object 20 0}} <<
-  /Type /Annot
-  /Subtype /Highlight
-  /AP <<
-    /N 11 0 R
-  >>
-  /NM (Highlight-2)
-  /F 4
-  /QuadPoints [83 453 178 453 83 440 178 440]
-  /P 3 0 R
-  /C [0.26667 0.78431 0.96078]
-  /Rect [83 440 178 453]
->>
-endobj
-{{object 21 0}} <<
-  /Type /Annot
-  /Subtype /Popup
-  /Parent 22 0 R
-  /Rect [191 377 443 488]
->>
-endobj
-{{object 22 0}} <<
-  /Type /Annot
-  /Subtype /Highlight
-  /Popup 21 0 R
-  /AP <<
-    /N 13 0 R
-  >>
-  /NM (Highlight-With-Popup-1)
-  /Contents (Text Note)
-  /QuadPoints [149 487 191 487 149 476 191 476]
-  /P 3 0 R
-  /C [0.14902 0.90196 0]
-  /Rect [149 476 191 487]
-  /F 4
->>
-endobj
-{{object 23 0}} <<
-  /ca 1
-  /Type /ExtGState
-  /CA 1
-  /BM /Normal
->>
-endobj
-{{object 24 0}} <<
-  /ca 1
-  /Type /ExtGState
-  /CA 1
-  /AIS false
-  /BM /Multiply
->>
-endobj
-{{xref}}
-{{trailer}}
-{{startxref}}
-%%EOF
diff --git a/testing/resources/link_annots.pdf b/testing/resources/link_annots.pdf
deleted file mode 100644
index b964bf5..0000000
--- a/testing/resources/link_annots.pdf
+++ /dev/null
@@ -1,364 +0,0 @@
-%PDF-1.7
-% ò¤ô
-1 0 obj <<
-  /Type /Catalog
-  /Pages 2 0 R
->>
-endobj
-2 0 obj <<
-  /Type /Pages
-  /Count 2
-  /Kids [3 0 R 4 0 R]
-  /MediaBox [0 0 612 792]
-  /CropBox [0 0 612 792]
-  /Resources <<
-    /Font <<
-      /F1 7 0 R
-      /F2 8 0 R
-    >>
-    /ProcSet [/PDF /Text /ImageC]
-    /ExtGState <<
-      /GS0 23 0 R
-    >>
-  >>
->>
-endobj
-3 0 obj <<
-  /Type /Page
-  /Parent 2 0 R
-  /Contents 5 0 R
-  /Annots [15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R]
->>
-endobj
-4 0 obj <<
-  /Type /Page
-  /Parent 2 0 R
-  /Contents 6 0 R
-  /Annots [15 0 R 16 0 R]
->>
-endobj
-5 0 obj <<
-  /Length 486
->>
-stream
-BT
-70 700 Td
-/F1 18 Tf
-(Link Annotations - Page 1) Tj
-0 -65 Td
-/F2 14 Tf
-(1. Link with destination to first page) Tj
-10 -20 Td
-/F2 14 Tf
-(2. Link with destination to second page) Tj
--12 -84 Td
-/F2 10 Tf
-(PDF Reference, Version 1.7, Section 8.4.5 defines Annotations) Tj
-2 -53 Td
-(3.  An example of Highlight with text notes) Tj
-0 -18 Td
-(https://pdfium.googlesource.com/pdfium is link in plain text, not link annotation. These are referred to) Tj
-0 -17 Td
-(as WebLinks in PDFium.)Tj
-ET
-endstream
-endobj
-6 0 obj <<
-  /Length 185
->>
-stream
-BT
-70 700 Td
-/F1 18 Tf
-(Link Annotations - Page 2) Tj
-0 -65 Td
-/F2 14 Tf
-(1. Link with destination to first page) Tj
-10 -20 Td
-/F2 14 Tf
-(2. Link with destination to second page) Tj
-ET
-endstream
-endobj
-7 0 obj <<
-  /Type /Font
-  /Subtype /Type1
-  /BaseFont /Times-Roman
->>
-endobj
-8 0 obj <<
-  /Type /Font
-  /Subtype /Type1
-  /BaseFont /Helvetica
->>
-endobj
-9 0 obj <<
-  /Type /XObject
-  /Subtype /Form
-  /FormType 1
-  /Length 18
-  /BBox [293 530 349 542]
-  /Resources <<
-    /XObject <<
-      /Form0 10 0 R
-    >>
-    /ExtGState <<
-      /GS0 24 0 R
-    >>
-  >>
->>
-stream
-/GS0 gs
-/Form0 Do
-endstream
-endobj
-10 0 obj <<
-  /Type /XObject
-  /Subtype /Form
-  /FormType 1
-  /Group <<
-    /S /Transparency
-  >>
-  /Length 59
-  /BBox [293 530 349 542]
->>
-stream
-1.0 1.0 0.0 rg
-293 530 m
-349 530 l
-349 542 l
-293 542 l
-h f
-endstream
-endobj
-11 0 obj <<
-  /Type /XObject
-  /Subtype /Form
-  /FormType 1
-  /Length 18
-  /BBox [83 440 178 453]
-  /Resources <<
-    /XObject <<
-      /Form0 12 0 R
-    >>
-    /ExtGState <<
-      /GS0 24 0 R
-    >>
-  >>
->>
-stream
-/GS0 gs
-/Form0 Do
-endstream
-endobj
-12 0 obj <<
-  /Type /XObject
-  /Subtype /Form
-  /FormType 1
-  /Group <<
-    /S /Transparency
-  >>
-  /Length 57
-  /BBox [83 440 178 453]
->>
-stream
-0.0 1.0 1.0 rg
-83 440 m
-178 440 l
-178 453 l
-83 453 l
-h f
-endstream
-endobj
-13 0 obj <<
-  /Type /XObject
-  /Subtype /Form
-  /FormType 1
-  /Length 18
-  /BBox [149 476 191 487]
-  /Resources <<
-    /XObject <<
-      /Form0 14 0 R
-    >>
-    /ExtGState <<
-      /GS0 24 0 R
-    >>
-  >>
->>
-stream
-/GS0 gs
-/Form0 Do
-endstream
-endobj
-14 0 obj <<
-  /Type /XObject
-  /Subtype /Form
-  /FormType 1
-  /Group <<
-    /S /Transparency
-  >>
-  /Length 59
-  /BBox [149 476 191 487]
->>
-stream
-0.0 1.0 0.0 rg
-149 476 m
-191 476 l
-191 487 l
-149 487 l
-h f
-endstream
-endobj
-15 0 obj <<
-  /Type /Annot
-  /Subtype /Link
-  /BS <<
-    /W 0
-  >>
-  /Rect [69 633 542 653]
-  /Dest [3 0 R /XYZ 200 725 0]
-  /F 4
->>
-endobj
-16 0 obj <<
-  /Type /Annot
-  /Subtype /Link
-  /BS <<
-    /W 0
-  >>
-  /Rect [80 613 542 633]
-  /Dest [4 0 R /XYZ 200 725 0]
-  /F 4
->>
-endobj
-17 0 obj <<
-  /Type /Annot
-  /Subtype /Link
-  /BS <<
-    /W 0
-  >>
-  /Rect [66 529 196 544]
-  /A <<
-    /Type /Action
-    /URI (https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf)
-    /S /URI
-  >>
-  /F 4
->>
-endobj
-18 0 obj <<
-  /Type /Annot
-  /Subtype /Link
-  /BS <<
-    /W 0
-  >>
-  /Rect [83 440 178 453]
-  /QuadPoints [83 453 178 453 83 440 178 440]
-  /A <<
-    /Type /Action
-    /URI (https://cs.chromium.org/chromium/src/third_party/pdfium/public/fpdf_text.h)
-    /S /URI
-  >>
-  /F 4
->>
-endobj
-19 0 obj <<
-  /Type /Annot
-  /Subtype /Highlight
-  /AP <<
-    /N 9 0 R
-  >>
-  /NM (Highlight-1)
-  /F 4
-  /QuadPoints [293 542 349 542 293 530 349 530]
-  /P 3 0 R
-  /C [1 0.90196 0]
-  /Rect [293 530 349 542]
->>
-endobj
-20 0 obj <<
-  /Type /Annot
-  /Subtype /Highlight
-  /AP <<
-    /N 11 0 R
-  >>
-  /NM (Highlight-2)
-  /F 4
-  /QuadPoints [83 453 178 453 83 440 178 440]
-  /P 3 0 R
-  /C [0.26667 0.78431 0.96078]
-  /Rect [83 440 178 453]
->>
-endobj
-21 0 obj <<
-  /Type /Annot
-  /Subtype /Popup
-  /Parent 22 0 R
-  /Rect [191 377 443 488]
->>
-endobj
-22 0 obj <<
-  /Type /Annot
-  /Subtype /Highlight
-  /Popup 21 0 R
-  /AP <<
-    /N 13 0 R
-  >>
-  /NM (Highlight-With-Popup-1)
-  /Contents (Text Note)
-  /QuadPoints [149 487 191 487 149 476 191 476]
-  /P 3 0 R
-  /C [0.14902 0.90196 0]
-  /Rect [149 476 191 487]
-  /F 4
->>
-endobj
-23 0 obj <<
-  /ca 1
-  /Type /ExtGState
-  /CA 1
-  /BM /Normal
->>
-endobj
-24 0 obj <<
-  /ca 1
-  /Type /ExtGState
-  /CA 1
-  /AIS false
-  /BM /Multiply
->>
-endobj
-xref
-0 25
-0000000000 65535 f 
-0000000015 00000 n 
-0000000068 00000 n 
-0000000338 00000 n 
-0000000475 00000 n 
-0000000570 00000 n 
-0000001108 00000 n 
-0000001345 00000 n 
-0000001423 00000 n 
-0000001499 00000 n 
-0000001749 00000 n 
-0000001972 00000 n 
-0000002222 00000 n 
-0000002442 00000 n 
-0000002693 00000 n 
-0000002916 00000 n 
-0000003056 00000 n 
-0000003196 00000 n 
-0000003443 00000 n 
-0000003727 00000 n 
-0000003944 00000 n 
-0000004171 00000 n 
-0000004269 00000 n 
-0000004544 00000 n 
-0000004615 00000 n 
-trailer <<
-  /Root 1 0 R
-  /Size 25
->>
-startxref
-4701
-%%EOF
diff --git a/testing/resources/listbox_form.in b/testing/resources/listbox_form.in
index 354d841..054f21b 100644
--- a/testing/resources/listbox_form.in
+++ b/testing/resources/listbox_form.in
@@ -3,7 +3,7 @@
   /Type /Catalog
   /Pages 2 0 R
   /AcroForm <<
-    /Fields [ 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R ]
+    /Fields [8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R]
     /DR 4 0 R
   >>
 >>
@@ -11,16 +11,16 @@
 {{object 2 0}} <<
   /Type /Pages
   /Count 1
-  /Kids [ 3 0 R ]
+  /Kids [3 0 R]
 >>
 endobj
 {{object 3 0}} <<
   /Type /Page
   /Parent 2 0 R
   /Resources 4 0 R
-  /MediaBox [ 0 0 300 600 ]
+  /MediaBox [0 0 300 600]
   /Contents 7 0 R
-  /Annots [ 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R ]
+  /Annots [8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R]
 >>
 endobj
 {{object 4 0}} <<
@@ -56,7 +56,7 @@
   /Ff 0
   /T (Listbox_SingleSelect)
   /DA (0 0 0 rg /F1 12 Tf)
-  /Rect [ 100 350 200 380 ]
+  /Rect [100 350 200 380]
   /Opt [[(foo) (Foo)] [(bar) (Bar)] [(qux) (Qux)]]
 >>
 endobj
@@ -67,7 +67,7 @@
   /Ff 2097152
   /T (Listbox_MultiSelect)
   /DA (0 0 0 rg /F1 12 Tf)
-  /Rect [ 100 400 200 430 ]
+  /Rect [100 400 200 430]
   /Opt [(Apple) (Banana) (Cherry) (Date) (Elderberry) (Fig) (Guava) (Honeydew)
         (Indian Fig) (Jackfruit) (Kiwi) (Lemon) (Mango) (Nectarine) (Orange)
         (Persimmon) (Quince) (Raspberry) (Strawberry) (Tamarind) (Ugli Fruit)
@@ -82,7 +82,7 @@
   /Ff 1
   /T (Listbox_ReadOnly)
   /DA (0 0 0 rg /F1 12 Tf)
-  /Rect [ 100 500 200 530 ]
+  /Rect [100 500 200 530]
   /Opt [(Dog) (Elephant) (Frog)]
 >>
 endobj
@@ -91,24 +91,49 @@
   /Subtype /Widget
   /FT /Ch
   /Ff 2097152
-  /T (Listbox_MultiSelectMultipleSelected)
+  /T (Listbox_MultiSelectMultipleIndices)
   /DA (0 0 0 rg /F1 12 Tf)
-  /Rect [ 100 200 200 230 ]
-  /Opt [(Alpha) (Beta) (Gamma) (Delta) (Epsilon)]
-  /V [(Epsilon) (Gamma)]
+  /Rect [100 250 200 280]
+  /Opt [(Albania) (Belgium) (Croatia) (Denmark) (Estonia)]
+  /I [1 3]
 >>
 endobj
 {{object 12 0}} <<
   /Type /Annot
   /Subtype /Widget
   /FT /Ch
+  /Ff 2097152
+  /T (Listbox_MultiSelectMultipleValues)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [100 200 200 230]
+  /Opt [(Alpha) (Beta) (Gamma) (Delta) (Epsilon)]
+  /V [(Epsilon) (Gamma)]
+>>
+endobj
+{{object 13 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /Ff 2097152
+  /T (Listbox_MultiSelectMultipleMismatch)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [100 150 200 180]
+  /Opt [(Alligator) (Bear) (Cougar) (Deer) (Echidna)]
+  /V [(Alligator) (Cougar)]
+  /I [1 3 4]
+>>
+endobj
+{{object 14 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
   /Ff 0
   /T (Listbox_SingleSelectLastSelected)
   /DA (0 0 0 rg /F1 12 Tf)
-  /Rect [ 100 100 200 130 ]
+  /Rect [100 100 200 130]
   /Opt [(Alberta) (British Columbia) (Manitoba) (New Brunswick)
         (Newfoundland and Labrador) (Nova Scotia) (Ontario)
-        (Prince Edward Island) (Quebec) (Saskatchewan) ]
+        (Prince Edward Island) (Quebec) (Saskatchewan)]
   /V (Saskatchewan)
   /TI 9
 >>
diff --git a/testing/resources/listbox_form.pdf b/testing/resources/listbox_form.pdf
index 9e1393e..10c5702 100644
--- a/testing/resources/listbox_form.pdf
+++ b/testing/resources/listbox_form.pdf
@@ -4,7 +4,7 @@
   /Type /Catalog
   /Pages 2 0 R
   /AcroForm <<
-    /Fields [ 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R ]
+    /Fields [8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R]
     /DR 4 0 R
   >>
 >>
@@ -12,16 +12,16 @@
 2 0 obj <<
   /Type /Pages
   /Count 1
-  /Kids [ 3 0 R ]
+  /Kids [3 0 R]
 >>
 endobj
 3 0 obj <<
   /Type /Page
   /Parent 2 0 R
   /Resources 4 0 R
-  /MediaBox [ 0 0 300 600 ]
+  /MediaBox [0 0 300 600]
   /Contents 7 0 R
-  /Annots [ 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R ]
+  /Annots [8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R]
 >>
 endobj
 4 0 obj <<
@@ -57,7 +57,7 @@
   /Ff 0
   /T (Listbox_SingleSelect)
   /DA (0 0 0 rg /F1 12 Tf)
-  /Rect [ 100 350 200 380 ]
+  /Rect [100 350 200 380]
   /Opt [[(foo) (Foo)] [(bar) (Bar)] [(qux) (Qux)]]
 >>
 endobj
@@ -68,7 +68,7 @@
   /Ff 2097152
   /T (Listbox_MultiSelect)
   /DA (0 0 0 rg /F1 12 Tf)
-  /Rect [ 100 400 200 430 ]
+  /Rect [100 400 200 430]
   /Opt [(Apple) (Banana) (Cherry) (Date) (Elderberry) (Fig) (Guava) (Honeydew)
         (Indian Fig) (Jackfruit) (Kiwi) (Lemon) (Mango) (Nectarine) (Orange)
         (Persimmon) (Quince) (Raspberry) (Strawberry) (Tamarind) (Ugli Fruit)
@@ -83,7 +83,7 @@
   /Ff 1
   /T (Listbox_ReadOnly)
   /DA (0 0 0 rg /F1 12 Tf)
-  /Rect [ 100 500 200 530 ]
+  /Rect [100 500 200 530]
   /Opt [(Dog) (Elephant) (Frog)]
 >>
 endobj
@@ -92,47 +92,74 @@
   /Subtype /Widget
   /FT /Ch
   /Ff 2097152
-  /T (Listbox_MultiSelectMultipleSelected)
+  /T (Listbox_MultiSelectMultipleIndices)
   /DA (0 0 0 rg /F1 12 Tf)
-  /Rect [ 100 200 200 230 ]
-  /Opt [(Alpha) (Beta) (Gamma) (Delta) (Epsilon)]
-  /V [(Epsilon) (Gamma)]
+  /Rect [100 250 200 280]
+  /Opt [(Albania) (Belgium) (Croatia) (Denmark) (Estonia)]
+  /I [1 3]
 >>
 endobj
 12 0 obj <<
   /Type /Annot
   /Subtype /Widget
   /FT /Ch
+  /Ff 2097152
+  /T (Listbox_MultiSelectMultipleValues)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [100 200 200 230]
+  /Opt [(Alpha) (Beta) (Gamma) (Delta) (Epsilon)]
+  /V [(Epsilon) (Gamma)]
+>>
+endobj
+13 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /Ff 2097152
+  /T (Listbox_MultiSelectMultipleMismatch)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [100 150 200 180]
+  /Opt [(Alligator) (Bear) (Cougar) (Deer) (Echidna)]
+  /V [(Alligator) (Cougar)]
+  /I [1 3 4]
+>>
+endobj
+14 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
   /Ff 0
   /T (Listbox_SingleSelectLastSelected)
   /DA (0 0 0 rg /F1 12 Tf)
-  /Rect [ 100 100 200 130 ]
+  /Rect [100 100 200 130]
   /Opt [(Alberta) (British Columbia) (Manitoba) (New Brunswick)
         (Newfoundland and Labrador) (Nova Scotia) (Ontario)
-        (Prince Edward Island) (Quebec) (Saskatchewan) ]
+        (Prince Edward Island) (Quebec) (Saskatchewan)]
   /V (Saskatchewan)
   /TI 9
 >>
 endobj
 xref
-0 13
+0 15
 0000000000 65535 f 
 0000000015 00000 n 
-0000000151 00000 n 
-0000000216 00000 n 
-0000000379 00000 n 
-0000000414 00000 n 
-0000000447 00000 n 
-0000000523 00000 n 
-0000000625 00000 n 
-0000000832 00000 n 
-0000001302 00000 n 
-0000001488 00000 n 
-0000001741 00000 n 
+0000000163 00000 n 
+0000000226 00000 n 
+0000000399 00000 n 
+0000000434 00000 n 
+0000000467 00000 n 
+0000000543 00000 n 
+0000000645 00000 n 
+0000000850 00000 n 
+0000001318 00000 n 
+0000001502 00000 n 
+0000001747 00000 n 
+0000001996 00000 n 
+0000002267 00000 n 
 trailer <<
   /Root 1 0 R
-  /Size 13
+  /Size 15
 >>
 startxref
-2119
+2642
 %%EOF
diff --git a/testing/resources/marked_content_id.in b/testing/resources/marked_content_id.in
new file mode 100644
index 0000000..ebc217f
--- /dev/null
+++ b/testing/resources/marked_content_id.in
@@ -0,0 +1,235 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /MarkInfo <<
+    /Marked true
+  >>
+  /Pages 2 0 R
+  /StructTreeRoot 11 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Parent 2 0 R
+  /Resources <<
+    /ProcSet [/PDF /Text /ImageC]
+    /ColorSpace <<
+      /CS0 /DeviceRGB
+    >>
+    /Font <<
+      /TT0 5 0 R
+    >>
+    /XObject <<
+      /Im0 9 0 R
+    >>
+  >>
+  /StructParents 0
+  /Tabs /S
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+/Artifact <<
+  /Type /Layout
+  /BBox [408 516 411 522 ]
+>>
+BDC
+q
+18 40 576 734 re
+W n
+BT
+/CS0 cs 0 0 0  scn
+/TT0 1 Tf
+3 Tr 12 0 0 12 408.3599 516 Tm
+(!)Tj
+ET
+EMC
+/Figure <<
+  /MCID 0
+>>
+BDC
+/ClipSpan
+BMC
+Q
+q
+/Clip
+BMC
+203.52 516 204.96 204 re
+W n
+EMC
+q
+/Perceptual ri
+204.96 0 0 204 203.52 516 cm
+/Im0 Do
+Q
+EMC
+EMC
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /TrueType
+  /BaseFont /KPRGOG+Calibri
+  /FirstChar 33
+  /FontDescriptor 6 0 R
+  /LastChar 33
+  /ToUnicode 8 0 R
+  /Widths [226]
+>>
+endobj
+{{object 6 0}} <<
+  /Type /FontDescriptor
+  /Ascent 952
+  /AvgWidth 521
+  /CapHeight 644
+  /Descent -269
+  /Flags 4
+  /FontBBox [-503 -307 1240 1026]
+  /FontFile2 7 0 R
+  /FontName /KPRGOG+Calibri
+  /ItalicAngle 0
+  /MaxWidth 1328
+  /StemV 0
+  /XHeight 476
+>>
+endobj
+{{object 7 0}} <<
+  /Filter [/ASCII85Decode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+GhOYs.SXL4?t`qUAme0T9e9ZP"3(Hd+dJWtR0Mc^!/VL0WJN&85/SHb2Tl(-5ik^jKM"^O8Hk>#!@0XF
+nrmMQ2*caF2mEF!5/SB`<ldqX9ndPSE^5#N*5h&A+<]SBUVVNeJe#T[$]OJp46d]&Jj98Q)'RR_N+Q$s
+&7@KA^s9^uV"GVZTScj%Z64ENVLB-%'.NJL2I@=I3P\P)?e:eKd2..*\c;[%\!usuLDc2s#rI26AhNtn
+^UG$s\/,#AokOTk)[8bg-SVgp!L942WeSUc0X(R4"+qe&KRog"#T4I])&W>f1LFVQ"3(C(K-c.'K-*pZ
+TV<PGMsrI+Z\u%:/*SdVWiMO*)]RHbR@:''/+G?F`D"M"m?ilmjHu<'?m9bf+?h%AV*@i$rdWUglBL\R
+N*58<pdKG\=IE\FKQM'1"bNLLD=hAlRG$8\=ZP+lbak!2F'OuJYm\^3MTH3FE!XRCM9612s1Q\"pZiJC
+hf!.nSDR84frS]M<L@E<EA4Z%Z0dX:OOUR:oF0ar(1?):((_2??l'I:d;Xs<gEnBP1.+N1'OoX#?V"9)
+62f*K!":%>J09&cGAZN]SBRe'9J-<tdVIk~>
+endstream
+endobj
+{{object 8 0}} <<
+  {{streamlen}}
+>>
+stream
+/CIDInit /ProcSet findresource begin
+12 dict begin
+begincmap
+/CIDSystemInfo <<
+  /Registry (Adobe)
+  /Ordering (UCS)
+  /Supplement 0
+>> def
+/CMapName /Adobe-Identity-UCS def
+/CMapType 2 def
+1 begincodespacerange
+<00><FF>
+endcodespacerange
+1 beginbfrange
+<21><21><0009>
+endbfrange
+endcmap
+CMapName currentdict /CMap defineresource pop
+end
+end
+endstream
+endobj
+{{object 9 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCII85Decode /FlateDecode]
+  /Height 272
+  /Intent /Perceptual
+  /Interpolate true
+  /SMask 10 0 R
+  /Width 273
+  {{streamlen}}
+>>
+stream
+GhVQ=BrkT/*<>Tnp)[K];NNO';(,Y,e3[reZBQQ?W!Kf<[A?;p.U_Il/M348;ME%+5X5%[`$K.l1b(tG
+TLMZ2+XWWp'13=j`\iPk)E1o?VW[ZsH+/!EYO=ZTq+Z;[m[Z0.gc9$0IqH7cL&Ks6zzzzzzzzzzzzzzz
+zzzzzzzzzzzi5kWPD5!Dac`&SeQ0\"gG?d2ed.;V%H`m^)\?kGADd=@:VD4jXEa<PJG,W`gbI)ZrQ!rE
+mq<cX8_p>e&W:iIMjU]Ac;PpMX%H8<GhV0ff3+WZ!HIIKD_2kcp(LEsJoBS&([m(jN>?9X4Isl\@B)VH
+ei?PN:-bUd:es1a46`.c4#oSK[[&N_Hpg#E(2et8dNjOo4Y\o6mC?8&0=r[0BSDQNBl[LHrrinESN2aM
+H]X[shFSd6Yp6u;/h'+G@dCrba%Fh+VIeW!SI"c<255j-(s+rVNHr1p8m8/;H/)Q#PhA/;iZ1`M;dHTj
+,*\s38\+d-:.HfJ"C2Z>LZMr;Er#sg8DXB?B*?@&/POJ=2Rp.acH1S'c:0h%Qf1/p(_WUYP..Hoq_lC7
+UqW'>J9UIja/=74H/IfJ0UNSK>[EA-iM;_7B;s#=jF65n=VC>ZS])C]u!<LrVP)Et-hQ^Ki?[<aU2WMi
+S[rGk5[#g-0I/C3k+d75h;$Ti8O+(Nt:dX;_GD?E)V!:\B9uBJN?e@>&4d!kgpFji7r5#KakOp4$nkmf
+'eOsCc@\^uX=/66#_jB5In+iQ57QP'hMiSe,i3Xk*8\MecZ[,s'^[&c+h!T9k85jhM&'r-%R25'SVHGB
+9k#XV_9R?#BDhfJLT,ZreD`jrr^qk`H-eIGW^[0@]]-7aBA\.>nnFsQ4`1G];\9@kt4\_Dgq4'8WPj-$
+u_lHNB^YCR9OEk;6NgF<)V&<.s$'T[U:/UA%GD$h=^Ge@VWtFutQ*iBll8J2VF878mS2e6qqssAI](5J
+ultb2`,%T8rR55Z(#hPT3c<3mQMD:rt@imcuhsQg'BB=mCc:obN[AW,VDr*/VCRGZ"VEEVs;/)_0AF;K
+CQS/T404*C@0Nr`kqmB+F&\k[a]3Mg4VAc.Jp05hk`Z?*&@jO2FR5eB8Qb7E`RXE\'mVP$=jZKk*a-=<
+o%<4Nj@9i1H+#gG_VS)(L\KPin1iabgE7Rl\`Hnt3e%u-@inXMl4,7JE5H'\>oG^]C/R82DqN.[/Z5TT
+Abj7/s5$+-\4KuJ!k$S"+oTc3@p]Cu:HCjQ\("LiV_\<pFcgMmJOGPL:Eg0]dUOb12i[T3@EV3=[Gl<O
+319HoCeUAPU>_cY5<G-Cl1?HM]9Fn;I+]GPt/j6b2q=os6KKn7dH[!G?F5"qpMQYU9=f^:m*H#&h#at!
+.Sc!,=c/W3>>8DB5aX)9EcKD*"9$<a19Z+H\fSQr'66$]N*AZ0%faVTGMT):eO*;([PPH$Lo>Ia.Dap7
+iQ7:[SR.Kt$nE;_eKDNmrSVYbTnei/am)MoEVY'#Z=EA*'B!![Bi\U1Xrq<r\/OJT(2s*J#Au5LrZ)c>
+-(V<:Ebf4dId6D-GdA-8uEa81%C#-u'fc&FYfU)VT2jqjEd^V#G65A,L)s5aq"(iVq5.b\DbPtR`F6:H
+J_p+*[1W>Oo_ofZZj>'MId`I8I*8epRQi,B$O&pXGCu"2s2f@43DHi6pg+r;^M=rN<4OWmC'u"Zq,9h=
+.;lAdFZM77U$fM9<Elh+ZD,n'-&",C^cCE>XU>iMX=?LaD1=,@qo8dg/Ses-NXqkT%9NRcJ^$.bA@T-;
+dBg6iBe%]J62X9a(XZ!F3XY7EgQG`YM1Hp%jI;53^`H0$jf:6@!PibJ?l[O&ZY(FDnq(4%U8'gO<>.9V
+)RHV,l_Y.'.$;Bk^_tl+Je#)m:?Iroo^Y)DNJ)(dpH!)%Bit]Q!Ramb%Qf#Dd5][2Kzzzzzzzzzzzzzz
+zzzzzzzzzz!!!#Ws20Ni!j00"QN~>
+endstream
+endobj
+{{object 10 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter [/ASCII85Decode /FlateDecode]
+  /Height 272
+  /Interpolate true
+  /Width 273
+  {{streamlen}}
+>>
+stream
+GhVQ$0b"*_!!8hNrdD20"'P[)zzzzzzzzzzzzzzzzz!8pQUgs,iQ~>
+endstream
+endobj
+{{object 11 0}} <<
+  /Type /StructTreeRoot
+  /K 12 0 R
+  /ParentTree <<
+    /Nums [0 13 0 R]
+  >>
+  /ParentTreeNextKey 1
+>>
+endobj
+{{object 12 0}} <<
+  /S /Figure
+  /P 11 0 R
+  /A 14 0 R
+  /Alt (Hello!\000)
+  /K 0
+  /Pg 3 0 R
+>>
+endobj
+{{object 13 0}}
+  [12 0 R]
+endobj
+{{object 14 0}} <<
+  /BBox [204 516 408 720]
+  /InlineAlign /Center
+  /O /Layout
+  /Placement /Block
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/marked_content_id.pdf b/testing/resources/marked_content_id.pdf
index ddbf11f..cf80171 100644
--- a/testing/resources/marked_content_id.pdf
+++ b/testing/resources/marked_content_id.pdf
Binary files differ
diff --git a/testing/resources/matte.in b/testing/resources/matte.in
new file mode 100644
index 0000000..57d8e13
--- /dev/null
+++ b/testing/resources/matte.in
@@ -0,0 +1,172 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 100 150]
+  /Contents [4 0 R]
+  /Resources <<
+    /XObject <<
+      /Mask 5 0 R
+      /NoMask 7 0 R
+      /MaskTooFewComps 9 0 R
+      /MaskTooManyComps 11 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+40 0 0 60 0 0 cm
+/Mask Do
+Q
+q
+40 0 0 60 50 0 cm
+/NoMask Do
+Q
+q
+40 0 0 60 0 90 cm
+/MaskTooFewComps Do
+Q
+q
+40 0 0 60 50 90 cm
+/MaskTooManyComps Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /SMask 6 0 R
+  {{streamlen}}
+>>
+stream
+789cedc2310d00000c03a07f2aaab3ea7bcf03842655555555555555f5bf01cc7818dc
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /Matte [0.0 0.2 1]
+  {{streamlen}}
+>>
+stream
+789cabaa1a05a3808e4061148c027a028351300ae809a246c128a027e0020065c9c90d
+endstream
+endobj
+{{object 7 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /SMask 8 0 R
+  {{streamlen}}
+>>
+stream
+789cedc2310d00000c03a07f2aaab3ea7bcf03842655555555555555f5bf01cc7818dc
+endstream
+endobj
+{{object 8 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+789cabaa1a05a3808e4061148c027a028351300ae809a246c128a027e0020065c9c90d
+endstream
+endobj
+{{object 9 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /SMask 10 0 R
+  {{streamlen}}
+>>
+stream
+789cedc2310d00000c03a07f2aaab3ea7bcf03842655555555555555f5bf01cc7818dc
+endstream
+endobj
+{{object 10 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /Matte [0.0 0.2]
+  {{streamlen}}
+>>
+stream
+789cabaa1a05a3808e4061148c027a028351300ae809a246c128a027e0020065c9c90d
+endstream
+endobj
+{{object 11 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /SMask 12 0 R
+  {{streamlen}}
+>>
+stream
+789cedc2310d00000c03a07f2aaab3ea7bcf03842655555555555555f5bf01cc7818dc
+endstream
+endobj
+{{object 12 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /Matte [0.0 0.2 1 0.5]
+  {{streamlen}}
+>>
+stream
+789cabaa1a05a3808e4061148c027a028351300ae809a246c128a027e0020065c9c90d
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/matte.pdf b/testing/resources/matte.pdf
new file mode 100644
index 0000000..05d1ecd
--- /dev/null
+++ b/testing/resources/matte.pdf
@@ -0,0 +1,191 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 100 150]
+  /Contents [4 0 R]
+  /Resources <<
+    /XObject <<
+      /Mask 5 0 R
+      /NoMask 7 0 R
+      /MaskTooFewComps 9 0 R
+      /MaskTooManyComps 11 0 R
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Length 149
+>>
+stream
+q
+40 0 0 60 0 0 cm
+/Mask Do
+Q
+q
+40 0 0 60 50 0 cm
+/NoMask Do
+Q
+q
+40 0 0 60 0 90 cm
+/MaskTooFewComps Do
+Q
+q
+40 0 0 60 50 90 cm
+/MaskTooManyComps Do
+Q
+endstream
+endobj
+5 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /SMask 6 0 R
+  /Length 71
+>>
+stream
+789cedc2310d00000c03a07f2aaab3ea7bcf03842655555555555555f5bf01cc7818dc
+endstream
+endobj
+6 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /Matte [0.0 0.2 1]
+  /Length 71
+>>
+stream
+789cabaa1a05a3808e4061148c027a028351300ae809a246c128a027e0020065c9c90d
+endstream
+endobj
+7 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /SMask 8 0 R
+  /Length 71
+>>
+stream
+789cedc2310d00000c03a07f2aaab3ea7bcf03842655555555555555f5bf01cc7818dc
+endstream
+endobj
+8 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /Length 71
+>>
+stream
+789cabaa1a05a3808e4061148c027a028351300ae809a246c128a027e0020065c9c90d
+endstream
+endobj
+9 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /SMask 10 0 R
+  /Length 71
+>>
+stream
+789cedc2310d00000c03a07f2aaab3ea7bcf03842655555555555555f5bf01cc7818dc
+endstream
+endobj
+10 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /Matte [0.0 0.2]
+  /Length 71
+>>
+stream
+789cabaa1a05a3808e4061148c027a028351300ae809a246c128a027e0020065c9c90d
+endstream
+endobj
+11 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /SMask 12 0 R
+  /Length 71
+>>
+stream
+789cedc2310d00000c03a07f2aaab3ea7bcf03842655555555555555f5bf01cc7818dc
+endstream
+endobj
+12 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /Matte [0.0 0.2 1 0.5]
+  /Length 71
+>>
+stream
+789cabaa1a05a3808e4061148c027a028351300ae809a246c128a027e0020065c9c90d
+endstream
+endobj
+xref
+0 13
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000370 00000 n 
+0000000571 00000 n 
+0000000856 00000 n 
+0000001148 00000 n 
+0000001433 00000 n 
+0000001704 00000 n 
+0000001990 00000 n 
+0000002281 00000 n 
+0000002568 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 13
+>>
+startxref
+2865
+%%EOF
diff --git a/testing/resources/mona_lisa.fragment b/testing/resources/mona_lisa.fragment
new file mode 100644
index 0000000..873677d
--- /dev/null
+++ b/testing/resources/mona_lisa.fragment
@@ -0,0 +1,109 @@
+/9j/4AAQSkZJRgABAQEAZABkAAD//gBSRmlsZSBzb3VyY2U6IGh0dHA6Ly9jb21tb25zLndpa2lt
+ZWRpYS5vcmcvd2lraS9GaWxlOk1vbmFfTGlzYV9mYWNlXzgwMHg4MDBweC5qcGf/2wBDAAYEBQYF
+BAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUo
+KSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgo
+KCgoKCgoKCgoKCgoKCj/wAARCAB4AHgDAREAAhEBAxEB/8QAHAAAAgIDAQEAAAAAAAAAAAAABQYE
+BwEDCAIA/8QAOxAAAgECBQIEBAQFAwMFAAAAAQIDBBEABRIhMQZBEyJRYQcycYEUkaGxFUJiwfAj
+JOEWF9ElM1LC8f/EABoBAAIDAQEAAAAAAAAAAAAAAAIDAQQFAAb/xAAvEQACAgICAgAEBQQCAwAA
+AAAAAQIRAyESMQRBBRMiUTJhcaGxFCNSgZHwweHx/9oADAMBAAIRAxEAPwBqiIChT5t7gEYwX1ss
+JUbY0ErKBHpFu498Q3fRKTMmJVNm2POo9/tiG2kTFG+FfIAq9t2ta2BtvsKtkqKmDE2UgMDiUn2C
+3ugTnvVGRdPy6M2zKFJxv+HivLN6i6LuPvbDsalPaRPy29Cw/wAYOn1kPgUmYS3OxZUjJvwbFrgY
+fHx8hziqCVF8V+namVUnSvojzqlp9SkD3QkYGWKa62SsY5ZVmVDmlMKnLamCqgvu8bBgPYjkH64U
+3x/FpkcWF4yT8oG4337YBzdUgVHezEwN1N1BxzurslJGI1uSpJG3bvjlew2bYUN+fS+Cj12DJkqE
+WNgOeL4OM66AaNscXkb2vziVK47ZD0IkcG1r/X/PthVWFZLRNKqAAdiCT9cC19jk77MRpqILKNjf
+i/tbCpcug40SooAz6tNyfLsOPbBQhslsrH4n/ECXLa+Tp7IZvBrxaOprEXU0Fx8if1+/a/ri3i8e
+1zn1/P8A6Ba6oR8h6YqY8szKshpauaWdNCzBHkZifmYk778ffAZvIU5Qg3pMsQg4xbS2KmcZQKPU
+aqmqItVhqeEqL9rnGhjzc3SaZXyY63QGDvSuJaScofVfMp57YsOPLUkLUnHcQplXUFfQZrFV5bK2
+XZkll8SEWRx6MnBB9D+mEywppqW0N+by/U6Y+EvXdP1rlrJKkVNnVMAtVTLsrDgSJ30nj2O3pjLz
+4XhlXr0d+JWh6eJQCy2b6DEOKAT+55hQrYsNrW98TH2S2ToAATYG5GwI4wUqSYPZuSEBNXAIBO2E
+8eO0TZ8UZVtf5hvbE7RHYkQRG6kix7f2wuMnexjr0SvDZiqpf6dsH30D12bIKe77gna7W7bYHbbC
+tIFdf9R0/R3S9VmcrIJFHh06t/PKR5Rbk+v0GLMISk1CK2wE03begP8ACHommhy6LM8zjhqM0qf9
+eeoljEjhm3sL7D7b++KmXK8+Tin9K0l+hcUflwTrbLXFHCABftYdvtthbjRHNgjOsoiqKaaJ9Tqy
+kFWAKn88Vsip6HwlfZzV8R+kKeikeSjS128wCD+2NT4f57yfTP0I8jCltFWyJJEXul0vYkLce4Jx
+tqSfRRoJ9K5/WdNdSUOb5a7iWmbUVPEkZI1o3qCt/wBMBmxLLjcP+P1IjOpWztvJa+lzTJ6PMqF9
+dJVxLLG47qwuPpbi2MqP0Opd9ByV9E2KNWF97AbNiWlu2crN8cIABIFxyL7YW2yTZGhlNr7eltsc
+nZD0ZkpyI++1+cMcVVg3sUUWMxRtHpbVc3GKr4voYrM063k32YEi9t9zhiTXRDJVHAzSB3uoK2F/
+bEwm4u7OaT0UL8cs9TqHPsqyZEH+nWxaWvuus7KB6keYn6Dti5gySm5Zn0kyflrGlBdtl7dNWp6F
+FY2awU39RjFxx1aNHJ9g6pdWa5LX3J9MS27aE0uyHXiSRCFN9rG3OEThyGxaQi9UdOpXwyCW1muD
+b/PbC4J4ZckNdTVM5u63y5cpzeemilCQDzebuff9bY9R4eV5cak+zMzxUJUhVmbUNQt6W9bC++Lp
+WaSOjvgT1JK/w+ho5mtHl9Y1OD6o1pB9hrP5Yy/Mhxm697Gp3Wy0aXNhNMsIYi29u1j2v37Yq7fZ
+LetB01arE5DKVQaRY8m+/wDbHUxTnvQRplvbcaTuL+/GIXdMY2bwbxFd2wXNtUD07KMyvqid3WGn
+LvqszF0PIB8o/LfEairZNT9D109mYzSmMiRaJUkMbjmxIDCx+hwacpNEKadk3Nq38HlE8oGpvw7s
+thvcKdsLlG6TCUt0jkbrCsaXramzCGSNr10MmkfNrUqtxfsQBtjS8eP9hwf2f7jJ25KS+6Lv/wC6
+VBDlQq0lSipEm/DhqpZSddr2IRG08H5iDtxjGXi5XJQjt1fa6L7yQrkx1n6p0dKSZwIwAE4vca+B
+Y973B++KXJyfFd9B/LS2xLTq7NKbIWzvOfHpaeRxGFWkknILfKCFsBe3rg/lKWR48cr/ADbpHcmo
+3JAOLrjqHPcwio8uyeaohcj/AHUaSJGBv5jrUabW4P64tT8XHjjynk39tX+wr5km6jErP4oUM8Od
++JUfM/zC99/rjT+GZYvHSRV8lNSViSmmF2XUrM1wQefe2NTsp7uix/hJWFKDNaUMViM0E5AYgsNJ
+Ui/2GKHmR/C/ZPWi5MkzgykxMYlABszCxuP32xVUFsGWR+w1Lm0cNJHGAJRIw3U302bfbne2Ipds
+SOXT9UKqEsHLJqBH0HbFKVqSTLqaadByJ/D1FgbW2vhuO07BbXRydQZocvqqclJCW31XA3IsSe1z
+fbFiWHndC1krQfyLrl6TI5KKFNFTGDaULfU1yGJ/NQCe2AyYJJ/T0Hjkktk2l6tmoTmVI0zSrBEq
+wySG5RmZthc8AXIxPy3KCZy1Oijxoi67yiKUf7eHNI1Z73DDxRck+v8A4xou/wCnlXbi/wCBqf1x
+17OwP4TBW05RJqiJTJ4jBQu7X3O4Nj7jHmFBTjyTNNzcHRo6ioUk6ckprBYGlBFxfYf/AIMdwaWi
+LTeyblGXRnLYYWqaiOwsdDDf87jELFyVSOlNx/Caa5abKKdjCZGL3LNI5YnC5pQdRQULkrZyz8Yc
+wNZ1G3hFrIeB2/y2PRfCYccNv2Z/lu5UhEcuXErWYHdibbE/2xq1ZRHz4VpNP1BVU1I0i+PRlyq2
+a5V1P/2/XFPyopQTfoJLk9Fy5TRVkMjvLTVMpA3ZrHew/I/bvijyW9kSg0uiWIK2PSYaGpYA7Cwv
+xyT3xzURLvsaukq2opJSKiCWJGJtZCdz9sJlFdpjMc2tMbJMxQqbyMCb8jAPI1sclZyPmMBZ2ZZW
+GkeJqLaix4A+mNODr0IaNbMYiDDJIAbgMBbUpAO4F9r3/K+J2yaT7AGeVdTYtHIG1Pu3qTcbn7n8
+sWMUVQSb5C3mFayrGYXa8bhyrdiDdbHvxh0YJ6GznpUdk9I9QJmuUUciOC00aEXO1iAf748jNPHc
+H6dGy6nUgN8QOuafpqloqGty+tkr5m3iiFw68albhhxxv64b4+CedaaSX3ETlHG7e7GLpPMzU9OU
+lZVRS0krhv8AQlPnUajpv72thTSg2rug/wASTFzrTNZTCywgkr9sLjHnJX6Dk6WjmLrCoaozeWR7
+sdR2btvwceq8VVjSRlZvxAaWcNEpK2f+axPGLCEMNdLVMkWYRmJn8UkqpHuLf87emByxuLvoh7Lb
+ynM8wphrSSrjB8xW1lJ2/wA7YoNKnQDeg7H1DXooAmrFckWUbn9rdsQ8a9CqDtB1XUJvUzh1202s
+Dv625tgJwXVEptK7CY6jlqoisTygggGy7geuFcFdkcm/pbOdpa7xpQzCXzeQG2s27WONCMKHHppn
+lHhqg1KbhhtfjkG37euJBe2Q81y92y8vM6HUy2AI8p3P9t//ADgoZVy0iYJ3QqThI4NBVZJmc3bk
+qB6fXFlNt2Ntca9l5fA7qWlzTp6PJ6ipNPmVIDGrgjV4d/Kwvza9vtjznxXBKGR5K+mX8ml4mVTg
+oe0WR1OlR4UUZrcwqHUXSdcvjk29yLAfljN0pW/5LXoh5DA1NMZszrKyrn07LKiokYHoFHJuNyTi
+ZSjJrVV+oKX52LvXGcwDxI42BYgjbfe22H+Pi5y2LyTpUc/Z9KZcwZ2Yl2a/t7DHqMKSjSMvK9gy
+VlvZCRtp+vqcNQtumFMhDfjIWifS9wb3taxwvJJKLOirLlpljjdYiulnF/KfNuNhx9+cZnPTdC3H
+VBCSjUxqZFOlPMCU77knb2xHNN7YFM30ckEQIAXTfY6fMLd8Tr2CyfJnsUKlIULNfsPl55PpxiJQ
+V2FG0ikqKoLlRpRlbSCjC4O/Fr8k+98aEo/caHaWYyOVMOXBRIWCSHYj6ffCZxXabIoL5hl0/wD0
+5WySJQNEkQmURhiWCkG1jyQL++EJrmtjIqip8x8JpnnkRXZyQNPC27n12tjSiq0TrtkGOtlocziq
+sullp54SGjkDWa+3+Wwc4KUeM9pkW4SuLOi+kfjrlz9PxwZ0r0+ZIgWTSl0f+ofX0x5/P8Kywf8A
+a3H+DRx+VCf4tMEZ/wDEuOoB/A6UjI80kjC/5emBx/DZ95Dp511ErvMuojVvppr1EzXJfgL32vzj
+Sx+Lw70V5ZL62LVXSy+O5ZjIxNy9tgMXLVUV3FoinQiG+7WNiB++D7AJuWzGJ1K3BUcn7EYiULVE
+xdOy6qLNoTl1LIWEr2XSVIsGNr77d7jb9cZrwtXE7l7DdVXxVETeV4ybKoQ727m3bfCfkxUrBcrV
+UQBXwq7GJZmXRqKActf0+ww2Ka0C4Jn1TnETVI0sCC1iBGPU/p+3ptjpJvs5QS9i1Q0IVUctAlmu
+PCjB/ckg4a5xYwPUpiiZ1srPqHnbw9TahyABzta+Ikv8URFm+oqA0Eo8VDGV0MqkEDm4P14++EO0
+9DE12UbnlGtPn1VTh2aNJLIVTc+VSARtv5gMa2KVwUgX2BZUu6nURcXJY73w7sGSoO9ARUcnWuUQ
+5giyUk1QIXRvl84Ki/3IxW8vl8mTi6dB4GlkXItXqb4WU8JY0EcSWO5AuAMZGH4nKL4z2Wsnjp7R
+AynonTIgnjUpcCygj8v3wefzk1pnQxv2TOrOlBT5S/gRqiqoJ2tbfck+mEeN5vLJ9TGSxaKlqKfw
+igdGUgHy25B4xvRmn0UZRNog8GjSWQeQFjbttYYlTTlR0o0rLL6OzCiqMshK1FPSLpWO7garj5u3
+YnnFDKppvt//AEFRTHVYacgQ0udQSMynWGS4tzzqxUtydyQTiq0YfpseHLWawaYC6u3kPoCvub4O
+OWvpbI4ezbTdJxC0r1XhIx1aW8oUn1v/AJvivPzI39DsdHx5PtUVPoneOVpayRKlKZfCaJiCdyTv
+ck2F/uMaXKnpavYiiMnW1Y9OwqxTtO6C0ynQoIuo1AA78/8AGHy8aL6YN0eM16rr5qeHxggpp0Cz
+aYwLMrndTyLGx9MQvHjvfR1+hPramevr6iaSZpJJZS2vgsext9hixGKjFJE22fZbltZmtctLQwy1
+FQ+yJGNRv+1vfHTyQhHnkdImMJSfFLZevw3+EUcEyV2czh6lfkVR5EbtYnk+/tjB8n4m8v0YVS/c
+u4vGUPql2XFJl5MAWYaibD5f8tjIk0XEjNNkSIxa2jfa/wCWBa+xNkHPMnSajdbgEIwIbv6XHpgV
+qXJBJXo5zqsnEWdGjIWS8oVSG+XU9h9bb3HbHpoZ+WHn+RQljqVEHqOkENFDSqiMq1UxW3JF7H7X
+Xj64d487lyv0hWRar8wJk0arVCJ2RUJJ1MdlbixPobYt5HcW0V0iysiyTMa6ro2pqOi0RvdHjk1K
++2wIBJIxk5pwjabewoxd2hpHRvU1TmFFXVGZapoTdjqKge3Nj2H0AxXl5eOMWlHsOOCTY7ZjUTUm
+VwrLL+JrVGyKCdbD1P8ALz3OMzDH63JaTLkp8Y17KG6k8OOKopKGIvII1GuxBtxb2G55/vj0HjqT
+lym/ZTlVUhPqqA/wWREVS8Uha+m+w2IB/LjGgprmhDTI2awuuVUxmLqI49Kqex1E2PvucdGVzdE1
+SsG0ccktSqxX1Dvbgep/PBt0rOirejpj4C9IJQ5TLWTxXnqW1K9iCYx8ot6Hn7jHmfifkPNlUU9L
++TVwY/lw/Nl009Ivg2CgWsRccHFSNpWiW7dGWijuBGLXbAPYa12bWj0wGx78WxMlUQU9i91AqpSz
+CUnwyp498VcjldD8Zz7EIaTOqd6dkll/EukjWuCQbkqSeNyfXtvj0D5TxvkvVlZ1FiP1DXvJmwic
+qRCWN/fUf73xqePjUcdr2UMsvqPa5LVVVG88dNK8RYBNKni3Jt62wXz4xbi2FxbV0Fum80qKevSn
+qZXpp1kChilib8bYR5GFSi5R2LafRfGW9RR1FHFBUxzyGMJTuIxpAfgAXtfb19748/ODT2WYz0e6
+l6GOnWVzXxr4nhp8urUb3BBa/wBzhP1ctfuE6rZVOe0aNCskauXSO7Kw0rIO2/pe36Y1sGRp02Vp
+xdWKeeS02V5PGs0aLUMRaPuSMaGLnlyWnoBpRVsQayplq5DJMQVDHSnZfYY0IxSWhTdlp/B3pMZt
+RzVksPiXa2+1gP2A2NxvjH+JeY8clCJf8TEmuTOm+k8pGU5VBT69ciLubW1H1AxhOTcm37LcpWHw
+BYDa/e+Du9AL7mDGRp3IOq9sQ4vom/Z9UJqW1rEbHvgZHJ7EvrVo48qqBJJa6FTcHk9vU4RdzikW
+I9MpSopZ6itCUdGoUEkuyhUDXW9zfbgbcXxtQmlG5S3/AOCrkT6iiZknw5paurmq5JRmFbK5Z1SI
+yxq1727Ltfe57YXk+JTSUIql+4C8ZW5NhvOsloaOkZMwzGKIR/8AtJNVrCL8GyxrueRtfAYcuSTk
+4x3+l/yFOMUtspvq5MtjrUejqF8YfM0c0rgenzqP0ON7xnkcWpr+DPy1eiT0v17XZUwhqJmeEEBJ
+lVQwP9RNwdu/IwryPAhkuUdMnHlcdMtTJ/iHPDCGzFEZJPKr+EmpbDctpA59TvjMn4bb+mi1GQKp
+0myd2rM8rqengC6pUldW2K7WUEkX+vNhbA5az/Rhi2/0ohfRuWim+r83Gd59PVxK0dKvkp47Wsg9
+vfnHofGwfIxqDdv2Uck+UrQMjjaQrCpbzDg84baQNM6J+Due02Vwfgc5XRS2DxS6SFF+dQ/Kx+2P
+M/EcDlk+bD/Zq+PlShxZfFPXUtTRGqpqiOSMjV4isCCPrx3xm8lX2D4uyTSFpRrIIU4OKs6TrROA
+TQe/IGHqOhLbs1SeGgYHn62H1wuSDTbK862rqGOOZDLrYA/JJpAJG4v22v72xWWJzmmh/wAxQVMq
+PMetMloJJHaMZhU2IjhtaFe3H83bnGvj+HZsi/xX7lV+RFO3sWM++JOdVkfgzVy5fSqDppqRLEDi
+5tsP0Pti/g+F4obrl+bEy8lvrQjVuc1Uxv8AiZyp/pA3+u+NKOCEVVFdzb7YPeeRyLs7G9zra+DS
+SAb9mI9QG1irHcDg46jhx6Unlqf/AEzYTIC8bjctYElSODtc7+n0xUz41H+5/wB/UtYJuX0P/v5A
+3rmeq/i38PqleMU41OjC12Nz+gNh9cT4UY8OcfYnNafF+hZbbzXv7W74t0IDmQQJJI81TfSGAuDx
+te+/+cYRlbWkGl9xsjzYitpIqYMksqDQNfyKCbk32vYHn/5DFT5Sabl6GqdPQcyjNJTI6ZfO1KsV
+tbU8ugsx33HDADfj88V8mBNXJXY6GV9Idss+IWf5epi/iaT6AAyVFOCRe1hdbeo5v2xVfiQ7imv9
+jVlT7C8HxZro95IKCcKbF4y4J/Q/TAf0kv8AIJzh3REz74pVE1NIbU9DDGt5JSS+/ZFIFy1+w3+g
+3wEfDcpV2BLyIpfSVD1D1f40FU5m8JpzaOJV8SZ151MSdMYNztuxxq4PD4ta6/4/19ytPNYjPXSy
+E+CCt99iSxPqTycaKikV07C2X5bClI1bVgtHTjdb7SPvZfzt+uFSnLkox7YVLshVcaRKseka7NyN
+gbL/AM4bFtnPQLWOQm6q5ABJ8p49T7e+D5AbZ6VvmNh2sL8Y6yVYRy+qemqaadLiSFw4sObdt8RJ
+cotP2HGTi7XZ/9k=
diff --git a/testing/resources/multiple_form_types.in b/testing/resources/multiple_form_types.in
new file mode 100644
index 0000000..ec6af63
--- /dev/null
+++ b/testing/resources/multiple_form_types.in
@@ -0,0 +1,131 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [8 0 R 9 0 R 10 0 R 11 0 R 12 0 R]
+    /DR 4 0 R
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources 4 0 R
+  /MediaBox [0 0 300 600]
+  /Contents 6 0 R
+  /Annots [7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 13 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Font <<
+    /F1 5 0 R
+  >>
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{object 6 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+0 0 0 rg
+/F1 12 Tf
+100 450 Td
+(Test Form) Tj
+ET
+endstream
+endobj
+{{object 7 0}} <<
+  /Type /Annot
+  /Subtype /Link
+  /BS <<
+    /W 0
+  >>
+  /Rect [100 450 160 460]
+  /A <<
+    /Type /Action
+    /URI (https://www.cs.chromium.org/)
+    /S /URI
+  >>
+  /F 4
+>>
+endobj
+{{object 8 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /Ff 393216
+  /T (Combo_Editable)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [100 400 200 430]
+  /Opt [[(foo) (Foo)] [(bar) (Bar)] [(qux) (Qux)]]
+>>
+endobj
+{{object 9 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /Ff 2097152
+  /T (Listbox_MultiSelectMultipleSelected)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [100 350 200 380]
+  /Opt [(Alpha) (Beta) (Gamma) (Delta) (Epsilon)]
+  /V [(Epsilon) (Gamma)]
+>>
+endobj
+{{object 10 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /T (Text Box)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [100 300 200 330]
+>>
+endobj
+{{object 11 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /F 4
+  /P 3 0 R
+  /Rect [135 250 155 270]
+  /T (Checkbox)
+>>
+endobj
+{{object 12 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /Ff 32768
+  /T (radioButton)
+  /TU (radioButton1)
+  /Kids [13 0 R]
+  /V /value3
+>>
+endobj
+{{object 13 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /F 4
+  /P 3 0 R
+  /Parent 12 0 R
+  /Rect [85 50 105 70]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/multiple_form_types.pdf b/testing/resources/multiple_form_types.pdf
new file mode 100644
index 0000000..da83c9e
--- /dev/null
+++ b/testing/resources/multiple_form_types.pdf
@@ -0,0 +1,151 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [8 0 R 9 0 R 10 0 R 11 0 R 12 0 R]
+    /DR 4 0 R
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources 4 0 R
+  /MediaBox [0 0 300 600]
+  /Contents 6 0 R
+  /Annots [7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 13 0 R]
+>>
+endobj
+4 0 obj <<
+  /Font <<
+    /F1 5 0 R
+  >>
+>>
+endobj
+5 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+6 0 obj <<
+  /Length 51
+>>
+stream
+BT
+0 0 0 rg
+/F1 12 Tf
+100 450 Td
+(Test Form) Tj
+ET
+endstream
+endobj
+7 0 obj <<
+  /Type /Annot
+  /Subtype /Link
+  /BS <<
+    /W 0
+  >>
+  /Rect [100 450 160 460]
+  /A <<
+    /Type /Action
+    /URI (https://www.cs.chromium.org/)
+    /S /URI
+  >>
+  /F 4
+>>
+endobj
+8 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /Ff 393216
+  /T (Combo_Editable)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [100 400 200 430]
+  /Opt [[(foo) (Foo)] [(bar) (Bar)] [(qux) (Qux)]]
+>>
+endobj
+9 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /Ff 2097152
+  /T (Listbox_MultiSelectMultipleSelected)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [100 350 200 380]
+  /Opt [(Alpha) (Beta) (Gamma) (Delta) (Epsilon)]
+  /V [(Epsilon) (Gamma)]
+>>
+endobj
+10 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /T (Text Box)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [100 300 200 330]
+>>
+endobj
+11 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /F 4
+  /P 3 0 R
+  /Rect [135 250 155 270]
+  /T (Checkbox)
+>>
+endobj
+12 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /Ff 32768
+  /T (radioButton)
+  /TU (radioButton1)
+  /Kids [13 0 R]
+  /V /value3
+>>
+endobj
+13 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /F 4
+  /P 3 0 R
+  /Parent 12 0 R
+  /Rect [85 50 105 70]
+>>
+endobj
+xref
+0 14
+0000000000 65535 f 
+0000000015 00000 n 
+0000000149 00000 n 
+0000000212 00000 n 
+0000000377 00000 n 
+0000000428 00000 n 
+0000000504 00000 n 
+0000000606 00000 n 
+0000000798 00000 n 
+0000001002 00000 n 
+0000001252 00000 n 
+0000001387 00000 n 
+0000001514 00000 n 
+0000001663 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 14
+>>
+startxref
+1788
+%%EOF
diff --git a/testing/resources/named_dests.in b/testing/resources/named_dests.in
index a97aebe..c71f2cc 100644
--- a/testing/resources/named_dests.in
+++ b/testing/resources/named_dests.in
@@ -64,8 +64,8 @@
 >>
 endobj
 % Old-style top-level Dests dictionary. Note that FirstAlternate
-% intentionally references non-exisstant page 11 and LastAlternate
-% intentionally references non-existant object 999.
+% intentionally references non-existent page 11 and LastAlternate
+% intentionally references non-existent object 999.
 {{object 14 0}} <<
   /FirstAlternate [11 /XYZ 200 400 800]
   /LastAlternate  <</D [999 0 R /XYZ 0 0 -200]>>
diff --git a/testing/resources/named_dests.pdf b/testing/resources/named_dests.pdf
index b99cead..ddcc985 100644
--- a/testing/resources/named_dests.pdf
+++ b/testing/resources/named_dests.pdf
@@ -65,8 +65,8 @@
 >>
 endobj
 % Old-style top-level Dests dictionary. Note that FirstAlternate
-% intentionally references non-exisstant page 11 and LastAlternate
-% intentionally references non-existant object 999.
+% intentionally references non-existent page 11 and LastAlternate
+% intentionally references non-existent object 999.
 14 0 obj <<
   /FirstAlternate [11 /XYZ 200 400 800]
   /LastAlternate  <</D [999 0 R /XYZ 0 0 -200]>>
@@ -117,16 +117,19 @@
 0000000638 00000 n 
 0000000766 00000 n 
 0000000000 65535 f 
-0000001060 00000 n 
-0000001188 00000 n 
+0000001059 00000 n 
+0000001187 00000 n 
 0000000000 65535 f 
 0000000000 65535 f 
 0000000000 65535 f 
 0000000000 65535 f 
 0000000000 65535 f 
-0000001283 00000 n 
-0000001393 00000 n 
-trailer<< /Root 1 0 R /Size 23 >>
+0000001282 00000 n 
+0000001392 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 23
+>>
 startxref
-1481
+1480
 %%EOF
diff --git a/testing/resources/named_dests_old_style.in b/testing/resources/named_dests_old_style.in
new file mode 100644
index 0000000..70e45ed
--- /dev/null
+++ b/testing/resources/named_dests_old_style.in
@@ -0,0 +1,55 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /Dests 6 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 2
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+  /Contents [4 0 R]
+  /MediaBox [0 0 612 792]
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+/F1 20 Tf
+100 600 TD (Page1)Tj
+ET
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Arial
+>>
+endobj
+% Old-style top-level Dests dictionary. Note that FirstAlternate
+% intentionally references non-existent page 11 and LastAlternate
+% intentionally references non-existent object 999.
+{{object 6 0}} <<
+  /FirstAlternate [11 /XYZ 200 400 800]
+  /LastAlternate  <<
+    /D [999 0 R /XYZ 0 0 -200]
+  >>
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/named_dests_old_style.pdf b/testing/resources/named_dests_old_style.pdf
new file mode 100644
index 0000000..df7f04a
--- /dev/null
+++ b/testing/resources/named_dests_old_style.pdf
@@ -0,0 +1,68 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /Dests 6 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 2
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+  /Contents [4 0 R]
+  /MediaBox [0 0 612 792]
+>>
+endobj
+4 0 obj <<
+  /Length 37
+>>
+stream
+BT
+/F1 20 Tf
+100 600 TD (Page1)Tj
+ET
+endstream
+endobj
+5 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Arial
+>>
+endobj
+% Old-style top-level Dests dictionary. Note that FirstAlternate
+% intentionally references non-existent page 11 and LastAlternate
+% intentionally references non-existent object 999.
+6 0 obj <<
+  /FirstAlternate [11 /XYZ 200 400 800]
+  /LastAlternate  <<
+    /D [999 0 R /XYZ 0 0 -200]
+  >>
+>>
+endobj
+xref
+0 7
+0000000000 65535 f 
+0000000015 00000 n 
+0000000083 00000 n 
+0000000146 00000 n 
+0000000300 00000 n 
+0000000388 00000 n 
+0000000643 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 7
+>>
+startxref
+761
+%%EOF
diff --git a/testing/resources/no_page_count.in b/testing/resources/no_page_count.in
new file mode 100644
index 0000000..e6b1a2d
--- /dev/null
+++ b/testing/resources/no_page_count.in
@@ -0,0 +1,50 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 200 200]
+  /Kids [3 0 R 3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Pages
+  /Kids [4 0 R 4 0 R 4 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+  /Contents 6 0 R
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+
+{{object 6 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+20 50 Td
+/F1 12 Tf
+(Hello, world!) Tj
+ET
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/no_page_count.pdf b/testing/resources/no_page_count.pdf
new file mode 100644
index 0000000..885df8c
--- /dev/null
+++ b/testing/resources/no_page_count.pdf
@@ -0,0 +1,63 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [0 0 200 200]
+  /Kids [3 0 R 3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Pages
+  /Kids [4 0 R 4 0 R 4 0 R]
+>>
+endobj
+4 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+  /Contents 6 0 R
+>>
+endobj
+5 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+
+6 0 obj <<
+  /Length 44
+>>
+stream
+BT
+20 50 Td
+/F1 12 Tf
+(Hello, world!) Tj
+ET
+endstream
+endobj
+xref
+0 7
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000152 00000 n 
+0000000216 00000 n 
+0000000342 00000 n 
+0000000421 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 7
+>>
+startxref
+516
+%%EOF
diff --git a/testing/resources/non_hex_file_id.in b/testing/resources/non_hex_file_id.in
new file mode 100644
index 0000000..7a93439
--- /dev/null
+++ b/testing/resources/non_hex_file_id.in
@@ -0,0 +1,26 @@
+{{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
+>>
+endobj
+{{xref}}
+trailer <<
+  /Root 1 0 R
+  /ID [(permanent non-hex) (changing non-hex)]
+  {{trailersize}}
+>>
+{{startxref}}
+%%EOF
diff --git a/testing/resources/non_hex_file_id.pdf b/testing/resources/non_hex_file_id.pdf
new file mode 100644
index 0000000..6f11eb6
--- /dev/null
+++ b/testing/resources/non_hex_file_id.pdf
@@ -0,0 +1,33 @@
+%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
+>>
+endobj
+xref
+0 4
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000161 00000 n 
+trailer <<
+  /Root 1 0 R
+  /ID [(permanent non-hex) (changing non-hex)]
+  /Size 4
+>>
+startxref
+212
+%%EOF
diff --git a/testing/resources/page_tree_empty_node.in b/testing/resources/page_tree_empty_node.in
new file mode 100644
index 0000000..4a9b901
--- /dev/null
+++ b/testing/resources/page_tree_empty_node.in
@@ -0,0 +1,40 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 200 200]
+  /Kids [3 0 R 4 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Pages
+  /Count 0
+  /Kids []
+  /Parent 2 0 R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Pages
+  /Count 2
+  /Kids [5 0 R 6 0 R]
+  /Parent 2 0 R
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Page
+  /Parent 4 0 R
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Page
+  /Parent 4 0 R
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/page_tree_empty_node.pdf b/testing/resources/page_tree_empty_node.pdf
new file mode 100644
index 0000000..bf80a1a
--- /dev/null
+++ b/testing/resources/page_tree_empty_node.pdf
@@ -0,0 +1,53 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [0 0 200 200]
+  /Kids [3 0 R 4 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Pages
+  /Count 0
+  /Kids []
+  /Parent 2 0 R
+>>
+endobj
+4 0 obj <<
+  /Type /Pages
+  /Count 2
+  /Kids [5 0 R 6 0 R]
+  /Parent 2 0 R
+>>
+endobj
+5 0 obj <<
+  /Type /Page
+  /Parent 4 0 R
+>>
+endobj
+6 0 obj <<
+  /Type /Page
+  /Parent 4 0 R
+>>
+endobj
+xref
+0 7
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000152 00000 n 
+0000000226 00000 n 
+0000000311 00000 n 
+0000000362 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 7
+>>
+startxref
+413
+%%EOF
diff --git a/testing/resources/pixel/axial_shading_point_at_border_no_extend_expected_skia.pdf.0.png b/testing/resources/pixel/axial_shading_point_at_border_no_extend_expected_skia.pdf.0.png
new file mode 100644
index 0000000..6c4c9b7
--- /dev/null
+++ b/testing/resources/pixel/axial_shading_point_at_border_no_extend_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1012369.in b/testing/resources/pixel/bug_1012369.in
index 7f56d35..db1fc5a 100644
--- a/testing/resources/pixel/bug_1012369.in
+++ b/testing/resources/pixel/bug_1012369.in
@@ -46,7 +46,7 @@
   /Filter [/ASCIIHexDecode /JPXDecode]
   /Height 64
   /Width 64
-{{streamlen}}
+  {{streamlen}}
 >>
 stream
 0000000c6a5020200d0a870a00000014667479706a703220000000006a7032200000004f6a7032
@@ -67,7 +67,7 @@
   /Filter [/ASCIIHexDecode /JPXDecode]
   /Height 64
   /Width 64
-{{streamlen}}
+  {{streamlen}}
 >>
 stream
 0000000c6a5020200d0a870a00000014667479706a703220000000006a7032200000004f6a7032
diff --git a/testing/resources/pixel/bug_1015233.in b/testing/resources/pixel/bug_1015233.in
new file mode 100644
index 0000000..aba521f
--- /dev/null
+++ b/testing/resources/pixel/bug_1015233.in
@@ -0,0 +1,113 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 600 200]
+  /Resources <<
+    /ProcSet [/PDF /ImageC]
+    /ColorSpace <<
+      /C1 [/Pattern]
+    >>
+    /Pattern <<
+      /P1 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+0.24 0 0 0.24 0 0 cm
+/C1 cs
+/P1 scn
+888 400 108 60 re
+f*
+0 0 0 rg
+888 460 m
+888 400 l
+S
+/C1 cs
+/P1 scn
+1536 400 108 60 re
+f*
+0 0 0 rg
+1536 460 m
+1536 400 l
+S
+/C1 cs
+/P1 scn
+2184 400 108 60 re
+f*
+0 0 0 rg
+2184 460 m
+2184 400 l
+S
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Pattern
+  /PaintType 1
+  /PatternType 1
+  /TilingType 1
+  /BBox [0 0 128 32]
+  /Matrix [0.202488 0 0 -0.449947 0 791.999]
+  /Resources <<
+    /ProcSet [/PDF /ImageC]
+    /XObject <<
+      /R17 6 0 R
+    >>
+  >>
+  /XStep 128
+  /YStep 32
+  {{streamlen}}
+>>
+stream
+0.24 0 0 0.24 0 0 cm
+q
+0 134 534 -134 re
+W n
+q 533.333 0 0 -133.333 0 133.333 cm
+/R17 Do
+Q
+Q
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /DecodeParms <<
+    /Columns 128
+    /Colors 3
+    /Predictor 15
+  >>
+  /Filter [/ASCII85Decode /FlateDecode]
+  /Height 32
+  /Width 128
+  {{streamlen}}
+>>
+stream
+Gb"0JYmJ3"&-Ti)eYrN1j:2;&T!_s\O.TqV*k'Yd:%,W`r[n(QqR?*8l/0q(V12/<QMrK7>Pbi$Ds?0-
+^SQEPrg*O8rEK--oPa!Od_DW.CH2@W5h#c57fSeR/cAoM\+FV'hpVPkGl`cd*^p
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1015233_expected.pdf.0.png b/testing/resources/pixel/bug_1015233_expected.pdf.0.png
new file mode 100644
index 0000000..aa39e7e
--- /dev/null
+++ b/testing/resources/pixel/bug_1015233_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1015233_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1015233_expected_skia.pdf.0.png
new file mode 100644
index 0000000..1a2a736
--- /dev/null
+++ b/testing/resources/pixel/bug_1015233_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1021762_expected.pdf.0.png b/testing/resources/pixel/bug_1021762_expected.pdf.0.png
index 39d531a..6c436c7 100644
--- a/testing/resources/pixel/bug_1021762_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_1021762_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1072440.in b/testing/resources/pixel/bug_1072440.in
new file mode 100644
index 0000000..1ef4fd6
--- /dev/null
+++ b/testing/resources/pixel/bug_1072440.in
@@ -0,0 +1,39 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [4 0 R]
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [4 0 R]
+  /MediaBox [0 0 200 200]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /F 4
+  /Ff 8388608
+  /MaxLen 2
+  /P 3 0 R
+  /Rect [70 90 130 110]
+  /T (zip code)
+  /V (11111)
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1072440_expected.pdf.0.png b/testing/resources/pixel/bug_1072440_expected.pdf.0.png
new file mode 100644
index 0000000..ef8b63d
--- /dev/null
+++ b/testing/resources/pixel/bug_1072440_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1072440_expected_mac.pdf.0.png b/testing/resources/pixel/bug_1072440_expected_mac.pdf.0.png
new file mode 100644
index 0000000..28d1ef0
--- /dev/null
+++ b/testing/resources/pixel/bug_1072440_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1072440_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1072440_expected_skia.pdf.0.png
new file mode 100644
index 0000000..6ce4fbe
--- /dev/null
+++ b/testing/resources/pixel/bug_1072440_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1099446.in b/testing/resources/pixel/bug_1099446.in
new file mode 100644
index 0000000..49d6dae
--- /dev/null
+++ b/testing/resources/pixel/bug_1099446.in
@@ -0,0 +1,65 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Contents 4 0 R
+  /MediaBox [0 0 200 200]
+  /Resources <<
+    /ExtGState <<
+      /G1 5 0 R
+    >>
+    /Pattern <<
+      /P1 6 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+200 0 m
+0 200 l
+/G1 gs
+/P1 SCN
+/P1 S
+endstream
+endobj
+{{object 5 0}} <<
+  /BM /Diff
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Pattern
+  /PatternType 1
+  /Resources <<
+    /ExtGState <<
+      /G1 5 0 R
+    >>
+    /Pattern <<
+      /P1 6 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+200 0 m
+0 200 l
+/G3 gs
+/P1 S
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1099446_expected.pdf.0.png b/testing/resources/pixel/bug_1099446_expected.pdf.0.png
new file mode 100644
index 0000000..1074bd8
--- /dev/null
+++ b/testing/resources/pixel/bug_1099446_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1116869.in b/testing/resources/pixel/bug_1116869.in
new file mode 100644
index 0000000..d123e38
--- /dev/null
+++ b/testing/resources/pixel/bug_1116869.in
@@ -0,0 +1,117 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 5 0 R
+  /MediaBox [0 0 400 400]
+  /Resources <<
+    /Font <<
+      /F1 4 0 R
+    >>
+    /Pattern <<
+      /P1 6 0 R
+    >>
+    /XObject <<
+      /X10 9 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+/X10 Do
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /Pattern
+  /PatternType 2
+  /Shading 7 0 R
+>>
+endobj
+{{object 7 0}} <<
+  /ShadingType 2
+  /ColorSpace /DeviceCMYK
+  /Coords [0.0 0.0 1.0 0.0]
+  /Extend [true true]
+  /Function 8 0 R
+>>
+endobj
+{{object 8 0}} <<
+  /FunctionType 0
+  /BitsPerSample 1
+  /Domain [0.0 1.0]
+  /Filter /ASCIIHexDecode
+  /Range [0.0 1.0 0.0 1.0 0.0 1.0 0.0 1.0]
+  /Size [2]
+  {{streamlen}}
+>>
+stream
+ff
+endstream
+endobj
+{{object 9 0}} <<
+  /Subtype /Form
+  /Resources <<
+    /ExtGState <<
+      /G7 10 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+q
+0 0 m
+3 31 l
+/G7 gs
+f
+endstream
+endobj
+{{object 10 0}} <<
+  /SMas <<
+    /G 11 0 R
+  >>
+>>
+endobj
+{{object 11 0}} <<
+  {{streamlen}}
+>>
+stream
+1 1 2 2 re
+W
+100 100 200 200 re s
+W
+0 g
+/F1 12 Tf
+0 0 60 30 re
+W
+m 100 100
+130 150 200 200 250 230 c
+/Pattern cs
+/P1 scn
+(ABC) Tj
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1116869_expected.pdf.0.png b/testing/resources/pixel/bug_1116869_expected.pdf.0.png
new file mode 100644
index 0000000..1ff3f1f
--- /dev/null
+++ b/testing/resources/pixel/bug_1116869_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1128284.in b/testing/resources/pixel/bug_1128284.in
new file mode 100644
index 0000000..0dfda97
--- /dev/null
+++ b/testing/resources/pixel/bug_1128284.in
@@ -0,0 +1,94 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+  /MediaBox [0 0 200 200]
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+0 g
+BT
+/F1 .1 Tf 1 0 0 1 100 100 Tm
+[(a)16000(b)17000(c)]TJ
+ET
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type3
+  /CharProcs <<
+    /a97 6 0 R
+    /a98 7 0 R
+    /a99 6 0 R
+  >>
+  /Encoding 8 0 R
+  /FirstChar 97
+  /FontBBox [0 0 100 100]
+  /LastChar 99
+  /Widths [-100 -50 -75]
+>>
+endobj
+{{object 6 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+6 0 0 48 6 0 cm
+BI
+/W 6
+/H 48
+/BPC 1
+/IM true
+ID
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+EI
+Q
+endstream
+endobj
+{{object 7 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+6 0 0 48 6 0 cm
+BI
+/W 6
+/H 48
+/BPC 1
+/IM true
+ID
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+EI
+Q
+endstream
+endobj
+{{object 8 0}} <<
+  /Type /Encoding
+  /BaseEncoding /WinAnsiEncoding
+  /Differences [97 /a97 /a98 /a99]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1128284_expected.pdf.0.png b/testing/resources/pixel/bug_1128284_expected.pdf.0.png
new file mode 100644
index 0000000..6a0df39
--- /dev/null
+++ b/testing/resources/pixel/bug_1128284_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_113910_expected.pdf.0.png b/testing/resources/pixel/bug_113910_expected.pdf.0.png
index 57c6f08..250ea73 100644
--- a/testing/resources/pixel/bug_113910_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_113910_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_113910_expected_mac.pdf.0.png b/testing/resources/pixel/bug_113910_expected_mac.pdf.0.png
index 5b21f5a..f6d8fca 100644
--- a/testing/resources/pixel/bug_113910_expected_mac.pdf.0.png
+++ b/testing/resources/pixel/bug_113910_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_113910_expected_skia.pdf.0.png b/testing/resources/pixel/bug_113910_expected_skia.pdf.0.png
new file mode 100644
index 0000000..3c3cd81
--- /dev/null
+++ b/testing/resources/pixel/bug_113910_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_113910_expected_win.pdf.0.png b/testing/resources/pixel/bug_113910_expected_win.pdf.0.png
deleted file mode 100644
index 1ff9194..0000000
--- a/testing/resources/pixel/bug_113910_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_1161.in b/testing/resources/pixel/bug_1161.in
index 1f02ab5..f3317e1 100644
--- a/testing/resources/pixel/bug_1161.in
+++ b/testing/resources/pixel/bug_1161.in
@@ -21,7 +21,7 @@
 >>
 endobj
 {{object 5 0}} <<
-{{streamlen}}
+  {{streamlen}}
 >>
 stream
 q 1 0 0 1 50 50 cm /X0 Do Q
@@ -48,20 +48,20 @@
 endstream
 endobj
 {{object 7 0}} <<
-/Type /ExtGState
-/ca 0.2
-/CA 1
+  /Type /ExtGState
+  /ca 0.2
+  /CA 1
 >>
 endobj
 {{object 8 0}} <<
-/Type /Pattern
-/TilingType 1
-/PatternType 1
-/BBox [0 0 18 18]
-/PaintType 1
-/YStep 18
-/XStep 18
-{{streamlen}}
+  /Type /Pattern
+  /TilingType 1
+  /PatternType 1
+  /BBox [0 0 18 18]
+  /PaintType 1
+  /YStep 18
+  /XStep 18
+  {{streamlen}}
 >>
 stream
 0 0 0 rg
diff --git a/testing/resources/pixel/bug_1161_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1161_expected_skia.pdf.0.png
new file mode 100644
index 0000000..f6a4fee
--- /dev/null
+++ b/testing/resources/pixel/bug_1161_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1236805.in b/testing/resources/pixel/bug_1236805.in
new file mode 100644
index 0000000..564f048
--- /dev/null
+++ b/testing/resources/pixel/bug_1236805.in
@@ -0,0 +1,86 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Contents 6 0 R
+  /CropBox [0 0 612 792]
+  /MediaBox [0 0 612 792]
+  /Parent 2 0 R
+  /Resources 7 0 R
+>>
+endobj
+{{object 6 0}} <<
+  {{streamlen}}
+ >>
+stream
+BT
+/T4 1 Tf
+0.12 0 0 -0.12 99.84 684.1801 Tm
+0 g
+/GS1 gs
+0 Tc
+0 Tw
+()Tj
+ET
+endstream
+endobj
+{{object 7 0}} <<
+  /Font <<
+    /T4 11 0 R
+  >>
+  /ProcSet [/PDF /Text]
+>>
+endobj
+{{object 11 0}} <<
+  /Type /Font
+  /Subtype /Type3
+  /CharProcs 12 0 R
+  /Encoding 13 0 R
+  /FirstChar 0
+  /FontBBox [4 -19 53 56]
+  /FontMatrix [1 0.0112920878908041593329204334 0 -1 0 0]
+  /LastChar 4
+  /Name /T4
+  /Widths [60 38 38 38 21]
+>>
+endobj
+{{object 12 0}} <<
+   /CV 17 0 R
+>>
+endobj
+{{object 13 0}} <<
+   /Type /Encoding
+   /Differences [0 /A0 /CU /CV /D2 /CY]
+ >>
+endobj
+{{object 17 0}} <<
+  {{streamlen}}
+>>
+stream
+38 0 5 -19 32 56 d1
+q
+27 0 0 75 5.1 -19.1 cm
+BI
+/W 27
+/H 75
+/BPC 1
+/IM true
+ID
+EI
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1236805_expected.pdf.0.png b/testing/resources/pixel/bug_1236805_expected.pdf.0.png
new file mode 100644
index 0000000..7d4eebe
--- /dev/null
+++ b/testing/resources/pixel/bug_1236805_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1236_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1236_expected_skia.pdf.0.png
new file mode 100644
index 0000000..248125f
--- /dev/null
+++ b/testing/resources/pixel/bug_1236_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1237898.in b/testing/resources/pixel/bug_1237898.in
new file mode 100644
index 0000000..c45e4c6
--- /dev/null
+++ b/testing/resources/pixel/bug_1237898.in
@@ -0,0 +1,60 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [4 0 R]
+  /MediaBox [0 0 100 100]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /Ff 65536
+  /P 3 0 R
+  /BS <<
+    /S /S
+    /W 1
+  >>
+  /MK <<
+    /I 5 0 R
+  >>
+  /N 6 0 R
+  /Rect [1 2 3 4]
+  /T (foo)
+  /TU (bar)
+>>
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Name /F#e9M
+  /FormType 1
+  {{streamlen}}
+>>
+stream
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  {{streamlen}}
+>>
+stream
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1237898_expected.pdf.0.png b/testing/resources/pixel/bug_1237898_expected.pdf.0.png
new file mode 100644
index 0000000..319932a
--- /dev/null
+++ b/testing/resources/pixel/bug_1237898_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1241587.in b/testing/resources/pixel/bug_1241587.in
new file mode 100644
index 0000000..61b1637
--- /dev/null
+++ b/testing/resources/pixel/bug_1241587.in
@@ -0,0 +1,78 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 100 100]
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+/F1 1 Tf
+(s) Tj
+ET
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type3
+  /CharProcs <<
+    /s 6 0 R
+  >>
+  /Encoding <<
+    /Differences [115 /s]
+  >>
+  /FirstChar 115
+  /FontBBox [0 0 1 2]
+  /LastChar 115
+  /Resources <<
+    /XObject <<
+      /Im1 7 0 R
+    >>
+  >>
+>>
+endobj
+{{object 6 0}} <<
+  {{streamlen}}
+>>
+stream
+/Im1 Do
+endstream
+endobj
+{{object 7 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+789cedc2310d00000c03a07f2aaab3ea7bcf03842655555555555555f5bf01cc7818dc
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1241587_expected.pdf.0.png b/testing/resources/pixel/bug_1241587_expected.pdf.0.png
new file mode 100644
index 0000000..319932a
--- /dev/null
+++ b/testing/resources/pixel/bug_1241587_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1258634.in b/testing/resources/pixel/bug_1258634.in
new file mode 100644
index 0000000..4f6c778
--- /dev/null
+++ b/testing/resources/pixel/bug_1258634.in
@@ -0,0 +1,52 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Kids [3 0 R]
+  /Count 1
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Resources <<
+    /XObject <</Im25 5 0 R>>
+    /ProcSet [/PDF /Text /ImageC /ImageI]
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+50 0 0 50 0 0 cm
+/Im25 Do
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 447
+  /BitsPerComponent 8
+  /Name /X
+  /Height 150
+  /Decode [0.0 255.0]
+  /Filter [/ASCII85Decode /FlateDecode /JPXDecode]
+  {{streamlen}}
+>>
+stream
+GhQG_Yti1j&;L4+&hHI;8ZC3n%U"m$jU!\c,9XuY,s^4`#\,T>=L,1U+n6>\!><`+M:^s'mTe/j=LRYs
+s8.3L+D^;KgeV9U!5mR3B@E<uP']`:c-mCld>-<04O@hs(t9_.OHhdR,`!m>B3Y]5fsTr<!RNbrCD*'p
+pBsV>I8B!]N0Qp5:2ZMS"Y$P]`Nj1F-AG$1/&,f>CDqEZc>b%B?K"ua*C"PE8JAWBAF'tm-j$q6qGfBk
+e[sGH+nbX3p>#O@GMK5P5JOj(TmQF;al^NZ[gDFm:K9qc8>D89+cSi-LGT>%=t\92mfZn.~>
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1258634_expected.pdf.0.png b/testing/resources/pixel/bug_1258634_expected.pdf.0.png
new file mode 100644
index 0000000..f6d66eb
--- /dev/null
+++ b/testing/resources/pixel/bug_1258634_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1258968.in b/testing/resources/pixel/bug_1258968.in
new file mode 100644
index 0000000..bb1318f
--- /dev/null
+++ b/testing/resources/pixel/bug_1258968.in
@@ -0,0 +1,211 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Contents 12 0 R
+  /MediaBox [0 0 720 540]
+  /Parent 2 0 R
+  /Resources <<
+    /Pattern <<
+      /P1 10 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Mask
+  /BC [1.0]
+  /S /Luminosity
+>>
+endobj
+{{object 5 0}} <<
+  /Type /ExtGState
+  /AIS true
+  /BM /Normal
+  /CA 1.0
+  /OP false
+  /OPM 1
+  /SA true
+  /SMask 4 0 R
+  /ca 1.0
+  /op false
+>>
+endobj
+{{object 6 0}} <<
+  /Bounds [0.300003]
+  /Domain [0.0 1.0]
+  /Encode [0.0 1.0 0.0 1.0]
+  /FunctionType 3
+  /Functions [8 0 R 9 0 R]
+  /Range [0.0 1.0 0.0 1.0 0.0 1.0]
+>>
+endobj
+{{object 7 0}} <<
+  /Filter [/ASCII85Decode /FlateDecode]
+  /N 3
+  {{streamlen}}
+>>
+stream
+GhS^SG>qPn(5L5LGC#t-0Q\.Xmqi'h]h+J9$H('f6Q':!Tns3j'o47^;QNDg`.65@8?@VV+Wr1@%nI7.
+@PE\<W+8BL8pko0n)`[*a1^BaBCBp9fmB@!/c[/h!%oo,Q3&2"-fBc+]@!CJA*DQ/!!>@!!-8.-XR+0H
+0X#F<P-:cF4^6tMr^F#G!@3-O1A2$Oe%u&3BiZQf!!!]a@S8U,RjI''&Z=^t1<&ri&Q9?@`=/N[bSMQR
+@X0[UI:f6M5+gLsbj9Em>K#Nk1YM#/0mAP#F,l>,@O+4W,ro4*Y)%"S==[+5c5#jgEGR[kR10.R,dn(?
+XCMp4M3u3l%s8]Y&ccmHUb=^d$Pm^P"QDeZB4U/r\OjmE\fOM:DJj+-T_i\Kdl7D[EA3TAb2fnnmMY8W
+9O9m_T?mF,1?.$977FX>\$N=o[P0H9U/:l4nS_%TgX#LIjAXAI>43QA^[fQ]!.ZdOWm.t:C^IKLZu6(4
+rn\_TXo/5jMYboTs2B7sid"*2#VtdA9lkAs9t21/M7B4nl[Ltn%=Rb0'Cg7\Q\Y"F8Rr1W"E\Y>=;bfj
+69[OoPKDh2NW2,Zs#BMOo.UO5,]`Z"SgYdL1hBouP,R2!2HR]=i6f*Js4"u:)"o>*SnjH:N?80Yc3-]2
+;ZZH100iBj!/]M^meZC#$c#74n]ctkS,>DCT*5,iY&K*iOj-Vs'^$MU:1jn/3M4pAf+O_W!D!8A:]RL\
+'BfI,?ib(p")n:4^^gR!E"EIZ!Z"<2l$s:b5p9>@8jF9H#'PSZYR`RB-I;fhJ9Gl9>Qe-D%<)shJbrgh
+9Rpb`(;'hZ&-QDb1.6[-+>l\/15%2$#35o4630n2'Fi#K8IuCF6A<bjW!o\ZlC\5U'D*ku)S-2[K*J!\
+cknSKr<g5j&XW]"@22lL2ME$Q/*me%_^jXj3"%?hMrCdW%GUq*KA5=Bn-sUJ*eg6&EsP$e#RsX$N"H9R
+NW^L[80Yo@ef^e[2:@#^+Iu%6EXNaRArsJM%#TY2UPr%[-m-qo'FU1[>(\:peY/"rg/-V(>Gs:+/F"IK
+N)iCeU@$@BkU:Nc3Yu(RY!sL"T0cmS&"tAtY;$k&%ASid`V<i80e&JiQu^Cl9Io6,AcOWIRDWL=$<TZs
+,;ohN`!W^pfNRVrm!7q?FoqOF8&VCc/9"q*(`X&fKWE*5`LXmUi[/XQGU^9`hB!blBB/UR(&17G-b^Ze
+JR9Nj'6FP@KQ'6b@6L<=cpG69GX-LF,SDtjG/Oq9Qai&X6Te,V8fT(i--;5W8FE9UP*!&9FI70$FI2W_
+9IB(e?-"a2!I63+M0t0XM#9_o,%2\S/&@NR.o@tI-8?q=>S8]h/a4aq;R/Qn>4SJ"^Z:=f?:m`1?&C)=
+7]Ke[[+'o]S2<[g>WPD:?+O;;W:Vt3.2f4GNf9kE>GbNbTZe^ME=,%[Ups?*PRWjHb%8]u'gpNJqGL7j
+A^kXsIFMD&)-JKC#h_>nHJ,X1UGRY$6h@.Nd4/fWBZ%3$E[l+4EZDVAE\2<aEqAionh0JQq,7#hq'#QX
+$?QGIKiEcn$LJ02'iBhmMBRkfML-'k/']P?PoQ#j8u5--PjU!<ap-"^b,^a;.ebF=WMlhAX(86[Wa&Gi
+;5L8'W2?LoWLd%Z<hlTRWH$IgeBY*XV9@:ml"Tc.H8g7Q?!5uR?)W3;]ha&B6W[cc7Zg0@`^<IP-:9Rd
+ZV%PsF&kq..o<9CX'+b[2I;lHSZKLt]173S7oq'9B9Q*&cBPKEY-2_oDR8a54gLZc?C"oMhJI\"5!qP7
+IujS'"5(EC+PEFET\g;=Zn^V[mP,\866"SqnVeBF7Z9pYY/WKAc,O6?1m7p',*R26kGDF[0rE]"HV?Ym
+=du7U24E>[QSJ4j0/DuQs4,!%Jn7etESHXml".EVZGZ6eQ(\r5C9Q/.CPm8-od8uWlOPn^2LLu0RqZ(Y
+gN_K/[Nc21,FD?R\8$f-F0f(dGK0'Q]oB_@re15'%`T!X%S%P:QKTs[/88?Em\o*cF?JJ@l9oiRGM;LB
+lE'EtFDc&Hm%Wa]FueV=l)O3@id/iA\XT!+hsHZ_]XtlSCA/)WLkiH0WCNSYN9;tB3mU<&)I?!Ac!Ia5
+f&!I?0DM2gr-c$NN;0FJZ@&-hBDlZF<r7%:PPi>rlhhh0(^0=13Wn>+^VCk@&!gr`jXk<[!^XOd4+\LX
+lNUu*Iij7K#nEpFGnOj5=r>s,7L=t\3u[fRgb$/_r;JIbA\r"a&X!a2&PWl/OWKXMOeYdN;6-I06n:`J
+UH;qh0bH-q@ciu'0&O3e,1\f`,^+EdmY\Xlf2aB(9Nud#:,7hIG;:3[*uTPbr]N&nfta+Nn4hQ+0["KL
+m:&4g]<J?gdi:kQ-"f!\0sNKR.`jDZ8()LbF)lEo%+h%Sa*j*0PHliQ4uFb]E9^n9A6Nb0/j2Ds1f&jm
+b3R>Lc,e'Fa>D*laW/R2ce=dEbP10Ab(d3'A9S<XU7\DkTq64uE%K$YV`s9<DFEp74R"t7?>mPDrg*K(
+!j@3Z=.r["<co.W(ToD2>OB#u^`$l\_O@X8RVRi5:9=%nG@WjFIHpYNMTgr-MdC7VgsB3*;S9/WI&$GU
+jq<L`I`]YXLJ@Y(UGd0PpMI1j;S2%#.D,4uR9<[8g&#ejq=Ur8^T.*EEGORb^'^?e^"Y6qGh[t^4)[>]
+.:$(Q3PH_W7p?_AWjM'AcFBFsWi`;qm3:m0mM`26W9L82Wng_Q^+Qs6W8=YjFgpr0/\C3?YkJRoFkPNT
+:M!nd^3BIbqsPNo51%lRdnkH(F"le8PK&EuT!'#=2]YdY<-IKC@G]tj,G@W=Rr"R_N[c-QBSN"1c-s^B
+O59`Rj-u&Ej-BeL[+;PCi4/:Odr49]*BrG3U?a)bABT2NhsETr3E=c;[H4mB[O8PFG!pN_SG#&#e`kG+
+l?e``&"c]`;h%jACrJEO\`.[gk6oS]?-'Z4F"uq=GZS/%rC^"SDKgkXpKdL-I.`BKPtO9C4m0"0?9NAS
+rn?W&N9fjiCi6WQ>5+Wb_>8pcI5ZLjrO2YF2skL+%P/JW2s<AKnWq4t/%9qFh\]NX1bcRYps)FsaQOZ<
+SZ058G@0.K\[QO-h#m;%dYC^2WuicTCA:[+a4=+WdBd=(A%dr@*oA&PNBd:hqks0%`D=QNW[H=DS#.a6
+c:iRZhgE$5l,9pPB+$LA5>2$hqmkSTms]9PV0=h4G2`]*`T")_0,7e7IV&2PCG'l"dHgGV$hL35']'gT
+bBVe50(RiZ6LK3eqjJ&l+$)X_IseJf^:6`/%I9DR"T~>
+endstream
+endobj
+{{object 8 0}} <<
+  /BitsPerSample 8
+  /Decode [0.0 1.0 0.0 1.0 0.0 1.0]
+  /Domain [0.0 1.0]
+  /Encode [0.0 255.0]
+  /FunctionType 0
+  /Order 1
+  /Range [0.0 1.0 0.0 1.0 0.0 1.0]
+  /Size [256]
+  /Filter [/ASCII85Decode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+GhP$l@s8#P'EukPrU7.2gU>qX[P\^,dq&..1\:=oP+ts]GDY.=PP0gGgdV+ET?;OHG1/`sLjOlq&KI(L
+f!-\HkIq7B&uVaYlSQoV!1g7^3I>]d(N>LlkRoLPlerR``AJ^1laUAaDW2FL2e\0<i3#5WL\l>3'D.A9
+M'G,`P1C:/jXGM1k<+/Y<ITd+C5-&Q2`uGFGN-<%UC[-5cTgAE^1[]9/&(8Tpb%$RVd?*)i\rOj:o!r(
+Y;jmc/hX\'%se]<CW\igDcUQPK,?@ZehBR,ZQ@)Q]b/9f-Fo9Pdsk?A?l!fgUNnD0]niMN^"bAV"XS'/
+=;>VRB!'mj@tXe1W1qj\;-c$4D6r7E%]l8Y~>
+endstream
+endobj
+{{object 9 0}} <<
+  /BitsPerSample 8
+  /Decode [0.0 1.0 0.0 1.0 0.0 1.0]
+  /Domain [0.0 1.0]
+  /Encode [0.0 255.0]
+  /FunctionType 0
+  /Order 1
+  /Range [0.0 1.0 0.0 1.0 0.0 1.0]
+  /Size [256]
+  /Filter [/ASCII85Decode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+GhW&pn(7TS56iq>1%`hbn&#3T)[V<.!'@^]o`~>
+endstream
+endobj
+{{object 10 0}} <<
+  /Type /Pattern
+  /BBox [-79.0947 -75.3227 29.2131 32.9851]
+  /Matrix [2.58008 0.0 0.0 -2.58008 0.0 540.0]
+  /PaintType 1
+  /PatternType 1
+  /Resources <<
+    /ExtGState << /GS0 5 0 R >>
+    /Shading << /Sh0 13 0 R >>
+  >>
+  /TilingType 3
+  /XStep 108.308
+  /YStep 108.308
+  {{streamlen}}
+>>
+stream
+/GS0 gs
+BX /Sh0 sh EX Q
+Q
+endstream
+endobj
+{{object 11 0}} <<
+  /Type /Pattern
+  /BBox [0.0 0.0 10.0 10.0]
+  /Matrix [0.899994 0.0 0.0 -0.899994 0.0 540.0]
+  /PaintType 1
+  /PatternType 1
+  /Resources <<
+    /ExtGState << /GS0 15 0 R >>
+    /ProcSet [/PDF /ImageC]
+  >>
+  /TilingType 3
+  /XStep 10.0
+  /YStep 10.0
+  {{streamlen}}
+>>
+stream
+/GS0 gs
+endstream
+endobj
+{{object 12 0}} <<
+  {{streamlen}}
+>>
+stream
+/P1 scn
+q 1 0 0 1 24.0804 441.1595 cm
+0 0 m
+14.956 18.738 42.588 21.551 61.719 6.282 c
+B*
+endstream
+endobj
+{{object 13 0}} <<
+  /ShadingType 4
+  /AntiAlias false
+  /BitsPerComponent 8
+  /BitsPerCoordinate 32
+  /BitsPerFlag 8
+  /ColorSpace [/ICCBased 7 0 R]
+  /Decode [-79.0947 49.8358 -75.3227 76.1016 0.0 1.0]
+  /Function 6 0 R
+  /Filter [/ASCII85Decode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+GhQY8J:de0msApO^RfKC56Jf5[]KL9Ib"F1@(I<ko)G&.2NW(&f'iVl^SO3H`a9ee^Y3Q0ZIY89s8Vk4
+0+)K'[da=[+W`o"m.eU#2uc&GYr(>85FqT^,V,YdJ+(;u+D](<_>cUbetB6*(-VW2^AJb.F\q5Ps&D?-
+HS=&+rElD6$-7Elci&@,$-39'=C1p_<'1hJopO.gs'@^7+R>rd\c8R"8jKJ.V#NA6dQt1DRfB!CA-K9s
+^;S%N!W[6dO+%~>
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1258968_expected.pdf.0.png b/testing/resources/pixel/bug_1258968_expected.pdf.0.png
new file mode 100644
index 0000000..0770f77
--- /dev/null
+++ b/testing/resources/pixel/bug_1258968_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1258968_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1258968_expected_skia.pdf.0.png
new file mode 100644
index 0000000..18f4279
--- /dev/null
+++ b/testing/resources/pixel/bug_1258968_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1271578.in b/testing/resources/pixel/bug_1271578.in
new file mode 100644
index 0000000..8837f5b
--- /dev/null
+++ b/testing/resources/pixel/bug_1271578.in
@@ -0,0 +1,95 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+  /MediaBox [0 0 100 100]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+40 30 Td
+/F1 36 Tf
+(!)Tj
+ET
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /TrueType
+  /BaseFont /Test
+  /FirstChar 33
+  /FontDescriptor 6 0 R
+  /LastChar 33
+  /ToUnicode 7 0 R
+  /Widths [415]
+>>
+endobj
+{{object 6 0}} <<
+  /Type /FontDescriptor
+  /Ascent 922
+  /CapHeight 1151
+  /Descent -196
+  /Flags 4
+  /FontBBox [0 -228 2000 921]
+  /FontFile2 8 0 R
+  /FontName /Test
+  /ItalicAngle 0
+  /MissingWidth 750
+  /StemV 0
+>>
+endobj
+{{object 7 0}} <<
+  {{streamlen}}
+>>
+stream
+/CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo <<
+/Registry (Test) /Ordering (UCS) /Supplement 0 >> def
+/CMapName /Test def
+1 begincodespacerange <21> <21> endcodespacerange
+1 beginbfrange
+<21> <21> <0629>
+endbfrange
+endcmap CMapName currentdict /CMap defineresource pop end end
+endstream
+endobj
+{{object 8 0}} <<
+  /Filter [/ASCII85Decode /FlateDecode]
+  {{streamlen}}
+  /Length1 1152
+>>
+stream
+GhV7Y6"got'R_dDS#gHM71g``Z@S[j_4%a(QV`9fkbUmea@WX0RPr-&&s@b[7(`ka2\?Q-3Jf-R,)CL5
++:'#&%pm0q>4<`1=:YRPqIXpiXEsF'?ZtVG5P+m"s7:FX!"@g9'FMfgC$OLFobW-97rRDZR3Q3#5!Kte
+`(BOQMXNDWo^*Ch6f,j"]QUX5UCu-\c8uD,Qf^N7h9H,n#J"'>H?6.=dl3,__9j/A".)HC>)PLCo/k-1
+"r["/>//RcGSl0.rOV.*\(nn.31Ne1G]h?9?>MjaN`CZpVqGa-MKe)6q>QV=&"Q?-pmAY>4Whjs`uq4c
+mV;`O*^=oD*[@G-\RA3RXTOF,W/FDeT#)as6^&HTUgHh!*X1;3AD^HB/!QKb:H@mrGO0<EmLMh\#4llE
+A';]e-+,IKG=UlFe>*J=FbK^:F'=Mm-cDb]6r/02KD#tL3CHuIn4i%?QsJjr7)5oI[)It$92\:,ce-B`
+4sGT-C-n@OG38UMfPkfOIQ-Jj@F(Y.hW2(Iq>+`)XgHXjHl9Ce9a"N+(bCO<)6AYC#4'DR1YAS5CiG&V
+?_<Lh/*K&3/;[j@/)h.n/`OYPgPCV49H9b@JeG(sUVkBV[e96!BUQ4ra!)G#ZWhGe794V,\_:dO2X50t
+2!b.710r>Oi8%b@WH:'*(#][r=j)N:=BIHST\St9pK-?]J,^S!!-4ih@f~>
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1271578_expected.pdf.0.png b/testing/resources/pixel/bug_1271578_expected.pdf.0.png
new file mode 100644
index 0000000..410bbdb
--- /dev/null
+++ b/testing/resources/pixel/bug_1271578_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1271578_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_1271578_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..7318ef2
--- /dev/null
+++ b/testing/resources/pixel/bug_1271578_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1286_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1286_expected_skia.pdf.0.png
new file mode 100644
index 0000000..b4442e4
--- /dev/null
+++ b/testing/resources/pixel/bug_1286_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1287409.pdf b/testing/resources/pixel/bug_1287409.pdf
new file mode 100644
index 0000000..3973822
--- /dev/null
+++ b/testing/resources/pixel/bug_1287409.pdf
Binary files differ
diff --git a/testing/resources/pixel/bug_1287409_expected.pdf.0.png b/testing/resources/pixel/bug_1287409_expected.pdf.0.png
new file mode 100644
index 0000000..60507e1
--- /dev/null
+++ b/testing/resources/pixel/bug_1287409_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1287409_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1287409_expected_skia.pdf.0.png
new file mode 100644
index 0000000..0b00554
--- /dev/null
+++ b/testing/resources/pixel/bug_1287409_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1288_1_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1288_1_expected_skia.pdf.0.png
new file mode 100644
index 0000000..df6b87e
--- /dev/null
+++ b/testing/resources/pixel/bug_1288_1_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1288_2_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1288_2_expected_skia.pdf.0.png
new file mode 100644
index 0000000..3a8cbee
--- /dev/null
+++ b/testing/resources/pixel/bug_1288_2_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1296_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1296_expected_skia.pdf.0.png
new file mode 100644
index 0000000..828856c
--- /dev/null
+++ b/testing/resources/pixel/bug_1296_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1304714.in b/testing/resources/pixel/bug_1304714.in
new file mode 100644
index 0000000..976d901
--- /dev/null
+++ b/testing/resources/pixel/bug_1304714.in
@@ -0,0 +1,107 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /AcroForm [4 0 R 6 0 R 8 0 R]
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [4 0 R 6 0 R 8 0 R]
+  /MediaBox [0 0 200 200]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /P 3 0 R
+  /AP <<
+    /N 5 0 R
+  >>
+  /F 4
+  /Ff 1
+  /Rect [20 10 180 190]
+  /T (Red bottom layer)
+>>
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 160 180]
+  {{streamlen}}
+>>
+stream
+1 0 0 rg
+0 0 160 180 re
+f
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /P 3 0 R
+  /AP <<
+    /N 7 0 R
+  >>
+  /F 4
+  /Ff 1
+  /Rect [80 90 130 130]
+  /T (Green middle layer)
+>>
+endobj
+{{object 7 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0.0 0.0 50 40]
+  {{streamlen}}
+>>
+stream
+0 1 0 rg
+0 0 50 40 re
+f
+Q
+endstream
+endobj
+{{object 8 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /P 3 0 R
+  /AP <<
+    /N 9 0 R
+  >>
+  /F 4
+  /Ff 1
+  /Rect [90 100 120 120]
+  /T (Blue top layer)
+>>
+endobj
+{{object 9 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0.0 0.0 30 20]
+  {{streamlen}}
+>>
+stream
+0 0 1 rg
+0 0 30 20 re
+f
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1304714_expected.pdf.0.png b/testing/resources/pixel/bug_1304714_expected.pdf.0.png
new file mode 100644
index 0000000..58b7600
--- /dev/null
+++ b/testing/resources/pixel/bug_1304714_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1308_1_expected.pdf.0.png b/testing/resources/pixel/bug_1308_1_expected.pdf.0.png
index 44c0446..33a45c0 100644
--- a/testing/resources/pixel/bug_1308_1_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_1308_1_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1308_1_expected_win.pdf.0.png b/testing/resources/pixel/bug_1308_1_expected_win.pdf.0.png
deleted file mode 100644
index 70d0f23..0000000
--- a/testing/resources/pixel/bug_1308_1_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_1308_expected.pdf.0.png b/testing/resources/pixel/bug_1308_expected.pdf.0.png
index 1c36944..1013fb1 100644
--- a/testing/resources/pixel/bug_1308_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_1308_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1308_expected_mac.pdf.0.png b/testing/resources/pixel/bug_1308_expected_mac.pdf.0.png
deleted file mode 100644
index 570f992..0000000
--- a/testing/resources/pixel/bug_1308_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_1308_expected_win.pdf.0.png b/testing/resources/pixel/bug_1308_expected_win.pdf.0.png
deleted file mode 100644
index 0acd825..0000000
--- a/testing/resources/pixel/bug_1308_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_1330_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1330_expected_skia.pdf.0.png
new file mode 100644
index 0000000..0ae57c7
--- /dev/null
+++ b/testing/resources/pixel/bug_1330_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1338_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1338_expected_skia.pdf.0.png
new file mode 100644
index 0000000..7698e64
--- /dev/null
+++ b/testing/resources/pixel/bug_1338_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1355.in b/testing/resources/pixel/bug_1355.in
new file mode 100644
index 0000000..a00fcf1
--- /dev/null
+++ b/testing/resources/pixel/bug_1355.in
@@ -0,0 +1,85 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents [6 0 R]
+  /Resources 4 0 R
+  /MediaBox [0 0 200 200]
+>>
+endobj
+{{object 4 0}} <<
+  /ProcSet [/PDF /Text]
+  /Font <<
+    /F1 5 0 R
+  >>
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type0
+  /BaseFont /HonMincho-M
+  /Encoding /90pv-RKSJ-H
+  /DescendantFonts [7 0 R]
+>>
+endobj
+{{object 6 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+/F1 4 Tf
+6 0 0 6 150 150 Tm
+0 0 0 1 k
+<eb5b>Tj
+-10 0 TD
+<eb69>Tj
+0 -8 TD
+<eb6a>Tj
+ET
+endstream
+endobj
+{{object 7 0}} <<
+  /Type /Font
+  /BaseFont /HonMincho-M
+  /CIDSystemInfo <<
+    /Ordering (Japan1)
+    /Registry (Adobe)
+    /Supplement 2
+  >>
+  /DW 1000
+  /FontDescriptor 8 0 R
+  /Subtype /CIDFontType2
+  /W [1 [250] 18 19 500 54 [833] 81 [583]]
+>>
+endobj
+{{object 8 0}} <<
+  /Type /FontDescriptor
+  /Ascent 880
+  /CapHeight 709
+  /Descent -120
+  /Flags 6
+  /FontBBox [-165 -221 1066 952]
+  /FontName /HonMincho-M
+  /ItalicAngle 0
+  /StemV 81
+  /Style <<
+    /Panose <010502020500000000000000>
+  >>
+  /XHeight 450
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1355_expected.pdf.0.png b/testing/resources/pixel/bug_1355_expected.pdf.0.png
new file mode 100644
index 0000000..3bd23af
--- /dev/null
+++ b/testing/resources/pixel/bug_1355_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1355_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1355_expected_skia.pdf.0.png
new file mode 100644
index 0000000..dae811b
--- /dev/null
+++ b/testing/resources/pixel/bug_1355_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1356149.in b/testing/resources/pixel/bug_1356149.in
new file mode 100644
index 0000000..6dd6502
--- /dev/null
+++ b/testing/resources/pixel/bug_1356149.in
@@ -0,0 +1,216 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 100 100]
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /Font <<
+      /T1_0 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+/T1_0 10 Tf
+-0.02 Tc 0.02 Tw
+1 0 0 1 10 20 Tm
+[(\201)-3.6(\027\036)-5.4(\007)-6.6(\007)-5(\026)-2.5(\034\033)]TJ
+ET
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /JXUIQN+BernhardGothic-Hvy
+  /Encoding 6 0 R
+  /FirstChar 1
+  /FontDescriptor 7 0 R
+  /LastChar 173
+  /Widths [665 576 576 625 576 255 535 616 247 0 250 529 970 540 534 529 531 542
+           518 714 522 238 447 352 372 487 200 399 235 538 499 0 0 0 0 0 0 0 0 0
+           0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+           0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+           0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 543 576 535 0 0 0 0 0 0 0 0 0 0 0 620
+           0 635 561 0 0 0 0 0 0 0 0 0 0 0 0 542 0 0 332 0 0 0 0 0 0 0 0 0 0 0 0
+           500]
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Encoding
+  /BaseEncoding /WinAnsiEncoding
+  /Differences [1 /H /zero /two /N /one /period /o /V /comma /.notdef
+                /quotesingle /E /W /n /d /b /u /p /fi /w /e /l /c /t /r /a
+                /space /s /i /h /T 127 /g /three /S 141 /C 143 /D /B 157 /P 160
+                /f 173 /bullet]
+>>
+endobj
+{{object 7 0}} <<
+  /Type /FontDescriptor
+  /Ascent 1000
+  /CapHeight 666
+  /CharSet (/H /zero /two /N /one /period /o /V /comma /quotesingle /E /W /n /d
+            /b /u /p /fi /w /e /l /c /t /r /a /space /s /i /h /T /g /three /S /C
+            /D /B /P /f /bullet)
+  /Descent -500
+  /Flags 4
+  /FontBBox [-500 -500 1500 1000]
+  /FontFamily (Bernhard Gothic)
+  /FontFile3 8 0 R
+  /FontName /JXUIQN+BernhardGothic-Hvy
+  /FontStretch /Normal
+  /FontWeight 700
+  /ItalicAngle 0
+  /StemV 144
+  /XHeight 472
+>>
+endobj
+{{object 8 0}} <<
+  /Subtype /Type1C
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+789c959769541467bac7ab68abab156d17ac11aab4aa1171178c1963d48c
+0b6e88a8b8a008ca8e022a48d3b2ef427757757575030aa24023c8ee8202
+c60ddce3763489a249266372332766726ecc7827b9f129eecb9c735f265f
+e6e3bda73f749fd3d5effb2cffe7f7fc9b2446b8112449b24161a1ebb76c
+9a1390604c498c31c6af4b352526c5cd0bccc81efed6a072a43a7984cd66
+97a92984c773710c788d8590719031be63f2d8f3130837927cffc399ab66
+bdb778f102c3f6c404c3dad414936155eac1433129d986f529717e730d2b
+83830d5b93f6259ad20d5b13d2138c1909f1810931f80282c42f229120c6
+10c4588218af217c34c42c82f02788f708e28f24b188240274c44682d8ec
+466cd3106104612288830439c140cc20e6127ec47c4d802690d8448410db
+c81d648cee0091e266d46412cdc43704311327488cc087fb1211442ef184
+f4218bc8bfb9e5b97da9d9a2691f9138e247ea20f5423b439bae6da359da
+49bfd0edd6758e9c3cf2e4a88051a65197dcd7ba978f2647078fee1a238c
+393be63ff46efaeca17e3d7a6773a9752e528feeaa9ed041c242d5936981
+0e0a79685b5007a547f76dae5f5c24903f68de210373a2c6e170b5eaba6e
+d6f6f4b277133ede1599941c15dd76e8aa99b7da6c56f67046943185d7a3
+1bc53083bc053334b0e731d3b3bf333636393926b6f340cfc7eded1ff3fa
+12f05057c224b2037828006f8d3a03be61901017b06c59dc000830e5d2d7
+2f5ef62e43061ebcab1988f40516f947ecc94b8de31787c4f9223f16ed79
+8b26821f2cfc193c2195d7db5c83340ed30453342a31e8cfa0590594d526
+5b5951ce510ef2ca7e7b52226ba645ba0cfd932aa5cb964b26314f2af392
+a4e1b06181a2154f4a8d52874ebc606b73b14edaa94a94de0663d525e045
+c25fc1a0f95c6d627e5e43dd69e9eeece1ce9f4a8f4f2d4b294c13920382
+9066f7fae27ccf3988a3d4955a074ca4ac8793c4744ed44e95cf5faaaabd
+f8d4ebcedb8647032c908b5e21b779013b6645f3fa23e0a5ae803f90f02d
+0efaefea1de6f54a473975b9a3b1b69d6b6f4a4f29114b2c2542ce5ee3a1
+d521668be79b8cfb3b37b091f1f1bb6362da6f24f348bb9e9adbf74de88f
+1c3040fc02e39e27df09ec16ae5caa6efe84fd61c997b357fc69d7ca045e
+5f5100132110f4e003e3c947608059e0a38118e8619c4d47ed8ddcb32b7b
+b787462705a615dbcb0f09a2ba8a71ba2ae57aee41cfa1d8a8c8031bf24b
+65255b90c08b42f3806464cb7055e5627ba6fda0e3434f9156d01794486f
+34af284be09d1295fbe8f191071cac00b7ef807d917c2ba853e86d6dba76
+8305adcf1768feb2546bd6525effa400387529f06433ccd2a89fabc0f444
+f977ace45010f247efa3cd681bf8a1f721f8bb9fcedfbe27982bf6f5e734
+992b8b4e7945ed0949dfc819967c0fa361dcabd730e25a6751c23dc1ee6c
+bb79b2d55959d3e0a5ff4b014c563f82a9e4139c7205f86a20013c18f57d
+dad17c546ee0c0fdcf0bd144e4b1f843e4915d6a73640af84a77e6984815
+3e7d547a8f83482cabd9b00b76a00f60210adbb2a1387bad5062a5647158
+2a4a913d4fc9d239f62be9d96c093de44b9be938cbfe23f1bcde88e5f20e
+3c60032ef43d98a9514d83f398a1008bd619ac6ca4cae8217f1a2b4fca90
+f6f262b24d140bd4a59267034c4097acd43bfa4799aad47ea57daa2dd7d6
+c044542f51104443af9dd283c6e6c239b070de45de86b920819f4645837e
+0c5aacc53189ac5c2217c8d9ba4c39af982da5511cb243245d41dbe55a7b
+072fd7c9c7ed4775b2cde610d0222d1e530b2b9be5623957673f2ce795b2
+39f4d0329c84b8564ac1b1157ad96c36c9ccc342ad1d7fb0f3e275e90655
+41c348b0cdc2f3532a658a49bc94226648f93adbf061b0f45f4fcabc784c
+aa954e8b4f3d155a5d4d2bf439c74d471daf7f86679375611a18a018cf7a
+df60005346cb5becc19499b60fed938ba503522c65068383b6dea72c0f6d
+c7ebd9725a863075d2d024aadc26490a5f2d9d96ba79e933ca49cbea24aa
+92969fcbe76c0d72b917be5a61d12c91c29590582b2e4406af2c1f3e1a65
+0d79a834a52fc4d59b83db22c244f231cc80e55872b04f3dcb381b2bb0ce
+07ae47042c0bdfbd24a75476e6086821fcc0c8a5d4b13067dc7e16f7cc9d
+2ea38b715d0ef0e27e5b9654aa33831f8d2f9559a9523a2135880f3c71b0
+f7a82aa77494fbe46675c365e155f79dbffdc4c25434e20d5ab424d5629c
+cdebb30a30f2d661e4f5c14c588869a2dad5d7cc27cb57b47ec4a1f133e6
+a251de8f83607694f02ca3293b894d8a3f101dbee3745f30bf783ab5fec5
+b7897fe1c0ebcd6b18db59da56d02c18afed699fdd51e8f05cd598d27481
+bd78e674df8d4b99117dbc7e3be679394c21610d9e72b5188b64280719ca
+b5caf5069ceb51ed1374517b84368991d675bc75b7b85fcad15960312d39
+ca5e5a7e93ef7b3969e8fc8e56b497ac4d92c3562d7ae91ffede3ef5b7c1
+090cf2be265973e333b68816340235a271d02d3a73bb727b2505cd89b69b
+5dd16d9b6533d887ae8053bd2a2b8d7dc75b2b4e2a0e2f9b6cb397f37af5
+8ff8305f17f93f3b077906f15791ff902f0a56bdd1e6210f345dd5a319f1
+d0f9cff770eb59081f3240d7e087943eb0e01fe0fdfac66f640b1e011f98
+a481bbb080893b9812179f74f642d799ce0b3d1d076279e4842ee6dc2caa
+f1d629e74dee6e8b69478550179b569ec84d5fb6d67ff6972bde5ceda969
+3d23d4ed068fceccaea2dbd2632f3c49d4597353a18bbf7c6855c79fb82d
+9b8b8c6b04bdb100c64229c6ea661779135f1a02ac0636c12ba6b6a3c6d1
+c17d7a39724d40f8ceb529058e5a4c4c08a72a609041f3d27fdf38b2552e
+b267ebec26b9c08c15543eb49a92e81831ab348d8fbcf355da0b0ee6fef7
+2f58919cff2f687474be35374c704006a5371460484b308eac87c91a75b9
+ba943917b5b1610b87a6181081783405084482d7ad87aeae5bc2a6b754da
+3e634932b76ee7cdcf9ef5dd7c72ee6c5e5ab3a0cf2c80f138740f0875e1
+73a6c2c6e1d0ff0e9399ccb47c0b7e3aeadaf3671f5f7f7ca6c69cd122cc
+c7dace9773e52c5e4eb71d29c1d555fb701e26ca422be19412291b8fb056
+5a0ea04e44e7d9e338346e1a72435e681a68a6c2cc81db7537be151c0a55
+5c6f3e738bb5d38aea4e3968e50bc7234abf3f1ff3e238b893f0015c606e
+357434f571fd1753c24f08ed680e9399baaf288e0b0e6fbd9e25c0b73255
+de2657b5b020f8ff2762d174a4c1e97a23ef77de30f5c5d7a7bbfbb19f30
+6259b7e045fc04788d537d8f910d9f48148aa625b31821c6486514ccd65a
+6b6de552934e6ab0553b5905d340e165488291b20c247297a9e71849e7eb
+9a9a9b9a0bb24ef2576024752e34f1782c977aa834df246cda971e8c2694
+d9cacab04dd017a1ede0fe14c640094cc075e4612b081a780bdf33682b6d
+4cca2b4be44276f73f1ce8bf31d0566dcd3f2dc0fc01e6f2aaa5a7f10a9b
+8d081f9c85e7db69e071b1d651d72b6c908cc6f55c6874d5e908210269a8
+d4fb7fce7dcec1b4e72f618ee0901c8e7ff12e9952e8fb158f8ed5f2fa5c
+8cacbfc224d8e822af8337e605ded851836b19b428156b6c98f847ec0572
+8e4ed9604fb2b2c318e5b1c6443a473a2ca5f252a6942b15e94498435b1a
+ac772e6037037be9dab34d156d5c7fa3718b60a525e48b5d90159ba0581e
+ff26bf8cc53e482ae3c14fd1e2c93fefc2114112b691ed96969253fcbd8c
+e09eb5dcce5c8b69bdf02b2ee319a5b7fc0a6ecb73dc963c08247bb0d3bb
+a8c6338ea1780a046db51a4fc9c03157a22fed898c8fd91dd11d7de56a77
+ef75fc8bcf300078177915f7b117db34b94c0e561660bda1ffc22b689198
+644ee261a6432bb6494f1fb0325d0e6f30f21d3f295fe13734df44e9f371
+69be86b178c17ac302bc4eba551fe65846e5ae70161f1284056db2e1e479
+b35428e5f056c02bebb8e4909a75d239a9a61a9fa8403a75b2fdb8d2ce3d
+3895b24428a3d130e18ba41cc9385c895ca94457063369a94a6aedc64550
+7063e653e5f4515be551b641aa976af8ef8b97f5233db7687566989f300c
+f67160c5aba51ea6401a4e4b1d0385ccfad02d9bb686df7efc59ff9dc70f
+2e87ade5815718988ce7974493f12c5348870c68caafd3b0b47c40036ee0
+cdeb0b4b602cd20ea73791bc8b5d70083e0dc2c09b91611b55d5d6e068e4
+9e5e8d58b72e223c2835bdb2de24a0f97014275f11138735e0c0cbd24cc7
+d25952a1f9f7bdec8b511c421575d558cf73c0bdfd070e904704b6f11f2d
+4addb55ad0a3205ccb077859dc012f0d04aa7b19643051d5bb1d9bd03c36
+48dc2a86f345e04b1f819d94c3e674b02f3b2edf3c75cc92d7c077c354aa
+ea708992c2ed4accdab3171fe55f80b3680237f20cf09f02a751ddd58f98
+0b5bf7d486723e4bd62c58752bf467935067a19acaaa0bb3d8cc9cc2d4d8
+9893adc9fc763f2aa6ff5ee63deed7af3f7d7de7e0951dcd427ad5e1cabd
+55ba4395c59527d8fafa93a7da2f651feac2da892c05bdbacd457e3a1cee
+80ba9a413abbfc127b930af9d871d65e6babacc41842067998c3128b9d6a
+899c2fc77896d2d23a2a582ba1118f44096da5acf4cacf37bdeaedade9e8
+e5f7fd48e51db048995c5266cb15018fe1090537438bbdbfc9827df3808b
+ec065f0c6b4ce434358e41be5a7b96dd64648fd0324aa7d2d212f3f67351
+b9cd0382030be5031c4bb9adba9e95ebf09fcc2a1d9a61d6ca264a2eb665
+e7b2221ebacd784ae32b228fa6f121aefb7b9f71575b2acfdf152a1c2776
+c21fdaa8c20bc50f7b70067658441da36be526b99d975df65ab95aa707bc
+39d5401739b813d7158dbe3a1d19d158c8441e28632ea4226e0f240f6dc5
+c430832712c1a4eea0f4a160844c1226438606ec6a28b3312c6c63707fd8
+c3477dfd8f1eeeea0f16f4fbfe8daa10a52e6006feef88dc24fdbf185c8e
+ee0d93a14a5d40e9b3ebd47845390e1b6acb152dcab8dcaad0e0310a7877
+7560a2dacf8cfa5fd09b4ba2
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1356149_expected.pdf.0.png b/testing/resources/pixel/bug_1356149_expected.pdf.0.png
new file mode 100644
index 0000000..93827a8
--- /dev/null
+++ b/testing/resources/pixel/bug_1356149_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1372651.evt b/testing/resources/pixel/bug_1372651.evt
new file mode 100644
index 0000000..eb762eb
--- /dev/null
+++ b/testing/resources/pixel/bug_1372651.evt
@@ -0,0 +1,4 @@
+# Open the dropdown
+mousemove,140,145
+mousedown,left,140,145
+mouseup,left,140,145
diff --git a/testing/resources/pixel/bug_1372651.in b/testing/resources/pixel/bug_1372651.in
new file mode 100644
index 0000000..95b1948
--- /dev/null
+++ b/testing/resources/pixel/bug_1372651.in
@@ -0,0 +1,71 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /AcroForm <<
+    /DA (/Helv 0 Tf 0 g)
+    /DR <<
+      /Font <<
+        /Helv 4 0 R
+      >>
+    >>
+    /Fields [5 0 R 6 0 R]
+  >>
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [5 0 R 6 0 R]
+  /MediaBox [0 0 200 200]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /DV (Item3)
+  /F 4
+  /Ff 131072
+  /I [2]
+  /MK <<
+    /BG [1.0]
+  >>
+  /Opt [(Item1) (Item2) (Item3)]
+  /P 3 0 R
+  /Rect [70 135 150 155]
+  /T (Dropdown)
+  /V (Item3)
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /F 4
+  /Ff 65536
+  /MK <<
+    /BG [0.5]
+  >>
+  /P 3 0 R
+  /Rect [50 90 150 110]
+  /T (Button)
+  /TU (Button Tooltip)
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1372651_expected.pdf.0.png b/testing/resources/pixel/bug_1372651_expected.pdf.0.png
new file mode 100644
index 0000000..c7386a1
--- /dev/null
+++ b/testing/resources/pixel/bug_1372651_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1372651_expected_mac.pdf.0.png b/testing/resources/pixel/bug_1372651_expected_mac.pdf.0.png
new file mode 100644
index 0000000..a749d63
--- /dev/null
+++ b/testing/resources/pixel/bug_1372651_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1372651_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1372651_expected_skia.pdf.0.png
new file mode 100644
index 0000000..f1823f3
--- /dev/null
+++ b/testing/resources/pixel/bug_1372651_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1383708.in b/testing/resources/pixel/bug_1383708.in
new file mode 100644
index 0000000..77114f6
--- /dev/null
+++ b/testing/resources/pixel/bug_1383708.in
@@ -0,0 +1,100 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 200 200]
+  /Resources 5 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+100 0 0 20 50 50 cm
+/I1 Do
+Q
+q
+80 0 0 20 50 100 cm
+/I2 Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /ProcSet [/PDF /ImageB /ImageC /ImageI]
+  /XObject <<
+    /I1 6 0 R
+    /I2 8 0 R
+  >>
+>>
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 1
+  /ColorSpace [/Indexed /DeviceRGB 1 7 0 R]
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /Height 5
+  /Width 177
+  {{streamlen}}
+>>
+stream
+789cfbff1f1b6860c00eb02afedf80431887215c0094ac4349
+endstream
+endobj
+{{object 7 0}} <<
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+789cbb70e5c6ffffffb90017e80584
+endstream
+endobj
+{{object 8 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace [/Indexed /DeviceRGB 39 9 0 R]
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /Height 15
+  /Width 58
+  {{streamlen}}
+>>
+stream
+789c9d928b12822010454d7c5510a2166a61befdff3f0c310151a7c93bb0
+ecec70b82c03845c411405f09086b21c0e814e68dba17384ecdf5cfda264
+592a935a818d9b0196b9cd3e39174cd8470830847c9d532ebae74c7ed73c
+a514304ad37ce539c6694e984126711c3f6f3c243ffb945591e49de77997
+2b0f5d0e8dedca77e93925f82185b75f65c793bcce5c808df14564ddf083
+1b9ed57d146062a9d6a418f2f27a0ba03d49b560975cb621442a9cd47e51
+1475822ba236e8e75beab08d8ff4bf3e7d492308
+endstream
+endobj
+{{object 9 0}} <<
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+789c01780087ff2c3339ffcc00ffffcca97600ebd17fe7c855ffdd48cc99
+00f6e9bfffd31dd4a92398873dffffffffee93655e3eb68300ebd067d8bf
+7fdda900ffe361be8b00ffcf0dffd831ffe87ae3bd3dfff3aaddc37fbe91
+18d9a90f333333d5a100fff8beffffddae7b00c89400e7cd7feab600deb2
+24d4a000dba70092a54914
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1383708_expected.pdf.0.png b/testing/resources/pixel/bug_1383708_expected.pdf.0.png
new file mode 100644
index 0000000..800eedc
--- /dev/null
+++ b/testing/resources/pixel/bug_1383708_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1383708_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1383708_expected_skia.pdf.0.png
new file mode 100644
index 0000000..5487865
--- /dev/null
+++ b/testing/resources/pixel/bug_1383708_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1388_2_expected.pdf.0.png b/testing/resources/pixel/bug_1388_2_expected.pdf.0.png
index 422a7e2..347f3e0 100644
--- a/testing/resources/pixel/bug_1388_2_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_1388_2_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1388_2_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_1388_2_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..2293e85
--- /dev/null
+++ b/testing/resources/pixel/bug_1388_2_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1388_2_expected_mac.pdf.0.png b/testing/resources/pixel/bug_1388_2_expected_mac.pdf.0.png
deleted file mode 100644
index b1a4843..0000000
--- a/testing/resources/pixel/bug_1388_2_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_1388_2_expected_win.pdf.0.png b/testing/resources/pixel/bug_1388_2_expected_win.pdf.0.png
deleted file mode 100644
index 58a14f4..0000000
--- a/testing/resources/pixel/bug_1388_2_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_1388_3_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_1388_3_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..3727440
--- /dev/null
+++ b/testing/resources/pixel/bug_1388_3_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1388_3_expected_mac.pdf.0.png b/testing/resources/pixel/bug_1388_3_expected_mac.pdf.0.png
deleted file mode 100644
index bb97a96..0000000
--- a/testing/resources/pixel/bug_1388_3_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_1388_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1388_expected_skia.pdf.0.png
new file mode 100644
index 0000000..eb348c8
--- /dev/null
+++ b/testing/resources/pixel/bug_1388_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1395648.in b/testing/resources/pixel/bug_1395648.in
new file mode 100644
index 0000000..6c38fb1
--- /dev/null
+++ b/testing/resources/pixel/bug_1395648.in
@@ -0,0 +1,67 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents [4 0 R]
+  /MediaBox [0 0 80 80]
+  /Resources <<
+    /XObject <<
+      /Mask 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+40 5 0 60 10 10 cm
+/Mask Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /Height 50
+  /SMask 6 0 R
+  /Width 50
+  {{streamlen}}
+>>
+stream
+789cedc2310d00000c03a07f2aaab3ea7bcf03842655555555555555f5bf01cc7818dc
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Height 50
+  /Matte [0.0 0.2 1]
+  /Width 50
+  {{streamlen}}
+>>
+stream
+789cabaa1a05a3808e4061148c027a028351300ae809246c128a027e0020065c9c90d
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1395648_expected.pdf.0.png b/testing/resources/pixel/bug_1395648_expected.pdf.0.png
new file mode 100644
index 0000000..4aca65e
--- /dev/null
+++ b/testing/resources/pixel/bug_1395648_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1395648_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1395648_expected_skia.pdf.0.png
new file mode 100644
index 0000000..80a944b
--- /dev/null
+++ b/testing/resources/pixel/bug_1395648_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1396266.in b/testing/resources/pixel/bug_1396266.in
new file mode 100644
index 0000000..fdeaf43
--- /dev/null
+++ b/testing/resources/pixel/bug_1396266.in
@@ -0,0 +1,98 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+
+% A page with scaled copies of a masked image. Any scaled image with an area
+% less than 8 times the stencil mask's original area (64x64 * 8 = 32,768) forces
+% bilinear interpolation of the stencil mask, which triggers the bug.
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 432 288]
+  /Resources <<
+    /XObject <<
+      /Masked 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+
+% 32x32 image has an area of 1,024, so it is interpolated.
+q
+  32 0 0 32 64 240 cm
+  /Masked Do
+Q
+
+% 64x64 image has an area of 4,096, so it is interpolated.
+q
+  64 0 0 64 48 160 cm
+  /Masked Do
+Q
+
+% 128x128 image has an area of 16,384, so it is interpolated.
+q
+  128 0 0 128 16 16 cm
+  /Masked Do
+Q
+
+% 256x256 image has an area of 65,536, so it is not interpolated.
+q
+  256 0 0 256 160 16 cm
+  /Masked Do
+Q
+
+endstream
+endobj
+
+% A 3x3 base image with a stencil mask.
+{{object 5 0}} <<
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter /ASCIIHexDecode
+  /Height 3
+  /Mask 6 0 R
+  /Width 3
+  {{streamlen}}
+>>
+stream
+B8C3E9 7793DB C3CEEF
+8CADF2 1B74E8 BFD5FB
+C2D3FA 9FBDF8 D9E5FC
+endstream
+endobj
+
+% A 64x64 stencil mask with horizontal and vertical lines.
+{{object 6 0}} <<
+  /Subtype /Image
+  /BitsPerComponent 1
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /Height 64
+  /ImageMask true
+  /Width 64
+  {{streamlen}}
+>>
+stream
+78DA63608080FAFF10E000E5C3687FA8388CF6818A63D0500330E802A83C41BA1EAA9E105D03554F
+36CD0C35876CFA0FD41CDAD3F2FF694D03002512CA0A
+endstream
+endobj
+
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1396266_expected.pdf.0.png b/testing/resources/pixel/bug_1396266_expected.pdf.0.png
new file mode 100644
index 0000000..6cacddf
--- /dev/null
+++ b/testing/resources/pixel/bug_1396266_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1396266_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1396266_expected_skia.pdf.0.png
new file mode 100644
index 0000000..e872c2c
--- /dev/null
+++ b/testing/resources/pixel/bug_1396266_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1402_expected.pdf.0.png b/testing/resources/pixel/bug_1402_expected.pdf.0.png
index cf913bd..751d63f 100644
--- a/testing/resources/pixel/bug_1402_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_1402_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1402_expected_mac.pdf.0.png b/testing/resources/pixel/bug_1402_expected_mac.pdf.0.png
deleted file mode 100644
index 9602820..0000000
--- a/testing/resources/pixel/bug_1402_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_1402_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1402_expected_skia.pdf.0.png
new file mode 100644
index 0000000..f34b78e
--- /dev/null
+++ b/testing/resources/pixel/bug_1402_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1430333.in b/testing/resources/pixel/bug_1430333.in
new file mode 100644
index 0000000..e715f8f
--- /dev/null
+++ b/testing/resources/pixel/bug_1430333.in
@@ -0,0 +1,44 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 200 200]
+  /Resources <<
+    /ExtGState <<
+      /A3 <<
+        /Type /ExtGState
+        /CA 0.5
+        /ca 0.5
+      >>
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+/A3 gs
+-100 10 m
+140 20 l
+b
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1430333_expected.pdf.0.png b/testing/resources/pixel/bug_1430333_expected.pdf.0.png
new file mode 100644
index 0000000..b9fc3e9
--- /dev/null
+++ b/testing/resources/pixel/bug_1430333_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1430333_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1430333_expected_skia.pdf.0.png
new file mode 100644
index 0000000..e6a1827
--- /dev/null
+++ b/testing/resources/pixel/bug_1430333_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1469.in b/testing/resources/pixel/bug_1469.in
new file mode 100644
index 0000000..8eb970d
--- /dev/null
+++ b/testing/resources/pixel/bug_1469.in
@@ -0,0 +1,98 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 300 100]
+  /Resources <<
+    /XObject <<
+      % All 3 images are BGRA with the same data but interpreted differently.
+      /ImNoSMaskInData 5 0 R
+      /ImSMaskInData0 6 0 R
+      /ImSMaskInData1 7 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+0 1 0 rg
+0 0 300 100 re f
+Q
+q
+64 0 0 64 0 0 cm
+/ImNoSMaskInData Do
+Q
+q
+64 0 0 64 100 0 cm
+/ImSMaskInData0 Do
+Q
+q
+64 0 0 64 200 0 cm
+/ImSMaskInData1 Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter /JPXDecode
+  /Height 64
+  % No /SMaskInData here.
+  /Width 64
+  {{streamlen}}
+>>
+stream
+{{include ../bug_1469.jp2}}
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter /JPXDecode
+  /Height 64
+  /SMaskInData 0
+  /Width 64
+  {{streamlen}}
+>>
+stream
+{{include ../bug_1469.jp2}}
+endstream
+endobj
+{{object 7 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter /JPXDecode
+  /Height 64
+  /SMaskInData 1
+  /Width 64
+  {{streamlen}}
+>>
+stream
+{{include ../bug_1469.jp2}}
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1469_expected.pdf.0.png b/testing/resources/pixel/bug_1469_expected.pdf.0.png
new file mode 100644
index 0000000..a87b79d
--- /dev/null
+++ b/testing/resources/pixel/bug_1469_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1491.in b/testing/resources/pixel/bug_1491.in
new file mode 100644
index 0000000..b973b2b
--- /dev/null
+++ b/testing/resources/pixel/bug_1491.in
@@ -0,0 +1,44 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /OpenAction 6 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [ 0 0 200 200 ]
+  /Count 1
+  /Kids [ 3 0 R ]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Pages
+  /Parent 2 0 R
+  /Count 1
+  /Kids [ 4 0 R ]
+>>
+{{object 4 0}} <<
+  /Type /Page
+  /Parent 3 0 R
+  /Contents 5 0 R
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+0.5 0.5 0.5 rg
+50 50 100 100 re B
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /Action
+  /S /GoTo
+  /D [ 3 0 R /Fit ]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1491_expected.pdf.0.png b/testing/resources/pixel/bug_1491_expected.pdf.0.png
new file mode 100644
index 0000000..21e9375
--- /dev/null
+++ b/testing/resources/pixel/bug_1491_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1491_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1491_expected_skia.pdf.0.png
new file mode 100644
index 0000000..69c5f08
--- /dev/null
+++ b/testing/resources/pixel/bug_1491_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1519.in b/testing/resources/pixel/bug_1519.in
new file mode 100644
index 0000000..d681156
--- /dev/null
+++ b/testing/resources/pixel/bug_1519.in
@@ -0,0 +1,48 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 400 400]
+  /Resources <<
+    /ExtGState <<
+      /GS1 <<
+        /ca 0.5
+      >>
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+/GS1 gs
+0 0 1 rg
+0 0 m
+130 130 l
+150 150 180 180 200 200 c
+400 400 l
+300 300 l
+400 400 l
+300 300 l
+f
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1519_expected.pdf.0.png b/testing/resources/pixel/bug_1519_expected.pdf.0.png
new file mode 100644
index 0000000..4a6cc19
--- /dev/null
+++ b/testing/resources/pixel/bug_1519_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1546.in b/testing/resources/pixel/bug_1546.in
new file mode 100644
index 0000000..fb423d0
--- /dev/null
+++ b/testing/resources/pixel/bug_1546.in
@@ -0,0 +1,77 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Shading <<
+      /S0 5 0 R
+    >>
+  >>
+  /Contents 4 0 R
+  /MediaBox [0 0 200 300]
+>>
+endobj
+{{object 4 0}} <<
+  {streamlen}}
+>>
+stream
+/S0 sh
+endstream
+endobj
+{{object 5 0}} <<
+  /ShadingType 4
+  /BitsPerComponent 8
+  /BitsPerCoordinate 16
+  /BitsPerFlag 8
+  /ColorSpace /DeviceRGB
+  /Decode [-32768 32767 -32768 32767 0 1]
+  /Filter /ASCIIHexDecode
+  /Function 6 0 R
+  {{streamlen}}
+>>
+stream
+00800a800a0000800a80c8ff00806480c800
+endstream
+endobj
+{{object 6 0}} <<
+  /FunctionType 3
+  /Bounds [0.5]
+  /Domain [0 1]
+  /Encode [0 1 0 1]
+  /Functions [7 0 R 8 0 R]
+  /Range [0 1 0 1 0 1]
+>>
+endobj
+{{object 7 0}} <<
+  /FunctionType 2
+  /C0 [1 0 0]
+  /C1 [0 1 0]
+  /Domain [0 1]
+  /N 1
+  /Range [0 1 0 1 0 1]
+>>
+endobj
+{{object 8 0}} <<
+  /FunctionType 2
+  /C0 [0 1 0]
+  /C1 [0 0 1]
+  /Domain [0 1]
+  /N 1
+  /Range [0 1 0 1 0 1]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1546_expected.pdf.0.png b/testing/resources/pixel/bug_1546_expected.pdf.0.png
new file mode 100644
index 0000000..e993bf1
--- /dev/null
+++ b/testing/resources/pixel/bug_1546_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1571.in b/testing/resources/pixel/bug_1571.in
new file mode 100644
index 0000000..b4a94e1
--- /dev/null
+++ b/testing/resources/pixel/bug_1571.in
@@ -0,0 +1,112 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 200 200]
+  /Contents 4 0 R
+  /Resources <<
+    /ProcSet [/PDF /ImageB /ImageC /ImageI]
+    /ExtGState <<
+      /GS1 5 0 R
+      /GS3 6 0 R
+    >>
+    /Pattern <<
+      /Pa2 7 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+1 0 0 1 50 100 cm
+1 1 1 RG
+/GS3 gs
+/Pattern cs
+/Pa2 scn
+/GS1 gs
+-7 7 m
+-7 -7 l
+-7 -7 l
+7 -7 l
+7 -7 l
+7 7 l
+7 7 l
+-7 7 l
+h
+B*
+Q
+q
+1 0 0 1 150 100 cm
+/Pattern cs
+/Pa2 scn
+/GS1 gs
+-7 7 m
+-7 -7 l
+-7 -7 l
+7 -7 l
+7 -7 l
+7 7 l
+7 7 l
+-7 7 l
+h
+B*
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /ExtGState
+  /ca 1
+>>
+endobj
+{{object 6 0}} <<
+  /Type /ExtGState
+  /CA 0
+>>
+endobj
+{{object 7 0}} <<
+  /Type /Pattern
+  /PaintType 1
+  /PatternType 1
+  /TilingType 2
+  /BBox [0 0 0.25 0.25]
+  /Matrix [1.414 -1.414 1.414 1.414 -95.043 -381.294]
+  /PaintType 1
+  /Resources <<
+    % Acrobat requires this dictionary, even if it is empty.
+  >>
+  /XStep 0.25
+  /YStep 0.25
+  {{streamlen}}
+>>
+stream
+/DeviceRGB CS 0.941 0 0 SC
+/DeviceRGB cs 1 1 1 sc
+0 0 0.25 0.25 re
+f
+0.094 w
+0 0 m
+0.25 0 l
+0.094 w
+0.125 0 m
+0.125 0.25 l
+S
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1571_expected.pdf.0.png b/testing/resources/pixel/bug_1571_expected.pdf.0.png
new file mode 100644
index 0000000..e3d54b8
--- /dev/null
+++ b/testing/resources/pixel/bug_1571_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1638.in b/testing/resources/pixel/bug_1638.in
new file mode 100644
index 0000000..f6dea38
--- /dev/null
+++ b/testing/resources/pixel/bug_1638.in
@@ -0,0 +1,57 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 400 400]
+  /Resources <<
+    /ExtGState <<
+      /GS1 <<
+        /ca 1.0
+      >>
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+/GS1 gs
+0 0 1 rg
+% The 1st sub path.
+50 350 m
+200 320 l
+50 350 l
+% The 2nd sub path.
+100 300 m
+50 300 l
+200 300 l
+300 200 l
+270 230 l
+% The 3rd sub path.
+200 50 m
+350 50 l
+200 50 l
+200 100 l
+200 50 l
+f
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1638_expected.pdf.0.png b/testing/resources/pixel/bug_1638_expected.pdf.0.png
new file mode 100644
index 0000000..23a39d5
--- /dev/null
+++ b/testing/resources/pixel/bug_1638_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1638_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1638_expected_skia.pdf.0.png
new file mode 100644
index 0000000..7ef152f
--- /dev/null
+++ b/testing/resources/pixel/bug_1638_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1639_1.in b/testing/resources/pixel/bug_1639_1.in
new file mode 100644
index 0000000..f283af7
--- /dev/null
+++ b/testing/resources/pixel/bug_1639_1.in
@@ -0,0 +1,50 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 200 200]
+  /Resources <<
+    /ExtGState <<
+      /GS1 <<
+        /ca 1.0
+      >>
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+/GS1 gs
+0 0 1 rg
+% A horizontal path.
+20 180 m
+180 180 l
+% A vertical path.
+50 160 m
+50 100 l
+% A diagonal path.
+20 50 m
+180 100 l
+f
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1639_1_expected.pdf.0.png b/testing/resources/pixel/bug_1639_1_expected.pdf.0.png
new file mode 100644
index 0000000..2734462
--- /dev/null
+++ b/testing/resources/pixel/bug_1639_1_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1639_1_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1639_1_expected_skia.pdf.0.png
new file mode 100644
index 0000000..fa53f12
--- /dev/null
+++ b/testing/resources/pixel/bug_1639_1_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1693.in b/testing/resources/pixel/bug_1693.in
new file mode 100644
index 0000000..e59b5f8
--- /dev/null
+++ b/testing/resources/pixel/bug_1693.in
@@ -0,0 +1,101 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 200 200]
+  /Resources <<
+    /ProcSet [/PDF /ImageC]
+    /ExtGState <<
+      /G3 4 0 R
+      /G8 5 0 R
+    >>
+    /Pattern <<
+      /P7 6 0 R
+    >>
+  >>
+  /Contents 7 0 R
+>>
+endobj
+{{object 4 0}} <<
+  /BM /Normal
+  /ca 1
+>>
+endobj
+{{object 5 0}} <<
+  /BM /Normal
+  /ca .5
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Pattern
+  /PaintType 1
+  /PatternType 1
+  /TilingType 1
+  /BBox [0 0 400 400]
+  /Matrix [256 0 0 32 0 0]
+  /XStep 512
+  /YStep 512
+  /Resources <<
+    /ProcSet [/PDF /ImageC]
+    /ExtGState <<
+      /G3 4 0 R
+    >>
+    /XObject <<
+      /X4 8 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+q
+1 0 0 -8 0 8 cm
+/G3 gs
+/X4 Do
+Q
+endstream
+endobj
+{{object 7 0}} <<
+  {{streamlen}}
+>>
+stream
+1 1 1 RG
+1 1 1 rg
+/G3 gs
+/Pattern CS
+/Pattern cs
+/P7 SCN
+/P7 scn
+/G8 gs
+0 0 200 150 re
+f
+endstream
+endobj
+{{object 8 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+789cedc2310d00000c03a07f2aaab3ea7bcf03842655555555555555f5bf01cc7818dc
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1693_expected.pdf.0.png b/testing/resources/pixel/bug_1693_expected.pdf.0.png
new file mode 100644
index 0000000..d65bd0d
--- /dev/null
+++ b/testing/resources/pixel/bug_1693_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1723.in b/testing/resources/pixel/bug_1723.in
new file mode 100644
index 0000000..aac04d2
--- /dev/null
+++ b/testing/resources/pixel/bug_1723.in
@@ -0,0 +1,61 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+  /MediaBox [0 0 300 400]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents [4 0 R]
+  /Resources <<
+    /ColorSpace <<
+      /DefaultCMYK /DeviceRGB
+    >>
+    /XObject <<
+      /Img 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+200 0 0 300 50 50 cm
+/Img Do
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceCMYK
+  /Filter /ASCIIHexDecode
+  /Height 8
+  /Width 8
+  {{streamlen}}
+>>
+stream
+FF000000 FF000000 FF000000 FF000000   00FF0000 00FF0000 00FF0000 00FF0000
+FF000000 FF000000 FF000000 FF000000   00FF0000 00FF0000 00FF0000 00FF0000
+FF000000 FF000000 FF000000 FF000000   00FF0000 00FF0000 00FF0000 00FF0000
+FF000000 FF000000 FF000000 FF000000   00FF0000 00FF0000 00FF0000 00FF0000
+
+0000FF00 0000FF00 0000FF00 0000FF00   000000FF 000000FF 000000FF 000000FF
+0000FF00 0000FF00 0000FF00 0000FF00   000000FF 000000FF 000000FF 000000FF
+0000FF00 0000FF00 0000FF00 0000FF00   000000FF 000000FF 000000FF 000000FF
+0000FF00 0000FF00 0000FF00 0000FF00   000000FF 000000FF 000000FF 000000FF
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1723_expected.pdf.0.png b/testing/resources/pixel/bug_1723_expected.pdf.0.png
new file mode 100644
index 0000000..46dceb7
--- /dev/null
+++ b/testing/resources/pixel/bug_1723_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1733.in b/testing/resources/pixel/bug_1733.in
new file mode 100644
index 0000000..9812a03
--- /dev/null
+++ b/testing/resources/pixel/bug_1733.in
@@ -0,0 +1,107 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 100 100]
+  /Count 2
+  /Kids [3 0 R 4 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 5 0 R
+  /Resources <<
+    /XObject 9 0 R
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 5 0 R
+  /Resources <<
+    /XObject 10 0 R
+  >>
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+100 0 0 100 0 0 cm
+/X1 Do
+Q
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 1
+  /Height 1
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter /ASCIIHexDecode
+  {{streamlen}}
+>>
+stream
+FF0000
+endstream
+endobj
+{{object 7 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 1
+  /Height 1
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter /ASCIIHexDecode
+  {{streamlen}}
+>>
+stream
+FFFF00
+endstream
+endobj
+% This object stream contains two different copies of object 9 and object 10.
+% To disambiguate them, a parser must use object 11 0 below, the cross-reference
+% stream, to look up the type 2 entries. Those entries reference this object,
+% and the position of the objects within this object.
+{{object 8 0}} <<
+  /Type /ObjStm
+  /N 4
+  /First 20
+  {{streamlen}}
+>>
+stream
+9 0 9 13 10 26 10 39<</X1 6 0 R>><</X1 7 0 R>><</X1 6 0 R>><</X1 7 0 R>>
+endstream
+endobj
+{{object 11 0}} <<
+  /Type /XRef
+  /Filter /ASCIIHexDecode
+  /Root 1 0 R
+  /Size 11
+  /W [1 2 2]
+  {{streamlen}}
+>>
+stream
+00 0000 FFFF
+01 000F 0000
+01 0044 0000
+01 00A3 0000
+01 0110 0000
+01 017E 0000
+01 01CF 0000
+01 028B 0000
+01 046A 0000
+02 0008 0000
+02 0008 0003
+endstream
+endobj
+{{startxrefobj 11 0}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1733_expected.pdf.0.png b/testing/resources/pixel/bug_1733_expected.pdf.0.png
new file mode 100644
index 0000000..d96c847
--- /dev/null
+++ b/testing/resources/pixel/bug_1733_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1733_expected.pdf.1.png b/testing/resources/pixel/bug_1733_expected.pdf.1.png
new file mode 100644
index 0000000..d1490da
--- /dev/null
+++ b/testing/resources/pixel/bug_1733_expected.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1746.in b/testing/resources/pixel/bug_1746.in
new file mode 100644
index 0000000..ca2a4b2
--- /dev/null
+++ b/testing/resources/pixel/bug_1746.in
@@ -0,0 +1,122 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /MediaBox [0 0 200 200]
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Resources <<
+    /ExtGState <<
+      /GS0 8 0 R
+    >>
+    /Font <<
+      /F1 5 0 R
+    >>
+    /XObject <<
+      /Img 9 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+/GS0 gs
+q
+0 1 0 rg
+BT
+/F1 2 Tf
+1 0 0 1 50 50 Tm
+(a)Tj
+ET
+Q
+q
+0 0 1 rg
+BT
+/F1 2 Tf
+1 0 0 1 72 80 Tm
+(a)Tj
+ET
+Q
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type3
+  /CharProcs <<
+    /a0 7 0 R
+  >>
+  /Encoding 6 0 R
+  /FirstChar 97
+  /FontBBox [0 0 1000 1000]
+  /FontMatrix [1 0 0 1 0 0]
+  /LastChar 97
+  /Widths [60]
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Encoding
+  /BaseEncoding /WinAnsiEncoding
+  /Differences [97 /a0]
+>>
+endobj
+{{object 7 0}} <<
+  {{streamlen}}
+>>
+stream
+50 0 4 -1 48 45 d1
+q
+4 -1 m
+4 45 l
+48 45 l
+48 -1 l
+h
+W n
+q
+44 0 0 46 4.1 -1.1 cm
+/Img Do
+Q
+Q
+endstream
+endobj
+{{object 8 0}} <<
+  /Type /ExtGState
+  /CA 0.5
+  /ca 0.5
+>>
+endobj
+{{object 9 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 1
+  /Decode [1 0]
+  /DecodeParms [null <</Columns 44 /K -1>>]
+  /Filter [/ASCIIHexDecode /CCITTFaxDecode]
+  /Height 46
+  /ImageMask true
+  /Width 44
+  {{streamlen}}
+>>
+stream
+26a08680de081e11e187840830f4137a4df0ef4dbedbffff6ffdaf0c2f1f
+fff21b34c82e33044e7c11a09c205e105e97a5e97a5ffa5fe0a3f5ffafff
+b5c9aaa30bed27adb4ad70da4da5b6128f0c426b216818500100100a
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1746_expected.pdf.0.png b/testing/resources/pixel/bug_1746_expected.pdf.0.png
new file mode 100644
index 0000000..08c41d1
--- /dev/null
+++ b/testing/resources/pixel/bug_1746_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1746_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1746_expected_skia.pdf.0.png
new file mode 100644
index 0000000..0ab21f9
--- /dev/null
+++ b/testing/resources/pixel/bug_1746_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1750.in b/testing/resources/pixel/bug_1750.in
new file mode 100644
index 0000000..ce0f46f
--- /dev/null
+++ b/testing/resources/pixel/bug_1750.in
@@ -0,0 +1,83 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 200 100]
+  /Resources <<
+    /XObject <<
+      /Img1 6 0 R
+      /Img2 8 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+60 0 0 60 20 20 cm
+/Img1 Do
+Q
+q
+60 0 0 60 120 20 cm
+/Img2 Do
+Q
+endstream
+endobj
+{{object 5 0}} [
+  /Indexed
+  /DeviceGray
+  1
+  <88 cc>
+]
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 1
+  /ColorSpace 5 0 R
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+0000
+endstream
+endobj
+{{object 7 0}} [
+  /Indexed
+  /DeviceGray
+  3
+  <11 44 88 cc>
+]
+endobj
+{{object 8 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 1
+  /ColorSpace 7 0 R
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+0000
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1750_expected.pdf.0.png b/testing/resources/pixel/bug_1750_expected.pdf.0.png
new file mode 100644
index 0000000..b574e46
--- /dev/null
+++ b/testing/resources/pixel/bug_1750_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1752.in b/testing/resources/pixel/bug_1752.in
new file mode 100644
index 0000000..0ba4a41
--- /dev/null
+++ b/testing/resources/pixel/bug_1752.in
@@ -0,0 +1,75 @@
+{{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
+  /Resources <<
+    /Font <<
+      /F1 4 0 R
+    >>
+  >>
+  /Contents 6 0 R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Font
+  /Subtype /TrueType
+  /BaseFont /NotoSansRegular
+  /Encoding /WinAnsiEncoding
+  /FirstChar 65
+  /LastChar 71
+  /FontDescriptor 5 0 R
+  /Name /F1
+  /Widths [250 250 250 250 800 800 800]
+>>
+endobj
+{{object 5 0}} <<
+  /Type /FontDescriptor
+  /CapHeight 750
+  /Descent -250
+  /Flags 32
+  /FontBBox [-503 -250 1240 750]
+  /FontFile2 7 0 R
+  /FontName /NotoSansRegular
+  /FontWeight 400
+  /ItalicAngle 0
+  /StemV 52
+>>
+endobj
+{{object 6 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+20 100 Td
+/F1 70 Tf
+(ABCD) Tj
+0 -80 Td
+(EFG) Tj
+ET
+endstream
+endobj
+{{object 7 0}} <<
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  {{streamlen}}
+  /Length1 11200
+>>
+stream
+{{include ../bug_1752_truetype_font.fragment}}
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1752_expected.pdf.0.png b/testing/resources/pixel/bug_1752_expected.pdf.0.png
new file mode 100644
index 0000000..ed9be10
--- /dev/null
+++ b/testing/resources/pixel/bug_1752_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1752_expected_mac.pdf.0.png b/testing/resources/pixel/bug_1752_expected_mac.pdf.0.png
new file mode 100644
index 0000000..a8cb3ba
--- /dev/null
+++ b/testing/resources/pixel/bug_1752_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1752_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1752_expected_skia.pdf.0.png
new file mode 100644
index 0000000..9cb41b8
--- /dev/null
+++ b/testing/resources/pixel/bug_1752_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1772.in b/testing/resources/pixel/bug_1772.in
new file mode 100644
index 0000000..026b713
--- /dev/null
+++ b/testing/resources/pixel/bug_1772.in
@@ -0,0 +1,93 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents [4 0 R]
+  /MediaBox [0 0 100 100]
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /ColorSpace <<
+      /CS1 [/Pattern]
+    >>
+    /Font <<
+      /F1 5 0 R
+    >>
+    /Pattern <<
+      /P1 6 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+/CS1 CS
+/P1 SCN
+/F1 1 Tf
+1 Tr
+20 0 0 20 20 30 Tm
+(S) Tj
+ET
+BT
+/CS1 CS
+/P1 SCN
+/F1 4 Tf
+1 Tr
+5 0 0 5 50 80 Tm
+(S) Tj
+ET
+BT
+/CS1 CS
+/P1 SCN
+/F1 20 Tf
+1 Tr
+1 0 0 1 70 40 Tm
+(S) Tj
+ET
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Pattern
+  /PatternType 2
+  /Shading 7 0 R
+>>
+endobj
+{{object 7 0}} <<
+  /ShadingType 3
+  /ColorSpace /DeviceRGB
+  /Coords [0.0 0.0 0.0 0.0 0.0 0.005] % Concentric circles
+  /Function 8 0 R
+  /Extend [true true]
+ >>
+endobj
+{{object 8 0}} <<
+  /FunctionType 2
+  /Domain [0.0 1.0]
+  /C0 [0 0 0]
+  /C1 [0 1 0]
+  /N 1
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1772_expected.pdf.0.png b/testing/resources/pixel/bug_1772_expected.pdf.0.png
new file mode 100644
index 0000000..7056d87
--- /dev/null
+++ b/testing/resources/pixel/bug_1772_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1772_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1772_expected_skia.pdf.0.png
new file mode 100644
index 0000000..477b2a2
--- /dev/null
+++ b/testing/resources/pixel/bug_1772_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1774.in b/testing/resources/pixel/bug_1774.in
new file mode 100644
index 0000000..d80b429
--- /dev/null
+++ b/testing/resources/pixel/bug_1774.in
@@ -0,0 +1,47 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 200 200]
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+0.001 w
+30 150 m
+170 150 l
+S
+Q
+q
+0.01 w
+30 95 m
+170 95 l
+S
+Q
+q
+0.1 w
+30 40 m
+170 40 l
+S
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1774_expected.pdf.0.png b/testing/resources/pixel/bug_1774_expected.pdf.0.png
new file mode 100644
index 0000000..32bcba3
--- /dev/null
+++ b/testing/resources/pixel/bug_1774_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1774_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1774_expected_skia.pdf.0.png
new file mode 100644
index 0000000..40ec74e
--- /dev/null
+++ b/testing/resources/pixel/bug_1774_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1822.in b/testing/resources/pixel/bug_1822.in
new file mode 100644
index 0000000..4075bb7
--- /dev/null
+++ b/testing/resources/pixel/bug_1822.in
@@ -0,0 +1,34 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 400 400]
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+[1.8 1.8] 96636760 d
+1 i
+100 300 m
+200 300 l
+S
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1822_expected.pdf.0.png b/testing/resources/pixel/bug_1822_expected.pdf.0.png
new file mode 100644
index 0000000..913f33f
--- /dev/null
+++ b/testing/resources/pixel/bug_1822_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1822_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1822_expected_skia.pdf.0.png
new file mode 100644
index 0000000..2467b79
--- /dev/null
+++ b/testing/resources/pixel/bug_1822_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1845.in b/testing/resources/pixel/bug_1845.in
new file mode 100644
index 0000000..9fd8052
--- /dev/null
+++ b/testing/resources/pixel/bug_1845.in
@@ -0,0 +1,80 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 96 96]
+  /Resources <<
+    /XObject <<
+      /X0 5 0 R
+    >>
+  >>
+>>
+endobj
+
+% Cross formed by red horizontal rectangle overlaid with blue vertical
+% rectangle.
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+  % Red horizontal rectangle defined by clip.
+  8 32 80 32 re W* n
+  1 0 0 rg
+  0 0 96 96 re f
+Q
+q
+  % Blue vertical rectangle defined by masked image.
+  32 0 0 80 32 8 cm
+  /X0 Do
+Q
+endstream
+endobj
+
+% Single pixel image with /SMask to trigger masked image rendering.
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 1
+  /Height 1
+  /ColorSpace /DeviceRGB
+  /BitsPerComponent 8
+  /SMask 6 0 R
+  /Filter /ASCIIHexDecode
+  {{streamlen}}
+>>
+stream
+00 00 FF
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 1
+  /Height 1
+  /ColorSpace /DeviceGray
+  /BitsPerComponent 8
+  /Filter /ASCIIHexDecode
+  {{streamlen}}
+>>
+stream
+80
+endstream
+endobj
+
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1845_expected.pdf.0.png b/testing/resources/pixel/bug_1845_expected.pdf.0.png
new file mode 100644
index 0000000..b246272
--- /dev/null
+++ b/testing/resources/pixel/bug_1845_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1847.in b/testing/resources/pixel/bug_1847.in
new file mode 100644
index 0000000..42d4ef9
--- /dev/null
+++ b/testing/resources/pixel/bug_1847.in
@@ -0,0 +1,65 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /MediaBox [0 0 400 400]
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [4 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Highlight
+  /AP <<
+    /N 5 0 R
+  >>
+  /F 4
+  /P 3 0 R
+  /Rect 6 0 R
+>>
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox 6 0 R
+  /ProcSet [/PDF]
+  /Resources <<
+    /ExtGState <<
+      /R0 <<
+        /Type /ExtGState
+        /AIS false
+        /BM /Multiply
+      >>
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/R0 gs
+1 1 0 rg
+1 w
+72 305 m
+68 309 68 317 72 321 c
+132 321 l
+136 317 136 309 132 305 c
+f
+endstream
+endobj
+{{object 6 0}}
+[67 304 137 322]
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1847_expected.pdf.0.png b/testing/resources/pixel/bug_1847_expected.pdf.0.png
new file mode 100644
index 0000000..1e9d7fb
--- /dev/null
+++ b/testing/resources/pixel/bug_1847_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1847_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1847_expected_skia.pdf.0.png
new file mode 100644
index 0000000..f201f8e
--- /dev/null
+++ b/testing/resources/pixel/bug_1847_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1883.in b/testing/resources/pixel/bug_1883.in
new file mode 100644
index 0000000..79593bb
--- /dev/null
+++ b/testing/resources/pixel/bug_1883.in
@@ -0,0 +1,92 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 4
+  /Kids [3 0 R 4 0 R 5 0 R 6 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Annots [7 0 R 8 0 R]
+  /MediaBox [0 0 612 792]
+  /Parent 2 0 R
+  /Rotate 0
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Page
+  /Annots [7 0 R 8 0 R]
+  /MediaBox [0 0 792 612]
+  /Parent 2 0 R
+  /Rotate 90
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Page
+  /Annots [7 0 R 8 0 R]
+  /MediaBox [0 0 612 792]
+  /Parent 2 0 R
+  /Rotate 180
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Page
+  /Annots [7 0 R 8 0 R]
+  /MediaBox [0 0 792 612]
+  /Parent 2 0 R
+  /Rotate 270
+>>
+endobj
+{{object 7 0}} <<
+  /Type /Annot
+  /Subtype /Line
+  /AP <<
+    /N 9 0 R
+  >>
+  % NoRotate
+  /F 16
+  /L [100 50 100 150]
+  /P 3 0 R
+  /Rect [90 370 270 500]
+>>
+endobj
+{{object 8 0}} <<
+  /Type /Annot
+  /Subtype /Line
+  /AP <<
+    /N 9 0 R
+  >>
+  /F 0
+  /L [100 50 100 150]
+  /P 3 0 R
+  /Rect [190 470 370 600]
+>>
+endobj
+{{object 9 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 360 130]
+  {{streamlen}}
+>>
+stream
+10 w
+1 0 0 RG
+30 65 m
+150 65 l
+S
+120 45 m
+150 65 l
+120 85 l
+S
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1883_expected.pdf.0.png b/testing/resources/pixel/bug_1883_expected.pdf.0.png
new file mode 100644
index 0000000..4bc6992
--- /dev/null
+++ b/testing/resources/pixel/bug_1883_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1883_expected.pdf.1.png b/testing/resources/pixel/bug_1883_expected.pdf.1.png
new file mode 100644
index 0000000..f18c1d7
--- /dev/null
+++ b/testing/resources/pixel/bug_1883_expected.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1883_expected.pdf.2.png b/testing/resources/pixel/bug_1883_expected.pdf.2.png
new file mode 100644
index 0000000..a3093ff
--- /dev/null
+++ b/testing/resources/pixel/bug_1883_expected.pdf.2.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1883_expected.pdf.3.png b/testing/resources/pixel/bug_1883_expected.pdf.3.png
new file mode 100644
index 0000000..e6e3988
--- /dev/null
+++ b/testing/resources/pixel/bug_1883_expected.pdf.3.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1883_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1883_expected_skia.pdf.0.png
new file mode 100644
index 0000000..8560bfe
--- /dev/null
+++ b/testing/resources/pixel/bug_1883_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1883_expected_skia.pdf.1.png b/testing/resources/pixel/bug_1883_expected_skia.pdf.1.png
new file mode 100644
index 0000000..0660877
--- /dev/null
+++ b/testing/resources/pixel/bug_1883_expected_skia.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1883_expected_skia.pdf.2.png b/testing/resources/pixel/bug_1883_expected_skia.pdf.2.png
new file mode 100644
index 0000000..f0050f8
--- /dev/null
+++ b/testing/resources/pixel/bug_1883_expected_skia.pdf.2.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1883_expected_skia.pdf.3.png b/testing/resources/pixel/bug_1883_expected_skia.pdf.3.png
new file mode 100644
index 0000000..88d3e25
--- /dev/null
+++ b/testing/resources/pixel/bug_1883_expected_skia.pdf.3.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1922.in b/testing/resources/pixel/bug_1922.in
new file mode 100644
index 0000000..c7b005b
--- /dev/null
+++ b/testing/resources/pixel/bug_1922.in
@@ -0,0 +1,66 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 100 100]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 5 0 R
+  /Resources <<
+    /Font <<
+      /F1 4 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Font
+  /Subtype /TrueType
+  /BaseFont /TimesNewRoman,Bold
+  /Encoding /WinAnsiEncoding
+  /FirstChar 32
+  /LastChar 121
+  /Widths [
+    230.769 307.692 538.462 538.462 538.462 1153.846 846.154 307.692
+    307.692 307.692 538.462 538.462 230.769 307.692 230.769 307.692
+    538.462 538.462 538.462 538.462 538.462 538.462 538.462 538.462
+    538.462 538.462 307.692 307.692 538.462 538.462 538.462 538.462
+    923.077 692.308 692.308 692.308 692.308 538.462 538.462 692.308
+    692.308 307.692 538.462 692.308 615.385 846.154 692.308 769.231
+    615.385 769.231 692.308 615.385 615.385 615.385 692.308 1000.000
+    692.308 615.385 615.385 307.692 307.692 307.692 615.385 538.462
+    307.692 461.538 461.538 461.538 461.538 461.538 307.692 538.462
+    538.462 307.692 307.692 615.385 307.692 769.231 538.462 461.538
+    461.538 461.538 461.538 461.538 307.692 538.462 384.615 615.385
+    461.538 461.538
+  ]
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+2 J
+BT
+0 0 0 rg
+/F1 20 Tf
+10 40 Td
+[(U) -107.72357178 (n) -14.68963623 (i) 31.01132202
+(v) -125.67745972 (e) 14.6895752 (r) 35.90786743 (s) 57.12615967
+(i) 31.01132202 (t) -11.42520142 (y) -48.96524048]TJ
+ET
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
\ No newline at end of file
diff --git a/testing/resources/pixel/bug_1922_expected.pdf.0.png b/testing/resources/pixel/bug_1922_expected.pdf.0.png
new file mode 100644
index 0000000..73057ac
--- /dev/null
+++ b/testing/resources/pixel/bug_1922_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1922_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_1922_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..2caa41a
--- /dev/null
+++ b/testing/resources/pixel/bug_1922_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1949.in b/testing/resources/pixel/bug_1949.in
new file mode 100644
index 0000000..5e7afd3
--- /dev/null
+++ b/testing/resources/pixel/bug_1949.in
@@ -0,0 +1,133 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 44 24]
+  /Resources <<
+    /ExtGState <<
+      /GSHalfAlphaConstant 5 0 R
+      /GSHalfAlphaMask 6 0 R
+    >>
+    /XObject <<
+      /HalfAlphaSquare 8 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+
+% Opaque black background.
+q
+  0 0 0 rg
+  0 0 44 24 re
+  f
+Q
+
+% Transparency group drawn with constant alpha.
+q
+  1 0 0 1 4 4 cm
+  /GSHalfAlphaConstant gs
+
+  /HalfAlphaSquare Do
+Q
+
+% Transparency group drawn with a soft mask.
+q
+  1 0 0 1 24 4 cm
+  /GSHalfAlphaMask gs
+
+  /HalfAlphaSquare Do
+Q
+
+endstream
+endobj
+
+% Graphics state with a 0.5 non-stroking alpha constant.
+{{object 5 0}} <<
+  /Type /ExtGState
+  /ca 0.5
+>>
+endobj
+
+% Graphics state with a soft mask containing a 0.5 alpha central hole.
+{{object 6 0}} <<
+  /Type /ExtGState
+  /SMask <<
+    /Type /Mask
+    /S /Luminosity
+    /G 7 0 R
+  >>
+>>
+endobj
+{{object 7 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 16 16]
+  /Group <<
+    /Type /Group
+    /S /Transparency
+    /CS /DeviceGray
+    /I true
+  >>
+  {{streamlen}}
+>>
+stream
+q
+  1 g
+  0 0 16 16 re
+  f
+
+  0.5 g
+  4 4 8 8 re
+  f
+Q
+endstream
+endobj
+
+% Transparency group containing a 0.5 alpha white square.
+{{object 8 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 16 16]
+  /Group <<
+    /Type /Group
+    /S /Transparency
+    /I true
+  >>
+  /Resources <<
+    /ExtGState <<
+      /GSHalfAlphaConstant 5 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+q
+  /GSHalfAlphaConstant gs
+
+  1 1 1 rg
+  0 0 16 16 re
+  f
+Q
+endstream
+endobj
+
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1949_expected.pdf.0.png b/testing/resources/pixel/bug_1949_expected.pdf.0.png
new file mode 100644
index 0000000..c215044
--- /dev/null
+++ b/testing/resources/pixel/bug_1949_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1949_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1949_expected_skia.pdf.0.png
new file mode 100644
index 0000000..6d4ea6d
--- /dev/null
+++ b/testing/resources/pixel/bug_1949_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1963.in b/testing/resources/pixel/bug_1963.in
new file mode 100644
index 0000000..d79560b
--- /dev/null
+++ b/testing/resources/pixel/bug_1963.in
@@ -0,0 +1,73 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [300 500 350 530]
+  /Resources <<
+    /XObject <<
+      /X1 5 0 R
+      /X2 6 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+  1 0 0 1 301.65 515.4 cm
+  /X1 Do
+Q
+q
+  1 0 0 1 304.05 503.7 cm
+  /X2 Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [301.65 515.4 342.75 527.4]
+  /Matrix [1 0 0 1 -301.65 -515.4]
+  {{streamlen}}
+>>
+stream
+1 0 0 RG
+2 w
+305.400 521.400 m
+339.000 521.400 l
+S
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [304.05 503.7 345.15 515.7]
+  /Matrix [1 0 0 1 -304.05 -503.7]
+  {{streamlen}}
+>>
+stream
+1 0 0 RG
+2 w
+307.800 510.000 m
+341.400 509.400 l
+S
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1963_expected.pdf.0.png b/testing/resources/pixel/bug_1963_expected.pdf.0.png
new file mode 100644
index 0000000..a2aec8c
--- /dev/null
+++ b/testing/resources/pixel/bug_1963_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1963_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1963_expected_skia.pdf.0.png
new file mode 100644
index 0000000..2d9ddae
--- /dev/null
+++ b/testing/resources/pixel/bug_1963_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1966.in b/testing/resources/pixel/bug_1966.in
new file mode 100644
index 0000000..b52a6cb
--- /dev/null
+++ b/testing/resources/pixel/bug_1966.in
@@ -0,0 +1,121 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+  /MediaBox [0 0 128.571 128.571]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 7 0 R
+  /Resources <<
+    /XObject <<
+      /Im1 4 0 R
+    >>
+    /Pattern <<
+      /P1 5 0 R
+      /P2 6 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 1
+  /Filter /ASCIIHexDecode
+  /Height 40
+  /ImageMask true
+  /Width 80
+  {{streamlen}}
+>>
+stream
+FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000FFFF
+FFFF000000000000FFFFFFFF0FFFFFFFFFF0FFFFFFFF0FFFFFFFFFF0FFFF
+FFFF0FFFFFFFFFF0FFFFFFFF0FFFFFFFFFF0FFFFFFFF000000000000FFFF
+FFFF000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+FFFF000000000000FFFFFFFF000000000000FFFFFFFF0FFFFFFFFFF0FFFF
+FFFF0FFFFFFFFFF0FFFFFFFF0FFFFFFFFFF0FFFFFFFF0FFFFFFFFFF0FFFF
+FFFF000000000000FFFFFFFF000000000000FFFFFFFFFFFFFFFFFFFFFFFF
+FFFFFFFFFFFFFFFFFFFF
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Pattern
+  /PaintType 1
+  /PatternType 1
+  /TilingType 1
+  /BBox [0 0 128.571 128.571]
+  /XStep 128.571
+  /YStep 128.571
+  {{streamlen}}
+>>
+stream
+q
+0.0 0.0 0.0 rg
+0 128.571 m
+128.571 128.571 l
+128.571 64 l
+0 64 l
+f
+Q
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /Pattern
+  /PaintType 1
+  /PatternType 1
+  /TilingType 1
+  /BBox [0 0 128.571 128.571]
+  /XStep 128.571
+  /YStep 128.571
+  {{streamlen}}
+>>
+stream
+% second pattern
+q
+0.0 0.0 0.0 rg
+0 64 m
+128.571 64 l
+128.571 0 l
+0 0 l
+f
+Q
+endstream
+endobj
+{{object 7 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+/Pattern cs
+/P1 scn
+q
+128.571 0 0 128.571 0 0 cm
+/Im1 Do
+Q
+/P2 scn
+q
+128.571 0 0 128.571 0 0 cm
+/Im1 Do
+Q
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
\ No newline at end of file
diff --git a/testing/resources/pixel/bug_1966_expected.pdf.0.png b/testing/resources/pixel/bug_1966_expected.pdf.0.png
new file mode 100644
index 0000000..47d12bd
--- /dev/null
+++ b/testing/resources/pixel/bug_1966_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1966_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1966_expected_skia.pdf.0.png
new file mode 100644
index 0000000..134ca64
--- /dev/null
+++ b/testing/resources/pixel/bug_1966_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1972_1.in b/testing/resources/pixel/bug_1972_1.in
new file mode 100644
index 0000000..e537e94
--- /dev/null
+++ b/testing/resources/pixel/bug_1972_1.in
@@ -0,0 +1,36 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 200 100]
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+5 0 0 5 0 0 cm
+0 1 0 RG
+0 0 m
+16000 1420 l
+S
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1972_1_expected.pdf.0.png b/testing/resources/pixel/bug_1972_1_expected.pdf.0.png
new file mode 100644
index 0000000..bca30df
--- /dev/null
+++ b/testing/resources/pixel/bug_1972_1_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1972_2.in b/testing/resources/pixel/bug_1972_2.in
new file mode 100644
index 0000000..4c1ad95
--- /dev/null
+++ b/testing/resources/pixel/bug_1972_2.in
@@ -0,0 +1,36 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 200 100]
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+5 0 0 5 0 0 cm
+0 1 0 RG
+-9000 -9000 m
+500 500 l
+S
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1972_2_expected.pdf.0.png b/testing/resources/pixel/bug_1972_2_expected.pdf.0.png
new file mode 100644
index 0000000..19b8c8a
--- /dev/null
+++ b/testing/resources/pixel/bug_1972_2_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1972_3.in b/testing/resources/pixel/bug_1972_3.in
new file mode 100644
index 0000000..5d84df7
--- /dev/null
+++ b/testing/resources/pixel/bug_1972_3.in
@@ -0,0 +1,36 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 200 100]
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+5 0 0 5 0 0 cm
+0 1 0 RG
+0 0 m
+2 10000 20 9000 40 -10000 c
+S
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1972_3_expected.pdf.0.png b/testing/resources/pixel/bug_1972_3_expected.pdf.0.png
new file mode 100644
index 0000000..d0f97c9
--- /dev/null
+++ b/testing/resources/pixel/bug_1972_3_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1973.in b/testing/resources/pixel/bug_1973.in
new file mode 100644
index 0000000..1d5602d
--- /dev/null
+++ b/testing/resources/pixel/bug_1973.in
@@ -0,0 +1,81 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [ 3 0 R ]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [ 240 570 345 650 ]
+  /Resources <<
+    /XObject <<
+      /Img 5 0 R
+    >>
+  >>
+>>
+endobj
+
+% Content stream matching coordinates in form_button0.pdf.
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+  89.555968 0 0 67.166976 247.7222 576.8333 cm
+  q
+    1 0 0 rg
+    0 0 1 1 re
+    f
+  Q
+  /Img Do
+Q
+endstream
+endobj
+
+% A 1024x768 image with a single (0x00), indexed color (0x00FF00).
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace [ /Indexed /DeviceRGB 0 <00FF00> ]
+  /Filter [ /ASCIIHexDecode /FlateDecode ]
+  /Height 768
+  /Width 1024
+  {{streamlen}}
+>>
+stream
+78DAEDC13101000000C2A0F54F6D067FA00000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000003E0300B40001
+endstream
+endobj
+
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1973_expected.pdf.0.png b/testing/resources/pixel/bug_1973_expected.pdf.0.png
new file mode 100644
index 0000000..8d75b62
--- /dev/null
+++ b/testing/resources/pixel/bug_1973_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1976.in b/testing/resources/pixel/bug_1976.in
new file mode 100644
index 0000000..696f5da
--- /dev/null
+++ b/testing/resources/pixel/bug_1976.in
@@ -0,0 +1,83 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 96 96]
+  /Resources <<
+    /XObject <<
+      /X0 5 0 R
+    >>
+  >>
+>>
+endobj
+
+% Green and red checkerboard. When scaled down, the green and red should blend
+% together into a darker yellow.
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+  % Solid green square.
+  0 1 0 rg
+  16 16 64 64 re f
+Q
+q
+  % Transparent and red checkerboard.
+  64 0 0 64 16 16 cm
+  /X0 Do
+Q
+endstream
+endobj
+
+% Single pixel image with /SMask to trigger masked image rendering.
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter /ASCIIHexDecode
+  /Height 1
+  /SMask 6 0 R
+  /Width 1
+  {{streamlen}}
+>>
+stream
+FF 00 00
+endstream
+endobj
+
+% Grayscale checkerboard to trigger 8-bit to 8-bit scaling. Scaling down by a
+% multiple of 2 using nearest neighbor gives particularly bad results.
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter [/ASCII85Decode /FlateDecode]
+  /Height 128
+  /Width 128
+  {{streamlen}}
+>>
+stream
+GhVQ20b"*_#f&.lRiX?CBI7,$dqQl"iofLfkND$kkND$kkND$kkND$kkND$kkND$kkND$kkND$kkND$k
+kND$kkND$kkND$kkND$kkND$kkN@?Q?he3kdJ~>
+endstream
+endobj
+
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1976_expected.pdf.0.png b/testing/resources/pixel/bug_1976_expected.pdf.0.png
new file mode 100644
index 0000000..59bfc84
--- /dev/null
+++ b/testing/resources/pixel/bug_1976_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1983.in b/testing/resources/pixel/bug_1983.in
new file mode 100644
index 0000000..0167258
--- /dev/null
+++ b/testing/resources/pixel/bug_1983.in
@@ -0,0 +1,43 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents [4 0 R]
+  /MediaBox [0 0 100 100]
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+20 w
+q
+1 0 0 1 20 10 cm
+1 J
+% The first dot
+10 40 m
+h
+S
+% The second dot
+50 40 m
+50 40 l
+h
+S
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1983_expected.pdf.0.png b/testing/resources/pixel/bug_1983_expected.pdf.0.png
new file mode 100644
index 0000000..ea59974
--- /dev/null
+++ b/testing/resources/pixel/bug_1983_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1983_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1983_expected_skia.pdf.0.png
new file mode 100644
index 0000000..1e341ac
--- /dev/null
+++ b/testing/resources/pixel/bug_1983_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1986.in b/testing/resources/pixel/bug_1986.in
new file mode 100644
index 0000000..9e35fb3
--- /dev/null
+++ b/testing/resources/pixel/bug_1986.in
@@ -0,0 +1,61 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Resources <<
+    /XObject <<
+      /Im0 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+612 0 0 792 0 0 cm
+/Im0 Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Filter [6 0 R /LZWDecode 7 0 R]
+  /Width 612
+  /Height 792
+  {{streamlen}}
+>>
+stream
+80002040c351404020068290e0a8100028663a1e4e06a380c8410d004522d0d16c68d10d0b1a4d06
+439408061881008c8000181c0f180003cc66f361c80330084358d1a31bfc9eff288005f189549a51
+30a24ae5947a44b00e0100d3ea3507f948000c86d400a040200002ff2e0002640201209050b35a2c
+f69b4bfe5625af10ce465309d0ca6410188f220279c0ca6e251408a47101d8ca72399a4de6e100c8
+5c33170c1fe9000432303faf3fd26cf5a14022502a2bdfe7f369a0b480980c12097d1801be801c05
+0005c57bf818daa6ee5fed9801
+endstream
+endobj
+{{object 6 0}}
+/ASCIIHexDecode
+enbobj
+{{object 7 0}}
+/JPXDecode
+enbobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1986_expected.pdf.0.png b/testing/resources/pixel/bug_1986_expected.pdf.0.png
new file mode 100644
index 0000000..e1b3d43
--- /dev/null
+++ b/testing/resources/pixel/bug_1986_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1995.in b/testing/resources/pixel/bug_1995.in
new file mode 100644
index 0000000..448fa58
--- /dev/null
+++ b/testing/resources/pixel/bug_1995.in
@@ -0,0 +1,81 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Group <<
+    /Type /Group
+    /S /Transparency
+  >>
+  /MediaBox [0 0 40 40]
+  /Resources <<
+    /ExtGState <<
+      /GSMask <<
+        /SMask <<
+          /Type /Mask
+          /S /Alpha
+          /G 5 0 R
+        >>
+      >>
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+  1 0 0 1 5 5 cm
+
+  % Red square.
+  1 0 0 rg
+  0 0 30 30 re
+  f
+
+  % Blue square with soft mask dictionary.
+  /GSMask gs
+  0 0 1 rg
+  0 0 30 30 re
+  f
+Q
+endstream
+endobj
+
+% Soft mask dictionary with a central hole.
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 30 30]
+  /Group <<
+    /Type /Group
+    /S /Transparency
+    /I true
+  >>
+  {{streamlen}}
+>>
+stream
+q
+  0 0 0 RG
+  15 w
+  0 0 30 30 re
+  S
+Q
+endstream
+endobj
+
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_1995_expected.pdf.0.png b/testing/resources/pixel/bug_1995_expected.pdf.0.png
new file mode 100644
index 0000000..daff3e9
--- /dev/null
+++ b/testing/resources/pixel/bug_1995_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_1995_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1995_expected_skia.pdf.0.png
new file mode 100644
index 0000000..97ac01f
--- /dev/null
+++ b/testing/resources/pixel/bug_1995_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_2001.pdf b/testing/resources/pixel/bug_2001.pdf
new file mode 100644
index 0000000..f73831d
--- /dev/null
+++ b/testing/resources/pixel/bug_2001.pdf
Binary files differ
diff --git a/testing/resources/pixel/bug_2001_expected.pdf.0.png b/testing/resources/pixel/bug_2001_expected.pdf.0.png
new file mode 100644
index 0000000..fd353a9
--- /dev/null
+++ b/testing/resources/pixel/bug_2001_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_237527_1.in b/testing/resources/pixel/bug_237527_1.in
new file mode 100644
index 0000000..503fa2a
--- /dev/null
+++ b/testing/resources/pixel/bug_237527_1.in
@@ -0,0 +1,37 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [4 0 R]
+  /MediaBox [0 0 300 300]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Link
+  /A <<
+    /Type /Action
+    /S /URI
+    /URI (www.google.com)
+  >>
+  /Border [0 0 1]
+  /C [0 1 1]
+  /H /I
+  /Rect [100 100 200 120]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_237527_1_expected.pdf.0.png b/testing/resources/pixel/bug_237527_1_expected.pdf.0.png
new file mode 100644
index 0000000..d8c8148
--- /dev/null
+++ b/testing/resources/pixel/bug_237527_1_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_237527_1_expected_skia.pdf.0.png b/testing/resources/pixel/bug_237527_1_expected_skia.pdf.0.png
new file mode 100644
index 0000000..eef577e
--- /dev/null
+++ b/testing/resources/pixel/bug_237527_1_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_237527_2.in b/testing/resources/pixel/bug_237527_2.in
new file mode 100644
index 0000000..fd4eefa
--- /dev/null
+++ b/testing/resources/pixel/bug_237527_2.in
@@ -0,0 +1,42 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [4 0 R]
+  /MediaBox [0 0 300 300]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Link
+  /A <<
+    /Type /Action
+    /S /URI
+    /URI (http://www.google.com)
+  >>
+  /BS <<
+    /Type /Border
+    /S /U
+    /W 1
+  >>
+  /Border [0 0 1]
+  /C [0 1 1]
+  /H /I
+  /Rect [100 100 200 120]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_237527_2_expected.pdf.0.png b/testing/resources/pixel/bug_237527_2_expected.pdf.0.png
new file mode 100644
index 0000000..14b04de
--- /dev/null
+++ b/testing/resources/pixel/bug_237527_2_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_304_expected.pdf.0.png b/testing/resources/pixel/bug_304_expected.pdf.0.png
index e1b3d43..62de87b 100644
--- a/testing/resources/pixel/bug_304_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_304_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_451366_expected_skia.pdf.0.png b/testing/resources/pixel/bug_451366_expected_skia.pdf.0.png
new file mode 100644
index 0000000..94b4524
--- /dev/null
+++ b/testing/resources/pixel/bug_451366_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_491_invisible_expected_skia.pdf.0.png b/testing/resources/pixel/bug_491_invisible_expected_skia.pdf.0.png
new file mode 100644
index 0000000..518a266
--- /dev/null
+++ b/testing/resources/pixel/bug_491_invisible_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_491_unspecified_expected_skia.pdf.0.png b/testing/resources/pixel/bug_491_unspecified_expected_skia.pdf.0.png
new file mode 100644
index 0000000..10665ac
--- /dev/null
+++ b/testing/resources/pixel/bug_491_unspecified_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_491_visible_expected_skia.pdf.0.png b/testing/resources/pixel/bug_491_visible_expected_skia.pdf.0.png
new file mode 100644
index 0000000..10665ac
--- /dev/null
+++ b/testing/resources/pixel/bug_491_visible_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_492.in b/testing/resources/pixel/bug_492.in
index 45c4a6f..e2922db 100644
--- a/testing/resources/pixel/bug_492.in
+++ b/testing/resources/pixel/bug_492.in
@@ -35,7 +35,7 @@
   /JS 21 0 R
 >>
 endobj
-% JS program to exexute
+% JS program to execute
 {{object 21 0}} <<
 >>
 stream
diff --git a/testing/resources/pixel/bug_524043_1_expected.pdf.0.png b/testing/resources/pixel/bug_524043_1_expected.pdf.0.png
index eabf957..a7e1fd7 100644
--- a/testing/resources/pixel/bug_524043_1_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_524043_1_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_1_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_524043_1_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..438e9f3
--- /dev/null
+++ b/testing/resources/pixel/bug_524043_1_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_1_expected_mac.pdf.0.png b/testing/resources/pixel/bug_524043_1_expected_mac.pdf.0.png
deleted file mode 100644
index b85b9d0..0000000
--- a/testing/resources/pixel/bug_524043_1_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_1_expected_win.pdf.0.png b/testing/resources/pixel/bug_524043_1_expected_win.pdf.0.png
deleted file mode 100644
index b2c134a..0000000
--- a/testing/resources/pixel/bug_524043_1_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_2_expected.pdf.0.png b/testing/resources/pixel/bug_524043_2_expected.pdf.0.png
index 04304fe..4dd2672 100644
--- a/testing/resources/pixel/bug_524043_2_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_524043_2_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_2_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_524043_2_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..e3bf21f
--- /dev/null
+++ b/testing/resources/pixel/bug_524043_2_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_2_expected_mac.pdf.0.png b/testing/resources/pixel/bug_524043_2_expected_mac.pdf.0.png
deleted file mode 100644
index ecede7d..0000000
--- a/testing/resources/pixel/bug_524043_2_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_2_expected_win.pdf.0.png b/testing/resources/pixel/bug_524043_2_expected_win.pdf.0.png
deleted file mode 100644
index 786d963..0000000
--- a/testing/resources/pixel/bug_524043_2_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_3_expected.pdf.0.png b/testing/resources/pixel/bug_524043_3_expected.pdf.0.png
index eabf957..a7e1fd7 100644
--- a/testing/resources/pixel/bug_524043_3_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_524043_3_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_3_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_524043_3_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..e794355
--- /dev/null
+++ b/testing/resources/pixel/bug_524043_3_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_3_expected_mac.pdf.0.png b/testing/resources/pixel/bug_524043_3_expected_mac.pdf.0.png
deleted file mode 100644
index b85b9d0..0000000
--- a/testing/resources/pixel/bug_524043_3_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_3_expected_win.pdf.0.png b/testing/resources/pixel/bug_524043_3_expected_win.pdf.0.png
deleted file mode 100644
index b2c134a..0000000
--- a/testing/resources/pixel/bug_524043_3_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_4_expected.pdf.0.png b/testing/resources/pixel/bug_524043_4_expected.pdf.0.png
index eabf957..a7e1fd7 100644
--- a/testing/resources/pixel/bug_524043_4_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_524043_4_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_4_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_524043_4_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..e794355
--- /dev/null
+++ b/testing/resources/pixel/bug_524043_4_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_4_expected_mac.pdf.0.png b/testing/resources/pixel/bug_524043_4_expected_mac.pdf.0.png
deleted file mode 100644
index b85b9d0..0000000
--- a/testing/resources/pixel/bug_524043_4_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_4_expected_win.pdf.0.png b/testing/resources/pixel/bug_524043_4_expected_win.pdf.0.png
deleted file mode 100644
index b2c134a..0000000
--- a/testing/resources/pixel/bug_524043_4_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_5_expected.pdf.0.png b/testing/resources/pixel/bug_524043_5_expected.pdf.0.png
index eabf957..a7e1fd7 100644
--- a/testing/resources/pixel/bug_524043_5_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_524043_5_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_5_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_524043_5_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..e794355
--- /dev/null
+++ b/testing/resources/pixel/bug_524043_5_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_5_expected_mac.pdf.0.png b/testing/resources/pixel/bug_524043_5_expected_mac.pdf.0.png
deleted file mode 100644
index b85b9d0..0000000
--- a/testing/resources/pixel/bug_524043_5_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_5_expected_win.pdf.0.png b/testing/resources/pixel/bug_524043_5_expected_win.pdf.0.png
deleted file mode 100644
index b2c134a..0000000
--- a/testing/resources/pixel/bug_524043_5_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_6_expected_mac.pdf.0.png b/testing/resources/pixel/bug_524043_6_expected_mac.pdf.0.png
deleted file mode 100644
index 3edcc2d..0000000
--- a/testing/resources/pixel/bug_524043_6_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_7_expected.pdf.0.png b/testing/resources/pixel/bug_524043_7_expected.pdf.0.png
index 04304fe..4dd2672 100644
--- a/testing/resources/pixel/bug_524043_7_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_524043_7_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_7_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_524043_7_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..e3bf21f
--- /dev/null
+++ b/testing/resources/pixel/bug_524043_7_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_7_expected_mac.pdf.0.png b/testing/resources/pixel/bug_524043_7_expected_mac.pdf.0.png
deleted file mode 100644
index ecede7d..0000000
--- a/testing/resources/pixel/bug_524043_7_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_524043_7_expected_win.pdf.0.png b/testing/resources/pixel/bug_524043_7_expected_win.pdf.0.png
deleted file mode 100644
index 786d963..0000000
--- a/testing/resources/pixel/bug_524043_7_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_528103_expected.pdf.0.png b/testing/resources/pixel/bug_528103_expected.pdf.0.png
index 5c0c254..79240dd 100644
--- a/testing/resources/pixel/bug_528103_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_528103_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_528103_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_528103_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..0d23b55
--- /dev/null
+++ b/testing/resources/pixel/bug_528103_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_528103_expected_mac.pdf.0.png b/testing/resources/pixel/bug_528103_expected_mac.pdf.0.png
deleted file mode 100644
index cb028f9..0000000
--- a/testing/resources/pixel/bug_528103_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_528103_expected_win.pdf.0.png b/testing/resources/pixel/bug_528103_expected_win.pdf.0.png
deleted file mode 100644
index 8ccda23..0000000
--- a/testing/resources/pixel/bug_528103_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_543018_1_expected.pdf.0.png b/testing/resources/pixel/bug_543018_1_expected.pdf.0.png
index eabf957..a7e1fd7 100644
--- a/testing/resources/pixel/bug_543018_1_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_543018_1_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_543018_1_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_543018_1_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..e794355
--- /dev/null
+++ b/testing/resources/pixel/bug_543018_1_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_543018_1_expected_mac.pdf.0.png b/testing/resources/pixel/bug_543018_1_expected_mac.pdf.0.png
deleted file mode 100644
index b85b9d0..0000000
--- a/testing/resources/pixel/bug_543018_1_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_543018_1_expected_win.pdf.0.png b/testing/resources/pixel/bug_543018_1_expected_win.pdf.0.png
deleted file mode 100644
index b2c134a..0000000
--- a/testing/resources/pixel/bug_543018_1_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_543018_2_expected.pdf.0.png b/testing/resources/pixel/bug_543018_2_expected.pdf.0.png
index eabf957..a7e1fd7 100644
--- a/testing/resources/pixel/bug_543018_2_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_543018_2_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_543018_2_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_543018_2_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..e794355
--- /dev/null
+++ b/testing/resources/pixel/bug_543018_2_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_543018_2_expected_mac.pdf.0.png b/testing/resources/pixel/bug_543018_2_expected_mac.pdf.0.png
deleted file mode 100644
index b85b9d0..0000000
--- a/testing/resources/pixel/bug_543018_2_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_543018_2_expected_win.pdf.0.png b/testing/resources/pixel/bug_543018_2_expected_win.pdf.0.png
deleted file mode 100644
index b2c134a..0000000
--- a/testing/resources/pixel/bug_543018_2_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_551258_1_expected.pdf.0.png b/testing/resources/pixel/bug_551258_1_expected.pdf.0.png
index eabf957..a7e1fd7 100644
--- a/testing/resources/pixel/bug_551258_1_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_551258_1_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_551258_1_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_551258_1_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..e794355
--- /dev/null
+++ b/testing/resources/pixel/bug_551258_1_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_551258_1_expected_mac.pdf.0.png b/testing/resources/pixel/bug_551258_1_expected_mac.pdf.0.png
deleted file mode 100644
index b85b9d0..0000000
--- a/testing/resources/pixel/bug_551258_1_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_551258_1_expected_win.pdf.0.png b/testing/resources/pixel/bug_551258_1_expected_win.pdf.0.png
deleted file mode 100644
index b2c134a..0000000
--- a/testing/resources/pixel/bug_551258_1_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_554151_expected.pdf.0.png b/testing/resources/pixel/bug_554151_expected.pdf.0.png
index 08c11b0..5b99a4d 100644
--- a/testing/resources/pixel/bug_554151_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_554151_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_557223.in b/testing/resources/pixel/bug_557223.in
index acb1ff2..c0c06b6 100644
--- a/testing/resources/pixel/bug_557223.in
+++ b/testing/resources/pixel/bug_557223.in
@@ -75,7 +75,7 @@
 trailer <<
     /Info 6 0 R
     /Root 1 0 R
-    /Size 7
+    {{trailersize}}
 >>
 {{startxref}}
 %%EOF
diff --git a/testing/resources/pixel/bug_585_expected_skia.pdf.0.png b/testing/resources/pixel/bug_585_expected_skia.pdf.0.png
new file mode 100644
index 0000000..c49e21e
--- /dev/null
+++ b/testing/resources/pixel/bug_585_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_591137_expected.pdf.0.png b/testing/resources/pixel/bug_591137_expected.pdf.0.png
index 265e682..74c5c64 100644
--- a/testing/resources/pixel/bug_591137_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_591137_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_601362_expected.pdf.0.png b/testing/resources/pixel/bug_601362_expected.pdf.0.png
index 5d868d5..49db8d9 100644
--- a/testing/resources/pixel/bug_601362_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_601362_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_601362_expected_skia.pdf.0.png b/testing/resources/pixel/bug_601362_expected_skia.pdf.0.png
new file mode 100644
index 0000000..d371b3a
--- /dev/null
+++ b/testing/resources/pixel/bug_601362_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_632_expected.pdf.0.png b/testing/resources/pixel/bug_632_expected.pdf.0.png
index 3807d93..4fc6081 100644
--- a/testing/resources/pixel/bug_632_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_632_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_632_expected.pdf.1.png b/testing/resources/pixel/bug_632_expected.pdf.1.png
index 141f3d8..ee11ce6 100644
--- a/testing/resources/pixel/bug_632_expected.pdf.1.png
+++ b/testing/resources/pixel/bug_632_expected.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/bug_632_expected_skia.pdf.1.png b/testing/resources/pixel/bug_632_expected_skia.pdf.1.png
new file mode 100644
index 0000000..002dcba
--- /dev/null
+++ b/testing/resources/pixel/bug_632_expected_skia.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/bug_660850_expected_skia.pdf.0.png b/testing/resources/pixel/bug_660850_expected_skia.pdf.0.png
new file mode 100644
index 0000000..254f45b
--- /dev/null
+++ b/testing/resources/pixel/bug_660850_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_665467.in b/testing/resources/pixel/bug_665467.in
index 6ef2c1c..289e8de 100644
--- a/testing/resources/pixel/bug_665467.in
+++ b/testing/resources/pixel/bug_665467.in
@@ -4,96 +4,66 @@
   /Pages 2 0 R
 >>
 endobj
-
 {{object 2 0}} <<
   /Type /Pages
-  /MediaBox [ 0 0 100 100 ]
   /Count 1
-  /Kids [ 3 0 R ]
+  /Kids [3 0 R]
+  /MediaBox [0 0 100 100]
 >>
 endobj
-
 {{object 3 0}} <<
   /Type /Page
   /Parent 2 0 R
-  /Resources
-  <<
-    /Font << /F1 4 0 R >>
+  /Contents 4 0 R
+  /Resources <<
+    /Font <<
+      /F1 5 0 R
+    >>
   >>
-  /Contents 8 0 R
 >>
 endobj
-
 {{object 4 0}} <<
-  /Type /Font
-  /Subtype /TrueType
-  /BaseFont /ChromeSansMM
-  /Encoding 5 0 R
-  /FirstChar 32
-  /LastChar 255
-  /Name /F1
-  /ToUnicode 6 0 R
-  /FontDescriptor 7 0 R
->>
-endobj
-
-{{object 5 0}} <<
-  /Differences [ 161 /someunknownname ]
-  /Type /Encoding
->>
-endobj
-
-{{object 6 0}} <<
->>
-stream
-/CIDInit /ProcSet findresource begin
-12 dict begin
-begincmap
-/CIDSystemInfo
-<</Registry (Adobe)
-/Ordering (Identity)
-/Supplement 0
->> def
-/CMapName /Adobe-Identity-H def
-CMapType 2 def
-1 begincodespacerange
-<00> <FF>
-endcodespacerange
-1 beginbfchar
-<A1> <043B>
-endbfchar
-endcmap
-CMapName currentdict /CMap defineresource pop
-end
-end
-endstream
-endobj
-
-{{object 7 0}} <<
-  << /Ascent 1000
-  /CapHeight 0
-  /Descent -200
-  /Flags 32
-  /FontBBox [ -599 -207 1338 1034 ]
-  /FontName /ChromeSansMM
-  /ItalicAngle 0
-  /StemV 0
-  /Type /FontDescriptor
->>
-endobj
-
-{{object 8 0}} <<
+  {{streamlen}}
 >>
 stream
 BT
-50 50 Td /F1 15 Tf <A1> Tj
+50 50 Td
+/F1 15 Tf
+[<8C>]TJ
 ET
 endstream
 endobj
-
-{{xref}}
-trailer <<
-  /Root 1 0 R
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /TrueType
+  /BaseFont /Tahoma
+  /Encoding 6 0 R
+  /FirstChar 139
+  /FontDescriptor 7 0 R
+  /LastChar 140
+  /Widths [943 677]
 >>
+endobj
+{{object 6 0}} <<
+  /Type /Encoding
+  /Differences [
+    140 /Elcyrillic
+  ]
+>>
+endobj
+{{object 7 0}} <<
+  /Type /FontDescriptor
+  /Ascent 1034
+  /CapHeight 0
+  /Descent -207
+  /Flags 32
+  /FontBBox [-599 -207 1338 1034]
+  /FontName /Tahoma
+  /ItalicAngle 0
+  /StemV 0
+>>
+endobj
+{{xref}}
+{{trailer}}
 {{startxref}}
 %%EOF
diff --git a/testing/resources/pixel/bug_665467_expected.pdf.0.png b/testing/resources/pixel/bug_665467_expected.pdf.0.png
index e3b37b4..4532565 100644
--- a/testing/resources/pixel/bug_665467_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_665467_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_665467_expected_mac.pdf.0.png b/testing/resources/pixel/bug_665467_expected_mac.pdf.0.png
index 9f51dcb..0faddf8 100644
--- a/testing/resources/pixel/bug_665467_expected_mac.pdf.0.png
+++ b/testing/resources/pixel/bug_665467_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_665467_expected_skia.pdf.0.png b/testing/resources/pixel/bug_665467_expected_skia.pdf.0.png
new file mode 100644
index 0000000..2cd13ff
--- /dev/null
+++ b/testing/resources/pixel/bug_665467_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_665467_expected_win.pdf.0.png b/testing/resources/pixel/bug_665467_expected_win.pdf.0.png
deleted file mode 100644
index 8feb717..0000000
--- a/testing/resources/pixel/bug_665467_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_714187_expected_skia.pdf.0.png b/testing/resources/pixel/bug_714187_expected_skia.pdf.0.png
new file mode 100644
index 0000000..80cac57
--- /dev/null
+++ b/testing/resources/pixel/bug_714187_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_725389.in b/testing/resources/pixel/bug_725389.in
new file mode 100644
index 0000000..0d0818b
--- /dev/null
+++ b/testing/resources/pixel/bug_725389.in
@@ -0,0 +1,58 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /AcroForm <<
+    /Fields [4 0 R]
+  >>
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Annots [4 0 R]
+  /Parent 2 0 R
+  /MediaBox [0 0 200 100]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /BS <<
+    /S /S
+    /W 1
+  >>
+  /DA (/Times-Roman 12 Tf 0 0 0 rg)
+  /DR <<
+    /Font <<
+      /Times-Roman 5 0 R
+    >>
+  >>
+  /DV ()
+  /F 4
+  /Ff 0
+  /MK <<
+    /BG [1 1 1]
+  >>
+  /P 3 0 R
+  /T (Default)
+  /V (\376\377\5\321\5\327\5\350\0.\0.\0.)
+  /Rect [50 40 150 70]
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_725389_expected.pdf.0.png b/testing/resources/pixel/bug_725389_expected.pdf.0.png
new file mode 100644
index 0000000..6da1b77
--- /dev/null
+++ b/testing/resources/pixel/bug_725389_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_725389_expected_mac.pdf.0.png b/testing/resources/pixel/bug_725389_expected_mac.pdf.0.png
new file mode 100644
index 0000000..453c2c3
--- /dev/null
+++ b/testing/resources/pixel/bug_725389_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_725389_expected_skia.pdf.0.png b/testing/resources/pixel/bug_725389_expected_skia.pdf.0.png
new file mode 100644
index 0000000..25ad890
--- /dev/null
+++ b/testing/resources/pixel/bug_725389_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_725389_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_725389_expected_skia_mac.pdf.0.png
new file mode 100644
index 0000000..4a5a4b0
--- /dev/null
+++ b/testing/resources/pixel/bug_725389_expected_skia_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_733528_expected.pdf.0.png b/testing/resources/pixel/bug_733528_expected.pdf.0.png
index eaf48b5..5e178da 100644
--- a/testing/resources/pixel/bug_733528_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_733528_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_733528_expected_mac.pdf.0.png b/testing/resources/pixel/bug_733528_expected_mac.pdf.0.png
index f7c8302..992e488 100644
--- a/testing/resources/pixel/bug_733528_expected_mac.pdf.0.png
+++ b/testing/resources/pixel/bug_733528_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_733528_expected_skia.pdf.0.png b/testing/resources/pixel/bug_733528_expected_skia.pdf.0.png
new file mode 100644
index 0000000..6dfa99d
--- /dev/null
+++ b/testing/resources/pixel/bug_733528_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_733528_expected_win.pdf.0.png b/testing/resources/pixel/bug_733528_expected_win.pdf.0.png
deleted file mode 100644
index a44b932..0000000
--- a/testing/resources/pixel/bug_733528_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_736695_1_expected.pdf.0.png b/testing/resources/pixel/bug_736695_1_expected.pdf.0.png
index 99d7bd7..10bbb0d 100644
--- a/testing/resources/pixel/bug_736695_1_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_736695_1_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_736695_1_expected_skia.pdf.0.png b/testing/resources/pixel/bug_736695_1_expected_skia.pdf.0.png
new file mode 100644
index 0000000..ecb7d2b
--- /dev/null
+++ b/testing/resources/pixel/bug_736695_1_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_736695_2_expected.pdf.0.png b/testing/resources/pixel/bug_736695_2_expected.pdf.0.png
index 33ea077..4fe962d 100644
--- a/testing/resources/pixel/bug_736695_2_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_736695_2_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_736695_2_expected_mac.pdf.0.png b/testing/resources/pixel/bug_736695_2_expected_mac.pdf.0.png
index 46c7085..e46e02d 100644
--- a/testing/resources/pixel/bug_736695_2_expected_mac.pdf.0.png
+++ b/testing/resources/pixel/bug_736695_2_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_736695_2_expected_skia.pdf.0.png b/testing/resources/pixel/bug_736695_2_expected_skia.pdf.0.png
new file mode 100644
index 0000000..801b157
--- /dev/null
+++ b/testing/resources/pixel/bug_736695_2_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_736695_2_expected_win.pdf.0.png b/testing/resources/pixel/bug_736695_2_expected_win.pdf.0.png
deleted file mode 100644
index ffa94f0..0000000
--- a/testing/resources/pixel/bug_736695_2_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_736695_3_expected.pdf.0.png b/testing/resources/pixel/bug_736695_3_expected.pdf.0.png
index cbffe0a..13c9d0a 100644
--- a/testing/resources/pixel/bug_736695_3_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_736695_3_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_736695_3_expected_mac.pdf.0.png b/testing/resources/pixel/bug_736695_3_expected_mac.pdf.0.png
index 1abcd18..f15d038 100644
--- a/testing/resources/pixel/bug_736695_3_expected_mac.pdf.0.png
+++ b/testing/resources/pixel/bug_736695_3_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_736695_3_expected_skia.pdf.0.png b/testing/resources/pixel/bug_736695_3_expected_skia.pdf.0.png
new file mode 100644
index 0000000..1b02f1b
--- /dev/null
+++ b/testing/resources/pixel/bug_736695_3_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_736695_3_expected_win.pdf.0.png b/testing/resources/pixel/bug_736695_3_expected_win.pdf.0.png
deleted file mode 100644
index d27a9b4..0000000
--- a/testing/resources/pixel/bug_736695_3_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_736695_4_expected.pdf.0.png b/testing/resources/pixel/bug_736695_4_expected.pdf.0.png
index 99d7bd7..10bbb0d 100644
--- a/testing/resources/pixel/bug_736695_4_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_736695_4_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_736695_4_expected_skia.pdf.0.png b/testing/resources/pixel/bug_736695_4_expected_skia.pdf.0.png
new file mode 100644
index 0000000..ecb7d2b
--- /dev/null
+++ b/testing/resources/pixel/bug_736695_4_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_736703.in b/testing/resources/pixel/bug_736703.in
new file mode 100644
index 0000000..05c4a35
--- /dev/null
+++ b/testing/resources/pixel/bug_736703.in
@@ -0,0 +1,73 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /Font <<
+      /F4 5 0 R
+    >>
+  >>
+  /MediaBox [0 0 200 200]
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+BT
+/F4 30 Tf
+0 0 0 rg
+1 0 0 1 30 100 Tm
+[(honorably)]TJ
+ET
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /AGaramond-Regular
+  /Encoding /WinAnsiEncoding
+  /FirstChar 32
+  /FontDescriptor 6 0 R
+  /LastChar 125
+  /Widths [250 220 404 500 500 844 818 235 320 320 394 500 250 320 250 327 500
+   500 500 500 500 500 500 500 500 500 250 250 500 500 500 321 765 623 605 696
+   780 584 538 747 806 338 345 675 553 912 783 795 549 795 645 489 660 746 676
+   960 643 574 641 320 309 320 500 500 360 404 500 400 509 396 290 446 515 257
+   253 482 247 787 525 486 507 497 332 323 307 512 432 660 432 438 377 320 239
+   320]
+>>
+endobj
+{{object 6 0}} <<
+  /Type /FontDescriptor
+  /Ascent 720
+  /CapHeight 663
+  /Descent -270
+  /Flags 34
+  /FontBBox [-183 -269 1099 851]
+  /FontName /AGaramond-Regular
+  /ItalicAngle 0
+  /StemH 40
+  /StemV 74
+  /XHeight 397
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_736703_expected.pdf.0.png b/testing/resources/pixel/bug_736703_expected.pdf.0.png
new file mode 100644
index 0000000..f2fe3a1
--- /dev/null
+++ b/testing/resources/pixel/bug_736703_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_820345_expected.pdf.0.png b/testing/resources/pixel/bug_820345_expected.pdf.0.png
index e3c8e24..e89a680 100644
--- a/testing/resources/pixel/bug_820345_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_820345_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_820345_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_820345_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..6013c5c
--- /dev/null
+++ b/testing/resources/pixel/bug_820345_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_820345_expected_mac.pdf.0.png b/testing/resources/pixel/bug_820345_expected_mac.pdf.0.png
deleted file mode 100644
index 6e79402..0000000
--- a/testing/resources/pixel/bug_820345_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_820345_expected_win.pdf.0.png b/testing/resources/pixel/bug_820345_expected_win.pdf.0.png
deleted file mode 100644
index c3b8b43..0000000
--- a/testing/resources/pixel/bug_820345_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_842.in b/testing/resources/pixel/bug_842.in
index 255a902..71f0104 100644
--- a/testing/resources/pixel/bug_842.in
+++ b/testing/resources/pixel/bug_842.in
@@ -24,7 +24,7 @@
 >>
 endobj
 {{object 4 0}} <<
-{{streamlen}}
+  {{streamlen}}
 >>
 stream
 q
diff --git a/testing/resources/pixel/bug_842_expected_skia.pdf.0.png b/testing/resources/pixel/bug_842_expected_skia.pdf.0.png
new file mode 100644
index 0000000..eb10aa7
--- /dev/null
+++ b/testing/resources/pixel/bug_842_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_843_expected_skia.pdf.0.png b/testing/resources/pixel/bug_843_expected_skia.pdf.0.png
new file mode 100644
index 0000000..f0c8193
--- /dev/null
+++ b/testing/resources/pixel/bug_843_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_845697_expected.pdf.0.png b/testing/resources/pixel/bug_845697_expected.pdf.0.png
index 3bc224b..15ec4dd 100644
--- a/testing/resources/pixel/bug_845697_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_845697_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_846.in b/testing/resources/pixel/bug_846.in
new file mode 100644
index 0000000..e405d67
--- /dev/null
+++ b/testing/resources/pixel/bug_846.in
@@ -0,0 +1,160 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources 4 0 R
+  /Contents 15 0 R
+  /MediaBox [0 0 200 200]
+>>
+endobj
+{{object 4 0}} <<
+  /ProcSet [/PDF /Text]
+  /Font <<
+    /F1 5 0 R
+    /F2 7 0 R
+    /F3 9 0 R
+    /F4 11 0 R
+  >>
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /TrueType
+  /BaseFont /Arial-Bold
+  /Encoding /WinAnsiEncoding
+  /FirstChar 65
+  /FontDescriptor 6 0 R
+  /LastChar 65
+  /Widths [778]
+>>
+endobj
+{{object 6 0}} <<
+  /Type /FontDescriptor
+  /Ascent 923
+  /CapHeight 687
+  /Descent -282
+  /Flags 34
+  /FontName /Arial-Bold
+  /FontBBox 14 0 R
+  /ItalicAngle 0
+  /StemV 140.269
+  /XHeight 0
+>>
+endobj
+{{object 7 0}} <<
+  /Type /Font
+  /Subtype /TrueType
+  /BaseFont /Arial-Bold,Bold
+  /Encoding /WinAnsiEncoding
+  /FirstChar 65
+  /FontDescriptor 8 0 R
+  /LastChar 65
+  /Widths [778]
+>>
+endobj
+{{object 8 0}} <<
+  /Type /FontDescriptor
+  /Ascent 923
+  /CapHeight 687
+  /Descent -282
+  /Flags 34
+  /FontName /Arial-Bold,Bold
+  /FontBBox 14 0 R
+  /ItalicAngle 0
+  /StemV 140.269
+  /XHeight 0
+>>
+endobj
+{{object 9 0}} <<
+  /Type /Font
+  /Subtype /TrueType
+  /BaseFont /DejaVuSans-Bold
+  /Encoding /WinAnsiEncoding
+  /FirstChar 58
+  /FontDescriptor 10 0 R
+  /LastChar 65
+  /Widths 13 0 R
+>>
+endobj
+{{object 10 0}} <<
+  /Type /FontDescriptor
+  /Ascent 923
+  /CapHeight 687
+  /Descent -282
+  /Flags 34
+  /FontName /DejaVuSans-Bold
+  /FontBBox 14 0 R
+  /ItalicAngle 0
+  /StemV 140.269
+  /XHeight 0
+>>
+endobj
+{{object 11 0}} <<
+  /Type /Font
+  /Subtype /TrueType
+  /BaseFont /DejaVuSans-Bold,Bold
+  /Encoding /WinAnsiEncoding
+  /FirstChar 58
+  /FontDescriptor 12 0 R
+  /LastChar 65
+  /Widths 13 0 R
+>>
+endobj
+{{object 12 0}} <<
+  /Type /FontDescriptor
+  /Ascent 923
+  /CapHeight 687
+  /Descent -282
+  /Flags 34
+  /FontName /DejaVuSans-Bold,Bold
+  /FontBBox 14 0 R
+  /ItalicAngle 0
+  /StemV 140.269
+  /XHeight 0
+>>
+endobj
+{{object 13 0}} [250 0 0 0 0 0 0 778]
+endobj
+{{object 14 0}} [-134 -337 1193 1021]
+endobj
+{{object 15 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+/F1 2 Tf
+18 0 0 18 50 120 Tm
+(A)Tj
+ET
+BT
+/F2 2 Tf
+18 0 0 18 120 120 Tm
+(A)Tj
+ET
+BT
+/F3 2 Tf
+18 0 0 18 50 60 Tm
+(A)Tj
+ET
+BT
+/F4 2 Tf
+18 0 0 18 120 60 Tm
+(A)Tj
+ET
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_846_expected.pdf.0.png b/testing/resources/pixel/bug_846_expected.pdf.0.png
new file mode 100644
index 0000000..1ff3944
--- /dev/null
+++ b/testing/resources/pixel/bug_846_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_846_expected_mac.pdf.0.png b/testing/resources/pixel/bug_846_expected_mac.pdf.0.png
new file mode 100644
index 0000000..b405dce
--- /dev/null
+++ b/testing/resources/pixel/bug_846_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_846_expected_skia.pdf.0.png b/testing/resources/pixel/bug_846_expected_skia.pdf.0.png
new file mode 100644
index 0000000..f7fa9ff
--- /dev/null
+++ b/testing/resources/pixel/bug_846_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_846_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_846_expected_skia_mac.pdf.0.png
new file mode 100644
index 0000000..5635527
--- /dev/null
+++ b/testing/resources/pixel/bug_846_expected_skia_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_846_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_846_expected_skia_win.pdf.0.png
new file mode 100644
index 0000000..5635527
--- /dev/null
+++ b/testing/resources/pixel/bug_846_expected_skia_win.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_909762_expected.pdf.0.png b/testing/resources/pixel/bug_909762_expected.pdf.0.png
index 219c163..d01a7fc 100644
--- a/testing/resources/pixel/bug_909762_expected.pdf.0.png
+++ b/testing/resources/pixel/bug_909762_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_909762_expected_win.pdf.0.png b/testing/resources/pixel/bug_909762_expected_win.pdf.0.png
deleted file mode 100644
index c6d2727..0000000
--- a/testing/resources/pixel/bug_909762_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_925736_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_925736_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..1763451
--- /dev/null
+++ b/testing/resources/pixel/bug_925736_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_925736_expected_mac.pdf.0.png b/testing/resources/pixel/bug_925736_expected_mac.pdf.0.png
deleted file mode 100644
index f71efde..0000000
--- a/testing/resources/pixel/bug_925736_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_966263.in b/testing/resources/pixel/bug_966263.in
new file mode 100644
index 0000000..bddf328
--- /dev/null
+++ b/testing/resources/pixel/bug_966263.in
@@ -0,0 +1,132 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 1 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 200 400]
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /Font <<
+      /F1 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+/F1 1 Tf
+1 0 0 1 20 55 Tm
+(ABCD)Tj
+ET
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type3
+  /FontBBox [0 0 600 600]
+  /FontMatrix [1 0 0 -100 0 0]
+  /CharProcs <<
+    /CA 6 0 R
+    /CB 7 0 R
+    /CC 8 0 R
+    /CD 9 0 R
+  >>
+  /Encoding <<
+    /Type /Encoding
+    /Differences [65 /CA 66 /CB 67 /CC 68 /CD]
+  >>
+  /FirstChar 65
+  /LastChar 68
+  /Widths [18 1000 18 18]
+  /Resources <<
+    /ProcSet [/PDF /ImageB]
+  >>
+>>
+endobj
+{{object 6 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+d0 0 0
+1 0 0 1 6 0 cm
+BI
+/W 1
+/H 1
+/BPC 1
+/IM true
+ID
+x
+EI
+Q
+endstream
+endobj
+{{object 7 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+6 0 0 48 6 0 cm
+BI
+/W 6
+/H 48
+/BPC 1
+/IM true
+ID
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+EI
+Q
+endstream
+endobj
+{{object 8 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+6 0 0 1 2147483570 0 cm
+BI
+/W 6
+/H 1
+/BPC 1
+/IM true
+ID
+x
+EI
+Q
+endstream
+endobj
+{{object 9 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+1 0 0 6 0 21474834 cm
+BI
+/W 1
+/H 1
+/BPC 1
+/IM true
+ID
+x
+EI
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_966263_expected.pdf.0.png b/testing/resources/pixel/bug_966263_expected.pdf.0.png
new file mode 100644
index 0000000..84650b6
--- /dev/null
+++ b/testing/resources/pixel/bug_966263_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_972999.in b/testing/resources/pixel/bug_972999.in
new file mode 100644
index 0000000..6e928da
--- /dev/null
+++ b/testing/resources/pixel/bug_972999.in
@@ -0,0 +1,603 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 6 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 7 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 7 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 8 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 8 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 9 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 9 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 10 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 10 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 11 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 11 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 12 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 12 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 13 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 13 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 14 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 14 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 15 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 15 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 16 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 16 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 17 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 17 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 18 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 18 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 19 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 19 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 20 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 20 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 21 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 21 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 22 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 22 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 23 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 23 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 24 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 24 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 25 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 25 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 26 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 26 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 27 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 27 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 28 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 28 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 29 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 29 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 30 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 30 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 31 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 31 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 32 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 32 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 33 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 33 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 34 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 34 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 35 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 35 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 36 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 36 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 37 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 37 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 38 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 38 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  /Resources <<
+    /XObject <<
+      /X1 39 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+/X1 Do
+endstream
+endobj
+{{object 39 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 200 300]
+  {{streamlen}}
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_972999_expected.pdf.0.png b/testing/resources/pixel/bug_972999_expected.pdf.0.png
new file mode 100644
index 0000000..ee652fa
--- /dev/null
+++ b/testing/resources/pixel/bug_972999_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_972999_expected_skia.pdf.0.png b/testing/resources/pixel/bug_972999_expected_skia.pdf.0.png
new file mode 100644
index 0000000..8a6b8ed
--- /dev/null
+++ b/testing/resources/pixel/bug_972999_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_983289.in b/testing/resources/pixel/bug_983289.in
new file mode 100644
index 0000000..438af5d
--- /dev/null
+++ b/testing/resources/pixel/bug_983289.in
@@ -0,0 +1,35 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+2 0 0 2 0 0 cm
+-10926 -22396 m
+11272 23097 l
+S
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_983289_expected.pdf.0.png b/testing/resources/pixel/bug_983289_expected.pdf.0.png
new file mode 100644
index 0000000..27049d5
--- /dev/null
+++ b/testing/resources/pixel/bug_983289_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_984811_expected_agg_mac.pdf.0.png b/testing/resources/pixel/bug_984811_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..4a217fa
--- /dev/null
+++ b/testing/resources/pixel/bug_984811_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/bug_984811_expected_mac.pdf.0.png b/testing/resources/pixel/bug_984811_expected_mac.pdf.0.png
deleted file mode 100644
index e1ae9f2..0000000
--- a/testing/resources/pixel/bug_984811_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/bug_986108.in b/testing/resources/pixel/bug_986108.in
new file mode 100644
index 0000000..7af65f4
--- /dev/null
+++ b/testing/resources/pixel/bug_986108.in
@@ -0,0 +1,122 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 200 200]
+  /Contents 4 0 R
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /ExtGState <<
+      /GS0 5 0 R
+    >>
+    /XObject <<
+      /Fm0 6 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+/GS0 gs
+/Fm0 Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /ExtGState
+  /OP false
+  /op false
+  /AIS false
+  /BM /Normal
+  /OPM 0
+  /CA 1.0
+  /SA true
+  /ca 1.0
+  /SMask 7 0 R
+>>
+endobj
+{{object 6 0}} <<
+  /Subtype /Form
+  {{streamlen}}
+>>
+stream
+q
+0 0 50 60 re f
+Q
+endstream
+endobj
+{{object 7 0}} <<
+  /Type /Mask
+  /BC [0.0]
+  /S /Luminosity
+  /G 8 0 R
+>>
+endobj
+{{object 8 0}} <<
+  /Subtype /Form
+  /BBox [0 411 621 -10]
+  /Resources <<
+    /XObject <<
+      /Fm0 9 0 R
+    >>
+  >>
+{{streamlen}}
+>>
+stream
+q
+0 410 500 -440 re f
+/Fm0 Do
+Q
+endstream
+endobj
+{{object 9 0}} <<
+  /Subtype /Form
+  /Resources <<
+    /Shading <<
+      /Sh0 10 0 R
+    >>
+  >>
+  {{streamlen}}
+>>
+stream
+q
+/Sh0 sh
+Q
+endstream
+endobj
+{{object 10 0}} <<
+  /ShadingType 2
+  /ColorSpace /DeviceRGB
+  /Coords [0.0 0.0 1.0 0.0]
+  /BBox [-0.1 -0.9 1.1 0.87]
+  /Extend [true true]
+  /Function 11 0 R
+  /Domain [0.0 1.0]
+>>
+endobj
+{{object 11 0}} <<
+  /FunctionType 2
+  /Domain [0.0 1.0]
+  /C0 [0.8 0.1 0.2]
+  /C1 [0.2 0.025 0.05]
+  /N 1
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/bug_986108_expected.pdf.0.png b/testing/resources/pixel/bug_986108_expected.pdf.0.png
new file mode 100644
index 0000000..f97e340
--- /dev/null
+++ b/testing/resources/pixel/bug_986108_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/checkbox_radiobutton.evt b/testing/resources/pixel/checkbox_radiobutton.evt
new file mode 100644
index 0000000..c519dcb
--- /dev/null
+++ b/testing/resources/pixel/checkbox_radiobutton.evt
@@ -0,0 +1,11 @@
+# Check the checkbox
+mousemove,145,220
+mousedown,left,145,220
+mouseup,left,145,220
+
+# Tab to radio button from checkbox
+keycode,9
+keycode,9
+
+# Press Enter to select radio button
+charcode,13
\ No newline at end of file
diff --git a/testing/resources/pixel/checkbox_radiobutton.fragment b/testing/resources/pixel/checkbox_radiobutton.fragment
new file mode 100644
index 0000000..013992e
--- /dev/null
+++ b/testing/resources/pixel/checkbox_radiobutton.fragment
@@ -0,0 +1,187 @@
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 300 300]
+  /Contents 4 0 R
+  /Annots [5 0 R 6 0 R 8 0 R 10 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+q
+1 w
+1 0 1 RG
+1 .752941 .796078 rg
+n 135.5 250.5 19 19 re B*
+Q
+q
+2 w
+.1 .1 .1 RG
+.8 .843 1 rg
+n 136 211 18 18 re B*
+Q
+q
+2 w
+0 .501961 0 RG
+0 0 1 rg
+n
+104 110 m
+104 114.9706 99.97056 119 95 119 c
+90.02944 119 86 114.9706 86 110 c
+86 105.0294 90.02944 101 95 101 c
+99.97056 101 104 105.0294 104 110 c
+B*
+Q
+q
+2 w
+0 .501961 0 RG
+0 0 1 rg
+n
+104 60 m
+104 64.97056 99.97056 69 95 69 c
+90.02944 69 86 64.97056 86 60 c
+86 55.02944 90.02944 51 95 51 c
+99.97056 51 104 55.02944 104 60 c
+B*
+Q
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /Ff 1
+  /Rect [135 250 155 270]
+  /T (readOnlyCheckbox)
+  /AP <<
+    /N <<
+      /Yes 11 0 R
+    >>
+  >>
+  /AS /Off
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /Ff 2
+  /Rect [135 210 155 230]
+  /T (checkbox)
+  /AP <<
+    /N <<
+      /Yes 11 0 R
+    >>
+  >>
+  /AS /Off
+>>
+endobj
+{{object 7 0}} <<
+  /FT /Btn
+  /Ff 49153
+  /T (readOnlyRadioButton)
+  /Kids [8 0 R]
+>>
+endobj
+{{object 8 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /T (Widget8)
+  /FT /Btn
+  /Parent 7 0 R
+  /Rect [85 100 105 120]
+  /AP <<
+    /N <<
+      /value1 12 0 R
+    >>
+  >>
+  /AS /Off
+>>
+endobj
+{{object 9 0}} <<
+  /FT /Btn
+  /Ff 49154
+  /T (radioButton)
+  /Kids [10 0 R]
+>>
+endobj
+{{object 10 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /Parent 9 0 R
+  /Rect [85 50 105 70]
+  /AP <<
+    /N <<
+      /value1 12 0 R
+    >>
+  >>
+  /AS /Off
+>>
+endobj
+{{object 11 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 20 20]
+  {{streamlen}}
+>>
+stream
+q
+.1 .1 .1 rg .1 .1 .1 RG
+17.2 15.95145 m
+11.20694 10 l
+17.2 4.027746 l
+15.97225 2.8 l
+10 8.793064 l
+4.027746 2.8 l
+2.8 4.027746 l
+8.813873 10 l
+2.8 15.97225 l
+4.027746 17.2 l
+10 11.20694 l
+15.97225 17.2 l
+17.2 15.95145 l
+h
+f
+Q
+endstream
+endobj
+{{object 12 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 20 20]
+  {{streamlen}}
+>>
+stream
+q
+1 .752941 .796078 rg 1 .752941 .796078 RG
+17.2 15.95145 m
+11.20694 10 l
+17.2 4.027746 l
+15.97225 2.8 l
+10 8.793064 l
+4.027746 2.8 l
+2.8 4.027746 l
+8.813873 10 l
+2.8 15.97225 l
+4.027746 17.2 l
+10 11.20694 l
+15.97225 17.2 l
+17.2 15.95145 l
+h
+f
+Q
+endstream
+endobj
diff --git a/testing/resources/pixel/checkbox_radiobutton.in b/testing/resources/pixel/checkbox_radiobutton.in
new file mode 100644
index 0000000..116c638
--- /dev/null
+++ b/testing/resources/pixel/checkbox_radiobutton.in
@@ -0,0 +1,14 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [5 0 R 6 0 R 7 0 R 9 0 R]
+  >>
+>>
+endobj
+{{include checkbox_radiobutton.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/checkbox_radiobutton_expected.pdf.0.png b/testing/resources/pixel/checkbox_radiobutton_expected.pdf.0.png
new file mode 100644
index 0000000..8baabb7
--- /dev/null
+++ b/testing/resources/pixel/checkbox_radiobutton_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/checkbox_radiobutton_expected_skia.pdf.0.png b/testing/resources/pixel/checkbox_radiobutton_expected_skia.pdf.0.png
new file mode 100644
index 0000000..fe713a6
--- /dev/null
+++ b/testing/resources/pixel/checkbox_radiobutton_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/checkbox_radiobutton_hide.in b/testing/resources/pixel/checkbox_radiobutton_hide.in
new file mode 100644
index 0000000..340b6d5
--- /dev/null
+++ b/testing/resources/pixel/checkbox_radiobutton_hide.in
@@ -0,0 +1,23 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [5 0 R 6 0 R 7 0 R 9 0 R]
+  >>
+  /OpenAction 13 0 R
+>>
+endobj
+{{include checkbox_radiobutton.fragment}}
+% Make some buttons be hidden. Note that /T as an array of dict references
+% does not seem to work as expected.
+{{object 13 0}} <<
+  /Type /Action
+  /S /Hide
+  /T [(readOnlyRadioButton.Widget8) (readOnlyCheckbox)]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/checkbox_radiobutton_hide_expected.pdf.0.png b/testing/resources/pixel/checkbox_radiobutton_hide_expected.pdf.0.png
new file mode 100644
index 0000000..361c05f
--- /dev/null
+++ b/testing/resources/pixel/checkbox_radiobutton_hide_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/checkbox_radiobutton_hide_expected_skia.pdf.0.png b/testing/resources/pixel/checkbox_radiobutton_hide_expected_skia.pdf.0.png
new file mode 100644
index 0000000..bdb2d6e
--- /dev/null
+++ b/testing/resources/pixel/checkbox_radiobutton_hide_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/checkbox_radiobutton_reset.in b/testing/resources/pixel/checkbox_radiobutton_reset.in
new file mode 100644
index 0000000..869107a
--- /dev/null
+++ b/testing/resources/pixel/checkbox_radiobutton_reset.in
@@ -0,0 +1,25 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [5 0 R 6 0 R 7 0 R 9 0 R]
+  >>
+  /OpenAction 13 0 R
+>>
+endobj
+{{include checkbox_radiobutton.fragment}}
+% Make some buttons be reset. Note that /T as an array of dict references
+% does not seem to work as expected. /Flags 1 means all fields *except*
+% those listed.
+{{object 13 0}} <<
+  /Type /Action
+  /S /ResetForm
+  /Fields [(readOnlyRadioButton.Widget8) (readOnlyCheckbox)]
+  /Flags 1
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/checkbox_radiobutton_reset_expected.pdf.0.png b/testing/resources/pixel/checkbox_radiobutton_reset_expected.pdf.0.png
new file mode 100644
index 0000000..dd94188
--- /dev/null
+++ b/testing/resources/pixel/checkbox_radiobutton_reset_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/checkbox_radiobutton_reset_expected_skia.pdf.0.png b/testing/resources/pixel/checkbox_radiobutton_reset_expected_skia.pdf.0.png
new file mode 100644
index 0000000..057ecda
--- /dev/null
+++ b/testing/resources/pixel/checkbox_radiobutton_reset_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/combobox_form.evt b/testing/resources/pixel/combobox_form.evt
new file mode 100644
index 0000000..8211937
--- /dev/null
+++ b/testing/resources/pixel/combobox_form.evt
@@ -0,0 +1,10 @@
+# Check the combobox control
+mousemove,102,410
+mousedown,left,102,410
+mouseup,left,102,410
+
+# Press Enter to open the pop-up menu
+charcode,13
+
+# Press Space to make sure that the pop-up is not dismissed
+charcode,32
\ No newline at end of file
diff --git a/testing/resources/pixel/combobox_form.in b/testing/resources/pixel/combobox_form.in
new file mode 100644
index 0000000..56adea4
--- /dev/null
+++ b/testing/resources/pixel/combobox_form.in
@@ -0,0 +1,35 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [4 0 R]
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [4 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /Ff 131072
+  /T (Combo)
+  /Rect [100 400 200 430]
+  /Opt [() () ()]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/combobox_form_expected.pdf.0.png b/testing/resources/pixel/combobox_form_expected.pdf.0.png
new file mode 100644
index 0000000..7ecd75d
--- /dev/null
+++ b/testing/resources/pixel/combobox_form_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/combobox_form_expected_skia.pdf.0.png b/testing/resources/pixel/combobox_form_expected_skia.pdf.0.png
new file mode 100644
index 0000000..4686e96
--- /dev/null
+++ b/testing/resources/pixel/combobox_form_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/font_size_expected.pdf.0.png b/testing/resources/pixel/font_size_expected.pdf.0.png
index b4506e0..91828b9 100644
--- a/testing/resources/pixel/font_size_expected.pdf.0.png
+++ b/testing/resources/pixel/font_size_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/font_size_expected_agg_mac.pdf.0.png b/testing/resources/pixel/font_size_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..02e91d4
--- /dev/null
+++ b/testing/resources/pixel/font_size_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/font_size_expected_mac.pdf.0.png b/testing/resources/pixel/font_size_expected_mac.pdf.0.png
deleted file mode 100644
index dfd0690..0000000
--- a/testing/resources/pixel/font_size_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/font_size_expected_win.pdf.0.png b/testing/resources/pixel/font_size_expected_win.pdf.0.png
deleted file mode 100644
index a0d56aa..0000000
--- a/testing/resources/pixel/font_size_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/generation_numbers1_expected.pdf.0.png b/testing/resources/pixel/generation_numbers1_expected.pdf.0.png
index f2d3ba9..522a738 100644
--- a/testing/resources/pixel/generation_numbers1_expected.pdf.0.png
+++ b/testing/resources/pixel/generation_numbers1_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/generation_numbers1_expected_agg_mac.pdf.0.png b/testing/resources/pixel/generation_numbers1_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..c946aa7
--- /dev/null
+++ b/testing/resources/pixel/generation_numbers1_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/generation_numbers1_expected_mac.pdf.0.png b/testing/resources/pixel/generation_numbers1_expected_mac.pdf.0.png
deleted file mode 100644
index 8d973ac..0000000
--- a/testing/resources/pixel/generation_numbers1_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/generation_numbers1_expected_win.pdf.0.png b/testing/resources/pixel/generation_numbers1_expected_win.pdf.0.png
deleted file mode 100644
index 7f1047c..0000000
--- a/testing/resources/pixel/generation_numbers1_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/generation_numbers2_expected.pdf.0.png b/testing/resources/pixel/generation_numbers2_expected.pdf.0.png
index f2d3ba9..522a738 100644
--- a/testing/resources/pixel/generation_numbers2_expected.pdf.0.png
+++ b/testing/resources/pixel/generation_numbers2_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/generation_numbers2_expected_agg_mac.pdf.0.png b/testing/resources/pixel/generation_numbers2_expected_agg_mac.pdf.0.png
new file mode 100644
index 0000000..c946aa7
--- /dev/null
+++ b/testing/resources/pixel/generation_numbers2_expected_agg_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/generation_numbers2_expected_mac.pdf.0.png b/testing/resources/pixel/generation_numbers2_expected_mac.pdf.0.png
deleted file mode 100644
index 8d973ac..0000000
--- a/testing/resources/pixel/generation_numbers2_expected_mac.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/generation_numbers2_expected_win.pdf.0.png b/testing/resources/pixel/generation_numbers2_expected_win.pdf.0.png
deleted file mode 100644
index 7f1047c..0000000
--- a/testing/resources/pixel/generation_numbers2_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/image_transformer_other.in b/testing/resources/pixel/image_transformer_other.in
new file mode 100644
index 0000000..5d1f580
--- /dev/null
+++ b/testing/resources/pixel/image_transformer_other.in
@@ -0,0 +1,128 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 200 200]
+  /Resources <<
+    /ColorSpace <<
+      /CS0 5 0 R
+    >>
+    /XObject <<
+      /ImMonoPalette 6 0 R
+      /ImMono 7 0 R
+      /ImColor 8 0 R
+      /ImColorWithMask 9 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+-16 64 64 0 20 20 cm
+/ImMonoPalette Do
+Q
+q
+16 64 64 0 120 20 cm
+/ImMono Do
+Q
+q
+16 64 64 16 20 120 cm
+/ImColor Do
+Q
+q
+16 64 -64 16 120 120 cm
+/ImColorWithMask Do
+Q
+endstream
+endobj
+{{object 5 0}} [
+  /Indexed
+  /DeviceGray
+  0
+  <cc>
+]
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 1
+  /ColorSpace 5 0 R
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+0000
+endstream
+endobj
+{{object 7 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 4
+  /Height 4
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter /ASCIIHexDecode
+  {{streamlen}}
+>>
+stream
+00000000
+33333333
+99999999
+cccccccc
+endstream
+endobj
+{{object 8 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter /ASCIIHexDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000
+00ff0000ff0000ff0000ff00
+00ff0000ff0000ff0000ff00
+endstream
+endobj
+{{object 9 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter /ASCIIHexDecode
+  /Height 4
+  /Width 4
+  /Mask [0 255 0 0 0 0]
+  {{streamlen}}
+>>
+stream
+ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000
+00ff0000ff0000ff0000ff00
+00ff0000ff0000ff0000ff00
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/image_transformer_other_expected.pdf.0.png b/testing/resources/pixel/image_transformer_other_expected.pdf.0.png
new file mode 100644
index 0000000..e9aab95
--- /dev/null
+++ b/testing/resources/pixel/image_transformer_other_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/image_transformer_other_expected_skia.pdf.0.png b/testing/resources/pixel/image_transformer_other_expected_skia.pdf.0.png
new file mode 100644
index 0000000..e65f919
--- /dev/null
+++ b/testing/resources/pixel/image_transformer_other_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/jpxdecode.in b/testing/resources/pixel/jpxdecode.in
new file mode 100644
index 0000000..d69b058
--- /dev/null
+++ b/testing/resources/pixel/jpxdecode.in
@@ -0,0 +1,177 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 88 128]
+  /Resources <<
+    /XObject <<
+      /ImGray 5 0 R
+      /ImGrayAlpha 6 0 R
+      /ImRGB 7 0 R
+      /ImRGBAlpha 8 0 R
+      /ImCMYK 9 0 R
+      /ImCMYKAlpha 10 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+
+% 50% gray background rectangle
+q
+  0.5 0.5 0.5 rg
+  0 0 88 128 re
+  f
+Q
+
+% grayscale, grayscale with alpha
+q
+  32 0 0 32 8 88 cm
+  /ImGray Do
+Q
+q
+  32 0 0 32 48 88 cm
+  /ImGrayAlpha Do
+Q
+
+% RGB, RGB with alpha
+q
+  32 0 0 32 8 48 cm
+  /ImRGB Do
+Q
+q
+  32 0 0 32 48 48 cm
+  /ImRGBAlpha Do
+Q
+
+% CMYK, CMYK with alpha
+q
+  32 0 0 32 8 8 cm
+  /ImCMYK Do
+Q
+q
+  32 0 0 32 48 8 cm
+  /ImCMYKAlpha Do
+Q
+
+endstream
+endobj
+
+% grayscale
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../gray.jp2}}
+endstream
+endobj
+
+% grayscale with opacity
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter /JPXDecode
+  /Height 4
+  /SMaskInData 1
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../gray-alpha.jp2}}
+endstream
+endobj
+
+% RGB
+{{object 7 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../RGB.jp2}}
+endstream
+endobj
+
+% RGB with opacity
+{{object 8 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter /JPXDecode
+  /Height 4
+  /SMaskInData 1
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../RGB-alpha.jp2}}
+endstream
+endobj
+
+% CMYK
+{{object 9 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceCMYK
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../CMYK.jpf}}
+endstream
+endobj
+
+% CMYK with opacity.
+{{object 10 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceCMYK
+  /Filter /JPXDecode
+  /Height 4
+  /SMaskInData 1
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../CMYK-alpha.jpf}}
+endstream
+endobj
+
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/jpxdecode_expected.pdf.0.png b/testing/resources/pixel/jpxdecode_expected.pdf.0.png
new file mode 100644
index 0000000..9051cb0
--- /dev/null
+++ b/testing/resources/pixel/jpxdecode_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/jpxdecode_with_mismatch_colorspace.in b/testing/resources/pixel/jpxdecode_with_mismatch_colorspace.in
new file mode 100644
index 0000000..ddf3068
--- /dev/null
+++ b/testing/resources/pixel/jpxdecode_with_mismatch_colorspace.in
@@ -0,0 +1,180 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 88 128]
+  /Resources <<
+    /XObject <<
+      /ImGrayCsRGB 5 0 R
+      /ImGrayCsCMYK 6 0 R
+      /ImRGBCsGray 7 0 R
+      /ImRGBCsCMYK 8 0 R
+      /ImCMYKCsGray 9 0 R
+      /ImCMYKCsRGB 10 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+
+% 50% gray background rectangle
+q
+  0.5 0.5 0.5 rg
+  0 0 88 128 re
+  f
+Q
+
+% grayscale image with RGB /ColorSpace entry
+q
+  32 0 0 32 8 88 cm
+  /ImGrayCsRGB Do
+Q
+
+% grayscale image with CMYK /ColorSpace entry
+q
+  32 0 0 32 48 88 cm
+  /ImGrayCsCMYK Do
+Q
+
+% RGB image with grayscale /ColorSpace entry
+q
+  32 0 0 32 8 48 cm
+  /ImRGBCsGray Do
+Q
+
+% RGB image with CMYK /ColorSpace entry
+q
+  32 0 0 32 48 48 cm
+  /ImRGBCsCMYK Do
+Q
+
+% CMYK image with grayscale /ColorSpace entry
+q
+  32 0 0 32 8 8 cm
+  /ImCMYKCsGray Do
+Q
+
+% CMYK image with RGB /ColorSpace entry
+q
+  32 0 0 32 48 8 cm
+  /ImCMYKCsRGB Do
+Q
+
+endstream
+endobj
+
+% grayscale image with RGB /ColorSpace
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../gray.jp2}}
+endstream
+endobj
+
+% grayscale image with CMYK /ColorSpace
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceCMYK
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../gray.jp2}}
+endstream
+endobj
+
+% RGB image with grayscale /ColorSpace
+{{object 7 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../RGB.jp2}}
+endstream
+endobj
+
+% RGB image with CMYK /ColorSpace
+{{object 8 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceCMYK
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../RGB.jp2}}
+endstream
+endobj
+
+% CMYK image with grayscale /ColorSpace
+{{object 9 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../CMYK.jpf}}
+endstream
+endobj
+
+% CMYK image with RGB /ColorSpace
+{{object 10 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../CMYK.jpf}}
+endstream
+endobj
+
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/jpxdecode_with_mismatch_colorspace_expected.pdf.0.png b/testing/resources/pixel/jpxdecode_with_mismatch_colorspace_expected.pdf.0.png
new file mode 100644
index 0000000..65743e0
--- /dev/null
+++ b/testing/resources/pixel/jpxdecode_with_mismatch_colorspace_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/jpxdecode_without_bitspercomponent.in b/testing/resources/pixel/jpxdecode_without_bitspercomponent.in
new file mode 100644
index 0000000..59b516f
--- /dev/null
+++ b/testing/resources/pixel/jpxdecode_without_bitspercomponent.in
@@ -0,0 +1,171 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 88 128]
+  /Resources <<
+    /XObject <<
+      /ImGray 5 0 R
+      /ImGrayAlpha 6 0 R
+      /ImRGB 7 0 R
+      /ImRGBAlpha 8 0 R
+      /ImCMYK 9 0 R
+      /ImCMYKAlpha 10 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+
+% 50% gray background rectangle
+q
+  0.5 0.5 0.5 rg
+  0 0 88 128 re
+  f
+Q
+
+% grayscale, grayscale with alpha
+q
+  32 0 0 32 8 88 cm
+  /ImGray Do
+Q
+q
+  32 0 0 32 48 88 cm
+  /ImGrayAlpha Do
+Q
+
+% RGB, RGB with alpha
+q
+  32 0 0 32 8 48 cm
+  /ImRGB Do
+Q
+q
+  32 0 0 32 48 48 cm
+  /ImRGBAlpha Do
+Q
+
+% CMYK, CMYK with alpha
+q
+  32 0 0 32 8 8 cm
+  /ImCMYK Do
+Q
+q
+  32 0 0 32 48 8 cm
+  /ImCMYKAlpha Do
+Q
+
+endstream
+endobj
+
+% grayscale
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /ColorSpace /DeviceGray
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../gray.jp2}}
+endstream
+endobj
+
+% grayscale with opacity
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /ColorSpace /DeviceGray
+  /Filter /JPXDecode
+  /Height 4
+  /SMaskInData 1
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../gray-alpha.jp2}}
+endstream
+endobj
+
+% RGB
+{{object 7 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /ColorSpace /DeviceRGB
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../RGB.jp2}}
+endstream
+endobj
+
+% RGB with opacity
+{{object 8 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /ColorSpace /DeviceRGB
+  /Filter /JPXDecode
+  /Height 4
+  /SMaskInData 1
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../RGB-alpha.jp2}}
+endstream
+endobj
+
+% CMYK
+{{object 9 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /ColorSpace /DeviceCMYK
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../CMYK.jpf}}
+endstream
+endobj
+
+% CMYK with opacity.
+{{object 10 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /ColorSpace /DeviceCMYK
+  /Filter /JPXDecode
+  /Height 4
+  /SMaskInData 1
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../CMYK-alpha.jpf}}
+endstream
+endobj
+
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/jpxdecode_without_bitspercomponent_expected.pdf.0.png b/testing/resources/pixel/jpxdecode_without_bitspercomponent_expected.pdf.0.png
new file mode 100644
index 0000000..9051cb0
--- /dev/null
+++ b/testing/resources/pixel/jpxdecode_without_bitspercomponent_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/jpxdecode_without_colorspace.in b/testing/resources/pixel/jpxdecode_without_colorspace.in
new file mode 100644
index 0000000..0ffb64f
--- /dev/null
+++ b/testing/resources/pixel/jpxdecode_without_colorspace.in
@@ -0,0 +1,171 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 88 128]
+  /Resources <<
+    /XObject <<
+      /ImGray 5 0 R
+      /ImGrayAlpha 6 0 R
+      /ImRGB 7 0 R
+      /ImRGBAlpha 8 0 R
+      /ImCMYK 9 0 R
+      /ImCMYKAlpha 10 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+
+% 50% gray background rectangle
+q
+  0.5 0.5 0.5 rg
+  0 0 88 128 re
+  f
+Q
+
+% grayscale, grayscale with alpha
+q
+  32 0 0 32 8 88 cm
+  /ImGray Do
+Q
+q
+  32 0 0 32 48 88 cm
+  /ImGrayAlpha Do
+Q
+
+% RGB, RGB with alpha
+q
+  32 0 0 32 8 48 cm
+  /ImRGB Do
+Q
+q
+  32 0 0 32 48 48 cm
+  /ImRGBAlpha Do
+Q
+
+% CMYK, CMYK with alpha
+q
+  32 0 0 32 8 8 cm
+  /ImCMYK Do
+Q
+q
+  32 0 0 32 48 8 cm
+  /ImCMYKAlpha Do
+Q
+
+endstream
+endobj
+
+% grayscale
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../gray.jp2}}
+endstream
+endobj
+
+% grayscale with opacity
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /Filter /JPXDecode
+  /Height 4
+  /SMaskInData 1
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../gray-alpha.jp2}}
+endstream
+endobj
+
+% RGB
+{{object 7 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../RGB.jp2}}
+endstream
+endobj
+
+% RGB with opacity
+{{object 8 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /Filter /JPXDecode
+  /Height 4
+  /SMaskInData 1
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../RGB-alpha.jp2}}
+endstream
+endobj
+
+% CMYK
+{{object 9 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../CMYK.jpf}}
+endstream
+endobj
+
+% CMYK with opacity.
+{{object 10 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /Filter /JPXDecode
+  /Height 4
+  /SMaskInData 1
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../CMYK-alpha.jpf}}
+endstream
+endobj
+
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/jpxdecode_without_colorspace_expected.pdf.0.png b/testing/resources/pixel/jpxdecode_without_colorspace_expected.pdf.0.png
new file mode 100644
index 0000000..9051cb0
--- /dev/null
+++ b/testing/resources/pixel/jpxdecode_without_colorspace_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/jpxdecode_without_smaskindata.in b/testing/resources/pixel/jpxdecode_without_smaskindata.in
new file mode 100644
index 0000000..ab29a60
--- /dev/null
+++ b/testing/resources/pixel/jpxdecode_without_smaskindata.in
@@ -0,0 +1,111 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 48 128]
+  /Resources <<
+    /XObject <<
+      /ImGrayAlpha 5 0 R
+      /ImRGBAlpha 6 0 R
+      /ImCMYKAlpha 7 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+
+% 50% gray background rectangle
+q
+  0.5 0.5 0.5 rg
+  0 0 48 128 re
+  f
+Q
+
+% grayscale with ignored alpha
+q
+  32 0 0 32 8 88 cm
+  /ImGrayAlpha Do
+Q
+
+% RGB with ignored alpha
+q
+  32 0 0 32 8 48 cm
+  /ImRGBAlpha Do
+Q
+
+% CMYK with ignored alpha
+q
+  32 0 0 32 8 8 cm
+  /ImCMYKAlpha Do
+Q
+
+endstream
+endobj
+
+% grayscale with opacity, but the soft-mask information is not used.
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceGray
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../gray-alpha.jp2}}
+endstream
+endobj
+
+% RGB with opacity, but the soft-mask information is not used.
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../RGB-alpha.jp2}}
+endstream
+endobj
+
+% CMYK with opacity, but the soft-mask information is not used.
+{{object 7 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 8
+  /ColorSpace /DeviceCMYK
+  /Filter /JPXDecode
+  /Height 4
+  /Width 4
+  {{streamlen}}
+>>
+stream
+{{include ../CMYK-alpha.jpf}}
+endstream
+endobj
+
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/jpxdecode_without_smaskindata_expected.pdf.0.png b/testing/resources/pixel/jpxdecode_without_smaskindata_expected.pdf.0.png
new file mode 100644
index 0000000..5405d1e
--- /dev/null
+++ b/testing/resources/pixel/jpxdecode_without_smaskindata_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/long_dashed_line.in b/testing/resources/pixel/long_dashed_line.in
new file mode 100644
index 0000000..623adfa
--- /dev/null
+++ b/testing/resources/pixel/long_dashed_line.in
@@ -0,0 +1,33 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 800 800]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+[2 2] 0 d
+100 100 m
+100 -75697008 l
+S
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/long_dashed_line_expected.pdf.0.png b/testing/resources/pixel/long_dashed_line_expected.pdf.0.png
new file mode 100644
index 0000000..41b3efb
--- /dev/null
+++ b/testing/resources/pixel/long_dashed_line_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/long_dashed_line_expected_skia.pdf.0.png b/testing/resources/pixel/long_dashed_line_expected_skia.pdf.0.png
new file mode 100644
index 0000000..3ffc2d6
--- /dev/null
+++ b/testing/resources/pixel/long_dashed_line_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/matte_expected_skia.pdf.0.png b/testing/resources/pixel/matte_expected_skia.pdf.0.png
new file mode 100644
index 0000000..bdab404
--- /dev/null
+++ b/testing/resources/pixel/matte_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/password.evt b/testing/resources/pixel/password.evt
new file mode 100644
index 0000000..3051015
--- /dev/null
+++ b/testing/resources/pixel/password.evt
@@ -0,0 +1,26 @@
+mousemove,102,102
+mousedown,left,102,102
+mouseup,left,102,102
+charcode,116
+charcode,105
+charcode,103
+charcode,101
+charcode,114
+charcode,115
+charcode,115
+charcode,115
+charcode,115
+
+mousemove,102,162
+mousedown,left,102,162
+mouseup,left,102,162
+charcode,116
+charcode,105
+charcode,103
+charcode,101
+charcode,114
+charcode,115
+charcode,115
+charcode,115
+charcode,115
+
diff --git a/testing/resources/pixel/password.in b/testing/resources/pixel/password.in
new file mode 100644
index 0000000..a50f0af
--- /dev/null
+++ b/testing/resources/pixel/password.in
@@ -0,0 +1,54 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [
+      4 0 R
+      5 0 R
+    ]
+    /NeedAppearances true
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [ 3 0 R ]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [ 0 0 300 200 ]
+  /Annots [
+    4 0 R
+    5 0 R
+  ]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /T (Text Box)
+  /Rect [ 100 100 200 130 ]
+  /MaxLen 5
+  /Q 1
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /Ff 8192
+  /T (Password Box)
+  /Rect [ 100 160 200 190 ]
+  /MaxLen 5
+  /Q 2
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/password_expected.pdf.0.png b/testing/resources/pixel/password_expected.pdf.0.png
new file mode 100644
index 0000000..5040ef6
--- /dev/null
+++ b/testing/resources/pixel/password_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/password_expected_mac.pdf.0.png b/testing/resources/pixel/password_expected_mac.pdf.0.png
new file mode 100644
index 0000000..2f4239f
--- /dev/null
+++ b/testing/resources/pixel/password_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/password_expected_skia.pdf.0.png b/testing/resources/pixel/password_expected_skia.pdf.0.png
new file mode 100644
index 0000000..01b4b68
--- /dev/null
+++ b/testing/resources/pixel/password_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/radial_shading_point_at_border_expected_skia.pdf.0.png b/testing/resources/pixel/radial_shading_point_at_border_expected_skia.pdf.0.png
new file mode 100644
index 0000000..92f2349
--- /dev/null
+++ b/testing/resources/pixel/radial_shading_point_at_border_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/radial_shading_point_at_border_no_extend_expected_skia.pdf.0.png b/testing/resources/pixel/radial_shading_point_at_border_no_extend_expected_skia.pdf.0.png
new file mode 100644
index 0000000..846a9ea
--- /dev/null
+++ b/testing/resources/pixel/radial_shading_point_at_border_no_extend_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/radial_shading_point_at_center_expected_skia.pdf.0.png b/testing/resources/pixel/radial_shading_point_at_center_expected_skia.pdf.0.png
new file mode 100644
index 0000000..756ddb8
--- /dev/null
+++ b/testing/resources/pixel/radial_shading_point_at_center_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/reset_button.evt b/testing/resources/pixel/reset_button.evt
new file mode 100644
index 0000000..1606926
--- /dev/null
+++ b/testing/resources/pixel/reset_button.evt
@@ -0,0 +1,10 @@
+# Check the checkbox
+mousemove,145,220
+mousedown,left,145,220
+mouseup,left,145,220
+
+# Tab to reset button from checkbox
+keycode,9
+
+# Press Enter to reset the form
+charcode,13
\ No newline at end of file
diff --git a/testing/resources/pixel/reset_button.in b/testing/resources/pixel/reset_button.in
new file mode 100644
index 0000000..6f93570
--- /dev/null
+++ b/testing/resources/pixel/reset_button.in
@@ -0,0 +1,97 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [5 0 R 6 0 R]
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 300 300]
+  /Contents 4 0 R
+  /Annots [5 0 R 6 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+2 w
+.1 .1 .1 RG
+.8 .843 1 rg
+n 136 211 18 18 re B*
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /F 4
+  /Ff 2
+  /P 3 0 R
+  /Rect [135 210 155 230]
+  /T (checkbox)
+  /AP <<
+    /N <<
+      /Yes 7 0 R
+    >>
+  >>
+  /AS /Off
+  /V /Off
+>>
+endobj
+{{object 6 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Btn
+  /F 4
+  /P 3 0 R
+  /Rect [135 110 185 150]
+  /T (Reset button)
+  /Ff 65536
+  /A <<
+   /S /ResetForm
+   /Fields [5 0 R]
+   /Flags 1
+  >>
+  /MK <<
+    /BC [0.0 0.0 0.4]
+    /BG [0.9 0.9 0.9]
+  >>
+>>
+endobj
+{{object 7 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /BBox [0 0 20 20]
+  {{streamlen}}
+>>
+stream
+q
+17.2 15.95145 m
+17.2 4.027746 l
+15.97225 2.8 l
+4.027746 2.8 l
+4.027746 17.2 l
+15.97225 17.2 l
+h
+f
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
\ No newline at end of file
diff --git a/testing/resources/pixel/reset_button_expected.pdf.0.png b/testing/resources/pixel/reset_button_expected.pdf.0.png
new file mode 100644
index 0000000..e36c30d
--- /dev/null
+++ b/testing/resources/pixel/reset_button_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/scrollable_widgets1.evt b/testing/resources/pixel/scrollable_widgets1.evt
new file mode 100644
index 0000000..c595cd7
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets1.evt
@@ -0,0 +1,29 @@
+# Must move the mouse and click to give widget focus.
+mousemove,150,415
+mousedown,left,150,415
+# Scroll all the way down.
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
diff --git a/testing/resources/pixel/scrollable_widgets1.in b/testing/resources/pixel/scrollable_widgets1.in
new file mode 100644
index 0000000..9453b7c
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets1.in
@@ -0,0 +1,52 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [5 0 R]
+    /DR <<
+      /Font <<
+        /F1 4 0 R
+      >>
+    >>
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 300 600]
+  /Annots [5 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /Ff 2097152
+  /T (Listbox_MultiSelect)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [100 400 200 430]
+  /Opt [(Apple) (Banana) (Cherry) (Date) (Elderberry) (Fig) (Guava) (Honeydew)
+        (Indian Fig) (Jackfruit) (Kiwi) (Lemon) (Mango) (Nectarine) (Orange)
+        (Persimmon) (Quince) (Raspberry) (Strawberry) (Tamarind) (Ugli Fruit)
+        (Voavanga) (Wolfberry) (Xigua) (Yangmei) (Zucchini)]
+  /V (Banana)
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/scrollable_widgets1_expected.pdf.0.png b/testing/resources/pixel/scrollable_widgets1_expected.pdf.0.png
new file mode 100644
index 0000000..7760af7
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets1_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/scrollable_widgets1_expected_mac.pdf.0.png b/testing/resources/pixel/scrollable_widgets1_expected_mac.pdf.0.png
new file mode 100644
index 0000000..5dbdc5a
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets1_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/scrollable_widgets1_expected_skia.pdf.0.png b/testing/resources/pixel/scrollable_widgets1_expected_skia.pdf.0.png
new file mode 100644
index 0000000..d4c1f9d
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets1_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/scrollable_widgets2.evt b/testing/resources/pixel/scrollable_widgets2.evt
new file mode 100644
index 0000000..59b8a4b
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets2.evt
@@ -0,0 +1,34 @@
+# Must move the mouse and click to give widget focus.
+mousemove,150,415
+mousedown,left,150,415
+# Scroll all the way down and then scroll back up a bit.
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,-1
+mousewheel,150,415,0,1
+mousewheel,150,415,0,1
+mousewheel,150,415,0,1
+mousewheel,150,415,0,1
+mousewheel,150,415,0,1
diff --git a/testing/resources/pixel/scrollable_widgets2.in b/testing/resources/pixel/scrollable_widgets2.in
new file mode 100644
index 0000000..9453b7c
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets2.in
@@ -0,0 +1,52 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [5 0 R]
+    /DR <<
+      /Font <<
+        /F1 4 0 R
+      >>
+    >>
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 300 600]
+  /Annots [5 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Ch
+  /Ff 2097152
+  /T (Listbox_MultiSelect)
+  /DA (0 0 0 rg /F1 12 Tf)
+  /Rect [100 400 200 430]
+  /Opt [(Apple) (Banana) (Cherry) (Date) (Elderberry) (Fig) (Guava) (Honeydew)
+        (Indian Fig) (Jackfruit) (Kiwi) (Lemon) (Mango) (Nectarine) (Orange)
+        (Persimmon) (Quince) (Raspberry) (Strawberry) (Tamarind) (Ugli Fruit)
+        (Voavanga) (Wolfberry) (Xigua) (Yangmei) (Zucchini)]
+  /V (Banana)
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/scrollable_widgets2_expected.pdf.0.png b/testing/resources/pixel/scrollable_widgets2_expected.pdf.0.png
new file mode 100644
index 0000000..06e06ec
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets2_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/scrollable_widgets2_expected_mac.pdf.0.png b/testing/resources/pixel/scrollable_widgets2_expected_mac.pdf.0.png
new file mode 100644
index 0000000..aa4917f
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets2_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/scrollable_widgets2_expected_skia.pdf.0.png b/testing/resources/pixel/scrollable_widgets2_expected_skia.pdf.0.png
new file mode 100644
index 0000000..dd9f6a1
--- /dev/null
+++ b/testing/resources/pixel/scrollable_widgets2_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/text_form_custom_font.in b/testing/resources/pixel/text_form_custom_font.in
new file mode 100644
index 0000000..af404e4
--- /dev/null
+++ b/testing/resources/pixel/text_form_custom_font.in
@@ -0,0 +1,81 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /DR <<
+      /Font <<
+        /TT0 5 0 R
+      >>
+    >>
+    /Fields [4 0 R]
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [4 0 R]
+  /MediaBox [0 0 200 200]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Tx
+  /DA (1 0 0 rg /TT0 16 Tf)
+  /Rect [50 100 150 130]
+  /T (Text Box)
+  /V (      )
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /TrueType
+  /FirstChar 32
+  /BaseFont /AAAAAD+Test
+  /FontDescriptor 6 0 R
+  /ToUnicode 7 0 R
+  /LastChar 32
+  /Widths [1055]
+>>
+endobj
+{{object 6 0}} <<
+  /Type /FontDescriptor
+  /Descent -68
+  /MissingWidth 1000
+  /CapHeight 1149
+  /StemV 0
+  /FontFile2 8 0 R
+  /Flags 4
+  /FontBBox [0 -215 1000 932]
+  /FontName /AAAAAD+Test
+  /ItalicAngle 0
+  /Ascent 933
+>>
+endobj
+{{object 7 0}} <<
+  {{streamlen}}
+>>
+stream
+{{include ../bug_1388_cmap.fragment}}
+endstream
+endobj
+{{object 8 0}} <<
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+{{include ../bug_1388_truetype_font.fragment}}
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/text_form_custom_font_expected.pdf.0.png b/testing/resources/pixel/text_form_custom_font_expected.pdf.0.png
new file mode 100644
index 0000000..3894ad5
--- /dev/null
+++ b/testing/resources/pixel/text_form_custom_font_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/text_form_custom_font_expected_mac.pdf.0.png b/testing/resources/pixel/text_form_custom_font_expected_mac.pdf.0.png
new file mode 100644
index 0000000..01b3dbf
--- /dev/null
+++ b/testing/resources/pixel/text_form_custom_font_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/text_form_custom_font_expected_skia.pdf.0.png b/testing/resources/pixel/text_form_custom_font_expected_skia.pdf.0.png
new file mode 100644
index 0000000..47582b6
--- /dev/null
+++ b/testing/resources/pixel/text_form_custom_font_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/type3_expected.pdf.0.png b/testing/resources/pixel/type3_expected.pdf.0.png
index 6837bb8..63d9e57 100644
--- a/testing/resources/pixel/type3_expected.pdf.0.png
+++ b/testing/resources/pixel/type3_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/use_symbolneu/bug_1449.in b/testing/resources/pixel/use_symbolneu/bug_1449.in
new file mode 100644
index 0000000..603eb56
--- /dev/null
+++ b/testing/resources/pixel/use_symbolneu/bug_1449.in
@@ -0,0 +1,47 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [ 0 0 100 100 ]
+  /Count 1
+  /Kids [ 3 0 R ]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F0 4 0 R
+    >>
+  >>
+  /Contents 5 0 R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Font
+  /Subtype /TrueType
+  /BaseFont /Symbol
+  /FirstChar 31
+  /LastChar 255
+  /Name /F0
+>>
+endobj
+{{object 5 0}} <<
+ {{streamlen}}
+>>
+stream
+BT
+25 25 Td /F0 12 Tf
+-0.036  Tc (\265) Tj
+ET
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/use_symbolneu/bug_1449_expected.pdf.0.png b/testing/resources/pixel/use_symbolneu/bug_1449_expected.pdf.0.png
new file mode 100644
index 0000000..319932a
--- /dev/null
+++ b/testing/resources/pixel/use_symbolneu/bug_1449_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/barcode_test_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/barcode_test_expected_skia.pdf.0.png
new file mode 100644
index 0000000..3716b9c
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/barcode_test_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/barcode_test_expected_win.pdf.0.png b/testing/resources/pixel/xfa_specific/barcode_test_expected_win.pdf.0.png
deleted file mode 100644
index 23b80a7..0000000
--- a/testing/resources/pixel/xfa_specific/barcode_test_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/bug_1282_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/bug_1282_expected_skia.pdf.0.png
new file mode 100644
index 0000000..a29272f
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/bug_1282_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/bug_1286970.in b/testing/resources/pixel/xfa_specific/bug_1286970.in
new file mode 100644
index 0000000..4454de7
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/bug_1286970.in
@@ -0,0 +1,31 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
+  <subform layout="rl-tb" name="subform1">
+    <pageSet>
+      <pageArea name="Page1" id="Page1">
+        <contentArea x="18pt" y="18pt" w="72pt" h="72pt"/>
+        <medium stock="default" short="72pt" long="72pt"/>
+      </pageArea>
+    </pageSet>
+    <subformSet>
+      <subform><field h="73pt"></field></subform>
+    </subformSet>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/xfa_specific/bug_1286970_expected.pdf.0.png b/testing/resources/pixel/xfa_specific/bug_1286970_expected.pdf.0.png
new file mode 100644
index 0000000..9b4c2d8
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/bug_1286970_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/bug_1337.in b/testing/resources/pixel/xfa_specific/bug_1337.in
new file mode 100644
index 0000000..d44a496
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/bug_1337.in
@@ -0,0 +1,38 @@
+{{header}}
+{{include ../../xfa_catalog_1_0.fragment}}
+{{include ../../xfa_object_2_0.fragment}}
+{{include ../../xfa_preamble_3_0.fragment}}
+{{include ../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
+  <subform name="form1" layout="tb" locale="en_US" restoreState="auto">
+    <pageSet>
+      <pageArea name="Page1" id="Page1">
+        <contentArea x="18pt" y="18pt" w="612pt" h="792pt"/>
+        <medium stock="default" short="612pt" long="792pt"/>
+      </pageArea>
+    </pageSet>
+    <subform w="576pt" h="756pt" name="Page1">
+      <draw name="StaticImage1" w="250pt" h="250pt" x="10pt" y="10pt">
+        <value>
+          <image contentType="image/gif">R0lGODdh+gD6AIABAP8AAP///ywAAAAA+gD6AAAC/oSPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+f0uv2Oz+v3/L7/DxgoOEhYaHiImKi4yNjo+AgZKTlJWWl5iZmpucnZ6fkJGio6SlpqeoqaqrrK2ur6ChsrO0tba3uLm6u7y9vr+wscLDxMXGx8jJysvMzc7PwMHS09TV1tfY2drb3N3e39DR4uPk5ebn6Onq6+zt7u/g4fLz9PX29/j5+vv8/f7/8PMKDAgQQLGjyIMKHChQwbOnwIMaLEiRQrWryIMaPGV40cO3r8CDKkyJEkS5o8iTKlypUsW7p8CTOmzJk0a9q8iTOnzp08e/r8CTSo0KFEixo9ijSp0qVMmzp9CjWq1KlUq1q9ijWr1q1cu3r9Cjas2LFky14oAAA7</image>
+        </value>
+        <ui>
+          <imageEdit/>
+        </ui>
+      </draw>
+    </subform>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../xfa_locale_6_0.fragment}}
+{{include ../../xfa_postamble_7_0.fragment}}
+{{include ../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/xfa_specific/bug_1337_expected.pdf.0.png b/testing/resources/pixel/xfa_specific/bug_1337_expected.pdf.0.png
new file mode 100644
index 0000000..c0d8380
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/bug_1337_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/bug_983137_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/bug_983137_expected_skia.pdf.0.png
new file mode 100644
index 0000000..7c19b45
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/bug_983137_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia.pdf.0.png
new file mode 100644
index 0000000..3ccc1a2
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia_mac.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia_mac.pdf.0.png
new file mode 100644
index 0000000..eb303b2
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected.pdf.0.png
index d20d7ea..c1cebee 100644
--- a/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected.pdf.0.png
+++ b/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_skia.pdf.0.png
new file mode 100644
index 0000000..072a537
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_win.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_win.pdf.0.png
deleted file mode 100644
index 734b7e4..0000000
--- a/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_skia.pdf.0.png
new file mode 100644
index 0000000..d443a35
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_win.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_win.pdf.0.png
deleted file mode 100644
index eae383c..0000000
--- a/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected.pdf.0.png b/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected.pdf.0.png
index a3d9830..adfabbc 100644
--- a/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected.pdf.0.png
+++ b/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected_skia.pdf.0.png
new file mode 100644
index 0000000..884a23f
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/standard_symbols_expected.pdf.0.png b/testing/resources/pixel/xfa_specific/standard_symbols_expected.pdf.0.png
new file mode 100644
index 0000000..62f3156
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/standard_symbols_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/standard_symbols_expected.pdf.1.png b/testing/resources/pixel/xfa_specific/standard_symbols_expected.pdf.1.png
new file mode 100644
index 0000000..e262e83
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/standard_symbols_expected.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/static_list_box_caption_expected_mac.pdf.0.png b/testing/resources/pixel/xfa_specific/static_list_box_caption_expected_mac.pdf.0.png
index 7cba6b9..b9066f4 100644
--- a/testing/resources/pixel/xfa_specific/static_list_box_caption_expected_mac.pdf.0.png
+++ b/testing/resources/pixel/xfa_specific/static_list_box_caption_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/static_list_box_caption_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/static_list_box_caption_expected_skia.pdf.0.png
new file mode 100644
index 0000000..3beefe0
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/static_list_box_caption_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/static_list_box_caption_expected_win.pdf.0.png b/testing/resources/pixel/xfa_specific/static_list_box_caption_expected_win.pdf.0.png
deleted file mode 100644
index 71edafd..0000000
--- a/testing/resources/pixel/xfa_specific/static_list_box_caption_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_mac.pdf.0.png b/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_mac.pdf.0.png
index 58aee9e..bc87726 100644
--- a/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_mac.pdf.0.png
+++ b/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_mac.pdf.1.png b/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_mac.pdf.1.png
index ad36aa9..4dbd85a 100644
--- a/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_mac.pdf.1.png
+++ b/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_mac.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_skia.pdf.0.png
new file mode 100644
index 0000000..4b35140
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_skia.pdf.1.png b/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_skia.pdf.1.png
new file mode 100644
index 0000000..fd88db2
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_skia.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_win.pdf.0.png b/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_win.pdf.0.png
deleted file mode 100644
index ea939ac..0000000
--- a/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_win.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_win.pdf.1.png b/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_win.pdf.1.png
deleted file mode 100644
index c73f1a9..0000000
--- a/testing/resources/pixel/xfa_specific/static_password_field_rotate_expected_win.pdf.1.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/use_ahem/README.md b/testing/resources/pixel/xfa_specific/use_ahem/README.md
new file mode 100644
index 0000000..f3126e2
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/use_ahem/README.md
@@ -0,0 +1,2 @@
+The expected results in this directory should match those generated by running
+`pdfium_test` with the option `--font-dir=/path/to/pdfium/testing/resources/fonts`.
diff --git a/testing/resources/pixel/xfa_specific/use_ahem/bug_997412.in b/testing/resources/pixel/xfa_specific/use_ahem/bug_997412.in
new file mode 100644
index 0000000..ecda561
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/use_ahem/bug_997412.in
@@ -0,0 +1,35 @@
+{{header}}
+{{include ../../../xfa_catalog_1_0.fragment}}
+{{include ../../../xfa_object_2_0.fragment}}
+{{include ../../../xfa_preamble_3_0.fragment}}
+{{include ../../../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
+  <subform layout="rl-tb" name="subform1">
+    <pageSet>
+      <pageArea name="Page1" id="Page1">
+        <contentArea x="18pt" y="18pt" w="612pt" h="792pt"/>
+        <medium stock="default" short="612pt" long="792pt"/>
+      </pageArea>
+    </pageSet>
+    <field h="3000pt" name="Field1">
+      <font typeface="Ahem" size="20pt"/>
+      <value>
+        <text>foo
+<!-- Intentionally formatted to trigger bug //--></text>
+      </value>
+    </field>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../../../xfa_locale_6_0.fragment}}
+{{include ../../../xfa_postamble_7_0.fragment}}
+{{include ../../../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/pixel/xfa_specific/use_ahem/bug_997412_expected.pdf.0.png b/testing/resources/pixel/xfa_specific/use_ahem/bug_997412_expected.pdf.0.png
new file mode 100644
index 0000000..08c11b0
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/use_ahem/bug_997412_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/use_ahem/bug_997412_expected.pdf.1.png b/testing/resources/pixel/xfa_specific/use_ahem/bug_997412_expected.pdf.1.png
new file mode 100644
index 0000000..7734ff7
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/use_ahem/bug_997412_expected.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/use_ahem/bug_997412_expected_skia.pdf.1.png b/testing/resources/pixel/xfa_specific/use_ahem/bug_997412_expected_skia.pdf.1.png
new file mode 100644
index 0000000..d4da28c
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/use_ahem/bug_997412_expected_skia.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/use_ahem/xfa_example_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/use_ahem/xfa_example_expected_skia.pdf.0.png
new file mode 100644
index 0000000..5a3eb9f
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/use_ahem/xfa_example_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/use_ahem/xfa_textfield_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/use_ahem/xfa_textfield_expected_skia.pdf.0.png
new file mode 100644
index 0000000..ad18d26
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/use_ahem/xfa_textfield_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_bmp_image.in b/testing/resources/pixel/xfa_specific/xfa_bmp_image.in
index 9b444f1..dfd9026 100644
--- a/testing/resources/pixel/xfa_specific/xfa_bmp_image.in
+++ b/testing/resources/pixel/xfa_specific/xfa_bmp_image.in
@@ -18,7 +18,7 @@
     <subform w="576pt" h="756pt" name="Page1">
       <field name="ImageField1" w="250pt" h="250pt">
         <value>
-          <image contentType="image/bmp">Qk0qHgAAAAAAAHoAAABsAAAAMgAAADIAAAABABgAAAAAALAdAAATCwAAEwsAAAAAAAAAAAAAQkdScwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAAAAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA/wAA</image>
+          <image contentType="image/bmp">Qk1mCgAAAAAAAD4AAAAoAAAAMgAAADIAAAABAAgAAAAAACgKAAATCwAAEwsAAAIAAAACAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==</image>
         </value>
         <border>
           <edge thickness="0.254mm"/>
diff --git a/testing/resources/pixel/xfa_specific/xfa_bmp_image_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_bmp_image_expected_skia.pdf.0.png
new file mode 100644
index 0000000..2f77624
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_bmp_image_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_gif_image_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_gif_image_expected_skia.pdf.0.png
new file mode 100644
index 0000000..2f77624
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_gif_image_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_jpg_image_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_jpg_image_expected_skia.pdf.0.png
new file mode 100644
index 0000000..70dcd8e
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_jpg_image_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected.pdf.0.png
index 6855361..4bfe7c8 100644
--- a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected.pdf.0.png
+++ b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected.pdf.1.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected.pdf.1.png
index 1f5c1bb..44aa0b1 100644
--- a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected.pdf.1.png
+++ b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_mac.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_mac.pdf.0.png
new file mode 100644
index 0000000..4912da0
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_mac.pdf.1.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_mac.pdf.1.png
new file mode 100644
index 0000000..427b5b3
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_mac.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia.pdf.0.png
new file mode 100644
index 0000000..fe509a8
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia.pdf.1.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia.pdf.1.png
new file mode 100644
index 0000000..e720b9b
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_mac.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_mac.pdf.0.png
new file mode 100644
index 0000000..384294c
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_mac.pdf.1.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_mac.pdf.1.png
new file mode 100644
index 0000000..3366bc0
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_mac.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_win.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_win.pdf.0.png
new file mode 100644
index 0000000..54f04d8
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_win.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_win.pdf.1.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_win.pdf.1.png
new file mode 100644
index 0000000..7691fb1
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_win.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_win.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_win.pdf.0.png
new file mode 100644
index 0000000..ffa36fe
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_win.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_win.pdf.1.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_win.pdf.1.png
new file mode 100644
index 0000000..2448322
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_win.pdf.1.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_png_image_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_png_image_expected_skia.pdf.0.png
new file mode 100644
index 0000000..2f77624
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_png_image_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_rectangle_node_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_rectangle_node_expected_skia.pdf.0.png
new file mode 100644
index 0000000..99cee53
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_rectangle_node_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_tiff_deflate_image.in b/testing/resources/pixel/xfa_specific/xfa_tiff_deflate_image.in
deleted file mode 100644
index 797f84c..0000000
--- a/testing/resources/pixel/xfa_specific/xfa_tiff_deflate_image.in
+++ /dev/null
@@ -1,39 +0,0 @@
-{{header}}
-{{include ../../xfa_catalog_1_0.fragment}}
-{{include ../../xfa_object_2_0.fragment}}
-{{include ../../xfa_preamble_3_0.fragment}}
-{{include ../../xfa_config_4_0.fragment}}
-{{object 5 0}} <<
-  {{streamlen}}
->>
-stream
-<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
-  <subform name="form1" layout="tb" locale="en_US" restoreState="auto">
-    <pageSet>
-      <pageArea name="Page1" id="Page1">
-        <contentArea x="18pt" y="18pt" w="612pt" h="792pt"/>
-        <medium stock="default" short="612pt" long="792pt"/>
-      </pageArea>
-    </pageSet>
-    <subform w="576pt" h="756pt" name="Page1">
-      <field name="ImageField1" w="250pt" h="250pt">
-        <value>
-          <image contentType="image/tiff">SUkqAAQDAAB4nO3SMQEAMAyAsPo33brYDhIFHOxAxf4OgGfcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ2OAxGIP8F4nO3SMQEAMAyAsPo33brYDhIFHOxAxf4OgGfcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ2OAxGIP8F4nO3SMQEAMAyAsPo33brYDhIFHOxAxf4OgGfcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ2OAxGIP8F4nO3SMQEAMAyAsPo33brYDhIFHOxAxf4OgGfcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjrcTofb6XA7HW6nw+10uJ0Ot9PhdjoO69g5xxIA/gAEAAEAAAAAAAAAAAEDAAEAAAD6AAAAAQEDAAEAAAD6AAAAAgEDAAMAAADyAwAAAwEDAAEAAAAIAAAABgEDAAEAAAACAAAADQECAEIAAAAYBAAADgECABIAAABaBAAAEQEEAAQAAAAIBAAAEgEDAAEAAAABAAAAFQEDAAEAAAADAAAAFgEDAAEAAABAAAAAFwEEAAQAAAD4AwAAGgEFAAEAAADiAwAAGwEFAAEAAADqAwAAHAEDAAEAAAABAAAAKAEDAAEAAAACAAAAPQEDAAEAAAACAAAAAAAAAEgAAAABAAAASAAAAAEAAAAIAAgACADDAAAAwwAAAMMAAACzAAAACAAAAMsAAACOAQAAUQIAAC91c3IvbG9jYWwvZ29vZ2xlL2hvbWUvcmhhcnJpc29uL1BpY3R1cmVzL1JlZF9TcXVhcmVfZGVmbGF0ZS50aWZmAENyZWF0ZWQgd2l0aCBHSU1QAA==</image>
-        </value>
-        <border>
-          <edge thickness="0.254mm"/>
-          <corner thickness="0.254mm"/>
-        </border>
-      </field>
-    </subform>
-  </subform>
-</template>
-endstream
-endobj
-{{include ../../xfa_locale_6_0.fragment}}
-{{include ../../xfa_postamble_7_0.fragment}}
-{{include ../../xfa_pages_8_0.fragment}}
-{{xref}}
-{{trailer}}
-{{startxref}}
-%%EOF
diff --git a/testing/resources/pixel/xfa_specific/xfa_tiff_deflate_image_expected.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_tiff_deflate_image_expected.pdf.0.png
deleted file mode 100644
index 5204fd7..0000000
--- a/testing/resources/pixel/xfa_specific/xfa_tiff_deflate_image_expected.pdf.0.png
+++ /dev/null
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_tiff_image_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_tiff_image_expected_skia.pdf.0.png
new file mode 100644
index 0000000..2f77624
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_tiff_image_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_tiff_lzw_image_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_tiff_lzw_image_expected_skia.pdf.0.png
new file mode 100644
index 0000000..2f77624
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_tiff_lzw_image_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/xfa_specific/xfa_tiff_packbits_image_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_tiff_packbits_image_expected_skia.pdf.0.png
new file mode 100644
index 0000000..2f77624
--- /dev/null
+++ b/testing/resources/pixel/xfa_specific/xfa_tiff_packbits_image_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/polygon_annot.in b/testing/resources/polygon_annot.in
new file mode 100644
index 0000000..069ddc1
--- /dev/null
+++ b/testing/resources/polygon_annot.in
@@ -0,0 +1,48 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [
+    4 0 R 5 0 R
+  ]
+  /Tabs /R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Polygon
+  /NM (Polygon-1)
+  /F 4
+  /Vertices [159 296 350 411 472 243.42]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+{{object 5 0}} <<
+  /Type /Annot
+  /Subtype /Polygon
+  /NM (Polygon-2)
+  /F 4
+  /Vertices [259 396 450 511 572 343 42]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/polygon_annot.pdf b/testing/resources/polygon_annot.pdf
new file mode 100644
index 0000000..8efaa27
--- /dev/null
+++ b/testing/resources/polygon_annot.pdf
@@ -0,0 +1,60 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [
+    4 0 R 5 0 R
+  ]
+  /Tabs /R
+>>
+endobj
+4 0 obj <<
+  /Type /Annot
+  /Subtype /Polygon
+  /NM (Polygon-1)
+  /F 4
+  /Vertices [159 296 350 411 472 243.42]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+5 0 obj <<
+  /Type /Annot
+  /Subtype /Polygon
+  /NM (Polygon-2)
+  /F 4
+  /Vertices [259 396 450 511 572 343 42]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000251 00000 n 
+0000000429 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+607
+%%EOF
diff --git a/testing/resources/rectangles.in b/testing/resources/rectangles.in
index 49932ff..7334516 100644
--- a/testing/resources/rectangles.in
+++ b/testing/resources/rectangles.in
@@ -6,9 +6,9 @@
 endobj
 {{object 2 0}} <<
   /Type /Pages
-  /MediaBox [ 0 0 200 300 ]
+  /MediaBox [0 0 200 300]
   /Count 1
-  /Kids [ 3 0 R ]
+  /Kids [3 0 R]
 >>
 endobj
 {{object 3 0}} <<
@@ -18,6 +18,7 @@
 >>
 endobj
 {{object 4 0}} <<
+  {{streamlen}}
 >>
 stream
 q
diff --git a/testing/resources/rectangles.pdf b/testing/resources/rectangles.pdf
index 7bad251..6911d22 100644
--- a/testing/resources/rectangles.pdf
+++ b/testing/resources/rectangles.pdf
@@ -7,9 +7,9 @@
 endobj
 2 0 obj <<
   /Type /Pages
-  /MediaBox [ 0 0 200 300 ]
+  /MediaBox [0 0 200 300]
   /Count 1
-  /Kids [ 3 0 R ]
+  /Kids [3 0 R]
 >>
 endobj
 3 0 obj <<
@@ -19,6 +19,7 @@
 >>
 endobj
 4 0 obj <<
+  /Length 188
 >>
 stream
 q
@@ -42,9 +43,12 @@
 0000000000 65535 f 
 0000000015 00000 n 
 0000000068 00000 n 
-0000000161 00000 n 
-0000000230 00000 n 
-trailer<< /Root 1 0 R /Size 5 >>
+0000000157 00000 n 
+0000000226 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 5
+>>
 startxref
-456
+466
 %%EOF
diff --git a/testing/resources/redact_annot.in b/testing/resources/redact_annot.in
new file mode 100644
index 0000000..99b7b4e
--- /dev/null
+++ b/testing/resources/redact_annot.in
@@ -0,0 +1,44 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [
+    5 0 R
+  ]
+  /Tabs /R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /Annot
+  /Subtype /Redact
+  /NM (Redact-1)
+  /F 4
+  /QuadPoints [293 542 349 542 293 530 349 530]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/redact_annot.pdf b/testing/resources/redact_annot.pdf
new file mode 100644
index 0000000..76c2607
--- /dev/null
+++ b/testing/resources/redact_annot.pdf
@@ -0,0 +1,56 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Annots [
+    5 0 R
+  ]
+  /Tabs /R
+>>
+endobj
+4 0 obj <<
+  /Length 0
+>>
+stream
+endstream
+endobj
+5 0 obj <<
+  /Type /Annot
+  /Subtype /Redact
+  /NM (Redact-1)
+  /F 4
+  /QuadPoints [293 542 349 542 293 530 349 530]
+  /P 3 0 R
+  /C [1 0.90196 0]
+  /Rect [293 530 349 542]
+>>
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000263 00000 n 
+0000000313 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+496
+%%EOF
diff --git a/testing/resources/rotated_image.in b/testing/resources/rotated_image.in
new file mode 100644
index 0000000..cf82fe8
--- /dev/null
+++ b/testing/resources/rotated_image.in
@@ -0,0 +1,52 @@
+{{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
+  /Contents 4 0 R
+  /Resources <<
+    /XObject <<
+      /Img 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+30 -30 40 40 100 100 cm
+/Img Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+789cedc2310d00000c03a07f2aaab3ea7bcf03842655555555555555f5bf01cc7818dc
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/rotated_image.pdf b/testing/resources/rotated_image.pdf
new file mode 100644
index 0000000..5bb1836
--- /dev/null
+++ b/testing/resources/rotated_image.pdf
@@ -0,0 +1,64 @@
+%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
+  /Contents 4 0 R
+  /Resources <<
+    /XObject <<
+      /Img 5 0 R
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Length 36
+>>
+stream
+q
+30 -30 40 40 100 100 cm
+/Img Do
+Q
+endstream
+endobj
+5 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /Length 71
+>>
+stream
+789cedc2310d00000c03a07f2aaab3ea7bcf03842655555555555555f5bf01cc7818dc
+endstream
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000287 00000 n 
+0000000374 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+644
+%%EOF
diff --git a/testing/resources/signature_no_sub_filter.in b/testing/resources/signature_no_sub_filter.in
new file mode 100644
index 0000000..5c1d1d8
--- /dev/null
+++ b/testing/resources/signature_no_sub_filter.in
@@ -0,0 +1,100 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 200 300]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
+
+%% First incremental update adds an initial signature and update objects to
+%% refer to it.
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [7 0 R]
+    /SigFlags 3
+  >>
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Annots [7 0 R]
+>>
+endobj
+%% ByteRange is a pairs of integers (starting byte offset, length in bytes)
+{{object 5 0}} <<
+  /Type /Sig
+  /Filter /Adobe.PPKMS
+  % Intentionally no /SubFilter
+  /ByteRange [0 10 30 10]
+  /Contents <308006092A864886F70D010702A0803080020101>
+  /M (D:20200624093114+02'00')
+>>
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 0 0]
+  /Length 0
+>>
+stream
+endstream
+endobj
+{{object 7 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Sig
+  /F 132
+  /Rect [0 0 0 0]
+  /P 3 0 R
+  /T (Signature1)
+  /V 5 0 R
+  /DV 5 0 R
+  /AP <<
+    /N 6 0 R
+  >>
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/signature_no_sub_filter.pdf b/testing/resources/signature_no_sub_filter.pdf
new file mode 100644
index 0000000..ed305ef
--- /dev/null
+++ b/testing/resources/signature_no_sub_filter.pdf
@@ -0,0 +1,124 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [0 0 200 300]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+4 0 obj <<
+  /Length 188
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000226 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 5
+>>
+startxref
+466
+%%EOF
+
+%% First incremental update adds an initial signature and update objects to
+%% refer to it.
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [7 0 R]
+    /SigFlags 3
+  >>
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Annots [7 0 R]
+>>
+endobj
+%% ByteRange is a pairs of integers (starting byte offset, length in bytes)
+5 0 obj <<
+  /Type /Sig
+  /Filter /Adobe.PPKMS
+  % Intentionally no /SubFilter
+  /ByteRange [0 10 30 10]
+  /Contents <308006092A864886F70D010702A0803080020101>
+  /M (D:20200624093114+02'00')
+>>
+endobj
+6 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 0 0]
+  /Length 0
+>>
+stream
+endstream
+endobj
+7 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Sig
+  /F 132
+  /Rect [0 0 0 0]
+  /P 3 0 R
+  /T (Signature1)
+  /V 5 0 R
+  /DV 5 0 R
+  /AP <<
+    /N 6 0 R
+  >>
+>>
+endobj
+xref
+0 8
+0000000000 65535 f 
+0000000726 00000 n 
+0000000068 00000 n 
+0000000835 00000 n 
+0000000226 00000 n 
+0000000998 00000 n 
+0000001199 00000 n 
+0000001301 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 8
+>>
+startxref
+1473
+%%EOF
diff --git a/testing/resources/signature_reason.in b/testing/resources/signature_reason.in
new file mode 100644
index 0000000..2c5d0f9
--- /dev/null
+++ b/testing/resources/signature_reason.in
@@ -0,0 +1,101 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 200 300]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
+
+%% First incremental update adds an initial signature and update objects to
+%% refer to it.
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [7 0 R]
+    /SigFlags 3
+  >>
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Annots [7 0 R]
+>>
+endobj
+%% ByteRange is a pairs of integers (starting byte offset, length in bytes)
+{{object 5 0}} <<
+  /Type /Sig
+  /Filter /Adobe.PPKMS
+  /SubFilter /ETSI.CAdES.detached
+  /ByteRange [0 10 30 10]
+  /Contents <308006092A864886F70D010702A0803080020101>
+  /M (D:20200624093114+02'00')
+  /Reason <FEFF007400650073007400200072006500610073006F006E>
+>>
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 0 0]
+  /Length 0
+>>
+stream
+endstream
+endobj
+{{object 7 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Sig
+  /F 132
+  /Rect [0 0 0 0]
+  /P 3 0 R
+  /T (Signature1)
+  /V 5 0 R
+  /DV 5 0 R
+  /AP <<
+    /N 6 0 R
+  >>
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/signature_reason.pdf b/testing/resources/signature_reason.pdf
new file mode 100644
index 0000000..3ad0205
--- /dev/null
+++ b/testing/resources/signature_reason.pdf
@@ -0,0 +1,125 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [0 0 200 300]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+4 0 obj <<
+  /Length 188
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000226 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 5
+>>
+startxref
+466
+%%EOF
+
+%% First incremental update adds an initial signature and update objects to
+%% refer to it.
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [7 0 R]
+    /SigFlags 3
+  >>
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Annots [7 0 R]
+>>
+endobj
+%% ByteRange is a pairs of integers (starting byte offset, length in bytes)
+5 0 obj <<
+  /Type /Sig
+  /Filter /Adobe.PPKMS
+  /SubFilter /ETSI.CAdES.detached
+  /ByteRange [0 10 30 10]
+  /Contents <308006092A864886F70D010702A0803080020101>
+  /M (D:20200624093114+02'00')
+  /Reason <FEFF007400650073007400200072006500610073006F006E>
+>>
+endobj
+6 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 0 0]
+  /Length 0
+>>
+stream
+endstream
+endobj
+7 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Sig
+  /F 132
+  /Rect [0 0 0 0]
+  /P 3 0 R
+  /T (Signature1)
+  /V 5 0 R
+  /DV 5 0 R
+  /AP <<
+    /N 6 0 R
+  >>
+>>
+endobj
+xref
+0 8
+0000000000 65535 f 
+0000000726 00000 n 
+0000000068 00000 n 
+0000000835 00000 n 
+0000000226 00000 n 
+0000000998 00000 n 
+0000001262 00000 n 
+0000001364 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 8
+>>
+startxref
+1536
+%%EOF
diff --git a/testing/resources/simple_xfa.in b/testing/resources/simple_xfa.in
index b20c746..d3c2d00 100644
--- a/testing/resources/simple_xfa.in
+++ b/testing/resources/simple_xfa.in
@@ -12,6 +12,7 @@
     <pageSet>
       <pageArea name="Page1" id="Page1">
         <contentArea x="0.25in" y="0.25in" w="8in" h="10.5in" />
+        <medium long="11in" short="8.5in" stock="letter"/>
       </pageArea>
     </pageSet>
     <field name="TextField1" y="31.75mm" x="44.45mm" w="114.291mm" h="12.7mm">
diff --git a/testing/resources/simple_xfa.pdf b/testing/resources/simple_xfa.pdf
index 89f9aea..8ff5b12 100644
--- a/testing/resources/simple_xfa.pdf
+++ b/testing/resources/simple_xfa.pdf
@@ -72,7 +72,7 @@
 endstream
 endobj
 5 0 obj <<
-  /Length 482
+  /Length 541
 >>
 stream
 <template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
@@ -80,6 +80,7 @@
     <pageSet>
       <pageArea name="Page1" id="Page1">
         <contentArea x="0.25in" y="0.25in" w="8in" h="10.5in" />
+        <medium long="11in" short="8.5in" stock="letter"/>
       </pageArea>
     </pageSet>
     <field name="TextField1" y="31.75mm" x="44.45mm" w="114.291mm" h="12.7mm">
@@ -215,7 +216,7 @@
 endobj
 9 0 obj <<
   /Type /Page
-  /Parent 2 0 R
+  /Parent 8 0 R
   /MediaBox [0 0 612 792]
 >>
 endobj
@@ -227,14 +228,14 @@
 0000000358 00000 n 
 0000000534 00000 n 
 0000001228 00000 n 
-0000001762 00000 n 
-0000005270 00000 n 
-0000005332 00000 n 
-0000005395 00000 n 
+0000001821 00000 n 
+0000005329 00000 n 
+0000005391 00000 n 
+0000005454 00000 n 
 trailer <<
   /Root 1 0 R
   /Size 10
 >>
 startxref
-5472
+5531
 %%EOF
diff --git a/testing/resources/split_streams.in b/testing/resources/split_streams.in
index b134769..729c67a 100644
--- a/testing/resources/split_streams.in
+++ b/testing/resources/split_streams.in
@@ -117,7 +117,7 @@
 {{xref}}
 trailer <<
   /Root 1 0 R
-  /Size 15
+  {{trailersize}}
   /ID [<f341ae654a77acd5065a7645e596e6e6><bc37298a3f87f479229bce997ca791f7>]
 >>
 {{startxref}}
diff --git a/testing/resources/tagged_actual_text.in b/testing/resources/tagged_actual_text.in
new file mode 100644
index 0000000..9bb917b
--- /dev/null
+++ b/testing/resources/tagged_actual_text.in
@@ -0,0 +1,162 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /StructTreeRoot 8 0 R
+  /Lang (en-US)
+  /MarkInfo <<
+    /Marked true
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Group <<
+    /CS /DeviceRGB
+    /I true
+    /S /Transparency
+  >>
+  /Resources <<
+    /ProcSet [/PDF /ImageC /ImageI /ImageB]
+    /XObject <<
+      /Tr8 5 0 R
+      /Im7 6 0 R
+    >>
+    /ExtGState <<
+      /EGS9 7 0 R
+    >>
+  >>
+  /StructParents 0
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+0.1 w
+/Artifact
+BMC
+q
+0 0 612 792 re
+W* n
+EMC
+/Figure<</MCID 0>>
+BDC
+Q
+q
+281 685.3 50 50 re
+W* n
+q
+49.9 0 0 50 281.1 685.4 cm
+/Im7 Do
+Q
+EMC
+Q
+q
+EGS9 gs /Tr8 Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [-140 395 753 395.1]
+  /Group <<
+    /CS /DeviceRGB
+    /K true
+    /S /Transparency
+  >>
+  {{streamlen}}
+>>
+stream
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+789cedc13101000000c2a0f54fed6f06a00000000000000078031d4c0001
+endstream
+endobj
+{{object 7 0}} <<
+  /ca 0.5
+  /CA 0.5
+>>
+endobj
+{{object 8 0}} <<
+  /Type /StructTreeRoot
+  /ParentTree 9 0 R
+  /K [11 0 R]
+  /RoleMap <<
+    /Document /Document
+    /Standard /P
+    /Figure /Figure
+  >>
+>>
+endobj
+{{object 9 0}} <<
+  /Nums [0 [10 0 R]]
+>>
+endobj
+{{object 10 0}} <<
+  /Type /StructElem
+  /S /Figure
+  /A 13 0 R
+  /K [0]
+  /P 12 0 R
+  /ActualText <feff00410063007400750061006c00200054006500780074>
+  /Pg 3 0 R
+>>
+endobj
+{{object 11 0}} <<
+  /Type /StructElem
+  /S /Document
+  /K [12 0 R]
+  /P 8 0 R
+  /T (TitleText)
+  /Pg 3 0 R
+>>
+endobj
+{{object 12 0}} <<
+  /Type /StructElem
+  /S /Standard
+  /A 14 0 R
+  /K [10 0 R]
+  /P 11 0 R
+  /T <feff00730079006d0062006f006c003a0020003100300030006b>
+  /Pg 3 0 R
+>>
+endobj
+{{object 13 0}} <<
+  /O /Layout
+  /Placement /Block
+  /BBox [281.1 685.3 331.1 735.3]
+  /Width 99.9
+  /Height 99.9
+>>
+endobj
+{{object 14 0}} <<
+  /O /Layout
+  /Placement /Block
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/tagged_actual_text.pdf b/testing/resources/tagged_actual_text.pdf
new file mode 100644
index 0000000..634e333
--- /dev/null
+++ b/testing/resources/tagged_actual_text.pdf
@@ -0,0 +1,183 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /StructTreeRoot 8 0 R
+  /Lang (en-US)
+  /MarkInfo <<
+    /Marked true
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Group <<
+    /CS /DeviceRGB
+    /I true
+    /S /Transparency
+  >>
+  /Resources <<
+    /ProcSet [/PDF /ImageC /ImageI /ImageB]
+    /XObject <<
+      /Tr8 5 0 R
+      /Im7 6 0 R
+    >>
+    /ExtGState <<
+      /EGS9 7 0 R
+    >>
+  >>
+  /StructParents 0
+>>
+endobj
+4 0 obj <<
+  /Length 162
+>>
+stream
+0.1 w
+/Artifact
+BMC
+q
+0 0 612 792 re
+W* n
+EMC
+/Figure<</MCID 0>>
+BDC
+Q
+q
+281 685.3 50 50 re
+W* n
+q
+49.9 0 0 50 281.1 685.4 cm
+/Im7 Do
+Q
+EMC
+Q
+q
+EGS9 gs /Tr8 Do
+Q
+endstream
+endobj
+5 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [-140 395 753 395.1]
+  /Group <<
+    /CS /DeviceRGB
+    /K true
+    /S /Transparency
+  >>
+  /Length 0
+>>
+stream
+endstream
+endobj
+6 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /Length 61
+>>
+stream
+789cedc13101000000c2a0f54fed6f06a00000000000000078031d4c0001
+endstream
+endobj
+7 0 obj <<
+  /ca 0.5
+  /CA 0.5
+>>
+endobj
+8 0 obj <<
+  /Type /StructTreeRoot
+  /ParentTree 9 0 R
+  /K [11 0 R]
+  /RoleMap <<
+    /Document /Document
+    /Standard /P
+    /Figure /Figure
+  >>
+>>
+endobj
+9 0 obj <<
+  /Nums [0 [10 0 R]]
+>>
+endobj
+10 0 obj <<
+  /Type /StructElem
+  /S /Figure
+  /A 13 0 R
+  /K [0]
+  /P 12 0 R
+  /ActualText <feff00410063007400750061006c00200054006500780074>
+  /Pg 3 0 R
+>>
+endobj
+11 0 obj <<
+  /Type /StructElem
+  /S /Document
+  /K [12 0 R]
+  /P 8 0 R
+  /T (TitleText)
+  /Pg 3 0 R
+>>
+endobj
+12 0 obj <<
+  /Type /StructElem
+  /S /Standard
+  /A 14 0 R
+  /K [10 0 R]
+  /P 11 0 R
+  /T <feff00730079006d0062006f006c003a0020003100300030006b>
+  /Pg 3 0 R
+>>
+endobj
+13 0 obj <<
+  /O /Layout
+  /Placement /Block
+  /BBox [281.1 685.3 331.1 735.3]
+  /Width 99.9
+  /Height 99.9
+>>
+endobj
+14 0 obj <<
+  /O /Layout
+  /Placement /Block
+>>
+endobj
+xref
+0 15
+0000000000 65535 f 
+0000000015 00000 n 
+0000000145 00000 n 
+0000000208 00000 n 
+0000000556 00000 n 
+0000000770 00000 n 
+0000000952 00000 n 
+0000001212 00000 n 
+0000001253 00000 n 
+0000001412 00000 n 
+0000001454 00000 n 
+0000001619 00000 n 
+0000001730 00000 n 
+0000001897 00000 n 
+0000002015 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 15
+>>
+startxref
+2070
+%%EOF
diff --git a/testing/resources/tagged_marked_content.in b/testing/resources/tagged_marked_content.in
new file mode 100644
index 0000000..a8ea64b
--- /dev/null
+++ b/testing/resources/tagged_marked_content.in
@@ -0,0 +1,140 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /StructTreeRoot 7 0 R
+  /MarkInfo <<
+    /Type /MarkInfo
+    /Marked true
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /StructParents 0
+  /Annots [4 0 R]
+  /Contents 5 0 R
+  /MediaBox [0 0 612 792]
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /Font <<
+      /F4 6 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Link
+  /Border [0 0 0]
+  /Dest /top
+  /F 4
+  /Rect [20 46 68 61]
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+BT
+/P <</MCID 0 >>BDC
+/F4 16 Tf
+20 650 Td
+(Top Left) Tj
+EMC
+ET
+BT
+/P <</MCID 1 >>BDC
+/F4 16 Tf
+20 50 Td
+(Bottom Left) Tj
+EMC
+ET
+BT
+/P <</MCID 2 >>BDC
+/F4 16 Tf
+400 50 Td
+(Bottom Right) Tj
+EMC
+ET
+BT
+/P <</MCID 3 >>BDC
+/F4 16 Tf
+400 650 Td
+(Top Right) Tj
+EMC
+ET
+Q
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+{{object 7 0}} <<
+  /Type /StructTreeRoot
+  /K [9 0 R 10 0 R 11 0 R 12 0 R]
+  /ParentTree 8 0 R
+  /ParentTreeNextKey 1
+>>
+endobj
+{{object 8 0}} <<
+  /Type /ParentTree
+  /Nums [0 [9 0 R 10 0 R 11 0 R 12 0 R]]
+>>
+endobj
+{{object 9 0}} <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 7 0 R
+  /K 0
+  /ID /3
+>>
+endobj
+{{object 10 0}} <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 7 0 R
+  /K <<
+      /Type /MCR
+      /MCID 1
+      /Pg 3 0 R
+     >>
+  /ID /4
+>>
+endobj
+{{object 11 0}} <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 7 0 R
+  /K [
+      <<
+       /Type /MCR
+       /MCID 2
+       /Pg 3 0 R
+      >>
+      3]
+  /ID /5
+>>
+endobj
+{{object 12 0}} <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 7 0 R
+  /ID /6
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/tagged_marked_content.pdf b/testing/resources/tagged_marked_content.pdf
new file mode 100644
index 0000000..92f731d
--- /dev/null
+++ b/testing/resources/tagged_marked_content.pdf
@@ -0,0 +1,159 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /StructTreeRoot 7 0 R
+  /MarkInfo <<
+    /Type /MarkInfo
+    /Marked true
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /StructParents 0
+  /Annots [4 0 R]
+  /Contents 5 0 R
+  /MediaBox [0 0 612 792]
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /Font <<
+      /F4 6 0 R
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Type /Annot
+  /Subtype /Link
+  /Border [0 0 0]
+  /Dest /top
+  /F 4
+  /Rect [20 46 68 61]
+>>
+endobj
+5 0 obj <<
+  /Length 264
+>>
+stream
+q
+BT
+/P <</MCID 0 >>BDC
+/F4 16 Tf
+20 650 Td
+(Top Left) Tj
+EMC
+ET
+BT
+/P <</MCID 1 >>BDC
+/F4 16 Tf
+20 50 Td
+(Bottom Left) Tj
+EMC
+ET
+BT
+/P <</MCID 2 >>BDC
+/F4 16 Tf
+400 50 Td
+(Bottom Right) Tj
+EMC
+ET
+BT
+/P <</MCID 3 >>BDC
+/F4 16 Tf
+400 650 Td
+(Top Right) Tj
+EMC
+ET
+Q
+endstream
+endobj
+6 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+7 0 obj <<
+  /Type /StructTreeRoot
+  /K [9 0 R 10 0 R 11 0 R 12 0 R]
+  /ParentTree 8 0 R
+  /ParentTreeNextKey 1
+>>
+endobj
+8 0 obj <<
+  /Type /ParentTree
+  /Nums [0 [9 0 R 10 0 R 11 0 R 12 0 R]]
+>>
+endobj
+9 0 obj <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 7 0 R
+  /K 0
+  /ID /3
+>>
+endobj
+10 0 obj <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 7 0 R
+  /K <<
+      /Type /MCR
+      /MCID 1
+      /Pg 3 0 R
+     >>
+  /ID /4
+>>
+endobj
+11 0 obj <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 7 0 R
+  /K [
+      <<
+       /Type /MCR
+       /MCID 2
+       /Pg 3 0 R
+      >>
+      3]
+  /ID /5
+>>
+endobj
+12 0 obj <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 7 0 R
+  /ID /6
+>>
+endobj
+xref
+0 13
+0000000000 65535 f 
+0000000015 00000 n 
+0000000149 00000 n 
+0000000212 00000 n 
+0000000427 00000 n 
+0000000540 00000 n 
+0000000856 00000 n 
+0000000934 00000 n 
+0000001056 00000 n 
+0000001138 00000 n 
+0000001222 00000 n 
+0000001363 00000 n 
+0000001525 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 13
+>>
+startxref
+1603
+%%EOF
diff --git a/testing/resources/tagged_mcr_objr.in b/testing/resources/tagged_mcr_objr.in
new file mode 100644
index 0000000..3394633
--- /dev/null
+++ b/testing/resources/tagged_mcr_objr.in
@@ -0,0 +1,160 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /StructTreeRoot 7 0 R
+  /MarkInfo <<
+    /Type /MarkInfo
+    /Marked true
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /StructParents 0
+  /Annots [4 0 R]
+  /Contents 5 0 R
+  /MediaBox [0 0 612 792]
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /Font <<
+      /F4 6 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Link
+  /Border [0 0 0]
+  /Dest /top
+  /F 4
+  /Rect [20 46 68 61]
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+BT
+/P <</MCID 0 >>BDC
+/F4 16 Tf
+20 650 Td
+(Hello, world!) Tj
+EMC
+ET
+BT
+/P <</MCID 1 >>BDC
+/F4 16 Tf
+20 50 Td
+(Link to top) Tj
+EMC
+ET
+Q
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+{{object 7 0}} <<
+  /Type /StructTreeRoot
+  /K 8 0 R
+  /ParentTree 9 0 R
+  /ParentTreeNextKey 1
+>>
+endobj
+{{object 8 0}} <<
+  /Type /StructElem
+  /S /Document
+  /P 7 0 R
+  /K [10 0 R 11 0 R]
+  /ID /2
+  /Lang (en-US)
+>>
+endobj
+{{object 9 0}} <<
+  /Type /ParentTree
+  /Nums [0 [13 0 R 15 0 R]]
+>>
+endobj
+{{object 10 0}} <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 8 0 R
+  /K [12 0 R]
+  /ID /6
+>>
+endobj
+{{object 11 0}} <<
+  /Type /StructElem
+  /S /P
+  /P 8 0 R
+  /K [14 0 R]
+  /ID /4
+>>
+endobj
+{{object 12 0}} <<
+  /Type /StructElem
+  /S /P
+  /P 10 0 R
+  /K [13 0 R]
+  /ID /3
+>>
+endobj
+{{object 13 0}} <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 12 0 R
+  /K [
+    <<
+      /Type /MCR
+      /MCID 0
+      /Pg 3 0 R
+    >>
+  ]
+  /ID /7
+>>
+endobj
+{{object 14 0}} <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 11 0 R
+  /K [
+    15 0 R
+    <<
+      /Type /OBJR
+      /Obj 4 0 R
+    >>
+  ]
+  /ID /9
+>>
+endobj
+{{object 15 0}} <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 14 0 R
+  /K [
+    <<
+      /Type /MCR
+      /Pg 3 0 R
+      /MCID 1
+    >>
+  ]
+  /ID /10
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/tagged_mcr_objr.pdf b/testing/resources/tagged_mcr_objr.pdf
new file mode 100644
index 0000000..a86faa7
--- /dev/null
+++ b/testing/resources/tagged_mcr_objr.pdf
@@ -0,0 +1,182 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /StructTreeRoot 7 0 R
+  /MarkInfo <<
+    /Type /MarkInfo
+    /Marked true
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /StructParents 0
+  /Annots [4 0 R]
+  /Contents 5 0 R
+  /MediaBox [0 0 612 792]
+  /Resources <<
+    /ProcSet [/PDF /Text]
+    /Font <<
+      /F4 6 0 R
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Type /Annot
+  /Subtype /Link
+  /Border [0 0 0]
+  /Dest /top
+  /F 4
+  /Rect [20 46 68 61]
+>>
+endobj
+5 0 obj <<
+  /Length 137
+>>
+stream
+q
+BT
+/P <</MCID 0 >>BDC
+/F4 16 Tf
+20 650 Td
+(Hello, world!) Tj
+EMC
+ET
+BT
+/P <</MCID 1 >>BDC
+/F4 16 Tf
+20 50 Td
+(Link to top) Tj
+EMC
+ET
+Q
+endstream
+endobj
+6 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+7 0 obj <<
+  /Type /StructTreeRoot
+  /K 8 0 R
+  /ParentTree 9 0 R
+  /ParentTreeNextKey 1
+>>
+endobj
+8 0 obj <<
+  /Type /StructElem
+  /S /Document
+  /P 7 0 R
+  /K [10 0 R 11 0 R]
+  /ID /2
+  /Lang (en-US)
+>>
+endobj
+9 0 obj <<
+  /Type /ParentTree
+  /Nums [0 [13 0 R 15 0 R]]
+>>
+endobj
+10 0 obj <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 8 0 R
+  /K [12 0 R]
+  /ID /6
+>>
+endobj
+11 0 obj <<
+  /Type /StructElem
+  /S /P
+  /P 8 0 R
+  /K [14 0 R]
+  /ID /4
+>>
+endobj
+12 0 obj <<
+  /Type /StructElem
+  /S /P
+  /P 10 0 R
+  /K [13 0 R]
+  /ID /3
+>>
+endobj
+13 0 obj <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 12 0 R
+  /K [
+    <<
+      /Type /MCR
+      /MCID 0
+      /Pg 3 0 R
+    >>
+  ]
+  /ID /7
+>>
+endobj
+14 0 obj <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 11 0 R
+  /K [
+    15 0 R
+    <<
+      /Type /OBJR
+      /Obj 4 0 R
+    >>
+  ]
+  /ID /9
+>>
+endobj
+15 0 obj <<
+  /Type /StructElem
+  /S /NonStruct
+  /P 14 0 R
+  /K [
+    <<
+      /Type /MCR
+      /Pg 3 0 R
+      /MCID 1
+    >>
+  ]
+  /ID /10
+>>
+endobj
+xref
+0 16
+0000000000 65535 f 
+0000000015 00000 n 
+0000000149 00000 n 
+0000000212 00000 n 
+0000000427 00000 n 
+0000000540 00000 n 
+0000000729 00000 n 
+0000000807 00000 n 
+0000000906 00000 n 
+0000001019 00000 n 
+0000001088 00000 n 
+0000001180 00000 n 
+0000001264 00000 n 
+0000001349 00000 n 
+0000001500 00000 n 
+0000001650 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 16
+>>
+startxref
+1802
+%%EOF
diff --git a/testing/resources/tagged_nested.in b/testing/resources/tagged_nested.in
new file mode 100644
index 0000000..b7d2022
--- /dev/null
+++ b/testing/resources/tagged_nested.in
@@ -0,0 +1,346 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /StructTreeRoot 5 0 R
+  /Lang (en-US)
+  /MarkInfo <<
+    /Marked true
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /StructParents 0
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+/Para<</MCID 0>>
+BMC
+0 0 Td
+/F1 18 Tf
+(Sample Text) Tj
+EMC
+ET
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /StructTreeRoot
+  /ParentTree 6 0 R
+  /K [8 0 R]
+  /RoleMap <<
+    /Document /Document
+    /Para /Para
+  >>
+>>
+endobj
+{{object 6 0}} <<
+  /Nums [0 [7 0 R]]
+>>
+endobj
+{{object 7 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [0]
+  /P 42 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 8 0}} <<
+  /Type /StructElem
+  /S /Document
+  /K [9 0 R]
+  /P 5 0 R
+  /T (TitleText)
+  /Pg 3 0 R
+>>
+endobj
+{{object 9 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [10 0 R]
+  /P 8 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 10 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [11 0 R]
+  /P 9 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 11 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [12 0 R]
+  /P 10 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 12 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [13 0 R]
+  /P 11 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 13 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [14 0 R]
+  /P 12 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 14 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [15 0 R]
+  /P 13 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 15 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [16 0 R]
+  /P 14 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 16 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [17 0 R]
+  /P 15 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 17 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [18 0 R]
+  /P 16 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 18 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [19 0 R]
+  /P 17 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 19 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [20 0 R]
+  /P 18 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 20 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [21 0 R]
+  /P 19 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 21 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [22 0 R]
+  /P 20 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 22 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [23 0 R]
+  /P 21 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 23 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [24 0 R]
+  /P 22 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 24 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [25 0 R]
+  /P 23 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 25 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [26 0 R]
+  /P 24 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 26 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [27 0 R]
+  /P 25 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 27 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [28 0 R]
+  /P 26 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 28 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [29 0 R]
+  /P 27 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 29 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [30 0 R]
+  /P 28 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 30 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [31 0 R]
+  /P 29 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 31 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [32 0 R]
+  /P 30 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 32 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [33 0 R]
+  /P 31 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 33 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [34 0 R]
+  /P 32 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 34 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [35 0 R]
+  /P 33 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 35 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [36 0 R]
+  /P 34 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 36 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [37 0 R]
+  /P 35 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 37 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [38 0 R]
+  /P 36 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 38 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [39 0 R]
+  /P 37 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 39 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [40 0 R]
+  /P 38 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 40 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [41 0 R]
+  /P 39 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 41 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [42 0 R]
+  /P 40 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{object 42 0}} <<
+  /Type /StructElem
+  /S /Para
+  /K [7 0 R]
+  /P 41 0 R
+  /Pg 3 0 R
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/tagged_nested.pdf b/testing/resources/tagged_nested.pdf
new file mode 100644
index 0000000..8e09360
--- /dev/null
+++ b/testing/resources/tagged_nested.pdf
@@ -0,0 +1,395 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /StructTreeRoot 5 0 R
+  /Lang (en-US)
+  /MarkInfo <<
+    /Marked true
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /StructParents 0
+>>
+endobj
+4 0 obj <<
+  /Length 65
+>>
+stream
+BT
+/Para<</MCID 0>>
+BMC
+0 0 Td
+/F1 18 Tf
+(Sample Text) Tj
+EMC
+ET
+endstream
+endobj
+5 0 obj <<
+  /Type /StructTreeRoot
+  /ParentTree 6 0 R
+  /K [8 0 R]
+  /RoleMap <<
+    /Document /Document
+    /Para /Para
+  >>
+>>
+endobj
+6 0 obj <<
+  /Nums [0 [7 0 R]]
+>>
+endobj
+7 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [0]
+  /P 42 0 R
+  /Pg 3 0 R
+>>
+endobj
+8 0 obj <<
+  /Type /StructElem
+  /S /Document
+  /K [9 0 R]
+  /P 5 0 R
+  /T (TitleText)
+  /Pg 3 0 R
+>>
+endobj
+9 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [10 0 R]
+  /P 8 0 R
+  /Pg 3 0 R
+>>
+endobj
+10 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [11 0 R]
+  /P 9 0 R
+  /Pg 3 0 R
+>>
+endobj
+11 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [12 0 R]
+  /P 10 0 R
+  /Pg 3 0 R
+>>
+endobj
+12 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [13 0 R]
+  /P 11 0 R
+  /Pg 3 0 R
+>>
+endobj
+13 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [14 0 R]
+  /P 12 0 R
+  /Pg 3 0 R
+>>
+endobj
+14 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [15 0 R]
+  /P 13 0 R
+  /Pg 3 0 R
+>>
+endobj
+15 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [16 0 R]
+  /P 14 0 R
+  /Pg 3 0 R
+>>
+endobj
+16 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [17 0 R]
+  /P 15 0 R
+  /Pg 3 0 R
+>>
+endobj
+17 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [18 0 R]
+  /P 16 0 R
+  /Pg 3 0 R
+>>
+endobj
+18 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [19 0 R]
+  /P 17 0 R
+  /Pg 3 0 R
+>>
+endobj
+19 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [20 0 R]
+  /P 18 0 R
+  /Pg 3 0 R
+>>
+endobj
+20 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [21 0 R]
+  /P 19 0 R
+  /Pg 3 0 R
+>>
+endobj
+21 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [22 0 R]
+  /P 20 0 R
+  /Pg 3 0 R
+>>
+endobj
+22 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [23 0 R]
+  /P 21 0 R
+  /Pg 3 0 R
+>>
+endobj
+23 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [24 0 R]
+  /P 22 0 R
+  /Pg 3 0 R
+>>
+endobj
+24 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [25 0 R]
+  /P 23 0 R
+  /Pg 3 0 R
+>>
+endobj
+25 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [26 0 R]
+  /P 24 0 R
+  /Pg 3 0 R
+>>
+endobj
+26 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [27 0 R]
+  /P 25 0 R
+  /Pg 3 0 R
+>>
+endobj
+27 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [28 0 R]
+  /P 26 0 R
+  /Pg 3 0 R
+>>
+endobj
+28 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [29 0 R]
+  /P 27 0 R
+  /Pg 3 0 R
+>>
+endobj
+29 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [30 0 R]
+  /P 28 0 R
+  /Pg 3 0 R
+>>
+endobj
+30 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [31 0 R]
+  /P 29 0 R
+  /Pg 3 0 R
+>>
+endobj
+31 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [32 0 R]
+  /P 30 0 R
+  /Pg 3 0 R
+>>
+endobj
+32 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [33 0 R]
+  /P 31 0 R
+  /Pg 3 0 R
+>>
+endobj
+33 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [34 0 R]
+  /P 32 0 R
+  /Pg 3 0 R
+>>
+endobj
+34 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [35 0 R]
+  /P 33 0 R
+  /Pg 3 0 R
+>>
+endobj
+35 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [36 0 R]
+  /P 34 0 R
+  /Pg 3 0 R
+>>
+endobj
+36 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [37 0 R]
+  /P 35 0 R
+  /Pg 3 0 R
+>>
+endobj
+37 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [38 0 R]
+  /P 36 0 R
+  /Pg 3 0 R
+>>
+endobj
+38 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [39 0 R]
+  /P 37 0 R
+  /Pg 3 0 R
+>>
+endobj
+39 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [40 0 R]
+  /P 38 0 R
+  /Pg 3 0 R
+>>
+endobj
+40 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [41 0 R]
+  /P 39 0 R
+  /Pg 3 0 R
+>>
+endobj
+41 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [42 0 R]
+  /P 40 0 R
+  /Pg 3 0 R
+>>
+endobj
+42 0 obj <<
+  /Type /StructElem
+  /S /Para
+  /K [7 0 R]
+  /P 41 0 R
+  /Pg 3 0 R
+>>
+endobj
+xref
+0 43
+0000000000 65535 f 
+0000000015 00000 n 
+0000000145 00000 n 
+0000000208 00000 n 
+0000000322 00000 n 
+0000000438 00000 n 
+0000000575 00000 n 
+0000000616 00000 n 
+0000000701 00000 n 
+0000000810 00000 n 
+0000000899 00000 n 
+0000000989 00000 n 
+0000001080 00000 n 
+0000001171 00000 n 
+0000001262 00000 n 
+0000001353 00000 n 
+0000001444 00000 n 
+0000001535 00000 n 
+0000001626 00000 n 
+0000001717 00000 n 
+0000001808 00000 n 
+0000001899 00000 n 
+0000001990 00000 n 
+0000002081 00000 n 
+0000002172 00000 n 
+0000002263 00000 n 
+0000002354 00000 n 
+0000002445 00000 n 
+0000002536 00000 n 
+0000002627 00000 n 
+0000002718 00000 n 
+0000002809 00000 n 
+0000002900 00000 n 
+0000002991 00000 n 
+0000003082 00000 n 
+0000003173 00000 n 
+0000003264 00000 n 
+0000003355 00000 n 
+0000003446 00000 n 
+0000003537 00000 n 
+0000003628 00000 n 
+0000003719 00000 n 
+0000003810 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 43
+>>
+startxref
+3900
+%%EOF
diff --git a/testing/resources/tagged_table.in b/testing/resources/tagged_table.in
new file mode 100644
index 0000000..ee298c5
--- /dev/null
+++ b/testing/resources/tagged_table.in
@@ -0,0 +1,217 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /StructTreeRoot 8 0 R
+  /Lang (en-US)
+  /MarkInfo <<
+    /Marked true
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Group <<
+    /CS /DeviceRGB
+    /I true
+    /S /Transparency
+  >>
+  /Resources <<
+    /ProcSet [/PDF /ImageC /ImageI /ImageB]
+    /XObject <<
+      /Tr8 5 0 R
+      /Im7 6 0 R
+    >>
+    /ExtGState <<
+      /EGS9 7 0 R
+    >>
+  >>
+  /StructParents 0
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+0.1 w
+/Artifact
+BMC
+q
+0 0 612 792 re
+W* n
+EMC
+/Figure<</MCID 0>>
+BDC
+Q
+q
+281 685.3 50 50 re
+W* n
+q
+49.9 0 0 50 281.1 685.4 cm
+/Im7 Do
+Q
+EMC
+Q
+q
+EGS9 gs /Tr8 Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [-140 395 753 395.1]
+  /Group <<
+    /CS /DeviceRGB
+    /K true
+    /S /Transparency
+  >>
+  {{streamlen}}
+>>
+stream
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+789cedc13101000000c2a0f54fed6f06a00000000000000078031d4c0001
+endstream
+endobj
+{{object 7 0}} <<
+  /ca 0.5
+  /CA 0.5
+>>
+endobj
+{{object 8 0}} <<
+  /Type /StructTreeRoot
+  /ParentTree 9 0 R
+  /K [10 0 R]
+  /RoleMap <<
+    /Document /Document
+    /Standard /P
+    /Figure /Figure
+  >>
+>>
+endobj
+{{object 9 0}} <<
+  /Nums [
+    0
+    [10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R 17 0 R]
+  ]
+>>
+endobj
+{{object 10 0}} <<
+  /Type /StructElem
+  /S /Document
+  /K [11 0 R]
+  /P 8 0 R
+  /T (TitleText)
+  /Pg 3 0 R
+  /Lang (en-US)
+>>
+endobj
+{{object 11 0}} <<
+  /Type /StructElem
+  /S /Table
+  /K [12 0 R 13 0 R]
+  /P 10 0 R
+  /Pg 3 0 R
+  /A [<<
+        /O /Table
+        /Summary ()
+      >>]
+  /ID (node12)
+  /Lang (hu)
+>>
+endobj
+{{object 12 0}} <<
+  /Type /StructElem
+  /S /TR
+  /K [14 0 R 15 0 R]
+  /P 11 0 R
+  /Pg 3 0 R
+  /ID ()
+>>
+endobj
+{{object 13 0}} <<
+  /Type /StructElem
+  /S /TR
+  /K [16 0 R 17 0 R]
+  /P 11 0 R
+  /Pg 3 0 R
+  /A <<
+      /O /Table
+     >>
+  /ID (node14)
+>>
+endobj
+{{object 14 0}} <<
+  /Type /StructElem
+  /S /TH
+  /P 12 0 R
+  /Pg 3 0 R
+  /A [<<
+        /O /Table
+        /Scope /Row
+      >>
+      <<
+        /O /Table
+        /ColSpan 2
+      >>]
+  /ID (node15)
+>>
+endobj
+{{object 15 0}} <<
+  /Type /StructElem
+  /S /TD
+  /P 12 0 R
+  /Pg 3 0 R
+  /ID (node16)
+>>
+endobj
+{{object 16 0}} <<
+  /Type /StructElem
+  /S /TH
+  /P 13 0 R
+  /Pg 3 0 R
+  /A [<<
+        /O /Table
+        /Scope /Row
+      >>]
+  /ID (node17)
+>>
+endobj
+{{object 17 0}} <<
+  /Type /StructElem
+  /S /TD
+  /P 13 0 R
+  /Pg 3 0 R
+  /A [<<
+        /O /Table
+        /ColProp (Sum)
+        /CurUSD true
+     >>]
+  /ID (node18)
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/tagged_table.pdf b/testing/resources/tagged_table.pdf
new file mode 100644
index 0000000..4428682
--- /dev/null
+++ b/testing/resources/tagged_table.pdf
@@ -0,0 +1,241 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /StructTreeRoot 8 0 R
+  /Lang (en-US)
+  /MarkInfo <<
+    /Marked true
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Group <<
+    /CS /DeviceRGB
+    /I true
+    /S /Transparency
+  >>
+  /Resources <<
+    /ProcSet [/PDF /ImageC /ImageI /ImageB]
+    /XObject <<
+      /Tr8 5 0 R
+      /Im7 6 0 R
+    >>
+    /ExtGState <<
+      /EGS9 7 0 R
+    >>
+  >>
+  /StructParents 0
+>>
+endobj
+4 0 obj <<
+  /Length 162
+>>
+stream
+0.1 w
+/Artifact
+BMC
+q
+0 0 612 792 re
+W* n
+EMC
+/Figure<</MCID 0>>
+BDC
+Q
+q
+281 685.3 50 50 re
+W* n
+q
+49.9 0 0 50 281.1 685.4 cm
+/Im7 Do
+Q
+EMC
+Q
+q
+EGS9 gs /Tr8 Do
+Q
+endstream
+endobj
+5 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [-140 395 753 395.1]
+  /Group <<
+    /CS /DeviceRGB
+    /K true
+    /S /Transparency
+  >>
+  /Length 0
+>>
+stream
+endstream
+endobj
+6 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /Length 61
+>>
+stream
+789cedc13101000000c2a0f54fed6f06a00000000000000078031d4c0001
+endstream
+endobj
+7 0 obj <<
+  /ca 0.5
+  /CA 0.5
+>>
+endobj
+8 0 obj <<
+  /Type /StructTreeRoot
+  /ParentTree 9 0 R
+  /K [10 0 R]
+  /RoleMap <<
+    /Document /Document
+    /Standard /P
+    /Figure /Figure
+  >>
+>>
+endobj
+9 0 obj <<
+  /Nums [
+    0
+    [10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R 17 0 R]
+  ]
+>>
+endobj
+10 0 obj <<
+  /Type /StructElem
+  /S /Document
+  /K [11 0 R]
+  /P 8 0 R
+  /T (TitleText)
+  /Pg 3 0 R
+  /Lang (en-US)
+>>
+endobj
+11 0 obj <<
+  /Type /StructElem
+  /S /Table
+  /K [12 0 R 13 0 R]
+  /P 10 0 R
+  /Pg 3 0 R
+  /A [<<
+        /O /Table
+        /Summary ()
+      >>]
+  /ID (node12)
+  /Lang (hu)
+>>
+endobj
+12 0 obj <<
+  /Type /StructElem
+  /S /TR
+  /K [14 0 R 15 0 R]
+  /P 11 0 R
+  /Pg 3 0 R
+  /ID ()
+>>
+endobj
+13 0 obj <<
+  /Type /StructElem
+  /S /TR
+  /K [16 0 R 17 0 R]
+  /P 11 0 R
+  /Pg 3 0 R
+  /A <<
+      /O /Table
+     >>
+  /ID (node14)
+>>
+endobj
+14 0 obj <<
+  /Type /StructElem
+  /S /TH
+  /P 12 0 R
+  /Pg 3 0 R
+  /A [<<
+        /O /Table
+        /Scope /Row
+      >>
+      <<
+        /O /Table
+        /ColSpan 2
+      >>]
+  /ID (node15)
+>>
+endobj
+15 0 obj <<
+  /Type /StructElem
+  /S /TD
+  /P 12 0 R
+  /Pg 3 0 R
+  /ID (node16)
+>>
+endobj
+16 0 obj <<
+  /Type /StructElem
+  /S /TH
+  /P 13 0 R
+  /Pg 3 0 R
+  /A [<<
+        /O /Table
+        /Scope /Row
+      >>]
+  /ID (node17)
+>>
+endobj
+17 0 obj <<
+  /Type /StructElem
+  /S /TD
+  /P 13 0 R
+  /Pg 3 0 R
+  /A [<<
+        /O /Table
+        /ColProp (Sum)
+        /CurUSD true
+     >>]
+  /ID (node18)
+>>
+endobj
+xref
+0 18
+0000000000 65535 f 
+0000000015 00000 n 
+0000000145 00000 n 
+0000000208 00000 n 
+0000000556 00000 n 
+0000000770 00000 n 
+0000000952 00000 n 
+0000001212 00000 n 
+0000001253 00000 n 
+0000001412 00000 n 
+0000001515 00000 n 
+0000001642 00000 n 
+0000001826 00000 n 
+0000001931 00000 n 
+0000002074 00000 n 
+0000002276 00000 n 
+0000002366 00000 n 
+0000002513 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 18
+>>
+startxref
+2683
+%%EOF
diff --git a/testing/resources/tagged_table_bad_elem.in b/testing/resources/tagged_table_bad_elem.in
new file mode 100644
index 0000000..c502047
--- /dev/null
+++ b/testing/resources/tagged_table_bad_elem.in
@@ -0,0 +1,153 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /StructTreeRoot 8 0 R
+  /Lang (en-US)
+  /MarkInfo <<
+    /Marked true
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Group <<
+    /CS /DeviceRGB
+    /I true
+    /S /Transparency
+  >>
+  /Resources <<
+    /ProcSet [/PDF /ImageC /ImageI /ImageB]
+    /XObject <<
+      /Tr8 5 0 R
+      /Im7 6 0 R
+    >>
+    /ExtGState <<
+      /EGS9 7 0 R
+    >>
+  >>
+  /StructParents 0
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+0.1 w
+/Artifact
+BMC
+q
+0 0 612 792 re
+W* n
+EMC
+/Figure<</MCID 0>>
+BDC
+Q
+q
+281 685.3 50 50 re
+W* n
+q
+49.9 0 0 50 281.1 685.4 cm
+/Im7 Do
+Q
+EMC
+Q
+q
+EGS9 gs /Tr8 Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [-140 395 753 395.1]
+  /Group <<
+    /CS /DeviceRGB
+    /K true
+    /S /Transparency
+  >>
+  {{streamlen}}
+>>
+stream
+endstream
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  {{streamlen}}
+>>
+stream
+789cedc13101000000c2a0f54fed6f06a00000000000000078031d4c0001
+endstream
+endobj
+{{object 7 0}} <<
+  /ca 0.5
+  /CA 0.5
+>>
+endobj
+{{object 8 0}} <<
+  /Type /StructTreeRoot
+  /ParentTree 9 0 R
+  /K [10 0 R]
+  /RoleMap <<
+    /Document /Document
+    /Standard /P
+    /Figure /Figure
+  >>
+>>
+endobj
+{{object 9 0}} <<
+  /Nums [
+    0
+    [10 0 R 11 0 R 12 0 R]
+  ]
+>>
+endobj
+{{object 10 0}} <<
+  /Type /StructElem
+  /S /Document
+  /K [11 0 R]
+  /P 8 0 R
+  /T (TitleText)
+  /Pg 3 0 R
+  /Lang (en-US)
+>>
+endobj
+{{object 11 0}} <<
+  % Deliberately missing /Type
+  /S /Table
+  /K [12 0 R]
+  /P 10 0 R
+  /Pg 3 0 R
+  /A [(bogus type)]
+  /ID (node12)
+  /Lang (hu)
+>>
+endobj
+{{object 12 0}} <<
+  % Deliberately bad /Type.
+  /Type /NotStructElem
+  /S /TR
+  /P 11 0 R
+  /Pg 3 0 R
+  /ID ()
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/tagged_table_bad_elem.pdf b/testing/resources/tagged_table_bad_elem.pdf
new file mode 100644
index 0000000..62beee0
--- /dev/null
+++ b/testing/resources/tagged_table_bad_elem.pdf
@@ -0,0 +1,172 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /StructTreeRoot 8 0 R
+  /Lang (en-US)
+  /MarkInfo <<
+    /Marked true
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /MediaBox [0 0 612 792]
+  /Group <<
+    /CS /DeviceRGB
+    /I true
+    /S /Transparency
+  >>
+  /Resources <<
+    /ProcSet [/PDF /ImageC /ImageI /ImageB]
+    /XObject <<
+      /Tr8 5 0 R
+      /Im7 6 0 R
+    >>
+    /ExtGState <<
+      /EGS9 7 0 R
+    >>
+  >>
+  /StructParents 0
+>>
+endobj
+4 0 obj <<
+  /Length 162
+>>
+stream
+0.1 w
+/Artifact
+BMC
+q
+0 0 612 792 re
+W* n
+EMC
+/Figure<</MCID 0>>
+BDC
+Q
+q
+281 685.3 50 50 re
+W* n
+q
+49.9 0 0 50 281.1 685.4 cm
+/Im7 Do
+Q
+EMC
+Q
+q
+EGS9 gs /Tr8 Do
+Q
+endstream
+endobj
+5 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [-140 395 753 395.1]
+  /Group <<
+    /CS /DeviceRGB
+    /K true
+    /S /Transparency
+  >>
+  /Length 0
+>>
+stream
+endstream
+endobj
+6 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /Width 50
+  /Height 50
+  /BitsPerComponent 8
+  /ColorSpace /DeviceRGB
+  /Filter [/ASCIIHexDecode /FlateDecode]
+  /Length 61
+>>
+stream
+789cedc13101000000c2a0f54fed6f06a00000000000000078031d4c0001
+endstream
+endobj
+7 0 obj <<
+  /ca 0.5
+  /CA 0.5
+>>
+endobj
+8 0 obj <<
+  /Type /StructTreeRoot
+  /ParentTree 9 0 R
+  /K [10 0 R]
+  /RoleMap <<
+    /Document /Document
+    /Standard /P
+    /Figure /Figure
+  >>
+>>
+endobj
+9 0 obj <<
+  /Nums [
+    0
+    [10 0 R 11 0 R 12 0 R]
+  ]
+>>
+endobj
+10 0 obj <<
+  /Type /StructElem
+  /S /Document
+  /K [11 0 R]
+  /P 8 0 R
+  /T (TitleText)
+  /Pg 3 0 R
+  /Lang (en-US)
+>>
+endobj
+11 0 obj <<
+  % Deliberately missing /Type
+  /S /Table
+  /K [12 0 R]
+  /P 10 0 R
+  /Pg 3 0 R
+  /A [(bogus type)]
+  /ID (node12)
+  /Lang (hu)
+>>
+endobj
+12 0 obj <<
+  % Deliberately bad /Type.
+  /Type /NotStructElem
+  /S /TR
+  /P 11 0 R
+  /Pg 3 0 R
+  /ID ()
+>>
+endobj
+xref
+0 13
+0000000000 65535 f 
+0000000015 00000 n 
+0000000145 00000 n 
+0000000208 00000 n 
+0000000556 00000 n 
+0000000770 00000 n 
+0000000952 00000 n 
+0000001212 00000 n 
+0000001253 00000 n 
+0000001412 00000 n 
+0000001480 00000 n 
+0000001607 00000 n 
+0000001758 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 13
+>>
+startxref
+1873
+%%EOF
diff --git a/testing/resources/text_in_page_marked.in b/testing/resources/text_in_page_marked.in
index e338479..5978110 100644
--- a/testing/resources/text_in_page_marked.in
+++ b/testing/resources/text_in_page_marked.in
@@ -131,7 +131,7 @@
 {{xref}}
 trailer <<
   /Root 1 0 R
-  /Size 13
+  {{trailersize}}
   /ID [<f341ae654a77acd5065a7645e596e6e6><bc37298a3f87f479229bce997ca791f7>]
 >>
 {{startxref}}
diff --git a/testing/resources/text_in_page_marked_indirect.in b/testing/resources/text_in_page_marked_indirect.in
index 1f989f6..6004ad4 100644
--- a/testing/resources/text_in_page_marked_indirect.in
+++ b/testing/resources/text_in_page_marked_indirect.in
@@ -137,7 +137,7 @@
 {{xref}}
 trailer <<
   /Root 1 0 R
-  /Size 14
+  {{trailersize}}
   /ID [<f341ae654a77acd5065a7645e596e6e6><bc37298a3f87f479229bce997ca791f7>]
 >>
 {{startxref}}
diff --git a/testing/resources/trailer_end_trailing_space.in b/testing/resources/trailer_end_trailing_space.in
new file mode 100644
index 0000000..0233b75
--- /dev/null
+++ b/testing/resources/trailer_end_trailing_space.in
@@ -0,0 +1,86 @@
+{{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
+  /Resources <<
+    /Font <<
+      /F1 4 0 R
+      /F2 5 0 R
+    >>
+  >>
+  /Contents 6 0 R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+% Single space after endstream and endobj.
+{{object 6 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+20 50 Td
+/F1 12 Tf
+(Hello, world!) Tj
+0 50 Td
+/F2 16 Tf
+(Goodbye, world!) Tj
+ET
+endstream 
+endobj 
+% Multiple spaces after endstream and endobj.
+{{object 6 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+20 50 Td
+/F1 12 Tf
+(Hello, world!) Tj
+0 50 Td
+/F2 16 Tf
+(Goodbye, world!) Tj
+ET
+endstream  
+endobj  
+% Tab after endstream and endobj.
+{{object 6 0}} <<
+  {{streamlen}}
+>>
+stream
+BT
+20 50 Td
+/F1 12 Tf
+(Hello, world!) Tj
+0 50 Td
+/F2 16 Tf
+(Goodbye, world!) Tj
+ET
+endstream	
+endobj	
+{{object 5 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/trailer_end_trailing_space.pdf b/testing/resources/trailer_end_trailing_space.pdf
new file mode 100644
index 0000000..e0ec6e4
--- /dev/null
+++ b/testing/resources/trailer_end_trailing_space.pdf
@@ -0,0 +1,99 @@
+%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
+  /Resources <<
+    /Font <<
+      /F1 4 0 R
+      /F2 5 0 R
+    >>
+  >>
+  /Contents 6 0 R
+>>
+endobj
+4 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+% Single space after endstream and endobj.
+6 0 obj <<
+  /Length 83
+>>
+stream
+BT
+20 50 Td
+/F1 12 Tf
+(Hello, world!) Tj
+0 50 Td
+/F2 16 Tf
+(Goodbye, world!) Tj
+ET
+endstream 
+endobj 
+% Multiple spaces after endstream and endobj.
+6 0 obj <<
+  /Length 83
+>>
+stream
+BT
+20 50 Td
+/F1 12 Tf
+(Hello, world!) Tj
+0 50 Td
+/F2 16 Tf
+(Goodbye, world!) Tj
+ET
+endstream  
+endobj  
+% Tab after endstream and endobj.
+6 0 obj <<
+  /Length 83
+>>
+stream
+BT
+20 50 Td
+/F1 12 Tf
+(Hello, world!) Tj
+0 50 Td
+/F2 16 Tf
+(Goodbye, world!) Tj
+ET
+endstream	
+endobj	
+5 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+>>
+endobj
+xref
+0 7
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000299 00000 n 
+0000000910 00000 n 
+0000000774 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 7
+>>
+startxref
+986
+%%EOF
diff --git a/testing/resources/two_signatures.in b/testing/resources/two_signatures.in
new file mode 100644
index 0000000..f18495f
--- /dev/null
+++ b/testing/resources/two_signatures.in
@@ -0,0 +1,155 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 200 300]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
+
+%% First incremental update adds an initial signature and update objects to
+%% refer to it.
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [7 0 R]
+    /SigFlags 3
+  >>
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Annots [7 0 R]
+>>
+endobj
+%% ByteRange is a pairs of integers (starting byte offset, length in bytes)
+{{object 5 0}} <<
+  /Type /Sig
+  /Filter /Adobe.PPKMS
+  /SubFilter /ETSI.CAdES.detached
+  /ByteRange [0 10 30 10]
+  /Contents <308006092A864886F70D010702A0803080020101>
+  /M (D:20200624093114+02'00')
+>>
+endobj
+{{object 6 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 0 0]
+  /Length 0
+>>
+stream
+endstream
+endobj
+{{object 7 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Sig
+  /F 132
+  /Rect [0 0 0 0]
+  /P 3 0 R
+  /T (Signature1)
+  /V 5 0 R
+  /DV 5 0 R
+  /AP <<
+    /N 6 0 R
+  >>
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
+
+%% Second incremental update adds a next signature and update objects once again to refer to it.
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [7 0 R 10 0 R]
+    /SigFlags 3
+  >>
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Annots [7 0 R 10 0 R]
+>>
+endobj
+{{object 8 0}} <<
+  /Type /Sig
+  /Filter /Adobe.PPKMS
+  /SubFilter /ETSI.CAdES.detached
+  /ByteRange [0 40 50 10]
+  /Contents <308006092A864886F70D010702A080308002010131>
+  /M (D:20200624093118+02'00')
+>>
+endobj
+{{object 9 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 0 0]
+  /Length 0
+>>
+stream
+endstream
+endobj
+{{object 10 0}} <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Sig
+  /F 132
+  /Rect [0 0 0 0]
+  /P 3 0 R
+  /T (Signature2)
+  /V 8 0 R
+  /DV 8 0 R
+  /AP <<
+    /N 9 0 R
+  >>
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/two_signatures.pdf b/testing/resources/two_signatures.pdf
new file mode 100644
index 0000000..9a033d6
--- /dev/null
+++ b/testing/resources/two_signatures.pdf
@@ -0,0 +1,195 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [0 0 200 300]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+4 0 obj <<
+  /Length 188
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000226 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 5
+>>
+startxref
+466
+%%EOF
+
+%% First incremental update adds an initial signature and update objects to
+%% refer to it.
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [7 0 R]
+    /SigFlags 3
+  >>
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Annots [7 0 R]
+>>
+endobj
+%% ByteRange is a pairs of integers (starting byte offset, length in bytes)
+5 0 obj <<
+  /Type /Sig
+  /Filter /Adobe.PPKMS
+  /SubFilter /ETSI.CAdES.detached
+  /ByteRange [0 10 30 10]
+  /Contents <308006092A864886F70D010702A0803080020101>
+  /M (D:20200624093114+02'00')
+>>
+endobj
+6 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 0 0]
+  /Length 0
+>>
+stream
+endstream
+endobj
+7 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Sig
+  /F 132
+  /Rect [0 0 0 0]
+  /P 3 0 R
+  /T (Signature1)
+  /V 5 0 R
+  /DV 5 0 R
+  /AP <<
+    /N 6 0 R
+  >>
+>>
+endobj
+xref
+0 8
+0000000000 65535 f 
+0000000726 00000 n 
+0000000068 00000 n 
+0000000835 00000 n 
+0000000226 00000 n 
+0000000998 00000 n 
+0000001201 00000 n 
+0000001303 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 8
+>>
+startxref
+1475
+%%EOF
+
+%% Second incremental update adds a next signature and update objects once again to refer to it.
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm <<
+    /Fields [7 0 R 10 0 R]
+    /SigFlags 3
+  >>
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Annots [7 0 R 10 0 R]
+>>
+endobj
+8 0 obj <<
+  /Type /Sig
+  /Filter /Adobe.PPKMS
+  /SubFilter /ETSI.CAdES.detached
+  /ByteRange [0 40 50 10]
+  /Contents <308006092A864886F70D010702A080308002010131>
+  /M (D:20200624093118+02'00')
+>>
+endobj
+9 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 0 0]
+  /Length 0
+>>
+stream
+endstream
+endobj
+10 0 obj <<
+  /Type /Annot
+  /Subtype /Widget
+  /FT /Sig
+  /F 132
+  /Rect [0 0 0 0]
+  /P 3 0 R
+  /T (Signature2)
+  /V 8 0 R
+  /DV 8 0 R
+  /AP <<
+    /N 9 0 R
+  >>
+>>
+endobj
+xref
+0 11
+0000000000 65535 f 
+0000001801 00000 n 
+0000000068 00000 n 
+0000001917 00000 n 
+0000000226 00000 n 
+0000000998 00000 n 
+0000001201 00000 n 
+0000001303 00000 n 
+0000002011 00000 n 
+0000002216 00000 n 
+0000002318 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 11
+>>
+startxref
+2491
+%%EOF
diff --git a/testing/resources/uri_action_nonascii.in b/testing/resources/uri_action_nonascii.in
new file mode 100644
index 0000000..d2ec540
--- /dev/null
+++ b/testing/resources/uri_action_nonascii.in
@@ -0,0 +1,44 @@
+{{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
+  /Annots [4 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /A 5 0 R
+  /FT /Tx
+  /Ff 29360128
+  /Type /Annot
+  /Subtype /Link
+  /F 4
+  /Rect [1 1 199 199]
+  /BS  <<
+    /W 1
+    /S /S
+  >>
+  /DA (/Helv 0 Tf 0 0 0 rg)
+  /V ()
+>>
+endobj
+{{object 5 0}} <<
+  /S /URI
+  /URI (https://example.com/\245octal\307chars)
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/uri_action_nonascii.pdf b/testing/resources/uri_action_nonascii.pdf
new file mode 100644
index 0000000..ffc084a
--- /dev/null
+++ b/testing/resources/uri_action_nonascii.pdf
@@ -0,0 +1,56 @@
+%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
+  /Annots [4 0 R]
+>>
+endobj
+4 0 obj <<
+  /A 5 0 R
+  /FT /Tx
+  /Ff 29360128
+  /Type /Annot
+  /Subtype /Link
+  /F 4
+  /Rect [1 1 199 199]
+  /BS  <<
+    /W 1
+    /S /S
+  >>
+  /DA (/Helv 0 Tf 0 0 0 rg)
+  /V ()
+>>
+endobj
+5 0 obj <<
+  /S /URI
+  /URI (https://example.com/\245octal\307chars)
+>>
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000226 00000 n 
+0000000414 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+493
+%%EOF
diff --git a/testing/resources/viewer_pref_types.in b/testing/resources/viewer_pref_types.in
new file mode 100644
index 0000000..11b8034
--- /dev/null
+++ b/testing/resources/viewer_pref_types.in
@@ -0,0 +1,36 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /ViewerPreferences <<
+    /Bool true
+    /Num 1
+    /Str (str)
+    /Name /name
+    /Null null
+    /Ref 3 0 R
+    /EmptyArray []
+    /GoodArray [true 1 (str) /name]
+    /BadArray1 [true []]
+    /BadArray2 [1 <<>>]
+    /BadArray3 [/name 3 0 R]
+    /Dict <<
+    >>
+  >>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/viewer_pref_types.pdf b/testing/resources/viewer_pref_types.pdf
new file mode 100644
index 0000000..f74ca6f
--- /dev/null
+++ b/testing/resources/viewer_pref_types.pdf
@@ -0,0 +1,46 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /ViewerPreferences <<
+    /Bool true
+    /Num 1
+    /Str (str)
+    /Name /name
+    /Null null
+    /Ref 3 0 R
+    /EmptyArray []
+    /GoodArray [true 1 (str) /name]
+    /BadArray1 [true []]
+    /BadArray2 [1 <<>>]
+    /BadArray3 [/name 3 0 R]
+    /Dict <<
+    >>
+  >>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+>>
+endobj
+xref
+0 4
+0000000000 65535 f 
+0000000015 00000 n 
+0000000337 00000 n 
+0000000400 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 4
+>>
+startxref
+451
+%%EOF
diff --git a/testing/resources/xfa/xfa_break_before_after.in b/testing/resources/xfa/xfa_break_before_after.in
new file mode 100644
index 0000000..1ff2f3a
--- /dev/null
+++ b/testing/resources/xfa/xfa_break_before_after.in
@@ -0,0 +1,54 @@
+{{header}}
+{{include ../xfa_catalog_1_0.fragment}}
+{{include ../xfa_object_2_0.fragment}}
+{{include ../xfa_preamble_3_0.fragment}}
+{{include ../xfa_config_4_0.fragment}}
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/3.0/">
+  <subform name="Form01" layout="tb" locale="en_ZA" restoreState="auto">
+    <pageSet>
+      <pageArea name="Page1" id="Page1" initialNumber="1">
+        <contentArea x="10.455mm" w="190.5mm" h="286mm"/>
+        <occur min="1" max="1"/>
+      </pageArea>
+    </pageSet>
+    <subform w="190.5mm" h="286mm" name="TForm1SubForm1">
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakBefore targetType="contentArea" />
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakAfter targetType="contentArea" />
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakBefore targetType="pageArea" />
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakAfter targetType="pageArea" />
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakBefore targetType="pageEven" />
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakAfter targetType="pageEven" />
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakBefore targetType="pageOdd" />
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakAfter targetType="pageOdd" />
+    </subform>
+  </subform>
+</template>
+endstream
+endobj
+{{include ../xfa_locale_6_0.fragment}}
+{{include ../xfa_postamble_7_0.fragment}}
+{{include ../xfa_pages_8_0.fragment}}
+{{xref}}
+{{trailer}}
+{{endxref}}
+%%EOF
diff --git a/testing/resources/xfa/xfa_break_before_after.pdf b/testing/resources/xfa/xfa_break_before_after.pdf
new file mode 100644
index 0000000..2235016
--- /dev/null
+++ b/testing/resources/xfa/xfa_break_before_after.pdf
@@ -0,0 +1,262 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /AcroForm 2 0 R
+  /Extensions <<
+    /ADBE <<
+      /BaseVersion /1.7
+      /ExtensionLevel 8
+    >>
+  >>
+  /NeedsRendering true
+  /Pages 8 0 R
+  /Type /Catalog
+>>
+endobj
+2 0 obj <<
+  /XFA [
+    (preamble)
+    3 0 R
+    (config)
+    4 0 R
+    (template)
+    5 0 R
+    (localeSet)
+    6 0 R
+    (postamble)
+    7 0 R
+  ]
+>>
+endobj
+3 0 obj <<
+  /Length 124
+>>
+stream
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/" timeStamp="2018-02-23T21:37:11Z" uuid="21482798-7bf0-40a4-bc5d-3cefdccf32b5">
+endstream
+endobj
+4 0 obj <<
+  /Length 642
+>>
+stream
+<config xmlns="http://www.xfa.org/schema/xci/3.0/">
+<agent name="designer">
+  <destination>pdf</destination>
+  <pdf>
+    <fontInfo/>
+  </pdf>
+</agent>
+<present>
+  <pdf>
+    <version>1.7</version>
+    <adobeExtensionLevel>8</adobeExtensionLevel>
+    <renderPolicy>client</renderPolicy>
+    <scriptModel>XFA</scriptModel>
+    <interactive>1</interactive>
+  </pdf>
+  <xdp>
+    <packets>*</packets>
+  </xdp>
+  <destination>pdf</destination>
+  <script>
+    <runScripts>server</runScripts>
+  </script>
+</present>
+<acrobat>
+  <acrobat7>
+    <dynamicRender>required</dynamicRender>
+  </acrobat7>
+  <validate>preSubmit</validate>
+</acrobat>
+</config>
+endstream
+endobj
+5 0 obj <<
+  /Length 1191
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/3.0/">
+  <subform name="Form01" layout="tb" locale="en_ZA" restoreState="auto">
+    <pageSet>
+      <pageArea name="Page1" id="Page1" initialNumber="1">
+        <contentArea x="10.455mm" w="190.5mm" h="286mm"/>
+        <occur min="1" max="1"/>
+      </pageArea>
+    </pageSet>
+    <subform w="190.5mm" h="286mm" name="TForm1SubForm1">
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakBefore targetType="contentArea" />
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakAfter targetType="contentArea" />
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakBefore targetType="pageArea" />
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakAfter targetType="pageArea" />
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakBefore targetType="pageEven" />
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakAfter targetType="pageEven" />
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakBefore targetType="pageOdd" />
+    </subform>
+    <subform w="190.5mm" h="286mm">
+      <breakAfter targetType="pageOdd" />
+    </subform>
+  </subform>
+</template>
+endstream
+endobj
+6 0 obj <<
+  /Length 3455
+>>
+stream
+<localeSet xmlns="http://www.xfa.org/schema/xfa-locale-set/2.7/">
+  <locale name="en_US" desc="English (United States)">
+    <calendarSymbols name="gregorian">
+      <monthNames>
+        <month>January</month>
+        <month>February</month>
+        <month>March</month>
+        <month>April</month>
+        <month>May</month>
+        <month>June</month>
+        <month>July</month>
+        <month>August</month>
+        <month>September</month>
+        <month>October</month>
+        <month>November</month>
+        <month>December</month>
+      </monthNames>
+      <monthNames abbr="1">
+        <month>Jan</month>
+        <month>Feb</month>
+        <month>Mar</month>
+        <month>Apr</month>
+        <month>May</month>
+        <month>Jun</month>
+        <month>Jul</month>
+        <month>Aug</month>
+        <month>Sep</month>
+        <month>Oct</month>
+        <month>Nov</month>
+        <month>Dec</month>
+      </monthNames>
+      <dayNames>
+        <day>Sunday</day>
+        <day>Monday</day>
+        <day>Tuesday</day>
+        <day>Wednesday</day>
+        <day>Thursday</day>
+        <day>Friday</day>
+        <day>Saturday</day>
+      </dayNames>
+      <dayNames abbr="1">
+        <day>Sun</day>
+        <day>Mon</day>
+        <day>Tue</day>
+        <day>Wed</day>
+        <day>Thu</day>
+        <day>Fri</day>
+        <day>Sat</day>
+      </dayNames>
+      <meridiemNames>
+        <meridiem>AM</meridiem>
+        <meridiem>PM</meridiem>
+      </meridiemNames>
+      <eraNames>
+        <era>BC</era>
+        <era>AD</era>
+      </eraNames>
+    </calendarSymbols>
+    <datePatterns>
+      <datePattern name="full">EEEE, MMMM D, YYYY</datePattern>
+      <datePattern name="long">MMMM D, YYYY</datePattern>
+      <datePattern name="med">MMM D, YYYY</datePattern>
+      <datePattern name="short">M/D/YY</datePattern>
+    </datePatterns>
+    <timePatterns>
+      <timePattern name="full">h:MM:SS A Z</timePattern>
+      <timePattern name="long">h:MM:SS A Z</timePattern>
+      <timePattern name="med">h:MM:SS A</timePattern>
+      <timePattern name="short">h:MM A</timePattern>
+    </timePatterns>
+    <dateTimeSymbols>GyMdkHmsSEDFwWahKzZ</dateTimeSymbols>
+    <numberPatterns>
+      <numberPattern name="numeric">z,zz9.zzz</numberPattern>
+      <numberPattern name="currency">$z,zz9.99|($z,zz9.99)</numberPattern>
+      <numberPattern name="percent">z,zz9%</numberPattern>
+    </numberPatterns>
+    <numberSymbols>
+      <numberSymbol name="decimal">.</numberSymbol>
+      <numberSymbol name="grouping">,</numberSymbol>
+      <numberSymbol name="percent">%</numberSymbol>
+      <numberSymbol name="minus">-</numberSymbol>
+      <numberSymbol name="zero">0</numberSymbol>
+    </numberSymbols>
+    <currencySymbols>
+      <currencySymbol name="symbol">$</currencySymbol>
+      <currencySymbol name="isoname">USD</currencySymbol>
+      <currencySymbol name="decimal">.</currencySymbol>
+    </currencySymbols>
+    <typefaces>
+      <typeface name="Myriad Pro"/>
+      <typeface name="Minion Pro"/>
+      <typeface name="Courier Std"/>
+      <typeface name="Adobe Pi Std"/>
+      <typeface name="Adobe Hebrew"/>
+      <typeface name="Adobe Arabic"/>
+      <typeface name="Adobe Thai"/>
+      <typeface name="Kozuka Gothic Pro-VI M"/>
+      <typeface name="Kozuka Mincho Pro-VI R"/>
+      <typeface name="Adobe Ming Std L"/>
+      <typeface name="Adobe Song Std L"/>
+      <typeface name="Adobe Myungjo Std M"/>
+    </typefaces>
+  </locale>
+</localeSet>
+endstream
+endobj
+7 0 obj <<
+  /Length 11
+>>
+stream
+</xdp:xdp>
+endstream
+endobj
+8 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [9 0 R]
+>>
+endobj
+9 0 obj <<
+  /Type /Page
+  /Parent 8 0 R
+  /MediaBox [0 0 612 792]
+>>
+endobj
+xref
+0 10
+0000000000 65535 f 
+0000000015 00000 n 
+0000000199 00000 n 
+0000000358 00000 n 
+0000000534 00000 n 
+0000001228 00000 n 
+0000002472 00000 n 
+0000005980 00000 n 
+0000006042 00000 n 
+0000006105 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 10
+>>
+{{endxref}}
+%%EOF
diff --git a/testing/resources/xfa/xfa_combobox.pdf b/testing/resources/xfa/xfa_combobox.pdf
index 0989361..a4b606c 100644
--- a/testing/resources/xfa/xfa_combobox.pdf
+++ b/testing/resources/xfa/xfa_combobox.pdf
@@ -224,7 +224,7 @@
 endobj
 9 0 obj <<
   /Type /Page
-  /Parent 2 0 R
+  /Parent 8 0 R
   /MediaBox [0 0 612 792]
 >>
 endobj
diff --git a/testing/resources/xfa/xfa_date_time_edit.pdf b/testing/resources/xfa/xfa_date_time_edit.pdf
index 04ea239..33f02bd 100644
--- a/testing/resources/xfa/xfa_date_time_edit.pdf
+++ b/testing/resources/xfa/xfa_date_time_edit.pdf
@@ -219,7 +219,7 @@
 endobj
 9 0 obj <<
   /Type /Page
-  /Parent 2 0 R
+  /Parent 8 0 R
   /MediaBox [0 0 612 792]
 >>
 endobj
diff --git a/testing/resources/xfa/xfa_image_edit.in b/testing/resources/xfa/xfa_image_edit.in
index 0e36fdd..a3bdae6 100644
--- a/testing/resources/xfa/xfa_image_edit.in
+++ b/testing/resources/xfa/xfa_image_edit.in
@@ -21,7 +21,9 @@
           <imageEdit data="embed"/>
         </ui>
         <value>
-          <image contentType="image/jpg">FOOOEY</image>
+          <image contentType="image/jpg">
+            {{include ../mona_lisa.fragment}}
+          </image>
         </value>
       </field>
     </subform>
diff --git a/testing/resources/xfa/xfa_image_edit.pdf b/testing/resources/xfa/xfa_image_edit.pdf
index 7496367..d35ef5d 100644
--- a/testing/resources/xfa/xfa_image_edit.pdf
+++ b/testing/resources/xfa/xfa_image_edit.pdf
@@ -72,7 +72,7 @@
 endstream
 endobj
 5 0 obj <<
-  /Length 667
+  /Length 9005
 >>
 stream
 <template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
@@ -89,7 +89,117 @@
           <imageEdit data="embed"/>
         </ui>
         <value>
-          <image contentType="image/jpg">FOOOEY</image>
+          <image contentType="image/jpg">
+/9j/4AAQSkZJRgABAQEAZABkAAD//gBSRmlsZSBzb3VyY2U6IGh0dHA6Ly9jb21tb25zLndpa2lt
+ZWRpYS5vcmcvd2lraS9GaWxlOk1vbmFfTGlzYV9mYWNlXzgwMHg4MDBweC5qcGf/2wBDAAYEBQYF
+BAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUo
+KSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgo
+KCgoKCgoKCgoKCgoKCj/wAARCAB4AHgDAREAAhEBAxEB/8QAHAAAAgIDAQEAAAAAAAAAAAAABQYE
+BwEDCAIA/8QAOxAAAgECBQIEBAQFAwMFAAAAAQIDBBEABRIhMQZBEyJRYQcycYEUkaGxFUJiwfAj
+JOEWF9ElM1LC8f/EABoBAAIDAQEAAAAAAAAAAAAAAAIDAQQFAAb/xAAvEQACAgICAgAEBQQCAwAA
+AAAAAQIRAyESMQRBBRMiUTJhcaGxFCNSgZHwweHx/9oADAMBAAIRAxEAPwBqiIChT5t7gEYwX1ss
+JUbY0ErKBHpFu498Q3fRKTMmJVNm2POo9/tiG2kTFG+FfIAq9t2ta2BtvsKtkqKmDE2UgMDiUn2C
+3ugTnvVGRdPy6M2zKFJxv+HivLN6i6LuPvbDsalPaRPy29Cw/wAYOn1kPgUmYS3OxZUjJvwbFrgY
+fHx8hziqCVF8V+namVUnSvojzqlp9SkD3QkYGWKa62SsY5ZVmVDmlMKnLamCqgvu8bBgPYjkH64U
+3x/FpkcWF4yT8oG4337YBzdUgVHezEwN1N1BxzurslJGI1uSpJG3bvjlew2bYUN+fS+Cj12DJkqE
+WNgOeL4OM66AaNscXkb2vziVK47ZD0IkcG1r/X/PthVWFZLRNKqAAdiCT9cC19jk77MRpqILKNjf
+i/tbCpcug40SooAz6tNyfLsOPbBQhslsrH4n/ECXLa+Tp7IZvBrxaOprEXU0Fx8if1+/a/ri3i8e
+1zn1/P8A6Ba6oR8h6YqY8szKshpauaWdNCzBHkZifmYk778ffAZvIU5Qg3pMsQg4xbS2KmcZQKPU
+aqmqItVhqeEqL9rnGhjzc3SaZXyY63QGDvSuJaScofVfMp57YsOPLUkLUnHcQplXUFfQZrFV5bK2
+XZkll8SEWRx6MnBB9D+mEywppqW0N+by/U6Y+EvXdP1rlrJKkVNnVMAtVTLsrDgSJ30nj2O3pjLz
+4XhlXr0d+JWh6eJQCy2b6DEOKAT+55hQrYsNrW98TH2S2ToAATYG5GwI4wUqSYPZuSEBNXAIBO2E
+8eO0TZ8UZVtf5hvbE7RHYkQRG6kix7f2wuMnexjr0SvDZiqpf6dsH30D12bIKe77gna7W7bYHbbC
+tIFdf9R0/R3S9VmcrIJFHh06t/PKR5Rbk+v0GLMISk1CK2wE03begP8ACHommhy6LM8zjhqM0qf9
+eeoljEjhm3sL7D7b++KmXK8+Tin9K0l+hcUflwTrbLXFHCABftYdvtthbjRHNgjOsoiqKaaJ9Tqy
+kFWAKn88Vsip6HwlfZzV8R+kKeikeSjS128wCD+2NT4f57yfTP0I8jCltFWyJJEXul0vYkLce4Jx
+tqSfRRoJ9K5/WdNdSUOb5a7iWmbUVPEkZI1o3qCt/wBMBmxLLjcP+P1IjOpWztvJa+lzTJ6PMqF9
+dJVxLLG47qwuPpbi2MqP0Opd9ByV9E2KNWF97AbNiWlu2crN8cIABIFxyL7YW2yTZGhlNr7eltsc
+nZD0ZkpyI++1+cMcVVg3sUUWMxRtHpbVc3GKr4voYrM063k32YEi9t9zhiTXRDJVHAzSB3uoK2F/
+bEwm4u7OaT0UL8cs9TqHPsqyZEH+nWxaWvuus7KB6keYn6Dti5gySm5Zn0kyflrGlBdtl7dNWp6F
+FY2awU39RjFxx1aNHJ9g6pdWa5LX3J9MS27aE0uyHXiSRCFN9rG3OEThyGxaQi9UdOpXwyCW1muD
+b/PbC4J4ZckNdTVM5u63y5cpzeemilCQDzebuff9bY9R4eV5cak+zMzxUJUhVmbUNQt6W9bC++Lp
+WaSOjvgT1JK/w+ho5mtHl9Y1OD6o1pB9hrP5Yy/Mhxm697Gp3Wy0aXNhNMsIYi29u1j2v37Yq7fZ
+LetB01arE5DKVQaRY8m+/wDbHUxTnvQRplvbcaTuL+/GIXdMY2bwbxFd2wXNtUD07KMyvqid3WGn
+LvqszF0PIB8o/LfEairZNT9D109mYzSmMiRaJUkMbjmxIDCx+hwacpNEKadk3Nq38HlE8oGpvw7s
+thvcKdsLlG6TCUt0jkbrCsaXramzCGSNr10MmkfNrUqtxfsQBtjS8eP9hwf2f7jJ25KS+6Lv/wC6
+VBDlQq0lSipEm/DhqpZSddr2IRG08H5iDtxjGXi5XJQjt1fa6L7yQrkx1n6p0dKSZwIwAE4vca+B
+Y973B++KXJyfFd9B/LS2xLTq7NKbIWzvOfHpaeRxGFWkknILfKCFsBe3rg/lKWR48cr/ADbpHcmo
+3JAOLrjqHPcwio8uyeaohcj/AHUaSJGBv5jrUabW4P64tT8XHjjynk39tX+wr5km6jErP4oUM8Od
++JUfM/zC99/rjT+GZYvHSRV8lNSViSmmF2XUrM1wQefe2NTsp7uix/hJWFKDNaUMViM0E5AYgsNJ
+Ui/2GKHmR/C/ZPWi5MkzgykxMYlABszCxuP32xVUFsGWR+w1Lm0cNJHGAJRIw3U302bfbne2Ipds
+SOXT9UKqEsHLJqBH0HbFKVqSTLqaadByJ/D1FgbW2vhuO07BbXRydQZocvqqclJCW31XA3IsSe1z
+fbFiWHndC1krQfyLrl6TI5KKFNFTGDaULfU1yGJ/NQCe2AyYJJ/T0Hjkktk2l6tmoTmVI0zSrBEq
+wySG5RmZthc8AXIxPy3KCZy1Oijxoi67yiKUf7eHNI1Z73DDxRck+v8A4xou/wCnlXbi/wCBqf1x
+17OwP4TBW05RJqiJTJ4jBQu7X3O4Nj7jHmFBTjyTNNzcHRo6ioUk6ckprBYGlBFxfYf/AIMdwaWi
+LTeyblGXRnLYYWqaiOwsdDDf87jELFyVSOlNx/Caa5abKKdjCZGL3LNI5YnC5pQdRQULkrZyz8Yc
+wNZ1G3hFrIeB2/y2PRfCYccNv2Z/lu5UhEcuXErWYHdibbE/2xq1ZRHz4VpNP1BVU1I0i+PRlyq2
+a5V1P/2/XFPyopQTfoJLk9Fy5TRVkMjvLTVMpA3ZrHew/I/bvijyW9kSg0uiWIK2PSYaGpYA7Cwv
+xyT3xzURLvsaukq2opJSKiCWJGJtZCdz9sJlFdpjMc2tMbJMxQqbyMCb8jAPI1sclZyPmMBZ2ZZW
+GkeJqLaix4A+mNODr0IaNbMYiDDJIAbgMBbUpAO4F9r3/K+J2yaT7AGeVdTYtHIG1Pu3qTcbn7n8
+sWMUVQSb5C3mFayrGYXa8bhyrdiDdbHvxh0YJ6GznpUdk9I9QJmuUUciOC00aEXO1iAf748jNPHc
+H6dGy6nUgN8QOuafpqloqGty+tkr5m3iiFw68albhhxxv64b4+CedaaSX3ETlHG7e7GLpPMzU9OU
+lZVRS0krhv8AQlPnUajpv72thTSg2rug/wASTFzrTNZTCywgkr9sLjHnJX6Dk6WjmLrCoaozeWR7
+sdR2btvwceq8VVjSRlZvxAaWcNEpK2f+axPGLCEMNdLVMkWYRmJn8UkqpHuLf87emByxuLvoh7Lb
+ynM8wphrSSrjB8xW1lJ2/wA7YoNKnQDeg7H1DXooAmrFckWUbn9rdsQ8a9CqDtB1XUJvUzh1202s
+Dv625tgJwXVEptK7CY6jlqoisTygggGy7geuFcFdkcm/pbOdpa7xpQzCXzeQG2s27WONCMKHHppn
+lHhqg1KbhhtfjkG37euJBe2Q81y92y8vM6HUy2AI8p3P9t//ADgoZVy0iYJ3QqThI4NBVZJmc3bk
+qB6fXFlNt2Ntca9l5fA7qWlzTp6PJ6ipNPmVIDGrgjV4d/Kwvza9vtjznxXBKGR5K+mX8ml4mVTg
+oe0WR1OlR4UUZrcwqHUXSdcvjk29yLAfljN0pW/5LXoh5DA1NMZszrKyrn07LKiokYHoFHJuNyTi
+ZSjJrVV+oKX52LvXGcwDxI42BYgjbfe22H+Pi5y2LyTpUc/Z9KZcwZ2Yl2a/t7DHqMKSjSMvK9gy
+VlvZCRtp+vqcNQtumFMhDfjIWifS9wb3taxwvJJKLOirLlpljjdYiulnF/KfNuNhx9+cZnPTdC3H
+VBCSjUxqZFOlPMCU77knb2xHNN7YFM30ckEQIAXTfY6fMLd8Tr2CyfJnsUKlIULNfsPl55PpxiJQ
+V2FG0ikqKoLlRpRlbSCjC4O/Fr8k+98aEo/caHaWYyOVMOXBRIWCSHYj6ffCZxXabIoL5hl0/wD0
+5WySJQNEkQmURhiWCkG1jyQL++EJrmtjIqip8x8JpnnkRXZyQNPC27n12tjSiq0TrtkGOtlocziq
+sullp54SGjkDWa+3+Wwc4KUeM9pkW4SuLOi+kfjrlz9PxwZ0r0+ZIgWTSl0f+ofX0x5/P8Kywf8A
+a3H+DRx+VCf4tMEZ/wDEuOoB/A6UjI80kjC/5emBx/DZ95Dp511ErvMuojVvppr1EzXJfgL32vzj
+Sx+Lw70V5ZL62LVXSy+O5ZjIxNy9tgMXLVUV3FoinQiG+7WNiB++D7AJuWzGJ1K3BUcn7EYiULVE
+xdOy6qLNoTl1LIWEr2XSVIsGNr77d7jb9cZrwtXE7l7DdVXxVETeV4ybKoQ727m3bfCfkxUrBcrV
+UQBXwq7GJZmXRqKActf0+ww2Ka0C4Jn1TnETVI0sCC1iBGPU/p+3ptjpJvs5QS9i1Q0IVUctAlmu
+PCjB/ckg4a5xYwPUpiiZ1srPqHnbw9TahyABzta+Ikv8URFm+oqA0Eo8VDGV0MqkEDm4P14++EO0
+9DE12UbnlGtPn1VTh2aNJLIVTc+VSARtv5gMa2KVwUgX2BZUu6nURcXJY73w7sGSoO9ARUcnWuUQ
+5giyUk1QIXRvl84Ki/3IxW8vl8mTi6dB4GlkXItXqb4WU8JY0EcSWO5AuAMZGH4nKL4z2Wsnjp7R
+AynonTIgnjUpcCygj8v3wefzk1pnQxv2TOrOlBT5S/gRqiqoJ2tbfck+mEeN5vLJ9TGSxaKlqKfw
+igdGUgHy25B4xvRmn0UZRNog8GjSWQeQFjbttYYlTTlR0o0rLL6OzCiqMshK1FPSLpWO7garj5u3
+YnnFDKppvt//AEFRTHVYacgQ0udQSMynWGS4tzzqxUtydyQTiq0YfpseHLWawaYC6u3kPoCvub4O
+OWvpbI4ezbTdJxC0r1XhIx1aW8oUn1v/AJvivPzI39DsdHx5PtUVPoneOVpayRKlKZfCaJiCdyTv
+ck2F/uMaXKnpavYiiMnW1Y9OwqxTtO6C0ynQoIuo1AA78/8AGHy8aL6YN0eM16rr5qeHxggpp0Cz
+aYwLMrndTyLGx9MQvHjvfR1+hPramevr6iaSZpJJZS2vgsext9hixGKjFJE22fZbltZmtctLQwy1
+FQ+yJGNRv+1vfHTyQhHnkdImMJSfFLZevw3+EUcEyV2czh6lfkVR5EbtYnk+/tjB8n4m8v0YVS/c
+u4vGUPql2XFJl5MAWYaibD5f8tjIk0XEjNNkSIxa2jfa/wCWBa+xNkHPMnSajdbgEIwIbv6XHpgV
+qXJBJXo5zqsnEWdGjIWS8oVSG+XU9h9bb3HbHpoZ+WHn+RQljqVEHqOkENFDSqiMq1UxW3JF7H7X
+Xj64d487lyv0hWRar8wJk0arVCJ2RUJJ1MdlbixPobYt5HcW0V0iysiyTMa6ro2pqOi0RvdHjk1K
++2wIBJIxk5pwjabewoxd2hpHRvU1TmFFXVGZapoTdjqKge3Nj2H0AxXl5eOMWlHsOOCTY7ZjUTUm
+VwrLL+JrVGyKCdbD1P8ALz3OMzDH63JaTLkp8Y17KG6k8OOKopKGIvII1GuxBtxb2G55/vj0HjqT
+lym/ZTlVUhPqqA/wWREVS8Uha+m+w2IB/LjGgprmhDTI2awuuVUxmLqI49Kqex1E2PvucdGVzdE1
+SsG0ccktSqxX1Dvbgep/PBt0rOirejpj4C9IJQ5TLWTxXnqW1K9iCYx8ot6Hn7jHmfifkPNlUU9L
++TVwY/lw/Nl009Ivg2CgWsRccHFSNpWiW7dGWijuBGLXbAPYa12bWj0wGx78WxMlUQU9i91AqpSz
+CUnwyp498VcjldD8Zz7EIaTOqd6dkll/EukjWuCQbkqSeNyfXtvj0D5TxvkvVlZ1FiP1DXvJmwic
+qRCWN/fUf73xqePjUcdr2UMsvqPa5LVVVG88dNK8RYBNKni3Jt62wXz4xbi2FxbV0Fum80qKevSn
+qZXpp1kChilib8bYR5GFSi5R2LafRfGW9RR1FHFBUxzyGMJTuIxpAfgAXtfb19748/ODT2WYz0e6
+l6GOnWVzXxr4nhp8urUb3BBa/wBzhP1ctfuE6rZVOe0aNCskauXSO7Kw0rIO2/pe36Y1sGRp02Vp
+xdWKeeS02V5PGs0aLUMRaPuSMaGLnlyWnoBpRVsQayplq5DJMQVDHSnZfYY0IxSWhTdlp/B3pMZt
+RzVksPiXa2+1gP2A2NxvjH+JeY8clCJf8TEmuTOm+k8pGU5VBT69ciLubW1H1AxhOTcm37LcpWHw
+BYDa/e+Du9AL7mDGRp3IOq9sQ4vom/Z9UJqW1rEbHvgZHJ7EvrVo48qqBJJa6FTcHk9vU4RdzikW
+I9MpSopZ6itCUdGoUEkuyhUDXW9zfbgbcXxtQmlG5S3/AOCrkT6iiZknw5paurmq5JRmFbK5Z1SI
+yxq1727Ltfe57YXk+JTSUIql+4C8ZW5NhvOsloaOkZMwzGKIR/8AtJNVrCL8GyxrueRtfAYcuSTk
+4x3+l/yFOMUtspvq5MtjrUejqF8YfM0c0rgenzqP0ON7xnkcWpr+DPy1eiT0v17XZUwhqJmeEEBJ
+lVQwP9RNwdu/IwryPAhkuUdMnHlcdMtTJ/iHPDCGzFEZJPKr+EmpbDctpA59TvjMn4bb+mi1GQKp
+0myd2rM8rqengC6pUldW2K7WUEkX+vNhbA5az/Rhi2/0ohfRuWim+r83Gd59PVxK0dKvkp47Wsg9
+vfnHofGwfIxqDdv2Uck+UrQMjjaQrCpbzDg84baQNM6J+Due02Vwfgc5XRS2DxS6SFF+dQ/Kx+2P
+M/EcDlk+bD/Zq+PlShxZfFPXUtTRGqpqiOSMjV4isCCPrx3xm8lX2D4uyTSFpRrIIU4OKs6TrROA
+TQe/IGHqOhLbs1SeGgYHn62H1wuSDTbK862rqGOOZDLrYA/JJpAJG4v22v72xWWJzmmh/wAxQVMq
+PMetMloJJHaMZhU2IjhtaFe3H83bnGvj+HZsi/xX7lV+RFO3sWM++JOdVkfgzVy5fSqDppqRLEDi
+5tsP0Pti/g+F4obrl+bEy8lvrQjVuc1Uxv8AiZyp/pA3+u+NKOCEVVFdzb7YPeeRyLs7G9zra+DS
+SAb9mI9QG1irHcDg46jhx6Unlqf/AEzYTIC8bjctYElSODtc7+n0xUz41H+5/wB/UtYJuX0P/v5A
+3rmeq/i38PqleMU41OjC12Nz+gNh9cT4UY8OcfYnNafF+hZbbzXv7W74t0IDmQQJJI81TfSGAuDx
+te+/+cYRlbWkGl9xsjzYitpIqYMksqDQNfyKCbk32vYHn/5DFT5Sabl6GqdPQcyjNJTI6ZfO1KsV
+tbU8ugsx33HDADfj88V8mBNXJXY6GV9Idss+IWf5epi/iaT6AAyVFOCRe1hdbeo5v2xVfiQ7imv9
+jVlT7C8HxZro95IKCcKbF4y4J/Q/TAf0kv8AIJzh3REz74pVE1NIbU9DDGt5JSS+/ZFIFy1+w3+g
+3wEfDcpV2BLyIpfSVD1D1f40FU5m8JpzaOJV8SZ151MSdMYNztuxxq4PD4ta6/4/19ytPNYjPXSy
+E+CCt99iSxPqTycaKikV07C2X5bClI1bVgtHTjdb7SPvZfzt+uFSnLkox7YVLshVcaRKseka7NyN
+gbL/AM4bFtnPQLWOQm6q5ABJ8p49T7e+D5AbZ6VvmNh2sL8Y6yVYRy+qemqaadLiSFw4sObdt8RJ
+cotP2HGTi7XZ/9k=
+          </image>
         </value>
       </field>
     </subform>
@@ -222,7 +332,7 @@
 endobj
 9 0 obj <<
   /Type /Page
-  /Parent 2 0 R
+  /Parent 8 0 R
   /MediaBox [0 0 612 792]
 >>
 endobj
@@ -234,14 +344,14 @@
 0000000358 00000 n 
 0000000534 00000 n 
 0000001228 00000 n 
-0000001947 00000 n 
-0000005455 00000 n 
-0000005517 00000 n 
-0000005580 00000 n 
+0000010286 00000 n 
+0000013794 00000 n 
+0000013856 00000 n 
+0000013919 00000 n 
 trailer <<
   /Root 1 0 R
   /Size 10
 >>
 startxref
-5657
+13996
 %%EOF
diff --git a/testing/resources/xfa/xfa_multiline_textfield.pdf b/testing/resources/xfa/xfa_multiline_textfield.pdf
index 6b7eae4..eedd6a2 100644
--- a/testing/resources/xfa/xfa_multiline_textfield.pdf
+++ b/testing/resources/xfa/xfa_multiline_textfield.pdf
@@ -221,7 +221,7 @@
 endobj
 9 0 obj <<
   /Type /Page
-  /Parent 2 0 R
+  /Parent 8 0 R
   /MediaBox [0 0 612 792]
 >>
 endobj
diff --git a/testing/resources/xfa_pages_8_0.fragment b/testing/resources/xfa_pages_8_0.fragment
index ce089c4..2a8a910 100644
--- a/testing/resources/xfa_pages_8_0.fragment
+++ b/testing/resources/xfa_pages_8_0.fragment
@@ -6,7 +6,7 @@
 endobj
 {{object 9 0}} <<
   /Type /Page
-  /Parent 2 0 R
+  /Parent 8 0 R
   /MediaBox [0 0 612 792]
 >>
 endobj
diff --git a/testing/scoped_set_tz.cpp b/testing/scoped_set_tz.cpp
new file mode 100644
index 0000000..06b70dc
--- /dev/null
+++ b/testing/scoped_set_tz.cpp
@@ -0,0 +1,44 @@
+// Copyright 2021 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/scoped_set_tz.h"
+
+#include <stdlib.h>
+#include <time.h>
+
+#include "build/build_config.h"
+#include "third_party/base/check_op.h"
+
+namespace {
+
+constexpr char kTZ[] = "TZ";
+
+#if BUILDFLAG(IS_WIN)
+#define SETENV(name, value) _putenv_s(name, value)
+#define TZSET _tzset
+#define UNSETENV(name) _putenv_s(name, "")
+#else
+#define SETENV(name, value) setenv(name, value, 1)
+#define TZSET tzset
+#define UNSETENV(name) unsetenv(name)
+#endif
+
+}  // namespace
+
+ScopedSetTZ::ScopedSetTZ(const std::string& tz) {
+  const char* old_tz = getenv(kTZ);
+  if (old_tz)
+    old_tz_ = old_tz;
+
+  CHECK_EQ(0, SETENV(kTZ, tz.c_str()));
+  TZSET();
+}
+
+ScopedSetTZ::~ScopedSetTZ() {
+  if (old_tz_.has_value())
+    CHECK_EQ(0, SETENV(kTZ, old_tz_.value().c_str()));
+  else
+    CHECK_EQ(0, UNSETENV(kTZ));
+  TZSET();
+}
diff --git a/testing/scoped_set_tz.h b/testing/scoped_set_tz.h
new file mode 100644
index 0000000..5928958
--- /dev/null
+++ b/testing/scoped_set_tz.h
@@ -0,0 +1,26 @@
+// Copyright 2021 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_SCOPED_SET_TZ_H_
+#define TESTING_SCOPED_SET_TZ_H_
+
+#include <string>
+
+#include "core/fxcrt/fx_memory.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+class ScopedSetTZ {
+ public:
+  FX_STACK_ALLOCATED();
+
+  explicit ScopedSetTZ(const std::string& tz);
+  ScopedSetTZ(const ScopedSetTZ&) = delete;
+  ScopedSetTZ& operator=(const ScopedSetTZ&) = delete;
+  ~ScopedSetTZ();
+
+ private:
+  absl::optional<std::string> old_tz_;
+};
+
+#endif  // TESTING_SCOPED_SET_TZ_H_
diff --git a/testing/string_write_stream.cpp b/testing/string_write_stream.cpp
index 53141eb..1a99d9a 100644
--- a/testing/string_write_stream.cpp
+++ b/testing/string_write_stream.cpp
@@ -1,8 +1,9 @@
-// Copyright 2018 PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "testing/string_write_stream.h"
+
 #include "core/fxcrt/bytestring.h"
 #include "core/fxcrt/widestring.h"
 
@@ -10,23 +11,7 @@
 
 StringWriteStream::~StringWriteStream() = default;
 
-FX_FILESIZE StringWriteStream::GetSize() {
-  return stream_.tellp();
-}
-
-bool StringWriteStream::Flush() {
-  return true;
-}
-
-bool StringWriteStream::WriteBlockAtOffset(const void* pData,
-                                           FX_FILESIZE offset,
-                                           size_t size) {
-  ASSERT(offset == 0);
-  stream_.write(static_cast<const char*>(pData), size);
-  return true;
-}
-
-bool StringWriteStream::WriteString(ByteStringView str) {
-  stream_.write(str.unterminated_c_str(), str.GetLength());
+bool StringWriteStream::WriteBlock(pdfium::span<const uint8_t> buffer) {
+  stream_.write(reinterpret_cast<const char*>(buffer.data()), buffer.size());
   return true;
 }
diff --git a/testing/string_write_stream.h b/testing/string_write_stream.h
index 08a2174..fc4dc02 100644
--- a/testing/string_write_stream.h
+++ b/testing/string_write_stream.h
@@ -1,4 +1,4 @@
-// Copyright 2018 PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -10,18 +10,13 @@
 
 #include "core/fxcrt/fx_stream.h"
 
-class StringWriteStream final : public IFX_SeekableWriteStream {
+class StringWriteStream final : public IFX_RetainableWriteStream {
  public:
   StringWriteStream();
   ~StringWriteStream() override;
 
-  // IFX_SeekableWriteStream
-  FX_FILESIZE GetSize() override;
-  bool Flush() override;
-  bool WriteBlockAtOffset(const void* pData,
-                          FX_FILESIZE offset,
-                          size_t size) override;
-  bool WriteString(ByteStringView str) override;
+  // IFX_WriteStream:
+  bool WriteBlock(pdfium::span<const uint8_t> buffer) override;
 
   std::string ToString() const { return stream_.str(); }
 
diff --git a/testing/test.gni b/testing/test.gni
index 5a8505f..6ad2c2d 100644
--- a/testing/test.gni
+++ b/testing/test.gni
@@ -1,4 +1,4 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
+# Copyright 2015 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -14,6 +14,7 @@
 template("test") {
   if (is_android) {
     import("//build/config/android/config.gni")
+    import("//build/config/android/internal_rules.gni")
     import("//build/config/android/rules.gni")
 
     _use_raw_android_executable = defined(invoker.use_raw_android_executable) &&
@@ -53,12 +54,9 @@
         # the default shared_library configs rather than executable configs.
         configs -= [
           "//build/config:shared_library_config",
-          "//build/config/android:hide_all_but_jni_onload",
-        ]
-        configs += [
-          "//build/config:executable_config",
           "//build/config/android:hide_all_but_jni",
         ]
+        configs += [ "//build/config:executable_config" ]
 
         # Don't output to the root or else conflict with the group() below.
         output_name = rebase_path(_exec_output, root_out_dir)
@@ -68,26 +66,41 @@
         testonly = true
         dist_dir = "$root_out_dir/$target_name"
         binary = _exec_output
-        deps = [
-          ":$_exec_target",
-        ]
+        deps = [ ":$_exec_target" ]
         if (defined(invoker.extra_dist_files)) {
           extra_files = invoker.extra_dist_files
         }
       }
     } else {
-      _library_target = "_${target_name}__library"
-      _apk_target = "${target_name}_apk"
+      _library_target = "${target_name}__library"
+      _apk_target = "${target_name}__apk"
       _apk_specific_vars = [
         "android_manifest",
         "android_manifest_dep",
         "enable_multidex",
+        "product_config_java_packages",
+        "min_sdk_version",
         "proguard_configs",
         "proguard_enabled",
+        "shared_resources",
+        "srcjar_deps",
+        "target_sdk_version",
         "use_default_launcher",
-        "write_asset_list",
         "use_native_activity",
       ]
+
+      # Adds the unwind tables from unstripped binary as an asset file in the
+      # apk, if |add_unwind_tables_in_apk| is specified by the test.
+      if (defined(invoker.add_unwind_tables_in_apk) &&
+          invoker.add_unwind_tables_in_apk) {
+        _unwind_table_asset_name = "${target_name}_unwind_assets"
+        unwind_table_asset(_unwind_table_asset_name) {
+          testonly = true
+          library_target = _library_target
+          deps = [ ":$_library_target" ]
+        }
+      }
+
       shared_library(_library_target) {
         # Configs will always be defined since we set_defaults in BUILDCONFIG.gn.
         configs = []  # Prevent list overwriting warning.
@@ -106,7 +119,7 @@
         }
       }
       unittest_apk(_apk_target) {
-        forward_variables_from(invoker, _apk_specific_vars + [ "deps" ])
+        forward_variables_from(invoker, _apk_specific_vars)
         shared_library = ":$_library_target"
         apk_name = invoker.target_name
         if (defined(invoker.output_name)) {
@@ -114,74 +127,59 @@
           install_script_name = "install_${invoker.output_name}"
         }
 
-        # TODO(agrieve): Remove this data_dep once bots don't build the _apk
-        #     target (post-GYP).
-        # It's a bit backwards for the apk to depend on the runner script, since
-        # the apk is conceptually a runtime_dep of the script. However, it is
-        # currently necessary because the bots build this _apk target directly
-        # rather than the group() below.
-        data_deps = [
-          ":$_test_runner_target",
-        ]
-      }
+        if (defined(invoker.deps)) {
+          deps = invoker.deps
+        } else {
+          deps = []
+        }
 
-      # Incremental test targets work only for .apks.
-      _incremental_test_runner_target =
-          "${_output_name}_incremental__test_runner_script"
-      test_runner_script(_incremental_test_runner_target) {
-        forward_variables_from(invoker,
-                               _wrapper_script_vars + [
-                                     "data",
-                                     "data_deps",
-                                     "deps",
-                                     "public_deps",
-                                   ])
-        apk_target = ":$_apk_target"
-        test_name = "${_output_name}_incremental"
-        test_type = "gtest"
-        test_suite = _output_name
-        incremental_install = true
-      }
-      group("${target_name}_incremental") {
-        testonly = true
-        datadeps = [
-          ":$_incremental_test_runner_target",
-        ]
-        deps = [
-          ":${_apk_target}_incremental",
-        ]
+        # Add the Java classes so that each target does not have to do it.
+        deps += [ "//base/test:test_support_java" ]
+
+        if (defined(_unwind_table_asset_name)) {
+          deps += [ ":${_unwind_table_asset_name}" ]
+        }
       }
     }
 
-    _test_runner_target = "${_output_name}__test_runner_script"
     test_runner_script(_test_runner_target) {
-      forward_variables_from(invoker,
-                             _wrapper_script_vars + [
-                                   "data",
-                                   "data_deps",
-                                   "deps",
-                                   "public_deps",
-                                 ])
+      forward_variables_from(invoker, _wrapper_script_vars)
 
       if (_use_raw_android_executable) {
         executable_dist_dir = "$root_out_dir/$_dist_target"
+        data_deps = [ ":$_exec_target" ]
       } else {
         apk_target = ":$_apk_target"
+        incremental_apk = incremental_install
+
+        # Dep needed for the test runner .runtime_deps file to pick up data
+        # deps from the forward_variables_from(invoker, "*") on the library.
+        data_deps = [ ":$_library_target" ]
       }
       test_name = _output_name
-      test_type = "gtest"
       test_suite = _output_name
+      test_type = "gtest"
     }
 
-    group(target_name) {
+    # Create a wrapper script rather than using a group() in order to ensure
+    # "ninja $target_name" always works. If this was a group(), then GN would
+    # not create a top-level alias for it if a target exists in another
+    # directory with the same $target_name.
+    # Also - bots run this script directly for "components_perftests".
+    generate_wrapper(target_name) {
       testonly = true
-      deps = [
-        ":$_test_runner_target",
-      ]
+      executable = "$root_build_dir/bin/run_$_output_name"
+      wrapper_script = "$root_build_dir/$_output_name"
+      deps = [ ":$_test_runner_target" ]
       if (_use_raw_android_executable) {
         deps += [ ":$_dist_target" ]
       } else {
-        deps += [ ":$_apk_target" ]
+        # Dep needed for the swarming .isolate file to pick up data
+        # deps from the forward_variables_from(invoker, "*") on the library.
+        deps += [
+          ":$_apk_target",
+          ":$_library_target",
+        ]
       }
     }
   } else if (is_ios) {
@@ -192,19 +190,14 @@
 
     bundle_data(_resources_bundle_data) {
       visibility = [ ":$_test_target" ]
-      sources = [
-        "//testing/gtest_ios/Default.png",
-      ]
-      outputs = [
-        "{{bundle_resources_dir}}/{{source_file_part}}",
-      ]
+      sources = [ "//testing/gtest_ios/Default.png" ]
+      outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
     }
 
     ios_app_bundle(_test_target) {
       testonly = true
 
       # See above call.
-      set_sources_assignment_filter([])
       forward_variables_from(invoker, "*", [ "testonly" ])
 
       # Provide sensible defaults in case invoker did not define any of those
@@ -244,9 +237,7 @@
     if (defined(invoker.output_name) && target_name != invoker.output_name) {
       group("${invoker.output_name}_run") {
         testonly = true
-        deps = [
-          ":${invoker.target_name}",
-        ]
+        deps = [ ":${invoker.target_name}" ]
       }
     }
   }
@@ -256,6 +247,8 @@
 set_defaults("test") {
   if (is_android) {
     configs = default_shared_library_configs
+    configs -= [ "//build/config/android:hide_all_but_jni_onload" ]
+    configs += [ "//build/config/android:hide_all_but_jni" ]
   } else {
     configs = default_executable_configs
   }
@@ -271,9 +264,7 @@
     if (defined(invoker.configs)) {
       configs += invoker.configs
     }
-    deps = [
-      _pdfium_root_dir + ":pdfium_unittest_deps",
-    ]
+    deps = [ _pdfium_root_dir + ":pdfium_unittest_deps" ]
     if (defined(invoker.deps)) {
       deps += invoker.deps
     }
@@ -292,9 +283,7 @@
     if (defined(invoker.configs)) {
       configs += invoker.configs
     }
-    deps = [
-      _pdfium_root_dir + ":pdfium_embeddertest_deps",
-    ]
+    deps = [ _pdfium_root_dir + ":pdfium_embeddertest_deps" ]
     if (defined(invoker.deps)) {
       deps += invoker.deps
     }
diff --git a/testing/test_fonts.cpp b/testing/test_fonts.cpp
new file mode 100644
index 0000000..e3b6559
--- /dev/null
+++ b/testing/test_fonts.cpp
@@ -0,0 +1,116 @@
+// Copyright 2021 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/test_fonts.h"
+
+#include <set>
+#include <utility>
+
+#include "core/fxge/cfx_fontmapper.h"
+#include "core/fxge/cfx_fontmgr.h"
+#include "core/fxge/cfx_gemodule.h"
+#include "core/fxge/systemfontinfo_iface.h"
+#include "testing/utils/path_service.h"
+
+namespace {
+
+ByteString RenameFontForTesting(const ByteString& face) {
+  ByteString result;
+  if (face.Contains("Arial") || face.Contains("Calibri") ||
+      face.Contains("Helvetica")) {
+    // Sans
+    result = "Arimo";
+  } else if (face.IsEmpty() || face.Contains("Times")) {
+    // Serif
+    result = "Tinos";
+  } else if (face.Contains("Courier")) {
+    // Mono
+    result = "Cousine";
+  } else {
+    // Some tests expect the fallback font.
+    return face;
+  }
+
+  if (face.Contains("Bold"))
+    result += " Bold";
+
+  if (face.Contains("Italic") || face.Contains("Oblique"))
+    result += " Italic";
+
+  return result;
+}
+
+// Intercepts font requests and renames font faces to those in test_fonts.
+class SystemFontInfoWrapper : public SystemFontInfoIface {
+ public:
+  explicit SystemFontInfoWrapper(std::unique_ptr<SystemFontInfoIface> impl)
+      : impl_(std::move(impl)) {}
+  ~SystemFontInfoWrapper() { CHECK(active_fonts_.empty()); }
+
+  bool EnumFontList(CFX_FontMapper* pMapper) override {
+    return impl_->EnumFontList(pMapper);
+  }
+  void* MapFont(int weight,
+                bool bItalic,
+                FX_Charset charset,
+                int pitch_family,
+                const ByteString& face) override {
+    void* font = impl_->MapFont(weight, bItalic, charset, pitch_family,
+                                RenameFontForTesting(face));
+    if (font) {
+      bool inserted = active_fonts_.insert(font).second;
+      CHECK(inserted);
+    }
+    return font;
+  }
+  void* GetFont(const ByteString& face) override {
+    return impl_->GetFont(RenameFontForTesting(face));
+  }
+  size_t GetFontData(void* hFont,
+                     uint32_t table,
+                     pdfium::span<uint8_t> buffer) override {
+    return impl_->GetFontData(hFont, table, buffer);
+  }
+  bool GetFaceName(void* hFont, ByteString* name) override {
+    auto face = RenameFontForTesting(*name);
+    return impl_->GetFaceName(hFont, &face);
+  }
+  bool GetFontCharset(void* hFont, FX_Charset* charset) override {
+    return impl_->GetFontCharset(hFont, charset);
+  }
+  void DeleteFont(void* hFont) override {
+    CHECK(active_fonts_.erase(hFont));
+    impl_->DeleteFont(hFont);
+  }
+
+ private:
+  std::unique_ptr<SystemFontInfoIface> impl_;
+  std::set<void*> active_fonts_;
+};
+
+}  // namespace
+
+TestFonts::TestFonts() {
+  if (!PathService::GetExecutableDir(&font_path_))
+    return;
+  font_path_.push_back(PATH_SEPARATOR);
+  font_path_.append("test_fonts");
+  font_paths_ = std::make_unique<const char*[]>(2);
+  font_paths_[0] = font_path_.c_str();
+  font_paths_[1] = nullptr;
+}
+
+TestFonts::~TestFonts() = default;
+
+void TestFonts::InstallFontMapper() {
+  auto* font_mapper = CFX_GEModule::Get()->GetFontMgr()->GetBuiltinMapper();
+  font_mapper->SetSystemFontInfo(std::make_unique<SystemFontInfoWrapper>(
+      font_mapper->TakeSystemFontInfo()));
+}
+
+// static
+std::string TestFonts::RenameFont(const char* face) {
+  ByteString renamed_face = RenameFontForTesting(face);
+  return std::string(renamed_face.c_str());
+}
diff --git a/testing/test_fonts.h b/testing/test_fonts.h
new file mode 100644
index 0000000..4e2a822
--- /dev/null
+++ b/testing/test_fonts.h
@@ -0,0 +1,27 @@
+// Copyright 2021 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_TEST_FONTS_H_
+#define TESTING_TEST_FONTS_H_
+
+#include <memory>
+#include <string>
+
+class TestFonts {
+ public:
+  TestFonts();
+  ~TestFonts();
+
+  const char** font_paths() const { return font_paths_.get(); }
+
+  void InstallFontMapper();
+
+  static std::string RenameFont(const char* face);
+
+ private:
+  std::string font_path_;
+  std::unique_ptr<const char*[]> font_paths_;
+};
+
+#endif  // TESTING_TEST_FONTS_H_
diff --git a/testing/test_loader.cpp b/testing/test_loader.cpp
index 33ee331..aac8429 100644
--- a/testing/test_loader.cpp
+++ b/testing/test_loader.cpp
@@ -1,4 +1,4 @@
-// Copyright 2019 PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -6,7 +6,7 @@
 
 #include <string.h>
 
-#include "third_party/base/logging.h"
+#include "third_party/base/notreached.h"
 
 TestLoader::TestLoader(pdfium::span<const char> span) : m_Span(span) {}
 
diff --git a/testing/test_loader.h b/testing/test_loader.h
index 17ca9e9..f3d0042 100644
--- a/testing/test_loader.h
+++ b/testing/test_loader.h
@@ -1,4 +1,4 @@
-// Copyright 2019 PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/test_support.cpp b/testing/test_support.cpp
deleted file mode 100644
index 94ce528..0000000
--- a/testing/test_support.cpp
+++ /dev/null
@@ -1,15 +0,0 @@
-// 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 "testing/test_support.h"
-
-#include "core/fxge/cfx_gemodule.h"
-
-void InitializePDFTestEnvironment() {
-  CFX_GEModule::Create(nullptr);
-}
-
-void DestroyPDFTestEnvironment() {
-  CFX_GEModule::Destroy();
-}
diff --git a/testing/test_support.h b/testing/test_support.h
index f237ea5..1faf1a8 100644
--- a/testing/test_support.h
+++ b/testing/test_support.h
@@ -1,4 +1,4 @@
-// Copyright 2015 PDFium Authors. All rights reserved.
+// Copyright 2015 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -46,7 +46,4 @@
 
 }  // namespace pdfium
 
-void InitializePDFTestEnvironment();
-void DestroyPDFTestEnvironment();
-
 #endif  // TESTING_TEST_SUPPORT_H_
diff --git a/testing/tools/.style.yapf b/testing/tools/.style.yapf
deleted file mode 100644
index de0c6a7..0000000
--- a/testing/tools/.style.yapf
+++ /dev/null
@@ -1,2 +0,0 @@
-[style]
-based_on_style = chromium
diff --git a/testing/tools/PRESUBMIT.py b/testing/tools/PRESUBMIT.py
index 59a3858..0b89656 100644
--- a/testing/tools/PRESUBMIT.py
+++ b/testing/tools/PRESUBMIT.py
@@ -1,16 +1,20 @@
-# Copyright 2019 The PDFium Authors. All rights reserved.
+# Copyright 2019 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
+
 """Presubmit script for PDFium testing tools.
 
 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
 for more details on the presubmit API built into depot_tools.
 """
 
+USE_PYTHON3 = True
+
 
 def _CommonChecks(input_api, output_api):
   tests = []
-  tests.extend(input_api.canned_checks.GetPylint(input_api, output_api))
+  tests.extend(
+      input_api.canned_checks.GetPylint(input_api, output_api, version='2.7'))
   return tests
 
 
diff --git a/testing/tools/api_check.py b/testing/tools/api_check.py
index 66b3077..bea8845 100755
--- a/testing/tools/api_check.py
+++ b/testing/tools/api_check.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# Copyright 2017 The PDFium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2017 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Verifies exported functions in public/*.h are in fpdf_view_c_api_test.c.
@@ -99,16 +99,16 @@
 
 
 def _FindDuplicates(functions):
-  return set([f for f in functions if functions.count(f) > 1])
+  return {f for f in functions if functions.count(f) > 1}
 
 
 def _CheckAndPrintFailures(failure_list, failure_message):
   if not failure_list:
     return True
 
-  print '%s:' % failure_message
+  print('%s:' % failure_message)
   for f in sorted(failure_list):
-    print f
+    print(f)
   return False
 
 
diff --git a/testing/tools/common.py b/testing/tools/common.py
index 108fcfd..80b7815 100755
--- a/testing/tools/common.py
+++ b/testing/tools/common.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# Copyright 2015 The PDFium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2015 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -10,6 +10,8 @@
 import subprocess
 import sys
 
+import pdfium_root
+
 
 def os_name():
   if sys.platform.startswith('linux'):
@@ -21,14 +23,6 @@
   raise Exception('Confused, can not determine OS, aborting.')
 
 
-def RunCommand(cmd):
-  try:
-    subprocess.check_call(cmd)
-    return None
-  except subprocess.CalledProcessError as e:
-    return e
-
-
 def RunCommandPropagateErr(cmd,
                            stdout_has_errors=False,
                            exit_status_on_error=None):
@@ -61,50 +55,17 @@
   return output
 
 
-# RunCommandExtractHashedFiles returns a tuple: (raised_exception, hashed_files)
-# It runs the given command. If it fails it will return an exception and None.
-# If it succeeds it will return None and the list of processed files extracted
-# from the output of the command. It expects lines in this format:
-#    MD5:<path_to_image_file>:<md5_hash_in_hex>
-# The returned hashed_files is a list of (file_path, MD5-hash) pairs.
-def RunCommandExtractHashedFiles(cmd):
-  try:
-    output = subprocess.check_output(cmd, universal_newlines=True)
-    ret = []
-    for line in output.split('\n'):
-      line = line.strip()
-      if line.startswith("MD5:"):
-        ret.append([x.strip() for x in line.lstrip("MD5:").rsplit(":", 1)])
-    return None, ret
-  except subprocess.CalledProcessError as e:
-    return e, None
-
-
 class DirectoryFinder:
   '''A class for finding directories and paths under either a standalone
   checkout or a chromium checkout of PDFium.'''
 
   def __init__(self, build_location):
-    # |build_location| is typically "out/Debug" or "out/Release".
-    # Expect |my_dir| to be .../pdfium/testing/tools.
-    self.my_dir = os.path.dirname(os.path.realpath(__file__))
-    self.testing_dir = os.path.dirname(self.my_dir)
-    if (os.path.basename(self.my_dir) != 'tools' or
-        os.path.basename(self.testing_dir) != 'testing'):
-      raise Exception('Confused, can not find pdfium root directory, aborting.')
-    self.pdfium_dir = os.path.dirname(self.testing_dir)
-    # Find path to build directory.  This depends on whether this is a
-    # standalone build vs. a build as part of a chromium checkout. For
-    # standalone, we expect a path like .../pdfium/out/Debug, but for
-    # chromium, we expect a path like .../src/out/Debug two levels
-    # higher (to skip over the third_party/pdfium path component under
-    # which chromium sticks pdfium).
-    self.base_dir = self.pdfium_dir
-    one_up_dir = os.path.dirname(self.base_dir)
-    two_up_dir = os.path.dirname(one_up_dir)
-    if (os.path.basename(two_up_dir) == 'src' and
-        os.path.basename(one_up_dir) == 'third_party'):
-      self.base_dir = two_up_dir
+    # `build_location` is typically "out/Debug" or "out/Release".
+    root_finder = pdfium_root.RootDirectoryFinder()
+    self.testing_dir = os.path.join(root_finder.pdfium_root, 'testing')
+    self.my_dir = os.path.join(self.testing_dir, 'tools')
+    self.pdfium_dir = root_finder.pdfium_root
+    self.base_dir = root_finder.source_root
     self.build_dir = os.path.join(self.base_dir, build_location)
     self.os_name = os_name()
 
@@ -133,26 +94,31 @@
       result = os.path.join(result, other_components)
     return result
 
+  def ThirdPartyFontsDir(self):
+    '''Finds directory with the test fonts.'''
+    return os.path.join(self.base_dir, 'third_party', 'test_fonts')
+
 
 def GetBooleanGnArg(arg_name, build_dir, verbose=False):
   '''Extract the value of a boolean flag in args.gn'''
   cwd = os.getcwd()
   os.chdir(build_dir)
   gn_args_output = subprocess.check_output(
-      ['gn', 'args', '.', '--list=%s' % arg_name, '--short'])
+      ['gn', 'args', '.', '--list=%s' % arg_name, '--short']).decode('utf8')
   os.chdir(cwd)
   arg_match_output = re.search('%s = (.*)' % arg_name, gn_args_output).group(1)
   if verbose:
-    print >> sys.stderr, "Found '%s' for value of %s" % (arg_match_output,
-                                                         arg_name)
+    print(
+        "Found '%s' for value of %s" % (arg_match_output, arg_name),
+        file=sys.stderr)
   return arg_match_output == 'true'
 
 
 def PrintWithTime(s):
   """Prints s prepended by a timestamp."""
-  print '[%s] %s' % (datetime.datetime.now().strftime("%Y%m%d %H:%M:%S"), s)
+  print('[%s] %s' % (datetime.datetime.now().strftime("%Y%m%d %H:%M:%S"), s))
 
 
 def PrintErr(s):
   """Prints s to stderr."""
-  print >> sys.stderr, s
+  print(s, file=sys.stderr)
diff --git a/testing/tools/coverage/coverage_report.py b/testing/tools/coverage/coverage_report.py
index 5785eab..2c57946 100755
--- a/testing/tools/coverage/coverage_report.py
+++ b/testing/tools/coverage/coverage_report.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# Copyright 2017 The PDFium Authors. All rights reserved.
+#!/usr/bin/env vpython3
+# Copyright 2017 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Generates a coverage report for given tests.
@@ -16,14 +16,11 @@
 import subprocess
 import sys
 
-# Add src dir to path to avoid having to set PYTHONPATH.
+# Add parent dir to avoid having to set PYTHONPATH.
 sys.path.append(
-    os.path.abspath(
-        os.path.join(
-            os.path.dirname(__file__), os.path.pardir, os.path.pardir,
-            os.path.pardir)))
+    os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
 
-from testing.tools.common import GetBooleanGnArg
+import common
 
 # 'binary' is the file that is to be run for the test.
 # 'use_test_runner' indicates if 'binary' depends on test_runner.py and thus
@@ -41,18 +38,32 @@
         TestSpec('run_corpus_tests.py', True, []),
     'corpus_tests_javascript_disabled':
         TestSpec('run_corpus_tests.py', True, ['--disable-javascript']),
+    'corpus_tests_xfa_disabled':
+        TestSpec('run_corpus_tests.py', True, ['--disable-xfa']),
+    'corpus_tests_render_oneshot':
+        TestSpec('run_corpus_tests.py', True, ['--render-oneshot']),
+    'corpus_tests_reverse_byte_order':
+        TestSpec('run_corpus_tests.py', True, ['--reverse-byte-order']),
     'javascript_tests':
         TestSpec('run_javascript_tests.py', True, []),
     'javascript_tests_javascript_disabled':
         TestSpec('run_javascript_tests.py', True, ['--disable-javascript']),
+    'javascript_tests_xfa_disabled':
+        TestSpec('run_javascript_tests.py', True, ['--disable-xfa']),
     'pixel_tests':
         TestSpec('run_pixel_tests.py', True, []),
     'pixel_tests_javascript_disabled':
         TestSpec('run_pixel_tests.py', True, ['--disable-javascript']),
+    'pixel_tests_xfa_disabled':
+        TestSpec('run_pixel_tests.py', True, ['--disable-xfa']),
+    'pixel_tests_render_oneshot':
+        TestSpec('run_pixel_tests.py', True, ['--render-oneshot']),
+    'pixel_tests_reverse_byte_order':
+        TestSpec('run_pixel_tests.py', True, ['--reverse-byte-order']),
 }
 
 
-class CoverageExecutor(object):
+class CoverageExecutor:
 
   def __init__(self, parser, args):
     """Initialize executor based on the current script environment
@@ -85,14 +96,14 @@
           'No valid tests in set to be run. This is likely due to bad command '
           'line arguments')
 
-    if not GetBooleanGnArg('use_clang_coverage', self.build_directory,
-                           self.verbose):
+    if not common.GetBooleanGnArg('use_clang_coverage', self.build_directory,
+                                  self.verbose):
       parser.error(
           'use_clang_coverage does not appear to be set to true for build, but '
           'is needed')
 
-    self.use_goma = GetBooleanGnArg('use_goma', self.build_directory,
-                                    self.verbose)
+    self.use_goma = common.GetBooleanGnArg('use_goma', self.build_directory,
+                                           self.verbose)
 
     self.output_directory = args['output_directory']
     if not os.path.exists(self.output_directory):
@@ -109,38 +120,38 @@
   def check_output(self, args, dry_run=False, env=None):
     """Dry run aware wrapper of subprocess.check_output()"""
     if dry_run:
-      print "Would have run '%s'" % ' '.join(args)
+      print("Would have run '%s'" % ' '.join(args))
       return ''
 
     output = subprocess.check_output(args, env=env)
 
     if self.verbose:
-      print "check_output(%s) returned '%s'" % (args, output)
+      print("check_output(%s) returned '%s'" % (args, output))
     return output
 
   def call(self, args, dry_run=False, env=None):
     """Dry run aware wrapper of subprocess.call()"""
     if dry_run:
-      print "Would have run '%s'" % ' '.join(args)
+      print("Would have run '%s'" % ' '.join(args))
       return 0
 
     output = subprocess.call(args, env=env)
 
     if self.verbose:
-      print 'call(%s) returned %s' % (args, output)
+      print('call(%s) returned %s' % (args, output))
     return output
 
   def call_silent(self, args, dry_run=False, env=None):
     """Dry run aware wrapper of subprocess.call() that eats output from call"""
     if dry_run:
-      print "Would have run '%s'" % ' '.join(args)
+      print("Would have run '%s'" % ' '.join(args))
       return 0
 
     with open(os.devnull, 'w') as f:
       output = subprocess.call(args, env=env, stdout=f)
 
     if self.verbose:
-      print 'call_silent(%s) returned %s' % (args, output)
+      print('call_silent(%s) returned %s' % (args, output))
     return output
 
   def calculate_coverage_tests(self, args):
@@ -186,10 +197,10 @@
         spec: Tuple containing the TestSpec.
     """
     if self.verbose:
-      print "Generating coverage for test '%s', using data '%s'" % (name, spec)
+      print("Generating coverage for test '%s', using data '%s'" % (name, spec))
     if not os.path.exists(spec.binary):
       print('Unable to generate coverage for %s, since it appears to not exist'
-            ' @ %s') % (name, spec.binary)
+            ' @ %s' % (name, spec.binary))
       return False
 
     binary_args = [spec.binary]
@@ -212,7 +223,7 @@
       binary_args.extend(['-j', '8', '--build-dir', self.build_directory])
     if self.call(binary_args, dry_run=self.dry_run, env=env) and self.verbose:
       print('Running %s appears to have failed, which might affect '
-            'results') % spec.binary
+            'results' % spec.binary)
 
     return True
 
@@ -241,7 +252,7 @@
     coverage_args += ['-o', report_directory]
     coverage_args += self.build_targets
 
-    # Whitelist the directories of interest
+    # Only analyze the directories of interest.
     coverage_args += ['-f', 'core']
     coverage_args += ['-f', 'fpdfsdk']
     coverage_args += ['-f', 'fxbarcode']
@@ -250,7 +261,7 @@
     coverage_args += ['-f', 'samples']
     coverage_args += ['-f', 'xfa']
 
-    # Blacklist test files
+    # Ignore test files.
     coverage_args += ['-i', '.*test.*']
 
     # Component view is only useful for Chromium
@@ -261,24 +272,24 @@
   def run(self):
     """Setup environment, execute the tests and generate coverage report"""
     if not self.fetch_profiling_tools():
-      print 'Unable to fetch profiling tools'
+      print('Unable to fetch profiling tools')
       return False
 
     if not self.build_binaries():
-      print 'Failed to successfully build binaries'
+      print('Failed to successfully build binaries')
       return False
 
-    for name in self.coverage_tests.keys():
+    for name in self.coverage_tests:
       if not self.generate_coverage(name, self.coverage_tests[name]):
-        print 'Failed to successfully generate coverage data'
+        print('Failed to successfully generate coverage data')
         return False
 
     if not self.merge_raw_coverage_results():
-      print 'Failed to successfully merge raw coverage results'
+      print('Failed to successfully merge raw coverage results')
       return False
 
     if not self.generate_html_report():
-      print 'Failed to successfully generate HTML report'
+      print('Failed to successfully generate HTML report')
       return False
 
     return True
diff --git a/testing/tools/encode_pdf_filter.py b/testing/tools/encode_pdf_filter.py
index 2d56543..1985fb7 100755
--- a/testing/tools/encode_pdf_filter.py
+++ b/testing/tools/encode_pdf_filter.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python3
-# Copyright 2019 The PDFium Authors. All rights reserved.
+# Copyright 2019 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Encodes binary data using one or more PDF stream filters.
@@ -13,6 +13,7 @@
 """
 
 import argparse
+from abc import ABCMeta, abstractmethod
 import base64
 import collections
 import collections.abc
@@ -21,10 +22,22 @@
 import zlib
 
 
-class _PdfStream:
+class _PdfStream(metaclass=ABCMeta):
   _unique_filter_classes = []
   _filter_classes = {}
 
+  @classmethod
+  @property
+  @abstractmethod
+  def name(cls):
+    pass
+
+  @classmethod
+  @property
+  @abstractmethod
+  def aliases(cls):
+    pass
+
   @staticmethod
   def GetFilterByName(name):
     # Tolerate any case-insensitive match for "/Name" or "Name", or an alias.
@@ -38,18 +51,24 @@
 
     return filter_class
 
-  @staticmethod
-  def RegisterFilter(filter_class):
-    assert filter_class not in _PdfStream._unique_filter_classes
-    _PdfStream._unique_filter_classes.append(filter_class)
+  @classmethod
+  def Register(cls):
+    assert cls not in _PdfStream._unique_filter_classes
+    _PdfStream._unique_filter_classes.append(cls)
+    cls.RegisterByName()
+    cls.RegisterByAliases()
 
-    assert filter_class.name[0] == '/'
-    lower_name = filter_class.name.lower()
-    _PdfStream._filter_classes[lower_name] = filter_class
-    _PdfStream._filter_classes[lower_name[1:]] = filter_class
+  @classmethod
+  def RegisterByName(cls):
+    assert cls.name[0] == '/'
+    lower_name = cls.name.lower()
+    _PdfStream._filter_classes[lower_name] = cls
+    _PdfStream._filter_classes[lower_name[1:]] = cls
 
-    for alias in filter_class.aliases:
-      _PdfStream._filter_classes[alias.lower()] = filter_class
+  @classmethod
+  def RegisterByAliases(cls):
+    for alias in cls.aliases:
+      _PdfStream._filter_classes[alias.lower()] = cls
 
   @staticmethod
   def GetHelp():
@@ -59,6 +78,21 @@
                                             ', '.join(filter_class.aliases))
     return text
 
+  @classmethod
+  def AddEntries(cls, entries):
+    _PdfStream.AddListEntry(entries, 'Filter', cls.name)
+
+  @staticmethod
+  def AddListEntry(entries, key, value):
+    old_value = entries.get(key)
+    if old_value is None:
+      entries[key] = value
+    else:
+      if not isinstance(old_value, collections.abc.MutableSequence):
+        old_value = [old_value]
+        entries[key] = old_value
+      old_value.append(value)
+
   def __init__(self, out_buffer, **kwargs):
     del kwargs
     self.buffer = out_buffer
@@ -78,6 +112,22 @@
   def __init__(self):
     super().__init__(io.BytesIO())
 
+  @classmethod
+  @property
+  def name(cls):
+    # Return an invalid name, so as to ensure _SinkPdfStream.Register()
+    # cannot be called. This method has to be implemented, because this
+    # script create `_SinkPdfStream` instances.
+    return ''
+
+  @classmethod
+  @property
+  def aliases(cls):
+    # Return an invalid aliases, so as to ensure _SinkPdfStream.Register()
+    # cannot be called. This method has to be implemented, because this
+    # script create `_SinkPdfStream` instances.
+    return ()
+
   def close(self):
     # Don't call io.BytesIO.close(); this deallocates the written data.
     self.flush()
@@ -93,6 +143,18 @@
     self.wrapcol = wrapcol
     self.column = 0
 
+  @classmethod
+  @property
+  @abstractmethod
+  def name(cls):
+    pass
+
+  @classmethod
+  @property
+  @abstractmethod
+  def aliases(cls):
+    pass
+
   def write(self, data):
     if not self.wrapcol:
       self.buffer.write(data)
@@ -113,8 +175,18 @@
 
 
 class _Ascii85DecodePdfStream(_AsciiPdfStream):
-  name = '/ASCII85Decode'
-  aliases = ('ascii85', 'base85')
+  _name = '/ASCII85Decode'
+  _aliases = ('ascii85', 'base85')
+
+  @classmethod
+  @property
+  def name(cls):
+    return cls._name
+
+  @classmethod
+  @property
+  def aliases(cls):
+    return cls._aliases
 
   def __init__(self, out_buffer, **kwargs):
     super().__init__(out_buffer, **kwargs)
@@ -137,8 +209,18 @@
 
 
 class _AsciiHexDecodePdfStream(_AsciiPdfStream):
-  name = '/ASCIIHexDecode'
-  aliases = ('base16', 'hex', 'hexadecimal')
+  _name = '/ASCIIHexDecode'
+  _aliases = ('base16', 'hex', 'hexadecimal')
+
+  @classmethod
+  @property
+  def name(cls):
+    return cls._name
+
+  @classmethod
+  @property
+  def aliases(cls):
+    return cls._aliases
 
   def __init__(self, out_buffer, **kwargs):
     super().__init__(out_buffer, **kwargs)
@@ -148,13 +230,23 @@
 
 
 class _FlateDecodePdfStream(_PdfStream):
-  name = '/FlateDecode'
-  aliases = ('deflate', 'flate', 'zlib')
+  _name = '/FlateDecode'
+  _aliases = ('deflate', 'flate', 'zlib')
 
   def __init__(self, out_buffer, **kwargs):
     super().__init__(out_buffer, **kwargs)
     self.deflate = zlib.compressobj(level=9, memLevel=9)
 
+  @classmethod
+  @property
+  def name(cls):
+    return cls._name
+
+  @classmethod
+  @property
+  def aliases(cls):
+    return cls._aliases
+
   def write(self, data):
     self.buffer.write(self.deflate.compress(data))
 
@@ -166,9 +258,126 @@
     self.buffer.close()
 
 
-_PdfStream.RegisterFilter(_Ascii85DecodePdfStream)
-_PdfStream.RegisterFilter(_AsciiHexDecodePdfStream)
-_PdfStream.RegisterFilter(_FlateDecodePdfStream)
+class _VirtualPdfStream(_PdfStream):
+
+  @classmethod
+  @property
+  @abstractmethod
+  def name(cls):
+    pass
+
+  @classmethod
+  @property
+  @abstractmethod
+  def aliases(cls):
+    pass
+
+  @classmethod
+  def RegisterByName(cls):
+    pass
+
+  @classmethod
+  def AddEntries(cls, entries):
+    pass
+
+
+class _PassthroughPdfStream(_VirtualPdfStream):
+  _name = '(virtual) passthrough'
+  _aliases = ('noop', 'passthrough')
+
+  @classmethod
+  @property
+  def name(cls):
+    return cls._name
+
+  @classmethod
+  @property
+  def aliases(cls):
+    return cls._aliases
+
+
+class _PngIdatPdfStream(_VirtualPdfStream):
+  _name = '(virtual) PNG IDAT'
+  _aliases = ('png',)
+
+  _EXPECT_HEADER = -1
+  _EXPECT_LENGTH = -2
+  _EXPECT_CHUNK_TYPE = -3
+  _EXPECT_CRC = -4
+
+  _PNG_HEADER = 0x89504E470D0A1A0A
+  _PNG_CHUNK_IDAT = 0x49444154
+
+  @classmethod
+  @property
+  def name(cls):
+    return cls._name
+
+  @classmethod
+  @property
+  def aliases(cls):
+    return cls._aliases
+
+  @classmethod
+  def AddEntries(cls, entries):
+    # Technically only true for compression method 0 (zlib), but no other
+    # methods have been standardized.
+    _PdfStream.AddListEntry(entries, 'Filter', '/FlateDecode')
+
+  def __init__(self, out_buffer, **kwargs):
+    super().__init__(out_buffer, **kwargs)
+    self.chunk = _PngIdatPdfStream._EXPECT_HEADER
+    self.remaining = 8
+    self.accumulator = 0
+    self.length = 0
+
+  def write(self, data):
+    position = 0
+    while position < len(data):
+      if self.chunk >= 0:
+        # Only pass through IDAT chunk data.
+        read_size = min(self.remaining, len(data) - position)
+        if self.chunk == _PngIdatPdfStream._PNG_CHUNK_IDAT:
+          self.buffer.write(data[position:position + read_size])
+        self.remaining -= read_size
+        if self.remaining == 0:
+          self.ResetAccumulator(_PngIdatPdfStream._EXPECT_CRC, 4)
+        position += read_size
+      else:
+        # As far as we're concerned, PNG files are just a header followed by a
+        # series of (length, chunk type, data[length], CRC) chunks.
+        if self.AccumulateByte(data[position]):
+          if self.chunk == _PngIdatPdfStream._EXPECT_HEADER:
+            if self.accumulator != _PngIdatPdfStream._PNG_HEADER:
+              raise ValueError('Invalid PNG header', self.accumulator)
+            self.ResetAccumulator(_PngIdatPdfStream._EXPECT_LENGTH, 4)
+          elif self.chunk == _PngIdatPdfStream._EXPECT_LENGTH:
+            self.length = self.accumulator
+            self.ResetAccumulator(_PngIdatPdfStream._EXPECT_CHUNK_TYPE, 4)
+          elif self.chunk == _PngIdatPdfStream._EXPECT_CHUNK_TYPE:
+            self.ResetAccumulator(self.accumulator, self.length)
+          elif self.chunk == _PngIdatPdfStream._EXPECT_CRC:
+            # Don't care if the CRC is correct.
+            self.ResetAccumulator(_PngIdatPdfStream._EXPECT_LENGTH, 4)
+        position += 1
+
+  def ResetAccumulator(self, chunk, remaining):
+    self.chunk = chunk
+    self.remaining = remaining
+    self.accumulator = 0
+
+  def AccumulateByte(self, byte):
+    assert self.remaining > 0
+    self.accumulator = self.accumulator << 8 | byte
+    self.remaining -= 1
+    return self.remaining == 0
+
+
+_Ascii85DecodePdfStream.Register()
+_AsciiHexDecodePdfStream.Register()
+_FlateDecodePdfStream.Register()
+_PassthroughPdfStream.Register()
+_PngIdatPdfStream.Register()
 
 _DEFAULT_FILTERS = (_Ascii85DecodePdfStream, _FlateDecodePdfStream)
 
@@ -261,7 +470,7 @@
 
 
 def _EncodePdfValue(value):
-  if isinstance(value, collections.abc.Sequence):
+  if isinstance(value, collections.abc.MutableSequence):
     value = '[' + ' '.join(value) + ']'
   return value
 
@@ -276,7 +485,8 @@
     out_buffer.close()
 
   entries = collections.OrderedDict()
-  entries['Filter'] = [f.name for f in args.filter]
+  for f in args.filter:
+    f.AddEntries(entries)
   _WritePdfStreamObject(
       args.outfile.buffer,
       data=encoded_sink.getbuffer(),
diff --git a/testing/tools/fixup_pdf_template.py b/testing/tools/fixup_pdf_template.py
index ee47c4b..da2d608 100755
--- a/testing/tools/fixup_pdf_template.py
+++ b/testing/tools/fixup_pdf_template.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# Copyright 2014 The PDFium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2014 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Expands a hand-written PDF testcase (template) into a valid PDF file.
@@ -11,17 +11,32 @@
   {{header}} - expands to the header comment required for PDF files.
   {{xref}} - expands to a generated xref table, noting the offset.
   {{trailer}} - expands to a standard trailer with "1 0 R" as the /Root.
+  {{trailersize}} - expands to `/Size n`, to be used in non-standard trailers.
   {{startxref} - expands to a startxref directive followed by correct offset.
-  {{object x y}} - expands to |x y obj| declaration, noting the offset.
-  {{streamlen}} - expands to |/Length n|.
+  {{startxrefobj x y} - expands to a startxref directive followed by correct
+                        offset pointing to the start of `x y obj`.
+  {{object x y}} - expands to `x y obj` declaration, noting the offset.
+  {{streamlen}} - expands to `/Length n`.
 """
 
-import cStringIO
+import io
 import optparse
 import os
 import re
 import sys
 
+# Line Endings.
+WINDOWS_LINE_ENDING = b'\r\n'
+UNIX_LINE_ENDING = b'\n'
+
+# List of extensions whose line endings should be modified after parsing.
+EXTENSION_OVERRIDE_LINE_ENDINGS = [
+    '.js',
+    '.fragment',
+    '.in',
+    '.xml',
+]
+
 
 class StreamLenState:
   START = 1
@@ -30,28 +45,33 @@
 
 
 class TemplateProcessor:
-  HEADER_TOKEN = '{{header}}'
-  HEADER_REPLACEMENT = '%PDF-1.7\n%\xa0\xf2\xa4\xf4'
+  HEADER_TOKEN = b'{{header}}'
+  HEADER_REPLACEMENT = b'%PDF-1.7\n%\xa0\xf2\xa4\xf4'
 
-  XREF_TOKEN = '{{xref}}'
-  XREF_REPLACEMENT = 'xref\n%d %d\n'
+  XREF_TOKEN = b'{{xref}}'
+  XREF_REPLACEMENT = b'xref\n%d %d\n'
 
-  XREF_REPLACEMENT_N = '%010d %05d n \n'
-  XREF_REPLACEMENT_F = '0000000000 65535 f \n'
+  XREF_REPLACEMENT_N = b'%010d %05d n \n'
+  XREF_REPLACEMENT_F = b'0000000000 65535 f \n'
   # XREF rows must be exactly 20 bytes - space required.
   assert len(XREF_REPLACEMENT_F) == 20
 
-  TRAILER_TOKEN = '{{trailer}}'
-  TRAILER_REPLACEMENT = 'trailer <<\n  /Root 1 0 R\n  /Size %d\n>>'
+  TRAILER_TOKEN = b'{{trailer}}'
+  TRAILER_REPLACEMENT = b'trailer <<\n  /Root 1 0 R\n  /Size %d\n>>'
 
-  STARTXREF_TOKEN = '{{startxref}}'
-  STARTXREF_REPLACEMENT = 'startxref\n%d'
+  TRAILERSIZE_TOKEN = b'{{trailersize}}'
+  TRAILERSIZE_REPLACEMENT = b'/Size %d'
 
-  OBJECT_PATTERN = r'\{\{object\s+(\d+)\s+(\d+)\}\}'
-  OBJECT_REPLACEMENT = r'\1 \2 obj'
+  STARTXREF_TOKEN = b'{{startxref}}'
+  STARTXREF_REPLACEMENT = b'startxref\n%d'
 
-  STREAMLEN_TOKEN = '{{streamlen}}'
-  STREAMLEN_REPLACEMENT = '/Length %d'
+  STARTXREFOBJ_PATTERN = b'\{\{startxrefobj\s+(\d+)\s+(\d+)\}\}'
+
+  OBJECT_PATTERN = b'\{\{object\s+(\d+)\s+(\d+)\}\}'
+  OBJECT_REPLACEMENT = b'\g<1> \g<2> obj'
+
+  STREAMLEN_TOKEN = b'{{streamlen}}'
+  STREAMLEN_REPLACEMENT = b'/Length %d'
 
   def __init__(self):
     self.streamlen_state = StreamLenState.START
@@ -82,12 +102,12 @@
       return
 
     if (self.streamlen_state == StreamLenState.FIND_STREAM and
-        line.rstrip() == 'stream'):
+        line.rstrip() == b'stream'):
       self.streamlen_state = StreamLenState.FIND_ENDSTREAM
       return
 
     if self.streamlen_state == StreamLenState.FIND_ENDSTREAM:
-      if line.rstrip() == 'endstream':
+      if line.rstrip() == b'endstream':
         self.streamlen_state = StreamLenState.START
       else:
         self.streamlens[-1] += len(line)
@@ -104,6 +124,9 @@
     if self.TRAILER_TOKEN in line:
       replacement = self.TRAILER_REPLACEMENT % (self.max_object_number + 1)
       line = line.replace(self.TRAILER_TOKEN, replacement)
+    if self.TRAILERSIZE_TOKEN in line:
+      replacement = self.TRAILERSIZE_REPLACEMENT % (self.max_object_number + 1)
+      line = line.replace(self.TRAILERSIZE_TOKEN, replacement)
     if self.STARTXREF_TOKEN in line:
       replacement = self.STARTXREF_REPLACEMENT % self.xref_offset
       line = line.replace(self.STARTXREF_TOKEN, replacement)
@@ -111,6 +134,12 @@
     if match:
       self.insert_xref_entry(int(match.group(1)), int(match.group(2)))
       line = re.sub(self.OBJECT_PATTERN, self.OBJECT_REPLACEMENT, line)
+    match = re.match(self.STARTXREFOBJ_PATTERN, line)
+    if match:
+      (offset, generation_number) = self.objects[int(match.group(1))]
+      assert int(match.group(2)) == generation_number
+      replacement = self.STARTXREF_REPLACEMENT % offset
+      line = re.sub(self.STARTXREFOBJ_PATTERN, replacement, line)
     self.offset += len(line)
     return line
 
@@ -119,7 +148,7 @@
   processor = TemplateProcessor()
   try:
     with open(output_path, 'wb') as outfile:
-      preprocessed = cStringIO.StringIO()
+      preprocessed = io.BytesIO()
       for line in infile:
         preprocessed.write(line)
         processor.preprocess_line(line)
@@ -127,27 +156,43 @@
       for line in preprocessed:
         outfile.write(processor.process_line(line))
   except IOError:
-    print >> sys.stderr, 'failed to process %s' % input_path
+    print('failed to process %s' % input_path, file=sys.stderr)
 
 
 def insert_includes(input_path, output_file, visited_set):
   input_path = os.path.normpath(input_path)
   if input_path in visited_set:
-    print >> sys.stderr, 'Circular inclusion %s, ignoring' % input_path
+    print('Circular inclusion %s, ignoring' % input_path, file=sys.stderr)
     return
   visited_set.add(input_path)
   try:
+    _, file_extension = os.path.splitext(input_path)
+    override_line_endings = (file_extension in EXTENSION_OVERRIDE_LINE_ENDINGS)
+
+    end_of_file_line_ending = False
     with open(input_path, 'rb') as infile:
       for line in infile:
-        match = re.match(r'\s*\{\{include\s+(.+)\}\}', line)
+        match = re.match(b'\s*\{\{include\s+(.+)\}\}', line)
         if match:
           insert_includes(
-              os.path.join(os.path.dirname(input_path), match.group(1)),
-              output_file, visited_set)
+              os.path.join(
+                  os.path.dirname(input_path),
+                  match.group(1).decode('utf-8')), output_file, visited_set)
         else:
+          if override_line_endings:
+            # Replace CRLF with LF line endings for .in files.
+            if line.endswith(WINDOWS_LINE_ENDING):
+              line = line.removesuffix(WINDOWS_LINE_ENDING) + UNIX_LINE_ENDING
+              end_of_file_line_ending = True
+            else:
+              end_of_file_line_ending = line.endswith(UNIX_LINE_ENDING)
           output_file.write(line)
+
+    # Ensure the include ends on its own line.
+    if not end_of_file_line_ending:
+      output_file.write(UNIX_LINE_ENDING)
   except IOError:
-    print >> sys.stderr, 'failed to include %s' % input_path
+    print('failed to include %s' % input_path, file=sys.stderr)
     raise
   visited_set.discard(input_path)
 
@@ -162,7 +207,7 @@
     output_dir = os.path.dirname(testcase_path)
     if options.output_dir:
       output_dir = options.output_dir
-    intermediate_stream = cStringIO.StringIO()
+    intermediate_stream = io.BytesIO()
     insert_includes(testcase_path, intermediate_stream, set())
     intermediate_stream.seek(0)
     output_path = os.path.join(output_dir, testcase_root + '.pdf')
diff --git a/testing/tools/githelper.py b/testing/tools/githelper.py
index 91a6c71..19071b0 100644
--- a/testing/tools/githelper.py
+++ b/testing/tools/githelper.py
@@ -1,15 +1,14 @@
-# Copyright 2017 The PDFium Authors. All rights reserved.
+# Copyright 2017 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Classes for dealing with git."""
 
 import subprocess
 
-# pylint: disable=relative-import
 from common import RunCommandPropagateErr
 
 
-class GitHelper(object):
+class GitHelper:
   """Issues git commands. Stateful."""
 
   def __init__(self):
@@ -20,8 +19,8 @@
     RunCommandPropagateErr(['git', 'checkout', branch], exit_status_on_error=1)
 
   def FetchOriginMaster(self):
-    """Fetches new changes on origin/master."""
-    RunCommandPropagateErr(['git', 'fetch', 'origin', 'master'],
+    """Fetches new changes on origin/main."""
+    RunCommandPropagateErr(['git', 'fetch', 'origin', 'main'],
                            exit_status_on_error=1)
 
   def StashPush(self):
diff --git a/testing/tools/gold.py b/testing/tools/gold.py
deleted file mode 100644
index c686bba..0000000
--- a/testing/tools/gold.py
+++ /dev/null
@@ -1,284 +0,0 @@
-# Copyright 2015 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.
-
-import json
-import os
-import shlex
-import shutil
-import ssl
-import urllib2
-
-
-def _ParseKeyValuePairs(kv_str):
-  """
-  Parses a string of the type 'key1 value1 key2 value2' into a dict.
-  """
-  kv_pairs = shlex.split(kv_str)
-  if len(kv_pairs) % 2:
-    raise ValueError('Uneven number of key/value pairs. Got %s' % kv_str)
-  return {kv_pairs[i]: kv_pairs[i + 1] for i in xrange(0, len(kv_pairs), 2)}
-
-
-# This module downloads a json provided by Skia Gold with the expected baselines
-# for each test file.
-#
-# The expected format for the json is:
-# {
-#   "commit": {
-#     "author": "John Doe (jdoe@chromium.org)",
-#     "commit_time": 1510598123,
-#     "hash": "cee39e6e90c219cc91f2c94a912a06977f4461a0"
-#   },
-#   "master": {
-#     "abc.pdf.1": {
-#       "0ec3d86f545052acd7c9a16fde8ca9d4": 1,
-#       "80455b71673becc9fbc100d6da56ca65": 1,
-#       "b68e2ecb80090b4502ec89ad1be2322c": 1
-#      },
-#     "defgh.pdf.0": {
-#       "01e020cd4cd05c6738e479a46a506044": 1,
-#       "b68e2ecb80090b4502ec89ad1be2322c": 1
-#     }
-#   },
-#   "changeLists": {
-#     "18499" : {
-#       "abc.pdf.1": {
-#         "d5dd649124cf1779152253dc8fb239c5": 1,
-#         "42a270581930579cdb0f28674972fb1a": 1,
-#       }
-#     }
-#   }
-# }
-class GoldBaseline(object):
-
-  def __init__(self, properties_str):
-    """
-    properties_str is a string with space separated key/value pairs that
-               is used to find the cl number for which to baseline
-    """
-    self._properties = _ParseKeyValuePairs(properties_str)
-    self._baselines = self._LoadSkiaGoldBaselines()
-
-  def _LoadSkiaGoldBaselines(self):
-    """
-    Download the baseline json and return a list of the two baselines that
-    should be used to match hashes (master and cl#).
-    """
-    GOLD_BASELINE_URL = 'https://pdfium-gold.skia.org/json/baseline'
-
-    # If we have an issue number add it to the baseline URL
-    cl_number_str = self._properties.get('issue', None)
-    url = GOLD_BASELINE_URL + ('/' + cl_number_str if cl_number_str else '')
-
-    json_data = ''
-    MAX_TIMEOUT = 33  # 5 tries. (2, 4, 8, 16, 32)
-    timeout = 2
-    while True:
-      try:
-        response = urllib2.urlopen(url, timeout=timeout)
-        c_type = response.headers.get('Content-type', '')
-        EXPECTED_CONTENT_TYPE = 'application/json'
-        if c_type != EXPECTED_CONTENT_TYPE:
-          raise ValueError('Invalid content type. Got %s instead of %s' %
-                           (c_type, EXPECTED_CONTENT_TYPE))
-        json_data = response.read()
-        break  # If this line is reached, then no exception occurred.
-      except (ssl.SSLError, urllib2.HTTPError, urllib2.URLError) as e:
-        timeout *= 2
-        if timeout < MAX_TIMEOUT:
-          continue
-        print('Error: Unable to read skia gold json from %s: %s' % (url, e))
-        return None
-
-    try:
-      data = json.loads(json_data)
-    except ValueError as e:
-      print 'Error: Malformed json read from %s: %s' % (url, e)
-      return None
-
-    return data.get('master', {})
-
-  # Return values for MatchLocalResult().
-  MATCH = 'match'
-  MISMATCH = 'mismatch'
-  NO_BASELINE = 'no_baseline'
-  BASELINE_DOWNLOAD_FAILED = 'baseline_download_failed'
-
-  def MatchLocalResult(self, test_name, md5_hash):
-    """
-    Match a locally generated hash of a test cases rendered image with the
-    expected hashes downloaded in the baselines json.
-
-    Each baseline is a dict mapping the test case name to a dict with the
-    expected hashes as keys. Therefore, this list of baselines should be
-    searched until the test case name is found, then the hash should be matched
-    with the options in that dict. If the hashes don't match, it should be
-    considered a failure and we should not continue searching the baseline list.
-
-    Returns MATCH if the md5 provided matches the ones in the baseline json,
-    MISMATCH if it does not, NO_BASELINE if the test case has no baseline, or
-    BASELINE_DOWNLOAD_FAILED if the baseline could not be downloaded and parsed.
-    """
-    if self._baselines is None:
-      return GoldBaseline.BASELINE_DOWNLOAD_FAILED
-
-    found_test_case = False
-    if test_name in self._baselines:
-      found_test_case = True
-      if md5_hash in self._baselines[test_name]:
-        return GoldBaseline.MATCH
-
-    return (GoldBaseline.MISMATCH
-            if found_test_case else GoldBaseline.NO_BASELINE)
-
-
-# This module collects and writes output in a format expected by the
-# Gold baseline tool. Based on meta data provided explicitly and by
-# adding a series of test results it can be used to produce
-# a JSON file that is uploaded to Google Storage and ingested by Gold.
-#
-# The output will look similar this:
-#
-# {
-#    "build_number" : "2",
-#    "gitHash" : "a4a338179013b029d6dd55e737b5bd648a9fb68c",
-#    "key" : {
-#       "arch" : "arm64",
-#       "compiler" : "Clang",
-#    },
-#    "results" : [
-#       {
-#          "key" : {
-#             "config" : "vk",
-#             "name" : "yuv_nv12_to_rgb_effect",
-#             "source_type" : "gm"
-#          },
-#          "md5" : "7db34da246868d50ab9ddd776ce6d779",
-#          "options" : {
-#             "ext" : "png",
-#             "gamma_correct" : "no"
-#          }
-#       },
-#       {
-#          "key" : {
-#             "config" : "vk",
-#             "name" : "yuv_to_rgb_effect",
-#             "source_type" : "gm"
-#          },
-#          "md5" : "0b955f387740c66eb23bf0e253c80d64",
-#          "options" : {
-#             "ext" : "png",
-#             "gamma_correct" : "no"
-#          }
-#       }
-#    ],
-# }
-#
-class GoldResults(object):
-
-  def __init__(self, source_type, output_dir, properties_str, key_str,
-               ignore_hashes_file):
-    """
-    source_type is the source_type (=corpus) field used for all results.
-    output_dir is the directory where the resulting images are copied and
-               the dm.json file is written. If the directory exists it will
-               be removed and recreated.
-    properties_str is a string with space separated key/value pairs that
-               is used to set the top level fields in the output JSON file.
-    key_str is a string with space separated key/value pairs that
-               is used to set the 'key' field in the output JSON file.
-    ignore_hashes_file is a file that contains a list of image hashes
-               that should be ignored.
-    """
-    self._source_type = source_type
-    self._properties = _ParseKeyValuePairs(properties_str)
-    self._properties['key'] = _ParseKeyValuePairs(key_str)
-    self._results = []
-    self._passfail = []
-    self._output_dir = output_dir
-
-    # make sure the output directory exists and is empty.
-    if os.path.exists(output_dir):
-      shutil.rmtree(output_dir, ignore_errors=True)
-    os.makedirs(output_dir)
-
-    self._ignore_hashes = set()
-    if ignore_hashes_file:
-      with open(ignore_hashes_file, 'r') as ig_file:
-        hashes = [x.strip() for x in ig_file.readlines() if x.strip()]
-        self._ignore_hashes = set(hashes)
-
-  def AddTestResult(self, testName, md5Hash, outputImagePath, matchResult):
-    # If the hash is in the list of hashes to ignore then we don'try
-    # make a copy, but add it to the result.
-    imgExt = os.path.splitext(outputImagePath)[1].lstrip('.')
-    if md5Hash not in self._ignore_hashes:
-      # Copy the image to <output_dir>/<md5Hash>.<image_extension>
-      if not imgExt:
-        raise ValueError('File %s does not have an extension' % outputImagePath)
-      newFilePath = os.path.join(self._output_dir, md5Hash + '.' + imgExt)
-      shutil.copy2(outputImagePath, newFilePath)
-
-    # Add an entry to the list of test results
-    self._results.append({
-        'key': {
-            'name': testName,
-            'source_type': self._source_type,
-        },
-        'md5': md5Hash,
-        'options': {
-            'ext': imgExt,
-            'gamma_correct': 'no'
-        }
-    })
-
-    self._passfail.append((testName, matchResult))
-
-  def WriteResults(self):
-    self._properties.update({'results': self._results})
-
-    output_file_name = os.path.join(self._output_dir, 'dm.json')
-    with open(output_file_name, 'wb') as outfile:
-      json.dump(self._properties, outfile, indent=1)
-      outfile.write('\n')
-
-    output_file_name = os.path.join(self._output_dir, 'passfail.json')
-    with open(output_file_name, 'wb') as outfile:
-      json.dump(self._passfail, outfile, indent=1)
-      outfile.write('\n')
-
-
-# Produce example output for manual testing.
-def _Example():
-  # Create a test directory with three empty 'image' files.
-  test_dir = './testdirectory'
-  if not os.path.exists(test_dir):
-    os.makedirs(test_dir)
-  open(os.path.join(test_dir, 'image1.png'), 'wb').close()
-  open(os.path.join(test_dir, 'image2.png'), 'wb').close()
-  open(os.path.join(test_dir, 'image3.png'), 'wb').close()
-
-  # Create an instance and add results.
-  prop_str = 'build_number 2 "builder name" Builder-Name gitHash ' \
-      'a4a338179013b029d6dd55e737b5bd648a9fb68c'
-
-  key_str = 'arch arm64 compiler Clang configuration Debug'
-
-  hash_file = os.path.join(test_dir, 'ignore_hashes.txt')
-  with open(hash_file, 'wb') as f:
-    f.write('\n'.join(['hash-1', 'hash-4']) + '\n')
-
-  output_dir = './output_directory'
-  gr = GoldResults('pdfium', output_dir, prop_str, key_str, hash_file)
-  gr.AddTestResult('test-1', 'hash-1', os.path.join(test_dir, 'image1.png'),
-                   GoldBaseline.MATCH)
-  gr.AddTestResult('test-2', 'hash-2', os.path.join(test_dir, 'image2.png'),
-                   GoldBaseline.MATCH)
-  gr.AddTestResult('test-3', 'hash-3', os.path.join(test_dir, 'image3.png'),
-                   GoldBaseline.MISMATCH)
-  gr.WriteResults()
-
-
-if __name__ == '__main__':
-  _Example()
diff --git a/testing/tools/libcxx_check.py b/testing/tools/libcxx_check.py
new file mode 100755
index 0000000..ad6abea
--- /dev/null
+++ b/testing/tools/libcxx_check.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+# Copyright 2022 The PDFium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Verifies libcxx_revision entries are in sync.
+
+DEPS and buildtools/deps_revisions.gni both have libcxx_revision entries.
+Check that they are in sync.
+"""
+
+import re
+import sys
+
+
+def _ExtractRevisionFromFile(path, regex):
+  """Gets the revision by reading path and searching the lines using regex."""
+  data = open(path, 'rb').read().splitlines()
+  revision = None
+  for line in data:
+    match = regex.match(line)
+    if not match:
+      continue
+    if revision:
+      return None
+    revision = match.group(1)
+  return revision
+
+
+def _GetDepsLibcxxRevision(deps_path):
+  """Gets the libcxx_revision from DEPS."""
+  regex = re.compile(b"^  'libcxx_revision': '(.*)',$")
+  return _ExtractRevisionFromFile(deps_path, regex)
+
+
+def _GetBuildtoolsLibcxxRevision(buildtools_deps_path):
+  """Gets the libcxx_revision from buildtools/deps_revisions.gni."""
+  regex = re.compile(b'^  libcxx_revision = "(.*)"$')
+  return _ExtractRevisionFromFile(buildtools_deps_path, regex)
+
+
+def main():
+  if len(sys.argv) != 3:
+    print('Wrong number of arguments')
+    return 0
+
+  deps_path = sys.argv[1]
+  deps_revision = _GetDepsLibcxxRevision(deps_path)
+  if not deps_revision:
+    print('Cannot parse', deps_path)
+    return 0
+
+  buildtools_deps_path = sys.argv[2]
+  buildtools_revision = _GetBuildtoolsLibcxxRevision(buildtools_deps_path)
+  if not buildtools_revision:
+    print('Cannot parse', buildtools_deps_path)
+    return 0
+
+  if deps_revision != buildtools_revision:
+    print('libcxx_revision mismatch between %s and %s: %s vs. %s' %
+          (deps_path, buildtools_deps_path, deps_revision, buildtools_revision))
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/testing/tools/make_expected.sh b/testing/tools/make_expected.sh
index 5237e51..8f8238c 100755
--- a/testing/tools/make_expected.sh
+++ b/testing/tools/make_expected.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 #
-# Copyright 2015 PDFium Authors. All rights reserved.
+# Copyright 2015 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 #
@@ -23,7 +23,9 @@
   if [ -f "$EVTFILE" ]; then
     SEND_EVENTS="--send-events"
   fi
-  out/Debug/pdfium_test $SEND_EVENTS --time=$TEST_SEED_TIME --png $INFILE
+  FONT_DIR=`readlink -f third_party/test_fonts`
+  out/Debug/pdfium_test $SEND_EVENTS --time=$TEST_SEED_TIME --png \
+      --croscore-font-names --font-dir=$FONT_DIR $INFILE
   RESULTS="$INFILE.*.png"
   for RESULT in $RESULTS ; do
     EXPECTED=`echo -n $RESULT | sed 's/[.]pdf[.]/_expected.pdf./'`
diff --git a/testing/tools/pdfium_root.py b/testing/tools/pdfium_root.py
new file mode 100644
index 0000000..7c4c861
--- /dev/null
+++ b/testing/tools/pdfium_root.py
@@ -0,0 +1,53 @@
+# Copyright 2022 The PDFium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Utilities for working with the PDFium tree's root directory."""
+
+import os
+import sys
+
+
+class RootDirectoryFinder:
+  """Finds the PDFium tree's root directories.
+
+  The implementation expects that either:
+  1. PDFium is a standalone checkout.
+  2. PDFium is part of another tree within a "third_party/pdfium" directory.
+
+  Attributes:
+      pdfium_root: The path to the root of the PDFium tree.
+      source_root: The path to the root of the source tree. May differ from
+        `pdfium_root` if PDFium is a third-party dependency in another tree.
+  """
+
+  def __init__(self):
+    # Expect `self_dir` to be ".../testing/tools".
+    self_dir = os.path.dirname(os.path.realpath(__file__))
+
+    self.pdfium_root = _remove_path_suffix(self_dir, ('testing', 'tools'))
+    if not self.pdfium_root:
+      raise Exception('Cannot find testing/tools within PDFium root directory')
+
+    # In a Chromium checkout, expect `self.pdfium_root` to be
+    # ".../third_party/pdfium".
+    self.source_root = _remove_path_suffix(self.pdfium_root,
+                                           ('third_party', 'pdfium'))
+    if not self.source_root:
+      self.source_root = self.pdfium_root
+
+
+def _remove_path_suffix(path, expected_suffix):
+  for expected_part in reversed(expected_suffix):
+    if os.path.basename(path) != expected_part:
+      return None
+    path = os.path.dirname(path)
+  return path
+
+
+def add_source_directory_to_import_path(source_directory_path):
+  """Adds a source root-relative directory to end of the import path."""
+  root_finder = RootDirectoryFinder()
+  path = os.path.realpath(
+      os.path.join(root_finder.source_root, source_directory_path))
+  if path not in sys.path:
+    sys.path.append(path)
diff --git a/testing/tools/pngdiffer.py b/testing/tools/pngdiffer.py
index a469506..2044a34 100755
--- a/testing/tools/pngdiffer.py
+++ b/testing/tools/pngdiffer.py
@@ -1,139 +1,239 @@
-#!/usr/bin/env python
-# Copyright 2015 The PDFium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2015 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import distutils.spawn
+from dataclasses import dataclass
 import itertools
 import os
 import shutil
+import subprocess
 import sys
 
-# pylint: disable=relative-import
-import common
+EXACT_MATCHING = 'exact'
+FUZZY_MATCHING = 'fuzzy'
 
+_PNG_OPTIMIZER = 'optipng'
+
+_COMMON_SUFFIX_ORDER = ('_{os}', '')
+_AGG_SUFFIX_ORDER = ('_agg_{os}', '_agg') + _COMMON_SUFFIX_ORDER
+_SKIA_SUFFIX_ORDER = ('_skia_{os}', '_skia') + _COMMON_SUFFIX_ORDER
+
+
+@dataclass
+class ImageDiff:
+  """Details about an image diff.
+
+  Attributes:
+    actual_path: Path to the actual image file.
+    expected_path: Path to the expected image file, or `None` if no matches.
+    diff_path: Path to the diff image file, or `None` if no diff.
+    reason: Optional reason for the diff.
+  """
+  actual_path: str
+  expected_path: str = None
+  diff_path: str = None
+  reason: str = None
 
 class PNGDiffer():
 
-  def __init__(self, finder, reverse_byte_order):
+  def __init__(self, finder, features, reverse_byte_order):
     self.pdfium_diff_path = finder.ExecutablePath('pdfium_diff')
     self.os_name = finder.os_name
     self.reverse_byte_order = reverse_byte_order
+    if 'SKIA' in features:
+      self.suffix_order = _SKIA_SUFFIX_ORDER
+    else:
+      self.suffix_order = _AGG_SUFFIX_ORDER
 
   def CheckMissingTools(self, regenerate_expected):
-    if (regenerate_expected and self.os_name == 'linux' and
-        not distutils.spawn.find_executable('optipng')):
-      return 'Please install "optipng" to regenerate expected images.'
+    if regenerate_expected and not shutil.which(_PNG_OPTIMIZER):
+      return f'Please install "{_PNG_OPTIMIZER}" to regenerate expected images.'
     return None
 
   def GetActualFiles(self, input_filename, source_dir, working_dir):
     actual_paths = []
-    path_templates = PathTemplates(input_filename, source_dir, working_dir)
+    path_templates = _PathTemplates(input_filename, source_dir, working_dir,
+                                    self.os_name, self.suffix_order)
 
     for page in itertools.count():
       actual_path = path_templates.GetActualPath(page)
-      expected_path = path_templates.GetExpectedPath(page)
-      platform_expected_path = path_templates.GetPlatformExpectedPath(
-          self.os_name, page)
-      if os.path.exists(platform_expected_path):
-        expected_path = platform_expected_path
-      elif not os.path.exists(expected_path):
+      if path_templates.GetExpectedPath(page, default_to_base=False):
+        actual_paths.append(actual_path)
+      else:
         break
-      actual_paths.append(actual_path)
-
     return actual_paths
 
-  def HasDifferences(self, input_filename, source_dir, working_dir):
-    path_templates = PathTemplates(input_filename, source_dir, working_dir)
+  def _RunCommand(self, cmd):
+    try:
+      subprocess.run(cmd, capture_output=True, check=True)
+      return None
+    except subprocess.CalledProcessError as e:
+      return e
 
+  def _RunImageCompareCommand(self, image_diff, image_matching_algorithm):
+    cmd = [self.pdfium_diff_path]
+    if self.reverse_byte_order:
+      cmd.append('--reverse-byte-order')
+    if image_matching_algorithm == FUZZY_MATCHING:
+      cmd.append('--fuzzy')
+    cmd.extend([image_diff.actual_path, image_diff.expected_path])
+    return self._RunCommand(cmd)
+
+  def _RunImageDiffCommand(self, image_diff):
+    # TODO(crbug.com/pdfium/1925): Diff mode ignores --reverse-byte-order.
+    return self._RunCommand([
+        self.pdfium_diff_path, '--subtract', image_diff.actual_path,
+        image_diff.expected_path, image_diff.diff_path
+    ])
+
+  def ComputeDifferences(self, input_filename, source_dir, working_dir,
+                         image_matching_algorithm):
+    """Computes differences between actual and expected image files.
+
+    Returns:
+      A list of `ImageDiff` instances, one per differing page.
+    """
+    image_diffs = []
+
+    path_templates = _PathTemplates(input_filename, source_dir, working_dir,
+                                    self.os_name, self.suffix_order)
     for page in itertools.count():
-      actual_path = path_templates.GetActualPath(page)
+      page_diff = ImageDiff(actual_path=path_templates.GetActualPath(page))
+      if not os.path.exists(page_diff.actual_path):
+        # No more actual pages.
+        break
+
       expected_path = path_templates.GetExpectedPath(page)
-      # PDFium tests should be platform independent. Platform based results are
-      # used to capture platform dependent implementations.
-      platform_expected_path = path_templates.GetPlatformExpectedPath(
-          self.os_name, page)
-      if (not os.path.exists(expected_path) and
-          not os.path.exists(platform_expected_path)):
-        if page == 0:
-          print "WARNING: no expected results files for " + input_filename
-        if os.path.exists(actual_path):
-          print('FAILURE: Missing expected result for 0-based page %d of %s' %
-                (page, input_filename))
-          return True
-        break
-      print "Checking " + actual_path
-      sys.stdout.flush()
       if os.path.exists(expected_path):
-        cmd = [self.pdfium_diff_path]
-        if self.reverse_byte_order:
-          cmd.append('--reverse-byte-order')
-        cmd.extend([expected_path, actual_path])
-        error = common.RunCommand(cmd)
+        page_diff.expected_path = expected_path
+
+        compare_error = self._RunImageCompareCommand(page_diff,
+                                                     image_matching_algorithm)
+        if compare_error:
+          page_diff.reason = str(compare_error)
+
+          # TODO(crbug.com/pdfium/1925): Compare and diff simultaneously.
+          page_diff.diff_path = path_templates.GetDiffPath(page)
+          if not self._RunImageDiffCommand(page_diff):
+            print(f'WARNING: No diff for {page_diff.actual_path}')
+            page_diff.diff_path = None
+        else:
+          # Validate that no other paths match.
+          for unexpected_path in path_templates.GetExpectedPaths(page)[1:]:
+            page_diff.expected_path = unexpected_path
+            if not self._RunImageCompareCommand(page_diff,
+                                                image_matching_algorithm):
+              page_diff.reason = f'Also matches {unexpected_path}'
+              break
+          page_diff.expected_path = expected_path
       else:
-        error = 1
-      if error:
-        # When failed, we check against platform based results.
-        if os.path.exists(platform_expected_path):
-          cmd = [self.pdfium_diff_path]
-          if self.reverse_byte_order:
-            cmd.append('--reverse-byte-order')
-          cmd.extend([platform_expected_path, actual_path])
-          error = common.RunCommand(cmd)
-        if error:
-          print "FAILURE: " + input_filename + "; " + str(error)
-          return True
+        if page == 0:
+          print(f'WARNING: no expected results files for {input_filename}')
+        page_diff.reason = f'{expected_path} does not exist'
 
-    return False
+      if page_diff.reason:
+        image_diffs.append(page_diff)
 
-  def Regenerate(self, input_filename, source_dir, working_dir, platform_only):
-    path_templates = PathTemplates(input_filename, source_dir, working_dir)
+    return image_diffs
 
+  def Regenerate(self, input_filename, source_dir, working_dir,
+                 image_matching_algorithm):
+    path_templates = _PathTemplates(input_filename, source_dir, working_dir,
+                                    self.os_name, self.suffix_order)
     for page in itertools.count():
-      # Loop through the generated page images. Stop when there is a page
-      # missing a png, which means the document ended.
-      actual_path = path_templates.GetActualPath(page)
-      if not os.path.isfile(actual_path):
+      expected_paths = path_templates.GetExpectedPaths(page)
+
+      first_match = None
+      last_match = None
+      page_diff = ImageDiff(actual_path=path_templates.GetActualPath(page))
+      if os.path.exists(page_diff.actual_path):
+        # Match against all expected page images.
+        for index, expected_path in enumerate(expected_paths):
+          page_diff.expected_path = expected_path
+          if not self._RunImageCompareCommand(page_diff,
+                                              image_matching_algorithm):
+            if first_match is None:
+              first_match = index
+            last_match = index
+
+        if last_match == 0:
+          # Regeneration not needed. This case may be reached if only some, but
+          # not all, pages need to be regenerated.
+          continue
+      elif expected_paths:
+        # Remove all expected page images.
+        print(f'WARNING: {input_filename} has extra expected page {page}')
+        first_match = 0
+        last_match = len(expected_paths)
+      else:
+        # No more expected or actual pages.
         break
 
-      platform_expected_path = path_templates.GetPlatformExpectedPath(
-          self.os_name, page)
+      # Try to reuse expectations by removing intervening non-matches.
+      #
+      # TODO(crbug.com/pdfium/1988): This can make mistakes due to a lack of
+      # global knowledge about other test configurations, which is why it just
+      # creates backup files rather than immediately removing files.
+      if last_match is not None:
+        if first_match > 1:
+          print(f'WARNING: {input_filename}.{page} has non-adjacent match')
+        if first_match != last_match:
+          print(f'WARNING: {input_filename}.{page} has redundant matches')
 
-      # If there is a platform expected png, we will overwrite it. Otherwise,
-      # overwrite the generic png in "all" mode, or do nothing in "platform"
-      # mode.
-      if os.path.exists(platform_expected_path):
-        expected_path = platform_expected_path
-      elif not platform_only:
-        expected_path = path_templates.GetExpectedPath(page)
-      else:
+        for expected_path in expected_paths[:last_match]:
+          os.rename(expected_path, expected_path + '.bak')
         continue
 
-      shutil.copyfile(actual_path, expected_path)
-      common.RunCommand(['optipng', expected_path])
+      # Regenerate the most specific expected path that exists. If there are no
+      # existing expectations, regenerate the base case.
+      expected_path = path_templates.GetExpectedPath(page)
+      shutil.copyfile(page_diff.actual_path, expected_path)
+      self._RunCommand([_PNG_OPTIMIZER, expected_path])
 
 
-ACTUAL_TEMPLATE = '.pdf.%d.png'
-EXPECTED_TEMPLATE = '_expected' + ACTUAL_TEMPLATE
-PLATFORM_EXPECTED_TEMPLATE = '_expected_%s' + ACTUAL_TEMPLATE
+_ACTUAL_TEMPLATE = '.pdf.%d.png'
+_DIFF_TEMPLATE = '.pdf.%d.diff.png'
 
 
-class PathTemplates(object):
+class _PathTemplates:
 
-  def __init__(self, input_filename, source_dir, working_dir):
+  def __init__(self, input_filename, source_dir, working_dir, os_name,
+               suffix_order):
     input_root, _ = os.path.splitext(input_filename)
     self.actual_path_template = os.path.join(working_dir,
-                                             input_root + ACTUAL_TEMPLATE)
-    self.expected_path = os.path.join(source_dir,
-                                      input_root + EXPECTED_TEMPLATE)
-    self.platform_expected_path = os.path.join(
-        source_dir, input_root + PLATFORM_EXPECTED_TEMPLATE)
+                                             input_root + _ACTUAL_TEMPLATE)
+    self.diff_path_template = os.path.join(working_dir,
+                                           input_root + _DIFF_TEMPLATE)
+
+    # Pre-create the available templates from most to least specific. We
+    # generally expect the most specific case to match first.
+    self.expected_templates = []
+    for suffix in suffix_order:
+      formatted_suffix = suffix.format(os=os_name)
+      self.expected_templates.append(
+          os.path.join(
+              source_dir,
+              f'{input_root}_expected{formatted_suffix}{_ACTUAL_TEMPLATE}'))
+    assert self.expected_templates
 
   def GetActualPath(self, page):
     return self.actual_path_template % page
 
-  def GetExpectedPath(self, page):
-    return self.expected_path % page
+  def GetDiffPath(self, page):
+    return self.diff_path_template % page
 
-  def GetPlatformExpectedPath(self, platform, page):
-    return self.platform_expected_path % (platform, page)
+  def _GetPossibleExpectedPaths(self, page):
+    return [template % page for template in self.expected_templates]
+
+  def GetExpectedPaths(self, page):
+    return list(filter(os.path.exists, self._GetPossibleExpectedPaths(page)))
+
+  def GetExpectedPath(self, page, default_to_base=True):
+    """Returns the most specific expected path that exists."""
+    last_not_found_expected_path = None
+    for expected_path in self._GetPossibleExpectedPaths(page):
+      if os.path.exists(expected_path):
+        return expected_path
+      last_not_found_expected_path = expected_path
+    return last_not_found_expected_path if default_to_base else None
diff --git a/testing/tools/run_corpus_tests.py b/testing/tools/run_corpus_tests.py
index c1bec3a..c9ad185 100755
--- a/testing/tools/run_corpus_tests.py
+++ b/testing/tools/run_corpus_tests.py
@@ -1,18 +1,16 @@
-#!/usr/bin/env python
-# Copyright 2015 The PDFium Authors. All rights reserved.
+#!/usr/bin/env vpython3
+# Copyright 2015 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
 import sys
 
-# pylint: disable=relative-import
 import test_runner
 
 
 def main():
   runner = test_runner.TestRunner('corpus')
   runner.SetEnforceExpectedImages(True)
-  runner.SetOneShotRenderer(True)
   return runner.Run()
 
 
diff --git a/testing/tools/run_javascript_tests.py b/testing/tools/run_javascript_tests.py
index 948c1af..013fe0b 100755
--- a/testing/tools/run_javascript_tests.py
+++ b/testing/tools/run_javascript_tests.py
@@ -1,11 +1,10 @@
-#!/usr/bin/env python
-# Copyright 2015 The PDFium Authors. All rights reserved.
+#!/usr/bin/env vpython3
+# Copyright 2015 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
 import sys
 
-# pylint: disable=relative-import
 import test_runner
 
 
diff --git a/testing/tools/run_pixel_tests.py b/testing/tools/run_pixel_tests.py
index 0992e0a..fb355b8 100755
--- a/testing/tools/run_pixel_tests.py
+++ b/testing/tools/run_pixel_tests.py
@@ -1,11 +1,10 @@
-#!/usr/bin/env python
-# Copyright 2015 The PDFium Authors. All rights reserved.
+#!/usr/bin/env vpython3
+# Copyright 2015 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
 import sys
 
-# pylint: disable=relative-import
 import test_runner
 
 
diff --git a/testing/tools/safetynet_compare.py b/testing/tools/safetynet_compare.py
index c76ce44..e1b5c85 100755
--- a/testing/tools/safetynet_compare.py
+++ b/testing/tools/safetynet_compare.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# Copyright 2017 The PDFium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2017 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Compares the performance of two versions of the pdfium code."""
@@ -16,7 +16,6 @@
 import sys
 import tempfile
 
-# pylint: disable=relative-import
 from common import GetBooleanGnArg
 from common import PrintErr
 from common import RunCommandPropagateErr
@@ -33,7 +32,7 @@
   return (test_case, result)
 
 
-class CompareRun(object):
+class CompareRun:
   """A comparison between two branches of pdfium."""
 
   def __init__(self, args):
@@ -527,8 +526,7 @@
       output_filename = (
           'callgrind.out.%s.%s' % (test_case.replace('/', '_'), run_label))
       return os.path.join(self.args.output_dir, output_filename)
-    else:
-      return None
+    return None
 
   def _DrawConclusions(self, times_before_branch, times_after_branch):
     """Draws conclusions comparing results of test runs in two branches.
@@ -564,7 +562,7 @@
           ComparisonConclusions.GetOutputDict().
     """
     if self.args.machine_readable:
-      print json.dumps(conclusions_dict)
+      print(json.dumps(conclusions_dict))
     else:
       PrintConclusionsDictHumanReadable(
           conclusions_dict, colored=True, key=self.args.case_order)
diff --git a/testing/tools/safetynet_conclusions.py b/testing/tools/safetynet_conclusions.py
index 8f0b28c..b5be14a 100644
--- a/testing/tools/safetynet_conclusions.py
+++ b/testing/tools/safetynet_conclusions.py
@@ -1,4 +1,4 @@
-# Copyright 2017 The PDFium Authors. All rights reserved.
+# Copyright 2017 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Classes that draw conclusions out of a comparison and represent them."""
@@ -31,7 +31,7 @@
 }
 
 
-class ComparisonConclusions(object):
+class ComparisonConclusions:
   """All conclusions drawn from a comparison.
 
   This is initialized empty and then processes pairs of results for each test
@@ -182,7 +182,7 @@
     return output_dict
 
 
-class ComparisonSummary(object):
+class ComparisonSummary:
   """Totals computed for a comparison."""
 
   def __init__(self):
@@ -207,7 +207,7 @@
     return result
 
 
-class CaseResult(object):
+class CaseResult:
   """The conclusion for the comparison of a single test case."""
 
   def __init__(self, case_name, before, after, ratio, rating):
@@ -245,9 +245,9 @@
     key: String with the CaseResult dictionary key to sort the cases.
   """
   # Print header
-  print '=' * 80
-  print '{0:>11s} {1:>15s}  {2}'.format('% Change', 'Time after', 'Test case')
-  print '-' * 80
+  print('=' * 80)
+  print('{0:>11s} {1:>15s}  {2}'.format('% Change', 'Time after', 'Test case'))
+  print('-' * 80)
 
   color = FORMAT_NORMAL
 
@@ -264,18 +264,18 @@
       color = RATING_TO_COLOR[case_dict['rating']]
 
     if case_dict['rating'] == RATING_FAILURE:
-      print u'{} to measure time for {}'.format(
-          color.format('Failed'), case_name).encode('utf-8')
+      print(u'{} to measure time for {}'.format(
+          color.format('Failed'), case_name).encode('utf-8'))
       continue
 
-    print u'{0} {1:15,d}  {2}'.format(
+    print(u'{0} {1:15,d}  {2}'.format(
         color.format('{:+11.4%}'.format(case_dict['ratio'])),
-        case_dict['after'], case_name).encode('utf-8')
+        case_dict['after'], case_name).encode('utf-8'))
 
   # Print totals
   totals = conclusions_dict['summary']
-  print '=' * 80
-  print 'Test cases run: %d' % totals['total']
+  print('=' * 80)
+  print('Test cases run: %d' % totals['total'])
 
   if colored:
     color = FORMAT_MAGENTA if totals[RATING_FAILURE] else FORMAT_GREEN
diff --git a/testing/tools/safetynet_image.py b/testing/tools/safetynet_image.py
index f300615..3295387 100644
--- a/testing/tools/safetynet_image.py
+++ b/testing/tools/safetynet_image.py
@@ -1,4 +1,4 @@
-# Copyright 2017 The PDFium Authors. All rights reserved.
+# Copyright 2017 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Compares pairs of page images and generates an HTML to look at differences.
@@ -13,7 +13,6 @@
 import sys
 import webbrowser
 
-# pylint: disable=relative-import
 from common import DirectoryFinder
 
 
@@ -21,7 +20,7 @@
   return image_comparison.GenerateOneDiff(image)
 
 
-class ImageComparison(object):
+class ImageComparison:
   """Compares pairs of page images and generates an HTML to look at differences.
 
   The images are all assumed to have the same name and be in two directories:
@@ -63,7 +62,7 @@
     # pylint: disable=attribute-defined-outside-init
 
     if len(self.two_labels) != 2:
-      print >> sys.stderr, 'two_labels must be a tuple of length 2'
+      print('two_labels must be a tuple of length 2', file=sys.stderr)
       return 1
 
     finder = DirectoryFinder(self.build_dir)
@@ -88,7 +87,7 @@
       for image in self.image_locations.Images():
         diff = difference[image]
         if diff is None:
-          print >> sys.stderr, 'Failed to compare image %s' % image
+          print('Failed to compare image %s' % image, file=sys.stderr)
         elif diff > self.threshold:
           self._WriteImageRows(f, image, diff)
         else:
@@ -170,7 +169,7 @@
     except subprocess.CalledProcessError as e:
       return image, percentage_change
     else:
-      print >> sys.stderr, 'Warning: Should have failed the previous diff.'
+      print('Warning: Should have failed the previous diff.', file=sys.stderr)
       return image, 0
 
   def _GetRelativePath(self, absolute_path):
@@ -239,7 +238,7 @@
       f.write('</td></tr>')
 
 
-class ImageLocations(object):
+class ImageLocations:
   """Contains the locations of input and output image files.
   """
 
diff --git a/testing/tools/safetynet_job.py b/testing/tools/safetynet_job.py
index 9b5cbfd..bbb3cca 100755
--- a/testing/tools/safetynet_job.py
+++ b/testing/tools/safetynet_job.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# Copyright 2017 The PDFium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2017 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Looks for performance regressions on all pushes since the last run.
@@ -15,14 +15,13 @@
 import os
 import sys
 
-# pylint: disable=relative-import
 from common import PrintWithTime
 from common import RunCommandPropagateErr
 from githelper import GitHelper
 from safetynet_conclusions import PrintConclusionsDictHumanReadable
 
 
-class JobContext(object):
+class JobContext:
   """Context for a single run, including name and directory paths."""
 
   def __init__(self, args):
@@ -36,7 +35,7 @@
                                             '%s.log' % self.datetime)
 
 
-class JobRun(object):
+class JobRun:
   """A single run looking for regressions since the last one."""
 
   def __init__(self, args, context):
@@ -69,7 +68,7 @@
 
     if not self.args.no_checkout:
       self.git.FetchOriginMaster()
-      self.git.Checkout('origin/master')
+      self.git.Checkout('origin/main')
 
     # Make sure results dir exists
     if not os.path.exists(self.context.results_dir):
@@ -200,7 +199,7 @@
   parser.add_argument(
       '--no-checkout',
       action='store_true',
-      help='whether to skip checking out origin/master. Use '
+      help='whether to skip checking out origin/main. Use '
       'for script debugging.')
   parser.add_argument(
       '--no-checkpoint',
diff --git a/testing/tools/safetynet_measure.py b/testing/tools/safetynet_measure.py
index 3577189..cc83b1d 100755
--- a/testing/tools/safetynet_measure.py
+++ b/testing/tools/safetynet_measure.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# Copyright 2017 The PDFium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2017 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Measures performance for rendering a single test case with pdfium.
@@ -13,7 +13,6 @@
 import subprocess
 import sys
 
-# pylint: disable=relative-import
 from common import PrintErr
 
 CALLGRIND_PROFILER = 'callgrind'
@@ -23,7 +22,7 @@
 PDFIUM_TEST = 'pdfium_test'
 
 
-class PerformanceRun(object):
+class PerformanceRun:
   """A single measurement of a test case."""
 
   def __init__(self, args):
@@ -65,7 +64,7 @@
     if time is None:
       return 1
 
-    print time
+    print(time)
     return 0
 
   def _RunCallgrind(self):
@@ -84,7 +83,8 @@
         '--instr-atstart=%s' % instrument_at_start,
         '--callgrind-out-file=%s' % output_path
     ] + self._BuildTestHarnessCommand())
-    output = subprocess.check_output(valgrind_cmd, stderr=subprocess.STDOUT)
+    output = subprocess.check_output(
+        valgrind_cmd, stderr=subprocess.STDOUT).decode('utf-8')
 
     # Match the line with the instruction count, eg.
     # '==98765== Collected : 12345'
@@ -100,7 +100,8 @@
     # -einstructions: print only instruction count
     cmd_to_run = (['perf', 'stat', '--no-big-num', '-einstructions'] +
                   self._BuildTestHarnessCommand())
-    output = subprocess.check_output(cmd_to_run, stderr=subprocess.STDOUT)
+    output = subprocess.check_output(
+        cmd_to_run, stderr=subprocess.STDOUT).decode('utf-8')
 
     # Match the line with the instruction count, eg.
     # '        12345      instructions'
diff --git a/testing/tools/skia_gold/__init__.py b/testing/tools/skia_gold/__init__.py
new file mode 100644
index 0000000..39381b9
--- /dev/null
+++ b/testing/tools/skia_gold/__init__.py
@@ -0,0 +1,7 @@
+# Copyright 2022 The PDFium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import pdfium_root
+
+pdfium_root.add_source_directory_to_import_path('build')
diff --git a/testing/tools/skia_gold/pdfium_skia_gold_properties.py b/testing/tools/skia_gold/pdfium_skia_gold_properties.py
new file mode 100644
index 0000000..aeb8ef9
--- /dev/null
+++ b/testing/tools/skia_gold/pdfium_skia_gold_properties.py
@@ -0,0 +1,27 @@
+# Copyright 2021 The PDFium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""PDFium implementation of //build/skia_gold_common/skia_gold_properties.py."""
+
+import subprocess
+import sys
+
+import pdfium_root
+from skia_gold_common import skia_gold_properties
+
+
+class PDFiumSkiaGoldProperties(skia_gold_properties.SkiaGoldProperties):
+
+  @staticmethod
+  def _GetGitOriginMainHeadSha1():
+    root_finder = pdfium_root.RootDirectoryFinder()
+    try:
+      return subprocess.check_output(['git', 'rev-parse', 'origin/main'],
+                                     shell=_IsWin(),
+                                     cwd=root_finder.pdfium_root).strip()
+    except subprocess.CalledProcessError:
+      return None
+
+
+def _IsWin():
+  return sys.platform == 'win32'
diff --git a/testing/tools/skia_gold/pdfium_skia_gold_session.py b/testing/tools/skia_gold/pdfium_skia_gold_session.py
new file mode 100644
index 0000000..98a7ef2
--- /dev/null
+++ b/testing/tools/skia_gold/pdfium_skia_gold_session.py
@@ -0,0 +1,29 @@
+# Copyright 2021 The PDFium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""PDFium implementation of //build/skia_gold_common/skia_gold_session.py."""
+
+from skia_gold_common import output_managerless_skia_gold_session as omsgs
+
+
+# ComparisonResults nested inside the SkiaGoldSession causes issues with
+# multiprocessing and pickling, so it was moved out here.
+class PDFiumComparisonResults:
+  """Struct-like object for storing results of an image comparison."""
+
+  def __init__(self):
+    self.public_triage_link = None
+    self.internal_triage_link = None
+    self.triage_link_omission_reason = None
+    self.local_diff_given_image = None
+    self.local_diff_closest_image = None
+    self.local_diff_diff_image = None
+
+
+class PDFiumSkiaGoldSession(omsgs.OutputManagerlessSkiaGoldSession):
+
+  def _GetDiffGoldInstance(self):
+    return str(self._instance)
+
+  def ComparisonResults(self):
+    return PDFiumComparisonResults()
diff --git a/testing/tools/skia_gold/pdfium_skia_gold_session_manager.py b/testing/tools/skia_gold/pdfium_skia_gold_session_manager.py
new file mode 100644
index 0000000..af6cc5e
--- /dev/null
+++ b/testing/tools/skia_gold/pdfium_skia_gold_session_manager.py
@@ -0,0 +1,21 @@
+# Copyright 2021 The PDFium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""PDFium implementation of
+//build/skia_gold_common/skia_gold_session_manager.py."""
+
+from . import pdfium_skia_gold_session
+from skia_gold_common import skia_gold_session_manager as sgsm
+
+SKIA_PDF_INSTANCE = 'pdfium'
+
+
+class PDFiumSkiaGoldSessionManager(sgsm.SkiaGoldSessionManager):
+
+  @staticmethod
+  def GetSessionClass():
+    return pdfium_skia_gold_session.PDFiumSkiaGoldSession
+
+  @staticmethod
+  def _GetDefaultInstance():
+    return SKIA_PDF_INSTANCE
diff --git a/testing/tools/skia_gold/skia_gold.py b/testing/tools/skia_gold/skia_gold.py
new file mode 100644
index 0000000..e3595d2
--- /dev/null
+++ b/testing/tools/skia_gold/skia_gold.py
@@ -0,0 +1,221 @@
+# Copyright 2021 The PDFium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import logging
+import shlex
+import shutil
+
+from . import pdfium_skia_gold_properties
+from . import pdfium_skia_gold_session_manager
+
+GS_BUCKET = 'skia-pdfium-gm'
+
+
+def _ParseKeyValuePairs(kv_str):
+  """
+  Parses a string of the type 'key1 value1 key2 value2' into a dict.
+  """
+  kv_pairs = shlex.split(kv_str)
+  if len(kv_pairs) % 2:
+    raise ValueError('Uneven number of key/value pairs. Got %s' % kv_str)
+  return {kv_pairs[i]: kv_pairs[i + 1] for i in range(0, len(kv_pairs), 2)}
+
+
+def add_skia_gold_args(parser):
+  group = parser.add_argument_group('Skia Gold Arguments')
+  group.add_argument(
+      '--git-revision', help='Revision being tested.', default=None)
+  group.add_argument(
+      '--gerrit-issue',
+      help='For Skia Gold integration. Gerrit issue ID.',
+      default='')
+  group.add_argument(
+      '--gerrit-patchset',
+      help='For Skia Gold integration. Gerrit patch set number.',
+      default='')
+  group.add_argument(
+      '--buildbucket-id',
+      help='For Skia Gold integration. Buildbucket build ID.',
+      default='')
+  group.add_argument(
+      '--bypass-skia-gold-functionality',
+      action='store_true',
+      default=False,
+      help='Bypass all interaction with Skia Gold, effectively disabling the '
+      'image comparison portion of any tests that use Gold. Only meant to '
+      'be used in case a Gold outage occurs and cannot be fixed quickly.')
+  local_group = group.add_mutually_exclusive_group()
+  local_group.add_argument(
+      '--local-pixel-tests',
+      action='store_true',
+      default=None,
+      help='Specifies to run the test harness in local run mode or not. When '
+      'run in local mode, uploading to Gold is disabled and links to '
+      'help with local debugging are output. Running in local mode also '
+      'implies --no-luci-auth. If both this and --no-local-pixel-tests are '
+      'left unset, the test harness will attempt to detect whether it is '
+      'running on a workstation or not and set this option accordingly.')
+  local_group.add_argument(
+      '--no-local-pixel-tests',
+      action='store_false',
+      dest='local_pixel_tests',
+      help='Specifies to run the test harness in non-local (bot) mode. When '
+      'run in this mode, data is actually uploaded to Gold and triage links '
+      'arge generated. If both this and --local-pixel-tests are left unset, '
+      'the test harness will attempt to detect whether it is running on a '
+      'workstation or not and set this option accordingly.')
+  group.add_argument(
+      '--no-luci-auth',
+      action='store_true',
+      default=False,
+      help='Don\'t use the service account provided by LUCI for '
+      'authentication for Skia Gold, instead relying on gsutil to be '
+      'pre-authenticated. Meant for testing locally instead of on the bots.')
+
+  group.add_argument(
+      '--gold_key',
+      default='',
+      dest="gold_key",
+      help='Key value pairs of config data such like the hardware/software '
+      'configuration the image was produced on.')
+  group.add_argument(
+      '--gold_output_dir',
+      default='',
+      dest="gold_output_dir",
+      help='Path to the dir where diff output image files are saved, '
+      'if running locally. If this is a tryjob run, will contain link to skia '
+      'gold CL triage link. Required with --run-skia-gold.')
+
+
+def clear_gold_output_dir(output_dir):
+  # make sure the output directory exists and is empty.
+  if os.path.exists(output_dir):
+    shutil.rmtree(output_dir, ignore_errors=True)
+  os.makedirs(output_dir)
+
+
+class SkiaGoldTester:
+
+  def __init__(self, source_type, skia_gold_args, process_name=None):
+    """
+    source_type: source_type (=corpus) field used for all results.
+    skia_gold_args: Parsed arguments from argparse.ArgumentParser.
+    process_name: Unique name of current process, if multiprocessing is on.
+    """
+    self._source_type = source_type
+    self._output_dir = skia_gold_args.gold_output_dir
+    # goldctl is not thread safe, so each process needs its own directory
+    if process_name:
+      self._output_dir = os.path.join(skia_gold_args.gold_output_dir,
+                                      process_name)
+      clear_gold_output_dir(self._output_dir)
+    self._keys = _ParseKeyValuePairs(skia_gold_args.gold_key)
+    self._old_gold_props = _ParseKeyValuePairs(skia_gold_args.gold_properties)
+    self._skia_gold_args = skia_gold_args
+    self._skia_gold_session_manager = None
+    self._skia_gold_properties = None
+
+  def WriteCLTriageLink(self, link):
+    # pdfium recipe will read from this file and display the link in the step
+    # presentation
+    assert isinstance(link, str)
+    output_file_name = os.path.join(self._output_dir, 'cl_triage_link.txt')
+    if os.path.exists(output_file_name):
+      os.remove(output_file_name)
+    with open(output_file_name, 'wb') as outfile:
+      outfile.write(link.encode('utf8'))
+
+  def GetSkiaGoldProperties(self):
+    if not self._skia_gold_properties:
+      if self._old_gold_props:
+        self._skia_gold_args.git_revision = self._old_gold_props['gitHash']
+        self._skia_gold_args.gerrit_issue = self._old_gold_props['issue']
+        self._skia_gold_args.gerrit_patchset = self._old_gold_props['patchset']
+        self._skia_gold_args.buildbucket_id = \
+            self._old_gold_props['buildbucket_build_id']
+
+      if self._skia_gold_args.local_pixel_tests is None:
+        self._skia_gold_args.local_pixel_tests = 'SWARMING_SERVER' \
+            not in os.environ
+
+      self._skia_gold_properties = pdfium_skia_gold_properties\
+          .PDFiumSkiaGoldProperties(self._skia_gold_args)
+    return self._skia_gold_properties
+
+  def GetSkiaGoldSessionManager(self):
+    if not self._skia_gold_session_manager:
+      self._skia_gold_session_manager = pdfium_skia_gold_session_manager\
+          .PDFiumSkiaGoldSessionManager(self._output_dir,
+                                        self.GetSkiaGoldProperties())
+    return self._skia_gold_session_manager
+
+  def IsTryjobRun(self):
+    return self.GetSkiaGoldProperties().IsTryjobRun()
+
+  def GetCLTriageLink(self):
+    return 'https://pdfium-gold.skia.org/search?issue={issue}&crs=gerrit&'\
+    'corpus={source_type}'.format(
+        issue=self.GetSkiaGoldProperties().issue, source_type=self._source_type)
+
+  def UploadTestResultToSkiaGold(self, image_name, image_path):
+    gold_properties = self.GetSkiaGoldProperties()
+    use_luci = not (gold_properties.local_pixel_tests or
+                    gold_properties.no_luci_auth)
+    gold_session = self.GetSkiaGoldSessionManager()\
+        .GetSkiaGoldSession(self._keys, corpus=self._source_type,
+                            bucket=GS_BUCKET)
+
+    status, error = gold_session.RunComparison(
+        name=image_name, png_file=image_path, use_luci=use_luci)
+
+    status_codes =\
+        self.GetSkiaGoldSessionManager().GetSessionClass().StatusCodes
+    if status == status_codes.SUCCESS:
+      return True
+    if status == status_codes.AUTH_FAILURE:
+      logging.error('Gold authentication failed with output %s', error)
+    elif status == status_codes.INIT_FAILURE:
+      logging.error('Gold initialization failed with output %s', error)
+    elif status == status_codes.COMPARISON_FAILURE_REMOTE:
+      logging.error('Remote comparison failed. See outputted triage links.')
+    elif status == status_codes.COMPARISON_FAILURE_LOCAL:
+      logging.error('Local comparison failed. Local diff files:')
+      _OutputLocalDiffFiles(gold_session, image_name)
+      print()
+    elif status == status_codes.LOCAL_DIFF_FAILURE:
+      logging.error(
+          'Local comparison failed and an error occurred during diff '
+          'generation: %s', error)
+      # There might be some files, so try outputting them.
+      logging.error('Local diff files:')
+      _OutputLocalDiffFiles(gold_session, image_name)
+      print()
+    else:
+      logging.error(
+          'Given unhandled SkiaGoldSession StatusCode %s with error %s', status,
+          error)
+
+    return False
+
+
+def _OutputLocalDiffFiles(gold_session, image_name):
+  """Logs the local diff image files from the given SkiaGoldSession.
+
+  Args:
+    gold_session: A skia_gold_session.SkiaGoldSession instance to pull files
+        from.
+    image_name: A string containing the name of the image/test that was
+        compared.
+  """
+  given_file = gold_session.GetGivenImageLink(image_name)
+  closest_file = gold_session.GetClosestImageLink(image_name)
+  diff_file = gold_session.GetDiffImageLink(image_name)
+  failure_message = 'Unable to retrieve link'
+  logging.error('Generated image for %s: %s', image_name, given_file or
+                failure_message)
+  logging.error('Closest image for %s: %s', image_name, closest_file or
+                failure_message)
+  logging.error('Diff image for %s: %s', image_name, diff_file or
+                failure_message)
diff --git a/testing/tools/strip_jp2_comments.py b/testing/tools/strip_jp2_comments.py
new file mode 100755
index 0000000..eb03cdc
--- /dev/null
+++ b/testing/tools/strip_jp2_comments.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+# Copyright 2023 The PDFium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Strips comments from a JP2 file.
+
+This is a simple filter script to strip comments from a JP2 file, in order to
+save a few bytes from the final file size.
+"""
+
+import struct
+import sys
+
+BOX_HEADER_SIZE = 8
+BOX_TAG_JP2C = b'jp2c'
+
+MARKER_SIZE = 2
+MARKER_START = 0xff
+MARKER_TAG_IGNORE = 0x00
+MARKER_TAG_COMMENT = 0x64
+MARKER_TAG_FILL = 0xff
+
+
+def parse_box(buffer, offset):
+  """Parses the next box in a JP2 file.
+
+  Args:
+    buffer: A buffer containing the JP2 file contents.
+    offset: The starting offset into the buffer.
+
+  Returns:
+    A tuple (next_offset, tag) where next_offset is the ending offset, and tag
+    is the type tag. The box contents will be buffer[offset + 8:next_offset].
+  """
+  length, tag = struct.unpack_from('>I4s', buffer, offset)
+  return offset + length, tag
+
+
+def parse_marker(buffer, offset):
+  """Parses the next marker in a codestream.
+
+  Args:
+    buffer: A buffer containing the codestream.
+    offset: The starting offset into the buffer.
+
+  Returns:
+    A tuple (next_offset, tag) where next_offset is the offset after the marker,
+    and tag is the type tag. If no marker was found, next_offset will point to
+    the end of the buffer, and tag will be None. A marker is always 2 bytes.
+  """
+  while True:
+    # Search for start of marker.
+    next_offset = buffer.find(MARKER_START, offset)
+    if next_offset == -1:
+      next_offset = len(buffer)
+      break
+    next_offset += 1
+
+    # Parse marker.
+    if next_offset == len(buffer):
+      break
+    tag = buffer[next_offset]
+    if tag == MARKER_TAG_FILL:
+      # Possible fill byte, reparse as start of marker.
+      continue
+    next_offset += 1
+
+    if tag == MARKER_TAG_IGNORE:
+      # Not a real marker.
+      continue
+    return next_offset, tag
+
+  return next_offset
+
+
+def rewrite_jp2c(buffer):
+  rewrite_buffer = bytearray(BOX_HEADER_SIZE)
+
+  offset = 0
+  start_offset = offset
+  while offset < len(buffer):
+    next_offset, marker = parse_marker(buffer, offset)
+    if marker == MARKER_TAG_COMMENT:
+      # Flush the codestream before the comment.
+      rewrite_buffer.extend(buffer[start_offset:next_offset - MARKER_SIZE])
+
+      # Find the next marker, skipping the comment.
+      next_offset, marker = parse_marker(buffer, next_offset)
+      if marker is not None:
+        # Reparse the marker.
+        next_offset -= MARKER_SIZE
+      start_offset = next_offset
+    else:
+      # Pass through other markers.
+      pass
+    offset = next_offset
+
+  # Flush the tail of the codestream.
+  rewrite_buffer.extend(buffer[start_offset:])
+
+  struct.pack_into('>I4s', rewrite_buffer, 0, len(rewrite_buffer), BOX_TAG_JP2C)
+  return rewrite_buffer
+
+
+def main(in_file, out_file):
+  buffer = in_file.read()
+
+  # Scan through JP2 boxes.
+  offset = 0
+  while offset < len(buffer):
+    next_offset, tag = parse_box(buffer, offset)
+    if tag == BOX_TAG_JP2C:
+      # Rewrite "jp2c" (codestream) box.
+      out_file.write(rewrite_jp2c(buffer[offset + BOX_HEADER_SIZE:next_offset]))
+    else:
+      # Pass through other boxes.
+      out_file.write(buffer[offset:next_offset])
+    offset = next_offset
+
+  out_file.flush()
+
+
+if __name__ == '__main__':
+  main(sys.stdin.buffer, sys.stdout.buffer)
diff --git a/testing/tools/suppressor.py b/testing/tools/suppressor.py
index 70eef99..989f4dd 100755
--- a/testing/tools/suppressor.py
+++ b/testing/tools/suppressor.py
@@ -1,31 +1,34 @@
-#!/usr/bin/env python
-# Copyright 2015 The PDFium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2015 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
 import os
 
-# pylint: disable=relative-import
 import common
+import pngdiffer
 
 
 class Suppressor:
 
-  def __init__(self, finder, feature_string, js_disabled, xfa_disabled):
-    feature_vector = feature_string.strip().split(",")
-    self.has_v8 = not js_disabled and "V8" in feature_vector
-    self.has_xfa = (not js_disabled and not xfa_disabled and
-                    "XFA" in feature_vector)
+  def __init__(self, finder, features, js_disabled, xfa_disabled):
+    self.has_v8 = not js_disabled and 'V8' in features
+    self.has_xfa = not js_disabled and not xfa_disabled and 'XFA' in features
+    self.has_skia = 'SKIA' in features
     self.suppression_set = self._LoadSuppressedSet('SUPPRESSIONS', finder)
     self.image_suppression_set = self._LoadSuppressedSet(
         'SUPPRESSIONS_IMAGE_DIFF', finder)
+    self.exact_matching_suppression_set = self._LoadSuppressedSet(
+        'SUPPRESSIONS_EXACT_MATCHING', finder)
 
   def _LoadSuppressedSet(self, suppressions_filename, finder):
     v8_option = "v8" if self.has_v8 else "nov8"
     xfa_option = "xfa" if self.has_xfa else "noxfa"
+    rendering_option = "skia" if self.has_skia else "agg"
     with open(os.path.join(finder.TestingDir(), suppressions_filename)) as f:
       return set(
-          self._FilterSuppressions(common.os_name(), v8_option, xfa_option,
+          self._FilterSuppressions(common.os_name(), v8_option,
+                                   xfa_option, rendering_option,
                                    self._ExtractSuppressions(f)))
 
   def _ExtractSuppressions(self, f):
@@ -34,35 +37,45 @@
                                for x in f.readlines()] if y
     ]
 
-  def _FilterSuppressions(self, os_name, js, xfa, unfiltered_list):
+  def _FilterSuppressions(self, os_name, js, xfa, rendering_option,
+                          unfiltered_list):
     return [
         x[0]
         for x in unfiltered_list
-        if self._MatchSuppression(x, os_name, js, xfa)
+        if self._MatchSuppression(x, os_name, js, xfa, rendering_option)
     ]
 
-  def _MatchSuppression(self, item, os_name, js, xfa):
+  def _MatchSuppression(self, item, os_name, js, xfa, rendering_option):
     os_column = item[1].split(",")
     js_column = item[2].split(",")
     xfa_column = item[3].split(",")
+    rendering_option_column = item[4].split(",")
     return (('*' in os_column or os_name in os_column) and
             ('*' in js_column or js in js_column) and
-            ('*' in xfa_column or xfa in xfa_column))
+            ('*' in xfa_column or xfa in xfa_column) and
+            ('*' in rendering_option_column or
+             rendering_option in rendering_option_column))
 
   def IsResultSuppressed(self, input_filename):
     if input_filename in self.suppression_set:
-      print "%s result is suppressed" % input_filename
+      print("%s result is suppressed" % input_filename)
       return True
     return False
 
   def IsExecutionSuppressed(self, input_filepath):
     if "xfa_specific" in input_filepath and not self.has_xfa:
-      print "%s execution is suppressed" % input_filepath
+      print("%s execution is suppressed" % input_filepath)
       return True
     return False
 
   def IsImageDiffSuppressed(self, input_filename):
     if input_filename in self.image_suppression_set:
-      print "%s image diff comparison is suppressed" % input_filename
+      print("%s image diff comparison is suppressed" % input_filename)
       return True
     return False
+
+  def GetImageMatchingAlgorithm(self, input_filename):
+    if input_filename in self.exact_matching_suppression_set:
+      print(f"{input_filename} image diff comparison is fuzzy")
+      return pngdiffer.FUZZY_MATCHING
+    return pngdiffer.EXACT_MATCHING
diff --git a/testing/tools/test_runner.py b/testing/tools/test_runner.py
index d3640d6..ad348aa 100644
--- a/testing/tools/test_runner.py
+++ b/testing/tools/test_runner.py
@@ -1,23 +1,30 @@
-#!/usr/bin/env python
-# Copyright 2016 The PDFium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2016 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import functools
+import argparse
+from dataclasses import dataclass, field
+from datetime import timedelta
+from io import BytesIO
 import multiprocessing
-import optparse
 import os
 import re
 import shutil
 import subprocess
 import sys
+import time
 
-# pylint: disable=relative-import
 import common
-import gold
+import pdfium_root
 import pngdiffer
+from skia_gold import skia_gold
 import suppressor
 
+pdfium_root.add_source_directory_to_import_path(os.path.join('build', 'util'))
+from lib.results import result_sink, result_types
+
+
 # Arbitrary timestamp, expressed in seconds since the epoch, used to make sure
 # that tests that depend on the current time are stable. Happens to be the
 # timestamp of the first commit to repo, 2014/5/9 17:48:50.
@@ -26,33 +33,10 @@
 # List of test types that should run text tests instead of pixel tests.
 TEXT_TESTS = ['javascript']
 
-
-class KeyboardInterruptError(Exception):
-  pass
-
-
-# Nomenclature:
-#   x_root - "x"
-#   x_filename - "x.ext"
-#   x_path - "path/to/a/b/c/x.ext"
-#   c_dir - "path/to/a/b/c"
-
-
-def TestOneFileParallel(this, test_case):
-  """Wrapper to call GenerateAndTest() and redirect output to stdout."""
-  try:
-    input_filename, source_dir = test_case
-    result = this.GenerateAndTest(input_filename, source_dir)
-    return (result, input_filename, source_dir)
-  except KeyboardInterrupt:
-    raise KeyboardInterruptError()
-
-
-def DeleteFiles(files):
-  """Utility function to delete a list of files"""
-  for f in files:
-    if os.path.exists(f):
-      os.remove(f)
+# Timeout (in seconds) for individual test commands.
+# TODO(crbug.com/pdfium/1967): array_buffer.in is slow under MSan, so need a
+# very generous 5 minute timeout for now.
+TEST_TIMEOUT = timedelta(minutes=5).total_seconds()
 
 
 class TestRunner:
@@ -62,391 +46,303 @@
     # which all correspond directly to the type for the test being run. In the
     # future if there are tests that don't have this clean correspondence, then
     # an argument for the type will need to be added.
-    self.test_dir = dirname
-    self.test_type = dirname
-    self.delete_output_on_success = False
-    self.enforce_expected_images = False
-    self.oneshot_renderer = False
+    self.per_process_config = _PerProcessConfig(
+        test_dir=dirname, test_type=dirname)
 
-  # GenerateAndTest returns a tuple <success, outputfiles> where
-  # success is a boolean indicating whether the tests passed comparison
-  # tests and outputfiles is a list tuples:
-  #          (path_to_image, md5_hash_of_pixelbuffer)
-  def GenerateAndTest(self, input_filename, source_dir):
-    input_root, _ = os.path.splitext(input_filename)
-    pdf_path = os.path.join(self.working_dir, input_root + '.pdf')
+  @property
+  def options(self):
+    return self.per_process_config.options
 
-    # Remove any existing generated images from previous runs.
-    actual_images = self.image_differ.GetActualFiles(input_filename, source_dir,
-                                                     self.working_dir)
-    DeleteFiles(actual_images)
+  def IsSkiaGoldEnabled(self):
+    return (self.options.run_skia_gold and
+            not self.per_process_config.test_type in TEXT_TESTS)
 
-    sys.stdout.flush()
+  def IsExecutionSuppressed(self, input_path):
+    return self.per_process_state.test_suppressor.IsExecutionSuppressed(
+        input_path)
 
-    raised_exception = self.Generate(source_dir, input_filename, input_root,
-                                     pdf_path)
+  def IsResultSuppressed(self, input_filename):
+    return self.per_process_state.test_suppressor.IsResultSuppressed(
+        input_filename)
 
-    if raised_exception is not None:
-      print 'FAILURE: %s; %s' % (input_filename, raised_exception)
-      return False, []
+  def HandleResult(self, test_case, test_result):
+    input_filename = os.path.basename(test_case.input_path)
 
-    results = []
-    if self.test_type in TEXT_TESTS:
-      expected_txt_path = os.path.join(source_dir, input_root + '_expected.txt')
-      raised_exception = self.TestText(input_filename, input_root,
-                                       expected_txt_path, pdf_path)
-    else:
-      use_ahem = 'use_ahem' in source_dir
-      raised_exception, results = self.TestPixel(pdf_path, use_ahem)
-
-    if raised_exception is not None:
-      print 'FAILURE: %s; %s' % (input_filename, raised_exception)
-      return False, results
-
-    if actual_images:
-      if self.image_differ.HasDifferences(input_filename, source_dir,
-                                          self.working_dir):
-        self.RegenerateIfNeeded_(input_filename, source_dir)
-        return False, results
-    else:
-      if (self.enforce_expected_images and
-          not self.test_suppressor.IsImageDiffSuppressed(input_filename)):
-        self.RegenerateIfNeeded_(input_filename, source_dir)
-        print 'FAILURE: %s; Missing expected images' % input_filename
-        return False, results
-
-    if self.delete_output_on_success:
-      DeleteFiles(actual_images)
-    return True, results
-
-  def RegenerateIfNeeded_(self, input_filename, source_dir):
-    if (not self.options.regenerate_expected or
-        self.test_suppressor.IsResultSuppressed(input_filename) or
-        self.test_suppressor.IsImageDiffSuppressed(input_filename)):
-      return
-
-    platform_only = (self.options.regenerate_expected == 'platform')
-    self.image_differ.Regenerate(input_filename, source_dir, self.working_dir,
-                                 platform_only)
-
-  def Generate(self, source_dir, input_filename, input_root, pdf_path):
-    original_path = os.path.join(source_dir, input_filename)
-    input_path = os.path.join(source_dir, input_root + '.in')
-
-    input_event_path = os.path.join(source_dir, input_root + '.evt')
-    if os.path.exists(input_event_path):
-      output_event_path = os.path.splitext(pdf_path)[0] + '.evt'
-      shutil.copyfile(input_event_path, output_event_path)
-
-    if not os.path.exists(input_path):
-      if os.path.exists(original_path):
-        shutil.copyfile(original_path, pdf_path)
-      return None
-
-    sys.stdout.flush()
-
-    return common.RunCommand([
-        sys.executable, self.fixup_path, '--output-dir=' + self.working_dir,
-        input_path
-    ])
-
-  def TestText(self, input_filename, input_root, expected_txt_path, pdf_path):
-    txt_path = os.path.join(self.working_dir, input_root + '.txt')
-
-    with open(txt_path, 'w') as outfile:
-      cmd_to_run = [
-          self.pdfium_test_path, '--send-events', '--time=' + TEST_SEED_TIME
-      ]
-
-      if self.options.disable_javascript:
-        cmd_to_run.append('--disable-javascript')
-
-      if self.options.disable_xfa:
-        cmd_to_run.append('--disable-xfa')
-
-      cmd_to_run.append(pdf_path)
-      subprocess.check_call(cmd_to_run, stdout=outfile)
-
-    # If the expected file does not exist, the output is expected to be empty.
-    if not os.path.exists(expected_txt_path):
-      return self._VerifyEmptyText(txt_path)
-
-    # If JavaScript is disabled, the output should be empty.
-    # However, if the test is suppressed and JavaScript is disabled, do not
-    # verify that the text is empty so the suppressed test does not surprise.
-    if (self.options.disable_javascript and
-        not self.test_suppressor.IsResultSuppressed(input_filename)):
-      return self._VerifyEmptyText(txt_path)
-
-    cmd = [sys.executable, self.text_diff_path, expected_txt_path, txt_path]
-    return common.RunCommand(cmd)
-
-  def _VerifyEmptyText(self, txt_path):
-    try:
-      with open(txt_path, "r") as txt_file:
-        txt_data = txt_file.readlines()
-      if not len(txt_data):
-        return None
-      sys.stdout.write('Unexpected output:\n')
-      for line in txt_data:
-        sys.stdout.write(line)
-      raise Exception('%s should be empty.' % txt_path)
-    except Exception as e:
-      return e
-
-  def TestPixel(self, pdf_path, use_ahem):
-    cmd_to_run = [
-        self.pdfium_test_path, '--send-events', '--png', '--md5',
-        '--time=' + TEST_SEED_TIME
-    ]
-
-    if self.oneshot_renderer:
-      cmd_to_run.append('--render-oneshot')
-
-    if use_ahem:
-      cmd_to_run.append('--font-dir=%s' % self.font_dir)
-
-    if self.options.disable_javascript:
-      cmd_to_run.append('--disable-javascript')
-
-    if self.options.disable_xfa:
-      cmd_to_run.append('--disable-xfa')
-
-    if self.options.reverse_byte_order:
-      cmd_to_run.append('--reverse-byte-order')
-
-    cmd_to_run.append(pdf_path)
-    return common.RunCommandExtractHashedFiles(cmd_to_run)
-
-  def HandleResult(self, input_filename, input_path, result):
-    success, image_paths = result
-
-    if image_paths:
-      for img_path, md5_hash in image_paths:
-        # The output filename without image extension becomes the test name.
-        # For example, "/path/to/.../testing/corpus/example_005.pdf.0.png"
-        # becomes "example_005.pdf.0".
-        test_name = os.path.splitext(os.path.split(img_path)[1])[0]
-
-        matched = "suppressed"
-        if not self.test_suppressor.IsResultSuppressed(input_filename):
-          matched = self.gold_baseline.MatchLocalResult(test_name, md5_hash)
-          if matched == gold.GoldBaseline.MISMATCH:
-            print 'Skia Gold hash mismatch for test case: %s' % test_name
-          elif matched == gold.GoldBaseline.NO_BASELINE:
-            print 'No Skia Gold baseline found for test case: %s' % test_name
-
-        if self.gold_results:
-          self.gold_results.AddTestResult(test_name, md5_hash, img_path,
-                                          matched)
-
-    if self.test_suppressor.IsResultSuppressed(input_filename):
+    test_result.status = self._SuppressStatus(input_filename,
+                                              test_result.status)
+    if test_result.status == result_types.UNKNOWN:
       self.result_suppressed_cases.append(input_filename)
-      if success:
-        self.surprises.append(input_path)
-    else:
-      if not success:
-        self.failures.append(input_path)
+      self.surprises.append(test_case.input_path)
+    elif test_result.status == result_types.SKIP:
+      self.result_suppressed_cases.append(input_filename)
+    elif not test_result.IsPass():
+      self.failures.append(test_case.input_path)
+
+    for artifact in test_result.image_artifacts:
+      if artifact.skia_gold_status == result_types.PASS:
+        if self.IsResultSuppressed(artifact.image_path):
+          self.skia_gold_unexpected_successes.append(artifact.GetSkiaGoldId())
+        else:
+          self.skia_gold_successes.append(artifact.GetSkiaGoldId())
+      elif artifact.skia_gold_status == result_types.FAIL:
+        self.skia_gold_failures.append(artifact.GetSkiaGoldId())
+
+    # Log test result.
+    print(f'{test_result.status}: {test_result.test_id}')
+    if not test_result.IsPass():
+      if test_result.reason:
+        print(f'Failure reason: {test_result.reason}')
+      if test_result.log:
+        decoded_log = bytes.decode(test_result.log, errors='backslashreplace')
+        print(f'Test output:\n{decoded_log}')
+      for artifact in test_result.image_artifacts:
+        if artifact.skia_gold_status == result_types.FAIL:
+          print(f'Failed Skia Gold: {artifact.image_path}')
+        if artifact.image_diff:
+          print(f'Failed image diff: {artifact.image_diff.reason}')
+
+    # Report test result to ResultDB.
+    if self.resultdb:
+      only_artifacts = None
+      only_failure_reason = test_result.reason
+      if len(test_result.image_artifacts) == 1:
+        only = test_result.image_artifacts[0]
+        only_artifacts = only.GetDiffArtifacts()
+        if only.GetDiffReason():
+          only_failure_reason += f': {only.GetDiffReason()}'
+      self.resultdb.Post(
+          test_id=test_result.test_id,
+          status=test_result.status,
+          duration=test_result.duration_milliseconds,
+          test_log=test_result.log,
+          test_file=None,
+          artifacts=only_artifacts,
+          failure_reason=only_failure_reason)
+
+      # Milo only supports a single diff per test, so if we have multiple pages,
+      # report each page as its own "test."
+      if len(test_result.image_artifacts) > 1:
+        for page, artifact in enumerate(test_result.image_artifacts):
+          self.resultdb.Post(
+              test_id=f'{test_result.test_id}/{page}',
+              status=self._SuppressArtifactStatus(test_result,
+                                                  artifact.GetDiffStatus()),
+              duration=None,
+              test_log=None,
+              test_file=None,
+              artifacts=artifact.GetDiffArtifacts(),
+              failure_reason=artifact.GetDiffReason())
+
+  def _SuppressStatus(self, input_filename, status):
+    if not self.IsResultSuppressed(input_filename):
+      return status
+
+    if status == result_types.PASS:
+      # There isn't an actual status for succeeded-but-ignored, so use the
+      # "abort" status to differentiate this from failed-but-ignored.
+      #
+      # Note that this appears as a preliminary failure in Gerrit.
+      return result_types.UNKNOWN
+
+    # There isn't an actual status for failed-but-ignored, so use the "skip"
+    # status to differentiate this from succeeded-but-ignored.
+    return result_types.SKIP
+
+  def _SuppressArtifactStatus(self, test_result, status):
+    if status != result_types.FAIL:
+      return status
+
+    if test_result.status != result_types.SKIP:
+      return status
+
+    return result_types.SKIP
 
   def Run(self):
     # Running a test defines a number of attributes on the fly.
     # pylint: disable=attribute-defined-outside-init
 
-    parser = optparse.OptionParser()
+    relative_test_dir = self.per_process_config.test_dir
+    if relative_test_dir != 'corpus':
+      relative_test_dir = os.path.join('resources', relative_test_dir)
 
-    parser.add_option(
+    parser = argparse.ArgumentParser()
+
+    parser.add_argument(
         '--build-dir',
         default=os.path.join('out', 'Debug'),
         help='relative path from the base source directory')
 
-    parser.add_option(
+    parser.add_argument(
         '-j',
         default=multiprocessing.cpu_count(),
         dest='num_workers',
-        type='int',
+        type=int,
         help='run NUM_WORKERS jobs in parallel')
 
-    parser.add_option(
+    parser.add_argument(
         '--disable-javascript',
-        action="store_true",
-        dest="disable_javascript",
+        action='store_true',
         help='Prevents JavaScript from executing in PDF files.')
 
-    parser.add_option(
+    parser.add_argument(
         '--disable-xfa',
-        action="store_true",
-        dest="disable_xfa",
+        action='store_true',
         help='Prevents processing XFA forms.')
 
-    parser.add_option(
+    parser.add_argument(
+        '--render-oneshot',
+        action='store_true',
+        help='Sets whether to use the oneshot renderer.')
+
+    parser.add_argument(
+        '--run-skia-gold',
+        action='store_true',
+        default=False,
+        help='When flag is on, skia gold tests will be run.')
+
+    # TODO: Remove when pdfium recipe stops passing this argument
+    parser.add_argument(
         '--gold_properties',
         default='',
-        dest="gold_properties",
-        help='Key value pairs that are written to the top level '
-        'of the JSON file that is ingested by Gold.')
+        help='Key value pairs that are written to the top level of the JSON '
+        'file that is ingested by Gold.')
 
-    parser.add_option(
-        '--gold_key',
-        default='',
-        dest="gold_key",
-        help='Key value pairs that are added to the "key" field '
-        'of the JSON file that is ingested by Gold.')
-
-    parser.add_option(
-        '--gold_output_dir',
-        default='',
-        dest="gold_output_dir",
-        help='Path of where to write the JSON output to be '
-        'uploaded to Gold.')
-
-    parser.add_option(
+    # TODO: Remove when pdfium recipe stops passing this argument
+    parser.add_argument(
         '--gold_ignore_hashes',
         default='',
-        dest="gold_ignore_hashes",
         help='Path to a file with MD5 hashes we wish to ignore.')
 
-    parser.add_option(
+    parser.add_argument(
         '--regenerate_expected',
-        default='',
-        dest="regenerate_expected",
-        help='Regenerates expected images. Valid values are '
-        '"all" to regenerate all expected pngs, and '
-        '"platform" to regenerate only platform-specific '
-        'expected pngs.')
+        action='store_true',
+        help='Regenerates expected images. For each failing image diff, this '
+        'will regenerate the most specific expected image file that exists. '
+        'This also will suggest removals of unnecessary expected image files '
+        'by renaming them with an additional ".bak" extension, although these '
+        'removals should be reviewed manually. Use "git clean" to quickly deal '
+        'with any ".bak" files.')
 
-    parser.add_option(
+    parser.add_argument(
         '--reverse-byte-order',
         action='store_true',
-        dest="reverse_byte_order",
         help='Run image-based tests using --reverse-byte-order.')
 
-    parser.add_option(
+    parser.add_argument(
         '--ignore_errors',
-        action="store_true",
-        dest="ignore_errors",
+        action='store_true',
         help='Prevents the return value from being non-zero '
         'when image comparison fails.')
 
-    self.options, self.args = parser.parse_args()
+    parser.add_argument(
+        'inputted_file_paths',
+        nargs='*',
+        help='Path to test files to run, relative to '
+        f'testing/{relative_test_dir}. If omitted, runs all test files under '
+        f'testing/{relative_test_dir}.',
+        metavar='relative/test/path')
 
-    if (self.options.regenerate_expected and
-        self.options.regenerate_expected not in ['all', 'platform']):
-      print 'FAILURE: --regenerate_expected must be "all" or "platform"'
+    skia_gold.add_skia_gold_args(parser)
+
+    self.per_process_config.options = parser.parse_args()
+
+    finder = self.per_process_config.NewFinder()
+    pdfium_test_path = self.per_process_config.GetPdfiumTestPath(finder)
+    if not os.path.exists(pdfium_test_path):
+      print(f"FAILURE: Can't find test executable '{pdfium_test_path}'")
+      print('Use --build-dir to specify its location.')
       return 1
+    self.per_process_config.InitializeFeatures(pdfium_test_path)
 
-    finder = common.DirectoryFinder(self.options.build_dir)
-    self.fixup_path = finder.ScriptPath('fixup_pdf_template.py')
-    self.text_diff_path = finder.ScriptPath('text_diff.py')
-    self.font_dir = os.path.join(finder.TestingDir(), 'resources', 'fonts')
+    self.per_process_state = _PerProcessState(self.per_process_config)
+    shutil.rmtree(self.per_process_state.working_dir, ignore_errors=True)
+    os.makedirs(self.per_process_state.working_dir)
 
-    self.source_dir = finder.TestingDir()
-    if self.test_dir != 'corpus':
-      test_dir = finder.TestingDir(os.path.join('resources', self.test_dir))
-    else:
-      test_dir = finder.TestingDir(self.test_dir)
-
-    self.pdfium_test_path = finder.ExecutablePath('pdfium_test')
-    if not os.path.exists(self.pdfium_test_path):
-      print "FAILURE: Can't find test executable '%s'" % self.pdfium_test_path
-      print 'Use --build-dir to specify its location.'
-      return 1
-
-    self.working_dir = finder.WorkingDir(os.path.join('testing', self.test_dir))
-    shutil.rmtree(self.working_dir, ignore_errors=True)
-    os.makedirs(self.working_dir)
-
-    self.feature_string = subprocess.check_output(
-        [self.pdfium_test_path, '--show-config'])
-    self.test_suppressor = suppressor.Suppressor(
-        finder, self.feature_string, self.options.disable_javascript,
-        self.options.disable_xfa)
-    self.image_differ = pngdiffer.PNGDiffer(finder,
-                                            self.options.reverse_byte_order)
-    error_message = self.image_differ.CheckMissingTools(
+    error_message = self.per_process_state.image_differ.CheckMissingTools(
         self.options.regenerate_expected)
     if error_message:
-      print "FAILURE: %s" % error_message
+      print('FAILURE:', error_message)
       return 1
 
-    self.gold_baseline = gold.GoldBaseline(self.options.gold_properties)
+    self.resultdb = result_sink.TryInitClient()
+    if self.resultdb:
+      print('Detected ResultSink environment')
 
-    walk_from_dir = finder.TestingDir(test_dir)
+    # Collect test cases.
+    walk_from_dir = finder.TestingDir(relative_test_dir)
 
-    self.test_cases = []
+    self.test_cases = TestCaseManager()
     self.execution_suppressed_cases = []
     input_file_re = re.compile('^.+[.](in|pdf)$')
-    if self.args:
-      for file_name in self.args:
-        file_name.replace('.pdf', '.in')
+    if self.options.inputted_file_paths:
+      for file_name in self.options.inputted_file_paths:
         input_path = os.path.join(walk_from_dir, file_name)
         if not os.path.isfile(input_path):
-          print "Can't find test file '%s'" % file_name
+          print(f"Can't find test file '{file_name}'")
           return 1
 
-        self.test_cases.append((os.path.basename(input_path),
-                                os.path.dirname(input_path)))
+        self.test_cases.NewTestCase(input_path)
     else:
       for file_dir, _, filename_list in os.walk(walk_from_dir):
         for input_filename in filename_list:
           if input_file_re.match(input_filename):
             input_path = os.path.join(file_dir, input_filename)
-            if self.test_suppressor.IsExecutionSuppressed(input_path):
+            if self.IsExecutionSuppressed(input_path):
               self.execution_suppressed_cases.append(input_path)
-            else:
-              if os.path.isfile(input_path):
-                self.test_cases.append((input_filename, file_dir))
+              continue
+            if not os.path.isfile(input_path):
+              continue
 
-    self.test_cases.sort()
+            self.test_cases.NewTestCase(input_path)
+
+    # Execute test cases.
     self.failures = []
     self.surprises = []
+    self.skia_gold_successes = []
+    self.skia_gold_unexpected_successes = []
+    self.skia_gold_failures = []
     self.result_suppressed_cases = []
 
-    # Collect Gold results if an output directory was named.
-    self.gold_results = None
-    if self.options.gold_output_dir:
-      self.gold_results = gold.GoldResults(
-          self.test_type, self.options.gold_output_dir,
-          self.options.gold_properties, self.options.gold_key,
-          self.options.gold_ignore_hashes)
+    if self.IsSkiaGoldEnabled():
+      assert self.options.gold_output_dir
+      # Clear out and create top level gold output directory before starting
+      skia_gold.clear_gold_output_dir(self.options.gold_output_dir)
 
-    if self.options.num_workers > 1 and len(self.test_cases) > 1:
-      try:
-        pool = multiprocessing.Pool(self.options.num_workers)
-        worker_func = functools.partial(TestOneFileParallel, self)
+    with multiprocessing.Pool(
+        processes=self.options.num_workers,
+        initializer=_InitializePerProcessState,
+        initargs=[self.per_process_config]) as pool:
+      if self.per_process_config.test_type in TEXT_TESTS:
+        test_function = _RunTextTest
+      else:
+        test_function = _RunPixelTest
+      for result in pool.imap(test_function, self.test_cases):
+        self.HandleResult(self.test_cases.GetTestCase(result.test_id), result)
 
-        worker_results = pool.imap(worker_func, self.test_cases)
-        for worker_result in worker_results:
-          result, input_filename, source_dir = worker_result
-          input_path = os.path.join(source_dir, input_filename)
-
-          self.HandleResult(input_filename, input_path, result)
-
-      except KeyboardInterrupt:
-        pool.terminate()
-      finally:
-        pool.close()
-        pool.join()
-    else:
-      for test_case in self.test_cases:
-        input_filename, input_file_dir = test_case
-        result = self.GenerateAndTest(input_filename, input_file_dir)
-        self.HandleResult(input_filename,
-                          os.path.join(input_file_dir, input_filename), result)
-
-    if self.gold_results:
-      self.gold_results.WriteResults()
-
+    # Report test results.
     if self.surprises:
       self.surprises.sort()
-      print '\n\nUnexpected Successes:'
+      print('\nUnexpected Successes:')
       for surprise in self.surprises:
-        print surprise
+        print(surprise)
 
     if self.failures:
       self.failures.sort()
-      print '\n\nSummary of Failures:'
+      print('\nSummary of Failures:')
       for failure in self.failures:
-        print failure
+        print(failure)
+
+    if self.skia_gold_unexpected_successes:
+      self.skia_gold_unexpected_successes.sort()
+      print('\nUnexpected Skia Gold Successes:')
+      for surprise in self.skia_gold_unexpected_successes:
+        print(surprise)
+
+    if self.skia_gold_failures:
+      self.skia_gold_failures.sort()
+      print('\nSummary of Skia Gold Failures:')
+      for failure in self.skia_gold_failures:
+        print(failure)
 
     self._PrintSummary()
 
@@ -462,23 +358,551 @@
     number_suppressed = len(self.result_suppressed_cases)
     number_successes = number_test_cases - number_failures - number_suppressed
     number_surprises = len(self.surprises)
-    print
-    print 'Test cases executed: %d' % number_test_cases
-    print '  Successes: %d' % number_successes
-    print '  Suppressed: %d' % number_suppressed
-    print '    Surprises: %d' % number_surprises
-    print '  Failures: %d' % number_failures
-    print
-    print 'Test cases not executed: %d' % len(self.execution_suppressed_cases)
+    print('\nTest cases executed:', number_test_cases)
+    print('  Successes:', number_successes)
+    print('  Suppressed:', number_suppressed)
+    print('  Surprises:', number_surprises)
+    print('  Failures:', number_failures)
+    if self.IsSkiaGoldEnabled():
+      number_gold_failures = len(self.skia_gold_failures)
+      number_gold_successes = len(self.skia_gold_successes)
+      number_gold_surprises = len(self.skia_gold_unexpected_successes)
+      number_total_gold_tests = sum(
+          [number_gold_failures, number_gold_successes, number_gold_surprises])
+      print('\nSkia Gold Test cases executed:', number_total_gold_tests)
+      print('  Skia Gold Successes:', number_gold_successes)
+      print('  Skia Gold Surprises:', number_gold_surprises)
+      print('  Skia Gold Failures:', number_gold_failures)
+      skia_tester = self.per_process_state.GetSkiaGoldTester()
+      if self.skia_gold_failures and skia_tester.IsTryjobRun():
+        cl_triage_link = skia_tester.GetCLTriageLink()
+        print('  Triage link for CL:', cl_triage_link)
+        skia_tester.WriteCLTriageLink(cl_triage_link)
+    print()
+    print('Test cases not executed:', len(self.execution_suppressed_cases))
 
   def SetDeleteOutputOnSuccess(self, new_value):
     """Set whether to delete generated output if the test passes."""
-    self.delete_output_on_success = new_value
+    self.per_process_config.delete_output_on_success = new_value
 
   def SetEnforceExpectedImages(self, new_value):
     """Set whether to enforce that each test case provide an expected image."""
-    self.enforce_expected_images = new_value
+    self.per_process_config.enforce_expected_images = new_value
 
-  def SetOneShotRenderer(self, new_value):
-    """Set whether to use the oneshot renderer. """
-    self.oneshot_renderer = new_value
+
+def _RunTextTest(test_case):
+  """Runs a text test case."""
+  test_case_runner = _TestCaseRunner(test_case)
+  with test_case_runner:
+    test_case_runner.test_result = test_case_runner.GenerateAndTest(
+        test_case_runner.TestText)
+  return test_case_runner.test_result
+
+
+def _RunPixelTest(test_case):
+  """Runs a pixel test case."""
+  test_case_runner = _TestCaseRunner(test_case)
+  with test_case_runner:
+    test_case_runner.test_result = test_case_runner.GenerateAndTest(
+        test_case_runner.TestPixel)
+  return test_case_runner.test_result
+
+
+# `_PerProcessState` singleton. This is initialized when creating the
+# `multiprocessing.Pool()`. `TestRunner.Run()` creates its own separate
+# instance of `_PerProcessState` as well.
+_per_process_state = None
+
+
+def _InitializePerProcessState(config):
+  """Initializes the `_per_process_state` singleton."""
+  global _per_process_state
+  assert not _per_process_state
+  _per_process_state = _PerProcessState(config)
+
+
+@dataclass
+class _PerProcessConfig:
+  """Configuration for initializing `_PerProcessState`.
+
+  Attributes:
+    test_dir: The name of the test directory.
+    test_type: The test type.
+    delete_output_on_success: Whether to delete output on success.
+    enforce_expected_images: Whether to enforce expected images.
+    options: The dictionary of command line options.
+    features: The list of features supported by `pdfium_test`.
+  """
+  test_dir: str
+  test_type: str
+  delete_output_on_success: bool = False
+  enforce_expected_images: bool = False
+  options: dict = None
+  features: list = None
+
+  def NewFinder(self):
+    return common.DirectoryFinder(self.options.build_dir)
+
+  def GetPdfiumTestPath(self, finder):
+    return finder.ExecutablePath('pdfium_test')
+
+  def InitializeFeatures(self, pdfium_test_path):
+    output = subprocess.check_output([pdfium_test_path, '--show-config'],
+                                     timeout=TEST_TIMEOUT)
+    self.features = output.decode('utf-8').strip().split(',')
+
+
+class _PerProcessState:
+  """State defined per process."""
+
+  def __init__(self, config):
+    self.test_dir = config.test_dir
+    self.test_type = config.test_type
+    self.delete_output_on_success = config.delete_output_on_success
+    self.enforce_expected_images = config.enforce_expected_images
+    self.options = config.options
+    self.features = config.features
+
+    finder = config.NewFinder()
+    self.pdfium_test_path = config.GetPdfiumTestPath(finder)
+    self.fixup_path = finder.ScriptPath('fixup_pdf_template.py')
+    self.text_diff_path = finder.ScriptPath('text_diff.py')
+    self.font_dir = os.path.join(finder.TestingDir(), 'resources', 'fonts')
+    self.third_party_font_dir = finder.ThirdPartyFontsDir()
+
+    self.source_dir = finder.TestingDir()
+    self.working_dir = finder.WorkingDir(os.path.join('testing', self.test_dir))
+
+    self.test_suppressor = suppressor.Suppressor(
+        finder, self.features, self.options.disable_javascript,
+        self.options.disable_xfa)
+    self.image_differ = pngdiffer.PNGDiffer(finder, self.features,
+                                            self.options.reverse_byte_order)
+
+    self.process_name = multiprocessing.current_process().name
+    self.skia_tester = None
+
+  def __getstate__(self):
+    raise RuntimeError('Cannot pickle per-process state')
+
+  def GetSkiaGoldTester(self):
+    """Gets the `SkiaGoldTester` singleton for this worker."""
+    if not self.skia_tester:
+      self.skia_tester = skia_gold.SkiaGoldTester(
+          source_type=self.test_type,
+          skia_gold_args=self.options,
+          process_name=self.process_name)
+    return self.skia_tester
+
+
+class _TestCaseRunner:
+  """Runner for a single test case."""
+
+  def __init__(self, test_case):
+    self.test_case = test_case
+    self.test_result = None
+    self.duration_start = 0
+
+    self.source_dir, self.input_filename = os.path.split(
+        self.test_case.input_path)
+    self.pdf_path = os.path.join(self.working_dir, f'{self.test_id}.pdf')
+    self.actual_images = None
+
+  def __enter__(self):
+    self.duration_start = time.perf_counter_ns()
+    return self
+
+  def __exit__(self, exc_type, exc_value, traceback):
+    if not self.test_result:
+      self.test_result = self.test_case.NewResult(
+          result_types.UNKNOWN, reason='No test result recorded')
+    duration = time.perf_counter_ns() - self.duration_start
+    self.test_result.duration_milliseconds = duration * 1e-6
+
+  @property
+  def options(self):
+    return _per_process_state.options
+
+  @property
+  def test_id(self):
+    return self.test_case.test_id
+
+  @property
+  def working_dir(self):
+    return _per_process_state.working_dir
+
+  def IsResultSuppressed(self):
+    return _per_process_state.test_suppressor.IsResultSuppressed(
+        self.input_filename)
+
+  def IsImageDiffSuppressed(self):
+    return _per_process_state.test_suppressor.IsImageDiffSuppressed(
+        self.input_filename)
+
+  def GetImageMatchingAlgorithm(self):
+    return _per_process_state.test_suppressor.GetImageMatchingAlgorithm(
+        self.input_filename)
+
+  def RunCommand(self, command, stdout=None):
+    """Runs a test command.
+
+    Args:
+      command: The list of command arguments.
+      stdout: Optional `file`-like object to send standard output.
+
+    Returns:
+      The test result.
+    """
+
+    # Standard output and error are directed to the test log. If `stdout` was
+    # provided, redirect standard output to it instead.
+    if stdout:
+      assert stdout != subprocess.PIPE
+      try:
+        stdout.fileno()
+      except OSError:
+        # `stdout` doesn't have a file descriptor, so it can't be passed to
+        # `subprocess.run()` directly.
+        original_stdout = stdout
+        stdout = subprocess.PIPE
+      stderr = subprocess.PIPE
+    else:
+      stdout = subprocess.PIPE
+      stderr = subprocess.STDOUT
+
+    test_result = self.test_case.NewResult(result_types.PASS)
+    try:
+      run_result = subprocess.run(
+          command,
+          stdout=stdout,
+          stderr=stderr,
+          timeout=TEST_TIMEOUT,
+          check=False)
+      if run_result.returncode != 0:
+        test_result.status = result_types.FAIL
+        test_result.reason = 'Command {} exited with code {}'.format(
+            run_result.args, run_result.returncode)
+    except subprocess.TimeoutExpired as timeout_expired:
+      run_result = timeout_expired
+      test_result.status = result_types.TIMEOUT
+      test_result.reason = 'Command {} timed out'.format(run_result.cmd)
+
+    if stdout == subprocess.PIPE and stderr == subprocess.PIPE:
+      # Copy captured standard output, if any, to the original `stdout`.
+      if run_result.stdout:
+        original_stdout.write(run_result.stdout)
+
+    if not test_result.IsPass():
+      # On failure, report captured output to the test log.
+      if stderr == subprocess.STDOUT:
+        test_result.log = run_result.stdout
+      else:
+        test_result.log = run_result.stderr
+    return test_result
+
+  def GenerateAndTest(self, test_function):
+    """Generate test input and run pdfium_test."""
+    test_result = self.Generate()
+    if not test_result.IsPass():
+      return test_result
+
+    return test_function()
+
+  def _RegenerateIfNeeded(self):
+    if not self.options.regenerate_expected:
+      return
+    if self.IsResultSuppressed() or self.IsImageDiffSuppressed():
+      return
+    _per_process_state.image_differ.Regenerate(
+        self.input_filename,
+        self.source_dir,
+        self.working_dir,
+        image_matching_algorithm=self.GetImageMatchingAlgorithm())
+
+  def Generate(self):
+    input_event_path = os.path.join(self.source_dir, f'{self.test_id}.evt')
+    if os.path.exists(input_event_path):
+      output_event_path = f'{os.path.splitext(self.pdf_path)[0]}.evt'
+      shutil.copyfile(input_event_path, output_event_path)
+
+    template_path = os.path.join(self.source_dir, f'{self.test_id}.in')
+    if not os.path.exists(template_path):
+      if os.path.exists(self.test_case.input_path):
+        shutil.copyfile(self.test_case.input_path, self.pdf_path)
+      return self.test_case.NewResult(result_types.PASS)
+
+    return self.RunCommand([
+        sys.executable, _per_process_state.fixup_path,
+        f'--output-dir={self.working_dir}', template_path
+    ])
+
+  def TestText(self):
+    txt_path = os.path.join(self.working_dir, f'{self.test_id}.txt')
+    with open(txt_path, 'w') as outfile:
+      cmd_to_run = [
+          _per_process_state.pdfium_test_path, '--send-events',
+          f'--time={TEST_SEED_TIME}'
+      ]
+
+      if self.options.disable_javascript:
+        cmd_to_run.append('--disable-javascript')
+
+      if self.options.disable_xfa:
+        cmd_to_run.append('--disable-xfa')
+
+      cmd_to_run.append(self.pdf_path)
+      test_result = self.RunCommand(cmd_to_run, stdout=outfile)
+      if not test_result.IsPass():
+        return test_result
+
+    # If the expected file does not exist, the output is expected to be empty.
+    expected_txt_path = os.path.join(self.source_dir,
+                                     f'{self.test_id}_expected.txt')
+    if not os.path.exists(expected_txt_path):
+      return self._VerifyEmptyText(txt_path)
+
+    # If JavaScript is disabled, the output should be empty.
+    # However, if the test is suppressed and JavaScript is disabled, do not
+    # verify that the text is empty so the suppressed test does not surprise.
+    if self.options.disable_javascript and not self.IsResultSuppressed():
+      return self._VerifyEmptyText(txt_path)
+
+    return self.RunCommand([
+        sys.executable, _per_process_state.text_diff_path, expected_txt_path,
+        txt_path
+    ])
+
+  def _VerifyEmptyText(self, txt_path):
+    with open(txt_path, "rb") as txt_file:
+      txt_data = txt_file.read()
+
+    if txt_data:
+      return self.test_case.NewResult(
+          result_types.FAIL, log=txt_data, reason=f'{txt_path} should be empty')
+
+    return self.test_case.NewResult(result_types.PASS)
+
+  # TODO(crbug.com/pdfium/1656): Remove when ready to fully switch over to
+  # Skia Gold
+  def TestPixel(self):
+    # Remove any existing generated images from previous runs.
+    self.actual_images = _per_process_state.image_differ.GetActualFiles(
+        self.input_filename, self.source_dir, self.working_dir)
+    self._CleanupPixelTest()
+
+    # Generate images.
+    cmd_to_run = [
+        _per_process_state.pdfium_test_path, '--send-events', '--png', '--md5',
+        f'--time={TEST_SEED_TIME}'
+    ]
+
+    if 'use_ahem' in self.source_dir or 'use_symbolneu' in self.source_dir:
+      cmd_to_run.append(f'--font-dir={_per_process_state.font_dir}')
+    else:
+      cmd_to_run.append(f'--font-dir={_per_process_state.third_party_font_dir}')
+      cmd_to_run.append('--croscore-font-names')
+
+    if self.options.disable_javascript:
+      cmd_to_run.append('--disable-javascript')
+
+    if self.options.disable_xfa:
+      cmd_to_run.append('--disable-xfa')
+
+    if self.options.render_oneshot:
+      cmd_to_run.append('--render-oneshot')
+
+    if self.options.reverse_byte_order:
+      cmd_to_run.append('--reverse-byte-order')
+
+    cmd_to_run.append(self.pdf_path)
+
+    with BytesIO() as command_output:
+      test_result = self.RunCommand(cmd_to_run, stdout=command_output)
+      if not test_result.IsPass():
+        return test_result
+
+      test_result.image_artifacts = []
+      for line in command_output.getvalue().splitlines():
+        # Expect this format: MD5:<path to image file>:<hexadecimal MD5 hash>
+        line = bytes.decode(line).strip()
+        if line.startswith('MD5:'):
+          image_path, md5_hash = line[4:].rsplit(':', 1)
+          test_result.image_artifacts.append(
+              self._NewImageArtifact(
+                  image_path=image_path.strip(), md5_hash=md5_hash.strip()))
+
+    if self.actual_images:
+      image_diffs = _per_process_state.image_differ.ComputeDifferences(
+          self.input_filename,
+          self.source_dir,
+          self.working_dir,
+          image_matching_algorithm=self.GetImageMatchingAlgorithm())
+      if image_diffs:
+        test_result.status = result_types.FAIL
+        test_result.reason = 'Images differ'
+
+        # Merge image diffs into test result.
+        diff_map = {}
+        diff_log = []
+        for diff in image_diffs:
+          diff_map[diff.actual_path] = diff
+          diff_log.append(f'{os.path.basename(diff.actual_path)} vs. ')
+          if diff.expected_path:
+            diff_log.append(f'{os.path.basename(diff.expected_path)}\n')
+          else:
+            diff_log.append('missing expected file\n')
+
+        for artifact in test_result.image_artifacts:
+          artifact.image_diff = diff_map.get(artifact.image_path)
+        test_result.log = ''.join(diff_log).encode()
+
+    elif _per_process_state.enforce_expected_images:
+      if not self.IsImageDiffSuppressed():
+        test_result.status = result_types.FAIL
+        test_result.reason = 'Missing expected images'
+
+    if not test_result.IsPass():
+      self._RegenerateIfNeeded()
+      return test_result
+
+    if _per_process_state.delete_output_on_success:
+      self._CleanupPixelTest()
+    return test_result
+
+  def _NewImageArtifact(self, *, image_path, md5_hash):
+    artifact = ImageArtifact(image_path=image_path, md5_hash=md5_hash)
+
+    if self.options.run_skia_gold:
+      if _per_process_state.GetSkiaGoldTester().UploadTestResultToSkiaGold(
+          artifact.GetSkiaGoldId(), artifact.image_path):
+        artifact.skia_gold_status = result_types.PASS
+      else:
+        artifact.skia_gold_status = result_types.FAIL
+
+    return artifact
+
+  def _CleanupPixelTest(self):
+    for image_file in self.actual_images:
+      if os.path.exists(image_file):
+        os.remove(image_file)
+
+
+@dataclass
+class TestCase:
+  """Description of a test case to run.
+
+  Attributes:
+    test_id: A unique identifier for the test.
+    input_path: The absolute path to the test file.
+  """
+  test_id: str
+  input_path: str
+
+  def NewResult(self, status, **kwargs):
+    """Derives a new test result corresponding to this test case."""
+    return TestResult(test_id=self.test_id, status=status, **kwargs)
+
+
+@dataclass
+class TestResult:
+  """Results from running a test case.
+
+  Attributes:
+    test_id: The corresponding test case ID.
+    status: The overall `result_types` status.
+    duration_milliseconds: Test time in milliseconds.
+    log: Optional log of the test's output.
+    image_artfacts: Optional list of image artifacts.
+    reason: Optional reason why the test failed.
+  """
+  test_id: str
+  status: str
+  duration_milliseconds: float = None
+  log: str = None
+  image_artifacts: list = field(default_factory=list)
+  reason: str = None
+
+  def IsPass(self):
+    """Whether the test passed."""
+    return self.status == result_types.PASS
+
+
+@dataclass
+class ImageArtifact:
+  """Image artifact for a test result.
+
+  Attributes:
+    image_path: The absolute path to the image file.
+    md5_hash: The MD5 hash of the pixel buffer.
+    skia_gold_status: Optional Skia Gold status.
+    image_diff: Optional image diff.
+  """
+  image_path: str
+  md5_hash: str
+  skia_gold_status: str = None
+  image_diff: pngdiffer.ImageDiff = None
+
+  def GetSkiaGoldId(self):
+    # The output filename without image extension becomes the test ID. For
+    # example, "/path/to/.../testing/corpus/example_005.pdf.0.png" becomes
+    # "example_005.pdf.0".
+    return _GetTestId(os.path.basename(self.image_path))
+
+  def GetDiffStatus(self):
+    return result_types.FAIL if self.image_diff else result_types.PASS
+
+  def GetDiffReason(self):
+    return self.image_diff.reason if self.image_diff else None
+
+  def GetDiffArtifacts(self):
+    if not self.image_diff:
+      return None
+    if not self.image_diff.expected_path or not self.image_diff.diff_path:
+      return None
+    return {
+        'actual_image':
+            _GetArtifactFromFilePath(self.image_path),
+        'expected_image':
+            _GetArtifactFromFilePath(self.image_diff.expected_path),
+        'image_diff':
+            _GetArtifactFromFilePath(self.image_diff.diff_path)
+    }
+
+
+class TestCaseManager:
+  """Manages a collection of test cases."""
+
+  def __init__(self):
+    self.test_cases = {}
+
+  def __len__(self):
+    return len(self.test_cases)
+
+  def __iter__(self):
+    return iter(self.test_cases.values())
+
+  def NewTestCase(self, input_path, **kwargs):
+    """Creates and registers a new test case."""
+    input_basename = os.path.basename(input_path)
+    test_id = _GetTestId(input_basename)
+    if test_id in self.test_cases:
+      raise ValueError(
+          f'Test ID "{test_id}" derived from "{input_basename}" must be unique')
+
+    test_case = TestCase(test_id=test_id, input_path=input_path, **kwargs)
+    self.test_cases[test_id] = test_case
+    return test_case
+
+  def GetTestCase(self, test_id):
+    """Looks up a test case previously registered by `NewTestCase()`."""
+    return self.test_cases[test_id]
+
+
+def _GetTestId(input_basename):
+  """Constructs a test ID by stripping the last extension from the basename."""
+  return os.path.splitext(input_basename)[0]
+
+
+def _GetArtifactFromFilePath(file_path):
+  """Constructs a ResultSink artifact from a file path."""
+  return {'filePath': file_path}
diff --git a/testing/tools/text_diff.py b/testing/tools/text_diff.py
index fdf45a0..7dcb1c2 100755
--- a/testing/tools/text_diff.py
+++ b/testing/tools/text_diff.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# Copyright 2015 The PDFium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2015 The PDFium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -9,7 +9,7 @@
 
 def main(argv):
   if len(argv) != 3:
-    print '%s: invalid arguments' % argv[0]
+    print('%s: invalid arguments' % argv[0])
     return 2
   filename1 = argv[1]
   filename2 = argv[2]
@@ -21,7 +21,7 @@
     diffs = difflib.unified_diff(
         str1, str2, fromfile=filename1, tofile=filename2)
   except Exception as e:
-    print "something went astray: %s" % e
+    print("something went astray: %s" % e)
     return 1
   status_code = 0
   for diff in diffs:
diff --git a/testing/unit_test_main.cpp b/testing/unit_test_main.cpp
index 5d50249..16e8f0f 100644
--- a/testing/unit_test_main.cpp
+++ b/testing/unit_test_main.cpp
@@ -1,57 +1,38 @@
-// Copyright 2017 PDFium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <memory>
-#include <string>
-
 #include "core/fxcrt/fx_memory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "testing/test_support.h"
+#include "testing/pdf_test_environment.h"
 
 #ifdef PDF_ENABLE_V8
-#include "testing/v8_initializer.h"
-#include "v8/include/v8-platform.h"
-#include "v8/include/v8.h"
-#endif  // PDF_ENABLE_V8
-
+#include "testing/v8_test_environment.h"
 #ifdef PDF_ENABLE_XFA
-#include "testing/xfa_unit_test_support.h"
+#include "testing/xfa_test_environment.h"
 #endif  // PDF_ENABLE_XFA
+#endif  // PDF_ENABLE_V8
 
 // Can't use gtest-provided main since we need to initialize partition
-// alloc before invoking any test.
+// alloc before invoking any test, and add test environments.
 int main(int argc, char** argv) {
-  FXMEM_InitializePartitionAlloc();
+  FX_InitializeMemoryAllocators();
+
+  // PDF test environment will be deleted by gtest.
+  AddGlobalTestEnvironment(new PDFTestEnvironment());
 
 #ifdef PDF_ENABLE_V8
-  std::unique_ptr<v8::Platform> platform;
-#ifdef V8_USE_EXTERNAL_STARTUP_DATA
-  static v8::StartupData* snapshot = new v8::StartupData;
-  platform =
-      InitializeV8ForPDFiumWithStartupData(argv[0], std::string(), snapshot);
-#else  // V8_USE_EXTERNAL_STARTUP_DATA
-  platform = InitializeV8ForPDFium(argv[0]);
-#endif  // V8_USE_EXTERNAL_STARTUP_DATA
-#endif  // PDF_ENABLE_V8
-
-  InitializePDFTestEnvironment();
+  // V8 test environment will be deleted by gtest.
+  AddGlobalTestEnvironment(new V8TestEnvironment(argv[0]));
 #ifdef PDF_ENABLE_XFA
-  InitializeXFATestEnvironment();
+  // XFA test environment will be deleted by gtest.
+  AddGlobalTestEnvironment(new XFATestEnvironment());
 #endif  // PDF_ENABLE_XFA
+#endif  // PDF_ENABLE_V8
 
   testing::InitGoogleTest(&argc, argv);
   testing::InitGoogleMock(&argc, argv);
 
-  int ret_val = RUN_ALL_TESTS();
-
-  DestroyPDFTestEnvironment();
-  // NOTE: XFA test environment, if present, destroyed by gtest.
-
-#ifdef PDF_ENABLE_V8
-  v8::V8::ShutdownPlatform();
-#endif  // PDF_ENABLE_V8
-
-  return ret_val;
+  return RUN_ALL_TESTS();
 }
diff --git a/testing/utils/bitmap_saver.cpp b/testing/utils/bitmap_saver.cpp
index 524a9f6..40a1339 100644
--- a/testing/utils/bitmap_saver.cpp
+++ b/testing/utils/bitmap_saver.cpp
@@ -1,4 +1,4 @@
-// Copyright 2018 PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,7 +9,7 @@
 
 #include "core/fxcrt/fx_safe_types.h"
 #include "testing/image_diff/image_diff_png.h"
-#include "third_party/base/logging.h"
+#include "third_party/base/check.h"
 
 // static
 void BitmapSaver::WriteBitmapToPng(FPDF_BITMAP bitmap,
@@ -27,8 +27,11 @@
       pdfium::base::ValueOrDieForType<size_t>(size));
 
   std::vector<uint8_t> png;
-  if (FPDFBitmap_GetFormat(bitmap) == FPDFBitmap_Gray) {
+  int format = FPDFBitmap_GetFormat(bitmap);
+  if (format == FPDFBitmap_Gray) {
     png = image_diff_png::EncodeGrayPNG(input, width, height, stride);
+  } else if (format == FPDFBitmap_BGR) {
+    png = image_diff_png::EncodeBGRPNG(input, width, height, stride);
   } else {
     png = image_diff_png::EncodeBGRAPNG(input, width, height, stride,
                                         /*discard_transparency=*/false);
diff --git a/testing/utils/bitmap_saver.h b/testing/utils/bitmap_saver.h
index 9f931fc..8a9b2c6 100644
--- a/testing/utils/bitmap_saver.h
+++ b/testing/utils/bitmap_saver.h
@@ -1,4 +1,4 @@
-// Copyright 2018 PDFium Authors. All rights reserved.
+// Copyright 2018 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/utils/file_util.cpp b/testing/utils/file_util.cpp
index 5d26d98..6998f0e 100644
--- a/testing/utils/file_util.cpp
+++ b/testing/utils/file_util.cpp
@@ -1,4 +1,4 @@
-// Copyright 2019 PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -8,6 +8,7 @@
 #include <string.h>
 
 #include "testing/utils/path_service.h"
+#include "third_party/base/numerics/safe_conversions.h"
 
 std::unique_ptr<char, pdfium::FreeDeleter> GetFileContents(const char* filename,
                                                            size_t* retlen) {
@@ -55,7 +56,7 @@
                                        unsigned char* pBuf,
                                        unsigned long size) {
   memcpy(pBuf, file_contents_.get() + pos, size);
-  return size;
+  return pdfium::base::checked_cast<int>(size);
 }
 
 // static
diff --git a/testing/utils/file_util.h b/testing/utils/file_util.h
index 352b882..e91f3d4 100644
--- a/testing/utils/file_util.h
+++ b/testing/utils/file_util.h
@@ -1,4 +1,4 @@
-// Copyright 2019 PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/testing/utils/hash.cpp b/testing/utils/hash.cpp
index 3170de2..5b4a2cf 100644
--- a/testing/utils/hash.cpp
+++ b/testing/utils/hash.cpp
@@ -1,4 +1,4 @@
-// Copyright 2019 PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -18,8 +18,8 @@
   return ret;
 }
 
-std::string GenerateMD5Base16(const uint8_t* data, uint32_t size) {
+std::string GenerateMD5Base16(pdfium::span<const uint8_t> data) {
   uint8_t digest[16];
-  CRYPT_MD5Generate({data, size}, digest);
+  CRYPT_MD5Generate(data, digest);
   return CryptToBase16(digest);
 }
diff --git a/testing/utils/hash.h b/testing/utils/hash.h
index 0e55165..c2164e8 100644
--- a/testing/utils/hash.h
+++ b/testing/utils/hash.h
@@ -1,4 +1,4 @@
-// Copyright 2019 PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -7,7 +7,9 @@
 
 #include <string>
 
+#include "third_party/base/span.h"
+
 std::string CryptToBase16(const uint8_t* digest);
-std::string GenerateMD5Base16(const uint8_t* data, uint32_t size);
+std::string GenerateMD5Base16(pdfium::span<const uint8_t> data);
 
 #endif  // TESTING_UTILS_HASH_H_
diff --git a/testing/utils/path_service.cpp b/testing/utils/path_service.cpp
index 5e1ce39..01a1bfe 100644
--- a/testing/utils/path_service.cpp
+++ b/testing/utils/path_service.cpp
@@ -1,14 +1,19 @@
-// Copyright 2015 PDFium Authors. All rights reserved.
+// Copyright 2015 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "testing/utils/path_service.h"
 
+#include <stddef.h>
+
 #ifdef _WIN32
 #include <Windows.h>
 #elif defined(__APPLE__)
 #include <mach-o/dyld.h>
 #include <sys/stat.h>
+#elif defined(__Fuchsia__)
+#include <sys/stat.h>
+#include <unistd.h>
 #else  // Linux
 #include <linux/limits.h>
 #include <sys/stat.h>
@@ -18,10 +23,12 @@
 #include <string>
 
 #include "core/fxcrt/fx_system.h"
+#include "third_party/base/check.h"
 
 namespace {
 
-#if defined(__APPLE__) || (defined(ANDROID) && __ANDROID_API__ < 21)
+#if defined(__APPLE__) || defined(__Fuchsia__) || \
+    (defined(ANDROID) && __ANDROID_API__ < 21)
 using stat_wrapper_t = struct stat;
 
 int CallStat(const char* path, stat_wrapper_t* sb) {
@@ -68,7 +75,7 @@
     return false;
   *path = std::string(path_buffer);
 #elif defined(__APPLE__)
-  ASSERT(path);
+  DCHECK(path);
   unsigned int path_length = 0;
   _NSGetExecutablePath(NULL, &path_length);
   if (path_length == 0)
@@ -89,10 +96,10 @@
 #endif  // _WIN32
 
   // Get the directory path.
-  std::size_t pos = path->size() - 1;
+  size_t pos = path->size() - 1;
   if (EndsWithSeparator(*path))
     pos--;
-  std::size_t found = path->find_last_of(PATH_SEPARATOR, pos);
+  size_t found = path->find_last_of(PATH_SEPARATOR, pos);
   if (found == std::string::npos)
     return false;
   path->resize(found);
@@ -160,3 +167,39 @@
   path->append(file_name);
   return true;
 }
+
+// static
+bool PathService::GetThirdPartyFilePath(const std::string& file_name,
+                                        std::string* path) {
+  if (!GetSourceDir(path))
+    return false;
+
+  if (!EndsWithSeparator(*path))
+    path->push_back(PATH_SEPARATOR);
+
+  std::string potential_path = *path;
+  potential_path.append("third_party");
+
+  // Use third_party/bigint as a way to distinguish PDFium's vs. other's.
+  std::string bigint = potential_path + PATH_SEPARATOR + "bigint";
+  if (PathService::DirectoryExists(bigint)) {
+    *path = potential_path;
+    path->append(PATH_SEPARATOR + file_name);
+    return true;
+  }
+
+  potential_path = *path;
+  potential_path.append("third_party");
+  potential_path.push_back(PATH_SEPARATOR);
+  potential_path.append("pdfium");
+  potential_path.push_back(PATH_SEPARATOR);
+  potential_path.append("third_party");
+  bigint = potential_path + PATH_SEPARATOR + "bigint";
+  if (PathService::DirectoryExists(potential_path)) {
+    *path = potential_path;
+    path->append(PATH_SEPARATOR + file_name);
+    return true;
+  }
+
+  return false;
+}
diff --git a/testing/utils/path_service.h b/testing/utils/path_service.h
index 63df808..a6582c3 100644
--- a/testing/utils/path_service.h
+++ b/testing/utils/path_service.h
@@ -1,4 +1,4 @@
-// Copyright 2015 PDFium Authors. All rights reserved.
+// Copyright 2015 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -37,5 +37,9 @@
 
   // Get the full path for a test file under the test data directory.
   static bool GetTestFilePath(const std::string& file_name, std::string* path);
+
+  // Get the full path for a file under the third-party directory.
+  static bool GetThirdPartyFilePath(const std::string& file_name,
+                                    std::string* path);
 };
 #endif  // TESTING_UTILS_PATH_SERVICE_H_
diff --git a/testing/v8_initializer.cpp b/testing/v8_initializer.cpp
index 8564b2e..f4ed611 100644
--- a/testing/v8_initializer.cpp
+++ b/testing/v8_initializer.cpp
@@ -1,4 +1,4 @@
-// Copyright 2019 PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,8 +9,14 @@
 #include "public/fpdfview.h"
 #include "testing/utils/file_util.h"
 #include "testing/utils/path_service.h"
+#include "third_party/base/numerics/safe_conversions.h"
 #include "v8/include/libplatform/libplatform.h"
-#include "v8/include/v8.h"
+#include "v8/include/v8-initialization.h"
+#include "v8/include/v8-snapshot.h"
+
+#ifdef PDF_ENABLE_XFA
+#include "v8/include/cppgc/platform.h"
+#endif
 
 namespace {
 
@@ -49,20 +55,27 @@
     return false;
 
   result_data->data = data_buffer.release();
-  result_data->raw_size = data_length;
+  result_data->raw_size = pdfium::base::checked_cast<int>(data_length);
   return true;
 }
 #endif  // V8_USE_EXTERNAL_STARTUP_DATA
 
-std::unique_ptr<v8::Platform> InitializeV8Common(const std::string& exe_path) {
+std::unique_ptr<v8::Platform> InitializeV8Common(const std::string& exe_path,
+                                                 const std::string& js_flags) {
   v8::V8::InitializeICUDefaultLocation(exe_path.c_str());
 
   std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
   v8::V8::InitializePlatform(platform.get());
+#ifdef PDF_ENABLE_XFA
+  cppgc::InitializeProcess(platform->GetPageAllocator());
+#endif
 
   const char* recommended_v8_flags = FPDF_GetRecommendedV8Flags();
   v8::V8::SetFlagsFromString(recommended_v8_flags);
 
+  if (!js_flags.empty())
+    v8::V8::SetFlagsFromString(js_flags.c_str());
+
   // By enabling predictable mode, V8 won't post any background tasks.
   // By enabling GC, it makes it easier to chase use-after-free.
   static const char kAdditionalV8Flags[] = "--predictable --expose-gc";
@@ -77,9 +90,11 @@
 #ifdef V8_USE_EXTERNAL_STARTUP_DATA
 std::unique_ptr<v8::Platform> InitializeV8ForPDFiumWithStartupData(
     const std::string& exe_path,
+    const std::string& js_flags,
     const std::string& bin_dir,
     v8::StartupData* snapshot_blob) {
-  std::unique_ptr<v8::Platform> platform = InitializeV8Common(exe_path);
+  std::unique_ptr<v8::Platform> platform =
+      InitializeV8Common(exe_path, js_flags);
   if (snapshot_blob) {
     if (!GetExternalData(exe_path, bin_dir, "snapshot_blob.bin", snapshot_blob))
       return nullptr;
@@ -89,7 +104,16 @@
 }
 #else   // V8_USE_EXTERNAL_STARTUP_DATA
 std::unique_ptr<v8::Platform> InitializeV8ForPDFium(
-    const std::string& exe_path) {
-  return InitializeV8Common(exe_path);
+    const std::string& exe_path,
+    const std::string& js_flags) {
+  return InitializeV8Common(exe_path, js_flags);
 }
 #endif  // V8_USE_EXTERNAL_STARTUP_DATA
+
+void ShutdownV8ForPDFium() {
+#ifdef PDF_ENABLE_XFA
+  cppgc::ShutdownProcess();
+#endif
+  v8::V8::Dispose();
+  v8::V8::DisposePlatform();
+}
diff --git a/testing/v8_initializer.h b/testing/v8_initializer.h
index bd2301c..ecd5911 100644
--- a/testing/v8_initializer.h
+++ b/testing/v8_initializer.h
@@ -1,4 +1,4 @@
-// Copyright 2019 PDFium Authors. All rights reserved.
+// Copyright 2019 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -23,11 +23,13 @@
 // |snapshot_blob| is an optional out parameter.
 std::unique_ptr<v8::Platform> InitializeV8ForPDFiumWithStartupData(
     const std::string& exe_path,
+    const std::string& js_flags,
     const std::string& bin_dir,
     v8::StartupData* snapshot_blob);
 #else
 std::unique_ptr<v8::Platform> InitializeV8ForPDFium(
+    const std::string& js_flags,
     const std::string& exe_path);
 #endif
-
+void ShutdownV8ForPDFium();
 #endif  // TESTING_V8_INITIALIZER_H_
diff --git a/testing/v8_test_environment.cpp b/testing/v8_test_environment.cpp
new file mode 100644
index 0000000..56eead7
--- /dev/null
+++ b/testing/v8_test_environment.cpp
@@ -0,0 +1,76 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/v8_test_environment.h"
+
+#include <memory>
+#include <string>
+
+#include "core/fxcrt/fx_system.h"
+#include "testing/v8_initializer.h"
+#include "third_party/base/check.h"
+#include "v8/include/libplatform/libplatform.h"
+#include "v8/include/v8-isolate.h"
+#include "v8/include/v8-platform.h"
+#include "v8/include/v8-snapshot.h"
+
+namespace {
+
+V8TestEnvironment* g_environment = nullptr;
+
+}  // namespace
+
+V8TestEnvironment::V8TestEnvironment(const char* exe_name)
+    : exe_path_(exe_name),
+      array_buffer_allocator_(std::make_unique<CFX_V8ArrayBufferAllocator>()) {
+  DCHECK(!g_environment);
+  g_environment = this;
+}
+
+V8TestEnvironment::~V8TestEnvironment() {
+  DCHECK(g_environment);
+
+#ifdef V8_USE_EXTERNAL_STARTUP_DATA
+  if (startup_data_)
+    free(const_cast<char*>(startup_data_->data));
+#endif  // V8_USE_EXTERNAL_STARTUP_DATA
+
+  g_environment = nullptr;
+}
+
+// static
+V8TestEnvironment* V8TestEnvironment::GetInstance() {
+  return g_environment;
+}
+
+// static
+void V8TestEnvironment::PumpPlatformMessageLoop(v8::Isolate* isolate) {
+  v8::Platform* platform = GetInstance()->platform();
+  while (v8::platform::PumpMessageLoop(platform, isolate))
+    continue;
+}
+
+void V8TestEnvironment::SetUp() {
+#ifdef V8_USE_EXTERNAL_STARTUP_DATA
+  if (startup_data_) {
+    platform_ = InitializeV8ForPDFiumWithStartupData(exe_path_, std::string(),
+                                                     std::string(), nullptr);
+  } else {
+    startup_data_ = std::make_unique<v8::StartupData>();
+    platform_ = InitializeV8ForPDFiumWithStartupData(
+        exe_path_, std::string(), std::string(), startup_data_.get());
+  }
+#else
+  platform_ = InitializeV8ForPDFium(std::string(), exe_path_);
+#endif  // V8_USE_EXTERNAL_STARTUP_DATA
+
+  v8::Isolate::CreateParams params;
+  params.array_buffer_allocator = array_buffer_allocator_.get();
+  isolate_.reset(v8::Isolate::New(params));
+}
+
+void V8TestEnvironment::TearDown() {
+  isolate_.reset();
+  ShutdownV8ForPDFium();
+}
diff --git a/testing/v8_test_environment.h b/testing/v8_test_environment.h
new file mode 100644
index 0000000..7a030e4
--- /dev/null
+++ b/testing/v8_test_environment.h
@@ -0,0 +1,51 @@
+// Copyright 2020 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_V8_TEST_ENVIRONMENT_H_
+#define TESTING_V8_TEST_ENVIRONMENT_H_
+
+#ifndef PDF_ENABLE_V8
+#error "V8 must be enabled"
+#endif  // PDF_ENABLE_V8
+
+#include <memory>
+
+#include "fxjs/cfx_v8.h"
+#include "fxjs/cfx_v8_array_buffer_allocator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace v8 {
+class Isolate;
+class Platform;
+class StartupData;
+}  // namespace v8
+
+class TestLoader;
+
+class V8TestEnvironment : public testing::Environment {
+ public:
+  explicit V8TestEnvironment(const char* exe_path);
+  ~V8TestEnvironment() override;
+
+  // Note: GetInstance() does not create one if it does not exist,
+  // so the main program must explicitly add this enviroment.
+  static V8TestEnvironment* GetInstance();
+  static void PumpPlatformMessageLoop(v8::Isolate* pIsolate);
+
+  // testing::Environment:
+  void SetUp() override;
+  void TearDown() override;
+
+  v8::Platform* platform() const { return platform_.get(); }
+  v8::Isolate* isolate() const { return isolate_.get(); }
+
+ private:
+  const char* const exe_path_;
+  std::unique_ptr<v8::StartupData> startup_data_;
+  std::unique_ptr<v8::Platform> platform_;
+  std::unique_ptr<CFX_V8ArrayBufferAllocator> array_buffer_allocator_;
+  std::unique_ptr<v8::Isolate, CFX_V8IsolateDeleter> isolate_;
+};
+
+#endif  // TESTING_V8_TEST_ENVIRONMENT_H_
diff --git a/testing/xfa_js_embedder_test.cpp b/testing/xfa_js_embedder_test.cpp
index 2f9cc67..2647157 100644
--- a/testing/xfa_js_embedder_test.cpp
+++ b/testing/xfa_js_embedder_test.cpp
@@ -1,47 +1,37 @@
-// Copyright 2017 PDFium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "testing/xfa_js_embedder_test.h"
 
+#include <memory>
 #include <string>
 
 #include "fpdfsdk/cpdfsdk_helpers.h"
 #include "fpdfsdk/fpdfxfa/cpdfxfa_context.h"
+#include "fxjs/fxv8.h"
 #include "fxjs/xfa/cfxjse_engine.h"
+#include "fxjs/xfa/cfxjse_isolatetracker.h"
 #include "fxjs/xfa/cfxjse_value.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/base/ptr_util.h"
-#include "xfa/fxfa/cxfa_ffapp.h"
+#include "third_party/base/check_op.h"
+#include "v8/include/v8-container.h"
+#include "v8/include/v8-local-handle.h"
+#include "v8/include/v8-value.h"
 
-XFAJSEmbedderTest::XFAJSEmbedderTest()
-    : array_buffer_allocator_(
-          pdfium::MakeUnique<CFX_V8ArrayBufferAllocator>()) {}
+XFAJSEmbedderTest::XFAJSEmbedderTest() = default;
 
-XFAJSEmbedderTest::~XFAJSEmbedderTest() {}
+XFAJSEmbedderTest::~XFAJSEmbedderTest() = default;
 
 void XFAJSEmbedderTest::SetUp() {
-  v8::Isolate::CreateParams params;
-  params.array_buffer_allocator = array_buffer_allocator_.get();
-  isolate_ = v8::Isolate::New(params);
-  ASSERT_TRUE(isolate_);
-
-  EmbedderTest::SetExternalIsolate(isolate_);
-  EmbedderTest::SetUp();
-
-  CXFA_FFApp::SkipFontLoadForTesting(true);
+  JSEmbedderTest::SetUp();
 }
 
 void XFAJSEmbedderTest::TearDown() {
-  CXFA_FFApp::SkipFontLoadForTesting(false);
-
-  value_.reset();
+  value_.Reset();
   script_context_ = nullptr;
 
-  EmbedderTest::TearDown();
-
-  isolate_->Dispose();
-  isolate_ = nullptr;
+  JSEmbedderTest::TearDown();
 }
 
 CXFA_Document* XFAJSEmbedderTest::GetXFADocument() const {
@@ -53,7 +43,15 @@
   if (!pContext)
     return nullptr;
 
-  return pContext->GetXFADoc()->GetXFADoc();
+  CXFA_FFDoc* pFFDoc = pContext->GetXFADoc();
+  if (!pFFDoc)
+    return nullptr;
+
+  return pFFDoc->GetXFADoc();
+}
+
+v8::Local<v8::Value> XFAJSEmbedderTest::GetValue() const {
+  return v8::Local<v8::Value>::New(isolate(), value_);
 }
 
 bool XFAJSEmbedderTest::OpenDocumentWithOptions(
@@ -62,37 +60,54 @@
     LinearizeOption linearize_option,
     JavaScriptOption javascript_option) {
   // JS required for XFA.
-  ASSERT(javascript_option == JavaScriptOption::kEnableJavaScript);
+  DCHECK_EQ(javascript_option, JavaScriptOption::kEnableJavaScript);
   if (!EmbedderTest::OpenDocumentWithOptions(
           filename, password, linearize_option, javascript_option)) {
     return false;
   }
-  script_context_ = GetXFADocument()->GetScriptContext();
+  CXFA_Document* pDoc = GetXFADocument();
+  if (!pDoc)
+    return false;
+
+  script_context_ = pDoc->GetScriptContext();
   return true;
 }
 
 bool XFAJSEmbedderTest::Execute(ByteStringView input) {
+  CFXJSE_ScopeUtil_IsolateHandleContext scope(
+      script_context_->GetJseContextForTest());
   if (ExecuteHelper(input))
     return true;
 
-  CFXJSE_Value msg(GetIsolate());
-  value_->GetObjectPropertyByIdx(1, &msg);
   fprintf(stderr, "FormCalc: %.*s\n", static_cast<int>(input.GetLength()),
           input.unterminated_c_str());
-  // If the parsing of the input fails, then v8 will not run, so there will be
-  // no value here to print.
-  if (msg.IsString() && !msg.ToWideString().IsEmpty())
-    fprintf(stderr, "JS ERROR: %ls\n", msg.ToWideString().c_str());
+
+  v8::Local<v8::Value> result = GetValue();
+  if (!fxv8::IsArray(result))
+    return false;
+
+  v8::Local<v8::Value> msg = fxv8::ReentrantGetArrayElementHelper(
+      isolate(), result.As<v8::Array>(), 1);
+  if (!fxv8::IsString(msg))
+    return false;
+
+  WideString str = fxv8::ReentrantToWideStringHelper(isolate(), msg);
+  if (!str.IsEmpty())
+    fprintf(stderr, "JS ERROR: %ls\n", str.c_str());
   return false;
 }
 
 bool XFAJSEmbedderTest::ExecuteSilenceFailure(ByteStringView input) {
+  CFXJSE_ScopeUtil_IsolateHandleContext scope(
+      script_context_->GetJseContextForTest());
   return ExecuteHelper(input);
 }
 
 bool XFAJSEmbedderTest::ExecuteHelper(ByteStringView input) {
-  value_ = pdfium::MakeUnique<CFXJSE_Value>(GetIsolate());
-  return script_context_->RunScript(CXFA_Script::Type::Formcalc,
-                                    WideString::FromUTF8(input).AsStringView(),
-                                    value_.get(), GetXFADocument()->GetRoot());
+  auto value = std::make_unique<CFXJSE_Value>();
+  bool ret = script_context_->RunScript(
+      CXFA_Script::Type::Formcalc, WideString::FromUTF8(input).AsStringView(),
+      value.get(), GetXFADocument()->GetRoot());
+  value_.Reset(isolate(), value->GetValue(isolate()));
+  return ret;
 }
diff --git a/testing/xfa_js_embedder_test.h b/testing/xfa_js_embedder_test.h
index 412ffd9..9c14867 100644
--- a/testing/xfa_js_embedder_test.h
+++ b/testing/xfa_js_embedder_test.h
@@ -1,22 +1,22 @@
-// Copyright 2017 PDFium Authors. All rights reserved.
+// Copyright 2017 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #ifndef TESTING_XFA_JS_EMBEDDER_TEST_H_
 #define TESTING_XFA_JS_EMBEDDER_TEST_H_
 
-#include <memory>
 #include <string>
 
-#include "testing/embedder_test.h"
-#include "xfa/fxfa/parser/cxfa_document.h"
-#include "xfa/fxfa/parser/cxfa_node.h"
+#include "core/fxcrt/string_view_template.h"
+#include "testing/js_embedder_test.h"
+#include "v8/include/v8-local-handle.h"
+#include "v8/include/v8-persistent-handle.h"
+#include "v8/include/v8-value.h"
 
 class CFXJSE_Engine;
-class CFXJSE_Value;
-class CFX_V8ArrayBufferAllocator;
+class CXFA_Document;
 
-class XFAJSEmbedderTest : public EmbedderTest {
+class XFAJSEmbedderTest : public JSEmbedderTest {
  public:
   XFAJSEmbedderTest();
   ~XFAJSEmbedderTest() override;
@@ -29,22 +29,18 @@
                                LinearizeOption linearize_option,
                                JavaScriptOption javascript_option) override;
 
-  v8::Isolate* GetIsolate() const { return isolate_; }
   CXFA_Document* GetXFADocument() const;
+  CFXJSE_Engine* GetScriptContext() const { return script_context_; }
+  v8::Local<v8::Value> GetValue() const;
 
   bool Execute(ByteStringView input);
   bool ExecuteSilenceFailure(ByteStringView input);
 
-  CFXJSE_Engine* GetScriptContext() const { return script_context_; }
-  CFXJSE_Value* GetValue() const { return value_.get(); }
-
  private:
-  std::unique_ptr<CFX_V8ArrayBufferAllocator> array_buffer_allocator_;
-  std::unique_ptr<CFXJSE_Value> value_;
-  v8::Isolate* isolate_ = nullptr;
-  CFXJSE_Engine* script_context_ = nullptr;
-
   bool ExecuteHelper(ByteStringView input);
+
+  v8::Global<v8::Value> value_;
+  CFXJSE_Engine* script_context_ = nullptr;
 };
 
 #endif  // TESTING_XFA_JS_EMBEDDER_TEST_H_
diff --git a/testing/xfa_test_environment.cpp b/testing/xfa_test_environment.cpp
new file mode 100644
index 0000000..07f09b6
--- /dev/null
+++ b/testing/xfa_test_environment.cpp
@@ -0,0 +1,40 @@
+// Copyright 2019 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/xfa_test_environment.h"
+
+#include "core/fxge/cfx_fontmgr.h"
+#include "core/fxge/cfx_gemodule.h"
+#include "core/fxge/systemfontinfo_iface.h"
+#include "third_party/base/check.h"
+#include "xfa/fgas/font/cfgas_gemodule.h"
+
+namespace {
+
+XFATestEnvironment* g_env = nullptr;
+
+}  // namespace
+
+XFATestEnvironment::XFATestEnvironment() {
+  DCHECK(!g_env);
+  g_env = this;
+}
+
+XFATestEnvironment::~XFATestEnvironment() {
+  DCHECK(g_env);
+  g_env = nullptr;
+}
+
+void XFATestEnvironment::SetUp() {
+  CFX_GEModule::Get()->GetFontMgr()->GetBuiltinMapper()->SetSystemFontInfo(
+      CFX_GEModule::Get()->GetPlatform()->CreateDefaultSystemFontInfo());
+
+  // The font loading that takes place in CFGAS_GEModule::Create() is slow,
+  // but we do it only once per binary execution, not once per test.
+  CFGAS_GEModule::Create();
+}
+
+void XFATestEnvironment::TearDown() {
+  CFGAS_GEModule::Destroy();
+}
diff --git a/testing/xfa_test_environment.h b/testing/xfa_test_environment.h
new file mode 100644
index 0000000..f8356bd
--- /dev/null
+++ b/testing/xfa_test_environment.h
@@ -0,0 +1,24 @@
+// Copyright 2019 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_XFA_TEST_ENVIRONMENT_H_
+#define TESTING_XFA_TEST_ENVIRONMENT_H_
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+#ifndef PDF_ENABLE_XFA
+#error "XFA must be enabled"
+#endif
+
+class XFATestEnvironment : public testing::Environment {
+ public:
+  XFATestEnvironment();
+  ~XFATestEnvironment();
+
+  // testing::TestEnvironment:
+  void SetUp() override;
+  void TearDown() override;
+};
+
+#endif  // TESTING_XFA_TEST_ENVIRONMENT_H_
diff --git a/testing/xfa_unit_test_support.cpp b/testing/xfa_unit_test_support.cpp
deleted file mode 100644
index 8cc2d6c..0000000
--- a/testing/xfa_unit_test_support.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-// 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 "testing/xfa_unit_test_support.h"
-
-#include <memory>
-#include <utility>
-
-#include "core/fxge/cfx_fontmgr.h"
-#include "core/fxge/cfx_gemodule.h"
-#include "core/fxge/systemfontinfo_iface.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/base/ptr_util.h"
-#include "xfa/fgas/font/cfgas_fontmgr.h"
-
-namespace {
-
-// The loading time of the CFGAS_FontMgr is linear in the number of times it is
-// loaded. So, if a test suite has a lot of tests that need a font manager they
-// can end up executing very, very slowly.
-class XFATestEnvironment final : public testing::Environment {
- public:
-  // testing::Environment:
-  void SetUp() override {
-    auto font_mgr = pdfium::MakeUnique<CFGAS_FontMgr>();
-    if (font_mgr->EnumFonts())
-      font_mgr_ = std::move(font_mgr);
-  }
-  void TearDown() override { font_mgr_.reset(); }
-
-  CFGAS_FontMgr* FontManager() const { return font_mgr_.get(); }
-
- private:
-  std::unique_ptr<CFGAS_FontMgr> font_mgr_;
-};
-
-XFATestEnvironment* g_env = nullptr;
-
-}  // namespace
-
-void InitializeXFATestEnvironment() {
-  // |g_env| will be deleted by gtest.
-  g_env = new XFATestEnvironment();
-  AddGlobalTestEnvironment(g_env);
-
-  // TODO(dsinclair): This font loading is slow. We should make a test font
-  // loader which loads up a single font we use in all tests.
-  CFX_GEModule::Get()->GetFontMgr()->SetSystemFontInfo(
-      SystemFontInfoIface::CreateDefault(nullptr));
-}
-
-CFGAS_FontMgr* GetGlobalFontManager() {
-  return g_env->FontManager();
-}
diff --git a/testing/xfa_unit_test_support.h b/testing/xfa_unit_test_support.h
deleted file mode 100644
index 0a54778..0000000
--- a/testing/xfa_unit_test_support.h
+++ /dev/null
@@ -1,18 +0,0 @@
-// 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 TESTING_XFA_UNIT_TEST_SUPPORT_H_
-#define TESTING_XFA_UNIT_TEST_SUPPORT_H_
-
-#ifndef PDF_ENABLE_XFA
-#error "XFA must be enabled"
-#endif
-
-class CFGAS_FontMgr;
-
-void InitializeXFATestEnvironment();
-
-CFGAS_FontMgr* GetGlobalFontManager();
-
-#endif  // TESTING_XFA_UNIT_TEST_SUPPORT_H_