// Copyright 2017 PDFium Authors. All rights reserved.
// 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 <memory>
#include <utility>

#include "constants/form_flags.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/fpdfdoc/cba_fontmap.h"
#include "core/fpdfdoc/cpdf_formcontrol.h"
#include "core/fpdfdoc/cpdf_icon.h"
#include "core/fpdfdoc/cpvt_word.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_icon.h"
#include "fpdfsdk/pwl/cpwl_wnd.h"
#include "base/stl_util.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
};

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 kSetCharacterSpacingOperator[] = "Tc";
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(std::ostringstream* stream,
                    ByteString open,
                    ByteString close)
      : stream_(stream), close_(close) {
    *stream_ << open << "\n";
  }

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

 private:
  std::ostringstream* stream_;
  ByteString close_;
};

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

ByteString GetColorAppStream(const CFX_Color& color,
                             const bool& bFillOrStroke) {
  std::ostringstream sColorStream;

  switch (color.nColorType) {
    case CFX_Color::kRGB:
      sColorStream << color.fColor1 << " " << color.fColor2 << " "
                   << color.fColor3 << " "
                   << (bFillOrStroke ? kSetRGBOperator : kSetRGBStrokedOperator)
                   << "\n";
      break;
    case CFX_Color::kGray:
      sColorStream << color.fColor1 << " "
                   << (bFillOrStroke ? kSetGrayOperator
                                     : kSetGrayStrokedOperator)
                   << "\n";
      break;
    case CFX_Color::kCMYK:
      sColorStream << color.fColor1 << " " << color.fColor2 << " "
                   << color.fColor3 << " " << color.fColor4 << " "
                   << (bFillOrStroke ? kSetCMYKOperator
                                     : kSetCMKYStrokedOperator)
                   << "\n";
      break;
  }

  return ByteString(sColorStream);
}

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

  CFX_PointF pts[8][3] = {{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 (size_t i = 0; i < pdfium::size(pts); ++i) {
    for (size_t j = 0; j < pdfium::size(pts[0]); ++j) {
      pts[i][j].x = pts[i][j].x * fWidth + crBBox.left;
      pts[i][j].y *= pts[i][j].y * fHeight + crBBox.bottom;
    }
  }

  std::ostringstream csAP;
  csAP << pts[0][0].x << " " << pts[0][0].y << " " << kMoveToOperator << "\n";

  for (size_t i = 0; i < pdfium::size(pts); ++i) {
    size_t nNext = i < pdfium::size(pts) - 1 ? i + 1 : 0;

    float px1 = pts[i][1].x - pts[i][0].x;
    float py1 = pts[i][1].y - pts[i][0].y;
    float px2 = pts[i][2].x - pts[nNext][0].x;
    float py2 = pts[i][2].y - pts[nNext][0].y;

    csAP << pts[i][0].x + px1 * FX_BEZIER << " "
         << pts[i][0].y + py1 * FX_BEZIER << " "
         << pts[nNext][0].x + px2 * FX_BEZIER << " "
         << pts[nNext][0].y + py2 * FX_BEZIER << " " << pts[nNext][0].x << " "
         << pts[nNext][0].y << " " << kCurveToOperator << "\n";
  }

  return ByteString(csAP);
}

ByteString GetAP_Circle(const CFX_FloatRect& crBBox) {
  std::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);

  csAP << pt1.x << " " << pt1.y << " " << kMoveToOperator << "\n";

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

  csAP << pt1.x << " " << pt1.y + py * FX_BEZIER << " "
       << pt2.x - px * FX_BEZIER << " " << pt2.y << " " << pt2.x << " " << pt2.y
       << " " << kCurveToOperator << "\n";

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

  csAP << pt2.x + px * FX_BEZIER << " " << pt2.y << " " << pt3.x << " "
       << pt3.y + py * FX_BEZIER << " " << pt3.x << " " << pt3.y << " "
       << kCurveToOperator << "\n";

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

  csAP << pt3.x << " " << pt3.y - py * FX_BEZIER << " "
       << pt4.x + px * FX_BEZIER << " " << pt4.y << " " << pt4.x << " " << pt4.y
       << " " << kCurveToOperator << "\n";

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

  csAP << pt4.x - px * FX_BEZIER << " " << pt4.y << " " << pt1.x << " "
       << pt1.y - py * FX_BEZIER << " " << pt1.x << " " << pt1.y << " "
       << kCurveToOperator << "\n";

  return ByteString(csAP);
}

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

  csAP << crBBox.left << " " << crBBox.top << " " << kMoveToOperator << "\n";
  csAP << crBBox.right << " " << crBBox.bottom << " " << kLineToOperator
       << "\n";
  csAP << crBBox.left << " " << crBBox.bottom << " " << kMoveToOperator << "\n";
  csAP << crBBox.right << " " << crBBox.top << " " << kLineToOperator << "\n";

  return ByteString(csAP);
}

