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 polygon shapes to the redacted area. // For example, you can add a star-shaped area to the redact annotation, // and all content within that area will be erased when the redact annotation is applied. // To show exactly what areas have been erased, we set the OverlayFillColor // of the redact annotations to different solid colors, so that in the redacted // PDF the colored polygons indicate the areas from which the original content // has been erased when the redacts were applied. // // We use the first page of the PDF generated by the BalancedColumns sample // to perform the redacts, and for reference the original page without redacts // is duplicated as the second page of the resulting document. public class RedactPolygon { 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]; // For reference, copy the first page: doc.Pages.ClonePage(0, 1); // 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 polygon areas to be redacted. // Note that the polygons specify the areas within which all original content // will be erased when the redact is applied. The redact fill colors are used // simply to visualize the redacted (erased) areas, the data within those areas // is completely erased after applying the redact, and cannot be retrieved // using low level PDF tools. // 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 static 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 static 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 static 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); } } }