SvgShapes.vb
''
'' This code is part of Document Solutions for Imaging demos.
'' Copyright (c) MESCIUS inc. All rights reserved.
''
Imports System
Imports System.IO
Imports System.Drawing
Imports System.Collections.Generic
Imports System.Linq
Imports System.Numerics
Imports GrapeCity.Documents.Drawing
Imports GrapeCity.Documents.Text
Imports GrapeCity.Documents.Imaging
Imports GrapeCity.Documents.Svg
Imports GCTEXT = GrapeCity.Documents.Text
Imports GCDRAW = GrapeCity.Documents.Drawing

'' This sample draws some geometric shapes using GcSvgDocument and GcGraphics.DrawSvg().
'' The code and the results are similar to the Shapes sample.
Public Class SvgShapes
    '' 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).
    '' Points are relative to 'center'.
    Private Function DrawPolygon(g As GcGraphics,
                                 center As PointF,
                                 r As Single,
                                 n As Integer,
                                 startAngle As Single,
                                 stroke As SvgPaint,
                                 Optional caption As String = Nothing) As SvgPoint()
        Const Q As Single = 1.4F
        Dim plgn As New SvgPolygonElement()
        plgn.Points = New List(Of SvgPoint)(n)
        For i As Integer = 0 To n - 1
            plgn.Points.Add(New SvgPoint(
                New SvgLength(CSng(r * Math.Cos(startAngle + 2 * Math.PI * i / n)) * Q, SvgLengthUnits.Percentage),
                New SvgLength(CSng(r * Math.Sin(startAngle + 2 * Math.PI * i / n)) * Q, SvgLengthUnits.Percentage)))
        Next

        If stroke IsNot Nothing Then
            plgn.Stroke = stroke
            plgn.Fill = New SvgPaint(SvgColor.Transparent)
            Dim svgDoc = New GcSvgDocument()
            Dim svg = svgDoc.RootSvg
            svg.Children.Add(plgn)
            g.DrawSvg(svgDoc, New RectangleF(center.X, center.Y, r * 2, r * 2))
        End If

        If Not String.IsNullOrEmpty(caption) Then
            DrawCaption(g, center, r, caption)
        End If

        Return plgn.Points.ToArray()
    End Function

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

    '' Main entry point.
    Public Function GenerateImage(pixelSize As Size, dpi As Single, opaque As Boolean, Optional sampleParams As String() = Nothing) As GcBitmap
        Dim bmp = New GcBitmap(pixelSize.Width, pixelSize.Height, opaque, dpi, dpi)
        Using g = bmp.CreateGraphics(Color.White)
            '' Document header:
            g.DrawString("Shapes (SVG)",
                         New TextFormat() With {.FontSize = 14, .Underline = True},
                         New RectangleF(PointF.Empty, New SizeF(pixelSize.Width, 44)),
                         TextAlignment.Center, ParagraphAlignment.Far)

            '' Set up the helper layout grid:
            Dim Cols As Integer = 3
            Dim Rows As Integer = 5
            Dim MarginX As Single = dpi
            Dim MarginY As Single = dpi / 2.0F
            Dim Radius As Single = dpi / 2.0F
            Dim StepX As Single = (pixelSize.Width - dpi * 2) / 3.0F
            Dim StepY As Single = (pixelSize.Height - dpi) / 5.0F

            '' Insertion point of the next figure's center:
            Dim startIp As New PointF(MarginX + StepX / 2.0F, MarginY + StepY / 2.0F + 10)
            Dim ip As PointF = startIp

            '' Reusable SVG primitives:
            Dim len0 = New SvgLength(0, SvgLengthUnits.Percentage)
            Dim len10 = New SvgLength(10, SvgLengthUnits.Percentage)
            Dim len20 = New SvgLength(20, SvgLengthUnits.Percentage)
            Dim len30 = New SvgLength(30, SvgLengthUnits.Percentage)
            Dim len40 = New SvgLength(40, SvgLengthUnits.Percentage)
            Dim len50 = New SvgLength(50, SvgLengthUnits.Percentage)
            Dim len60 = New SvgLength(60, SvgLengthUnits.Percentage)
            Dim len70 = New SvgLength(70, SvgLengthUnits.Percentage)
            Dim len80 = New SvgLength(80, SvgLengthUnits.Percentage)
            Dim len90 = New SvgLength(90, SvgLengthUnits.Percentage)
            Dim len100 = New SvgLength(100, SvgLengthUnits.Percentage)
            Dim paintTrans = New SvgPaint(SvgColor.Transparent)
            Dim pntStroke0 = New SvgPaint(New SvgColor(Color.Green))
            Dim pntStroke0inv = New SvgPaint(New SvgColor(Color.FromArgb(100, Color.Green)))
            Dim pntFill0 = New SvgPaint(New SvgColor(Color.FromArgb(100, Color.Orange)))
            Dim pt0_0 = New SvgPoint(len0, len0)
            Dim pt100_100 = New SvgPoint(len100, len100)

            '' Circle:
            Dim svgDoc = New GcSvgDocument()
            Dim svg = svgDoc.RootSvg
            svg.Children.Add(New SvgEllipseElement() With {
                .CenterX = len50,
                .CenterY = len50,
                .RadiusX = len50,
                .RadiusY = len50,
                .Fill = paintTrans,
                .Stroke = pntStroke0
            })
            g.DrawSvg(svgDoc, New RectangleF(ip.X - Radius, ip.Y - Radius, Radius * 2, Radius * 2))
            DrawCaption(g, ip, Radius, "Circle")
            ip.X += StepX

            '' Ellipse:
            svg.Children.Clear()
            svg.Children.Add(New SvgEllipseElement() With {
                .CenterX = len50,
                .CenterY = len50,
                .RadiusX = len50,
                .RadiusY = len50,
                .Fill = paintTrans,
                .Stroke = pntStroke0
            })
            g.DrawSvg(svgDoc, New RectangleF(ip.X - Radius * 1.4F, ip.Y - Radius / 2.0F, Radius * 2 * 1.4F, Radius))
            DrawCaption(g, ip, Radius, "Ellipse")
            ip.X += StepX

            '' Cylinder:
            svg.Children.Clear()
            Dim radX As Single = Radius / 1.4F
            Dim radY As Single = Radius / 6.0F
            Dim height As Single = Radius * 1.8F
            svg.Children.Add(New SvgEllipseElement() With {
                .CenterX = len50,
                .CenterY = len10,
                .RadiusX = len50,
                .RadiusY = len10,
                .Fill = paintTrans,
                .Stroke = pntStroke0
            })
            svg.Children.Add(New SvgEllipseElement() With {
                .CenterX = len50,
                .CenterY = len90,
                .RadiusX = len50,
                .RadiusY = len10,
                .Fill = pntFill0,
                .Stroke = pntStroke0
            })
            svg.Children.Add(New SvgLineElement() With {
                .X1 = len0,
                .Y1 = len10,
                .X2 = len0,
                .Y2 = len90,
                .Stroke = pntStroke0
            })
            svg.Children.Add(New SvgLineElement() With {
                .X1 = len100,
                .Y1 = len10,
                .X2 = len100,
                .Y2 = len90,
                .Stroke = pntStroke0
            })
            g.DrawSvg(svgDoc, New RectangleF(ip.X - Radius, ip.Y - Radius, Radius * 2, Radius * 2))
            DrawCaption(g, ip, Radius, "Cylinder")
            ip.X = startIp.X
            ip.Y += StepY

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

            '' Rectangle:
            svg.Children.Clear()
            svg.Children.Add(New SvgRectElement() With {
                .X = New SvgLength(-20, SvgLengthUnits.Percentage),
                .Y = len20,
                .Width = New SvgLength(140, SvgLengthUnits.Percentage),
                .Height = len60,
                .Stroke = pntStroke0,
                .Fill = paintTrans
            })
            g.DrawSvg(svgDoc, New RectangleF(ip.X - Radius, ip.Y - Radius, Radius * 2, Radius * 2))
            DrawCaption(g, ip, Radius, "Rectangle")
            ip.X += StepX

            '' Cube:
            svg.Children.Clear()
            svg.Children.Add(New SvgRectElement() With {
                .X = len10,
                .Y = len10,
                .Width = len60,
                .Height = len60,
                .Stroke = pntStroke0,
                .Fill = paintTrans
            })
            svg.Children.Add(New SvgRectElement() With {
                .X = len30,
                .Y = len30,
                .Width = len60,
                .Height = len60,
                .Stroke = pntStroke0,
                .Fill = paintTrans
            })
            svg.Children.Add(New SvgLineElement() With {
                .X1 = len10, .Y1 = len10, .X2 = len30, .Y2 = len30, .Stroke = pntStroke0
            })
            svg.Children.Add(New SvgLineElement() With {
                .X1 = len10, .Y1 = len70, .X2 = len30, .Y2 = len90, .Stroke = pntStroke0
            })
            svg.Children.Add(New SvgLineElement() With {
                .X1 = len70, .Y1 = len10, .X2 = len90, .Y2 = len30, .Stroke = pntStroke0
            })
            svg.Children.Add(New SvgLineElement() With {
                .X1 = len70, .Y1 = len70, .X2 = len90, .Y2 = len90, .Stroke = pntStroke0
            })
            g.DrawSvg(svgDoc, New RectangleF(ip.X - Radius, ip.Y - Radius, Radius * 2, Radius * 2))
            DrawCaption(g, ip, Radius, "Cube")
            ip.X = startIp.X
            ip.Y += StepY

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

            '' Hexagon (apply transform to make it wider and shorter):
            g.Transform = Matrix3x2.CreateScale(1.4F, 0.8F) * Matrix3x2.CreateTranslation(ip.X, ip.Y)
            DrawPolygon(g, PointF.Empty, Radius, 6, 0, pntStroke0, Nothing)
            g.Transform = Matrix3x2.Identity
            DrawCaption(g, ip, Radius, "Hexagon")
            ip.X += StepX

            '' Octagon:
            DrawPolygon(g, ip, Radius, 8, CSng(-Math.PI / 8), pntStroke0, "Octagon")
            ip.X = startIp.X
            ip.Y += StepY

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

            '' Filled pentagram:
            Dim svgPts = DrawPolygon(g, ip, Radius, 5, CSng(-Math.PI / 2), pntStroke0, "Pentagram")
            svg.Children.Clear()
            svg.Children.Add(New SvgPolygonElement() With {
                .Points = New List(Of SvgPoint) From {svgPts(0), svgPts(2), svgPts(4), svgPts(1), svgPts(3)},
                .Stroke = pntStroke0,
                .Fill = pntFill0,
                .FillRule = SvgFillRule.EvenOdd
            })
            g.DrawSvg(svgDoc, New RectangleF(ip.X, ip.Y, Radius * 2, Radius * 2))
            ip.X += StepX

            '' 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 p3d As Func(Of Vector3, PointF) = Function(v_) project(v_.X, v_.Y, v_.Z)
            Dim hedge As Single = Radius '' 1/2 edge

            '' 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:
            Dim pts = pts3d.Select(Function(v_) p3d(v_))
            svgPts = pts.Select(Function(p_) New SvgPoint(New SvgLength(p_.X, SvgLengthUnits.Percentage),
                                                          New SvgLength(p_.Y, SvgLengthUnits.Percentage))).ToArray()
            g.Transform = Matrix3x2.CreateTranslation(0, hedge * 0.7F)
            svg.Children.Clear()
            '' Visible edges:
            svg.Children.Add(New SvgPolygonElement() With {
                .Points = New List(Of SvgPoint) From {
                    svgPts(4), svgPts(1), svgPts(2), svgPts(3), svgPts(4), svgPts(2)
                },
                .Stroke = pntStroke0,
                .Fill = paintTrans
            })
            '' Bottom:
            svg.Children.Add(New SvgPolygonElement() With {
                .Points = svgPts.Take(4).ToList(),
                .Fill = pntFill0
            })
            '' Invisible edges:
            svg.Children.Add(New SvgLineElement() With {
                .X1 = svgPts(0).X, .Y1 = svgPts(0).Y, .X2 = svgPts(4).X, .Y2 = svgPts(4).Y, .Stroke = pntStroke0inv
            })
            svg.Children.Add(New SvgLineElement() With {
                .X1 = svgPts(0).X, .Y1 = svgPts(0).Y, .X2 = svgPts(1).X, .Y2 = svgPts(1).Y, .Stroke = pntStroke0inv
            })
            svg.Children.Add(New SvgLineElement() With {
                .X1 = svgPts(0).X, .Y1 = svgPts(0).Y, .X2 = svgPts(3).X, .Y2 = svgPts(3).Y, .Stroke = pntStroke0inv
            })
            g.DrawSvg(svgDoc, New RectangleF(ip.X, ip.Y, Radius * 2, Radius * 2))
            g.Transform = Matrix3x2.Identity
            DrawCaption(g, ip, Radius, "Pyramid")
            ip.X = startIp.X
            ip.Y += StepY

            '' Cone:
            Dim baseh As Single = Radius * 0.3F
            svgPts = DrawPolygon(g, ip, Radius, 3, CSng(-Math.PI / 2), Nothing, "Cone")
            svg.Children.Clear()
            svg.Children.Add(New SvgPolylineElement() With {
                .Points = New List(Of SvgPoint) From {svgPts(2), svgPts(0), svgPts(1)},
                .Stroke = pntStroke0,
                .Fill = paintTrans
            })
            svg.Children.Add(New SvgEllipseElement() With {
                .CenterX = New SvgLength(svgPts(0).X.Value, SvgLengthUnits.Percentage),
                .CenterY = svgPts(2).Y,
                .RadiusX = New SvgLength((svgPts(1).X.Value - svgPts(2).X.Value) / 2.0F, SvgLengthUnits.Percentage),
                .RadiusY = len10,
                .Fill = pntFill0,
                .Stroke = pntStroke0
            })
            g.DrawSvg(svgDoc, New RectangleF(ip.X, ip.Y, Radius * 2, Radius * 2))
            ip.X += StepX

            '' Parallelogram (use graphics.Transform on a rectangle):
            svg.Children.Clear()
            Dim svgParal = New SvgRectElement() With {
                .X = New SvgLength(-20, SvgLengthUnits.Percentage),
                .Y = len20,
                .Width = New SvgLength(140, SvgLengthUnits.Percentage),
                .Height = len60,
                .Stroke = pntStroke0,
                .Fill = paintTrans
            }
            svg.Children.Add(svgParal)
            g.Transform = Matrix3x2.CreateSkew(CSng(Math.PI / 6), 0) * Matrix3x2.CreateTranslation(ip.X, ip.Y)
            g.DrawSvg(svgDoc, New RectangleF(-Radius, -Radius, Radius * 2, Radius * 2))
            g.Transform = Matrix3x2.Identity
            DrawCaption(g, ip, Radius, "Parallelogram")
            ip.X += StepX

            '' Trapezoid (use DrawPolygon to just get the points of the square):
            svg.Children.Clear()
            Dim dx As Single = 10
            svgPts = DrawPolygon(g, ip, Radius, 4, CSng(-Math.PI / 4), Nothing, "Trapezoid")
            svg.Children.Add(New SvgPolygonElement() With {
                .Points = New List(Of SvgPoint) From {
                    New SvgPoint(New SvgLength(svgPts(0).X.Value - dx, SvgLengthUnits.Percentage), svgPts(0).Y),
                    New SvgPoint(New SvgLength(svgPts(1).X.Value + dx, SvgLengthUnits.Percentage), svgPts(1).Y),
                    New SvgPoint(New SvgLength(svgPts(2).X.Value - dx, SvgLengthUnits.Percentage), svgPts(2).Y),
                    New SvgPoint(New SvgLength(svgPts(3).X.Value + dx, SvgLengthUnits.Percentage), svgPts(3).Y)
                },
                .Stroke = pntStroke0,
                .Fill = paintTrans
            })
            g.DrawSvg(svgDoc, New RectangleF(ip.X, ip.Y, Radius * 2, Radius * 2))
        End Using

        '' Done:
        Return bmp
    End Function
End Class