ByteString GetAP_Diamond(const CFX_FloatRect& crBBox) {
  std::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);

  csAP << pt1.x << " " << pt1.y << " " << kMoveToOperator << "\n";
  csAP << pt2.x << " " << pt2.y << " " << kLineToOperator << "\n";
  csAP << pt3.x << " " << pt3.y << " " << kLineToOperator << "\n";
  csAP << pt4.x << " " << pt4.y << " " << kLineToOperator << "\n";
  csAP << pt1.x << " " << pt1.y << " " << kLineToOperator << "\n";

  return ByteString(csAP);
}

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

  csAP << crBBox.left << " " << crBBox.top << " " << kMoveToOperator << "\n";
  csAP << crBBox.right << " " << crBBox.top << " " << kLineToOperator << "\n";
  csAP << crBBox.right << " " << crBBox.bottom << " " << kLineToOperator
       << "\n";
  csAP << crBBox.left << " " << crBBox.bottom << " " << kLineToOperator << "\n";
  csAP << crBBox.left << " " << crBBox.top << " " << kLineToOperator << "\n";

  return ByteString(csAP);
}

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

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

  CFX_PointF points[5];
  float fAngle = FX_PI / 10.0f;
  for (auto& point : points) {
    point =
        ptCenter + CFX_PointF(fRadius * cosf(fAngle), fRadius * sinf(fAngle));
    fAngle += FX_PI * 2 / 5.0f;
  }

  csAP << points[0].x << " " << points[0].y << " " << kMoveToOperator << "\n";

  int next = 0;
  for (size_t i = 0; i < pdfium::size(points); ++i) {
    next = (next + 2) % pdfium::size(points);
    csAP << points[next].x << " " << points[next].y << " " << kLineToOperator
         << "\n";
  }

  return ByteString(csAP);
}

ByteString GetAP_HalfCircle(const CFX_FloatRect& crBBox, float fRotate) {
  std::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);

  float px;
  float py;

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

  csAP << pt1.x << " " << pt1.y << " " << kMoveToOperator << "\n";

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

  csAP << pt1.x << " " << pt1.y + py * FX_BEZIER << " "
       << pt2.x - px * FX_BEZIER << " " << pt2.y << " " << pt2.x << " " << pt2.y
       << " " << kCurveToOperator << "\n";

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

  csAP << pt2.x + px * FX_BEZIER << " " << pt2.y << " " << pt3.x << " "
       << pt3.y + py * FX_BEZIER << " " << pt3.x << " " << pt3.y << " "
       << kCurveToOperator << "\n";

  return ByteString(csAP);
}

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

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

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

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

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

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

