Attribute VB_Name = "modPageDrawings"
'to keep the Form-Code a bit more clean, we place all the Drawings - those which
'are really only relevant for our Page-Rendering - here in a separate Module

Option Explicit

'this defines the Page-Margins, let's choose 1 inch (= 2.54cm) on all sides
Private Const LeftMarginPoints# = 72, RightMarginPoints# = 72
Private Const TopMarginPoints# = 72, BottomMarginPoints# = 72

Private PageWidthPoints As Double, PageHeightPoints As Double
Private PageScaleWidth As Double, PageScaleHeight As Double

Private CC As cCairoContext, QuarterColors(0 To 4) As Long


'here in this Main-routine all the Page-Rendering takes place (it triggers different, smaller SubRoutines
'which are also hosted in this Module as Private Subs... (some already known Sub-Drawings from earlier Demoms are incorporated here)
Public Sub DrawPage(PageCC As cCairoContext, TableData) '<- note, that the CC which we get passed here is *not* our usual ucCanvas.CC...
  'it is either the PagePreview.PixelCC (derived from the cPagePreview-Class'-internal PixelSurface)...
  'or it is a "PDF-Surface-derived CC", when the very same Drawing-Procedure here is called for PDF-rendering
  
  Set CC = PageCC 'store the CC in our module-internal Variable, to avoid passing it around into each Sub-Procedure
  
  'define the Colors, which will be used later on for our ChartDrawings
  QuarterColors(0) = &H444411 'this is only used in the small Tables "Year-Column"
  QuarterColors(1) = vbGreen
  QuarterColors(2) = vbRed
  QuarterColors(3) = vbYellow
  QuarterColors(4) = vbMagenta
  
  
  'Ok, let's ensure, that our Page-Background is rendered plain-white first
  CC.SetSourceColor vbWhite
  CC.Paint

    'the following determines the current PageWidth and -Height in Points, directly from our
    'passed CC-Variable (and the resulting values should be the same, no matter if the CC-Param
    'we've got passed here, was derived from a PDF-Surface or from our Pixel-Surface in cPagePreview
    PageWidthPoints = CC.Surface.Width
    PageHeightPoints = CC.Surface.Height
    'at this point here, the two variables above yet contain the Surfaces "Device-Dimensions"
    '  (in case of the Pixel-Surface, these are its current PixelDimensions, in case of the PDF-
    '   Surface we'd already have the Dimension in Point-Units, since its internal scale is at 1)
    'but in case of a passed PixelSurface-CC, and after we will have performed the call below, we will receive
    'correctly scaled values in Point-UserCoordinates (which in both cases, PDF- or Pixel-CC) should come out as the same Values
    CC.DeviceToUserDistance PageWidthPoints, PageHeightPoints
    
  'so, now in the following we can work in this "unified, everything is given in Points = 1/72 inch" coord-system
  'no matter what Surface-Type (Pixels or PDF) the rendering finally happens against
  CC.Save

    'Let's translate the Coord-System first, according to the TopLeft-Margin-Corner, to work with our Drawings relative to that Point
    CC.TranslateDrawings LeftMarginPoints, TopMarginPoints
    
    'and for completeness, let's calculate new relative Width- and Height-Values, so that we are able to use
    'correct "margin-reduced" PageSize-Dimensions for Drawing-Calls which want to stay within these Borders
    PageScaleWidth = PageWidthPoints - LeftMarginPoints - RightMarginPoints
    PageScaleHeight = PageHeightPoints - TopMarginPoints - BottomMarginPoints
    
    
    'Ok, here comes our first Render-output, which visualizes our "margin-respecting" Drawing-Area (with a simple rectangle)
    DrawPageMargins
    
    'drawings, which take place outside the Page-Margins
    DrawLogo
    DrawHeaderText
    DrawFooterText
    
    'and all the rest (within the Page-margins)
    DrawHeadline
    DrawBall
    DrawCurvedText
    
    Dim CurX As Double, CurY As Double 'just to show, how you can work "dynamically" within a document by "chaining" CurX/Y-infos
    DrawTextWithinBounds CurX, CurY   'in this routine CurY is updated (ByRef), describing the End of the Textblock on the Page...
    DrawTable CurX, CurY, TableData   '...so that this routine here can use this Coord, to know where to start drawing, but then updates these params again when finished
    DrawChart CurX, CurY, TableData   '...this way a simple "chaining" is achieved
    
  CC.Restore
  
  Set CC = Nothing 'reset our internal CC-Variable to nothing here, since we're done with it
End Sub

