blob: ebd5f058c15f4b2ec9baf23e97db05d3cca7e854 [file] [log] [blame] [edit]
// 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/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->m_GdiplusExt;
}
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(&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<size_t>(
std::streamoff(m_InterStream.tellp()) - m_ReadPos);
size_t bytes_out = std::min(pdfium::checked_cast<size_t>(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<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 = 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<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].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<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;
}