ByteString GetCircleFillAppStream(const CFX_FloatRect& rect,
                                  const CFX_Color& color) {
  std::ostringstream sAppStream;
  ByteString sColor = GetColorAppStream(color, true);
  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) {
  std::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) {
      default:
      case BorderStyle::kSolid:
      case BorderStyle::kUnderline: {
        sColor = GetColorAppStream(color, false);
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q2(&sAppStream);
          sAppStream << fWidth << " " << kSetLineWidthOperator << "\n"
                     << sColor << GetAP_Circle(rect_by_2) << " "
                     << kStrokeOperator << "\n";
        }
      } break;
      case BorderStyle::kDash: {
        sColor = GetColorAppStream(color, false);
        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 = GetColorAppStream(color, false);
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q2(&sAppStream);
          sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
                     << sColor << GetAP_Circle(rect) << " " << kStrokeOperator
                     << "\n";
        }

        sColor = GetColorAppStream(crLeftTop, false);
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q2(&sAppStream);
          sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
                     << sColor << GetAP_HalfCircle(rect_by_75, FX_PI / 4.0f)
                     << " " << kStrokeOperator << "\n";
        }

        sColor = GetColorAppStream(crRightBottom, false);
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q2(&sAppStream);
          sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
                     << sColor << GetAP_HalfCircle(rect_by_75, FX_PI * 5 / 4.0f)
                     << " " << kStrokeOperator << "\n";
        }
      } break;
      case BorderStyle::kInset: {
        sColor = GetColorAppStream(color, false);
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q2(&sAppStream);
          sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
                     << sColor << GetAP_Circle(rect) << " " << kStrokeOperator
                     << "\n";
        }

        sColor = GetColorAppStream(crLeftTop, false);
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q2(&sAppStream);
          sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
                     << sColor << GetAP_HalfCircle(rect_by_75, FX_PI / 4.0f)
                     << " " << kStrokeOperator << "\n";
        }

        sColor = GetColorAppStream(crRightBottom, false);
        if (sColor.GetLength() > 0) {
          AutoClosedQCommand q2(&sAppStream);
          sAppStream << fHalfWidth << " " << kSetLineWidthOperator << "\n"
                     << sColor << GetAP_HalfCircle(rect_by_75, FX_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) {
    default:
    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) {
    default:
    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* pFontMap,
                            int32_t nFontIndex,
                            float fFontSize) {
  if (!pFontMap)
    return ByteString();

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

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

ByteString GetWordRenderString(const ByteString& strWords) {
  if (strWords.GetLength() > 0) {
    return PDF_EncodeString(strWords, false) + " " + kShowTextOperator + "\n";
  }
  return ByteString();
}

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

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

  while (pIterator->NextWord()) {
    CPVT_WordPlace place = pIterator->GetAt();
    if (bContinuous) {
      if (place.LineCmp(oldplace) != 0) {
        if (sWords.tellp() > 0) {
          sEditStream << GetWordRenderString(ByteString(sWords));
          sWords.str("");
        }

        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) {
          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.tellp() > 0) {
            sEditStream << GetWordRenderString(ByteString(sWords));
            sWords.str("");
          }
          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) {
          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));
      }
    }
  }

  if (sWords.tellp() > 0) {
    sEditStream << GetWordRenderString(ByteString(sWords));
    sWords.str("");
  }

  std::ostringstream sAppStream;
  if (sEditStream.tellp() > 0) {
    float fCharSpace = pEdit->GetCharSpace();
    if (!IsFloatZero(fCharSpace))
      sAppStream << fCharSpace << " " << kSetCharacterSpacingOperator << "\n";

    sAppStream << sEditStream.str();
  }

  return ByteString(sAppStream);
}

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

  CPWL_Wnd::CreateParams cp;
  cp.dwFlags = PWS_VISIBLE;

  CPWL_Icon icon(cp, std::make_unique<CPDF_Icon>(pIconStream), &fit);
  icon.Realize();
  if (!icon.Move(rcIcon, false, false))
    return ByteString();

  ByteString sAlias = icon.GetImageAlias();
  if (sAlias.GetLength() <= 0)
    return ByteString();

  CFX_FloatRect rcPlate = icon.GetClientRect();
  CFX_Matrix mt = icon.GetImageMatrix().GetInverse();

  float fHScale;
  float fVScale;
  std::tie(fHScale, fVScale) = icon.GetScale();

  float fx;
  float fy;
  std::tie(fx, fy) = icon.GetImageOffset();

  std::ostringstream str;
  {
    AutoClosedQCommand q(&str);
    str << rcPlate.left << " " << rcPlate.bottom << " "
        << rcPlate.right - rcPlate.left << " " << rcPlate.top - rcPlate.bottom
        << " " << kAppendRectOperator << " " << kSetNonZeroWindingClipOperator
        << " " << kEndPathNoFillOrStrokeOperator << "\n";

    str << fHScale << " 0 0 " << fVScale << " " << rcPlate.left + fx << " "
        << rcPlate.bottom + fy << " " << kConcatMatrixOperator << "\n";
    str << mt.a << " " << mt.b << " " << mt.c << " " << mt.d << " " << mt.e
        << " " << mt.f << " " << kConcatMatrixOperator << "\n";

    str << "0 " << kSetGrayOperator << " 0 " << kSetGrayStrokedOperator << " 1 "
        << kSetLineWidthOperator << " /" << sAlias << " "
        << kInvokeNamedXObjectOperator << "\n";
  }
  icon.Destroy();

  return ByteString(str);
}

