Fix FPDFTextObj_SetTextRenderMode() to stick when the document is saved

CPDF_PageContentGenerator::ProcessText() neglected to set the text
rendering mode in the content stream.

Bug: pdfium:1470
Change-Id: I507109cf65b35ce491342d59fc0f37309f9a9d21
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/71092
Commit-Queue: Daniel Hosseinian <dhoss@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
index 34dde67..580b36e 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
+++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
@@ -488,6 +488,7 @@
 // This method adds text to the buffer, BT begins the text object, ET ends it.
 // Tm sets the text matrix (allows positioning and transforming text).
 // Tf sets the font name (from Font in Resources) and font size.
+// Tr sets the text rendering mode.
 // Tj sets the actual text, <####...> is used when specifying charcodes.
 void CPDF_PageContentGenerator::ProcessText(std::ostringstream* buf,
                                             CPDF_TextObject* pTextObj) {
@@ -534,6 +535,7 @@
   }
   *buf << "/" << PDF_NameEncode(dictName) << " ";
   WriteFloat(*buf, pTextObj->GetFontSize()) << " Tf ";
+  *buf << static_cast<int>(pTextObj->GetTextRenderMode()) << " Tr ";
   ByteString text;
   for (uint32_t charcode : pTextObj->GetCharCodes()) {
     if (charcode != CPDF_Font::kInvalidCharCode)
diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp
index f21d449..60f5057 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp
+++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp
@@ -15,6 +15,7 @@
 #include "core/fpdfapi/page/cpdf_pagemodule.h"
 #include "core/fpdfapi/page/cpdf_pathobject.h"
 #include "core/fpdfapi/page/cpdf_textobject.h"
+#include "core/fpdfapi/page/cpdf_textstate.h"
 #include "core/fpdfapi/parser/cpdf_dictionary.h"
 #include "core/fpdfapi/parser/cpdf_document.h"
 #include "core/fpdfapi/parser/cpdf_name.h"
@@ -274,7 +275,7 @@
       "q 0.501961 0.701961 0.34902 rg 1 0.901961 0 RG /";
   // Color RGB values used are integers divided by 255.
   ByteString compareString2 = " gs BT 1 0 0 1 100 100 Tm /";
-  ByteString compareString3 = " 10 Tf <48656C6C6F20576F726C64> Tj ET Q\n";
+  ByteString compareString3 = " 10 Tf 0 Tr <48656C6C6F20576F726C64> Tj ET Q\n";
   EXPECT_LT(compareString1.GetLength() + compareString2.GetLength() +
                 compareString3.GetLength(),
             textString.GetLength());
@@ -329,6 +330,7 @@
     pTextObj->m_TextState.SetFont(pLoadedFont);
     pTextObj->m_TextState.SetFontSize(15.5f);
     pTextObj->SetText("I am indirect");
+    pTextObj->SetTextRenderMode(TextRenderingMode::MODE_FILL_CLIP);
     TestProcessText(&generator, &buf, pTextObj.get());
   }
 
@@ -341,7 +343,8 @@
       textString.Last(textString.GetLength() - firstResourceAt.value());
   // q and Q must be outside the BT .. ET operations
   ByteString compareString1 = "q BT 1 0 0 1 0 0 Tm /";
-  ByteString compareString2 = " 15.5 Tf <4920616D20696E646972656374> Tj ET Q\n";
+  ByteString compareString2 =
+      " 15.5 Tf 4 Tr <4920616D20696E646972656374> Tj ET Q\n";
   EXPECT_LT(compareString1.GetLength() + compareString2.GetLength(),
             textString.GetLength());
   EXPECT_EQ(compareString1, textString.First(compareString1.GetLength()));
diff --git a/core/fpdfapi/page/cpdf_textobject.cpp b/core/fpdfapi/page/cpdf_textobject.cpp
index 3872a2c..beb3ce9 100644
--- a/core/fpdfapi/page/cpdf_textobject.cpp
+++ b/core/fpdfapi/page/cpdf_textobject.cpp
@@ -258,6 +258,7 @@
 
 void CPDF_TextObject::SetTextRenderMode(TextRenderingMode mode) {
   m_TextState.SetTextMode(mode);
+  SetDirty(true);
 }
 
 CFX_PointF CPDF_TextObject::CalcPositionData(float horz_scale) {
diff --git a/fpdfsdk/fpdf_edit_embeddertest.cpp b/fpdfsdk/fpdf_edit_embeddertest.cpp
index cb7c8fb..cffeb7c 100644
--- a/fpdfsdk/fpdf_edit_embeddertest.cpp
+++ b/fpdfsdk/fpdf_edit_embeddertest.cpp
@@ -2122,27 +2122,27 @@
       CompareBitmap(page_bitmap.get(), 612, 446, md5_stroke);
     }
 
+    // Save a copy.
+    EXPECT_TRUE(FPDFPage_GenerateContent(page));
+    EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
+
     UnloadPage(page);
   }
 
   {
-    // Save a copy, open the copy, and render it.
-    // Note that the text render mode should be set to
-    // |FPDF_TEXTRENDERMODE_STROKE|, but isn't.
-    // TODO(crbug.com/pdfium/1470): Fix FPDFTextObj_SetTextRenderMode() to work
-    // on saved documents.
-    EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
+    // Open the saved copy and render it. Check that the changed text render
+    // mode is kept in the saved copy.
     ASSERT_TRUE(OpenSavedDocument());
     FPDF_PAGE saved_page = LoadSavedPage(0);
     ASSERT_TRUE(saved_page);
 
     FPDF_PAGEOBJECT page_object = FPDFPage_GetObject(saved_page, 0);
     EXPECT_TRUE(page_object);
-    EXPECT_EQ(FPDF_TEXTRENDERMODE_FILL,
+    EXPECT_EQ(FPDF_TEXTRENDERMODE_STROKE,
               FPDFTextObj_GetTextRenderMode(page_object));
 
     ScopedFPDFBitmap bitmap = RenderSavedPage(saved_page);
-    CompareBitmap(bitmap.get(), 612, 446, md5);
+    CompareBitmap(bitmap.get(), 612, 446, md5_stroke);
 
     CloseSavedPage(saved_page);
     CloseSavedDocument();