Add path objects tests for a PDF saving bug

Add FPDFEditPathEmbedderTest cases to demonstrate a PDF saving bug,
where a no-op FPDFPageObj_SetMatrix() call causes the saved PDF to have
incorrect transformation matrices. These test cases cover path objects
and path objects in forms.

Bug: pdfium:2132
Change-Id: I81f13fa6a471c4f7184e43f105ff0cc151bfccff
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/117052
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Thomas Sepez <tsepez@google.com>
diff --git a/fpdfsdk/fpdf_editpath_embeddertest.cpp b/fpdfsdk/fpdf_editpath_embeddertest.cpp
index 2a889d0..91103aa 100644
--- a/fpdfsdk/fpdf_editpath_embeddertest.cpp
+++ b/fpdfsdk/fpdf_editpath_embeddertest.cpp
@@ -4,10 +4,14 @@
 
 #include "core/fxcrt/check.h"
 #include "core/fxcrt/fx_system.h"
+#include "core/fxge/cfx_defaultrenderdevice.h"
 #include "public/fpdf_edit.h"
 #include "testing/embedder_test.h"
+#include "testing/embedder_test_constants.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using pdfium::RectanglesChecksum;
+
 class FPDFEditPathEmbedderTest : public EmbedderTest {};
 
 TEST_F(FPDFEditPathEmbedderTest, VerifyCorrectColoursReturned) {
@@ -62,3 +66,132 @@
   CloseSavedPage(page);
   CloseSavedDocument();
 }
