// Copyright 2016 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "core/fpdfdoc/cpdf_generateap.h"

#include <algorithm>
#include <sstream>
#include <utility>

#include "constants/annotation_common.h"
#include "constants/appearance.h"
#include "constants/font_encodings.h"
#include "constants/form_fields.h"
#include "core/fpdfapi/font/cpdf_font.h"
#include "core/fpdfapi/page/cpdf_docpagedata.h"
#include "core/fpdfapi/parser/cpdf_array.h"
#include "core/fpdfapi/parser/cpdf_boolean.h"
#include "core/fpdfapi/parser/cpdf_dictionary.h"
#include "core/fpdfapi/parser/cpdf_document.h"
#include "core/fpdfapi/parser/cpdf_name.h"
#include "core/fpdfapi/parser/cpdf_number.h"
#include "core/fpdfapi/parser/cpdf_reference.h"
#include "core/fpdfapi/parser/cpdf_stream.h"
#include "core/fpdfapi/parser/cpdf_string.h"
#include "core/fpdfapi/parser/fpdf_parser_decode.h"
#include "core/fpdfapi/parser/fpdf_parser_utility.h"
#include "core/fpdfdoc/cpdf_annot.h"
#include "core/fpdfdoc/cpdf_color_utils.h"
#include "core/fpdfdoc/cpdf_defaultappearance.h"
#include "core/fpdfdoc/cpdf_formfield.h"
#include "core/fpdfdoc/cpvt_fontmap.h"
#include "core/fpdfdoc/cpvt_variabletext.h"
#include "core/fpdfdoc/cpvt_word.h"
#include "core/fxcrt/fx_string_wrappers.h"
#include "core/fxge/cfx_renderdevice.h"

namespace {

struct CPVT_Dash {
  CPVT_Dash(int32_t dash, int32_t gap, int32_t phase)
      : nDash(dash), nGap(gap), nPhase(phase) {}

  int32_t nDash;
  int32_t nGap;
  int32_t nPhase;
};

enum class PaintOperation { kStroke, kFill };

ByteString GetPDFWordString(IPVT_FontMap* pFontMap,
                            int32_t nFontIndex,
                            uint16_t Word,
                            uint16_t SubWord) {
  if (SubWord > 0)
    return ByteString::Format("%c", SubWord);

  if (!pFontMap)
    return ByteString();

  RetainPtr<CPDF_Font> pPDFFont = pFontMap->GetPDFFont(nFontIndex);
  if (!pPDFFont)
    return ByteString();

  if (pPDFFont->GetBaseFontName() == "Symbol" ||
      pPDFFont->GetBaseFontName() == "ZapfDingbats") {
    return ByteString::Format("%c", Word);
  }

  ByteString sWord;
  uint32_t dwCharCode = pPDFFont->CharCodeFromUnicode(Word);
  if (dwCharCode != CPDF_Font::kInvalidCharCode)
    pPDFFont->AppendChar(&sWord, dwCharCode);

  return sWord;
}

ByteString GetWordRenderString(ByteStringView strWords) {
  if (strWords.IsEmpty())
    return ByteString();
  return PDF_EncodeString(strWords) + " Tj\n";
}

ByteString GetFontSetString(IPVT_FontMap* pFontMap,
                            int32_t nFontIndex,
                            float fFontSize) {
  fxcrt::ostringstream sRet;
  if (pFontMap) {
    ByteString sFontAlias = pFontMap->GetPDFFontAlias(nFontIndex);
    if (sFontAlias.GetLength() > 0 && fFontSize > 0)
      sRet << "/" << sFontAlias << " " << fFontSize << " Tf\n";
  }
  return ByteString(sRet);
}

ByteString GenerateEditAP(IPVT_FontMap* pFontMap,
                          CPVT_VariableText::Iterator* pIterator,
                          const CFX_PointF& ptOffset,
                          bool bContinuous,
                          uint16_t SubWord) {
  fxcrt::ostringstream sEditStream;
  fxcrt::ostringstream sLineStream;
  CFX_PointF ptOld;
  CFX_PointF ptNew;
  int32_t nCurFontIndex = -1;
  CPVT_WordPlace oldplace;
  ByteString sWords;
  pIterator->SetAt(0);
  while (pIterator->NextWord()) {
    CPVT_WordPlace place = pIterator->GetWordPlace();
    if (bContinuous) {
      if (place.LineCmp(oldplace) != 0) {
        if (!sWords.IsEmpty()) {
          sLineStream << GetWordRenderString(sWords.AsStringView());
          sEditStream << sLineStream.str();
          sLineStream.str("");
          sWords.clear();
        }
        CPVT_Word word;
        if (pIterator->GetWord(word)) {
          ptNew = CFX_PointF(word.ptWord.x + ptOffset.x,
                             word.ptWord.y + ptOffset.y);
        } else {
          CPVT_Line line;
          pIterator->GetLine(line);
          ptNew = CFX_PointF(line.ptLine.x + ptOffset.x,
                             line.ptLine.y + ptOffset.y);
        }
        if (ptNew != ptOld) {
          sLineStream << ptNew.x - ptOld.x << " " << ptNew.y - ptOld.y
                      << " Td\n";
          ptOld = ptNew;
        }
      }
      CPVT_Word word;
      if (pIterator->GetWord(word)) {
        if (word.nFontIndex != nCurFontIndex) {
          if (!sWords.IsEmpty()) {
            sLineStream << GetWordRenderString(sWords.AsStringView());
            sWords.clear();
          }
          sLineStream << GetFontSetString(pFontMap, word.nFontIndex,
                                          word.fFontSize);
          nCurFontIndex = word.nFontIndex;
        }
        sWords += GetPDFWordString(pFontMap, nCurFontIndex, word.Word, SubWord);
      }
      oldplace = place;
    } else {
      CPVT_Word word;
      if (pIterator->GetWord(word)) {
        ptNew =
            CFX_PointF(word.ptWord.x + ptOffset.x, word.ptWord.y + ptOffset.y);
        if (ptNew != ptOld) {
          sEditStream << ptNew.x - ptOld.x << " " << ptNew.y - ptOld.y
                      << " Td\n";
          ptOld = ptNew;
        }
        if (word.nFontIndex != nCurFontIndex) {
          sEditStream << GetFontSetString(pFontMap, word.nFontIndex,
                                          word.fFontSize);
          nCurFontIndex = word.nFontIndex;
        }
        sEditStream << GetWordRenderString(
            GetPDFWordString(pFontMap, nCurFontIndex, word.Word, SubWord)
                .AsStringView());
      }
    }
  }
  if (!sWords.IsEmpty()) {
    sLineStream << GetWordRenderString(sWords.AsStringView());
    sEditStream << sLineStream.str();
  }
  return ByteString(sEditStream);
}

ByteString GenerateColorAP(const CFX_Color& color, PaintOperation nOperation) {
  fxcrt::ostringstream sColorStream;
  switch (color.nColorType) {
    case CFX_Color::Type::kRGB:
      sColorStream << color.fColor1 << " " << color.fColor2 << " "
                   << color.fColor3 << " "
                   << (nOperation == PaintOperation::kStroke ? "RG" : "rg")
                   << "\n";
      break;
    case CFX_Color::Type::kGray:
      sColorStream << color.fColor1 << " "
                   << (nOperation == PaintOperation::kStroke ? "G" : "g")
                   << "\n";
      break;
    case CFX_Color::Type::kCMYK:
      sColorStream << color.fColor1 << " " << color.fColor2 << " "
                   << color.fColor3 << " " << color.fColor4 << " "
                   << (nOperation == PaintOperation::kStroke ? "K" : "k")
                   << "\n";
      break;
    case CFX_Color::Type::kTransparent:
      break;
  }
  return ByteString(sColorStream);
}

ByteString GenerateBorderAP(const CFX_FloatRect& rect,
                            float fWidth,
                            const CFX_Color& color,
                            const CFX_Color& crLeftTop,
                            const CFX_Color& crRightBottom,
                            BorderStyle nStyle,
                            const CPVT_Dash& dash) {
  fxcrt::ostringstream sAppStream;
  ByteString sColor;
  float fLeft = rect.left;
  float fRight = rect.right;
  float fTop = rect.top;
  float fBottom = rect.bottom;
  if (fWidth > 0.0f) {
    float fHalfWidth = fWidth / 2.0f;
    switch (nStyle) {
      case BorderStyle::kSolid:
        sColor = GenerateColorAP(color, PaintOperation::kFill);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          sAppStream << fLeft << " " << fBottom << " " << fRight - fLeft << " "
                     << fTop - fBottom << " re\n";
          sAppStream << fLeft + fWidth << " " << fBottom + fWidth << " "
                     << fRight - fLeft - fWidth * 2 << " "
                     << fTop - fBottom - fWidth * 2 << " re\n";
          sAppStream << "f*\n";
        }
        break;
      case BorderStyle::kDash:
        sColor = GenerateColorAP(color, PaintOperation::kStroke);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          sAppStream << fWidth << " w"
                     << " [" << dash.nDash << " " << dash.nGap << "] "
                     << dash.nPhase << " d\n";
          sAppStream << fLeft + fWidth / 2 << " " << fBottom + fWidth / 2
                     << " m\n";
          sAppStream << fLeft + fWidth / 2 << " " << fTop - fWidth / 2
                     << " l\n";
          sAppStream << fRight - fWidth / 2 << " " << fTop - fWidth / 2
                     << " l\n";
          sAppStream << fRight - fWidth / 2 << " " << fBottom + fWidth / 2
                     << " l\n";
          sAppStream << fLeft + fWidth / 2 << " " << fBottom + fWidth / 2
                     << " l S\n";
        }
        break;
      case BorderStyle::kBeveled:
      case BorderStyle::kInset:
        sColor = GenerateColorAP(crLeftTop, PaintOperation::kFill);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          sAppStream << fLeft + fHalfWidth << " " << fBottom + fHalfWidth
                     << " m\n";
          sAppStream << fLeft + fHalfWidth << " " << fTop - fHalfWidth
                     << " l\n";
          sAppStream << fRight - fHalfWidth << " " << fTop - fHalfWidth
                     << " l\n";
          sAppStream << fRight - fHalfWidth * 2 << " " << fTop - fHalfWidth * 2
                     << " l\n";
          sAppStream << fLeft + fHalfWidth * 2 << " " << fTop - fHalfWidth * 2
                     << " l\n";
          sAppStream << fLeft + fHalfWidth * 2 << " "
                     << fBottom + fHalfWidth * 2 << " l f\n";
        }
        sColor = GenerateColorAP(crRightBottom, PaintOperation::kFill);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          sAppStream << fRight - fHalfWidth << " " << fTop - fHalfWidth
                     << " m\n";
          sAppStream << fRight - fHalfWidth << " " << fBottom + fHalfWidth
                     << " l\n";
          sAppStream << fLeft + fHalfWidth << " " << fBottom + fHalfWidth
                     << " l\n";
          sAppStream << fLeft + fHalfWidth * 2 << " "
                     << fBottom + fHalfWidth * 2 << " l\n";
          sAppStream << fRight - fHalfWidth * 2 << " "
                     << fBottom + fHalfWidth * 2 << " l\n";
          sAppStream << fRight - fHalfWidth * 2 << " " << fTop - fHalfWidth * 2
                     << " l f\n";
        }
        sColor = GenerateColorAP(color, PaintOperation::kFill);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          sAppStream << fLeft << " " << fBottom << " " << fRight - fLeft << " "
                     << fTop - fBottom << " re\n";
          sAppStream << fLeft + fHalfWidth << " " << fBottom + fHalfWidth << " "
                     << fRight - fLeft - fHalfWidth * 2 << " "
                     << fTop - fBottom - fHalfWidth * 2 << " re f*\n";
        }
        break;
      case BorderStyle::kUnderline:
        sColor = GenerateColorAP(color, PaintOperation::kStroke);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          sAppStream << fWidth << " w\n";
          sAppStream << fLeft << " " << fBottom + fWidth / 2 << " m\n";
          sAppStream << fRight << " " << fBottom + fWidth / 2 << " l S\n";
        }
        break;
    }
  }
  return ByteString(sAppStream);
}