ByteString GetPushButtonAppStream(const CFX_FloatRect& rcBBox,
                                  IPVT_FontMap* pFontMap,
                                  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(pFontMap);
  pEdit->SetAlignmentH(1, true);
  pEdit->SetAlignmentV(1, true);
  pEdit->SetMultiLine(false, true);
  pEdit->SetAutoReturn(false, true);
  if (IsFloatZero(fFontSize))
    pEdit->SetAutoFontSize(true, true);
  else
    pEdit->SetFontSize(fFontSize);

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

  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 (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 (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 (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 (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;
  }

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

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

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

  std::ostringstream sAppStream;
  {
    AutoClosedQCommand q(&sAppStream);
    sAppStream << rcBBox.left << " " << rcBBox.bottom << " "
               << rcBBox.right - rcBBox.left << " "
               << rcBBox.top - rcBBox.bottom << " " << kAppendRectOperator
               << " " << 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) {
  std::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) {
      default:
      case BorderStyle::kSolid:
        sColor = GetColorAppStream(color, true);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          sAppStream << fLeft << " " << fBottom << " " << fRight - fLeft << " "
                     << fTop - fBottom << " " << kAppendRectOperator << "\n";
          sAppStream << fLeft + fWidth << " " << fBottom + fWidth << " "
                     << fRight - fLeft - fWidth * 2 << " "
                     << fTop - fBottom - fWidth * 2 << " "
                     << kAppendRectOperator << "\n";
          sAppStream << kFillEvenOddOperator << "\n";
        }
        break;
      case BorderStyle::kDash:
        sColor = GetColorAppStream(color, false);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          sAppStream << fWidth << " " << kSetLineWidthOperator << " ["
                     << dash.nDash << " " << dash.nGap << "] " << dash.nPhase
                     << " " << kSetDashOperator << "\n";
          sAppStream << fLeft + fWidth / 2 << " " << fBottom + fWidth / 2 << " "
                     << kMoveToOperator << "\n";
          sAppStream << fLeft + fWidth / 2 << " " << fTop - fWidth / 2 << " "
                     << kLineToOperator << "\n";
          sAppStream << fRight - fWidth / 2 << " " << fTop - fWidth / 2 << " "
                     << kLineToOperator << "\n";
          sAppStream << fRight - fWidth / 2 << " " << fBottom + fWidth / 2
                     << " " << kLineToOperator << "\n";
          sAppStream << fLeft + fWidth / 2 << " " << fBottom + fWidth / 2 << " "
                     << kLineToOperator << " " << kStrokeOperator << "\n";
        }
        break;
      case BorderStyle::kBeveled:
      case BorderStyle::kInset:
        sColor = GetColorAppStream(crLeftTop, true);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          sAppStream << fLeft + fHalfWidth << " " << fBottom + fHalfWidth << " "
                     << kMoveToOperator << "\n";
          sAppStream << fLeft + fHalfWidth << " " << fTop - fHalfWidth << " "
                     << kLineToOperator << "\n";
          sAppStream << fRight - fHalfWidth << " " << fTop - fHalfWidth << " "
                     << kLineToOperator << "\n";
          sAppStream << fRight - fHalfWidth * 2 << " " << fTop - fHalfWidth * 2
                     << " " << kLineToOperator << "\n";
          sAppStream << fLeft + fHalfWidth * 2 << " " << fTop - fHalfWidth * 2
                     << " " << kLineToOperator << "\n";
          sAppStream << fLeft + fHalfWidth * 2 << " "
                     << fBottom + fHalfWidth * 2 << " " << kLineToOperator
                     << " " << kFillOperator << "\n";
        }

        sColor = GetColorAppStream(crRightBottom, true);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          sAppStream << fRight - fHalfWidth << " " << fTop - fHalfWidth << " "
                     << kMoveToOperator << "\n";
          sAppStream << fRight - fHalfWidth << " " << fBottom + fHalfWidth
                     << " " << kLineToOperator << "\n";
          sAppStream << fLeft + fHalfWidth << " " << fBottom + fHalfWidth << " "
                     << kLineToOperator << "\n";
          sAppStream << fLeft + fHalfWidth * 2 << " "
                     << fBottom + fHalfWidth * 2 << " " << kLineToOperator
                     << "\n";
          sAppStream << fRight - fHalfWidth * 2 << " "
                     << fBottom + fHalfWidth * 2 << " " << kLineToOperator
                     << "\n";
          sAppStream << fRight - fHalfWidth * 2 << " " << fTop - fHalfWidth * 2
                     << " " << kLineToOperator << " " << kFillOperator << "\n";
        }

        sColor = GetColorAppStream(color, true);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          sAppStream << fLeft << " " << fBottom << " " << fRight - fLeft << " "
                     << fTop - fBottom << " " << kAppendRectOperator << "\n";
          sAppStream << fLeft + fHalfWidth << " " << fBottom + fHalfWidth << " "
                     << fRight - fLeft - fHalfWidth * 2 << " "
                     << fTop - fBottom - fHalfWidth * 2 << " "
                     << kAppendRectOperator << " " << kFillEvenOddOperator
                     << "\n";
        }
        break;
      case BorderStyle::kUnderline:
        sColor = GetColorAppStream(color, false);
        if (sColor.GetLength() > 0) {
          sAppStream << sColor;
          sAppStream << fWidth << " " << kSetLineWidthOperator << "\n";
          sAppStream << fLeft << " " << fBottom + fWidth / 2 << " "
                     << kMoveToOperator << "\n";
          sAppStream << fRight << " " << fBottom + fWidth / 2 << " "
                     << kLineToOperator << " " << kStrokeOperator << "\n";
        }
        break;
    }
  }

  return ByteString(sAppStream);
}

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

  std::ostringstream sAppStream;
  {
    AutoClosedQCommand q(&sAppStream);
    sAppStream << GetColorAppStream(CFX_Color(CFX_Color::kRGB, 220.0f / 255.0f,
                                              220.0f / 255.0f, 220.0f / 255.0f),
                                    true)
               << rcBBox.left << " " << rcBBox.bottom << " "
               << rcBBox.right - rcBBox.left << " "
               << rcBBox.top - rcBBox.bottom << " " << kAppendRectOperator
               << " " << kFillOperator << "\n";
  }

  {
    AutoClosedQCommand q(&sAppStream);
    sAppStream << GetBorderAppStreamInternal(
        rcBBox, 2, CFX_Color(CFX_Color::kGray, 0),
        CFX_Color(CFX_Color::kGray, 1), CFX_Color(CFX_Color::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 (IsFloatBigger(rcBBox.right - rcBBox.left, 6) &&
      IsFloatBigger(rcBBox.top - rcBBox.bottom, 6)) {
    AutoClosedQCommand q(&sAppStream);
    sAppStream << " 0 " << kSetGrayOperator << "\n"
               << ptCenter.x - 3 << " " << ptCenter.y + 1.5f << " "
               << kMoveToOperator << "\n"
               << ptCenter.x + 3 << " " << ptCenter.y + 1.5f << " "
               << kLineToOperator << "\n"
               << ptCenter.x << " " << ptCenter.y - 1.5f << " "
               << kLineToOperator << "\n"
               << ptCenter.x - 3 << " " << ptCenter.y + 1.5f << " "
               << kLineToOperator << " " << kFillOperator << "\n";
  }

  return ByteString(sAppStream);
}

ByteString GetRectFillAppStream(const CFX_FloatRect& rect,
                                const CFX_Color& color) {
  std::ostringstream sAppStream;
  ByteString sColor = GetColorAppStream(color, true);
  if (sColor.GetLength() > 0) {
    AutoClosedQCommand q(&sAppStream);
    sAppStream << sColor << rect.left << " " << rect.bottom << " "
               << rect.right - rect.left << " " << rect.top - rect.bottom << " "
               << kAppendRectOperator << " " << kFillOperator << "\n";
  }

  return ByteString(sAppStream);
}

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

  CPDF_Dictionary* pImageDict = pIcon->GetDict();
  if (!pImageDict)
    return;

  if (pImageDict->KeyExist("Name"))
    return;

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

Optional<CheckStyle> CheckStyleFromCaption(const WideString& caption) {
  if (caption.IsEmpty())
    return pdfium::nullopt;

  // Character values are ZapfDingbats encodings of named glyphs.
  switch (caption[0]) {
    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 pdfium::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 = ButtonStyle::kLabel;
  switch (pControl->GetTextPosition()) {
    case TEXTPOS_ICON:
      nLayout = ButtonStyle::kIcon;
      break;
    case TEXTPOS_BELOW:
      nLayout = ButtonStyle::kIconTopLabelBottom;
      break;
    case TEXTPOS_ABOVE:
      nLayout = ButtonStyle::kIconBottomLabelTop;
      break;
    case TEXTPOS_RIGHT:
      nLayout = ButtonStyle::kIconLeftLabelRight;
      break;
    case TEXTPOS_LEFT:
      nLayout = ButtonStyle::kIconRightLabelLeft;
      break;
    case TEXTPOS_OVERLAID:
      nLayout = ButtonStyle::kLabelOverIcon;
      break;
    default:
      nLayout = ButtonStyle::kLabel;
      break;
  }

  CFX_Color crBackground;
  CFX_Color crBorder;
  int iColorType;
  float fc[4];
  pControl->GetOriginalBackgroundColor(iColorType, fc);
  if (iColorType > 0)
    crBackground = CFX_Color(iColorType, fc[0], fc[1], fc[2], fc[3]);

  pControl->GetOriginalBorderColor(iColorType, fc);
  if (iColorType > 0)
    crBorder = CFX_Color(iColorType, fc[0], fc[1], fc[2], fc[3]);

  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::kGray, 1);
      crRightBottom = crBackground / 2.0f;
      break;
    case BorderStyle::kInset:
      fBorderWidth *= 2;
      crLeftTop = CFX_Color(CFX_Color::kGray, 0.5);
      crRightBottom = CFX_Color(CFX_Color::kGray, 0.75);
      break;
    default:
      break;
  }

  CFX_FloatRect rcClient = rcWindow.GetDeflated(fBorderWidth, fBorderWidth);
  CFX_Color crText(CFX_Color::kGray, 0);
  ByteString csNameTag;
  CPDF_DefaultAppearance da = pControl->GetDefaultAppearance();
  Optional<CFX_Color::Type> color = da.GetColor(fc);
  if (color) {
    iColorType = *color;
    crText = CFX_Color(iColorType, fc[0], fc[1], fc[2], fc[3]);
  }

  float fFontSize;
  Optional<ByteString> font = da.GetFont(&fFontSize);
  if (font)
    csNameTag = *font;
  else
    fFontSize = 12.0f;

  WideString csWCaption;
  WideString csNormalCaption;
  WideString csRolloverCaption;
  WideString csDownCaption;
  if (pControl->HasMKEntry("CA"))
    csNormalCaption = pControl->GetNormalCaption();

  if (pControl->HasMKEntry("RC"))
    csRolloverCaption = pControl->GetRolloverCaption();

  if (pControl->HasMKEntry("AC"))
    csDownCaption = pControl->GetDownCaption();

  CPDF_Stream* pNormalIcon = nullptr;
  CPDF_Stream* pRolloverIcon = nullptr;
  CPDF_Stream* pDownIcon = nullptr;
  if (pControl->HasMKEntry("I"))
    pNormalIcon = pControl->GetNormalIcon();

  if (pControl->HasMKEntry("RI"))
    pRolloverIcon = pControl->GetRolloverIcon();

  if (pControl->HasMKEntry("IX"))
    pDownIcon = pControl->GetDownIcon();

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

  CBA_FontMap font_map(widget_->GetPDFPage()->GetDocument(),
                       widget_->GetPDFAnnot()->GetAnnotDict());
  font_map.SetAPType("N");

  CPDF_IconFit iconFit = pControl->GetIconFit();
  ByteString csAP =
      GetRectFillAppStream(rcWindow, crBackground) +
      GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
                                 crRightBottom, nBorderStyle, dsBorder) +
      GetPushButtonAppStream(iconFit.GetFittingBounds() ? rcWindow : rcClient,
                             &font_map, pNormalIcon, iconFit, csNormalCaption,
                             crText, fFontSize, nLayout);

  Write("N", csAP, ByteString());
  if (pNormalIcon)
    AddImage("N", pNormalIcon);

  CPDF_FormControl::HighlightingMode eHLM = pControl->GetHighlightingMode();
  if (eHLM == CPDF_FormControl::Push || eHLM == CPDF_FormControl::Toggle) {
    if (csRolloverCaption.IsEmpty() && !pRolloverIcon) {
      csRolloverCaption = csNormalCaption;
      pRolloverIcon = pNormalIcon;
    }

    font_map.SetAPType("R");

    csAP =
        GetRectFillAppStream(rcWindow, crBackground) +
        GetBorderAppStreamInternal(rcWindow, fBorderWidth, crBorder, crLeftTop,
                                   crRightBottom, nBorderStyle, dsBorder) +
        GetPushButtonAppStream(iconFit.GetFittingBounds() ? rcWindow : rcClient,
                               &font_map, pRolloverIcon, iconFit,
                               csRolloverCaption, crText, fFontSize, nLayout);

    Write("R", csAP, ByteString());
    if (pRolloverIcon)
      AddImage("R", pRolloverIcon);

    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::kGray, 0);
        crRightBottom = CFX_Color(CFX_Color::kGray, 1);
        break;
      }
      default:
        break;
    }

    font_map.SetAPType("D");

    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, fFontSize, nLayout);

    Write("D", csAP, ByteString());
    if (pDownIcon)
      AddImage("D", pDownIcon);
  } else {
    Remove("D");
    Remove("R");
  }
}

