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

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

#include "core/fxge/win32/cgdi_plus_ext.h"

#include <windows.h>

#include <objidl.h>

#include <algorithm>
#include <array>
#include <sstream>
#include <utility>
#include <vector>

#include "core/fxcrt/check_op.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/fx_memcpy_wrappers.h"
#include "core/fxcrt/fx_memory.h"
#include "core/fxcrt/fx_string.h"
#include "core/fxcrt/fx_string_wrappers.h"
#include "core/fxcrt/fx_system.h"
#include "core/fxcrt/numerics/safe_conversions.h"
#include "core/fxcrt/span.h"
#include "core/fxge/cfx_fillrenderoptions.h"
#include "core/fxge/cfx_gemodule.h"
#include "core/fxge/cfx_graphstatedata.h"
#include "core/fxge/cfx_path.h"
#include "core/fxge/dib/cfx_dibbase.h"
#include "core/fxge/dib/cfx_dibitmap.h"
#include "core/fxge/win32/cwin32_platform.h"

// Has to come before gdiplus.h
namespace Gdiplus {
using std::max;
using std::min;
}  // namespace Gdiplus

#include <gdiplus.h>  // NOLINT

namespace {

enum {
  FuncId_GdipCreatePath2,
  FuncId_GdipSetPenDashArray,
  FuncId_GdipSetPenLineJoin,
  FuncId_GdipCreateFromHDC,
  FuncId_GdipSetPageUnit,
  FuncId_GdipSetSmoothingMode,
  FuncId_GdipCreateSolidFill,
  FuncId_GdipFillPath,
  FuncId_GdipDeleteBrush,
  FuncId_GdipCreatePen1,
  FuncId_GdipSetPenMiterLimit,
  FuncId_GdipDrawPath,
  FuncId_GdipDeletePen,
  FuncId_GdipDeletePath,
  FuncId_GdipDeleteGraphics,
  FuncId_GdipDisposeImage,
  FuncId_GdipCreateBitmapFromScan0,
  FuncId_GdipSetInterpolationMode,
  FuncId_GdipDrawImagePointsI,
  FuncId_GdiplusStartup,
  FuncId_GdipDrawLineI,
  FuncId_GdipCreatePath,
  FuncId_GdipSetPathFillMode,
  FuncId_GdipSetClipRegion,
  FuncId_GdipWidenPath,
  FuncId_GdipAddPathLine,
  FuncId_GdipAddPathRectangle,
  FuncId_GdipDeleteRegion,
  FuncId_GdipSetPenLineCap197819,
  FuncId_GdipSetPenDashOffset,
  FuncId_GdipCreateMatrix2,
  FuncId_GdipDeleteMatrix,
  FuncId_GdipSetWorldTransform,
  FuncId_GdipSetPixelOffsetMode,
};

LPCSTR g_GdipFuncNames[] = {
    "GdipCreatePath2",
    "GdipSetPenDashArray",
    "GdipSetPenLineJoin",
    "GdipCreateFromHDC",
    "GdipSetPageUnit",
    "GdipSetSmoothingMode",
    "GdipCreateSolidFill",
    "GdipFillPath",
    "GdipDeleteBrush",
    "GdipCreatePen1",
    "GdipSetPenMiterLimit",
    "GdipDrawPath",
    "GdipDeletePen",
    "GdipDeletePath",
    "GdipDeleteGraphics",
    "GdipDisposeImage",
    "GdipCreateBitmapFromScan0",
    "GdipSetInterpolationMode",
    "GdipDrawImagePointsI",
    "GdiplusStartup",
    "GdipDrawLineI",
    "GdipCreatePath",
    "GdipSetPathFillMode",
    "GdipSetClipRegion",
    "GdipWidenPath",
    "GdipAddPathLine",
    "GdipAddPathRectangle",
    "GdipDeleteRegion",
    "GdipSetPenLineCap197819",
    "GdipSetPenDashOffset",
    "GdipCreateMatrix2",
    "GdipDeleteMatrix",
    "GdipSetWorldTransform",
    "GdipSetPixelOffsetMode",
};
static_assert(std::size(g_GdipFuncNames) ==
                  static_cast<size_t>(FuncId_GdipSetPixelOffsetMode) + 1,
              "g_GdipFuncNames has wrong size");

using FuncType_GdipCreatePath2 =
    decltype(&Gdiplus::DllExports::GdipCreatePath2);
using FuncType_GdipSetPenDashArray =
    decltype(&Gdiplus::DllExports::GdipSetPenDashArray);
using FuncType_GdipSetPenLineJoin =
    decltype(&Gdiplus::DllExports::GdipSetPenLineJoin);
using FuncType_GdipCreateFromHDC =
    decltype(&Gdiplus::DllExports::GdipCreateFromHDC);
using FuncType_GdipSetPageUnit =
    decltype(&Gdiplus::DllExports::GdipSetPageUnit);
using FuncType_GdipSetSmoothingMode =
    decltype(&Gdiplus::DllExports::GdipSetSmoothingMode);
using FuncType_GdipCreateSolidFill =
    decltype(&Gdiplus::DllExports::GdipCreateSolidFill);
using FuncType_GdipFillPath = decltype(&Gdiplus::DllExports::GdipFillPath);
using FuncType_GdipDeleteBrush =
    decltype(&Gdiplus::DllExports::GdipDeleteBrush);
using FuncType_GdipCreatePen1 = decltype(&Gdiplus::DllExports::GdipCreatePen1);
using FuncType_GdipSetPenMiterLimit =
    decltype(&Gdiplus::DllExports::GdipSetPenMiterLimit);
using FuncType_GdipDrawPath = decltype(&Gdiplus::DllExports::GdipDrawPath);
using FuncType_GdipDeletePen = decltype(&Gdiplus::DllExports::GdipDeletePen);
using FuncType_GdipDeletePath = decltype(&Gdiplus::DllExports::GdipDeletePath);
using FuncType_GdipDeleteGraphics =
    decltype(&Gdiplus::DllExports::GdipDeleteGraphics);
using FuncType_GdipDisposeImage =
    decltype(&Gdiplus::DllExports::GdipDisposeImage);
using FuncType_GdipCreateBitmapFromScan0 =
    decltype(&Gdiplus::DllExports::GdipCreateBitmapFromScan0);
using FuncType_GdipSetInterpolationMode =
    decltype(&Gdiplus::DllExports::GdipSetInterpolationMode);
using FuncType_GdipDrawImagePointsI =
    decltype(&Gdiplus::DllExports::GdipDrawImagePointsI);
using FuncType_GdiplusStartup = decltype(&Gdiplus::GdiplusStartup);
using FuncType_GdipDrawLineI = decltype(&Gdiplus::DllExports::GdipDrawLineI);
using FuncType_GdipCreatePath = decltype(&Gdiplus::DllExports::GdipCreatePath);
using FuncType_GdipSetPathFillMode =
    decltype(&Gdiplus::DllExports::GdipSetPathFillMode);
using FuncType_GdipSetClipRegion =
    decltype(&Gdiplus::DllExports::GdipSetClipRegion);
using FuncType_GdipWidenPath = decltype(&Gdiplus::DllExports::GdipWidenPath);
using FuncType_GdipAddPathLine =
    decltype(&Gdiplus::DllExports::GdipAddPathLine);
using FuncType_GdipAddPathRectangle =
    decltype(&Gdiplus::DllExports::GdipAddPathRectangle);
using FuncType_GdipDeleteRegion =
    decltype(&Gdiplus::DllExports::GdipDeleteRegion);
using FuncType_GdipSetPenLineCap197819 =
    decltype(&Gdiplus::DllExports::GdipSetPenLineCap197819);
using FuncType_GdipSetPenDashOffset =
    decltype(&Gdiplus::DllExports::GdipSetPenDashOffset);
using FuncType_GdipCreateMatrix2 =
    decltype(&Gdiplus::DllExports::GdipCreateMatrix2);
using FuncType_GdipDeleteMatrix =
    decltype(&Gdiplus::DllExports::GdipDeleteMatrix);
using FuncType_GdipSetWorldTransform =
    decltype(&Gdiplus::DllExports::GdipSetWorldTransform);
using FuncType_GdipSetPixelOffsetMode =
    decltype(&Gdiplus::DllExports::GdipSetPixelOffsetMode);

#define CALLFUNC(gdi_plus_ext, funcname, ...) \
  reinterpret_cast<FuncType_##funcname>(      \
      (gdi_plus_ext).functions()[FuncId_##funcname])(__VA_ARGS__)

Gdiplus::GpFillMode FillType2Gdip(CFX_FillRenderOptions::FillType fill_type) {
  return fill_type == CFX_FillRenderOptions::FillType::kEvenOdd
             ? Gdiplus::FillModeAlternate
             : Gdiplus::FillModeWinding;
}

const CGdiplusExt& GetGdiplusExt() {
  auto* pData =
      static_cast<CWin32Platform*>(CFX_GEModule::Get()->GetPlatform());
  return pData->gdiplus_ext_;
}

Gdiplus::GpBrush* GdipCreateBrushImpl(DWORD argb) {
  Gdiplus::GpSolidFill* solidBrush = nullptr;
  CALLFUNC(GetGdiplusExt(), GdipCreateSolidFill,
           static_cast<Gdiplus::ARGB>(argb), &solidBrush);
  return solidBrush;
}

void OutputImage(Gdiplus::GpGraphics* graphics,
                 RetainPtr<const CFX_DIBBase> source,
                 int dest_left,
                 int dest_top,
                 int dest_width,
                 int dest_height) {
  CHECK_EQ(FXDIB_Format::kBgra, source->GetFormat());
  const CGdiplusExt& gdi_plus_ext = GetGdiplusExt();

  RetainPtr<const CFX_DIBitmap> realized_source = source->RealizeIfNeeded();
  if (!realized_source) {
    return;
  }

  // `GdipCreateBitmapFromScan0()` requires a `BYTE*` scanline buffer, but in
  // this case, the bitmap only gets read by `GdipDrawImagePointsI()`, then
  // disposed of, so it's safe to cast away `const` here.
  uint8_t* scan0 = const_cast<uint8_t*>(realized_source->GetBuffer().data());
  Gdiplus::GpBitmap* bitmap = nullptr;
  CALLFUNC(gdi_plus_ext, GdipCreateBitmapFromScan0, realized_source->GetWidth(),
           realized_source->GetHeight(), realized_source->GetPitch(),
           PixelFormat32bppARGB, scan0, &bitmap);
  if (dest_height < 0) {
    dest_height--;
  }
  if (dest_width < 0) {
    dest_width--;
  }
  const Gdiplus::Point destination_points[] = {
      Gdiplus::Point(dest_left, dest_top),
      Gdiplus::Point(dest_left + dest_width, dest_top),
      Gdiplus::Point(dest_left, dest_top + dest_height)};
  CALLFUNC(gdi_plus_ext, GdipDrawImagePointsI, graphics, bitmap,
           destination_points, std::size(destination_points));
  CALLFUNC(gdi_plus_ext, GdipDisposeImage, bitmap);
}

Gdiplus::GpPen* GdipCreatePenImpl(const CFX_GraphStateData* pGraphState,
                                  const CFX_Matrix* pMatrix,
                                  DWORD argb,
                                  bool bTextMode) {
  const CGdiplusExt& gdi_plus_ext = GetGdiplusExt();
  float width = pGraphState->line_width();
  if (!bTextMode) {
    float unit = pMatrix
                     ? 1.0f / ((pMatrix->GetXUnit() + pMatrix->GetYUnit()) / 2)
                     : 1.0f;
    width = std::max(width, unit);
  }
  Gdiplus::GpPen* pPen = nullptr;
  CALLFUNC(gdi_plus_ext, GdipCreatePen1, static_cast<Gdiplus::ARGB>(argb),
           width, Gdiplus::UnitWorld, &pPen);
  Gdiplus::LineCap lineCap = Gdiplus::LineCapFlat;
  Gdiplus::DashCap dashCap = Gdiplus::DashCapFlat;
  bool bDashExtend = false;
  switch (pGraphState->line_cap()) {
    case CFX_GraphStateData::LineCap::kButt:
      lineCap = Gdiplus::LineCapFlat;
      break;
    case CFX_GraphStateData::LineCap::kRound:
      lineCap = Gdiplus::LineCapRound;
      dashCap = Gdiplus::DashCapRound;
      bDashExtend = true;
      break;
    case CFX_GraphStateData::LineCap::kSquare:
      lineCap = Gdiplus::LineCapSquare;
      bDashExtend = true;
      break;
  }
  CALLFUNC(gdi_plus_ext, GdipSetPenLineCap197819, pPen, lineCap, lineCap,
           dashCap);
  Gdiplus::LineJoin lineJoin = Gdiplus::LineJoinMiterClipped;
  switch (pGraphState->line_join()) {
    case CFX_GraphStateData::LineJoin::kMiter:
      lineJoin = Gdiplus::LineJoinMiterClipped;
      break;
    case CFX_GraphStateData::LineJoin::kRound:
      lineJoin = Gdiplus::LineJoinRound;
      break;
    case CFX_GraphStateData::LineJoin::kBevel:
      lineJoin = Gdiplus::LineJoinBevel;
      break;
  }
  CALLFUNC(gdi_plus_ext, GdipSetPenLineJoin, pPen, lineJoin);
  const std::vector<float>& dash_array = pGraphState->dash_array();
  if (!dash_array.empty()) {
    float* gdi_dash_array =
        FX_Alloc(float, FxAlignToBoundary<2>(dash_array.size()));
    int nCount = 0;
    float on_leftover = 0;
    float off_leftover = 0;
    for (size_t i = 0; i < dash_array.size(); i += 2) {
      float on_phase = dash_array[i];
      float off_phase =
          i + 1 < dash_array.size() ? dash_array[i + 1] : on_phase;
      on_phase /= width;
      off_phase /= width;
      if (on_phase + off_phase <= 0.00002f) {
        on_phase = 0.1f;
        off_phase = 0.1f;
      }
      if (bDashExtend) {
        if (off_phase < 1) {
          off_phase = 0;
        } else {
          --off_phase;
        }
        ++on_phase;
      }
      if (on_phase == 0 || off_phase == 0) {
        if (nCount == 0) {
          on_leftover += on_phase;
          off_leftover += off_phase;
        } else {
          UNSAFE_TODO({
            gdi_dash_array[nCount - 2] += on_phase;
            gdi_dash_array[nCount - 1] += off_phase;
          });
        }
      } else {
        UNSAFE_TODO({
          gdi_dash_array[nCount++] = on_phase + on_leftover;
          on_leftover = 0;
          gdi_dash_array[nCount++] = off_phase + off_leftover;
          off_leftover = 0;
        });
      }
    }
    CALLFUNC(gdi_plus_ext, GdipSetPenDashArray, pPen, gdi_dash_array, nCount);
    float phase = pGraphState->dash_phase();
    if (bDashExtend) {
      if (phase < 0.5f) {
        phase = 0;
      } else {
        phase -= 0.5f;
      }
    }
    CALLFUNC(gdi_plus_ext, GdipSetPenDashOffset, pPen, phase);
    FX_Free(gdi_dash_array);
    gdi_dash_array = nullptr;
  }
  CALLFUNC(gdi_plus_ext, GdipSetPenMiterLimit, pPen,
           pGraphState->miter_limit());
  return pPen;
}

std::optional<std::pair<size_t, size_t>> IsSmallTriangle(
    pdfium::span<const Gdiplus::PointF> points,
    const CFX_Matrix* pMatrix) {
  static constexpr std::array<std::pair<const size_t, const size_t>, 3> kPairs =
      {{{1, 2}, {0, 2}, {0, 1}}};
  for (size_t i = 0; i < std::size(kPairs); ++i) {
    size_t pair1 = kPairs[i].first;
    size_t pair2 = kPairs[i].second;
    CFX_PointF p1(points[pair1].X, points[pair1].Y);
    CFX_PointF p2(points[pair2].X, points[pair2].Y);
    if (pMatrix) {
      p1 = pMatrix->Transform(p1);
      p2 = pMatrix->Transform(p2);
    }
    CFX_PointF diff = p1 - p2;
    float distance_square = (diff.x * diff.x) + (diff.y * diff.y);
    if (distance_square < 2.25f) {
      return std::make_pair(i, pair1);
    }
  }
  return std::nullopt;
}

class GpStream final : public IStream {
 public:
  GpStream() = default;
  ~GpStream() = default;

  // IUnknown
  HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid,
                                           void** ppvObject) override {
    if (iid == __uuidof(IUnknown) || iid == __uuidof(IStream) ||
        iid == __uuidof(ISequentialStream)) {
      *ppvObject = static_cast<IStream*>(this);
      AddRef();
      return S_OK;
    }
    return E_NOINTERFACE;
  }
  ULONG STDMETHODCALLTYPE AddRef() override {
    return (ULONG)InterlockedIncrement(&ref_count_);
  }
  ULONG STDMETHODCALLTYPE Release() override {
    ULONG res = (ULONG)InterlockedDecrement(&ref_count_);
    if (res == 0) {
      delete this;
    }
    return res;
  }

  // ISequentialStream
  HRESULT STDMETHODCALLTYPE Read(void* output,
                                 ULONG cb,
                                 ULONG* pcbRead) override {
    if (pcbRead) {
      *pcbRead = 0;
    }

    if (read_pos_ >= inter_stream_.tellp()) {
      return HRESULT_FROM_WIN32(ERROR_END_OF_MEDIA);
    }

    size_t bytes_left = pdfium::checked_cast<size_t>(
        std::streamoff(inter_stream_.tellp()) - read_pos_);
    size_t bytes_out = std::min(pdfium::checked_cast<size_t>(cb), bytes_left);
    UNSAFE_TODO(FXSYS_memcpy(output, inter_stream_.str().c_str() + read_pos_,
                             bytes_out));
    read_pos_ += bytes_out;
    if (pcbRead) {
      *pcbRead = (ULONG)bytes_out;
    }

    return S_OK;
  }
  HRESULT STDMETHODCALLTYPE Write(const void* input,
                                  ULONG cb,
                                  ULONG* pcbWritten) override {
    if (cb <= 0) {
      if (pcbWritten) {
        *pcbWritten = 0;
      }
      return S_OK;
    }
    inter_stream_.write(reinterpret_cast<const char*>(input), cb);
    if (pcbWritten) {
      *pcbWritten = cb;
    }
    return S_OK;
  }

  // IStream
  HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER) override {
    return E_NOTIMPL;
  }
  HRESULT STDMETHODCALLTYPE CopyTo(IStream*,
                                   ULARGE_INTEGER,
                                   ULARGE_INTEGER*,
                                   ULARGE_INTEGER*) override {
    return E_NOTIMPL;
  }
  HRESULT STDMETHODCALLTYPE Commit(DWORD) override { return E_NOTIMPL; }
  HRESULT STDMETHODCALLTYPE Revert() override { return E_NOTIMPL; }
  HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER,
                                       ULARGE_INTEGER,
                                       DWORD) override {
    return E_NOTIMPL;
  }
  HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER,
                                         ULARGE_INTEGER,
                                         DWORD) override {
    return E_NOTIMPL;
  }
  HRESULT STDMETHODCALLTYPE Clone(IStream** stream) override {
    return E_NOTIMPL;
  }
  HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER liDistanceToMove,
                                 DWORD dwOrigin,
                                 ULARGE_INTEGER* lpNewFilePointer) override {
    std::streamoff start;
    std::streamoff new_read_position;
    switch (dwOrigin) {
      case STREAM_SEEK_SET:
        start = 0;
        break;
      case STREAM_SEEK_CUR:
        start = read_pos_;
        break;
      case STREAM_SEEK_END:
        if (inter_stream_.tellp() < 0) {
          return STG_E_SEEKERROR;
        }
        start = inter_stream_.tellp();
        break;
      default:
        return STG_E_INVALIDFUNCTION;
    }
    new_read_position = start + liDistanceToMove.QuadPart;
    if (new_read_position > inter_stream_.tellp()) {
      return STG_E_SEEKERROR;
    }

    read_pos_ = new_read_position;
    if (lpNewFilePointer) {
      lpNewFilePointer->QuadPart = read_pos_;
    }

    return S_OK;
  }
  HRESULT STDMETHODCALLTYPE Stat(STATSTG* pStatstg,
                                 DWORD grfStatFlag) override {
    if (!pStatstg) {
      return STG_E_INVALIDFUNCTION;
    }

    UNSAFE_TODO(ZeroMemory(pStatstg, sizeof(STATSTG)));

    if (inter_stream_.tellp() < 0) {
      return STG_E_SEEKERROR;
    }

    pStatstg->cbSize.QuadPart = inter_stream_.tellp();
    return S_OK;
  }

 private:
  LONG ref_count_ = 1;
  std::streamoff read_pos_ = 0;
  fxcrt::ostringstream inter_stream_;
};

}  // namespace