ByteString GetColorStringWithDefault(const CPDF_Array* pColor,
                                     const CFX_Color& crDefaultColor,
                                     PaintOperation nOperation) {
  if (pColor) {
    CFX_Color color = fpdfdoc::CFXColorFromArray(*pColor);
    return GenerateColorAP(color, nOperation);
  }

  return GenerateColorAP(crDefaultColor, nOperation);
}

float GetBorderWidth(const CPDF_Dictionary* pDict) {
  RetainPtr<const CPDF_Dictionary> pBorderStyleDict = pDict->GetDictFor("BS");
  if (pBorderStyleDict && pBorderStyleDict->KeyExist("W"))
    return pBorderStyleDict->GetFloatFor("W");

  auto pBorderArray = pDict->GetArrayFor(pdfium::annotation::kBorder);
  if (pBorderArray && pBorderArray->size() > 2)
    return pBorderArray->GetFloatAt(2);

  return 1;
}

RetainPtr<const CPDF_Array> GetDashArray(const CPDF_Dictionary* pDict) {
  RetainPtr<const CPDF_Dictionary> pBorderStyleDict = pDict->GetDictFor("BS");
  if (pBorderStyleDict && pBorderStyleDict->GetByteStringFor("S") == "D")
    return pBorderStyleDict->GetArrayFor("D");

  RetainPtr<const CPDF_Array> pBorderArray =
      pDict->GetArrayFor(pdfium::annotation::kBorder);
  if (pBorderArray && pBorderArray->size() == 4)
    return pBorderArray->GetArrayAt(3);

  return nullptr;
}

ByteString GetDashPatternString(const CPDF_Dictionary* pDict) {
  RetainPtr<const CPDF_Array> pDashArray = GetDashArray(pDict);
  if (!pDashArray || pDashArray->IsEmpty())
    return ByteString();

  // Support maximum of ten elements in the dash array.
  size_t pDashArrayCount = std::min<size_t>(pDashArray->size(), 10);
  fxcrt::ostringstream sDashStream;

  sDashStream << "[";
  for (size_t i = 0; i < pDashArrayCount; ++i)
    sDashStream << pDashArray->GetFloatAt(i) << " ";
  sDashStream << "] 0 d\n";

  return ByteString(sDashStream);
}

ByteString GetPopupContentsString(CPDF_Document* pDoc,
                                  const CPDF_Dictionary& pAnnotDict,
                                  RetainPtr<CPDF_Font> pDefFont,
                                  const ByteString& sFontName) {
  WideString swValue(pAnnotDict.GetUnicodeTextFor(pdfium::form_fields::kT));
  swValue += L'\n';
  swValue += pAnnotDict.GetUnicodeTextFor(pdfium::annotation::kContents);

  CPVT_FontMap map(pDoc, nullptr, std::move(pDefFont), sFontName);
  CPVT_VariableText::Provider prd(&map);
  CPVT_VariableText vt(&prd);
  vt.SetPlateRect(pAnnotDict.GetRectFor(pdfium::annotation::kRect));
  vt.SetFontSize(12);
  vt.SetAutoReturn(true);
  vt.SetMultiLine(true);
  vt.Initialize();
  vt.SetText(swValue);
  vt.RearrangeAll();

  CFX_PointF ptOffset(3.0f, -3.0f);
  ByteString sContent =
      GenerateEditAP(&map, vt.GetIterator(), ptOffset, false, 0);

  if (sContent.IsEmpty())
    return ByteString();

  ByteString sColorAP = GenerateColorAP(
      CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0), PaintOperation::kFill);

  return ByteString{"BT\n", sColorAP.AsStringView(), sContent.AsStringView(),
                    "ET\n", "Q\n"};
}

