Add method to convert to an indirect object in a dictionary.

Avoid an assert which previously could only be overcome
by removing/re-inserting.

Back-fill a unit test for the equivalent Array method.

BUG=654387

Review-Url: https://codereview.chromium.org/2403143002
diff --git a/core/fpdfapi/parser/cpdf_dictionary.cpp b/core/fpdfapi/parser/cpdf_dictionary.cpp
index 7ef5a53..54aafd4 100644
--- a/core/fpdfapi/parser/cpdf_dictionary.cpp
+++ b/core/fpdfapi/parser/cpdf_dictionary.cpp
@@ -192,6 +192,17 @@
     m_Map.erase(it);
 }
 
+void CPDF_Dictionary::ConvertToIndirectObjectFor(
+    const CFX_ByteString& key,
+    CPDF_IndirectObjectHolder* pHolder) {
+  auto it = m_Map.find(key);
+  if (it == m_Map.end() || it->second->IsReference())
+    return;
+
+  uint32_t objnum = pHolder->AddIndirectObject(it->second);
+  it->second = new CPDF_Reference(pHolder, objnum);
+}
+
 void CPDF_Dictionary::RemoveFor(const CFX_ByteString& key) {
   auto it = m_Map.find(key);
   if (it == m_Map.end())
diff --git a/core/fpdfapi/parser/cpdf_dictionary.h b/core/fpdfapi/parser/cpdf_dictionary.h
index 23f2e0e..b56e40a 100644
--- a/core/fpdfapi/parser/cpdf_dictionary.h
+++ b/core/fpdfapi/parser/cpdf_dictionary.h
@@ -70,6 +70,9 @@
   void SetMatrixFor(const CFX_ByteString& key, const CFX_Matrix& matrix);
   void SetBooleanFor(const CFX_ByteString& key, bool bValue);
 
+  void ConvertToIndirectObjectFor(const CFX_ByteString& key,
+                                  CPDF_IndirectObjectHolder* pHolder);
+
   // Invalidates iterators for the element with the key |key|.
   void RemoveFor(const CFX_ByteString& key);
 
diff --git a/core/fpdfapi/parser/cpdf_object_unittest.cpp b/core/fpdfapi/parser/cpdf_object_unittest.cpp
index b2177af..502d2a0 100644
--- a/core/fpdfapi/parser/cpdf_object_unittest.cpp
+++ b/core/fpdfapi/parser/cpdf_object_unittest.cpp
@@ -766,6 +766,21 @@
   EXPECT_FALSE(cloned_obj);
 }
 
+TEST(PDFArrayTest, ConvertIndirect) {
+  CPDF_IndirectObjectHolder objects_holder;
+  ScopedArray array(new CPDF_Array);
+  CPDF_Object* pObj = new CPDF_Number(42);
+  array->Add(pObj);
+  array->ConvertToIndirectObjectAt(0, &objects_holder);
+  CPDF_Object* pRef = array->GetObjectAt(0);
+  CPDF_Object* pNum = array->GetDirectObjectAt(0);
+  EXPECT_TRUE(pRef->IsReference());
+  EXPECT_TRUE(pNum->IsNumber());
+  EXPECT_NE(pObj, pRef);
+  EXPECT_EQ(pObj, pNum);
+  EXPECT_EQ(42, array->GetIntegerAt(0));
+}
+
 TEST(PDFDictionaryTest, CloneDirectObject) {
   CPDF_IndirectObjectHolder objects_holder;
   ScopedDict dict(new CPDF_Dictionary());
@@ -833,3 +848,18 @@
     EXPECT_EQ(nullptr, cloned_arr->AsArray()->GetObjectAt(0));
   }
 }
