// Copyright 2017 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 "fpdfsdk/cpdfsdk_appstream.h"

#include <math.h>

#include <array>
#include <iterator>
#include <memory>
#include <sstream>
#include <utility>

#include "constants/appearance.h"
#include "constants/form_flags.h"
#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h"
#include "core/fpdfapi/font/cpdf_font.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_bafontmap.h"
#include "core/fpdfdoc/cpdf_formcontrol.h"
#include "core/fpdfdoc/cpdf_icon.h"
#include "core/fpdfdoc/cpvt_word.h"
#include "core/fxcrt/fx_string_wrappers.h"
#include "core/fxcrt/notreached.h"
#include "core/fxcrt/numerics/safe_conversions.h"
#include "core/fxcrt/span.h"
#include "fpdfsdk/cpdfsdk_formfillenvironment.h"
#include "fpdfsdk/cpdfsdk_interactiveform.h"
#include "fpdfsdk/cpdfsdk_pageview.h"
#include "fpdfsdk/cpdfsdk_widget.h"
#include "fpdfsdk/pwl/cpwl_edit.h"
#include "fpdfsdk/pwl/cpwl_edit_impl.h"
#include "fpdfsdk/pwl/cpwl_wnd.h"

namespace {

// Checkbox & radiobutton styles.
enum class CheckStyle { kCheck = 0, kCircle, kCross, kDiamond, kSquare, kStar };

// Pushbutton layout styles.
enum class ButtonStyle {
  kLabel = 0,
  kIcon,
  kIconTopLabelBottom,
  kIconBottomLabelTop,
  kIconLeftLabelRight,
  kIconRightLabelLeft,
  kLabelOverIcon
};

ButtonStyle TextPositionToButtonStyle(CPDF_ApSettings::TextPosition position) {
  switch (position) {
    case CPDF_ApSettings::TextPosition::kCaption:
      return ButtonStyle::kLabel;
    case CPDF_ApSettings::TextPosition::kIcon:
      return ButtonStyle::kIcon;
    case CPDF_ApSettings::TextPosition::kBelow:
      return ButtonStyle::kIconTopLabelBottom;
    case CPDF_ApSettings::TextPosition::kAbove:
      return ButtonStyle::kIconBottomLabelTop;
    case CPDF_ApSettings::TextPosition::kRight:
      return ButtonStyle::kIconLeftLabelRight;
    case CPDF_ApSettings::TextPosition::kLeft:
      return ButtonStyle::kIconRightLabelLeft;
    case CPDF_ApSettings::TextPosition::kOverlaid:
      return ButtonStyle::kLabelOverIcon;
  }
  NOTREACHED();
}

const char kAppendRectOperator[] = "re";
const char kConcatMatrixOperator[] = "cm";
const char kCurveToOperator[] = "c";
const char kEndPathNoFillOrStrokeOperator[] = "n";
const char kFillOperator[] = "f";
const char kFillEvenOddOperator[] = "f*";
const char kInvokeNamedXObjectOperator[] = "Do";
const char kLineToOperator[] = "l";
const char kMarkedSequenceBeginOperator[] = "BMC";
const char kMarkedSequenceEndOperator[] = "EMC";
const char kMoveTextPositionOperator[] = "Td";
const char kMoveToOperator[] = "m";
const char kSetCMYKOperator[] = "k";
const char kSetCMKYStrokedOperator[] = "K";
const char kSetDashOperator[] = "d";
const char kSetGrayOperator[] = "g";
const char kSetGrayStrokedOperator[] = "G";
const char kSetLineCapStyleOperator[] = "J";
const char kSetLineJoinStyleOperator[] = "j";
const char kSetLineWidthOperator[] = "w";
const char kSetNonZeroWindingClipOperator[] = "W";
const char kSetRGBOperator[] = "rg";
const char kSetRGBStrokedOperator[] = "RG";
const char kSetTextFontAndSizeOperator[] = "Tf";
const char kShowTextOperator[] = "Tj";
const char kStateRestoreOperator[] = "Q";
const char kStateSaveOperator[] = "q";
const char kStrokeOperator[] = "S";
const char kTextBeginOperator[] = "BT";
const char kTextEndOperator[] = "ET";

class AutoClosedCommand {
 public:
  AutoClosedCommand(fxcrt::ostringstream* stream,
                    ByteString open,
                    ByteString close)
      : stream_(stream), close_(close) {
    *stream_ << open << "\n";
  }

  virtual ~AutoClosedCommand() { *stream_ << close_ << "\n"; }

