Shapes.vb
''
'' This code is part of GrapeCity Documents for PDF samples.
'' Copyright (c) GrapeCity, Inc. All rights reserved.
''
Imports System.IO
Imports System.Drawing
Imports System.Numerics
Imports System.Linq
Imports GrapeCity.Documents.Pdf
Imports GrapeCity.Documents.Text
Imports GrapeCity.Documents.Drawing

'' Demonstrates how various shapes can be drawn in GcPdf.
'' Shows how simple shapes can be combined to produce more complex shapes.
'' Simple graphics transformations are used to draw some shapes.
Public Class Shapes

    '' Helper method to draw a polygon and a caption beneath it.
    '' Can also be used to just calculate the points without actual drawing.
    '' startAngle is for the first point, clockwise from (1,0).
    Private Function DrawPolygon(
            ByVal g As GcGraphics,
            ByVal center As PointF,
            ByVal r As Single,
            ByVal n As Integer,
            ByVal startAngle As Single,
            ByVal pn As Pen,
            Optional ByVal caption As String = Nothing) As PointF()
        Dim pts(n - 1) As PointF
        For i = 0 To n - 1
            pts(i) = New PointF(center.X + (r * Math.Cos(startAngle + 2 * Math.PI * i / n)), center.Y + (r * Math.Sin(startAngle + 2 * Math.PI * i / n)))
        Next

        If pn IsNot Nothing Then
            g.DrawPolygon(pts, pn)
        End If
        If Not String.IsNullOrEmpty(caption) Then
            DrawCaption(g, center, r, caption)
        End If
        Return pts
    End Function

    '' Helper method to draw a caption beneath a shape:
    Private Sub DrawCaption(ByVal g As GcGraphics, ByVal center As PointF, ByVal r As Single, ByVal caption As String)
        g.DrawString(
            caption,
            New TextFormat() With {.Font = StandardFonts.Times, .FontSize = 10},
            New RectangleF(center.X - r, center.Y + r, r * 2, 24),
            TextAlignment.Center, ParagraphAlignment.Center, False)
    End Sub

    '' Main entry point.
    Function CreatePDF(ByVal stream As Stream) As Integer
        Dim doc = New GcPdfDocument()
        Dim page = doc.Pages.Add()
        Dim g = page.Graphics
        '' Document header:
        g.DrawString("Shapes",
                New TextFormat() With {.Font = StandardFonts.TimesBold, .FontSize = 14, .Underline = True},
                New RectangleF(PointF.Empty, New SizeF(page.Size.Width, 44)),
                TextAlignment.Center, ParagraphAlignment.Far)
        '' Pen used to draw shapes:
        Dim pen = New Pen(Color.Orange, 1)
        pen.LineJoin = PenLineJoin.Round
        Dim fill = 100 '' Surfaces fill alpha

        '' Set up the helper layout grid:
        Dim grid = New With {
                .Cols = 3,
                .Rows = 5,
                .MarginX = 72,
                .MarginY = 36,
                .Radius = 36,
                .StepX = (page.Size.Width - 144) / 3,
                .StepY = (page.Size.Height - 72) / 5
            }

        '' Insertion point of the next figure's center:
        Dim startIp = New PointF(grid.MarginX + grid.StepX / 2, grid.MarginY + grid.StepY / 2 + 10)
        Dim ip = startIp
#If False Then
        '' Debug code to show the layout grid:
        Dim ipp = ip
        For i = 1 To grid.Cols
            ipp.Y = ip.Y
            For j = 1 To grid.Rows
                g.DrawRectangle(New RectangleF(ipp.X - grid.Radius, ipp.Y - grid.Radius, grid.Radius * 2, grid.Radius * 2), Color.LightGreen, 0.5F)
                ipp.Y += grid.StepY
            Next
            ipp.X += grid.StepX
        Next
