Fix intersection line and rectangles

This commit is contained in:
Svetlana Kulikova
2025-10-09 11:54:06 +03:00
parent c5a047f83a
commit 992f6616e0
6 changed files with 182 additions and 247 deletions

View File

@ -1654,7 +1654,6 @@ bool CPdfEditor::EditPage(int _nPageIndex, bool bSet, bool bActualPos)
if (m_nMode == Mode::Unknown && !IncrementalUpdates())
return false;
m_pWriter->AddRedact({});
WriteRedact({});
m_arrRedact.clear();
@ -2701,7 +2700,6 @@ bool CPdfEditor::DeletePage(int nPageIndex)
if (m_nMode == Mode::Unknown && !IncrementalUpdates())
return false;
m_pWriter->AddRedact({});
WriteRedact({});
m_arrRedact.clear();
@ -2730,7 +2728,6 @@ bool CPdfEditor::AddPage(int nPageIndex)
if (m_nMode == Mode::Unknown && !IncrementalUpdates())
return false;
m_pWriter->AddRedact({});
WriteRedact({});
m_arrRedact.clear();
@ -2765,7 +2762,6 @@ bool CPdfEditor::AddPage(int nPageIndex)
}
bool CPdfEditor::MovePage(int nPageIndex, int nPos)
{
m_pWriter->AddRedact({});
WriteRedact({});
m_arrRedact.clear();
@ -3318,7 +3314,6 @@ bool CPdfEditor::EditWidgets(IAdvancedCommand* pCommand)
if (m_nMode == Mode::Unknown && !IncrementalUpdates())
return false;
m_pWriter->AddRedact({});
WriteRedact({});
m_arrRedact.clear();
@ -3633,10 +3628,21 @@ std::vector<double> CPdfEditor::WriteRedact(const std::vector<std::wstring>& arr
double B = ret / 100000.0;
LONG lColor = (LONG)(((LONG)(R * 255)) | ((LONG)(G * 255) << 8) | ((LONG)(B * 255) << 16) | ((LONG)255 << 24));
m_pWriter->AddRedact({});
double dM1, dM2, dM3, dM4, dM5, dM6;
m_pWriter->GetTransform(&dM1, &dM2, &dM3, &dM4, &dM5, &dM6);
LONG lType, lColorB, lAlpha1, lAlpha2;
m_pWriter->get_BrushType(&lType);
m_pWriter->get_BrushColor1(&lColorB);
m_pWriter->get_BrushAlpha1(&lAlpha1);
m_pWriter->get_BrushAlpha2(&lAlpha2);
m_pWriter->SetTransform(1, 0, 0, 1, 0, 0);
m_pWriter->PathCommandEnd();
m_pWriter->put_BrushType(c_BrushTypeSolid);
m_pWriter->put_BrushColor1(lColor);
m_pWriter->put_BrushAlpha1(255);
m_pWriter->put_BrushAlpha2(255);
for (int i = 0; i < oRedact.arrQuads.size(); i += 8)
{
@ -3650,6 +3656,12 @@ std::vector<double> CPdfEditor::WriteRedact(const std::vector<std::wstring>& arr
m_pWriter->DrawPath(NULL, L"", c_nWindingFillMode);
m_pWriter->PathCommandEnd();
m_pWriter->SetTransform(dM1, dM2, dM3, dM4, dM5, dM6);
m_pWriter->put_BrushType(lType);
m_pWriter->put_BrushColor1(lColorB);
m_pWriter->put_BrushAlpha1(lAlpha1);
m_pWriter->put_BrushAlpha2(lAlpha2);
// TODO рендер редакта должен быть пересечён со всеми последующими редактами
// TODO на самом деле должен быть рендер команд редакта
/*

View File

@ -992,6 +992,7 @@ void RedactOutputDev::DoPathRedact(GfxState* pGState, GfxPath* pPath, double* pC
m_pRenderer->m_oPath.Clear();
CMatrix oMatrix(m_arrMatrix[0], m_arrMatrix[1], m_arrMatrix[2], m_arrMatrix[3], m_arrMatrix[4], m_arrMatrix[5]);
CMatrix oInverse = oMatrix.Inverse();
std::vector<CSegment> arrForStroke;
Aggplus::CGraphicsPath oPath, oPathResult;
@ -1000,12 +1001,17 @@ void RedactOutputDev::DoPathRedact(GfxState* pGState, GfxPath* pPath, double* pC
if (bStroke)
{
std::vector<std::vector<CPoint>> rectangles;
for (int i = 0; i < m_arrQuadPoints.size(); i += 8)
{
arrForStroke.push_back(CSegment(CPoint(m_arrQuadPoints[i + 0], m_arrQuadPoints[i + 1]), CPoint(m_arrQuadPoints[i + 2], m_arrQuadPoints[i + 3])));
arrForStroke.push_back(CSegment(CPoint(m_arrQuadPoints[i + 2], m_arrQuadPoints[i + 3]), CPoint(m_arrQuadPoints[i + 4], m_arrQuadPoints[i + 5])));
arrForStroke.push_back(CSegment(CPoint(m_arrQuadPoints[i + 4], m_arrQuadPoints[i + 5]), CPoint(m_arrQuadPoints[i + 6], m_arrQuadPoints[i + 7])));
arrForStroke.push_back(CSegment(CPoint(m_arrQuadPoints[i + 6], m_arrQuadPoints[i + 7]), CPoint(m_arrQuadPoints[i + 0], m_arrQuadPoints[i + 1])));
std::vector<CPoint> rectangle = { CPoint(m_arrQuadPoints[i + 0], m_arrQuadPoints[i + 1]), CPoint(m_arrQuadPoints[i + 2], m_arrQuadPoints[i + 3]),
CPoint(m_arrQuadPoints[i + 4], m_arrQuadPoints[i + 5]), CPoint(m_arrQuadPoints[i + 6], m_arrQuadPoints[i + 7]) };
rectangles.push_back(rectangle);
}
for (int nSubPathIndex = 0, nSubPathCount = pPath->getNumSubpaths(); nSubPathIndex < nSubPathCount; ++nSubPathIndex)
@ -1052,23 +1058,10 @@ void RedactOutputDev::DoPathRedact(GfxState* pGState, GfxPath* pPath, double* pC
oMatrix.Apply(dX, dY);
++nCurPointIndex;
oPath.StartFigure();
oPath.MoveTo(dXCur, dYCur);
oPath.LineTo(dX, dY);
oPath.CloseFigure();
dXCur = dX, dYCur = dY;
oPathResult = Aggplus::CalcBooleanOperation(oPath, m_oPathRedact, Aggplus::BooleanOpType::Subtraction);
DrawPathRedact(&oPathResult, bStroke);
oPathResult.Reset(); oPath.Reset();
/*
CLineClipper clipper(rectangles);
CSegment line(CPoint(dXCur, dYCur), CPoint(dX, dY));
dXCur = dX; dYCur = dY;
auto visibleSegments = RectangleIntersection::findSegmentsOutsideRectangles(line, rectangles);
auto visibleSegments = clipper.getVisibleSegments(line);
for (int i = 0; i < visibleSegments.size(); ++i)
{
double dX1 = visibleSegments[i].start.x, dY1 = visibleSegments[i].start.y;
@ -1078,24 +1071,12 @@ void RedactOutputDev::DoPathRedact(GfxState* pGState, GfxPath* pPath, double* pC
oInverse.Apply(dX2, dY2);
m_pRenderer->m_oPath.LineTo(dX2, dY2);
}
*/
}
}
if (pSubpath->isClosed())
{
oPath.StartFigure();
oPath.MoveTo(dXCur, dYCur);
oPath.LineTo(dXStart, dYStart);
oPath.CloseFigure();
oPathResult = Aggplus::CalcBooleanOperation(oPath, m_oPathRedact, Aggplus::BooleanOpType::Subtraction);
DrawPathRedact(&oPathResult, bStroke);
oPathResult.Reset(); oPath.Reset();
/*
CLineClipper clipper(rectangles);
CSegment line(CPoint(dXCur, dYCur), CPoint(dXStart, dYStart));
auto visibleSegments = clipper.getVisibleSegments(line);
auto visibleSegments = RectangleIntersection::findSegmentsOutsideRectangles(line, rectangles);
for (int i = 0; i < visibleSegments.size(); ++i)
{
double dX1 = visibleSegments[i].start.x, dY1 = visibleSegments[i].start.y;
@ -1105,7 +1086,6 @@ void RedactOutputDev::DoPathRedact(GfxState* pGState, GfxPath* pPath, double* pC
oInverse.Apply(dX2, dY2);
m_pRenderer->m_oPath.LineTo(dX2, dY2);
}
*/
}
}
}