 private:
  UnownedPtr<fxcrt::ostringstream> const stream_;
  ByteString close_;
};

class AutoClosedQCommand final : public AutoClosedCommand {
 public:
  explicit AutoClosedQCommand(fxcrt::ostringstream* stream)
      : AutoClosedCommand(stream, kStateSaveOperator, kStateRestoreOperator) {}
  ~AutoClosedQCommand() override = default;
};

void WriteMove(fxcrt::ostringstream& stream, const CFX_PointF& point) {
  WritePoint(stream, point) << " " << kMoveToOperator << "\n";
}

void WriteLine(fxcrt::ostringstream& stream, const CFX_PointF& point) {
  WritePoint(stream, point) << " " << kLineToOperator << "\n";
}

void WriteClosedLoop(fxcrt::ostringstream& stream,
                     pdfium::span<const CFX_PointF> points) {
  WriteMove(stream, points[0]);
  for (const auto& point : points.subspan<1u>()) {
    WriteLine(stream, point);
  }
  WriteLine(stream, points[0]);
}

void WriteBezierCurve(fxcrt::ostringstream& stream,
                      const CFX_PointF& point1,
                      const CFX_PointF& point2,
                      const CFX_PointF& point3) {
  WritePoint(stream, point1) << " ";
  WritePoint(stream, point2) << " ";
  WritePoint(stream, point3) << " " << kCurveToOperator << "\n";
}

void WriteAppendRect(fxcrt::ostringstream& stream, const CFX_FloatRect& rect) {
  WriteRect(stream, rect) << " " << kAppendRectOperator << "\n";
}

ByteString GetStrokeColorAppStream(const CFX_Color& color) {
  fxcrt::ostringstream sColorStream;
  switch (color.nColorType) {
    case CFX_Color::Type::kTransparent:
      break;
    case CFX_Color::Type::kGray:
      sColorStream << color.fColor1 << " " << kSetGrayStrokedOperator << "\n";
      break;
    case CFX_Color::Type::kRGB:
      sColorStream << color.fColor1 << " " << color.fColor2 << " "
                   << color.fColor3 << " " << kSetRGBStrokedOperator << "\n";
      break;
    case CFX_Color::Type::kCMYK:
      sColorStream << color.fColor1 << " " << color.fColor2 << " "
                   << color.fColor3 << " " << color.fColor4 << " "
                   << kSetCMKYStrokedOperator << "\n";
      break;
  }
  return ByteString(sColorStream);
}

ByteString GetFillColorAppStream(const CFX_Color& color) {
  fxcrt::ostringstream sColorStream;
  switch (color.nColorType) {
    case CFX_Color::Type::kTransparent:
      break;
    case CFX_Color::Type::kGray:
      sColorStream << color.fColor1 << " " << kSetGrayOperator << "\n";
      break;
    case CFX_Color::Type::kRGB:
      sColorStream << color.fColor1 << " " << color.fColor2 << " "
                   << color.fColor3 << " " << kSetRGBOperator << "\n";
      break;
    case CFX_Color::Type::kCMYK:
      sColorStream << color.fColor1 << " " << color.fColor2 << " "
                   << color.fColor3 << " " << color.fColor4 << " "
                   << kSetCMYKOperator << "\n";
      break;
  }
  return ByteString(sColorStream);
}

ByteString GetAP_Check(const CFX_FloatRect& crBBox) {
  const float fWidth = crBBox.Width();
  const float fHeight = crBBox.Height();

  using PointRow = std::array<CFX_PointF, 3>;
  std::array<PointRow, 8> point_table = {{
      {{CFX_PointF(0.28f, 0.52f), CFX_PointF(0.27f, 0.48f),
        CFX_PointF(0.29f, 0.40f)}},
      {{CFX_PointF(0.30f, 0.33f), CFX_PointF(0.31f, 0.29f),
        CFX_PointF(0.31f, 0.28f)}},
      {{CFX_PointF(0.39f, 0.28f), CFX_PointF(0.49f, 0.29f),
        CFX_PointF(0.77f, 0.67f)}},
      {{CFX_PointF(0.76f, 0.68f), CFX_PointF(0.78f, 0.69f),
        CFX_PointF(0.76f, 0.75f)}},
      {{CFX_PointF(0.76f, 0.75f), CFX_PointF(0.73f, 0.80f),
        CFX_PointF(0.68f, 0.75f)}},
      {{CFX_PointF(0.68f, 0.74f), CFX_PointF(0.68f, 0.74f),
        CFX_PointF(0.44f, 0.47f)}},
      {{CFX_PointF(0.43f, 0.47f), CFX_PointF(0.40f, 0.47f),
        CFX_PointF(0.41f, 0.58f)}},
      {{CFX_PointF(0.40f, 0.60f), CFX_PointF(0.28f, 0.66f),
        CFX_PointF(0.30f, 0.56f)}},
  }};

  for (PointRow& row : point_table) {
    for (CFX_PointF& point : row) {
      point.x = point.x * fWidth + crBBox.left;
      point.y = point.y * fHeight + crBBox.bottom;
    }
  }

  fxcrt::ostringstream csAP;
  WriteMove(csAP, point_table[0][0]);

  for (size_t i = 0; i < point_table.size(); ++i) {
    size_t nNext = i < point_table.size() - 1 ? i + 1 : 0;
    const CFX_PointF& pt_next = point_table[nNext][0];
    float px1 = point_table[i][1].x - point_table[i][0].x;
    float py1 = point_table[i][1].y - point_table[i][0].y;
    float px2 = point_table[i][2].x - pt_next.x;
    float py2 = point_table[i][2].y - pt_next.y;
    WriteBezierCurve(
        csAP,
        {point_table[i][0].x + px1 * FXSYS_BEZIER,
         point_table[i][0].y + py1 * FXSYS_BEZIER},
        {pt_next.x + px2 * FXSYS_BEZIER, pt_next.y + py2 * FXSYS_BEZIER},
        pt_next);
  }
  return ByteString(csAP);
}

ByteString GetAP_Circle(const CFX_FloatRect& crBBox) {
  fxcrt::ostringstream csAP;

  float fWidth = crBBox.Width();
  float fHeight = crBBox.Height();

  CFX_PointF pt1(crBBox.left, crBBox.bottom + fHeight / 2);
  CFX_PointF pt2(crBBox.left + fWidth / 2, crBBox.top);
  CFX_PointF pt3(crBBox.right, crBBox.bottom + fHeight / 2);
  CFX_PointF pt4(crBBox.left + fWidth / 2, crBBox.bottom);

  WriteMove(csAP, pt1);

  float px = pt2.x - pt1.x;
  float py = pt2.y - pt1.y;

  WriteBezierCurve(csAP, {pt1.x, pt1.y + py * FXSYS_BEZIER},
                   {pt2.x - px * FXSYS_BEZIER, pt2.y}, pt2);

  px = pt3.x - pt2.x;
  py = pt2.y - pt3.y;

  WriteBezierCurve(csAP, {pt2.x + px * FXSYS_BEZIER, pt2.y},
                   {pt3.x, pt3.y + py * FXSYS_BEZIER}, pt3);

  px = pt3.x - pt4.x;
  py = pt3.y - pt4.y;

  WriteBezierCurve(csAP, {pt3.x, pt3.y - py * FXSYS_BEZIER},
                   {pt4.x + px * FXSYS_BEZIER, pt4.y}, pt4);

  px = pt4.x - pt1.x;
  py = pt1.y - pt4.y;

  WriteBezierCurve(csAP, {pt4.x - px * FXSYS_BEZIER, pt4.y},
                   {pt1.x, pt1.y - py * FXSYS_BEZIER}, pt1);

  return ByteString(csAP);
}

ByteString GetAP_Cross(const CFX_FloatRect& crBBox) {
  fxcrt::ostringstream csAP;

  WriteMove(csAP, {crBBox.left, crBBox.top});
  WriteLine(csAP, {crBBox.right, crBBox.bottom});
  WriteMove(csAP, {crBBox.left, crBBox.bottom});
  WriteLine(csAP, {crBBox.right, crBBox.top});

  return ByteString(csAP);
}

ByteString GetAP_Diamond(const CFX_FloatRect& crBBox) {
  fxcrt::ostringstream csAP;

  float fWidth = crBBox.Width();
  float fHeight = crBBox.Height();

  const CFX_PointF points[] = {{crBBox.left, crBBox.bottom + fHeight / 2},
                               {crBBox.left + fWidth / 2, crBBox.top},
                               {crBBox.right, crBBox.bottom + fHeight / 2},
                               {crBBox.left + fWidth / 2, crBBox.bottom}};
  WriteClosedLoop(csAP, points);

  return ByteString(csAP);
}

ByteString GetAP_Square(const CFX_FloatRect& crBBox) {
  fxcrt::ostringstream csAP;

  const CFX_PointF points[] = {{crBBox.left, crBBox.top},
                               {crBBox.right, crBBox.top},
                               {crBBox.right, crBBox.bottom},
                               {crBBox.left, crBBox.bottom}};
  WriteClosedLoop(csAP, points);

  return ByteString(csAP);
}

ByteString GetAP_Star(const CFX_FloatRect& crBBox) {
  fxcrt::ostringstream csAP;

  float fRadius = (crBBox.top - crBBox.bottom) / (1 + cosf(FXSYS_PI / 5.0f));
  CFX_PointF ptCenter = CFX_PointF((crBBox.left + crBBox.right) / 2.0f,
                                   (crBBox.top + crBBox.bottom) / 2.0f);

  std::array<CFX_PointF, 5> points;
  float fAngle = FXSYS_PI / 10.0f;
  for (auto& point : points) {
    point =
        ptCenter + CFX_PointF(fRadius * cosf(fAngle), fRadius * sinf(fAngle));
    fAngle += FXSYS_PI * 2 / 5.0f;
  }

  WriteMove(csAP, points[0]);
  WriteLine(csAP, points[2]);
  WriteLine(csAP, points[4]);
  WriteLine(csAP, points[1]);
  WriteLine(csAP, points[3]);
  WriteLine(csAP, points[0]);

  return ByteString(csAP);
}

ByteString GetAP_HalfCircle(const CFX_FloatRect& crBBox, float fRotate) {
  fxcrt::ostringstream csAP;

  float fWidth = crBBox.Width();
  float fHeight = crBBox.Height();

  CFX_PointF pt1(-fWidth / 2, 0);
  CFX_PointF pt2(0, fHeight / 2);
  CFX_PointF pt3(fWidth / 2, 0);

  CFX_Matrix rotate_matrix(cos(fRotate), sin(fRotate), -sin(fRotate),
                           cos(fRotate), crBBox.left + fWidth / 2,
                           crBBox.bottom + fHeight / 2);
  WriteMatrix(csAP, rotate_matrix) << " " << kConcatMatrixOperator << "\n";

  WriteMove(csAP, pt1);

  float px = pt2.x - pt1.x;
  float py = pt2.y - pt1.y;

  WriteBezierCurve(csAP, {pt1.x, pt1.y + py * FXSYS_BEZIER},
                   {pt2.x - px * FXSYS_BEZIER, pt2.y}, pt2);

  px = pt3.x - pt2.x;
  py = pt2.y - pt3.y;

  WriteBezierCurve(csAP, {pt2.x + px * FXSYS_BEZIER, pt2.y},
                   {pt3.x, pt3.y + py * FXSYS_BEZIER}, pt3);

  return ByteString(csAP);
}

ByteString GetAppStream_Check(const CFX_FloatRect& rcBBox,
                              const CFX_Color& crText) {
  fxcrt::ostringstream sAP;
  {
    AutoClosedQCommand q(&sAP);
    sAP << GetFillColorAppStream(crText) << GetAP_Check(rcBBox) << kFillOperator
        << "\n";
  }
  return ByteString(sAP);
}

ByteString GetAppStream_Circle(const CFX_FloatRect& rcBBox,
                               const CFX_Color& crText) {
  fxcrt::ostringstream sAP;
  {
    AutoClosedQCommand q(&sAP);
    sAP << GetFillColorAppStream(crText) << GetAP_Circle(rcBBox)
        << kFillOperator << "\n";
  }
  return ByteString(sAP);
}

ByteString GetAppStream_Cross(const CFX_FloatRect& rcBBox,
                              const CFX_Color& crText) {
  fxcrt::ostringstream sAP;
  {
    AutoClosedQCommand q(&sAP);
    sAP << GetStrokeColorAppStream(crText) << GetAP_Cross(rcBBox)
        << kStrokeOperator << "\n";
  }
  return ByteString(sAP);
}

ByteString GetAppStream_Diamond(const CFX_FloatRect& rcBBox,
                                const CFX_Color& crText) {
  fxcrt::ostringstream sAP;
  {
    AutoClosedQCommand q(&sAP);
    sAP << "1 " << kSetLineWidthOperator << "\n"
        << GetFillColorAppStream(crText) << GetAP_Diamond(rcBBox)
        << kFillOperator << "\n";
  }
  return ByteString(sAP);
}

ByteString GetAppStream_Square(const CFX_FloatRect& rcBBox,
                               const CFX_Color& crText) {
  fxcrt::ostringstream sAP;
  {
    AutoClosedQCommand q(&sAP);
    sAP << GetFillColorAppStream(crText) << GetAP_Square(rcBBox)
        << kFillOperator << "\n";
  }
  return ByteString(sAP);
}

ByteString GetAppStream_Star(const CFX_FloatRect& rcBBox,
                             const CFX_Color& crText) {
  fxcrt::ostringstream sAP;
  {
    AutoClosedQCommand q(&sAP);
    sAP << GetFillColorAppStream(crText) << GetAP_Star(rcBBox) << kFillOperator
        << "\n";
  }
  return ByteString(sAP);
}

ByteString GetCircleFillAppStream(const CFX_FloatRect& rect,
                                  const CFX_Color& color) {
  fxcrt::ostringstream sAppStream;
  ByteString sColor = GetFillColorAppStream(color);
  if (sColor.GetLength() > 0) {
    AutoClosedQCommand q(&sAppStream);
    sAppStream << sColor << GetAP_Circle(rect) << kFillOperator << "\n";
  }
  return ByteString(sAppStream);
}

ByteString GetCircleBorderAppStream(const CFX_FloatRect& rect,
                                    float fWidth,
                                    const CFX_Color& color,
                                    const CFX_Color& crLeftTop,
                                    const CFX_Color& crRightBottom,
                                    BorderStyle nStyle,
                                    const CPWL_Dash& dash) {
  fxcrt::ostringstream sAppStream;
  ByteString sColor;

  if (fWidth > 0.0f) {
    AutoClosedQCommand q(&sAppStream);

    float fHalfWidth = fWidth / 2.0f;
    CFX_FloatRect rect_by_2 = rect.GetDeflated(fHalfWidth, fHalfWidth);

    float div = fHalfWidth * 0.75f;
    CFX_FloatRect rect_by_75 = rect.GetDeflated(div, div);
    switch (nStyle) {
      case BorderStyle::kSolid:
      case BorderStyle::kUnderline: {
        sColor = GetStrokeColorAppStream(color);
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q2(&sAppStream);
          sAppStream << fWidth << " " << kSetLineWidthOperator << "\n"
                     << sColor << GetAP_Circle(rect_by_2) << " "
                     << kStrokeOperator << "\n";
        }
      } break;
      case BorderStyle::kDash: {
        sColor = GetStrokeColorAppStream(color);
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q2(&sAppStream);
          sAppStream << fWidth << " " << kSetLineWidthOperator << "\n"
                     << "[" << dash.nDash << " " << dash.nGap << "] "
                     << dash.nPhase << " " << kSetDashOperator << "\n"
                     << sColor << GetAP_Circle(rect_by_2) << " "
                     << kStrokeOperator << "\n";
        }
      } break;
      case BorderStyle::kBeveled: {
        sColor = GetStrokeColorAppStream(color);
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q2(&sAppStream);
          sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
                     << sColor << GetAP_Circle(rect) << " " << kStrokeOperator
                     << "\n";
        }
        sColor = GetStrokeColorAppStream(crLeftTop);
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q2(&sAppStream);
          sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
                     << sColor << GetAP_HalfCircle(rect_by_75, FXSYS_PI / 4.0f)
                     << " " << kStrokeOperator << "\n";
        }
        sColor = GetStrokeColorAppStream(crRightBottom);
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q2(&sAppStream);
          sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
                     << sColor
                     << GetAP_HalfCircle(rect_by_75, FXSYS_PI * 5 / 4.0f) << " "
                     << kStrokeOperator << "\n";
        }
      } break;
      case BorderStyle::kInset: {
        sColor = GetStrokeColorAppStream(color);
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q2(&sAppStream);
          sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
                     << sColor << GetAP_Circle(rect) << " " << kStrokeOperator
                     << "\n";
        }
        sColor = GetStrokeColorAppStream(crLeftTop);
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q2(&sAppStream);
          sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
                     << sColor << GetAP_HalfCircle(rect_by_75, FXSYS_PI / 4.0f)
                     << " " << kStrokeOperator << "\n";
        }
        sColor = GetStrokeColorAppStream(crRightBottom);
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q2(&sAppStream);
          sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
                     << sColor
                     << GetAP_HalfCircle(rect_by_75, FXSYS_PI * 5 / 4.0f) << " "
                     << kStrokeOperator << "\n";
        }
      } break;
    }
  }
  return ByteString(sAppStream);
}

