Beispiel #1
0
        protected void SetStrokeDashEffect(Paint strokePaint)
        {
            if (StrokeDashArray != null && StrokeDashArray.Count > 0)
            {
                // If only value specified in the dash array, copy and add it
                if (StrokeDashArray.Count == 1)
                {
                    StrokeDashArray.Add(StrokeDashArray[0]);
                }

                // Make sure the dash array has a postive number of items, Android cannot have an odd number
                // of items in the array (in such a case we skip the dash effect and log the error)
                //		https://developer.android.com/reference/android/graphics/DashPathEffect.html
                //		**  The intervals array must contain an even number of entries (>=2), with
                //			the even indices specifying the "on" intervals, and the odd indices
                //			specifying the "off" intervals.  **
                if (StrokeDashArray.Count % 2 == 0)
                {
                    var pattern = StrokeDashArray.Select(d => (float)d).ToArray();
                    strokePaint.SetPathEffect(new DashPathEffect(pattern, 0));
                }
                else
                {
                    this.Log().ErrorIfEnabled(() => "StrokeDashArray containing an odd number of values is not supported on Android.");
                }
            }
        }
Beispiel #2
0
        private CALayer CreateLayer(CGPath path)
        {
            var pathLayer = new CAShapeLayer()
            {
                Path        = path,
                StrokeColor = (Stroke as SolidColorBrush)?.ColorWithOpacity ?? Colors.Transparent,
                LineWidth   = (nfloat)ActualStrokeThickness,
            };

            switch (Fill)
            {
            case SolidColorBrush colorFill:
                pathLayer.FillColor = colorFill.ColorWithOpacity;
                break;

            case ImageBrush imageFill when TryCreateImageBrushLayers(imageFill, GetFillMask(path), out var imageLayer):
                pathLayer.FillColor = Colors.Transparent;

                pathLayer.AddSublayer(imageLayer);
                break;

            case LinearGradientBrush gradientFill:
                var gradientLayer = gradientFill.GetLayer(Frame.Size);
                gradientLayer.Frame         = Bounds;
                gradientLayer.Mask          = GetFillMask(path);
                gradientLayer.MasksToBounds = true;

                pathLayer.FillColor = Colors.Transparent;
                pathLayer.AddSublayer(gradientLayer);
                break;

            case null:
                pathLayer.FillColor = Colors.Transparent;
                break;

            default:
                Application.Current.RaiseRecoverableUnhandledException(new NotSupportedException($"The brush {Fill} is not supported as Fill for a {this} on this platform."));
                pathLayer.FillColor = Colors.Transparent;
                break;
            }

            if (StrokeDashArray != null)
            {
                var pattern = StrokeDashArray.Select(d => (global::Foundation.NSNumber)d).ToArray();

                pathLayer.LineDashPhase   = 0;               // Starting position of the pattern
                pathLayer.LineDashPattern = pattern;
            }

            return(pathLayer);

            CAShapeLayer GetFillMask(CGPath mask)
            => new CAShapeLayer
            {
                Path  = mask,
                Frame = Bounds,
                // We only use the fill color to create the mask area
                FillColor = _Color.White.CGColor,
            };
        }
Beispiel #3
0
 public AnimatedBorder()
 {
     borderAnimation                = new DoubleAnimation();
     borderAnimation.From           = 0.0;
     borderAnimation.To             = StrokeDashArray.Single() * 2;
     borderAnimation.Duration       = new Duration(TimeSpan.FromSeconds(1.0));
     borderAnimation.AutoReverse    = false;
     borderAnimation.RepeatBehavior = RepeatBehavior.Forever;
 }
Beispiel #4
0
        public static DoubleCollection ToDashArray(this StrokeDashArray value)
        {
            var result = new DoubleCollection(value.Data.Length);

            foreach (var val in value.Data)
            {
                result.Add(val);
            }

            return(result);
        }
