예제 #1
0
        static CompositionShape TranslateShapeLayerContents(
            ShapeContext context,
            IReadOnlyList <ShapeLayerContent> contents)
        {
            // The Contents of a ShapeLayer is a list of instructions for a stack machine.

            // When evaluated, the stack of ShapeLayerContent produces a list of CompositionShape.
            // Some ShapeLayerContent modify the evaluation context (e.g. stroke, fill, trim)
            // Some ShapeLayerContent evaluate to geometries (e.g. any geometry, merge path)

            // Create a container to hold the contents.
            var container = context.ObjectFactory.CreateContainerShape();

            // This is the object that will be returned. Containers may be added above this
            // as necessary to hold transforms.
            var result = container;

            // If the contents contains a repeater, generate repeated contents
            if (contents.Any(slc => slc.ContentType == ShapeContentType.Repeater))
            {
                // The contents contains a repeater. Treat it as if there are n sets of items (where n
                // equals the Count of the repeater). In each set, replace the repeater with
                // the transform of the repeater, multiplied.

                // Find the index of the repeater
                var repeaterIndex = 0;
                while (contents[repeaterIndex].ContentType != ShapeContentType.Repeater)
                {
                    // Keep going until the first repeater is found.
                    repeaterIndex++;
                }

                // Get the repeater.
                var repeater = (Repeater)contents[repeaterIndex];

                var repeaterCount  = Optimizer.TrimAnimatable(context, repeater.Count);
                var repeaterOffset = Optimizer.TrimAnimatable(context, repeater.Offset);

                // Make sure we can handle it.
                if (repeaterCount.IsAnimated || repeaterOffset.IsAnimated || repeaterOffset.InitialValue != 0)
                {
                    // TODO - handle all cases.
                    context.Issues.RepeaterIsNotSupported();
                }
                else
                {
                    // Get the items before the repeater, and the items after the repeater.
                    var itemsBeforeRepeater = contents.Slice(0, repeaterIndex).ToArray();
                    var itemsAfterRepeater  = contents.Slice(repeaterIndex + 1).ToArray();

                    var nonAnimatedRepeaterCount = (int)Math.Round(repeaterCount.InitialValue);
                    for (var i = 0; i < nonAnimatedRepeaterCount; i++)
                    {
                        // Treat each repeated value as a list of items where the repeater is replaced
                        // by n transforms.
                        // TODO - currently ignoring the StartOpacity and EndOpacity - should generate a new transform
                        //        that interpolates that.
                        var generatedItems = itemsBeforeRepeater.Concat(Enumerable.Repeat(repeater.Transform, i + 1)).Concat(itemsAfterRepeater).ToArray();

                        // Recurse to translate the synthesized items.
                        container.Shapes.Add(TranslateShapeLayerContents(context, generatedItems));
                    }

                    return(result);
                }
            }

            CheckForUnsupportedShapeGroup(context, contents);

            var stack = new Stack <ShapeLayerContent>(contents.ToArray());

            while (true)
            {
                context.UpdateFromStack(stack);
                if (stack.Count == 0)
                {
                    break;
                }

                var shapeContent = stack.Pop();

                // Complain if the BlendMode is not supported.
                if (shapeContent.BlendMode != BlendMode.Normal)
                {
                    context.Issues.BlendModeNotNormal(context.LayerContext.Layer.Name, shapeContent.BlendMode.ToString());
                }

                switch (shapeContent.ContentType)
                {
                case ShapeContentType.Ellipse:
                    container.Shapes.Add(Ellipses.TranslateEllipseContent(context, (Ellipse)shapeContent));
                    break;

                case ShapeContentType.Group:
                    container.Shapes.Add(TranslateGroupShapeContent(context.Clone(), (ShapeGroup)shapeContent));
                    break;

                case ShapeContentType.MergePaths:
                    var mergedPaths = TranslateMergePathsContent(context, stack, ((MergePaths)shapeContent).Mode);
                    if (mergedPaths != null)
                    {
                        container.Shapes.Add(mergedPaths);
                    }

                    break;

                case ShapeContentType.Path:
                {
                    var paths = new List <Path>();
                    paths.Add(Optimizer.OptimizePath(context, (Path)shapeContent));

                    // Get all the paths that are part of the same group.
                    while (stack.TryPeek(out var item) && item.ContentType == ShapeContentType.Path)
                    {
                        // Optimize the paths as they are added. Optimized paths have redundant keyframes
                        // removed. Optimizing here increases the chances that an animated path will be
                        // turned into a non-animated path which will allow us to group the paths.
                        paths.Add(Optimizer.OptimizePath(context, (Path)stack.Pop()));
                    }

                    CheckForRoundCornersOnPath(context);

                    if (paths.Count == 1)
                    {
                        // There's a single path.
                        container.Shapes.Add(Paths.TranslatePathContent(context, paths[0]));
                    }
                    else
                    {
                        // There are multiple paths. They need to be grouped.
                        container.Shapes.Add(Paths.TranslatePathGroupContent(context, paths));
                    }
                }

                break;

                case ShapeContentType.Polystar:
                    context.Issues.PolystarIsNotSupported();
                    break;

                case ShapeContentType.Rectangle:
                    container.Shapes.Add(Rectangles.TranslateRectangleContent(context, (Rectangle)shapeContent));
                    break;

                case ShapeContentType.Transform:
                {
                    var transform = (Transform)shapeContent;

                    // Multiply the opacity in the transform.
                    context.UpdateOpacityFromTransform(context, transform);

                    // Insert a new container at the top. The transform will be applied to it.
                    var newContainer = context.ObjectFactory.CreateContainerShape();
                    newContainer.Shapes.Add(result);
                    result = newContainer;

                    // Apply the transform to the new container at the top.
                    Transforms.TranslateAndApplyTransform(context, transform, result);
                }

                break;

                case ShapeContentType.Repeater:
                    // TODO - handle all cases. Not clear whether this is valid. Seen on 0605.traffic_light.
                    context.Issues.RepeaterIsNotSupported();
                    break;

                default:
                case ShapeContentType.SolidColorStroke:
                case ShapeContentType.LinearGradientStroke:
                case ShapeContentType.RadialGradientStroke:
                case ShapeContentType.SolidColorFill:
                case ShapeContentType.LinearGradientFill:
                case ShapeContentType.RadialGradientFill:
                case ShapeContentType.TrimPath:
                case ShapeContentType.RoundCorners:
                    throw new InvalidOperationException();
                }
            }

            return(result);
        }