ByteString GetCheckBoxAppStream(const CFX_FloatRect& rcBBox,
                                CheckStyle nStyle,
                                const CFX_Color& crText) {
  CFX_FloatRect rcCenter = rcBBox.GetCenterSquare();
  switch (nStyle) {
    case CheckStyle::kCheck:
      return GetAppStream_Check(rcCenter, crText);
    case CheckStyle::kCircle:
      rcCenter.ScaleFromCenterPoint(2.0f / 3.0f);
      return GetAppStream_Circle(rcCenter, crText);
    case CheckStyle::kCross:
      return GetAppStream_Cross(rcCenter, crText);
    case CheckStyle::kDiamond:
      rcCenter.ScaleFromCenterPoint(2.0f / 3.0f);
      return GetAppStream_Diamond(rcCenter, crText);
    case CheckStyle::kSquare:
      rcCenter.ScaleFromCenterPoint(2.0f / 3.0f);
      return GetAppStream_Square(rcCenter, crText);
    case CheckStyle::kStar:
      rcCenter.ScaleFromCenterPoint(2.0f / 3.0f);
      return GetAppStream_Star(rcCenter, crText);
  }
}

ByteString GetRadioButtonAppStream(const CFX_FloatRect& rcBBox,
                                   CheckStyle nStyle,
                                   const CFX_Color& crText) {
  CFX_FloatRect rcCenter = rcBBox.GetCenterSquare();
  switch (nStyle) {
    case CheckStyle::kCheck:
      return GetAppStream_Check(rcCenter, crText);
    case CheckStyle::kCircle:
      rcCenter.ScaleFromCenterPoint(1.0f / 2.0f);
      return GetAppStream_Circle(rcCenter, crText);
    case CheckStyle::kCross:
      return GetAppStream_Cross(rcCenter, crText);
    case CheckStyle::kDiamond:
      rcCenter.ScaleFromCenterPoint(2.0f / 3.0f);
      return GetAppStream_Diamond(rcCenter, crText);
    case CheckStyle::kSquare:
      rcCenter.ScaleFromCenterPoint(2.0f / 3.0f);
      return GetAppStream_Square(rcCenter, crText);
    case CheckStyle::kStar:
      rcCenter.ScaleFromCenterPoint(2.0f / 3.0f);
      return GetAppStream_Star(rcCenter, crText);
  }
}

ByteString GetFontSetString(IPVT_FontMap* font_map,
                            int32_t nFontIndex,
                            float fFontSize) {
  if (!font_map) {
    return ByteString();
  }

  ByteString sFontAlias = font_map->GetPDFFontAlias(nFontIndex);
  if (sFontAlias.GetLength() <= 0 || fFontSize <= 0) {
    return ByteString();
  }

  fxcrt::ostringstream sRet;
  sRet << "/" << sFontAlias << " " << fFontSize << " "
       << kSetTextFontAndSizeOperator << "\n";
  return ByteString(sRet);
}

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

ByteString GetEditAppStream(CPWL_EditImpl* pEdit,
                            const CFX_PointF& ptOffset,
                            bool bContinuous,
                            uint16_t SubWord) {
  CPWL_EditImpl::Iterator* pIterator = pEdit->GetIterator();
  pIterator->SetAt(0);

  fxcrt::ostringstream sEditStream;
  int32_t nCurFontIndex = -1;
  CFX_PointF ptOld;
  CFX_PointF ptNew;
  CPVT_WordPlace oldplace;
  ByteString sWords;

  while (pIterator->NextWord()) {
    CPVT_WordPlace place = pIterator->GetAt();
    if (bContinuous) {
      if (place.LineCmp(oldplace) != 0) {
        if (!sWords.IsEmpty()) {
          sEditStream << GetWordRenderString(sWords.AsStringView());
          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.x != ptOld.x || ptNew.y != ptOld.y) {
          WritePoint(sEditStream, {ptNew.x - ptOld.x, ptNew.y - ptOld.y})
              << " " << kMoveTextPositionOperator << "\n";

          ptOld = ptNew;
        }
      }

      CPVT_Word word;
      if (pIterator->GetWord(word)) {
        if (word.nFontIndex != nCurFontIndex) {
          if (!sWords.IsEmpty()) {
            sEditStream << GetWordRenderString(sWords.AsStringView());
            sWords.clear();
          }
          sEditStream << GetFontSetString(pEdit->GetFontMap(), word.nFontIndex,
                                          word.fFontSize);
          nCurFontIndex = word.nFontIndex;
        }

        sWords += pEdit->GetPDFWordString(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.x != ptOld.x || ptNew.y != ptOld.y) {
          WritePoint(sEditStream, {ptNew.x - ptOld.x, ptNew.y - ptOld.y})
              << " " << kMoveTextPositionOperator << "\n";
          ptOld = ptNew;
        }
        if (word.nFontIndex != nCurFontIndex) {
          sEditStream << GetFontSetString(pEdit->GetFontMap(), word.nFontIndex,
                                          word.fFontSize);
          nCurFontIndex = word.nFontIndex;
        }
        sEditStream << GetWordRenderString(
            pEdit->GetPDFWordString(nCurFontIndex, word.Word, SubWord)
                .AsStringView());
      }
    }
  }

  if (!sWords.IsEmpty()) {
    sEditStream << GetWordRenderString(sWords.AsStringView());
  }

  fxcrt::ostringstream sAppStream;
  if (sEditStream.tellp() > 0) {
    sAppStream << sEditStream.str();
  }
  return ByteString(sAppStream);
}