Beispiel #5
0
        private string DashArrayToString()
        {
            string dashArray = null;

            if (StrokeDashArray != null && StrokeDashArray.Count > 0)
            {
                dashArray = string.Join(" ",
                                        StrokeDashArray.ConvertAll(v => v.ToString(CultureInfo.InvariantCulture)).ToArray());
            }
            return(dashArray);
        }
Beispiel #6
0
        partial void OnStrokeDashArrayUpdatedPartial()
        {
            var svgElement = GetMainSvgElement();

            if (StrokeDashArray == null)
            {
                svgElement.ResetStyle("stroke-dasharray");
            }
            else
            {
                var str = string.Join(",", StrokeDashArray.Select(d => $"{d}px"));
                svgElement.SetStyle("stroke-dasharray", str);
            }
        }
Beispiel #7
0
        protected override void AssignDefaultValuesToSceneNode(SceneNode2D node)
        {
            base.AssignDefaultValuesToSceneNode(node);
            var c = node as ShapeNode2D;

            c.StrokeDashArray    = StrokeDashArray == null ? new float[0] : StrokeDashArray.Select(x => (float)x).ToArray();
            c.StrokeDashCap      = StrokeDashCap.ToD2DCapStyle();
            c.StrokeDashOffset   = (float)StrokeDashOffset;
            c.StrokeEndLineCap   = StrokeEndLineCap.ToD2DCapStyle();
            c.StrokeLineJoin     = StrokeLineJoin.ToD2DLineJoin();
            c.StrokeMiterLimit   = (float)StrokeMiterLimit;
            c.StrokeStartLineCap = StrokeStartLineCap.ToD2DCapStyle();
            c.StrokeThickness    = (float)StrokeThickness;
            c.StrokeDashStyle    = DashStyle.ToD2DDashStyle();
        }
