UNPKG

18.3 kBPlain TextView Raw
1/**
2 * Copyright (c) 2015-present, Horcrux.
3 * All rights reserved.
4 *
5 * This source code is licensed under the MIT-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9#import "RNSVGRenderable.h"
10#import "RNSVGClipPath.h"
11#import "RNSVGMask.h"
12#import "RNSVGViewBox.h"
13#import "RNSVGVectorEffect.h"
14#import "RNSVGBezierElement.h"
15#import "RNSVGMarker.h"
16#import "RNSVGMarkerPosition.h"
17
18@implementation RNSVGRenderable
19{
20 NSMutableDictionary *_originProperties;
21 NSArray<NSString *> *_lastMergedList;
22 NSArray<NSString *> *_attributeList;
23 NSArray<RNSVGLength *> *_sourceStrokeDashArray;
24 CGFloat *_strokeDashArrayData;
25 CGPathRef _srcHitPath;
26 CGPathRef _hitArea;
27}
28
29static RNSVGRenderable * _contextElement;
30+ (RNSVGRenderable *)contextElement { return _contextElement; }
31+ (void)setContextElement:(RNSVGRenderable *)contextElement { _contextElement = contextElement; }
32
33- (id)init
34{
35 if (self = [super init]) {
36 _fillOpacity = 1;
37 _strokeOpacity = 1;
38 _strokeWidth = [RNSVGLength lengthWithNumber:1];
39 _fillRule = kRNSVGCGFCRuleNonzero;
40 }
41 return self;
42}
43
44- (void)invalidate
45{
46 _sourceStrokeDashArray = nil;
47 if (self.dirty || self.merging) {
48 return;
49 }
50 _srcHitPath = nil;
51 [super invalidate];
52 self.dirty = true;
53}
54
55- (void)setFill:(RNSVGBrush *)fill
56{
57 if (fill == _fill) {
58 return;
59 }
60 [self invalidate];
61 _fill = fill;
62}
63
64- (void)setFillOpacity:(CGFloat)fillOpacity
65{
66 if (fillOpacity == _fillOpacity) {
67 return;
68 }
69 [self invalidate];
70 _fillOpacity = fillOpacity;
71}
72
73- (void)setFillRule:(RNSVGCGFCRule)fillRule
74{
75 if (fillRule == _fillRule) {
76 return;
77 }
78 [self invalidate];
79 _fillRule = fillRule;
80}
81
82- (void)setStroke:(RNSVGBrush *)stroke
83{
84 if (stroke == _stroke) {
85 return;
86 }
87 [self invalidate];
88 _stroke = stroke;
89}
90
91- (void)setStrokeOpacity:(CGFloat)strokeOpacity
92{
93 if (strokeOpacity == _strokeOpacity) {
94 return;
95 }
96 [self invalidate];
97 _strokeOpacity = strokeOpacity;
98}
99
100- (void)setStrokeWidth:(RNSVGLength*)strokeWidth
101{
102 if ([strokeWidth isEqualTo:_strokeWidth]) {
103 return;
104 }
105 [self invalidate];
106 _strokeWidth = strokeWidth;
107}
108
109- (void)setStrokeLinecap:(CGLineCap)strokeLinecap
110{
111 if (strokeLinecap == _strokeLinecap) {
112 return;
113 }
114 [self invalidate];
115 _strokeLinecap = strokeLinecap;
116}
117
118- (void)setStrokeJoin:(CGLineJoin)strokeLinejoin
119{
120 if (strokeLinejoin == _strokeLinejoin) {
121 return;
122 }
123 [self invalidate];
124 _strokeLinejoin = strokeLinejoin;
125}
126
127- (void)setStrokeMiterlimit:(CGFloat)strokeMiterlimit
128{
129 if (strokeMiterlimit == _strokeMiterlimit) {
130 return;
131 }
132 [self invalidate];
133 _strokeMiterlimit = strokeMiterlimit;
134}
135
136- (void)setStrokeDasharray:(NSArray<RNSVGLength *> *)strokeDasharray
137{
138 if (strokeDasharray == _strokeDasharray) {
139 return;
140 }
141 [self invalidate];
142 _strokeDasharray = strokeDasharray;
143}
144
145- (void)setStrokeDashoffset:(CGFloat)strokeDashoffset
146{
147 if (strokeDashoffset == _strokeDashoffset) {
148 return;
149 }
150 [self invalidate];
151 _strokeDashoffset = strokeDashoffset;
152}
153
154- (void)setVectorEffect:(RNSVGVectorEffect)vectorEffect
155{
156 if (vectorEffect == _vectorEffect) {
157 return;
158 }
159 [self invalidate];
160 _vectorEffect = vectorEffect;
161}
162
163- (void)setPropList:(NSArray<NSString *> *)propList
164{
165 if (propList == _propList) {
166 return;
167 }
168
169 _propList = _attributeList = propList;
170 [self invalidate];
171}
172
173- (void)dealloc
174{
175 CGPathRelease(_hitArea);
176 _sourceStrokeDashArray = nil;
177 if (_strokeDashArrayData) {
178 free(_strokeDashArrayData);
179 }
180 _strokeDashArrayData = nil;
181}
182
183UInt32 saturate(CGFloat value) {
184 return value <= 0 ? 0 : value >= 255 ? 255 : (UInt32)value;
185}
186
187- (void)renderTo:(CGContextRef)context rect:(CGRect)rect
188{
189 self.dirty = false;
190 // This needs to be painted on a layer before being composited.
191 CGContextSaveGState(context);
192 CGContextConcatCTM(context, self.matrix);
193 CGContextConcatCTM(context, self.transforms);
194 CGContextSetAlpha(context, self.opacity);
195
196 [self beginTransparencyLayer:context];
197
198 if (self.mask) {
199 // https://www.w3.org/TR/SVG11/masking.html#MaskElement
200 RNSVGMask *_maskNode = (RNSVGMask*)[self.svgView getDefinedMask:self.mask];
201 CGRect bounds = CGContextGetClipBoundingBox(context);
202 CGSize boundsSize = bounds.size;
203 CGFloat height = boundsSize.height;
204 CGFloat width = boundsSize.width;
205 NSUInteger iheight = (NSUInteger)height;
206 NSUInteger iwidth = (NSUInteger)width;
207 NSUInteger npixels = iheight * iwidth;
208 CGRect drawBounds = CGRectMake(0, 0, width, height);
209
210 // Allocate pixel buffer and bitmap context for mask
211 NSUInteger bytesPerPixel = 4;
212 NSUInteger bitsPerComponent = 8;
213 NSUInteger bytesPerRow = bytesPerPixel * iwidth;
214 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
215 UInt32 * pixels = (UInt32 *) calloc(npixels, sizeof(UInt32));
216 CGContextRef bcontext = CGBitmapContextCreate(pixels, iwidth, iheight, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
217
218 // Clip to mask bounds and render the mask
219 CGFloat x = [self relativeOn:[_maskNode x]
220 relative:width];
221 CGFloat y = [self relativeOn:[_maskNode y]
222 relative:height];
223 CGFloat w = [self relativeOn:[_maskNode maskwidth]
224 relative:width];
225 CGFloat h = [self relativeOn:[_maskNode maskheight]
226 relative:height];
227 CGRect maskBounds = CGRectMake(x, y, w, h);
228 CGContextClipToRect(bcontext, maskBounds);
229 [_maskNode renderLayerTo:bcontext rect:rect];
230
231 // Apply luminanceToAlpha filter primitive
232 // https://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement
233 UInt32 * currentPixel = pixels;
234 for (NSUInteger i = 0; i < npixels; i++) {
235 UInt32 color = *currentPixel;
236
237 UInt32 r = color & 0xFF;
238 UInt32 g = (color >> 8) & 0xFF;
239 UInt32 b = (color >> 16) & 0xFF;
240
241 CGFloat luma = (CGFloat)(0.299 * r + 0.587 * g + 0.144 * b);
242 *currentPixel = saturate(luma) << 24;
243 currentPixel++;
244 }
245
246 // Create mask image and release memory
247 CGImageRef maskImage = CGBitmapContextCreateImage(bcontext);
248 CGColorSpaceRelease(colorSpace);
249 CGContextRelease(bcontext);
250 free(pixels);
251
252 // Render content of current SVG Renderable to image
253 UIGraphicsBeginImageContextWithOptions(boundsSize, NO, 0.0);
254 CGContextRef newContext = UIGraphicsGetCurrentContext();
255 CGContextTranslateCTM(newContext, 0.0, height);
256 CGContextScaleCTM(newContext, 1.0, -1.0);
257 [self renderLayerTo:newContext rect:rect];
258 CGImageRef contentImage = CGBitmapContextCreateImage(newContext);
259 UIGraphicsEndImageContext();
260
261 // Blend current element and mask
262 UIGraphicsBeginImageContextWithOptions(boundsSize, NO, 0.0);
263 newContext = UIGraphicsGetCurrentContext();
264 CGContextTranslateCTM(newContext, 0.0, height);
265 CGContextScaleCTM(newContext, 1.0, -1.0);
266
267 CGContextSetBlendMode(newContext, kCGBlendModeCopy);
268 CGContextDrawImage(newContext, drawBounds, maskImage);
269 CGImageRelease(maskImage);
270
271 CGContextSetBlendMode(newContext, kCGBlendModeSourceIn);
272 CGContextDrawImage(newContext, drawBounds, contentImage);
273 CGImageRelease(contentImage);
274
275 CGImageRef blendedImage = CGBitmapContextCreateImage(newContext);
276 UIGraphicsEndImageContext();
277
278 // Render blended result into current render context
279 CGContextDrawImage(context, drawBounds, blendedImage);
280 CGImageRelease(blendedImage);
281 } else {
282 [self renderLayerTo:context rect:rect];
283 }
284 [self endTransparencyLayer:context];
285
286 CGContextRestoreGState(context);
287
288 [self renderMarkers:context path:self.path rect:&rect];
289}
290
291- (void)prepareStrokeDash:(NSUInteger)count strokeDasharray:(NSArray<RNSVGLength *> *)strokeDasharray {
292 if (strokeDasharray != _sourceStrokeDashArray) {
293 CGFloat *dash = _strokeDashArrayData;
294 _strokeDashArrayData = realloc(dash, sizeof(CGFloat) * count);
295 if (!_strokeDashArrayData) {
296 free(dash);
297 return;
298 }
299 _sourceStrokeDashArray = strokeDasharray;
300 for (NSUInteger i = 0; i < count; i++) {
301 _strokeDashArrayData[i] = (CGFloat)[self relativeOnOther:strokeDasharray[i]];
302 }
303 }
304}
305
306- (void)renderMarkers:(CGContextRef)context path:(CGPathRef)path rect:(const CGRect *)rect {
307 RNSVGMarker *markerStart = (RNSVGMarker*)[self.svgView getDefinedMarker:self.markerStart];
308 RNSVGMarker *markerMid = (RNSVGMarker*)[self.svgView getDefinedMarker:self.markerMid];
309 RNSVGMarker *markerEnd = (RNSVGMarker*)[self.svgView getDefinedMarker:self.markerEnd];
310 if (markerStart || markerMid || markerEnd) {
311 _contextElement = self;
312 NSArray<RNSVGMarkerPosition*>* positions = [RNSVGMarkerPosition fromCGPath:path];
313 CGFloat width = self.strokeWidth ? [self relativeOnOther:self.strokeWidth] : 1;
314 for (RNSVGMarkerPosition* position in positions) {
315 RNSVGMarkerType type = [position type];
316 switch (type) {
317 case kStartMarker:
318 [markerStart renderMarker:context rect:*rect position:position strokeWidth:width];
319 break;
320
321 case kMidMarker:
322 [markerMid renderMarker:context rect:*rect position:position strokeWidth:width];
323 break;
324
325 case kEndMarker:
326 [markerEnd renderMarker:context rect:*rect position:position strokeWidth:width];
327 break;
328
329 default:
330 break;
331 }
332 }
333 _contextElement = nil;
334 }
335}
336
337- (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect
338{
339 CGPathRef path = self.path;
340 if (!path) {
341 path = [self getPath:context];
342 if (!self.path) {
343 self.path = CGPathRetain(path);
344 }
345 [self setHitArea:path];
346 const CGRect fillBounds = CGPathGetBoundingBox(path);
347 const CGRect strokeBounds = CGPathGetBoundingBox(self.strokePath);
348 self.pathBounds = CGRectUnion(fillBounds, strokeBounds);
349 }
350 const CGRect pathBounds = self.pathBounds;
351
352 CGAffineTransform current = CGContextGetCTM(context);
353 CGAffineTransform svgToClientTransform = CGAffineTransformConcat(current, self.svgView.invInitialCTM);
354 CGRect clientRect = CGRectApplyAffineTransform(pathBounds, svgToClientTransform);
355
356 self.clientRect = clientRect;
357
358 if (_vectorEffect == kRNSVGVectorEffectNonScalingStroke) {
359 path = CGPathCreateCopyByTransformingPath(path, &svgToClientTransform);
360 CGContextConcatCTM(context, CGAffineTransformInvert(svgToClientTransform));
361 }
362
363 CGAffineTransform vbmatrix = self.svgView.getViewBoxTransform;
364 CGAffineTransform transform = CGAffineTransformConcat(self.matrix, self.transforms);
365 CGAffineTransform matrix = CGAffineTransformConcat(transform, vbmatrix);
366
367 CGRect bounds = CGRectMake(0, 0, CGRectGetWidth(clientRect), CGRectGetHeight(clientRect));
368 CGPoint mid = CGPointMake(CGRectGetMidX(pathBounds), CGRectGetMidY(pathBounds));
369 CGPoint center = CGPointApplyAffineTransform(mid, matrix);
370
371 self.bounds = bounds;
372 if (!isnan(center.x) && !isnan(center.y)) {
373 self.center = center;
374 }
375 self.frame = clientRect;
376
377 if (self.skip || self.opacity == 0) {
378 return;
379 }
380
381 if (!self.fill && !self.stroke) {
382 return;
383 }
384
385 CGPathDrawingMode mode = kCGPathStroke;
386 BOOL fillColor = NO;
387 [self clip:context];
388
389 BOOL evenodd = self.fillRule == kRNSVGCGFCRuleEvenodd;
390
391 if (self.fill) {
392 if (self.fill.class == RNSVGBrush.class) {
393 CGContextSetFillColorWithColor(context, [self.tintColor CGColor]);
394 fillColor = YES;
395 } else {
396 fillColor = [self.fill applyFillColor:context opacity:self.fillOpacity];
397 }
398
399 if (fillColor) {
400 mode = evenodd ? kCGPathEOFill : kCGPathFill;
401 } else {
402 CGContextSaveGState(context);
403 CGContextAddPath(context, path);
404 evenodd ? CGContextEOClip(context) : CGContextClip(context);
405 [self.fill paint:context
406 opacity:self.fillOpacity
407 painter:[self.svgView getDefinedPainter:self.fill.brushRef]
408 bounds:pathBounds
409 ];
410 CGContextRestoreGState(context);
411
412 if (!self.stroke) {
413 return;
414 }
415 }
416 }
417
418 if (self.stroke) {
419 CGFloat width = self.strokeWidth ? [self relativeOnOther:self.strokeWidth] : 1;
420 CGContextSetLineWidth(context, width);
421 CGContextSetLineCap(context, self.strokeLinecap);
422 CGContextSetLineJoin(context, self.strokeLinejoin);
423 NSArray<RNSVGLength *>* strokeDasharray = self.strokeDasharray;
424 NSUInteger count = strokeDasharray.count;
425
426 if (count) {
427 [self prepareStrokeDash:count strokeDasharray:strokeDasharray];
428 if (_strokeDashArrayData) {
429 CGContextSetLineDash(context, self.strokeDashoffset, _strokeDashArrayData, count);
430 }
431 }
432
433 if (!fillColor) {
434 CGContextAddPath(context, path);
435 CGContextReplacePathWithStrokedPath(context);
436 CGContextClip(context);
437 }
438
439 BOOL strokeColor;
440
441 if (self.stroke.class == RNSVGBrush.class) {
442 CGContextSetStrokeColorWithColor(context,[self.tintColor CGColor]);
443 strokeColor = YES;
444 } else {
445 strokeColor = [self.stroke applyStrokeColor:context opacity:self.strokeOpacity];
446 }
447
448 if (strokeColor && fillColor) {
449 mode = evenodd ? kCGPathEOFillStroke : kCGPathFillStroke;
450 } else if (!strokeColor) {
451 // draw fill
452 if (fillColor) {
453 CGContextAddPath(context, path);
454 CGContextDrawPath(context, mode);
455 }
456
457 // draw stroke
458 CGContextAddPath(context, path);
459 CGContextReplacePathWithStrokedPath(context);
460 evenodd ? CGContextEOClip(context) : CGContextClip(context);
461
462 [self.stroke paint:context
463 opacity:self.strokeOpacity
464 painter:[self.svgView getDefinedPainter:self.stroke.brushRef]
465 bounds:pathBounds
466 ];
467 return;
468 }
469 }
470
471 CGContextAddPath(context, path);
472 CGContextDrawPath(context, mode);
473}
474
475- (void)setHitArea:(CGPathRef)path
476{
477 if (_srcHitPath == path) {
478 return;
479 }
480 _srcHitPath = path;
481 CGPathRelease(_hitArea);
482 CGPathRelease(self.strokePath);
483 _hitArea = CGPathCreateCopy(path);
484 self.strokePath = nil;
485 if (self.stroke && self.strokeWidth) {
486 // Add stroke to hitArea
487 CGFloat width = [self relativeOnOther:self.strokeWidth];
488 self.strokePath = CGPathRetain(CFAutorelease(CGPathCreateCopyByStrokingPath(path, nil, width, self.strokeLinecap, self.strokeLinejoin, self.strokeMiterlimit)));
489 // TODO add dashing
490 // CGPathCreateCopyByDashingPath(CGPathRef _Nullable path, const CGAffineTransform * _Nullable transform, CGFloat phase, const CGFloat * _Nullable lengths, size_t count)
491 }
492}
493
494- (BOOL)isUserInteractionEnabled
495{
496 return NO;
497}
498
499// hitTest delegate
500- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
501{
502 if (!_hitArea) {
503 return nil;
504 }
505
506 if (self.active) {
507 if (!event) {
508 self.active = NO;
509 }
510 return self;
511 }
512
513 CGPoint transformed = CGPointApplyAffineTransform(point, self.invmatrix);
514 transformed = CGPointApplyAffineTransform(transformed, self.invTransform);
515
516 if (!CGRectContainsPoint(self.pathBounds, transformed)) {
517 return nil;
518 }
519
520 BOOL evenodd = self.fillRule == kRNSVGCGFCRuleEvenodd;
521 if (!CGPathContainsPoint(_hitArea, nil, transformed, evenodd) &&
522 !CGPathContainsPoint(self.strokePath, nil, transformed, NO)) {
523 return nil;
524 }
525
526 if (self.clipPath) {
527 RNSVGClipPath *clipNode = (RNSVGClipPath*)[self.svgView getDefinedClipPath:self.clipPath];
528 if ([clipNode isSimpleClipPath]) {
529 CGPathRef clipPath = [self getClipPath];
530 if (clipPath && !CGPathContainsPoint(clipPath, nil, transformed, clipNode.clipRule == kRNSVGCGFCRuleEvenodd)) {
531 return nil;
532 }
533 } else {
534 RNSVGRenderable *clipGroup = (RNSVGRenderable*)clipNode;
535 if (![clipGroup hitTest:transformed withEvent:event]) {
536 return nil;
537 }
538 }
539 }
540
541 return self;
542}
543
544- (NSArray<NSString *> *)getAttributeList
545{
546 return _attributeList;
547}
548
549- (void)mergeProperties:(__kindof RNSVGRenderable *)target
550{
551 NSArray<NSString *> *targetAttributeList = [target getAttributeList];
552
553 if (targetAttributeList.count == 0) {
554 return;
555 }
556 self.merging = true;
557
558 NSMutableArray* attributeList = [self.propList mutableCopy];
559 _originProperties = [[NSMutableDictionary alloc] init];
560
561 for (NSString *key in targetAttributeList) {
562 [_originProperties setValue:[self valueForKey:key] forKey:key];
563 if (![attributeList containsObject:key]) {
564 [attributeList addObject:key];
565 [self setValue:[target valueForKey:key] forKey:key];
566 }
567 }
568
569 _lastMergedList = targetAttributeList;
570 _attributeList = [attributeList copy];
571 self.merging = false;
572}
573
574- (void)resetProperties
575{
576 self.merging = true;
577 for (NSString *key in _lastMergedList) {
578 [self setValue:[_originProperties valueForKey:key] forKey:key];
579 }
580
581 _lastMergedList = nil;
582 _attributeList = _propList;
583 self.merging = false;
584}
585
586@end