+
+TEST_F(FPDFEditPathEmbedderTest, GetAndSetMatrixForPath) {
+  constexpr int kExpectedWidth = 200;
+  constexpr int kExpectedHeight = 300;
+
+  OpenDocument("rectangles_double_flipped.pdf");
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  {
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
+    CompareBitmap(bitmap.get(), kExpectedWidth, kExpectedHeight,
+                  RectanglesChecksum());
+  }
+
+  FPDF_PAGEOBJECT path = FPDFPage_GetObject(page, 0);
+  ASSERT_TRUE(path);
+  ASSERT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path));
+
+  FS_MATRIX matrix;
+  ASSERT_TRUE(FPDFPageObj_GetMatrix(path, &matrix));
+  EXPECT_FLOAT_EQ(1.0f, matrix.a);
+  EXPECT_FLOAT_EQ(0.0f, matrix.b);
+  EXPECT_FLOAT_EQ(0.0f, matrix.c);
+  EXPECT_FLOAT_EQ(-1.0f, matrix.d);
+  EXPECT_FLOAT_EQ(0.0f, matrix.e);
+  EXPECT_FLOAT_EQ(300.0f, matrix.f);
+
+  ASSERT_TRUE(FPDFPageObj_SetMatrix(path, &matrix));
+  {
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
+    CompareBitmap(bitmap.get(), kExpectedWidth, kExpectedHeight,
+                  RectanglesChecksum());
+  }
+
+  ASSERT_TRUE(FPDFPage_GenerateContent(page));
+  ASSERT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
+
+  {
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
+    CompareBitmap(bitmap.get(), kExpectedWidth, kExpectedHeight,
+                  RectanglesChecksum());
+  }
+
+  UnloadPage(page);
+
+  // TODO(crbug.com/pdfium/2132): This should use RectanglesChecksum().
+  const char* const kWrongChecksum = []() {
+    if (CFX_DefaultRenderDevice::UseSkiaRenderer()) {
+      return "a8b00bc40677a73c15a08b9769d1b576";
+    }
+    return "8f3a555ef9c0d5031831ae3715273707";
+  }();
+  VerifySavedDocument(kExpectedWidth, kExpectedHeight, kWrongChecksum);
+}
+
+TEST_F(FPDFEditPathEmbedderTest, GetAndSetMatrixForFormWithPath) {
+  constexpr int kExpectedWidth = 200;
+  constexpr int kExpectedHeight = 300;
+
+  OpenDocument("form_object_with_path.pdf");
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  {
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
+    CompareBitmap(bitmap.get(), kExpectedWidth, kExpectedHeight,
+                  RectanglesChecksum());
+  }
+
+  FPDF_PAGEOBJECT form = FPDFPage_GetObject(page, 0);
+  ASSERT_TRUE(form);
+  ASSERT_EQ(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(form));
+
+  FS_MATRIX matrix;
+  ASSERT_TRUE(FPDFPageObj_GetMatrix(form, &matrix));
+  EXPECT_FLOAT_EQ(2.0f, matrix.a);
+  EXPECT_FLOAT_EQ(0.0f, matrix.b);
+  EXPECT_FLOAT_EQ(0.0f, matrix.c);
+  EXPECT_FLOAT_EQ(-1.0f, matrix.d);
+  EXPECT_FLOAT_EQ(0.0f, matrix.e);
+  EXPECT_FLOAT_EQ(300.0f, matrix.f);
+
+  ASSERT_TRUE(FPDFPageObj_SetMatrix(form, &matrix));
+  {
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
+    CompareBitmap(bitmap.get(), kExpectedWidth, kExpectedHeight,
+                  RectanglesChecksum());
+  }
+
+  FPDF_PAGEOBJECT path = FPDFFormObj_GetObject(form, 0);
+  ASSERT_TRUE(path);
+  ASSERT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path));
+
+  ASSERT_TRUE(FPDFPageObj_GetMatrix(path, &matrix));
+  EXPECT_FLOAT_EQ(0.5f, matrix.a);
+  EXPECT_FLOAT_EQ(0.0f, matrix.b);
+  EXPECT_FLOAT_EQ(0.0f, matrix.c);
+  EXPECT_FLOAT_EQ(-1.0f, matrix.d);
+  EXPECT_FLOAT_EQ(0.0f, matrix.e);
+  EXPECT_FLOAT_EQ(300.0f, matrix.f);
+
+  ASSERT_TRUE(FPDFPageObj_SetMatrix(path, &matrix));
+  {
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
+    CompareBitmap(bitmap.get(), kExpectedWidth, kExpectedHeight,
+                  RectanglesChecksum());
+  }
+
+  ASSERT_TRUE(FPDFPage_GenerateContent(page));
+  ASSERT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
+
+  {
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
+    CompareBitmap(bitmap.get(), kExpectedWidth, kExpectedHeight,
+                  RectanglesChecksum());
+  }
+
+  UnloadPage(page);
+
+  // TODO(crbug.com/pdfium/2132): This should use RectanglesChecksum().
+  const char* const kWrongChecksum = []() {
+    if (CFX_DefaultRenderDevice::UseSkiaRenderer()) {
+      return "f7c69f8d0bb92541d48dbbbba003396d";
+    }
+    return "37a215071985bd88079fedb56b001685";
+  }();
+  VerifySavedDocument(kExpectedWidth, kExpectedHeight, kWrongChecksum);
+}
diff --git a/testing/resources/form_object_with_path.in b/testing/resources/form_object_with_path.in
new file mode 100644
index 0000000..aeb1dac
--- /dev/null
+++ b/testing/resources/form_object_with_path.in
@@ -0,0 +1,63 @@
+{{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
+  /Resources <<
+    /XObject <<
+      /F1 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+2 0 0 -1 0 300 cm
+q
+/F1 Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 200 300]
+>>
+stream
+q
+0.5 0 0 -1 0 300 cm
+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
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/form_object_with_path.pdf b/testing/resources/form_object_with_path.pdf
new file mode 100644
index 0000000..a49dd0c
--- /dev/null
+++ b/testing/resources/form_object_with_path.pdf
@@ -0,0 +1,75 @@
+%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
+  /Resources <<
+    /XObject <<
+      /F1 5 0 R
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Length 29
+>>
+stream
+2 0 0 -1 0 300 cm
+q
+/F1 Do
+Q
+endstream
+endobj
+5 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /BBox [0 0 200 300]
+>>
+stream
+q
+0.5 0 0 -1 0 300 cm
+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
+Q
+endstream
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000286 00000 n 
+0000000366 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+672
+%%EOF
diff --git a/testing/resources/rectangles_double_flipped.in b/testing/resources/rectangles_double_flipped.in
new file mode 100644
index 0000000..e2f8a95
--- /dev/null
+++ b/testing/resources/rectangles_double_flipped.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 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
+1 0 0 -1 0 300 cm
+q
+0 0 0 rg
+0 10 10 -10 re B*
+10 150 50 -30 re B*
+0 0 1 rg
+190 10 10 -10 re B*
+70 68 50 -30 re B*
+0 1 0 rg
+190 300 10 -10 re B*
+130 150 50 -30 re B*
+1 0 0 rg
+0 300 10 -10 re B*
+70 233 50 -30 re B*
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/rectangles_double_flipped.pdf b/testing/resources/rectangles_double_flipped.pdf
new file mode 100644
index 0000000..1180401
--- /dev/null
+++ b/testing/resources/rectangles_double_flipped.pdf
@@ -0,0 +1,55 @@
+%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 216
+>>
+stream
+1 0 0 -1 0 300 cm
+q
+0 0 0 rg
+0 10 10 -10 re B*
+10 150 50 -30 re B*
+0 0 1 rg
+190 10 10 -10 re B*
+70 68 50 -30 re B*
+0 1 0 rg
+190 300 10 -10 re B*
+130 150 50 -30 re B*
+1 0 0 rg
+0 300 10 -10 re B*
+70 233 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
+494
+%%EOF