Fix wrong matrix outputs in CPDF_PageContentGenerator for path objects

When CPDF_PageContentGenerator generates content stream data for a path
page object, it currently does not take the object's container's current
transformation matrix into account. Fix this and update the test that
now renders correctly.

With the above fix, FPDFEditEmbedderTest.SaveAndRender fails. This is
because the SaveAndRender test case has a non-identity current
transformation matrix in the original PDF, and then
FPDFPage_InsertObject() added a page object without any knowledge of the
CTM. Given the behavior of FPDFPage_InsertObject() cannot change w.r.t
the CTM, the only way to fix this is to set an extra bit to
differentiate CPDF_PageObject instances that were parsed from the PDF,
vs. CPDF_PageObject instances that were synthesized using PDFium APIs.

Bug: pdfium:2132
Change-Id: Id9238dc075dae7843eb1ed5131d120784f66c192
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/117055
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Thomas Sepez <tsepez@google.com>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
index 428dc33..e2f6f82 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
+++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
@@ -421,9 +421,11 @@
 
   *buf << "q ";
 
-  const CFX_Matrix& ctm = m_pObjHolder->GetLastCTM();
-  if (!ctm.IsIdentity()) {
-    matrix.Concat(ctm.GetInverse());
+  if (pImageObj->UsesCTM()) {
+    const CFX_Matrix& ctm = m_pObjHolder->GetLastCTM();
+    if (!ctm.IsIdentity()) {
+      matrix.Concat(ctm.GetInverse());
+    }
   }
   if (!matrix.IsIdentity()) {
     WriteMatrix(*buf, matrix) << " cm ";
@@ -460,9 +462,11 @@
 
   *buf << "q\n";
 
-  const CFX_Matrix& ctm = m_pObjHolder->GetLastCTM();
-  if (!ctm.IsIdentity()) {
-    matrix.Concat(ctm.GetInverse());
+  if (pFormObj->UsesCTM()) {
+    const CFX_Matrix& ctm = m_pObjHolder->GetLastCTM();
+    if (!ctm.IsIdentity()) {
+      matrix.Concat(ctm.GetInverse());
+    }
   }
   if (!matrix.IsIdentity()) {
     WriteMatrix(*buf, matrix) << " cm ";
@@ -526,9 +530,13 @@
                                             CPDF_PathObject* pPathObj) {
   ProcessGraphics(buf, pPathObj);
 
-  // TODO(crbug.com/pdfium/2132): Does this need to take the current
-  // transformation matrix in `m_pObjHolder` into account?
-  const CFX_Matrix& matrix = pPathObj->matrix();
+  CFX_Matrix matrix = pPathObj->matrix();
+  if (pPathObj->UsesCTM()) {
+    const CFX_Matrix& ctm = m_pObjHolder->GetLastCTM();
+    if (!ctm.IsIdentity()) {
+      matrix.Concat(ctm.GetInverse());
+    }
+  }
   if (!matrix.IsIdentity()) {
     WriteMatrix(*buf, matrix) << " cm ";
   }
@@ -683,9 +691,11 @@
   *buf << "BT ";
 
   CFX_Matrix matrix = pTextObj->GetTextMatrix();
-  const CFX_Matrix& ctm = m_pObjHolder->GetLastCTM();
-  if (!ctm.IsIdentity()) {
-    matrix.Concat(ctm.GetInverse());
+  if (pTextObj->UsesCTM()) {
+    const CFX_Matrix& ctm = m_pObjHolder->GetLastCTM();
+    if (!ctm.IsIdentity()) {
+      matrix.Concat(ctm.GetInverse());
+    }
   }
   if (!matrix.IsIdentity()) {
     WriteMatrix(*buf, matrix) << " Tm ";
diff --git a/core/fpdfapi/page/cpdf_pageobject.h b/core/fpdfapi/page/cpdf_pageobject.h
index 4821321..999c942 100644
--- a/core/fpdfapi/page/cpdf_pageobject.h
+++ b/core/fpdfapi/page/cpdf_pageobject.h
@@ -62,6 +62,8 @@
 
   void SetDirty(bool value) { m_bDirty = value; }
   bool IsDirty() const { return m_bDirty; }
+  void SetUsesCTM(bool uses_ctm) { m_bUsesCTM = uses_ctm; }
+  bool UsesCTM() const { return m_bUsesCTM; }
   void TransformClipPath(const CFX_Matrix& matrix);
 
   void SetOriginalRect(const CFX_FloatRect& rect) { m_OriginalRect = rect; }
@@ -142,6 +144,11 @@
   CFX_FloatRect m_OriginalRect;
   CPDF_ContentMarks m_ContentMarks;
   bool m_bDirty = false;
+  // If a page object is created by CPDF_StreamContentParser, then it uses the
+  // current transformation matrix. If it is created by a public API and
+  // inserted into a page, then it does not use the CTM. True by default since
+  // the majority of CPDF_PageObjects are created by CPDF_StreamContentParser.
+  bool m_bUsesCTM = true;
   int32_t m_ContentStream;
   // The resource name for this object.
   ByteString m_ResourceName;
diff --git a/fpdfsdk/fpdf_editpage.cpp b/fpdfsdk/fpdf_editpage.cpp
index e5c380a..512bbf8 100644
--- a/fpdfsdk/fpdf_editpage.cpp
+++ b/fpdfsdk/fpdf_editpage.cpp
@@ -257,6 +257,7 @@
   if (!IsPageObject(pPage))
     return;
 
+  pPageObj->SetUsesCTM(false);
   pPageObj->SetDirty(true);
   pPage->AppendPageObject(std::move(pPageObjHolder));
   CalcBoundingBox(pPageObj);
diff --git a/fpdfsdk/fpdf_editpath_embeddertest.cpp b/fpdfsdk/fpdf_editpath_embeddertest.cpp
index 9a9fe63..110b7c3 100644
--- a/fpdfsdk/fpdf_editpath_embeddertest.cpp
+++ b/fpdfsdk/fpdf_editpath_embeddertest.cpp
@@ -4,7 +4,6 @@
 
 #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"
@@ -112,14 +111,7 @@
 
   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);
+  VerifySavedDocument(kExpectedWidth, kExpectedHeight, RectanglesChecksum());
 }
 
 TEST_F(FPDFEditPathEmbedderTest, GetAndSetMatrixForFormWithPath) {