Fix rendering for freetext_annotation_with_da.in

Add GenerateFreeTextAP() and use it to handle freetext annotations
without appearance streams in CPDF_GenerateAP::GenerateAnnotAP(). Since
GenerateFreeTextAP() reuses a lot of code from GenerateFormAP(),
refactor some common bits into helpers. This is a stepping stone towards
fixing freetext_annotation_without_da.pdf.

Bug: 380434959
Change-Id: I6c16131f711b4b011766165518d15c08ab8a9480
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/130010
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Thomas Sepez <tsepez@google.com>
diff --git a/core/fpdfdoc/cpdf_generateap.cpp b/core/fpdfdoc/cpdf_generateap.cpp
index e2d704c..bd7b446 100644
--- a/core/fpdfdoc/cpdf_generateap.cpp
+++ b/core/fpdfdoc/cpdf_generateap.cpp
@@ -105,6 +105,14 @@
   return ByteString(font_stream);
 }
 
+void SetVtFontSize(float font_size, CPVT_VariableText& vt) {
+  if (FXSYS_IsFloatZero(font_size)) {
+    vt.SetAutoFontSize(true);
+  } else {
+    vt.SetFontSize(font_size);
+  }
+}
+
 // ISO 32000-1:2008 spec, table 166.
 // ISO 32000-2:2020 spec, table 168.
 struct BorderStyleInfo {
@@ -251,6 +259,32 @@
   return default_appearance_string;
 }
 
+struct DefaultAppearanceInfo {
+  ByteString font_name;
+  float font_size;
+  CFX_Color text_color;
+};
+
+std::optional<DefaultAppearanceInfo> GetDefaultAppearanceInfo(
+    const ByteString& default_appearance_string) {
+  if (default_appearance_string.IsEmpty()) {
+    return std::nullopt;
+  }
+
+  CPDF_DefaultAppearance appearance(default_appearance_string);
+
+  float font_size = 0;
+  std::optional<ByteString> font = appearance.GetFont(&font_size);
+  if (!font.has_value()) {
+    return std::nullopt;
+  }
+
+  return DefaultAppearanceInfo{
+      .font_name = font.value(),
+      .font_size = font_size,
+      .text_color = fpdfdoc::CFXColorFromString(default_appearance_string)};
+}
+
 bool CloneResourcesDictIfMissingFromStream(CPDF_Dictionary* stream_dict,
                                            const CPDF_Dictionary* dr_dict) {
   RetainPtr<CPDF_Dictionary> resources_dict =
@@ -599,6 +633,22 @@
   return font_dict;
 }
 