RetainPtr<CPDF_Dictionary> GenerateResourceFontDict(
    CPDF_Document* pDoc,
    const ByteString& sFontDictName) {
  auto pFontDict = pDoc->NewIndirect<CPDF_Dictionary>();
  pFontDict->SetNewFor<CPDF_Name>("Type", "Font");
  pFontDict->SetNewFor<CPDF_Name>("Subtype", "Type1");
  pFontDict->SetNewFor<CPDF_Name>("BaseFont", CFX_Font::kDefaultAnsiFontName);
  pFontDict->SetNewFor<CPDF_Name>("Encoding",
                                  pdfium::font_encodings::kWinAnsiEncoding);

  auto pResourceFontDict = pDoc->New<CPDF_Dictionary>();
  pResourceFontDict->SetNewFor<CPDF_Reference>(sFontDictName, pDoc,
                                               pFontDict->GetObjNum());
  return pResourceFontDict;
}

ByteString GetPaintOperatorString(bool bIsStrokeRect, bool bIsFillRect) {
  if (bIsStrokeRect)
    return bIsFillRect ? "b" : "s";
  return bIsFillRect ? "f" : "n";
}

ByteString GenerateTextSymbolAP(const CFX_FloatRect& rect) {
  fxcrt::ostringstream sAppStream;
  sAppStream << GenerateColorAP(CFX_Color(CFX_Color::Type::kRGB, 1, 1, 0),
                                PaintOperation::kFill);
  sAppStream << GenerateColorAP(CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0),
                                PaintOperation::kStroke);

  const float fBorderWidth = 1;
  sAppStream << fBorderWidth << " w\n";

  const float fHalfWidth = fBorderWidth / 2;
  const float fTipDelta = 4;

  CFX_FloatRect outerRect1 = rect;
  outerRect1.Deflate(fHalfWidth, fHalfWidth);
  outerRect1.bottom += fTipDelta;

  CFX_FloatRect outerRect2 = outerRect1;
  outerRect2.left += fTipDelta;
  outerRect2.right = outerRect2.left + fTipDelta;
  outerRect2.top = outerRect2.bottom - fTipDelta;
  float outerRect2Middle = (outerRect2.left + outerRect2.right) / 2;

  // Draw outer boxes.
  sAppStream << outerRect1.left << " " << outerRect1.bottom << " m\n"
             << outerRect1.left << " " << outerRect1.top << " l\n"
             << outerRect1.right << " " << outerRect1.top << " l\n"
             << outerRect1.right << " " << outerRect1.bottom << " l\n"
             << outerRect2.right << " " << outerRect2.bottom << " l\n"
             << outerRect2Middle << " " << outerRect2.top << " l\n"
             << outerRect2.left << " " << outerRect2.bottom << " l\n"
             << outerRect1.left << " " << outerRect1.bottom << " l\n";

  // Draw inner lines.
  CFX_FloatRect lineRect = outerRect1;
  const float fXDelta = 2;
  const float fYDelta = (lineRect.top - lineRect.bottom) / 4;

  lineRect.left += fXDelta;
  lineRect.right -= fXDelta;
  for (int i = 0; i < 3; ++i) {
    lineRect.top -= fYDelta;
    sAppStream << lineRect.left << " " << lineRect.top << " m\n"
               << lineRect.right << " " << lineRect.top << " l\n";
  }
  sAppStream << "B*\n";

  return ByteString(sAppStream);
}

RetainPtr<CPDF_Dictionary> GenerateExtGStateDict(
    const CPDF_Dictionary& pAnnotDict,
    const ByteString& sExtGSDictName,
    const ByteString& sBlendMode) {
  auto pGSDict =
      pdfium::MakeRetain<CPDF_Dictionary>(pAnnotDict.GetByteStringPool());
  pGSDict->SetNewFor<CPDF_Name>("Type", "ExtGState");

  float fOpacity = pAnnotDict.KeyExist("CA") ? pAnnotDict.GetFloatFor("CA") : 1;
  pGSDict->SetNewFor<CPDF_Number>("CA", fOpacity);
  pGSDict->SetNewFor<CPDF_Number>("ca", fOpacity);
  pGSDict->SetNewFor<CPDF_Boolean>("AIS", false);
  pGSDict->SetNewFor<CPDF_Name>("BM", sBlendMode);

  auto pExtGStateDict =
      pdfium::MakeRetain<CPDF_Dictionary>(pAnnotDict.GetByteStringPool());
  pExtGStateDict->SetFor(sExtGSDictName, pGSDict);
  return pExtGStateDict;
}

RetainPtr<CPDF_Dictionary> GenerateResourceDict(
    CPDF_Document* pDoc,
    RetainPtr<CPDF_Dictionary> pExtGStateDict,
    RetainPtr<CPDF_Dictionary> pResourceFontDict) {
  auto pResourceDict = pDoc->New<CPDF_Dictionary>();
  if (pExtGStateDict)
    pResourceDict->SetFor("ExtGState", pExtGStateDict);
  if (pResourceFontDict)
    pResourceDict->SetFor("Font", pResourceFontDict);
  return pResourceDict;
}

void GenerateAndSetAPDict(CPDF_Document* pDoc,
                          CPDF_Dictionary* pAnnotDict,
                          fxcrt::ostringstream* psAppStream,
                          RetainPtr<CPDF_Dictionary> pResourceDict,
                          bool bIsTextMarkupAnnotation) {
  auto pNormalStream = pDoc->NewIndirect<CPDF_Stream>();
  pNormalStream->SetDataFromStringstream(psAppStream);

  RetainPtr<CPDF_Dictionary> pAPDict =
      pAnnotDict->GetOrCreateDictFor(pdfium::annotation::kAP);
  pAPDict->SetNewFor<CPDF_Reference>("N", pDoc, pNormalStream->GetObjNum());

  RetainPtr<CPDF_Dictionary> pStreamDict = pNormalStream->GetMutableDict();
  pStreamDict->SetNewFor<CPDF_Number>("FormType", 1);
  pStreamDict->SetNewFor<CPDF_Name>("Type", "XObject");
  pStreamDict->SetNewFor<CPDF_Name>("Subtype", "Form");
  pStreamDict->SetMatrixFor("Matrix", CFX_Matrix());

  CFX_FloatRect rect = bIsTextMarkupAnnotation
                           ? CPDF_Annot::BoundingRectFromQuadPoints(pAnnotDict)
                           : pAnnotDict->GetRectFor(pdfium::annotation::kRect);
  pStreamDict->SetRectFor("BBox", rect);
  pStreamDict->SetFor("Resources", pResourceDict);
}