+
+TEST(PDFDictionaryTest, ConvertIndirect) {
+  CPDF_IndirectObjectHolder objects_holder;
+  ScopedDict dict(new CPDF_Dictionary);
+  CPDF_Object* pObj = new CPDF_Number(42);
+  dict->SetFor("clams", pObj);
+  dict->ConvertToIndirectObjectFor("clams", &objects_holder);
+  CPDF_Object* pRef = dict->GetObjectFor("clams");
+  CPDF_Object* pNum = dict->GetDirectObjectFor("clams");
+  EXPECT_TRUE(pRef->IsReference());
+  EXPECT_TRUE(pNum->IsNumber());
+  EXPECT_NE(pObj, pRef);
+  EXPECT_EQ(pObj, pNum);
+  EXPECT_EQ(42, dict->GetIntegerFor("clams"));
+}
diff --git a/fpdfsdk/fpdf_flatten.cpp b/fpdfsdk/fpdf_flatten.cpp
index 854a992..251da34 100644
--- a/fpdfsdk/fpdf_flatten.cpp
+++ b/fpdfsdk/fpdf_flatten.cpp
@@ -24,6 +24,8 @@
 enum FPDF_TYPE { MAX, MIN };
 enum FPDF_VALUE { TOP, LEFT, RIGHT, BOTTOM };
 