+RetainPtr<CPDF_Dictionary> GetFontFromDrFontDictOrGenerateFallback(
+    CPDF_Document* doc,
+    CPDF_Dictionary* dr_font_dict,
+    const ByteString& font_name) {
+  RetainPtr<CPDF_Dictionary> font_dict =
+      dr_font_dict->GetMutableDictFor(font_name);
+  if (font_dict) {
+    return font_dict;
+  }
+
+  RetainPtr<CPDF_Dictionary> new_font_dict = GenerateFallbackFontDict(doc);
+  dr_font_dict->SetNewFor<CPDF_Reference>(font_name, doc,
+                                          new_font_dict->GetObjNum());
+  return new_font_dict;
+}
+
 RetainPtr<CPDF_Dictionary> GenerateResourceFontDict(
     CPDF_Document* doc,
     const ByteString& font_name,
@@ -742,11 +792,7 @@
   const uint32_t max_len = max_len_field ? max_len_field->GetInteger() : 0;
   vt.SetPlateRect(body_rect);
   vt.SetAlignment(align);
-  if (FXSYS_IsFloatZero(font_size)) {
-    vt.SetAutoFontSize(true);
-  } else {
-    vt.SetFontSize(font_size);
-  }
+  SetVtFontSize(font_size, vt);
 
   bool is_multi_line = (flags >> 12) & 1;
   if (is_multi_line) {
@@ -795,11 +841,7 @@
   edit_rect.right = button_rect.left;
   edit_rect.Normalize();
   vt.SetPlateRect(edit_rect);
-  if (FXSYS_IsFloatZero(font_size)) {
-    vt.SetAutoFontSize(true);
-  } else {
-    vt.SetFontSize(font_size);
-  }
+  SetVtFontSize(font_size, vt);
 
   vt.Initialize();
   vt.SetText(value);
@@ -1000,6 +1042,109 @@
   return true;
 }
 
+bool GenerateFreeTextAP(CPDF_Document* doc, CPDF_Dictionary* annot_dict) {
+  RetainPtr<CPDF_Dictionary> root_dict = doc->GetMutableRoot();
+  if (!root_dict) {
+    return false;
+  }
+
+  RetainPtr<CPDF_Dictionary> form_dict =
+      root_dict->GetMutableDictFor("AcroForm");
+  if (!form_dict) {
+    return false;
+  }
+
+  std::optional<DefaultAppearanceInfo> default_appearance_info =
+      GetDefaultAppearanceInfo(
+          GetDefaultAppearanceString(annot_dict, form_dict));
+  if (!default_appearance_info.has_value()) {
+    return false;
+  }
+
+  RetainPtr<CPDF_Dictionary> dr_dict = form_dict->GetMutableDictFor("DR");
+  if (!dr_dict) {
+    return false;
+  }
+
+  RetainPtr<CPDF_Dictionary> dr_font_dict = dr_dict->GetMutableDictFor("Font");
+  if (!ValidateFontResourceDict(dr_font_dict.Get())) {
+    return false;
+  }
+
+  const ByteString& font_name = default_appearance_info.value().font_name;
+  RetainPtr<CPDF_Dictionary> font_dict =
+      GetFontFromDrFontDictOrGenerateFallback(doc, dr_font_dict, font_name);
+  auto* doc_page_data = CPDF_DocPageData::FromDocument(doc);
+  RetainPtr<CPDF_Font> default_font = doc_page_data->GetFont(font_dict);
+  if (!default_font) {
+    return false;
+  }
+
+  fxcrt::ostringstream appearance_stream;
+  appearance_stream << "/" << kGSDictName << " gs ";
+
+  const BorderStyleInfo border_style_info =
+      GetBorderStyleInfo(annot_dict->GetDictFor("BS"));
+  CFX_FloatRect rect = annot_dict->GetRectFor(pdfium::annotation::kRect);
+  const float half_border_width = border_style_info.width / 2.0f;
+  CFX_FloatRect background_rect = rect;
+  background_rect.Deflate(half_border_width, half_border_width);
+  CFX_FloatRect body_rect = background_rect;
+  body_rect.Deflate(half_border_width, half_border_width);
+
+  auto color_array = annot_dict->GetArrayFor(pdfium::annotation::kC);
+  if (color_array) {
+    CFX_Color color = fpdfdoc::CFXColorFromArray(*color_array);
+    appearance_stream << "q\n" << GenerateColorAP(color, PaintOperation::kFill);
+    WriteRect(appearance_stream, background_rect) << " re f\nQ\n";
+  }
+
+  const CFX_Color& text_color = default_appearance_info.value().text_color;
+  const ByteString border_stream =
+      GenerateBorderAP(rect, border_style_info, text_color);
+  if (border_stream.GetLength() > 0) {
+    appearance_stream << "q\n" << border_stream << "Q\n";
+  }
+
+  CPVT_FontMap map(doc, nullptr, std::move(default_font), font_name);
+  CPVT_VariableText::Provider provider(&map);
+  CPVT_VariableText vt(&provider);
+
+  vt.SetPlateRect(body_rect);
+  vt.SetAlignment(annot_dict->GetIntegerFor("Q"));
+  SetVtFontSize(default_appearance_info.value().font_size, vt);
+
+  vt.Initialize();
+  vt.SetText(annot_dict->GetUnicodeTextFor(pdfium::annotation::kContents));
+  vt.RearrangeAll();
+  const CFX_FloatRect content_rect = vt.GetContentRect();
+  CFX_PointF offset(0.0f, (content_rect.Height() - body_rect.Height()) / 2.0f);
+  const ByteString body =
+      GenerateEditAP(vt.GetProvider()->GetFontMap(), vt.GetIterator(), offset,
+                     /*continuous=*/true, /*sub_word=*/0);
+  if (body.GetLength() > 0) {
+    appearance_stream << "/Tx BMC\n" << "q\n";
+    if (content_rect.Width() > body_rect.Width() ||
+        content_rect.Height() > body_rect.Height()) {
+      WriteRect(appearance_stream, body_rect) << " re\nW\nn\n";
+    }
+    appearance_stream << "BT\n"
+                      << GenerateColorAP(text_color, PaintOperation::kFill)
+                      << body << "ET\n"
+                      << "Q\nEMC\n";
+  }
+
+  auto graphics_state_dict = GenerateExtGStateDict(*annot_dict, "Normal");
+  auto resource_font_dict =
+      GenerateResourceFontDict(doc, font_name, font_dict->GetObjNum());
+  auto resource_dict = GenerateResourcesDict(
+      doc, std::move(graphics_state_dict), std::move(resource_font_dict));
+  GenerateAndSetAPDict(doc, annot_dict, &appearance_stream,
+                       std::move(resource_dict),
+                       /*is_text_markup_annotation=*/false);
+  return true;
+}
+
 bool GenerateHighlightAP(CPDF_Document* doc, CPDF_Dictionary* annot_dict) {
   fxcrt::ostringstream app_stream;
   app_stream << "/" << kGSDictName << " gs ";
@@ -1311,22 +1456,13 @@
     return;
   }
 
-  const ByteString default_appearance_string =
-      GetDefaultAppearanceString(annot_dict, form_dict);
-  if (default_appearance_string.IsEmpty()) {
+  std::optional<DefaultAppearanceInfo> default_appearance_info =
+      GetDefaultAppearanceInfo(
+          GetDefaultAppearanceString(annot_dict, form_dict));
+  if (!default_appearance_info.has_value()) {
     return;
   }
 
-  CPDF_DefaultAppearance appearance(default_appearance_string);
-
-  float font_size = 0;
-  std::optional<ByteString> font = appearance.GetFont(&font_size);
-  if (!font.has_value())
-    return;
-
-  ByteString font_name = font.value();
-
-  CFX_Color text_color = fpdfdoc::CFXColorFromString(default_appearance_string);
   RetainPtr<CPDF_Dictionary> dr_dict = form_dict->GetMutableDictFor("DR");
   if (!dr_dict) {
     return;
@@ -1337,13 +1473,9 @@
     return;
   }
 
+  const ByteString& font_name = default_appearance_info.value().font_name;
   RetainPtr<CPDF_Dictionary> font_dict =
-      dr_font_dict->GetMutableDictFor(font_name);
-  if (!font_dict) {
-    font_dict = GenerateFallbackFontDict(doc);
-    dr_font_dict->SetNewFor<CPDF_Reference>(font_name, doc,
-                                            font_dict->GetObjNum());
-  }
+      GetFontFromDrFontDictOrGenerateFallback(doc, dr_font_dict, font_name);
   auto* doc_page_data = CPDF_DocPageData::FromDocument(doc);
   RetainPtr<CPDF_Font> default_font = doc_page_data->GetFont(font_dict);
   if (!default_font) {
@@ -1393,6 +1525,8 @@
     ap_dict->SetNewFor<CPDF_Reference>("N", doc, normal_stream->GetObjNum());
   }
 
+  const float font_size = default_appearance_info.value().font_size;
+  const CFX_Color& text_color = default_appearance_info.value().text_color;
   CPVT_FontMap map(doc, std::move(resources_dict), std::move(default_font),
                    font_name);
   CPVT_VariableText::Provider provider(&map);
@@ -1463,6 +1597,8 @@
   switch (subtype) {
     case CPDF_Annot::Subtype::CIRCLE:
       return GenerateCircleAP(doc, annot_dict);
+    case CPDF_Annot::Subtype::FREETEXT:
+      return GenerateFreeTextAP(doc, annot_dict);
     case CPDF_Annot::Subtype::HIGHLIGHT:
       return GenerateHighlightAP(doc, annot_dict);
     case CPDF_Annot::Subtype::INK:
diff --git a/testing/resources/pixel/freetext_annotation_with_da_expected.pdf.0.png b/testing/resources/pixel/freetext_annotation_with_da_expected.pdf.0.png
index f97e340..feb9983 100644
--- a/testing/resources/pixel/freetext_annotation_with_da_expected.pdf.0.png
+++ b/testing/resources/pixel/freetext_annotation_with_da_expected.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/freetext_annotation_with_da_expected_mac.pdf.0.png b/testing/resources/pixel/freetext_annotation_with_da_expected_mac.pdf.0.png
new file mode 100644
index 0000000..1469799
--- /dev/null
+++ b/testing/resources/pixel/freetext_annotation_with_da_expected_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/freetext_annotation_with_da_expected_skia.pdf.0.png b/testing/resources/pixel/freetext_annotation_with_da_expected_skia.pdf.0.png
new file mode 100644
index 0000000..a77cd69
--- /dev/null
+++ b/testing/resources/pixel/freetext_annotation_with_da_expected_skia.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/freetext_annotation_with_da_expected_skia_mac.pdf.0.png b/testing/resources/pixel/freetext_annotation_with_da_expected_skia_mac.pdf.0.png
new file mode 100644
index 0000000..04e4110
--- /dev/null
+++ b/testing/resources/pixel/freetext_annotation_with_da_expected_skia_mac.pdf.0.png
Binary files differ
diff --git a/testing/resources/pixel/freetext_annotation_with_da_expected_skia_win.pdf.0.png b/testing/resources/pixel/freetext_annotation_with_da_expected_skia_win.pdf.0.png
new file mode 100644
index 0000000..66be929
--- /dev/null
+++ b/testing/resources/pixel/freetext_annotation_with_da_expected_skia_win.pdf.0.png
Binary files differ