Private Sub DrawPageMargins()
  CC.Save
    CC.Rectangle 0, 0, PageScaleWidth, PageScaleHeight
      CC.SetLineWidth 1
      CC.SetLineCap CAIRO_LINE_CAP_ROUND
      CC.SetSourceColor &HC0C0C0
      
      'at this occassion, we introduce the Dashed-Stroking-Definitions cairo has to offer:
      'the first param is the Offset, which the Dash-Sequence will start on the Path (usually at Zero)
      'and in the following Param-Array you can define a repeatedly iterated On/Off-sequence -
      'which defines, for how many units the used Pen is "set down" or "lifted off" again...
      'the '4' in the Param-List below describes the "Lift-off" parts of the dash-sequence
      CC.SetDashes 0, 0.5, 4, 6, 4
    CC.Stroke
  CC.Restore
End Sub


Private Sub DrawLogo()
  If Not Cairo.ImageList.Exists("The Logo") Then 'in case it does not exist yet, let's ensure we add it to our global ImageList
    Cairo.ImageList.AddImage "The Logo", App.Path & "\_oxygen-icons format-fill-color.svgz", 600, 600, True
  End If
  
  CC.Save
    'for drawings, which need to be done outside of our "Page-Client-Area" (defined by the Page-Margins) -
    'we just temporarily switch back the Translation-Offset, and calculate the Offsets from absolute (full-page-)coords then
    CC.TranslateDrawings -LeftMarginPoints, -TopMarginPoints
    
    CC.RenderSurfaceContent "The Logo", PageWidthPoints - 65, 15, 45, 45
  CC.Restore
End Sub

Private Sub DrawHeaderText()
  CC.Save
    CC.TranslateDrawings -LeftMarginPoints, -TopMarginPoints
    CC.SelectFont "Arial", 8, &HA0A0A0
    
    CC.TextOut 30, 15, "A left-aligned Header-Text, placed outside the Page-Margins"
  CC.Restore
End Sub

Private Sub DrawFooterText()
  CC.Save
    CC.TranslateDrawings -LeftMarginPoints, -TopMarginPoints
    CC.SelectFont "Arial", 8, &HA0A0A0
    
    CC.DrawText PageWidthPoints - 430, PageHeightPoints - 25, 400, 40, "A right-aligned Footer-Text, placed outside the Page-Margins", True, vbRightJustify
  CC.Restore
End Sub

Private Sub DrawHeadline()
  CC.SelectFont "Times New Roman", 13, &H88, True, True
  CC.DrawText 0, 5, PageScaleWidth, 40, "A centered HeadLine, placed within the Page-Margins", True, vbCenter
End Sub

Private Sub DrawCurvedText()
Dim i&, PolyArr(0 To 11) As Double
Dim CurvePath As cCairoPath, TextPath As cCairoPath
  Rnd -9
  For i = 0 To UBound(PolyArr) Step 2
    PolyArr(i) = 49 + i * 28 'define the X-coord of the PolyPoint
    PolyArr(i + 1) = 31 + Rnd * 150 'and the Y-coord of the PolyPoint as well
  Next i
  
  CC.Save
    CC.Polygon PolyArr, False, splNormal, True
      Set CurvePath = CC.CopyPath(True, 0.02) '...copy the path from the CC...
    CC.ClearPath '...and clear the Path from the CC
    
    CC.SelectFont "Times New Roman", 11
    CC.TextOut 0, -3, " ... Hello cairo-PDF-rendering ... OMG - I'm so curvy...", True, 1, True  '<- note the last TextOut-Param
      Set TextPath = CC.CopyPath()
    CC.ClearPath
    
    'now the output of the pre-saved BSpline-Curve (without performing any changes on the internally stored Path-Coords)
    CC.AppendPath CurvePath 'here the Path is back again on the Cairo-Context
      CC.SetSourceColor vbYellow 'and as usual we prepare some color-settings...
      CC.SetLineWidth 2 '... and the current Line-Width-info...
    CC.Stroke 'and finally the Stroke, which draws the Line-Color along the path
    
    TextPath.ProjectPathData_Using CurvePath 'this call recalculates the Path-Coords of our pre-saved Text-Path
    CC.AppendPath TextPath
      CC.SetSourceColor &H202020
    CC.Fill
  CC.Restore
End Sub