bool GenerateCircleAP(CPDF_Document* pDoc, CPDF_Dictionary* pAnnotDict) {
  fxcrt::ostringstream sAppStream;
  ByteString sExtGSDictName = "GS";
  sAppStream << "/" << sExtGSDictName << " gs ";

  RetainPtr<const CPDF_Array> pInteriorColor = pAnnotDict->GetArrayFor("IC");
  sAppStream << GetColorStringWithDefault(
      pInteriorColor.Get(), CFX_Color(CFX_Color::Type::kTransparent),
      PaintOperation::kFill);

  sAppStream << GetColorStringWithDefault(
      pAnnotDict->GetArrayFor(pdfium::annotation::kC).Get(),
      CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0), PaintOperation::kStroke);

  float fBorderWidth = GetBorderWidth(pAnnotDict);
  bool bIsStrokeRect = fBorderWidth > 0;

  if (bIsStrokeRect) {
    sAppStream << fBorderWidth << " w ";
    sAppStream << GetDashPatternString(pAnnotDict);
  }

  CFX_FloatRect rect = pAnnotDict->GetRectFor(pdfium::annotation::kRect);
  rect.Normalize();

  if (bIsStrokeRect) {
    // Deflating rect because stroking a path entails painting all points
    // whose perpendicular distance from the path in user space is less than
    // or equal to half the line width.
    rect.Deflate(fBorderWidth / 2, fBorderWidth / 2);
  }

  const float fMiddleX = (rect.left + rect.right) / 2;
  const float fMiddleY = (rect.top + rect.bottom) / 2;

  // |fL| is precalculated approximate value of 4 * tan((3.14 / 2) / 4) / 3,
  // where |fL| * radius is a good approximation of control points for
  // arc with 90 degrees.
  const float fL = 0.5523f;
  const float fDeltaX = fL * rect.Width() / 2.0;
  const float fDeltaY = fL * rect.Height() / 2.0;

  // Starting point
  sAppStream << fMiddleX << " " << rect.top << " m\n";
  // First Bezier Curve
  sAppStream << fMiddleX + fDeltaX << " " << rect.top << " " << rect.right
             << " " << fMiddleY + fDeltaY << " " << rect.right << " "
             << fMiddleY << " c\n";
  // Second Bezier Curve
  sAppStream << rect.right << " " << fMiddleY - fDeltaY << " "
             << fMiddleX + fDeltaX << " " << rect.bottom << " " << fMiddleX
             << " " << rect.bottom << " c\n";
  // Third Bezier Curve
  sAppStream << fMiddleX - fDeltaX << " " << rect.bottom << " " << rect.left
             << " " << fMiddleY - fDeltaY << " " << rect.left << " " << fMiddleY
             << " c\n";
  // Fourth Bezier Curve
  sAppStream << rect.left << " " << fMiddleY + fDeltaY << " "
             << fMiddleX - fDeltaX << " " << rect.top << " " << fMiddleX << " "
             << rect.top << " c\n";

  bool bIsFillRect = pInteriorColor && !pInteriorColor->IsEmpty();
  sAppStream << GetPaintOperatorString(bIsStrokeRect, bIsFillRect) << "\n";

  auto pExtGStateDict =
      GenerateExtGStateDict(*pAnnotDict, sExtGSDictName, "Normal");
  auto pResourceDict =
      GenerateResourceDict(pDoc, std::move(pExtGStateDict), nullptr);
  GenerateAndSetAPDict(pDoc, pAnnotDict, &sAppStream, std::move(pResourceDict),
                       false /*IsTextMarkupAnnotation*/);
  return true;
}

bool GenerateHighlightAP(CPDF_Document* pDoc, CPDF_Dictionary* pAnnotDict) {
  fxcrt::ostringstream sAppStream;
  ByteString sExtGSDictName = "GS";
  sAppStream << "/" << sExtGSDictName << " gs ";

  sAppStream << GetColorStringWithDefault(
      pAnnotDict->GetArrayFor(pdfium::annotation::kC).Get(),
      CFX_Color(CFX_Color::Type::kRGB, 1, 1, 0), PaintOperation::kFill);

  RetainPtr<const CPDF_Array> pArray = pAnnotDict->GetArrayFor("QuadPoints");
  if (pArray) {
    size_t nQuadPointCount = CPDF_Annot::QuadPointCount(pArray.Get());
    for (size_t i = 0; i < nQuadPointCount; ++i) {
      CFX_FloatRect rect = CPDF_Annot::RectFromQuadPoints(pAnnotDict, i);
      rect.Normalize();

      sAppStream << rect.left << " " << rect.top << " m " << rect.right << " "
                 << rect.top << " l " << rect.right << " " << rect.bottom
                 << " l " << rect.left << " " << rect.bottom << " l h f\n";
    }
  }

  auto pExtGStateDict =
      GenerateExtGStateDict(*pAnnotDict, sExtGSDictName, "Multiply");
  auto pResourceDict =
      GenerateResourceDict(pDoc, std::move(pExtGStateDict), nullptr);
  GenerateAndSetAPDict(pDoc, pAnnotDict, &sAppStream, std::move(pResourceDict),
                       true /*IsTextMarkupAnnotation*/);

  return true;
}

bool GenerateInkAP(CPDF_Document* pDoc, CPDF_Dictionary* pAnnotDict) {
  RetainPtr<const CPDF_Array> pInkList = pAnnotDict->GetArrayFor("InkList");
  if (!pInkList || pInkList->IsEmpty())
    return false;

  float fBorderWidth = GetBorderWidth(pAnnotDict);
  const bool bIsStroke = fBorderWidth > 0;
  if (!bIsStroke)
    return false;

  ByteString sExtGSDictName = "GS";
  fxcrt::ostringstream sAppStream;
  sAppStream << "/" << sExtGSDictName << " gs ";
  sAppStream << GetColorStringWithDefault(
      pAnnotDict->GetArrayFor(pdfium::annotation::kC).Get(),
      CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0), PaintOperation::kStroke);

  sAppStream << fBorderWidth << " w ";
  sAppStream << GetDashPatternString(pAnnotDict);

  // Set inflated rect as a new rect because paths near the border with large
  // width should not be clipped to the original rect.
  CFX_FloatRect rect = pAnnotDict->GetRectFor(pdfium::annotation::kRect);
  rect.Inflate(fBorderWidth / 2, fBorderWidth / 2);
  pAnnotDict->SetRectFor(pdfium::annotation::kRect, rect);

  for (size_t i = 0; i < pInkList->size(); i++) {
    RetainPtr<const CPDF_Array> pInkCoordList = pInkList->GetArrayAt(i);
    if (!pInkCoordList || pInkCoordList->size() < 2)
      continue;

    sAppStream << pInkCoordList->GetFloatAt(0) << " "
               << pInkCoordList->GetFloatAt(1) << " m ";

    for (size_t j = 0; j < pInkCoordList->size() - 1; j += 2) {
      sAppStream << pInkCoordList->GetFloatAt(j) << " "
                 << pInkCoordList->GetFloatAt(j + 1) << " l ";
    }

    sAppStream << "S\n";
  }

  auto pExtGStateDict =
      GenerateExtGStateDict(*pAnnotDict, sExtGSDictName, "Normal");
  auto pResourceDict =
      GenerateResourceDict(pDoc, std::move(pExtGStateDict), nullptr);
  GenerateAndSetAPDict(pDoc, pAnnotDict, &sAppStream, std::move(pResourceDict),
                       false /*IsTextMarkupAnnotation*/);
  return true;
}

bool GenerateTextAP(CPDF_Document* pDoc, CPDF_Dictionary* pAnnotDict) {
  fxcrt::ostringstream sAppStream;
  ByteString sExtGSDictName = "GS";
  sAppStream << "/" << sExtGSDictName << " gs ";

  CFX_FloatRect rect = pAnnotDict->GetRectFor(pdfium::annotation::kRect);
  const float fNoteLength = 20;
  CFX_FloatRect noteRect(rect.left, rect.bottom, rect.left + fNoteLength,
                         rect.bottom + fNoteLength);
  pAnnotDict->SetRectFor(pdfium::annotation::kRect, noteRect);

  sAppStream << GenerateTextSymbolAP(noteRect);

  auto pExtGStateDict =
      GenerateExtGStateDict(*pAnnotDict, sExtGSDictName, "Normal");
  auto pResourceDict =
      GenerateResourceDict(pDoc, std::move(pExtGStateDict), nullptr);
  GenerateAndSetAPDict(pDoc, pAnnotDict, &sAppStream, std::move(pResourceDict),
                       false /*IsTextMarkupAnnotation*/);
  return true;
}

