mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-14 19:05:01 +08:00
implement clipping using Clipper (Boost Software License)
This commit is contained in:
@@ -238,9 +238,9 @@
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="PdfRectangle"/> which entirely contains the geometry of the defined path.
|
||||
/// Gets a <see cref="PdfRectangle"/> which entirely contains the geometry of the defined subpath.
|
||||
/// </summary>
|
||||
/// <returns>For paths which don't define any geometry this returns <see langword="null"/>.</returns>
|
||||
/// <returns>For subpaths which don't define any geometry this returns <see langword="null"/>.</returns>
|
||||
public PdfRectangle? GetBoundingRectangle()
|
||||
{
|
||||
if (commands.Count == 0)
|
||||
|
@@ -1,6 +1,11 @@
|
||||
namespace UglyToad.PdfPig.Geometry
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.Graphics;
|
||||
using static UglyToad.PdfPig.Core.PdfSubpath;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -8,16 +13,172 @@
|
||||
internal static class Clipping
|
||||
{
|
||||
const double factor = 10_000.0;
|
||||
const int linesInCurve = 10; // number of lines to use when transforming bezier curve to polyline.
|
||||
|
||||
/// <summary>
|
||||
/// DOES NOTHING
|
||||
/// </summary>
|
||||
/// <param name="clipping"></param>
|
||||
/// <param name="subject"></param>
|
||||
/// <returns></returns>
|
||||
public static PdfPath Clip(this PdfPath clipping, PdfPath subject)
|
||||
{
|
||||
return subject;
|
||||
if (clipping == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clipping), "Clip(): the clipping path cannot be null.");
|
||||
}
|
||||
|
||||
if (!clipping.IsClipping)
|
||||
{
|
||||
throw new ArgumentException("Clip(): the clipping path does not have the IsClipping flag set to true.", nameof(clipping));
|
||||
}
|
||||
|
||||
if (subject == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(subject), "Clip(): the subject path cannot be null.");
|
||||
}
|
||||
|
||||
if (subject.Count == 0)
|
||||
{
|
||||
return subject;
|
||||
}
|
||||
|
||||
Clipper clipper = new Clipper();
|
||||
|
||||
// Clipping path
|
||||
foreach (var subPathClipping in clipping)
|
||||
{
|
||||
if (subPathClipping.Commands.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Force close clipping polygon
|
||||
if (!subPathClipping.IsClosed())
|
||||
{
|
||||
subPathClipping.CloseSubpath();
|
||||
}
|
||||
|
||||
clipper.AddPath(subPathClipping.ToClipperPolygon().ToList(), PolyType.ptClip, true);
|
||||
}
|
||||
|
||||
// Subject path
|
||||
// Filled and clipping path need to be closed
|
||||
bool subjectClose = subject.IsFilled || subject.IsClipping;
|
||||
foreach (var subPathSubject in subject)
|
||||
{
|
||||
if (subPathSubject.Commands.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Force close subject if need be
|
||||
if (subjectClose && !subPathSubject.IsClosed())
|
||||
{
|
||||
subPathSubject.CloseSubpath();
|
||||
}
|
||||
|
||||
clipper.AddPath(subPathSubject.ToClipperPolygon().ToList(), PolyType.ptSubject, subjectClose);
|
||||
}
|
||||
|
||||
var clippingFillType = clipping.FillingRule == FillingRule.NonZeroWinding ? PolyFillType.pftNonZero : PolyFillType.pftEvenOdd;
|
||||
var subjectFillType = subject.FillingRule == FillingRule.NonZeroWinding ? PolyFillType.pftNonZero : PolyFillType.pftEvenOdd;
|
||||
|
||||
if (!subjectClose)
|
||||
{
|
||||
PdfPath clippedPath = subject.CloneEmpty();
|
||||
|
||||
// Case where subject is not closed
|
||||
var solutions = new PolyTree();
|
||||
if (clipper.Execute(ClipType.ctIntersection, solutions, subjectFillType, clippingFillType))
|
||||
{
|
||||
foreach (var solution in solutions.Childs)
|
||||
{
|
||||
if (solution.Contour.Count > 0)
|
||||
{
|
||||
PdfSubpath clippedSubpath = new PdfSubpath();
|
||||
clippedSubpath.MoveTo(solution.Contour[0].X / factor, solution.Contour[0].Y / factor);
|
||||
|
||||
for (int i = 1; i < solution.Contour.Count; i++)
|
||||
{
|
||||
clippedSubpath.LineTo(solution.Contour[i].X / factor, solution.Contour[i].Y / factor);
|
||||
}
|
||||
clippedPath.Add(clippedSubpath);
|
||||
}
|
||||
}
|
||||
if (clippedPath.Count > 0) return clippedPath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
PdfPath clippedPath = subject.CloneEmpty();
|
||||
|
||||
// Case where subject is closed
|
||||
var solutions = new List<List<IntPoint>>();
|
||||
if (clipper.Execute(ClipType.ctIntersection, solutions, subjectFillType, clippingFillType))
|
||||
{
|
||||
foreach (var solution in solutions)
|
||||
{
|
||||
if (solution.Count > 0)
|
||||
{
|
||||
PdfSubpath clippedSubpath = new PdfSubpath();
|
||||
clippedSubpath.MoveTo(solution[0].X / factor, solution[0].Y / factor);
|
||||
|
||||
for (int i = 1; i < solution.Count; i++)
|
||||
{
|
||||
clippedSubpath.LineTo(solution[i].X / factor, solution[i].Y / factor);
|
||||
}
|
||||
clippedSubpath.CloseSubpath();
|
||||
clippedPath.Add(clippedSubpath);
|
||||
}
|
||||
}
|
||||
if (clippedPath.Count > 0) return clippedPath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows duplicate points as they will be removed by Clipper.
|
||||
/// </summary>
|
||||
private static IEnumerable<IntPoint> ToClipperPolygon(this PdfSubpath pdfPath)
|
||||
{
|
||||
if (pdfPath.Commands.Count == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (pdfPath.Commands[0] is Move currentMove)
|
||||
{
|
||||
var previous = new IntPoint(currentMove.Location.X * factor, currentMove.Location.Y * factor);
|
||||
yield return previous;
|
||||
if (pdfPath.Commands.Count == 1) yield break;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("ToClipperPolygon(): First command is not a Move command. Type is '" + pdfPath.Commands[0].GetType().ToString() + "'.", nameof(pdfPath));
|
||||
}
|
||||
|
||||
for (int i = 1; i < pdfPath.Commands.Count; i++)
|
||||
{
|
||||
var command = pdfPath.Commands[i];
|
||||
if (command is Move move)
|
||||
{
|
||||
throw new ArgumentException("ToClipperPolygon():only one move allowed per subpath.", nameof(pdfPath));
|
||||
}
|
||||
else if (command is Line line)
|
||||
{
|
||||
yield return new IntPoint(line.From.X * factor, line.From.Y * factor);
|
||||
yield return new IntPoint(line.To.X * factor, line.To.Y * factor);
|
||||
}
|
||||
else if (command is BezierCurve curve)
|
||||
{
|
||||
foreach (var lineB in curve.ToLines(linesInCurve))
|
||||
{
|
||||
yield return new IntPoint(lineB.From.X * factor, lineB.From.Y * factor);
|
||||
yield return new IntPoint(lineB.To.X * factor, lineB.To.Y * factor);
|
||||
}
|
||||
}
|
||||
else if (command is Close)
|
||||
{
|
||||
yield return new IntPoint(currentMove.Location.X * factor, currentMove.Location.Y * factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4792
src/UglyToad.PdfPig/Geometry/clipper.cs
Normal file
4792
src/UglyToad.PdfPig/Geometry/clipper.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -23,47 +23,48 @@
|
||||
public bool IsClipping { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Returns true if the path is filled.
|
||||
/// </summary>
|
||||
public bool IsFilled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// The fill color.
|
||||
/// </summary>
|
||||
public IColor FillColor { get; set; }
|
||||
public IColor FillColor { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Returns true if the path is stroked.
|
||||
/// </summary>
|
||||
public bool IsStroked { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// The stroke color.
|
||||
/// </summary>
|
||||
public IColor StrokeColor { get; set; }
|
||||
public IColor StrokeColor { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Thickness in user space units of path to be stroked.
|
||||
/// </summary>
|
||||
public decimal LineWidth { get; set; }
|
||||
public decimal LineWidth { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The pattern to be used for stroked lines.
|
||||
/// </summary>
|
||||
public LineDashPattern? LineDashPattern { get; set; }
|
||||
public LineDashPattern? LineDashPattern { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The cap style to be used for stroked lines.
|
||||
/// </summary>
|
||||
public LineCapStyle LineCapStyle { get; set; }
|
||||
public LineCapStyle LineCapStyle { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The join style to be used for stroked lines.
|
||||
/// </summary>
|
||||
public LineJoinStyle LineJoinStyle { get; set; }
|
||||
public LineJoinStyle LineJoinStyle { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set the clipping mode for this path.
|
||||
/// Set the clipping mode for this path and IsClipping to true.
|
||||
/// <para>IsFilled and IsStroked flags will be set to false.</para>
|
||||
/// </summary>
|
||||
public void SetClipping(FillingRule fillingRule)
|
||||
{
|
||||
@@ -74,7 +75,7 @@
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the filling rule for this path.
|
||||
/// Set the filling rule for this path and IsFilled to true.
|
||||
/// </summary>
|
||||
public void SetFilled(FillingRule fillingRule)
|
||||
{
|
||||
@@ -83,7 +84,7 @@
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Set IsStroked to true.
|
||||
/// </summary>
|
||||
public void SetStroked()
|
||||
{
|
||||
@@ -93,7 +94,7 @@
|
||||
/// <summary>
|
||||
/// Create a clone with no Subpaths.
|
||||
/// </summary>
|
||||
public PdfPath CloneEmpty()
|
||||
internal PdfPath CloneEmpty()
|
||||
{
|
||||
PdfPath newPath = new PdfPath();
|
||||
if (IsClipping)
|
||||
@@ -122,9 +123,9 @@
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Gets a <see cref="PdfRectangle"/> which entirely contains the geometry of the defined path.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <returns>For paths which don't define any geometry this returns <see langword="null"/>.</returns>
|
||||
public PdfRectangle? GetBoundingRectangle()
|
||||
{
|
||||
if (this.Count == 0)
|
||||
|
Reference in New Issue
Block a user