Private Sub DrawBall()
Dim Pat As cCairoPattern, Radius As Double
  Radius = 15
  CC.Save
    CC.TranslateDrawings 35, 90 'this defines, where the center of the Ball is placed on the Page
    CC.RotateDrawingsDeg -30 'adjust the "light-angle"
    
    'first the "glowing-main-corpus"
    CC.Save 'we wrap this code-block in an additional Save/Restore, since we want to use additional transforms
      CC.RotateDrawingsDeg 33 'here we de-rotate, since the  glowing-effect should be more or less vertically
      CC.Arc 0, 0, Radius
        Set Pat = Cairo.CreateRadialPattern(0, -Radius * 0.16, Radius * 1.66, _
                                            0, Radius * 0.61, 0)
        Pat.AddGaussianStops_TwoColors RGB(0, 0, 99), RGB(144, 220, 255), 1, 1, , 10
      CC.Fill , Pat
    CC.Restore
    
    'now the shine
    CC.Save 'we wrap this code-block in an additional Save/Restore, since we want to use additional transforms
      CC.TranslateDrawings 0, -Radius * 0.5 'as said, now we center the whole thing half a radius-distance upwards first
  
      CC.ScaleDrawings 0.8, 0.55 'and as in 1.) we apply an extra-shrinking of the ellipse in y-direction, but other than in 1.) we use scales below 1, since we want to draw it smaller than the main-ellipse
      
      CC.Arc 0, 0, Radius '<-now as always, the same Path-Definition-Call is possible, due to our "extra-transformations" above
        Set Pat = Cairo.CreateRadialPattern(0, 0, Radius, 0, -Radius * 0.25, 0) 'the center of the shine is slightly shifted upwards (over the 5th paramter)
        Pat.AddGaussianStops_TwoColors RGB(222, 255, 200), vbWhite, 0, 0.95
      CC.Fill , Pat
    CC.Restore
  CC.Restore
End Sub

Private Sub DrawTextWithinBounds(CurX As Double, CurY As Double)
Dim S As String, XOffs As Double, YOffs As Double, InnerSpace As Double, TextRowCount As Long
  
  XOffs = 20
  YOffs = 155
    CC.SelectFont "Arial", 8, , True
    CC.TextOut XOffs, YOffs, "Ok, now to somewhat more ""serious"" stuff!"
  YOffs = YOffs + CC.GetFontHeight
  
  CC.SelectFont "Arial", 8
  S = "This entire Textblock was drawn with the wrappers DrawText-Call, which allows - " & _
      "similar to the wellknown Windows GDI-Call - a precalculation of the needed Height for all " & _
      "the automatically created linebreaks due to the performed wordwrapping..." & vbCrLf & _
      "The thin rectangle around this text was drawn *after* the DrawText-Call took place!"
  InnerSpace = 2
  TextRowCount = CC.DrawText(XOffs, YOffs, PageScaleWidth - 2 * XOffs, 0, _
                             S, False, vbLeftJustify, InnerSpace) '<-- note the Zero-dY-Param, to force the DrawText-Call to calculate the needed RowCount beforehand
  CC.DrawText XOffs, YOffs, _
              PageScaleWidth - 2 * XOffs, TextRowCount * CC.GetFontHeight + 2 * InnerSpace, _
              S, False, vbLeftJustify, InnerSpace
  
  CC.Rectangle XOffs, YOffs, PageScaleWidth - 2 * XOffs, TextRowCount * CC.GetFontHeight + InnerSpace
    CC.SetLineWidth 0.5
    CC.SetSourceColor &HC0C0C0
  CC.Stroke
  
  'return with the CurrentY on that page
  CurY = YOffs + TextRowCount * CC.GetFontHeight + InnerSpace
End Sub


Private Sub DrawTable(CurX As Double, CurY As Double, TableData)
Dim S As String, InnerSpace As Double
Dim i&, j&, X As Double, Y As Double, DX As Double, DY As Double

  CurX = CurX + 20
  CurY = CurY + 25

  CC.Save
    
    CC.TranslateDrawings CurX, CurY
    
    CC.SelectFont "Arial", 8, , True
    CC.TextOut 0, 0, "A small ""Quarterly Sales Table"""
    
    CC.TranslateDrawings 0, CC.GetFontHeight + 2
    
    CC.SelectFont "Arial", 8
    DX = 50: DY = 14
    InnerSpace = 2
    For i = 0 To UBound(TableData, 1)
      For j = 0 To UBound(TableData, 2)
        If i = 0 Then 'Header-Line
          CC.DrawTextCell X, Y, DX, DY, CStr(TableData(i, j)), True, vbCenter, 1, &HE0E0E0, 0.15
        ElseIf j = 0 Then 'the Years-Column
          CC.DrawTextCell X, Y, DX, DY, CStr(TableData(i, j)), True, vbCenter, 1, Cairo.ShadeColor(QuarterColors(j), 13)
        Else 'the Quarter-Values
          CC.DrawTextCell X, Y, DX, DY, Format$(TableData(i, j), "0.00"), True, vbRightJustify, 1, Cairo.ShadeColor(QuarterColors(j), 13)
        End If
        X = X + DX
      Next j
      X = 0: Y = Y + DY
    Next i
    
  CC.Restore
  
  If CC.Surface.Width > CC.Surface.Height Then 'landscape
    CurX = CurX + (UBound(TableData, 2) + 1) * DX + 50
  Else
    CurY = CurY + (UBound(TableData, 1) + 1) * DY + CC.GetFontHeight + 20
  End If