View File

@ -36,6 +36,7 @@
#include "FontCidTT.h"
#include "../PdfWriter.h"
#include "Streams.h"
#include "Utils.h"
//----------------------------------------------------------------------------------------
//
@ -448,12 +449,17 @@ void CPath::Redact(PdfWriter::CMatrix* pMatrix, const std::vector<double>& arrRe
{
bool bPath = false;
std::vector<PdfWriter::CSegment> arrForStroke;
std::vector<std::vector<PdfWriter::CPoint>> rectangles;
for (int i = 0; i < arrRedact.size(); i += 8)
{
arrForStroke.push_back(PdfWriter::CSegment(PdfWriter::CPoint(arrRedact[i + 0], arrRedact[i + 1]), PdfWriter::CPoint(arrRedact[i + 2], arrRedact[i + 3])));
arrForStroke.push_back(PdfWriter::CSegment(PdfWriter::CPoint(arrRedact[i + 2], arrRedact[i + 3]), PdfWriter::CPoint(arrRedact[i + 4], arrRedact[i + 5])));
arrForStroke.push_back(PdfWriter::CSegment(PdfWriter::CPoint(arrRedact[i + 4], arrRedact[i + 5]), PdfWriter::CPoint(arrRedact[i + 6], arrRedact[i + 7])));
arrForStroke.push_back(PdfWriter::CSegment(PdfWriter::CPoint(arrRedact[i + 6], arrRedact[i + 7]), PdfWriter::CPoint(arrRedact[i + 0], arrRedact[i + 1])));
std::vector<PdfWriter::CPoint> rectangle = { PdfWriter::CPoint(arrRedact[i + 0], arrRedact[i + 1]), PdfWriter::CPoint(arrRedact[i + 2], arrRedact[i + 3]),
PdfWriter::CPoint(arrRedact[i + 4], arrRedact[i + 5]), PdfWriter::CPoint(arrRedact[i + 6], arrRedact[i + 7]) };
rectangles.push_back(rectangle);
}
size_t length = oPath.GetPointCount(), compound = oPath.GetCloseCount();
@ -492,25 +498,10 @@ void CPath::Redact(PdfWriter::CMatrix* pMatrix, const std::vector<double>& arrRe
else if (oPath.IsLinePoint(i))
{
double dX = points[i].X, dY = points[i].Y;
Aggplus::CGraphicsPath _oPath;
_oPath.StartFigure();
_oPath.MoveTo(dXCur, dYCur);
_oPath.LineTo(dX, dY);
_oPath.CloseFigure();
dXCur = dX, dYCur = dY;
oPathResult = Aggplus::CalcBooleanOperation(_oPath, oPathRedact, Aggplus::BooleanOpType::Subtraction);
bPath = bPath || DrawPathRedact(pMatrix, &oPathResult, bStroke);
oPathResult.Reset();
/*
PdfWriter::CLineClipper clipper(rectangles);
PdfWriter::CSegment line(PdfWriter::CPoint(dXCur, dYCur), PdfWriter::CPoint(dX, dY));
dXCur = dX; dYCur = dY;
auto visibleSegments = clipper.getVisibleSegments(line);
auto visibleSegments = PdfWriter::RectangleIntersection::findSegmentsOutsideRectangles(line, rectangles);
bPath = bPath || visibleSegments.size() != 0;
for (int i = 0; i < visibleSegments.size(); ++i)
{
@ -521,23 +512,11 @@ void CPath::Redact(PdfWriter::CMatrix* pMatrix, const std::vector<double>& arrRe
MoveTo(dX1, dY1);
LineTo(dX2, dY2);
}
*/
}
else if (oPath.IsClosePoint(i))
{
Aggplus::CGraphicsPath _oPath;
_oPath.StartFigure();
_oPath.MoveTo(dXCur, dYCur);
_oPath.LineTo(dXStart, dYStart);
_oPath.CloseFigure();
oPathResult = Aggplus::CalcBooleanOperation(_oPath, oPathRedact, Aggplus::BooleanOpType::Subtraction);
bPath = bPath || DrawPathRedact(pMatrix, &oPathResult, bStroke);
oPathResult.Reset();
/*
PdfWriter::CLineClipper clipper(rectangles);
PdfWriter::CSegment line(PdfWriter::CPoint(dXCur, dYCur), PdfWriter::CPoint(dXStart, dYStart));
auto visibleSegments = clipper.getVisibleSegments(line);
auto visibleSegments = PdfWriter::RectangleIntersection::findSegmentsOutsideRectangles(line, rectangles);
bPath = bPath || visibleSegments.size() != 0;
for (int i = 0; i < visibleSegments.size(); ++i)
{
@ -548,7 +527,6 @@ void CPath::Redact(PdfWriter::CMatrix* pMatrix, const std::vector<double>& arrRe
MoveTo(dX1, dY1);
LineTo(dX2, dY2);
}
*/
}
}

View File

@ -399,190 +399,6 @@ namespace PdfWriter
CPoint end;
CSegment(const CPoint& s, const CPoint& e) : start(s), end(e) {}
};
class CLineClipper
{
private:
std::vector<TRect> rectangles;
// Проверка пересечения отрезка с прямоугольником
bool intersectsRectangle(const CSegment& segment, const TRect& rect)
{
// Используем алгоритм Коэна-Сазерленда для определения пересечения
int code1 = computeCohenSutherlandCode(segment.start, rect);
int code2 = computeCohenSutherlandCode(segment.end, rect);
// Если оба конца внутри - полное пересечение
if (code1 == 0 && code2 == 0) return true;
// Если оба конца с одной стороны - нет пересечения
if ((code1 & code2) != 0) return false;
// Есть пересечение
return true;
}
// Вычисление кода Коэна-Сазерленда
int computeCohenSutherlandCode(const CPoint& p, const TRect& rect)
{
int code = 0;
if (p.x < rect.fLeft) code |= 1; // Слева
if (p.x > rect.fRight) code |= 2; // Справа
if (p.y < rect.fBottom) code |= 4; // Снизу
if (p.y > rect.fTop) code |= 8; // Сверху
return code;
}
// Нахождение точек пересечения отрезка с прямоугольником
std::vector<CPoint> findIntersectionPoints(const CSegment& segment, const TRect& rect)
{
std::vector<CPoint> intersections;
// Проверяем пересечение с каждой стороной прямоугольника
std::vector<CSegment> rectSides =
{
CSegment(CPoint(rect.fLeft, rect.fBottom), CPoint(rect.fRight, rect.fBottom)), // нижняя
CSegment(CPoint(rect.fRight, rect.fBottom), CPoint(rect.fRight, rect.fTop)), // правая
CSegment(CPoint(rect.fRight, rect.fTop), CPoint(rect.fLeft, rect.fTop)), // верхняя
CSegment(CPoint(rect.fLeft, rect.fTop), CPoint(rect.fLeft, rect.fBottom)) // левая
};
for (const auto& side : rectSides)
{
CPoint intersection;
if (findLineIntersection(segment, side, intersection))
{
// Проверяем, что точка лежит на обоих отрезках
if (pointOnSegment(intersection, segment) && pointOnSegment(intersection, side))
intersections.push_back(intersection);
}
}
return intersections;
}
// Нахождение пересечения двух отрезков
bool findLineIntersection(const CSegment& seg1, const CSegment& seg2, CPoint& result)
{
double x1 = seg1.start.x, y1 = seg1.start.y;
double x2 = seg1.end.x, y2 = seg1.end.y;
double x3 = seg2.start.x, y3 = seg2.start.y;
double x4 = seg2.end.x, y4 = seg2.end.y;
double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (std::abs(denom) < 1e-9) return false; // Параллельные линии
double t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
double u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom;
result.x = x1 + t * (x2 - x1);
result.y = y1 + t * (y2 - y1);
return true;
}
// Проверка, лежит ли точка на отрезке
bool pointOnSegment(const CPoint& p, const CSegment& seg)
{
// Проверяем коллинеарность и нахождение между концами
return std::abs(distance(seg.start, p) + distance(p, seg.end) - distance(seg.start, seg.end)) < 1e-9;
}
// Расстояние между двумя точками
double distance(const CPoint& a, const CPoint& b)
{
return std::sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
// Параметрическое значение точки на линии (0 = start, 1 = end)
double getParametricValue(const CPoint& p, const CSegment& line)
{
double lineLength = distance(line.start, line.end);
if (lineLength < 1e-9) return 0;
// Проекция на направляющий вектор линии
double dx = line.end.x - line.start.x;
double dy = line.end.y - line.start.y;
if (std::abs(dx) > std::abs(dy))
return (p.x - line.start.x) / dx;
else
return (p.y - line.start.y) / dy;
}
public:
CLineClipper(const std::vector<TRect>& rects) : rectangles(rects) {}
// Основная функция для вычисления видимых сегментов
std::vector<CSegment> getVisibleSegments(const CSegment& line)
{
std::vector<double> cutPoints = {0.0, 1.0}; // Начало и конец линии
// Собираем все точки пересечения со всеми прямоугольниками
for (const auto& rect : rectangles)
{
if (intersectsRectangle(line, rect))
{
auto intersections = findIntersectionPoints(line, rect);
for (const auto& p : intersections)
{
double t = getParametricValue(p, line);
if (t >= 0 && t <= 1)
cutPoints.push_back(t);
}
}
}
// Сортируем точки по параметрическому значению
std::sort(cutPoints.begin(), cutPoints.end());
cutPoints.erase(std::unique(cutPoints.begin(), cutPoints.end()), cutPoints.end());
// Проверяем каждый сегмент на видимость
std::vector<CSegment> visibleSegments;
for (size_t i = 0; i < cutPoints.size() - 1; i++)
{
double t1 = cutPoints[i];
double t2 = cutPoints[i + 1];
// Средняя точка сегмента для проверки видимости
double t_mid = (t1 + t2) / 2.0;
CPoint midPoint(
line.start.x + t_mid * (line.end.x - line.start.x),
line.start.y + t_mid * (line.end.y - line.start.y)
);
// Сегмент видим, если его средняя точка не внутри ни одного прямоугольника
if (!isPointInsideAnyRectangle(midPoint))
{
CPoint segStart(
line.start.x + t1 * (line.end.x - line.start.x),
line.start.y + t1 * (line.end.y - line.start.y)
);
CPoint segEnd(
line.start.x + t2 * (line.end.x - line.start.x),
line.start.y + t2 * (line.end.y - line.start.y)
);
visibleSegments.push_back(CSegment(segStart, segEnd));
}
}
return visibleSegments;
}
private:
// Проверка, находится ли точка внутри любого прямоугольника
bool isPointInsideAnyRectangle(const CPoint& p)
{
for (const auto& rect : rectangles)
{
if (p.x >= rect.fLeft && p.x <= rect.fRight && p.y >= rect.fBottom && p.y <= rect.fTop)
return true;
}
return false;
}
};
enum EGrMode
{
grmode_PAGE = 0x01,

View File

@ -379,4 +379,135 @@ namespace PdfWriter
return (cross1 >= 0 && cross2 >= 0 && cross3 >= 0 && cross4 >= 0) ||
(cross1 <= 0 && cross2 <= 0 && cross3 <= 0 && cross4 <= 0);
}
bool RectangleIntersection::segmentsIntersect(const CPoint& a, const CPoint& b, const CPoint& c, const CPoint& d, CPoint& intersection)
{
double x1 = a.x, y1 = a.y;
double x2 = b.x, y2 = b.y;
double x3 = c.x, y3 = c.y;
double x4 = d.x, y4 = d.y;
double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (std::abs(denom) < 1e-10) return false;
double t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
double u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom;
if (t >= 0 && t <= 1 && u >= 0 && u <= 1)
{
intersection.x = x1 + t * (x2 - x1);
intersection.y = y1 + t * (y2 - y1);
return true;
}
return false;
}
bool RectangleIntersection::pointInRectangle(const CPoint& p, const std::vector<CPoint>& rect)
{
double totalAngle = 0;
int n = rect.size();
for (int i = 0; i < n; i++)
{
CPoint v1 = { rect[i].x - p.x, rect[i].y - p.y };
CPoint v2 = { rect[(i + 1) % n].x - p.x, rect[(i + 1) % n].y - p.y };
double dot = v1.x * v2.x + v1.y * v2.y;
double cross = v1.x * v2.y - v1.y * v2.x;
double angle = std::atan2(cross, dot);
totalAngle += angle;
}
return std::abs(totalAngle) > 1;
}
double RectangleIntersection::distanceAlongLine(const CPoint& start, const CPoint& end, const CPoint& point)
{
double dx = end.x - start.x;
double dy = end.y - start.y;
double length = std::sqrt(dx * dx + dy * dy);
if (length < 1e-10) return 0;
// Проекция вектора (point - start) на направление отрезка
double proj = ((point.x - start.x) * dx + (point.y - start.y) * dy) / length;
return proj;
}
std::vector<CSegment> RectangleIntersection::findSegmentsOutsideRectangles(const CSegment& line, const std::vector<std::vector<CPoint>>& rectangles)
{
std::vector<CPoint> allIntersections;
// Собираем все точки пересечения со всеми прямоугольниками
for (const auto& rect : rectangles)
{
for (int i = 0; i < rect.size(); i++)
{
CPoint intersection;
if (segmentsIntersect(line.start, line.end, rect[i], rect[(i + 1) % rect.size()], intersection))
allIntersections.push_back(intersection);
}
}
// Добавляем концы отрезка
allIntersections.push_back(line.start);
allIntersections.push_back(line.end);
// Удаляем дубликаты
std::sort(allIntersections.begin(), allIntersections.end(), [&line](const CPoint& a, const CPoint& b)
{
return distanceAlongLine(line.start, line.end, a) < distanceAlongLine(line.start, line.end, b);
});
auto last = std::unique(allIntersections.begin(), allIntersections.end());
allIntersections.erase(last, allIntersections.end());
// Проверяем каждый сегмент между точками пересечения
std::vector<CSegment> result;
for (size_t i = 0; i < allIntersections.size() - 1; i++) {
CPoint start = allIntersections[i];
CPoint end = allIntersections[i + 1];
// Находим среднюю точку сегмента
CPoint mid =
{
(start.x + end.x) / 2,
(start.y + end.y) / 2
};
// Проверяем, находится ли средняя точка внутри какого-либо прямоугольника
bool isInsideAnyRectangle = false;
for (const auto& rect : rectangles)
{
if (pointInRectangle(mid, rect))
{
isInsideAnyRectangle = true;
break;
}
}
// Если средняя точка не внутри ни одного прямоугольника - это внешний сегмент
if (!isInsideAnyRectangle)
result.push_back(CSegment(start, end));
}
return result;
}
std::vector<CSegment> RectangleIntersection::findSegmentsOutsideRectanglesSequential(const CSegment& line, const std::vector<std::vector<CPoint>>& rectangles)
{
// Начинаем с полного отрезка
std::vector<CSegment> currentSegments = {line};
// Последовательно вычитаем каждый прямоугольник
for (const auto& rect : rectangles) {
std::vector<CSegment> newSegments;
for (const auto& segment : currentSegments) {
auto segmentsOutside = findSegmentsOutsideRectangles(segment, {rect});
newSegments.insert(newSegments.end(), segmentsOutside.begin(), segmentsOutside.end());
}
currentSegments = newSegments;
}
return currentSegments;
}
}

View File

@ -148,6 +148,24 @@ namespace PdfWriter
bool isPointInQuad(double px, double py,
double x1, double y1, double x2, double y2,
double x3, double y3, double x4, double y4);
class RectangleIntersection
{
private:
// Проверка на пересечение двух отрезков
static bool segmentsIntersect(const CPoint& a, const CPoint& b, const CPoint& c, const CPoint& d, CPoint& intersection);
// Проверка, находится ли точка внутри прямоугольника
static bool pointInRectangle(const CPoint& p, const std::vector<CPoint>& rect);
// Вычисление расстояния от точки до начала отрезка вдоль направления
static double distanceAlongLine(const CPoint& start, const CPoint& end, const CPoint& point);
public:
// Основная функция для нахождения отрезков вне всех прямоугольников
static std::vector<CSegment> findSegmentsOutsideRectangles(const CSegment& line, const std::vector<std::vector<CPoint>>& rectangles);
// Альтернативный подход: последовательное вычитание прямоугольников
static std::vector<CSegment> findSegmentsOutsideRectanglesSequential(const CSegment& line, const std::vector<std::vector<CPoint>>& rectangles);
};
}
#endif // _PDF_WRITER_SRC_UTILS_H