ByteString GenerateIconAppStream(CPDF_IconFit& fit,
                                 RetainPtr<CPDF_Stream> pIconStream,
                                 const CFX_FloatRect& rcIcon) {
  if (rcIcon.IsEmpty() || !pIconStream) {
    return ByteString();
  }

  CPWL_Wnd::CreateParams cp(nullptr, nullptr, nullptr);
  cp.dwFlags = CPWL_Wnd::Styles::kWindowVisible;
  auto pWnd = std::make_unique<CPWL_Wnd>(cp, nullptr);
  pWnd->Realize();
  if (!pWnd->Move(rcIcon, false, false)) {
    return ByteString();
  }

  auto pPDFIcon = std::make_unique<CPDF_Icon>(std::move(pIconStream));
  ByteString sAlias = pPDFIcon->GetImageAlias();
  if (sAlias.GetLength() <= 0) {
    return ByteString();
  }

  const CFX_FloatRect rcPlate = pWnd->GetClientRect();
  const CFX_SizeF image_size = pPDFIcon->GetImageSize();
  const CFX_Matrix mt = pPDFIcon->GetImageMatrix().GetInverse();
  const CFX_VectorF scale = fit.GetScale(image_size, rcPlate);
  const CFX_VectorF offset = fit.GetImageOffset(image_size, scale, rcPlate);

  fxcrt::ostringstream str;
  {
    AutoClosedQCommand q(&str);
    WriteAppendRect(str, rcPlate);
    str << kSetNonZeroWindingClipOperator << " "
        << kEndPathNoFillOrStrokeOperator << "\n";

    CFX_Matrix scale_matrix(scale.x, 0, 0, scale.y, rcPlate.left + offset.x,
                            rcPlate.bottom + offset.y);
    WriteMatrix(str, scale_matrix) << " " << kConcatMatrixOperator << "\n";
    WriteMatrix(str, mt) << " " << kConcatMatrixOperator << "\n";

    str << "0 " << kSetGrayOperator << " 0 " << kSetGrayStrokedOperator << " 1 "
        << kSetLineWidthOperator << " /" << sAlias << " "
        << kInvokeNamedXObjectOperator << "\n";
  }
  pWnd->Destroy();
  return ByteString(str);
}

ByteString GetPushButtonAppStream(const CFX_FloatRect& rcBBox,
                                  IPVT_FontMap* font_map,
                                  RetainPtr<CPDF_Stream> pIconStream,
                                  CPDF_IconFit& IconFit,
                                  const WideString& sLabel,
                                  const CFX_Color& crText,
                                  float fFontSize,
                                  ButtonStyle nLayOut) {
  const float fAutoFontScale = 1.0f / 3.0f;

  auto pEdit = std::make_unique<CPWL_EditImpl>();
  pEdit->SetFontMap(font_map);
  pEdit->SetAlignmentH(1);
  pEdit->SetAlignmentV(1);
  pEdit->SetMultiLine(false);
  pEdit->SetAutoReturn(false);
  if (FXSYS_IsFloatZero(fFontSize)) {
    pEdit->SetAutoFontSize(true);
  } else {
    pEdit->SetFontSize(fFontSize);
  }

  pEdit->Initialize();
  pEdit->SetText(sLabel);
  pEdit->Paint();

  CFX_FloatRect rcLabelContent = pEdit->GetContentRect();
  CFX_FloatRect rcLabel;
  CFX_FloatRect rcIcon;
  float fWidth = 0.0f;
  float fHeight = 0.0f;

  switch (nLayOut) {
    case ButtonStyle::kLabel:
      rcLabel = rcBBox;
      break;
    case ButtonStyle::kIcon:
      rcIcon = rcBBox;
      break;
    case ButtonStyle::kIconTopLabelBottom:
      if (pIconStream) {
        if (FXSYS_IsFloatZero(fFontSize)) {
          fHeight = rcBBox.Height();
          rcLabel = CFX_FloatRect(rcBBox.left, rcBBox.bottom, rcBBox.right,
                                  rcBBox.bottom + fHeight * fAutoFontScale);
          rcIcon =
              CFX_FloatRect(rcBBox.left, rcLabel.top, rcBBox.right, rcBBox.top);
        } else {
          fHeight = rcLabelContent.Height();

          if (rcBBox.bottom + fHeight > rcBBox.top) {
            rcLabel = rcBBox;
          } else {
            rcLabel = CFX_FloatRect(rcBBox.left, rcBBox.bottom, rcBBox.right,
                                    rcBBox.bottom + fHeight);
            rcIcon = CFX_FloatRect(rcBBox.left, rcLabel.top, rcBBox.right,
                                   rcBBox.top);
          }
        }
      } else {
        rcLabel = rcBBox;
      }
      break;
    case ButtonStyle::kIconBottomLabelTop:
      if (pIconStream) {
        if (FXSYS_IsFloatZero(fFontSize)) {
          fHeight = rcBBox.Height();
          rcLabel =
              CFX_FloatRect(rcBBox.left, rcBBox.top - fHeight * fAutoFontScale,
                            rcBBox.right, rcBBox.top);
          rcIcon = CFX_FloatRect(rcBBox.left, rcBBox.bottom, rcBBox.right,
                                 rcLabel.bottom);
        } else {
          fHeight = rcLabelContent.Height();

          if (rcBBox.bottom + fHeight > rcBBox.top) {
            rcLabel = rcBBox;
          } else {
            rcLabel = CFX_FloatRect(rcBBox.left, rcBBox.top - fHeight,
                                    rcBBox.right, rcBBox.top);
            rcIcon = CFX_FloatRect(rcBBox.left, rcBBox.bottom, rcBBox.right,
                                   rcLabel.bottom);
          }
        }
      } else {
        rcLabel = rcBBox;
      }
      break;
    case ButtonStyle::kIconLeftLabelRight:
      if (pIconStream) {
        if (FXSYS_IsFloatZero(fFontSize)) {
          fWidth = rcBBox.right - rcBBox.left;
          if (rcLabelContent.Width() < fWidth * fAutoFontScale) {
            rcLabel = CFX_FloatRect(rcBBox.right - fWidth * fAutoFontScale,
                                    rcBBox.bottom, rcBBox.right, rcBBox.top);
            rcIcon = CFX_FloatRect(rcBBox.left, rcBBox.bottom, rcLabel.left,
                                   rcBBox.top);
          } else {
            if (rcLabelContent.Width() < fWidth) {
              rcLabel = CFX_FloatRect(rcBBox.right - rcLabelContent.Width(),
                                      rcBBox.bottom, rcBBox.right, rcBBox.top);
              rcIcon = CFX_FloatRect(rcBBox.left, rcBBox.bottom, rcLabel.left,
                                     rcBBox.top);
            } else {
              rcLabel = rcBBox;
            }
          }
        } else {
          fWidth = rcLabelContent.Width();
          if (rcBBox.left + fWidth > rcBBox.right) {
            rcLabel = rcBBox;
          } else {
            rcLabel = CFX_FloatRect(rcBBox.right - fWidth, rcBBox.bottom,
                                    rcBBox.right, rcBBox.top);
            rcIcon = CFX_FloatRect(rcBBox.left, rcBBox.bottom, rcLabel.left,
                                   rcBBox.top);
          }
        }
      } else {
        rcLabel = rcBBox;
      }
      break;
    case ButtonStyle::kIconRightLabelLeft:
      if (pIconStream) {
        if (FXSYS_IsFloatZero(fFontSize)) {
          fWidth = rcBBox.right - rcBBox.left;
          if (rcLabelContent.Width() < fWidth * fAutoFontScale) {
            rcLabel = CFX_FloatRect(rcBBox.left, rcBBox.bottom,
                                    rcBBox.left + fWidth * fAutoFontScale,
                                    rcBBox.top);
            rcIcon = CFX_FloatRect(rcLabel.right, rcBBox.bottom, rcBBox.right,
                                   rcBBox.top);
          } else {
            if (rcLabelContent.Width() < fWidth) {
              rcLabel = CFX_FloatRect(rcBBox.left, rcBBox.bottom,
                                      rcBBox.left + rcLabelContent.Width(),
                                      rcBBox.top);
              rcIcon = CFX_FloatRect(rcLabel.right, rcBBox.bottom, rcBBox.right,
                                     rcBBox.top);
            } else {
              rcLabel = rcBBox;
            }
          }
        } else {
          fWidth = rcLabelContent.Width();
          if (rcBBox.left + fWidth > rcBBox.right) {
            rcLabel = rcBBox;
          } else {
            rcLabel = CFX_FloatRect(rcBBox.left, rcBBox.bottom,
                                    rcBBox.left + fWidth, rcBBox.top);
            rcIcon = CFX_FloatRect(rcLabel.right, rcBBox.bottom, rcBBox.right,
                                   rcBBox.top);
          }
        }
      } else {
        rcLabel = rcBBox;
      }
      break;
    case ButtonStyle::kLabelOverIcon:
      rcLabel = rcBBox;
      rcIcon = rcBBox;
      break;
  }

  fxcrt::ostringstream sTemp;
  sTemp << GenerateIconAppStream(IconFit, std::move(pIconStream), rcIcon);

  if (!rcLabel.IsEmpty()) {
    pEdit->SetPlateRect(rcLabel);
    pEdit->Paint();
    ByteString sEdit =
        GetEditAppStream(pEdit.get(), CFX_PointF(0.0f, 0.0f), true, 0);
    if (sEdit.GetLength() > 0) {
      AutoClosedCommand bt(&sTemp, kTextBeginOperator, kTextEndOperator);
      sTemp << GetFillColorAppStream(crText) << sEdit;
    }
  }

  if (sTemp.tellp() <= 0) {
    return ByteString();
  }

  fxcrt::ostringstream sAppStream;
  {
    AutoClosedQCommand q(&sAppStream);
    WriteAppendRect(sAppStream, rcBBox);
    sAppStream << kSetNonZeroWindingClipOperator << " "
               << kEndPathNoFillOrStrokeOperator << "\n";
    sAppStream << sTemp.str().c_str();
  }
  return ByteString(sAppStream);
}