예제 #2
0
        static IEnumerable <CanvasGeometry> CreateCanvasGeometries(
            ShapeContext context,
            Stack <ShapeLayerContent> stack,
            ShapeFill.PathFillType pathFillType)
        {
            while (stack.Count > 0)
            {
                // Ignore context on the stack - we only want geometries.
                var shapeContent = stack.Pop();
                switch (shapeContent.ContentType)
                {
                case ShapeContentType.Group:
                {
                    // Convert all the shapes in the group to a list of geometries
                    var group             = (ShapeGroup)shapeContent;
                    var groupedGeometries = CreateCanvasGeometries(context.Clone(), new Stack <ShapeLayerContent>(group.Contents.ToArray()), pathFillType).ToArray();
                    foreach (var geometry in groupedGeometries)
                    {
                        yield return(geometry);
                    }
                }

                break;

                case ShapeContentType.MergePaths:
                    yield return(MergeShapeLayerContent(context, stack, ((MergePaths)shapeContent).Mode));

                    break;

                case ShapeContentType.Repeater:
                    context.Issues.RepeaterIsNotSupported();
                    break;

                case ShapeContentType.Transform:
                    // TODO - do we need to clear out the transform when we've finished with this call to CreateCanvasGeometries?? Maybe the caller should clone the context.
                    context.SetTransform((Transform)shapeContent);
                    break;

                case ShapeContentType.SolidColorStroke:
                case ShapeContentType.LinearGradientStroke:
                case ShapeContentType.RadialGradientStroke:
                case ShapeContentType.SolidColorFill:
                case ShapeContentType.RadialGradientFill:
                case ShapeContentType.LinearGradientFill:
                case ShapeContentType.TrimPath:
                case ShapeContentType.RoundCorners:
                    // Ignore commands that set the context - we only want geometries.
                    break;

                case ShapeContentType.Path:
                    yield return(Paths.CreateWin2dPathGeometryFromShape(context, (Path)shapeContent, pathFillType, optimizeLines: true));

                    break;

                case ShapeContentType.Ellipse:
                    yield return(Ellipses.CreateWin2dEllipseGeometry(context, (Ellipse)shapeContent));

                    break;

                case ShapeContentType.Rectangle:
                    yield return(Rectangles.CreateWin2dRectangleGeometry(context, (Rectangle)shapeContent));

                    break;

                case ShapeContentType.Polystar:
                    context.Issues.PolystarIsNotSupported();
                    break;

                default:
                    throw new InvalidOperationException();
                }
            }
        }