bool GenerateUnderlineAP(CPDF_Document* pDoc, CPDF_Dictionary* pAnnotDict) {
  fxcrt::ostringstream sAppStream;
  ByteString sExtGSDictName = "GS";
  sAppStream << "/" << sExtGSDictName << " gs ";

  sAppStream << GetColorStringWithDefault(
      pAnnotDict->GetArrayFor(pdfium::annotation::kC).Get(),
      CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0), PaintOperation::kStroke);

  RetainPtr<const CPDF_Array> pArray = pAnnotDict->GetArrayFor("QuadPoints");
  if (pArray) {
    static constexpr float kLineWidth = 1.0f;
    sAppStream << kLineWidth << " w ";
    size_t nQuadPointCount = CPDF_Annot::QuadPointCount(pArray.Get());
    for (size_t i = 0; i < nQuadPointCount; ++i) {
      CFX_FloatRect rect = CPDF_Annot::RectFromQuadPoints(pAnnotDict, i);
      rect.Normalize();
      sAppStream << rect.left << " " << rect.bottom + kLineWidth << " m "
                 << rect.right << " " << rect.bottom + kLineWidth << " l S\n";
    }
  }

  auto pExtGStateDict =
      GenerateExtGStateDict(*pAnnotDict, sExtGSDictName, "Normal");
  auto pResourceDict =
      GenerateResourceDict(pDoc, std::move(pExtGStateDict), nullptr);
  GenerateAndSetAPDict(pDoc, pAnnotDict, &sAppStream, std::move(pResourceDict),
                       true /*IsTextMarkupAnnotation*/);
  return true;
}

bool GeneratePopupAP(CPDF_Document* pDoc, CPDF_Dictionary* pAnnotDict) {
  fxcrt::ostringstream sAppStream;
  ByteString sExtGSDictName = "GS";
  sAppStream << "/" << sExtGSDictName << " gs\n";

  sAppStream << GenerateColorAP(CFX_Color(CFX_Color::Type::kRGB, 1, 1, 0),
                                PaintOperation::kFill);
  sAppStream << GenerateColorAP(CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0),
                                PaintOperation::kStroke);

  const float fBorderWidth = 1;
  sAppStream << fBorderWidth << " w\n";

  CFX_FloatRect rect = pAnnotDict->GetRectFor(pdfium::annotation::kRect);
  rect.Normalize();
  rect.Deflate(fBorderWidth / 2, fBorderWidth / 2);

  sAppStream << rect.left << " " << rect.bottom << " " << rect.Width() << " "
             << rect.Height() << " re b\n";

  ByteString sFontName = "FONT";
  RetainPtr<CPDF_Dictionary> pResourceFontDict =
      GenerateResourceFontDict(pDoc, sFontName);

  auto* pData = CPDF_DocPageData::FromDocument(pDoc);
  RetainPtr<CPDF_Font> pDefFont = pData->GetFont(pResourceFontDict);
  if (!pDefFont)
    return false;

  RetainPtr<CPDF_Dictionary> pExtGStateDict =
      GenerateExtGStateDict(*pAnnotDict, sExtGSDictName, "Normal");
  RetainPtr<CPDF_Dictionary> pResourceDict = GenerateResourceDict(
      pDoc, std::move(pExtGStateDict), std::move(pResourceFontDict));

  sAppStream << GetPopupContentsString(pDoc, *pAnnotDict, std::move(pDefFont),
                                       sFontName);
  GenerateAndSetAPDict(pDoc, pAnnotDict, &sAppStream, std::move(pResourceDict),
                       false /*IsTextMarkupAnnotation*/);
  return true;
}

bool GenerateSquareAP(CPDF_Document* pDoc, CPDF_Dictionary* pAnnotDict) {
  const ByteString sExtGSDictName = "GS";
  fxcrt::ostringstream sAppStream;
  sAppStream << "/" << sExtGSDictName << " gs ";

  RetainPtr<const CPDF_Array> pInteriorColor = pAnnotDict->GetArrayFor("IC");
  sAppStream << GetColorStringWithDefault(
      pInteriorColor.Get(), CFX_Color(CFX_Color::Type::kTransparent),
      PaintOperation::kFill);

  sAppStream << GetColorStringWithDefault(
      pAnnotDict->GetArrayFor(pdfium::annotation::kC).Get(),
      CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0), PaintOperation::kStroke);

  float fBorderWidth = GetBorderWidth(pAnnotDict);
  const bool bIsStrokeRect = fBorderWidth > 0;
  if (bIsStrokeRect) {
    sAppStream << fBorderWidth << " w ";
    sAppStream << GetDashPatternString(pAnnotDict);
  }

  CFX_FloatRect rect = pAnnotDict->GetRectFor(pdfium::annotation::kRect);
  rect.Normalize();

  if (bIsStrokeRect) {
    // Deflating rect because stroking a path entails painting all points
    // whose perpendicular distance from the path in user space is less than
    // or equal to half the line width.
    rect.Deflate(fBorderWidth / 2, fBorderWidth / 2);
  }

  const bool bIsFillRect = pInteriorColor && (pInteriorColor->size() > 0);
  sAppStream << rect.left << " " << rect.bottom << " " << rect.Width() << " "
             << rect.Height() << " re "
             << GetPaintOperatorString(bIsStrokeRect, bIsFillRect) << "\n";

  auto pExtGStateDict =
      GenerateExtGStateDict(*pAnnotDict, sExtGSDictName, "Normal");
  auto pResourceDict =
      GenerateResourceDict(pDoc, std::move(pExtGStateDict), nullptr);
  GenerateAndSetAPDict(pDoc, pAnnotDict, &sAppStream, std::move(pResourceDict),
                       false /*IsTextMarkupAnnotation*/);
  return true;
}

bool GenerateSquigglyAP(CPDF_Document* pDoc, CPDF_Dictionary* pAnnotDict) {
  fxcrt::ostringstream sAppStream;
  ByteString sExtGSDictName = "GS";
  sAppStream << "/" << sExtGSDictName << " gs ";

  sAppStream << GetColorStringWithDefault(
      pAnnotDict->GetArrayFor(pdfium::annotation::kC).Get(),
      CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0), PaintOperation::kStroke);

  RetainPtr<const CPDF_Array> pArray = pAnnotDict->GetArrayFor("QuadPoints");
  if (pArray) {
    static constexpr float kLineWidth = 1.0f;
    static constexpr float kDelta = 2.0f;
    sAppStream << kLineWidth << " w ";
    size_t nQuadPointCount = CPDF_Annot::QuadPointCount(pArray.Get());
    for (size_t i = 0; i < nQuadPointCount; ++i) {
      CFX_FloatRect rect = CPDF_Annot::RectFromQuadPoints(pAnnotDict, i);
      rect.Normalize();

      const float fTop = rect.bottom + kDelta;
      const float fBottom = rect.bottom;
      sAppStream << rect.left << " " << fTop << " m ";

      float fX = rect.left + kDelta;
      bool isUpwards = false;
      while (fX < rect.right) {
        sAppStream << fX << " " << (isUpwards ? fTop : fBottom) << " l ";
        fX += kDelta;
        isUpwards = !isUpwards;
      }

      float fRemainder = rect.right - (fX - kDelta);
      if (isUpwards)
        sAppStream << rect.right << " " << fBottom + fRemainder << " l ";
      else
        sAppStream << rect.right << " " << fTop - fRemainder << " l ";

      sAppStream << "S\n";
    }
  }

  auto pExtGStateDict =
      GenerateExtGStateDict(*pAnnotDict, sExtGSDictName, "Normal");
  auto pResourceDict =
      GenerateResourceDict(pDoc, std::move(pExtGStateDict), nullptr);
  GenerateAndSetAPDict(pDoc, pAnnotDict, &sAppStream, std::move(pResourceDict),
                       true /*IsTextMarkupAnnotation*/);
  return true;
}

