Try DoPathStroke

This commit is contained in:
Svetlana Kulikova
2025-08-21 15:47:26 +03:00
parent d092b88d9b
commit 3cf3a3e098
8 changed files with 609 additions and 41 deletions

View File

@ -160,9 +160,9 @@ CFile.prototype["UndoMergePages"] = function()
{
return this._UndoMergePages();
};
CFile.prototype["RedactPage"] = function(pageIndex, arrRedactBox, arrayBufferChanges)
CFile.prototype["RedactPage"] = function(pageIndex, arrRedactBox, arrayBufferFiller)
{
return this._RedactPage(pageIndex, arrRedactBox, arrayBufferChanges);
return this._RedactPage(pageIndex, arrRedactBox, arrayBufferFiller);
};
CFile.prototype["UndoRedact"] = function()
{

View File

@ -192,13 +192,13 @@ CFile.prototype._UndoMergePages = function()
return Module["_UnmergePages"](this.nativeFile) == 1;
};
CFile.prototype._RedactPage = function(pageIndex, arrRedactBox, arrayBufferChanges)
CFile.prototype._RedactPage = function(pageIndex, arrRedactBox, arrayBufferFiller)
{
let changesPtr = 0;
let changesLen = 0;
if (arrayBufferChanges)
if (arrayBufferFiller)
{
let changes = new Uint8Array(arrayBufferChanges);
let changes = new Uint8Array(arrayBufferFiller);
changesLen = changes.length;
changesPtr = Module["_malloc"](changesLen);
Module["HEAP8"].set(changes, changesPtr);

View File

@ -191,6 +191,7 @@ SOURCES += \
SrcWriter/ResourcesDictionary.cpp \
SrcWriter/Shading.cpp \
SrcWriter/Streams.cpp \
SrcWriter/Types.cpp \
SrcWriter/Utils.cpp \
SrcWriter/Metadata.cpp \
SrcWriter/States.cpp \

View File

@ -302,13 +302,13 @@ void RedactOutputDev::updateTextShift(GfxState *pGState, double shift)
//----- path painting
void RedactOutputDev::stroke(GfxState *pGState)
{
DoPath(pGState, pGState->getPath(), pGState->getCTM(), NULL, true);
DoPathStroke(pGState, pGState->getPath(), pGState->getCTM());
DrawPath(c_nStroke);
// TODO Нужно пересечь путь с областями Redact, результат записать и сделать stroke
}
void RedactOutputDev::fill(GfxState *pGState)
{
DoPath(pGState, pGState->getPath(), pGState->getCTM());
DoPathFill(pGState, pGState->getPath(), pGState->getCTM());
DrawPath(c_nWindingFillMode);
// TODO Нужно пересечь путь с областями Redact, результат записать и сделать fill
}
@ -601,27 +601,15 @@ void ConvertConicToQuads(const pk::SkPoint& p0, const pk::SkPoint& p1, const pk:
std::copy(left, left + 3, pts);
std::copy(right + 1, right + 3, pts + 3);
}
void RedactOutputDev::DoPath(GfxState* pGState, GfxPath* pPath, double* pCTM, GfxClipMatrix* pCTM2, bool bStroke)
void RedactOutputDev::DoPathFill(GfxState* pGState, GfxPath* pPath, double* pCTM)
{
double arrMatrix[6];
if (pCTM2)
{
arrMatrix[0] = pCTM2->dA;
arrMatrix[1] = pCTM2->dB;
arrMatrix[2] = pCTM2->dC;
arrMatrix[3] = pCTM2->dD;
arrMatrix[4] = pCTM2->dE;
arrMatrix[5] = pCTM2->dF;
}
else
{
arrMatrix[0] = pCTM[0];
arrMatrix[1] = pCTM[1];
arrMatrix[2] = pCTM[2];
arrMatrix[3] = pCTM[3];
arrMatrix[4] = pCTM[4];
arrMatrix[5] = pCTM[5];
}
arrMatrix[0] = pCTM[0];
arrMatrix[1] = pCTM[1];
arrMatrix[2] = pCTM[2];
arrMatrix[3] = pCTM[3];
arrMatrix[4] = pCTM[4];
arrMatrix[5] = pCTM[5];
double dShiftX = 0, dShiftY = 0;
DoTransform(arrMatrix, &dShiftX, &dShiftY, true);
@ -640,14 +628,6 @@ void RedactOutputDev::DoPath(GfxState* pGState, GfxPath* pPath, double* pCTM, Gf
oPathRedact.LineTo(m_arrQuadPoints[i + 6], m_arrQuadPoints[i + 7]);
oPathRedact.CloseFigure();
}
size_t length2 = oPathRedact.GetPointCount(), compound2 = oPathRedact.GetCloseCount();
std::vector<Aggplus::PointD> points2 = oPathRedact.GetPoints(0, length2 + compound2);
std::cout << "Path2:" <<std::endl;
for (int i = 0; i < points2.size(); ++i)
{
std::cout << "( " << points2[i].X << ", " << points2[i].Y << " ); ";
}
std::cout <<std::endl;
for (int nSubPathIndex = 0, nSubPathCount = pPath->getNumSubpaths(); nSubPathIndex < nSubPathCount; ++nSubPathIndex)
{
@ -910,6 +890,158 @@ void RedactOutputDev::DoPath(GfxState* pGState, GfxPath* pPath, double* pCTM, Gf
}
}
}
void ToPath(CMatrix* pMatrix, const std::vector<CSegment>& oSegment, CPath* pPath, double& dXCur, double& dYCur)
{
for (int i = 0; i < oSegment.size(); ++i)
{
double dX = oSegment[i].start.x;
double dY = oSegment[i].start.y;
pMatrix->Apply(dX, dY);
double dX2 = oSegment[i].end.x;
double dY2 = oSegment[i].end.y;
dXCur = dX, dYCur = dY;
pMatrix->Apply(dX2, dY2);
pPath->MoveTo(dX, dY);
pPath->LineTo(dX, dY);
}
}
void ToPath(CMatrix* pMatrix, const std::vector<CCubicBezier>& oBezier, CPath* pPath, double& dXCur, double& dYCur)
{
for (int i = 0; i < oBezier.size(); ++i)
{
double dX = oBezier[i].p0.x;
double dY = oBezier[i].p0.y;
pMatrix->Apply(dX, dY);
double dX1 = oBezier[i].p1.x;
double dY1 = oBezier[i].p1.y;
pMatrix->Apply(dX1, dY1);
double dX2 = oBezier[i].p2.x;
double dY2 = oBezier[i].p2.y;
pMatrix->Apply(dX2, dY2);
double dX3 = oBezier[i].p3.x;
double dY3 = oBezier[i].p3.y;
dXCur = dX3, dYCur = dY3;
pMatrix->Apply(dX3, dY3);
pPath->MoveTo(dX, dY);
pPath->CurveTo(dX1, dY1, dX2, dY2, dX3, dY3);
}
}
void RedactOutputDev::DoPathStroke(GfxState* pGState, GfxPath* pPath, double* pCTM)
{
double arrMatrix[6];
arrMatrix[0] = pCTM[0];
arrMatrix[1] = pCTM[1];
arrMatrix[2] = pCTM[2];
arrMatrix[3] = pCTM[3];
arrMatrix[4] = pCTM[4];
arrMatrix[5] = pCTM[5];
double dShiftX = 0, dShiftY = 0;
DoTransform(arrMatrix, &dShiftX, &dShiftY, true);
CMatrix oMatrix(m_arrMatrix[0], m_arrMatrix[1], m_arrMatrix[2], m_arrMatrix[3], m_arrMatrix[4], m_arrMatrix[5]);
CMatrix oInverse = oMatrix.Inverse();
m_pRenderer->m_oPath.Clear();
for (int nSubPathIndex = 0, nSubPathCount = pPath->getNumSubpaths(); nSubPathIndex < nSubPathCount; ++nSubPathIndex)
{
GfxSubpath* pSubpath = pPath->getSubpath(nSubPathIndex);
int nPointsCount = pSubpath->getNumPoints();
double dX = pSubpath->getX(0), dY = pSubpath->getY(0);
oMatrix.Apply(dX, dY);
double dXCur = dX, dYCur = dY;
double dXStart = dX, dYStart = dY;
int nCurPointIndex = 1;
while (nCurPointIndex < nPointsCount)
{
if (pSubpath->getCurve(nCurPointIndex))
{
dX = pSubpath->getX(nCurPointIndex);
dY = pSubpath->getY(nCurPointIndex);
oMatrix.Apply(dX, dY);
double dX2 = pSubpath->getX(nCurPointIndex + 1);
double dY2 = pSubpath->getY(nCurPointIndex + 1);
oMatrix.Apply(dX2, dY2);
double dX3 = pSubpath->getX(nCurPointIndex + 2);
double dY3 = pSubpath->getY(nCurPointIndex + 2);
oMatrix.Apply(dX3, dY3);
nCurPointIndex += 3;
std::vector<CCubicBezier> oResBezier = { CCubicBezier(CPoint(dXCur, dYCur), CPoint(dX, dY), CPoint(dX2, dY2), CPoint(dX3, dY3)) };
for (int i = 0; i < m_arrQuadPoints.size(); i += 4)
{
std::vector<CCubicBezier> oResBezier2;
for (int iBezier = 0; iBezier < oResBezier.size(); ++iBezier)
{
std::vector<CCubicBezier> oResBezier3 = cutBezierWithRectangle(
oResBezier[iBezier].p0.x, oResBezier[iBezier].p0.y, oResBezier[iBezier].p1.x, oResBezier[iBezier].p1.y,
oResBezier[iBezier].p2.x, oResBezier[iBezier].p2.y, oResBezier[iBezier].p3.x, oResBezier[iBezier].p3.y,
m_arrQuadPoints[i + 0], m_arrQuadPoints[i + 1], m_arrQuadPoints[i + 2], m_arrQuadPoints[i + 3]);
oResBezier2.insert(oResBezier2.end(), oResBezier3.begin(), oResBezier3.end());
}
oResBezier = oResBezier2;
}
if (oResBezier.empty())
dXCur = dX3, dYCur = dY3;
else
ToPath(&oInverse, oResBezier, &m_pRenderer->m_oPath, dXCur, dYCur);
}
else
{
dX = pSubpath->getX(nCurPointIndex);
dY = pSubpath->getY(nCurPointIndex);
oMatrix.Apply(dX, dY);
++nCurPointIndex;
std::vector<CSegment> oResSegment = { CSegment(CPoint(dXCur, dYCur), CPoint(dX, dY)) };
for (int i = 0; i < m_arrQuadPoints.size(); i += 4)
{
std::vector<CSegment> oResSegment2;
for (int iSegment = 0; iSegment < oResSegment.size(); ++iSegment)
{
std::vector<CSegment> oResSegment3 = cutLineWithRectangle(
oResSegment[iSegment].start.x, oResSegment[iSegment].start.y, oResSegment[iSegment].end.x, oResSegment[iSegment].end.y,
m_arrQuadPoints[i + 0], m_arrQuadPoints[i + 1], m_arrQuadPoints[i + 2], m_arrQuadPoints[i + 3]);
oResSegment2.insert(oResSegment2.end(), oResSegment3.begin(), oResSegment3.end());
}
oResSegment = oResSegment2;
}
if (oResSegment.empty())
dXCur = dX, dYCur = dY;
else
ToPath(&oInverse, oResSegment, &m_pRenderer->m_oPath, dXCur, dYCur);
}
}
if (pSubpath->isClosed() && dXCur != dXStart && dYCur != dYStart)
{
std::vector<CSegment> oResSegment = { CSegment(CPoint(dXCur, dYCur), CPoint(dXStart, dYStart)) };
for (int i = 0; i < m_arrQuadPoints.size(); i += 4)
{
std::vector<CSegment> oResSegment2;
for (int iSegment = 0; iSegment < oResSegment.size(); ++iSegment)
{
std::vector<CSegment> oResSegment3 = cutLineWithRectangle(
oResSegment[iSegment].start.x, oResSegment[iSegment].start.y, oResSegment[iSegment].end.x, oResSegment[iSegment].end.y,
m_arrQuadPoints[i + 0], m_arrQuadPoints[i + 1], m_arrQuadPoints[i + 2], m_arrQuadPoints[i + 3]);
oResSegment2.insert(oResSegment2.end(), oResSegment3.begin(), oResSegment3.end());
}
oResSegment = oResSegment2;
}
if (!oResSegment.empty())
ToPath(&oInverse, oResSegment, &m_pRenderer->m_oPath, dXCur, dYCur);
}
}
}
void RedactOutputDev::DoTransform(double* pMatrix, double* pdShiftX, double* pdShiftY, bool bActual)
{
if (1 == pMatrix[0] && 0 == pMatrix[1] && 0 == pMatrix[2] && 1 == pMatrix[3] && !bActual)

View File

@ -181,7 +181,8 @@ namespace PdfWriter
std::vector<std::string> m_arrExtGState;
};
void DoPath(GfxState* pGState, GfxPath* pPath, double* pCTM, GfxClipMatrix* pCTM2 = NULL, bool bStroke = false);
void DoPathFill (GfxState* pGState, GfxPath* pPath, double* pCTM);
void DoPathStroke(GfxState* pGState, GfxPath* pPath, double* pCTM);
void DoTransform(double* pMatrix, double* pdShiftX, double* pdShiftY, bool bActual = false);
void DrawPath(const LONG& lType);
void UpdateTransform();

387
PdfFile/SrcWriter/Types.cpp Normal file
View File

@ -0,0 +1,387 @@
/*
* (c) Copyright Ascensio System SIA 2010-2023
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation. In accordance with
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
* that Ascensio System SIA expressly excludes the warranty of non-infringement
* of any third-party rights.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
* street, Riga, Latvia, EU, LV-1050.
*
* The interactive user interfaces in modified source and object code versions
* of the Program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU AGPL version 3.
*
* Pursuant to Section 7(b) of the License you must retain the original Product
* logo when distributing the program. Pursuant to Section 7(e) we decline to
* grant you any rights under trademark law for use of our trademarks.
*
* All the Product's GUI elements, including illustrations and icon sets, as
* well as technical writing content are licensed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
*
*/
#include "Types.h"
namespace PdfWriter
{
// Проверка пересечения отрезков и нахождение точки пересечения
bool findIntersection(const CPoint& p1, const CPoint& p2, const CPoint& q1, const CPoint& q2, CPoint& intersection, double& t)
{
double det = (p2.x - p1.x) * (q2.y - q1.y) - (p2.y - p1.y) * (q2.x - q1.x);
if (std::abs(det) < EPS)
return false; // Параллельны или совпадают
t = ((q1.x - p1.x) * (q2.y - q1.y) - (q1.y - p1.y) * (q2.x - q1.x)) / det;
double u = ((q1.x - p1.x) * (p2.y - p1.y) - (q1.y - p1.y) * (p2.x - p1.x)) / det;
if (t >= -EPS && t <= 1.0 + EPS && u >= -EPS && u <= 1.0 + EPS)
{
intersection.x = p1.x + t * (p2.x - p1.x);
intersection.y = p1.y + t * (p2.y - p1.y);
return true;
}
return false;
}
// Проверка, находится ли точка внутри прямоугольника
bool isPointInsideRectangle(const CPoint& p, double dL, double dB, double dR, double dT)
{
return p.x >= dL - EPS && p.x <= dR + EPS &&
p.y >= dB - EPS && p.y <= dT + EPS;
}
// Проверка, лежит ли прямая на стороне прямоугольника
bool isLineOnRectangleSide(double dX1, double dY1, double dX2, double dY2, double dL, double dB, double dR, double dT)
{
// Проверяем совпадение с каждой стороной
bool onBottom = std::abs(dY1 - dB) < EPS && std::abs(dY2 - dB) < EPS;
bool onTop = std::abs(dY1 - dT) < EPS && std::abs(dY2 - dT) < EPS;
bool onLeft = std::abs(dX1 - dL) < EPS && std::abs(dX2 - dL) < EPS;
bool onRight = std::abs(dX1 - dR) < EPS && std::abs(dX2 - dR) < EPS;
if (onBottom || onTop)
{
double minX = std::min(dX1, dX2);
double maxX = std::max(dX1, dX2);
return minX <= dR + EPS && maxX >= dL - EPS;
}
if (onLeft || onRight)
{
double minY = std::min(dY1, dY2);
double maxY = std::max(dY1, dY2);
return minY <= dT + EPS && maxY >= dB - EPS;
}
return false;
}
// Основная функция: разрезание прямой прямоугольником
std::vector<CSegment> cutLineWithRectangle(double dX1, double dY1, double dX2, double dY2, double dL, double dB, double dR, double dT)
{
std::vector<CSegment> result;
CPoint p1(dX1, dY1);
CPoint p2(dX2, dY2);
// Проверяем особый случай - прямая лежит на стороне
if (isLineOnRectangleSide(dX1, dY1, dX2, dY2, dL, dB, dR, dT))
{
// Вся прямая находится на границе прямоугольника
return result; // Пустой результат - вся прямая "внутри"
}
// Находим все точки пересечения с прямоугольником
std::vector<CPoint> intersections;
std::vector<double> tValues;
// Стороны прямоугольника
CPoint sides[4][2] =
{
{ CPoint(dL, dB), CPoint(dR, dB) }, // нижняя
{ CPoint(dR, dB), CPoint(dR, dT) }, // правая
{ CPoint(dR, dT), CPoint(dL, dT) }, // верхняя
{ CPoint(dL, dT), CPoint(dL, dB) } // левая
};
double t;
CPoint intersection;
for (int i = 0; i < 4; i++)
{
if (findIntersection(p1, p2, sides[i][0], sides[i][1], intersection, t))
{
// Проверяем, нет ли уже такой точки
bool exists = false;
for (const auto& existing : intersections)
{
if (existing == intersection) {
exists = true;
break;
}
}
if (!exists)
{
intersections.push_back(intersection);
tValues.push_back(t);
}
}
}
// Если нет пересечений
if (intersections.empty())
{
// Проверяем, полностью ли прямая внутри или снаружи
bool startInside = isPointInsideRectangle(p1, dL, dB, dR, dT);
bool endInside = isPointInsideRectangle(p2, dL, dB, dR, dT);
if (startInside && endInside)
{
// Вся прямая внутри прямоугольника - возвращаем пустой вектор
return result;
}
else
{
// Вся прямая снаружи прямоугольника
result.push_back(CSegment(p1, p2));
return result;
}
}
// Сортируем точки пересечения по параметру t
for (size_t i = 0; i < tValues.size(); i++)
{
for (size_t j = i + 1; j < tValues.size(); j++)
{
if (tValues[j] < tValues[i])
{
std::swap(tValues[i], tValues[j]);
std::swap(intersections[i], intersections[j]);
}
}
}
// Добавляем начальную и конечную точки для удобства
std::vector<double> allT = { -EPS };
std::vector<CPoint> allPoints = { p1 };
for (size_t i = 0; i < intersections.size(); i++)
{
allT.push_back(tValues[i]);
allPoints.push_back(intersections[i]);
}
allT.push_back(1.0 + EPS);
allPoints.push_back(p2);
// Проходим по всем сегментам и оставляем те, которые снаружи прямоугольника
for (size_t i = 0; i < allPoints.size() - 1; i++)
{
CPoint midPoint((allPoints[i].x + allPoints[i + 1].x) / 2.0,
(allPoints[i].y + allPoints[i + 1].y) / 2.0);
if (!isPointInsideRectangle(midPoint, dL, dB, dR, dT))
{
// Этот сегмент снаружи прямоугольника
result.push_back(CSegment(allPoints[i], allPoints[i + 1]));
}
}
return result;
}
CPoint CCubicBezier::evaluate(double t) const
{
double t2 = t * t;
double t3 = t2 * t;
double u = 1 - t;
double u2 = u * u;
double u3 = u2 * u;
return p0 * u3 + p1 * (3 * u2 * t) + p2 * (3 * u * t2) + p3 * t3;
}
CCubicBezier CCubicBezier::split(double t) const
{
CPoint q0 = p0;
CPoint q1 = p0 + (p1 - p0) * t;
CPoint q2 = p1 + (p2 - p1) * t;
CPoint q3 = p2 + (p3 - p2) * t;
CPoint r0 = q1 + (q2 - q1) * t;
CPoint r1 = q2 + (q3 - q2) * t;
CPoint r2 = r0 + (r1 - r0) * t;
return CCubicBezier(q0, q1, r0, r2);
}
CCubicBezier CCubicBezier::getSegment(double t0, double t1) const
{
if (std::abs(t0 - t1) < EPS)
{
CPoint pt = evaluate(t0);
return CCubicBezier(pt, pt, pt, pt);
}
// Разделяем кривую в точке t1
CCubicBezier firstPart = split(t1);
// Затем разделяем первую часть в точке t0/t1
double normalizedT = t0 / t1;
return firstPart.split(normalizedT);
}
// Проверка пересечения отрезка с границей прямоугольника
bool findLineRectangleIntersection(const CPoint& p1, const CPoint& p2, double dL, double dB, double dR, double dT, std::vector<double>& tValues)
{
bool found = false;
// Проверяем пересечение с каждой стороной
auto checkSide = [&](double x1, double y1, double x2, double y2, int side)
{
double det = (p2.x - p1.x) * (y2 - y1) - (p2.y - p1.y) * (x2 - x1);
if (std::abs(det) < EPS) return;
double t = ((x1 - p1.x) * (y2 - y1) - (y1 - p1.y) * (x2 - x1)) / det;
double u = ((x1 - p1.x) * (p2.y - p1.y) - (y1 - p1.y) * (p2.x - p1.x)) / det;
if (t >= -EPS && t <= 1.0 + EPS && u >= -EPS && u <= 1.0 + EPS)
{
tValues.push_back(t);
found = true;
}
};
// Нижняя сторона
checkSide(dL, dB, dR, dB, 0);
// Правая сторона
checkSide(dR, dB, dR, dT, 1);
// Верхняя сторона
checkSide(dR, dT, dL, dT, 2);
// Левая сторона
checkSide(dL, dT, dL, dB, 3);
return found;
}
// Рекурсивное нахождение пересечений кривой с прямоугольником
void findBezierRectangleIntersections(const CCubicBezier& bezier, double dL, double dB, double dR, double dT,
double t0, double t1, std::vector<double>& intersections, int depth = 0)
{
if (depth > 20) return; // Ограничение глубины рекурсии
CPoint p0 = bezier.evaluate(t0);
CPoint p1 = bezier.evaluate(t1);
// Проверяем, полностью ли сегмент внутри или снаружи
bool inside0 = isPointInsideRectangle(p0, dL, dB, dR, dT);
bool inside1 = isPointInsideRectangle(p1, dL, dB, dR, dT);
if (inside0 && inside1) {
// Весь сегмент внутри - пересечений нет
return;
}
// Проверяем плоскую аппроксимацию (хорду)
std::vector<double> chordIntersections;
if (findLineRectangleIntersection(p0, p1, dL, dB, dR, dT, chordIntersections))
{
if (chordIntersections.size() == 1)
{
// Одно пересечение - добавляем его
double t = t0 + chordIntersections[0] * (t1 - t0);
intersections.push_back(t);
}
else if (chordIntersections.size() == 2)
{
// Два пересечения - добавляем оба
double t1_val = t0 + chordIntersections[0] * (t1 - t0);
double t2_val = t0 + chordIntersections[1] * (t1 - t0);
intersections.push_back(t1_val);
intersections.push_back(t2_val);
}
return;
}
// Если не нашли пересечений с хордой, но сегмент пересекает границу,
// рекурсивно разделяем кривую
double tMid = (t0 + t1) / 2.0;
findBezierRectangleIntersections(bezier, dL, dB, dR, dT, t0, tMid, intersections, depth + 1);
findBezierRectangleIntersections(bezier, dL, dB, dR, dT, tMid, t1, intersections, depth + 1);
}
// Быстрая проверка: полностью ли кривая внутри прямоугольника
bool isBezierCompletelyInside(const CCubicBezier& bezier, const TRect& rect)
{
return isPointInsideRectangle(bezier.p0, rect.fLeft, rect.fBottom, rect.fRight, rect.fTop) &&
isPointInsideRectangle(bezier.p1, rect.fLeft, rect.fBottom, rect.fRight, rect.fTop) &&
isPointInsideRectangle(bezier.p2, rect.fLeft, rect.fBottom, rect.fRight, rect.fTop) &&
isPointInsideRectangle(bezier.p3, rect.fLeft, rect.fBottom, rect.fRight, rect.fTop);
}
// Быстрая проверка: полностью ли кривая снаружи прямоугольника
bool isBezierCompletelyOutside(const CCubicBezier& bezier, const TRect& rect)
{
// Проверяем по ограничивающему прямоугольнику кривой
double minX = std::min({bezier.p0.x, bezier.p1.x, bezier.p2.x, bezier.p3.x});
double maxX = std::max({bezier.p0.x, bezier.p1.x, bezier.p2.x, bezier.p3.x});
double minY = std::min({bezier.p0.y, bezier.p1.y, bezier.p2.y, bezier.p3.y});
double maxY = std::max({bezier.p0.y, bezier.p1.y, bezier.p2.y, bezier.p3.y});
return (maxX < rect.fLeft - EPS) || (minX > rect.fRight + EPS) ||
(maxY < rect.fBottom - EPS) || (minY > rect.fTop + EPS);
}
// Основная функция для разрезания кривой Безье прямоугольником
std::vector<CCubicBezier> cutBezierWithRectangle(
double p0x, double p0y, double p1x, double p1y, double p2x, double p2y, double p3x, double p3y,
double dL, double dB, double dR, double dT)
{
std::vector<CCubicBezier> result;
CCubicBezier bezier(CPoint(p0x, p0y), CPoint(p1x, p1y), CPoint(p2x, p2y), CPoint(p3x, p3y));
// Быстрая проверка: если кривая полностью внутри или снаружи
TRect rect(dL, dT, dR, dB);
if (isBezierCompletelyInside(bezier, rect))
{
return result; // полностью внутри
}
if (isBezierCompletelyOutside(bezier, rect))
{
result.push_back(bezier);
return result; // полностью снаружи
}
// Находим все параметры t пересечения с прямоугольником
std::vector<double> intersections;
findBezierRectangleIntersections(bezier, dL, dB, dR, dT, 0.0, 1.0, intersections);
// Сортируем и удаляем дубликаты
std::sort(intersections.begin(), intersections.end());
intersections.erase(std::unique(intersections.begin(), intersections.end(),
[](double a, double b) { return std::abs(a - b) < EPS; }), intersections.end());
// Добавляем граничные точки
std::vector<double> allT = {0.0};
allT.insert(allT.end(), intersections.begin(), intersections.end());
allT.push_back(1.0);
// Создаем сегменты и проверяем, какие из них находятся снаружи
for (size_t i = 0; i < allT.size() - 1; i++)
{
double t0 = allT[i];
double t1 = allT[i + 1];
double tMid = (t0 + t1) / 2.0;
CPoint midPoint = bezier.evaluate(tMid);
if (!isPointInsideRectangle(midPoint, dL, dB, dR, dT))
{
// Сегмент снаружи прямоугольника
CCubicBezier segment = bezier.getSegment(t0, t1);
result.push_back(segment);
}
}
return result;
}
}

View File

@ -46,6 +46,7 @@
#include <string>
#include <algorithm>
#include <math.h>
#include <vector>
#ifdef __linux__
#include <string.h>
@ -53,6 +54,7 @@
namespace PdfWriter
{
const double EPS = 1e-10;
struct TRect
{
TRect()
@ -76,7 +78,7 @@ namespace PdfWriter
double fTop;
};
typedef TRect TBox;
class CMatrix
class CMatrix
{
public:
@ -356,12 +358,17 @@ namespace PdfWriter
class CPoint
{
public:
CPoint()
{
x = 0;
y = 0;
}
CPoint(double dX, double dY) : x(dX), y(dY) {}
CPoint(const CPoint& oPoint)
{
x = oPoint.x;
y = oPoint.y;
}
void Set(double dX, double dY)
{
x = dX;
@ -378,12 +385,50 @@ namespace PdfWriter
y = oPoint.y;
return *this;
}
bool operator==(const CPoint& oPoint) const
{
return std::abs(x - oPoint.x) < EPS && std::abs(y - oPoint.y) < EPS;
}
CPoint operator+(const CPoint& other) const
{
return CPoint(x + other.x, y + other.y);
}
CPoint operator-(const CPoint& other) const
{
return CPoint(x - other.x, y - other.y);
}
CPoint operator*(double scalar) const
{
return CPoint(x * scalar, y * scalar);
}
CPoint operator/(double scalar) const
{
return CPoint(x / scalar, y / scalar);
}
public:
double x;
double y;
};
struct CSegment
{
CPoint start;
CPoint end;
CSegment(const CPoint& s, const CPoint& e) : start(s), end(e) {}
};
struct CCubicBezier
{
CPoint p0, p1, p2, p3;
CCubicBezier(const CPoint& p0, const CPoint& p1, const CPoint& p2, const CPoint& p3)
: p0(p0), p1(p1), p2(p2), p3(p3) {}
CPoint evaluate(double t) const;
// Разделение кривой в точке t
CCubicBezier split(double t) const;
// Получение сегмента кривой от t0 до t1
CCubicBezier getSegment(double t0, double t1) const;
};
enum EGrMode
{
grmode_PAGE = 0x01,
@ -464,6 +509,8 @@ namespace PdfWriter
RenderingIntent_Saturation,
RenderingIntent_Perceptual
};
std::vector<CSegment> cutLineWithRectangle(double dX1, double dY1, double dX2, double dY2, double dL, double dB, double dR, double dT);
std::vector<CCubicBezier> cutBezierWithRectangle( double p0x, double p0y, double p1x, double p1y, double p2x, double p2y, double p3x, double p3y, double dL, double dB, double dR, double dT);
}
#endif // _PDF_WRITER_SRC_TYPES_H

View File

@ -482,9 +482,9 @@ TEST_F(CPdfFileTest, EditPdfFromBase64)
RELEASEARRAYOBJECTS(pBuffer);
RELEASEARRAYOBJECTS(pFileContent);
// pdfFile->Redact({ 136.145, 6.82016, 136.145, 293.838, 250.691, 293.838, 250.691, 6.82016 }); test1
pdfFile->Redact({ 383.565, 343.134, 383.565, 773.827, 397.965, 773.827, 397.965, 343.134,
88.364, 316.298, 88.364, 773.827, 214.037, 773.827, 214.037, 316.298 });
// pdfFile->Redact({ 136.145, 6.82016, 250.691, 293.838 }); test1
pdfFile->Redact({ 383.565, 343.134, 397.965, 773.827,
88.364, 316.298, 214.037, 773.827 });
pdfFile->Close();
}