Beispiel #8
0
        private CALayer CreateLayer()
        {
            var path = this.GetPath();

            if (path == null)
            {
                return(null);
            }

            var pathBounds = path.PathBoundingBox;

            if (
                nfloat.IsInfinity(pathBounds.Left) ||
                nfloat.IsInfinity(pathBounds.Left)
                )
            {
                if (this.Log().IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
                {
                    this.Log().Debug($"Ignoring path with invalid bounds {pathBounds}");
                }

                return(null);
            }

            var transform = CGAffineTransform.MakeIdentity();

            var scaleX = _scaleX;
            var scaleY = _scaleY;

            switch (this.Stretch)
            {
            case Stretch.Fill:
            case Stretch.None:
                break;

            case Stretch.Uniform:
                scaleX = (nfloat)Math.Min(_scaleX, _scaleY);
                scaleY = scaleX;
                break;

            case Stretch.UniformToFill:
                scaleX = (nfloat)Math.Max(_scaleX, _scaleY);
                scaleY = scaleX;
                break;
            }

            transform = CGAffineTransform.MakeScale(scaleX, scaleY);

            if (Stretch != Stretch.None)
            {
                // When stretching, we can't use 0,0 as the origin, but must instead
                // use the path's bounds.
                transform.Translate(-pathBounds.Left * scaleX, -pathBounds.Top * scaleY);
            }

            if (!ShouldPreserveOrigin)
            {
                //We need to translate the shape to take in account the stroke thickness
                transform.Translate((nfloat)ActualStrokeThickness * 0.5f, (nfloat)ActualStrokeThickness * 0.5f);
            }

            if (nfloat.IsNaN(transform.x0) || nfloat.IsNaN(transform.y0) ||
                nfloat.IsNaN(transform.xx) || nfloat.IsNaN(transform.yy) ||
                nfloat.IsNaN(transform.xy) || nfloat.IsNaN(transform.yx)
                )
            {
                //transformedPath creation will crash natively if the transform contains NaNs
                throw new InvalidOperationException($"transform {transform} contains NaN values, transformation will fail.");
            }

            var colorFill    = Fill as SolidColorBrush ?? SolidColorBrushHelper.Transparent;
            var imageFill    = Fill as ImageBrush;
            var gradientFill = Fill as LinearGradientBrush;
            var stroke       = this.Stroke as SolidColorBrush ?? SolidColorBrushHelper.Transparent;

            var transformedPath = new CGPath(path, transform);
            var layer           = new CAShapeLayer()
            {
                Path        = transformedPath,
                StrokeColor = stroke.ColorWithOpacity,
                LineWidth   = (nfloat)ActualStrokeThickness,
            };

            if (colorFill != null)
            {
                layer.FillColor = colorFill.ColorWithOpacity;
            }

            if (imageFill != null)
            {
                var fillMask = new CAShapeLayer()
                {
                    Path  = path,
                    Frame = Bounds,
                    // We only use the fill color to create the mask area
                    FillColor = _Color.White.CGColor,
                };

                CreateImageBrushLayers(
                    layer,
                    imageFill,
                    fillMask
                    );
            }
            else if (gradientFill != null)
            {
                var fillMask = new CAShapeLayer()
                {
                    Path  = transformedPath,
                    Frame = Bounds,
                    // We only use the fill color to create the mask area
                    FillColor = _Color.White.CGColor,
                };

                var gradientLayer = gradientFill.GetLayer(Frame.Size);
                gradientLayer.Frame         = Bounds;
                gradientLayer.Mask          = fillMask;
                gradientLayer.MasksToBounds = true;
                layer.AddSublayer(gradientLayer);
            }

            if (StrokeDashArray != null)
            {
                var pattern = StrokeDashArray.Select(d => (global::Foundation.NSNumber)d).ToArray();

                layer.LineDashPhase   = 0;               // Starting position of the pattern
                layer.LineDashPattern = pattern;
            }

            return(layer);
        }
Beispiel #9
0
        /// <summary>
        /// Renders the stroke of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/>
        /// </summary>
        /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
        protected internal virtual bool RenderStroke(ISvgRenderer renderer)
        {
            if (Stroke != null && Stroke != SvgPaintServer.None && StrokeWidth > 0f)
            {
                var strokeWidth = StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this);
                using (var brush = Stroke.GetBrush(this, renderer, Math.Min(Math.Max(StrokeOpacity, 0f), 1f), true))
                {
                    if (brush != null)
                    {
                        var path   = Path(renderer);
                        var bounds = path.GetBounds();
                        if (path.PointCount < 1)
                        {
                            return(false);
                        }
                        if (bounds.Width <= 0f && bounds.Height <= 0f)
                        {
                            switch (StrokeLineCap)
                            {
                            case SvgStrokeLineCap.Round:
                                using (var capPath = new GraphicsPath())
                                {
                                    capPath.AddEllipse(path.PathPoints[0].X - strokeWidth / 2f, path.PathPoints[0].Y - strokeWidth / 2f, strokeWidth, strokeWidth);
                                    renderer.FillPath(brush, capPath);
                                }
                                break;

                            case SvgStrokeLineCap.Square:
                                using (var capPath = new GraphicsPath())
                                {
                                    capPath.AddRectangle(new RectangleF(path.PathPoints[0].X - strokeWidth / 2f, path.PathPoints[0].Y - strokeWidth / 2f, strokeWidth, strokeWidth));
                                    renderer.FillPath(brush, capPath);
                                }
                                break;
                            }
                        }
                        else
                        {
                            using (var pen = new Pen(brush, strokeWidth))
                            {
                                if (StrokeDashArray != null && StrokeDashArray.Count > 0)
                                {
                                    strokeWidth = strokeWidth <= 0 ? 1f : strokeWidth;
                                    if (StrokeDashArray.Count % 2 != 0)
                                    {
                                        // handle odd dash arrays by repeating them once
                                        StrokeDashArray.AddRange(StrokeDashArray);
                                    }

                                    var dashOffset = StrokeDashOffset;

                                    /* divide by stroke width - GDI uses stroke width as unit.*/
                                    var dashPattern = StrokeDashArray.Select(u => ((u.ToDeviceValue(renderer, UnitRenderingType.Other, this) <= 0f) ? 1f :
                                                                                   u.ToDeviceValue(renderer, UnitRenderingType.Other, this)) / strokeWidth).ToArray();
                                    var length = dashPattern.Length;

                                    if (StrokeLineCap == SvgStrokeLineCap.Round)
                                    {
                                        // to handle round caps, we have to adapt the dash pattern
                                        // by increasing the dash length by the stroke width - GDI draws the rounded
                                        // edge inside the dash line, SVG draws it outside the line
                                        var pattern = new float[length];
                                        var offset  = 1; // the values are already normalized to dash width
                                        for (var i = 0; i < length; i++)
                                        {
                                            pattern[i] = dashPattern[i] + offset;
                                            if (pattern[i] <= 0f)
                                            {
                                                // overlapping caps - remove the gap for simplicity, see #508
                                                if (i < length - 1)
                                                {
                                                    // add the next dash segment to the current one
                                                    dashPattern[i - 1] += dashPattern[i] + dashPattern[i + 1];
                                                    length             -= 2;
                                                    for (var k = i; k < length; k++)
                                                    {
                                                        dashPattern[k] = dashPattern[k + 2];
                                                    }

                                                    // and handle the combined segment again
                                                    i -= 2;
                                                }
                                                else if (i > 2)
                                                {
                                                    // add the last dash segment to the first one
                                                    // this will change the start point, so adapt the offset
                                                    var dashLength = dashPattern[i - 1] + dashPattern[i];
                                                    pattern[0] += dashLength;
                                                    length     -= 2;
                                                    dashOffset += dashLength * strokeWidth;
                                                }
                                                else
                                                {
                                                    // we have only one dash with the gap too small -
                                                    // do not use dash at all
                                                    length = 0;
                                                    break;
                                                }
                                            }
                                            offset *= -1; // increase dash length, decrease spaces
                                        }
                                        if (length > 0)
                                        {
                                            if (length < dashPattern.Length)
                                            {
                                                Array.Resize(ref pattern, length);
                                            }
                                            dashPattern = pattern;
                                            pen.DashCap = DashCap.Round;
                                        }
                                    }

                                    if (length > 0)
                                    {
                                        pen.DashPattern = dashPattern;

                                        if (dashOffset != 0f)
                                        {
                                            pen.DashOffset = ((dashOffset.ToDeviceValue(renderer, UnitRenderingType.Other, this) <= 0f) ? 1f :
                                                              dashOffset.ToDeviceValue(renderer, UnitRenderingType.Other, this)) / strokeWidth;
                                        }
                                    }
                                }
                                switch (StrokeLineJoin)
                                {
                                case SvgStrokeLineJoin.Bevel:
                                    pen.LineJoin = LineJoin.Bevel;
                                    break;

                                case SvgStrokeLineJoin.Round:
                                    pen.LineJoin = LineJoin.Round;
                                    break;

                                default:
                                    pen.LineJoin = LineJoin.Miter;
                                    break;
                                }
                                pen.MiterLimit = StrokeMiterLimit;
                                switch (StrokeLineCap)
                                {
                                case SvgStrokeLineCap.Round:
                                    pen.StartCap = LineCap.Round;
                                    pen.EndCap   = LineCap.Round;
                                    break;

                                case SvgStrokeLineCap.Square:
                                    pen.StartCap = LineCap.Square;
                                    pen.EndCap   = LineCap.Square;
                                    break;
                                }

                                renderer.DrawPath(pen, path);

                                return(true);
                            }
                        }
                    }
                }
            }

            return(false);
        }
Beispiel #10
0
 public static nfloat[] ToLineDash(this StrokeDashArray value)
 {
     return(value.Data.Select(v => (nfloat)v).ToArray());
 }