#include "LottieRenderTree.h" #include "LottieRenderTreeInternal.h" #include "Lottie/Public/Primitives/CGPath.hpp" #include "Lottie/Public/Primitives/CGPathCocoa.h" #include "Lottie/Public/Primitives/Color.hpp" #include "Lottie/Public/Primitives/CALayer.hpp" #include "Lottie/Public/Primitives/VectorsCocoa.h" #include "RenderNode.hpp" namespace { } @interface LottiePath () { std::vector _paths; NSData *_customData; } @end @implementation LottiePath - (instancetype)initWithPaths:(std::vector)paths __attribute__((objc_direct)) { self = [super init]; if (self != nil) { _paths = paths; } return self; } - (instancetype _Nonnull)initWithCustomData:(NSData * _Nonnull)customData __attribute__((objc_direct)) { self = [super init]; if (self != nil) { _customData = customData; } return self; } - (void)enumerateItems:(void (^ _Nonnull)(LottiePathItem * _Nonnull))iterate { LottiePathItem item; if (_customData != nil) { int dataOffset = 0; int dataLength = (int)_customData.length; uint8_t const *dataBytes = (uint8_t const *)_customData.bytes; while (dataOffset < dataLength) { uint8_t itemType = dataBytes[dataOffset]; dataOffset += 1; switch (itemType) { case 0: { Float32 px; memcpy(&px, dataBytes + dataOffset, 4); dataOffset += 4; Float32 py; memcpy(&py, dataBytes + dataOffset, 4); dataOffset += 4; item.type = LottiePathItemTypeMoveTo; item.points[0] = CGPointMake(px, py); iterate(&item); break; } case 1: { Float32 px; memcpy(&px, dataBytes + dataOffset, 4); dataOffset += 4; Float32 py; memcpy(&py, dataBytes + dataOffset, 4); dataOffset += 4; item.type = LottiePathItemTypeLineTo; item.points[0] = CGPointMake(px, py); iterate(&item); break; } case 2: { Float32 p1x; memcpy(&p1x, dataBytes + dataOffset, 4); dataOffset += 4; Float32 p1y; memcpy(&p1y, dataBytes + dataOffset, 4); dataOffset += 4; Float32 p2x; memcpy(&p2x, dataBytes + dataOffset, 4); dataOffset += 4; Float32 p2y; memcpy(&p2y, dataBytes + dataOffset, 4); dataOffset += 4; Float32 px; memcpy(&px, dataBytes + dataOffset, 4); dataOffset += 4; Float32 py; memcpy(&py, dataBytes + dataOffset, 4); dataOffset += 4; item.type = LottiePathItemTypeCurveTo; item.points[0] = CGPointMake(p1x, p1y); item.points[1] = CGPointMake(p2x, p2y); item.points[2] = CGPointMake(px, py); iterate(&item); break; } case 3: { item.type = LottiePathItemTypeClose; iterate(&item); break; } default: { break; } } } } else { for (const auto &path : _paths) { std::optional previousElement; for (const auto &element : path.elements()) { if (previousElement.has_value()) { if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { item.type = LottiePathItemTypeLineTo; item.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); iterate(&item); } else { item.type = LottiePathItemTypeCurveTo; item.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y); item.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y); item.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y); iterate(&item); } } else { item.type = LottiePathItemTypeMoveTo; item.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); iterate(&item); } previousElement = element; } if (path.closed().value_or(true)) { item.type = LottiePathItemTypeClose; iterate(&item); } } } } @end @implementation LottieColorStop : NSObject - (instancetype _Nonnull)initWithColor:(LottieColor)color location:(CGFloat)location __attribute__((objc_direct)) { self = [super init]; if (self != nil) { _color = color; _location = location; } return self; } @end @implementation LottieRenderContentShading - (instancetype _Nonnull)init { self = [super init]; if (self != nil) { } return self; } @end static LottieColor lottieColorFromColor(lottie::Color color) { LottieColor result; result.r = color.r; result.g = color.g; result.b = color.b; result.a = color.a; return result; } @implementation LottieRenderContentSolidShading - (instancetype _Nonnull)initWithSolidShading:(lottie::RenderTreeNodeContent::SolidShading *)solidShading __attribute__((objc_direct)) { self = [super init]; if (self != nil) { _color = lottieColorFromColor(solidShading->color); _opacity = solidShading->opacity; } return self; } - (instancetype _Nonnull)initWithColor:(LottieColor)color opacity:(CGFloat)opacity { self = [super init]; if (self != nil) { _color = color; _opacity = opacity; } return self; } @end @implementation LottieRenderContentGradientShading - (instancetype _Nonnull)initWithGradientShading:(lottie::RenderTreeNodeContent::GradientShading *)gradientShading __attribute__((objc_direct)) { self = [super init]; if (self != nil) { _opacity = gradientShading->opacity; switch (gradientShading->gradientType) { case lottie::GradientType::Radial: { _gradientType = LottieGradientTypeRadial; break; } default: { _gradientType = LottieGradientTypeLinear; break; } } NSMutableArray *colorStops = [[NSMutableArray alloc] initWithCapacity:gradientShading->colors.size()]; for (size_t i = 0; i < gradientShading->colors.size(); i++) { [colorStops addObject:[[LottieColorStop alloc] initWithColor:lottieColorFromColor(gradientShading->colors[i]) location:gradientShading->locations[i]]]; } _colorStops = colorStops; _start = CGPointMake(gradientShading->start.x, gradientShading->start.y); _end = CGPointMake(gradientShading->end.x, gradientShading->end.y); } return self; } - (instancetype _Nonnull)initWithOpacity:(CGFloat)opacity gradientType:(LottieGradientType)gradientType colorStops:(NSArray * _Nonnull)colorStops start:(CGPoint)start end:(CGPoint)end __attribute__((objc_direct)) { self = [super init]; if (self != nil) { _opacity = opacity; _gradientType = gradientType; _colorStops = colorStops; _start = start; _end = end; } return self; } @end @implementation LottieRenderContentFill - (instancetype _Nonnull)initWithFill:(std::shared_ptr const &)fill __attribute__((objc_direct)) { self = [super init]; if (self != nil) { switch (fill->shading->type()) { case lottie::RenderTreeNodeContent::ShadingType::Solid: { _shading = [[LottieRenderContentSolidShading alloc] initWithSolidShading:(lottie::RenderTreeNodeContent::SolidShading *)fill->shading.get()]; break; } case lottie::RenderTreeNodeContent::ShadingType::Gradient: { _shading = [[LottieRenderContentGradientShading alloc] initWithGradientShading:(lottie::RenderTreeNodeContent::GradientShading *)fill->shading.get()]; break; } default: { abort(); } } switch (fill->rule) { case lottie::FillRule::EvenOdd: { _fillRule = LottieFillRuleEvenOdd; break; } default: { _fillRule = LottieFillRuleWinding; break; } } } return self; } - (instancetype _Nonnull)initWithShading:(LottieRenderContentShading * _Nonnull)shading fillRule:(LottieFillRule)fillRule __attribute__((objc_direct)) { self = [super init]; if (self != nil) { _shading = shading; _fillRule = fillRule; } return self; } @end @implementation LottieRenderContentStroke - (instancetype _Nonnull)initWithStroke:(std::shared_ptr const &)stroke __attribute__((objc_direct)) { self = [super init]; if (self != nil) { switch (stroke->shading->type()) { case lottie::RenderTreeNodeContent::ShadingType::Solid: { _shading = [[LottieRenderContentSolidShading alloc] initWithSolidShading:(lottie::RenderTreeNodeContent::SolidShading *)stroke->shading.get()]; break; } case lottie::RenderTreeNodeContent::ShadingType::Gradient: { _shading = [[LottieRenderContentGradientShading alloc] initWithGradientShading:(lottie::RenderTreeNodeContent::GradientShading *)stroke->shading.get()]; break; } default: { abort(); } } _lineWidth = stroke->lineWidth; switch (stroke->lineJoin) { case lottie::LineJoin::Miter: { _lineJoin = kCGLineJoinMiter; break; } case lottie::LineJoin::Round: { _lineJoin = kCGLineJoinRound; break; } case lottie::LineJoin::Bevel: { _lineJoin = kCGLineJoinBevel; break; } default: { _lineJoin = kCGLineJoinBevel; break; } } switch (stroke->lineCap) { case lottie::LineCap::Butt: { _lineCap = kCGLineCapButt; break; } case lottie::LineCap::Round: { _lineCap = kCGLineCapRound; break; } case lottie::LineCap::Square: { _lineCap = kCGLineCapSquare; break; } default: { _lineCap = kCGLineCapSquare; break; } } _miterLimit = stroke->miterLimit; _dashPhase = stroke->dashPhase; if (!stroke->dashPattern.empty()) { NSMutableArray *dashPattern = [[NSMutableArray alloc] initWithCapacity:stroke->dashPattern.size()]; for (auto value : stroke->dashPattern) { [dashPattern addObject:@(value)]; } _dashPattern = dashPattern; } } return self; } - (instancetype _Nonnull)initWithShading:(LottieRenderContentShading * _Nonnull)shading lineWidth:(CGFloat)lineWidth lineJoin:(CGLineJoin)lineJoin lineCap:(CGLineCap)lineCap miterLimit:(CGFloat)miterLimit dashPhase:(CGFloat)dashPhase dashPattern:(NSArray * _Nullable)dashPattern __attribute__((objc_direct)) { self = [super init]; if (self != nil) { _shading = shading; _lineWidth = lineWidth; _lineJoin = lineJoin; _lineCap = lineCap; _miterLimit = miterLimit; _dashPhase = dashPhase; _dashPattern = dashPattern; } return self; } @end @implementation LottieRenderContent - (instancetype _Nonnull)initWithRenderContent:(std::shared_ptr const &)content __attribute__((objc_direct)) { self = [super init]; if (self != nil) { _path = [[LottiePath alloc] initWithPaths:content->paths]; if (content->stroke) { _stroke = [[LottieRenderContentStroke alloc] initWithStroke:content->stroke]; } if (content->fill) { _fill = [[LottieRenderContentFill alloc] initWithFill:content->fill]; } } return self; } - (instancetype _Nonnull)initWithPath:(LottiePath * _Nonnull)path stroke:(LottieRenderContentStroke * _Nullable)stroke fill:(LottieRenderContentFill * _Nullable)fill __attribute__((objc_direct)) { self = [super init]; if (self != nil) { _path = path; _stroke = stroke; _fill = fill; } return self; } @end @implementation LottieRenderNode - (instancetype _Nonnull)initWithPosition:(CGPoint)position bounds:(CGRect)bounds transform:(CATransform3D)transform opacity:(CGFloat)opacity masksToBounds:(bool)masksToBounds isHidden:(bool)isHidden globalRect:(CGRect)globalRect globalTransform:(CATransform3D)globalTransform renderContent:(LottieRenderContent * _Nullable)renderContent hasSimpleContents:(bool)hasSimpleContents isInvertedMatte:(bool)isInvertedMatte subnodes:(NSArray * _Nonnull)subnodes mask:(LottieRenderNode * _Nullable)mask __attribute__((objc_direct)) { self = [super init]; if (self != nil) { _position = position; _bounds = bounds; _transform = transform; _opacity = opacity; _masksToBounds = masksToBounds; _isHidden = isHidden; _globalRect = globalRect; _globalTransform= globalTransform; _renderContent = renderContent; _hasSimpleContents = hasSimpleContents; _isInvertedMatte = isInvertedMatte; _subnodes = subnodes; _mask = mask; } return self; } @end @implementation LottieRenderNode (Internal) - (instancetype _Nonnull)initWithRenderNode:(std::shared_ptr const &)renderNode __attribute__((objc_direct)) { self = [super init]; if (self != nil) { auto position = renderNode->layer.position(); _position = CGPointMake(position.x, position.y); auto bounds = renderNode->layer.bounds(); _bounds = CGRectMake(bounds.x, bounds.y, bounds.width, bounds.height); _transform = lottie::nativeTransform(renderNode->layer.transform()); _opacity = renderNode->layer.opacity(); _masksToBounds = renderNode->layer.masksToBounds(); _isHidden = renderNode->layer.isHidden(); auto globalRect = renderNode->globalRect; _globalRect = CGRectMake(globalRect.x, globalRect.y, globalRect.width, globalRect.height); _globalTransform = lottie::nativeTransform(renderNode->globalTransform); if (renderNode->renderContent) { _renderContent = [[LottieRenderContent alloc] initWithRenderContent:renderNode->renderContent]; } _hasSimpleContents = renderNode->drawContentDescendants <= 1; _isInvertedMatte = renderNode->isInvertedMatte; if (!renderNode->subnodes.empty()) { NSMutableArray *subnodes = [[NSMutableArray alloc] init]; for (const auto &subnode : renderNode->subnodes) { [subnodes addObject:[[LottieRenderNode alloc] initWithRenderNode:subnode]]; } _subnodes = subnodes; } else { _subnodes = [[NSArray alloc] init]; } if (renderNode->mask) { _mask = [[LottieRenderNode alloc] initWithRenderNode:renderNode->mask]; } } return self; } @end