bool GenerateStrikeOutAP(CPDF_Document* pDoc, CPDF_Dictionary* pAnnotDict) {
  fxcrt::ostringstream sAppStream;
  ByteString sExtGSDictName = "GS";
  sAppStream << "/" << sExtGSDictName << " gs ";

  sAppStream << GetColorStringWithDefault(
      pAnnotDict->GetArrayFor(pdfium::annotation::kC).Get(),
      CFX_Color(CFX_Color::Type::kRGB, 0, 0, 0), PaintOperation::kStroke);

  RetainPtr<const CPDF_Array> pArray = pAnnotDict->GetArrayFor("QuadPoints");
  if (pArray) {
    static constexpr float kLineWidth = 1.0f;
    size_t nQuadPointCount = CPDF_Annot::QuadPointCount(pArray.Get());
    for (size_t i = 0; i < nQuadPointCount; ++i) {
      CFX_FloatRect rect = CPDF_Annot::RectFromQuadPoints(pAnnotDict, i);
      rect.Normalize();

      float fY = (rect.top + rect.bottom) / 2;
      sAppStream << kLineWidth << " w " << rect.left << " " << fY << " m "
                 << rect.right << " " << fY << " l S\n";
    }
  }

  auto pExtGStateDict =
      GenerateExtGStateDict(*pAnnotDict, sExtGSDictName, "Normal");
  auto pResourceDict =
      GenerateResourceDict(pDoc, std::move(pExtGStateDict), nullptr);
  GenerateAndSetAPDict(pDoc, pAnnotDict, &sAppStream, std::move(pResourceDict),
                       true /*IsTextMarkupAnnotation*/);
  return true;
}

}  // namespace