void CPDFSDK_AppStream::SetAsCheckBox() {
  CPDF_FormControl* pControl = widget_->GetFormControl();
  CFX_Color crBackground, crBorder, crText;
  int iColorType;
  float fc[4];

  pControl->GetOriginalBackgroundColor(iColorType, fc);
  if (iColorType > 0)
    crBackground = CFX_Color(iColorType, fc[0], fc[1], fc[2], fc[3]);

  pControl->GetOriginalBorderColor(iColorType, fc);
  if (iColorType > 0)
    crBorder = CFX_Color(iColorType, fc[0], fc[1], fc[2], fc[3]);

  float fBorderWidth = static_cast<float>(widget_->GetBorderWidth());
  CPWL_Dash dsBorder(3, 0, 0);
  CFX_Color crLeftTop, 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::kGray, 1);
      crRightBottom = crBackground / 2.0f;
      break;
    case BorderStyle::kInset:
      fBorderWidth *= 2;
      crLeftTop = CFX_Color(CFX_Color::kGray, 0.5);
      crRightBottom = CFX_Color(CFX_Color::kGray, 0.75);
      break;
    default:
      break;
  }

  CFX_FloatRect rcWindow = widget_->GetRotatedRect();
  CFX_FloatRect rcClient = rcWindow.GetDeflated(fBorderWidth, fBorderWidth);
  CPDF_DefaultAppearance da = pControl->GetDefaultAppearance();
  Optional<CFX_Color::Type> color = da.GetColor(fc);
  if (color) {
    iColorType = *color;
    crText = CFX_Color(iColorType, fc[0], fc[1], fc[2], fc[3]);
  }

  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::kGray, 0);
      crRightBottom = CFX_Color(CFX_Color::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_->SetAppState("Off");
}