+namespace {
+
 FX_BOOL IsValiableRect(CFX_FloatRect rect, CFX_FloatRect rcPage) {
   if (rect.left - rect.right > 0.000001f || rect.bottom - rect.top > 0.000001f)
     return FALSE;
@@ -182,67 +184,48 @@
   return rcRet;
 }
 
-void SetPageContents(CFX_ByteString key,
+uint32_t NewIndirectContentsStream(const CFX_ByteString& key,
+                                   CPDF_Document* pDocument) {
+  CPDF_Stream* pNewContents = new CPDF_Stream(
+      nullptr, 0, new CPDF_Dictionary(pDocument->GetByteStringPool()));
+  CFX_ByteString sStream;
+  sStream.Format("q 1 0 0 1 0 0 cm /%s Do Q", key.c_str());
+  pNewContents->SetData(sStream.raw_str(), sStream.GetLength());
+  return pDocument->AddIndirectObject(pNewContents);
+}
+
+void SetPageContents(const CFX_ByteString& key,
                      CPDF_Dictionary* pPage,
                      CPDF_Document* pDocument) {
-  CPDF_Object* pContentsObj = pPage->GetStreamFor("Contents");
-  if (!pContentsObj) {
-    pContentsObj = pPage->GetArrayFor("Contents");
-  }
-
-  if (!pContentsObj) {
-    // Create a new contents dictionary
-    if (!key.IsEmpty()) {
-      CPDF_Stream* pNewContents = new CPDF_Stream(
-          nullptr, 0, new CPDF_Dictionary(pDocument->GetByteStringPool()));
-      CFX_ByteString sStream;
-      sStream.Format("q 1 0 0 1 0 0 cm /%s Do Q", key.c_str());
-      pNewContents->SetData(sStream.raw_str(), sStream.GetLength());
-      pPage->SetReferenceFor("Contents", pDocument,
-                             pDocument->AddIndirectObject(pNewContents));
-    }
-    return;
-  }
-
   CPDF_Array* pContentsArray = nullptr;
-  switch (pContentsObj->GetType()) {
-    case CPDF_Object::STREAM: {
-      pContentsArray = new CPDF_Array;
-      CPDF_Stream* pContents = pContentsObj->AsStream();
-      uint32_t dwObjNum = pDocument->AddIndirectObject(pContents);
-      CPDF_StreamAcc acc;
-      acc.LoadAllData(pContents);
-      CFX_ByteString sStream = "q\n";
-      CFX_ByteString sBody =
-          CFX_ByteString((const FX_CHAR*)acc.GetData(), acc.GetSize());
-      sStream = sStream + sBody + "\nQ";
-      pContents->SetData(sStream.raw_str(), sStream.GetLength());
-      pContentsArray->AddReference(pDocument, dwObjNum);
-      break;
+  CPDF_Stream* pContentsStream = pPage->GetStreamFor("Contents");
+  if (!pContentsStream) {
+    pContentsArray = pPage->GetArrayFor("Contents");
+    if (!pContentsArray) {
+      if (!key.IsEmpty()) {
+        pPage->SetReferenceFor("Contents", pDocument,
+                               NewIndirectContentsStream(key, pDocument));
+      }
+      return;
     }
-
-    case CPDF_Object::ARRAY: {
-      pContentsArray = pContentsObj->AsArray();
-      break;
-    }
-    default:
-      break;
   }
-
-  if (!pContentsArray)
-    return;
-
-  pPage->SetReferenceFor("Contents", pDocument,
-                         pDocument->AddIndirectObject(pContentsArray));
-
+  pPage->ConvertToIndirectObjectFor("Contents", pDocument);
+  if (!pContentsArray) {
+    pContentsArray = new CPDF_Array;
+    CPDF_StreamAcc acc;
+    acc.LoadAllData(pContentsStream);
+    CFX_ByteString sStream = "q\n";
+    CFX_ByteString sBody =
+        CFX_ByteString((const FX_CHAR*)acc.GetData(), acc.GetSize());
+    sStream = sStream + sBody + "\nQ";
+    pContentsStream->SetData(sStream.raw_str(), sStream.GetLength());
+    pContentsArray->AddReference(pDocument, pContentsStream->GetObjNum());
+    pPage->SetReferenceFor("Contents", pDocument,
+                           pDocument->AddIndirectObject(pContentsArray));
+  }
   if (!key.IsEmpty()) {
-    CPDF_Stream* pNewContents = new CPDF_Stream(
-        nullptr, 0, new CPDF_Dictionary(pDocument->GetByteStringPool()));
-    CFX_ByteString sStream;
-    sStream.Format("q 1 0 0 1 0 0 cm /%s Do Q", key.c_str());
-    pNewContents->SetData(sStream.raw_str(), sStream.GetLength());
     pContentsArray->AddReference(pDocument,
-                                 pDocument->AddIndirectObject(pNewContents));
+                                 NewIndirectContentsStream(key, pDocument));
   }
 }
 
@@ -263,45 +246,7 @@
   return CFX_Matrix(a, 0, 0, d, e, f);
 }
 
-void GetOffset(FX_FLOAT& fa,
-               FX_FLOAT& fd,
-               FX_FLOAT& fe,
-               FX_FLOAT& ff,
-               CFX_FloatRect rcAnnot,
-               CFX_FloatRect rcStream,
-               const CFX_Matrix& matrix) {
-  FX_FLOAT fStreamWidth = 0.0f;
-  FX_FLOAT fStreamHeight = 0.0f;
-
-  if (matrix.a != 0 && matrix.d != 0) {
-    fStreamWidth = rcStream.right - rcStream.left;
-    fStreamHeight = rcStream.top - rcStream.bottom;
-  } else {
-    fStreamWidth = rcStream.top - rcStream.bottom;
-    fStreamHeight = rcStream.right - rcStream.left;
-  }
-
-  FX_FLOAT x1 =
-      matrix.a * rcStream.left + matrix.c * rcStream.bottom + matrix.e;
-  FX_FLOAT y1 =
-      matrix.b * rcStream.left + matrix.d * rcStream.bottom + matrix.f;
-  FX_FLOAT x2 = matrix.a * rcStream.left + matrix.c * rcStream.top + matrix.e;
-  FX_FLOAT y2 = matrix.b * rcStream.left + matrix.d * rcStream.top + matrix.f;
-  FX_FLOAT x3 =
-      matrix.a * rcStream.right + matrix.c * rcStream.bottom + matrix.e;
-  FX_FLOAT y3 =
-      matrix.b * rcStream.right + matrix.d * rcStream.bottom + matrix.f;
-  FX_FLOAT x4 = matrix.a * rcStream.right + matrix.c * rcStream.top + matrix.e;
-  FX_FLOAT y4 = matrix.b * rcStream.right + matrix.d * rcStream.top + matrix.f;
-
-  FX_FLOAT left = std::min(std::min(x1, x2), std::min(x3, x4));
-  FX_FLOAT bottom = std::min(std::min(y1, y2), std::min(y3, y4));
-
-  fa = (rcAnnot.right - rcAnnot.left) / fStreamWidth;
-  fd = (rcAnnot.top - rcAnnot.bottom) / fStreamHeight;
-  fe = rcAnnot.left - left * fa;
-  ff = rcAnnot.bottom - bottom * fd;
-}
+}  // namespace
 
 DLLEXPORT int STDCALL FPDFPage_Flatten(FPDF_PAGE page, int nFlag) {
   CPDF_Page* pPage = CPDFPageFromFPDFPage(page);