// static
void CPDF_GenerateAP::GenerateFormAP(CPDF_Document* pDoc,
                                     CPDF_Dictionary* pAnnotDict,
                                     FormType type) {
  RetainPtr<CPDF_Dictionary> pRootDict = pDoc->GetMutableRoot();
  if (!pRootDict)
    return;

  RetainPtr<CPDF_Dictionary> pFormDict =
      pRootDict->GetMutableDictFor("AcroForm");
  if (!pFormDict)
    return;

  ByteString DA;
  RetainPtr<const CPDF_Object> pDAObj =
      CPDF_FormField::GetFieldAttrForDict(pAnnotDict, "DA");
  if (pDAObj)
    DA = pDAObj->GetString();
  if (DA.IsEmpty())
    DA = pFormDict->GetByteStringFor("DA");
  if (DA.IsEmpty())
    return;

  CPDF_DefaultAppearance appearance(DA);

  float fFontSize = 0;
  absl::optional<ByteString> font = appearance.GetFont(&fFontSize);
  if (!font.has_value())
    return;

  ByteString font_name = font.value();

  CFX_Color crText = fpdfdoc::CFXColorFromString(DA);
  RetainPtr<CPDF_Dictionary> pDRDict = pFormDict->GetMutableDictFor("DR");
  if (!pDRDict)
    return;

  RetainPtr<CPDF_Dictionary> pDRFontDict = pDRDict->GetMutableDictFor("Font");
  if (!ValidateFontResourceDict(pDRFontDict.Get()))
    return;

  RetainPtr<CPDF_Dictionary> pFontDict =
      pDRFontDict->GetMutableDictFor(font_name);
  if (!pFontDict) {
    pFontDict = pDoc->NewIndirect<CPDF_Dictionary>();
    pFontDict->SetNewFor<CPDF_Name>("Type", "Font");
    pFontDict->SetNewFor<CPDF_Name>("Subtype", "Type1");
    pFontDict->SetNewFor<CPDF_Name>("BaseFont", CFX_Font::kDefaultAnsiFontName);
    pFontDict->SetNewFor<CPDF_Name>("Encoding",
                                    pdfium::font_encodings::kWinAnsiEncoding);
    pDRFontDict->SetNewFor<CPDF_Reference>(font_name, pDoc,
                                           pFontDict->GetObjNum());
  }
  auto* pData = CPDF_DocPageData::FromDocument(pDoc);
  RetainPtr<CPDF_Font> pDefFont = pData->GetFont(pFontDict);
  if (!pDefFont)
    return;

  CFX_FloatRect rcAnnot = pAnnotDict->GetRectFor(pdfium::annotation::kRect);
  RetainPtr<const CPDF_Dictionary> pMKDict = pAnnotDict->GetDictFor("MK");
  int32_t nRotate =
      pMKDict ? pMKDict->GetIntegerFor(pdfium::appearance::kR) : 0;

  CFX_FloatRect rcBBox;
  CFX_Matrix matrix;
  switch (nRotate % 360) {
    case 0:
      rcBBox = CFX_FloatRect(0, 0, rcAnnot.right - rcAnnot.left,
                             rcAnnot.top - rcAnnot.bottom);
      break;
    case 90:
      matrix = CFX_Matrix(0, 1, -1, 0, rcAnnot.right - rcAnnot.left, 0);
      rcBBox = CFX_FloatRect(0, 0, rcAnnot.top - rcAnnot.bottom,
                             rcAnnot.right - rcAnnot.left);
      break;
    case 180:
      matrix = CFX_Matrix(-1, 0, 0, -1, rcAnnot.right - rcAnnot.left,
                          rcAnnot.top - rcAnnot.bottom);
      rcBBox = CFX_FloatRect(0, 0, rcAnnot.right - rcAnnot.left,
                             rcAnnot.top - rcAnnot.bottom);
      break;
    case 270:
      matrix = CFX_Matrix(0, -1, 1, 0, 0, rcAnnot.top - rcAnnot.bottom);
      rcBBox = CFX_FloatRect(0, 0, rcAnnot.top - rcAnnot.bottom,
                             rcAnnot.right - rcAnnot.left);
      break;
  }

  BorderStyle nBorderStyle = BorderStyle::kSolid;
  float fBorderWidth = 1;
  CPVT_Dash dsBorder(3, 0, 0);
  CFX_Color crLeftTop;
  CFX_Color crRightBottom;
  if (RetainPtr<const CPDF_Dictionary> pBSDict = pAnnotDict->GetDictFor("BS")) {
    if (pBSDict->KeyExist("W"))
      fBorderWidth = pBSDict->GetFloatFor("W");

    if (RetainPtr<const CPDF_Array> pArray = pBSDict->GetArrayFor("D")) {
      dsBorder = CPVT_Dash(pArray->GetIntegerAt(0), pArray->GetIntegerAt(1),
                           pArray->GetIntegerAt(2));
    }
    if (pBSDict->GetByteStringFor("S").GetLength()) {
      switch (pBSDict->GetByteStringFor("S")[0]) {
        case 'S':
          nBorderStyle = BorderStyle::kSolid;
          break;
        case 'D':
          nBorderStyle = BorderStyle::kDash;
          break;
        case 'B':
          nBorderStyle = BorderStyle::kBeveled;
          fBorderWidth *= 2;
          crLeftTop = CFX_Color(CFX_Color::Type::kGray, 1);
          crRightBottom = CFX_Color(CFX_Color::Type::kGray, 0.5);
          break;
        case 'I':
          nBorderStyle = BorderStyle::kInset;
          fBorderWidth *= 2;
          crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0.5);
          crRightBottom = CFX_Color(CFX_Color::Type::kGray, 0.75);
          break;
        case 'U':
          nBorderStyle = BorderStyle::kUnderline;
          break;
      }
    }
  }
  CFX_Color crBorder;
  CFX_Color crBG;
  if (pMKDict) {
    RetainPtr<const CPDF_Array> pArray =
        pMKDict->GetArrayFor(pdfium::appearance::kBC);
    if (pArray)
      crBorder = fpdfdoc::CFXColorFromArray(*pArray);
    pArray = pMKDict->GetArrayFor(pdfium::appearance::kBG);
    if (pArray)
      crBG = fpdfdoc::CFXColorFromArray(*pArray);
  }
  fxcrt::ostringstream sAppStream;
  ByteString sBG = GenerateColorAP(crBG, PaintOperation::kFill);
  if (sBG.GetLength() > 0) {
    sAppStream << "q\n"
               << sBG << rcBBox.left << " " << rcBBox.bottom << " "
               << rcBBox.Width() << " " << rcBBox.Height() << " re f\n"
               << "Q\n";
  }
  ByteString sBorderStream =
      GenerateBorderAP(rcBBox, fBorderWidth, crBorder, crLeftTop, crRightBottom,
                       nBorderStyle, dsBorder);
  if (sBorderStream.GetLength() > 0)
    sAppStream << "q\n" << sBorderStream << "Q\n";

  CFX_FloatRect rcBody =
      CFX_FloatRect(rcBBox.left + fBorderWidth, rcBBox.bottom + fBorderWidth,
                    rcBBox.right - fBorderWidth, rcBBox.top - fBorderWidth);
  rcBody.Normalize();

  RetainPtr<CPDF_Dictionary> pAPDict =
      pAnnotDict->GetOrCreateDictFor(pdfium::annotation::kAP);
  RetainPtr<CPDF_Stream> pNormalStream = pAPDict->GetMutableStreamFor("N");
  if (!pNormalStream) {
    pNormalStream = pDoc->NewIndirect<CPDF_Stream>();
    pAPDict->SetNewFor<CPDF_Reference>("N", pDoc, pNormalStream->GetObjNum());
  }
  RetainPtr<CPDF_Dictionary> pStreamDict = pNormalStream->GetMutableDict();
  if (pStreamDict) {
    RetainPtr<CPDF_Dictionary> pStreamResList =
        pStreamDict->GetMutableDictFor("Resources");
    if (pStreamResList) {
      RetainPtr<CPDF_Dictionary> pStreamResFontList =
          pStreamResList->GetMutableDictFor("Font");
      if (pStreamResFontList) {
        if (!ValidateFontResourceDict(pStreamResFontList.Get()))
          return;
      } else {
        pStreamResFontList = pStreamResList->SetNewFor<CPDF_Dictionary>("Font");
      }
      if (!pStreamResFontList->KeyExist(font_name)) {
        pStreamResFontList->SetNewFor<CPDF_Reference>(font_name, pDoc,
                                                      pFontDict->GetObjNum());
      }
    } else {
      pStreamDict->SetFor("Resources", pFormDict->GetDictFor("DR")->Clone());
    }
    pStreamDict->SetMatrixFor("Matrix", matrix);
    pStreamDict->SetRectFor("BBox", rcBBox);
  }
  CPVT_FontMap map(
      pDoc, pStreamDict ? pStreamDict->GetMutableDictFor("Resources") : nullptr,
      std::move(pDefFont), font_name);
  CPVT_VariableText::Provider prd(&map);

  switch (type) {
    case CPDF_GenerateAP::kTextField: {
      RetainPtr<const CPDF_Object> pV = CPDF_FormField::GetFieldAttrForDict(
          pAnnotDict, pdfium::form_fields::kV);
      WideString swValue = pV ? pV->GetUnicodeText() : WideString();
      RetainPtr<const CPDF_Object> pQ =
          CPDF_FormField::GetFieldAttrForDict(pAnnotDict, "Q");
      int32_t nAlign = pQ ? pQ->GetInteger() : 0;
      RetainPtr<const CPDF_Object> pFf = CPDF_FormField::GetFieldAttrForDict(
          pAnnotDict, pdfium::form_fields::kFf);
      uint32_t dwFlags = pFf ? pFf->GetInteger() : 0;
      RetainPtr<const CPDF_Object> pMaxLen =
          CPDF_FormField::GetFieldAttrForDict(pAnnotDict, "MaxLen");
      uint32_t dwMaxLen = pMaxLen ? pMaxLen->GetInteger() : 0;
      CPVT_VariableText vt(&prd);
      vt.SetPlateRect(rcBody);
      vt.SetAlignment(nAlign);
      if (FXSYS_IsFloatZero(fFontSize))
        vt.SetAutoFontSize(true);
      else
        vt.SetFontSize(fFontSize);

      bool bMultiLine = (dwFlags >> 12) & 1;
      if (bMultiLine) {
        vt.SetMultiLine(true);
        vt.SetAutoReturn(true);
      }
      uint16_t subWord = 0;
      if ((dwFlags >> 13) & 1) {
        subWord = '*';
        vt.SetPasswordChar(subWord);
      }
      bool bCharArray = (dwFlags >> 24) & 1;
      if (bCharArray)
        vt.SetCharArray(dwMaxLen);
      else
        vt.SetLimitChar(dwMaxLen);

      vt.Initialize();
      vt.SetText(swValue);
      vt.RearrangeAll();
      CFX_FloatRect rcContent = vt.GetContentRect();
      CFX_PointF ptOffset;
      if (!bMultiLine) {
        ptOffset =
            CFX_PointF(0.0f, (rcContent.Height() - rcBody.Height()) / 2.0f);
      }
      ByteString sBody = GenerateEditAP(&map, vt.GetIterator(), ptOffset,
                                        !bCharArray, subWord);
      if (sBody.GetLength() > 0) {
        sAppStream << "/Tx BMC\n"
                   << "q\n";
        if (rcContent.Width() > rcBody.Width() ||
            rcContent.Height() > rcBody.Height()) {
          sAppStream << rcBody.left << " " << rcBody.bottom << " "
                     << rcBody.Width() << " " << rcBody.Height()
                     << " re\nW\nn\n";
        }
        sAppStream << "BT\n"
                   << GenerateColorAP(crText, PaintOperation::kFill) << sBody
                   << "ET\n"
                   << "Q\nEMC\n";
      }
      break;
    }
    case CPDF_GenerateAP::kComboBox: {
      RetainPtr<const CPDF_Object> pV = CPDF_FormField::GetFieldAttrForDict(
          pAnnotDict, pdfium::form_fields::kV);
      WideString swValue = pV ? pV->GetUnicodeText() : WideString();
      CPVT_VariableText vt(&prd);
      CFX_FloatRect rcButton = rcBody;
      rcButton.left = rcButton.right - 13;
      rcButton.Normalize();
      CFX_FloatRect rcEdit = rcBody;
      rcEdit.right = rcButton.left;
      rcEdit.Normalize();
      vt.SetPlateRect(rcEdit);
      if (FXSYS_IsFloatZero(fFontSize))
        vt.SetAutoFontSize(true);
      else
        vt.SetFontSize(fFontSize);

      vt.Initialize();
      vt.SetText(swValue);
      vt.RearrangeAll();
      CFX_FloatRect rcContent = vt.GetContentRect();
      CFX_PointF ptOffset =
          CFX_PointF(0.0f, (rcContent.Height() - rcEdit.Height()) / 2.0f);
      ByteString sEdit =
          GenerateEditAP(&map, vt.GetIterator(), ptOffset, true, 0);
      if (sEdit.GetLength() > 0) {
        sAppStream << "/Tx BMC\n"
                   << "q\n";
        sAppStream << rcEdit.left << " " << rcEdit.bottom << " "
                   << rcEdit.Width() << " " << rcEdit.Height() << " re\nW\nn\n";
        sAppStream << "BT\n"
                   << GenerateColorAP(crText, PaintOperation::kFill) << sEdit
                   << "ET\n"
                   << "Q\nEMC\n";
      }
      ByteString sButton =
          GenerateColorAP(CFX_Color(CFX_Color::Type::kRGB, 220.0f / 255.0f,
                                    220.0f / 255.0f, 220.0f / 255.0f),
                          PaintOperation::kFill);
      if (sButton.GetLength() > 0 && !rcButton.IsEmpty()) {
        sAppStream << "q\n" << sButton;
        sAppStream << rcButton.left << " " << rcButton.bottom << " "
                   << rcButton.Width() << " " << rcButton.Height() << " re f\n";
        sAppStream << "Q\n";
        ByteString sButtonBorder =
            GenerateBorderAP(rcButton, 2, CFX_Color(CFX_Color::Type::kGray, 0),
                             CFX_Color(CFX_Color::Type::kGray, 1),
                             CFX_Color(CFX_Color::Type::kGray, 0.5),
                             BorderStyle::kBeveled, CPVT_Dash(3, 0, 0));
        if (sButtonBorder.GetLength() > 0)
          sAppStream << "q\n" << sButtonBorder << "Q\n";

        CFX_PointF ptCenter = CFX_PointF((rcButton.left + rcButton.right) / 2,
                                         (rcButton.top + rcButton.bottom) / 2);
        if (FXSYS_IsFloatBigger(rcButton.Width(), 6) &&
            FXSYS_IsFloatBigger(rcButton.Height(), 6)) {
          sAppStream << "q\n"
                     << " 0 g\n";
          sAppStream << ptCenter.x - 3 << " " << ptCenter.y + 1.5f << " m\n";
          sAppStream << ptCenter.x + 3 << " " << ptCenter.y + 1.5f << " l\n";
          sAppStream << ptCenter.x << " " << ptCenter.y - 1.5f << " l\n";
          sAppStream << ptCenter.x - 3 << " " << ptCenter.y + 1.5f << " l f\n";
          sAppStream << sButton << "Q\n";
        }
      }
      break;
    }
    case CPDF_GenerateAP::kListBox: {
      RetainPtr<const CPDF_Array> pOpts =
          ToArray(CPDF_FormField::GetFieldAttrForDict(pAnnotDict, "Opt"));
      RetainPtr<const CPDF_Array> pSels =
          ToArray(CPDF_FormField::GetFieldAttrForDict(pAnnotDict, "I"));
      RetainPtr<const CPDF_Object> pTi =
          CPDF_FormField::GetFieldAttrForDict(pAnnotDict, "TI");
      int32_t nTop = pTi ? pTi->GetInteger() : 0;
      fxcrt::ostringstream sBody;
      if (pOpts) {
        float fy = rcBody.top;
        for (size_t i = nTop, sz = pOpts->size(); i < sz; i++) {
          if (FXSYS_IsFloatSmaller(fy, rcBody.bottom))
            break;

          if (RetainPtr<const CPDF_Object> pOpt = pOpts->GetDirectObjectAt(i)) {
            WideString swItem;
            if (pOpt->IsString()) {
              swItem = pOpt->GetUnicodeText();
            } else if (const CPDF_Array* pArray = pOpt->AsArray()) {
              RetainPtr<const CPDF_Object> pDirectObj =
                  pArray->GetDirectObjectAt(1);
              if (pDirectObj)
                swItem = pDirectObj->GetUnicodeText();
            }
            bool bSelected = false;
            if (pSels) {
              for (size_t s = 0, ssz = pSels->size(); s < ssz; s++) {
                int value = pSels->GetIntegerAt(s);
                if (value >= 0 && i == static_cast<size_t>(value)) {
                  bSelected = true;
                  break;
                }
              }
            }
            CPVT_VariableText vt(&prd);
            vt.SetPlateRect(
                CFX_FloatRect(rcBody.left, 0.0f, rcBody.right, 0.0f));
            vt.SetFontSize(FXSYS_IsFloatZero(fFontSize) ? 12.0f : fFontSize);
            vt.Initialize();
            vt.SetText(swItem);
            vt.RearrangeAll();

            float fItemHeight = vt.GetContentRect().Height();
            if (bSelected) {
              CFX_FloatRect rcItem = CFX_FloatRect(
                  rcBody.left, fy - fItemHeight, rcBody.right, fy);
              sBody << "q\n"
                    << GenerateColorAP(
                           CFX_Color(CFX_Color::Type::kRGB, 0, 51.0f / 255.0f,
                                     113.0f / 255.0f),
                           PaintOperation::kFill)
                    << rcItem.left << " " << rcItem.bottom << " "
                    << rcItem.Width() << " " << rcItem.Height() << " re f\n"
                    << "Q\n";
              sBody << "BT\n"
                    << GenerateColorAP(CFX_Color(CFX_Color::Type::kGray, 1),
                                       PaintOperation::kFill)
                    << GenerateEditAP(&map, vt.GetIterator(),
                                      CFX_PointF(0.0f, fy), true, 0)
                    << "ET\n";
            } else {
              sBody << "BT\n"
                    << GenerateColorAP(crText, PaintOperation::kFill)
                    << GenerateEditAP(&map, vt.GetIterator(),
                                      CFX_PointF(0.0f, fy), true, 0)
                    << "ET\n";
            }
            fy -= fItemHeight;
          }
        }
      }
      if (sBody.tellp() > 0) {
        sAppStream << "/Tx BMC\nq\n"
                   << rcBody.left << " " << rcBody.bottom << " "
                   << rcBody.Width() << " " << rcBody.Height() << " re\nW\nn\n"
                   << sBody.str() << "Q\nEMC\n";
      }
      break;
    }
  }

  if (!pNormalStream)
    return;

  pNormalStream->SetDataFromStringstreamAndRemoveFilter(&sAppStream);
  pStreamDict = pNormalStream->GetMutableDict();
  if (!pStreamDict)
    return;

  pStreamDict->SetMatrixFor("Matrix", matrix);
  pStreamDict->SetRectFor("BBox", rcBBox);
  RetainPtr<CPDF_Dictionary> pStreamResList =
      pStreamDict->GetMutableDictFor("Resources");
  if (!pStreamResList) {
    pStreamDict->SetFor("Resources", pFormDict->GetDictFor("DR")->Clone());
    return;
  }

  RetainPtr<CPDF_Dictionary> pStreamResFontList =
      pStreamResList->GetMutableDictFor("Font");
  if (pStreamResFontList) {
    if (!ValidateFontResourceDict(pStreamResFontList.Get()))
      return;
  } else {
    pStreamResFontList = pStreamResList->SetNewFor<CPDF_Dictionary>("Font");
  }

  if (!pStreamResFontList->KeyExist(font_name)) {
    pStreamResFontList->SetNewFor<CPDF_Reference>(font_name, pDoc,
                                                  pFontDict->GetObjNum());
  }
}