void CPDFSDK_AppStream::SetAsRadioButton() {
  CPDF_FormControl* pControl = widget_->GetFormControl();
  CFX_Color crBackground;
  CFX_Color crBorder;
  CFX_Color crText;
  int iColorType;
  float fc[4];

  pControl->GetOriginalBackgroundColor(iColorType, fc);
  if (iColorType > 0)
    crBackground = CFX_Color(iColorType, fc[0], fc[1], fc[2], fc[3]);

  pControl->GetOriginalBorderColor(iColorType, fc);
  if (iColorType > 0)
    crBorder = CFX_Color(iColorType, fc[0], fc[1], fc[2], fc[3]);

  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::kGray, 1);
      crRightBottom = crBackground / 2.0f;
      break;
    case BorderStyle::kInset:
      fBorderWidth *= 2;
      crLeftTop = CFX_Color(CFX_Color::kGray, 0.5);
      crRightBottom = CFX_Color(CFX_Color::kGray, 0.75);
      break;
    default:
      break;
  }

  CFX_FloatRect rcWindow = widget_->GetRotatedRect();
  CFX_FloatRect rcClient = rcWindow.GetDeflated(fBorderWidth, fBorderWidth);
  CPDF_DefaultAppearance da = pControl->GetDefaultAppearance();
  Optional<CFX_Color::Type> color = da.GetColor(fc);
  if (color) {
    iColorType = *color;
    crText = CFX_Color(iColorType, fc[0], fc[1], fc[2], fc[3]);
  }

  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::kGray, 1);
      crRightBottom = crBackground - 0.25f;
    } else if (nBorderStyle == BorderStyle::kInset) {
      crLeftTop = CFX_Color(CFX_Color::kGray, 0.5f);
      crRightBottom = CFX_Color(CFX_Color::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::kGray, 0);
      crRightBottom = CFX_Color(CFX_Color::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::kGray, 1);
      crBK = crBackground;
    } else if (nBorderStyle == BorderStyle::kInset) {
      crLeftTop = CFX_Color(CFX_Color::kGray, 0);
      crRightBottom = CFX_Color(CFX_Color::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_->SetAppState("Off");
}

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

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

  // Font map must outlive |pEdit|.
  CBA_FontMap font_map(widget_->GetPDFPage()->GetDocument(),
                       widget_->GetPDFAnnot()->GetAnnotDict());

  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, true);

  float fFontSize = widget_->GetFontSize();
  if (IsFloatZero(fFontSize))
    pEdit->SetAutoFontSize(true, 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));
  }

  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()) {
      sBody << rcEdit.left << " " << rcEdit.bottom << " " << rcEdit.Width()
            << " " << rcEdit.Height() << " " << kAppendRectOperator << "\n"
            << kSetNonZeroWindingClipOperator << "\n"
            << kEndPathNoFillOrStrokeOperator << "\n";
    }

    CFX_Color crText = widget_->GetTextPWLColor();
    AutoClosedCommand bt(&sBody, kTextBeginOperator, kTextEndOperator);
    sBody << GetColorAppStream(crText, true) << 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();
  std::ostringstream sBody;

  // Font map must outlive |pEdit|.
  CBA_FontMap font_map(widget_->GetPDFPage()->GetDocument(),
                       widget_->GetPDFAnnot()->GetAnnotDict());

  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(IsFloatZero(fFontSize) ? 12.0f : fFontSize);
  pEdit->Initialize();

  std::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));

    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 << GetColorAppStream(CFX_Color(CFX_Color::kRGB, 0, 51.0f / 255.0f,
                                             113.0f / 255.0f),
                                   true)
              << rcItem.left << " " << rcItem.bottom << " " << rcItem.Width()
              << " " << rcItem.Height() << " " << kAppendRectOperator << " "
              << kFillOperator << "\n";
      }

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

      AutoClosedCommand bt(&sList, kTextBeginOperator, kTextEndOperator);
      sList << GetColorAppStream(crText, true)
            << 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);

    sBody << rcClient.left << " " << rcClient.bottom << " " << rcClient.Width()
          << " " << rcClient.Height() << " " << kAppendRectOperator << "\n"
          << kSetNonZeroWindingClipOperator << "\n"
          << kEndPathNoFillOrStrokeOperator << "\n"
          << sList.str();
  }
  Write("N",
        GetBackgroundAppStream() + GetBorderAppStream() + ByteString(sBody),
        ByteString());
}

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

  // Font map must outlive |pEdit|.
  CBA_FontMap font_map(widget_->GetPDFPage()->GetDocument(),
                       widget_->GetPDFAnnot()->GetAnnotDict());

  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(), true);

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

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

  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 (IsFloatZero(fFontSize)) {
        fFontSize = CPWL_Edit::GetCharArrayAutoFontSize(
            font_map.GetPDFFont(0).Get(), rcClient, nMaxLen);
      }
    } else {
      if (sValue.has_value())
        nMaxLen = sValue.value().GetLength();
      pEdit->SetLimitChar(nMaxLen);
    }
  }

  if (IsFloatZero(fFontSize))
    pEdit->SetAutoFontSize(true, true);
  else
    pEdit->SetFontSize(fFontSize);

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

  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()) {
      sBody << rcClient.left << " " << rcClient.bottom << " "
            << rcClient.Width() << " " << rcClient.Height() << " "
            << kAppendRectOperator << "\n"
            << kSetNonZeroWindingClipOperator << "\n"
            << kEndPathNoFillOrStrokeOperator << "\n";
    }
    CFX_Color crText = widget_->GetTextPWLColor();

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

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

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

          for (int32_t i = 1; i < nMaxLen; ++i) {
            sLines << rcClient.left +
                          ((rcClient.right - rcClient.left) / nMaxLen) * i
                   << " " << rcClient.bottom << " " << kMoveToOperator << "\n"
                   << rcClient.left +
                          ((rcClient.right - rcClient.left) / nMaxLen) * i
                   << " " << rcClient.top << " " << kLineToOperator << " "
                   << kStrokeOperator << "\n";
          }
        }
        break;
      }
      default:
        break;
    }
  }

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