CGdiplusExt::CGdiplusExt() = default;

CGdiplusExt::~CGdiplusExt() {
  FreeLibrary(gdiplus_module_);
}

void CGdiplusExt::Load() {
  char buf[MAX_PATH];
  GetSystemDirectoryA(buf, MAX_PATH);
  ByteString dllpath = buf;
  dllpath += "\\GDIPLUS.DLL";
  gdiplus_module_ = LoadLibraryA(dllpath.c_str());
  if (!gdiplus_module_) {
    return;
  }

  gdiplus_functions_.resize(std::size(g_GdipFuncNames));
  UNSAFE_TODO({
    for (size_t i = 0; i < std::size(g_GdipFuncNames); ++i) {
      gdiplus_functions_[i] =
          GetProcAddress(gdiplus_module_, g_GdipFuncNames[i]);
      if (!gdiplus_functions_[i]) {
        FreeLibrary(gdiplus_module_);
        gdiplus_module_ = nullptr;
        return;
      }
    }
  });
  ULONG_PTR gdiplus_token;
  Gdiplus::GdiplusStartupInput gdiplus_startup_input;
  ((FuncType_GdiplusStartup)gdiplus_functions_[FuncId_GdiplusStartup])(
      &gdiplus_token, &gdiplus_startup_input, nullptr);
}