// static
void CPDF_GenerateAP::GenerateEmptyAP(CPDF_Document* pDoc,
                                      CPDF_Dictionary* pAnnotDict) {
  auto pExtGStateDict = GenerateExtGStateDict(*pAnnotDict, "GS", "Normal");
  auto pResourceDict =
      GenerateResourceDict(pDoc, std::move(pExtGStateDict), nullptr);

  fxcrt::ostringstream sStream;
  GenerateAndSetAPDict(pDoc, pAnnotDict, &sStream, std::move(pResourceDict),
                       false);
}

// static
bool CPDF_GenerateAP::GenerateAnnotAP(CPDF_Document* pDoc,
                                      CPDF_Dictionary* pAnnotDict,
                                      CPDF_Annot::Subtype subtype) {
  switch (subtype) {
    case CPDF_Annot::Subtype::CIRCLE:
      return GenerateCircleAP(pDoc, pAnnotDict);
    case CPDF_Annot::Subtype::HIGHLIGHT:
      return GenerateHighlightAP(pDoc, pAnnotDict);
    case CPDF_Annot::Subtype::INK:
      return GenerateInkAP(pDoc, pAnnotDict);
    case CPDF_Annot::Subtype::POPUP:
      return GeneratePopupAP(pDoc, pAnnotDict);
    case CPDF_Annot::Subtype::SQUARE:
      return GenerateSquareAP(pDoc, pAnnotDict);
    case CPDF_Annot::Subtype::SQUIGGLY:
      return GenerateSquigglyAP(pDoc, pAnnotDict);
    case CPDF_Annot::Subtype::STRIKEOUT:
      return GenerateStrikeOutAP(pDoc, pAnnotDict);
    case CPDF_Annot::Subtype::TEXT:
      return GenerateTextAP(pDoc, pAnnotDict);
    case CPDF_Annot::Subtype::UNDERLINE:
      return GenerateUnderlineAP(pDoc, pAnnotDict);
    default:
      return false;
  }
}