End Sub


'all the rest of the functions in this module is used for chart-construction,
'beginning with the main-chart-routine below (which triggers all the others that follow)
Private Sub DrawChart(CurX As Double, CurY As Double, TableData)
Dim DX As Double, DY As Double
  DX = 305: DY = 195 'define the Charts "Outer-Size" in Points
  
  CC.Save
    CC.TranslateDrawings CurX, CurY
    
    CC.SelectFont "Arial", 8, , True
    CC.TextOut 0, 0, "And a small Chart, to visualize the Table-Data"
    
    CC.TranslateDrawings 0, CC.GetFontHeight + 2
     
    DrawChartBackground DX, DY
    CC.TranslateDrawings 5, 0 'this shifts the inner contents of the Chart a bit to the right
    
    DrawAxisY DX, DY, 0.25, 18
    DrawAxisX DX, DY, 0.25, 18, TableData 'in this routine here, only the Year-Header-Info from our TableData is used
    
    'now to the rendering of the real Data-Values of our Table-Data-Array
    Dim Offs As Double, YScaleToPoints As Double
    Offs = 18
    YScaleToPoints = (DY - 3 * Offs) / 60
    

    'now the stacke-up rendering of the Quarter-Data in the different years
    DrawDataCylinders DX, DY, Offs, YScaleToPoints, TableData
    
    'this draws the "intermediate X-Axis-Panels" (at Y-Intersections 15, 30, 45, 60), rendered with a low Alpha
    DrawYIntersections DX, DY, Offs, YScaleToPoints
    
  CC.Restore
End Sub

Private Sub DrawChartBackground(DX As Double, DY As Double)
Dim i&
  'first the DropShadow
  CC.SetLineCap CAIRO_LINE_CAP_ROUND
  For i = 8 To 1 Step -1
    CC.RoundedRect 2.5, 2.5, DX - 2.5, DY - 2.5, 0.5
      CC.SetLineWidth i
      CC.SetSourceColor &HE0E0E0, 1, i * 0.22
    CC.Stroke
  Next i
  
  'now the (gradient-filled) Chart-MainRectangle
  CC.Rectangle 0, 0, DX, DY
    CC.SetLineWidth 0.5
    With Cairo.CreateLinearPattern(0, 0, DX, DX)
      .AddColorStop 0, vbWhite
      .AddColorStop 1, &HE8E8E8
      CC.Fill True, .This
    End With
    CC.SetSourceColor vbBlack
  CC.Stroke
End Sub

Private Sub DrawAxisY(DX As Double, DY As Double, Alpha As Double, Z As Double)
Dim i&, X As Double, Y As Double
  X = Z: Y = 2 * Z 'define the start-point of this horizontal Axis
  
  CC.MoveTo X, DY - Y / 2
    CC.LineTo X, Y
    CC.LineTo X + Z, Y - Z
    CC.LineTo X + Z, DY - Y / 2 - Z
    With Cairo.CreateLinearPattern(X, Y, X + Z, Y)
      .AddColorStop 0, &HFFFFAA, Alpha + 0.1
      .AddColorStop 1, &HFFFFAA, Alpha
      CC.Fill True, .This
    End With
    CC.SetSourceColor vbBlack, Alpha
  CC.Stroke
  
  CC.Rectangle X + Z, Y - Z, DX - 3 * Z, DY - 3 * Z
    CC.SetSourceColor &HFFFFAA, Alpha
  CC.Fill
End Sub

Private Sub DrawAxisX(DX As Double, DY As Double, Alpha As Double, Z As Double, TableData)
Dim i&, X As Double, Y As Double, YearDX As Double
  X = Z: Y = DY - Z 'define the start-point of this horizontal Axis
  
  'this is resuing a small routine, to render the "X-Axis-Bottom-Panel"
  DrawAxisPanelX X, Y, Z, DX, &HFFFFAA, Alpha, "0"

  CC.SelectFont "Arial", 6, , True
  CC.SetSourceColor vbBlack, 1
  
  YearDX = (DX - 3 * Z) / UBound(TableData, 1) 'this is the range-width in X-Direction, which is covered by one Year
  For i = 1 To UBound(TableData, 1)
    CC.DrawText X - 1, Y + 2, YearDX, 20, CStr(TableData(i, 0)), True, vbCenter
    CC.DrawLine X, Y, X - 1.5, Y + 1.5
    CC.Stroke
    X = X + YearDX
  Next i
  CC.DrawLine X, Y, X - 1.5, Y + 1.5
  CC.Stroke