bool CGdiplusExt::StretchDIBits(HDC hDC,
                                RetainPtr<const CFX_DIBBase> source,
                                int dest_left,
                                int dest_top,
                                int dest_width,
                                int dest_height) {
  CHECK(source->IsAlphaFormat());
  Gdiplus::GpGraphics* pGraphics;
  const CGdiplusExt& gdi_plus_ext = GetGdiplusExt();
  CALLFUNC(gdi_plus_ext, GdipCreateFromHDC, hDC, &pGraphics);
  CALLFUNC(gdi_plus_ext, GdipSetPageUnit, pGraphics, Gdiplus::UnitPixel);
  if (source->GetWidth() > abs(dest_width) / 2 ||
      source->GetHeight() > abs(dest_height) / 2) {
    CALLFUNC(gdi_plus_ext, GdipSetInterpolationMode, pGraphics,
             Gdiplus::InterpolationModeHighQuality);
  } else {
    CALLFUNC(gdi_plus_ext, GdipSetInterpolationMode, pGraphics,
             Gdiplus::InterpolationModeBilinear);
  }
  OutputImage(pGraphics, std::move(source), dest_left, dest_top, dest_width,
              dest_height);
  CALLFUNC(gdi_plus_ext, GdipDeleteGraphics, pGraphics);
  return true;
}

