// 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 #include #include #include #include #include #include #include "core/fxcrt/check_op.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 // 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(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( \ (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(CFX_GEModule::Get()->GetPlatform()); return pData->m_GdiplusExt; } Gdiplus::GpBrush* GdipCreateBrushImpl(DWORD argb) { Gdiplus::GpSolidFill* solidBrush = nullptr; CALLFUNC(GetGdiplusExt(), GdipCreateSolidFill, static_cast(argb), &solidBrush); return solidBrush; } void OutputImage(Gdiplus::GpGraphics* graphics, RetainPtr 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 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(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(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& 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> IsSmallTriangle( pdfium::span points, const CFX_Matrix* pMatrix) { static constexpr std::array, 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(this); AddRef(); return S_OK; } return E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef() override { return (ULONG)InterlockedIncrement(&m_RefCount); } ULONG STDMETHODCALLTYPE Release() override { ULONG res = (ULONG)InterlockedDecrement(&m_RefCount); if (res == 0) { delete this; } return res; } // ISequentialStream HRESULT STDMETHODCALLTYPE Read(void* output, ULONG cb, ULONG* pcbRead) override { if (pcbRead) *pcbRead = 0; if (m_ReadPos >= m_InterStream.tellp()) return HRESULT_FROM_WIN32(ERROR_END_OF_MEDIA); size_t bytes_left = pdfium::checked_cast( std::streamoff(m_InterStream.tellp()) - m_ReadPos); size_t bytes_out = std::min(pdfium::checked_cast(cb), bytes_left); UNSAFE_TODO(FXSYS_memcpy(output, m_InterStream.str().c_str() + m_ReadPos, bytes_out)); m_ReadPos += 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; } m_InterStream.write(reinterpret_cast(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 = m_ReadPos; break; case STREAM_SEEK_END: if (m_InterStream.tellp() < 0) return STG_E_SEEKERROR; start = m_InterStream.tellp(); break; default: return STG_E_INVALIDFUNCTION; } new_read_position = start + liDistanceToMove.QuadPart; if (new_read_position > m_InterStream.tellp()) return STG_E_SEEKERROR; m_ReadPos = new_read_position; if (lpNewFilePointer) lpNewFilePointer->QuadPart = m_ReadPos; return S_OK; } HRESULT STDMETHODCALLTYPE Stat(STATSTG* pStatstg, DWORD grfStatFlag) override { if (!pStatstg) return STG_E_INVALIDFUNCTION; ZeroMemory(pStatstg, sizeof(STATSTG)); if (m_InterStream.tellp() < 0) return STG_E_SEEKERROR; pStatstg->cbSize.QuadPart = m_InterStream.tellp(); return S_OK; } private: LONG m_RefCount = 1; std::streamoff m_ReadPos = 0; fxcrt::ostringstream m_InterStream; }; } // 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 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 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 gp_points(points.size()); std::vector 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].m_Point.x; gp_points[i].Y = points[i].m_Point.y; CFX_PointF pos = points[i].m_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].m_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].m_CloseFigure) { 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(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(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; }