RedactPolygon.cs
// 
// このコードは、DioDocs for PDF のサンプルの一部として提供されています。
// © 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
{
    // このサンプルでは、​​RedactAnnotation.AddPolygon() メソッドの使用方法を示します。
    // このメソッドを使用すると、墨消し領域に任意の多角形を追加できます。
    // 例えば、墨消し注釈に星形の領域を追加すると、その領域内のすべてのコンテンツが墨消し注釈の適用時に消去されます。
    // 消去された領域を正確に示すために、墨消し注釈の OverlayFillColor を異なる単色に設定しました。
    // これにより、墨消し後の PDF では、色付きの多角形が、墨消しが適用されたときに元のコンテンツが消去された領域を示します。
    // 
    // BalancedColumns サンプルによって生成された PDF の最初のページを使用して墨消しを実行し、参考までに、
    // 墨消しされていない元のページが、結果ドキュメントの 2 ページ目として複製されます。
    public class RedactPolygonQ
    {
        public int CreatePDF(Stream stream)
        {
            var doc = new GcPdfDocument();
            using var fs = File.OpenRead(Path.Combine("Resources", "PDFs", "BalancedColumns.pdf"));
            // ドキュメントを読み込みます(最初のページのみを使用)。
            var tdoc = new GcPdfDocument();
            tdoc.Load(fs);
            // 最初のページを取得します。
            doc.MergeWithDocument(tdoc, new MergeDocumentOptions() { PagesRange = new OutputRange(1, 1) });
            var page = doc.Pages[0];
            // 最初のページをコピーします。
            doc.Pages.ClonePage(0, 1);

            // レイアウトグリッドを設定します。
            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,
            };
            // 墨消しする多角形の挿入ポイントを指定します。
            var startIp = new PointF(page.Size.Width / 3, grid.MarginY + grid.StepY / 2);
            var ip = startIp;
            int ipIdx = 0;
            // 塗りつぶし色として パステルカラー を使用します。
            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];
            }

            // レイアウトの設定が完了したので、墨消しする多角形を追加します。
            // レイアウトヘルパーの設定が完了したら、墨消しするポリゴン領域をいくつか追加します。
            // ポリゴンは、墨消しを適用した際に元のコンテンツがすべて消去される領域を指定することに注意してください。
            // 墨消しの塗りつぶし色は、墨消し(消去)された領域を視覚的に表現するためのものであり、
            // 墨消しを適用するとこれらの領域内のデータは完全に消去され、
            // 低レベルのPDFツールでは復元できません。

            // 五角形
            var pts = MakePolygon(ip, grid.Radius, 5, (float)-Math.PI / 2);
            AddPolygonToRedact(page, fillColor, pts);
            nextIp();

            // 六角形
            pts = MakePolygon(ip, grid.Radius, 6, 0);
            // 少し形を歪ませます。
            pts[0].X += 18;
            pts[3].X -= 72;
            AddPolygonToRedact(page, fillColor, pts);
            nextIp();

            // 八角形
            pts = MakePolygon(ip, grid.Radius, 8, (float)-Math.PI / 8);
            AddPolygonToRedact(page, fillColor, pts);
            nextIp();

            // 三角形
            pts = MakePolygon(ip, grid.Radius, 3, (float)-Math.PI / 2);
            AddPolygonToRedact(page, fillColor, pts);
            nextIp();

            // 五芒星
            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();

            // FillMode.Winding(デフォルトはFillMode.Alternate)を使用した同じ五芒星
            pts = pts.Select(p_ => new PointF(p_.X + grid.StepX, p_.Y)).ToArray();
            AddPolygonToRedact(page, fillColor, pts, FillMode.Winding);
            nextIp();

            // 単純な斜投影を設定してピラミッドを描画します。
            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
            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ポイントを指定します。
            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),
            };
            // ポイントを投影してピラミッドを描画します。
            pts = pts3d.Select(v_ => p3d(v_)).ToArray();
            // 表示される辺
            AddPolygonToRedact(page, fillColor, new PointF[] { pts[4], pts[1], pts[2], pts[3], pts[4], pts[2] });
            // <参考>表示されない辺: pts[0]~pts[4], pts[0]~pts[1], pts[0]~pts[3]
            nextIp();

            // 平行四辺形
            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();

            // 台形
            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();

            // 六芒星
            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();

            // 八芒星
            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();

            // もう一つの八芒星
            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);

            // すべての墨消しを適用します。
            doc.Redact();

            // PDFドキュメントを保存します。
            doc.Save(stream);
            return doc.Pages.Count;
        }

        // ポイント a0~a1とポイントb0~b1で指定された2本の直線の交点を求めます。
        private static PointF Intersect(PointF a0, PointF a1, PointF b0, PointF b1)
        {
            // 計算式
            // 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);
            // 非関数(縦線)に対応
            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);
            // デバッグ用
            // p.Graphics.DrawPolygon(pts, Color.Magenta);
        }
    }
}