Add a pixel test for indexing into an object stream.

Entries in a cross-reference stream can reference compressed objects in
an object stream, with an index of the object within the object stream.
Generate an XRef dictionary by hand to use this PDF feature and
demonstrate PDFium not correctly handling the index. The test results
are suppressed because PDFium does not currently render as expected.

To avoid also having to write the startxref entry by hand, teach
fixup_pdf_template.py about {{startxrefobj x y} to reference the XRef
dictionary object.

The objects in the object stream do not actually need to be compressed.

Bug: pdfium:1733
Change-Id: I986c5fa3768a6aaf4b859bf4d87f4f23379e4bd6
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/86072
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/testing/SUPPRESSIONS b/testing/SUPPRESSIONS
index 1ed29ab..3d687da 100644
--- a/testing/SUPPRESSIONS
+++ b/testing/SUPPRESSIONS
@@ -344,6 +344,9 @@
 # TODO(pdfium:1571): Remove after associated bug is fixed
 bug_1571.in * * * *
 
+# TODO(pdfium:1733): Remove after associated bug is fixed
+bug_1733.in * * * *
+
 # TODO(chromium:237527): Remove after associated bug is fixed
 bug_237527_1.in * * * *
 
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/tools/fixup_pdf_template.py b/testing/tools/fixup_pdf_template.py
index 84c19d4..50265ed 100755
--- a/testing/tools/fixup_pdf_template.py
+++ b/testing/tools/fixup_pdf_template.py
@@ -11,10 +11,12 @@
   {{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.
+  {{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`.
 """
 
 from __future__ import print_function
@@ -65,6 +67,8 @@
   STARTXREF_TOKEN = b'{{startxref}}'
   STARTXREF_REPLACEMENT = b'startxref\n%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'
 
@@ -132,6 +136,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