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); }