RedactPolygon.cs
//
// This code is part of Document Solutions for PDF demos.
// Copyright (c) MESCIUS inc. All rights reserved.
//
using System;
using System.IO;
using System.Numerics;
using System.Linq;
using System.Drawing;
using System.Text.RegularExpressions;
using GrapeCity.Documents.Pdf;
using GrapeCity.Documents.Pdf.Annotations;
using GrapeCity.Documents.Pdf.TextMap;
using GrapeCity.Documents.Pdf.AcroForms;
using GrapeCity.Documents.Drawing;
using GrapeCity.Documents.Common;
namespace DsPdfWeb.Demos
{
// This sample demonstrates the use of RedactAnnotation.AddPolygon() method
// which allows you to add arbitrary polygons to the redact annotation.
// It loads the first page of the PDF generated by the BalancedColumns sample,
// and adds redact annotations that redact out some polygons on that page.
public class RedactPolygonQ
{
public int CreatePDF(Stream stream)
{
var doc = new GcPdfDocument();
using (var fs = File.OpenRead(Path.Combine("Resources", "PDFs", "BalancedColumns.pdf")))
{
// Load the original document (we will use its first page only):
var tdoc = new GcPdfDocument();
tdoc.Load(fs);
// Get the first page:
doc.MergeWithDocument(tdoc, new MergeDocumentOptions() { PagesRange = new OutputRange(1, 1) });
var page = doc.Pages[0];
// Set up a layout grid:
var grid = new
{
Cols = 2,
Rows = 6,
MarginX = 72,
MarginY = 72,
Radius = 36,
StepX = (page.Size.Width - 72) / 2,
StepY = (page.Size.Height - 144) / 6,
};
// Insertion point for next polygon to be redacted:
var startIp = new PointF(page.Size.Width / 3, grid.MarginY + grid.StepY / 2);
var ip = startIp;
int ipIdx = 0;
// Some pastels to use as redact fill colors :
var fills = new Color[]
{
Color.FromArgb(unchecked((int)0xff70ae98)),
Color.FromArgb(unchecked((int)0xffecbe7a)),
Color.FromArgb(unchecked((int)0xffe58b88)),
Color.FromArgb(unchecked((int)0xff9dabdd)),
Color.FromArgb(unchecked((int)0xff9dabd0)),
Color.FromArgb(unchecked((int)0xff38908f)),
Color.FromArgb(unchecked((int)0xffb2ebe0)),
Color.FromArgb(unchecked((int)0xff5e96ae)),
Color.FromArgb(unchecked((int)0xffffbfa3)),
Color.FromArgb(unchecked((int)0xffe08963)),
Color.FromArgb(unchecked((int)0xff9799ba)),
Color.FromArgb(unchecked((int)0xffbc85a3)),
};
var fillColor = fills[0];
void nextIp()
{
if (++ipIdx % 2 != 0)
{
ip.X += grid.StepX;
}
else
{
ip.X = startIp.X;
ip.Y += grid.StepY;
}
fillColor = fills[ipIdx];
}
// Layout helper setup done, now add some polygons to be redacted:
// Pentagon:
var pts = MakePolygon(ip, grid.Radius, 5, (float)-Math.PI / 2);
AddPolygonToRedact(page, fillColor, pts);
nextIp();
// Hexagon:
pts = MakePolygon(ip, grid.Radius, 6, 0);
// Distort the shape a bit:
pts[0].X += 18;
pts[3].X -= 72;
AddPolygonToRedact(page, fillColor, pts);
nextIp();
// Octagon:
pts = MakePolygon(ip, grid.Radius, 8, (float)-Math.PI / 8);
AddPolygonToRedact(page, fillColor, pts);
nextIp();
// Triangle:
pts = MakePolygon(ip, grid.Radius, 3, (float)-Math.PI / 2);
AddPolygonToRedact(page, fillColor, pts);
nextIp();
// Pentagram:
pts = MakePolygon(ip, grid.Radius, 5, (float)-Math.PI / 2);
pts = new PointF[] { pts[0], pts[2], pts[4], pts[1], pts[3], };
AddPolygonToRedact(page, fillColor, pts);
nextIp();
// Same pentagram using FillMode.Winding (the default is FillMode.Alternate):
pts = pts.Select(p_ => new PointF(p_.X + grid.StepX, p_.Y)).ToArray();
AddPolygonToRedact(page, fillColor, pts, FillMode.Winding);
nextIp();
// Set up a simple oblique projection to draw a pyramid:
var angle = Math.PI / 6;
float s = (float)Math.Sin(angle);
float c = (float)Math.Cos(angle);
Func<float, float, float, PointF> project = (x_, y_, z_) => new PointF(x_ - c * y_ * 0.5f, -(z_ - s * y_ * 0.5f));
float hedge = grid.Radius; // 1/2 edge
Func<Vector3, PointF> p3d = v_ => { var p_ = project(v_.X, v_.Y, v_.Z); p_.X += ip.X; p_.Y += ip.Y + hedge * 0.7f; return p_; };
// 3d points forming a square pyramid:
var pts3d = new Vector3[]
{
new Vector3(-hedge, -hedge, 0),
new Vector3(hedge, -hedge, 0),
new Vector3(hedge, hedge, 0),
new Vector3(-hedge, hedge, 0),
new Vector3(0, 0, hedge * 2),
};
// Project the points to draw the pyramid:
pts = pts3d.Select(v_ => p3d(v_)).ToArray();
// Visible edges:
AddPolygonToRedact(page, fillColor, new PointF[] { pts[4], pts[1], pts[2], pts[3], pts[4], pts[2] });
// For reference, invisible edges are: pts[0]..pts[4], pts[0]..pts[1], pts[0]..pts[3]
nextIp();
// Parallelogram:
float dx = 10;
pts = MakePolygon(ip, grid.Radius, 4, (float)-Math.PI / 4);
pts[0].X += dx;
pts[1].X -= dx;
pts[2].X -= dx;
pts[3].X += dx;
AddPolygonToRedact(page, fillColor, pts);
nextIp();
// Trapezoid:
pts = MakePolygon(ip, grid.Radius, 4, (float)-Math.PI / 4);
pts[0].X -= dx;
pts[1].X += dx;
pts[2].X -= dx;
pts[3].X += dx;
AddPolygonToRedact(page, fillColor, pts);
nextIp();
// Hexagram:
pts = MakePolygon(ip, grid.Radius, 6, (float)-Math.PI / 2);
pts = new PointF[]
{
pts[0],
Intersect(pts[0], pts[2], pts[5], pts[1]),
pts[1],
Intersect(pts[0], pts[2], pts[1], pts[3]),
pts[2],
Intersect(pts[1], pts[3], pts[2], pts[4]),
pts[3],
Intersect(pts[2], pts[4], pts[3], pts[5]),
pts[4],
Intersect(pts[4], pts[0], pts[3], pts[5]),
pts[5],
Intersect(pts[4], pts[0], pts[5], pts[1]),
};
AddPolygonToRedact(page, fillColor, pts);
nextIp();
// Octagram:
pts = MakePolygon(ip, grid.Radius, 8, (float)-Math.PI / 2);
pts = new PointF[]
{
pts[0],
Intersect(pts[0], pts[2], pts[7], pts[1]),
pts[1],
Intersect(pts[1], pts[3], pts[0], pts[2]),
pts[2],
Intersect(pts[2], pts[4], pts[1], pts[3]),
pts[3],
Intersect(pts[2], pts[4], pts[3], pts[5]),
pts[4],
Intersect(pts[3], pts[5], pts[4], pts[6]),
pts[5],
Intersect(pts[5], pts[7], pts[4], pts[6]),
pts[6],
Intersect(pts[6], pts[0], pts[5], pts[7]),
pts[7],
Intersect(pts[6], pts[0], pts[1], pts[7]),
};
AddPolygonToRedact(page, fillColor, pts);
nextIp();
// Another octagram:
pts = MakePolygon(ip, grid.Radius, 8, (float)-Math.PI / 2);
pts = new PointF[] { pts[0], pts[3], pts[6], pts[1], pts[4], pts[7], pts[2], pts[5] };
AddPolygonToRedact(page, fillColor, pts);
// Apply all redacts:
doc.Redact();
// Done:
doc.Save(stream);
return doc.Pages.Count;
}
}
// Finds the intersection of two infinite lines specified by
// points a0..a1 and b0..b1.
private PointF Intersect(PointF a0, PointF a1, PointF b0, PointF b1)
{
// Math:
// y = (x - a0.X) * qa + a0.Y
// (x - a0.X) * qa + a0.Y = (x - b0.X) * qb + b0.Y
// (x - a0.X) * qa - (x - b0.X) * qb = b0.Y - a0.Y
// x = (b0.Y - a0.Y + a0.X * qa - b0.X * qb) / (qa - qb)
var ret = PointF.Empty;
var qa = (a1.Y - a0.Y) / (a1.X - a0.X);
var qb = (b1.Y - b0.Y) / (b1.X - b0.X);
// Deal with non-functions (vertical lines):
if (!float.IsFinite(qa) || !float.IsFinite(qb))
{
if (float.IsFinite(qa) || a0.X == b0.X)
ret.X = b0.X;
else if (float.IsFinite(qb))
ret.X = a0.X;
else
ret.X = float.NaN;
}
else if (qa == qb)
ret.X = float.NaN;
else
ret.X = (b0.Y - a0.Y + a0.X * qa - b0.X * qb) / (qa - qb);
if (float.IsFinite(qa))
ret.Y = (ret.X - a0.X) * qa + a0.Y;
else
ret.Y = (ret.X - b0.X) * qb + b0.Y;
return ret;
}
private PointF[] MakePolygon(PointF center, float r, int n, float startAngle)
{
PointF[] pts = new PointF[n];
for (int i = 0; i < n; ++i)
pts[i] = new PointF(center.X + (float)(r * Math.Cos(startAngle + 2 * Math.PI * i / n)), center.Y + (float)(r * Math.Sin(startAngle + 2 * Math.PI * i / n)));
return pts;
}
private void AddPolygonToRedact(Page p, Color fillColor, PointF[] pts, FillMode fillMode = FillMode.Alternate)
{
var redact = new RedactAnnotation()
{
Page = p,
OverlayFillColor = fillColor
};
redact.AddPolygon(pts, fillMode);
// Debug:
// p.Graphics.DrawPolygon(pts, Color.Magenta);
}
}
}