1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | #import "RNSVGNode.h"
|
10 | #import "RNSVGContainer.h"
|
11 | #import "RNSVGClipPath.h"
|
12 | #import "RNSVGGroup.h"
|
13 | #import "RNSVGGlyphContext.h"
|
14 |
|
15 | @interface RNSVGNode()
|
16 | @property (nonatomic, readwrite, weak) RNSVGSvgView *svgView;
|
17 | @property (nonatomic, readwrite, weak) RNSVGGroup *textRoot;
|
18 | @end
|
19 |
|
20 | @implementation RNSVGNode
|
21 | {
|
22 | RNSVGGlyphContext *glyphContext;
|
23 | BOOL _transparent;
|
24 | RNSVGClipPath *_clipNode;
|
25 | CGPathRef _cachedClipPath;
|
26 | CGImageRef _clipMask;
|
27 | CGFloat canvasWidth;
|
28 | CGFloat canvasHeight;
|
29 | CGFloat canvasDiagonal;
|
30 | }
|
31 |
|
32 | CGFloat const RNSVG_M_SQRT1_2l = (CGFloat)0.707106781186547524400844362104849039;
|
33 | CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12;
|
34 |
|
35 | - (instancetype)init
|
36 | {
|
37 | if (self = [super init]) {
|
38 | self.opacity = 1;
|
39 | self.matrix = CGAffineTransformIdentity;
|
40 | self.transforms = CGAffineTransformIdentity;
|
41 | self.invTransform = CGAffineTransformIdentity;
|
42 | _merging = false;
|
43 | _dirty = false;
|
44 | }
|
45 | return self;
|
46 | }
|
47 |
|
48 | - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
|
49 | {
|
50 | [super insertReactSubview:subview atIndex:atIndex];
|
51 | [self insertSubview:subview atIndex:atIndex];
|
52 | [self invalidate];
|
53 | }
|
54 |
|
55 | - (void)removeReactSubview:(UIView *)subview
|
56 | {
|
57 | [super removeReactSubview:subview];
|
58 | [self invalidate];
|
59 | }
|
60 |
|
61 | - (void)didUpdateReactSubviews
|
62 | {
|
63 | // Do nothing, as subviews are inserted by insertReactSubview:
|
64 | }
|
65 |
|
66 | - (void)invalidate
|
67 | {
|
68 | if (_dirty || _merging) {
|
69 | return;
|
70 | }
|
71 | _dirty = true;
|
72 | id<RNSVGContainer> container = (id<RNSVGContainer>)self.superview;
|
73 | [container invalidate];
|
74 | [self clearPath];
|
75 | canvasWidth = -1;
|
76 | canvasHeight = -1;
|
77 | canvasDiagonal = -1;
|
78 | }
|
79 |
|
80 | - (void)clearPath
|
81 | {
|
82 | CGPathRelease(_path);
|
83 | self.path = nil;
|
84 | }
|
85 |
|
86 | - (void)clearChildCache
|
87 | {
|
88 | [self clearPath];
|
89 | for (__kindof RNSVGNode *node in self.subviews) {
|
90 | if ([node isKindOfClass:[RNSVGNode class]]) {
|
91 | [node clearChildCache];
|
92 | }
|
93 | }
|
94 | }
|
95 |
|
96 | - (void)clearParentCache
|
97 | {
|
98 | RNSVGNode* node = self;
|
99 | while (node != nil) {
|
100 | UIView* parent = [node superview];
|
101 |
|
102 | if (![parent isKindOfClass:[RNSVGNode class]]) {
|
103 | return;
|
104 | }
|
105 | node = (RNSVGNode*)parent;
|
106 | if (!node.path) {
|
107 | return;
|
108 | }
|
109 | [node clearPath];
|
110 | }
|
111 | }
|
112 |
|
113 | - (RNSVGGroup *)textRoot
|
114 | {
|
115 | if (_textRoot) {
|
116 | return _textRoot;
|
117 | }
|
118 |
|
119 | RNSVGNode* node = self;
|
120 | while (node != nil) {
|
121 | if ([node isKindOfClass:[RNSVGGroup class]] && [((RNSVGGroup*) node) getGlyphContext] != nil) {
|
122 | _textRoot = (RNSVGGroup*)node;
|
123 | break;
|
124 | }
|
125 |
|
126 | UIView* parent = [node superview];
|
127 |
|
128 | if (![node isKindOfClass:[RNSVGNode class]]) {
|
129 | node = nil;
|
130 | } else {
|
131 | node = (RNSVGNode*)parent;
|
132 | }
|
133 | }
|
134 |
|
135 | return _textRoot;
|
136 | }
|
137 |
|
138 | - (RNSVGGroup *)getParentTextRoot
|
139 | {
|
140 | RNSVGNode* parent = (RNSVGGroup*)[self superview];
|
141 | if (![parent isKindOfClass:[RNSVGGroup class]]) {
|
142 | return nil;
|
143 | } else {
|
144 | return parent.textRoot;
|
145 | }
|
146 | }
|
147 |
|
148 | - (CGFloat)getFontSizeFromContext
|
149 | {
|
150 | RNSVGGroup* root = self.textRoot;
|
151 | if (root == nil) {
|
152 | return RNSVG_DEFAULT_FONT_SIZE;
|
153 | }
|
154 |
|
155 | if (glyphContext == nil) {
|
156 | glyphContext = [root getGlyphContext];
|
157 | }
|
158 |
|
159 | return [glyphContext getFontSize];
|
160 | }
|
161 |
|
162 | - (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor
|
163 | {
|
164 | self.backgroundColor = inheritedBackgroundColor;
|
165 | }
|
166 |
|
167 | - (void)setName:(NSString *)name
|
168 | {
|
169 | if ([name isEqualToString:_name]) {
|
170 | return;
|
171 | }
|
172 |
|
173 | [self invalidate];
|
174 | _name = name;
|
175 | }
|
176 |
|
177 | - (void)setOpacity:(CGFloat)opacity
|
178 | {
|
179 | if (opacity == _opacity) {
|
180 | return;
|
181 | }
|
182 |
|
183 | if (opacity <= 0) {
|
184 | opacity = 0;
|
185 | } else if (opacity > 1) {
|
186 | opacity = 1;
|
187 | }
|
188 |
|
189 | [self invalidate];
|
190 | _transparent = opacity < 1;
|
191 | _opacity = opacity;
|
192 | }
|
193 |
|
194 | - (void)setMatrix:(CGAffineTransform)matrix
|
195 | {
|
196 | if (CGAffineTransformEqualToTransform(matrix, _matrix)) {
|
197 | return;
|
198 | }
|
199 | _matrix = matrix;
|
200 | _invmatrix = CGAffineTransformInvert(matrix);
|
201 | id<RNSVGContainer> container = (id<RNSVGContainer>)self.superview;
|
202 | [container invalidate];
|
203 | }
|
204 |
|
205 | - (void)setClientRect:(CGRect)clientRect {
|
206 | if (CGRectEqualToRect(_clientRect, clientRect)) {
|
207 | return;
|
208 | }
|
209 | _clientRect = clientRect;
|
210 | if (self.onLayout) {
|
211 | self.onLayout(@{
|
212 | @"layout": @{
|
213 | @"x": @(_clientRect.origin.x),
|
214 | @"y": @(_clientRect.origin.y),
|
215 | @"width": @(_clientRect.size.width),
|
216 | @"height": @(_clientRect.size.height),
|
217 | }
|
218 | });
|
219 |
|
220 | }
|
221 | }
|
222 |
|
223 | - (void)setClipPath:(NSString *)clipPath
|
224 | {
|
225 | if ([_clipPath isEqualToString:clipPath]) {
|
226 | return;
|
227 | }
|
228 | CGPathRelease(_cachedClipPath);
|
229 | CGImageRelease(_clipMask);
|
230 | _cachedClipPath = nil;
|
231 | _clipPath = clipPath;
|
232 | _clipMask = nil;
|
233 | [self invalidate];
|
234 | }
|
235 |
|
236 | - (void)setClipRule:(RNSVGCGFCRule)clipRule
|
237 | {
|
238 | if (_clipRule == clipRule) {
|
239 | return;
|
240 | }
|
241 | CGPathRelease(_cachedClipPath);
|
242 | CGImageRelease(_clipMask);
|
243 | _cachedClipPath = nil;
|
244 | _clipRule = clipRule;
|
245 | _clipMask = nil;
|
246 | [self invalidate];
|
247 | }
|
248 |
|
249 | - (void)setMask:(NSString *)mask
|
250 | {
|
251 | if ([_mask isEqualToString:mask]) {
|
252 | return;
|
253 | }
|
254 | _mask = mask;
|
255 | [self invalidate];
|
256 | }
|
257 |
|
258 | - (void)setMarkerStart:(NSString *)markerStart
|
259 | {
|
260 | if ([_markerStart isEqualToString:markerStart]) {
|
261 | return;
|
262 | }
|
263 | _markerStart = markerStart;
|
264 | [self invalidate];
|
265 | }
|
266 |
|
267 | - (void)setMarkerMid:(NSString *)markerMid
|
268 | {
|
269 | if ([_markerMid isEqualToString:markerMid]) {
|
270 | return;
|
271 | }
|
272 | _markerMid = markerMid;
|
273 | [self invalidate];
|
274 | }
|
275 |
|
276 | - (void)setMarkerEnd:(NSString *)markerEnd
|
277 | {
|
278 | if ([_markerEnd isEqualToString:markerEnd]) {
|
279 | return;
|
280 | }
|
281 | _markerEnd = markerEnd;
|
282 | [self invalidate];
|
283 | }
|
284 |
|
285 | - (void)beginTransparencyLayer:(CGContextRef)context
|
286 | {
|
287 | if (_transparent) {
|
288 | CGContextBeginTransparencyLayer(context, NULL);
|
289 | }
|
290 | }
|
291 |
|
292 | - (void)endTransparencyLayer:(CGContextRef)context
|
293 | {
|
294 | if (_transparent) {
|
295 | CGContextEndTransparencyLayer(context);
|
296 | }
|
297 | }
|
298 |
|
299 | - (void)renderTo:(CGContextRef)context rect:(CGRect)rect
|
300 | {
|
301 | self.dirty = false;
|
302 | // abstract
|
303 | }
|
304 |
|
305 | - (CGPathRef)getClipPath
|
306 | {
|
307 | return _cachedClipPath;
|
308 | }
|
309 |
|
310 | - (CGPathRef)getClipPath:(CGContextRef)context
|
311 | {
|
312 | if (self.clipPath) {
|
313 | _clipNode = (RNSVGClipPath*)[self.svgView getDefinedClipPath:self.clipPath];
|
314 | if (_cachedClipPath) {
|
315 | CGPathRelease(_cachedClipPath);
|
316 | }
|
317 | _cachedClipPath = CGPathRetain([_clipNode getPath:context]);
|
318 | if (_clipMask) {
|
319 | CGImageRelease(_clipMask);
|
320 | }
|
321 | if ([_clipNode isSimpleClipPath] || _clipNode.clipRule == kRNSVGCGFCRuleEvenodd) {
|
322 | _clipMask = nil;
|
323 | } else {
|
324 | CGRect bounds = CGContextGetClipBoundingBox(context);
|
325 | CGSize size = bounds.size;
|
326 |
|
327 | UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
|
328 | CGContextRef newContext = UIGraphicsGetCurrentContext();
|
329 | CGContextTranslateCTM(newContext, 0.0, size.height);
|
330 | CGContextScaleCTM(newContext, 1.0, -1.0);
|
331 |
|
332 | [_clipNode renderLayerTo:newContext rect:bounds];
|
333 | _clipMask = CGBitmapContextCreateImage(newContext);
|
334 | UIGraphicsEndImageContext();
|
335 | }
|
336 | }
|
337 |
|
338 | return _cachedClipPath;
|
339 | }
|
340 |
|
341 | - (void)clip:(CGContextRef)context
|
342 | {
|
343 | CGPathRef clipPath = [self getClipPath:context];
|
344 |
|
345 | if (clipPath) {
|
346 | if (!_clipMask) {
|
347 | CGContextAddPath(context, clipPath);
|
348 | if (_clipNode.clipRule == kRNSVGCGFCRuleEvenodd) {
|
349 | CGContextEOClip(context);
|
350 | } else {
|
351 | CGContextClip(context);
|
352 | }
|
353 | } else {
|
354 | CGRect bounds = CGContextGetClipBoundingBox(context);
|
355 | CGContextClipToMask(context, bounds, _clipMask);
|
356 | }
|
357 | }
|
358 | }
|
359 |
|
360 | - (CGPathRef)getPath: (CGContextRef)context
|
361 | {
|
362 | // abstract
|
363 | return nil;
|
364 | }
|
365 |
|
366 | - (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect
|
367 | {
|
368 | // abstract
|
369 | }
|
370 |
|
371 | // hitTest delagate
|
372 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
373 | {
|
374 |
|
375 | // abstract
|
376 | return nil;
|
377 | }
|
378 |
|
379 | - (RNSVGSvgView *)svgView
|
380 | {
|
381 | if (_svgView) {
|
382 | return _svgView;
|
383 | }
|
384 |
|
385 | __kindof UIView *parent = self.superview;
|
386 |
|
387 | if ([parent class] == [RNSVGSvgView class]) {
|
388 | _svgView = parent;
|
389 | } else if ([parent isKindOfClass:[RNSVGNode class]]) {
|
390 | _svgView = ((RNSVGNode *)parent).svgView;
|
391 | } else {
|
392 | RCTLogError(@"RNSVG: %@ should be descendant of a SvgViewShadow.", NSStringFromClass(self.class));
|
393 | }
|
394 |
|
395 | return _svgView;
|
396 | }
|
397 |
|
398 | - (CGFloat)relativeOnWidthString:(NSString *)length
|
399 | {
|
400 | return [RNSVGPropHelper fromRelativeWithNSString:length
|
401 | relative:[self getCanvasWidth]
|
402 | fontSize:[self getFontSizeFromContext]];
|
403 | }
|
404 |
|
405 | - (CGFloat)relativeOnHeightString:(NSString *)length
|
406 | {
|
407 | return [RNSVGPropHelper fromRelativeWithNSString:length
|
408 | relative:[self getCanvasHeight]
|
409 | fontSize:[self getFontSizeFromContext]];
|
410 | }
|
411 |
|
412 | - (CGFloat)relativeOnOtherString:(NSString *)length
|
413 | {
|
414 | return [RNSVGPropHelper fromRelativeWithNSString:length
|
415 | relative:[self getCanvasDiagonal]
|
416 | fontSize:[self getFontSizeFromContext]];
|
417 | }
|
418 |
|
419 | - (CGFloat)relativeOn:(RNSVGLength *)length relative:(CGFloat)relative
|
420 | {
|
421 | RNSVGLengthUnitType unit = length.unit;
|
422 | if (unit == SVG_LENGTHTYPE_NUMBER){
|
423 | return length.value;
|
424 | } else if (unit == SVG_LENGTHTYPE_PERCENTAGE){
|
425 | return length.value / 100 * relative;
|
426 | }
|
427 | return [self fromRelative:length];
|
428 | }
|
429 |
|
430 | - (CGFloat)relativeOnWidth:(RNSVGLength *)length
|
431 | {
|
432 | RNSVGLengthUnitType unit = length.unit;
|
433 | if (unit == SVG_LENGTHTYPE_NUMBER){
|
434 | return length.value;
|
435 | } else if (unit == SVG_LENGTHTYPE_PERCENTAGE){
|
436 | return length.value / 100 * [self getCanvasWidth];
|
437 | }
|
438 | return [self fromRelative:length];
|
439 | }
|
440 |
|
441 | - (CGFloat)relativeOnHeight:(RNSVGLength *)length
|
442 | {
|
443 | RNSVGLengthUnitType unit = length.unit;
|
444 | if (unit == SVG_LENGTHTYPE_NUMBER){
|
445 | return length.value;
|
446 | } else if (unit == SVG_LENGTHTYPE_PERCENTAGE){
|
447 | return length.value / 100 * [self getCanvasHeight];
|
448 | }
|
449 | return [self fromRelative:length];
|
450 | }
|
451 |
|
452 | - (CGFloat)relativeOnOther:(RNSVGLength *)length
|
453 | {
|
454 | RNSVGLengthUnitType unit = length.unit;
|
455 | if (unit == SVG_LENGTHTYPE_NUMBER){
|
456 | return length.value;
|
457 | } else if (unit == SVG_LENGTHTYPE_PERCENTAGE){
|
458 | return length.value / 100 * [self getCanvasDiagonal];
|
459 | }
|
460 | return [self fromRelative:length];
|
461 | }
|
462 |
|
463 | - (CGFloat)fromRelative:(RNSVGLength*)length {
|
464 | CGFloat unit;
|
465 | switch (length.unit) {
|
466 | case SVG_LENGTHTYPE_EMS:
|
467 | unit = [self getFontSizeFromContext];
|
468 | break;
|
469 | case SVG_LENGTHTYPE_EXS:
|
470 | unit = [self getFontSizeFromContext] / 2;
|
471 | break;
|
472 |
|
473 | case SVG_LENGTHTYPE_CM:
|
474 | unit = (CGFloat)35.43307;
|
475 | break;
|
476 | case SVG_LENGTHTYPE_MM:
|
477 | unit = (CGFloat)3.543307;
|
478 | break;
|
479 | case SVG_LENGTHTYPE_IN:
|
480 | unit = 90;
|
481 | break;
|
482 | case SVG_LENGTHTYPE_PT:
|
483 | unit = 1.25;
|
484 | break;
|
485 | case SVG_LENGTHTYPE_PC:
|
486 | unit = 15;
|
487 | break;
|
488 |
|
489 | default:
|
490 | unit = 1;
|
491 | }
|
492 | return length.value * unit;
|
493 | }
|
494 |
|
495 | - (CGRect)getContextBounds
|
496 | {
|
497 | return CGContextGetClipBoundingBox(UIGraphicsGetCurrentContext());
|
498 | }
|
499 |
|
500 | - (CGFloat)getContextWidth
|
501 | {
|
502 | return CGRectGetWidth([self getContextBounds]);
|
503 | }
|
504 |
|
505 | - (CGFloat)getContextHeight
|
506 | {
|
507 | return CGRectGetHeight([self getContextBounds]);
|
508 | }
|
509 |
|
510 | - (CGFloat)getContextDiagonal {
|
511 | CGRect bounds = [self getContextBounds];
|
512 | CGFloat width = CGRectGetWidth(bounds);
|
513 | CGFloat height = CGRectGetHeight(bounds);
|
514 | CGFloat powX = width * width;
|
515 | CGFloat powY = height * height;
|
516 | CGFloat r = sqrt(powX + powY) * RNSVG_M_SQRT1_2l;
|
517 | return r;
|
518 | }
|
519 |
|
520 | - (CGFloat) getCanvasWidth {
|
521 | if (canvasWidth != -1) {
|
522 | return canvasWidth;
|
523 | }
|
524 | RNSVGGroup* root = [self textRoot];
|
525 | if (root == nil) {
|
526 | canvasWidth = [self getContextWidth];
|
527 | } else {
|
528 | canvasWidth = [[root getGlyphContext] getWidth];
|
529 | }
|
530 |
|
531 | return canvasWidth;
|
532 | }
|
533 |
|
534 | - (CGFloat) getCanvasHeight {
|
535 | if (canvasHeight != -1) {
|
536 | return canvasHeight;
|
537 | }
|
538 | RNSVGGroup* root = [self textRoot];
|
539 | if (root == nil) {
|
540 | canvasHeight = [self getContextHeight];
|
541 | } else {
|
542 | canvasHeight = [[root getGlyphContext] getHeight];
|
543 | }
|
544 |
|
545 | return canvasHeight;
|
546 | }
|
547 |
|
548 | - (CGFloat) getCanvasDiagonal {
|
549 | if (canvasDiagonal != -1) {
|
550 | return canvasDiagonal;
|
551 | }
|
552 | CGFloat width = [self getCanvasWidth];
|
553 | CGFloat height = [self getCanvasHeight];
|
554 | CGFloat powX = width * width;
|
555 | CGFloat powY = height * height;
|
556 | canvasDiagonal = sqrt(powX + powY) * RNSVG_M_SQRT1_2l;
|
557 | return canvasDiagonal;
|
558 | }
|
559 |
|
560 | - (void)parseReference
|
561 | {
|
562 | self.dirty = false;
|
563 | if (self.name) {
|
564 | typeof(self) __weak weakSelf = self;
|
565 | [self.svgView defineTemplate:weakSelf templateName:self.name];
|
566 | }
|
567 | }
|
568 |
|
569 | - (void)traverseSubviews:(BOOL (^)(__kindof UIView *node))block
|
570 | {
|
571 | for (UIView *node in self.subviews) {
|
572 | if (!block(node)) {
|
573 | break;
|
574 | }
|
575 | }
|
576 | }
|
577 |
|
578 | - (void)dealloc
|
579 | {
|
580 | CGPathRelease(_cachedClipPath);
|
581 | CGPathRelease(_strokePath);
|
582 | CGImageRelease(_clipMask);
|
583 | CGPathRelease(_path);
|
584 | _clipMask = nil;
|
585 | }
|
586 |
|
587 | @end
|