2021-06-19 15:18:30 +04:00

232 lines
9.0 KiB
Objective-C
Executable File

#import <Svg/Svg.h>
#import "nanosvg.h"
#define UIColorRGBA(rgb,a) ([[UIColor alloc] initWithRed:(((rgb >> 16) & 0xff) / 255.0f) green:(((rgb >> 8) & 0xff) / 255.0f) blue:(((rgb) & 0xff) / 255.0f) alpha:a])
CGSize aspectFillSize(CGSize size, CGSize bounds) {
CGFloat scale = MAX(bounds.width / MAX(1.0, size.width), bounds.height / MAX(1.0, size.height));
return CGSizeMake(floor(size.width * scale), floor(size.height * scale));
}
@interface SvgXMLParsingDelegate : NSObject <NSXMLParserDelegate> {
NSString *_elementName;
NSString *_currentStyleString;
}
@property (nonatomic, strong, readonly) NSMutableDictionary<NSString *, NSString *> *styles;
@end
@implementation SvgXMLParsingDelegate
- (instancetype)init {
self = [super init];
if (self != nil) {
_styles = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary<NSString *,NSString *> *)attributeDict {
_elementName = elementName;
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([_elementName isEqualToString:@"style"]) {
int currentClassNameStartIndex = -1;
int currentClassContentsStartIndex = -1;
NSString *currentClassName = nil;
NSCharacterSet *alphanumeric = [NSCharacterSet alphanumericCharacterSet];
for (int i = 0; i < _currentStyleString.length; i++) {
unichar c = [_currentStyleString characterAtIndex:i];
if (currentClassNameStartIndex != -1) {
if (![alphanumeric characterIsMember:c]) {
currentClassName = [_currentStyleString substringWithRange:NSMakeRange(currentClassNameStartIndex, i - currentClassNameStartIndex)];
currentClassNameStartIndex = -1;
}
} else if (currentClassContentsStartIndex != -1) {
if (c == '}') {
NSString *classContents = [_currentStyleString substringWithRange:NSMakeRange(currentClassContentsStartIndex, i - currentClassContentsStartIndex)];
if (currentClassName != nil && classContents != nil) {
_styles[currentClassName] = classContents;
currentClassName = nil;
}
currentClassContentsStartIndex = -1;
}
}
if (currentClassNameStartIndex == -1 && currentClassContentsStartIndex == -1) {
if (c == '.') {
currentClassNameStartIndex = i + 1;
} else if (c == '{') {
currentClassContentsStartIndex = i + 1;
}
}
}
}
_elementName = nil;
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if ([_elementName isEqualToString:@"style"]) {
if (_currentStyleString == nil) {
_currentStyleString = string;
} else {
_currentStyleString = [_currentStyleString stringByAppendingString:string];
}
}
}
@end
UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor *backgroundColor, UIColor *foregroundColor) {
NSDate *startTime = [NSDate date];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
if (parser == nil) {
return nil;
}
SvgXMLParsingDelegate *delegate = [[SvgXMLParsingDelegate alloc] init];
parser.delegate = delegate;
[parser parse];
NSMutableString *xmlString = [[NSMutableString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (xmlString == nil) {
return nil;
}
for (NSString *styleName in delegate.styles) {
NSString *styleValue = delegate.styles[styleName];
[xmlString replaceOccurrencesOfString:[NSString stringWithFormat:@"class=\"%@\"", styleName] withString:[NSString stringWithFormat:@"style=\"%@\"", styleValue] options:0 range:NSMakeRange(0, xmlString.length)];
}
const char *zeroTerminatedData = xmlString.UTF8String;
NSVGimage *image = nsvgParse((char *)zeroTerminatedData, "px", 96);
if (image == nil || image->width < 1.0f || image->height < 1.0f) {
return nil;
}
double deltaTime = -1.0f * [startTime timeIntervalSinceNow];
printf("parseTime = %f\n", deltaTime);
startTime = [NSDate date];
UIGraphicsBeginImageContextWithOptions(size, true, 1.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillRect(context, CGRectMake(0.0f, 0.0f, size.width, size.height));
CGSize svgSize = CGSizeMake(image->width, image->height);
CGSize drawingSize = aspectFillSize(svgSize, size);
CGFloat scale = MAX(size.width / MAX(1.0, svgSize.width), size.height / MAX(1.0, svgSize.height));
CGContextScaleCTM(context, scale, scale);
CGContextTranslateCTM(context, (size.width - drawingSize.width) / 2.0, (size.height - drawingSize.height) / 2.0);
for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) {
if (!(shape->flags & NSVG_FLAGS_VISIBLE)) {
continue;
}
if (shape->fill.type != NSVG_PAINT_NONE) {
CGContextSetFillColorWithColor(context, [foregroundColor colorWithAlphaComponent:shape->opacity].CGColor);
bool isFirst = true;
bool hasStartPoint = false;
CGPoint startPoint;
for (NSVGpath *path = shape->paths; path != NULL; path = path->next) {
if (isFirst) {
CGContextBeginPath(context);
isFirst = false;
hasStartPoint = true;
startPoint.x = path->pts[0];
startPoint.y = path->pts[1];
}
CGContextMoveToPoint(context, path->pts[0], path->pts[1]);
for (int i = 0; i < path->npts - 1; i += 3) {
float *p = &path->pts[i * 2];
CGContextAddCurveToPoint(context, p[2], p[3], p[4], p[5], p[6], p[7]);
}
if (path->closed) {
if (hasStartPoint) {
hasStartPoint = false;
CGContextAddLineToPoint(context, startPoint.x, startPoint.y);
}
}
}
switch (shape->fillRule) {
case NSVG_FILLRULE_EVENODD:
CGContextEOFillPath(context);
break;
default:
CGContextFillPath(context);
break;
}
}
if (shape->stroke.type != NSVG_PAINT_NONE) {
CGContextSetStrokeColorWithColor(context, [foregroundColor colorWithAlphaComponent:shape->opacity].CGColor);
CGContextSetMiterLimit(context, shape->miterLimit);
CGContextSetLineWidth(context, shape->strokeWidth);
switch (shape->strokeLineCap) {
case NSVG_CAP_BUTT:
CGContextSetLineCap(context, kCGLineCapButt);
break;
case NSVG_CAP_ROUND:
CGContextSetLineCap(context, kCGLineCapRound);
break;
case NSVG_CAP_SQUARE:
CGContextSetLineCap(context, kCGLineCapSquare);
break;
default:
break;
}
switch (shape->strokeLineJoin) {
case NSVG_JOIN_BEVEL:
CGContextSetLineJoin(context, kCGLineJoinBevel);
break;
case NSVG_JOIN_MITER:
CGContextSetLineJoin(context, kCGLineJoinMiter);
break;
case NSVG_JOIN_ROUND:
CGContextSetLineJoin(context, kCGLineJoinRound);
break;
default:
break;
}
for (NSVGpath *path = shape->paths; path != NULL; path = path->next) {
CGContextBeginPath(context);
CGContextMoveToPoint(context, path->pts[0], path->pts[1]);
for (int i = 0; i < path->npts - 1; i += 3) {
float *p = &path->pts[i * 2];
CGContextAddCurveToPoint(context, p[2], p[3], p[4], p[5], p[6], p[7]);
}
if (path->closed) {
CGContextClosePath(context);
}
CGContextStrokePath(context);
}
}
}
UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
deltaTime = -1.0f * [startTime timeIntervalSinceNow];
printf("drawingTime %fx%f = %f\n", size.width, size.height, deltaTime);
nsvgDelete(image);
return resultImage;
}