/// <summary>Calculate major anchor points</summary>
        /// <param name="info"></param>
        /// <param name="canvas"></param>
        private void CalcFrame(SKImageInfo info, SKCanvas canvas)
        {
            // the vertical center line
            float vertCenter = Convert.ToSingle(info.Width / 2.0);

            // length to use for outer square - shortest of vertical vs horizontal
            int edgeLen = new int[] { info.Width, info.Height }.Min();

            // 1/2 the distance
            float halfEdge = edgeLen / 2.0F;

            // set the arc line thickness
            this.ArcLineWidth = (edgeLen / 20.0F) * this.ArcLineThickness;

            // inset border of the graph frame based on the arc line width
            float border = this.ArcLineWidth * BorderFactor;

            // center of the drawing area
            this.Origin = new SKPoint(vertCenter, info.Height / 2.0F);

            // the square to draw the ring inside of
            this.OuterRect = new SKRect(this.Origin.X - halfEdge + border, this.Origin.Y - halfEdge + border, this.Origin.X + halfEdge - 1 - border, this.Origin.Y + halfEdge - 1 - border);

            // the square inscribed inside the circle
            this.InscribedRect = QuickCalc.InscribeSquare(this.Origin, halfEdge - border);

            // calculate the font for the labels, the same width is used for both which could cause issues
            this.PrimeLabelMetrics = this.CalculateLabels(this.PrimeLabel, this.PrimeLabelAttributes, this.PrimeLabelFontScale);
            this.SubLabelMetrics   = this.CalculateLabels(this.SubLabel, this.SubLabelAttributes, this.SubLabelFontScale);
        }
        /// <summary>Calculates the needed font size for the X labels</summary>
        /// <returns></returns>
        /// <remarks>Right now only calculates the prime label text box</remarks>
        internal LabelMetrics CalculateLabels(string label, FontAttributes attr, float fontScale = 100.0F)
        {
            // if the label is empty
            if (String.IsNullOrEmpty(label))
            {
                return(LabelMetrics.Default());
            }

            if (fontScale < 1)
            {
                fontScale = 1.0F;
            }

            float maxHeight = 0.0F;
            float fontSize  = 250.0F;
            float desc      = 0.0F;

            // set the max label width to some default
            // use the inscribed square width
            //this.MaxPrimeLabelWidth = this.OuterRect.Width - (this.ArcLineWidth * BorderFactor * 2.0F);
            this.MaxPrimeLabelWidth = this.InscribedRect.Width;

            // determine the ideal font
            using (SKPaint textPaint = new SKPaint())
            {
                // init the font with all its properties
                this.SetFont(textPaint, fontSize, attr);

                // find the min font size for the width
                LabelMetrics widthMet = QuickCalc.CalcSizeForWidth(textPaint, this.MaxPrimeLabelWidth, label);
                textPaint.TextSize = widthMet.FontSize;

                // get the NEW font metrics which includes max height
                SKFontMetrics metrics;
                textPaint.GetFontMetrics(out metrics);

                // set the calculated values thus far
                desc      = metrics.Descent;
                maxHeight = metrics.Descent - metrics.Ascent;

                // for fun calculate the width of the circle at the top of the text
                //float chord = Convert.ToSingle( QuickCalc.CalcChord( this.OuterRect.Width / 2.0F, maxHeight / 2.0F ) );
                //this.MaxPrimeLabelWidth = chord - ( this.ArcLineWidth * BorderFactor * 2.0F );

                // now check its not too tall
                if (maxHeight > this.InscribedRect.Height)
                {
                    // scale the font further based on height
                    float scale = this.InscribedRect.Height / maxHeight;
                    textPaint.TextSize = textPaint.TextSize * scale;
                }

                // now scale by the relative user set scale
                if (fontScale < 100.0F)
                {
                    textPaint.TextSize = textPaint.TextSize * (fontScale / 100.0F);
                }

                // remeasure after user set scaling
                textPaint.GetFontMetrics(out metrics);
                fontSize  = textPaint.TextSize;
                desc      = metrics.Descent;
                maxHeight = metrics.Descent - metrics.Ascent;
            }

            return(new LabelMetrics(fontSize, maxHeight, desc));
        }
        /// <summary>Draw a pie meter</summary>
        private void PaintPie(SKImageInfo info, SKCanvas canvas, bool handles = false)
        {
            float arcWidth = (info.Width / 15.0F) * 1.0F;

            // always draw a background ring
            using (SKPath path = new SKPath())
            {
                path.AddArc(this.OuterRect, 0, 360);
                canvas.DrawPath(path, new SKPaint()
                {
                    Color = _disabled, StrokeWidth = this.ArcLineWidth, Style = SKPaintStyle.Stroke, IsAntialias = true
                });
            }

            // calc the sum
            float total = this.Value1 + this.Value2;

            // bail if both are 0
            if (total <= 0.0)
            {
                return;
            }

            // calc the sweep angle
            float sweep = (this.Value1 / total) * 360.0F;

            // calc the angles, 0 is to the right, start from overhead
            float rewardSweep     = (sweep > 360.0) ? 360.0F : sweep;
            float bonusStartAngle = QuickCalc.Revolution(270.0F + rewardSweep);

            // draw arc 1
            using (SKPath path = new SKPath())
            {
                using (SKPaint arcPaint = new SKPaint {
                    Style = SKPaintStyle.Stroke, Color = this.ArcColor1.ToSKColor(), StrokeWidth = this.ArcLineWidth, IsAntialias = true
                })
                {
                    path.AddArc(this.OuterRect, 270, rewardSweep);
                    canvas.DrawPath(path, arcPaint);
                }
            }

            // draw arc 2
            using (SKPath path = new SKPath())
            {
                using (SKPaint arcPaint = new SKPaint {
                    Style = SKPaintStyle.Stroke, Color = this.ArcColor2.ToSKColor(), StrokeWidth = this.ArcLineWidth, IsAntialias = true
                })
                {
                    path.AddArc(this.OuterRect, bonusStartAngle, 360 - rewardSweep);
                    canvas.DrawPath(path, arcPaint);
                }
            }

            if (!handles)
            {
                return;
            }

            // draw handles
            using (SKPaint handlePaint = new SKPaint()
            {
                Color = this.ArcColor1.ToSKColor(), StrokeCap = SKStrokeCap.Round, StrokeWidth = this.ArcLineWidth * 2, IsAntialias = true
            })
            {
                canvas.DrawPoint(this.VerticalCenter, this.OuterRect.Top, handlePaint);

                // where's the other handle?
                float rad = QuickCalc.Deg2Rad(QuickCalc.Revolution(360 - rewardSweep + 90));
                Tuple <float, float> pt = QuickCalc.Transform(rad, this.OuterRect.Width / 2.0F);

                handlePaint.Color = this.ArcColor2.ToSKColor();
                canvas.DrawPoint(pt.Item1 + info.Rect.MidX, info.Rect.MidY - pt.Item2, handlePaint);
            }
        }
        /// <summary>Draw a progress meter</summary>
        /// <remarks>
        /// Value 1 is the part and Value 2 is the total possible
        /// Value 1 can exceed value 2 for mutiple revolutions
        /// </remarks>
        private void PaintProgress(SKImageInfo info, SKCanvas canvas)
        {
            // set up the gradient colors
            SKColor[] colors = new SKColor[2] {
                this.ArcColor1.ToSKColor(), this.ArcColor2.ToSKColor()
            };

            // sweep is in DEG -> normalize the sweep angle between 0 and 360
            //float sweep = QuickCalc.Revolution( ( this.Value1 / this.Value2 ) * 360.0F );
            //sweep = ( sweep > 360.0 ) ? 360.0F : sweep;
            float sweep = (this.Value1 % this.Value2 / this.Value2) * 360.0F;

            // we have to roate the drawing canvas 90 degrees CCW
            canvas.RotateDegrees(-90, info.Width / 2, info.Height / 2);

            // no value
            if (this.Value1 <= 0.0)
            {
                // draw background ring
                using (SKPath path = new SKPath())
                {
                    using (SKPaint bkgPaint = new SKPaint()
                    {
                        Color = _disabled, StrokeWidth = this.ArcLineWidth, Style = SKPaintStyle.Stroke, IsAntialias = true
                    })
                    {
                        path.AddArc(this.OuterRect, 0, 360.0F);
                        canvas.DrawPath(path, bkgPaint);
                    }
                }
            }
            // less than 1 revolution
            else if (this.Value1 < this.Value2)
            {
                // draw background ring
                using (SKPath path = new SKPath())
                {
                    using (SKPaint bkgPaint = new SKPaint()
                    {
                        Color = _disabled, StrokeWidth = this.ArcLineWidth, Style = SKPaintStyle.Stroke, IsAntialias = true
                    })
                    {
                        path.AddArc(this.OuterRect, 0, 360.0F);
                        canvas.DrawPath(path, bkgPaint);
                    }
                }

                // draw the partial arc
                using (SKPath path = new SKPath())
                {
                    using (SKPaint arcPaint = new SKPaint {
                        Style = SKPaintStyle.Stroke, Color = this.ArcColor1.ToSKColor()
                    })
                    {
                        arcPaint.StrokeWidth = this.ArcLineWidth;
                        arcPaint.StrokeCap   = SKStrokeCap.Butt;
                        arcPaint.IsAntialias = true;
                        arcPaint.Shader      = SKShader.CreateSweepGradient(this.Origin, colors, new Single[] { 0, 1 }, SKShaderTileMode.Clamp, 0, sweep);

                        // create an arc to sweep along
                        path.AddArc(this.OuterRect, 0, sweep);
                        canvas.DrawPath(path, arcPaint);
                    }
                }
            }
            else             // 1 or more revolution
            {
                // draw background ring
                using (SKPath path = new SKPath())
                {
                    using (SKPaint bkgPaint = new SKPaint()
                    {
                        Color = this.ArcColor1.ToSKColor(), StrokeWidth = this.ArcLineWidth
                    })
                    {
                        bkgPaint.Style       = SKPaintStyle.Stroke;
                        bkgPaint.IsAntialias = true;

                        path.AddArc(this.OuterRect, 0, 360.0F);
                        canvas.DrawPath(path, bkgPaint);
                    }
                }

                // rotate the canvas by the sweep angle so we always start at 0
                canvas.RotateDegrees(sweep - 180, info.Width / 2, info.Height / 2);

                // draw the partial gradiant arc
                using (SKPath path = new SKPath())
                {
                    using (SKPaint arcPaint = new SKPaint {
                        Style = SKPaintStyle.Stroke
                    })
                    {
                        arcPaint.Color       = this.ArcColor2.ToSKColor();
                        arcPaint.StrokeWidth = this.ArcLineWidth;
                        arcPaint.StrokeCap   = SKStrokeCap.Butt;
                        arcPaint.IsAntialias = true;

                        // sweep gradient uses start angle to end angle
                        arcPaint.Shader = SKShader.CreateSweepGradient(this.Origin, colors, new Single[] { 0, 1 }, SKShaderTileMode.Clamp, 0, 180);

                        // create an arc to sweep along - uses start angle and then how many degrees to rotate from start
                        path.AddArc(this.OuterRect, 0, 180);
                        canvas.DrawPath(path, arcPaint);
                    }
                }

                canvas.RotateDegrees(-(sweep - 180), info.Width / 2, info.Height / 2);
            }

            // calc pts for the trailing handle
            Tuple <float, float> pt1 = QuickCalc.Transform(QuickCalc.Deg2Rad(sweep + (this.ArcLineWidth * 0.075F)), this.OuterRect.Width / 2.0F);
            Tuple <float, float> pt2 = QuickCalc.Transform(QuickCalc.Deg2Rad(sweep), this.OuterRect.Width / 2.0F);

            // draw the trailing point with shadow
            using (SKPaint handlePaint = new SKPaint()
            {
                Color = this.BackgroundColor.ToSKColor()
            })
            {
                handlePaint.StrokeCap   = SKStrokeCap.Round;
                handlePaint.StrokeWidth = this.ArcLineWidth * 2;
                handlePaint.IsAntialias = true;

                // shadow
                canvas.DrawPoint(pt1.Item1 + info.Rect.MidX, info.Rect.MidY + pt1.Item2, handlePaint);

                // change color
                handlePaint.Color = this.ArcColor2.ToSKColor();

                // handle
                canvas.DrawPoint(pt2.Item1 + info.Rect.MidX, info.Rect.MidY + pt2.Item2, handlePaint);
            }

            // rotate it all back
            canvas.RotateDegrees(90, info.Width / 2, info.Height / 2);
        }