diff --git a/Sources/SVGBezierPath.mm b/Sources/SVGBezierPath.mm index c9249abb..f9599618 100644 --- a/Sources/SVGBezierPath.mm +++ b/Sources/SVGBezierPath.mm @@ -35,9 +35,21 @@ - (instancetype)init }); return pathCache; } + ++ (NSCache *)_svg_viewBoxCache +{ + static NSCache *viewBoxCache; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + viewBoxCache = [NSCache new]; + }); + return viewBoxCache; +} + + (void)resetCache { [self._svg_pathCache removeAllObjects]; + [self._svg_viewBoxCache removeAllObjects]; } + (NSArray *)pathsFromSVGAtURL:(NSURL *)aURL @@ -54,6 +66,29 @@ + (void)resetCache return [[NSArray alloc] initWithArray:paths copyItems:YES]; } ++ (NSArray *)pathsFromSVGAtURL:(NSURL *)aURL viewBox:(CGRect *)viewBox +{ + NSArray *paths = [self.class._svg_pathCache objectForKey:aURL]; + NSString *strViewBox = [self.class._svg_viewBoxCache objectForKey:aURL]; + if (!paths) { + paths = [self pathsFromSVGString:[NSString stringWithContentsOfURL:aURL + usedEncoding:NULL + error:NULL] viewBox:viewBox]; + if (paths) { + [self.class._svg_pathCache setObject:paths forKey:aURL]; + [self.class._svg_viewBoxCache setObject:[NSString stringWithFormat:@"%f %f %f %f", viewBox->origin.x, viewBox->origin.y, viewBox->size.width, viewBox->size.height] forKey:aURL]; + } + } + + if (strViewBox) { + NSArray *stringArray = [strViewBox componentsSeparatedByString:@" "]; + if (stringArray && stringArray.count == 4) { + *viewBox = CGRectMake(CGFloat([stringArray[0] doubleValue]), CGFloat([stringArray[1] doubleValue]), CGFloat([stringArray[2] doubleValue]), CGFloat([stringArray[3] doubleValue])); + } + } + return [[NSArray alloc] initWithArray:paths copyItems:YES]; +} + + (NSArray *)pathsFromSVGString:(NSString * const)svgString { SVGAttributeSet *cgAttrs; @@ -68,6 +103,20 @@ + (void)resetCache return paths; } ++ (NSArray *)pathsFromSVGString:(NSString * const)svgString viewBox:(CGRect *)viewBox +{ + SVGAttributeSet *cgAttrs; + NSArray * const pathRefs = CGPathsFromSVGStringViewBox(svgString, &cgAttrs, viewBox); + NSMutableArray * const paths = [NSMutableArray arrayWithCapacity:pathRefs.count]; + for(id pathRef in pathRefs) { + SVGBezierPath * const uiPath = [self bezierPathWithCGPath:(__bridge CGPathRef)pathRef]; + uiPath->_svgAttributes = [cgAttrs attributesForPath:(__bridge CGPathRef)pathRef] ?: @{}; + uiPath.lineWidth = uiPath->_svgAttributes[@"stroke-width"] ? [uiPath->_svgAttributes[@"stroke-width"] doubleValue] : 1.0; + [paths addObject:uiPath]; + } + return paths; +} + - (NSString *)SVGRepresentation { SVGMutableAttributeSet *attributes = [SVGMutableAttributeSet new]; diff --git a/Sources/SVGEngine.mm b/Sources/SVGEngine.mm index 96793431..62a8169a 100755 --- a/Sources/SVGEngine.mm +++ b/Sources/SVGEngine.mm @@ -18,7 +18,7 @@ struct svgParser { svgParser(NSString *); - NSArray *parse(NSMapTable **aoAttributes); + NSArray *parse(NSMapTable **aoAttributes, CGRect *viewBox); protected: NSString *_source; @@ -95,7 +95,7 @@ @interface SVGAttributeSet () { _source = aSource; } -NSArray *svgParser::parse(NSMapTable ** const aoAttributes) +NSArray *svgParser::parse(NSMapTable ** const aoAttributes, CGRect *viewBox) { _xmlReader = xmlReaderForDoc((xmlChar *)[_source UTF8String], NULL, NULL, 0); NSCAssert(_xmlReader, @"Failed to create XML parser"); @@ -118,7 +118,20 @@ @interface SVGAttributeSet () { else if(type == XML_READER_TYPE_END_ELEMENT) --depthWithinUnknownElement; } else if(type == XML_READER_TYPE_ELEMENT && strcasecmp(tag, "svg") == 0) { - // recognize the root svg element but we don't need to do anything with it + // recognize the root svg element: we get the viewBox information + NSDictionary * const attrs = readAttributes(); + if (attrs) { + NSString *strViewBox = attrs[@"viewBox"]; + if (strViewBox) { + NSError *error = nil; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\s{2,}" options:NSRegularExpressionCaseInsensitive error:&error]; + NSString *modifiedString = [regex stringByReplacingMatchesInString:strViewBox options:0 range:NSMakeRange(0, [strViewBox length]) withTemplate:@" "]; + NSArray *stringArray = [modifiedString componentsSeparatedByString:@" "]; + if (stringArray && stringArray.count == 4) { + *viewBox = CGRectMake(CGFloat([stringArray[0] doubleValue]), CGFloat([stringArray[1] doubleValue]), CGFloat([stringArray[2] doubleValue]), CGFloat([stringArray[3] doubleValue])); + } + } + } } else if(type == XML_READER_TYPE_ELEMENT && strcasecmp(tag, "path") == 0) path = readPathTag(); else if(type == XML_READER_TYPE_ELEMENT && strcasecmp(tag, "polyline") == 0) @@ -433,7 +446,18 @@ @interface SVGAttributeSet () { NSArray *CGPathsFromSVGString(NSString * const svgString, SVGAttributeSet **outAttributes) { NSMapTable *attributes; - NSArray *paths = svgParser(svgString).parse(outAttributes ? &attributes : NULL); + CGRect viewBox = CGRectZero; + NSArray *paths = svgParser(svgString).parse(outAttributes ? &attributes : NULL, &viewBox); + if (outAttributes && (*outAttributes = [SVGAttributeSet new])) { + (*outAttributes)->_attributes = attributes; + } + return paths; +} + +NSArray *CGPathsFromSVGStringViewBox(NSString * const svgString, SVGAttributeSet **outAttributes, CGRect *viewBox) +{ + NSMapTable *attributes; + NSArray *paths = svgParser(svgString).parse(outAttributes ? &attributes : NULL, viewBox); if (outAttributes && (*outAttributes = [SVGAttributeSet new])) { (*outAttributes)->_attributes = attributes; } diff --git a/Sources/SVGImageView.m b/Sources/SVGImageView.m index fac8e04d..cd12eabf 100644 --- a/Sources/SVGImageView.m +++ b/Sources/SVGImageView.m @@ -18,6 +18,7 @@ @interface SVGImageView () @implementation SVGImageView { SVGLayer *_svgLayer; + CGRect _viewBox; #ifdef DEBUG dispatch_source_t _fileWatcher; @@ -30,6 +31,7 @@ - (instancetype)initWithFrame:(CGRect)frame if ((self = [super initWithFrame:frame])) { _svgLayer = (SVGLayer *)self.layer; } + _viewBox = CGRectZero; return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder @@ -37,6 +39,7 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder if ((self = [super initWithCoder:aDecoder])) { _svgLayer = (SVGLayer *)self.layer; } + _viewBox = CGRectZero; return self; } #else @@ -46,6 +49,7 @@ - (instancetype)initWithFrame:(CGRect)frame _svgLayer = [SVGLayer new]; self.wantsLayer = YES; } + _viewBox = CGRectZero; return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder @@ -54,17 +58,31 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder _svgLayer = [SVGLayer new]; self.wantsLayer = YES; } + _viewBox = CGRectZero; return self; } #endif - (instancetype)initWithContentsOfURL:(NSURL *)url { + _viewBox = CGRectZero; if (self = [self init]) { [self _cr_loadSVGFromURL:url]; } return self; } +- (instancetype)initWithContentsOfURL:(NSURL *)url readViewBox:(bool)readViewBox { + _viewBox = CGRectZero; + if (self = [self init]) { + [self _cr_loadSVGFromURL:url readViewBox:readViewBox]; + } + return self; +} + + +- (CGRect)getViewBox { + return _viewBox; +} #if TARGET_OS_IPHONE + (Class)layerClass @@ -163,6 +181,40 @@ - (void)_cr_loadSVGFromURL:(NSURL *)url _svgLayer.paths = [SVGBezierPath pathsFromSVGAtURL:url]; } +- (void)_cr_loadSVGFromURL:(NSURL *)url readViewBox:(bool)readViewBox { + if(!readViewBox) { + [self _cr_loadSVGFromURL:url]; + return; + } +#if defined(DEBUG) && !defined(POCKETSVG_DISABLE_FILEWATCH) + if(_fileWatcher) + dispatch_source_cancel(_fileWatcher); + + int const fdes = open([url fileSystemRepresentation], O_RDONLY); + _fileWatcher = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fdes, + DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE, + dispatch_get_main_queue()); + dispatch_source_set_event_handler(_fileWatcher, ^{ + unsigned long const l = dispatch_source_get_data(self->_fileWatcher); + if(l & DISPATCH_VNODE_DELETE || l & DISPATCH_VNODE_WRITE) { + NSLog(@"Reloading %@", url.lastPathComponent); + dispatch_source_cancel(self->_fileWatcher); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + [SVGBezierPath resetCache]; + [self _cr_loadSVGFromURL:url readViewBox:readViewBox]; + }); + } + }); + dispatch_source_set_cancel_handler(_fileWatcher, ^{ + close(fdes); + }); + dispatch_resume(_fileWatcher); +#endif + + _svgLayer.paths = [SVGBezierPath pathsFromSVGAtURL:url viewBox:&_viewBox]; +} + - (void)dealloc { #ifdef DEBUG @@ -182,6 +234,16 @@ - (PSVGColor *)strokeColor { return _svgLayer.strokeColor : nil; } - (void)setStrokeColor:(PSVGColor * const)aColor { _svgLayer.strokeColor = aColor.CGColor; } + +- (CGFloat)strokeWidth +{ + return _svgLayer.strokeWidth; +} +- (void)setStrokeWidth:(CGFloat const)aSize +{ + _svgLayer.strokeWidth = aSize; +} + - (CGSize)sizeThatFits:(CGSize)aSize { return self.layer.preferredFrameSize; diff --git a/Sources/SVGLayer.m b/Sources/SVGLayer.m index 21a2cc9e..82ab21ec 100644 --- a/Sources/SVGLayer.m +++ b/Sources/SVGLayer.m @@ -37,7 +37,8 @@ - (instancetype)init - (instancetype)initWithContentsOfURL:(NSURL *)url { if ((self = [self init])) { - [self setPaths:[SVGBezierPath pathsFromSVGAtURL:url]]; + CGRect viewBox = CGRectMake(0, 0, 0, 0); + [self setPaths:[SVGBezierPath pathsFromSVGAtURL:url viewBox:&viewBox]]; } return self; } @@ -111,6 +112,14 @@ - (void)setStrokeColor:(CGColorRef)aColor [_shapeLayers setValue:(__bridge id)_strokeColor forKey:@"strokeColor"]; } +- (void)setStrokeWidth:(CGFloat)aSize +{ + _strokeWidth = aSize; + + //[_shapeLayers setValue:(__bridge id)_strokeWidth forKey:@"strokeWidth"]; +} + + - (CGSize)preferredFrameSize { return SVGBoundingRectForPaths(_untouchedPaths).size; @@ -150,6 +159,9 @@ - (void)layoutSublayers CGFloat lineScale = (frame.size.width/size.width + frame.size.height/size.height) / 2.0; layer.lineWidth = path.lineWidth * lineScale; } + else { + layer.lineWidth = _strokeWidth; + } layer.fillRule = [path.svgAttributes[@"fill-rule"] isEqualToString:@"evenodd"] ? kCAFillRuleEvenOdd : kCAFillRuleNonZero; NSString *lineCap = path.svgAttributes[@"stroke-linecap"]; layer.lineCap = [lineCap isEqualToString:@"round"] ? kCALineCapRound : ([lineCap isEqualToString:@"square"] ? kCALineCapSquare : kCALineCapButt); diff --git a/Sources/include/SVGBezierPath.h b/Sources/include/SVGBezierPath.h index dd0eda11..f01eeb29 100644 --- a/Sources/include/SVGBezierPath.h +++ b/Sources/include/SVGBezierPath.h @@ -41,7 +41,6 @@ FOUNDATION_EXTERN void SVGDrawPathsWithBlock(NSArray * const pat @property(nonatomic, readonly) NSString *SVGRepresentation; - /*! * @brief Returns an array of SVGBezierPaths given an SVG's URL. * @@ -51,11 +50,22 @@ FOUNDATION_EXTERN void SVGDrawPathsWithBlock(NSArray * const pat + (NSArray *)pathsFromSVGAtURL:(NSURL *)aURL; +/*! + * @brief Returns an array of SVGBezierPaths given an SVG's URL. + * + * @param aURL The URL from which to load an SVG. + * @param viewBox The SVG viewBox tag rect. + * + */ ++ (NSArray *)pathsFromSVGAtURL:(NSURL *)aURL viewBox:(CGRect *)viewBox; + + /*! * @brief Returns an array of paths given the XML string of an SVG. * */ + (NSArray *)pathsFromSVGString:(NSString *)svgString; ++ (NSArray *)pathsFromSVGString:(NSString *)svgString viewBox:(CGRect *)viewBox; /*! * @brief Returns a new path with the values of `attributes` added to `svgAttributes` diff --git a/Sources/include/SVGEngine.h b/Sources/include/SVGEngine.h index 6b2e435d..5ec334a4 100755 --- a/Sources/include/SVGEngine.h +++ b/Sources/include/SVGEngine.h @@ -27,6 +27,7 @@ extern "C" { * */ NSArray *CGPathsFromSVGString(NSString *svgString, SVGAttributeSet **attributes); +NSArray *CGPathsFromSVGStringViewBox(NSString *svgString, SVGAttributeSet **attributes, CGRect *viewBox); /*! * @brief Returns a single CGPathRef parsed from the contents of a single string formatted like the d attribute inside a path element diff --git a/Sources/include/SVGImageView.h b/Sources/include/SVGImageView.h index 22793ea4..56695012 100644 --- a/Sources/include/SVGImageView.h +++ b/Sources/include/SVGImageView.h @@ -28,6 +28,14 @@ IB_DESIGNABLE * */ - (instancetype)initWithContentsOfURL:(NSURL *)url; +- (instancetype)initWithContentsOfURL:(NSURL *)url readViewBox:(bool)readViewBox; + + +/*! +* @brief retrieve the viewBox size saved inside the original SVG file. +* +*/ +- (CGRect)getViewBox; /*! @@ -53,6 +61,13 @@ IB_DESIGNABLE @property(nonatomic, copy) IBInspectable PSVGColor *strokeColor; +/*! + * @brief Specifies the line thickness of every paths. + * + */ +@property(nonatomic) IBInspectable CGFloat strokeWidth; + + /*! * @brief Specifies whether line thickness should be scaled when scaling paths. * diff --git a/Sources/include/SVGLayer.h b/Sources/include/SVGLayer.h index 57682170..badc2182 100644 --- a/Sources/include/SVGLayer.h +++ b/Sources/include/SVGLayer.h @@ -54,6 +54,13 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic) CGColorRef strokeColor; +/*! +* @brief Specifies the line thickness for every paths. +* +*/ +@property(nonatomic) CGFloat strokeWidth; + + /*! * @brief Specifies whether line thickness should be scaled when scaling paths. *