ByteString GetBorderAppStreamInternal(const CFX_FloatRect& rect,
                                      float fWidth,
                                      const CFX_Color& color,
                                      const CFX_Color& crLeftTop,
                                      const CFX_Color& crRightBottom,
                                      BorderStyle nStyle,
                                      const CPWL_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;
    AutoClosedQCommand q(&sAppStream);

    switch (nStyle) {
      case BorderStyle::kSolid:
        sColor = GetFillColorAppStream(color);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          WriteAppendRect(sAppStream, {fLeft, fBottom, fRight, fTop});
          WriteAppendRect(sAppStream, {fLeft + fWidth, fBottom + fWidth,
                                       fRight - fWidth, fTop - fWidth});
          sAppStream << kFillEvenOddOperator << "\n";
        }
        break;
      case BorderStyle::kDash:
        sColor = GetStrokeColorAppStream(color);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          sAppStream << fWidth << " " << kSetLineWidthOperator << " ["
                     << dash.nDash << " " << dash.nGap << "] " << dash.nPhase
                     << " " << kSetDashOperator << "\n";
          const CFX_PointF points[] = {
              {fLeft + fWidth / 2, fBottom + fWidth / 2},
              {fLeft + fWidth / 2, fTop - fWidth / 2},
              {fRight - fWidth / 2, fTop - fWidth / 2},
              {fRight - fWidth / 2, fBottom + fWidth / 2}};
          WriteClosedLoop(sAppStream, points);
          sAppStream << kStrokeOperator << "\n";
        }
        break;
      case BorderStyle::kBeveled:
      case BorderStyle::kInset:
        sColor = GetFillColorAppStream(crLeftTop);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          WriteMove(sAppStream, {fLeft + fHalfWidth, fBottom + fHalfWidth});
          WriteLine(sAppStream, {fLeft + fHalfWidth, fTop - fHalfWidth});
          WriteLine(sAppStream, {fRight - fHalfWidth, fTop - fHalfWidth});
          WriteLine(sAppStream,
                    {fRight - fHalfWidth * 2, fTop - fHalfWidth * 2});
          WriteLine(sAppStream,
                    {fLeft + fHalfWidth * 2, fTop - fHalfWidth * 2});
          WriteLine(sAppStream,
                    {fLeft + fHalfWidth * 2, fBottom + fHalfWidth * 2});
          sAppStream << kFillOperator << "\n";
        }
        sColor = GetFillColorAppStream(crRightBottom);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          WriteMove(sAppStream, {fRight - fHalfWidth, fTop - fHalfWidth});
          WriteLine(sAppStream, {fRight - fHalfWidth, fBottom + fHalfWidth});
          WriteLine(sAppStream, {fLeft + fHalfWidth, fBottom + fHalfWidth});
          WriteLine(sAppStream,
                    {fLeft + fHalfWidth * 2, fBottom + fHalfWidth * 2});
          WriteLine(sAppStream,
                    {fRight - fHalfWidth * 2, fBottom + fHalfWidth * 2});
          WriteLine(sAppStream,
                    {fRight - fHalfWidth * 2, fTop - fHalfWidth * 2});
          sAppStream << kFillOperator << "\n";
        }
        sColor = GetFillColorAppStream(color);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          WriteAppendRect(sAppStream, {fLeft, fBottom, fRight, fTop});
          WriteAppendRect(sAppStream, {fLeft + fHalfWidth, fBottom + fHalfWidth,
                                       fRight - fHalfWidth, fTop - fHalfWidth});
          sAppStream << kFillEvenOddOperator << "\n";
        }
        break;
      case BorderStyle::kUnderline:
        sColor = GetStrokeColorAppStream(color);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          sAppStream << fWidth << " " << kSetLineWidthOperator << "\n";
          WriteMove(sAppStream, {fLeft, fBottom + fWidth / 2});
          WriteLine(sAppStream, {fRight, fBottom + fWidth / 2});
          sAppStream << kStrokeOperator << "\n";
        }
        break;
    }
  }
  return ByteString(sAppStream);
}

ByteString GetDropButtonAppStream(const CFX_FloatRect& rcBBox) {
  if (rcBBox.IsEmpty()) {
    return ByteString();
  }

  fxcrt::ostringstream sAppStream;
  {
    AutoClosedQCommand q(&sAppStream);
    sAppStream << GetFillColorAppStream(
        CFX_Color(CFX_Color::Type::kRGB, 220.0f / 255.0f, 220.0f / 255.0f,
                  220.0f / 255.0f));
    WriteAppendRect(sAppStream, rcBBox);
    sAppStream << kFillOperator << "\n";
  }

  {
    AutoClosedQCommand q(&sAppStream);
    sAppStream << GetBorderAppStreamInternal(
        rcBBox, 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,
        CPWL_Dash{3, 0, 0});
  }

  CFX_PointF ptCenter = CFX_PointF((rcBBox.left + rcBBox.right) / 2,
                                   (rcBBox.top + rcBBox.bottom) / 2);
  if (FXSYS_IsFloatBigger(rcBBox.right - rcBBox.left, 6) &&
      FXSYS_IsFloatBigger(rcBBox.top - rcBBox.bottom, 6)) {
    AutoClosedQCommand q(&sAppStream);
    const CFX_PointF points[] = {{ptCenter.x - 3, ptCenter.y + 1.5f},
                                 {ptCenter.x + 3, ptCenter.y + 1.5f},
                                 {ptCenter.x, ptCenter.y - 1.5f}};
    sAppStream << " 0 " << kSetGrayOperator << "\n";
    WriteClosedLoop(sAppStream, points);
    sAppStream << kFillOperator << "\n";
  }

  return ByteString(sAppStream);
}

ByteString GetRectFillAppStream(const CFX_FloatRect& rect,
                                const CFX_Color& color) {
  fxcrt::ostringstream sAppStream;
  ByteString sColor = GetFillColorAppStream(color);
  if (sColor.GetLength() > 0) {
    AutoClosedQCommand q(&sAppStream);
    sAppStream << sColor;
    WriteAppendRect(sAppStream, rect);
    sAppStream << kFillOperator << "\n";
  }

  return ByteString(sAppStream);
}

void SetDefaultIconName(CPDF_Stream* pIcon, const char* name) {
  if (!pIcon) {
    return;
  }

  RetainPtr<CPDF_Dictionary> pImageDict = pIcon->GetMutableDict();
  if (pImageDict->KeyExist("Name")) {
    return;
  }

  pImageDict->SetNewFor<CPDF_String>("Name", name);
}

std::optional<CheckStyle> CheckStyleFromCaption(const WideString& caption) {
  // Character values are ZapfDingbats encodings of named glyphs.
  switch (caption.Front()) {  // Front() safe when empty.
    case L'4':
      return CheckStyle::kCheck;
    case L'8':
      return CheckStyle::kCross;
    case L'H':
      return CheckStyle::kStar;
    case L'l':
      return CheckStyle::kCircle;
    case L'n':
      return CheckStyle::kSquare;
    case L'u':
      return CheckStyle::kDiamond;
    default:
      return std::nullopt;
  }
}

}  // namespace

CPDFSDK_AppStream::CPDFSDK_AppStream(CPDFSDK_Widget* widget,
                                     CPDF_Dictionary* dict)
    : widget_(widget), dict_(dict) {}

CPDFSDK_AppStream::~CPDFSDK_AppStream() = default;