End Sub

Private Sub DrawAxisPanelX(X As Double, Y As Double, ByVal Z As Double, DX As Double, Color As Long, Alpha As Double, Optional YText As String, Optional InBetween As Boolean)
    CC.MoveTo X + Z, Y - Z
      CC.LineTo X, Y
      CC.LineTo DX - 2 * X, Y
      CC.LineTo DX - 2 * X + Z, Y - Z
    If Not InBetween Then CC.ClosePath
      With Cairo.CreateLinearPattern(X, Y, X, Y - Z)
        .AddColorStop 0, Color, Alpha + 0.05
        .AddColorStop 1, Color, Alpha

        CC.Fill True, .This
      End With
      CC.SetSourceColor vbBlack, Alpha
    CC.Stroke
    
    If Len(YText) > 0 Then
      CC.DrawLine X, Y, X - 2.5, Y
         CC.SetSourceColor vbBlack, 1
      CC.Stroke
      
      CC.SelectFont "Arial", 6, , True
      CC.DrawText X - 100, Y - 10, 100, 20, YText, True, vbRightJustify, 2, True
    End If
End Sub

Private Sub DrawYIntersections(DX As Double, DY As Double, Offs As Double, YScaleToPoints)
Dim i&, YAxisValue As Double
    For i = 1 To 4
      YAxisValue = i * 15 'this variable gets incremented in DataValue-Units (which in this example cannot go over 60 = 4 stacked QuarterValues with a max of 15 each)
      
      DrawAxisPanelX Offs, (DY - Offs) - (YAxisValue * YScaleToPoints), _
                     Offs, DX, &HFF4444, 0.02, CStr(YAxisValue), True
    Next i
End Sub

Private Sub DrawDataCylinders(DX As Double, DY As Double, Offs As Double, YScaleToPoints As Double, TableData)
Dim i&, j&, XOffs As Double, YOffs As Double, YearDX As Double, CylinderWidth As Double
  
  CC.Save
  
    CC.TranslateDrawings Offs - 1, DY - Offs + 1
    CC.ScaleDrawings 1, -YScaleToPoints
    
    CylinderWidth = 15
    YearDX = (DX - 3 * Offs) / UBound(TableData, 1) 'this is the range-width in X-Direction, which is covered by one Year

    XOffs = (YearDX + Offs) / 2
    For i = 1 To UBound(TableData, 1)
      YOffs = (Offs / 2) / YScaleToPoints
      For j = 1 To UBound(TableData, 2)
        DrawCylinder XOffs, YOffs, CylinderWidth, TableData(i, j), QuarterColors(j)
        YOffs = YOffs + TableData(i, j)
      Next j
      XOffs = XOffs + YearDX
    Next i
  
  CC.Restore
End Sub

Private Sub DrawCylinder(X As Double, Y As Double, Width As Double, ByVal Height As Double, Color As Long)
Dim RadiusX As Double, RadiusY As Double, ColorHighLight As Long
  RadiusX = Width / 2
  RadiusY = RadiusX / 6
  ColorHighLight = Cairo.ShadeColor(Color, 10)
  
  CC.Save
    CC.MoveTo X - RadiusX, Y
      CC.EllipticArcTo RadiusX, RadiusY, 0, False, True, X + RadiusX, Y
      CC.LineTo X + RadiusX, Y + Height
      CC.EllipticArcTo RadiusX, RadiusY, 0, False, False, X - RadiusX, Y + Height
      CC.LineTo X - RadiusX, Y
    CC.ClosePath

    With Cairo.CreateLinearPattern(X - RadiusX, Y, X + RadiusX, Y)
      .AddGaussianStops_TwoColors ColorHighLight, Color, 0.8, 0.8, gpNormal, 4
      CC.Fill , .This
    End With
    
    CC.MoveTo X + RadiusX, Y + Height
      CC.EllipticArcTo RadiusX, RadiusY, 0, False, True, X - RadiusX, Y + Height
      CC.EllipticArcTo RadiusX, RadiusY, 0, False, True, X + RadiusX, Y + Height
    CC.ClosePath
      CC.SetSourceColor Color, 0.5
    CC.Fill
  CC.Restore
End Sub
