RedactPolygon.vb
''
'' This code is part of Document Solutions for PDF .NET demos.
'' Copyright (c) MESCIUS inc. All rights reserved.
''
Imports System
Imports System.IO
Imports System.Numerics
Imports System.Linq
Imports System.Drawing
Imports GrapeCity.Documents.Pdf
Imports GrapeCity.Documents.Pdf.Annotations
Imports GrapeCity.Documents.Drawing
Imports GrapeCity.Documents.Common

'' 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 Function CreatePDF(ByVal stream As Stream) As Integer
        Dim doc = New GcPdfDocument()
        Using fs = File.OpenRead(Path.Combine("Resources", "PDFs", "BalancedColumns.pdf"))
            '' Load the original document (we will use its first page only):
            Dim tdoc = New GcPdfDocument()
            tdoc.Load(fs)
            ' Get the first page:
            doc.MergeWithDocument(tdoc, New MergeDocumentOptions() With {.PagesRange = New OutputRange(1, 1)})
            Dim page = doc.Pages(0)
            ' For reference, copy the first page:
            doc.Pages.ClonePage(0, 1)

            '' Set up a layout grid:
            Dim grid = New With {
                .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:
            Dim startIp = New PointF(page.Size.Width / 3, grid.MarginY + grid.StepY / 2)
            Dim ip = startIp
            Dim ipIdx = 0
            '' Some pastels to use as redact fill colors:
            Dim fills = New Color() {
                Color.FromArgb(&HFF70AE98),
                Color.FromArgb(&HFFECBE7A),
                Color.FromArgb(&HFFE58B88),
                Color.FromArgb(&HFF9DABDD),
                Color.FromArgb(&HFF9DABD0),
                Color.FromArgb(&HFF38908F),
                Color.FromArgb(&HFFB2EBE0),
                Color.FromArgb(&HFF5E96AE),
                Color.FromArgb(&HFFFFBFA3),
                Color.FromArgb(&HFFE08963),
                Color.FromArgb(&HFF9799BA),
                Color.FromArgb(&HFFBC85A3)
            }
            Dim fillColor = fills(0)

            Dim nextIp As Action = Sub()
                ipIdx += 1
                If ipIdx Mod 2 <> 0 Then
                    ip.X += CSng(grid.StepX)
                Else
                    ip.X = startIp.X
                    ip.Y += CSng(grid.StepY)
                End If
                fillColor = fills(ipIdx)
            End Sub

            '' 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:
            Dim pts = MakePolygon(ip, grid.Radius, 5, CSng(-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, CSng(-Math.PI / 8))
            AddPolygonToRedact(page, fillColor, pts)
            nextIp()

            ' Triangle:
            pts = MakePolygon(ip, grid.Radius, 3, CSng(-Math.PI / 2))
            AddPolygonToRedact(page, fillColor, pts)
            nextIp()

            ' Pentagram:
            pts = MakePolygon(ip, grid.Radius, 5, CSng(-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(Function(p_) New PointF(p_.X + CSng(grid.StepX), p_.Y)).ToArray()
            AddPolygonToRedact(page, fillColor, pts, FillMode.Winding)
            nextIp()

            '' Set up a simple oblique projection to draw a pyramid:
            Dim angle = Math.PI / 6
            Dim s As Single = CSng(Math.Sin(angle))
            Dim c As Single = CSng(Math.Cos(angle))
            Dim project As Func(Of Single, Single, Single, PointF) = Function(x_, y_, z_) New PointF(x_ - c * y_ * 0.5F, -(z_ - s * y_ * 0.5F))
            Dim hedge As Single = grid.Radius
            Dim p3d As Func(Of Vector3, PointF) = Function(v_)
                Dim p_ = project(v_.X, v_.Y, v_.Z)
                p_.X += ip.X
                p_.Y += ip.Y + hedge * 0.7F
                Return p_
            End Function
            ' 3d points forming a square pyramid:
            Dim 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(Function(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:
            Dim dx As Single = 10
            pts = MakePolygon(ip, grid.Radius, 4, CSng(-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, CSng(-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, CSng(-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, CSng(-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, CSng(-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
        End Using
    End Function

    '' Finds the intersection of two infinite lines specified by
    '' points a0..a1 and b0..b1.
    Private Shared Function Intersect(a0 As PointF, a1 As PointF, b0 As PointF, b1 As PointF) As PointF
        ' 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)
        Dim ret = PointF.Empty
        Dim qa = (a1.Y - a0.Y) / (a1.X - a0.X)
        Dim qb = (b1.Y - b0.Y) / (b1.X - b0.X)
        ' Deal with non-functions (vertical lines):
        If Not Single.IsFinite(qa) OrElse Not Single.IsFinite(qb) Then
            If Single.IsFinite(qa) OrElse a0.X = b0.X Then
                ret.X = b0.X
            ElseIf Single.IsFinite(qb) Then
                ret.X = a0.X
            Else
                ret.X = Single.NaN
            End If
        ElseIf qa = qb Then
            ret.X = Single.NaN
        Else
            ret.X = (b0.Y - a0.Y + a0.X * qa - b0.X * qb) / (qa - qb)
        End If
        If Single.IsFinite(qa) Then
            ret.Y = (ret.X - a0.X) * qa + a0.Y
        Else
            ret.Y = (ret.X - b0.X) * qb + b0.Y
        End If
        Return ret
    End Function

    Private Shared Function MakePolygon(center As PointF, r As Single, n As Integer, startAngle As Single) As PointF()
        Dim pts = New PointF(n - 1) {}
        For i = 0 To n - 1
            pts(i) = New PointF(center.X + CSng(r * Math.Cos(startAngle + 2 * Math.PI * i / n)), center.Y + CSng(r * Math.Sin(startAngle + 2 * Math.PI * i / n)))
        Next
        Return pts
    End Function

    Private Shared Sub AddPolygonToRedact(p As Page, fillColor As Color, pts As PointF(), Optional fillMode As FillMode = FillMode.Alternate)
        Dim redact = New RedactAnnotation() With {
            .Page = p,
            .OverlayFillColor = fillColor
        }
        redact.AddPolygon(pts, fillMode)
    End Sub
End Class