Let FPDFPageObj_SetMatrix() reset dirty bit if restoring image matrix

With existing public image APIs, embedders may want to first call
FPDFPageObj_SetMatrix() to set an image to a desired size, render, and
call FPDFPageObj_SetMatrix() to restore the original matrix. This has
the side effect of marking the image object as being dirty, which can
exacerbate issues in CPDF_PageContentGenerator.

Avoid this by remembering a page object's initial matrix, and have a
second dirty bit to track if the matrix has changed, separate from
other modifications. Do this only for image objects for now.

Bug: 405433817, 406540676, 407950121
Change-Id: I0e66ef4e590cd7bcc79c21f31eae2e02724c422f
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/130390
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/core/fpdfapi/page/cpdf_imageobject.cpp b/core/fpdfapi/page/cpdf_imageobject.cpp
index 8078bd9..4781fb3 100644
--- a/core/fpdfapi/page/cpdf_imageobject.cpp
+++ b/core/fpdfapi/page/cpdf_imageobject.cpp
@@ -73,6 +73,11 @@
   return pSource ? pSource->Realize() : nullptr;
 }
 
+void CPDF_ImageObject::SetInitialImageMatrix(const CFX_Matrix& matrix) {
+  InitializeOriginalMatrix(matrix);
+  SetImageMatrix(matrix);
+}
+
 void CPDF_ImageObject::SetImageMatrix(const CFX_Matrix& matrix) {
   m_Matrix = matrix;
   CalcBoundingBox();
diff --git a/core/fpdfapi/page/cpdf_imageobject.h b/core/fpdfapi/page/cpdf_imageobject.h
index 06e5a64..bb8e40e 100644
--- a/core/fpdfapi/page/cpdf_imageobject.h
+++ b/core/fpdfapi/page/cpdf_imageobject.h
@@ -32,6 +32,8 @@
   RetainPtr<CPDF_Image> GetImage() const;
   RetainPtr<CFX_DIBitmap> GetIndependentBitmap() const;
 
+  void SetInitialImageMatrix(const CFX_Matrix& matrix);
+
   void SetImageMatrix(const CFX_Matrix& matrix);
   const CFX_Matrix& matrix() const { return m_Matrix; }
 
diff --git a/core/fpdfapi/page/cpdf_pageobject.cpp b/core/fpdfapi/page/cpdf_pageobject.cpp
index 744aea6..dbe19c7 100644
--- a/core/fpdfapi/page/cpdf_pageobject.cpp
+++ b/core/fpdfapi/page/cpdf_pageobject.cpp
@@ -90,6 +90,10 @@
   m_bDirty = true;
 }
 