void CPDFSDK_AppStream::SetAsPushButton() {
  CPDF_FormControl* pControl = widget_->GetFormControl();
  CFX_FloatRect rcWindow = widget_->GetRotatedRect();
  ButtonStyle nLayout = TextPositionToButtonStyle(pControl->GetTextPosition());

  CFX_Color crBackground = pControl->GetOriginalBackgroundColor();
  CFX_Color crBorder = pControl->GetOriginalBorderColor();

  float fBorderWidth = static_cast<float>(widget_->GetBorderWidth());
  CPWL_Dash dsBorder{3, 0, 0};
  CFX_Color crLeftTop;
  CFX_Color crRightBottom;

  BorderStyle nBorderStyle = widget_->GetBorderStyle();
  switch (nBorderStyle) {
    case BorderStyle::kDash:
      dsBorder = CPWL_Dash{3, 3, 0};
      break;
    case BorderStyle::kBeveled:
      fBorderWidth *= 2;
      crLeftTop = CFX_Color(CFX_Color::Type::kGray, 1);
      crRightBottom = crBackground / 2.0f;
      break;
    case BorderStyle::kInset:
      fBorderWidth *= 2;
      crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0.5);
      crRightBottom = CFX_Color(CFX_Color::Type::kGray, 0.75);
      break;
    default:
      break;
  }

  CFX_FloatRect rcClient = rcWindow.GetDeflated(fBorderWidth, fBorderWidth);
  CPDF_DefaultAppearance da = pControl->GetDefaultAppearance();
  std::optional<CFX_Color> color = da.GetColor();
  CFX_Color crText = color.value_or(CFX_Color(CFX_Color::Type::kGray, 0));

  const float font_size =
      da.GetFont()
          .value_or(CPDF_DefaultAppearance::FontNameAndSize{.size = 12.0f})
          .size;

  WideString csWCaption;
  WideString csNormalCaption;
  WideString csRolloverCaption;
  WideString csDownCaption;
  if (pControl->HasMKEntry(pdfium::appearance::kCA)) {
    csNormalCaption = pControl->GetNormalCaption();
  }

  if (pControl->HasMKEntry(pdfium::appearance::kRC)) {
    csRolloverCaption = pControl->GetRolloverCaption();
  }

  if (pControl->HasMKEntry(pdfium::appearance::kAC)) {
    csDownCaption = pControl->GetDownCaption();
  }

  RetainPtr<CPDF_Stream> pNormalIcon;
  RetainPtr<CPDF_Stream> pRolloverIcon;
  RetainPtr<CPDF_Stream> pDownIcon;
  if (pControl->HasMKEntry(pdfium::appearance::kI)) {
    pNormalIcon = pControl->GetNormalIcon();
  }

  if (pControl->HasMKEntry(pdfium::appearance::kRI)) {
    pRolloverIcon = pControl->GetRolloverIcon();
  }

  if (pControl->HasMKEntry(pdfium::appearance::kIX)) {
    pDownIcon = pControl->GetDownIcon();
  }

  SetDefaultIconName(pNormalIcon.Get(), "ImgA");
  SetDefaultIconName(pRolloverIcon.Get(), "ImgB");
  SetDefaultIconName(pDownIcon.Get(), "ImgC");

  CPDF_IconFit iconFit = pControl->GetIconFit();
  {
    CPDF_BAFontMap font_map(widget_->GetPDFPage()->GetDocument(),
                            widget_->GetPDFAnnot()->GetMutableAnnotDict(), "N");
    ByteString csAP =
        GetRectFillAppStream(rcWindow, crBackground) +
        GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
                                   crRightBottom, nBorderStyle, dsBorder) +
        GetPushButtonAppStream(iconFit.GetFittingBounds() ? rcWindow : rcClient,
                               &font_map, pNormalIcon, iconFit, csNormalCaption,
                               crText, font_size, nLayout);

    Write("N", csAP, ByteString());
    if (pNormalIcon) {
      AddImage("N", pNormalIcon.Get());
    }

    CPDF_FormControl::HighlightingMode eHLM = pControl->GetHighlightingMode();
    if (eHLM != CPDF_FormControl::kPush && eHLM != CPDF_FormControl::kToggle) {
      Remove("D");
      Remove("R");
      return;
    }

    if (csRolloverCaption.IsEmpty() && !pRolloverIcon) {
      csRolloverCaption = csNormalCaption;
      pRolloverIcon = pNormalIcon;
    }
  }
  {
    CPDF_BAFontMap font_map(widget_->GetPDFPage()->GetDocument(),
                            widget_->GetPDFAnnot()->GetMutableAnnotDict(), "R");
    ByteString csAP =
        GetRectFillAppStream(rcWindow, crBackground) +
        GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
                                   crRightBottom, nBorderStyle, dsBorder) +
        GetPushButtonAppStream(iconFit.GetFittingBounds() ? rcWindow : rcClient,
                               &font_map, pRolloverIcon, iconFit,
                               csRolloverCaption, crText, font_size, nLayout);

    Write("R", csAP, ByteString());
    if (pRolloverIcon) {
      AddImage("R", pRolloverIcon.Get());
    }

    if (csDownCaption.IsEmpty() && !pDownIcon) {
      csDownCaption = csNormalCaption;
      pDownIcon = pNormalIcon;
    }

    switch (nBorderStyle) {
      case BorderStyle::kBeveled: {
        CFX_Color crTemp = crLeftTop;
        crLeftTop = crRightBottom;
        crRightBottom = crTemp;
        break;
      }
      case BorderStyle::kInset: {
        crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0);
        crRightBottom = CFX_Color(CFX_Color::Type::kGray, 1);
        break;
      }
      default:
        break;
    }
  }
  {
    CPDF_BAFontMap font_map(widget_->GetPDFPage()->GetDocument(),
                            widget_->GetPDFAnnot()->GetMutableAnnotDict(), "D");
    ByteString csAP =
        GetRectFillAppStream(rcWindow, crBackground - 0.25f) +
        GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
                                   crRightBottom, nBorderStyle, dsBorder) +
        GetPushButtonAppStream(iconFit.GetFittingBounds() ? rcWindow : rcClient,
                               &font_map, pDownIcon, iconFit, csDownCaption,
                               crText, font_size, nLayout);

    Write("D", csAP, ByteString());
    if (pDownIcon) {
      AddImage("D", pDownIcon.Get());
    }
  }
}

void CPDFSDK_AppStream::SetAsCheckBox() {
  CPDF_FormControl* pControl = widget_->GetFormControl();
  CFX_Color crBackground = pControl->GetOriginalBackgroundColor();
  CFX_Color crBorder = pControl->GetOriginalBorderColor();
  float fBorderWidth = static_cast<float>(widget_->GetBorderWidth());
  CPWL_Dash dsBorder{3, 0, 0};
  CFX_Color crLeftTop;
  CFX_Color crRightBottom;

  BorderStyle nBorderStyle = widget_->GetBorderStyle();
  switch (nBorderStyle) {
    case BorderStyle::kDash:
      dsBorder = CPWL_Dash{3, 3, 0};
      break;
    case BorderStyle::kBeveled:
      fBorderWidth *= 2;
      crLeftTop = CFX_Color(CFX_Color::Type::kGray, 1);
      crRightBottom = crBackground / 2.0f;
      break;
    case BorderStyle::kInset:
      fBorderWidth *= 2;
      crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0.5);
      crRightBottom = CFX_Color(CFX_Color::Type::kGray, 0.75);
      break;
    default:
      break;
  }

  CFX_FloatRect rcWindow = widget_->GetRotatedRect();
  CFX_FloatRect rcClient = rcWindow.GetDeflated(fBorderWidth, fBorderWidth);
  std::optional<CFX_Color> color = pControl->GetDefaultAppearance().GetColor();
  CFX_Color crText = color.value_or(CFX_Color());

  CheckStyle nStyle = CheckStyleFromCaption(pControl->GetNormalCaption())
                          .value_or(CheckStyle::kCheck);
  ByteString csAP_N_ON =
      GetRectFillAppStream(rcWindow, crBackground) +
      GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
                                 crRightBottom, nBorderStyle, dsBorder);

  ByteString csAP_N_OFF = csAP_N_ON;

  switch (nBorderStyle) {
    case BorderStyle::kBeveled: {
      CFX_Color crTemp = crLeftTop;
      crLeftTop = crRightBottom;
      crRightBottom = crTemp;
      break;
    }
    case BorderStyle::kInset: {
      crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0);
      crRightBottom = CFX_Color(CFX_Color::Type::kGray, 1);
      break;
    }
    default:
      break;
  }

  ByteString csAP_D_ON =
      GetRectFillAppStream(rcWindow, crBackground - 0.25f) +
      GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
                                 crRightBottom, nBorderStyle, dsBorder);

  ByteString csAP_D_OFF = csAP_D_ON;

  csAP_N_ON += GetCheckBoxAppStream(rcClient, nStyle, crText);
  csAP_D_ON += GetCheckBoxAppStream(rcClient, nStyle, crText);

  Write("N", csAP_N_ON, pControl->GetCheckedAPState());
  Write("N", csAP_N_OFF, "Off");

  Write("D", csAP_D_ON, pControl->GetCheckedAPState());
  Write("D", csAP_D_OFF, "Off");

  ByteString csAS = widget_->GetAppState();
  if (csAS.IsEmpty()) {
    widget_->SetAppStateOff();
  }
}

void CPDFSDK_AppStream::SetAsRadioButton() {
  CPDF_FormControl* pControl = widget_->GetFormControl();
  CFX_Color crBackground = pControl->GetOriginalBackgroundColor();
  CFX_Color crBorder = pControl->GetOriginalBorderColor();
  float fBorderWidth = static_cast<float>(widget_->GetBorderWidth());
  CPWL_Dash dsBorder{3, 0, 0};
  CFX_Color crLeftTop;
  CFX_Color crRightBottom;

  BorderStyle nBorderStyle = widget_->GetBorderStyle();
  switch (nBorderStyle) {
    case BorderStyle::kDash:
      dsBorder = CPWL_Dash{3, 3, 0};
      break;
    case BorderStyle::kBeveled:
      fBorderWidth *= 2;
      crLeftTop = CFX_Color(CFX_Color::Type::kGray, 1);
      crRightBottom = crBackground / 2.0f;
      break;
    case BorderStyle::kInset:
      fBorderWidth *= 2;
      crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0.5);
      crRightBottom = CFX_Color(CFX_Color::Type::kGray, 0.75);
      break;
    default:
      break;
  }

  CFX_FloatRect rcWindow = widget_->GetRotatedRect();
  CFX_FloatRect rcClient = rcWindow.GetDeflated(fBorderWidth, fBorderWidth);
  std::optional<CFX_Color> color = pControl->GetDefaultAppearance().GetColor();
  CFX_Color crText = color.value_or(CFX_Color());
  CheckStyle nStyle = CheckStyleFromCaption(pControl->GetNormalCaption())
                          .value_or(CheckStyle::kCircle);

  ByteString csAP_N_ON;
  CFX_FloatRect rcCenter = rcWindow.GetCenterSquare().GetDeflated(1.0f, 1.0f);
  if (nStyle == CheckStyle::kCircle) {
    if (nBorderStyle == BorderStyle::kBeveled) {
      crLeftTop = CFX_Color(CFX_Color::Type::kGray, 1);
      crRightBottom = crBackground - 0.25f;
    } else if (nBorderStyle == BorderStyle::kInset) {
      crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0.5f);
      crRightBottom = CFX_Color(CFX_Color::Type::kGray, 0.75f);
    }

    csAP_N_ON =
        GetCircleFillAppStream(rcCenter, crBackground) +
        GetCircleBorderAppStream(rcCenter, fBorderWidth, crBorder, crLeftTop,
                                 crRightBottom, nBorderStyle, dsBorder);
  } else {
    csAP_N_ON =
        GetRectFillAppStream(rcWindow, crBackground) +
        GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
                                   crRightBottom, nBorderStyle, dsBorder);
  }

  ByteString csAP_N_OFF = csAP_N_ON;

  switch (nBorderStyle) {
    case BorderStyle::kBeveled: {
      CFX_Color crTemp = crLeftTop;
      crLeftTop = crRightBottom;
      crRightBottom = crTemp;
      break;
    }
    case BorderStyle::kInset: {
      crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0);
      crRightBottom = CFX_Color(CFX_Color::Type::kGray, 1);
      break;
    }
    default:
      break;
  }

  ByteString csAP_D_ON;

  if (nStyle == CheckStyle::kCircle) {
    CFX_Color crBK = crBackground - 0.25f;
    if (nBorderStyle == BorderStyle::kBeveled) {
      crLeftTop = crBackground - 0.25f;
      crRightBottom = CFX_Color(CFX_Color::Type::kGray, 1);
      crBK = crBackground;
    } else if (nBorderStyle == BorderStyle::kInset) {
      crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0);
      crRightBottom = CFX_Color(CFX_Color::Type::kGray, 1);
    }

    csAP_D_ON =
        GetCircleFillAppStream(rcCenter, crBK) +
        GetCircleBorderAppStream(rcCenter, fBorderWidth, crBorder, crLeftTop,
                                 crRightBottom, nBorderStyle, dsBorder);
  } else {
    csAP_D_ON =
        GetRectFillAppStream(rcWindow, crBackground - 0.25f) +
        GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
                                   crRightBottom, nBorderStyle, dsBorder);
  }

  ByteString csAP_D_OFF = csAP_D_ON;

  ByteString app_stream = GetRadioButtonAppStream(rcClient, nStyle, crText);
  csAP_N_ON += app_stream;
  csAP_D_ON += app_stream;

  Write("N", csAP_N_ON, pControl->GetCheckedAPState());
  Write("N", csAP_N_OFF, "Off");

  Write("D", csAP_D_ON, pControl->GetCheckedAPState());
  Write("D", csAP_D_OFF, "Off");

  ByteString csAS = widget_->GetAppState();
  if (csAS.IsEmpty()) {
    widget_->SetAppStateOff();
  }
}

