Setup testing XFA using .in files

This adds a number of {{}} style tags to the .in format that allow the
fix-up script to inject XFA related boilerplate into the PDF, instead of
having the .in file replicate this.

A simple example XFA .in file, called xfa_example.in has been added as a
template for future files to follow.

Finally a run_xfa_tests.py has been added for executing all of the tests
in testing/resources/xfa, which from what I can tell are not executed any
other way. All of the existing PDFs in that directory are currently
suppressed.

BUG=pdfium:1008

Change-Id: Ie055b6640969ce8291b4c96b401ebf6887dfa0c0
Reviewed-on: https://pdfium-review.googlesource.com/27631
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/testing/SUPPRESSIONS b/testing/SUPPRESSIONS
index 1edc158..317f370 100644
--- a/testing/SUPPRESSIONS
+++ b/testing/SUPPRESSIONS
@@ -321,3 +321,18 @@
 #
 bug_492.in * nov8 *
 
+#
+# XFA Tests
+#
+# TODO(rharrison): Create expectations for existing files.
+barcode_test.pdf * * *
+dynamic_list_box_allow_multiple_selection.pdf * * *
+dynamic_password_field_background_fill.pdf * * *
+dynamic_table_color_and_width.pdf * * *
+email_recommended.pdf * * *
+resolve_nodes.pdf * * *
+standard_symbols.pdf * * *
+static_list_box_caption.pdf * * *
+static_password_field_rotate.pdf * * *
+xfa_node_caption.pdf * * *
+
diff --git a/testing/resources/xfa/xfa_example.in b/testing/resources/xfa/xfa_example.in
new file mode 100644
index 0000000..d8be96f
--- /dev/null
+++ b/testing/resources/xfa/xfa_example.in
@@ -0,0 +1,71 @@
+{{header}}
+
+%% Original object ID: 24 0
+{{object 1 0}}
+<<
+  /AcroForm 2 0 R
+  /Extensions <<
+    /ADBE <<
+      /BaseVersion /1.7
+      /ExtensionLevel 8
+    >>
+  >>
+  /NeedsRendering true
+  /Type /Catalog
+>>
+endobj
+
+%% Original object ID: 32 0
+{{object 2 0}}
+<<
+  /XFA [
+    (preamble)
+    3 0 R
+    (config)
+    4 0 R
+    (template)
+    5 0 R
+    (localeSet)
+    6 0 R
+    (postamble)
+    7 0 R
+  ]
+>>
+endobj
+
+{{xfapreamble 3 0}}
+{{xfaconfig 4 0}}
+
+{{object 5 0}}
+<<
+  {{streamlen}}
+>>
+stream
+<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
+  <subform name="form1" layout="tb" locale="zh_CN" restoreState="auto">
+    <pageSet>
+      <pageArea name="Page1" id="Page1">
+        <contentArea x="0.25in" y="0.25in" w="7.75in" h="11.25in"/>
+        <medium stock="default" short="612pt" long="792pt"/>
+      </pageArea>
+    </pageSet>
+    <subform w="576pt" h="756pt" name="Page1">
+      <draw name="HelloWorld" y="0.5in" x="0.5in" w="5in" h="2in">
+        <value>
+          <text>Hello, world.</text>
+        </value>
+      </draw>
+    </subform>
+  </subform>
+</template>
+endstream
+endobj
+
+{{xfalocale 6 0}}
+
+{{xfapostamble 7 0}}
+
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
\ No newline at end of file
diff --git a/testing/resources/xfa/xfa_example_expected.pdf.0.png b/testing/resources/xfa/xfa_example_expected.pdf.0.png
new file mode 100644
index 0000000..9a682f7
--- /dev/null
+++ b/testing/resources/xfa/xfa_example_expected.pdf.0.png
Binary files differ
diff --git a/testing/tools/fixup_pdf_template.py b/testing/tools/fixup_pdf_template.py
index 19f75e0..fed0ed8 100755
--- a/testing/tools/fixup_pdf_template.py
+++ b/testing/tools/fixup_pdf_template.py
@@ -8,12 +8,20 @@
 There are several places in a PDF file where byte-offsets are required. This
 script replaces {{name}}-style variables in the input with calculated results
 