+void CPDF_PageObject::InitializeOriginalMatrix(const CFX_Matrix& matrix) {
+  m_OriginalMatrix = matrix;
+}
+
 void CPDF_PageObject::SetIsActive(bool value) {
   if (m_bIsActive != value) {
     m_bIsActive = value;
diff --git a/core/fpdfapi/page/cpdf_pageobject.h b/core/fpdfapi/page/cpdf_pageobject.h
index 000dd29..f000aff 100644
--- a/core/fpdfapi/page/cpdf_pageobject.h
+++ b/core/fpdfapi/page/cpdf_pageobject.h
@@ -61,7 +61,8 @@
   virtual const CPDF_FormObject* AsForm() const;
 
   void SetDirty(bool value) { m_bDirty = value; }
-  bool IsDirty() const { return m_bDirty; }
+  bool IsDirty() const { return m_bDirty || m_bMatrixDirty; }
+  void SetMatrixDirty(bool value) { m_bMatrixDirty = value; }
   void SetIsActive(bool value);
   bool IsActive() const { return m_bIsActive; }
   void TransformClipPath(const CFX_Matrix& matrix);
@@ -135,19 +136,28 @@
 
   void SetDefaultStates();
 
+  const CFX_Matrix& original_matrix() const { return m_OriginalMatrix; }
+
  protected:
   void CopyData(const CPDF_PageObject* pSrcObject);
+  void InitializeOriginalMatrix(const CFX_Matrix& matrix);
 
  private:
   CPDF_GraphicStates m_GraphicStates;
   CFX_FloatRect m_Rect;
   CFX_FloatRect m_OriginalRect;
+  // Only used with `CPDF_ImageObject` for now.
+  // TODO(thestig): Use with `CPDF_FormObject` and `CPDF_PageObject` as well.
+  CFX_Matrix m_OriginalMatrix;
   CPDF_ContentMarks m_ContentMarks;
   // Modifying `m_bIsActive` automatically set `m_bDirty` to be true, but
   // otherwise `m_bDirty` and `m_bIsActive` are independent.  A
   // `CPDF_PageObject` can remain dirty until page object processing completes
   // and marks it no longer dirty.
   bool m_bDirty = false;
+  // Separately track if the current matrix is different from
+  // `m_OriginalMatrix`.
+  bool m_bMatrixDirty = false;
   bool m_bIsActive = true;
   int32_t m_ContentStream;
   // The resource name for this object.
diff --git a/core/fpdfapi/page/cpdf_streamcontentparser.cpp b/core/fpdfapi/page/cpdf_streamcontentparser.cpp
index 340f0d7..62eea03 100644
--- a/core/fpdfapi/page/cpdf_streamcontentparser.cpp
+++ b/core/fpdfapi/page/cpdf_streamcontentparser.cpp
@@ -839,9 +839,8 @@
   SetGraphicStates(pImageObj.get(), pImageObj->GetImage()->IsMask(), false,
                    false);
 
-  CFX_Matrix ImageMatrix =
-      m_pCurStates->current_transformation_matrix() * m_mtContentToUser;
-  pImageObj->SetImageMatrix(ImageMatrix);
+  pImageObj->SetInitialImageMatrix(
+      m_pCurStates->current_transformation_matrix() * m_mtContentToUser);
 
   CPDF_ImageObject* pRet = pImageObj.get();
   m_pObjectHolder->AppendPageObject(std::move(pImageObj));
diff --git a/fpdfsdk/fpdf_editpage.cpp b/fpdfsdk/fpdf_editpage.cpp
index 223f899..68f3af5 100644
--- a/fpdfsdk/fpdf_editpage.cpp
+++ b/fpdfsdk/fpdf_editpage.cpp
@@ -36,6 +36,7 @@
 #include "core/fxcrt/compiler_specific.h"
 #include "core/fxcrt/fx_extension.h"
 #include "core/fxcrt/fx_memcpy_wrappers.h"
+#include "core/fxcrt/notreached.h"
 #include "core/fxcrt/numerics/safe_conversions.h"
 #include "core/fxcrt/span.h"
 #include "core/fxcrt/span_util.h"
@@ -722,28 +723,32 @@
 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
 FPDFPageObj_SetMatrix(FPDF_PAGEOBJECT page_object, const FS_MATRIX* matrix) {
   CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object);
-  if (!pPageObj || !matrix)
+  if (!pPageObj || !matrix) {
     return false;
+  }
 
   CFX_Matrix cmatrix = CFXMatrixFromFSMatrix(*matrix);
   switch (pPageObj->GetType()) {
     case CPDF_PageObject::Type::kText:
       pPageObj->AsText()->SetTextMatrix(cmatrix);
-      break;
+      pPageObj->SetMatrixDirty(true);
+      return true;
     case CPDF_PageObject::Type::kPath:
       pPageObj->AsPath()->SetPathMatrix(cmatrix);
-      break;
+      pPageObj->SetMatrixDirty(true);
+      return true;
     case CPDF_PageObject::Type::kImage:
       pPageObj->AsImage()->SetImageMatrix(cmatrix);
-      break;
+      pPageObj->SetMatrixDirty(pPageObj->original_matrix() != cmatrix);
+      return true;
     case CPDF_PageObject::Type::kShading:
       return false;
     case CPDF_PageObject::Type::kForm:
       pPageObj->AsForm()->SetFormMatrix(cmatrix);
-      break;
+      pPageObj->SetMatrixDirty(true);
+      return true;
   }
-  pPageObj->SetDirty(true);
-  return true;
+  NOTREACHED();
 }
 
 FPDF_EXPORT void FPDF_CALLCONV