void CPDFSDK_AppStream::SetAsComboBox(std::optional<WideString> sValue) {
  CPDF_FormControl* pControl = widget_->GetFormControl();
  CPDF_FormField* pField = pControl->GetField();
  fxcrt::ostringstream sBody;

  CFX_FloatRect rcClient = widget_->GetClientRect();
  CFX_FloatRect rcButton = rcClient;
  rcButton.left = rcButton.right - 13;
  rcButton.Normalize();

  // Font map must outlive |pEdit|.
  CPDF_BAFontMap font_map(widget_->GetPDFPage()->GetDocument(),
                          widget_->GetPDFAnnot()->GetMutableAnnotDict(), "N");

  auto pEdit = std::make_unique<CPWL_EditImpl>();
  pEdit->EnableRefresh(false);
  pEdit->SetFontMap(&font_map);

  CFX_FloatRect rcEdit = rcClient;
  rcEdit.right = rcButton.left;
  rcEdit.Normalize();

  pEdit->SetPlateRect(rcEdit);
  pEdit->SetAlignmentV(1);

  float fFontSize = widget_->GetFontSize();
  if (FXSYS_IsFloatZero(fFontSize)) {
    pEdit->SetAutoFontSize(true);
  } else {
    pEdit->SetFontSize(fFontSize);
  }

  pEdit->Initialize();
  if (sValue.has_value()) {
    pEdit->SetText(sValue.value());
  } else {
    int32_t nCurSel = pField->GetSelectedIndex(0);
    if (nCurSel < 0) {
      pEdit->SetText(pField->GetValue());
    } else {
      pEdit->SetText(pField->GetOptionLabel(nCurSel));
    }
  }
  pEdit->Paint();

  CFX_FloatRect rcContent = pEdit->GetContentRect();
  ByteString sEdit = GetEditAppStream(pEdit.get(), CFX_PointF(), true, 0);
  if (sEdit.GetLength() > 0) {
    sBody << "/Tx ";
    AutoClosedCommand bmc(&sBody, kMarkedSequenceBeginOperator,
                          kMarkedSequenceEndOperator);
    AutoClosedQCommand q(&sBody);

    if (rcContent.Width() > rcEdit.Width() ||
        rcContent.Height() > rcEdit.Height()) {
      WriteAppendRect(sBody, rcEdit);
      sBody << kSetNonZeroWindingClipOperator << "\n"
            << kEndPathNoFillOrStrokeOperator << "\n";
    }

    CFX_Color crText = widget_->GetTextPWLColor();
    AutoClosedCommand bt(&sBody, kTextBeginOperator, kTextEndOperator);
    sBody << GetFillColorAppStream(crText) << sEdit;
  }

  sBody << GetDropButtonAppStream(rcButton);
  Write("N",
        GetBackgroundAppStream() + GetBorderAppStream() + ByteString(sBody),
        ByteString());
}

void CPDFSDK_AppStream::SetAsListBox() {
  CPDF_FormControl* pControl = widget_->GetFormControl();
  CPDF_FormField* pField = pControl->GetField();
  CFX_FloatRect rcClient = widget_->GetClientRect();
  fxcrt::ostringstream sBody;

  // Font map must outlive |pEdit|.
  CPDF_BAFontMap font_map(widget_->GetPDFPage()->GetDocument(),
                          widget_->GetPDFAnnot()->GetMutableAnnotDict(), "N");

  auto pEdit = std::make_unique<CPWL_EditImpl>();
  pEdit->EnableRefresh(false);
  pEdit->SetFontMap(&font_map);
  pEdit->SetPlateRect(CFX_FloatRect(rcClient.left, 0.0f, rcClient.right, 0.0f));

  float fFontSize = widget_->GetFontSize();
  pEdit->SetFontSize(FXSYS_IsFloatZero(fFontSize) ? 12.0f : fFontSize);
  pEdit->Initialize();

  fxcrt::ostringstream sList;
  float fy = rcClient.top;

  int32_t nTop = pField->GetTopVisibleIndex();
  int32_t nCount = pField->CountOptions();
  int32_t nSelCount = pField->CountSelectedItems();

  for (int32_t i = nTop; i < nCount; ++i) {
    bool bSelected = false;
    for (int32_t j = 0; j < nSelCount; ++j) {
      if (pField->GetSelectedIndex(j) == i) {
        bSelected = true;
        break;
      }
    }

    pEdit->SetText(pField->GetOptionLabel(i));
    pEdit->Paint();

    CFX_FloatRect rcContent = pEdit->GetContentRect();
    float fItemHeight = rcContent.Height();

    if (bSelected) {
      CFX_FloatRect rcItem =
          CFX_FloatRect(rcClient.left, fy - fItemHeight, rcClient.right, fy);
      {
        AutoClosedQCommand q(&sList);
        sList << GetFillColorAppStream(CFX_Color(
            CFX_Color::Type::kRGB, 0, 51.0f / 255.0f, 113.0f / 255.0f));
        WriteAppendRect(sList, rcItem);
        sList << kFillOperator << "\n";
      }

      AutoClosedCommand bt(&sList, kTextBeginOperator, kTextEndOperator);
      sList << GetFillColorAppStream(CFX_Color(CFX_Color::Type::kGray, 1))
            << GetEditAppStream(pEdit.get(), CFX_PointF(0.0f, fy), true, 0);
    } else {
      CFX_Color crText = widget_->GetTextPWLColor();

      AutoClosedCommand bt(&sList, kTextBeginOperator, kTextEndOperator);
      sList << GetFillColorAppStream(crText)
            << GetEditAppStream(pEdit.get(), CFX_PointF(0.0f, fy), true, 0);
    }

    fy -= fItemHeight;
  }

  if (sList.tellp() > 0) {
    sBody << "/Tx ";
    AutoClosedCommand bmc(&sBody, kMarkedSequenceBeginOperator,
                          kMarkedSequenceEndOperator);
    AutoClosedQCommand q(&sBody);

    WriteAppendRect(sBody, rcClient);
    sBody << kSetNonZeroWindingClipOperator << "\n"
          << kEndPathNoFillOrStrokeOperator << "\n"
          << sList.str();
  }
  Write("N",
        GetBackgroundAppStream() + GetBorderAppStream() + ByteString(sBody),
        ByteString());
}