-  {{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.
-  {{startxref}   - expands to a startxref directive followed by correct offset.
+  {{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.
+  {{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|.
+  {{streamlen}} - expands to |/Length n|.
+  {{xfapreamble x y}} - expands to an object |x y obj| containing a XML preamble
+                        to be used in XFA docs.
+  {{xfaconfig x y}} - expands to an object |x y obj| containing a config XML
+                      block to be used in XFA docs.
+  {{xfalocale x y}} - expands to an object |x y obj| containing a locale XML
+                      block to be used in XFA docs.
+  {{xfapostamble x y}} - expands to an object |x y obj| containing a XML
+                         posteamble to be used in XFA docs.
 """
 
 import cStringIO
@@ -51,6 +59,154 @@
   STREAMLEN_TOKEN = '{{streamlen}}'
   STREAMLEN_REPLACEMENT = '/Length %d'
 
+  XFAPREAMBLE_PATTERN = r'\{\{xfapreamble\s+(\d+)\s+(\d+)\}\}'
+  XFAPREAMBLE_REPLACEMENT = '%d %d obj\n<<\n  /Length %d\n>>\nstream\n%s\nendstream\nendobj\n'
+  XFAPREAMBLE_STREAM = '<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/" timeStamp="2018-02-23T21:37:11Z" uuid="21482798-7bf0-40a4-bc5d-3cefdccf32b5">'
+
+  XFAPOSTAMBLE_PATTERN = r'\{\{xfapostamble\s+(\d+)\s+(\d+)\}\}'
+  XFAPOSTAMBLE_REPLACEMENT = '%d %d obj\n<<\n  /Length %d\n>>\nstream\n%s\nendstream\nendobj\n'
+  XFAPOSTAMBLE_STREAM = '</xdp:xdp>'
+
+  XFACONFIG_PATTERN = r'\{\{xfaconfig\s+(\d+)\s+(\d+)\}\}'
+  XFACONFIG_REPLACEMENT = '%d %d obj\n<<\n  /Length %d\n>>\nstream\n%s\nendstream\nendobj\n'
+  XFACONFIG_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>'''
+
+  XFALOCALE_PATTERN = r'\{\{xfalocale\s+(\d+)\s+(\d+)\}\}'
+  XFALOCALE_REPLACEMENT = '%d %d obj\n<<\n  /Length %d\n>>\nstream\n%s\nendstream\nendobj\n'
+  XFALOCALE_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>'''
+
   def __init__(self):
     self.streamlen_state = StreamLenState.START
     self.streamlens = []
@@ -109,10 +265,35 @@
     if match:
       self.insert_xref_entry(int(match.group(1)), int(match.group(2)))
       line = re.sub(self.OBJECT_PATTERN, self.OBJECT_REPLACEMENT, line)
+    line = self.replace_xfa_tag(line,
+                                self.XFAPREAMBLE_PATTERN,
+                                self.XFAPREAMBLE_REPLACEMENT,
+                                self.XFAPREAMBLE_STREAM)
+    line = self.replace_xfa_tag(line,
+                                self.XFACONFIG_PATTERN,
+                                self.XFACONFIG_REPLACEMENT,
+                                self.XFACONFIG_STREAM)
+    line = self.replace_xfa_tag(line,
+                                self.XFALOCALE_PATTERN,
+                                self.XFALOCALE_REPLACEMENT,
+                                self.XFALOCALE_STREAM)
+    line = self.replace_xfa_tag(line,
+                                self.XFAPOSTAMBLE_PATTERN,
+                                self.XFAPOSTAMBLE_REPLACEMENT,
+                                self.XFAPOSTAMBLE_STREAM)
 
     self.offset += len(line)
     return line
 
+  def replace_xfa_tag(self, line, pattern, replacement, stream):
+    match = re.match(pattern, line)
+    if match:
+      x = int(match.group(1))
+      y = int(match.group(2))
+      self.insert_xref_entry(x, y)
+      line = replacement % (x, y, len(stream), stream)
+    return line
+
 
 def expand_file(input_path, output_path):
   processor = TemplateProcessor()
diff --git a/testing/tools/run_xfa_tests.py b/testing/tools/run_xfa_tests.py
new file mode 100755
index 0000000..6647cfd
--- /dev/null
+++ b/testing/tools/run_xfa_tests.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+# Copyright 2018 The PDFium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import sys
+
+import test_runner
+
+def main():
+  runner = test_runner.TestRunner('xfa')
+  runner.SetEnforceExpectedImages(True)
+  return runner.Run()
+
+if __name__ == '__main__':
+  sys.exit(main())