#End If
        '' Circle:
        g.DrawEllipse(New RectangleF(ip.X - grid.Radius, ip.Y - grid.Radius, grid.Radius * 2, grid.Radius * 2), pen)
        DrawCaption(g, ip, grid.Radius, "Circle")
        ip.X += grid.StepX

        '' Ellipse:
        g.DrawEllipse(New RectangleF(ip.X - grid.Radius * 1.4F, ip.Y - grid.Radius / 2, grid.Radius * 2 * 1.4F, grid.Radius), pen)
        DrawCaption(g, ip, grid.Radius, "Ellipse")
        ip.X += grid.StepX

        '' Cylinder:
        Dim radX = grid.Radius / 1.4F
        Dim radY = grid.Radius / 6
        Dim height = grid.Radius * 1.8F
        g.DrawEllipse(New RectangleF(ip.X - radX, ip.Y - height / 2, radX * 2, radY * 2), pen)
        g.FillEllipse(New RectangleF(ip.X - radX, ip.Y + height / 2 - radY * 2, radX * 2, radY * 2), Color.FromArgb(fill, pen.Color))
        g.DrawEllipse(New RectangleF(ip.X - radX, ip.Y + height / 2 - radY * 2, radX * 2, radY * 2), pen)
        g.DrawLine(New PointF(ip.X - radX, ip.Y - height / 2 + radY), New PointF(ip.X - radX, ip.Y + height / 2 - radY), pen)
        g.DrawLine(New PointF(ip.X + radX, ip.Y - height / 2 + radY), New PointF(ip.X + radX, ip.Y + height / 2 - radY), pen)
        DrawCaption(g, ip, grid.Radius, "Cylinder")
        ip.X = startIp.X
        ip.Y += grid.StepY
        pen.Color = Color.Indigo

        '' Square:
        DrawPolygon(g, ip, grid.Radius, 4, -Math.PI / 4, pen, "Square")
        ip.X += grid.StepX

        '' Rectangle:
        Dim rectQx = 1.4F
        Dim rectQy = 0.6F
        Dim rect = New RectangleF(ip.X - grid.Radius * rectQx, ip.Y - grid.Radius * rectQy, grid.Radius * 2 * rectQx, grid.Radius * 2 * rectQy)
        g.DrawRectangle(rect, pen)
        DrawCaption(g, ip, grid.Radius, "Rectangle")
        ip.X += grid.StepX

        '' Cube:
        Dim cubex = 6
        Dim cubePtsFar = DrawPolygon(g, New PointF(ip.X - cubex, ip.Y - cubex), grid.Radius, 4, -Math.PI / 4, pen)
        Dim cubePtsNear = DrawPolygon(g, New PointF(ip.X + cubex, ip.Y + cubex), grid.Radius, 4, -Math.PI / 4, pen)
        g.DrawLine(cubePtsFar(0), cubePtsNear(0), pen)
        g.DrawLine(cubePtsFar(1), cubePtsNear(1), pen)
        g.DrawLine(cubePtsFar(2), cubePtsNear(2), pen)
        g.DrawLine(cubePtsFar(3), cubePtsNear(3), pen)
        g.FillPolygon(New PointF() {cubePtsFar(1), cubePtsFar(2), cubePtsNear(2), cubePtsNear(1)}, Color.FromArgb(fill, pen.Color))
        DrawCaption(g, ip, grid.Radius, "Cube")
        ip.X = startIp.X
        ip.Y += grid.StepY
        pen.Color = Color.DarkGreen

        '' Pentagon:
        DrawPolygon(g, ip, grid.Radius, 5, -Math.PI / 2, pen, "Pentagon")
        ip.X += grid.StepX

        '' Hexagon:
        '' For sample sake, we apply a transform to make the hexagon wider and shorter:
        g.Transform = Matrix3x2.CreateScale(1.4F, 0.8F) * Matrix3x2.CreateTranslation(ip.X, ip.Y)
        DrawPolygon(g, PointF.Empty, grid.Radius, 6, 0, pen, Nothing)
        g.Transform = Matrix3x2.Identity
        DrawCaption(g, ip, grid.Radius, "Hexagon")
        ip.X += grid.StepX

        '' Octagon:
        DrawPolygon(g, ip, grid.Radius, 8, -Math.PI / 8, pen, "Octagon")
        ip.X = startIp.X
        ip.Y += grid.StepY
        pen.Color = Color.DarkRed

        '' Triangle:
        DrawPolygon(g, ip, grid.Radius, 3, -Math.PI / 2, pen, "Triangle")
        ip.X += grid.StepX

        '' Filled pentagram:
        Dim pts = DrawPolygon(g, ip, grid.Radius, 5, -Math.PI / 2, pen, "Pentagram")
        pts = New PointF() {pts(0), pts(2), pts(4), pts(1), pts(3)}
        g.FillPolygon(pts, Color.FromArgb(fill, pen.Color))
        g.DrawPolygon(pts, pen)
        ip.X += grid.StepX

        '' Set up a simple kind of oblique projection to draw a pyramid:
        Dim angle = Math.PI / 6
        Dim s = Math.Sin(angle)
        Dim c = Math.Cos(angle)

        Dim project As Func(Of Single, Single, Single, PointF) =
            Function(ByVal x_, ByVal y_, ByVal z_)
                Return New PointF(x_ - c * y_ * 0.5F, -(z_ - s * y_ * 0.5F))
            End Function

        Dim p3d As Func(Of Vector3, PointF) =
            Function(ByVal v_)
                Return project(v_.X, v_.Y, v_.Z)
            End Function

        Dim hedge = grid.Radius '' 1/2 edge
        '' Debug - draw the 3 axis:
        '' g.DrawLine(project(0, 0, 0), project(100, 0, 0), Color.Red)
        '' g.DrawLine(project(0, 0, 0), project(0, 100, 0), Color.Green)
        '' g.DrawLine(project(0, 0, 0), project(0, 0, 100), Color.Blue)

        '' 3d points forming a square pyramid:
        Dim pts3d As 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()
        g.Transform = Matrix3x2.CreateTranslation(ip.X, ip.Y + hedge * 0.7F)
        '' Visible edges:
        g.DrawPolygon(New PointF() {pts(4), pts(1), pts(2), pts(3), pts(4), pts(2)}, pen)
        '' Invisible edges:
        pen.Width /= 2
        pen.Color = Color.FromArgb(fill, pen.Color)
        g.DrawLine(pts(0), pts(4), pen)
        g.DrawLine(pts(0), pts(1), pen)
        g.DrawLine(pts(0), pts(3), pen)
        g.FillPolygon(pts.Take(4).ToArray(), pen.Color)
        ''
        g.Transform = Matrix3x2.Identity
        DrawCaption(g, ip, grid.Radius, "Pyramid")
        ip.X = startIp.X
        ip.Y += grid.StepY
        pen.Width *= 2
        pen.Color = Color.Green

        '' Cone:
        Dim baseh = grid.Radius * 0.3F
        pts = DrawPolygon(g, ip, grid.Radius, 3, -Math.PI / 2, Nothing, "Cone")
        g.DrawLines(New PointF() {pts(2), pts(0), pts(1)}, pen)
        rect = New RectangleF(pts(2).X, pts(2).Y - baseh / 2, pts(1).X - pts(2).X, baseh)
        g.FillEllipse(rect, Color.FromArgb(fill, pen.Color))
        g.DrawEllipse(rect, pen)
        ip.X += grid.StepX

        '' Parallelogram (use graphics.Transform on a rectangle):
        rect = New RectangleF(-grid.Radius * rectQx, -grid.Radius * rectQy, grid.Radius * 2 * rectQx, grid.Radius * 2 * rectQy)
        g.Transform = Matrix3x2.CreateSkew(Math.PI / 6, 0) * Matrix3x2.CreateTranslation(ip.X, ip.Y)
        g.DrawRectangle(rect, pen)
        g.Transform = Matrix3x2.Identity
        DrawCaption(g, ip, grid.Radius, "Parallelogram")
        ip.X += grid.StepX

        '' Trapezoid (use DrawPolygon to just get the points of the square):
        Dim dx = 10
        pts = DrawPolygon(g, ip, grid.Radius, 4, -Math.PI / 4, Nothing, "Trapezoid")
        pts(0).X -= dx
        pts(1).X += dx
        pts(2).X -= dx
        pts(3).X += dx
        g.DrawPolygon(pts, pen)
        ''
        '' Done:
        doc.Save(stream)
        Return doc.Pages.Count
    End Function
End Class