void CPDFSDK_AppStream::SetAsTextField(std::optional<WideString> sValue) {
  CPDF_FormControl* pControl = widget_->GetFormControl();
  CPDF_FormField* pField = pControl->GetField();
  fxcrt::ostringstream sBody;
  fxcrt::ostringstream sLines;

  // Font map must outlive |pEdit|.
  CPDF_BAFontMap font_map(widget_->GetPDFPage()->GetDocument(),
                          widget_->GetPDFAnnot()->GetMutableAnnotDict(), "N");

  auto pEdit = std::make_unique<CPWL_EditImpl>();
  pEdit->EnableRefresh(false);
  pEdit->SetFontMap(&font_map);

  CFX_FloatRect rcClient = widget_->GetClientRect();
  pEdit->SetPlateRect(rcClient);
  pEdit->SetAlignmentH(pControl->GetControlAlignment());

  uint32_t dwFieldFlags = pField->GetFieldFlags();
  bool bMultiLine = dwFieldFlags & pdfium::form_flags::kTextMultiline;
  if (bMultiLine) {
    pEdit->SetMultiLine(true);
    pEdit->SetAutoReturn(true);
  } else {
    pEdit->SetAlignmentV(1);
  }

  uint16_t subWord = 0;
  if (dwFieldFlags & pdfium::form_flags::kTextPassword) {
    subWord = '*';
    pEdit->SetPasswordChar(subWord);
  }

  int nMaxLen = pField->GetMaxLen();
  bool bCharArray = dwFieldFlags & pdfium::form_flags::kTextComb;
  float fFontSize = widget_->GetFontSize();

#ifdef PDF_ENABLE_XFA
  if (!sValue.has_value() && widget_->GetMixXFAWidget()) {
    sValue = widget_->GetValue();
  }
#endif  // PDF_ENABLE_XFA

  if (nMaxLen > 0) {
    if (bCharArray) {
      pEdit->SetCharArray(nMaxLen);
      if (FXSYS_IsFloatZero(fFontSize)) {
        fFontSize = CPWL_Edit::GetCharArrayAutoFontSize(
            font_map.GetPDFFont(0).Get(), rcClient, nMaxLen);
      }
    } else {
      if (sValue.has_value()) {
        nMaxLen = pdfium::checked_cast<int>(sValue.value().GetLength());
      }
      pEdit->SetLimitChar(nMaxLen);
    }
  }

  if (FXSYS_IsFloatZero(fFontSize)) {
    pEdit->SetAutoFontSize(true);
  } else {
    pEdit->SetFontSize(fFontSize);
  }

  pEdit->Initialize();
  pEdit->SetText(sValue.value_or(pField->GetValue()));
  pEdit->Paint();

  CFX_FloatRect rcContent = pEdit->GetContentRect();
  ByteString sEdit =
      GetEditAppStream(pEdit.get(), CFX_PointF(), !bCharArray, subWord);

  if (sEdit.GetLength() > 0) {
    sBody << "/Tx ";
    AutoClosedCommand bmc(&sBody, kMarkedSequenceBeginOperator,
                          kMarkedSequenceEndOperator);
    AutoClosedQCommand q(&sBody);

    if (rcContent.Width() > rcClient.Width() ||
        rcContent.Height() > rcClient.Height()) {
      WriteAppendRect(sBody, rcClient);
      sBody << kSetNonZeroWindingClipOperator << "\n"
            << kEndPathNoFillOrStrokeOperator << "\n";
    }
    CFX_Color crText = widget_->GetTextPWLColor();

    AutoClosedCommand bt(&sBody, kTextBeginOperator, kTextEndOperator);
    sBody << GetFillColorAppStream(crText) << sEdit;
  }

  if (bCharArray) {
    switch (widget_->GetBorderStyle()) {
      case BorderStyle::kSolid: {
        ByteString sColor =
            GetStrokeColorAppStream(widget_->GetBorderPWLColor());
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q(&sLines);
          sLines << widget_->GetBorderWidth() << " " << kSetLineWidthOperator
                 << "\n"
                 << GetStrokeColorAppStream(widget_->GetBorderPWLColor())
                 << " 2 " << kSetLineCapStyleOperator << " 0 "
                 << kSetLineJoinStyleOperator << "\n";

          const float width = rcClient.right - rcClient.left;
          for (int32_t i = 1; i < nMaxLen; ++i) {
            const float left = rcClient.left + (width / nMaxLen) * i;
            WriteMove(sLines, {left, rcClient.bottom});
            WriteLine(sLines, {left, rcClient.top});
            sLines << kStrokeOperator << "\n";
          }
        }
        break;
      }
      case BorderStyle::kDash: {
        ByteString sColor =
            GetStrokeColorAppStream(widget_->GetBorderPWLColor());
        if (sColor.GetLength() > 0) {
          CPWL_Dash dsBorder = CPWL_Dash{3, 3, 0};
          AutoClosedQCommand q(&sLines);
          sLines << widget_->GetBorderWidth() << " " << kSetLineWidthOperator
                 << "\n"
                 << GetStrokeColorAppStream(widget_->GetBorderPWLColor()) << "["
                 << dsBorder.nDash << " " << dsBorder.nGap << "] "
                 << dsBorder.nPhase << " " << kSetDashOperator << "\n";

          const float width = rcClient.right - rcClient.left;
          for (int32_t i = 1; i < nMaxLen; ++i) {
            const float left = rcClient.left + (width / nMaxLen) * i;
            WriteMove(sLines, {left, rcClient.bottom});
            WriteLine(sLines, {left, rcClient.top});
            sLines << kStrokeOperator << "\n";
          }
        }
        break;
      }
      default:
        break;
    }
  }

  Write("N",
        GetBackgroundAppStream() + GetBorderAppStream() + ByteString(sLines) +
            ByteString(sBody),
        ByteString());
}

void CPDFSDK_AppStream::AddImage(const ByteString& sAPType,
                                 const CPDF_Stream* pImage) {
  RetainPtr<CPDF_Stream> pStream =
      dict_->GetMutableStreamFor(sAPType.AsStringView());
  RetainPtr<CPDF_Dictionary> pStreamDict = pStream->GetMutableDict();

  const ByteString sImageAlias = pImage->GetDict()->GetByteStringFor("Name");

  RetainPtr<CPDF_Dictionary> pStreamResList =
      pStreamDict->GetOrCreateDictFor("Resources");
  auto pXObject = pStreamResList->SetNewFor<CPDF_Dictionary>("XObject");
  pXObject->SetNewFor<CPDF_Reference>(sImageAlias,
                                      widget_->GetPageView()->GetPDFDocument(),
                                      pImage->GetObjNum());
}

void CPDFSDK_AppStream::Write(ByteStringView sAPType,
                              const ByteString& sContents,
                              const ByteString& sAPState) {
  RetainPtr<CPDF_Dictionary> parent_dict;
  ByteString key;
  if (sAPState.IsEmpty()) {
    parent_dict = dict_;
    key = sAPType;
  } else {
    parent_dict = dict_->GetOrCreateDictFor(sAPType);
    key = sAPState;
  }

  // If `stream` is created by CreateModifiedAPStream(), then it is safe to
  // edit, as it is not shared.
  RetainPtr<CPDF_Stream> stream =
      parent_dict->GetMutableStreamFor(key.AsStringView());
  CPDF_Document* doc = widget_->GetPageView()->GetPDFDocument();
  if (!doc->IsModifiedAPStream(stream.Get())) {
    auto new_stream_dict = doc->New<CPDF_Dictionary>();
    new_stream_dict->SetNewFor<CPDF_Name>("Type", "XObject");
    new_stream_dict->SetNewFor<CPDF_Name>("Subtype", "Form");
    new_stream_dict->SetNewFor<CPDF_Number>("FormType", 1);

    if (stream) {
      RetainPtr<const CPDF_Dictionary> original_stream_dict = stream->GetDict();
      if (original_stream_dict) {
        RetainPtr<const CPDF_Dictionary> resources_dict =
            original_stream_dict->GetDictFor("Resources");
        if (resources_dict) {
          new_stream_dict->SetFor("Resources", resources_dict->Clone());
        }
      }
    }
    stream = doc->CreateModifiedAPStream(std::move(new_stream_dict));
    parent_dict->SetNewFor<CPDF_Reference>(key, doc, stream->GetObjNum());
  }

  RetainPtr<CPDF_Dictionary> stream_dict = stream->GetMutableDict();
  stream_dict->SetMatrixFor("Matrix", widget_->GetMatrix());
  stream_dict->SetRectFor("BBox", widget_->GetRotatedRect());
  stream->SetDataAndRemoveFilter(sContents.unsigned_span());
}

void CPDFSDK_AppStream::Remove(ByteStringView sAPType) {
  dict_->RemoveFor(sAPType);
}

ByteString CPDFSDK_AppStream::GetBackgroundAppStream() const {
  CFX_Color crBackground = widget_->GetFillPWLColor();
  if (crBackground.nColorType != CFX_Color::Type::kTransparent) {
    return GetRectFillAppStream(widget_->GetRotatedRect(), crBackground);
  }

  return ByteString();
}

ByteString CPDFSDK_AppStream::GetBorderAppStream() const {
  CFX_FloatRect rcWindow = widget_->GetRotatedRect();
  CFX_Color crBorder = widget_->GetBorderPWLColor();
  CFX_Color crBackground = widget_->GetFillPWLColor();
  CFX_Color crLeftTop;
  CFX_Color crRightBottom;

  float fBorderWidth = static_cast<float>(widget_->GetBorderWidth());
  CPWL_Dash dsBorder{3, 0, 0};

  BorderStyle nBorderStyle = widget_->GetBorderStyle();
  switch (nBorderStyle) {
    case BorderStyle::kDash:
      dsBorder = CPWL_Dash{3, 3, 0};
      break;
    case BorderStyle::kBeveled:
      fBorderWidth *= 2;
      crLeftTop = CFX_Color(CFX_Color::Type::kGray, 1);
      crRightBottom = crBackground / 2.0f;
      break;
    case BorderStyle::kInset:
      fBorderWidth *= 2;
      crLeftTop = CFX_Color(CFX_Color::Type::kGray, 0.5);
      crRightBottom = CFX_Color(CFX_Color::Type::kGray, 0.75);
      break;
    default:
      break;
  }

  return GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
                                    crRightBottom, nBorderStyle, dsBorder);
}