bool CGdiplusExt::DrawPath(HDC hDC,
                           const CFX_Path& path,
                           const CFX_Matrix* pObject2Device,
                           const CFX_GraphStateData* pGraphState,
                           uint32_t fill_argb,
                           uint32_t stroke_argb,
                           const CFX_FillRenderOptions& fill_options) {
  pdfium::span<const CFX_Path::Point> points = path.GetPoints();
  if (points.empty()) {
    return true;
  }

  Gdiplus::GpGraphics* pGraphics = nullptr;
  const CGdiplusExt& gdi_plus_ext = GetGdiplusExt();
  CALLFUNC(gdi_plus_ext, GdipCreateFromHDC, hDC, &pGraphics);
  CALLFUNC(gdi_plus_ext, GdipSetPageUnit, pGraphics, Gdiplus::UnitPixel);
  CALLFUNC(gdi_plus_ext, GdipSetPixelOffsetMode, pGraphics,
           Gdiplus::PixelOffsetModeHalf);
  Gdiplus::GpMatrix* pMatrix = nullptr;
  if (pObject2Device) {
    CALLFUNC(gdi_plus_ext, GdipCreateMatrix2, pObject2Device->a,
             pObject2Device->b, pObject2Device->c, pObject2Device->d,
             pObject2Device->e, pObject2Device->f, &pMatrix);
    CALLFUNC(gdi_plus_ext, GdipSetWorldTransform, pGraphics, pMatrix);
  }
  std::vector<Gdiplus::PointF> gp_points(points.size());
  std::vector<BYTE> gp_types(points.size());
  int nSubPathes = 0;
  bool bSubClose = false;
  bool bSmooth = false;
  size_t pos_subclose = 0;
  size_t startpoint = 0;
  for (size_t i = 0; i < points.size(); ++i) {
    gp_points[i].X = points[i].point_.x;
    gp_points[i].Y = points[i].point_.y;

    CFX_PointF pos = points[i].point_;
    if (pObject2Device) {
      pos = pObject2Device->Transform(pos);
    }

    if (pos.x > 50000.0f) {
      gp_points[i].X = 50000.0f;
    }
    if (pos.x < -50000.0f) {
      gp_points[i].X = -50000.0f;
    }
    if (pos.y > 50000.0f) {
      gp_points[i].Y = 50000.0f;
    }
    if (pos.y < -50000.0f) {
      gp_points[i].Y = -50000.0f;
    }

    CFX_Path::Point::Type point_type = points[i].type_;
    if (point_type == CFX_Path::Point::Type::kMove) {
      gp_types[i] = Gdiplus::PathPointTypeStart;
      nSubPathes++;
      bSubClose = false;
      startpoint = i;
    } else if (point_type == CFX_Path::Point::Type::kLine) {
      gp_types[i] = Gdiplus::PathPointTypeLine;
      if (points[i - 1].IsTypeAndOpen(CFX_Path::Point::Type::kMove) &&
          (i + 1 == points.size() ||
           points[i + 1].IsTypeAndOpen(CFX_Path::Point::Type::kMove)) &&
          gp_points[i].Y == gp_points[i - 1].Y &&
          gp_points[i].X == gp_points[i - 1].X) {
        gp_points[i].X += 0.01f;
        continue;
      }
      if (!bSmooth && gp_points[i].X != gp_points[i - 1].X &&
          gp_points[i].Y != gp_points[i - 1].Y) {
        bSmooth = true;
      }
    } else if (point_type == CFX_Path::Point::Type::kBezier) {
      gp_types[i] = Gdiplus::PathPointTypeBezier;
      bSmooth = true;
    }
    if (points[i].close_figure_) {
      if (bSubClose) {
        gp_types[pos_subclose] &= ~Gdiplus::PathPointTypeCloseSubpath;
      } else {
        bSubClose = true;
      }
      pos_subclose = i;
      gp_types[i] |= Gdiplus::PathPointTypeCloseSubpath;
      if (!bSmooth && gp_points[i].X != gp_points[startpoint].X &&
          gp_points[i].Y != gp_points[startpoint].Y) {
        bSmooth = true;
      }
    }
  }
  const bool fill =
      fill_options.fill_type != CFX_FillRenderOptions::FillType::kNoFill;
  if (fill_options.aliased_path) {
    bSmooth = false;
    CALLFUNC(gdi_plus_ext, GdipSetSmoothingMode, pGraphics,
             Gdiplus::SmoothingModeNone);
  } else if (!fill_options.full_cover) {
    if (!bSmooth && fill) {
      bSmooth = true;
    }

    if (bSmooth || (pGraphState && pGraphState->line_width() > 2)) {
      CALLFUNC(gdi_plus_ext, GdipSetSmoothingMode, pGraphics,
               Gdiplus::SmoothingModeAntiAlias);
    }
  }
  if (points.size() == 4 && !pGraphState) {
    auto indices = IsSmallTriangle(gp_points, pObject2Device);
    if (indices.has_value()) {
      auto [v1, v2] = indices.value();
      Gdiplus::GpPen* pPen = nullptr;
      CALLFUNC(gdi_plus_ext, GdipCreatePen1, fill_argb, 1.0f,
               Gdiplus::UnitPixel, &pPen);
      CALLFUNC(gdi_plus_ext, GdipDrawLineI, pGraphics, pPen,
               FXSYS_roundf(gp_points[v1].X), FXSYS_roundf(gp_points[v1].Y),
               FXSYS_roundf(gp_points[v2].X), FXSYS_roundf(gp_points[v2].Y));
      CALLFUNC(gdi_plus_ext, GdipDeletePen, pPen);
      return true;
    }
  }
  Gdiplus::GpPath* pGpPath = nullptr;
  const Gdiplus::GpFillMode gp_fill_mode =
      FillType2Gdip(fill_options.fill_type);
  CALLFUNC(gdi_plus_ext, GdipCreatePath2, gp_points.data(), gp_types.data(),
           pdfium::checked_cast<int>(points.size()), gp_fill_mode, &pGpPath);
  if (!pGpPath) {
    if (pMatrix) {
      CALLFUNC(gdi_plus_ext, GdipDeleteMatrix, pMatrix);
    }

    CALLFUNC(gdi_plus_ext, GdipDeleteGraphics, pGraphics);
    return false;
  }
  if (fill) {
    Gdiplus::GpBrush* pBrush = GdipCreateBrushImpl(fill_argb);
    CALLFUNC(gdi_plus_ext, GdipSetPathFillMode, pGpPath, gp_fill_mode);
    CALLFUNC(gdi_plus_ext, GdipFillPath, pGraphics, pBrush, pGpPath);
    CALLFUNC(gdi_plus_ext, GdipDeleteBrush, pBrush);
  }
  if (pGraphState && stroke_argb) {
    Gdiplus::GpPen* pPen =
        GdipCreatePenImpl(pGraphState, pObject2Device, stroke_argb,
                          fill_options.stroke_text_mode);
    if (nSubPathes == 1) {
      CALLFUNC(gdi_plus_ext, GdipDrawPath, pGraphics, pPen, pGpPath);
    } else {
      size_t iStart = 0;
      for (size_t i = 0; i < points.size(); ++i) {
        if (i + 1 == points.size() ||
            gp_types[i + 1] == Gdiplus::PathPointTypeStart) {
          Gdiplus::GpPath* pSubPath;
          CALLFUNC(gdi_plus_ext, GdipCreatePath2, &gp_points[iStart],
                   &gp_types[iStart], pdfium::checked_cast<int>(i - iStart + 1),
                   gp_fill_mode, &pSubPath);
          iStart = i + 1;
          CALLFUNC(gdi_plus_ext, GdipDrawPath, pGraphics, pPen, pSubPath);
          CALLFUNC(gdi_plus_ext, GdipDeletePath, pSubPath);
        }
      }
    }
    CALLFUNC(gdi_plus_ext, GdipDeletePen, pPen);
  }
  if (pMatrix) {
    CALLFUNC(gdi_plus_ext, GdipDeleteMatrix, pMatrix);
  }
  CALLFUNC(gdi_plus_ext, GdipDeletePath, pGpPath);
  CALLFUNC(gdi_plus_ext, GdipDeleteGraphics, pGraphics);
  return true;
}
