mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-18 03:20:09 +00:00
412 lines
12 KiB
Objective-C
412 lines
12 KiB
Objective-C
//
|
|
// LOTBezierPath.m
|
|
// Lottie
|
|
//
|
|
// Created by brandon_withrow on 7/20/17.
|
|
// Copyright © 2017 Airbnb. All rights reserved.
|
|
//
|
|
|
|
#import "LOTBezierPath.h"
|
|
#import "CGGeometry+LOTAdditions.h"
|
|
|
|
typedef struct LOT_Subpath LOT_Subpath;
|
|
|
|
struct LOT_Subpath {
|
|
CGPathElementType type;
|
|
CGFloat length;
|
|
CGPoint endPoint;
|
|
CGPoint controlPoint1;
|
|
CGPoint controlPoint2;
|
|
LOT_Subpath *nextSubpath;
|
|
};
|
|
|
|
@interface LOTBezierPath ()
|
|
@property (nonatomic, readonly) LOT_Subpath *headSubpath;
|
|
@end
|
|
|
|
@implementation LOTBezierPath {
|
|
LOT_Subpath *headSubpath_;
|
|
LOT_Subpath *tailSubpath_;
|
|
CGPoint subpathStartPoint_;
|
|
CGFloat *_lineDashPattern;
|
|
NSInteger _lineDashCount;
|
|
CGFloat _lineDashPhase;
|
|
CGMutablePathRef _path;
|
|
}
|
|
|
|
// MARK - Lifecycle
|
|
|
|
+ (instancetype)newPath {
|
|
return [[LOTBezierPath alloc] init];
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
_length = 0;
|
|
headSubpath_ = NULL;
|
|
tailSubpath_ = NULL;
|
|
_path = CGPathCreateMutable();
|
|
_lineWidth = 1;
|
|
_lineCapStyle = kCGLineCapButt;
|
|
_lineJoinStyle = kCGLineJoinMiter;
|
|
_miterLimit = 10;
|
|
_flatness = 0.6;
|
|
_usesEvenOddFillRule = NO;
|
|
_lineDashPattern = NULL;
|
|
_lineDashCount = 0;
|
|
_lineDashPhase = 0;
|
|
_cacheLengths = NO;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[self removeAllSubpaths];
|
|
if (_path) CGPathRelease(_path);
|
|
}
|
|
|
|
- (id)copyWithZone:(NSZone *)zone {
|
|
LOTBezierPath *copy = [[self class] new];
|
|
|
|
copy.cacheLengths = self.cacheLengths;
|
|
copy.lineWidth = self.lineWidth;
|
|
copy.lineCapStyle = self.lineCapStyle;
|
|
copy.lineJoinStyle = self.lineJoinStyle;
|
|
copy.miterLimit = self.miterLimit;
|
|
copy.flatness = self.flatness;
|
|
copy.usesEvenOddFillRule = self.usesEvenOddFillRule;
|
|
|
|
[copy LOT_appendPath:self];
|
|
|
|
return copy;
|
|
}
|
|
|
|
// MARK - Subpaths List
|
|
|
|
- (void)removeAllSubpaths {
|
|
LOT_Subpath *node = headSubpath_;
|
|
while (node) {
|
|
LOT_Subpath *nextNode = node->nextSubpath;
|
|
node->nextSubpath = NULL;
|
|
free(node);
|
|
node = nextNode;
|
|
}
|
|
headSubpath_ = NULL;
|
|
tailSubpath_ = NULL;
|
|
}
|
|
|
|
- (void)addSubpathWithType:(CGPathElementType)type
|
|
length:(CGFloat)length
|
|
endPoint:(CGPoint)endPoint
|
|
controlPoint1:(CGPoint)controlPoint1
|
|
controlPoint1:(CGPoint)controlPoint2 {
|
|
LOT_Subpath *subPath = (LOT_Subpath *)malloc(sizeof(LOT_Subpath));
|
|
subPath->type = type;
|
|
subPath->length = length;
|
|
subPath->endPoint = endPoint;
|
|
subPath->controlPoint1 = controlPoint1;
|
|
subPath->controlPoint2 = controlPoint2;
|
|
subPath->nextSubpath = NULL;
|
|
if (tailSubpath_ == NULL) {
|
|
headSubpath_ = subPath;
|
|
tailSubpath_ = subPath;
|
|
} else {
|
|
tailSubpath_->nextSubpath = subPath;
|
|
tailSubpath_ = subPath;
|
|
}
|
|
}
|
|
|
|
// MARK Getters Setters
|
|
|
|
- (CGPoint)currentPoint {
|
|
CGPoint previousPoint = tailSubpath_ ? tailSubpath_->endPoint : CGPointZero;
|
|
return previousPoint;
|
|
}
|
|
|
|
- (CGPathRef)CGPath {
|
|
return _path;
|
|
}
|
|
|
|
- (LOT_Subpath *)headSubpath {
|
|
return headSubpath_;
|
|
}
|
|
|
|
// MARK - External
|
|
|
|
- (void)LOT_moveToPoint:(CGPoint)point {
|
|
subpathStartPoint_ = point;
|
|
[self addSubpathWithType:kCGPathElementMoveToPoint length:0 endPoint:point controlPoint1:CGPointZero controlPoint1:CGPointZero];
|
|
CGPathMoveToPoint(_path, NULL, point.x, point.y);
|
|
}
|
|
|
|
- (void)LOT_addLineToPoint:(CGPoint)point {
|
|
CGFloat length = 0;
|
|
if (_cacheLengths) {
|
|
length = LOT_PointDistanceFromPoint(self.currentPoint, point);
|
|
_length = _length + length;
|
|
}
|
|
[self addSubpathWithType:kCGPathElementAddLineToPoint length:length endPoint:point controlPoint1:CGPointZero controlPoint1:CGPointZero];
|
|
CGPathAddLineToPoint(_path, NULL, point.x, point.y);
|
|
}
|
|
|
|
- (void)LOT_addCurveToPoint:(CGPoint)point
|
|
controlPoint1:(CGPoint)cp1
|
|
controlPoint2:(CGPoint)cp2 {
|
|
CGFloat length = 0;
|
|
if (_cacheLengths) {
|
|
length = LOT_CubicLengthWithPrecision(self.currentPoint, point, cp1, cp2, 5);
|
|
_length = _length + length;
|
|
}
|
|
[self addSubpathWithType:kCGPathElementAddCurveToPoint length:length endPoint:point controlPoint1:cp1 controlPoint1:cp2];
|
|
CGPathAddCurveToPoint(_path, NULL, cp1.x, cp1.y, cp2.x, cp2.y, point.x, point.y);
|
|
}
|
|
|
|
- (void)LOT_closePath {
|
|
CGFloat length = 0;
|
|
if (_cacheLengths) {
|
|
length = LOT_PointDistanceFromPoint(self.currentPoint, subpathStartPoint_);
|
|
_length = _length + length;
|
|
}
|
|
[self addSubpathWithType:kCGPathElementCloseSubpath length:length endPoint:subpathStartPoint_ controlPoint1:CGPointZero controlPoint1:CGPointZero];
|
|
CGPathCloseSubpath(_path);
|
|
}
|
|
|
|
- (void)_clearPathData {
|
|
_length = 0;
|
|
subpathStartPoint_ = CGPointZero;
|
|
CGPathRelease(_path);
|
|
_path = CGPathCreateMutable();
|
|
}
|
|
|
|
- (void)LOT_removeAllPoints {
|
|
[self removeAllSubpaths];
|
|
[self _clearPathData];
|
|
}
|
|
|
|
- (BOOL)containsPoint:(CGPoint)point {
|
|
return CGPathContainsPoint(_path, NULL, point, _usesEvenOddFillRule);
|
|
}
|
|
|
|
- (BOOL)isEmpty {
|
|
return CGPathIsEmpty(_path);
|
|
}
|
|
|
|
- (CGRect)bounds {
|
|
return CGPathGetBoundingBox(_path);
|
|
}
|
|
|
|
- (void)LOT_applyTransform:(CGAffineTransform)transform {
|
|
CGMutablePathRef mutablePath = CGPathCreateMutable();
|
|
CGPathAddPath(mutablePath, &transform, _path);
|
|
CGPathRelease(_path);
|
|
_path = mutablePath;
|
|
}
|
|
|
|
- (void)LOT_appendPath:(LOTBezierPath *)bezierPath {
|
|
CGPathAddPath(_path, NULL, bezierPath.CGPath);
|
|
|
|
LOT_Subpath *nextSubpath = bezierPath.headSubpath;
|
|
while (nextSubpath) {
|
|
CGFloat length = 0;
|
|
if (self.cacheLengths) {
|
|
if (bezierPath.cacheLengths) {
|
|
length = nextSubpath->length;
|
|
} else {
|
|
// No previous length data, measure.
|
|
if (nextSubpath->type == kCGPathElementAddLineToPoint) {
|
|
length = LOT_PointDistanceFromPoint(self.currentPoint, nextSubpath->endPoint);
|
|
} else if (nextSubpath->type == kCGPathElementAddCurveToPoint) {
|
|
length = LOT_CubicLengthWithPrecision(self.currentPoint, nextSubpath->endPoint, nextSubpath->controlPoint1, nextSubpath->controlPoint2, 5);
|
|
} else if (nextSubpath->type == kCGPathElementCloseSubpath) {
|
|
length = LOT_PointDistanceFromPoint(self.currentPoint, nextSubpath->endPoint);
|
|
}
|
|
}
|
|
}
|
|
_length = _length + length;
|
|
[self addSubpathWithType:nextSubpath->type
|
|
length:length
|
|
endPoint:nextSubpath->endPoint
|
|
controlPoint1:nextSubpath->controlPoint1
|
|
controlPoint1:nextSubpath->controlPoint2];
|
|
|
|
nextSubpath = nextSubpath->nextSubpath;
|
|
}
|
|
|
|
}
|
|
|
|
- (void)trimPathFromT:(CGFloat)fromT toT:(CGFloat)toT offset:(CGFloat)offset {
|
|
if (fromT > toT) {
|
|
CGFloat to = fromT;
|
|
fromT = toT;
|
|
toT = to;
|
|
}
|
|
|
|
offset = offset - floor(offset);
|
|
CGFloat fromLength = fromT + offset;
|
|
CGFloat toLength = toT + offset;
|
|
|
|
if (toT - fromT == 1) {
|
|
// Do Nothing, Full Path returned.
|
|
return;
|
|
}
|
|
|
|
if (fromLength == toLength) {
|
|
// Empty Path
|
|
[self LOT_removeAllPoints];
|
|
return;
|
|
}
|
|
|
|
if (fromLength >= 1) {
|
|
fromLength = fromLength - floor(fromLength);
|
|
}
|
|
if (toLength > 1) {
|
|
toLength = toLength - floor(toLength);
|
|
}
|
|
|
|
if (fromLength == 0 &&
|
|
toLength == 1) {
|
|
// Do Nothing. Full Path returned.
|
|
return;
|
|
}
|
|
|
|
if (fromLength == toLength) {
|
|
// Empty Path
|
|
[self LOT_removeAllPoints];
|
|
return;
|
|
}
|
|
|
|
CGFloat totalLength = _length;
|
|
|
|
[self _clearPathData];
|
|
|
|
|
|
LOT_Subpath *subpath = headSubpath_;
|
|
headSubpath_ = NULL;
|
|
tailSubpath_ = NULL;
|
|
|
|
fromLength = fromLength * totalLength;
|
|
toLength = toLength * totalLength;
|
|
|
|
CGFloat currentStartLength = fromLength < toLength ? fromLength : 0;
|
|
CGFloat currentEndLength = toLength;
|
|
|
|
CGFloat subpathBeginningLength = 0;
|
|
CGPoint currentPoint = CGPointZero;
|
|
|
|
while (subpath) {
|
|
|
|
CGFloat pathLength = subpath->length;
|
|
if (!_cacheLengths) {
|
|
if (subpath->type == kCGPathElementAddLineToPoint) {
|
|
pathLength = LOT_PointDistanceFromPoint(currentPoint, subpath->endPoint);
|
|
} else if (subpath->type == kCGPathElementAddCurveToPoint) {
|
|
pathLength = LOT_CubicLengthWithPrecision(currentPoint, subpath->endPoint, subpath->controlPoint1, subpath->controlPoint2, 5);
|
|
} else if (subpath->type == kCGPathElementCloseSubpath) {
|
|
pathLength = LOT_PointDistanceFromPoint(currentPoint, subpath->endPoint);
|
|
}
|
|
}
|
|
CGFloat subpathEndLength = subpathBeginningLength + pathLength;
|
|
|
|
if (subpath->type != kCGPathElementMoveToPoint &&
|
|
subpathEndLength > currentStartLength) {
|
|
// The end of this path overlaps the current drawing region
|
|
|
|
// x x x x
|
|
// ---------------ooooooooooooooooooooooooooooooooooooooooooooooooo-------------------
|
|
// Start |currentStartLength currentEndLength| End
|
|
|
|
CGFloat currentSpanStartT = LOT_RemapValue(currentStartLength, subpathBeginningLength, subpathEndLength, 0, 1);
|
|
CGFloat currentSpanEndT = LOT_RemapValue(currentEndLength, subpathBeginningLength, subpathEndLength, 0, 1);
|
|
|
|
// At this point currentSpan start and end T can be less than 0 or greater than 1
|
|
|
|
if (subpath->type == kCGPathElementAddLineToPoint) {
|
|
|
|
if (currentSpanStartT >= 0) {
|
|
// The current drawable span either starts with this subpath or along this subpath.
|
|
// If this is the middle of a segment then currentSpanStartT would be less than 0
|
|
if (currentSpanStartT > 0) {
|
|
currentPoint = LOT_PointInLine(currentPoint, subpath->endPoint, currentSpanStartT);
|
|
}
|
|
[self LOT_moveToPoint:currentPoint];
|
|
// Now we are ready to draw a line
|
|
}
|
|
|
|
CGPoint toPoint = subpath->endPoint;
|
|
if (currentSpanEndT < 1) {
|
|
// The end of the span is inside of the current subpath. Find it.
|
|
toPoint = LOT_PointInLine(currentPoint, subpath->endPoint, currentSpanEndT);
|
|
}
|
|
[self LOT_addLineToPoint:toPoint];
|
|
currentPoint = toPoint;
|
|
} else if (subpath->type == kCGPathElementAddCurveToPoint) {
|
|
|
|
CGPoint cp1, cp2, end;
|
|
cp1 = subpath->controlPoint1;
|
|
cp2 = subpath->controlPoint2;
|
|
end = subpath->endPoint;
|
|
|
|
if (currentSpanStartT >= 0) {
|
|
// The current drawable span either starts with this subpath or along this subpath.
|
|
// If this is the middle of a segment then currentSpanStartT would be less than 0
|
|
// Beginning of a segment Move start point and calculate cp1 and 2 is necessary
|
|
if (currentSpanStartT > 0) {
|
|
CGPoint A = LOT_PointInLine(currentPoint, cp1, currentSpanStartT);
|
|
CGPoint B = LOT_PointInLine(cp1, cp2, currentSpanStartT);
|
|
CGPoint C = LOT_PointInLine(cp2, end, currentSpanStartT);
|
|
CGPoint D = LOT_PointInLine(A, B, currentSpanStartT);
|
|
CGPoint E = LOT_PointInLine(B, C, currentSpanStartT);
|
|
CGPoint F = LOT_PointInLine(D, E, currentSpanStartT);
|
|
currentPoint = F;
|
|
cp1 = E;
|
|
cp2 = C;
|
|
currentSpanEndT = LOT_RemapValue(currentSpanEndT, currentSpanStartT, 1, 0, 1);
|
|
}
|
|
[self LOT_moveToPoint:currentPoint];
|
|
}
|
|
|
|
if (currentSpanEndT < 1) {
|
|
CGPoint A = LOT_PointInLine(currentPoint, cp1, currentSpanEndT);
|
|
CGPoint B = LOT_PointInLine(cp1, cp2, currentSpanEndT);
|
|
CGPoint C = LOT_PointInLine(cp2, end, currentSpanEndT);
|
|
CGPoint D = LOT_PointInLine(A, B, currentSpanEndT);
|
|
CGPoint E = LOT_PointInLine(B, C, currentSpanEndT);
|
|
CGPoint F = LOT_PointInLine(D, E, currentSpanEndT);
|
|
cp1 = A;
|
|
cp2 = D;
|
|
end = F;
|
|
}
|
|
[self LOT_addCurveToPoint:end controlPoint1:cp1 controlPoint2:cp2];
|
|
}
|
|
|
|
if (currentSpanEndT <= 1) {
|
|
// We have possibly reached the end.
|
|
// Current From and To will possibly need to be reset.
|
|
if (fromLength < toLength) {
|
|
while (subpath) {
|
|
LOT_Subpath *nextNode = subpath->nextSubpath;
|
|
subpath->nextSubpath = NULL;
|
|
free(subpath);
|
|
subpath = nextNode;
|
|
}
|
|
break;
|
|
} else {
|
|
currentStartLength = fromLength;
|
|
currentEndLength = totalLength;
|
|
}
|
|
}
|
|
}
|
|
currentPoint = subpath->endPoint;
|
|
subpathBeginningLength = subpathEndLength;
|
|
|
|
LOT_Subpath *nextNode = subpath->nextSubpath;
|
|
subpath->nextSubpath = NULL;
|
|
free(subpath);
|
|
subpath = nextNode;
|
|
}
|
|
}
|
|
|
|
@end
|