void CPDFSDK_AppStream::AddImage(const ByteString& sAPType,
                                 CPDF_Stream* pImage) {
  CPDF_Stream* pStream = dict_->GetStreamFor(sAPType);
  CPDF_Dictionary* pStreamDict = pStream->GetDict();
  ByteString sImageAlias = "IMG";

  if (CPDF_Dictionary* pImageDict = pImage->GetDict()) {
    sImageAlias = pImageDict->GetStringFor("Name");
    if (sImageAlias.IsEmpty())
      sImageAlias = "IMG";
  }

  CPDF_Dictionary* pStreamResList = pStreamDict->GetDictFor("Resources");
  if (!pStreamResList)
    pStreamResList = pStreamDict->SetNewFor<CPDF_Dictionary>("Resources");

  CPDF_Dictionary* pXObject =
      pStreamResList->SetNewFor<CPDF_Dictionary>("XObject");
  pXObject->SetNewFor<CPDF_Reference>(sImageAlias,
                                      widget_->GetPageView()->GetPDFDocument(),
                                      pImage->GetObjNum());
}

void CPDFSDK_AppStream::Write(const ByteString& sAPType,
                              const ByteString& sContents,
                              const ByteString& sAPState) {
  CPDF_Stream* pStream = nullptr;
  CPDF_Dictionary* pParentDict = nullptr;
  if (sAPState.IsEmpty()) {
    pParentDict = dict_.Get();
    pStream = dict_->GetStreamFor(sAPType);
  } else {
    CPDF_Dictionary* pAPTypeDict = dict_->GetDictFor(sAPType);
    if (!pAPTypeDict)
      pAPTypeDict = dict_->SetNewFor<CPDF_Dictionary>(sAPType);

    pParentDict = pAPTypeDict;
    pStream = pAPTypeDict->GetStreamFor(sAPState);
  }

  if (!pStream) {
    CPDF_Document* doc = widget_->GetPageView()->GetPDFDocument();
    pStream = doc->NewIndirect<CPDF_Stream>();
    pParentDict->SetNewFor<CPDF_Reference>(sAPType, doc, pStream->GetObjNum());
  }

  CPDF_Dictionary* pStreamDict = pStream->GetDict();
  if (!pStreamDict) {
    auto pNewDict =
        widget_->GetPDFAnnot()->GetDocument()->New<CPDF_Dictionary>();
    pStreamDict = pNewDict.Get();
    pStreamDict->SetNewFor<CPDF_Name>("Type", "XObject");
    pStreamDict->SetNewFor<CPDF_Name>("Subtype", "Form");
    pStreamDict->SetNewFor<CPDF_Number>("FormType", 1);
    pStream->InitStream({}, std::move(pNewDict));
  }
  pStreamDict->SetMatrixFor("Matrix", widget_->GetMatrix());
  pStreamDict->SetRectFor("BBox", widget_->GetRotatedRect());
  pStream->SetDataAndRemoveFilter(sContents.raw_span());
}

void CPDFSDK_AppStream::Remove(const ByteString& sAPType) {
  dict_->RemoveFor(sAPType);
}

ByteString CPDFSDK_AppStream::GetBackgroundAppStream() const {
  CFX_Color crBackground = widget_->GetFillPWLColor();
  if (crBackground.nColorType != CFX_Color::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::kGray, 1);
      crRightBottom = crBackground / 2.0f;
      break;
    case BorderStyle::kInset:
      fBorderWidth *= 2;
      crLeftTop = CFX_Color(CFX_Color::kGray, 0.5);
      crRightBottom = CFX_Color(CFX_Color::kGray, 0.75);
      break;
    default:
      break;
  }

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